pax_global_header00006660000000000000000000000064125755715120014524gustar00rootroot0000000000000052 comment=58fbdab08ba1eab0fe2675a815086f767cec6557 openMSX-RELEASE_0_12_0/000077500000000000000000000000001257557151200144375ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/.gitignore000066400000000000000000000001001257557151200164160ustar00rootroot00000000000000/derived /*.diff /*.log *.swp *~ core.* core .cproject .project openMSX-RELEASE_0_12_0/Contrib/000077500000000000000000000000001257557151200160375ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/Contrib/README000066400000000000000000000006421257557151200167210ustar00rootroot00000000000000About Contrib ============= This directory contains third-party software that is distributed together with openMSX. These packages are not part of openMSX: they are maintained and licensed separately. Please read the README. file and the documentation files of the contributed packages for details. We would like to thank the contributors for the software they created and for allowing us to distribute it. openMSX-RELEASE_0_12_0/Contrib/README.cbios000066400000000000000000000234221257557151200200200ustar00rootroot00000000000000C-BIOS 0.27 =========== This software is a substitute BIOS which is can be used for running MSX emulators. It currently supports only execution of cartridge image ("ROMs"). Before you use it, you should read and accept the license (see below). On the C-BIOS web site, you can download newer versions, download the source code, and report bugs. http://cbios.sourceforge.net/ License ------- Copyright (c) 2002-2005 BouKiCHi. All rights reserved. Copyright (c) 2003 Reikan. All rights reserved. Copyright (c) 2004-2006,2008-2010 Maarten ter Huurne. All rights reserved. Copyright (c) 2004-2006,2008-2011 Albert Beevendorp. All rights reserved. Copyright (c) 2004-2005 Patrick van Arkel. All rights reserved. Copyright (c) 2004,2010-2011 Manuel Bilderbeek. All rights reserved. Copyright (c) 2004-2006 Joost Yervante Damad. All rights reserved. Copyright (c) 2004-2006 Jussi Pitkänen. All rights reserved. Copyright (c) 2004-2007 Eric Boon. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. History ------- ver 0.01 Initial ver 0.02 2002-08-15(JST) * Added original font and drawing screen. * Added dump-mode. * Changed recognition method of cartridges to recognize cartridges taking priority. ver 0.03 2002-08-19(JST) * Based on a suggestion from Ms.Okei, wrote 20h of BIOS(compare HL and DE). In the result, shooting game of a certain company became runnable more correctly. Thank Ms.Okei!! ver 0.04 2002-08-20(JST) * Added initialize of FCC2h-FCC4h. * Added function of GTSTCK and GTTRIG temporarily. * Divided msxb.bin to halfs. doing combining/copying with setb.bat now. ver 0.05 2002-08-27(JST) * Added INITGRP(only screen2), CHGMOD(graphic mode change routine), a routine calls H.STKE. * Rewrite memory recognition routine. * Some bug fixes. * Added sound test function. ver 0.06 2002-09-01(JST) * Fixed around of color. ver 0.07 2002-09-09(JST) * Added some sorts of keyboard routines. * Added joystich function to GTSTCK and GTTRIG. ver 0.08 2002-09-12(JST) * Restructured memory initialize routine. * Added error display routine. * Fixed routine of finding kinds of cartridges. * Fixed using method of EXPTBL. * Added initialize of from RG8SAV to RG23SA. * Now return within disabled interrupt from ENASLT routine. ver 0.09 2002-09-19(JST) * Made the rest half of font. * Improved key input routine. * Added CHPUT. With it, rewrote display routine. * Fixed init_grp. * Changed filenames to CBIOS.ROM, CBIOS_SUB.ROM. ver 0.10 2002-09-20(JST) * Fixed indent. * and so on... ver 0.10a 2002-09-22(JST) * Fixed license. * Added support of ROMs in page3. ver 0.11 2002-09-22(JST) * Small fix in init_sc5. ver 0.12beta 2002-09-25(JST) * Added test routine for disk access. need DISK.ROM. * Added init_sc7. * Improved ENASLT. now finding cartridge uses ENASLT. * Improved RAM detection. ver 0.12 2002-09-27(JST) * Changed finding cartridge again. * Changed screen mode of cartridge running time. * Fixed keyboard routine. * Fixed stick routine against to interrupt. ver 0.13 2002-10-02(JST) * Based on info from Mr.Maarten (a member of openMSX developers), fixed around of SCREEN 5. For detail, switching line numbers, temporary treatment for a bug of reading from VDP status register, and so on. ver 0.14 2002-10-10(JST) * Rewrote comments in source within Japanese. ver 0.15 2003-02-26(JST) * Rewrote some of comments back to English again. * Fixed non-assemblable condition becauseof lack of font file. * Changed filename, some of label name, strings and so on. ver 0.16 2003-04-16(JST) * Separated sound test from source. (Disabled) ver 0.16a 2003-06-01(JST) * CHGMOD: When screen0/1, now load font to VRAM. * CHPUT: Now support also screen1 not only screen0. ver 0.16b 2003-08-10(JST) * Added entry: INITXT, INIT32. These were exist only as internal routine of CHGMOD. * INITXT, INIT32: Fixed screen clear failure. * CHPUT: Fixed scroll failure. ver 0.17 2003-08-10(JST) * Changed LICENSE. New LICENSE will be suitable in various situations. e.g. use as a firmware for hand-made hardware. ver 0.18 2004-12-18(CET) * First release since moving to SourceForge. * Much improved support for MSX2 games. * Graphical boot logo. * Included machine config files for several MSX emulators. * Various bug fixes. ver 0.19 2004-12-24(CET) * Added support for SCREEN4 and SCREEN8. * Added support for clock chip. * Added support for palette. This fixes a lot of wrong colours. * Stubbed many calls: non-implemented calls print their name on the openMSX debugdevice (if present). * Various bug fixes. ver 0.20 2005-02-09(CET) * Added an MSX2+ configuration, which includes V9958 and MSX MUSIC. * Separate main ROMs for MSX1/MSX2/MSX2+. * Implemented several MSX2 specific routines, including BLT*. * Display is disabled when switching to a different screen mode. * Improved CHPUT a lot; implemented control and escape codes. * Rewrote key buffering; fixes bug of keys being repeated. * New boot logo, even cooler than the previous one. * New font, placed at a fixed address so all games can find it. * Started work on a disk ROM, but it is not functional yet, so it is not enabled in the configurations. * Stubbed all non-implemented calls. * Various bug fixes. ver 0.21 2005-06-07(CET) * Fixed RuMSX configuration files, thanks to Rudolf Lechleitner. * Rewrote ROM search code; now all ROMs are recognized. Also a clear error message is printed for BASIC ROMs. * New boot logo for MSX2 and MSX2+. * Changed boot sequence: Show logo, switch to SCREEN 1 and search for ROMs. * Improved video code; fixes several games. * Various bug fixes. ver 0.22 2008-12-27(CET) * Use separate logo ROM to save space in the main ROM. * Set lower bits of PSG reg 15 before reading joystick trigger status. * Improved RAM search. * Many new routines implemented and existing implementations made more complete, especially character I/O and bitmap graphics. * Added lots of documentation to system variables. * Added support for GNU assembler. * Various bug fixes. ver 0.23 2009-01-04(CET) * Updated blueMSX configuration files, thanks to Benoît Delvaux. * Fixed version reported by MSX1 logo ROM. * Fixed several video routines so they work on MSX1 VDPs (TMS99xx). * A couple of other bug fixes. ver 0.24 2010-05-24(CET) * VRAM size is now properly checked, fixing R-Type's V9938 detection. * C-BIOS doesn't lie anymore about the interrupt frequency. * Don't di; halt when no ROM is found, the warning in openMSX may be confusing * A few minor bug fixes and tweaks. ver 0.25 2011-02-01(CET) * C-BIOS now offers localized versions in the flavours INT (default), JP and BR. * Bug fixes for compatibility with Mirai, Family Billiards. * A couple of other bug fixes. * This version only compiles with Pasmo 0.5.3, due to lack of standards in assembler directives... ver 0.26 2014-11-02(CET) * Restored support to compile with tniASM (v1.0 Beta 17 or higher) * Moved to git, which means a.o.: archived changelog.txt, use git log from now on * Fixed VDP VRAM access timing for MSX1 VDP's * Update openMSX configurations to the new structure * Fixed bug blueMSX configurations * Fixed build on Mac OS X and add support for "make dist" ver 0.27 2014-11-05(CET) * Fixed bug (regression) in filvrm on non-MSX1-VDP's * Fixed some small bugs in openMSX configs * Fixed line endings of this file Special Thanks -------------- People uploading MSX information to the internet. People developing any kind of emulators. All users. Font edit tool: Gameboy Tile Designer version 2.2 Copyright H. Mulder 1999 openMSX-RELEASE_0_12_0/Contrib/README.openmsx-control000066400000000000000000000010561257557151200220670ustar00rootroot00000000000000openMSX Control Example ======================= Using the "-control" command line parameter, the openMSX process can be controlled from another process. The control protocol XML-based. In openmsx-control.cc you'll find an example client application which communicates with openMSX using the control protocol. Note: We try to keep the control protocol stable, but there is no hard guarantee it won't change in the next release. author: Wouter Vermaelen openmsx-control.cc is public domain, use it as you see fit. There is no warranty of any kind. openMSX-RELEASE_0_12_0/Contrib/base64.cc000066400000000000000000000043001257557151200174270ustar00rootroot00000000000000// compile with: // g++ -Wall -Os base64.cc -I ../src/utils/ ../src/utils/Base64.cc -lz -o encode-gz-base64 // g++ -Wall -Os base64.cc -I ../src/utils/ ../src/utils/Base64.cc -lz -o decode-gz-base64 #include "Base64.hh" #include #include #include #include #include #include #include using namespace std; string encode(const void* data, unsigned len) { uLongf dstLen = len + len / 1000 + 12 + 1; // worst-case vector buf(dstLen); if (compress2(buf.data(), &dstLen, reinterpret_cast(data), len, 9) != Z_OK) { cerr << "Error while compressing blob." << endl; exit(1); } return Base64::encode(buf.data(), dstLen); } string decode(const char* data, unsigned len) { static const unsigned MAX_SIZE = 1024 * 1024; // 1MB string tmp = Base64::decode(string(data, len)); vector buf(MAX_SIZE); uLongf dstLen = MAX_SIZE; if (uncompress(reinterpret_cast(buf.data()), &dstLen, reinterpret_cast(tmp.data()), uLong(tmp.size())) != Z_OK) { cerr << "Error while decompressing blob." << endl; exit(1); } return string(buf.data(), dstLen); } int main(int argc, char** argv) { if (argc != 3) { cerr << "Usage: " << argv[0] << " \n"; exit(1); } FILE* inf = fopen(argv[1], "rb"); if (!inf) { cerr << "Error while opening " << argv[1] << endl; exit(1); } struct stat st; fstat(fileno(inf), &st); size_t size = st.st_size; vector inBuf(size); if (fread(inBuf.data(), size, 1, inf) != 1) { cerr << "Error whle reading " << argv[1] << endl; exit(1); } string result; if (strstr(argv[0], "encode-gz-base64")) { result = encode(inBuf.data(), inBuf.size()); } else if (strstr(argv[0], "decode-gz-base64")) { result = decode(inBuf.data(), inBuf.size()); } else { cerr << "This executable should be named 'encode-gz-base64' or " "'decode-gz-base64'." << endl; exit(1); } FILE* outf = fopen(argv[2], "wb+"); if (!outf) { cerr << "Error while opening " << argv[2] << endl; exit(1); } if (fwrite(result.data(), result.size(), 1, outf) != 1) { cerr << "Error whle writing " << argv[2] << endl; exit(1); } } openMSX-RELEASE_0_12_0/Contrib/basictorom.tcl000066400000000000000000000041411257557151200207050ustar00rootroot00000000000000### basictorom.tcl ### # # This script was developed together with Daniel Vik to have an automated tool # to convert BASIC programs to ROM files. See this forum thread for more # details: # # http://www.msx.org/forumtopic9249.html # # To use it, put a file 'prog.bas' in the current directory (can be either in # ascii format or an already tokenized basic file). Then execute this script by # using the openMSX commandline. And after a few seconds the ROM image # 'prog.rom' will be generated. # # input: prog.bas # output prog.rom # start with: openmsx -script basictorom.tcl # # Note: This script only works on MSX machines that have a disk drive and have # MSX-BASIC built-in. So for example it won't work on the default C-bios # based machines. So either select a different MSX machine as your # default machine, or pass the '-machine ' as extra # option when starting openMSX. # proc do_stuff1 {} { # insert openMSX ramdisk in the MSX disk drive diska ramdsk # import host file to ramdisk diskmanipulator import diska "prog.bas" # change basic start address poke16 0xf676 0x8011 # add rom header poke 0x8000 0x41 poke 0x8001 0x42 poke16 0x8002 0x0000 poke16 0x8004 0x0000 poke16 0x8006 0x0000 poke16 0x8008 0x8010 poke16 0x800a 0x0000 poke16 0x800c 0x0000 poke16 0x800e 0x0000 poke 0x8010 0x00 # instruct MSX to load the BASIC program type "load\"prog.bas\"\r" # give MSX some time to process this # wait long enough so that even very long BASIC programs can be loaded after time 100 do_stuff2 } proc do_stuff2 {} { # save rom file set data [debug read_block "memory" 0x8000 0x4000] set file [open "prog.rom" "WRONLY CREAT TRUNC"] fconfigure $file -translation binary puts -nonewline $file $data close $file # exit emulator exit } # don't store the settings below for future openmsx sessions set save_settings_on_exit false # don't show MSX screen (remove if you want to see what's going on) set renderer none # go as fast as possible set throttle off # give emulated MSX some time to boot after time 20 do_stuff1 openMSX-RELEASE_0_12_0/Contrib/cbios/000077500000000000000000000000001257557151200171365ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/Contrib/cbios/C-BIOS_MSX1.xml000066400000000000000000000035601257557151200214100ustar00rootroot00000000000000 C-BIOS MSX1 2003 An MSX1 machine using C-BIOS, with an international keyboard layout and 50Hz interrupt frequency. MSX f41b26357bac1b75a3cfba933763764187b365f0 cbios_main_msx1.rom 16000 false int false false false TMS9929A 16 YM2149 21000 openMSX-RELEASE_0_12_0/Contrib/cbios/C-BIOS_MSX1_BR.xml000066400000000000000000000035551257557151200217770ustar00rootroot00000000000000 C-BIOS MSX1 BR 2010 An MSX1 machine using C-BIOS, with Brazillian settings, like 60Hz interrupt frequency. MSX 5577f8cf11c51d4d49192e8fdbd8da4d7b1e9c52 cbios_main_msx1_br.rom 16000 false int true false false TMS99X8A 16 YM2149 21000 openMSX-RELEASE_0_12_0/Contrib/cbios/C-BIOS_MSX1_JP.xml000066400000000000000000000036431257557151200220030ustar00rootroot00000000000000 C-BIOS MSX1 JP 2010 An MSX1 machine using C-BIOS, with an Japanese keyboard layout and 60Hz interrupt frequency. MSX 2bdde7097ffb1208c27d99b1bf18eb32bf1c735a cbios_main_msx1_jp.rom 16000 false jp_ansi false true false TMS99X8A 16 50on YM2149 21000 openMSX-RELEASE_0_12_0/Contrib/cbios/C-BIOS_MSX2+.xml000066400000000000000000000060211257557151200214570ustar00rootroot00000000000000 C-BIOS MSX2+ 2005 An MSX2+ machine using C-BIOS, with MSX-MUSIC, an international keyboard layout and 50Hz interrupt frequency. MSX2+ largest 1755a38a29c7df6c7360e9e97ba1730f66e203a7 cbios_main_msx2+.rom 2fcb40413e7d373f0f2dbdc815ce18746ddf3684 cbios_sub.rom 5c5eb001e6a1fe29edb7abd428a3967bb388e5db cbios_music.rom 9000 512 16000 false int true false false V9958 128 YM2149 21000 cbios-msx2+.cmos true openMSX-RELEASE_0_12_0/Contrib/cbios/C-BIOS_MSX2+_BR.xml000066400000000000000000000060301257557151200220420ustar00rootroot00000000000000 C-BIOS MSX2+ BR 2010 An MSX2+ machine using C-BIOS, with MSX-MUSIC, and Brazillian style settings like 60Hz interrupt frequency. MSX2+ largest 7e32510e89b07b0b4888dc815b33bd5552084059 cbios_main_msx2+_br.rom 2fcb40413e7d373f0f2dbdc815ce18746ddf3684 cbios_sub.rom 5c5eb001e6a1fe29edb7abd428a3967bb388e5db cbios_music.rom 9000 512 16000 false int true false false V9958 128 YM2149 21000 cbios-msx2+_br.cmos true openMSX-RELEASE_0_12_0/Contrib/cbios/C-BIOS_MSX2+_JP.xml000066400000000000000000000061061257557151200220540ustar00rootroot00000000000000 C-BIOS MSX2+ JP 2010 An MSX2+ machine using C-BIOS, with MSX-MUSIC, a Japanese keyboard layout and 60Hz interrupt frequency. MSX2+ largest 0ee5d3ab75bd579b7d08502b24afbb73d5d9f8bc cbios_main_msx2+_jp.rom 2fcb40413e7d373f0f2dbdc815ce18746ddf3684 cbios_sub.rom 5c5eb001e6a1fe29edb7abd428a3967bb388e5db cbios_music.rom 9000 512 16000 false jp_ansi true true false V9958 128 YM2149 50on 21000 cbios-msx2+_jp.cmos true openMSX-RELEASE_0_12_0/Contrib/cbios/C-BIOS_MSX2.xml000066400000000000000000000047141257557151200214130ustar00rootroot00000000000000 C-BIOS MSX2 2003 An MSX2 machine using C-BIOS, with an international keyboard layout and 50Hz interrupt frequency. MSX2 largest b1f4851f1c27fcbee2ddc67f4e658eaf227568d9 cbios_main_msx2.rom 2fcb40413e7d373f0f2dbdc815ce18746ddf3684 cbios_sub.rom 512 16000 false int true false false V9938 128 YM2149 21000 cbios-msx2.cmos openMSX-RELEASE_0_12_0/Contrib/cbios/C-BIOS_MSX2_BR.xml000066400000000000000000000047121257557151200217740ustar00rootroot00000000000000 C-BIOS MSX2 BR 2010 An MSX2 machine using C-BIOS, with Brazillian settings, like 60Hz interrupt frequency. MSX2 largest c9dfa8b97f628aaf299951d49be7fa68173e2fb8 cbios_main_msx2_br.rom 2fcb40413e7d373f0f2dbdc815ce18746ddf3684 cbios_sub.rom 512 16000 false int true false false V9938 128 YM2149 21000 cbios-msx2_br.cmos openMSX-RELEASE_0_12_0/Contrib/cbios/C-BIOS_MSX2_JP.xml000066400000000000000000000050011257557151200217720ustar00rootroot00000000000000 C-BIOS MSX2 JP 2010 An MSX2 machine using C-BIOS, with a Japanese keyboard layout and 60Hz interrupt frequency. MSX2 largest c3a774e650f1ed39bd93777c0525bfd4099f3cc6 cbios_main_msx2_jp.rom 2fcb40413e7d373f0f2dbdc815ce18746ddf3684 cbios_sub.rom 512 16000 false jp_ansi true true false V9938 128 YM2149 50on 21000 cbios-msx2_jp.cmos openMSX-RELEASE_0_12_0/Contrib/cbios/cbios_basic.rom000066400000000000000000000400001257557151200221070ustar00rootroot00000000000000AB@>_3B!$0@!IbB!JbBE@!">w!-J!IbB!_@!-A!͖@͟( (͢w#͢> ͢wɯ(+>͢> ͢>͢A(!IbBͦ@!J~ #~+@(##!J(## ~#fo~#fo@ɯ( #>ɯ(=($( #>!IbB!oA~0(890>ɯ~a(8z0 w#~#foeA(~#fo#F#N#xAA> ͢~# ҢAҿAAAWO!nB ~#fo z͢ÇAbBÇA͢ÇAAAÇA!JbBoAn#f#A!JbB*BB*BB*BB*BB}0͢ɷ(B0â <8B=8O! _~(_####~ {~mB͢#nFrFvF{FFFFFFFFFFFFFFFFFFFFFFFFFFGG GGGG!G'G.G5G:G@GGGNGUG\GaGfGlGpGtGzGGGGGGGGGGGGGGGGGGGGGGGGHH HHHH H%H*H.H3H7H>HAHFHKHPHTHWH\H`HdHhHpHvH|HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHII IIIIII#I'I+I0I5I;IBIGILIQIVI[I`IeIiIoIuIyI}IIIIIIIIIIIENDFORNEXTDATAINPUTDIMREADLETGOTORUNIFRESTOREGOSUBRETURNSTOPPRINTCLEARLISTNEWONWAITDEFPOKECONTCSAVECLOADOUTLPRINTLLISTCLSWIDTHTRONTROFFSWAPERASEERRORRESUMEDELETEAUTORENUMDEFSTRDEFINTDEFSNGDEFDBLLINEOPENFIELDGETPUTCLOSELOADMERGEFILESLSETRSETSAVELFILESCIRCLECOLORDRAWPAINTBEEPPLAYPSETPRESETSOUNDSCREENVPOKESPRITEVDPBASECALLTIMEKEYMAXMOTORBLOADBSAVEDSKO$SETNAMEKILLIPLCOPYCMDLOCATETOTHENTAB(STEPUSRFNSPC(NOTERLERRSTRING$USINGINSTRVARPTRCSRLINATTR$DSKI$OFFINKEY$POINT>=<+-*/^ANDORXOREQVIMPMOD\LEFT$RIGHT$MID$SGNINTABSSQRRNDSINLOGEXPCOSTANATNREMINPPOSLENSTR$VALASCCHR$PEEKVPEEKSPACE$OCT$HEX$LPOSBIN$CINTCSNGCDBLFIXSTICKSTRIGPDLPADDSKFFPOSCVICVSCVDEOFLOCLOFMKI$MKS$MKD$C-BASIC ver 0.02 (050607) Copyright (C) BouKiCHi Ok recognized Line num Not implemented yet "J'JA ALISTVLIST III IopenMSX-RELEASE_0_12_0/Contrib/cbios/cbios_disk.rom000066400000000000000000000400001257557151200217600ustar00rootroot00000000000000AB0@@)BRByBúBB C7/CVC}C!@́DۨO:/! !Aw#w#w#w!!6#}CwgD}CC!1͠@C!͠@/C!͠@@!͠@>!12}"~>2`6#w#s#r#6C-DISK is initializing!@́D!@!>7C-DISK booting͠A8> 7ښAA|@88")E)))|2`xG!` >2`:C}C&$2<2d&$:B&@$)E)))|2xG! >2&@$ɯ7>#.!A͍D!A8!A͍D!A͍DkbͲD!B͍DxͥD!B͍DͲD!B͍DyͥD>.disk: READWRITE sectors: first $, num $, to $, media $!6B́Ddisk: DSKCHG ($4013) called!]B́Ddisk: GETDPB ($4016) called!}B 1 - Single sided, 80 tracks 2 - Double sided, 80 tracks !B́D7disk: DSKFMT ($401C) called!B́Ddisk: LOC_DS ($401F) called!ĆDdisk: BASIC ($4022) called!:ĆDdisk: FORMAT ($4026) called!aĆDdisk: DSKSTP ($4029) called>@y10!Co>g~#fo>oCC>DCCCCCCSDCCC`DCCCCCCCCCCCdDjDCCCCCCCCCCCCCCCCCCCoDpDC>#.!D͍DyͥD>.disk: BDOS ($F37D/$0005) called, function $!*{$_>DgDɯoS=}D*=̀C8<>#.͍D>.~#/ 00/7/͕D͕D|ͥD}ͥDopenMSX-RELEASE_0_12_0/Contrib/cbios/cbios_logo_msx1.rom000066400000000000000000000400001257557151200227360ustar00rootroot00000000000000C-BIOS Logo ROMo>22b*">VGGG:G:*V*$ 0!\*$ * \* 0!\*>V*" ! \  ????????????|??|?????????|<<<????????~~~~?~xxx|??????|||||????~~~~<🟟|~~>???>??}!R؂!">_!EA!"b:G G>!1_G !q w> ә>ә!q>ә>ә>!1_G( !!"!a">2>2!҂T2D!O~(0O~wO~(0<=O~w#O~(0<=w#!Ev ! # !^#V#Evvә>ә :0 ~͢#~!_#U''''''''''''''''3Uwtcsrrrpp'3Uwtcsrrrpw'3Uwtcsrrrwp'3Uwtcsrrwpp'3Uwtcsrwrpp'3Uwtcswrrpp'3Uwtcwrrrpp'3Uwwcsrrrpp'3Uwtwsrrrpp @`V0.27.8~#(0_P0....8˻;bkB:bk+!~#O! !.0.j#~3@Cy64}c:j$a~?~~Y~g~}[~j+~CrWcPzfٶa 8M9<ҩ7_ ~&9l戗8_wcfA1|W9~ɓ|+fN|6!WwvN5?|Vg<(3|fGUVdCwc93?:3k/yĊw ΰ:z?c!8Dw{gKWQ\#v'C>Rg~xfǀY+#ySzYgeUn8vc9FV#f+zU8CVgsfEޏHgf-=f_of1'< ۭi;Yfc#C=K^fNfҙ7z;S 8;cߨ8~f)Gf_f6{܏WVviDN>se >sVޠJOӡ}):.G583ҔZ|4ΞSfEfCi*dODFd~C| 6Wv>-wfHe|%EVS.̭~4FPpD'VUf?2)S|rheG)4UTC~3e6̀uS{|[e7UeDUgUe|o*eU$eUSUgGD8rS(ɀ6%gDÔNVgbD~Rd͉ U0"qUbĀEf ~?8_ͺ9Cg~73߀7ۀkm8?R:k>:?~~dS9|QJj>~^(/e~/$~)? })8c΃$|4"~3>A3@"D>_~c?30~#^}C34~8K#~ȀLcD~ȀS4D~~-@c/openMSX-RELEASE_0_12_0/Contrib/cbios/cbios_logo_msx2.rom000066400000000000000000000400001257557151200227370ustar00rootroot00000000000000C-BIOS Logo ROM!H΂!">_!v;A!"b:G G>!1_G !g w> ә>ә!g>ә>ә>!1_G( !v!"!a">2>2!ȂJ2D!O~(0O~wO~(0<=O~w#O~(0<=w#!;v ! # !^#V#;vvә>ә :0 ~͢#~!_#U''''''''''''''''3Uwtcsrrrpp'3Uwtcsrrrpw'3Uwtcsrrrwp'3Uwtcsrrwpp'3Uwtcsrwrpp'3Uwtcswrrpp'3Uwtcwrrrpp'3Uwwcsrrrpp'3Uwtwsrrrppց6VvV0.27$8~#(0_P0$$$$8˻1bkB0bk+!~#O! !$0$j#~3@Cy64}c:j$a~?~~Y~g~}[~j+~CrWcPzfٶa 8M9<ҩ7_ ~&9l戗8_wcfA1|W9~ɓ|+fN|6!WwvN5?|Vg<(3|fGUVdCwc93?:3k/yĊw ΰ:z?c!8Dw{gKWQ\#v'C>Rg~xfǀY+#ySzYgeUn8vc9FV#f+zU8CVgsfEޏHgf-=f_of1'< ۭi;Yfc#C=K^fNfҙ7z;S 8;cߨ8~f)Gf_f6{܏WVviDN>se >sVޠJOӡ}):.G583ҔZ|4ΞSfEfCi*dODFd~C| 6Wv>-wfHe|%EVS.̭~4FPpD'VUf?2)S|rheG)4UTC~3e6̀uS{|[e7UeDUgUe|o*eU$eUSUgGD8rS(ɀ6%gDÔNVgbD~Rd͉ U0"qUbĀEf ~?8_ͺ9Cg~73߀7ۀkm8?R:k>:?~~dS9|QJj>~^(/e~/$~)? })8c΃$|4"~3>A3@"D>_~c?30~#^}C34~8K#~ȀLcD~ȀS4D~~-@c/openMSX-RELEASE_0_12_0/Contrib/cbios/cbios_main_msx1.rom000066400000000000000000001000001257557151200227170ustar00rootroot00000000000000 ÿ#$4$!s$'9NX".EMU`mÁ×í^ÂCÏ÷oÂÌ×:R\j|Ïô6TK%ASVõfj{ÍßÏGYò!jyDVgvÆ×éù  & 7 G Y k } Î çajmpsÂÆÊórO #(##N#fi:G.:@G.˹xәyә!x wUۘ`Ә}ә|?ә}ә|?@ә` xAO Ә U xA<= ` xA<£= !C!xQ ":(:=:o:G.:!* `Ә x :G.:H:8*(%%:m*&m:8*(`:W {Ә>ӘyӘ ͌0 zӘ>22:2>22*""*"$*"(*"&͏ͨþ>22>22*""*"$*"&*"(ͨ:2ͷSþ>2*""`Ә< *"$*"(*"&Sþ>2*""` Ә< *"$*"(*"&Sþ:G.:G .J J:G.:G .JJJJJ:G.:G .J>J>JJJ:G.:G .JJJJJ!h F~#fo)GG.   &o)))͌0))[&ɇ*(_:>> 8 :("0 * "!""[K͹:2**K @:O  :/O* ":͛:(:,/2, ͛:,/2, WXK O:GE( /z(MMAә}әۘӘә>әۙә>әۙ......>22>!m>!  m.!͗![$×:!:(8*"> m>!wE*$om:*m:G*$mK o&))) @:Ϳ##!O͟ RIGHTC!a͟ LEFTC!r͟ UPC!͟ TUPC!͟ DOWNC!͟ TDOWNCK[SCALXYCS*))))).>2,y( G>2,yO "*MAPXY:,**FETCHC! ͟ STOREC! ͟ SETATR!1 ͟ READC!B ͟ SETC!R ͟ NSETCX!d ͟ GTASPC!v ͟ PNTINI! ͟ SCANR! ͟ SCANL>#.ͫ >.~#/ 00/7/ͳ ͳ | } >ӫ>PӪ<<w /w %$|(ټ8( .gxGyOyO0xG0}(%ÉxӨy2!CMN!:Ϳ# #!*4$>22!V%͎x͊>2>2>2>2!V%͎">22)!&͎gC-BIOS Logo ROM!?6!!@iv!iv(g(#< GͿ##xͿ##W_xY!AB!x!%͎G0ʹxx(>.ʹx0ʹ> ʹ> ʹ##(Oۙ4$(((G _x0_|!_qxYzx!@~(!%͎#>!w} >!w>!wM>!w>!w'!"""H!"J"t!;%!"!"!"! "!"!"!8"!"! "!"!"!8"!"!"!"!8"!Y"!u"]!"c!u"i>2\2b2h>'2> 2:2>2>2>2>2>2:2*" >!"2!ۨW?OӨ:/_2G:/ {P2G:/ {{/2zӨp#y@O0v:0 ~ʹ#~!A# :(:)Pͫ:DR:!c͟ INIFNK!u͟ STRTMS*[> *[ v~#} !"ͤ:0:2a(:½ 8*ͽM*:$8"&w*l. !> : *:,0 E͐-"!".*l:!0..">2>2:!0<:!<2:= :=2:2:=2:!<2G< 2!4(5(.y2(y2"22> > >>2:!<2:=2l!:_w: ͽml*E:2G:ͽ=2ͽ"&:W=O!bk+.l*E:G:ͽ<2ͽ."&:=OT]#:Ó!͗ :> Ò:::ͽM::ͽE2_! **́: !!~/w#!͗ͽ>MV  . E  lrxjEK.JLl+LLMY ABCD"HExyͶ%8( > 27ӑ>Ӑ/Ӑͻې>0/!w  w@8 `0@P7:T>2T? !H͎*"&}w͏ 0!22:ċ*"> 2ͽE (*: &~(:ͽM*"> :27ɯ۪Ӫ۩۪Ӫ۩7!L͟ ISCNTCA!a͟ BEEP"!u͟ FNKSB!͟ ERAFNK!͟ DSPFNK::ͽʂ!͟ 7TAPION!͟ 7TAPIN!͟ TAPIOF!͟ 7TAPOON!͟ 7TAPOUT!͟ TAPOOFG۪( ( ˧(Ӫ>RRӠ{ӡӠۢɷ>(<ӫۨӨۙO۪Ӫ۩ͧͬ:d >s/!_~=(>\濳_>R>\/!O ~>0 >s<=G(L>\濳_>Rx( >\Ѡ(>ɯ!S͟ GTPAD!d͟ GTPDL͚F#~##o&͚~2/2c*[ :=2 ! q#c>2E۪O !yӪ۩w# !:0!&!&͖!'ݾĶ/ݦw8# #>2x((y >! > 5>y > $>!_(" ~("ì*w#} !["AW:4$`%É!͟ %ÉCALBASۙ!' Ә x !~Ә# x >ә>@ә!%~Ә#~ Ә g#_E#GۨẀ{z%Wէ$|oG>#_Ez#GۨWYͅz%W$zG(= !g$ۨÌz%oG>U{$W|gG>$_/OzG}$G>U$GzgۨoӨ:/O2}Ө!zo|gywۨӨW_oۨO?Ө{o|&))@0|/g:/_2oyӨzO}! wzGۨO?Ө{2yӨzO! sӨ^ӨszӨӨ͘ӨC-BIOS 0.27 cbios.sf.net Localization: EU/INT Init ROM in slot: Cannot execute a BASIC ROM. ERROR:MEMORY NOT FOUND.CALLED NON EXISTING BASIC.STACK ERROR. No cartridge found. This version of C-BIOS can only start cartridges. Please restart your MSX (emulator) with a cartridge inserted.0123456789-=\[];'`,./abcdefghijklmnopqrstuvwxyz)!@#$%^&*(_+|{}:"~<>?ABCDEFGHIJKLMNOPQRSTUVWXYZ     ˩ٿڷ冦椢ح궸  *+/0123456789-,.p@{( !1͟ > .z/{/ROMBAS>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:!I:͟ BASIC statements are not implemented yet͎!"}͟ unknown@7D179}!A!A!~͟ unknown@7E14openMSX-RELEASE_0_12_0/Contrib/cbios/cbios_main_msx1_br.rom000066400000000000000000001000001257557151200234020ustar00rootroot00000000000000 ÿ#$4$!s$'!9NX".EMU`mÁ×í^ÂCÏ÷oÂÌ×:R\j|Ïô6TK%ASVõfj{ÍßÏGYò!jyDVgvÆ×éù  & 7 G Y k } Î çajmpsÂÆÊórO #(##N#fi:G.:@G.˹xәyә!x wUۘ`Ә}ә|?ә}ә|?@ә` xAO Ә U xA<= ` xA<£= !C!xQ ":(:=:o:G.:!* `Ә x :G.:H:8*(%%:m*&m:8*(`:W {Ә>ӘyӘ ͌0 zӘ>22:2>22*""*"$*"(*"&͏ͨþ>22>22*""*"$*"&*"(ͨ:2ͷSþ>2*""`Ә< *"$*"(*"&Sþ>2*""` Ә< *"$*"(*"&Sþ:G.:G .J J:G.:G .JJJJJ:G.:G .J>J>JJJ:G.:G .JJJJJ!h F~#fo)GG.   &o)))͌0))[&ɇ*(_:>> 8 :("0 * "!""[K͹:2**K @:O  :/O* ":͛:(:,/2, ͛:,/2, WXK O:GE( /z(MMAә}әۘӘә>әۙә>әۙ......>22>!m>!  m.!͗![$×:!:(8*"> m>!wE*$om:*m:G*$mK o&))) @:Ϳ##!O͟ RIGHTC!a͟ LEFTC!r͟ UPC!͟ TUPC!͟ DOWNC!͟ TDOWNCK[SCALXYCS*))))).>2,y( G>2,yO "*MAPXY:,**FETCHC! ͟ STOREC! ͟ SETATR!1 ͟ READC!B ͟ SETC!R ͟ NSETCX!d ͟ GTASPC!v ͟ PNTINI! ͟ SCANR! ͟ SCANL>#.ͫ >.~#/ 00/7/ͳ ͳ | } >ӫ>PӪ<<w /w %$|(ټ8( .gxGyOyO0xG0}(%ÉxӨy2!CMN!:Ϳ# #!*4$>22!V%͎x͊>2>2>2>2!V%͎">22)!%͎gC-BIOS Logo ROM!?6!!@iv!iv(g(#< GͿ##xͿ##W_xY!AB!x!%͎G0ʹxx(>.ʹx0ʹ> ʹ> ʹ##(Oۙ4$(((G _x0_|!_qxYzx!@~(!%͎#>!w} >!w>!wM>!w>!w'!"""H!"J"t!;%!"!"!"! "!"!"!8"!"! "!"!"!8"!"!"!"!8"!Y"!u"]!"c!u"i>2\2b2h>'2> 2:2>2>2>2>2>2:2*" >!"2!ۨW?OӨ:/_2G:/ {P2G:/ {{/2zӨp#y@O0v:0 ~ʹ#~!A# :(:)Pͫ:DR:!c͟ INIFNK!u͟ STRTMS*[> *[ v~#} !"ͤ:0:2a(:½ 8*ͽM*:$8"&w*l. !> : *:,0 E͐-"!".*l:!0..">2>2:!0<:!<2:= :=2:2:=2:!<2G< 2!4(5(.y2(y2"22> > >>2:!<2:=2l!:_w: ͽml*E:2G:ͽ=2ͽ"&:W=O!bk+.l*E:G:ͽ<2ͽ."&:=OT]#:Ó!͗ :> Ò:::ͽM::ͽE2_! **́: !!~/w#!͗ͽ>MV  . E  lrxjEK.JLl+LLMY ABCD"HExyͶ%8( > 27ӑ>Ӑ/Ӑͻې>0/!w  w@8 `0@P7:T>2T? !H͎*"&}w͏ 0!22:ċ*"> 2ͽE (*: &~(:ͽM*"> :27ɯ۪Ӫ۩۪Ӫ۩7!L͟ ISCNTCA!a͟ BEEP"!u͟ FNKSB!͟ ERAFNK!͟ DSPFNK::ͽʂ!͟ 7TAPION!͟ 7TAPIN!͟ TAPIOF!͟ 7TAPOON!͟ 7TAPOUT!͟ TAPOOFG۪( ( ˧(Ӫ>RRӠ{ӡӠۢɷ>(<ӫۨӨۙO۪Ӫ۩ͧͬ:d >s/!_~=(>\濳_>R>\/!O ~>0 >s<=G(L>\濳_>Rx( >\Ѡ(>ɯ!S͟ GTPAD!d͟ GTPDL͚F#~##o&͚~2/2c*[ :=2 ! q#c>2E۪O !yӪ۩w# !:0!&!&͖!'ݾĶ/ݦw8# #>2x((y >! > 5>y > $>!_(" ~("ì*w#} !["AW:4$`%É!͟ %ÉCALBASۙ!' Ә x !~Ә# x >ә>@ә!%~Ә#~ Ә g#_E#GۨẀ{z%Wէ$|oG>#_Ez#GۨWYͅz%W$zG(= !g$ۨÌz%oG>U{$W|gG>$_/OzG}$G>U$GzgۨoӨ:/O2}Ө!zo|gywۨӨW_oۨO?Ө{o|&))@0|/g:/_2oyӨzO}! wzGۨO?Ө{2yӨzO! sӨ^ӨszӨӨ͘ӨC-BIOS 0.27 cbios.sf.net Localization: BR Init ROM in slot: Cannot execute a BASIC ROM. ERROR:MEMORY NOT FOUND.CALLED NON EXISTING BASIC.STACK ERROR. No cartridge found. This version of C-BIOS can only start cartridges. Please restart your MSX (emulator) with a cartridge inserted.0123456789-=\[];'`,./abcdefghijklmnopqrstuvwxyz)!@#$%^&*(_+|{}:"~<>?ABCDEFGHIJKLMNOPQRSTUVWXYZ     ˩ٿڷ冦椢ح궸  *+/0123456789-,.p@{( !1͟ > .z/{/ROMBAS>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:!I:͟ BASIC statements are not implemented yet͎!"}͟ unknown@7D179}!A!A!~͟ unknown@7E14openMSX-RELEASE_0_12_0/Contrib/cbios/cbios_main_msx1_jp.rom000066400000000000000000001000001257557151200234100ustar00rootroot00000000000000 ÿ#$4$!s$'9NX".EMU`mÁ×í^ÂCÏ÷oÂÌ×:R\j|Ïô6TK%ASVõfj{ÍßÏGYò!jyDVgvÆ×éù  & 7 G Y k } Î çajmpsÂÆÊórO #(##N#fi:G.:@G.˹xәyә!x wUۘ`Ә}ә|?ә}ә|?@ә` xAO Ә U xA<= ` xA<£= !C!xQ ":(:=:o:G.:!* `Ә x :G.:H:8*(%%:m*&m:8*(`:W {Ә>ӘyӘ ͌0 zӘ>22:2>22*""*"$*"(*"&͏ͨþ>22>22*""*"$*"&*"(ͨ:2ͷSþ>2*""`Ә< *"$*"(*"&Sþ>2*""` Ә< *"$*"(*"&Sþ:G.:G .J J:G.:G .JJJJJ:G.:G .J>J>JJJ:G.:G .JJJJJ!h F~#fo)GG.   &o)))͌0))[&ɇ*(_:>> 8 :("0 * "!""[K͹:2**K @:O  :/O* ":͛:(:,/2, ͛:,/2, WXK O:GE( /z(MMAә}әۘӘә>әۙә>әۙ......>22>!m>!  m.!͗![$×:!:(8*"> m>!wE*$om:*m:G*$mK o&))) @:Ϳ##!O͟ RIGHTC!a͟ LEFTC!r͟ UPC!͟ TUPC!͟ DOWNC!͟ TDOWNCK[SCALXYCS*))))).>2,y( G>2,yO "*MAPXY:,**FETCHC! ͟ STOREC! ͟ SETATR!1 ͟ READC!B ͟ SETC!R ͟ NSETCX!d ͟ GTASPC!v ͟ PNTINI! ͟ SCANR! ͟ SCANL>#.ͫ >.~#/ 00/7/ͳ ͳ | } >ӫ>PӪ<<w /w %$|(ټ8( .gxGyOyO0xG0}(%ÉxӨy2!CMN!:Ϳ# #!*4$>22!V%͎x͊>2>2>2>2!V%͎">22)!%͎gC-BIOS Logo ROM!?6!!@iv!iv(g(#< GͿ##xͿ##W_xY!AB!x!%͎G0ʹxx(>.ʹx0ʹ> ʹ> ʹ##(Oۙ4$(((G _x0_|!_qxYzx!@~(!%͎#>!w} >!w>!wM>!w>!w'!"""H!"J"t!;%!"!"!"! "!"!"!8"!"! "!"!"!8"!"!"!"!8"!Y"!u"]!"c!u"i>2\2b2h>'2> 2:2>2>2>2>2>2:2*" >!"2!ۨW?OӨ:/_2G:/ {P2G:/ {{/2zӨp#y@O0v:0 ~ʹ#~!A# :(:)Pͫ:DR:!c͟ INIFNK!u͟ STRTMS*[> *[ v~#} !"ͤ:0:2a(:½ 8*ͽM*:$8"&w*l. !> : *:,0 E͐-"!".*l:!0..">2>2:!0<:!<2:= :=2:2:=2:!<2G< 2!4(5(.y2(y2"22> > >>2:!<2:=2l!:_w: ͽml*E:2G:ͽ=2ͽ"&:W=O!bk+.l*E:G:ͽ<2ͽ."&:=OT]#:Ó!͗ :> Ò:::ͽM::ͽE2_! **́: !!~/w#!͗ͽ>MV  . E  lrxjEK.JLl+LLMY ABCD"HExyͶ%8( > 27ӑ>Ӑ/Ӑͻې>0/!w  w@8 `0@P7:T>2T? !H͎*"&}w͏ 0!22:ċ*"> 2ͽE (*: &~(:ͽM*"> :27ɯ۪Ӫ۩۪Ӫ۩7!L͟ ISCNTCA!a͟ BEEP"!u͟ FNKSB!͟ ERAFNK!͟ DSPFNK::ͽʂ!͟ 7TAPION!͟ 7TAPIN!͟ TAPIOF!͟ 7TAPOON!͟ 7TAPOUT!͟ TAPOOFG۪( ( ˧(Ӫ>RRӠ{ӡӠۢɷ>(<ӫۨӨۙO۪Ӫ۩ͧͬ:d >s/!_~=(>\濳_>R>\/!O ~>0 >s<=G(L>\濳_>Rx( >\Ѡ(>ɯ!S͟ GTPAD!d͟ GTPDL͚F#~##o&͚~2/2c*[ :=2 ! q#c>2E۪O !yӪ۩w# !:0!&!&͖! (ݾĶ/ݦw8# #>2x((y >! > 5>y > $>!_(" ~("ì*w#} !["AW:4$`%É!͟ %ÉCALBASۙ!3( Ә x !~Ә# x >ә>@ә!%~Ә#~ Ә g|D|D|D 0H8T|8T8T8T||DD|DD|@||P (D|$DH$UbD| |D|D|p@@|D|((HHB$$B|(H|T|TT0ll$$~$~$$>TT>>"T(%B8DD(2J|`` T88T|`` ~`` @8LTTd8088D8@|x8xDDD||@xx8@xDD8|D8D8DD88DD<8 @ ||  8D#_E#GۨẀ{z%Wէ$|oG>#_Ez#GۨWYͅz%W$zG(= !g$ۨÌz%oG>U{$W|gG>$_/OzG}$G>U$GzgۨoӨ:/O2}Ө!zo|gywۨӨW_oۨO?Ө{o|&))@0|/g:/_2oyӨzO}! wzGۨO?Ө{2yӨzO! sӨ^ӨszӨӨ͘ӨC-BIOS 0.27 cbios.sf.net Localization: JP Init ROM in slot: Cannot execute a BASIC ROM. ERROR:MEMORY NOT FOUND.CALLED NON EXISTING BASIC.STACK ERROR. No cartridge found. This version of C-BIOS can only start cartridges. Please restart your MSX (emulator) with a cartridge inserted.0123456789-^\@[;:],./_abcdefghijklmnopqrstuvwxyz0!"#$%&'()=~|`{+*}<>?_ABCDEFGHIJKLMNOPQRSTUVWXYZ          쑓᚟ꗘ̱Ͱڹʷ׾Ķû†  *+/0123456789-,.p@{( !1͟ > .z/{/ROMBAS>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:!I:͟ BASIC statements are not implemented yet͎!"}͟ unknown@7D179}!A!A!~͟ unknown@7E14openMSX-RELEASE_0_12_0/Contrib/cbios/cbios_main_msx2+.rom000066400000000000000000001000001257557151200227730ustar00rootroot00000000000000 Ò#ä$G$Æ$Ú".Z`hzÎí>ÝæïøH{!4YÍ÷ 2/ASCö fyËÝð-Yk|ËÛìþ  ) ; L \ n À Ò ã [!$'6:>gy}Ó&5=;PZÖÜNRO #(##N#fi:G.:@G.˹xәyә!y8!8 ( 0!x whۘzӘә>ә}ә|?әә>ә}ә|?@ә:0zZ xAO Ә :0hP xA<= :0zZ xA<= !:(:=:o:G.:!* zӘ x :G.:h:8*(%%:;*&;:8*(Z:W {Ә>ӘyӘ 0 zӘ!!!!:)0(:G.:G .ͬ ͬ:G.:G .">ͬ ͬ:G.:G .ͬͬͬͬͬ:G.:G .ͬ>ͬ>ͬͬͬ:G.:G .ͬͬͬͬͬ! F~#fo)GG.   &o)))0))[&ɇ*(_:>> 8 :(+0 * "!"!7[K:2**K @:O } :/O}* "::(:,/2, :,/2, WXK O:GZ( /z(``Aә}әPۘZӘә>әۙә>әۙ......>22c>!͎>!  ͎.!.;..![$: !BTc:(8*"> ͎>!w*$o͎:*Î:G*$Î:G!):G!:G!:!.,$(!*!$:g.&>-$>.$.}$ |әyә>ͤ8K o&))) @:##!dʹ RIGHTC!vʹ LEFTC!ʹ UPC!ʹ TUPC!ʹ DOWNC!ʹ TDOWNCK[SCALXYCS*))))).>2,y( G>2,yO "*MAPXY:,**FETCHC!" ʹ STOREC!4 ʹ SETATR!F ʹ READC!W ʹ SETC!g ʹ NSETCX!y ʹ GTASPC! ʹ PNTINI! ʹ SCANR! ʹ SCANL>#. >.~#/ 00/7/ | } >ӫ>PӪ<<w /w %$|(ټ8( .gxGyOyO0xG0}(%VxӨy2!S͗͸O!:# #!*G$>22ͦ!i%3>ә>әә@ә>vӘә!әۘv(֯ә>әx/Ϳ#>2>2>2>2ͦ!A!i%3^=>22)!&3C-BIOS Logo ROM!?6!!@̲ͥ!̲ͥ(g(#< G##x##W_x͕!ABx!%3G0Yxx(>.Yx0Y> Y> Y##4(OۙG$4(4(4(G _x0_|!_qx͕zx!@~(!%3#>!w} >!w>!wM>!w>!w'>!w>2>2!"""H!"J"t!N%!"!"!"! "!"!"!8"!"! "!"!"!8"!"!"!"!8"!Y"!u"]!"c!u"i>2\2b2h>'2> 2:2>2>2>2>2>2:2*" >!Y"2!ۨW?OӨ:/_2G:/ {P2G:/ {{/2zӨp#y@O0!y  # 2 O2!&C #&Dy7ȷy#v:0 ~Y#~!# :(:)PP:D!ʹ INIFNK!ʹ STRTMS*[> *[ v~#} !"ͤ:0͸:2a(:b 8*ʪb`*:$8"&w* !<> ͗: *:,0 5-"!"*:!0">2>2:!0<:!<2:= :=2:2:=2:!<2G< 2!`4(5(.y2(y2"22> > >>2:!<2:=2!:_w: bÎ*E:2G:b=2bs"&:W=O!bk+*E:G:b<2bs"&:=OT]#:O)8 (͐:(O͐ͭ! :> ͗7:::b`::bZ2_! **ͭ: !!~/w#!b>` 7    3 7LUj3E3KJlLM5YALBUCDHxyͶ8ͷ( > ͪ27ӑ>Ӑ/Ӑͻې>0/!w  w@8 `0@P7:>2? !3*"&}w4ʘ 0!22:?͸*"> 2bZ (*: &~(:b`*"> :27ɯ۪Ӫ۩۪Ӫ۩7!ʹ ISCNTC!ʹ BEEP"!)ʹ FNKSB!:ʹ ERAFNK!Lʹ DSPFNK::ͽʝæ!rʹ 7TAPION!ʹ 7TAPIN!ʹ TAPIOF!ʹ 7TAPOON!ʹ 7TAPOUT!ʹ TAPOOFG۪( ( ˧(Ӫ><<<>Ӡ{ӡӠۢɷ>(<ӫۨӨۙO۪Ӫ۩ͧͬ:d >'/!_~=(>濳_>>/!O ~>0 >'<=G(L>濳_>x( >Ѡ(>ɯ!ʹ GTPAD!ʹ GTPDLNF#~##o&N~2Ͷ/2*[ :=2 ! q#>2E۪O !yӪ۩w# !:0!&!&J!'ݾj/ݦw8# #>2x((y >! > 5>y > $>!_( ~(`*w#} !["W:G$&V!6ʹ %VCALBAS["S//ۙ!' Ә x !~Ә# x >ә>@ә!%~Ә#~ Ә  $_E $GۨẀ{z-%Wէ$|oG> $_Ez $GۨWYͅz-%W$zG(= !z$ۨÌz-%oG>U$W|gG>$_/OzG}$G>U$GzgۨoӨ:/O2}Ө!zo|gywۨӨW_oۨO?Ө{o|&))@0|/g:/_2oyӨzO}! wzGۨO?Ө{2yӨzO! sӨ^ӨszӨӨ͘ӨC-BIOS 0.27 cbios.sf.net Localization: EU/INT Init ROM in slot: Cannot execute a BASIC ROM. ERROR:MEMORY NOT FOUND.CALLED NON EXISTING BASIC.STACK ERROR. No cartridge found. This version of C-BIOS can only start cartridges. Please restart your MSX (emulator) with a cartridge inserted.0123456789-=\[];'`,./abcdefghijklmnopqrstuvwxyz)!@#$%^&*(_+|{}:"~<>?ABCDEFGHIJKLMNOPQRSTUVWXYZ     ˩ٿڷ冦椢ح궸  *+/0123456789-,.p@{( !1ʹ > .z/{/ROMBAS>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:!I:ʹ BASIC statements are not implemented yet3!"}ʹ unknown@7D179}!!!~ʹ unknown@7E14openMSX-RELEASE_0_12_0/Contrib/cbios/cbios_main_msx2+_br.rom000066400000000000000000001000001257557151200234560ustar00rootroot00000000000000 Ò#ä$G$Æ$!Ú".Z`hzÎí>ÝæïøH{!4YÍ÷ 2/ASCö fyËÝð-Yk|ËÛìþ  ) ; L \ n À Ò ã [!$'6:>gy}Ó&5=;PZÖÜNRO #(##N#fi:G.:@G.˹xәyә!y8!8 ( 0!x whۘzӘә>ә}ә|?әә>ә}ә|?@ә:0zZ xAO Ә :0hP xA<= :0zZ xA<= !:(:=:o:G.:!* zӘ x :G.:h:8*(%%:;*&;:8*(Z:W {Ә>ӘyӘ 0 zӘ!!!!:)0(:G.:G .ͬ ͬ:G.:G .">ͬ ͬ:G.:G .ͬͬͬͬͬ:G.:G .ͬ>ͬ>ͬͬͬ:G.:G .ͬͬͬͬͬ! F~#fo)GG.   &o)))0))[&ɇ*(_:>> 8 :(+0 * "!"!7[K:2**K @:O } :/O}* "::(:,/2, :,/2, WXK O:GZ( /z(``Aә}әPۘZӘә>әۙә>әۙ......>22c>!͎>!  ͎.!.;..![$: !BTc:(8*"> ͎>!w*$o͎:*Î:G*$Î:G!):G!:G!:!.,$(!*!$:g.&>-$>.$.}$ |әyә>ͤ8K o&))) @:##!dʹ RIGHTC!vʹ LEFTC!ʹ UPC!ʹ TUPC!ʹ DOWNC!ʹ TDOWNCK[SCALXYCS*))))).>2,y( G>2,yO "*MAPXY:,**FETCHC!" ʹ STOREC!4 ʹ SETATR!F ʹ READC!W ʹ SETC!g ʹ NSETCX!y ʹ GTASPC! ʹ PNTINI! ʹ SCANR! ʹ SCANL>#. >.~#/ 00/7/ | } >ӫ>PӪ<<w /w %$|(ټ8( .gxGyOyO0xG0}(%VxӨy2!S͗͸O!:# #!*G$>22ͦ!i%3>ә>әә@ә>vӘә!әۘv(֯ә>әx/Ϳ#>2>2>2>2ͦ!A!i%3^=>22)!&3C-BIOS Logo ROM!?6!!@̲ͥ!̲ͥ(g(#< G##x##W_x͕!ABx!%3G0Yxx(>.Yx0Y> Y> Y##4(OۙG$4(4(4(G _x0_|!_qx͕zx!@~(!%3#>!w} >!w>!wM>!w>!w'>!w>2>2!"""H!"J"t!N%!"!"!"! "!"!"!8"!"! "!"!"!8"!"!"!"!8"!Y"!u"]!"c!u"i>2\2b2h>'2> 2:2>2>2>2>2>2:2*" >!Y"2!ۨW?OӨ:/_2G:/ {P2G:/ {{/2zӨp#y@O0!y  # 2 O2!&C #&Dy7ȷy#v:0 ~Y#~!# :(:)PP:D!ʹ INIFNK!ʹ STRTMS*[> *[ v~#} !"ͤ:0͸:2a(:b 8*ʪb`*:$8"&w* !<> ͗: *:,0 5-"!"*:!0">2>2:!0<:!<2:= :=2:2:=2:!<2G< 2!`4(5(.y2(y2"22> > >>2:!<2:=2!:_w: bÎ*E:2G:b=2bs"&:W=O!bk+*E:G:b<2bs"&:=OT]#:O)8 (͐:(O͐ͭ! :> ͗7:::b`::bZ2_! **ͭ: !!~/w#!b>` 7    3 7LUj3E3KJlLM5YALBUCDHxyͶ8ͷ( > ͪ27ӑ>Ӑ/Ӑͻې>0/!w  w@8 `0@P7:>2? !3*"&}w4ʘ 0!22:?͸*"> 2bZ (*: &~(:b`*"> :27ɯ۪Ӫ۩۪Ӫ۩7!ʹ ISCNTC!ʹ BEEP"!)ʹ FNKSB!:ʹ ERAFNK!Lʹ DSPFNK::ͽʝæ!rʹ 7TAPION!ʹ 7TAPIN!ʹ TAPIOF!ʹ 7TAPOON!ʹ 7TAPOUT!ʹ TAPOOFG۪( ( ˧(Ӫ><<<>Ӡ{ӡӠۢɷ>(<ӫۨӨۙO۪Ӫ۩ͧͬ:d >'/!_~=(>濳_>>/!O ~>0 >'<=G(L>濳_>x( >Ѡ(>ɯ!ʹ GTPAD!ʹ GTPDLNF#~##o&N~2Ͷ/2*[ :=2 ! q#>2E۪O !yӪ۩w# !:0!&!&J!'ݾj/ݦw8# #>2x((y >! > 5>y > $>!_( ~(`*w#} !["W:G$&V!6ʹ %VCALBAS["S//ۙ!' Ә x !~Ә# x >ә>@ә!%~Ә#~ Ә  $_E $GۨẀ{z-%Wէ$|oG> $_Ez $GۨWYͅz-%W$zG(= !z$ۨÌz-%oG>U$W|gG>$_/OzG}$G>U$GzgۨoӨ:/O2}Ө!zo|gywۨӨW_oۨO?Ө{o|&))@0|/g:/_2oyӨzO}! wzGۨO?Ө{2yӨzO! sӨ^ӨszӨӨ͘ӨC-BIOS 0.27 cbios.sf.net Localization: BR Init ROM in slot: Cannot execute a BASIC ROM. ERROR:MEMORY NOT FOUND.CALLED NON EXISTING BASIC.STACK ERROR. No cartridge found. This version of C-BIOS can only start cartridges. Please restart your MSX (emulator) with a cartridge inserted.0123456789-=\[];'`,./abcdefghijklmnopqrstuvwxyz)!@#$%^&*(_+|{}:"~<>?ABCDEFGHIJKLMNOPQRSTUVWXYZ     ˩ٿڷ冦椢ح궸  *+/0123456789-,.p@{( !1ʹ > .z/{/ROMBAS>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:!I:ʹ BASIC statements are not implemented yet3!"}ʹ unknown@7D179}!!!~ʹ unknown@7E14openMSX-RELEASE_0_12_0/Contrib/cbios/cbios_main_msx2+_jp.rom000066400000000000000000001000001257557151200234640ustar00rootroot00000000000000 Ò#ä$G$Æ$Ú".Z`hzÎí>ÝæïøH{!4YÍ÷ 2/ASCö fyËÝð-Yk|ËÛìþ  ) ; L \ n À Ò ã [!$'6:>gy}Ó&5=;PZÖÜNRO #(##N#fi:G.:@G.˹xәyә!y8!8 ( 0!x whۘzӘә>ә}ә|?әә>ә}ә|?@ә:0zZ xAO Ә :0hP xA<= :0zZ xA<= !:(:=:o:G.:!* zӘ x :G.:h:8*(%%:;*&;:8*(Z:W {Ә>ӘyӘ 0 zӘ!!!!:)0(:G.:G .ͬ ͬ:G.:G .">ͬ ͬ:G.:G .ͬͬͬͬͬ:G.:G .ͬ>ͬ>ͬͬͬ:G.:G .ͬͬͬͬͬ! F~#fo)GG.   &o)))0))[&ɇ*(_:>> 8 :(+0 * "!"!7[K:2**K @:O } :/O}* "::(:,/2, :,/2, WXK O:GZ( /z(``Aә}әPۘZӘә>әۙә>әۙ......>22c>!͎>!  ͎.!.;..![$: !BTc:(8*"> ͎>!w*$o͎:*Î:G*$Î:G!):G!:G!:!.,$(!*!$:g.&>-$>.$.}$ |әyә>ͤ8K o&))) @:##!dʹ RIGHTC!vʹ LEFTC!ʹ UPC!ʹ TUPC!ʹ DOWNC!ʹ TDOWNCK[SCALXYCS*))))).>2,y( G>2,yO "*MAPXY:,**FETCHC!" ʹ STOREC!4 ʹ SETATR!F ʹ READC!W ʹ SETC!g ʹ NSETCX!y ʹ GTASPC! ʹ PNTINI! ʹ SCANR! ʹ SCANL>#. >.~#/ 00/7/ | } >ӫ>PӪ<<w /w %$|(ټ8( .gxGyOyO0xG0}(%VxӨy2!S͗͸O!:# #!*G$>22ͦ!i%3>ә>әә@ә>vӘә!әۘv(֯ә>әx/Ϳ#>2>2>2>2ͦ!A!i%3^=>22)!&3C-BIOS Logo ROM!?6!!@̲ͥ!̲ͥ(g(#< G##x##W_x͕!ABx!%3G0Yxx(>.Yx0Y> Y> Y##4(OۙG$4(4(4(G _x0_|!_qx͕zx!@~(!%3#>!w} >!w>!wM>!w>!w'>!w>2>2!"""H!"J"t!N%!"!"!"! "!"!"!8"!"! "!"!"!8"!"!"!"!8"!Y"!u"]!"c!u"i>2\2b2h>'2> 2:2>2>2>2>2>2:2*" >!Y"2!ۨW?OӨ:/_2G:/ {P2G:/ {{/2zӨp#y@O0!y  # 2 O2!&C #&Dy7ȷy#v:0 ~Y#~!# :(:)PP:D!ʹ INIFNK!ʹ STRTMS*[> *[ v~#} !"ͤ:0͸:2a(:b 8*ʪb`*:$8"&w* !<> ͗: *:,0 5-"!"*:!0">2>2:!0<:!<2:= :=2:2:=2:!<2G< 2!`4(5(.y2(y2"22> > >>2:!<2:=2!:_w: bÎ*E:2G:b=2bs"&:W=O!bk+*E:G:b<2bs"&:=OT]#:O)8 (͐:(O͐ͭ! :> ͗7:::b`::bZ2_! **ͭ: !!~/w#!b>` 7    3 7LUj3E3KJlLM5YALBUCDHxyͶ8ͷ( > ͪ27ӑ>Ӑ/Ӑͻې>0/!w  w@8 `0@P7:>2? !3*"&}w4ʘ 0!22:?͸*"> 2bZ (*: &~(:b`*"> :27ɯ۪Ӫ۩۪Ӫ۩7!ʹ ISCNTC!ʹ BEEP"!)ʹ FNKSB!:ʹ ERAFNK!Lʹ DSPFNK::ͽʝæ!rʹ 7TAPION!ʹ 7TAPIN!ʹ TAPIOF!ʹ 7TAPOON!ʹ 7TAPOUT!ʹ TAPOOFG۪( ( ˧(Ӫ><<<>Ӡ{ӡӠۢɷ>(<ӫۨӨۙO۪Ӫ۩ͧͬ:d >'/!_~=(>濳_>>/!O ~>0 >'<=G(L>濳_>x( >Ѡ(>ɯ!ʹ GTPAD!ʹ GTPDLNF#~##o&N~2Ͷ/2*[ :=2 ! q#>2E۪O !yӪ۩w# !:0!&!&J!(ݾj/ݦw8# #>2x((y >! > 5>y > $>!_( ~(`*w#} !["W:G$&V!6ʹ %VCALBAS["S//ۙ!F( Ә x !~Ә# x >ә>@ә!%~Ә#~ Ә |D|D|D 0H8T|8T8T8T||DD|DD|@||P (D|$DH$UbD| |D|D|p@@|D|((HHB$$B|(H|T|TT0ll$$~$~$$>TT>>"T(%B8DD(2J|`` T88T|`` ~`` @8LTTd8088D8@|x8xDDD||@xx8@xDD8|D8D8DD88DD<8 @ ||  8D $_E $GۨẀ{z-%Wէ$|oG> $_Ez $GۨWYͅz-%W$zG(= !z$ۨÌz-%oG>U$W|gG>$_/OzG}$G>U$GzgۨoӨ:/O2}Ө!zo|gywۨӨW_oۨO?Ө{o|&))@0|/g:/_2oyӨzO}! wzGۨO?Ө{2yӨzO! sӨ^ӨszӨӨ͘ӨC-BIOS 0.27 cbios.sf.net Localization: JP Init ROM in slot: Cannot execute a BASIC ROM. ERROR:MEMORY NOT FOUND.CALLED NON EXISTING BASIC.STACK ERROR. No cartridge found. This version of C-BIOS can only start cartridges. Please restart your MSX (emulator) with a cartridge inserted.0123456789-^\@[;:],./_abcdefghijklmnopqrstuvwxyz0!"#$%&'()=~|`{+*}<>?_ABCDEFGHIJKLMNOPQRSTUVWXYZ          쑓᚟ꗘ̱Ͱڹʷ׾Ķû†  *+/0123456789-,.p@{( !1ʹ > .z/{/ROMBAS>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:!I:ʹ BASIC statements are not implemented yet3!"}ʹ unknown@7D179}!!!~ʹ unknown@7E14openMSX-RELEASE_0_12_0/Contrib/cbios/cbios_main_msx2.rom000066400000000000000000001000001257557151200227200ustar00rootroot00000000000000 Ò#ä$G$Æ$Ú".QW_qÅä5ÔÝæïø?r!4YÍ÷ )/ASCö fyËÝð-PbsÂÒãõ  2 C S e w É Ú [!$'6:>gy}Ó&,=2GQÍÓO #(##N#fi:G.:@G.˹xәyә!y80!x w_ۘqӘә>ә}ә|?әә>ә}ә|?@ә:0qQ xAO Ә :0_G xA<= :0qQ xA<= !:(:=:o:G.:!* qӘ x :G.:_:8*(%%:2*&2:8*(Q:W {Ә>ӘyӘ 0 zӘ!!!!:)0(:G.:G .ͣ ͣ:G.:G .">ͣ ͣ:G.:G .ͣͣͣͣͣ:G.:G .ͣ>ͣ>ͣͣͣ:G.:G .ͣͣͣͣͣ! F~#fo)GG.   &o)))0))[&ɇ*(_:>> 8 :(+0 * "!"!.[K:2**K @:O t :/Ot* "::(:,/2, :,/2, WXK O:GQ( /z(WWAә}әGۘQӘә>әۙә>әۙ......>22Z>!ͅ>!  ͅ.!.;..![$: !9KZvv:(8*"> ͅ>!w*$oͅ:*Å:G*$Å:G!):G!:G!:!%,(!*!$:g.&>->.%} |әyә>͛8K o&))) @:##![ͫ RIGHTC!mͫ LEFTC!~ͫ UPC!ͫ TUPC!ͫ DOWNC!ͫ TDOWNCK[SCALXYCS*))))).>2,y( G>2,yO "*MAPXY:,**FETCHC! ͫ STOREC!+ ͫ SETATR!= ͫ READC!N ͫ SETC!^ ͫ NSETCX!p ͫ GTASPC! ͫ PNTINI! ͫ SCANR! ͫ SCANL>#.ͷ >.~#/ 00/7/Ϳ Ϳ | } >ӫ>PӪ<<w /w %$|(ټ8( .gxGyOyO0xG0}(%NxӨy2!S͗ͯO!:# #!*G$>22͝!i%3>ә>әә@ә>vӘә!әۘv(֯ә>әx/Ϳ#>2>2>2>2͝!A!i%3^=>22)!&3C-BIOS Logo ROM!?6!!@̲ͥ!̲ͥ(g(#< G##x##W_x͕!ABx!%3G0Yxx(>.Yx0Y> Y> Y##4(OۙG$4(4(4(G _x0_|!_qx͕zx!@~(!%3#>!w} >!w>!wM>!w>!w'>!w>2>2!"""H!"J"t!N%!"!"!"! "!"!"!8"!"! "!"!"!8"!"!"!"!8"!Y"!u"]!"c!u"i>2\2b2h>'2> 2:2>2>2>2>2>2:2*" >!Y"2!ۨW?OӨ:/_2G:/ {P2G:/ {{/2zӨp#y@O0!y  # 2 O2!&C #&Dy7ȷy#v:0 ~Y#~!# :(:)PP:D!ͫ INIFNK!ͫ STRTMS*[> *[ v~#} !"ͤ:0͸:2a(:b 8*ʪbW*:$8"&w* !<> ͗: *:,0 5-"!"*:!0">2>2:!0<:!<2:= :=2:2:=2:!<2G< 2!`4(5(.y2(y2"22> > >>2:!<2:=2!:_w: bÅ*E:2G:b=2bs"&:W=O!bk+*E:G:b<2bs"&:=OT]#:O)8 (͐:(O͐ͤ! :> ͗7:::bW::bQ2_! **ͤ: !!~/w#!b>W 7    * 7LUj*E*KJlLM5YALBUCDHxyͶ8ͷ( > ͪ27ӑ>Ӑ/Ӑͻې>0/!w  w@8 `0@P7:>2? !3*"&}w4ʘ 0!22:?͸*"> 2bQ (*: &~(:bW*"> :27ɯ۪Ӫ۩۪Ӫ۩7!ͫ ISCNTC!ͫ BEEP"!)ͫ FNKSB!:ͫ ERAFNK!Lͫ DSPFNK::ͽʔÝ!rͫ 7TAPION!ͫ 7TAPIN!ͫ TAPIOF!ͫ 7TAPOON!ͫ 7TAPOUT!ͫ TAPOOFG۪( ( ˧(Ӫ><<<>Ӡ{ӡӠۢɷ>(<ӫۨӨۙO۪Ӫ۩ͧͬ:d >'/!_~=(>濳_>>/!O ~>0 >'<=G(L>濳_>x( >Ѡ(>ɯ!ͫ GTPAD!ͫ GTPDLNF#~##o&N~2Ͷ/2*[ :=2 ! q#>2E۪O !yӪ۩w# !:0!&!&J!'ݾj/ݦw8# #>2x((y >! > 5>y > $>!_( ~(`*w#} !["W:G$&N!6ͫ %NCALBAS["Sۙ!' Ә x !~Ә# x >ә>@ә!%~Ә#~ Ә  $_E $GۨẀ{z-%Wէ$|oG> $_Ez $GۨWYͅz-%W$zG(= !z$ۨÌz-%oG>U$W|gG>$_/OzG}$G>U$GzgۨoӨ:/O2}Ө!zo|gywۨӨW_oۨO?Ө{o|&))@0|/g:/_2oyӨzO}! wzGۨO?Ө{2yӨzO! sӨ^ӨszӨӨ͘ӨC-BIOS 0.27 cbios.sf.net Localization: EU/INT Init ROM in slot: Cannot execute a BASIC ROM. ERROR:MEMORY NOT FOUND.CALLED NON EXISTING BASIC.STACK ERROR. No cartridge found. This version of C-BIOS can only start cartridges. Please restart your MSX (emulator) with a cartridge inserted.0123456789-=\[];'`,./abcdefghijklmnopqrstuvwxyz)!@#$%^&*(_+|{}:"~<>?ABCDEFGHIJKLMNOPQRSTUVWXYZ     ˩ٿڷ冦椢ح궸  *+/0123456789-,.p@{( !1ͫ > .z/{/ROMBAS>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:!I:ͫ BASIC statements are not implemented yet3!"}ͫ unknown@7D179}!!!~ͫ unknown@7E14openMSX-RELEASE_0_12_0/Contrib/cbios/cbios_main_msx2_br.rom000066400000000000000000001000001257557151200234030ustar00rootroot00000000000000 Ò#ä$G$Æ$!Ú".QW_qÅä5ÔÝæïø?r!4YÍ÷ )/ASCö fyËÝð-PbsÂÒãõ  2 C S e w É Ú [!$'6:>gy}Ó&,=2GQÍÓO #(##N#fi:G.:@G.˹xәyә!y80!x w_ۘqӘә>ә}ә|?әә>ә}ә|?@ә:0qQ xAO Ә :0_G xA<= :0qQ xA<= !:(:=:o:G.:!* qӘ x :G.:_:8*(%%:2*&2:8*(Q:W {Ә>ӘyӘ 0 zӘ!!!!:)0(:G.:G .ͣ ͣ:G.:G .">ͣ ͣ:G.:G .ͣͣͣͣͣ:G.:G .ͣ>ͣ>ͣͣͣ:G.:G .ͣͣͣͣͣ! F~#fo)GG.   &o)))0))[&ɇ*(_:>> 8 :(+0 * "!"!.[K:2**K @:O t :/Ot* "::(:,/2, :,/2, WXK O:GQ( /z(WWAә}әGۘQӘә>әۙә>әۙ......>22Z>!ͅ>!  ͅ.!.;..![$: !9KZvv:(8*"> ͅ>!w*$oͅ:*Å:G*$Å:G!):G!:G!:!%,(!*!$:g.&>->.%} |әyә>͛8K o&))) @:##![ͫ RIGHTC!mͫ LEFTC!~ͫ UPC!ͫ TUPC!ͫ DOWNC!ͫ TDOWNCK[SCALXYCS*))))).>2,y( G>2,yO "*MAPXY:,**FETCHC! ͫ STOREC!+ ͫ SETATR!= ͫ READC!N ͫ SETC!^ ͫ NSETCX!p ͫ GTASPC! ͫ PNTINI! ͫ SCANR! ͫ SCANL>#.ͷ >.~#/ 00/7/Ϳ Ϳ | } >ӫ>PӪ<<w /w %$|(ټ8( .gxGyOyO0xG0}(%NxӨy2!S͗ͯO!:# #!*G$>22͝!i%3>ә>әә@ә>vӘә!әۘv(֯ә>әx/Ϳ#>2>2>2>2͝!A!i%3^=>22)!&3C-BIOS Logo ROM!?6!!@̲ͥ!̲ͥ(g(#< G##x##W_x͕!ABx!%3G0Yxx(>.Yx0Y> Y> Y##4(OۙG$4(4(4(G _x0_|!_qx͕zx!@~(!%3#>!w} >!w>!wM>!w>!w'>!w>2>2!"""H!"J"t!N%!"!"!"! "!"!"!8"!"! "!"!"!8"!"!"!"!8"!Y"!u"]!"c!u"i>2\2b2h>'2> 2:2>2>2>2>2>2:2*" >!Y"2!ۨW?OӨ:/_2G:/ {P2G:/ {{/2zӨp#y@O0!y  # 2 O2!&C #&Dy7ȷy#v:0 ~Y#~!# :(:)PP:D!ͫ INIFNK!ͫ STRTMS*[> *[ v~#} !"ͤ:0͸:2a(:b 8*ʪbW*:$8"&w* !<> ͗: *:,0 5-"!"*:!0">2>2:!0<:!<2:= :=2:2:=2:!<2G< 2!`4(5(.y2(y2"22> > >>2:!<2:=2!:_w: bÅ*E:2G:b=2bs"&:W=O!bk+*E:G:b<2bs"&:=OT]#:O)8 (͐:(O͐ͤ! :> ͗7:::bW::bQ2_! **ͤ: !!~/w#!b>W 7    * 7LUj*E*KJlLM5YALBUCDHxyͶ8ͷ( > ͪ27ӑ>Ӑ/Ӑͻې>0/!w  w@8 `0@P7:>2? !3*"&}w4ʘ 0!22:?͸*"> 2bQ (*: &~(:bW*"> :27ɯ۪Ӫ۩۪Ӫ۩7!ͫ ISCNTC!ͫ BEEP"!)ͫ FNKSB!:ͫ ERAFNK!Lͫ DSPFNK::ͽʔÝ!rͫ 7TAPION!ͫ 7TAPIN!ͫ TAPIOF!ͫ 7TAPOON!ͫ 7TAPOUT!ͫ TAPOOFG۪( ( ˧(Ӫ><<<>Ӡ{ӡӠۢɷ>(<ӫۨӨۙO۪Ӫ۩ͧͬ:d >'/!_~=(>濳_>>/!O ~>0 >'<=G(L>濳_>x( >Ѡ(>ɯ!ͫ GTPAD!ͫ GTPDLNF#~##o&N~2Ͷ/2*[ :=2 ! q#>2E۪O !yӪ۩w# !:0!&!&J!'ݾj/ݦw8# #>2x((y >! > 5>y > $>!_( ~(`*w#} !["W:G$&N!6ͫ %NCALBAS["Sۙ!' Ә x !~Ә# x >ә>@ә!%~Ә#~ Ә  $_E $GۨẀ{z-%Wէ$|oG> $_Ez $GۨWYͅz-%W$zG(= !z$ۨÌz-%oG>U$W|gG>$_/OzG}$G>U$GzgۨoӨ:/O2}Ө!zo|gywۨӨW_oۨO?Ө{o|&))@0|/g:/_2oyӨzO}! wzGۨO?Ө{2yӨzO! sӨ^ӨszӨӨ͘ӨC-BIOS 0.27 cbios.sf.net Localization: BR Init ROM in slot: Cannot execute a BASIC ROM. ERROR:MEMORY NOT FOUND.CALLED NON EXISTING BASIC.STACK ERROR. No cartridge found. This version of C-BIOS can only start cartridges. Please restart your MSX (emulator) with a cartridge inserted.0123456789-=\[];'`,./abcdefghijklmnopqrstuvwxyz)!@#$%^&*(_+|{}:"~<>?ABCDEFGHIJKLMNOPQRSTUVWXYZ     ˩ٿڷ冦椢ح궸  *+/0123456789-,.p@{( !1ͫ > .z/{/ROMBAS>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:!I:ͫ BASIC statements are not implemented yet3!"}ͫ unknown@7D179}!!!~ͫ unknown@7E14openMSX-RELEASE_0_12_0/Contrib/cbios/cbios_main_msx2_jp.rom000066400000000000000000001000001257557151200234110ustar00rootroot00000000000000 Ò#ä$G$Æ$Ú".QW_qÅä5ÔÝæïø?r!4YÍ÷ )/ASCö fyËÝð-PbsÂÒãõ  2 C S e w É Ú [!$'6:>gy}Ó&,=2GQÍÓO #(##N#fi:G.:@G.˹xәyә!y80!x w_ۘqӘә>ә}ә|?әә>ә}ә|?@ә:0qQ xAO Ә :0_G xA<= :0qQ xA<= !:(:=:o:G.:!* qӘ x :G.:_:8*(%%:2*&2:8*(Q:W {Ә>ӘyӘ 0 zӘ!!!!:)0(:G.:G .ͣ ͣ:G.:G .">ͣ ͣ:G.:G .ͣͣͣͣͣ:G.:G .ͣ>ͣ>ͣͣͣ:G.:G .ͣͣͣͣͣ! F~#fo)GG.   &o)))0))[&ɇ*(_:>> 8 :(+0 * "!"!.[K:2**K @:O t :/Ot* "::(:,/2, :,/2, WXK O:GQ( /z(WWAә}әGۘQӘә>әۙә>әۙ......>22Z>!ͅ>!  ͅ.!.;..![$: !9KZvv:(8*"> ͅ>!w*$oͅ:*Å:G*$Å:G!):G!:G!:!%,(!*!$:g.&>->.%} |әyә>͛8K o&))) @:##![ͫ RIGHTC!mͫ LEFTC!~ͫ UPC!ͫ TUPC!ͫ DOWNC!ͫ TDOWNCK[SCALXYCS*))))).>2,y( G>2,yO "*MAPXY:,**FETCHC! ͫ STOREC!+ ͫ SETATR!= ͫ READC!N ͫ SETC!^ ͫ NSETCX!p ͫ GTASPC! ͫ PNTINI! ͫ SCANR! ͫ SCANL>#.ͷ >.~#/ 00/7/Ϳ Ϳ | } >ӫ>PӪ<<w /w %$|(ټ8( .gxGyOyO0xG0}(%NxӨy2!S͗ͯO!:# #!*G$>22͝!i%3>ә>әә@ә>vӘә!әۘv(֯ә>әx/Ϳ#>2>2>2>2͝!A!i%3^=>22)!&3C-BIOS Logo ROM!?6!!@̲ͥ!̲ͥ(g(#< G##x##W_x͕!ABx!%3G0Yxx(>.Yx0Y> Y> Y##4(OۙG$4(4(4(G _x0_|!_qx͕zx!@~(!%3#>!w} >!w>!wM>!w>!w'>!w>2>2!"""H!"J"t!N%!"!"!"! "!"!"!8"!"! "!"!"!8"!"!"!"!8"!Y"!u"]!"c!u"i>2\2b2h>'2> 2:2>2>2>2>2>2:2*" >!Y"2!ۨW?OӨ:/_2G:/ {P2G:/ {{/2zӨp#y@O0!y  # 2 O2!&C #&Dy7ȷy#v:0 ~Y#~!# :(:)PP:D!ͫ INIFNK!ͫ STRTMS*[> *[ v~#} !"ͤ:0͸:2a(:b 8*ʪbW*:$8"&w* !<> ͗: *:,0 5-"!"*:!0">2>2:!0<:!<2:= :=2:2:=2:!<2G< 2!`4(5(.y2(y2"22> > >>2:!<2:=2!:_w: bÅ*E:2G:b=2bs"&:W=O!bk+*E:G:b<2bs"&:=OT]#:O)8 (͐:(O͐ͤ! :> ͗7:::bW::bQ2_! **ͤ: !!~/w#!b>W 7    * 7LUj*E*KJlLM5YALBUCDHxyͶ8ͷ( > ͪ27ӑ>Ӑ/Ӑͻې>0/!w  w@8 `0@P7:>2? !3*"&}w4ʘ 0!22:?͸*"> 2bQ (*: &~(:bW*"> :27ɯ۪Ӫ۩۪Ӫ۩7!ͫ ISCNTC!ͫ BEEP"!)ͫ FNKSB!:ͫ ERAFNK!Lͫ DSPFNK::ͽʔÝ!rͫ 7TAPION!ͫ 7TAPIN!ͫ TAPIOF!ͫ 7TAPOON!ͫ 7TAPOUT!ͫ TAPOOFG۪( ( ˧(Ӫ><<<>Ӡ{ӡӠۢɷ>(<ӫۨӨۙO۪Ӫ۩ͧͬ:d >'/!_~=(>濳_>>/!O ~>0 >'<=G(L>濳_>x( >Ѡ(>ɯ!ͫ GTPAD!ͫ GTPDLNF#~##o&N~2Ͷ/2*[ :=2 ! q#>2E۪O !yӪ۩w# !:0!&!&J!(ݾj/ݦw8# #>2x((y >! > 5>y > $>!_( ~(`*w#} !["W:G$&N!6ͫ %NCALBAS["Sۙ!F( Ә x !~Ә# x >ә>@ә!%~Ә#~ Ә |D|D|D 0H8T|8T8T8T||DD|DD|@||P (D|$DH$UbD| |D|D|p@@|D|((HHB$$B|(H|T|TT0ll$$~$~$$>TT>>"T(%B8DD(2J|`` T88T|`` ~`` @8LTTd8088D8@|x8xDDD||@xx8@xDD8|D8D8DD88DD<8 @ ||  8D $_E $GۨẀ{z-%Wէ$|oG> $_Ez $GۨWYͅz-%W$zG(= !z$ۨÌz-%oG>U$W|gG>$_/OzG}$G>U$GzgۨoӨ:/O2}Ө!zo|gywۨӨW_oۨO?Ө{o|&))@0|/g:/_2oyӨzO}! wzGۨO?Ө{2yӨzO! sӨ^ӨszӨӨ͘ӨC-BIOS 0.27 cbios.sf.net Localization: JP Init ROM in slot: Cannot execute a BASIC ROM. ERROR:MEMORY NOT FOUND.CALLED NON EXISTING BASIC.STACK ERROR. No cartridge found. This version of C-BIOS can only start cartridges. Please restart your MSX (emulator) with a cartridge inserted.0123456789-^\@[;:],./_abcdefghijklmnopqrstuvwxyz0!"#$%&'()=~|`{+*}<>?_ABCDEFGHIJKLMNOPQRSTUVWXYZ          쑓᚟ꗘ̱Ͱڹʷ׾Ķû†  *+/0123456789-,.p@{( !1ͫ > .z/{/ROMBAS>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:>:!I:ͫ BASIC statements are not implemented yet3!"}ͫ unknown@7D179}!!!~ͫ unknown@7E14openMSX-RELEASE_0_12_0/Contrib/cbios/cbios_music.rom000066400000000000000000000400001257557151200221460ustar00rootroot00000000000000ABAPRLOPLL%AMAuAÝAAAB!0A#.HB>.~#/ 00/7/PBPB|`B}`BopenMSX-RELEASE_0_12_0/Contrib/cbios/cbios_sub.rom000066400000000000000000000400001257557151200216170ustar00rootroot00000000000000CD~|}!8*ME sygÊÅ× ×é ú é ó`åPòf%u n M aü|ÌÞðÒ%6HOa~O #(##N#fi>#.">.~#/ 00/7/**|:}:Wo|oG>͉_E͉GۨẀ{zīWէo|oG>͉_Ez͉GۨWYͅzīWozG(= !ۨÌzīoG>U W|gG>_/OzG}gG>U6GzgۨoӨ:/O2}Ө!zo|gywۨӨW_oۨO?Ө{o|&))@0|/g:/_2oyӨzO}! wzGۨO?Ө{2yӨzO! sӨ^ӨszӨӨ͘Ө:G:@G˹xәyә!y80!x w0ۘBӘә>ә}ә|?әә>ә}ә|?@ә:0B͈ xAO Ә :00~ xA<= :0B͈ xA<= !`s [  !:0˾22!xQ >ә>ә!###>ә>ә:(:=:o:G:!* BӘ x :G:͐:8*(%%:i *&i :8*(͈ :W {Ә>ӘyӘ %0 zӘ>2222:2>22*""*:)8!"$*"(*"&\ ͏ >22>22*""*"$*"&*"(\ :222P͛͞ >2*""BӘ< *"$*"(*"&22͛ͽ >2*""B Ә< *"$*"(*"&22Ͳ͛ :)0(:G:G  :G:G "> :G:G :G:G >>:G:G ! F~#fo)GG   &o)))%0))[&ɇ*(_:>> 8 :("0 * "!"u [K :2**K @:O ͫ :/Oͫ* ":4 :(:,/2, 4 :,/2, WXK O:G"( /z(((Aә}ә~ ͈ۘ Әә>әۙә>әۙ>22͞ >!V>!  V!*!\;![$*!\>2*""BӘ< *"$*"(*"&:G:G >> >!6#6͛ͽ >2:G: G !""!x"&!v"(22 ͗ >2:G:G !""!x"&!v"(22 ͗ >2: G:G !""!"&!"(22 ͗ >2:G:G !""!"&!"(22 ͗ : !} ɏ  :(8*"> V!"}!wɯ*$oV:*V:G*$V:G!):G!:G!:!l ,b (\ !*\ !$\ :g.&\ >-b >.b l }b |әyә> 8K o&))) @:P#! RIGHTC! LEFTC! UPC! TUPC! DOWNC! TDOWNCK[SCALXYCS*))))).>2,y( G>2,yO "*MAPXY:,**FETCHC!`STOREC!rSETATR!READC!SETC!NSETCX!GTASPC!PNTINI!SCANR!SCANL!DOGRPHu :2*"f ":*g"hCjCl!@:8:2n:y>ә>ә!@:8:ӛ #!~MAPXYC!TRIGHT!TRIGHT:WCfSh*B"j!"l2o:2n*:g"hSh>2o:2j*"f:py*0R#"l:WSh*0R#"jSf2o:2n:y!Zn CLRTXT*":G: :G:G:O: !*()o>GE '͈  !\~Ә#Ӛ'~  ۘӚ'O ~ ۘGۘO'zO ͈ BӚӘ{ӚӘ:> 8> ә>ә!bx7z7!:: $B!RKj[l~y*bN#F#Cj^#V#Sl( (NC2nB~y>ә>ә> G(x(ůCӛyO#NB*fKj[lq#p#s#r#( (~>yB>  G > COq#> 8!BLTVD!BLTDV!BLTMD!0BLTDM!ANEWPADͳͼ!ZKNJPRT> ӴyG۵ӵyӴ۵G> ӴyG۵ӵyӴxӵopenMSX-RELEASE_0_12_0/Contrib/codec/000077500000000000000000000000001257557151200171145ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/Contrib/codec/Win32/000077500000000000000000000000001257557151200200165ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/Contrib/codec/Win32/README000066400000000000000000000004521257557151200206770ustar00rootroot00000000000000The ZMBV codec was written by the DOSBox project, see http://dosbox.sf.net/ - the source code is in their CVS repository. This binary has been built by the author, for the openMSX project. Thanks guys! Copyright (C) 2002-2007 The DOSBox Team It is licensed under the GNU GPL version 2 or later. openMSX-RELEASE_0_12_0/Contrib/codec/Win32/zmbv.dll000066400000000000000000003300001257557151200214650ustar00rootroot00000000000000MZ@ !L!This program cannot be run in DOS mode. $r]u!]u!]u!z!Lu!z!u!z!Tu!]u!u!z!zu!z!\u!z!\u!z!\u!Rich]u!PELE! W0nFhdHT Pha@.text `.rdatav^`@@.datapp@.rsrcH@@.reloc` @BD$ =@T$wptXH $HD$P D$t P6!QL$/!T$D$L$RPd#=P$pT$D$L$RP#L$QL$R'D$PQL$ z#T$D$L$RP#L$h'L$QL$R%D$PQL$ R(T$D$L$RP%L$QL$R 'D$PQL$ &L$&T$D$L$RP$ $L$ƒu؃PL$ƒu؃PT$D$L$RPL$QL$Rt '3=@sT$D$ %$$,<$$U$$$1 YmG-  -̋D$} QSU3VWAA݉t$;Fƙ3+;t Ǚ3+;u @t@|;~ċt$;~+݃t$}_^][YËD$T$Dj8 hl@HǁLTjQ?̋D$T$D j8@HǁLThlQ@ QSUVW7HPD39\$L$~0L$;@} QL$ RV蠀 ;\$|_^][ỸD$ SVD@ʃL$ \$KUL$ W$H|$LPP$3? /BA@D;|3 oыʃʁف|ʃʃ D;|\$3I oыʃʁفʃ ʃ D;|\$33Ʌ~-WWD;|D$l$ۉ\$k_]^[4|}JV0W3;tPρ0;tP趁 ;tP蝁 $;tP脁$_^V3j8P0 $TPO ^̃SVDD$(3ۃ<w$džPdž<džP džP@ PHWP(蘁(P膁(Q t$D|$< ;ӉT$ t@|$4U;ӉT$D$tl$3ɉ,  Q90l9 `9$T;L;\$D$D$ I3҅3\$ \$($H\$0\ (\$$to;u 0\(0l$4l|$t\$l$9\$tl$80L$4l ;|\$(l$D$L$8L$;ʼnD$H(RjP( QjR~($PjQ~ L$T$]_L^[ ]_^2[ ^2[ I ̋D$SU;LtjjP!u][ \$ T$Vt$W3\$}U]u 48EE XXLH@@E9<t$;t@h@WR} 39<~rA $d$4$L$ H 244$ 4H4$ L$ 4˃;<|TU4_^][<;AT$;5@L$r:;9ustY29+u<+ӅtA29+u(+Ӆt-29+u+Ӆt2+tt$  t$ 339<~}L$A$P2Q4$4Q24$4P24$4;<|<L$T$PQR]x _^][̃H4$ЉT$D,SDP3ۃ;Ӊ4\$$0UVW\$43ۋ0l$4E<D$H‹U;Ӊl$,\$D\$(\$|$ D$8T$$~T+ljD$@T$u;~0Ǎ|$@<+߁)\$u܋|$ H3ۃl$|$ u9D$D$D$ @\$@qt$ $It$|$ c|$XF>H\$HD$Å҉D$LD$~]t$Pt$8z+\$< \$<~5D$S0(+)l$ul$,D$LT$PT$u|$T$$l$ 3ҋ؉\$~aut$t$8+T$<I|$~3T$ÍI0(++ul$,T$$\$Hl$<\$u;|$}D$p|$t$DD$(D$@D$ ;D$@T$D\$(|$Tt$0ۃ|$w\w wHD$(u‹փ}T$DD$@~g]3~@+l$Pl$P.24$+4l$,];|ЋT$DHƋt$@;uT$Dt$@|D$0D$4 ;,D$0_^][H̃H4$ЉT$,WDP3;׉4|$]SUV|$430l$4U];ߍPT$LVl$<|$0|$H|$D$$T$8\$(~cH+Љt$ T$D\$U;~5$t$D48+)|$u܋t$ D$$3l$D$$u9T$T$D$$@|$DQT$|$$u|$jH|$LzۍxD$ D$PD$Ut$Tt$8{+T$@T$@~5D$0(+)l$ uD$Pl$+ $I8$6$6F.F"FFL$D$WxD$tD$T$hUjQL$$Q FC3D$ 9n~#IT$L$ Q j)|$ ;n|D$D$L$A_tS]^3[ C]^3[ ]^[ 555)575UKV1;0ust]1(+uEtF1(+u.t/1(+ut1+t3^]t2ÅtufuK;OuS;WuܰS\$W|$1_[Vt$WNU9;8ust]9(+uEtF9(+u.t/9(+ut9+t3]t _^D$u _(^ H IH_f@ (f@H@@ @$3^VWt8t$ t0FPFRPNtNQ_3^_^_^̸Vt P&`3^jh dPVWp3PD$ dt P_|$L$ juL$ d Y_^ h`D$ D$t 3D$uL$ d Y_^ OWQRHL$ d Y_^ jh dPSVWp3PD$dt P^|$$\$ iuL$d Y_^[ h_D$$D$t '3D$uL$d Y_^[ KSQRL$d Y_^[ VpWx>xpP :p_^VFNWx;vt5@N WPQ{ZF~ x~)~F)xv ~uVV_^VW|$ wL$ ?u F=u |uGG_^Ã*V(SU>T$N(9FjjjOG0NF^FV^FN^Fn V*^FN^FV^FN^FV^ Nu} |3V^FN ^FqP$H,҃ɃыHɃыHɃу8NЈ)^FVRN^NIFV ^VRFN^NIFV ^ Nu} |3V^NFI V ^FxNt!@V^NIFV ^NVz,tFQO0PQM G0F FEN0 9};|}3҃3 ȃ~lt B+ˋ+ȋFq~ltO2rO0ijjjN- G0~EFx@9F NslIF;F u6Vz,t;v+PFO0PQL G0,F;F t)VR^ n(FF FP9V rFx,tF;vV+PG0RPwL G0NV ;QuF FI~IFxVF;F u6Ny,t;vO0+PFPQL G0eF;F t!N ^[ ͉N Nnu݋Fx,tF;vN+PʋW0QRK G0u ^ F[~[Fx$VF;F u6Ny,t;vO0+PFPQaK G0F;F t"N ^[$ N Nnu݋Fx,tF;vN+PʋW0QRK G0uFg~gu[Fx,tKN;N v2FP;V w5W0NnW1FNjjnjJ G0Fq~tu5F(][_3^Ã\$u!;\$tG][G_^Ë\$F=ut G][O_^Ãu~tu=@SVЃtuF:1;uW;u VC7jjjVlBu#NLVDfDJFLVDLQjRV F][_^ÃW0NFnW1FNnW2FNnW3FNnWFNnW FNnW FNnW FNnO2O0F~؉F]39F[_^Ã][_F(3^áGG_^Vt$FWx*t)Et$It[tgtqtt_^Ë@t N$PF(PуVBDt N$PF(PуVB@t N$PF(PуVB8t N$PF(PуVF(N$RPу3q_F^ø^̋FW;vu3_+ljFF@uV0WQR'uN0WPQKGF0 D$WRP7Q>~ _̋F,NLVDWF<3f|JFLVDLQWRhT@ ~l~\~t~h~HV|FxF`_̃O|WlSUoxVL$O8t$w,;v+ցT$D$;T)T$)T$rl$Wt9T$vT$W8\$8*\$8\*:Z:YY:u_Y:uRY:uEY:u8Y:u+Y:uY:uY:u;r+ց;Ս~;T$Gp},\  \$T$W4#ЋG@P;D$v l$ Gt;w^][̋N8FlUl$):WQ:PP:u_P:uRP:uEP:u8P:u+P:uP:uP:u;r+|Nt;npv _]ø_]S_,UVo<+otGlO, +;r_G8S QPNWLGD)_p)_l )_\ PA;r+3fuO@Ӎ YA;r+3fu7~tSWtWlW8RGtWtr GlO84OXGHN3#GTGHs z-^][VW|$ G ;sGtwGtGlO\WlGt1t;r9+ЅɉWtGl|W83j+PRWG>GlG\GytGO\WlG,+-;r|G83jRPW>OlO\zO_3^Ët$tO\|G833҃RWl+RPW=GlG\39Au_^Ã_^DSUVW|$3Gt=s#Gt=t$s~rIGHOXWlw4O8L3#GTODGHfA#W@frOl#O4W@,JGHODfWlfAtBGlO,+Ł;w0ttu;uUUG``8fWlf+WpG`ʋf V,2%ffs # $f 3+9G`)Gt;OtwjreG`_lWlO8D oHOXOD3#GTo4#W@GHfAfjOl#O4W@,JGHODfWlfAG`uGlGlO8OXG`GHR3#GTGH^GlO8fQ f3+9Gt_lO\|G83Wlj+RPW:GlG\y\_^]3[ËO\|G833҃RWl+RPW:GlG\39Au_^][Ã_^][DQSUVW|$D$Gt=s#Gt=t$s[rMGHOXWlw4O8L3#GTODGHA#W@frOl#O4W@JOHWDD$GlfJWpO`WdT$һOx_`tq;sgGlO,+;wU;ttu;uR G`G`w9tuWl+Wpv_`Gxp9G`gfWlf+WdGlOttGxf+ʋf S,%ffs # $f Gx+39+OtGxolWl;wOGHOXo4O8L3#GTODGHA#W@fjOl#O4W@JOHWDD$GlfJGxuolۋGlGhG`W\|O83j+PQW7OlO\z_^]3[YÃhGlO8DfQ f+9u/O\|G83Wlj+RPWF7GlG\FolGtyTolGtohhtJWlG8DfJfGhO\|G833҃RWl+RPW6GlG\39Au_^][YÃ_^][DYSVW|$3;tyw;tr9_ tm9_$th___G,FFF;É^}؉FFɃSqSNSu: G0V^(/p_^3[_^[̋D$3;U81|$$8W|$ ;u_]9O Ou G `O(9O$uG$|$uD$S\$;}3 ~D$ }|$rKf|$ [|$$Pu W(G VhjRЋ wn˽^0\$$MN4KNP>FLFTFn,VXW(G jURЋN,F8W(G jQRЋNLF@W(G jQRЉFDKjO(W PQҋ0~8FV tN~@tH~DtBt>PHL$T$(WF$^[_]FGWG^[_][_]ø]̋D$L$ T$PD$QjjjjRP ̋T$3;tMB;tFHJJJB0HH H H(H,H0H8H<0@HlHPHL3ø̋D$ W3;81|$8Vt$ ;9~ ~u F `~(9~$uF$F(N h0%jPу ;u^_ËL$;ωF}x0P}QwVH$x4^_ËN$PF(Pу~^_ø_̋D$ L$T$PQjR@Lh@T @Ph@XUVs39n4Wu)N$S K(jPQ҃ ;ʼnF4u _^]9n(uN$n0n,F(+{F(;r"K V4P+QRV@F( _n0F,^3]+F0;vK V4V0U+QR&@ +t"C N4W+PQ@V( ~0_V,^3]n0N0F(;uF0N,;s͉N,_^3]̋D$,WLxAx 78u x(? u H PS_8U(L$$HVw 3ۉG@\$3G@D$;v‰T$L$;vD$b L$T$(QUR6D$ )D$)D$(D$4T$ )G@ s$$ E؃T$r܋˃ÉO`˃`GdO\\$GhOh;O\sUs$ E؃T$r܋GhE˃fLGpGhOh;O\\$rhs+3 $WhUfDWpOhhr獇0OlGLRGTPQjGpPjz0T$(D$0t L$@As D$@@c GhGdG`9Gh8OTOL#Ë;ΉD$vD~ EOTظOLT$#Ë;ΉD$wsW;s*$ E؋D$;T$r̋Gh+fL$fLGpGh\$_fL$fuk̍A;L$$s*EL$$؍A;T$r֋Gh+\$oLGnÃL$$D$f̉L$$uIA;s*OEL$$؍A;T$rÃD$IA;s,EL$$؍A;T$rà D$+D$D$$OhȋGdG`\$;|$tD$$Ohl$fDOpGh|$uGdG`9Gh? 0OlRGLGTPQO`QWpRj -D$0t4D$@T$@L$@AD$@@WlOlWPRGXPQO`GdPTOpRjR-T$(D$0tD$@@Ke|$r[D$@T$L$(PT$,H L$R(HP_8w<.(D$HPH (@_8w;s$E;wHT$r܋OH#G@+OX#ËȋGP;ΉD$vK$$EOXظOPT$#Ë;ΉD$wL$4ɉL$$L$$D$L$$#L$ȋGPL$4D$;]EL$ȉD$$L$$T$#L$OPD$D$$;wL$D$++@L$$\$tD$@@`ODGHGHtB;s$E;wHT$r܋OH#GD+\$O,+L$L$,9ODvD$@@@|$iL$,+L$GD;v<+O0;D$v+O4O(D$+ O4+O0D$L$$O@;L$4vL$(+ȋG@L$$D$4D$L$;vD$+ȉL$L$4+ȋD$(O@L$$ D$$l$u@D$(eZ|$D$(O@l$D$(/ s*tE؃ T$\$r؋D$,+D$L$@AGD$,t/L$(WP+ȃQRt"L$LT$G A0D$D$,u%3Ɋl$;GtD$@@(d3ۉ\$3 s-IE؃ T$\$r;_tSL$@Aw^][_,ËD$@L$(H L$(HPw<^]_8[_,33D$0D$0D$@L$(H L$H(P(_8w fT$tvt$ I6t$tR\ Ml$;L$$l$,.|;tt +l$,fl$t$u|$t$ ҉t$ u_^][ ̃ SUl$VWx3҅D$u fD\$D$Nj|$?;};tn;} f| 0t;D$tf| f   f f 3҅D$u ;u  D$)\$p_^][ ̃SUjV3WD$l$u T$\$ \$T$;T$$\$t$};;I~ +;~[| Hf PhHPh*fL:T$$ft$f| ff +t$f;T$~ +;ˉ|$~[| Hf x9hHxh*fL$L ft$f| ff |$+t$ +;ˉ|$~Z Hf x9hHxh*fL$L ft$f ff |$σ~SHf x9hxHh*f򉘼f f   +;ˉ|$~Z Hf x9hHxh*fL$L ft$f ff |$σ ~SHf x9hxHh*f󉘼ff  +;ˉ|$~Z Hf x9hHxh*fL$L ft$f ff |$σ ~PHf x9hxHh*fff L$3ɉT$u ;u  D$)l$ G_^][Q S\$UVW~^t$Hf PhHPh\$ *ffT$f  ~_t$H\$f PhHPh\$*ffT$f  ~[sH\$f PhHPh\$*f􉐼fSf 3I D~\~ Hf PhHPh\$ *f󉐼ff~ ff ;mL$L$_^] [̃ S\$U39VW9$,J4L$|+;~\4H\$f P@HP@\$*fL:ffff r%+;ˉ|$|$ T$~cHf P@HP@*fL$T T$ffff |$ω<\$ tz+4&+;~QHf P@HP@\$ *fL:f f ωs # $L$$T+;ΉT$~cT$$4Hf P@HP@\$ *fL$T ft$$f4ff ʉ4X~+,&+;~UH\$f P@HP@\$*fL2f f ΉL$;+;~sH\$ f P@hH\$ )@*fL:_f^][ fff _^][ 3f9u  | Ifyf9u5fyu?fyuIfy uSfyu] |ȋ3Ƀ J,Ë3Ƀ J,Ë3Ƀ J,Ë3Ƀ J,Ë3Ƀ J,Ã3Ƀ J,3V ƃ^̋Su9PH@HP@3ɉf[Ã|)HPf@f[̋SV~=PHPpHp3^f[3;~pP2@^f[SVWًt|$ǀt:HPxHP<xPUhш *xhPш,*x]tHP+߉\$\$xu_^[̋T$ | 0 3 ǂ (p$ ǂ, PAl$TQAuӋl$TtQvAl$T$;;t$,T$H+HNPHL$TL$Tw̅tPv@냋L$PT$l$A@3D$P@` t T$ D$P@xL$T$++ϸL$P+Ճ#؍EFA D$,+AD$Qx<_^]X8[<́p3ĉ$$$S$U$V$D$3l$P\$8T$HD$TD$XD$\D$`D$dD$hD$lD$pvLEfDLTLLT;rD$f|LTus;L$vL$uAfL$ D$ @D$ D$  ^]3[$3Čþ$f|tTu+Dž~;T$ rt$HD$($D$Du|$(֋t$< D$DL$$+T$4fLM|$<*ÅD$@D$fD$tVt$$tL$@#;L$4tD$73ۉD$D$ЋˋL$ Jtut H#ȋuT$(L$LT$3$_^][3ČUWVu M};v;r=܉tWV;^_u^_]X ur*$Ǻr $ȕ$Ė$Hؕ(#ъFGFGr$I#ъFGr$#ъr$IxphDDDDDDDDDDDDDD$Ė̖ؖE^_ÐE^_ÐFGE^_ÍIFGFGE^_Ðt1|9u$r $P$IǺr +$T$PdF#шGr$PIF#шGFGr$PF#шGFGFGV$PI $,4GDDDDDDDDD D DDDD$P`hxE^_ÐFGE^_ÍIFGFGE^_ÐFGFGFGE^_ ̋T$ L$ti3D$ur=܉tQ Wr1كt +шuʃtt uD$_ËD$Vjhpp H^H, VH D$tV(Vju P$ދF~ E FEFEP$E tM4Ej<@ D=8{(Ysu (`' r&} }n%| ##|jYu }"3;u59=} }9=pu"9}u{"&(juYhj;YYV5q5$7YЅtWVYYNVEYmuWEY3@_^[ j hd)]3@Eu 9}e;tu.HtWVSЉE}WVSEWVSrEu$u WPSrWjSHtWjSЅtu&WVS~u!E}tHtWVSЉEEEE PQ(YYËeE3(Ã|$u*t$L$T$ Y j h d(eu;5w"jv,YeV4YEE E(js+YUl$S,VW395u8jJ7hYYu;t3@Pu US;Yu;u3GWV5Ӌu&9j _t UYu8888_^[]UxY8 3]j h@dz'utu=uCjc+YeV+YEt VP+YYE }u7u jQ*YVj5$uS84P 8Y>'U}uu }MfofoNfoV fo^0ffOfW f_0fof@fonPfov`fo~pfg@foPfw`fpIuu}]U}u]] ÙȋE3+ʃ3+ʙ3+3+ uJu΃M;t+VSP' EMtw]U +щU+ى]u}MES;u5كMu }MMMU UE+EPRQL Eu }MʃE]u}]Ã%؉)7؉3U}}M f$ffGfG fG0fG@fGPfG`fGpIuЋ}]U}E3+3+ucsmuB~uWt?s9t+>MOCt#u$u uuuu V} u uEPEPVu WE;Es[S;7|G;wBG OHtyu*X@u"u$u u juuuuuEE;Er[_^U,M S]C=VWEIIM|;| ucsm9>~ )F;t=!t ="~J8u*jVEa-YYu 9>u&~u F;t=!t="u ~uV u3Yu\39~GLhpu F;7|junYYEPME`IheEPEXIucsm9>~~F;t=!t ="e} EPEPuu WE;EE9;G|GEG E~lF@ XE~#vPuE uM9EME}(u$]u EuuuuVu @uE]}}t jV7YY}%=!VY}$MVuu u$kujVuu v]{ v&}u$u uSuuu V t_^[Vt$XI^USVWO EMcsm"u ;t&t#;r @ Aft#x}u}jPuu jx u#ց!rXxtR99u2yr,9Yv'QRtu$Vu uPuuu Q҃ u uu$Puuu Q 3@_^[]V5q5Xօt!qtP5qЅthITthpIPPt t$ЉD$D$^jYV5q5Xօt!qtP5qЅthITthIPPt t$ЉD$D$^\5qXu5 yYP5q`áqtP5(TYЃ qqtPd qj hehITEuF\@q3G~t$hpIPPӉhIuӉ~pƆCƆKCvFhPhj lYeE FluvFlvl,YEj [YVW45q5qXЋuNhjYYt:V5q5$*YЅtjVYYN V7Y3Wl_^VwujY^jheuF$tPYF,tPYF4tPYFYj]YV tЃ;t$r^Vt$3utу;t$ r^ËL$V3;u/VVVVV83jX^á4;tډ3^ËD$V3;uVVVVV3jX^954tۋ @3^Ã=ЉthЉ3Yt t$ЉY2h0h6YYuTVWhxƿ;YstЃ;r=ԉ_^thԉ3Yt jjjԉ3j hHf jYe3F95ttk5pEl} uG5ȉ5ĉYYEtm9}r Eth84FYhD<6YE}u(5tjqYu3F}tjXYn jjt$ % jjj VAVV4VE0Vz4Vj4VZ2VK Vh($q^VW3t$ fYu'9xvVx;xvuɋ_^VW3jt$t$4 u'9xvVx;xvu_^VW3t$t$5YYu-9D$t'9xvVx;xvu_^jThhf 3}EPEj(j ^V@YY;5)@@ x@$@% @& ( ;rf9}E;8X;E;|3FRj(j YYtM  &@@ ``$@% @& (;rF9=|=e~mEtVtQtKu Qt%uN@ uNhF P1YYt7F N@Cg5|33@ËeEVW>t1t G P(;r6&Y|_^S39̉VWuY*5}3;u<=tGVYt:ujGW;YY=Ttˋ5}U@V{E>=Yt/jUn;YYtJVUP t SSSSS*,8u5},}3Y]_^[5TTUQMS39EVU t ]EE>"u39E"FE<tBU PF/4Yt} t M E FۋU Mt2}u t utBe>< t< uFN>}t EE3C3FA>\t>"u&u}t F8"u 339EEtIt\BuU tU}u< tK< tGt=Pt#J3Yt M E FM E  '3YtFU FVtBU ME^[t U S39̉VWu'hVS;É5dt8EuuUEPSS}E =?sJMsB;r6P;Yt)UEPWV}E HH5L3_^[QQSUVW=33;j]u-׋;t "4xu ţ;u׋;u3f9tf9uf9u=SSS+S@PVSSD$4׋;t2U;YD$t#SSUPt$$VSSׅut$Y\$\$VX;t;u;p8t @8u@8u+@Un;Yu VDUVW V_^][YYVWDcDc;NjstЃ;r_^VWLcLc;NjstЃ;r_^UQQVE3PuuYt VVVVV'EPYt VVVVV'}^u }r3@jX39D$jhPu3}u$hNYu5%3@U3=uTS$W39-~1V5hUv6U5ӃG;=|^5U5_[5-]UQQVNuu DMV\qW}S99tk ;rk ;s99u3t Xۉ]uu Du `3@N`MM N`H q=q;}$k ~\d9=qqB߃ ;|]=~du Fd^=u FdN=u Fd>=u Fd.=u Fd=u Fd=uFdvdjY~d`QӋEYF`[_^øcsm9D$u t$P|YY3hd5D$l$l$+SVWp1E3PeuEEEEdËMd Y__^[]Q̃S\$ UVs35pWD$D${t N3 8 N F3 8D$(@fk L$0T$D$L$ St^DmLɍ\D$t.D$|DLD$ù|$t$t N3 8N F3 8sD$_^][D$ƋL$(9csmu*=PIt!hPI%tT$(jRPIL$,-D$,9h thpWՋ-D$,L$H t N3 8N V3 :KL-{ PhpW˺c-UpeeSWN@;ǻt t Уp`VEPu3u333EPE3E3;uO@ u 5p։5p^_[VW3t~t WW&Yr|ܾq_t ~uPӃr|^[UE4q]j hf3G}39ur j h7YYu4q9tnjY;u 3Qj YY]9u,hW%YYuWYx ] >WYE EOj *YUEV4q>uP$Yuj7Y6^]h@j5,uËL$%%3 @Ë kT$+P r ;r3UMAVu W+y iDMIMS1UVUU] utJ?vj?ZK;KuB sL!\D u#M!JL! uM!Y] S[MMZU ZRSMJ?vj?Z]]+u]j?u K^;vMJ;։Mv;t^M q;qu; s!tDLu!M!1K!LuM!qM qINM qINu ]}u;M ыYN^qNqN;Nu`LM Ls%}uʻM DD )}uJM YJꍄ ED0E 5h@H SQ֋  P@ @HCHyCu `xueSjp ֡pj5$ k+ȍLQHQP-E ;vmE=[_^áV5W3;u4kP5W5;u3x5k5hAj5,;ljFtjh hW;ljF uvW5$뛃N>~F_^UQQMASVqW3C}i0Dj?EZ@@Jujhy hWup;UwC+ GAH@PǀIuˋUEO HAJ HAdD3GFCENCu x!P_^[U MASVuW} +Q iDMOI;|9M]UE;;MI?Mvj?YM_;_uC sML!\D u&M!ML! uM!YO_YOyM+M}}M O?L1vj?_]][Y]YKYKY;YuWLM Ls}uϻM DD }uOM YO U MD2LU FBD2<38/] )uNK\3uN?] Kvj?^EuN?vj?^O;OuB st!\Du#M!NL! uM!Y] OwqwOquuuN?vj?^M yK{YKYK;KuWLM Ls}uοM 9DD }uNM yN ED3@_^[UMkMSI VW} M 3U S;#U# u ;؉]r;uS;#U# u ;ى]r;u[ {u ];r;u1 {u ;ى]r;u؅ۉ]u3 S@YKC8tCUt|D#M# u)eHD9#U# uEUiDMLD3#u#Mj _G}MT +MN?M~j?^;J;Ju\ }&M|8Ӊ]#\D\Du3M]! ,OM|8!]u ]M!K]}JzyJzyM yJzQJQJ;Ju^LM L}#} u ;οM |D)} u N {MN 7Mt LMuэN L2uɍy>u;uM; u%MB_^[QS\$ VW33;rtG|wUj"Y1j"Yu =}?hOSUs t VVVVVhVju&hpOhV1 t3PPPPPkV@Yu0utVxY^3j hgOt|Fpt"~ltpluj Ybj YeFl=viEEj Yu-t"t t Ht3øøøøSUVWU3^WS~~~ 3~v +Ɗ  CMuANu_^][U$dp3ʼnSWEPv3@;rEƅ t+];w+@P j RN CCujv EvPWPjj3SvWPWPWv SDSvWPWPhv S$3LEtLtL Ƅ@;rME3)EUЍZ w Lр wL р A;rŋ_3[聲Ŝj h(gt|Gptltwhuj Yj Yewhu;5@zt6tVpuvtVY@zGh5@zuVhE뎋uj ]YËD$VF ucqFHlHhN; vt t|HpuF;@ztF t|HpuFF@puHpF  @F^US3SMl4u48]tEMapEDzD;FG;v}FF>uыuE}urlj{C/jC CLzZf1Af0A@@JuL@;vFF~4C@IuCC Ss3{954M_^3[0jhHgM}_huE;CWh Y؅Fwh#SuYYEuvhpuFh=vtPvY^hS=hFpt|j YeCDCHC L3E}fLCf E8@3E=} L8x@3E=}@y@5@zpu@z=vtP轵Y@zSE0j LY%u vtS臵YKeE8Ã=̉ujVỶ3U$X(p3ʼn8{Vtj YmtjoY8{]|ux}tfff]pfElfehfmdE@jPEjPtE EЍEjE@uEHEPDjUSVWUjjhu]_^[]ËL$At2D$H3BUhP(RP$R]D$T$SVWD$UPjhd5p3PD$dD$(Xp t:|$,t;t$,v-4v L$ H |uhDID_뷋L$d _^[3d yuQ R 9QuSQ<{ SQ<{L$ KCk UQPXY]Y[ËD$PU$X(p3ʼnV]|ux}tfff]pfElfehfmdE@jPEjPnEEЍE؃ E uELjHEPDu uj(Yh @P<3^MŨU5PYt]jY]VW3L{6J(Yr_^ËL$f9MZt3ËA<8PEu3fx ̋D$H<ASVq3҅WDv|$H ;r X;r (;r3_^[jhhgeRsYt=E+PRYYt+@$ЃE E3=ËeE3ËD$TX\`ËD$ qV9Ptk t$ ;rk L$^;s9Pt35\rYj hg3}}؋] LtjY+t"+t+td+uD}؅uaTT`w\`ZÃt<t+Ht3PPPPP뮾\\XX ``EPEY3}9Euj"9EtP?Y3Et tuO`MԉG`u@OdMGdu. qM܋ qq9M}Mk W\DEEuwdSUY]}؃}tjYSUYt tuEԉG`uEЉGd3ËD$hËD$tËD$xt$3@jhg>3}5xvY;uSEP#Y;t WWWWWp}t!hTPT;th,PPP;uVYx}u u։E/EE3=Ëe}ujleEEj hgyM3;v.jX3;E @u WWWWW3M u;u3F3ۉ]wi=uKu E;w7jY}uEYEE_];tuWS7 ;uaVj5,;uL9=t3V菲YrE;P E3u jY;u E;t jhg[]uu Yu u S賬Y=3}jY}SYE;;5wIVSPY t]5V&YE;t'CH;rPSu譟S0ESPQ9}uH;u3Fu u VW5,E;t CH;rPSuYSuE.}u1uFu VSj5u ]jDYË}9=t,VY79}ul4PY_9}th quFVSj5uV9t4VzYtvVjY 3|u4P\YUuME MUTu}tMA#E3t3@}tMapjjt$ jSVWT$D$L$URPQQhd5p3ĉD$d%D$0XL$,3p t;T$4t;v.4v\ H {uhCCd_^[ËL$At3D$H3ڡUhp pp> ]D$T$UL$)qqq( ]UVWS33333[_^]Ëj33333USVWjjhQ_^[]Ul$RQt$ ]U SVWƼ39|E]]]hP;u3Y5PhPW;tP$PW|P$PWPEP YYt SSSSSV}u,h|PWP躻;YthdPWP袻Yu;tm95tePY;t%MQj MQjP5ϻYЅtEu3EPYt SSSSS}r M :M1;t(P耻Y;ÉEt;tuPdYЉEuu uu5|GY_^[ËD$S3;VWt|$;w!j^0SSSSS+=t$;uً8tBOu;t BF:tOu;uj"Y3_^[USVu39]Wu;u9] u3_^[];t} ;wj^0SSSSS9]uʋU;uу}u @B:tOu @B:tOtMu9]u;u}uE jP\Xx j"Y낋L$V3;|~ u}^á} }^VVVVV^Vt$vvv vڥvҥvʥ6åv 軥v$賥v(諥v,裥v0蛥v4蓥v若v8胥v<{@v@pvDhvH`vLXvPPvTHvX@v\8v`0vd(vh vlvpvtvxv|@ߤԤɤ辤賤訤蝤蒤臤|,^Vt$t5;@|tP^YF;D|tPLYv;5H|tV:Y^Vt$t~F ;L|tPYF;P|tP YF;T|tPYF;X|tPYF;\|tPգYF ;`|tPãYv$;5d|tV豣Y^ËD$t8uP蕣YUV3PPPPPPPPU I t $uI t $s ^UQQp3ʼnĖSV3;Wu:EP3FVh$aVt5̇44xu jẊ̇;9]]u@E539] SSuu Pu֋;~<w4D?=w;tP裡;Yt ؅ti?PjS, WSuu juօtuPSuESkEYu39]u@E9]u@Eu!Yu3G;EtSSMQu PuG;t܉u uuu uu;tV衡YǍe_^[M3趙UuMu$Mu uuuuu }tMapUV3PPPPPPPPU I t $u t $sF ^Up3ʼnESV39ЇWu8SS3GWh$ahS t=Ї4xu Ї9]~"MEI8t@;uE+H;E}@EЇ;9] ]u@E 539]$SSuuPu ֋;~Cj3Xr7D?=w;tP;;Yt E]9]=Wuuuju օ5 SSWuu u֋;ˉMfE t)9];MuuWuu u;~Ej3Xr9D =wZ;tjPz;Yt 3;tAuVWuu u t"9]SSuSSuuuVSu EV7Yu.EYY9]]]u@E9] u@E uYEu3!;E SSMQuPu ;ÉEtԋ5SSuPu u;ÉEu3~=w8=wD;tPd;Yt 3;tuSW uWuuu u;ÉEu3%uEuPWu uHu#uW Yuuuuu u9]t u萝YE;t 9EtP}Yƍe_^[M3蒕UuMu(Mu$u uuuuu - }tMapjYVD$ u(L$D$ 3؋D$d$ȋd$Gȋ\$T$ D$ ud$ȋD$r;T$ wr;D$v N+D$T$3+D$T$ ؃ʋӋًȋ^̋D$L$ ȋL$ u D$S؋D$d$؋D$[j jt$ E Uu MwEMA%}tMapQL$+ȃ YQL$+ȃ YU p3ʼnEjEPhuEu EPNYM3豓U4p3ʼnEEME؋ESEЋVE܋EW3;E M̉}}_5MQPօt^}uXEPu օtK}uEu܃Eu uYF;~[wSD6=w/;t8-WWuujuӋ;u3P;Yt E}9}t؍6PWup VuuujuӅt];tWWuSVuWu t`][9}ԋuWWWWVuWu Ӌ;tt }w u,9uv'!E"tME$ƉEEt8Et]}tE`pEEt0}tE`p3[_^U39Puu uuhvP]QL$+#ȋ%;r Y$-UWVSM tMu} AZ I& t' t#:r:w:r:w:u u3:t rً[^_̍B[Í$d$3D$ST$t :tτtQu WV ؋ ~333ƃu%t%uu^_[3ËB:t6t:t't:tt:tt^_B[ÍB^_[ÍB^_[ÍB^_[%̋EPYËT$BJ3VXc̋EPYËT$BJ3&c鴍T$B J3 d降}H}ukiin nm jj.j@jLj\jhjzjjjjjjjk$k8kFkRk`kjkkkkkkkk l l:lRllllllllllm m8mPm`mnmzmmmmmmmmiiixi£W^gEFaa1.2.3http://dosbox.sf.netmailto:db.crew@gmail.comZMBV deflate 1.2.3 Copyright 1995-2005 Jean-loup Gailly GII I0L 0L0L 0L 0L 0L1.2.3`Psp0  ` @ X ;x8 h( H T+t4  d$ D \ S|< l,  L R#r2  b" B Z Cz: j*  J V@3v6 f& F  ^ c~> n. N `Qq1  a! A Y ;y9 i)  I U+u5  e% E ] S}= m-  M S#s3  c# C [ C{; k+  K W@3w7 g' G  _ c? o/ O `Psp0  ` @ X ;x8 h( H T+t4  d$ D \ S|< l,  L R#r2  b" B Z Cz: j*  J V@3v6 f& F  ^ c~> n. N `Qq1  a! A Y ;y9 i)  I U+u5  e% E ] S}= m-  M S#s3  c# C [ C{; k+  K W@3w7 g' G  _ c? o/ O A@!  @a`10  @     incorrect length checkincorrect data checkinvalid distance too far backinvalid distance codeinvalid literal/length codeinvalid distances setinvalid bit length repeatinvalid literal/lengths settoo many length or distance symbolsinvalid code lengths setinvalid stored block lengthsinvalid block typeheader crc mismatchunknown header flags setincorrect header checkinvalid window sizeunknown compression method       L,l\<|B"bR2r J*jZ:zF&fV6vN.n^>~A!aQ1q I)iY9yE%eU5u M-m]=}   S S  3 3  s s    K K  + +  k k     [ [  ; ;  { {     G G  ' '  g g     W W  7 7  w w     O O  / /  o o     _ _  ? ?     @ `P0pH(hX8xD$dT4tC#c         (08@P`p  0@`  0@`0w,aQ mjp5c飕d2yҗ+L |~-d jHqA}mQDžӃVlkdzbeO\lcc=  n;^iLA`rqgjm Zjz  ' }Dңhi]Wbgeq6lknv+ӉZzJgo߹ホCՎ`~ѡ8ROggW?K6H+ L J6`zA`Ugn1yiFafo%6hRw G "/&U;( Z+j\1е,[d&c윣ju m ?6grWJz+{8 Ғ |! ӆBhn[&wowGZpj;f\ eibkaElx TN³9a&g`MGiIwn>JjѮZf @;7SŞϲG0򽽊º0S$6к)WTg#.zfJah]+o*7 Z-A1b62S-+ldEw}ZVǖAOIъ OM~-QJ#SpxAaU׮.7׵Y-6]]wll?AԞZ͢$ Faw$eڪ]]FD(koipvk19Z* ,  m86F߲]qTp0ek*1u4yީ%8S1bSWĔՖk1**ykʬHpo].*F6fcTT"eM©g0&):{ϼkZ> 8$,52F*sw1pHkQ6Fzw]cN̵J #pAF]#l8?1(BOgT~yUbL8^#ܖTZ1ObbSyOIV~P-{b-R4٠~^eGnHl/Su6: #jT$+e?yHf'*b#ٽЧ ?&~?$pi;FBzw[keZ~7 Sv8H 3?r$7jnԄYFܨ |OQ;օ U d S - =G\ p&Gw)` /a߫i5&LsZ<#0zMzFM8,9; :R:(q-v,.7/pXqYs3r%w+OQvrtEux܉~OK }!b|tyBxʠz{.lD~m8onlk[wjR1h58ib?mcf+aQ`צedd"fig HINSKyuJcO NZLݘMFGN@E$DD2AsX@*IBCPhTg3U>uW ַVS:R|P~Q9ZS [fYX4])\ZEo^m/_5qϱ٥s\ۼqދ!K7 kfֶԁ-b3Πjp]$^'~*I@VW<âM˟ŏ{ tDCm-@wm.B+(铜>Td"ŀǼϭ~8y$owJ1}05_K^ iϏ은BI#ƈdX܁T̓cQ: rՆ⩗ fn|xK)o%ƭ/3vUuA?)C:|sĵ@͂ Ͳ;bIUeh"׻_HS1޼^Z4eg Wb27_k%8ם(ŊO}do׸Jj3wVcXWP0qB{߭gCru&op-?'Bs ưGz>2[Ȏg; i8P/ _Y=чe:ZO?(3wwXR @hQ+ğH*0"ZOWoI}@mNП5+#*'G| AH=XX?#1jvʬ`p^Y<L~i/{kHwâ hs)aLoD~Pf7VM'(@ﰤ ہg9x+n&;f?/X)T`D1 ߨMߒ.FgTp'Hq/L0UEc?kǃh6ry7]P\@TN%s7@'>$!AxUʰ\3;Y^U~PGl!;b F2ȂpԞ(Q_V:1X: n3 m:@/)IJNv"2x+ٗ Kx.HҥfAj^y9*O]#kM`~b_R 7zFh!1߈Vc0a"j6nS Nr)Υ{t*F8#vufz`rs"WG9^EMvc΍&DAdQy/4Aڱ&S֚E biLQ<6'5P..T&q]w4.6IE? v\[YI>U!lDa>Ԫ΋ϩ7~8A]&nv|oY yKiw\¹9~$ 66nQfq>,o,IӔ 渱{I .H>C-Yn馑gQz tafw0a, Qmpjc5dۈ2yܸو L+~|-dj qHA}mԵQӅlVdkbze\Ocl=c ;n Li^`Agqr<KG k5Blۻ֬@2lE\u ϫ=Y&0Q:Qa!V#Ϻ(_ ٲ $/o|XhLaf-=vAq *q3xɢ4 j m=-dlc\kkQlabe0bNl{WeP|b-I|LeMaX:QΣtԻ0JߥA=ؕפmCij4ngF`D-s3 L_ |Pq<'A  Wh% of a^)ɘИ"רY=. \;l  tҚG9w&sc d; mj>zjZ '}DhibW]egl6qnkv+zZgJoC`֣ѓ~8ORѻgWg?H6K +گ L6JAz``ègU1nFiyaf%oҠRh6 w G"U&/ź; (+Z\j1,ٞ[ޮd°c&ujm 6?rgWJz{+ 8Ҏվ | !Bhݳڃn&[owGwZjpf; \ebiaklE x NT9§g&a`IiGM>nwۮjJZ@ f7;𩼮S޻G0齽ʺŠS0$6TW)#gfz.aJ]h*o+ 7 Z-1A26b+-Sdl}wEVZOAي»IˬO ~M-JQS#xpaA.U7Y-۩6˚w]]llA?Z$㧲F waރ$Ųe]]DFok(vpi91k *Z  ,8mF6]pTqke0*1¶u4%y<8syjHA}X*ݹ1SbSW§ٖծ1k**kypH]oF*.f6TTcMe"¤0g)&Ůޟ:{kZ >8,$5*F21wsHpQkzF6c]wN̵ׄJ# pȄA#]F8l1?(gOB~TUyLbˁ8#^TO1ZbbySIOP~V{-b-4R^~Ge­lHnuS/:6# $Tj?e+y䏼Hf*'˼Ѝb# &??~p$iBF;[wzek~ZS 7H8v ?3$rj7nFY |OQ; U dؓS - \G=&pGw`)/ ai5&sL <:R=Pe6^X7}o5641W0ճ2k3$k%'1&-[#bML"'{ "!$*x(+)`F(> q-q,v.Ț/7pqXsYr3w%vQO+tru՛E~xKO} |b!ytxBz{l.m~Do8nkljw[h1Ri85bcm?a+f`Qeddf"giH IKSNJuyOcN LZMݥFĚGE@ND$A2D@XsBI*CThPU3gWu>V SR:P|Q~Z9[ SYfX]4\)^oEZ_/m5qs<\kg2z &J8 좞V`a/6i\lU,zB\uHƒ=&FW A+Ox]`غ7W>9q߳!7Kk ֩fض-bѠ3pj$]^Ĝ'*~@IWVÕ<ӂMʏş{ Dt͆mC-@mw+B.(>dT"ş~Ϝ8yo$w1J}50K_ ^ϋiBۉI#dXфTQc:r Р fΫnx|)Ko%3/uUv?A)ġ:C|sд@͉ ;IbeU"hH_S1ފZ^ھ4ge ȋbW72%k_ܝ8Ŵ(}OodJֿjw3XcVPW0Bq{gǧurCo&p-?О'sB zGɠ2>[ ;g/P8i_ Y=嗇e:ϏOZw3(?RXw@ Qh+HZ"0*WOIo@}m5N#+'*GA |􏒨HX=#?X1vjʨ`^pYL'!$UxAׯ3\Y;U^GP~b;!lڇF 2p(ԐQV_:X1: 3n :m@I)/NJ2"v+x xKH.jAf^O*9y]#Mk~`bю_޶ Rz7hFм!10cV"ajحn6 SrN){t*8Fv#fu`zrϮsɛW"G9E^vMcD&dA/yQA4S&ֿ EbLil!>aDƋΪ~7A8n&]|vYoᡱ Kyi׫w¡\~9$66 Qnf>q,o,ӹI 散 I{.C>HnY-Qg̰t zfaincompatible versionbuffer errorinsufficient memorydata errorstream errorfile errorstream endneed dictionaryxGlGkG`GPGDG0G GGkG inflate 1.2.3 Copyright 1995-2005 Mark Adler  #+3;CScs !1Aa  0@`@@bad allocationaNxbUnknown exceptionbǥcsm }P~bbad exceptionEncodePointerKERNEL32.DLLDecodePointerFlsFreeFlsSetValueFlsGetValueFlsAllocCorExitProcessmscoree.dllruntime error TLOSS error SING error DOMAIN error R6034 An application has made an attempt to load the C runtime library incorrectly. Please contact the application's support team for more information. R6033 - Attempt to use MSIL code from this assembly during native code initialization This indicates a bug in your application. It is most likely the result of calling an MSIL-compiled (/clr) function from a native constructor or from DllMain. R6032 - not enough space for locale information R6031 - Attempt to initialize the CRT more than once. This indicates a bug in your application. R6030 - CRT not initialized R6028 - unable to initialize heap R6027 - not enough space for lowio initialization R6026 - not enough space for stdio initialization R6025 - pure virtual function call R6024 - not enough space for _onexit/atexit table R6019 - unable to open console device R6018 - unexpected heap error R6017 - unexpected multithread lock error R6016 - not enough space for thread data This application has requested the Runtime to terminate it in an unusual way. Please contact the application's support team for more information. R6009 - not enough space for environment R6008 - not enough space for arguments R6002 - floating point not loaded Microsoft Visual C++ Runtime Library ...Runtime Error! Program:   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~=InitializeCriticalSectionAndSpinCountkernel32.dllGetProcessWindowStationGetUserObjectInformationAGetLastActivePopupGetActiveWindowMessageBoxAUSER32.DLL Complete Object Locator' Class Hierarchy Descriptor' Base Class Array' Base Class Descriptor at ( Type Descriptor'`local static thread guard'`managed vector copy constructor iterator'`vector vbase copy constructor iterator'`vector copy constructor iterator'`dynamic atexit destructor for '`dynamic initializer for '`eh vector vbase copy constructor iterator'`eh vector copy constructor iterator'`managed vector destructor iterator'`managed vector constructor iterator'`placement delete[] closure'`placement delete closure'`omni callsig' delete[] new[]`local vftable constructor closure'`local vftable'`RTTI`EH`udt returning'`copy constructor closure'`eh vector vbase constructor iterator'`eh vector destructor iterator'`eh vector constructor iterator'`virtual displacement map'`vector vbase constructor iterator'`vector destructor iterator'`vector constructor iterator'`scalar deleting destructor'`default constructor closure'`vector deleting destructor'`vbase destructor'`string'`local static guard'`typeof'`vcall'`vbtable'`vftable'^=|=&=<<=>>=%=/=-=+=*=||&&|^~(),>=><=<%/->*&+---++*->operator[]!===!<<>> delete new__unaligned__restrict__ptr64__clrcall__fastcall__thiscall__stdcall__pascal__cdecl__based(`VXVLV@V4V(VVVVUkG@Q$QQPPUU(PUUUUUUUUUUUUUUUUUUUUUUUUU|UxUtUpUlUhUdU`U\UXUTUPULU@U4U,U UUTTTTThTHT$TTSSSSpSlSdSTS0S(SS SRRRRXR,RRQQQpQTQkG ((((( H h(((( H H  !"#$%&'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\]^_`abcdefghijklmnopqrstuvwxyz{|}~  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~HH:mm:ssdddd, MMMM dd, yyyyMM/dd/yyPMAMDecemberNovemberOctoberSeptemberAugustJulyJuneAprilMarchFebruaryJanuaryDecNovOctSepAugJulJunMayAprMarFebJanSaturdayFridayThursdayWednesdayTuesdayMondaySundaySatFriThuWedTueMonSunSunMonTueWedThuFriSatJanFebMarAprMayJunJulAugSepOctNovDecHp cRSDSGF$ >c:\prog\dosbox\src\libs\zmbv\Release\zmbv.pdbtp bb(bDbtp@ bp@`bpbDbp`bpbbbp@bpbbcDbp@bś    "Pc "|cCccctp pp "t /ت@d"dd`i׮ۮpteecp 0øϸ8<G_%'piihhidii\ijkiin nm jj.j@jLj\jhjzjjjjjjjk$k8kFkRk`kjkkkkkkkk l l:lRllllllllllm m8mPm`mnmzmmmmmmmmiiixiDefDriverProcWINMM.dllGetStdHandleuMultiByteToWideCharKERNEL32.dllEndDialogDialogBoxParamAUSER32.dllShellExecuteASHELL32.dllRtlUnwindFGetCurrentThreadIdGetCommandLineAHeapFreeGetVersionExAHeapAllocGetProcessHeapqGetLastErrorRaiseException^TerminateProcessBGetCurrentProcessnUnhandledExceptionFilterJSetUnhandledExceptionFilter9IsDebuggerPresentGetProcAddressGetModuleHandleAeTlsGetValuecTlsAllocfTlsSetValuedTlsFree,InterlockedIncrement(SetLastError(InterlockedDecrementExitProcessVSleep$SetHandleCountfGetFileTypeGetStartupInfoADeleteCriticalSection}GetModuleFileNameAFreeEnvironmentStringsAUGetEnvironmentStringsFreeEnvironmentStringsWWideCharToMultiByteWGetEnvironmentStringsWHeapDestroyHeapCreateVirtualFreeQueryPerformanceCounterGetTickCountCGetCurrentProcessIdGetSystemTimeAsFileTimeQLeaveCriticalSectionEnterCriticalSectionVirtualAllocHeapReAllocWriteFileHeapSizeGetCPInfoGetACPGetOEMCPRLoadLibraryA#InitializeCriticalSectiontGetLocaleInfoAGetStringTypeAGetStringTypeWDLCMapStringAELCMapStringWEbnXn\n`nknZMBV.dllDriverProcZipped Motion Block Video v0.1ZMBV "XH$I.?AVbad_alloc@std@@$I.?AVexception@std@@N@DHH$I.?AVtype_info@@H$I.?AVbad_exception@std@@(  x ON N (NMMM|MDMMLLLdLL K!J"0JxJy JzIII        ! 5A CPR S WY l m pr   )    HZC(u(u(u(u(u@|Xx]^{0u0uv abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZv`y!@~ڣ @ڣ AϢ[@~QQ^ _j21~ XZ aaaaa aaa````````````````````x`p``h```X`L`D`8`,`(`$```_ {.<|ʇʇʇʇʇʇʇʇʇ@|.pPSTPDT||;Zx0Nm:Yw/Ml @eXfp   Аt|VȀ7DOSBox Video Codec v0.1MS Sans SerifP"OKPZipped Motion Block Video v 0.1 Copyright 2005, DOSBox TeamP"2Email authorP;":Visit home pagePAȀZMBV configuration dialogMS Sans SerifP,V2Email authorPmW:Visit home pageP,}2OKPu}2CancelP9\msctls_trackbar32 PAPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXl 00011>2H2L2P2T2X2\2`2d2h2l2p2t2x2|22222222222222233 405666667:: :$:(: S????`\222356I78d9::::::::::;;; ;;;;; ;$;(;,;0;4;8;<;@;D;H;L;P;T;X;p8}:$;3;;;h<}<pj0z000s3333o8t88889949>9P9Z9u9|9999999999:: :,:9:S:Z:k:u:::::::::!;???W0L2T2_2m2j555555555#6A6H6L6P6T6X6\6`6d666666&717L7S7X7\7`777777777778J8P8T8X8\88.9;9E9S9~99999999M:;;;q====>>:>P>b>g>m>s>>>>>>? ?R?X?s????+0X0001"1C1{1111.242E23345,55556666666-747c7777777777777777888$8)8/898B8M8Y8^8n8s8y888888r9:;;o=>D232=2H2>4/555@5L5a5g5p5w55555555556 66#6)6/6;6I6O6[6a6n6x6~66666666#70767<7_7e7777)8L8V888888999919?9F9L9b9g9o9u9|9999999999999999999:::*:/:::?:L:Z:`:p:::::;";L;W;;;;;;<~<<<<<<<<===3===S=]=w==========>>>>>>>?=?F?R?j?~???? 0(0b0l00001011111112@2H222222q444444 55"5,5E5Q5]5d555506G6W6\6{66 77#7:7@7F7V7`7i7r77777777777,8i8o888Q9n99::::0;M;p;};;;;;;;;;;<&<,@>h>n>y>>>>>>>>*?_?x????????00 00000 0$0n0t0x0|0000 1111 1A1k1111111111 22222z2?3e33394}444 5E5556|77777777C8K8`8k8888888 9W9q999F:::<;H;[;m;;;;;;;;;;<<7<`>>>>]?g?q?????e0s0000001+1r1w1111111H2Q2W22222 33K3Q3Z3a3l3x333*4?444445W55566C6f66666}7788888999+919>9e9v9}99999 :#:H:;;;`=r======== >>>>>>>>> ??H00000011?1o1222V3'46 6J666o77::p<<<<<<<(1 1$1(1,1@11122 2,282D2P2\2@87777777777888999 9$9H9L9P9T9X9\9Pp6t6x6|666666666666666666666666666666666777 77777 7$7(7,7074787<7@7D7H7L7P7T7X7\7`7d7h7l7p7t7x7|77777777777777777777777777777`112222 2(2@2D2\2l2p2222222222222233T3`333333333334484X4x444444445585@5D5\5`5|555555555566 6<6@6`6|6666667 7@7`7|7777778p(0,0<0@0T0p0t00000000012223 333$3,343<3D3L3T3\3d3l3t3|333335 55555555566666@:L;P;T;X;\;`;d;h;l;p;t;x;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<< <<<<< <$<(<8<@ addr_buffer ld (addr_buf_end),de ld hl,0 jr addr_found addr_done ld (addr_buf_end),de ; Now each address mark is read for at least 3 full revolutions. Analyze this ; data to detect the number of address marks in one revolution. Note that it's ; possible a single track contains several identical address marks. So ; detecting the number of sectors in the track is not as simple as searching ; for the 2nd occurrence of the 1st read address mark. ; ; Some examples (the latters 'A'-'Z' each represent a unique address mark): ; * ABCDEFGHIABCDEFGHIABCDEFGHIABCD ; -> 'normal' track with 9 unique address marks ; * ABCDABEABCDABEABCDABEABC ; -> 8 unique address marks, the marks A and B appear twice ; * ABCABCABCABCABCABCABCA ; -> the current algorithm detects a track with 3 unique address marks ; so it cannot distinguish the track 'ABC' from 'ABCABC' ; TODO fix this by taking the timing into account ; ; Note that the detection can fail if we did not read at least 2 full ; revolutions. For example, suppose the actual track contains these marks: ; 'AAAAAAABAA' (10 unique marks, mark A is duplicated 9 times). Now suppose we ; only read 'AAAAAAABAAAAAAA'. Valid periods for this sequence could be 8, 9 ; or 10. (The current algorithm would return 8). This ambiguity disappears if ; we read at least 2 full revolutions. ld hl,0 next_period ld ix,(addr_buf_end) ld a,ixl and 7 jp nz,read_addr_err ld de,addr_buffer call get_period addr_found ld (nb_sectors),hl ; TODO check not more than 64 (DMK cannot handle that) ; if (nb_sectors > 64) error("not supported by dmk"); call print_pc db " found ",0 ld hl,(nb_sectors) call print_dec_hl ; TODO print_dec_a call print_pc db " sectors",13,10,0 ; We now know the period after which the address marks start repeating. ; Take the difference of the counter value that was stored at the 1st ; and address mark and the 1st replica of this mark. ; ; This difference should be around 0x38E0 (tested on real machine). ; TODO if measured value is far below 0x38E0, try doubling, tripling, ... ; until it gets in range. ld hl,(nb_sectors) ld a,h or l jp z,read_track ; skip ticks-check add hl,hl add hl,hl add hl,hl ld de,addr_buffer add hl,de ld de,(addr_buf_end) or a sbc hl,de jr c,ok_periodic ; period is whole buffer -> not periodic call print_pc db "Not periodic (read noise?)",13,10,0 jp addr_retry ok_periodic add hl,de ld de,ofst_amark_tick add hl,de ld a,(hl) inc hl ld h,(hl) ld l,a ld de,(addr_buffer+ofst_amark_tick) or a sbc hl,de ld (ticks),hl ld a,(debug) or a jr z,debug_2 call print_pc db ", ticks: ",0 ld hl,(ticks) call print_dec_hl ; is this info useful? call print_pc db 13,10,0 debug_2 ; If there were read errors during the 'read address' command, then it's ; possible the detected period is larger than one disk revolution. This for ; example happened for the disks of 'New Vision Benelux'. ; We read the address marks for about 3 revolutions, but if there was a read ; error for e.g. an address mark in the 2nd revolution (so the read address ; command didn't return the exact same 6 bytes than for revolution 1 and 3), ; then the get_period routine won't detect the true period, but instead it ; returns the full 3 revolutions as the period (without internal repetition). ; We can detect this error by looking at the detected number of ticks for ; the period. If the detected period is for a single revolution, it should ; be in range [13834, 15290]. But if it's higher we've probably estimated ; more than one revolution. ; TODO also check for lower than 13834. ld hl,(ticks) ld de,(ticks_min) ; depends on driver or a sbc hl,de jr c,too_short add hl,de ld de,(ticks_max) ; depends on driver or a sbc hl,de jp nc,addr_retry ; maybe it works in the next attempt jr read_track too_short ld hl,(nb_sectors) jp next_period ; Use the WD2793 "read track" command to get the raw track data. ; Also store the number of read bytes. This is the track length. ; The 'normal' track length is 6250 bytes, but this can also vary. read_track ld a,'t' call debug_log call print_pc db "Read track ...",0 ;; TODO is this call required? ;; Quibus reported a hang during 'Read track' at one point. ;; The LED of drive B was turned on when that happened. ;; The only way I can see how that's possible is that the ;; slot in page was somehow changed. Any other explanations??? track_retry call select_fdc ld iy,(driver_rd_trck) jp (iy) track_too_much call print_pc db "Read track returned too much data",13,10,0 jr read_track track_err call print_pc db "Read track error",13,10,0 jp debug_exit track_no_start call print_pc db "Read track command failed to start!",13,10,0 jp debug_exit track_end ex de,hl ld (track_stop),hl ; store for later ld de,trck_buffer or a sbc hl,de ld (track_len),hl ; TODO check in range [5938, 6562]? call print_pc db " length: ",0 ld hl,(track_len) call print_dec_hl call print_pc db 13,10,0 ; Quibus reported a hang after the tool printed "Read track .. length=12438" ; So it seemed the FDC somehow missed the index pulse and read the track data ; twice. This causes a buffer overflow later in the tool. To workaround this ; hardware quirk, we check that the tracklength in in range [5800..6700]. ; That's +/-5% of the nominal track length. ld hl,(track_len) ld bc,6700+1 or a sbc hl,bc jr nc,wrong_length ld hl,(track_len) ld bc,5800 or a sbc hl,bc jr nc,length_ok wrong_length call print_pc db "Unexpected track length .. retrying ...",0 jp track_retry length_ok ; Calculate the ratio between the number of ticks (the difference in counter ; value) and the track length. A typical value for the former is around 14560, ; for the latter it's around 6250. So the ratio between the two is smaller ; than 1. We calculate this ration as a 0.16-bit fractional number. ; Later this ratio will be used to estimate the position of the address marks ; in the raw track. ld hl,(track_len) ld bc,(ticks) call frac_div ld (ratio),de ; Copy the track data so that it is twice directly after each other in memory. ; This is a cheap way to implement 'circular-reads': on a real disk, if you ; read past the end of a track, you simply wrap around to the beginning of the ; track. With the copy reads (that only wrap once!) now behave the same. ; ; Note that the first dozen or so bytes returned by the read track command are ; totally unreliable. This is because the FDC has not yet seen any ; synchronization marks, so it has no idea which bit in the stream is the first ; bit of a byte. For us this is not a big problem because in the code below we ; only scan for these synchronization marks. ld bc,(track_len) ld hl,trck_buffer ld d,h ld e,l add hl,bc ex de,hl ldir ; Clear the DMK header. There is room for 64 IDAM entries. Unused position ; should contain the value 0x0000. ld hl,dmk_header ld de,dmk_header+1 ld bc,sizeof_dmk_h-1 ld (hl),0 ldir ; Later we fill search the position of address and data marks in the raw track ; data. Clear that table now. Note that this table partly overlaps with the ; addr_buffer (where we stored the result of the "read address" commands). ; That's OK, from this point on we only need the first 64 entries from the ; that table anymore. ld hl,offset_buffer ld de,offset_buffer+1 ld bc,sizeof_off_buf-1 ld (hl),0 ldir ; Now, for each data adress mark (returned by the "read address" command), try ; to locate it in the raw track data. For this we use the recorded counter ; value for that address mark and the earlier calculated ratio. ld a,(nb_sectors) or a ; if there are no address marks on jp z,no_sectors ; this track we're done ld a,'A' call debug_log ld a,(debug) or a jr z,skip_ana_prt call print_pc db "Analyze raw track ... ",0 skip_ana_prt ld a,(nb_sectors) ld hl,offset_buffer ; store results in this table ld (ofst_buf_ptr),hl ld hl,addr_buffer addr_mark_loop ld (sector_cntr),a ld (addr_buf_ptr),hl ld a,(debug) or a jr z,skip_ana_prt2 push hl ld a,(sector_cntr) call print_dec_a call print_pc db " ",0 pop hl skip_ana_prt2 ld a,'a' call debug_log ld de,ofst_amark_tick add hl,de ld e,(hl) inc hl ld d,(hl) ld bc,(ratio) call mul_hi ; hl = (de * bc) >> 16 ld bc,trck_buffer add hl,bc ld (addr_estimate),hl ; We can only use this calculated position as a (fairly good) estimate of the ; position. The real position can be different for the following reasons: ; - we recorded the counter at the end of the "read address" command, while ; here we're looking for the start of the address mark ; - the counter we've used does not 100% increase with a constant rate (e.g. ; at the end of a command we have to do some extra stuff, and we don't ; increase the counter during that time) ; - the rotation speed (and maybe also the flux density?) is not 100% ; constant ; To compensate for this we only use the calculated position as the starting ; point for the search. If we don't find the mark at exactly this position, we ; try the adjacent few bytes (both up and down). Tests on a real machine have ; shown that usually we find the mark withing 2 or 3 bytes from the calculated ; position (but here we try up to 5 bytes (up and down) from the calculated ; position). ; TODO more tests on Quibus machine indicated that (in rare cases?) 5 bytes is ; not enough. I've currently increased it up to 20 bytes. But that's most ; likely too much (searching too far from the initial position has a risk of ; finding a different mark in the wrong position) ; ; We look for the bytes 0xA1 0xA1 0xFE. The real address mark still has a 3rd ; byte 0xA1 in front of this sequence. But tests on a real WD2793 have shown ; that this byte is very often read wrong by the "read track" command. (Later ; we will correct this so that the data in the DMK file has the correct ; sequence A1 A1 A1 FE). ; ; The disk "Demo Kit Deluxe" has at the start of the track the sequence ; "A1 A1 A1 FC" (instead of the usual sequence C2 C2 C2 FC). When doing tests ; with this disk on a real machine, we found that the "read address" command ; also returns the 6 bytes following the "A1 A1 A1 FC" sequence (the expected ; sequence for an address mark is A1 A1 A1 FE). So it seems the WD2793 accepts ; both FC and FE in the address mark sequence. ld de,ofst_tab ofst_next ld hl,(addr_estimate) ; restore initial estimate ld a,(de) inc de cp #80 jp z,ofst_err ; reached the end of the offset table ld c,a ld a,(de) inc de ld b,a ; 16-bit offset add hl,bc ; add offset ld a,(hl) cp #A1 jr nz,ofst_next inc hl ld a,(hl) cp #A1 jr nz,ofst_next inc hl ld a,(hl) cp #FE jr z,ofst_1 cp #FC ; see comments above jr nz,ofst_next ofst_1 dec hl dec hl dec hl ; points to start of A1 A1 A1 FE sequence ex de,hl ld hl,(ofst_buf_ptr) ld (hl),e inc hl ld (hl),d ; store location in offset_buffer ;;push de ;;call print_pc ;;db " addr_mark=0x",0 ;;pop hl ;;call print_hex_hl ; Now verify the CRC of the address mark. We use the data returned by the ; "read address" command (not the data in the raw track). If there is a CRC ; error we don't need to look for the data mark. ; ; Alternatively we could use the CRC-error-bit in the WD2793 status register ; to see if there was a CRC error. Though I prefered to not do that to keep ; the read-all-address-marks loop as fast as possible. ; ; The CRC value includes the 4 bytes of the address mark header (A1 A1 A1 FE) ; and the 4 actual "C H R N" bytes stored in the address. ld hl,#B230 ; precalculated CRC for A1 A1 A1 FE ld de,(addr_buf_ptr) ld a,(de) ; C call crc_byte inc de ld a,(de) ; H call crc_byte inc de ld a,(de) ; R call crc_byte inc de ld a,(de) ; N call crc_byte inc de ld a,(de) ; CRC (high byte) cp h jr nz,addr_crc_err inc de ld a,(de) ; CRC (low byte) cp l jr nz,addr_crc_err ;;call print_pc ;;db " CRC-OK",0 ; We found the address mark and it has a valid CRC. Now search for the data ; mark. According to the WD2793 datasheet, the data mark should occur within ; 43 bytes from (the end of) the address mark. ; ; A data mark starts with the sequence "A1 A1 A1 FB" (normal data mark) or ; "A1 A1 A1 F8" (deleted data mark). But just as for the address mark, the ; WD2793 "read track" command cannot reliably read the first A1 byte of this ; sequence, so we ignore it while searching. ld hl,(ofst_buf_ptr) ld a,(hl) inc hl ld h,(hl) ld l,a ; hl = start of address mark ld de,10-1 add hl,de ; end of address mark ld b,43+4 ; should find data mark within 43 bytes data_mark_1 dec b jp z,data_err inc hl ld a,(hl) cp #A1 jr nz,data_mark_1 data_mark_2 dec b jp z,data_err inc hl ld a,(hl) cp #A1 jr nz,data_mark_1 data_mark_3 dec b jp z,data_err inc hl ld a,(hl) ; data mark type (deleted or normal) cp #A1 jr z,data_mark_3 cp #FB jr z,data_mark_found cp #F8 jr nz,data_mark_1 data_mark_found dec hl dec hl dec hl ; points to start of A1 A1 A1 FB sequence ex de,hl ld hl,(ofst_buf_ptr) inc hl inc hl ld (hl),e inc hl ld (hl),d ; data mark offset inc hl ld (hl),a ; data mark type ;;push af ;;push de ;;call print_pc ;;db " data_mark=0x",0 ;;pop hl ;;call print_hex_hl ;;call print_pc ;;db " type=0x",0 ;;pop af ;;call print_hex_a jr next_addr_mark addr_crc_err ; address mark had a CRC error (not an error for us) ;;call print_pc ;;db " CRC-ERR",0 jr next_addr_mark data_err ; didn't find data mark in time (not an error for us) ;;call print_pc ;;db " data mark not found",0 jr next_addr_mark ; Also locate address and data marks for the other sectors in this track next_addr_mark ;;call print_pc ;;db 13,10,0 ld hl,(ofst_buf_ptr) ld de,sizeof_offinfo add hl,de ld (ofst_buf_ptr),hl ld hl,(addr_buf_ptr) ld de,sizeof_amark add hl,de ld a,(sector_cntr) dec a jp nz,addr_mark_loop ld a,(debug) or a jr z,skip_ana_prt3 call print_pc db 13,10,0 skip_ana_prt3 ; We've located the address and data mark for each sector in the raw track ; data. Now start overwriting the sector data in the raw track data with ; data from an actual "read sector" command. Do this because tests on a real ; WD2793 have shown that the data returned by "read sector" is more reliable ; than the same data returned by "read track". This is especially true for a ; sector that wrap around the end of the track (so past the index point). ld a,'S' call debug_log call print_pc db "Read sectors ... ",0 ld hl,sector_buffer ld (sector_buf_ptr),hl ; The appraoch below reads the correct sector data even if there are multiple ; sectors with the same ID present in the same track. It works well, but it's ; very slow (see below for details about the used approach). It can take 45 ; minutes to dump a whole disk using this appraoch! ; In the majority of the cases the track does not have duplicate sectors IDs, ; in that case we can use a much faster approach. ld hl,unique_buffer ld de,unique_buffer+1 ld bc,256-1 ld (hl),#ff ldir ; fill unique_buffer with #ff ld a,(nb_sectors) ld bc,sizeof_amark ld hl,addr_buffer+ofst_amark_R ld d,unique_buffer/256 count_loop ld e,(hl) ; 'R'-value of address-mark ex de,hl inc (hl) jr nz,read_slow ; if we increase #ff more than once ex de,hl ; it's non-zero, in that case there's add hl,bc ; a duplicate and we must use the dec a ; slow approach jr nz,count_loop read_fast ld a,1 jr set_speed read_slow xor a set_speed ld (read_speed),a ld hl,offset_buffer ld (ofst_buf_ptr),hl ld a,(nb_sectors) ld hl,addr_buffer sector_loop ld (sector_cntr),a ld (addr_buf_ptr),hl call print_dec_a call print_pc db " ",0 ; Skip sectors for which we didn't find a data mark previously. ld hl,(ofst_buf_ptr) inc hl inc hl ld a,(hl) inc hl or (hl) jp z,next_sector ; Reading a specific sector on some copy protected disks is not that simple ; because it can happen that a sector with the same identification header (so ; same address mark) appears multiple times on the same track. It's really ; important that we read the correct version. ; ; To solve this, we wait till the index pulse, then execute some delay loop ; (delay is based on the counter value we recorded during the "read address" ; phase above) and only then we execute the "read sector" command. For extra ; safety we measure the time between the start of the command and the moment ; we receive the first data from the command. If this difference is too big we ; adjust the delay value and try again. ld a,'s' call debug_log ld hl,64 ld (adjust_ofst),hl ld (adjust_scale),hl try_read_sector xor a retry_crc ld (crc_retries),a ld hl,(addr_buf_ptr) ld b,(hl) ; 'C' value from read address command inc hl ; 'H' inc hl ld c,(hl) ; 'R' inc hl ; 'N' inc hl ; CRC1 inc hl ; CRC2 inc hl ld a,(hl) inc hl ld h,(hl) ld l,a ; hl = counter value ld de,(adjust_ofst) or a ; counter value was recorded at end of the sbc hl,de ; "read address" command, we need to wait ; till the start of the address mark, so ; wait a bit less ld de,(sector_buf_ptr) ld iy,(driver_rd_sect) jp (iy) sector_end ex de,hl ld de,(sector_buf_ptr) or a ; sector length, should be one of sbc hl,de ; 128, 256, 512 or 1024 ld (sector_size),hl ex de,hl ld hl,(ofst_buf_ptr) ld bc,ofst_oi_size add hl,bc ld (hl),e inc hl ld (hl),d ; store sector size inc hl ld bc,(sector_buf_ptr) ld (hl),c inc hl ld (hl),b ; store pointer to sector data ld b,a and 8 ; crc error? jr z,read_no_crc_err ld a,(crc_retries) ; Only when we have 5 crc errors, we inc a ; believe it's an intentional crc error cp 5 ; on the disk (and not some random read jp c,retry_crc ; error on an (old) disk). read_no_crc_err ld a,(read_speed) or a jr nz,sector_ok ld a,ixh or a jr nz,sector_retry ld a,ixl cp 150 jr c,sector_ok sector_retry ld a,'t' call debug_log ld a,(debug) or a jr z,debug_1 push ix call print_pc db 13,10,"|",0 pop hl push hl call print_hex_hl call print_pc db " ",0 ld hl,(adjust_ofst) call print_hex_hl call print_pc db " ",0 ld hl,(adjust_scale) call print_hex_hl call print_pc db "|",0 pop ix debug_1 ld hl,(adjust_scale) ld d,h ld e,l srl d rr e srl d rr e srl d rr e or a sbc hl,de ld (adjust_scale),hl ; scale = scale - scale/8 ex de,hl ld hl,(adjust_ofst) ld a,ixh cp 5 jr c,sector_sub add hl,de jr sector_2 sector_sub or a sbc hl,de sector_2: ld (adjust_ofst),hl jp try_read_sector sector_ok ld a,'o' call debug_log ld hl,(ofst_buf_ptr) ld de,ofst_oi_status add hl,de ld (hl),b ; b = status register ; We've read the sector, now copy it to the correct location in the raw track ; buffer. We can't immediately read it in the correct place because we have ; to perform a copy to a circular destination buffer (doing it inside the ; read-sector loop might be too slow). ld hl,(ofst_buf_ptr) ld bc,ofst_oi_ptr+1 add hl,bc ld b,(hl) dec hl ld c,(hl) dec hl ; bc = pointer to sector data push bc ld b,(hl) dec hl ld c,(hl) ; bc = sector size dec hl dec hl dec hl dec hl ld a,(hl) inc hl ld h,(hl) ld l,a ; hl = pos of data mark inc hl inc hl inc hl inc hl ; position of actual sector data ld de,(track_stop) or a sbc hl,de jr c,circular_1 ld de,trck_buffer circular_1 add hl,de ex de,hl pop hl ; hl = pointer to sector data call circular_ldir ld (sector_stop),de ; Did the "read sector" command return a CRC error? If so, we keep the CRC value ; from the raw track data. But if there was no CRC error, we calculate the ; correct CRC value and store that in the raw track data. Most of the time this ; calculated value will be the same as the value that is already present in the ; raw track data, though not in case the sector wrapped around the end of the ; track. ld hl,(ofst_buf_ptr) ld bc,ofst_oi_status add hl,bc ld a,(hl) ; sector status and 8 jr nz,sector_crc_err ; skip calculating CRC ;;push hl ;;call print_pc ;;db " CRC-OK",0 ;;pop hl dec hl ld a,(hl) ; a = data mark type (is part of CRC) inc hl inc hl ld c,(hl) inc hl ld b,(hl) ; bc = sector size inc hl ld e,(hl) inc hl ld d,(hl) ; de = pointer to sector data ld hl,#CDB4 ; precalculated CRC for sequence "A1 A1 A1" push bc call crc_byte pop bc sector_crc_loop ld a,(de) inc de push bc call crc_byte pop bc dec bc ld a,b or c jr nz,sector_crc_loop ex de,hl ; de = CRC value ld hl,(sector_stop) ld (hl),d ; CRC is stored big endian inc hl ld a,(track_stop) cp l jr nz,copy_crc_next ld a,(track_stop+1) cp h jr nz,copy_crc_next ld hl,trck_buffer copy_crc_next ld (hl),e jr next_sector sector_crc_err ;;call print_pc ;;db " CRC-ERR",0 ; Sector data (and possibly CRC value) is stored in raw track buffer. ; Continue with the next sector. next_sector ld hl,(sector_buf_ptr) ld bc,(sector_size) add hl,bc ld (sector_buf_ptr),hl ld a,h cp unique_buffer/256 jp nc,buffer_overflow ;;call print_pc ;;db 13,10,0 ld hl,(ofst_buf_ptr) ld de,sizeof_offinfo add hl,de ld (ofst_buf_ptr),hl ld hl,(addr_buf_ptr) ld de,sizeof_amark add hl,de ld a,(sector_cntr) dec a jp nz,sector_loop call print_pc db 13,10,0 ; At this point we've read the raw track data and overwritten it with data ; from "read sector" commands. Though we still need to make some more ; adjustments: ; - the read track command doesn't reliably read the first A1 byte in a ; address or data mark sequence. ; - I found that sometimes the "read sector" command had overwritten the ; address mark of another sector with different data (e.g. this happened ; while experimenting with the Pixess game disk which intentionally has ; overlapping sector) ; To fix both problems we now restore the address and data marks. ; ; We also record the position of the address marks in the DMK track header. ld a,'F' call debug_log ld a,(debug) or a jr z,skip_fix_prt call print_pc db "Fixup markers ...",13,10,0 skip_fix_prt ld hl,offset_buffer ld (ofst_buf_ptr),hl ld hl,dmk_header ld (dmk_ptr),hl ld a,(nb_sectors) ld hl,addr_buffer fixup_loop ld (sector_cntr),a ld (addr_buf_ptr),hl ld a,'f' call debug_log ld hl,(ofst_buf_ptr) ld e,(hl) inc hl ld d,(hl) ; de = pos of addr mark inc hl push hl push de ld hl,addr_mark ld bc,4 call circular_ldir ld hl,(addr_buf_ptr) ld bc,6 call circular_ldir pop hl ; pos of addr mark ld de,trck_buffer or a sbc hl,de ld de,#8083 add hl,de ex de,hl ld hl,(dmk_ptr) ld (hl),e inc hl ld (hl),d inc hl ld (dmk_ptr),hl pop hl ld e,(hl) inc hl ld d,(hl) ld a,e or d jr z,fixup_next ; no data mark inc hl push hl ld hl,addr_mark ld bc,3 call circular_ldir pop hl ld a,(hl) ld (de),a ; copy data mark type fixup_next ld hl,(ofst_buf_ptr) ld de,sizeof_offinfo add hl,de ld (ofst_buf_ptr),hl ld hl,(addr_buf_ptr) ld de,sizeof_amark add hl,de ld a,(sector_cntr) dec a jr nz,fixup_loop ; Done. ... Well, not quite ... ; In theory everything should be done at this point. However tests on a real ; machine have shown that for some disks we *sometimes* still get the wrong ; data at this point. This happened for disks which have overlapping sectors ; (e.g. sunrise disks). When I did a "read track" on such a disk, I found that ; the distance in bytes between the start of the sectors is sometimes N but ; sometimes N+1! Normally this doesn't matter (and this variation might be the ; reason why there are gaps between sectors). Though in case of overlapping ; sectors, if the relative start of the two sectors is shifted by a byte, then ; the CRC of the sectors will be different (in case of the sunrise protection, ; the CRC of the first sector is part of the data of the second sector). The ; only way I found to detect/correct this problem is to verify the CRCs of the ; sectors and if we find one that is not correct (while it should be) just try ; again and hope we get the correct relative distance on the next attempt. ; On a real machine I found that trying 3-4 times is enough to get the correct ; data. Here we try up to 20 times. ld a,'V' call debug_log call print_pc db "Verifying ... ",0 ld hl,offset_buffer ld a,(nb_sectors) verify_loop ld (sector_cntr),a ld (ofst_buf_ptr),hl ld a,'v' call debug_log ;;push hl ;;call print_dec_a ; TODO print_dec ;;call print_pc ;;db ": ",0 ;;pop hl inc hl inc hl ld e,(hl) inc hl ld d,(hl) ; de = location of data mark ld a,e or d jp z,verify_next inc hl inc hl ld a,(hl) ; a = read sector status ld (crc_status),a inc hl ld c,(hl) inc hl ld b,(hl) ; bc = sector length inc hl push bc ld c,(hl) inc hl ld b,(hl) ; bc = sector data push bc pop ix ; ix = sector data ld b,4 ; length of data mark ld hl,#ffff ; initialize CRC circular_crc1 ld a,(de) inc de push bc call crc_byte pop bc ld a,(track_stop) cp e jr nz,circ_crc_next1 ld a,(track_stop+1) cp d jr nz,circ_crc_next1 ld de,trck_buffer circ_crc_next1 djnz circular_crc1 ld a,'w' call debug_log pop bc ; bc = sector length circular_crc ld a,(de) cp (ix+0) jr nz,verify_data inc de inc ix push bc call crc_byte pop bc ld a,(track_stop) cp e jr nz,circ_crc_next ld a,(track_stop+1) cp d jr nz,circ_crc_next ld de,trck_buffer circ_crc_next dec bc ld a,b or c jr nz,circular_crc ld a,'x' call debug_log ex de,hl ld b,(hl) inc hl ld c,(hl) ; bc = on-disk CRC (stored big endian) ex de,hl ; hl = calculated CRC or a sbc hl,bc jr z,crc_match ;;call print_pc ;;db " should have CRC error ... ", 0 crc_mismatch ld a,'m' call debug_log ld a,(crc_status) and 8 jr nz,verify_next ; ok, read sector command also returned CRC err ld a,'M' jr verify_retry verify_data ld a,'D' verify_retry call debug_log call print_pc db "FAILED",13,10,0 maybe_retry ld a,(retries) inc a ld (retries),a cp 20 jr c,do_retry jp retry_error do_retry ld a,(debug) or a jr z,no_debug_write call write_debug call select_fdc call seek no_debug_write jp retry crc_match ld a,'n' call debug_log ;;call print_pc ;;db " should not have CRC error ... ", 0 ld a,(crc_status) and 8 ld a,'N' jr nz,verify_retry ; CRC did match, but it shouldn't have verify_next ;;call print_pc ;;db "OK",13,10,0 ld hl,(ofst_buf_ptr) ld de,sizeof_offinfo add hl,de ld a,(sector_cntr) dec a jp nz,verify_loop verify_done ; Really done. ; Write the data to disk (the second drive). ; TODO this tool should also be usuable on machines with only one disk drive. ; So we should ask the user to swap disks. To make it more comfortable, we ; should try to minimize the number of required swaps. So we should buffer the ; data in (v)ram. success call enable_irq call print_pc db "Success!!!",13,10,0 ld a,(cylinder) ld hl,fcb+1+4 ld b,'0'-1 dec_loop inc b sub 10 jr nc,dec_loop ld (hl),b add a,'0'+10 inc hl ld (hl),a ld a,(side) add a,'0' inc hl inc hl ld (hl),a retry_open ld de,fcb ld c,#16 ; create file call #0005 or a jp nz,open_error ld hl,1 ld (fcb+14),hl ; set record size (1 byte) ld hl,0 ld (fcb+33),hl ld (fcb+35),hl ; record number ld de,dmk_header ld c,#1a ; set disk transfer address call #0005 ld hl,(track_len) ld bc,128 add hl,bc ; hl = #bytes to write ld de,fcb ld c,#26 ; random block write call #0005 or a jp nz,write_error ld de,fcb ld c,#10 ; close file call #0005 or a jp nz,close_error ld a,(side) inc a cp 2 jp nz,side_loop ;; TODO use step-in ld a,(stop_track) inc a ld b,a ld a,(cylinder) inc a cp b jp nz,cylinder_loop exit call deselect_fdc call enable_irq ld c,#00 jp #0005 ; exit program ; We didn't find any sectors in this track. If we're already at cylinder 80 or ; higher this means we're done. Though if the user explicitly set the ; stop-cylinder, we continue until that cylinder. ; ; In case there's a 81th track on side 0, but not on side 1 we don't want to ; stop dumping because the combine-dmk tool expects to see always track info ; for both sides. no_sectors ld a,(cylinder) cp 80 jp c,verify_done ; not yet at cylinder 80 ld a,(stop_set) or a jp nz,verify_done ; user explicitly set end-cylinder ld a,(side) or a jp nz,verify_done ; if we're not on side 0, then continue call print_pc db 13,10 db "End of disk detected.",13,10,0 jp exit open_error call print_pc db "Error opening file",0 err_retry call print_pc db " ... press key to retry ...",0 ld ix,#009f ld iy,(#fcc0) call #001c call print_pc db 13,10,0 jp retry_open write_error call print_pc db "Error writing file",13,10,0 delete_file ld de,fcb ld c,#13 ; delete file call #0005 ; when the disk is full we get a write error ; but it also leaves a zero-sized file behind jr err_retry ; so here we delete it close_error call print_pc db "Error closing file",13,10,0 jr delete_file ofst_err call print_pc db "Failed to find address mark in raw track data 0x",0 ld hl,(addr_estimate) call print_hex_hl call print_pc db 13,10,0 jp maybe_retry sector_err call print_pc db "Read sector command didn't find sector",0 jp maybe_retry retry_error call print_pc db "Unsuccessful after 20 retries :-(",13,10,0 jr debug_exit buffer_overflow call print_pc db "Sector buffer overflow",13,10,0 debug_exit call write_debug jp exit write_debug call print_pc db "Writing debug file ...",13,10,0 ld de,fcb_debug ld c,#16 ; create file call #0005 or a jr nz,debug_error ld hl,1 ld (fcb_debug+14),hl ; set record size (1 byte) ld hl,0 ld (fcb_debug+33),hl ld (fcb_debug+35),hl ; record number ld de,#8000 ld c,#1a ; set disk transfer address call #0005 ld hl,#4000 ld de,fcb_debug ld c,#26 ; random block write call #0005 or a jr nz,debug_error ld de,fcb_debug ld c,#10 ; close file call #0005 or a jr nz,debug_error ret debug_error call print_pc db "Error while writing debug file",13,10,0 ret unknown_option call print_pc db "Unknown command line option",13,10,0 jp exit expected_int call print_pc db "Error parsing command line: expected integer",13,10,0 jp exit ; We disable VDP IRQs because the BIOS print routine enables interrupts (EI) ; and that interferes with the low level drive settings we're doing (e.g. it ; can turn the motor off) a better solution would be to make sure we only print ; at non-critical places and/or restore all FDC state after a print. Or write ; our own printing routine. And of course there could be other IRQ sources in ; the MSX machine, so this is really only a hack (though would those other ; sources also trigger the motor timeout mechanism??) disable_irq di ld a,(#f3e0) and #df out (#99),a ld (#f3e0),a ld a,1+128 out (#99),a ; disable VDP IRQs ret enable_irq di ld a,(#f3e0) or #20 out (#99),a ld (#f3e0),a ld a,1+128 out (#99),a ei ret select_fdc call disable_irq ld iy,(driver_select) jp (iy) deselect_fdc ld iy,(driver_deselect) jp (iy) seek ld iy,(driver_seek) jp (iy) circular_ldir ld a,(hl) ld (de),a inc de inc hl ld a,(track_stop) cp e jr nz,ldir_next ld a,(track_stop+1) cp d jr nz,ldir_next ld de,trck_buffer ldir_next dec bc ld a,b or c jr nz,circular_ldir ret delay: ld hl,50000 delay0 ex (sp),hl ex (sp),hl dec hl ld a,h or l jr nz,delay0 ret ; Input: [DE] = ptr to start of block ; [IX] = ptr to end of block (right after end) ; [HL] = previous result (or zero), now search for a longer period ; Output [HL] = period get_period: inc hl p_loop: push hl push de call test_period pop de pop hl ret z inc hl jr p_loop ; In: [de] = start ; [ix] = end ; [hl] = candidate period ; Out: Z -> period found ; NZ -> period not found test_period: add hl,hl add hl,hl add hl,hl add hl,de t_loop: ld a,l cp ixl jr nz,test_1 ld a,h cp ixh ret z test_1: ld b,6 c_loop ld a,(de) cp (hl) ret nz inc hl inc de djnz c_loop inc de inc de inc hl inc hl jr t_loop ; Fractional division. ; Requires that the divisor is strictly bigger than the dividend (BC > HL). ; In: [HL] Dividend ; [BC] Divisor ; Out: [DE] = fractional part of [HL]/[BC] frac_div: ld a,b cpl ld b,a ld a,c cpl ld c,a inc bc ; bc = -divider ld de,1 ; stop after 16 iterations fdiv_loop add hl,hl ; hl <<= 1 add hl,bc ; hl -= divider jr c,fdiv1 ; hl.prev >= divider sbc hl,bc ; restore hl (carry flag remains clear) fdiv1 rl e rl d ; adc de,de jr nc,fdiv_loop ret ; [HL] = mul-high([DE], [BC]) mul_hi: ld hl,0 ld a,16 mul_loop srl d rr e jr nc,mul_skip add hl,bc mul_skip rr h rr l dec a jr nz,mul_loop ret ; In: [HL] = current CRC value ; [A] = input byte ; Out [HL] = updated CRC Value crc_byte: ld c,a ld b,8 crc_l: add hl,hl jr c,crc_1 crc_0: rlc c jr c,crc_2 jr crc_3 crc_1: rlc c jr c,crc_3 crc_2 ld a,h xor #10 ld h,a ld a,l xor #21 ld l,a crc_3: djnz crc_l ret parse_dec ld b,0 ld a,(hl) parse_dec_loop inc hl sub '0' jp c,expected_int cp '9'+1 jp nc,expected_int ld c,a ld a,b add a,a add a,a add a,a add a,b add a,b ; b*10 add a,c ld b,a ld a,(hl) or a jr z,parse_dec_end cp 13 jr z,parse_dec_end cp ' ' jr z,parse_dec_end cp 9 jr nz,parse_dec_loop parse_dec_end ld a,b ret print_pc: pop hl call print_str jp (hl) print_str: ld a,(hl) or a ret z ld ix,#00A2 ld iy,(#fcc0) call #001c inc hl jr print_str print_hex_hl: ld a,h call print_hex_a ld a,l print_hex_a: ld b,a rrca rrca rrca rrca call print_hdig ld a,b print_hdig: and #0f cp 10 jr c,prt_1 add a,'A'-'0'-10 prt_1 add a,'0' ld ix,#00A2 ld iy,(#fcc0) jp #001c print_dec_hl ld e,0 ; number of non-zero digits ld a,h or l jr z,print_dec_hl_3 ; hl==0 -> print at least one 0-digit ld bc,-10000 call print_dec_hl_1 ld bc,-1000 call print_dec_hl_1 ld bc,-100 call print_dec_hl_1 ld c,-10 call print_dec_hl_1 ld c,-1 print_dec_hl_1 ld a,-1 print_dec_hl_2 inc a add hl,bc jr c,print_dec_hl_2 sbc hl,bc inc e or a jr nz,print_dec_hl_3 dec e ret z ; skip leading zeros print_dec_hl_3 add a,'0' ld ix,#00A2 ld iy,(#fcc0) jp #001c print_dec_a ld e,0 or a jr z,print_dec_a_3 ld b,100 call print_dec_a_1 ld b,10 call print_dec_a_1 ld b,1 print_dec_a_1 ld c,-1 print_dec_a_2 inc c sub b jr nc,print_dec_a_2 add a,b ld b,a inc e ld a,c or a jr nz,print_dec_a_3 dec e jr z,print_dec_a_4 print_dec_a_3 add a,'0' ld ix,#00A2 ld iy,(#fcc0) call #001c print_dec_a_4 ld a,b ret debug_log push hl ld hl,(debug_ptr) ld (hl),a inc l ld (debug_ptr),hl pop hl ret ;--- ; Machine specific routines ; All these routine are still very much based on a WD2793 FDC. They only ; differ in how the WD2793 is connected to the MSX. Each 'driver' needs ; to implement 6 routines: ; 1) select ; input: ; 'side' global variable ; output: - ; description: ; Needs to select the disk rom in page 1 (for memory mapped FDCs). ; Needs to select correct side, select drive A, turn motor on. ; 2) deselct ; input: - ; output: - ; description: ; Called right before program exit. Should e.g. turn drive motor off. ; 3) seek ; input: 'cylinder' ; output: - ; description: ; Should seek to the correct cylinder. E.g. by first seeking to track 0 ; and then seek to the requested cylinder. It's not allowed to use the data ; in the track for this seek (e.g. verify the destination track). ; TODO in the future we may add a step-in command as well. ; 4) rd_addr ; input: - ; output: ; 'addr_buffer' is filled in ; [DE] points to end of addr_buffer ; description: ; Executes 'read address' commands in a loop and put the result in a buffer. ; During this loop it keep a counter running and on each successful read ; address command, the value of this counter is also stored in the buffer. ; return: ; on success this routine jumps to 'addr_done' ; on error it jumps to 'addr_error' ; 5) rd_track ; input: - ; output: ; 'trck_buffer' is filled in ; [DE] points to the end of trck_buffer ; description: ; return: ; on success this routine jumps to 'track_end' ; on error it jumps to 'track_err' or 'track_too_much' ; 6) rd_sector ; input: ; [B] = track number (number found in address mark, not physical track number) ; [C] = Sector number (found in address mark) ; [DE] = pointer to output buffer ; [HL] = delay value ; 'read_speed' global variable ; output: ; buffer is filled in ; [A] = WD2793 status register after command has ended (e.g. contains CRC status) ; [DE] = points to end of buffer ; [IX] = time between end-of-delay and first-byte-received ; description: ; Reads the sector with given number in the given buffer. This routine should ; wait for the index pulse and then delay for the given amount of time before ; actually starting the read sector command ; return: ; on success this routine jumps to sector_end ; on error it jumps to sector_err driver_routines driver_select dw 0 driver_deselect dw 0 driver_seek dw 0 driver_rd_addr dw 0 driver_rd_trck dw 0 driver_rd_sect dw 0 ticks_min dw 0 ticks_max dw 0 driver_size equ $ - driver_routines driver dw phil_driver ;--- ; Philips phil_status equ #7ff8 phil_command equ #7ff8 phil_track equ #7ff9 phil_sector equ #7ffa phil_data equ #7ffb phil_control1 equ #7ffc phil_control2 equ #7ffd phil_stat2 equ #7fff phil_driver dw phil_select dw phil_deselect dw phil_seek dw phil_rd_addr dw phil_rd_trck dw phil_rd_sector dw 13834 dw 15290 ; 1) select phil_select ld a,(#f348) ld h,#40 call #0024 ; select FDC slot in page 1 ld a,(side) ld (phil_control1),a ld a,#c2 ; motor on, led on, drive A ld (phil_control2),a ret ; 2) deselct phil_deselect ld a,3 ld (phil_control2),a ret ; 3) seek phil_seek wait_busy_3 ld a,(phil_status) and 1 jr nz,wait_busy_3 ld a,#0b ; restore, load head ld (phil_command),a call delay wait_busy_4 ld a,(phil_status) and 1 jr nz,wait_busy_4 ld a,(cylinder) ld (phil_data),a ; track ld a,#18 ; seek ld (phil_command),a ret ; 4) rd_addr phil_rd_addr: ld de,addr_buffer ld ix,0 ; counter ld hl,phil_stat2 ; irq/dtrq ld bc,phil_data wait_busy_1 ld a,(phil_status) and 1 jr nz,wait_busy_1 ld a,#d0 ; Forced interrupt command ld (phil_command),a ; otherwise we don't see the index ex (sp),hl ex (sp),hl ; needed? wait_index_a1 ld a,(phil_status) and 2 ; wait till index pulse = 0 jr nz,wait_index_a1 wait_index_b1 ld a,(phil_status) and 2 ; wait till index pulse = 1 jr z,wait_index_b1 addr_loop ld a,#c0 ld (phil_command),a ; read addr ; Note: the timinig of this loop is important! Don't change ; the instructions (e.g. JP->JR) without also changing the ; other timing critical routines below. addr_wait inc ix ; 12 cycles <-- this subset ld a,(hl) ; 8 <-- of the loop add a,a ; 5 <-- takes jp p,addr_end ; 11 <-- 47 jp c,addr_wait ; 11 <-- cycles ld a,(bc) ; 8 ld (de),a ; 8 inc de ; 7 jp addr_wait ; 11 addr_end ld a,(phil_status) and 16 jr nz,addr_error_ ; prefer to keep this a short jump ld a,ixl ld (de),a inc de ld a,ixh ld (de),a ; store counter inc de inc ix inc ix cp #c0 jp c,addr_loop jp addr_done addr_error_ jp addr_error ; 5) rd_track phil_rd_trck ld de,trck_buffer ld hl,phil_stat2 ; irq/dtrq ld bc,phil_data ld ix,0 ld a,#e0 ; read track ld (phil_command),a track_wait2 ld a,(hl) add a,a jp p,track_err jp nc,track_first inc ix ld a,ixh inc a jp nz,track_wait2 jp track_no_start track_wait ld a,(hl) add a,a jp p,track_end jp c,track_wait track_first ld a,(bc) ld (de),a inc de ld a,d cp #be jp c,track_wait jp track_too_much ; 6) rd_sector phil_rd_sector ld a,#d0 ; Forced interrupt command ld (phil_command),a ; otherwise we don't see the index ; pulse status bits ld a,b ld (phil_track),a ld a,c ld (phil_sector),a ld bc,phil_data ld ix,0 ld a,(read_speed) or a jr nz,fast_read_1 wait_index_a2 ld a,(phil_status) and 2 ; wait till index pulse = 0 jr nz,wait_index_a2 wait_index_b2 ld a,(phil_status) and 2 ; wait till index pulse = 1 jr z,wait_index_b2 ; This loop is tuned for an exact number of Z80 cycles! delay_loop dec hl ; 7 cycles ld a,(0) ; dummy read 14 cycles nop ; 5 cycles ld a,h ; 5 or l ; 5 jp nz,delay_loop ; 11 together 47 cycles fast_read_1 ld hl,phil_stat2 ; irq/dtrq ld a,#80 ; read sector ld (phil_command),a sector_wait1 inc ix ; count till first byte is received ld a,(hl) add a,a jp p,sector_err ; command stopped before we got 1st byte jp c,sector_wait1 ld a,(bc) ld (de),a inc de sector_wait2 ld a,(hl) add a,a jp p,sector_end_ jp c,sector_wait2 ld a,(bc) ld (de),a inc de jp sector_wait2 sector_end_ ld a,(phil_status) jp sector_end ;--- ; National natl_status equ #7fb8 natl_command equ #7fb8 natl_track equ #7fb9 natl_sector equ #7fba natl_data equ #7fbb natl_control equ #7fbc natl_stat2 equ #7fbc natl_driver dw natl_select dw natl_deselect dw natl_seek dw natl_rd_addr dw natl_rd_trck dw natl_rd_sector dw 13834 dw 15290 ; 1) select natl_select ld a,(#f348) ld h,#40 call #0024 ; select FDC slot in page 1 ld a,(side) rlca rlca ; bit 2 = side or #09 ; motor on, drive A ld (natl_control),a ret ; 2) deselct natl_deselect xor a ld (natl_control),a ret ; 3) seek natl_seek natl_wait_3 ld a,(natl_status) and 1 jr nz,natl_wait_3 ld a,#0b ; restore, load head ld (natl_command),a call delay natl_wait_4 ld a,(natl_status) and 1 jr nz,natl_wait_4 ld a,(cylinder) ld (natl_data),a ; track ld a,#18 ; seek ld (natl_command),a ret ; 4) rd_addr natl_rd_addr: ld de,addr_buffer ld ix,0 ; counter ld hl,natl_stat2 ; irq/dtrq ld bc,natl_data natl_busy_1 ld a,(natl_status) and 1 jr nz,natl_busy_1 ld a,#d0 ; Forced interrupt command ld (natl_command),a ; otherwise we don't see the index ex (sp),hl ex (sp),hl ; needed? natl_index_a1 ld a,(natl_status) and 2 ; wait till index pulse = 0 jr nz,natl_index_a1 natl_index_b1 ld a,(natl_status) and 2 ; wait till index pulse = 1 jr z,natl_index_b1 natl_addr_loop ld a,#c0 ld (natl_command),a ; read addr ; Note: the timinig of this loop is important! Don't change ; the instructions (e.g. JP->JR) without also changing the ; other timing critical routines below. natl_addr_wait inc ix ; 12 cycles <-- this subset ld a,(hl) ; 8 <-- of the loop add a,a ; 5 <-- takes jp c,natl_addr_end ; 11 <-- 47 jp m,natl_addr_wait; 11 <-- cycles ld a,(bc) ; 8 ld (de),a ; 8 inc de ; 7 jp natl_addr_wait ; 11 natl_addr_end ld a,(natl_status) and 16 jr nz,natl_addr_err ; prefer to keep this a short jump ld a,ixl ld (de),a inc de ld a,ixh ld (de),a ; store counter inc de inc ix inc ix cp #c0 jp c,natl_addr_loop jp addr_done natl_addr_err jp addr_error ; 5) rd_track natl_rd_trck ld de,trck_buffer ld hl,natl_stat2 ; irq/dtrq ld bc,natl_data ld ix,0 ld a,#e0 ; read track ld (natl_command),a natl_trk_wt2 ld a,(hl) add a,a jp c,track_err jp p,natl_trk_first inc ix ld a,ixh inc a jp nz,natl_trk_wt2 jp track_no_start natl_trk_wait ld a,(hl) add a,a jp c,track_end jp m,natl_trk_wait natl_trk_first ld a,(bc) ld (de),a inc de ld a,d cp #be jp c,natl_trk_wait jp track_too_much ; 6) rd_sector natl_rd_sector ld a,#d0 ; Forced interrupt command ld (natl_command),a ; otherwise we don't see the index ; pulse status bits ld a,b ld (natl_track),a ld a,c ld (natl_sector),a ld bc,natl_data ld ix,0 ld a,(read_speed) or a jr nz,natl_fast_read natl_index_a2 ld a,(natl_status) and 2 ; wait till index pulse = 0 jr nz,natl_index_a2 natl_index_b2 ld a,(natl_status) and 2 ; wait till index pulse = 1 jr z,natl_index_b2 ; This loop is tuned for an exact number of Z80 cycles! natl_delay dec hl ; 7 cycles ld a,(0) ; dummy read 14 cycles nop ; 5 cycles ld a,h ; 5 or l ; 5 jp nz,natl_delay ; 11 together 47 cycles natl_fast_read ld hl,natl_stat2 ; irq/dtrq ld a,#80 ; read sector ld (natl_command),a natl_sect_wt1 inc ix ; count till first byte is received ld a,(hl) add a,a jp c,sector_err ; command stopped before we got 1st byte jp m,natl_sect_wt1 ld a,(bc) ld (de),a inc de natl_sect_wt2 ld a,(hl) add a,a jp c,natl_sect_end jp m,natl_sect_wt2 ld a,(bc) ld (de),a inc de jp natl_sect_wt2 natl_sect_end ld a,(natl_status) jp sector_end ;--- ; Microsol mics_status equ #d0 mics_command equ #d0 mics_track equ #d1 mics_sector equ #d2 mics_data equ #d3 mics_control equ #d4 mics_stat2 equ #d4 mics_driver dw mics_select dw mics_deselect dw mics_seek dw mics_rd_addr dw mics_rd_trck dw mics_rd_sector dw 14135 dw 15622 ; 1) select mics_select ld a,(side) rlca rlca rlca rlca ; bit 4 = side or #21 ; bit 5 = motor, bit 0 = drive A out (mics_control),a ret ; 2) deselct mics_deselect xor a ; no drive selected, motor off out (mics_control),a ret ; 3) seek mics_seek mics_busy_3 in a,(mics_status) and 1 jr nz,mics_busy_3 ld a,#0b ; restore, load head out (mics_command),a call delay mics_busy_4 in a,(mics_status) and 1 jr nz,mics_busy_4 ld a,(cylinder) out (mics_data),a ; track ld a,#18 ; seek out (mics_command),a ret ; 4) rd_addr mics_rd_addr: ld de,addr_buffer ld hl,0 ; counter mics_busy_1 in a,(mics_status) and 1 jr nz,mics_busy_1 ld a,#d0 ; Forced interrupt command out (mics_command),a ; otherwise we don't see the index ex (sp),hl ex (sp),hl ; needed? mics_index_a1 in a,(mics_status) and 2 ; wait till index pulse = 0 jr nz,mics_index_a1 mics_index_b1 in a,(mics_status) and 2 ; wait till index pulse = 1 jr z,mics_index_b1 mics_addr_loop ld a,#c0 ; read addr out (mics_command),a ; Note: the timinig of this loop is important! Don't change ; the instructions (e.g. JP->JR) without also changing the ; other timing critical routines below. mics_addr_wait inc hl ; 7 cycles <-- this in a,(mics_stat2) ; 12 <-- takes add a,a ; 5 <-- 46 jp c,mics_addr_end ; 11 <-- cycles jp m,mics_addr_wait ; 11 <-- in a,(mics_data) ; 12 ld (de),a ; 8 inc de ; 7 jp mics_addr_wait ; 11 mics_addr_end in a,(mics_status) and 16 ; record not found jr nz,mics_addr_err ; prefer to keep this a short jump ld a,l ld (de),a inc de ld a,h ld (de),a ; store counter inc de inc hl inc hl cp #c0 jp c,mics_addr_loop jp addr_done mics_addr_err jp addr_error ; 5) rd_track mics_rd_trck ld de,trck_buffer ld hl,0 ld a,#e0 ; read track out (mics_command),a mics_trk_wait2 in a,(mics_stat2) add a,a jp c,track_err jp p,mics_trk_first inc hl ld a,h inc a jp nz,mics_trk_wait2 jp track_no_start mics_trk_wait in a,(mics_stat2) add a,a jp c,track_end jp m,mics_trk_wait mics_trk_first in a,(mics_data) ld (de),a inc de ld a,d cp #be jp c,mics_trk_wait jp track_too_much ; 6) rd_sector mics_rd_sector ld a,#d0 ; Forced interrupt command out (mics_command),a ; otherwise we don't see the index ; pulse status bits ld a,b out (mics_track),a ld a,c out (mics_sector),a ld ix,0 ld a,(read_speed) or a jr nz,mics_fast_1 mics_index_a2 in a,(mics_status) and 2 ; wait till index pulse = 0 jr nz,mics_index_a2 mics_index_b2 in a,(mics_status) and 2 ; wait till index pulse = 1 jr z,mics_index_b2 ; This loop is tuned for an exact number of Z80 cycles! mics_delay dec hl ; 7 cycles cp 0 ; 8 (dummy compare) nop ; 5 nop ; 5 ld a,h ; 5 or l ; 5 jp nz,mics_delay ; 11 together 46 cycles mics_fast_1 ld a,#80 ; read sector out (mics_command),a mics_sctr_wt1 inc ix ; count till first byte is received in a,(mics_stat2) add a,a jp c,sector_err ; command stopped before we got 1st byte jp m,mics_sctr_wt1 in a,(mics_data) ld (de),a inc de mics_sctr_wt2 in a,(mics_stat2) add a,a jp c,mics_sctr_end jp m,mics_sctr_wt2 in a,(mics_data) ld (de),a inc de jp mics_sctr_wt2 mics_sctr_end in a,(mics_status) jp sector_end ; ----- ofst_tab: dw -6,-7,-5,-8,-4,-9,-3,-10,-2,-11,-1,-12,0 dw -13,1,-14,2,-15,3,-16,4,-17,5,-18,6,-19,7 dw -20,8,-21,9,-22,10,-23,11,-24,12,-25,13,-26 dw 14,-27,15,-28,16,-29,17,-30,18,-31,29,-32 dw 20,-33,21,-34,22,-35,23,-36,24,-37,25,-38 dw 26,-39,27,-40,28,-41,29,-42,30,-43,31,-44 dw #8080 addr_mark db #A1,#A1,#A1,#FE debug db 0 start_track db 0 ; initial value matters stop_track db 81 ; by default we try 2 extra cylinders stop_set db 0 fcb db 2 ; drive B db "DMK-TT-S" ; filename db "DAT" ; extension ds 37-12 fcb_debug db 2 ; drive B db "DEBUG " ; filename db "DAT" ; extension ds 37-12 ; struct Addr_Mark ; byte C ; byte H ; byte R ; byte N ; word CRC ; word ticks sizeof_amark equ 8 ofst_amark_C equ 0 ofst_amark_H equ 1 ofst_amark_R equ 2 ofst_amark_N equ 3 ofst_amark_CRC equ 4 ofst_amark_tick equ 6 ; struct Offset_Info ; word address_mark_offset ; word data_mark_offset ; byte data_mark_type ; byte read_sector_status ; word sector_size sizeof_offinfo equ 10 ofst_oi_addr equ 0 ofst_oi_data equ 2 ofst_oi_type equ 4 ofst_oi_status equ 5 ofst_oi_size equ 6 ofst_oi_ptr equ 8 sizeof_off_buf equ 64 * sizeof_offinfo sizeof_dmk_h equ 128 cylinder equ #8000 ; db 0 0 side equ cylinder+ 1 ; db 0 1 retries equ side+1 ; db 0 2 nb_sectors equ retries+1 ; dw 0 3 track_len equ nb_sectors+2 ; dw 0 5 track_stop equ track_len+2 ; dw 0 7 ticks equ track_stop+2 ; dw 0 9 ratio equ ticks+2 ; dw 0 11 crc_retries equ ratio+2 ; db 0 13 adjust_ofst equ crc_retries+1 ; dw 0 14 adjust_scale equ adjust_ofst+2 ; dw 0 16 sector_cntr equ adjust_scale+2 ; db 0 18 sector_stop equ sector_cntr+1 ; dw 0 19 addr_buf_ptr equ sector_stop+2 ; dw 0 21 ofst_buf_ptr equ addr_buf_ptr+2 ; dw 0 23 dmk_ptr equ ofst_buf_ptr+2 ; dw 0 25 addr_estimate equ dmk_ptr+2 ; dw 0 27 read_speed equ addr_estimate+2 ; db 0 29 sector_buf_ptr equ read_speed+1 ; dw 0 30 crc_status equ sector_buf_ptr+2; db 0 32 debug_ptr equ crc_status+1 ; dw 0 33 sector_size equ debug_ptr+2 ; dw 0 35 addr_buf_end equ sector_size+2 ; dw 0 37 addr_retries equ addr_buf_end+2 ; db 0 39 addr_buffer equ #8100 offset_buffer equ addr_buffer+64*sizeof_amark ; can overlap part of addr_buffer dmk_header equ offset_buffer+sizeof_off_buf trck_buffer equ dmk_header+sizeof_dmk_h sector_buffer equ trck_buffer+#1A00 ; overlaps 2nd copy of track`` unique_buffer equ #BE00 ; possibly overlaps sector buffer (that's OK) ; must start at a 256-byte boundary debug_buffer equ #BF00 ; must be 256-bytes aligned openMSX-RELEASE_0_12_0/Contrib/dmk/READ-DMK_instructions.txt000066400000000000000000000055211257557151200233260ustar00rootroot00000000000000Instructions: * It only works on MSX systems with 2 disk drives * Your disk drive controller needs to be WD2793 compatible * The output of the tool is a DAT file for each cylinder/head combination. Typically that means 160 DAT files for a 720kB DS DD disk How to make a DMK file (happy flow): * Start the tool from MSX-DOS. Extra command line options below. * As the tool instructs you: put the disk to dump in drive A and the destination disk in drive B. * Not all DAT files will fit on a single MSX disk. Swap disks when the tool reports a write error when saving the DAT file. You can use 2 different destination disks or first copy the DAT files to another place and erase the destination disk to continue. * Get the DAT files to your PC. You'll need a PC program to combine the DAT files into a single DMK disk image. A Windows binary can be found here: http://openmsx.org/temp/combine-dmk.exe (source code is in this directory). Run this tool in a command shell from a directory where you put all the DAT files created by the read-dmk tool. It will output the file out.dmk, which is the DMK disk image which works in openMSX. If the tool gives any error, please let us know. * You can test the resulting DMK image on openMSX Command line options: * type=[PHILIPS|NATIONAL|MICROSOL]: specify the way the FDC registers are mapped into memory or I/O ports. Philips is mostly for the Philips and Sony machines/drives (e.g. Sony HBK-30/HBD-F1/HBD-50, Sharp HB-3600, Philips VY-0010, Philips NMS 8245/8250/8255/8280, Philips VG 8230/35, Sony HB-F1XD/F900/F1XDJ/F700x/G900/F500, Sanyo MPC-35FD), National for National and some others (e.g. National FS-5500F2/4700/4600/5000/5500F1, National CF-3300, Yamaha AX350II, Daewoo CPC-400S, Gradiente Expert DDPlus, Spectrtavideo SVI-738, Yamaha YIS-805/128R2 and probably Talent TPC-310) and Microsol for the port-based ones often seen in Brazil (like Microsol CDX-2). If you don't specify this option, the program will assume you use the Philips type. Note that only the Philips type has been tested on real hardware so far... (feedback welcome!) * debug=1: enable debugging mode. When dumping fails, the tool will write a DEBUG.DAT file. Please provide us with a photo of your MSX screen and the DEBUG.DAT file when dumping fails, so we can improve the tool :) * start=NN: specify start cylinder (use if you don't want to start from the beginning of the disk, first cylinder is 0) * stop=NN: specify stop cylinder (use if you don't need the whole disk dumped, stop earlier than end-of-disk detection finds) Example: read-dmk type=national debug=1 start=38 end=38 This will dump only cylinder 38 (for both sides, so 2 DAT files) with debug enabled and for national-type disk drives (see above). Usually you don't specify any options, except for the type if you don't have a Philips-like drive. openMSX-RELEASE_0_12_0/Contrib/dmk/analyze-dmk.cc000066400000000000000000000136021257557151200213370ustar00rootroot00000000000000#include #include #include #include #include #include using namespace std; typedef unsigned char byte; typedef unsigned short word; struct DmkHeader { byte writeProtected; byte numTracks; byte trackLen[2]; byte flags; byte reserved[7]; byte format[4]; }; class File { public: File(const string& filename, const char* mode) : f(fopen(filename.c_str(), mode)) { if (!f) { throw runtime_error("Couldn't open: " + filename); } } ~File() { fclose(f); } void read(void* data, int size) { if (fread(data, size, 1, f) != 1) { throw runtime_error("Couldn't read file"); } } private: FILE* f; }; // global variables for circular buffer vector buffer; int dmkTrackLen; byte readCircular(int idx) { return buffer[128 + idx % (dmkTrackLen - 128)]; } static void updateCrc(word& crc, byte val) { for (int i = 8; i < 16; ++i) { crc = (crc << 1) ^ ((((crc ^ (val << i)) & 0x8000) ? 0x1021 : 0)); } } bool isValidDmkHeader(const DmkHeader& header) { if (!((header.writeProtected == 0x00) || (header.writeProtected == 0xff))) { return false; } int trackLen = header.trackLen[0] + 256 * header.trackLen[1]; if (trackLen >= 0x4000) return false; // too large track length if (trackLen <= 128) return false; // too small if (header.flags & ~0xd0) return false; // unknown flag set const byte* p = header.reserved; for (int i = 0; i < 7 + 4; ++i) { if (p[i] != 0) return false; } return true; } void analyzeTrack() { for (int i = 0; i < 64; ++i) { // Get (and check) pointer into track data int dmkIdx = buffer[2 * i + 0] + 256 * buffer[2 * i + 1]; if (dmkIdx == 0) { // end of table reached break; } printf("%2d: ", i); if ((dmkIdx & 0xC000) != 0x8000) { printf("... skipping single-density sector\n"); continue; } dmkIdx &= ~0xC000; // clear flags if ((dmkIdx < 128) || (dmkIdx >= dmkTrackLen)) { printf("... skipping invalid IDAM offset (wrong DMK file)\n"); continue; } dmkIdx -= 128; // read (and check) address mark int addrIdx = dmkIdx - 3; // might be negative byte d0 = readCircular(addrIdx + 0); byte d1 = readCircular(addrIdx + 1); byte d2 = readCircular(addrIdx + 2); byte d3 = readCircular(addrIdx + 3); byte c = readCircular(addrIdx + 4); byte h = readCircular(addrIdx + 5); byte r = readCircular(addrIdx + 6); byte n = readCircular(addrIdx + 7); byte ch = readCircular(addrIdx + 8); byte cl = readCircular(addrIdx + 9); if ((d0 != 0xA1) || (d1 != 0xA1) || (d2 != 0xA1) || (d3 != 0xFE)) { printf("... skipping wrong IDAM entry, does not point to an address mark\n"); continue; } // address mark CRC word addrCrc = 0xFFFF; updateCrc(addrCrc, d0); updateCrc(addrCrc, d1); updateCrc(addrCrc, d2); updateCrc(addrCrc, d3); updateCrc(addrCrc, c); updateCrc(addrCrc, h); updateCrc(addrCrc, r); updateCrc(addrCrc, n); int onDiskAddrCrc = 256 * ch + cl; bool addrCrcErr = onDiskAddrCrc != addrCrc; // print address mark info printf("AOfst=%4d C=%3d H=%3d R=%3d N=%3d ACrc=%04x,%s", addrIdx, c, h, r, n, onDiskAddrCrc, (addrCrcErr ? "ERR\n" : "ok ")); if (onDiskAddrCrc != addrCrc) { continue; } // locate data mark, should be within 43 bytes from end // of address mark (according to WD2793 datasheet) int j; for (j = 10; j < 53; ++j) { int dataIdx = addrIdx + j; byte a0 = readCircular(dataIdx + 0); byte a1 = readCircular(dataIdx + 1); byte a2 = readCircular(dataIdx + 2); byte t = readCircular(dataIdx + 3); if ((a0 != 0xA1) || (a1 != 0xA1) || (a2 != 0xA1)) { continue; } // calculate data CRC, data mark part word dataCrc = 0xFFFF; updateCrc(dataCrc, a0); updateCrc(dataCrc, a1); updateCrc(dataCrc, a2); updateCrc(dataCrc, t); // actual sector data int sectorSize = 128 << (n & 7); for (int j = 0; j < sectorSize; ++j) { byte d = readCircular(dataIdx + 4 + j); updateCrc(dataCrc, d); } byte crc1 = readCircular(dataIdx + 4 + sectorSize + 0); byte crc2 = readCircular(dataIdx + 4 + sectorSize + 1); int onDiskDataCrc = 256 * crc1 + crc2; bool dataCrcErr = onDiskDataCrc != dataCrc; char type = (t == 0xFB) ? 'n' : (t == 0xF8) ? 'd' : '?'; printf(" DOfst=%4d T=%c DCrc=%04x,%s\n", dataIdx, type, onDiskDataCrc, (dataCrcErr ? "ERR" : "ok ")); break; } if (j == 53) { printf(" data mark not found within 43 bytes from address mark\n"); } } } void analyzeDisk(const string& input) { File inf(input, "rb"); DmkHeader header; inf.read(&header, sizeof(header)); if (!isValidDmkHeader(header)) { throw runtime_error("Invalid DMK header"); } int numCylinders = header.numTracks; int numSides = (header.flags & 0x10) ? 1 : 2; dmkTrackLen = header.trackLen[0] + 256 * header.trackLen[1]; buffer.resize(dmkTrackLen); printf("Legend:\n" " AOfst: address mark offset in track\n" " C: cylinder\n" " H: head\n" " R: record (sector number)\n" " N: sector size (in bytes: 128 << N)\n" " ACrc: CRC value of the address block\n" " DOfst: data mark offset in track\n" " T: data mark type (n = normal, d = deleted, ? = unknown)\n" " DCrc: CRC value of data block\n" "\n"); printf("Raw track length = %d bytes\n\n", dmkTrackLen - 128); for (int t = 0; t < numCylinders; ++t) { for (int h = 0; h < numSides; ++h) { printf("-- physical track %d, head %d\n", t, h); inf.read(&buffer[0], dmkTrackLen); analyzeTrack(); } } } int main(int argc, char** argv) { if (argc != 2) { printf("analyze-dmk\n" "\n" "Analyze the content of a DMK disk image.\n" "\n" "usage: %s \n", argv[0]); exit(1); } try { analyzeDisk(argv[1]); } catch (std::exception& e) { fprintf(stderr, "Error: %s\n", e.what()); } } openMSX-RELEASE_0_12_0/Contrib/dmk/combine-dmk.cc000066400000000000000000000216621257557151200213150ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include using namespace std; typedef unsigned char byte; typedef unsigned short word; struct DmkHeader { byte writeProtected; byte numTracks; byte trackLen[2]; byte flags; byte reserved[7]; byte format[4]; }; /////// class Gaps { public: Gaps(int totalSize); void addInterval(int start, int stop); int getLargestGap(); private: void addUse2(int start, int stop); const int totalSize; vector v; }; Gaps::Gaps(int totalSize_) : totalSize(totalSize_) { } // [start, stop) void Gaps::addInterval(int start, int stop) { start %= totalSize; stop %= totalSize; if (start < stop) { addUse2(start, stop); } else if (start > stop) { addUse2(start, totalSize); if (stop != 0) addUse2(0, stop); } } void Gaps::addUse2(int start, int stop) { assert(start < stop); assert(stop <= totalSize); v.push_back(2 * start + 0); v.push_back(2 * stop + 1); } int Gaps::getLargestGap() { if (v.empty()) return totalSize / 2; // sort begin and end point sort(v.begin(), v.end()); // largest gap found so far (and its start and stop position) int maxLen = 0; int maxStart = 0; int maxStop = 0; // last end point is start of first gap int start = v.back() / 2; if (start == totalSize) start = 0; int count = 0; for (vector::const_iterator it = v.begin(); it != v.end(); ++it) { int i = *it; if (i & 1) { // interval end point --count; // no more overlapping intervals -> start of new gap if (count == 0) start = i / 2; } else { // interval begin point int stop = i / 2; if (count == 0) { // found gap: [start, stop) int len = stop - start; if (len < 0) len += totalSize; if (len > maxLen) { maxLen = len; maxStart = start; maxStop = stop; } } ++count; } } assert(count == 0); if (maxLen == 0) return -1; if (maxStop < maxStart) maxStop += totalSize; int mid = (maxStart + maxStop) / 2; return mid % totalSize; } /////// static byte readCircular(const vector& buffer, int idx) { int dmkTrackLen = buffer.size(); return buffer[128 + idx % (dmkTrackLen - 128)]; } static void updateCrc(word& crc, byte val) { for (int i = 8; i < 16; ++i) { crc = (crc << 1) ^ ((((crc ^ (val << i)) & 0x8000) ? 0x1021 : 0)); } } static void verifyDMK(bool b, const char* message) { if (!b) { fprintf(stderr, "Invalid input: %s\n", message); exit(1); } } static int analyzeTrack(vector& buffer) { int dmkTrackLen = buffer.size(); int trackLen = dmkTrackLen - 128; Gaps gaps(trackLen); for (int i = 0; i < 64; ++i) { // Get (and check) pointer into track data int dmkIdx = buffer[2 * i + 0] + 256 * buffer[2 * i + 1]; if (dmkIdx == 0) { // end of table reached break; } verifyDMK((dmkIdx & 0xC000) == 0x8000, "double density flag"); dmkIdx &= ~0xC000; // clear flags verifyDMK(dmkIdx >= 128, "IDAM offset too small"); verifyDMK(dmkIdx < dmkTrackLen, "IDAM offset too large"); dmkIdx -= 128; // read address mark int addrIdx = dmkIdx - 3; // might be negative byte d0 = readCircular(buffer, addrIdx + 0); byte d1 = readCircular(buffer, addrIdx + 1); byte d2 = readCircular(buffer, addrIdx + 2); byte d3 = readCircular(buffer, addrIdx + 3); byte c = readCircular(buffer, addrIdx + 4); byte h = readCircular(buffer, addrIdx + 5); byte r = readCircular(buffer, addrIdx + 6); byte n = readCircular(buffer, addrIdx + 7); byte ch = readCircular(buffer, addrIdx + 8); byte cl = readCircular(buffer, addrIdx + 9); // address mark CRC word addrCrc = 0xFFFF; updateCrc(addrCrc, d0); updateCrc(addrCrc, d1); updateCrc(addrCrc, d2); updateCrc(addrCrc, d3); updateCrc(addrCrc, c); updateCrc(addrCrc, h); updateCrc(addrCrc, r); updateCrc(addrCrc, n); word onDiskAddrCrc = 256 * ch + cl; if (onDiskAddrCrc != addrCrc) { // only mark address mark as in-use gaps.addInterval(addrIdx, addrIdx + 10); continue; } // locate data mark, should be within 43 bytes from end // of address mark (according to WD2793 datasheet) for (int i = 10; i < 53; ++i) { int dataIdx = addrIdx + i; byte a0 = readCircular(buffer, dataIdx + 0); byte a1 = readCircular(buffer, dataIdx + 1); byte a2 = readCircular(buffer, dataIdx + 2); byte t = readCircular(buffer, dataIdx + 3); if ((a0 != 0xA1) || (a1 != 0xA1) || (a2 != 0xA1) || ((t != 0xFB) && (t != 0xF8))) { continue; } // Mark whole region from begin of address mark to end // of data as in-use (so including the gap between // address and data section). int sectorSize = 128 << (n & 3); int end = dataIdx + 4 + sectorSize + 2; // header + data + crc gaps.addInterval(addrIdx, end); break; } // if (i == 53) --> data mark not found } return gaps.getLargestGap(); } int main() { vector > data; // buffer all .DAT files string name = "DMK-tt-s.DAT"; for (int t = 0; t <= 99; ++t) { for (int h = 0; h < 2; ++h) { name[4] = (t / 10) + '0'; name[5] = (t % 10) + '0'; name[7] = h + '0'; FILE* file = fopen(name.c_str(), "rb"); if (!file) { if (h == 0) goto done_read; fprintf(stderr, "Couldn't open file %s, but the " "corresponding file for side 0 was " "found.\n", name.c_str()); exit(1); } struct stat st; fstat(fileno(file), &st); size_t size = st.st_size; if (size < 128) { fprintf(stderr, "File %s is too small.\n", name.c_str()); exit(1); } vector dat(size); if (fread(dat.data(), size, 1, file) != 1) { fprintf(stderr, "Error reading file %s.\n", name.c_str()); exit(1); } data.push_back(dat); } } done_read: assert((data.size() & 1) == 0); int numTracks = data.size() / 2; // Check that no .dat files with higher track number are found. for (int t = numTracks; t <= 99; ++t) { for (int h = 0; h < 2; ++h) { name[4] = (t / 10) + '0'; name[5] = (t % 10) + '0'; name[7] = h + '0'; FILE* file = fopen(name.c_str(), "rb"); if (!file) continue; // ok, we should have this file string name2 = "DMK-tt-0.DAT"; name2[4] = (numTracks / 10) + '0'; name2[5] = (numTracks % 10) + '0'; fprintf(stderr, "Found file %s, but file %s is missing.\n", name.c_str(), name2.c_str()); exit(1); } } printf("Found .dat files for %d tracks (double sided).\n", numTracks); // Create histogram of tracklengths. map sizes; // length, count for (vector >::iterator it = data.begin(); it != data.end(); ++it) { unsigned size = it->size(); ++sizes[size]; } // Search the peak in this histogram (= the tracklength that occurs // most often). unsigned maxCount = 0; unsigned trackSize = 0; for (map::const_iterator it = sizes.begin(); it != sizes.end(); ++it) { if (it->second >= maxCount) { maxCount = it->second; trackSize = it->first; } } // Open output file. FILE* file = fopen("out.dmk", "wb+"); if (!file) { fprintf(stderr, "Couldn't open output file out.dmk.\n"); exit(1); } // Write DMK header. DmkHeader header; memset(&header, 0, sizeof(header)); header.numTracks = numTracks; header.trackLen[0] = trackSize % 256; header.trackLen[1] = trackSize / 256; if (fwrite(&header, sizeof(header), 1, file) != 1) { fprintf(stderr, "Error writing out.dmk.\n"); exit(1); } // Process each track. for (vector >::iterator it = data.begin(); it != data.end(); ++it) { vector& v = *it; // Adjust track size while (v.size() != trackSize) { // Locate (middle of) largest gap in this track. int mid = analyzeTrack(v) + 128; if (mid == -1) { fprintf(stderr, "Couldn't adjust track length.\n"); exit(1); } assert(mid < int(v.size())); // We insert or delete one byte at a time. This may not // be the most efficient approach (but still more than // fast enough, we typically only nned to adjust a few // bytes anyway). This has the advantage of being very // simple: it can easily handle gaps that wrap around // from the end to the beginning of the track and it // can handle the case that after decreasing a gap with // a few bytes another gaps becomes the largest gap. int delta; if (trackSize > v.size()) { delta = 1; v.insert(v.begin() + mid, v[mid]); } else { delta = -1; v.erase(v.begin() + mid); } // After inserting/deleting byte(s), we need to adjust // the IDAM table. for (int i = 0; i < 64; ++i) { int t = v[2 * i + 0] + 256 * v[2 * i + 1]; if (t == 0) break; if ((t & 0x3fff) > mid) t += delta; v[2 * i + 0] = t % 256; v[2 * i + 1] = t / 256; } } // Write track to DMK file. if (fwrite(v.data(), v.size(), 1, file) != 1) { fprintf(stderr, "Error writing out.dmk.\n"); exit(1); } } if (fclose(file)) { fprintf(stderr, "Error closing out.dmk.\n"); exit(1); } printf("Successfully wrote out.dmk.\n"); exit(0); } openMSX-RELEASE_0_12_0/Contrib/dmk/der2dmk.cc000066400000000000000000000133631257557151200204570ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include using namespace std; typedef unsigned char byte; // 8 bit typedef unsigned short word; // 16 bit struct DiskInfo { int gap1; int gap2; int gap3; int gap4a; int gap4b; int sectorsPerTrack; int numberCylinders; int sectorSizeCode; bool doubleSided; }; struct DmkHeader { byte writeProtected; byte numTracks; byte trackLen[2]; byte flags; byte reserved[7]; byte format[4]; }; class File { public: File(const string& filename, const char* mode) : f(fopen(filename.c_str(), mode)) { if (!f) { throw runtime_error("Couldn't open: " + filename); } } ~File() { fclose(f); } void read(void* data, int size) { if (fread(data, size, 1, f) != 1) { throw runtime_error("Couldn't read file"); } } void write(const void* data, int size) { if (fwrite(data, size, 1, f) != 1) { throw runtime_error("Couldn't write file"); } } private: FILE* f; }; static void updateCrc(word& crc, byte val) { for (int i = 8; i < 16; ++i) { crc = (crc << 1) ^ ((((crc ^ (val << i)) & 0x8000) ? 0x1021 : 0)); } } static void fill(byte*& p, int len, byte value) { memset(p, value, len); p += len; } void convert(const DiskInfo& info, const string& dsk, const string& der, const string& dmk) { int numSides = info.doubleSided ? 2 : 1; int sectorSize = 128 << info.sectorSizeCode; int totalTracks = numSides * info.numberCylinders; int totalSectors = totalTracks * info.sectorsPerTrack; int totalSize = totalSectors * sectorSize; struct stat st; stat(dsk.c_str(), &st); if (st.st_size != totalSize) { throw runtime_error("Wrong input filesize"); } File inf (dsk, "rb"); File derf(der, "rb"); File outf(dmk, "wb"); static const char* const DER_HEADER = "DiskImage errors\r\n\032"; char derHeader[20]; derf.read(derHeader, 20); if (memcmp(derHeader, DER_HEADER, 20) != 0) { throw runtime_error("Invalid .der file."); } int derSize = (totalSectors + 7) / 8; vector derBuf(derSize); derf.read(derBuf.data(), derSize); int rawSectorLen = 12 + 10 + info.gap2 + 12 + 4 + sectorSize + 2 + info.gap3; int rawTrackLen = info.gap4a + 12 + 4 + info.gap1 + info.sectorsPerTrack * rawSectorLen + info.gap4b; assert(rawTrackLen == 6250); int dmkTrackLen = rawTrackLen + 128; DmkHeader header; memset(&header, 0, sizeof(header)); header.numTracks = info.numberCylinders; header.trackLen[0] = dmkTrackLen & 0xff; header.trackLen[1] = dmkTrackLen >> 8; header.flags = (info.doubleSided ? (0 << 4) : (1 << 4)) | (0 << 6); // double density (MFM) outf.write(&header, sizeof(header)); vector addrPos(info.sectorsPerTrack); vector dataPos(info.sectorsPerTrack); vector buf(dmkTrackLen); // zero-initialized byte* ip = &buf[ 0]; // pointer in IDAM table byte* tp = &buf[128]; // pointer in actual track data fill(tp, info.gap4a, 0x4e); // gap4a fill(tp, 12, 0x00); // sync fill(tp, 3, 0xc2); // index mark fill(tp, 1, 0xfc); // fill(tp, info.gap1, 0x4e); // gap1 for (int sec = 0; sec < info.sectorsPerTrack; ++sec) { fill(tp, 12, 0x00); // sync fill(tp, 3, 0xa1); // ID addr mark int pos = tp - &buf[0]; assert(pos < 0x4000); *ip++ = pos & 0xff; *ip++ = (pos >> 8) | 0x80; // double density (MFM) sector fill(tp, 1, 0xfe); // ID addr mark (cont) addrPos[sec] = tp; fill(tp, 6, 0x00); // C H R N CRC (overwritten later) fill(tp, info.gap2, 0x4e); // gap2 fill(tp, 12, 0x00); // sync fill(tp, 3, 0xa1); // data mark fill(tp, 1, 0xfb); // dataPos[sec] = tp; fill(tp, sectorSize, 0x00); // sector data (overwritten later) fill(tp, 2, 0x00); // CRC (overwritten later) fill(tp, info.gap3, 0x4e); // gap3 } fill(tp, info.gap4b, 0x4e); // gap4b assert((tp - &buf[0]) == dmkTrackLen); int derCount = 0; for (int cyl = 0; cyl < info.numberCylinders; ++cyl) { for (int head = 0; head < numSides; ++head) { for (int sec = 0; sec < info.sectorsPerTrack; ++sec) { byte* ap = addrPos[sec]; *ap++ = cyl; *ap++ = head; *ap++ = sec + 1; *ap++ = info.sectorSizeCode; word addrCrc = 0xffff; const byte* t1 = ap - 8; for (int i = 0; i < 8; ++i) { updateCrc(addrCrc, t1[i]); } *ap++ = addrCrc >> 8; *ap++ = addrCrc & 0xff; byte* dp = dataPos[sec]; inf.read(dp, sectorSize); dp += sectorSize; word dataCrc = 0xffff; const byte* t2 = dp - sectorSize - 4; for (int i = 0; i < sectorSize + 4; ++i) { updateCrc(dataCrc, t2[i]); } if (derBuf[derCount / 8] & (0x80 >> (derCount % 8))) { // create CRC error for this sector dataCrc ^= 0xffff; } ++derCount; *dp++ = dataCrc >> 8; *dp++ = dataCrc & 0xff; } outf.write(&buf[0], dmkTrackLen); } } } int main(int argc, char** argv) { if (argc != 4) { printf("der2dmk\n" "\n" "Utility to convert a dsk disk image with associated disk\n" "error file into a dmk disk image. At the moment this utility\n" "is limited to 720kB double sided, double density dsk images.\n" "\n" "Usage: %s \n", argv[0]); exit(1); } // TODO add command line options to make these parameters configurable DiskInfo info; info.gap1 = 50; info.gap2 = 22; info.gap3 = 84; info.gap4a = 80; info.gap4b = 182; // TODO calculate from other values info.sectorsPerTrack = 9; info.numberCylinders = 80; info.sectorSizeCode = 2; // 512 = 128 << 2 info.doubleSided = true; try { convert(info, argv[1], argv[2], argv[3]); } catch (std::exception& e) { fprintf(stderr, "Error: %s\n", e.what()); } } openMSX-RELEASE_0_12_0/Contrib/dmk/dsk2dmk.cc000066400000000000000000000122571257557151200204670ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include using namespace std; typedef unsigned char byte; // 8 bit typedef unsigned short word; // 16 bit struct DiskInfo { int gap1; int gap2; int gap3; int gap4a; int gap4b; int sectorsPerTrack; int numberCylinders; int sectorSizeCode; bool doubleSided; }; struct DmkHeader { byte writeProtected; byte numTracks; byte trackLen[2]; byte flags; byte reserved[7]; byte format[4]; }; class File { public: File(const string& filename, const char* mode) : f(fopen(filename.c_str(), mode)) { if (!f) { throw runtime_error("Couldn't open: " + filename); } } ~File() { fclose(f); } void read(void* data, int size) { if (fread(data, size, 1, f) != 1) { throw runtime_error("Couldn't read file"); } } void write(const void* data, int size) { if (fwrite(data, size, 1, f) != 1) { throw runtime_error("Couldn't write file"); } } private: FILE* f; }; static void updateCrc(word& crc, byte val) { for (int i = 8; i < 16; ++i) { crc = (crc << 1) ^ ((((crc ^ (val << i)) & 0x8000) ? 0x1021 : 0)); } } static void fill(byte*& p, int len, byte value) { memset(p, value, len); p += len; } void convert(const DiskInfo& info, const string& input, const string& output) { int numSides = info.doubleSided ? 2 : 1; int sectorSize = 128 << info.sectorSizeCode; int totalTracks = numSides * info.numberCylinders; int totalSectors = totalTracks * info.sectorsPerTrack; int totalSize = totalSectors * sectorSize; struct stat st; stat(input.c_str(), &st); if (st.st_size != totalSize) { throw runtime_error("Wrong input filesize"); } File inf(input, "rb"); File outf(output, "wb"); int rawSectorLen = 12 + 10 + info.gap2 + 12 + 4 + sectorSize + 2 + info.gap3; int rawTrackLen = info.gap4a + 12 + 4 + info.gap1 + info.sectorsPerTrack * rawSectorLen + info.gap4b; assert(rawTrackLen == 6250); int dmkTrackLen = rawTrackLen + 128; DmkHeader header; memset(&header, 0, sizeof(header)); header.numTracks = info.numberCylinders; header.trackLen[0] = dmkTrackLen & 0xff; header.trackLen[1] = dmkTrackLen >> 8; header.flags = (info.doubleSided ? (0 << 4) : (1 << 4)) | (0 << 6); // double density (MFM) outf.write(&header, sizeof(header)); vector addrPos(info.sectorsPerTrack); vector dataPos(info.sectorsPerTrack); vector buf(dmkTrackLen); // zero-initialized byte* ip = &buf[ 0]; // pointer in IDAM table byte* tp = &buf[128]; // pointer in actual track data fill(tp, info.gap4a, 0x4e); // gap4a fill(tp, 12, 0x00); // sync fill(tp, 3, 0xc2); // index mark fill(tp, 1, 0xfc); // fill(tp, info.gap1, 0x4e); // gap1 for (int sec = 0; sec < info.sectorsPerTrack; ++sec) { fill(tp, 12, 0x00); // sync fill(tp, 3, 0xa1); // ID addr mark int pos = tp - &buf[0]; assert(pos < 0x4000); *ip++ = pos & 0xff; *ip++ = (pos >> 8) | 0x80; // double density (MFM) sector fill(tp, 1, 0xfe); // ID addr mark (cont) addrPos[sec] = tp; fill(tp, 6, 0x00); // C H R N CRC (overwritten later) fill(tp, info.gap2, 0x4e); // gap2 fill(tp, 12, 0x00); // sync fill(tp, 3, 0xa1); // data mark fill(tp, 1, 0xfb); // dataPos[sec] = tp; fill(tp, sectorSize, 0x00); // sector data (overwritten later) fill(tp, 2, 0x00); // CRC (overwritten later) fill(tp, info.gap3, 0x4e); // gap3 } fill(tp, info.gap4b, 0x4e); // gap4b assert((tp - &buf[0]) == dmkTrackLen); for (int cyl = 0; cyl < info.numberCylinders; ++cyl) { for (int head = 0; head < numSides; ++head) { for (int sec = 0; sec < info.sectorsPerTrack; ++sec) { byte* ap = addrPos[sec]; *ap++ = cyl; *ap++ = head; *ap++ = sec + 1; *ap++ = info.sectorSizeCode; word addrCrc = 0xffff; const byte* t1 = ap - 8; for (int i = 0; i < 8; ++i) { updateCrc(addrCrc, t1[i]); } *ap++ = addrCrc >> 8; *ap++ = addrCrc & 0xff; byte* dp = dataPos[sec]; inf.read(dp, sectorSize); dp += sectorSize; word dataCrc = 0xffff; const byte* t2 = dp - sectorSize - 4; for (int i = 0; i < sectorSize + 4; ++i) { updateCrc(dataCrc, t2[i]); } *dp++ = dataCrc >> 8; *dp++ = dataCrc & 0xff; } outf.write(&buf[0], dmkTrackLen); } } } int main(int argc, char** argv) { if (argc != 3) { printf("dsk2dmk\n" "\n" "Utility to convert a dsk disk image into a dmk disk\n" "image. At the moment this utility is limited to 720kB\n" "double sided, double density dsk images.\n" "\n" "Usage: %s \n", argv[0]); exit(1); } // TODO add command line options to make these parameters configurable DiskInfo info; info.gap1 = 50; info.gap2 = 22; info.gap3 = 84; info.gap4a = 80; info.gap4b = 182; // TODO calculate from other values info.sectorsPerTrack = 9; info.numberCylinders = 80; info.sectorSizeCode = 2; // 512 = 128 << 2 info.doubleSided = true; try { convert(info, argv[1], argv[2]); } catch (std::exception& e) { fprintf(stderr, "Error: %s\n", e.what()); } } openMSX-RELEASE_0_12_0/Contrib/dmk/empty-dmk.cc000066400000000000000000000020211257557151200210230ustar00rootroot00000000000000#include #include #include typedef unsigned char byte; struct DmkHeader { byte writeProtected; byte numTracks; byte trackLen[2]; byte flags; byte reserved[7]; byte format[4]; }; static const int RAW_TRACK_SIZE = 6250; // 250kbps, 300rpm int main(int argc, char** argv) { if (argc != 2) { printf("empty-dmk\n" "\n" "Utility to create an empty DMK disk image.\n" "The disk image is double sided and contains\n" "80 unformatted tracks.\n" "\n" "usage: %s \n", argv[0]); exit(1); } FILE* f = fopen(argv[1], "wb"); DmkHeader header; memset(&header, 0, sizeof(header)); header.numTracks = 80; header.trackLen[0] = (128 + RAW_TRACK_SIZE) & 255; header.trackLen[1] = (128 + RAW_TRACK_SIZE) >> 8; fwrite(&header, sizeof(header), 1, f); byte buf[128 + RAW_TRACK_SIZE]; memset(&buf[ 0], 0, 128); memset(&buf[128], 0x4e, RAW_TRACK_SIZE); for (int i = 0; i < 2 * 80; ++i) { fwrite(buf, sizeof(buf), 1, f); } fclose(f); } openMSX-RELEASE_0_12_0/Contrib/openmsx-complete.bash000066400000000000000000000010761257557151200222010ustar00rootroot00000000000000# This file enables openmsx specific tab-completion in the bash shell. # # To enable it system-wide, copy and rename this file to # /etc/bash_completion.d/openmsx # To enable it only for the current user, source this file from e.g. your # ~/.bashrc # file. # # This needs an openMSX version newer than openmsx-0.9.1 (TODO replace with # actual version number once that number is known). _openmsx_complete() { local cur tmp cur=${COMP_WORDS[COMP_CWORD]} tmp=$(openmsx -bash $3) COMPREPLY=($(compgen -W '$tmp' -- "$cur")) } complete -f -F _openmsx_complete openmsx openMSX-RELEASE_0_12_0/Contrib/openmsx-control-socket.cc000066400000000000000000000243471257557151200230150ustar00rootroot00000000000000/** * Example implementation for bidirectional communication with openMSX. * * requires: libxml2 * compile: * *nix: g++ `xml2-config --cflags` `xml2-config --libs` openmsx-control-socket.cc * win32: g++ `xml2-config --cflags` `xml2-config --libs` openmsx-control-socket.cc -lwsock32 */ #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #else #include #include #include #include #endif using std::cout; using std::endl; using std::deque; using std::string; using std::vector; class ReadDir { public: ReadDir(const std::string& directory) { dir = opendir(directory.c_str()); } ~ReadDir() { if (dir) { closedir(dir); } } dirent* getEntry() { if (!dir) { return 0; } return readdir(dir); } private: DIR* dir; }; static string getTempDir() { const char* result = NULL; if (!result) result = getenv("TMPDIR"); if (!result) result = getenv("TMP"); if (!result) result = getenv("TEMP"); if (!result) { #ifdef _WIN32 result = "C:/WINDOWS/TEMP"; #else result = "/tmp"; #endif } return result; } static string getUserName() { #ifdef _WIN32 return "default"; #else struct passwd* pw = getpwuid(getuid()); return pw->pw_name ? pw->pw_name : ""; #endif } class OpenMSXComm { public: // main loop void start(int sd); // send a command to openmsx void sendCommand(const string& command); private: // XML parsing call-back functions static void cb_start_element(OpenMSXComm* comm, const xmlChar* name, const xmlChar** attrs); static void cb_end_element(OpenMSXComm* comm, const xmlChar* name); static void cb_text(OpenMSXComm* comm, const xmlChar* chars, int len); void parseReply(const char** attrs); void parseLog(const char** attrs); void parseUpdate(const char** attrs); void doReply(); void doLog(); void doUpdate(); // commands being executed deque commandStack; // XML parsing enum State { START, TAG_OPENMSX, TAG_REPLY, TAG_LOG, TAG_UPDATE, } state; unsigned unknownLevel; string content; xmlSAXHandler sax_handler; xmlParserCtxt* parser_context; enum ReplyStatus { REPLY_UNKNOWN, REPLY_OK, REPLY_NOK } replyStatus; enum LogLevel { LOG_UNKNOWN, LOG_INFO, LOG_WARNING } logLevel; string updateType; string updateName; // communication with openmsx process int sd; }; void OpenMSXComm::cb_start_element(OpenMSXComm* comm, const xmlChar* name, const xmlChar** attrs) { if (comm->unknownLevel) { ++(comm->unknownLevel); return; } switch (comm->state) { case START: if (strcmp((const char*)name, "openmsx-output") == 0) { comm->state = TAG_OPENMSX; } else { ++(comm->unknownLevel); } break; case TAG_OPENMSX: if (strcmp((const char*)name, "reply") == 0) { comm->state = TAG_REPLY; comm->parseReply((const char**)attrs); } else if (strcmp((const char*)name, "log") == 0) { comm->state = TAG_LOG; comm->parseLog((const char**)attrs); } else if (strcmp((const char*)name, "update") == 0) { comm->state = TAG_UPDATE; comm->parseUpdate((const char**)attrs); } else { ++(comm->unknownLevel); } break; default: ++(comm->unknownLevel); break; } comm->content.clear(); } void OpenMSXComm::parseReply(const char** attrs) { replyStatus = REPLY_UNKNOWN; if (attrs) { for ( ; *attrs; attrs += 2) { if (strcmp(attrs[0], "result") == 0) { if (strcmp(attrs[1], "ok") == 0) { replyStatus = REPLY_OK; } else if (strcmp(attrs[1], "nok") == 0) { replyStatus = REPLY_NOK; } } } } } void OpenMSXComm::parseLog(const char** attrs) { logLevel = LOG_UNKNOWN; if (attrs) { for ( ; *attrs; attrs += 2) { if (strcmp(attrs[0], "level") == 0) { if (strcmp(attrs[1], "info") == 0) { logLevel = LOG_INFO; } else if (strcmp(attrs[1], "warning") == 0) { logLevel = LOG_WARNING; } } } } } void OpenMSXComm::parseUpdate(const char** attrs) { updateType = "unknown"; if (attrs) { for ( ; *attrs; attrs += 2) { if (strcmp(attrs[0], "type") == 0) { updateType = attrs[1]; } else if (strcmp(attrs[0], "name") == 0) { updateName = attrs[1]; } } } } void OpenMSXComm::cb_end_element(OpenMSXComm* comm, const xmlChar* name) { if (comm->unknownLevel) { --(comm->unknownLevel); return; } switch (comm->state) { case TAG_OPENMSX: comm->state = START; break; case TAG_REPLY: comm->doReply(); comm->state = TAG_OPENMSX; break; case TAG_LOG: comm->doLog(); comm->state = TAG_OPENMSX; break; case TAG_UPDATE: comm->doUpdate(); comm->state = TAG_OPENMSX; break; default: break; } } void OpenMSXComm::doReply() { switch (replyStatus) { case REPLY_OK: cout << "OK: "; break; case REPLY_NOK: cout << "ERR: "; break; } cout << commandStack.front() << endl; commandStack.pop_front(); if (!content.empty()) { cout << content << endl; } } void OpenMSXComm::doLog() { switch (logLevel) { case LOG_INFO: cout << "INFO: "; break; case LOG_WARNING: cout << "WARNING: "; break; } cout << content << endl; } void OpenMSXComm::doUpdate() { cout << "UPDATE: " << updateType << " " << updateName << " " << content << endl; } void OpenMSXComm::cb_text(OpenMSXComm* comm, const xmlChar* chars, int len) { switch (comm->state) { case TAG_REPLY: case TAG_LOG: case TAG_UPDATE: comm->content.append((const char*)chars, len); break; default: break; } } void OpenMSXComm::sendCommand(const string& command) { write(sd, "", 9); write(sd, command.c_str(), command.length()); write(sd, "", 10); commandStack.push_back(command); } void OpenMSXComm::start(int sd_) { sd = sd_; // init XML parser state = START; unknownLevel = 0; memset(&sax_handler, 0, sizeof(sax_handler)); sax_handler.startElement = (startElementSAXFunc)cb_start_element; sax_handler.endElement = (endElementSAXFunc) cb_end_element; sax_handler.characters = (charactersSAXFunc) cb_text; parser_context = xmlCreatePushParserCtxt(&sax_handler, this, 0, 0, 0); write(sd, "", 17); // event loop string command; // (partial) input from STDIN while (true) { char buf[4096]; fd_set rdfs; FD_ZERO(&rdfs); FD_SET(sd, &rdfs); FD_SET(STDIN_FILENO, &rdfs); select(sd + 1, &rdfs, NULL, NULL, NULL); if (FD_ISSET(sd, &rdfs)) { // data available from openMSX ssize_t size = read(sd, buf, 4096); if (size == 0) { // openmsx process died break; } xmlParseChunk(parser_context, buf, size, 0); } if (FD_ISSET(STDIN_FILENO, &rdfs)) { // data available from STDIN ssize_t size = read(STDIN_FILENO, buf, 4096); char* oldpos = buf; while (true) { char* pos = (char*)memchr(oldpos, '\n', size); if (pos) { unsigned num = pos - oldpos; command.append(oldpos, num); sendCommand(command); command.clear(); oldpos = pos + 1; size -= num + 1; } else { command.append(pos, size); break; } } } } // cleanup xmlFreeParserCtxt(parser_context); } static bool checkSocketDir(const string& dir) { struct stat st; if (stat(dir.c_str(), &st)) { // cannot stat return false; } if (!S_ISDIR(st.st_mode)) { // not a directory return false; } #ifndef _WIN32 // only do permission and owner checks on *nix if ((st.st_mode & 0777) != 0700) { // wrong permissions return false; } if (st.st_uid != getuid()) { // wrong uid return false; } #endif return true; } static bool checkSocket(const string& socket) { string dir = socket.substr(0, socket.find_last_of('/')); string name = socket.substr(socket.find_last_of('/') + 1); if (name.substr(0, 7) != "socket.") { // wrong name return false; } struct stat st; if (stat(socket.c_str(), &st)) { // cannot stat return false; } #ifdef _WIN32 if (!S_ISREG(st.st_mode)) { // not a regular file return false; } #else if (!S_ISSOCK(st.st_mode)) { // not a socket return false; } #endif #ifndef _WIN32 // only do permission and owner checks on *nix if ((st.st_mode & 0777) != 0600) { // check will be different on win32 (!= 777) thus actually useless // wrong permissions return false; } if (st.st_uid != getuid()) { // does this work on win32? is this check meaningful? // wrong uid return false; } #endif return true; } static void deleteSocket(const string& socket) { unlink(socket.c_str()); // ignore errors string dir = socket.substr(0, socket.find_last_of('/')); rmdir(dir.c_str()); // ignore errors } static int openSocket(const string& socketName) { if (!checkSocket(socketName)) { return -1; } #ifdef _WIN32 int port = -1; std::ifstream in(socketName.c_str()); in >> port; if (port == -1) { return -1; } int sd = socket(AF_INET, SOCK_STREAM, 0); if (sd == -1) { return -1; } sockaddr_in addr; memset((char*)&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); addr.sin_port = htons(port); #else int sd = socket(AF_UNIX, SOCK_STREAM, 0); if (sd == -1) { return -1; } sockaddr_un addr; addr.sun_family = AF_UNIX; strcpy(addr.sun_path, socketName.c_str()); #endif if (connect(sd, (sockaddr*)&addr, sizeof(addr)) == -1) { // It appears to be a socket but we cannot connect to it. // Must be a stale socket. Try to clean it up. deleteSocket(socketName); close(sd); return -1; } return sd; } void collectServers(vector& servers) { string dir = getTempDir() + "/openmsx-" + getUserName(); if (!checkSocketDir(dir)) { return; } ReadDir readDir(dir); while (dirent* entry = readDir.getEntry()) { string socketName = dir + '/' + entry->d_name; int sd = openSocket(socketName); if (sd != -1) { close(sd); servers.push_back(socketName); } } } int main() { #ifdef _WIN32 WSAData wsaData; WSAStartup(MAKEWORD(1, 1), &wsaData); #endif vector servers; collectServers(servers); if (servers.empty()) { cout << "No running openmsx found." << endl; return 0; } // TODO let the user pick one if there is more than 1 int sd = openSocket(servers.front()); OpenMSXComm comm; comm.start(sd); return 0; } openMSX-RELEASE_0_12_0/Contrib/openmsx-control-stdio.cc000066400000000000000000000227301257557151200226410ustar00rootroot00000000000000/** * Example implementation for bidirectional communication with openMSX. * * requires: libxml2 * compile: g++ `xml2-config --cflags` `xml2-config --libs` openmsx-control.cc */ #include #include #include #include #include #include #include using std::cout; using std::endl; using std::list; using std::string; class OpenMSXComm { public: // main loop void start(); // send a command to openmsx void sendCommand(const string& command); private: // therse methods get called when openMSX has send the corresponding tag void openmsx_cmd_ok(const string& msg); void openmsx_cmd_nok(const string& msg); void openmsx_cmd_info(const string& msg); void openmsx_cmd_warning(const string& msg); void openmsx_cmd_update(const string& name, const string& value); // XML parsing call-back functions static void cb_start_element(OpenMSXComm* comm, const xmlChar* name, const xmlChar** attrs); static void cb_end_element(OpenMSXComm* comm, const xmlChar* name); static void cb_text(OpenMSXComm* comm, const xmlChar* chars, int len); void parseReply(const char** attrs); void parseLog(const char** attrs); void parseUpdate(const char** attrs); void doReply(); void doLog(); void doUpdate(); void deprecated(); // commands being executed list commandStack; // XML parsing enum State { START, TAG_OPENMSX, TAG_REPLY, TAG_LOG, TAG_UPDATE, TAG_OK, TAG_NOK, TAG_INFO, TAG_WARNING, } state; unsigned unknownLevel; string content; xmlSAXHandler sax_handler; xmlParserCtxt* parser_context; enum ReplyStatus { REPLY_UNKNOWN, REPLY_OK, REPLY_NOK } replyStatus; enum LogLevel { LOG_UNKNOWN, LOG_INFO, LOG_WARNING } logLevel; enum UpdateType { UPDATE_UNKNOWN, UPDATE_LED } updateType; string updateName; // communication with openmsx process int fd_out; }; void OpenMSXComm::openmsx_cmd_ok(const string& msg) { const string& command = commandStack.front(); cout << "CMD: '" << command << "' executed" << endl; if (!msg.empty()) { cout << msg << endl; } commandStack.pop_front(); } void OpenMSXComm::openmsx_cmd_nok(const string& msg) { const string& command = commandStack.front(); cout << "CMD: '" << command << "' failed" << endl; if (!msg.empty()) { cout << msg << endl; } commandStack.pop_front(); } void OpenMSXComm::openmsx_cmd_info(const string& msg) { cout << "INFO: " << msg << endl; } void OpenMSXComm::openmsx_cmd_warning(const string& msg) { cout << "WARNING: " << msg << endl; } void OpenMSXComm::openmsx_cmd_update(const string& name, const string& value) { cout << "UPDATE: " << name << " " << value << endl; } void OpenMSXComm::deprecated() { static bool alreadyPrinted = false; if (!alreadyPrinted) { alreadyPrinted = true; cout << "The openMSX you're running still uses the old communication protocol." << endl << "Because of this some stuff will not work (LED status for example)." << endl << "Please upgrade to openMSX 0.4.0 or higher." << endl; } } void OpenMSXComm::cb_start_element(OpenMSXComm* comm, const xmlChar* name, const xmlChar** attrs) { if (comm->unknownLevel) { ++(comm->unknownLevel); return; } switch (comm->state) { case START: if (strcmp((const char*)name, "openmsx-output") == 0) { comm->state = TAG_OPENMSX; } else { ++(comm->unknownLevel); } break; case TAG_OPENMSX: if (strcmp((const char*)name, "reply") == 0) { comm->state = TAG_REPLY; comm->parseReply((const char**)attrs); } else if (strcmp((const char*)name, "log") == 0) { comm->state = TAG_LOG; comm->parseLog((const char**)attrs); } else if (strcmp((const char*)name, "update") == 0) { comm->state = TAG_UPDATE; comm->parseUpdate((const char**)attrs); } // backwards compatibilty stuff else if (strcmp((const char*)name, "ok") == 0) { comm->state = TAG_OK; comm->replyStatus = REPLY_OK; comm->deprecated(); } else if (strcmp((const char*)name, "nok") == 0) { comm->state = TAG_NOK; comm->replyStatus = REPLY_NOK; comm->deprecated(); } else if (strcmp((const char*)name, "info") == 0) { comm->state = TAG_INFO; comm->logLevel = LOG_INFO; comm->deprecated(); } else if (strcmp((const char*)name, "warning") == 0) { comm->state = TAG_WARNING; comm->logLevel = LOG_WARNING; comm->deprecated(); } else { ++(comm->unknownLevel); } break; default: ++(comm->unknownLevel); break; } comm->content.clear(); } void OpenMSXComm::parseReply(const char** attrs) { replyStatus = REPLY_UNKNOWN; if (attrs) { for ( ; *attrs; attrs += 2) { if (strcmp(attrs[0], "result") == 0) { if (strcmp(attrs[1], "ok") == 0) { replyStatus = REPLY_OK; } else if (strcmp(attrs[1], "nok") == 0) { replyStatus = REPLY_NOK; } } } } } void OpenMSXComm::parseLog(const char** attrs) { logLevel = LOG_UNKNOWN; if (attrs) { for ( ; *attrs; attrs += 2) { if (strcmp(attrs[0], "level") == 0) { if (strcmp(attrs[1], "info") == 0) { logLevel = LOG_INFO; } else if (strcmp(attrs[1], "warning") == 0) { logLevel = LOG_WARNING; } } } } } void OpenMSXComm::parseUpdate(const char** attrs) { updateType = UPDATE_UNKNOWN; if (attrs) { for ( ; *attrs; attrs += 2) { if (strcmp(attrs[0], "type") == 0) { if (strcmp(attrs[1], "led") == 0) { updateType = UPDATE_LED; } } else if (strcmp(attrs[0], "name") == 0) { updateName = attrs[1]; } } } } void OpenMSXComm::cb_end_element(OpenMSXComm* comm, const xmlChar* name) { if (comm->unknownLevel) { --(comm->unknownLevel); return; } switch (comm->state) { case TAG_OPENMSX: comm->state = START; break; case TAG_REPLY: comm->doReply(); comm->state = TAG_OPENMSX; break; case TAG_LOG: comm->doLog(); comm->state = TAG_OPENMSX; break; case TAG_UPDATE: comm->doUpdate(); comm->state = TAG_OPENMSX; break; // backwards compatibilty stuff case TAG_OK: comm->openmsx_cmd_ok(comm->content); comm->state = TAG_OPENMSX; break; case TAG_NOK: comm->openmsx_cmd_nok(comm->content); comm->state = TAG_OPENMSX; break; case TAG_INFO: comm->openmsx_cmd_info(comm->content); comm->state = TAG_OPENMSX; break; case TAG_WARNING: comm->openmsx_cmd_warning(comm->content); comm->state = TAG_OPENMSX; break; default: break; } } void OpenMSXComm::doReply() { switch (replyStatus) { case REPLY_OK: openmsx_cmd_ok(content); break; case REPLY_NOK: openmsx_cmd_nok(content); break; } } void OpenMSXComm::doLog() { switch (logLevel) { case LOG_INFO: openmsx_cmd_info(content); break; case LOG_WARNING: openmsx_cmd_warning(content); break; } } void OpenMSXComm::doUpdate() { switch (updateType) { case UPDATE_LED: openmsx_cmd_update(updateName, content); break; } } void OpenMSXComm::cb_text(OpenMSXComm* comm, const xmlChar* chars, int len) { switch (comm->state) { case TAG_REPLY: case TAG_LOG: case TAG_UPDATE: case TAG_OK: case TAG_NOK: case TAG_INFO: case TAG_WARNING: comm->content.append((const char*)chars, len); break; default: break; } } void OpenMSXComm::sendCommand(const string& command) { write(fd_out, "", 9); write(fd_out, command.c_str(), command.length()); write(fd_out, "", 10); commandStack.push_back(command); } void OpenMSXComm::start() { // init XML parser state = START; unknownLevel = 0; memset(&sax_handler, 0, sizeof(sax_handler)); sax_handler.startElement = (startElementSAXFunc)cb_start_element; sax_handler.endElement = (endElementSAXFunc) cb_end_element; sax_handler.characters = (charactersSAXFunc) cb_text; parser_context = xmlCreatePushParserCtxt(&sax_handler, this, 0, 0, 0); // create pipes int pipe_to_child[2]; int pipe_from_child[2]; pipe(pipe_to_child); pipe(pipe_from_child); fd_out = pipe_to_child[1]; // start openmsx sub-process pid_t pid = fork(); if (pid == 0) { dup2(pipe_to_child[0], STDIN_FILENO); dup2(pipe_from_child[1], STDOUT_FILENO); close(pipe_to_child[0]); close(pipe_to_child[1]); close(pipe_from_child[0]); close(pipe_from_child[1]); execlp("openmsx", "openmsx", "-control", "stdio:", 0); exit(0); } close(pipe_to_child[0]); close(pipe_from_child[1]); // send initial commands write(pipe_to_child[1], "", 17); sendCommand("set power on"); sendCommand("restoredefault renderer"); // event loop string command; // (partial) input from STDIN while (true) { char buf[4096]; fd_set rdfs; FD_ZERO(&rdfs); FD_SET(pipe_from_child[0], &rdfs); FD_SET(STDIN_FILENO, &rdfs); select(pipe_from_child[0] + 1, &rdfs, NULL, NULL, NULL); if (FD_ISSET(pipe_from_child[0], &rdfs)) { // data available from openMSX ssize_t size = read(pipe_from_child[0], buf, 4096); if (size == 0) { // openmsx process died break; } xmlParseChunk(parser_context, buf, size, 0); } if (FD_ISSET(STDIN_FILENO, &rdfs)) { // data available from STDIN ssize_t size = read(STDIN_FILENO, buf, 4096); char* oldpos = buf; while (true) { char* pos = (char*)memchr(oldpos, '\n', size); if (pos) { unsigned num = pos - oldpos; command.append(oldpos, num); sendCommand(command); command.clear(); oldpos = pos + 1; size -= num + 1; } else { command.append(pos, size); break; } } } } // cleanup xmlFreeParserCtxt(parser_context); } int main() { OpenMSXComm comm; comm.start(); return 0; } openMSX-RELEASE_0_12_0/Contrib/tas/000077500000000000000000000000001257557151200166265ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/Contrib/tas/README.txt000066400000000000000000000074621257557151200203350ustar00rootroot00000000000000Speedrun tools for openMSX ========================== The omr2txt.py tool allows extracting the input event log of an openMSX replay (OMR file) to a plain text format. The txt2omr.py tool performs the opposite operation: it reads an input event log in text format and inserts it into an OMR file. Together, these tools can be used to view and edit your event logs in a text editor. They can also be used to filter events and align them to frame boundaries. And you could use them if you want to write your own scripts to generate or optimize event logs. Another use for these tools is to be able to generate a version of your speedrun in progress that can be stored in a version control system with human-readable diffs. The tools require Python version 3.x. Limitations ----------- Currently omr2txt.py will only convert key presses listed in its "inputMap" dictionary. If your game uses keys other than the cursors and space, add the name and the keyboard matrix row/column in this dictionary near the top of the script. I wrote these tools when working on my King's Valley TAS, so I could replace the recording of one pyramid's solution without having to re-record all pyramids that come after it. While I did implement some rudimentary error handling, don't expect them to be robust to bad input. Format ------ Empty lines and lines starting with "#" are ignored. Lines starting with "=" are processing instructions: = base wsx-kv1.omr = base wsx-kv1.xml Specifies the recording to use as a base when converting back to OMR. The initial snapshot of this recording sets up the MSX machine and the game. Both ".omr" (OMR file, which is gzip-ed XML) and ".xml" (uncompressed XML from OMR file) file name extensions are supported. = out kings_valley_mth_v0.omr Pathname of the output file that txt2omr.py will write. = scale 57346560 Number of master clock ticks per time unit (frame). All following time intervals in the event log will be multiplied by this number. = input r key 8 7 Defines an input name "r" as a key press of keyboard matrix row 8, column 7. Currently key presses are the only supported types of events. = include pyramid01.txt Includes another text file. Can be used to store the input events for every level in a separate file, for example. All other lines are event log entries. Log entries are whitespace-separated lists, where the first element is a time interval length and the following elements are the input events that are active during that interval. For example: 2 r 1 r s 24 This will press the "r" input, as defined earlier with a "=input" processing instruction, for two frames. Then it will hold "r" pressed while also pressing the "s" input for one frame. Finally it will release both inputs and wait for 24 frames. License ------- Copyright (c) 2015 Maarten ter Huurne 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. openMSX-RELEASE_0_12_0/Contrib/tas/omr2txt.py000077500000000000000000000210641257557151200206250ustar00rootroot00000000000000#!/usr/bin/env python3 from collections import defaultdict from gzip import GzipFile from math import modf from sys import stderr from xml.sax import make_parser from xml.sax.handler import feature_external_ges from xml.dom import pulldom cpuClock = 3579545 vdpClock = cpuClock * 6 masterClock = cpuClock * 960 vdpTicksPerLine = 1368 # The keys we're interested in: the letter to be used in the text file, # followed by the keyboard matrix location (row, column). # This should probably be made configurable at some point, but for now you # can edit them here. inputMap = { 'r': (8, 7), 'd': (8, 6), 'u': (8, 5), 'l': (8, 4), 's': (8, 0) } inputMapReverse = dict((pos, name) for name, pos in inputMap.items()) def readEvents(filename): '''Reads input events from an openMSX replay file (OMR). Returns a list of tuples consisting of a timestamp, keyboard row, press mask and release mask. ''' print('reading replay:', filename, file=stderr) inputEvents = [] def getText(node): return ''.join( child.data for child in node.childNodes if child.nodeType == child.TEXT_NODE ) def parseItem(): time = None row = None press = None release = None for event, node in xmlEvents: if event == pulldom.START_ELEMENT: doc.expandNode(node) if node.tagName == 'StateChange': assert time is None, time timeNode1, timeNode2 = node.getElementsByTagName('time') time = int(getText(timeNode2)) elif node.tagName == 'row': assert row is None, row row = int(getText(node)) elif node.tagName == 'press': assert press is None, press press = int(getText(node)) elif node.tagName == 'release': assert release is None, release release = int(getText(node)) else: pass #print(node.toxml()) elif event == pulldom.END_ELEMENT: if node.tagName == 'item': break assert time is not None assert row is not None assert press is not None assert release is not None inputEvents.append((time, row, press, release)) def parseEvents(): for event, node in xmlEvents: if event == pulldom.START_ELEMENT: if node.tagName == 'item' \ and node.getAttribute('type') == 'KeyMatrixState': parseItem() elif event == pulldom.END_ELEMENT: if node.tagName == 'events': break def parseTopLevel(): for event, node in xmlEvents: if event == pulldom.START_ELEMENT: if node.tagName == 'events': parseEvents() parser = make_parser() parser.setFeature(feature_external_ges, False) with GzipFile(filename) as inp: doc = pulldom.parse(inp, parser) xmlEvents = iter(doc) parseTopLevel() print('read %d input events' % len(inputEvents), file=stderr) return inputEvents def combineEvents(inputEvents): '''Combines multiple events on the same keyboard row at the same timestamp into a single event. ''' pendingTime = None def outputPending(): if pendingTime is not None: for row in sorted(pressForRow.keys() | releaseForRow.keys()): press = pressForRow[row] release = releaseForRow[row] if press != 0 or release != 0: yield pendingTime, row, press, release for time, row, press, release in inputEvents: if time != pendingTime: assert pendingTime is None or time > pendingTime, time # Output previous event. yield from outputPending() # Start new event. pendingTime = time pressForRow = defaultdict(int) releaseForRow = defaultdict(int) pressForRow[row] = (pressForRow[row] | press) & ~release releaseForRow[row] = (releaseForRow[row] | release) & ~press else: yield from outputPending() def removeRedundantEvents(inputEvents): '''Remove (parts of) events that don't change the keyboard matrix state. ''' matrix = [0] * 12 for time, row, press, release in inputEvents: old = matrix[row] press &= ~old release &= old if press != 0 or release != 0: matrix[row] = (old | press) & ~release yield time, row, press, release def filterEvents(inputEvents, wantedKeys): '''Filter the given input events to contain only presses and releases of the given keys. The wantedKeys argument should be mapping from row to a column bitmask, where bits are set if we want to preserve changes in the corresponding position in the keyboard matrix. ''' for time, row, press, release in inputEvents: mask = wantedKeys.get(row, 0) press &= mask release &= mask if press != 0 or release != 0: yield time, row, press, release def scaleTime(inputEvents, tickScale): '''Yields the given input events, with the time stamps divided by the given scale and rounded to the nearest integer. ''' for time, row, press, release in inputEvents: yield int(time / tickScale + 0.5), row, press, release def checkAlignment(timestamps, alignment): '''Returns the number of timestamps from the given sequence that are close to multiples of the given alignment. ''' aligned = 0 for time in timestamps: offset = modf(time / alignment)[0] if offset < 0.001 or offset > 0.999: aligned += 1 return aligned def detectTicksPerFrame(timestamps): '''Determine the most likely number of master clock ticks per frame, by looking at how well the timestamps align with frame boundaries when using the two frame timings that openMSX supports. This assumes that the frame timing is the same throughout the replay, which is not guaranteed in general, but will hopefully be the case for tool-assisted speedruns. ''' ticksPerFrame50 = (masterClock * vdpTicksPerLine * 313) // vdpClock ticksPerFrame60 = (masterClock * vdpTicksPerLine * 262) // vdpClock timestamps = tuple(timestamps) aligned50 = checkAlignment(timestamps, ticksPerFrame50) aligned60 = checkAlignment(timestamps, ticksPerFrame60) if aligned50 > aligned60: print('event timestamps align with 50 fps timing: %.2f%%' % (100 * aligned50 / len(timestamps)), file=stderr) return ticksPerFrame50 else: print('event timestamps align with 60 fps timing: %.2f%%' % (100 * aligned60 / len(timestamps)), file=stderr) return ticksPerFrame60 def eventsToState(inputEvents): '''Yields pairs of a timestamp and a set of the active keys at that time. ''' active = set() prevTime = None for time, row, press, release in inputEvents: assert (press & release) == 0, (press, release) if prevTime != time: if prevTime is not None: yield prevTime, frozenset(active) prevTime = time col = 0 while press: if press & 1: active.add((row, col)) col += 1 press >>= 1 col = 0 while release: if release & 1: active.remove((row, col)) col += 1 release >>= 1 if prevTime is not None: yield prevTime, frozenset(active) def formatState(active): return ' '.join( inputMapReverse[pos] for pos in sorted(active, key=lambda pos: (pos[0], -pos[1])) ) def convert(inFilename, outFilename): wantedKeys = {} for name, (row, col) in inputMap.items(): wantedKeys[row] = wantedKeys.get(row, 0) | (1 << col) inputEvents = list(removeRedundantEvents( filterEvents(combineEvents(readEvents(inFilename)), wantedKeys) )) print('after cleanup %d events remain' % len(inputEvents), file=stderr) ticksPerFrame = detectTicksPerFrame(evt[0] for evt in inputEvents) scaledEvents = list(removeRedundantEvents( combineEvents(scaleTime(inputEvents, ticksPerFrame)) )) with open(outFilename, 'w') as out: print('writing output:', outFilename) print('= base', inFilename, file=out) print('= out', 'replay.omr', file=out) print('= scale', ticksPerFrame, file=out) for name, (row, col) in sorted( inputMap.items(), key=lambda item: (item[1][0], -item[1][1]) ): print('= input', name, 'key', row, col, file=out) print(file=out) def printMilestone(frame, seconds): print('# frame %d (%d:%02d)' % ((frame,) + divmod(seconds, 60)), file=out) milestone = 10 # seconds prevFrame = 0 prevSeconds = 0 printMilestone(prevFrame, prevSeconds) prevActive = set() for frame, active in eventsToState(scaledEvents): delta = frame - prevFrame print(('%4d %s' % (delta, formatState(prevActive))).rstrip(), file=out) seconds = (frame * ticksPerFrame) // masterClock if seconds // milestone != prevSeconds // milestone: printMilestone(frame, seconds) prevFrame = frame prevSeconds = seconds prevActive = active printMilestone(prevFrame, prevSeconds) if __name__ == '__main__': from sys import argv if len(argv) != 2: print('Usage: omr2txt.py file1.omr', file=stderr) print('Converts an openMSX replay to a text file.', file=stderr) exit(2) else: inFilename = argv[1] if inFilename.endswith('.omr'): outFilename = inFilename[:-4] + '.txt' convert(inFilename, outFilename) else: print('File name does not end in ".omr":', inFilename) exit(2) openMSX-RELEASE_0_12_0/Contrib/tas/txt2omr.py000077500000000000000000000170161257557151200206270ustar00rootroot00000000000000#!/usr/bin/env python3 from collections import defaultdict from datetime import datetime from gzip import GzipFile from io import TextIOWrapper from sys import stderr from xml.etree.ElementTree import SubElement, parse as parseXML import platform cpuClock = 3579545 masterClock = cpuClock * 960 def readStates(topFilename): openFilenames = [] base = None out = None scale = 1 inputMap = {} inputStates = [] time = 0 def handleProcessingInstruction(words): if not words: raise ValueError('Missing processing instruction') elif words[0] == 'base': if len(words) != 2: raise ValueError( 'Processing instruction "base" expects 1 argument, ' 'got %d' % (len(words) - 1)) nonlocal base if base is not None: raise ValueError('Attempt to change base file name') base = words[1] elif words[0] == 'out': if len(words) != 2: raise ValueError( 'Processing instruction "out" expects 1 argument, ' 'got %d' % (len(words) - 1)) nonlocal out if out is not None: raise ValueError('Attempt to change output file name') out = words[1] elif words[0] == 'scale': if len(words) != 2: raise ValueError( 'Processing instruction "scale" expects 1 argument, ' 'got %d' % (len(words) - 1)) nonlocal scale scale = int(words[1]) print('time scale: %.2f frames per second' % (masterClock / scale)) elif words[0] == 'input': if len(words) != 5: raise ValueError( 'Processing instruction "input" expects 4 arguments, ' 'got %d' % (len(words) - 1)) name, inpType, rowStr, colStr = words[1:] if inpType != 'key': raise ValueError('Unknown input type "%s"' % inpType) row = int(rowStr) col = int(colStr) inputMap[name] = (row, col) elif words[0] == 'include': if len(words) != 2: raise ValueError( 'Processing instruction "include" expects 1 argument, ' 'got %d' % (len(words) - 1)) readFile(words[1]) else: raise ValueError('Unknown processing instruction: %s' % words[0]) def handleState(words): frames = int(words[0]) try: inputs = [inputMap[name] for name in words[1:]] except KeyError as ex: raise ValueError('Undefined input: %s' % ex) nonlocal time inputStates.append((time, inputs)) time += frames * scale return frames def readFile(filename): if filename in openFilenames: raise ValueError('Circular include: %s' % filename) localStateCount = 0 localFrameCount = 0 startTime = time openFilenames.append(filename) try: with open(filename, 'r') as stream: for lineNr, line in enumerate(stream, 1): try: line = line.strip() if not line or line[0] == '#': # Ignore empty line or comment. continue if line[0] == '=': handleProcessingInstruction(line[1:].split()) else: localFrameCount += handleState(line.split()) localStateCount += 1 except ValueError: role = 'In' if filename == openFilenames[-1] \ else 'included from' print('%s "%s" line %d,' % (role, filename, lineNr), file=stderr) raise except OSError as ex: print('Failed to open input file "%s",' % filename, file=stderr) raise ValueError(str(ex)) from ex del openFilenames[-1] localTime = time - startTime print('%-17s %5d states, %6d frames, %7.2f seconds' % ( filename + ':', localStateCount, localFrameCount, localTime / masterClock ), file=stderr) readFile(topFilename) inputStates.append((time, [])) if base is None: raise ValueError('Base file not defined') return base, out, inputStates def statesToEvents(inputStates): active = {} for time, state in inputStates: stateByRow = defaultdict(int) for row, col in state: stateByRow[row] |= 1 << col for row in sorted(active.keys() | stateByRow.keys()): old = active.get(row, 0) new = stateByRow.get(row, 0) press = new & ~old release = ~new & old if press != 0 or release != 0: yield time, row, press, release active[row] = new def replaceEvents(inp, out, inputEvents): doc = parseXML(inp) # Set the serialization date to now. rootElem = doc.getroot() rootElem.attrib['date_time'] = \ datetime.now().strftime('%a %b %d %H:%M:%S %Y') rootElem.attrib['openmsx_version'] = 'txt2omr' rootElem.attrib['platform'] = platform.system().lower() # Remove snapshots except the one at timestamp 0. snapshots = doc.find('replay/snapshots') if snapshots is None: print('Base replay lacks snapshots', file=stderr) else: seenInitialSnapshot = False for snapshot in snapshots.findall('item'): timeElem = snapshot.find('scheduler/currentTime/time') time = int(timeElem.text) if time == 0: seenInitialSnapshot = True else: snapshots.remove(snapshot) if not seenInitialSnapshot: print('No snapshot found with timestamp 0', file=stderr) # Replace event log. eventsElem = doc.find('replay/events') if eventsElem is None: print('No events tag found; cannot insert events', file=stderr) else: tail = eventsElem.tail eventsElem.clear() eventsElem.text = '\n' eventsElem.tail = tail # IDs must be unique for the entire document. We look for the highest # in-use ID and generate new IDs counting up from there. baseID = max( int(elem.attrib['id']) for elem in doc.iterfind('.//*[@id]') ) + 1 def createEvent(i, time): itemElem = SubElement(eventsElem, 'item', id=str(baseID + i), type='KeyMatrixState') itemElem.tail = '\n' stateChangeElem = SubElement(itemElem, 'StateChange') timeElem1 = SubElement(stateChangeElem, 'time') timeElem2 = SubElement(timeElem1, 'time') timeElem2.text = str(time) return itemElem for i, (time, row, press, release) in enumerate(inputEvents): itemElem = createEvent(i, time) SubElement(itemElem, 'row').text = str(row) SubElement(itemElem, 'press').text = str(press) SubElement(itemElem, 'release').text = str(release) endTime = inputEvents[-1][0] if inputEvents else 0 createEvent(len(inputEvents), endTime).attrib['type'] = 'EndLog' # Reset re-record count. reRecordCount = doc.find('replay/reRecordCount') if reRecordCount is not None: reRecordCount.text = '0' # Reset the current time. currentTime = doc.find('replay/currentTime/time') if currentTime is not None: currentTime.text = '0' out.write(b'\n') out.write(b"\n") doc.write(out, encoding='utf-8', xml_declaration=False) def convert(inFilename): try: base, outFilename, inputStates = readStates(inFilename) except ValueError as ex: print('ERROR: %s' % ex, file=stderr) exit(1) inputEvents = list(statesToEvents(inputStates)) def createOutput(inp): if outFilename is None: print('No output file name set', file=stderr) else: try: with GzipFile(outFilename, 'wb') as out: replaceEvents(inp, out, inputEvents) except OSError as ex: print('Failed to open output replay:', ex) exit(1) else: print('wrote output replay:', outFilename) try: if base.endswith('.omr'): with GzipFile(base, 'rb') as inp: createOutput(inp) elif base.endswith('.xml'): with open(base, 'rb') as inp: createOutput(inp) else: print('Unknown base file type in "%s" ' '(".xml" and ".omr" are supported)' % base, file=stderr) exit(1) except OSError as ex: print('Failed to open base replay:', ex, file=stderr) exit(1) if __name__ == '__main__': from sys import argv if len(argv) != 2: print('Usage: txt2omr.py replay.txt', file=stderr) print('Converts the text version of an openMSX replay to an OMR file.', file=stderr) exit(2) else: convert(argv[1]) openMSX-RELEASE_0_12_0/GNUmakefile000066400000000000000000000000271257557151200165100ustar00rootroot00000000000000include build/entry.mk openMSX-RELEASE_0_12_0/README000066400000000000000000000025311257557151200153200ustar00rootroot00000000000000---------------------------------------------------------------------------- openMSX - the MSX emulator that aims for perfection ---------------------------------------------------------------------------- openMSX comes with a set of HTML manuals that tell what you need to know to install, configure and run openMSX. You can find these manuals in the directory 'manual' inside the directory 'doc'. You can read them using a web browser. You can read what has changed in this and the previous releases in the release notes. You can find the release notes of this release in the file 'release-notes.txt' in the directory 'doc'. Highlights of previous releases can be found in 'release-history.txt'. All source code and other works that are part of, or distributed with openMSX are copyrighted by their respective authors. The file 'authors.txt' contains a list of people who made works for openMSX or contributed works to openMSX. Some source files contain a license notice; all other source files are licensed under the GNU Public License (GPL), of which you can find a copy in the file 'GPL.txt'. If you got a binary release of openMSX and are interested in the sources, please visit our home page: http://openmsx.org/ Happy MSX-ing! the openMSX developers ---------------------------------------------------------------------------- openMSX-RELEASE_0_12_0/build/000077500000000000000000000000001257557151200155365ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/build/.gitignore000066400000000000000000000000071257557151200175230ustar00rootroot00000000000000/*.pyc openMSX-RELEASE_0_12_0/build/3rdparty.mk000066400000000000000000000244341257557151200176460ustar00rootroot00000000000000# Compiles 3rd party libraries needed by openMSX. # It enables only the features needed by openMSX. ifeq ($(origin PYTHON),undefined) $(error You should pass PYTHON) endif ifeq ($(origin BUILD_PATH),undefined) $(error You should pass BUILD_PATH) endif ifeq ($(origin OPENMSX_TARGET_OS),undefined) $(error You should pass OPENMSX_TARGET_OS) endif ifeq ($(origin OPENMSX_TARGET_CPU),undefined) $(error You should pass OPENMSX_TARGET_CPU) endif .PHONY: all all: default # Get information about packages. -include derived/3rdparty/packages.mk ifneq ($(origin PACKAGE_SDL),undefined) # These libraries are part of the base system, therefore we do not need to # link them statically for building a redistributable binary. SYSTEM_LIBS:=$(shell $(PYTHON) build/list_system_libs.py $(OPENMSX_TARGET_OS)) # Compiler selection, compiler flags, SDK selection. # These variables are already exported, but we make it explicit here. export CC export NEXT_ROOT export MACOSX_DEPLOYMENT_TARGET CC=$(_CC) TIMESTAMP_DIR:=$(BUILD_PATH)/timestamps BUILD_DIR:=$(BUILD_PATH)/build INSTALL_DIR:=$(BUILD_PATH)/install # Create a GNU-style system triple. TRIPLE_VENDOR:=unknown ifeq ($(OPENMSX_TARGET_CPU),x86) TRIPLE_MACHINE:=i686 else ifeq ($(OPENMSX_TARGET_CPU),ppc) TRIPLE_MACHINE:=powerpc else ifeq ($(OPENMSX_TARGET_CPU),ppc64) TRIPLE_MACHINE:=powerpc64 else TRIPLE_MACHINE:=$(OPENMSX_TARGET_CPU) endif endif endif ifeq ($(OPENMSX_TARGET_OS),dingux) TRIPLE_OS:=linux else TRIPLE_OS:=$(OPENMSX_TARGET_OS) endif ifeq ($(OPENMSX_TARGET_OS),mingw-w64) TRIPLE_OS:=mingw32 TRIPLE_VENDOR:=w64 endif TARGET_TRIPLE:=$(TRIPLE_MACHINE)-$(TRIPLE_VENDOR)-$(TRIPLE_OS) # Ask the compiler for the names and locations of other toolchain components. # This works with GCC and Clang at least, so it should be pretty safe. export LD:=$(shell $(CC) -print-prog-name=ld) export AR:=$(shell $(CC) -print-prog-name=ar) export RANLIB:=$(shell $(CC) -print-prog-name=ranlib) export STRIP:=$(shell $(CC) -print-prog-name=strip) ifeq ($(TRIPLE_OS),mingw32) # SDL calls it WINDRES, Tcl calls it RC; provide both. export WINDRES export RC:=$(WINDRES) endif # Work around some autoconf versions returning "universal" for endianess when # compiling with "-arch" in the CFLAGS, even in a single arch compile. BIGENDIAN:=$(shell PYTHONPATH=build/ $(PYTHON) -c 'from cpu import getCPU ; print "yes" if getCPU("$(OPENMSX_TARGET_CPU)").bigEndian else "no"') ifeq ($(BIGENDIAN),) $(error Could not determine endianess of "$(OPENMSX_TARGET_CPU)") endif # Although X11 is available on Windows and Mac OS X, most people do not have # it installed, so do not link against it. ifeq ($(filter linux freebsd netbsd openbsd gnu,$(OPENMSX_TARGET_OS)),) USE_VIDEO_X11:=disable else USE_VIDEO_X11:=enable endif PACKAGES_BUILD:=$(shell $(PYTHON) build/3rdparty_libraries.py $(OPENMSX_TARGET_OS) $(LINK_MODE)) PACKAGES_NOBUILD:= ifneq ($(filter mingw%,$(OPENMSX_TARGET_OS)),) PACKAGES_NOBUILD+=DIRECTX endif PACKAGES_3RD:=$(PACKAGES_BUILD) $(PACKAGES_NOBUILD) BUILD_TARGETS:=$(foreach PACKAGE,$(PACKAGES_BUILD),$(TIMESTAMP_DIR)/build-$(PACKAGE_$(PACKAGE))) INSTALL_BUILD_TARGETS:=$(foreach PACKAGE,$(PACKAGES_BUILD),$(TIMESTAMP_DIR)/install-$(PACKAGE_$(PACKAGE))) INSTALL_NOBUILD_TARGETS:=$(foreach PACKAGE,$(PACKAGES_NOBUILD),$(TIMESTAMP_DIR)/install-$(PACKAGE_$(PACKAGE))) ifeq ($(filter $(PACKAGES_3RD),DIRECTX),) INSTALL_DIRECTX:= else INSTALL_DIRECTX:=$(TIMESTAMP_DIR)/install-$(PACKAGE_DIRECTX) endif INSTALL_PARAMS_GLEW:=\ GLEW_DEST=$(PWD)/$(INSTALL_DIR) \ LIBDIR=$(PWD)/$(INSTALL_DIR)/lib # Function which, given a variable name prefix and the variable's value, # returns the name of the package. findpackage=$(strip $(foreach PACKAGE,$(PACKAGES_3RD),$(if $(filter $(2),$($(1)_$(PACKAGE))),$(PACKAGE),))) .PHONY: default default: $(INSTALL_BUILD_TARGETS) $(INSTALL_NOBUILD_TARGETS) .PHONY: clean clean: rm -rf $(SOURCE_DIR) rm -rf $(BUILD_DIR) rm -rf $(INSTALL_DIR) # Install. $(INSTALL_BUILD_TARGETS): $(TIMESTAMP_DIR)/install-%: $(TIMESTAMP_DIR)/build-% $(MAKE) -C $(BUILD_DIR)/$* install \ $(MAKEVAR_OVERRIDE_$(call findpackage,PACKAGE,$*)) \ $(INSTALL_PARAMS_$(call findpackage,PACKAGE,$*)) mkdir -p $(@D) touch $@ ifneq ($(INSTALL_DIRECTX),) # Install DirectX headers. $(INSTALL_DIRECTX): $(TARBALL_DIRECTX) mkdir -p $(INSTALL_DIR) tar -zxf $< -C $(INSTALL_DIR) mkdir -p $(@D) touch $@ endif # Build. $(BUILD_TARGETS): $(TIMESTAMP_DIR)/build-%: $(BUILD_DIR)/%/Makefile $(MAKE) -C $( $@ openMSX-RELEASE_0_12_0/build/3rdparty/000077500000000000000000000000001257557151200173065ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/build/3rdparty/3rdparty.props000066400000000000000000000115531257557151200221500ustar00rootroot00000000000000 ..\.. $(OpenMSXRootDir)\derived $(DerivedDir)\$(Platform)-VC-$(Configuration) $(BuildDir)\3rdParty $(DerivedDir)\3rdParty\src $(ThirdPartyBuildDir)\build $(ThirdPartyBuildDir)\install\lib $(OpenMSXRootDir)\src $(BuildDir)\build $(BuildDir)\install $(BuildDir)\config freetype-2.4.12 glew-1.9.0 libpng-1.2.50 libogg-1.3.0 SDL-1.2.15 SDL_ttf-2.0.11 SDLmain tcl8.5.15 libtheora-1.1.1 libvorbis-1.3.3 zlib-1.2.8 <_ProjectFileVersion>10.0.30319.1 $(OpenMSXRootDir) true $(DerivedDir) true $(BuildDir) true $(ThirdPartyBuildDir) true $(ThirdPartySrcDir) true $(ThirdPartyIntDir) true $(ThirdPartyOutDir) true $(OpenMSXSrcDir) true $(OpenMSXIntDir) true $(OpenMSXOutDir) true $(OpenMSXConfigDir) true $(LibNameFreeType) true $(LibNameGlew) true $(LibNameLibPng) true $(LibNameOgg) true $(LibNameSDL) true $(LibNameSDL_ttf) true $(LibNameSDLmain) true $(LibNameTcl) true $(LibNameTheora) true $(LibNameVorbis) true $(LibNameZlib) true openMSX-RELEASE_0_12_0/build/3rdparty/3rdparty.sln000066400000000000000000000551541257557151200216060ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "glew", "glew.vcxproj", "{E78E1412-E9F8-475B-9504-72031C8FBAEA}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "freetype", "freetype.vcxproj", "{78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libogg", "libogg.vcxproj", "{15CBFEFF-7965-41F5-B4E2-21E8795C9159}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libpng", "libpng.vcxproj", "{0008960E-E0DD-41A6-8265-00B31DDB4C21}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libtheora", "libtheora.vcxproj", "{653F3841-3F26-49B9-AFCF-091DB4B67031}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libvorbis", "libvorbis.vcxproj", "{3A214E06-B95E-4D61-A291-1F8DF2EC10FD}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SDL", "SDL.vcxproj", "{81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SDL_ttf", "SDL_ttf.vcxproj", "{DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SDLmain", "SDLmain.vcxproj", "{DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tcl", "tcl.vcxproj", "{CCCEA506-D026-4915-BEE0-374F65D7C764}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "zlib.vcxproj", "{05A68CD4-A282-4475-B9F7-ED3C9A0109B5}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Debug|x64 = Debug|x64 Developer|Win32 = Developer|Win32 Developer|x64 = Developer|x64 LIB ASM Debug|Win32 = LIB ASM Debug|Win32 LIB ASM Debug|x64 = LIB ASM Debug|x64 LIB ASM Release|Win32 = LIB ASM Release|Win32 LIB ASM Release|x64 = LIB ASM Release|x64 Release_STDIO|Win32 = Release_STDIO|Win32 Release_STDIO|x64 = Release_STDIO|x64 Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {E78E1412-E9F8-475B-9504-72031C8FBAEA}.Debug|Win32.ActiveCfg = Debug|Win32 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.Debug|Win32.Build.0 = Debug|Win32 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.Debug|x64.ActiveCfg = Debug|x64 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.Debug|x64.Build.0 = Debug|x64 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.Developer|Win32.ActiveCfg = Developer|Win32 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.Developer|Win32.Build.0 = Developer|Win32 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.Developer|x64.ActiveCfg = Developer|x64 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.Developer|x64.Build.0 = Developer|x64 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.LIB ASM Debug|Win32.ActiveCfg = Debug|Win32 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.LIB ASM Debug|Win32.Build.0 = Debug|Win32 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.LIB ASM Debug|x64.ActiveCfg = Debug|x64 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.LIB ASM Debug|x64.Build.0 = Debug|x64 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.LIB ASM Release|Win32.ActiveCfg = Release|Win32 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.LIB ASM Release|Win32.Build.0 = Release|Win32 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.LIB ASM Release|x64.ActiveCfg = Release|x64 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.LIB ASM Release|x64.Build.0 = Release|x64 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.Release_STDIO|Win32.ActiveCfg = Release|x64 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.Release_STDIO|x64.ActiveCfg = Release|x64 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.Release_STDIO|x64.Build.0 = Release|x64 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.Release|Win32.ActiveCfg = Release|Win32 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.Release|Win32.Build.0 = Release|Win32 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.Release|x64.ActiveCfg = Release|x64 {E78E1412-E9F8-475B-9504-72031C8FBAEA}.Release|x64.Build.0 = Release|x64 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Debug|Win32.ActiveCfg = Debug|Win32 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Debug|Win32.Build.0 = Debug|Win32 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Debug|x64.ActiveCfg = Debug|x64 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Debug|x64.Build.0 = Debug|x64 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Developer|Win32.ActiveCfg = Developer|Win32 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Developer|Win32.Build.0 = Developer|Win32 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Developer|x64.ActiveCfg = Developer|x64 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Developer|x64.Build.0 = Developer|x64 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.LIB ASM Debug|Win32.ActiveCfg = Debug|Win32 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.LIB ASM Debug|Win32.Build.0 = Debug|Win32 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.LIB ASM Debug|x64.ActiveCfg = Debug|x64 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.LIB ASM Debug|x64.Build.0 = Debug|x64 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.LIB ASM Release|Win32.ActiveCfg = Release|Win32 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.LIB ASM Release|Win32.Build.0 = Release|Win32 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.LIB ASM Release|x64.ActiveCfg = Release|x64 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.LIB ASM Release|x64.Build.0 = Release|x64 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Release_STDIO|Win32.ActiveCfg = Release|x64 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Release_STDIO|x64.ActiveCfg = Release|x64 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Release_STDIO|x64.Build.0 = Release|x64 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Release|Win32.ActiveCfg = Release|Win32 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Release|Win32.Build.0 = Release|Win32 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Release|x64.ActiveCfg = Release|x64 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B}.Release|x64.Build.0 = Release|x64 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.Debug|Win32.ActiveCfg = Debug|Win32 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.Debug|Win32.Build.0 = Debug|Win32 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.Debug|x64.ActiveCfg = Debug|x64 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.Debug|x64.Build.0 = Debug|x64 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.Developer|Win32.ActiveCfg = Developer|Win32 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.Developer|Win32.Build.0 = Developer|Win32 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.Developer|x64.ActiveCfg = Developer|x64 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.Developer|x64.Build.0 = Developer|x64 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.LIB ASM Debug|Win32.ActiveCfg = Debug|Win32 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.LIB ASM Debug|Win32.Build.0 = Debug|Win32 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.LIB ASM Debug|x64.ActiveCfg = Debug|x64 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.LIB ASM Debug|x64.Build.0 = Debug|x64 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.LIB ASM Release|Win32.ActiveCfg = Release|Win32 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.LIB ASM Release|Win32.Build.0 = Release|Win32 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.LIB ASM Release|x64.ActiveCfg = Release|x64 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.LIB ASM Release|x64.Build.0 = Release|x64 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.Release_STDIO|Win32.ActiveCfg = Release|x64 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.Release_STDIO|x64.ActiveCfg = Release|x64 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.Release_STDIO|x64.Build.0 = Release|x64 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.Release|Win32.ActiveCfg = Release|Win32 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.Release|Win32.Build.0 = Release|Win32 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.Release|x64.ActiveCfg = Release|x64 {15CBFEFF-7965-41F5-B4E2-21E8795C9159}.Release|x64.Build.0 = Release|x64 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.Debug|Win32.ActiveCfg = Debug|Win32 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.Debug|Win32.Build.0 = Debug|Win32 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.Debug|x64.ActiveCfg = Debug|x64 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.Debug|x64.Build.0 = Debug|x64 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.Developer|Win32.ActiveCfg = Developer|Win32 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.Developer|Win32.Build.0 = Developer|Win32 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.Developer|x64.ActiveCfg = Developer|x64 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.Developer|x64.Build.0 = Developer|x64 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.LIB ASM Debug|Win32.ActiveCfg = Debug|Win32 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.LIB ASM Debug|Win32.Build.0 = Debug|Win32 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.LIB ASM Debug|x64.ActiveCfg = Debug|x64 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.LIB ASM Debug|x64.Build.0 = Debug|x64 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.LIB ASM Release|Win32.ActiveCfg = Release|Win32 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.LIB ASM Release|Win32.Build.0 = Release|Win32 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.LIB ASM Release|x64.ActiveCfg = Release|x64 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.LIB ASM Release|x64.Build.0 = Release|x64 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.Release_STDIO|Win32.ActiveCfg = Release|x64 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.Release_STDIO|x64.ActiveCfg = Release|x64 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.Release_STDIO|x64.Build.0 = Release|x64 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.Release|Win32.ActiveCfg = Release|Win32 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.Release|Win32.Build.0 = Release|Win32 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.Release|x64.ActiveCfg = Release|x64 {0008960E-E0DD-41A6-8265-00B31DDB4C21}.Release|x64.Build.0 = Release|x64 {653F3841-3F26-49B9-AFCF-091DB4B67031}.Debug|Win32.ActiveCfg = Debug|Win32 {653F3841-3F26-49B9-AFCF-091DB4B67031}.Debug|Win32.Build.0 = Debug|Win32 {653F3841-3F26-49B9-AFCF-091DB4B67031}.Debug|x64.ActiveCfg = Debug|x64 {653F3841-3F26-49B9-AFCF-091DB4B67031}.Debug|x64.Build.0 = Debug|x64 {653F3841-3F26-49B9-AFCF-091DB4B67031}.Developer|Win32.ActiveCfg = Developer|Win32 {653F3841-3F26-49B9-AFCF-091DB4B67031}.Developer|Win32.Build.0 = Developer|Win32 {653F3841-3F26-49B9-AFCF-091DB4B67031}.Developer|x64.ActiveCfg = Developer|x64 {653F3841-3F26-49B9-AFCF-091DB4B67031}.Developer|x64.Build.0 = Developer|x64 {653F3841-3F26-49B9-AFCF-091DB4B67031}.LIB ASM Debug|Win32.ActiveCfg = Debug|Win32 {653F3841-3F26-49B9-AFCF-091DB4B67031}.LIB ASM Debug|Win32.Build.0 = Debug|Win32 {653F3841-3F26-49B9-AFCF-091DB4B67031}.LIB ASM Debug|x64.ActiveCfg = Debug|x64 {653F3841-3F26-49B9-AFCF-091DB4B67031}.LIB ASM Debug|x64.Build.0 = Debug|x64 {653F3841-3F26-49B9-AFCF-091DB4B67031}.LIB ASM Release|Win32.ActiveCfg = Release|Win32 {653F3841-3F26-49B9-AFCF-091DB4B67031}.LIB ASM Release|Win32.Build.0 = Release|Win32 {653F3841-3F26-49B9-AFCF-091DB4B67031}.LIB ASM Release|x64.ActiveCfg = Release|x64 {653F3841-3F26-49B9-AFCF-091DB4B67031}.LIB ASM Release|x64.Build.0 = Release|x64 {653F3841-3F26-49B9-AFCF-091DB4B67031}.Release_STDIO|Win32.ActiveCfg = Release|x64 {653F3841-3F26-49B9-AFCF-091DB4B67031}.Release_STDIO|x64.ActiveCfg = Release|x64 {653F3841-3F26-49B9-AFCF-091DB4B67031}.Release_STDIO|x64.Build.0 = Release|x64 {653F3841-3F26-49B9-AFCF-091DB4B67031}.Release|Win32.ActiveCfg = Release|Win32 {653F3841-3F26-49B9-AFCF-091DB4B67031}.Release|Win32.Build.0 = Release|Win32 {653F3841-3F26-49B9-AFCF-091DB4B67031}.Release|x64.ActiveCfg = Release|x64 {653F3841-3F26-49B9-AFCF-091DB4B67031}.Release|x64.Build.0 = Release|x64 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.Debug|Win32.ActiveCfg = Debug|Win32 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.Debug|Win32.Build.0 = Debug|Win32 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.Debug|x64.ActiveCfg = Debug|x64 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.Debug|x64.Build.0 = Debug|x64 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.Developer|Win32.ActiveCfg = Developer|Win32 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.Developer|Win32.Build.0 = Developer|Win32 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.Developer|x64.ActiveCfg = Developer|x64 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.Developer|x64.Build.0 = Developer|x64 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.LIB ASM Debug|Win32.ActiveCfg = Debug|Win32 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.LIB ASM Debug|Win32.Build.0 = Debug|Win32 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.LIB ASM Debug|x64.ActiveCfg = Debug|x64 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.LIB ASM Debug|x64.Build.0 = Debug|x64 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.LIB ASM Release|Win32.ActiveCfg = Release|Win32 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.LIB ASM Release|Win32.Build.0 = Release|Win32 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.LIB ASM Release|x64.ActiveCfg = Release|x64 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.LIB ASM Release|x64.Build.0 = Release|x64 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.Release_STDIO|Win32.ActiveCfg = Release|x64 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.Release_STDIO|x64.ActiveCfg = Release|x64 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.Release_STDIO|x64.Build.0 = Release|x64 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.Release|Win32.ActiveCfg = Release|Win32 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.Release|Win32.Build.0 = Release|Win32 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.Release|x64.ActiveCfg = Release|x64 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD}.Release|x64.Build.0 = Release|x64 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.Debug|Win32.ActiveCfg = Debug|Win32 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.Debug|Win32.Build.0 = Debug|Win32 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.Debug|x64.ActiveCfg = Debug|x64 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.Debug|x64.Build.0 = Debug|x64 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.Developer|Win32.ActiveCfg = Developer|Win32 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.Developer|Win32.Build.0 = Developer|Win32 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.Developer|x64.ActiveCfg = Developer|x64 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.Developer|x64.Build.0 = Developer|x64 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.LIB ASM Debug|Win32.ActiveCfg = Debug|Win32 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.LIB ASM Debug|Win32.Build.0 = Debug|Win32 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.LIB ASM Debug|x64.ActiveCfg = Debug|x64 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.LIB ASM Debug|x64.Build.0 = Debug|x64 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.LIB ASM Release|Win32.ActiveCfg = Release|Win32 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.LIB ASM Release|Win32.Build.0 = Release|Win32 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.LIB ASM Release|x64.ActiveCfg = Release|x64 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.LIB ASM Release|x64.Build.0 = Release|x64 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.Release_STDIO|Win32.ActiveCfg = Release|x64 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.Release_STDIO|x64.ActiveCfg = Release|x64 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.Release_STDIO|x64.Build.0 = Release|x64 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.Release|Win32.ActiveCfg = Release|Win32 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.Release|Win32.Build.0 = Release|Win32 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.Release|x64.ActiveCfg = Release|x64 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68}.Release|x64.Build.0 = Release|x64 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.Debug|Win32.ActiveCfg = Debug|Win32 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.Debug|Win32.Build.0 = Debug|Win32 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.Debug|x64.ActiveCfg = Debug|x64 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.Debug|x64.Build.0 = Debug|x64 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.Developer|Win32.ActiveCfg = Developer|Win32 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.Developer|Win32.Build.0 = Developer|Win32 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.Developer|x64.ActiveCfg = Developer|x64 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.Developer|x64.Build.0 = Developer|x64 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.LIB ASM Debug|Win32.ActiveCfg = Debug|Win32 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.LIB ASM Debug|Win32.Build.0 = Debug|Win32 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.LIB ASM Debug|x64.ActiveCfg = Debug|x64 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.LIB ASM Debug|x64.Build.0 = Debug|x64 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.LIB ASM Release|Win32.ActiveCfg = Release|Win32 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.LIB ASM Release|Win32.Build.0 = Release|Win32 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.LIB ASM Release|x64.ActiveCfg = Release|x64 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.LIB ASM Release|x64.Build.0 = Release|x64 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.Release_STDIO|Win32.ActiveCfg = Release|x64 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.Release_STDIO|x64.ActiveCfg = Release|x64 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.Release_STDIO|x64.Build.0 = Release|x64 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.Release|Win32.ActiveCfg = Release|Win32 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.Release|Win32.Build.0 = Release|Win32 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.Release|x64.ActiveCfg = Release|x64 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255}.Release|x64.Build.0 = Release|x64 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.Debug|Win32.ActiveCfg = Debug|Win32 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.Debug|Win32.Build.0 = Debug|Win32 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.Debug|x64.ActiveCfg = Debug|x64 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.Debug|x64.Build.0 = Debug|x64 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.Developer|Win32.ActiveCfg = Developer|Win32 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.Developer|Win32.Build.0 = Developer|Win32 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.Developer|x64.ActiveCfg = Developer|x64 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.Developer|x64.Build.0 = Developer|x64 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.LIB ASM Debug|Win32.ActiveCfg = Debug|Win32 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.LIB ASM Debug|Win32.Build.0 = Debug|Win32 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.LIB ASM Debug|x64.ActiveCfg = Debug|x64 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.LIB ASM Debug|x64.Build.0 = Debug|x64 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.LIB ASM Release|Win32.ActiveCfg = Release|Win32 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.LIB ASM Release|Win32.Build.0 = Release|Win32 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.LIB ASM Release|x64.ActiveCfg = Release|x64 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.LIB ASM Release|x64.Build.0 = Release|x64 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.Release_STDIO|Win32.ActiveCfg = Release_STDIO|Win32 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.Release_STDIO|Win32.Build.0 = Release_STDIO|Win32 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.Release_STDIO|x64.ActiveCfg = Release_STDIO|x64 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.Release_STDIO|x64.Build.0 = Release_STDIO|x64 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.Release|Win32.ActiveCfg = Release|Win32 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.Release|Win32.Build.0 = Release|Win32 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.Release|x64.ActiveCfg = Release|x64 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A}.Release|x64.Build.0 = Release|x64 {CCCEA506-D026-4915-BEE0-374F65D7C764}.Debug|Win32.ActiveCfg = Debug|Win32 {CCCEA506-D026-4915-BEE0-374F65D7C764}.Debug|Win32.Build.0 = Debug|Win32 {CCCEA506-D026-4915-BEE0-374F65D7C764}.Debug|x64.ActiveCfg = Debug|x64 {CCCEA506-D026-4915-BEE0-374F65D7C764}.Debug|x64.Build.0 = Debug|x64 {CCCEA506-D026-4915-BEE0-374F65D7C764}.Developer|Win32.ActiveCfg = Developer|Win32 {CCCEA506-D026-4915-BEE0-374F65D7C764}.Developer|Win32.Build.0 = Developer|Win32 {CCCEA506-D026-4915-BEE0-374F65D7C764}.Developer|x64.ActiveCfg = Developer|x64 {CCCEA506-D026-4915-BEE0-374F65D7C764}.Developer|x64.Build.0 = Developer|x64 {CCCEA506-D026-4915-BEE0-374F65D7C764}.LIB ASM Debug|Win32.ActiveCfg = Debug|Win32 {CCCEA506-D026-4915-BEE0-374F65D7C764}.LIB ASM Debug|Win32.Build.0 = Debug|Win32 {CCCEA506-D026-4915-BEE0-374F65D7C764}.LIB ASM Debug|x64.ActiveCfg = Debug|x64 {CCCEA506-D026-4915-BEE0-374F65D7C764}.LIB ASM Debug|x64.Build.0 = Debug|x64 {CCCEA506-D026-4915-BEE0-374F65D7C764}.LIB ASM Release|Win32.ActiveCfg = Release|Win32 {CCCEA506-D026-4915-BEE0-374F65D7C764}.LIB ASM Release|Win32.Build.0 = Release|Win32 {CCCEA506-D026-4915-BEE0-374F65D7C764}.LIB ASM Release|x64.ActiveCfg = Release|x64 {CCCEA506-D026-4915-BEE0-374F65D7C764}.LIB ASM Release|x64.Build.0 = Release|x64 {CCCEA506-D026-4915-BEE0-374F65D7C764}.Release_STDIO|Win32.ActiveCfg = Release|x64 {CCCEA506-D026-4915-BEE0-374F65D7C764}.Release_STDIO|x64.ActiveCfg = Release|x64 {CCCEA506-D026-4915-BEE0-374F65D7C764}.Release_STDIO|x64.Build.0 = Release|x64 {CCCEA506-D026-4915-BEE0-374F65D7C764}.Release|Win32.ActiveCfg = Release|Win32 {CCCEA506-D026-4915-BEE0-374F65D7C764}.Release|Win32.Build.0 = Release|Win32 {CCCEA506-D026-4915-BEE0-374F65D7C764}.Release|x64.ActiveCfg = Release|x64 {CCCEA506-D026-4915-BEE0-374F65D7C764}.Release|x64.Build.0 = Release|x64 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.Debug|Win32.ActiveCfg = Debug|Win32 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.Debug|Win32.Build.0 = Debug|Win32 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.Debug|x64.ActiveCfg = Debug|x64 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.Debug|x64.Build.0 = Debug|x64 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.Developer|Win32.ActiveCfg = Developer|Win32 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.Developer|Win32.Build.0 = Developer|Win32 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.Developer|x64.ActiveCfg = Developer|x64 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.Developer|x64.Build.0 = Developer|x64 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.LIB ASM Debug|Win32.ActiveCfg = LIB ASM Debug|Win32 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.LIB ASM Debug|Win32.Build.0 = LIB ASM Debug|Win32 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.LIB ASM Debug|x64.ActiveCfg = LIB ASM Debug|x64 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.LIB ASM Debug|x64.Build.0 = LIB ASM Debug|x64 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.LIB ASM Release|Win32.ActiveCfg = LIB ASM Release|Win32 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.LIB ASM Release|Win32.Build.0 = LIB ASM Release|Win32 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.LIB ASM Release|x64.ActiveCfg = LIB ASM Release|x64 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.LIB ASM Release|x64.Build.0 = LIB ASM Release|x64 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.Release_STDIO|Win32.ActiveCfg = Release|x64 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.Release_STDIO|x64.ActiveCfg = Release|x64 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.Release_STDIO|x64.Build.0 = Release|x64 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.Release|Win32.ActiveCfg = Release|Win32 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.Release|Win32.Build.0 = Release|Win32 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.Release|x64.ActiveCfg = Release|x64 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal openMSX-RELEASE_0_12_0/build/3rdparty/SDL-1.2.15.diff000066400000000000000000000062071257557151200213110ustar00rootroot00000000000000diff -ru SDL-1.2.15.org/src/video/quartz/SDL_QuartzEvents.m SDL-1.2.15/src/video/quartz/SDL_QuartzEvents.m --- SDL-1.2.15.org/src/video/quartz/SDL_QuartzEvents.m 2012-01-19 07:30:06.000000000 +0100 +++ SDL-1.2.15/src/video/quartz/SDL_QuartzEvents.m 2013-10-23 07:16:42.000000000 +0200 @@ -345,7 +345,9 @@ the scancode/keysym. */ if (SDL_TranslateUNICODE && state == SDL_PRESSED) { - [field_edit interpretKeyEvents:[NSArray arrayWithObject:event]]; + if (!([event modifierFlags] & NSCommandKeyMask)) { + [field_edit interpretKeyEvents:[NSArray arrayWithObject:event]]; + } chars = [ event characters ]; numChars = [ chars length ]; if (numChars > 0) @@ -383,7 +385,7 @@ } } - if (SDL_getenv ("SDL_ENABLEAPPEVENTS")) + if (SDL_getenv ("SDL_ENABLEAPPEVENTS") && !(mode_flags & SDL_FULLSCREEN)) [ NSApp sendEvent:event ]; } diff -ru SDL-1.2.15.org/src/video/quartz/SDL_QuartzVideo.h SDL-1.2.15/src/video/quartz/SDL_QuartzVideo.h --- SDL-1.2.15.org/src/video/quartz/SDL_QuartzVideo.h 2012-01-19 07:30:06.000000000 +0100 +++ SDL-1.2.15/src/video/quartz/SDL_QuartzVideo.h 2014-04-04 23:48:34.000000000 +0200 @@ -91,7 +91,9 @@ CGDirectDisplayID display; /* 0 == main display (only support single display) */ const void *mode; /* current mode of the display */ const void *save_mode; /* original mode of the display */ +#if (MAC_OS_X_VERSION_MIN_REQUIRED < 1070) CGDirectPaletteRef palette; /* palette of an 8-bit display */ +#endif NSOpenGLContext *gl_context; /* OpenGL rendering context */ NSGraphicsContext *nsgfx_context; /* Cocoa graphics context */ Uint32 width, height, bpp; /* frequently used data about the display */ diff -ru SDL-1.2.15.org/src/video/quartz/SDL_QuartzWindow.h SDL-1.2.15/src/video/quartz/SDL_QuartzWindow.h --- SDL-1.2.15.org/src/video/quartz/SDL_QuartzWindow.h 2012-01-19 07:30:06.000000000 +0100 +++ SDL-1.2.15/src/video/quartz/SDL_QuartzWindow.h 2013-10-23 07:16:42.000000000 +0200 @@ -38,6 +38,7 @@ - (void)appWillUnhide:(NSNotification*)note; - (void)appDidUnhide:(NSNotification*)note; - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask backing:(NSBackingStoreType)backingType defer:(BOOL)flag; +- (BOOL)performKeyEquivalent:(NSEvent*)event; @end /* Delegate for our NSWindow to send SDLQuit() on close */ diff -ru SDL-1.2.15.org/src/video/quartz/SDL_QuartzWindow.m SDL-1.2.15/src/video/quartz/SDL_QuartzWindow.m --- SDL-1.2.15.org/src/video/quartz/SDL_QuartzWindow.m 2012-01-19 07:30:06.000000000 +0100 +++ SDL-1.2.15/src/video/quartz/SDL_QuartzWindow.m 2013-10-23 07:16:42.000000000 +0200 @@ -197,6 +197,14 @@ return [ super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag ]; } +- (BOOL)performKeyEquivalent:(NSEvent*)event +{ + /* give the menu a chance to handle the key equivalent */ + [[NSApp mainMenu] performKeyEquivalent:event]; + /* avoid beep by pretending we handled it */ + return YES; +} + @end @implementation SDL_QuartzWindowDelegate openMSX-RELEASE_0_12_0/build/3rdparty/SDL.vcxproj000066400000000000000000000644331257557151200213570ustar00rootroot00000000000000 Debug Win32 Debug x64 Developer Win32 Developer x64 Release Win32 Release x64 {81CE8DAF-EBB2-4761-8E45-B71ABCCA8C68} SDL StaticLibrary false StaticLibrary false StaticLibrary false StaticLibrary false StaticLibrary false StaticLibrary false <_ProjectFileVersion>10.0.30319.1 $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDL)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDL)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDL)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDL)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDL)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDL)\ _DEBUG;%(PreprocessorDefinitions) true true Win32 .\Debug/SDL.tlb Disabled $(DXSDK_DIR)\include;$(ThirdPartySrcDir)\$(LibNameSDL)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;_DEBUG;_WINDOWS;_WIN32_WINNT=0x0400;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebug Level3 true EditAndContinue Default _DEBUG;%(PreprocessorDefinitions) 0x0409 MachineX86 _DEBUG;%(PreprocessorDefinitions) true true X64 .\Debug/SDL.tlb Disabled $(DXSDK_DIR)\include;$(ThirdPartySrcDir)\$(LibNameSDL)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;_DEBUG;_WINDOWS;_WIN32_WINNT=0x0400;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebug Level3 true ProgramDatabase Default _DEBUG;%(PreprocessorDefinitions) 0x0409 MachineX64 NDEBUG;%(PreprocessorDefinitions) true true Win32 .\Release/SDL.tlb Full AnySuitable true Size true true true $(DXSDK_DIR)\include;$(ThirdPartySrcDir)\$(LibNameSDL)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;NDEBUG;_WINDOWS;_WIN32_WINNT=0x0400;%(PreprocessorDefinitions) true Sync MultiThreaded true true Level3 true Default NDEBUG;%(PreprocessorDefinitions) 0x0409 /LTCG %(AdditionalOptions) MachineX86 NDEBUG;%(PreprocessorDefinitions) true true X64 .\Release/SDL.tlb Full AnySuitable true Size true true true $(DXSDK_DIR)\include;$(ThirdPartySrcDir)\$(LibNameSDL)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;NDEBUG;_WINDOWS;_WIN32_WINNT=0x0400;%(PreprocessorDefinitions) true Sync MultiThreaded true true Level3 true Default NDEBUG;%(PreprocessorDefinitions) 0x0409 /LTCG %(AdditionalOptions) MachineX64 _DEBUG;%(PreprocessorDefinitions) true true Win32 .\Debug/SDL.tlb Disabled $(DXSDK_DIR)\include;$(ThirdPartySrcDir)\$(LibNameSDL)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;_DEBUG;_WINDOWS;_WIN32_WINNT=0x0400;%(PreprocessorDefinitions) true true Default MultiThreadedDebug Level3 true ProgramDatabase Default _DEBUG;%(PreprocessorDefinitions) 0x0409 MachineX86 _DEBUG;%(PreprocessorDefinitions) true true X64 .\Debug/SDL.tlb Disabled $(DXSDK_DIR)\include;$(ThirdPartySrcDir)\$(LibNameSDL)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;_DEBUG;_WINDOWS;_WIN32_WINNT=0x0400;%(PreprocessorDefinitions) true true Default MultiThreadedDebug Level3 true ProgramDatabase Default _DEBUG;%(PreprocessorDefinitions) 0x0409 MachineX64 openMSX-RELEASE_0_12_0/build/3rdparty/SDL_ttf-2.0.11.diff000066400000000000000000000011351257557151200221540ustar00rootroot00000000000000diff -ru SDL_ttf-2.0.11.org/Makefile.in SDL_ttf-2.0.11/Makefile.in --- SDL_ttf-2.0.11.org/Makefile.in 2012-01-15 05:44:08.000000000 +0100 +++ SDL_ttf-2.0.11/Makefile.in 2012-08-05 22:55:28.000000000 +0200 @@ -353,7 +353,7 @@ rm -f "$${dir}/so_locations"; \ done libSDL_ttf.la: $(libSDL_ttf_la_OBJECTS) $(libSDL_ttf_la_DEPENDENCIES) - $(libSDL_ttf_la_LINK) -rpath $(libdir) $(libSDL_ttf_la_OBJECTS) $(libSDL_ttf_la_LIBADD) $(LIBS) + $(libSDL_ttf_la_LINK) -rpath $(libdir) $(libSDL_ttf_la_OBJECTS) $(libSDL_ttf_la_LIBADD) clean-noinstPROGRAMS: @list='$(noinst_PROGRAMS)'; for p in $$list; do \ openMSX-RELEASE_0_12_0/build/3rdparty/SDL_ttf.vcxproj000066400000000000000000000463521257557151200222340ustar00rootroot00000000000000 Debug Win32 Debug x64 Developer Win32 Developer x64 Release Win32 Release x64 {DDDBD07D-DC76-4AF6-8D02-3E2DEB6EE255} SDL_ttf StaticLibrary false StaticLibrary false true StaticLibrary false StaticLibrary false StaticLibrary false true StaticLibrary false <_ProjectFileVersion>10.0.30319.1 $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDL_ttf)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDL_ttf)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDL_ttf)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDL_ttf)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDL_ttf)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDL_ttf)\ _DEBUG;%(PreprocessorDefinitions) true true Win32 .\Debug/SDL_ttf.tlb Disabled $(ThirdPartySrcDir)\$(LibNameSDL)\include;$(ThirdPartySrcDir)\$(LibNameFreeType)\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebug Level3 true EditAndContinue _DEBUG;%(PreprocessorDefinitions) 0x0409 true .\Debug/SDL_ttf.bsc MachineX86 _DEBUG;%(PreprocessorDefinitions) true true X64 .\Debug/SDL_ttf.tlb Disabled $(ThirdPartySrcDir)\$(LibNameSDL)\include;$(ThirdPartySrcDir)\$(LibNameFreeType)\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebug Level3 true ProgramDatabase _DEBUG;%(PreprocessorDefinitions) 0x0409 true .\Debug/SDL_ttf.bsc MachineX64 NDEBUG;%(PreprocessorDefinitions) true true Win32 .\Release/SDL_ttf.tlb Full AnySuitable true Size true true true $(ThirdPartySrcDir)\$(LibNameSDL)\include;$(ThirdPartySrcDir)\$(LibNameFreeType)\include;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true Sync MultiThreaded true true Level3 true NDEBUG;%(PreprocessorDefinitions) 0x0409 true .\Release/SDL_ttf.bsc MachineX86 NDEBUG;%(PreprocessorDefinitions) true true X64 .\Release/SDL_ttf.tlb Full AnySuitable true Size true true true $(ThirdPartySrcDir)\$(LibNameSDL)\include;$(ThirdPartySrcDir)\$(LibNameFreeType)\include;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true Sync MultiThreaded true true Level3 true NDEBUG;%(PreprocessorDefinitions) 0x0409 true .\Release/SDL_ttf.bsc MachineX64 _DEBUG;%(PreprocessorDefinitions) true true Win32 .\Debug/SDL_ttf.tlb Disabled $(ThirdPartySrcDir)\$(LibNameSDL)\include;$(ThirdPartySrcDir)\$(LibNameFreeType)\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) true true Default MultiThreadedDebug Level3 true ProgramDatabase _DEBUG;%(PreprocessorDefinitions) 0x0409 true .\Debug/SDL_ttf.bsc MachineX86 _DEBUG;%(PreprocessorDefinitions) true true X64 .\Debug/SDL_ttf.tlb Disabled $(ThirdPartySrcDir)\$(LibNameSDL)\include;$(ThirdPartySrcDir)\$(LibNameFreeType)\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) true true Default MultiThreadedDebug Level3 true ProgramDatabase _DEBUG;%(PreprocessorDefinitions) 0x0409 true .\Debug/SDL_ttf.bsc MachineX64 %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) openMSX-RELEASE_0_12_0/build/3rdparty/SDLmain.vcxproj000066400000000000000000000453351257557151200222240ustar00rootroot00000000000000 Debug Win32 Debug x64 Developer Win32 Developer x64 Release_STDIO Win32 Release_STDIO x64 Release Win32 Release x64 {DA956FD3-E142-46F2-9DD5-C78BEBB56B7A} SDLmain StaticLibrary false StaticLibrary false StaticLibrary false StaticLibrary false StaticLibrary false StaticLibrary false StaticLibrary false StaticLibrary false <_ProjectFileVersion>10.0.30319.1 $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDLmain)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDLmain)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDLmain)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDLmain)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDLmain)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDLmain)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDLmain)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameSDLmain)\ Full AnySuitable true true true true $(ThirdPartySrcDir)\$(LibNameSDL)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true MultiThreaded true Level3 true Default /LTCG %(AdditionalOptions) true X64 Full AnySuitable true true true true $(ThirdPartySrcDir)\$(LibNameSDL)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) true MultiThreaded true Level3 true Default /LTCG %(AdditionalOptions) true Full AnySuitable true Size true true true $(ThirdPartySrcDir)\$(LibNameSDL)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_WINDOWS;NO_STDIO_REDIRECT;%(PreprocessorDefinitions) true Sync MultiThreaded true true Level3 true Default /LTCG %(AdditionalOptions) true MachineX86 X64 Full AnySuitable true Size true true true $(ThirdPartySrcDir)\$(LibNameSDL)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;WIN32;NDEBUG;_WINDOWS;NO_STDIO_REDIRECT;%(PreprocessorDefinitions) true Sync MultiThreaded true true Level3 true Default /LTCG %(AdditionalOptions) true MachineX64 Disabled $(ThirdPartySrcDir)\$(LibNameSDL)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebug Level3 true ProgramDatabase Default true MachineX86 X64 Disabled $(ThirdPartySrcDir)\$(LibNameSDL)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebug Level3 true ProgramDatabase Default true MachineX64 Disabled $(ThirdPartySrcDir)\$(LibNameSDL)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) true true Default MultiThreadedDebug Level3 true ProgramDatabase Default true MachineX86 X64 Disabled $(ThirdPartySrcDir)\$(LibNameSDL)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) true true Default MultiThreadedDebug Level3 true ProgramDatabase Default true MachineX64 openMSX-RELEASE_0_12_0/build/3rdparty/freetype-2.4.12.diff000066400000000000000000000240771257557151200225170ustar00rootroot00000000000000diff -ru freetype-2.4.12.orig/include/freetype/config/ftoption.h freetype-2.4.12/include/freetype/config/ftoption.h --- freetype-2.4.12.orig/include/freetype/config/ftoption.h 2013-01-22 14:31:11.000000000 +0100 +++ freetype-2.4.12/include/freetype/config/ftoption.h 2013-05-18 12:01:54.000000000 +0200 @@ -148,7 +148,7 @@ /* */ /* Define this macro if you want to enable this `feature'. */ /* */ -#define FT_CONFIG_OPTION_USE_LZW +/* #define FT_CONFIG_OPTION_USE_LZW */ /*************************************************************************/ @@ -163,7 +163,7 @@ /* Define this macro if you want to enable this `feature'. See also */ /* the macro FT_CONFIG_OPTION_SYSTEM_ZLIB below. */ /* */ -#define FT_CONFIG_OPTION_USE_ZLIB +/* #define FT_CONFIG_OPTION_USE_ZLIB */ /*************************************************************************/ @@ -296,7 +296,7 @@ /* able to synthesize a Unicode charmap out of the glyphs found in the */ /* fonts. */ /* */ -#define FT_CONFIG_OPTION_ADOBE_GLYPH_LIST +#undef FT_CONFIG_OPTION_ADOBE_GLYPH_LIST /*************************************************************************/ @@ -309,7 +309,7 @@ /* */ /* Note that the `FOND' resource isn't checked. */ /* */ -#define FT_CONFIG_OPTION_MAC_FONTS +/* #define FT_CONFIG_OPTION_MAC_FONTS */ /*************************************************************************/ @@ -487,7 +487,7 @@ /* embedded bitmaps in all formats using the SFNT module (namely */ /* TrueType & OpenType). */ /* */ -#define TT_CONFIG_OPTION_EMBEDDED_BITMAPS +/* #define TT_CONFIG_OPTION_EMBEDDED_BITMAPS */ /*************************************************************************/ @@ -502,7 +502,7 @@ /* */ /* (By default, the module uses `PSNames' to extract glyph names.) */ /* */ -#define TT_CONFIG_OPTION_POSTSCRIPT_NAMES +#undef TT_CONFIG_OPTION_POSTSCRIPT_NAMES /*************************************************************************/ @@ -516,7 +516,7 @@ /* Accessing SFNT names is done through the functions declared in */ /* `freetype/ftsnames.h'. */ /* */ -#define TT_CONFIG_OPTION_SFNT_NAMES +/* #define TT_CONFIG_OPTION_SFNT_NAMES */ /*************************************************************************/ @@ -669,7 +669,7 @@ /* and avar tables). This has many similarities to Type 1 Multiple */ /* Masters support. */ /* */ -#define TT_CONFIG_OPTION_GX_VAR_SUPPORT +/* #define TT_CONFIG_OPTION_GX_VAR_SUPPORT */ /*************************************************************************/ @@ -677,7 +677,7 @@ /* Define TT_CONFIG_OPTION_BDF if you want to include support for */ /* an embedded `BDF ' table within SFNT-based bitmap formats. */ /* */ -#define TT_CONFIG_OPTION_BDF +/* #define TT_CONFIG_OPTION_BDF */ /*************************************************************************/ @@ -723,7 +723,7 @@ /* files into an existing face. Note that if set, the T1 driver will be */ /* unable to produce kerning distances. */ /* */ -#undef T1_CONFIG_OPTION_NO_AFM +#define T1_CONFIG_OPTION_NO_AFM /*************************************************************************/ diff -ru freetype-2.4.12.orig/modules.cfg freetype-2.4.12/modules.cfg --- freetype-2.4.12.orig/modules.cfg 2011-11-26 13:19:10.000000000 +0100 +++ freetype-2.4.12/modules.cfg 2013-05-18 12:06:58.000000000 +0200 @@ -37,35 +37,35 @@ # PostScript Type 1 font driver. # # This driver needs the `psaux', `pshinter', and `psnames' modules. -FONT_MODULES += type1 +#FONT_MODULES += type1 # CFF/OpenType font driver. # # This driver needs the `sfnt', `pshinter', and `psnames' modules. -FONT_MODULES += cff +#FONT_MODULES += cff # Type 1 CID-keyed font driver. # # This driver needs the `psaux', `pshinter', and `psnames' modules. -FONT_MODULES += cid +#FONT_MODULES += cid # PFR/TrueDoc font driver. See optional extension ftpfr.c below also. -FONT_MODULES += pfr +#FONT_MODULES += pfr # PostScript Type 42 font driver. # # This driver needs the `truetype' and `psaux' modules. -FONT_MODULES += type42 +#FONT_MODULES += type42 # Windows FONT/FNT font driver. See optional extension ftwinfnt.c below # also. -FONT_MODULES += winfonts +#FONT_MODULES += winfonts # PCF font driver. -FONT_MODULES += pcf +#FONT_MODULES += pcf # BDF font driver. See optional extension ftbdf.c below also. -FONT_MODULES += bdf +#FONT_MODULES += bdf # SFNT files support. If used without `truetype' or `cff', it supports # bitmap-only fonts within an SFNT wrapper. @@ -79,10 +79,10 @@ #### # FreeType's auto hinter. -HINTING_MODULES += autofit +#HINTING_MODULES += autofit # PostScript hinter. -HINTING_MODULES += pshinter +#HINTING_MODULES += pshinter # The TrueType hinting engine doesn't have a module of its own but is # controlled in file include/freetype/config/ftoption.h @@ -94,7 +94,7 @@ #### # Monochrome rasterizer. -RASTER_MODULES += raster +#RASTER_MODULES += raster # Anti-aliasing rasterizer. RASTER_MODULES += smooth @@ -107,7 +107,7 @@ # FreeType's cache sub-system (quite stable but still in beta -- this means # that its public API is subject to change if necessary). See # include/freetype/ftcache.h. Needs ftglyph.c. -AUX_MODULES += cache +#AUX_MODULES += cache # TrueType GX/AAT table validation. Needs ftgxval.c below. # AUX_MODULES += gxvalid @@ -115,12 +115,12 @@ # Support for streams compressed with gzip (files with suffix .gz). # # See include/freetype/ftgzip.h for the API. -AUX_MODULES += gzip +#AUX_MODULES += gzip # Support for streams compressed with LZW (files with suffix .Z). # # See include/freetype/ftlzw.h for the API. -AUX_MODULES += lzw +#AUX_MODULES += lzw # Support for streams compressed with bzip2 (files with suffix .bz2). # @@ -134,13 +134,13 @@ # Auxiliary PostScript driver component to share common code. # # This module depends on `psnames'. -AUX_MODULES += psaux +#AUX_MODULES += psaux # Support for PostScript glyph names. # # This module can be controlled in ftconfig.h # (FT_CONFIG_OPTION_POSTSCRIPT_NAMES). -AUX_MODULES += psnames +#AUX_MODULES += psnames #### @@ -150,12 +150,12 @@ # Exact bounding box calculation. # # See include/freetype/ftbbox.h for the API. -BASE_EXTENSIONS += ftbbox.c +#BASE_EXTENSIONS += ftbbox.c # Access BDF-specific strings. Needs BDF font driver. # # See include/freetype/ftbdf.h for the API. -BASE_EXTENSIONS += ftbdf.c +#BASE_EXTENSIONS += ftbdf.c # Utility functions for converting 1bpp, 2bpp, 4bpp, and 8bpp bitmaps into # 8bpp format, and for emboldening of bitmap glyphs. @@ -166,17 +166,17 @@ # Access CID font information. # # See include/freetype/ftcid.h for the API. -BASE_EXTENSIONS += ftcid.c +#BASE_EXTENSIONS += ftcid.c # Access FSType information. Needs fttype1.c. # # See include/freetype/freetype.h for the API. -BASE_EXTENSIONS += ftfstype.c +#BASE_EXTENSIONS += ftfstype.c # Support for GASP table queries. # # See include/freetype/ftgasp.h for the API. -BASE_EXTENSIONS += ftgasp.c +#BASE_EXTENSIONS += ftgasp.c # Convenience functions to handle glyphs. Needs ftbitmap.c. # @@ -186,32 +186,32 @@ # Interface for gxvalid module. # # See include/freetype/ftgxval.h for the API. -BASE_EXTENSIONS += ftgxval.c +#BASE_EXTENSIONS += ftgxval.c # Support for LCD color filtering of subpixel bitmaps. # # See include/freetype/ftlcdfil.h for the API. -BASE_EXTENSIONS += ftlcdfil.c +#BASE_EXTENSIONS += ftlcdfil.c # Multiple Master font interface. # # See include/freetype/ftmm.h for the API. -BASE_EXTENSIONS += ftmm.c +#BASE_EXTENSIONS += ftmm.c # Interface for otvalid module. # # See include/freetype/ftotval.h for the API. -BASE_EXTENSIONS += ftotval.c +#BASE_EXTENSIONS += ftotval.c # Support for FT_Face_CheckTrueTypePatents. # # See include/freetype/freetype.h for the API. -BASE_EXTENSIONS += ftpatent.c +#BASE_EXTENSIONS += ftpatent.c # Interface for accessing PFR-specific data. Needs PFR font driver. # # See include/freetype/ftpfr.h for the API. -BASE_EXTENSIONS += ftpfr.c +#BASE_EXTENSIONS += ftpfr.c # Path stroker. Needs ftglyph.c. # @@ -221,24 +221,24 @@ # Support for synthetic embolding and slanting of fonts. Needs ftbitmap.c. # # See include/freetype/ftsynth.h for the API. -BASE_EXTENSIONS += ftsynth.c +#BASE_EXTENSIONS += ftsynth.c # Interface to access data specific to PostScript Type 1 and Type 2 (CFF) # fonts. # # See include/freetype/t1tables.h for the API. -BASE_EXTENSIONS += fttype1.c +#BASE_EXTENSIONS += fttype1.c # Interface for accessing data specific to Windows FNT files. Needs winfnt # driver. # # See include/freetype/ftwinfnt.h for the API. -BASE_EXTENSIONS += ftwinfnt.c +#BASE_EXTENSIONS += ftwinfnt.c # Support functions for X11. # # See include/freetype/ftxf86.h for the API. -BASE_EXTENSIONS += ftxf86.c +#BASE_EXTENSIONS += ftxf86.c #### #### The components `ftsystem.c' (for memory allocation and stream I/O openMSX-RELEASE_0_12_0/build/3rdparty/freetype.vcxproj000066400000000000000000002636521257557151200225640ustar00rootroot00000000000000 Debug Win32 Debug x64 Developer Win32 Developer x64 Release Win32 Release x64 {78B079BD-9FC7-4B9E-B4A6-96DA0F00248B} freetype StaticLibrary false MultiByte StaticLibrary false MultiByte StaticLibrary false MultiByte true StaticLibrary false MultiByte StaticLibrary false MultiByte StaticLibrary false MultiByte true <_ProjectFileVersion>10.0.30319.1 $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameFreeType)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameFreeType)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameFreeType)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameFreeType)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameFreeType)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameFreeType)\ Full AnySuitable true Size true true true $(ThirdPartySrcDir)\$(LibNameFreeType)\include;%(AdditionalIncludeDirectories) NDEBUG;WIN32;_LIB;FT2_BUILD_LIBRARY;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true Sync Default MultiThreaded true true true Level4 Default 4001;%(DisableSpecificWarnings) NDEBUG;%(PreprocessorDefinitions) 0x0409 true MachineX86 X64 Full AnySuitable true Size true true true $(ThirdPartySrcDir)\$(LibNameFreeType)\include;%(AdditionalIncludeDirectories) NDEBUG;WIN32;_LIB;FT2_BUILD_LIBRARY;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true Sync Default MultiThreaded true true true Level4 Default 4001;%(DisableSpecificWarnings) NDEBUG;%(PreprocessorDefinitions) 0x0409 true MachineX64 set LibFullName=Foo Disabled $(ThirdPartySrcDir)\$(LibNameFreeType)\include;%(AdditionalIncludeDirectories) _DEBUG;WIN32;_LIB;FT_DEBUG_LEVEL_ERROR;FT_DEBUG_LEVEL_TRACE;FT2_BUILD_LIBRARY;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) false false true EnableFastChecks MultiThreadedDebug true Level4 ProgramDatabase Default 4001;%(DisableSpecificWarnings) _DEBUG;%(PreprocessorDefinitions) 0x0409 true MachineX86 set LibFullName=Foo X64 Disabled $(ThirdPartySrcDir)\$(LibNameFreeType)\include;%(AdditionalIncludeDirectories) _DEBUG;WIN32;_LIB;FT_DEBUG_LEVEL_ERROR;FT_DEBUG_LEVEL_TRACE;FT2_BUILD_LIBRARY;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) false false true EnableFastChecks MultiThreadedDebug true Level4 ProgramDatabase Default 4001;%(DisableSpecificWarnings) _DEBUG;%(PreprocessorDefinitions) 0x0409 true MachineX64 set LibFullName=Foo Disabled $(ThirdPartySrcDir)\$(LibNameFreeType)\include;%(AdditionalIncludeDirectories) _DEBUG;WIN32;_LIB;FT_DEBUG_LEVEL_ERROR;FT_DEBUG_LEVEL_TRACE;FT2_BUILD_LIBRARY;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) false false true true Default MultiThreadedDebug true Level4 ProgramDatabase Default 4001;%(DisableSpecificWarnings) _DEBUG;%(PreprocessorDefinitions) 0x0409 true MachineX86 set LibFullName=Foo X64 Disabled $(ThirdPartySrcDir)\$(LibNameFreeType)\include;%(AdditionalIncludeDirectories) _DEBUG;WIN32;_LIB;FT_DEBUG_LEVEL_ERROR;FT_DEBUG_LEVEL_TRACE;FT2_BUILD_LIBRARY;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) false false true true Default MultiThreadedDebug true Level4 ProgramDatabase Default 4001;%(DisableSpecificWarnings) _DEBUG;%(PreprocessorDefinitions) 0x0409 true MachineX64 Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks false Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks false Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks false Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks false MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) false MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) false Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks Disabled %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) EnableFastChecks MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) MaxSpeed %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) openMSX-RELEASE_0_12_0/build/3rdparty/freetype.vcxproj.filters000066400000000000000000000140141257557151200242150ustar00rootroot00000000000000 {62c90e15-3afc-4349-8061-9d4644f7b19c} cpp;c;cxx;rc;def;r;odl;idl;hpj;bat {b30a8e21-f649-4e57-88c5-ea62fc84dd51} h;hpp;hxx;hm;inl Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Header Files Header Files Header Files Header Files Header Files Header Files openMSX-RELEASE_0_12_0/build/3rdparty/glew-1.9.0.diff000066400000000000000000000137061257557151200215500ustar00rootroot00000000000000diff -ru glew-1.9.0.orig/build/glew.rc glew-1.9.0/build/glew.rc --- glew-1.9.0.orig/build/glew.rc 2012-08-06 17:59:08.000000000 +0200 +++ glew-1.9.0/build/glew.rc 2013-05-25 21:44:27.000000000 +0200 @@ -56,7 +56,7 @@ BEGIN BLOCK "040904b0" BEGIN - VALUE "Comments", "The OpenGL Extension Wrangler Library\r\nCopyright (C) 2002-2008, Milan Ikits \r\nCopyright (C) 2002-2008, Marcelo E. Magallon \r\nCopyright (C) 2002, Lev Povalahev\r\nAll rights reserved.\r\n\r\nRedistribution and use in source and binary forms, with or without \r\nmodification, are permitted provided that the following conditions are met:\r\n\r\n* Redistributions of source code must retain the above copyright notice, \r\n this list of conditions and the following disclaimer.\r\n* Redistributions in binary form must reproduce the above copyright notice, \r\n this list of conditions and the following disclaimer in the documentation \r\n and/or other materials provided with the distribution.\r\n* The name of the author may be used to endorse or promote products \r\n derived from this software without specific prior written permission.\r\n\r\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS'' \r\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE \r\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r\nARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE \r\nLIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR \r\nCONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF \r\nSUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r\nINTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\r\nCONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\r\nARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF\r\nTHE POSSIBILITY OF SUCH DAMAGE.\r\n\r\n\r\nMesa 3-D graphics library\r\n\r\nVersion: 7.0\r\n\r\nCopyright (C) 1999-2007 Brian Paul All Rights Reserved.\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a\r\ncopy of this software and associated documentation files (the ''Software''),\r\nto deal in the Software without restriction, including without limitation\r\nthe rights to use, copy, modify, merge, publish, distribute, sublicense,\r\nand/or sell copies of the Software, and to permit persons to whom the\r\nSoftware is furnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included\r\nin all copies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED ''AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS\r\nOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\r\nBRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN\r\nAN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\r\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n\r\n\r\nCopyright (c) 2007 The Khronos Group Inc.\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a\r\ncopy of this software and/or associated documentation files (the\r\n''Materials''), to deal in the Materials without restriction, including\r\nwithout limitation the rights to use, copy, modify, merge, publish,\r\ndistribute, sublicense, and/or sell copies of the Materials, and to\r\npermit persons to whom the Materials are furnished to do so, subject to\r\nthe following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included\r\nin all copies or substantial portions of the Materials.\r\n\r\nTHE MATERIALS ARE PROVIDED ''AS IS'', WITHOUT WARRANTY OF ANY KIND,\r\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\r\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\r\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\r\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\r\nMATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.\0" + VALUE "Comments", "The description that was here fails to compile on Visual Studio, see https://sourceforge.net/p/glew/bugs/201/. It doesn't end up in any openMSX binary anyway.\0" VALUE "CompanyName", "\0" VALUE "FileDescription", "The OpenGL Extension Wrangler Library\0" VALUE "FileVersion", "1,9,0,0\0" diff -ru glew-1.9.0.orig/include/GL/glew.h glew-1.9.0/include/GL/glew.h --- glew-1.9.0.orig/include/GL/glew.h 2012-08-06 17:59:08.000000000 +0200 +++ glew-1.9.0/include/GL/glew.h 2013-05-25 21:39:27.000000000 +0200 @@ -113,7 +113,7 @@ #define GLEW_APIENTRY_DEFINED # if defined(__MINGW32__) || defined(__CYGWIN__) # define APIENTRY __stdcall -# elif (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED) || defined(__BORLANDC__) +# elif (defined(_MSC_VER) && (_MSC_VER >= 800)) || defined(_STDCALL_SUPPORTED) || defined(__BORLANDC__) # define APIENTRY __stdcall # else # define APIENTRY diff -ru glew-1.9.0.orig/Makefile glew-1.9.0/Makefile --- glew-1.9.0.orig/Makefile 2012-08-06 17:59:08.000000000 +0200 +++ glew-1.9.0/Makefile 2013-05-25 21:39:27.000000000 +0200 @@ -72,7 +72,9 @@ OPT = $(POPT) endif INCLUDE = -Iinclude -CFLAGS = $(OPT) $(WARN) $(INCLUDE) $(CFLAGS.EXTRA) +# openMSX dedicated build: use flavour's optimization flags instead: +#CFLAGS = $(OPT) $(WARN) $(INCLUDE) $(CFLAGS.EXTRA) +CFLAGS += $(WARN) $(INCLUDE) $(CFLAGS.EXTRA) all debug: glew.lib glew.lib.mx glew.bin @@ -262,7 +264,7 @@ install.bin: glew.bin $(INSTALL) -d -m 0755 $(BINDIR) - $(INSTALL) -s -m 0755 bin/$(GLEWINFO.BIN) bin/$(VISUALINFO.BIN) $(BINDIR)/ +# $(INSTALL) -s -m 0755 bin/$(GLEWINFO.BIN) bin/$(VISUALINFO.BIN) $(BINDIR)/ install.include: $(INSTALL) -d -m 0755 $(INCDIR) openMSX-RELEASE_0_12_0/build/3rdparty/glew.vcxproj000066400000000000000000000454411257557151200216710ustar00rootroot00000000000000 Debug Win32 Debug x64 Developer Win32 Developer x64 Release Win32 Release x64 {E78E1412-E9F8-475B-9504-72031C8FBAEA} glew_static StaticLibrary false MultiByte StaticLibrary false MultiByte true StaticLibrary false MultiByte StaticLibrary false MultiByte StaticLibrary false MultiByte true StaticLibrary false MultiByte <_ProjectFileVersion>10.0.30319.1 $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameGlew)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameGlew)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameGlew)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameGlew)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameGlew)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameGlew)\ Disabled $(ThirdPartySrcDir)\$(LibNameGlew)\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_LIB;WIN32_LEAN_AND_MEAN;VC_EXTRALEAN;GLEW_STATIC;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebug Level3 true ProgramDatabase _DEBUG;GLEW_MX;GLEW_STATIC;%(PreprocessorDefinitions) 0x0409 true MachineX86 true .\glew/lib/glew_static.bsc X64 Disabled $(ThirdPartySrcDir)\$(LibNameGlew)\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_LIB;WIN32_LEAN_AND_MEAN;VC_EXTRALEAN;GLEW_STATIC;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebug Level3 true ProgramDatabase _DEBUG;GLEW_MX;GLEW_STATIC;%(PreprocessorDefinitions) 0x0409 true MachineX64 true .\glew/lib/glew_static.bsc Full AnySuitable true Size true true true $(ThirdPartySrcDir)\$(LibNameGlew)\include;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_LIB;WIN32_LEAN_AND_MEAN;VC_EXTRALEAN;GLEW_STATIC;%(PreprocessorDefinitions) true Sync MultiThreaded true true Level3 true NDEBUG;GLEW_MX;GLEW_STATIC;%(PreprocessorDefinitions) 0x0409 true MachineX86 true .\glew/lib/glew_static.bsc X64 Full AnySuitable true Size true true true $(ThirdPartySrcDir)\$(LibNameGlew)\include;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_LIB;WIN32_LEAN_AND_MEAN;VC_EXTRALEAN;GLEW_STATIC;%(PreprocessorDefinitions) true Sync MultiThreaded true true Level3 true NDEBUG;GLEW_MX;GLEW_STATIC;%(PreprocessorDefinitions) 0x0409 true MachineX64 true .\glew/lib/glew_static.bsc Disabled $(ThirdPartySrcDir)\$(LibNameGlew)\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_LIB;WIN32_LEAN_AND_MEAN;VC_EXTRALEAN;GLEW_STATIC;%(PreprocessorDefinitions) true true Default MultiThreadedDebug Level3 true ProgramDatabase _DEBUG;GLEW_MX;GLEW_STATIC;%(PreprocessorDefinitions) 0x0409 true MachineX86 true .\glew/lib/glew_static.bsc X64 Disabled $(ThirdPartySrcDir)\$(LibNameGlew)\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_LIB;WIN32_LEAN_AND_MEAN;VC_EXTRALEAN;GLEW_STATIC;%(PreprocessorDefinitions) true true Default MultiThreadedDebug Level3 true ProgramDatabase _DEBUG;GLEW_MX;GLEW_STATIC;%(PreprocessorDefinitions) 0x0409 true MachineX64 true .\glew/lib/glew_static.bsc %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) openMSX-RELEASE_0_12_0/build/3rdparty/glew.vcxproj.filters000066400000000000000000000024541257557151200233350ustar00rootroot00000000000000 {16b0a29f-ab69-458e-9125-149ccd95d72c} cpp;c;cxx;rc;def;r;odl;idl;hpj;bat {86b057dd-7908-43d0-b8ff-6e7630b2d0b1} h;hpp;hxx;hm;inl {1fc2b525-9c62-4fc0-8055-27e54fe40b7f} Source Files Header Files Header Files Resources openMSX-RELEASE_0_12_0/build/3rdparty/libogg.vcxproj000066400000000000000000000325311257557151200221720ustar00rootroot00000000000000 Debug Win32 Debug x64 Developer Win32 Developer x64 Release Win32 Release x64 {15CBFEFF-7965-41F5-B4E2-21E8795C9159} libogg Win32Proj StaticLibrary Unicode StaticLibrary Unicode true StaticLibrary Unicode StaticLibrary Unicode StaticLibrary Unicode true StaticLibrary Unicode <_ProjectFileVersion>10.0.30319.1 $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameOgg)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameOgg)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameOgg)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameOgg)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameOgg)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameOgg)\ Disabled $(ThirdPartySrcDir)\$(LibNameOgg)\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBOGG_EXPORTS;%(PreprocessorDefinitions) true Default MultiThreadedDebug Level4 EditAndContinue CompileAsC MachineX86 X64 Disabled $(ThirdPartySrcDir)\$(LibNameOgg)\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBOGG_EXPORTS;%(PreprocessorDefinitions) true Default MultiThreadedDebug Level4 ProgramDatabase CompileAsC MachineX64 Full AnySuitable true Size true true $(ThirdPartySrcDir)\$(LibNameOgg)\include;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBOGG_EXPORTS;%(PreprocessorDefinitions) true Sync MultiThreaded true true Level4 CompileAsC 4244;%(DisableSpecificWarnings) MachineX86 X64 Full AnySuitable true Size true true $(ThirdPartySrcDir)\$(LibNameOgg)\include;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBOGG_EXPORTS;%(PreprocessorDefinitions) true Sync MultiThreaded true true Level4 CompileAsC 4244;%(DisableSpecificWarnings) MachineX64 Disabled $(ThirdPartySrcDir)\$(LibNameOgg)\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBOGG_EXPORTS;%(PreprocessorDefinitions) true true Default MultiThreadedDebug Level4 EditAndContinue CompileAsC MachineX86 X64 Disabled $(ThirdPartySrcDir)\$(LibNameOgg)\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBOGG_EXPORTS;%(PreprocessorDefinitions) true Default MultiThreadedDebug Level4 ProgramDatabase CompileAsC MachineX64 openMSX-RELEASE_0_12_0/build/3rdparty/libogg.vcxproj.filters000066400000000000000000000025611257557151200236410ustar00rootroot00000000000000 {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hpp;hxx;hm;inl;inc;xsd {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx Source Files Source Files Header Files Header Files openMSX-RELEASE_0_12_0/build/3rdparty/libpng.vcxproj000066400000000000000000000365761257557151200222170ustar00rootroot00000000000000 Debug Win32 Debug x64 Developer Win32 Developer x64 Release Win32 Release x64 {0008960E-E0DD-41A6-8265-00B31DDB4C21} libpng StaticLibrary StaticLibrary StaticLibrary StaticLibrary StaticLibrary StaticLibrary <_ProjectFileVersion>10.0.30319.1 $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameLibPng)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameLibPng)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameLibPng)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameLibPng)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameLibPng)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameLibPng)\ Full AnySuitable true Size true true true $(ThirdPartySrcDir)\$(LibNameLibPng);$(ThirdPartySrcDir)\$(LibNameZlib);%(AdditionalIncludeDirectories) WIN32;NDEBUG;PNG_USE_PNGVCRD;PNG_LIBPNG_SPECIALBUILD;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true Sync MultiThreaded true true png.h Level3 Default /LTCG %(AdditionalOptions) MachineX86 X64 Full AnySuitable true Size true true true $(ThirdPartySrcDir)\$(LibNameLibPng);$(ThirdPartySrcDir)\$(LibNameZlib);%(AdditionalIncludeDirectories) WIN32;NDEBUG;PNG_USE_PNGVCRD;PNG_LIBPNG_SPECIALBUILD;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true Sync MultiThreaded true true png.h Level3 Default /LTCG %(AdditionalOptions) MachineX64 Disabled $(ThirdPartySrcDir)\$(LibNameLibPng);$(ThirdPartySrcDir)\$(LibNameZlib);%(AdditionalIncludeDirectories) WIN32;_DEBUG;DEBUG;PNG_DEBUG=1;PNG_USE_PNGVCRD;PNG_LIBPNG_SPECIALBUILD;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebug png.h Level3 EditAndContinue Default MachineX86 X64 Disabled $(ThirdPartySrcDir)\$(LibNameLibPng);$(ThirdPartySrcDir)\$(LibNameZlib);%(AdditionalIncludeDirectories) WIN32;_DEBUG;DEBUG;PNG_DEBUG=1;PNG_USE_PNGVCRD;PNG_LIBPNG_SPECIALBUILD;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebug png.h Level3 ProgramDatabase Default MachineX64 Disabled $(ThirdPartySrcDir)\$(LibNameLibPng);$(ThirdPartySrcDir)\$(LibNameZlib);%(AdditionalIncludeDirectories) WIN32;_DEBUG;DEBUG;PNG_DEBUG=1;PNG_USE_PNGVCRD;PNG_LIBPNG_SPECIALBUILD;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true true Default MultiThreadedDebug png.h Level3 ProgramDatabase Default MachineX86 X64 Disabled $(ThirdPartySrcDir)\$(LibNameLibPng);$(ThirdPartySrcDir)\$(LibNameZlib);%(AdditionalIncludeDirectories) WIN32;_DEBUG;DEBUG;PNG_DEBUG=1;PNG_USE_PNGVCRD;PNG_LIBPNG_SPECIALBUILD;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true true Default MultiThreadedDebug png.h Level3 ProgramDatabase Default MachineX64 openMSX-RELEASE_0_12_0/build/3rdparty/libpng.vcxproj.filters000066400000000000000000000064511257557151200236530ustar00rootroot00000000000000 {cfb12311-2c25-406d-90fa-1a94e04b9107} cpp;c;cxx;rc;def;r;odl;idl;hpj;bat {dadb6a38-0397-4c12-958f-43fc35c69553} h;hpp;hxx;hm;inl {d19955fe-f136-45bb-baa2-ab0c5710c4f1} ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Header Files Header Files Resource Files openMSX-RELEASE_0_12_0/build/3rdparty/libtheora-1.1.1.diff000066400000000000000000000012471257557151200225510ustar00rootroot00000000000000diff -ru libtheora-1.1.1.org/configure libtheora-1.1.1/configure --- libtheora-1.1.1.org/configure 2009-10-01 20:04:08.000000000 +0200 +++ libtheora-1.1.1/configure 2014-04-05 00:00:32.000000000 +0200 @@ -11863,7 +11863,7 @@ case $host in *) DEBUG="-g -Wall -Wno-parentheses -DDEBUG -D__NO_MATH_INLINES" - CFLAGS="-Wall -Wno-parentheses -O3 -fforce-addr -fomit-frame-pointer -finline-functions -funroll-loops" + CFLAGS="-Wall -Wno-parentheses -O3 -fomit-frame-pointer -finline-functions -funroll-loops" PROFILE="-Wall -Wno-parentheses -pg -g -O3 -fno-inline-functions -DDEBUG";; esac fi openMSX-RELEASE_0_12_0/build/3rdparty/libtheora.vcxproj000066400000000000000000000576741257557151200227170ustar00rootroot00000000000000 Debug Win32 Debug x64 Developer Win32 Developer x64 Release Win32 Release x64 {653F3841-3F26-49B9-AFCF-091DB4B67031} libtheora Win32Proj StaticLibrary Unicode StaticLibrary Unicode true StaticLibrary Unicode StaticLibrary Unicode StaticLibrary Unicode true StaticLibrary Unicode <_ProjectFileVersion>10.0.30319.1 $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameTheora)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameTheora)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameTheora)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameTheora)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameTheora)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameTheora)\ Disabled $(ThirdPartySrcDir)\$(LibNameTheora)\include;$(ThirdPartySrcDir)\$(LibNameOgg)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_BIND_TO_CURRENT_CRT_VERSION;WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBTHEORA_EXPORTS;DEBUG;OC_X86_ASM;%(PreprocessorDefinitions) true Default MultiThreadedDebug Level3 ProgramDatabase MachineX86 X64 Disabled $(ThirdPartySrcDir)\$(LibNameTheora)\include;$(ThirdPartySrcDir)\$(LibNameOgg)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_BIND_TO_CURRENT_CRT_VERSION;WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBTHEORA_EXPORTS;DEBUG;%(PreprocessorDefinitions) true Default MultiThreadedDebug Level3 ProgramDatabase MachineX64 Full AnySuitable true Size true true $(ThirdPartySrcDir)\$(LibNameTheora)\include;$(ThirdPartySrcDir)\$(LibNameOgg)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_BIND_TO_CURRENT_CRT_VERSION;WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBTHEORA_EXPORTS;OC_X86_ASM;%(PreprocessorDefinitions) true Sync MultiThreaded true true Level4 CompileAsC 4244;4267;4057;4100;4245;%(DisableSpecificWarnings) MachineX86 X64 Full AnySuitable true Size true true $(ThirdPartySrcDir)\$(LibNameTheora)\include;$(ThirdPartySrcDir)\$(LibNameOgg)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_BIND_TO_CURRENT_CRT_VERSION;WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBTHEORA_EXPORTS;%(PreprocessorDefinitions) true Sync MultiThreaded true true Level4 CompileAsC 4244;4267;4057;4100;4245;%(DisableSpecificWarnings) MachineX64 Disabled $(ThirdPartySrcDir)\$(LibNameTheora)\include;$(ThirdPartySrcDir)\$(LibNameOgg)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_BIND_TO_CURRENT_CRT_VERSION;WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBTHEORA_EXPORTS;DEBUG;OC_X86_ASM;%(PreprocessorDefinitions) true true Default MultiThreadedDebug Level3 ProgramDatabase MachineX86 X64 Disabled $(ThirdPartySrcDir)\$(LibNameTheora)\include;$(ThirdPartySrcDir)\$(LibNameOgg)\include;%(AdditionalIncludeDirectories) _CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_BIND_TO_CURRENT_CRT_VERSION;WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBTHEORA_EXPORTS;DEBUG;%(PreprocessorDefinitions) true Default MultiThreadedDebug Level3 ProgramDatabase MachineX64 true true true true true true $(IntDir)%(Filename)1.obj $(IntDir)%(Filename)1.xdc $(IntDir)%(Filename)1.obj $(IntDir)%(Filename)1.xdc $(IntDir)%(Filename)1.obj $(IntDir)%(Filename)1.xdc $(IntDir)%(Filename)1.obj $(IntDir)%(Filename)1.xdc $(IntDir)%(Filename)1.obj $(IntDir)%(Filename)1.xdc $(IntDir)%(Filename)1.obj $(IntDir)%(Filename)1.xdc $(IntDir)%(Filename)1.obj $(IntDir)%(Filename)1.xdc $(IntDir)%(Filename)1.obj $(IntDir)%(Filename)1.xdc $(IntDir)%(Filename)1.obj $(IntDir)%(Filename)1.xdc $(IntDir)%(Filename)1.obj $(IntDir)%(Filename)1.xdc $(IntDir)%(Filename)1.obj $(IntDir)%(Filename)1.xdc $(IntDir)%(Filename)1.obj $(IntDir)%(Filename)1.xdc $(IntDir)%(Filename)1.obj $(IntDir)%(Filename)1.xdc $(IntDir)%(Filename)1.obj $(IntDir)%(Filename)1.xdc $(IntDir)%(Filename)1.obj $(IntDir)%(Filename)1.xdc $(IntDir)%(Filename)1.obj $(IntDir)%(Filename)1.xdc $(IntDir)%(Filename)1.obj $(IntDir)%(Filename)1.xdc $(IntDir)%(Filename)1.obj $(IntDir)%(Filename)1.xdc openMSX-RELEASE_0_12_0/build/3rdparty/libtheora.vcxproj.filters000066400000000000000000000200231257557151200243400ustar00rootroot00000000000000 {83741e6a-3d10-476a-b1ec-e9098fabacb7} {7f957e1c-7cef-42de-858b-962247fff00b} {e0aa57f5-2050-46a2-b36c-2a9e6ac323ff} {ba0ac045-9ac6-442e-9b34-8573540c5e6e} {8c1d2b59-d884-4c59-8ae2-6f33fc26187e} {e8759675-802a-4da8-b9e9-c845f9952a12} {965f7fba-63ea-4828-8dac-2ee27d82acd8} include\theora include\theora include\theora lib lib lib\enc lib\enc lib\enc lib\enc lib\enc lib\dec lib\dec lib\dec lib\dec lib\dec lib\dec lib\dec lib\dec lib\dec lib\dec lib lib\enc lib\enc lib\enc lib\enc lib\enc lib\enc lib\enc lib\enc lib\enc lib\enc lib\enc lib\enc lib\enc lib\enc\x86_vc lib\enc\x86_vc lib\enc\x86_vc lib\dec lib\dec lib\dec lib\dec lib\dec lib\dec lib\dec lib\dec lib\dec lib\dec lib\dec lib\dec lib\dec lib\dec\x86_vc lib\dec\x86_vc lib\dec\x86_vc lib\dec\x86_vc openMSX-RELEASE_0_12_0/build/3rdparty/libvorbis.vcxproj000066400000000000000000000452001257557151200227170ustar00rootroot00000000000000 Debug Win32 Debug x64 Developer Win32 Developer x64 Release Win32 Release x64 {3A214E06-B95E-4D61-A291-1F8DF2EC10FD} libvorbis Win32Proj StaticLibrary Unicode StaticLibrary Unicode true StaticLibrary Unicode StaticLibrary Unicode StaticLibrary Unicode true StaticLibrary Unicode <_ProjectFileVersion>10.0.30319.1 $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameVorbis)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameVorbis)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameVorbis)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameVorbis)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameVorbis)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameVorbis)\ Disabled $(ThirdPartySrcDir)\$(LibNameVorbis)\include;$(ThirdPartySrcDir)\$(LibNameOgg)\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBVORBIS_EXPORTS;%(PreprocessorDefinitions) true Default MultiThreadedDebug Level4 EditAndContinue CompileAsC X64 Disabled $(ThirdPartySrcDir)\$(LibNameVorbis)\include;$(ThirdPartySrcDir)\$(LibNameOgg)\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBVORBIS_EXPORTS;%(PreprocessorDefinitions) true Default MultiThreadedDebug Level4 ProgramDatabase CompileAsC Full AnySuitable true Size true true $(ThirdPartySrcDir)\$(LibNameVorbis)\include;$(ThirdPartySrcDir)\$(LibNameOgg)\include;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBVORBIS_EXPORTS;%(PreprocessorDefinitions) true Sync MultiThreaded true true Level4 ProgramDatabase CompileAsC 4244;4100;4267;4189;4305;4127;4706;%(DisableSpecificWarnings) X64 Full AnySuitable true Size true true $(ThirdPartySrcDir)\$(LibNameVorbis)\include;$(ThirdPartySrcDir)\$(LibNameOgg)\include;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBVORBIS_EXPORTS;%(PreprocessorDefinitions) true Sync MultiThreaded true true Level4 ProgramDatabase CompileAsC 4244;4100;4267;4189;4305;4127;4706;%(DisableSpecificWarnings) Disabled $(ThirdPartySrcDir)\$(LibNameVorbis)\include;$(ThirdPartySrcDir)\$(LibNameOgg)\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBVORBIS_EXPORTS;%(PreprocessorDefinitions) true true Default MultiThreadedDebug Level4 EditAndContinue CompileAsC X64 Disabled $(ThirdPartySrcDir)\$(LibNameVorbis)\include;$(ThirdPartySrcDir)\$(LibNameOgg)\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBVORBIS_EXPORTS;%(PreprocessorDefinitions) true Default MultiThreadedDebug Level4 ProgramDatabase CompileAsC openMSX-RELEASE_0_12_0/build/3rdparty/libvorbis.vcxproj.filters000066400000000000000000000225341257557151200243730ustar00rootroot00000000000000 {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hpp;hxx;hm;inl;inc;xsd {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files openMSX-RELEASE_0_12_0/build/3rdparty/tcl.vcxproj000066400000000000000000001002771257557151200215140ustar00rootroot00000000000000 Debug Win32 Debug x64 Developer Win32 Developer x64 Release Win32 Release x64 {CCCEA506-D026-4915-BEE0-374F65D7C764} tcl Win32Proj StaticLibrary MultiByte StaticLibrary MultiByte true StaticLibrary MultiByte StaticLibrary MultiByte StaticLibrary MultiByte true StaticLibrary MultiByte <_ProjectFileVersion>10.0.30319.1 $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameTcl)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameTcl)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameTcl)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameTcl)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameTcl)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameTcl)\ Disabled $(ThirdPartySrcDir)\$(LibNameTcl)\win;$(ThirdPartySrcDir)\$(LibNameTcl)\generic;$(ThirdPartySrcDir)\$(LibNameTcl)\libtommath;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;TCL_TOMMATH;MP_PREC=4;TCL_CFGVAL_ENCODING="cp1252";STDC_HEADERS;STATIC_BUILD;TCL_CFG_DEBUG;TCL_USE_STATIC_PACKAGES=1;BUILD_tcl;inline=__inline;TCL_PIPE_DLL="tclpip85sg.dll";CFG_INSTALL_LIBDIR="C:\\Program Files\\Tcl\\lib";CFG_INSTALL_BINDIR="C:\\Program Files\\Tcl\\bin";CFG_INSTALL_SCRDIR="C:\\Program Files\\Tcl\\lib\\tcl8.5";CFG_INSTALL_INCDIR="C:\\Program Files\\Tcl\\include";CFG_INSTALL_DOCDIR="C:\\Program Files\\Tcl\\doc";CFG_RUNTIME_LIBDIR="C:\\Program Files\\Tcl\\lib";CFG_RUNTIME_BINDIR="C:\\Program Files\\Tcl\\bin";CFG_RUNTIME_SCRDIR="C:\\Program Files\\Tcl\\lib\\tcl8.5";CFG_RUNTIME_INCDIR="C:\\Program Files\\Tcl\\include";CFG_RUNTIME_DOCDIR="C:\\Program Files\\Tcl\\doc";%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebug Level3 EditAndContinue MachineX86 X64 Disabled $(ThirdPartySrcDir)\$(LibNameTcl)\win;$(ThirdPartySrcDir)\$(LibNameTcl)\generic;$(ThirdPartySrcDir)\$(LibNameTcl)\libtommath;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;TCL_TOMMATH;MP_PREC=4;TCL_CFGVAL_ENCODING="cp1252";STDC_HEADERS;STATIC_BUILD;TCL_CFG_DEBUG;TCL_USE_STATIC_PACKAGES=1;BUILD_tcl;inline=__inline;TCL_PIPE_DLL="tclpip85sg.dll";CFG_INSTALL_LIBDIR="C:\\Program Files\\Tcl\\lib";CFG_INSTALL_BINDIR="C:\\Program Files\\Tcl\\bin";CFG_INSTALL_SCRDIR="C:\\Program Files\\Tcl\\lib\\tcl8.5";CFG_INSTALL_INCDIR="C:\\Program Files\\Tcl\\include";CFG_INSTALL_DOCDIR="C:\\Program Files\\Tcl\\doc";CFG_RUNTIME_LIBDIR="C:\\Program Files\\Tcl\\lib";CFG_RUNTIME_BINDIR="C:\\Program Files\\Tcl\\bin";CFG_RUNTIME_SCRDIR="C:\\Program Files\\Tcl\\lib\\tcl8.5";CFG_RUNTIME_INCDIR="C:\\Program Files\\Tcl\\include";CFG_RUNTIME_DOCDIR="C:\\Program Files\\Tcl\\doc";_stati64=_stat64;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebug Level3 ProgramDatabase MachineX64 /MP %(AdditionalOptions) Full AnySuitable true Size true true $(ThirdPartySrcDir)\$(LibNameTcl)\win;$(ThirdPartySrcDir)\$(LibNameTcl)\generic;$(ThirdPartySrcDir)\$(LibNameTcl)\libtommath;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;TCL_TOMMATH;MP_PREC=4;TCL_CFGVAL_ENCODING="cp1252";STDC_HEADERS;STATIC_BUILD;TCL_CFG_DEBUG;TCL_USE_STATIC_PACKAGES=1;BUILD_tcl;inline=__inline;TCL_PIPE_DLL="tclpip85sg.dll";CFG_INSTALL_LIBDIR="C:\\Program Files\\Tcl\\lib";CFG_INSTALL_BINDIR="C:\\Program Files\\Tcl\\bin";CFG_INSTALL_SCRDIR="C:\\Program Files\\Tcl\\lib\\tcl8.5";CFG_INSTALL_INCDIR="C:\\Program Files\\Tcl\\include";CFG_INSTALL_DOCDIR="C:\\Program Files\\Tcl\\doc";CFG_RUNTIME_LIBDIR="C:\\Program Files\\Tcl\\lib";CFG_RUNTIME_BINDIR="C:\\Program Files\\Tcl\\bin";CFG_RUNTIME_SCRDIR="C:\\Program Files\\Tcl\\lib\\tcl8.5";CFG_RUNTIME_INCDIR="C:\\Program Files\\Tcl\\include";CFG_RUNTIME_DOCDIR="C:\\Program Files\\Tcl\\doc";%(PreprocessorDefinitions) true Sync MultiThreaded true true Level3 ProgramDatabase MachineX86 X64 /MP %(AdditionalOptions) Full AnySuitable true Size true true $(ThirdPartySrcDir)\$(LibNameTcl)\win;$(ThirdPartySrcDir)\$(LibNameTcl)\generic;$(ThirdPartySrcDir)\$(LibNameTcl)\libtommath;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_LIB;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;TCL_TOMMATH;MP_PREC=4;TCL_CFGVAL_ENCODING="cp1252";STDC_HEADERS;STATIC_BUILD;TCL_CFG_DEBUG;TCL_USE_STATIC_PACKAGES=1;BUILD_tcl;inline=__inline;TCL_PIPE_DLL="tclpip85sg.dll";CFG_INSTALL_LIBDIR="C:\\Program Files\\Tcl\\lib";CFG_INSTALL_BINDIR="C:\\Program Files\\Tcl\\bin";CFG_INSTALL_SCRDIR="C:\\Program Files\\Tcl\\lib\\tcl8.5";CFG_INSTALL_INCDIR="C:\\Program Files\\Tcl\\include";CFG_INSTALL_DOCDIR="C:\\Program Files\\Tcl\\doc";CFG_RUNTIME_LIBDIR="C:\\Program Files\\Tcl\\lib";CFG_RUNTIME_BINDIR="C:\\Program Files\\Tcl\\bin";CFG_RUNTIME_SCRDIR="C:\\Program Files\\Tcl\\lib\\tcl8.5";CFG_RUNTIME_INCDIR="C:\\Program Files\\Tcl\\include";CFG_RUNTIME_DOCDIR="C:\\Program Files\\Tcl\\doc";_stati64=_stat64;%(PreprocessorDefinitions) true Sync MultiThreaded true true Level3 ProgramDatabase MachineX64 Disabled $(ThirdPartySrcDir)\$(LibNameTcl)\win;$(ThirdPartySrcDir)\$(LibNameTcl)\generic;$(ThirdPartySrcDir)\$(LibNameTcl)\libtommath;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;TCL_TOMMATH;MP_PREC=4;TCL_CFGVAL_ENCODING="cp1252";STDC_HEADERS;STATIC_BUILD;TCL_CFG_DEBUG;TCL_USE_STATIC_PACKAGES=1;BUILD_tcl;inline=__inline;TCL_PIPE_DLL="tclpip85sg.dll";CFG_INSTALL_LIBDIR="C:\\Program Files\\Tcl\\lib";CFG_INSTALL_BINDIR="C:\\Program Files\\Tcl\\bin";CFG_INSTALL_SCRDIR="C:\\Program Files\\Tcl\\lib\\tcl8.5";CFG_INSTALL_INCDIR="C:\\Program Files\\Tcl\\include";CFG_INSTALL_DOCDIR="C:\\Program Files\\Tcl\\doc";CFG_RUNTIME_LIBDIR="C:\\Program Files\\Tcl\\lib";CFG_RUNTIME_BINDIR="C:\\Program Files\\Tcl\\bin";CFG_RUNTIME_SCRDIR="C:\\Program Files\\Tcl\\lib\\tcl8.5";CFG_RUNTIME_INCDIR="C:\\Program Files\\Tcl\\include";CFG_RUNTIME_DOCDIR="C:\\Program Files\\Tcl\\doc";%(PreprocessorDefinitions) true true Default MultiThreadedDebug Level3 ProgramDatabase MachineX86 X64 Disabled $(ThirdPartySrcDir)\$(LibNameTcl)\win;$(ThirdPartySrcDir)\$(LibNameTcl)\generic;$(ThirdPartySrcDir)\$(LibNameTcl)\libtommath;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_LIB;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;TCL_TOMMATH;MP_PREC=4;TCL_CFGVAL_ENCODING="cp1252";STDC_HEADERS;STATIC_BUILD;TCL_CFG_DEBUG;TCL_USE_STATIC_PACKAGES=1;BUILD_tcl;inline=__inline;TCL_PIPE_DLL="tclpip85sg.dll";CFG_INSTALL_LIBDIR="C:\\Program Files\\Tcl\\lib";CFG_INSTALL_BINDIR="C:\\Program Files\\Tcl\\bin";CFG_INSTALL_SCRDIR="C:\\Program Files\\Tcl\\lib\\tcl8.5";CFG_INSTALL_INCDIR="C:\\Program Files\\Tcl\\include";CFG_INSTALL_DOCDIR="C:\\Program Files\\Tcl\\doc";CFG_RUNTIME_LIBDIR="C:\\Program Files\\Tcl\\lib";CFG_RUNTIME_BINDIR="C:\\Program Files\\Tcl\\bin";CFG_RUNTIME_SCRDIR="C:\\Program Files\\Tcl\\lib\\tcl8.5";CFG_RUNTIME_INCDIR="C:\\Program Files\\Tcl\\include";CFG_RUNTIME_DOCDIR="C:\\Program Files\\Tcl\\doc";_stati64=_stat64;%(PreprocessorDefinitions) true true Default MultiThreadedDebug Level3 ProgramDatabase MachineX64 openMSX-RELEASE_0_12_0/build/3rdparty/tcl.vcxproj.filters000066400000000000000000000577321257557151200231720ustar00rootroot00000000000000 {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hpp;hxx;hm;inl;inc;xsd Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files openMSX-RELEASE_0_12_0/build/3rdparty/tcl8.5.15.diff000066400000000000000000000260421257557151200214050ustar00rootroot00000000000000diff -ru tcl8.5.15.org/generic/tcl.h tcl8.5.15/generic/tcl.h --- tcl8.5.15.org/generic/tcl.h 2013-08-30 17:58:39.000000000 +0200 +++ tcl8.5.15/generic/tcl.h 2014-05-08 17:57:29.373173568 +0200 @@ -168,7 +168,7 @@ * MSVCRT. */ -#if (defined(__WIN32__) && (defined(_MSC_VER) || (__BORLANDC__ >= 0x0550) || defined(__LCC__) || defined(__WATCOMC__) || (defined(__GNUC__) && defined(__declspec)))) +#if (defined(__WIN32__) && (defined(_MSC_VER) || (defined(__BORLANDC__) && (__BORLANDC__ >= 0x0550)) || defined(__LCC__) || defined(__WATCOMC__) || (defined(__GNUC__) && defined(__declspec)))) # define HAVE_DECLSPEC 1 # ifdef STATIC_BUILD # define DLLIMPORT diff -ru tcl8.5.15.org/unix/configure tcl8.5.15/unix/configure --- tcl8.5.15.org/unix/configure 2013-09-13 18:07:56.000000000 +0200 +++ tcl8.5.15/unix/configure 2014-05-08 17:57:29.384173253 +0200 @@ -14976,7 +14976,7 @@ else if test "$cross_compiling" = yes; then - tcl_cv_strtod_buggy=buggy + tcl_cv_strtod_buggy=ok else cat >conftest.$ac_ext <<_ACEOF /* confdefs.h. */ diff -ru tcl8.5.15.org/win/Makefile.in tcl8.5.15/win/Makefile.in --- tcl8.5.15.org/win/Makefile.in 2013-09-13 18:07:59.000000000 +0200 +++ tcl8.5.15/win/Makefile.in 2014-05-08 17:57:29.385173225 +0200 @@ -606,7 +606,7 @@ $(COPY) $(REG_LIB_FILE) $(LIB_INSTALL_DIR)/reg$(REGDOTVER); \ fi -install-libraries: libraries install-tzdata install-msgs +install-libraries: libraries @for i in $(prefix)/lib $(INCLUDE_INSTALL_DIR) \ $(SCRIPT_INSTALL_DIR); \ do \ diff -ru tcl8.5.15.org/win/tclWin32Dll.c tcl8.5.15/win/tclWin32Dll.c --- tcl8.5.15.org/win/tclWin32Dll.c 2013-08-30 17:58:40.000000000 +0200 +++ tcl8.5.15/win/tclWin32Dll.c 2014-05-08 17:39:34.159854591 +0200 @@ -46,23 +46,6 @@ static HINSTANCE hInstance; /* HINSTANCE of this DLL. */ static int platformId; /* Running under NT, or 95/98? */ -#ifdef HAVE_NO_SEH -/* - * Unlike Borland and Microsoft, we don't register exception handlers by - * pushing registration records onto the runtime stack. Instead, we register - * them by creating an EXCEPTION_REGISTRATION within the activation record. - */ - -typedef struct EXCEPTION_REGISTRATION { - struct EXCEPTION_REGISTRATION *link; - EXCEPTION_DISPOSITION (*handler)( - struct _EXCEPTION_RECORD*, void*, struct _CONTEXT*, void*); - void *ebp; - void *esp; - int status; -} EXCEPTION_REGISTRATION; -#endif - /* * VC++ 5.x has no 'cpuid' assembler instruction, so we must emulate it */ @@ -1086,7 +1069,7 @@ # else - EXCEPTION_REGISTRATION registration; + TCLEXCEPTION_REGISTRATION registration; /* * Execute the CPUID instruction with the given index, and store results @@ -1095,7 +1078,7 @@ __asm__ __volatile__( /* - * Construct an EXCEPTION_REGISTRATION to protect the CPUID + * Construct an TCLEXCEPTION_REGISTRATION to protect the CPUID * instruction (early 486's don't have CPUID) */ @@ -1109,7 +1092,7 @@ "movl %[error], 0x10(%%edx)" "\n\t" /* status */ /* - * Link the EXCEPTION_REGISTRATION on the chain + * Link the TCLEXCEPTION_REGISTRATION on the chain */ "movl %%edx, %%fs:0" "\n\t" @@ -1128,7 +1111,7 @@ "movl %%edx, 0xc(%%edi)" "\n\t" /* - * Come here on a normal exit. Recover the EXCEPTION_REGISTRATION and + * Come here on a normal exit. Recover the TCLEXCEPTION_REGISTRATION and * store a TCL_OK status. */ @@ -1138,7 +1121,7 @@ "jmp 2f" "\n" /* - * Come here on an exception. Get the EXCEPTION_REGISTRATION that we + * Come here on an exception. Get the TCLEXCEPTION_REGISTRATION that we * previously put on the chain. */ @@ -1148,7 +1131,7 @@ /* * Come here however we exited. Restore context from the - * EXCEPTION_REGISTRATION in case the stack is unbalanced. + * TCLEXCEPTION_REGISTRATION in case the stack is unbalanced. */ "2:" "\t" diff -ru tcl8.5.15.org/win/tclWinChan.c tcl8.5.15/win/tclWinChan.c --- tcl8.5.15.org/win/tclWinChan.c 2013-08-30 17:58:40.000000000 +0200 +++ tcl8.5.15/win/tclWinChan.c 2014-05-08 17:50:02.715941103 +0200 @@ -119,23 +119,6 @@ FileThreadActionProc, /* Thread action proc. */ FileTruncateProc, /* Truncate proc. */ }; - -#ifdef HAVE_NO_SEH -/* - * Unlike Borland and Microsoft, we don't register exception handlers by - * pushing registration records onto the runtime stack. Instead, we register - * them by creating an EXCEPTION_REGISTRATION within the activation record. - */ - -typedef struct EXCEPTION_REGISTRATION { - struct EXCEPTION_REGISTRATION* link; - EXCEPTION_DISPOSITION (*handler)( - struct _EXCEPTION_RECORD*, void*, struct _CONTEXT*, void*); - void* ebp; - void* esp; - int status; -} EXCEPTION_REGISTRATION; -#endif /* *---------------------------------------------------------------------- @@ -1027,7 +1010,7 @@ * TCL_WRITABLE to indicate file mode. */ { #if defined(HAVE_NO_SEH) && !defined(_WIN64) - EXCEPTION_REGISTRATION registration; + TCLEXCEPTION_REGISTRATION registration; #endif char channelName[16 + TCL_INTEGER_SPACE]; Tcl_Channel channel = NULL; @@ -1108,7 +1091,7 @@ "movl %[dupedHandle], %%ebx" "\n\t" /* - * Construct an EXCEPTION_REGISTRATION to protect the call to + * Construct an TCLEXCEPTION_REGISTRATION to protect the call to * CloseHandle. */ @@ -1122,7 +1105,7 @@ "movl $0, 0x10(%%edx)" "\n\t" /* status */ /* - * Link the EXCEPTION_REGISTRATION on the chain. + * Link the TCLEXCEPTION_REGISTRATION on the chain. */ "movl %%edx, %%fs:0" "\n\t" @@ -1135,7 +1118,7 @@ "call _CloseHandle@4" "\n\t" /* - * Come here on normal exit. Recover the EXCEPTION_REGISTRATION + * Come here on normal exit. Recover the TCLEXCEPTION_REGISTRATION * and put a TRUE status return into it. */ @@ -1145,7 +1128,7 @@ "jmp 2f" "\n" /* - * Come here on an exception. Recover the EXCEPTION_REGISTRATION + * Come here on an exception. Recover the TCLEXCEPTION_REGISTRATION */ "1:" "\t" @@ -1154,7 +1137,7 @@ /* * Come here however we exited. Restore context from the - * EXCEPTION_REGISTRATION in case the stack is unbalanced. + * TCLEXCEPTION_REGISTRATION in case the stack is unbalanced. */ "2:" "\t" diff -ru tcl8.5.15.org/win/tclWinFCmd.c tcl8.5.15/win/tclWinFCmd.c --- tcl8.5.15.org/win/tclWinFCmd.c 2013-08-30 17:58:40.000000000 +0200 +++ tcl8.5.15/win/tclWinFCmd.c 2014-05-08 17:53:40.526718744 +0200 @@ -67,25 +67,6 @@ {GetWinFileShortName, CannotSetAttribute}, {GetWinFileAttributes, SetWinFileAttributes}}; -#ifdef HAVE_NO_SEH - -/* - * Unlike Borland and Microsoft, we don't register exception handlers by - * pushing registration records onto the runtime stack. Instead, we register - * them by creating an EXCEPTION_REGISTRATION within the activation record. - */ - -typedef struct EXCEPTION_REGISTRATION { - struct EXCEPTION_REGISTRATION *link; - EXCEPTION_DISPOSITION (*handler)( - struct _EXCEPTION_RECORD *, void *, struct _CONTEXT *, void *); - void *ebp; - void *esp; - int status; -} EXCEPTION_REGISTRATION; - -#endif - /* * Prototype for the TraverseWinTree callback function. */ @@ -176,7 +157,7 @@ * (native). */ { #if defined(HAVE_NO_SEH) && !defined(_WIN64) - EXCEPTION_REGISTRATION registration; + TCLEXCEPTION_REGISTRATION registration; #endif DWORD srcAttr, dstAttr; int retval = -1; @@ -213,7 +194,7 @@ "movl %[nativeSrc], %%ecx" "\n\t" /* - * Construct an EXCEPTION_REGISTRATION to protect the call to + * Construct an TCLEXCEPTION_REGISTRATION to protect the call to * MoveFile. */ @@ -227,7 +208,7 @@ "movl $0, 0x10(%%edx)" "\n\t" /* status */ /* - * Link the EXCEPTION_REGISTRATION on the chain. + * Link the TCLEXCEPTION_REGISTRATION on the chain. */ "movl %%edx, %%fs:0" "\n\t" @@ -242,7 +223,7 @@ "call *%%eax" "\n\t" /* - * Come here on normal exit. Recover the EXCEPTION_REGISTRATION and + * Come here on normal exit. Recover the TCLEXCEPTION_REGISTRATION and * put the status return from MoveFile into it. */ @@ -251,7 +232,7 @@ "jmp 2f" "\n" /* - * Come here on an exception. Recover the EXCEPTION_REGISTRATION + * Come here on an exception. Recover the TCLEXCEPTION_REGISTRATION */ "1:" "\t" @@ -260,7 +241,7 @@ /* * Come here however we exited. Restore context from the - * EXCEPTION_REGISTRATION in case the stack is unbalanced. + * TCLEXCEPTION_REGISTRATION in case the stack is unbalanced. */ "2:" "\t" @@ -568,7 +549,7 @@ CONST TCHAR *nativeDst) /* Pathname of file to copy to (native). */ { #if defined(HAVE_NO_SEH) && !defined(_WIN64) - EXCEPTION_REGISTRATION registration; + TCLEXCEPTION_REGISTRATION registration; #endif int retval = -1; @@ -605,7 +586,7 @@ "movl %[nativeSrc], %%ecx" "\n\t" /* - * Construct an EXCEPTION_REGISTRATION to protect the call to + * Construct an TCLEXCEPTION_REGISTRATION to protect the call to * CopyFile. */ @@ -619,7 +600,7 @@ "movl $0, 0x10(%%edx)" "\n\t" /* status */ /* - * Link the EXCEPTION_REGISTRATION on the chain. + * Link the TCLEXCEPTION_REGISTRATION on the chain. */ "movl %%edx, %%fs:0" "\n\t" @@ -635,7 +616,7 @@ "call *%%eax" "\n\t" /* - * Come here on normal exit. Recover the EXCEPTION_REGISTRATION and + * Come here on normal exit. Recover the TCLEXCEPTION_REGISTRATION and * put the status return from CopyFile into it. */ @@ -644,7 +625,7 @@ "jmp 2f" "\n" /* - * Come here on an exception. Recover the EXCEPTION_REGISTRATION + * Come here on an exception. Recover the TCLEXCEPTION_REGISTRATION */ "1:" "\t" @@ -653,7 +634,7 @@ /* * Come here however we exited. Restore context from the - * EXCEPTION_REGISTRATION in case the stack is unbalanced. + * TCLEXCEPTION_REGISTRATION in case the stack is unbalanced. */ "2:" "\t" diff -ru tcl8.5.15.org/win/tclWinInt.h tcl8.5.15/win/tclWinInt.h --- tcl8.5.15.org/win/tclWinInt.h 2013-08-30 17:58:40.000000000 +0200 +++ tcl8.5.15/win/tclWinInt.h 2014-05-08 17:52:31.051704267 +0200 @@ -22,6 +22,23 @@ #define TCL_WIN_STACK_THRESHOLD 0x8000 +#ifdef HAVE_NO_SEH +/* + * Unlike Borland and Microsoft, we don't register exception handlers by + * pushing registration records onto the runtime stack. Instead, we register + * them by creating an TCLEXCEPTION_REGISTRATION within the activation record. + */ + +typedef struct TCLEXCEPTION_REGISTRATION { + struct TCLEXCEPTION_REGISTRATION *link; + EXCEPTION_DISPOSITION (*handler)( + struct _EXCEPTION_RECORD*, void*, struct _CONTEXT*, void*); + void *ebp; + void *esp; + int status; +} TCLEXCEPTION_REGISTRATION; +#endif + /* * Some versions of Borland C have a define for the OSVERSIONINFO for * Win32s and for NT, but not for Windows 95. openMSX-RELEASE_0_12_0/build/3rdparty/unzip.py000066400000000000000000000011741257557151200210300ustar00rootroot00000000000000import sys import zipfile import os import os.path def UnzipFile(file, outputdir): if not os.path.exists(outputdir): os.mkdir(outputdir, 755) zip = zipfile.ZipFile(open(file, 'rb')) for name in zip.namelist(): output = os.path.join(outputdir, name) if name.endswith('/'): if not os.path.exists(output): os.mkdir(output) else: output = open(output, 'wb') output.write(zip.read(name)) output.close() if __name__ == '__main__': if len(sys.argv) == 3: UnzipFile(sys.argv[1], sys.argv[2]) else: print >> sys.stderr, \ 'Usage: python unzip.py zipfile outputdir' sys.exit(2) openMSX-RELEASE_0_12_0/build/3rdparty/zlib-1.2.8.diff000066400000000000000000000012111257557151200215370ustar00rootroot00000000000000diff -ru zlib-1.2.8.orig/configure zlib-1.2.8/configure --- zlib-1.2.8.orig/configure 2013-03-24 06:30:09.000000000 +0100 +++ zlib-1.2.8/configure 2013-05-18 18:22:58.000000000 +0200 @@ -192,9 +192,9 @@ EXE='.exe' ;; MINGW* | mingw*) # temporary bypass - rm -f $test.[co] $test $test$shared_ext - echo "Please use win32/Makefile.gcc instead." | tee -a configure.log - leave 1 +# rm -f $test.[co] $test $test$shared_ext +# echo "Please use win32/Makefile.gcc instead." | tee -a configure.log +# leave 1 LDSHARED=${LDSHARED-"$cc -shared"} LDSHAREDLIBC="" EXE='.exe' ;; openMSX-RELEASE_0_12_0/build/3rdparty/zlib.vcxproj000066400000000000000000001062761257557151200216770ustar00rootroot00000000000000 Debug Win32 Debug x64 Developer Win32 Developer x64 LIB ASM Debug Win32 LIB ASM Debug x64 LIB ASM Release Win32 LIB ASM Release x64 Release Win32 Release x64 {05A68CD4-A282-4475-B9F7-ED3C9A0109B5} zlib StaticLibrary false StaticLibrary false StaticLibrary false StaticLibrary false StaticLibrary false StaticLibrary false StaticLibrary false StaticLibrary false StaticLibrary false StaticLibrary false <_ProjectFileVersion>10.0.30319.1 $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameZlib)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameZlib)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameZlib)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameZlib)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameZlib)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameZlib)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameZlib)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameZlib)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameZlib)\ $(ThirdPartyOutDir)\ $(ThirdPartyIntDir)\$(LibNameZlib)\ Disabled WIN32;_DEBUG;ASMV;ASMINF;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL .\Win32_LIB_ASM_Debug/zlib.pch .\Win32_LIB_ASM_Debug/ .\Win32_LIB_ASM_Debug/ .\Win32_LIB_ASM_Debug/ Level3 true EditAndContinue _DEBUG;%(PreprocessorDefinitions) 0x0409 Win32_LIB_ASM_Debug\zlibd.lib true true .\Win32_LIB_ASM_Debug/zlib.bsc Disabled WIN32;_DEBUG;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebug Level3 true EditAndContinue _DEBUG;%(PreprocessorDefinitions) 0x0409 true MachineX86 true .\Win32_LIB_Debug/zlib.bsc Full AnySuitable true Size true true true WIN32;NDEBUG;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true Sync MultiThreaded true true Level3 true NDEBUG;%(PreprocessorDefinitions) 0x0409 /LTCG %(AdditionalOptions) true MachineX86 true .\Win32_LIB_Release/zlib.bsc MaxSpeed OnlyExplicitInline WIN32;NDEBUG;ASMV;ASMINF;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true MultiThreadedDLL true .\Win32_LIB_ASM_Release/zlib.pch .\Win32_LIB_ASM_Release/ .\Win32_LIB_ASM_Release/ .\Win32_LIB_ASM_Release/ Level3 true NDEBUG;%(PreprocessorDefinitions) 0x0409 .\Win32_LIB_ASM_Release\zlib.lib true true .\Win32_LIB_ASM_Release/zlib.bsc Disabled WIN32;_DEBUG;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true true Default MultiThreadedDebug Level3 true ProgramDatabase _DEBUG;%(PreprocessorDefinitions) 0x0409 true MachineX86 true .\Win32_LIB_Debug/zlib.bsc X64 Disabled WIN32;_DEBUG;ASMV;ASMINF;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL .\Win32_LIB_ASM_Debug/zlib.pch .\Win32_LIB_ASM_Debug/ .\Win32_LIB_ASM_Debug/ .\Win32_LIB_ASM_Debug/ Level3 true ProgramDatabase _DEBUG;%(PreprocessorDefinitions) 0x0409 Win32_LIB_ASM_Debug\zlibd.lib true true .\Win32_LIB_ASM_Debug/zlib.bsc X64 Disabled WIN32;_DEBUG;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebug Level3 true ProgramDatabase _DEBUG;%(PreprocessorDefinitions) 0x0409 true MachineX64 true .\Win32_LIB_Debug/zlib.bsc X64 Full AnySuitable true Size true true true WIN32;NDEBUG;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true Sync MultiThreaded true true Level3 true NDEBUG;%(PreprocessorDefinitions) 0x0409 /LTCG %(AdditionalOptions) true MachineX64 true .\Win32_LIB_Release/zlib.bsc X64 MaxSpeed OnlyExplicitInline WIN32;NDEBUG;ASMV;ASMINF;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true MultiThreadedDLL true .\Win32_LIB_ASM_Release/zlib.pch .\Win32_LIB_ASM_Release/ .\Win32_LIB_ASM_Release/ .\Win32_LIB_ASM_Release/ Level3 true NDEBUG;%(PreprocessorDefinitions) 0x0409 .\Win32_LIB_ASM_Release\zlib.lib true true .\Win32_LIB_ASM_Release/zlib.bsc X64 Disabled WIN32;_DEBUG;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) true true Default MultiThreadedDebug Level3 true ProgramDatabase _DEBUG;%(PreprocessorDefinitions) 0x0409 true MachineX64 true .\Win32_LIB_Debug/zlib.bsc %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) %(PreprocessorDefinitions) true true true true %(PreprocessorDefinitions) \Scratch\Build\openmsx\external\zlib-1.2.3\win32;%(AdditionalIncludeDirectories) %(PreprocessorDefinitions) \Scratch\Build\openmsx\external\zlib-1.2.3\win32;%(AdditionalIncludeDirectories) %(PreprocessorDefinitions) \Scratch\Build\openmsx\external\zlib-1.2.3\win32;%(AdditionalIncludeDirectories) %(PreprocessorDefinitions) \Scratch\Build\openmsx\external\zlib-1.2.3\win32;%(AdditionalIncludeDirectories) openMSX-RELEASE_0_12_0/build/3rdparty/zlib.vcxproj.filters000066400000000000000000000110621257557151200233320ustar00rootroot00000000000000 {269dfb7b-8e1f-4ee4-9e90-6694ad6d215d} cpp;c;cxx;rc;def;r;odl;idl;hpj;bat {087dbb60-2787-47d2-9ae0-07f401f8e7a4} h;hpp;hxx;hm;inl {63bf4111-ea8c-486d-9d72-a9edb977d301} ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe {2e1192dc-b6bb-4833-96e9-7588f23bb7dc} asm;obj;c;cpp;cxx;h;hpp;hxx Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Assembler Files %28Unsupported%29 Assembler Files %28Unsupported%29 Resource Files Source Files openMSX-RELEASE_0_12_0/build/3rdparty_libraries.py000066400000000000000000000017301257557151200217150ustar00rootroot00000000000000# Prints which 3rd party libraries are desired for the given configuration. from components import requiredLibrariesFor from configurations import getConfiguration from libraries import allDependencies, librariesByName from packages import iterDownloadablePackages def main(platform, linkMode): configuration = getConfiguration(linkMode) components = configuration.iterDesiredComponents() # Compute the set of all directly and indirectly required libraries, # then filter out system libraries. thirdPartyLibs = set( makeName for makeName in allDependencies(requiredLibrariesFor(components)) if not librariesByName[makeName].isSystemLibrary(platform) ) print ' '.join(sorted(thirdPartyLibs)) if __name__ == '__main__': import sys if len(sys.argv) == 3: try: main(*sys.argv[1 : ]) except ValueError, ex: print >> sys.stderr, ex sys.exit(2) else: print >> sys.stderr, ( 'Usage: python 3rdparty_libraries.py TARGET_OS LINK_MODE' ) sys.exit(2) openMSX-RELEASE_0_12_0/build/3rdparty_packages2make.py000066400000000000000000000040101257557151200224310ustar00rootroot00000000000000from packages import getPackage, iterDownloadablePackages import sys def printPackagesMake(): patchesDir = 'build/3rdparty' sourceDir = 'derived/3rdparty/src' tarballsDir = 'derived/3rdparty/download' print 'SOURCE_DIR:=%s' % sourceDir print print '# Information about packages.' print '# Generated from the data in "build/packages.py".' print tarballs = [] for package in iterDownloadablePackages(): makeName = package.getMakeName() tarball = tarballsDir + '/' + package.getTarballName() tarballs.append(tarball) print '# %s' % package.niceName print 'PACKAGE_%s:=%s' % (makeName, package.getSourceDirName()) print 'TARBALL_%s:=%s' % (makeName, tarball) print '# Download:' print '%s:' % tarball print '\tmkdir -p %s' % tarballsDir print '\t$(PYTHON) build/download.py %s/%s %s' % ( package.downloadURL, package.getTarballName(), tarballsDir ) packageSourceDirName = package.getSourceDirName() packageSourceDir = sourceDir + '/' + packageSourceDirName patchFile = '%s/%s.diff' % (patchesDir, packageSourceDirName) print '# Verify:' verifyMarker = '%s.verified' % tarball print '%s: %s' % (verifyMarker, tarball) print '\t$(PYTHON) build/checksum.py %s %d %s' % ( tarball, package.fileLength, ' '.join('%s=%s' % item for item in package.checksums.iteritems()) ) print '\ttouch %s' % verifyMarker print '# Extract:' print '%s: %s' % (packageSourceDir, verifyMarker) print '\trm -rf %s' % packageSourceDir print '\tmkdir -p %s' % sourceDir print '\t$(PYTHON) build/extract.py %s %s %s' % ( tarball, sourceDir, packageSourceDirName ) print '\ttest ! -e %s || $(PYTHON) build/patch.py %s %s' % ( patchFile, patchFile, sourceDir ) print '\ttouch %s' % sourceDir print print '# Convenience target to download all source packages.' print '.PHONY: download' print 'download: %s' % ' '.join(tarballs) if __name__ == '__main__': if len(sys.argv) == 1: printPackagesMake() else: print >> sys.stderr, \ 'Usage: python 3rdparty_packages2make.py' sys.exit(2) openMSX-RELEASE_0_12_0/build/android/000077500000000000000000000000001257557151200171565ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/build/android/openmsx/000077500000000000000000000000001257557151200206475ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/build/android/openmsx/.gitignore000066400000000000000000000002131257557151200226330ustar00rootroot00000000000000# TODO: make sure that these do not end up in this dir... AndroidAppSettings.cfg AndroidData/ environment.props icon.png libapplication.so openMSX-RELEASE_0_12_0/build/android/openmsx/AndroidAppSettings.cfg.template.noVersion000066400000000000000000000330141257557151200306660ustar00rootroot00000000000000# The application settings for Android libSDL port # Specify application name (e.x. My Application) AppName="openMSX" # Specify reversed site name of application (e.x. com.mysite.myapp) AppFullName=org.openmsx.android.openmsx # Application version code (integer) AppVersionCode=VERSION_CODE_PLACEHOLDER # Application user-visible version name (string) AppVersionName="VERSION_NAME_PLACEHOLDER" # Specify path to download application data in zip archive in the form 'Description|URL|MirrorURL^Description2|URL2|MirrorURL2^...' # If you'll start Description with '!' symbol it will be enabled by default, other downloads should be selected by user from startup config menu # If the URL in in the form ':dir/file.dat:http://URL/' it will be downloaded as binary BLOB to the application dir and not unzipped # If the URL does not contain 'http://' it is treated as file from 'project/jni/application/src/AndroidData' dir - # these files are put inside .apk package by build system # Also please avoid 'https://' URLs, many Android devices do not have trust certificates and will fail to connect to SF.net over HTTPS AppDataDownloadUrl="Application data|appdata.zip" # Reset SDL config when updating application to the new version (y) / (n) ResetSdlConfigForThisVersion=y # Delete application data files when upgrading (specify file/dir paths separated by spaces) DeleteFilesOnUpgrade="libsdl-DownloadFinished-0.flag openmsx_system" # Here you may type readme text, which will be shown during startup. Format is: # Text in English, use \\\\\\\\n to separate lines (that's four backslashes)^de:Text in Deutsch^ru:Text in Russian^button:Button that will open some URL:http://url-to-open/ ReadmeText='^Readme text' # libSDL version to use (1.2/1.3/2.0) LibSdlVersion=1.2 # Specify screen orientation: (v)ertical/(p)ortrait or (h)orizontal/(l)andscape ScreenOrientation=h # Do not allow device to sleep when the application is in foreground, set this for video players or apps which use accelerometer InhibitSuspend=y # Video color depth - 16 BPP is the fastest and supported for all modes, 24 bpp is supported only # with SwVideoMode=y, SDL_OPENGL mode supports everything. (16)/(24)/(32) VideoDepthBpp=16 # Enable OpenGL depth buffer (needed only for 3-d applications, small speed decrease) (y) or (n) NeedDepthBuffer=n # Enable OpenGL stencil buffer (needed only for 3-d applications, small speed decrease) (y) or (n) NeedStencilBuffer=n # Try to use GLES 2.x context - will revert to GLES 1.X if unsupported by device # you need this option only if you're developing 3-d app (y) or (n) NeedGles2=n # Application uses software video buffer - you're calling SDL_SetVideoMode() without SDL_HWSURFACE and without SDL_OPENGL, # this will allow small speed optimization. Enable this even when you're using SDL_HWSURFACE. (y) or (n) SwVideoMode=y # Application video output will be resized to fit into native device screen (y)/(n) SdlVideoResize=y # Application resizing will keep 4:3 aspect ratio, with black bars at sides (y)/(n) SdlVideoResizeKeepAspect=y # Application does not call SDL_Flip() or SDL_UpdateRects() appropriately, or draws from non-main thread - # enabling the compatibility mode will force screen update every 100 milliseconds, which is laggy and inefficient (y) or (n) CompatibilityHacks=n # Application initializes SDL audio/video inside static constructors (which is bad, you won't be able to run ndk-gdb) (y)/(n) CompatibilityHacksStaticInit=n # On-screen Android soft text input emulates hardware keyboard, this will only work with Hackers Keyboard app (y)/(n) CompatibilityHacksTextInputEmulatesHwKeyboard=y # Hack for broken devices: prevent audio chopping, by sleeping a bit after pushing each audio chunk (y)/(n) CompatibilityHacksPreventAudioChopping=n # Hack for broken apps: application ignores audio buffer size returned by SDL (y)/(n) CompatibilityHacksAppIgnoresAudioBufferSize=n # Hack for VCMI: preload additional shared libraries before aplication start CompatibilityHacksAdditionalPreloadedSharedLibraries="" # Hack for Free Heroes 2, which redraws the screen inside SDL_PumpEvents(): slow and compatible SDL event queue - # do not use it with accelerometer/gyroscope, or your app may freeze at random (y)/(n) CompatibilityHacksSlowCompatibleEventQueue=n # Save and restore OpenGL state when drawing on-screen keyboard for apps that use SDL_OPENGL CompatibilityHacksTouchscreenKeyboardSaveRestoreOpenGLState= # Application uses SDL_UpdateRects() properly, and does not draw in any region outside those rects. # This improves drawing speed, but I know only one application that does that, and it's written by me (y)/(n) CompatibilityHacksProperUsageOfSDL_UpdateRects= # Application uses mouse (y) or (n), this will show mouse emulation dialog to the user AppUsesMouse=y # Application needs two-button mouse, will also enable advanced point-and-click features (y) or (n) AppNeedsTwoButtonMouse=n # Show SDL mouse cursor, for applications that do not draw cursor at all (y) or (n) ShowMouseCursor=n # Generate more touch events, by default SDL generates one event per one video frame, this is useful for drawing apps (y) or (n) GenerateSubframeTouchEvents= # Force relative (laptop) mouse movement mode, useful when both on-screen keyboard and mouse are needed (y) or (n) ForceRelativeMouseMode=n # Application needs arrow keys (y) or (n), will show on-screen dpad/joystick (y) or (n) AppNeedsArrowKeys=y # Application needs text input (y) or (n), enables button for text input on screen AppNeedsTextInput=y # Application uses joystick (y) or (n), the on-screen DPAD will be used as joystick 0 axes 0-1 AppUsesJoystick=y # Application uses second on-screen joystick, as SDL joystick 0 axes 2-3 (y)/(n) AppUsesSecondJoystick=n # Application uses accelerometer (y) or (n), the accelerometer will be used as joystick 1 axes 0-1 and 5-7 AppUsesAccelerometer=n # Application uses gyroscope (y) or (n), the gyroscope will be used as joystick 1 axes 2-4 AppUsesGyroscope=n # Application uses multitouch (y) or (n), multitouch events are passed as SDL_JOYBALLMOTION events for the joystick 0 AppUsesMultitouch=n # Application records audio (it will use any available source, such a s microphone) # API is defined in file SDL_android.h: int SDL_ANDROID_OpenAudioRecording(SDL_AudioSpec *spec); void SDL_ANDROID_CloseAudioRecording(void); # This option will add additional permission to Android manifest (y)/(n) AppRecordsAudio=n # Application needs to access SD card. If your data files are bigger than 5 Mb, enable it. (y) / (n) AccessSdCard= # Immersive mode - Android will hide on-screen Home/Back keys. Looks bad if you invoke Android keyboard. (y) / (n) ImmersiveMode=n # Application implements Android-specific routines to put to background, and will not draw anything to screen # between SDL_ACTIVEEVENT lost / gained notifications - you should check for them # rigth after SDL_Flip(), if (n) then SDL_Flip() will block till app in background (y) or (n) # This option is reported to be buggy, sometimes failing to restore video state NonBlockingSwapBuffers=y # Redefine common hardware keys to SDL keysyms # BACK hardware key is available on all devices, MENU is available on pre-ICS devices, other keys may be absent # SEARCH and CALL by default return same keycode as DPAD_CENTER - one of those keys is available on most devices # Use word NO_REMAP if you want to preserve native functionality for certain key (volume keys are 3-rd and 4-th) # Keys: TOUCHSCREEN (works only when AppUsesMouse=n), DPAD_CENTER/SEARCH, VOLUMEUP, VOLUMEDOWN, MENU, BACK, CAMERA RedefinedKeys="SPACE RETURN NO_REMAP NO_REMAP MENU WORLD_92 NO_REMAP" # Number of virtual keyboard keys (currently 6 is maximum) AppTouchscreenKeyboardKeysAmount=4 # Number of virtual keyboard keys that support autofire (currently 2 is maximum) AppTouchscreenKeyboardKeysAmountAutoFire=0 # Redefine on-screen keyboard keys to SDL keysyms - 6 keyboard keys + 4 multitouch gestures (zoom in/out and rotate left/right) RedefinedKeysScreenKb="WORLD_93 WORLD_94 WORLD_95 F10 F1 F2 F3 F4 F5 0" # Names for on-screen keyboard keys, such as Fire, Jump, Run etc, separated by spaces, they are used in SDL config menu RedefinedKeysScreenKbNames="Joystick-button-1 Joystick-button-2 Console F10 F1 F2 F3 F4 F5 0" # On-screen keys theme # 0 = Ultimate Droid by Sean Stieber (green, with gamepad joystick) # 1 = Simple Theme by Beholder (white, with gamepad joystick) # 2 = Sun by Sirea (yellow, with round joystick) # 3 = Keen by Gerstrong (multicolor, with round joystick) TouchscreenKeysTheme=2 # Redefine gamepad keys to SDL keysyms, button order is: # A B X Y L1 R1 L2 R2 LThumb RThumb RedefinedKeysGamepad="WORLD_93 WORLD_94 WORLD_95 MENU F1 F2 F3 F4 F5 F10" # How long to show startup menu button, in msec, 0 to disable startup menu StartupMenuButtonTimeout=3000 # Menu items to hide from startup menu, available menu items: # SettingsMenu.OkButton SettingsMenu.DummyMenu SettingsMenu.MainMenu SettingsMenuMisc.DownloadConfig SettingsMenuMisc.OptionalDownloadConfig SettingsMenuMisc.AudioConfig SettingsMenuMisc.VideoSettingsConfig SettingsMenuMisc.ShowReadme SettingsMenuMisc.GyroscopeCalibration SettingsMenuMisc.ResetToDefaultsConfig SettingsMenuMouse.MouseConfigMainMenu SettingsMenuMouse.DisplaySizeConfig SettingsMenuMouse.LeftClickConfig SettingsMenuMouse.RightClickConfig SettingsMenuMouse.AdditionalMouseConfig SettingsMenuMouse.JoystickMouseConfig SettingsMenuMouse.TouchPressureMeasurementTool SettingsMenuMouse.CalibrateTouchscreenMenu SettingsMenuKeyboard.KeyboardConfigMainMenu SettingsMenuKeyboard.ScreenKeyboardSizeConfig SettingsMenuKeyboard.ScreenKeyboardDrawSizeConfig SettingsMenuKeyboard.ScreenKeyboardThemeConfig SettingsMenuKeyboard.ScreenKeyboardTransparencyConfig SettingsMenuKeyboard.RemapHwKeysConfig SettingsMenuKeyboard.RemapScreenKbConfig SettingsMenuKeyboard.ScreenGesturesConfig SettingsMenuKeyboard.CustomizeScreenKbLayout HiddenMenuOptions='SettingsMenuMouse.MouseConfigMainMenu SettingsMenuMouse.LeftClickConfig SettingsMenuMouse.RightClickConfig SettingsMenuMouse.AdditionalMouseConfig SettingsMenuMouse.TouchPressureMeasurementTool SettingsMenuKeyboard.ScreenGesturesConfig SettingsMenuKeyboard.CustomizeScreenKbLayout' # Menu items to show at startup - this is Java code snippet, leave empty for default # new SettingsMenuMisc.ShowReadme(), (AppUsesMouse \&\& \! ForceRelativeMouseMode \? new SettingsMenuMouse.DisplaySizeConfig(true) : new SettingsMenu.DummyMenu()), new SettingsMenuMisc.OptionalDownloadConfig(true), new SettingsMenuMisc.GyroscopeCalibration() # Available menu items: # SettingsMenu.OkButton SettingsMenu.DummyMenu SettingsMenu.MainMenu SettingsMenuMisc.DownloadConfig SettingsMenuMisc.OptionalDownloadConfig SettingsMenuMisc.AudioConfig SettingsMenuMisc.VideoSettingsConfig SettingsMenuMisc.ShowReadme SettingsMenuMisc.GyroscopeCalibration SettingsMenuMisc.ResetToDefaultsConfig SettingsMenuMouse.MouseConfigMainMenu SettingsMenuMouse.DisplaySizeConfig SettingsMenuMouse.LeftClickConfig SettingsMenuMouse.RightClickConfig SettingsMenuMouse.AdditionalMouseConfig SettingsMenuMouse.JoystickMouseConfig SettingsMenuMouse.TouchPressureMeasurementTool SettingsMenuMouse.CalibrateTouchscreenMenu SettingsMenuKeyboard.KeyboardConfigMainMenu SettingsMenuKeyboard.ScreenKeyboardSizeConfig SettingsMenuKeyboard.ScreenKeyboardDrawSizeConfig SettingsMenuKeyboard.ScreenKeyboardThemeConfig SettingsMenuKeyboard.ScreenKeyboardTransparencyConfig SettingsMenuKeyboard.RemapHwKeysConfig SettingsMenuKeyboard.RemapScreenKbConfig SettingsMenuKeyboard.ScreenGesturesConfig SettingsMenuKeyboard.CustomizeScreenKbLayout FirstStartMenuOptions='SettingsMenu.DummyMenu SettingsMenuMisc.OptionalDownloadConfig' # Enable multi-ABI binary, with hardware FPU support - it will also work on old devices, # but .apk size is 2x bigger (y) / (n) / (x86) / (all) MultiABI=n # Minimum amount of RAM application requires, in Mb, SDL will print warning to user if it's lower AppMinimumRAM=50 # Optional shared libraries to compile - removing some of them will save space # MP3 support by libMAD is encumbered by patents and libMAD is GPL-ed # Available libraries: mad (GPL-ed!) sdl_mixer sdl_image sdl_ttf sdl_net sdl_blitpool sdl_gfx sdl_sound intl xml2 lua jpeg png ogg flac tremor vorbis freetype xerces curl theora fluidsynth lzma lzo2 mikmod openal timidity zzip bzip2 yaml-cpp python boost_date_time boost_filesystem boost_iostreams boost_program_options boost_regex boost_signals boost_system boost_thread glu avcodec avdevice avfilter avformat avresample avutil swscale swresample bzip2 CompiledLibraries="freetype sdl_ttf png ogg vorbis theora tcl8.5" # Application uses custom build script AndroidBuild.sh instead of Android.mk (y) or (n) CustomBuildScript=y # Aditional CFLAGS for application AppCflags='-frtti -fexceptions' # Additional LDFLAGS for application AppLdflags='' # If application has headers with the same name as system headers, this option tries to fix compiler flags to make it compilable AppOverlapsSystemHeaders= # Build only following subdirs (empty will build all dirs, ignored with custom script) AppSubdirsBuild='' # Exclude these files from build AppBuildExclude='' # Application command line parameters, including app name as 0-th param AppCmdline='' # Screen size is used by Google Play to prevent an app to be installed on devices with smaller screens # Minimum screen size that application supports: (s)mall / (m)edium / (l)arge MinimumScreenSize=s # Your AdMob Publisher ID, (n) if you don't want advertisements AdmobPublisherId=n # Your AdMob test device ID, to receive a test ad AdmobTestDeviceId= # Your AdMob banner size (BANNER/IAB_BANNER/IAB_LEADERBOARD/IAB_MRECT/IAB_WIDE_SKYSCRAPER/SMART_BANNER) AdmobBannerSize= openMSX-RELEASE_0_12_0/build/android/openmsx/AndroidBuild.sh000077500000000000000000000115101257557151200235440ustar00rootroot00000000000000#!/bin/bash #set -xv # TODO: find out if flavour can be passed from SDL build environment #openmsx_flavour="android-debug" openmsx_flavour="android" echo "AB:INFO Starting AndroidBuild.sh, #params: $#, params: $*" echo "AB:INFO pwd: $(pwd)" # Read environment.props (if it exists) to get following two params: # sdl_android_port_path # my_home_dir if [ ! -f environment.props ]; then echo "AB:ERROR: No file environment.props in $(pwd)" exit 1 fi . ./environment.props # Remember location of the current directory, which is the directory # with all android specific code for the app my_app_android_dir="$(pwd)" # Use latest version of the setEnvironment script; it is the one that uses GCC 4.6 set_sdl_app_environment="${sdl_android_port_path}/project/jni/application/setEnvironment.sh" # Parsing the CPU architecture information CPU_ARCH="$1" if [ "${CPU_ARCH}" = "armeabi" ]; then so_file=libapplication.so openmsx_target_cpu=arm else echo "AB:ERROR Unsupported architecture: $1" exit 1 fi #echo "AB:INFO current shell: ${SHELL}" #echo "AB:INFO BEGIN all environment params:" #set #echo "AB:INFO END all environment params:" #cd ${my_home_dir} # Unset make related environment parameters that get set by the # SDL for Android build system and that conflict with openMSX # build system unset BUILD_NUM_CPUS unset MAKEFLAGS unset MAKELEVEL unset MAKEOVERRIDES unset MFLAGS unset V cpu_count=1 if [ -f /proc/cpuinfo ]; then cpu_count=$(grep "processor[[:space:]]*:" /proc/cpuinfo | wc -l) if [ ${cpu_count} -eq 0 ]; then cpu_count=1 fi fi echo "AB:INFO Detected ${cpu_count} CPUs for parallel build" echo "AB:INFO Making this app for CPU architecture ${CPU_ARCH}" export SDL_ANDROID_PORT_PATH="${sdl_android_port_path}" export CXXFLAGS='-frtti -fexceptions -marm' export LDFLAGS='-lpng' unset BUILD_EXECUTABLE if [ $openmsx_flavour = "android" ]; then CXX_FLAGS_FILTER="sed -e 's/\\-mthumb//'" elif [ $openmsx_flavour = "android-debug" ]; then CXX_FLAGS_FILTER="sed -e 's/\\-mthumb//' -e 's/\\-DNDEBUG//g'" else echo "AB:ERROR Unknown openmsx_flavour: $openmsx_flavour" fi echo "AB:DEBUG CXX_FLAGS_FILTER: $CXX_FLAGS_FILTER" #"${set_sdl_app_environment}" /bin/bash -c "set" "${set_sdl_app_environment}" /bin/bash -c "\ echo \"AB:INFO entering openMSX home directory: ${my_home_dir}\"; \ cd ${my_home_dir};\ echo \"AB:INFO CXX: \${CXX}\";\ echo \"AB:INFO CXXFLAGS: \${CXXFLAGS}\";\ export _CC=\${CC};\ export _LD=\${LD};\ export ANDROID_LDFLAGS=\${LDFLAGS};\ export ANDROID_CXXFLAGS=\$(echo \${CXXFLAGS} | $CXX_FLAGS_FILTER);\ echo \"AB:INFO ANDROID_CXXFLAGS: \${ANDROID_CXXFLAGS}\";\ unset CXXFLAGS;\ make -k -j ${cpu_count} all\ OPENMSX_TARGET_CPU=${openmsx_target_cpu}\ OPENMSX_TARGET_OS=android\ OPENMSX_FLAVOUR=${openmsx_flavour}\ " if [ $? -ne 0 ]; then echo "AB:ERROR Make failed" exit 1 fi # Return to the directory containing this script and all data about this application # that the SDL APK build system requires cd "${my_app_android_dir}" # Copy the shared library overhere echo "AB:INFO Copying output file into android directory $(pwd)" cp "${my_home_dir}/derived/${openmsx_target_cpu}-android-${openmsx_flavour}/lib/openmsx.so" "${so_file}" if [ $? -ne 0 ]; then echo "AB:ERROR Copy failed" fi echo "AB:INFO Done with build of app" echo "AB:INFO Copying icon file" openmsx_icon_file="${my_home_dir}/share/icons/openMSX-logo-256.png" cp -p "${openmsx_icon_file}" icon.png echo "AB:INFO Validating if appdata.zip must be rebuild" if [ ! -f AndroidData/appdata.zip ]; then newfiles=1 else newfiles=$(find ${my_home_dir}/share ${my_home_dir}/Contrib/cbios ${my_app_android_dir}/AndroidBuild.sh -newer AndroidData/appdata.zip | wc -l) fi if [ ${newfiles} -gt 0 ]; then echo "AB:INFO Rebuilding appdata.zip" rm -f AndroidData/appdata.zip rm -rf AndroidData/appdata mkdir -p AndroidData/appdata/openmsx_system cd "${my_home_dir}"/share tar -c --exclude-vcs -f - . | ( cd "${my_app_android_dir}"/AndroidData/appdata/openmsx_system ; tar xf - ) cd "${my_home_dir}"/Contrib/cbios tar -c --exclude-vcs -f - . | ( cd "${my_app_android_dir}"/AndroidData/appdata/openmsx_system/machines ; tar xf - ) cd "${my_app_android_dir}"/AndroidData/appdata zip -r ../appdata.zip * > /dev/null cd .. rm -rf appdata echo "AB:INFO Done rebuilding appdata.zip" else echo "AB/INFO appdata.zip is still fine" fi MANIFEST="${sdl_android_port_path}/project/AndroidManifest.xml" # Patch manifest file to target android 2.3 and older so that the # virtual menu button will be rendered by newer Android versions sed -i "s^android:targetSdkVersion=\"[0-9]*\"^android:targetSdkVersion=\"10\"^" ${MANIFEST} # Remove network permissions from manifest. OpenMSX does not need it sed -i "s/<\/uses-permission>/<\!-- -->/" ${MANIFEST} exit 0 openMSX-RELEASE_0_12_0/build/android/openmsx/generate_AndroidAppSettings.sh000077500000000000000000000062741257557151200266330ustar00rootroot00000000000000#!/bin/bash echo "LAB:INFO Generating AndroidAppSettings.cfg from template file" # Read environment.props (if it exists) to get following two params: # sdl_android_port_path # my_home_dir if [ ! -f environment.props ]; then echo "LAB:ERROR: No file environment.props in $(pwd)" exit 1 fi . ./environment.props # Determine current revision and version name PYTHONPATH="${my_home_dir}/build" export PYTHONPATH cd "${my_home_dir}" # The (Android) version code must be an increasing number so that Android application # manager can recognize that it is a new version of the same application and take # appropriate action like retaining or migrating the user settings # The easiest way to get an increasing number for new builds in git is by # counting the number of commit messages. #VERSION_CODE=$(git log --oneline | wc -l) VERSION_CODE=$(python -c "import version; print version.getAndroidVersionCode()") # The (Android) version name can be any arbitrary string that is hopefully # meaningfull to the user. Best is to use the version package name # to be aligned with the version name used for builds for other platforms. VERSION_NAME=$(python -c "import version; print version.getVersionedPackageName()") # Return to the directory containing this script and the AndroidAppSettings files cd "${my_home_dir}/build/android/openmsx" # Determine version number of AndroidAppSettings supported # by current version of anddev Android build script CHANGE_APP_SETTINS_SCRIPT="${sdl_android_port_path}/changeAppSettings.sh" if [ ! -f "${CHANGE_APP_SETTINS_SCRIPT}" ]; then echo "LAB:ERROR: No such file ${CHANGE_APP_SETTINS_SCRIPT}." echo " Please follow instructions in compilation guide for android" echo " port to correctly set-up the android build environment." exit 1 fi CHANGE_APP_SETTINGS_VERSION=$(grep 'CHANGE_APP_SETTINGS_VERSION=[0-9][0-9]*' "${CHANGE_APP_SETTINS_SCRIPT}") CHANGE_APP_SETTINGS_VERSION=${CHANGE_APP_SETTINGS_VERSION#*=} if [ -z "${CHANGE_APP_SETTINGS_VERSION}" ]; then # Latest version of changeAppSettings.sh no longer contains an explicit version # number for the app-settings # However, it does have a mechanism to automagically upgrade to newer app-settings # with (hopefully sane) default values # As such, use a non-versioned template when the changeAppSettings.sh does not # contain an explicit settings version CHANGE_APP_SETTINGS_VERSION=noVersion fi APP_SETTINGS_CFG="AndroidAppSettings.cfg" APP_SETTINGS_TEMPLATE="${APP_SETTINGS_CFG}.template.${CHANGE_APP_SETTINGS_VERSION}" if [ ! -f ${APP_SETTINGS_TEMPLATE} ]; then echo "LAB:ERROR: No such file ${APP_SETTINGS_TEMPLATE}." echo " Please create one manually. It can be based on one" echo " of the existing app settings template file" exit 1 fi cp ${APP_SETTINGS_TEMPLATE} ${APP_SETTINGS_CFG} . ${APP_SETTINGS_CFG} if [ "$AppVersionCode" != "${VERSION_CODE}" ]; then sed -i "s/^AppVersionCode=.*$/AppVersionCode=${VERSION_CODE}/" ${APP_SETTINGS_CFG} fi if [ "$AppVersionName" != "${VERSION_NAME}" ]; then sed -i "s/^AppVersionName=.*$/AppVersionName=${VERSION_NAME}/" ${APP_SETTINGS_CFG} fi echo "LAB:INFO AndroidAppSettings.cfg generated for version ${VERSION_CODE} with version name ${VERSION_NAME}" openMSX-RELEASE_0_12_0/build/android/openmsx/launch_anddev_build.sh000077500000000000000000000010251257557151200251560ustar00rootroot00000000000000#!/bin/bash echo "LAB:INFO Starting launch_anddev_build.sh" # Read environment.props (if it exists) to get following two params: # sdl_android_port_path # my_home_dir if [ ! -f environment.props ]; then echo "LAB:ERROR: No file environment.props in $(pwd)" exit 1 fi . ./environment.props export GCCVER=4.8 export NDK_TOOLCHAIN_VERSION=${GCCVER} ./generate_AndroidAppSettings.sh if [ $? -ne 0 ]; then exit 1 fi echo "LAB:INFO launching commandergenius build file" cd "${sdl_android_port_path}" ./build.sh $* exit $? openMSX-RELEASE_0_12_0/build/android/openmsx/sign_official_build_with_xelasoft_key.sh000077500000000000000000000012711257557151200307720ustar00rootroot00000000000000#!/bin/bash echo "LAB:INFO Signing APK with xelasoft certificate, for publication in keystore" # Read environment.props (if it exists) to get following two params: # sdl_android_port_path # my_home_dir if [ ! -f environment.props ]; then echo "LAB:ERROR: No file environment.props in $(pwd)" exit 1 fi . ./environment.props export ANDROID_KEYSTORE_FILE=~awulms/Ontwikkel/android/release_certificate/xelasoft_eu.keystore export ANDROID_KEYSTORE_ALIAS=xelasoft if [ ! -f ${ANDROID_KEYSTORE_FILE} ]; then echo "LAB:ERROR Keystore not found ${ANDROID_KEYSTORE_FILE}" exit 1 fi echo "LAB:INFO launching commandergenius sign.sh script" cd "${sdl_android_port_path}" ./sign.sh exit $? openMSX-RELEASE_0_12_0/build/android/setup_anddev.sh000077500000000000000000000130311257557151200221740ustar00rootroot00000000000000#!/bin/bash function explain_usage() { echo "This script configures the \"commandergenius\" SDL Android port" echo "for the build of openMSX." echo "" echo "The script depends on the Android SDK and NDK and on the" echo "\"commandergenius\" SDL Android port." echo "" echo "First download and install the Android SDK and NDK" echo "as per the instructions on the Android website." echo "" echo "Please make sure to add the Android SDK and NDK tools locations" echo "to your PATH variable, as per the instructions on the Android" echo "website. Otherwise the setup will fail." echo "" echo "Subsequently download the \"commandergenius\" SDL Android port" echo "into your favorite location." echo "" echo "Example:" echo "> cd /opt" echo "> git clone https://github.com/pelya/commandergenius.git" echo "" echo "Once that is done, you can launch this script." echo "You must specify the path to the \"commandergenius\" SDL Android port" echo "on the command line or in environment parameter SDL_ANDROID_PORT_PATH." echo "" echo "Example 1:" echo "> export SDL_ANDROID_PORT_PATH=/opt/commandergenius" echo "> ./setup_anddev.sh" echo "" echo "Example 2:" echo "> ./setup_anddev.sh /opt/commandergenius" echo "" } if [ $# -eq 0 -a -z "$SDL_ANDROID_PORT_PATH" ]; then explain_usage exit 1 fi if [ $# -eq 1 ]; then sdl_android_port_path="$1" else sdl_android_port_path="$SDL_ANDROID_PORT_PATH" fi if [ ! -d "${sdl_android_port_path}" ]; then echo "Can not find directory ${sdl_android_port_path}" exit 1 fi sdl_port_app_dir="${sdl_android_port_path}/project/jni/application" if [ ! -d "${sdl_port_app_dir}" ]; then echo "Can not find expected sub-directory project/jni/application" echo "in specified directory ${sdl_android_port_path}" exit 1 fi this_script_dir=$(dirname $0) this_script_dir=$(cd ${this_script_dir}; pwd) my_home_dir=${this_script_dir%/*} my_home_dir=${my_home_dir%/*} my_android_dir="${my_home_dir}/build/android/openmsx" my_relative_dir="${my_android_dir##*/}" tcl_build=8.5.11 tcl_version=${tcl_build%.*} tcl_archive="${my_home_dir}/derived/3rdparty/download/tcl${tcl_build}-src.tar.gz" tcl_sdl_dir="${sdl_android_port_path}/project/jni/tcl${tcl_version}" if [ -d "${tcl_sdl_dir}" ]; then if [ ! -f "${tcl_sdl_dir}/${tcl_build}.txt" ]; then echo "ERROR: expecting TCL build ${tcl_build} in ${tcl_sdl_dir}" exit 1 fi fi export GCCVER=4.8 export NDK_TOOLCHAIN_VERSION=${GCCVER} if [ ! -d "${tcl_sdl_dir}" ]; then if [ ! -f "${tcl_archive}" ]; then cd "${my_home_dir}" echo "Downloading TCL" python build/android_download.py if [ $? -ne 0 ]; then echo "ERROR: Failed to download TCL" exit 1 fi if [ ! -f "${tcl_archive}" ]; then echo "ERROR: Failed to download TCL ${tcl_build}" exit 1 fi fi cd "${sdl_android_port_path}/project/jni" tar xzf "${tcl_archive}" tcl_relative_dir=tcl${tcl_version} mv tcl${tcl_build} ${tcl_relative_dir} touch ${tcl_relative_dir}/${tcl_build}.txt cp -p "${my_home_dir}/build/android/tcl${tcl_build}_Android.mk" ${tcl_relative_dir}/Android.mk cd ${tcl_relative_dir} mkdir -p lib/armeabi mkdir -p lib/armeabi-v7a mkdir -p include cp -p generic/*.h include cd unix if [ ! -f Makefile.in.original ]; then cp -p Makefile.in Makefile.in.original patch -i "${my_home_dir}/build/android/tcl${tcl_build}_unix_Makefile.in.patch" fi export BUILD_EXECUTABLE=yes ../../setCrossEnvironment.sh ./configure --host=arm-eabi make clean ../../setCrossEnvironment.sh make mv libtcl8.5.so ../lib/armeabi ../../setCrossEnvironment.sh ./configure --host=arm-eabi-v7a make clean ../../setCrossEnvironment.sh make mv libtcl8.5.so ../lib/armeabi-v7a fi echo "Setting-up softlink to this application in the SDL android port." if [ -h "${sdl_port_app_dir}/${my_relative_dir}" ]; then rm "${sdl_port_app_dir}/${my_relative_dir}" fi if [ -d "${sdl_port_app_dir}/${my_relative_dir}" ]; then echo "ERROR: found directory ${my_relative_dir} in ${sdl_port_app_dir}" echo "This seems to be another app with the same name." echo "Please resolve the conflict and then re-run this script." exit 1 fi ln -s "${my_android_dir}" "${sdl_port_app_dir}/${my_relative_dir}" rm -f "${sdl_port_app_dir}/src" ln -s "${my_relative_dir}" "${sdl_port_app_dir}/src" echo "Making environment.props file for the build script" cat > "${my_android_dir}/environment.props" << @EOT # Do not edit this file. It is generated by setup_anddev.sh sdl_android_port_path="${sdl_android_port_path}" my_home_dir="${my_home_dir}" @EOT cd "${my_android_dir}" ./generate_AndroidAppSettings.sh if [ $? -ne 0 ]; then exit 1 fi echo "Configuring SDL android port to build this application" cd "${sdl_android_port_path}" ./changeAppSettings.sh -a if [ $? -ne 0 ]; then echo "ERROR: an unexpected problem occurred while running changeAppSettings.sh -a" echo " in ${sdl_android_port_path}" exit 1 fi android update project -p project -t android-19 if [ $? -ne 0 ]; then echo "ERROR: an unexpected problem occurred while running \"android update ...\"" exit 1 fi echo "" echo "You can now build the application from the openMSX android build directory" echo "" echo "Example" echo "> cd ${my_android_dir}" echo "> ./launch_anddev_build.sh" echo "" echo "Note: the first time that you run the launch_anddev_build.sh script, you may get some error" echo " message. It is due to a subtle bug in the SDL android port." echo " Simply re-run the build.sh script a second time and it will work fine." openMSX-RELEASE_0_12_0/build/android/tcl8.5.11_Android.mk000066400000000000000000000012641257557151200224070ustar00rootroot00000000000000LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := tcl8.5 LOCAL_C_INCLUDES := $(LOCAL_PATH)/include #ifneq ($(NDK_R5_TOOLCHAIN),) LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/lib$(LOCAL_MODULE).so include $(PREBUILT_SHARED_LIBRARY) #else #LOCAL_SRC_FILES := dummy.c #include $(BUILD_SHARED_LIBRARY) #$(abspath $(LOCAL_PATH)/../../obj/local/armeabi/lib$(LOCAL_MODULE).so): $(LOCAL_PATH)/lib/armeabi/lib$(LOCAL_MODULE).so OVERRIDE_CUSTOM_LIB # cp -f $< $@ #$(abspath $(LOCAL_PATH)/../../obj/local/armeabi-v7a/lib$(LOCAL_MODULE).so): $(LOCAL_PATH)/lib/armeabi-v7a/lib$(LOCAL_MODULE).so OVERRIDE_CUSTOM_LIB # cp -f $< $@ #.PHONY: OVERRIDE_CUSTOM_LIB #OVERRIDE_CUSTOM_LIB: #endif openMSX-RELEASE_0_12_0/build/android/tcl8.5.11_unix_Makefile.in.patch000066400000000000000000000026331257557151200247050ustar00rootroot00000000000000*** Makefile.in 2011-11-04 13:47:57.000000000 +0100 --- Makefile.in.android 2012-11-03 15:40:36.000000000 +0100 *************** *** 211,217 **** COMPAT_OBJS = @LIBOBJS@ ! AC_FLAGS = @DEFS@ AR = @AR@ RANLIB = @RANLIB@ DTRACE = @DTRACE@ --- 211,217 ---- COMPAT_OBJS = @LIBOBJS@ ! AC_FLAGS = -DPACKAGE_NAME=\"tcl\" -DPACKAGE_TARNAME=\"tcl\" -DPACKAGE_VERSION=\"8.5\" -DPACKAGE_STRING=\"tcl\ 8.5\" -DPACKAGE_BUGREPORT=\"\" -DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 -DNO_VALUES_H=1 -DHAVE_LIMITS_H=1 -DHAVE_SYS_PARAM_H=1 -DTCL_CFGVAL_ENCODING=\"iso8859-1\" -DSTATIC_BUILD=1 -DMODULE_SCOPE=extern\ __attribute__\(\(__visibility__\(\"hidden\"\)\)\) -DTCL_SHLIB_EXT=\".so\" -DTCL_CFG_OPTIMIZED=1 -DTCL_CFG_DEBUG=1 -DTCL_TOMMATH=1 -DMP_PREC=4 -DTCL_WIDE_INT_TYPE=long\ long -DHAVE_OPENDIR=1 -DHAVE_STRTOL=1 -DHAVE_WAITPID=1 -DNO_GETWD=1 -DHAVE_GETADDRINFO=1 -DNO_FD_SET=1 -DHAVE_SYS_TIME_H=1 -DTIME_WITH_SYS_TIME=1 -DHAVE_GMTIME_R=1 -DHAVE_LOCALTIME_R=1 -DHAVE_MKTIME=1 -DHAVE_TM_GMTOFF=1 -DHAVE_STRUCT_STAT_ST_BLOCKS=1 -DHAVE_STRUCT_STAT_ST_BLKSIZE=1 -DHAVE_BLKCNT_T=1 -DHAVE_INTPTR_T=1 -DHAVE_UINTPTR_T=1 -DNO_UNION_WAIT=1 -DHAVE_SIGNED_CHAR=1 -DHAVE_SYS_IOCTL_H=1 -DTCL_UNLOAD_DLLS=1 -DTCL_CROSS_COMPILE=1 AR = @AR@ RANLIB = @RANLIB@ DTRACE = @DTRACE@ openMSX-RELEASE_0_12_0/build/android_download.py000066400000000000000000000006401257557151200214170ustar00rootroot00000000000000from thirdparty_download import fetchPackageSource import sys def main(tarballsDir, sourcesDir, patchesDir): fetchPackageSource('TCL_ANDROID', tarballsDir, sourcesDir, patchesDir) if __name__ == '__main__': if len(sys.argv) == 1: main( 'derived/3rdparty/download', 'derived/3rdparty/src', 'build/3rdparty' ) else: print >> sys.stderr, ( 'Usage: python android_download.py' ) sys.exit(2) openMSX-RELEASE_0_12_0/build/buildinfo2code.py000066400000000000000000000073561257557151200210130ustar00rootroot00000000000000from cpu import getCPU, X86, X86_64 from makeutils import extractMakeVariables, parseBool from outpututils import rewriteIfChanged import sys def iterBuildInfoHeader(targetPlatform, cpuName, flavour, installShareDir): platformVars = extractMakeVariables( 'build/platform-%s.mk' % targetPlatform, dict.fromkeys( ('COMPILE_FLAGS', 'LINK_FLAGS', 'TARGET_FLAGS', 'COMPILE_ENV', 'LINK_ENV', 'ANDROID_LDFLAGS', 'ANDROID_CXXFLAGS', 'OPENMSX_TARGET_CPU'), '' ) ) setWindowIcon = parseBool(platformVars.get('SET_WINDOW_ICON', 'true')) targetCPU = getCPU(cpuName) # TODO: Add support for device-specific configuration. platformDingux = targetPlatform == 'dingux' platformMaemo5 = targetPlatform == 'maemo5' platformPandora = targetPlatform == 'pandora' platformAndroid = targetPlatform == 'android' # Defaults. have16BPP = True have32BPP = True minScaleFactor = 1 maxScaleFactor = 4 # Platform overrides. if platformDingux: have32BPP = False maxScaleFactor = 1 elif platformAndroid: # At the moment, libsdl android crashes when trying to dynamically change the scale factor # TODO: debug why it crashes and then change the maxScaleFactor parameter here # so that people with a powerfull enough android device can use a higher scale factor have32BPP = False maxScaleFactor = 1 elif platformMaemo5: # TODO: These are in fact N900 specific settings, but we have no # support yet for device specific configuration and the N900 # is the most popular Maemo device currently. have32BPP = False maxScaleFactor = 2 elif platformPandora: have32BPP = False maxScaleFactor = 3 yield '// Automatically generated by build process.' yield '' yield '#ifndef BUILD_INFO_HH' yield '#define BUILD_INFO_HH' yield '' # Use a macro i.s.o. a boolean to prevent compilation errors on inline asm. # Assembly doesn't appear to work with MINGW64... TODO: find out why yield '#ifdef __MINGW64__' yield '#define ASM_X86 0' yield '#define ASM_X86 0' yield '#define ASM_X86_32 0' yield '#define ASM_X86_64 0' yield '#else' # A compiler will typically only understand the instruction set that it # generates code for. yield '#define ASM_X86 %d' % (targetCPU is X86 or targetCPU is X86_64) yield '#define ASM_X86_32 %d' % (targetCPU is X86) yield '#define ASM_X86_64 %d' % (targetCPU is X86_64) yield '#endif' # Use a macro iso integer because we really need to exclude code sections # based on this. yield '#define PLATFORM_DINGUX %d' % platformDingux yield '#define PLATFORM_MAEMO5 %d' % platformMaemo5 yield '#define PLATFORM_ANDROID %d' % platformAndroid yield '#define HAVE_16BPP %d' % have16BPP yield '#define HAVE_32BPP %d' % have32BPP yield '#define MIN_SCALE_FACTOR %d' % minScaleFactor yield '#define MAX_SCALE_FACTOR %d' % maxScaleFactor yield '' yield 'namespace openmsx {' yield '' # Note: Don't call it "BIG_ENDIAN", because some system header may #define # that. yield 'static const bool OPENMSX_BIGENDIAN = %s;' \ % str(targetCPU.bigEndian).lower() yield 'static const bool OPENMSX_UNALIGNED_MEMORY_ACCESS = %s;' \ % str(targetCPU.unalignedMemoryAccess).lower() yield 'static const bool OPENMSX_SET_WINDOW_ICON = %s;' \ % str(setWindowIcon).lower() yield 'static const char* const DATADIR = "%s";' % installShareDir yield 'static const char* const BUILD_FLAVOUR = "%s";' % flavour yield 'static const char* const TARGET_PLATFORM = "%s";' % targetPlatform yield '' yield '} // namespace openmsx' yield '' yield '#endif // BUILD_INFO_HH' if __name__ == '__main__': if len(sys.argv) == 6: rewriteIfChanged(sys.argv[1], iterBuildInfoHeader(*sys.argv[2 : ])) else: print >> sys.stderr, \ 'Usage: python buildinfo2code.py CONFIG_HEADER ' \ 'platform cpu flavour share-install-dir' sys.exit(2) openMSX-RELEASE_0_12_0/build/checksum.py000066400000000000000000000033731257557151200177200ustar00rootroot00000000000000from hashlib import new as newhash from os import stat from os.path import isfile import sys def verifyFile(filePath, fileLength, checksums): actualLength = stat(filePath).st_size if actualLength != fileLength: raise IOError( 'Expected length %d, actual length %d' % (fileLength, actualLength) ) hashers = {} for algo in checksums.iterkeys(): try: hashers[algo] = newhash(algo) except ValueError, ex: raise IOError('Failed to create "%s" hasher: %s' % (algo, ex)) inp = open(filePath, 'rb') bufSize = 16384 try: while True: buf = inp.read(bufSize) if not buf: break for hasher in hashers.itervalues(): hasher.update(buf) finally: inp.close() for algo, hasher in sorted(hashers.iteritems()): if checksums[algo] != hasher.hexdigest(): raise IOError('%s checksum mismatch' % algo) def main(filePath, fileLengthStr, checksumStrs): if not isfile(filePath): print >> sys.stderr, 'No such file: %s' % filePath sys.exit(2) try: fileLength = int(fileLengthStr) except ValueError: print >> sys.stderr, 'Length should be an integer' sys.exit(2) checksums = {} for checksumStr in checksumStrs: try: algo, hashval = checksumStr.split('=') except ValueError: print >> sys.stderr, 'Invalid checksum format: %s' % checksumStr sys.exit(2) else: checksums[algo] = hashval print 'Validating: %s' % filePath try: verifyFile(filePath, fileLength, checksums) except IOError, ex: print >> sys.stderr, 'Validation FAILED: %s' % ex sys.exit(1) else: print 'Validation passed' sys.exit(0) if __name__ == '__main__': if len(sys.argv) >= 3: main( sys.argv[1], sys.argv[2], sys.argv[3 : ] ) else: print >> sys.stderr, ( 'Usage: python checksum.py FILE LENGTH (ALGO=HASH)*' ) sys.exit(2) openMSX-RELEASE_0_12_0/build/compilers.py000066400000000000000000000105131257557151200201050ustar00rootroot00000000000000from msysutils import msysActive, msysPathToNative from os import environ from shlex import split as shsplit from subprocess import PIPE, STDOUT, Popen if msysActive(): def fixArgs(args): for arg in args: if arg.startswith('-I') or arg.startswith('-L'): yield arg[ : 2] + msysPathToNative(arg[2 : ]) elif arg.startswith('/'): yield msysPathToNative(arg) else: yield arg else: def fixArgs(args): return iter(args) class _Command(object): @classmethod def fromLine(cls, commandStr, flagsStr): commandParts = shsplit(commandStr) flags = shsplit(flagsStr) env = {} while commandParts: if '=' in commandParts[0]: name, value = commandParts[0].split('=', 1) del commandParts[0] env[name] = value else: return cls( env, commandParts[0], list(fixArgs(commandParts[1 : ] + flags)) ) else: raise ValueError('No command specified in "%s"' % commandStr) def __init__(self, env, executable, flags): self.__env = env self.__executable = executable self.__flags = flags mergedEnv = dict(environ) mergedEnv.update(env) self.__mergedEnv = mergedEnv def __str__(self): return ' '.join( [ self.__executable ] + self.__flags + ( [ '(%s)' % ' '.join( '%s=%s' % item for item in sorted(self.__env.iteritems()) ) ] if self.__env else [] ) ) def _run(self, log, name, args, inputSeq, captureOutput): commandLine = [ self.__executable ] + args + self.__flags try: proc = Popen( commandLine, bufsize = -1, env = self.__mergedEnv, stdin = None if inputSeq is None else PIPE, stdout = PIPE, stderr = PIPE if captureOutput else STDOUT, ) except OSError, ex: print >> log, 'failed to execute %s: %s' % (name, ex) return None if captureOutput else False inputText = None if inputSeq is None else '\n'.join(inputSeq) + '\n' stdoutdata, stderrdata = proc.communicate(inputText) if captureOutput: assert stderrdata is not None messages = stderrdata else: assert stderrdata is None messages = stdoutdata if messages: log.write('%s command: %s\n' % (name, ' '.join(commandLine))) if inputText is not None: log.write('input:\n') log.write(inputText) if not inputText.endswith('\n'): log.write('\n') log.write('end input.\n') # pylint 0.18.0 somehow thinks 'messages' is a list, not a string. # pylint: disable-msg=E1103 messages = messages.replace('\r', '') log.write(messages) if not messages.endswith('\n'): log.write('\n') if proc.returncode == 0: return stdoutdata if captureOutput else True else: print >> log, 'return code from %s: %d' % (name, proc.returncode) return None if captureOutput else False class CompileCommand(_Command): __expandSignature = 'EXPAND_MACRO_' def compile(self, log, sourcePath, objectPath): return self._run( log, 'compiler', [ '-c', sourcePath, '-o', objectPath ], None, False ) def expand(self, log, headers, *keys): signature = self.__expandSignature def iterLines(): for header in headers: yield '#include %s' % header for key in keys: yield '%s%s %s' % (signature, key, key) output = self._run( log, 'preprocessor', [ '-E', '-' ], iterLines(), True ) if output is None: if len(keys) == 1: return None else: return (None, ) * len(keys) else: expanded = {} prevKey = None for line in output.split('\n'): line = line.strip() if not line or line.startswith('#'): continue if line.startswith(signature): prevKey = None keyValueStr = line[len(signature) : ] try: key, value = keyValueStr.split(None, 1) except ValueError: key, value = keyValueStr, '' if key not in keys: log.write( 'Ignoring macro expand signature match on ' 'non-requested macro "%s"\n' % key ) continue elif value == '': # GCC5 puts value on separate line. prevKey = key continue elif prevKey is not None: key = prevKey value = line prevKey = None else: continue if value != key: expanded[key] = value if len(keys) == 1: return expanded.get(keys[0]) else: return tuple(expanded.get(key) for key in keys) class LinkCommand(_Command): def link(self, log, objectPaths, binaryPath): return self._run( log, 'linker', objectPaths + [ '-o', binaryPath ], None, False ) openMSX-RELEASE_0_12_0/build/components.py000066400000000000000000000021011257557151200202670ustar00rootroot00000000000000# Defines the building blocks of openMSX and their dependencies. class Component(object): niceName = None makeName = None dependsOn = None @classmethod def canBuild(cls, probeVars): return all( probeVars.get('HAVE_%s_H' % makeName) and probeVars.get('HAVE_%s_LIB' % makeName) for makeName in cls.dependsOn ) class EmulationCore(Component): niceName = 'Emulation core' makeName = 'CORE' dependsOn = ('SDL', 'SDL_TTF', 'PNG', 'TCL', 'ZLIB') class GLRenderer(Component): niceName = 'GL renderer' makeName = 'GL' dependsOn = ('GL', 'GLEW') class Laserdisc(Component): niceName = 'Laserdisc' makeName = 'LASERDISC' dependsOn = ('OGG', 'VORBIS', 'THEORA') def iterComponents(): yield EmulationCore yield GLRenderer yield Laserdisc def requiredLibrariesFor(components): '''Compute the library packages required to build the given components. Only the direct dependencies from openMSX are included, not dependencies between libraries. Returns a set of Make names. ''' return set( makeName for comp in components for makeName in comp.dependsOn ) openMSX-RELEASE_0_12_0/build/components2code.py000066400000000000000000000021541257557151200212140ustar00rootroot00000000000000# Creates the components header file. from components import iterComponents from makeutils import extractMakeVariables from outpututils import rewriteIfChanged import sys def iterComponentsHeader(probeMakePath): probeVars = extractMakeVariables(probeMakePath) buildComponents = set( component.makeName for component in iterComponents() if component.canBuild(probeVars) ) yield '// Automatically generated by build process.' yield '' yield '#ifndef COMPONENTS_HH' yield '#define COMPONENTS_HH' yield '' for component in iterComponents(): varName = component.makeName yield '#define COMPONENT_%s %d' % (varName, varName in buildComponents) yield '' yield 'namespace openmsx {' yield '' yield 'static const char* const BUILD_COMPONENTS = "%s";' \ % ' '.join(sorted(buildComponents)) yield '' yield '} // namespace openmsx' yield '' yield '#endif // COMPONENTS_HH' if __name__ == '__main__': if len(sys.argv) == 3: rewriteIfChanged(sys.argv[1], iterComponentsHeader(sys.argv[2])) else: print >> sys.stderr, ( 'Usage: python components2code.py COMPONENTS_HEADER PROBE_MAKE' ) sys.exit(2) openMSX-RELEASE_0_12_0/build/components2defs.py000066400000000000000000000013441257557151200212230ustar00rootroot00000000000000# Generates the contents of "components_defs.mk". from components import EmulationCore, iterComponents from makeutils import extractMakeVariables from outpututils import rewriteIfChanged import sys def iterComponentDefs(probeMakePath): probeVars = extractMakeVariables(probeMakePath) yield '# Automatically generated by build process.' yield 'CORE_LIBS:=%s' % ' '.join(EmulationCore.dependsOn) for component in iterComponents(): yield 'COMPONENT_%s:=%s' % ( component.makeName, str(component.canBuild(probeVars)).lower() ) if len(sys.argv) == 3: rewriteIfChanged(sys.argv[1], iterComponentDefs(sys.argv[2])) else: print >> sys.stderr, ( 'Usage: python components2defs.py COMPONENTS_DEFS PROBE_MAKE' ) sys.exit(2) openMSX-RELEASE_0_12_0/build/configurations.py000066400000000000000000000030301257557151200211360ustar00rootroot00000000000000from components import ( EmulationCore, GLRenderer, Laserdisc, iterComponents ) class Configuration(object): def __init__(self, requiredComponents, optionalComponents, linkStatic): self.__requiredComponents = requiredComponents self.__optionalComponents = optionalComponents self.__linkStatic = linkStatic def iterRequiredComponents(self): return iter(self.__requiredComponents) def iterOptionalComponents(self): return iter(self.__optionalComponents) def iterDesiredComponents(self): return iter(self.__requiredComponents | self.__optionalComponents) def linkStatic(self): '''Returns True iff static linking should be used for non-system libs. ''' return self.__linkStatic def getConfiguration(name): if name == 'SYS_DYN': requiredComponents = set((EmulationCore, )) optionalComponents = set(iterComponents()) - requiredComponents linkStatic = False elif name == '3RD_STA': requiredComponents = set((EmulationCore, GLRenderer)) optionalComponents = set(iterComponents()) - requiredComponents linkStatic = True elif name == '3RD_STA_GLES': # TODO: We don't have an OpenGL ES component yet. requiredComponents = set((EmulationCore, )) optionalComponents = \ set(iterComponents()) - requiredComponents - set((GLRenderer, )) linkStatic = True elif name == '3RD_STA_MIN': requiredComponents = set((EmulationCore, )) optionalComponents = set() linkStatic = True else: raise ValueError('No configuration named "%s"' % name) return Configuration(requiredComponents, optionalComponents, linkStatic) openMSX-RELEASE_0_12_0/build/cpu.py000066400000000000000000000044761257557151200167120ustar00rootroot00000000000000class CPU(object): '''Abstract base class for CPU families. ''' # String that we use to identify this CPU type. name = None # Big (True) or little (False) endian? # Note that some CPUs can do both; if both settings are used in practice # we treat them like different CPUs (see MIPS/MIPSel). bigEndian = None # Allow unaligned memory accesses? unalignedMemoryAccess = False # GCC flags to pass to the compile and link commands. gccFlags = () class Alpha(CPU): '''DEC Alpha. ''' name = 'alpha' bigEndian = False class ARM(CPU): '''ARM. ''' name = 'arm' bigEndian = False class ARM64(CPU): '''ARM 64-bit, little endian mode. ''' name = 'aarch64' bigEndian = False class ARM64BE(CPU): '''ARM 64-bit, big endian mode. ''' name = 'aarch64_be' bigEndian = True class AVR32(CPU): '''Atmel AVR32, an embedded RISC CPU. ''' name = 'avr32' bigEndian = True class HPPA(CPU): '''HP PA-RISC. ''' name = 'hppa' bigEndian = True class IA64(CPU): '''Intel Itanium. ''' name = 'ia64' bigEndian = False class M68k(CPU): '''Motorola 680x0. ''' name = 'm68k' bigEndian = True class MIPS(CPU): '''Big endian MIPS. ''' name = 'mips' bigEndian = True class MIPSel(MIPS): '''Little endian MIPS. ''' name = 'mipsel' bigEndian = False class PPC(CPU): '''32-bit Power PC. ''' name = 'ppc' bigEndian = True class PPC64(CPU): '''64-bit Power PC. ''' name = 'ppc64' bigEndian = True class S390(CPU): '''IBM S/390. ''' name = 's390' bigEndian = True class SH(CPU): '''Little endian Renesas SuperH. ''' name = 'sh' bigEndian = False class SHeb(CPU): '''Big endian Renesas SuperH. ''' name = 'sheb' bigEndian = True class Sparc(CPU): '''Sun Sparc. ''' name = 'sparc' bigEndian = True class X86(CPU): '''32-bit x86: Intel Pentium, AMD Athlon etc. ''' name = 'x86' bigEndian = False unalignedMemoryAccess = True gccFlags = '-m32', class X86_64(CPU): '''64-bit x86. Also known as AMD64 or x64. ''' name = 'x86_64' bigEndian = False unalignedMemoryAccess = True gccFlags = '-m64', # Build a dictionary of CPUs using introspection. def _discoverCPUs(localObjects): for obj in localObjects: if isinstance(obj, type) and issubclass(obj, CPU): if not (obj is CPU): yield obj.name, obj _cpusByName = dict(_discoverCPUs(locals().itervalues())) def getCPU(name): return _cpusByName[name] openMSX-RELEASE_0_12_0/build/cpu2flags.py000066400000000000000000000006111257557151200177740ustar00rootroot00000000000000from cpu import getCPU import sys def getCPUFlags(cpuName): cpu = getCPU(cpuName) return ' '.join(cpu.gccFlags) if __name__ == '__main__': if len(sys.argv) == 2: cpuName = sys.argv[1] try: print getCPUFlags(cpuName) except KeyError: print >> sys.stderr, 'Unknown CPU "%s"' % cpuName else: print >> sys.stderr, 'Usage: python cpu2flags.py OPENMSX_TARGET_CPU' sys.exit(2) openMSX-RELEASE_0_12_0/build/custom.mk000066400000000000000000000015111257557151200173770ustar00rootroot00000000000000# Customize openMSX to your system/preferences. # Directory to install to. # openMSX is always installed into a single self-contained directory. # But you can change that directory to for example /usr/local/openMSX # or /usr/games/openMSX if you like. INSTALL_BASE:=/opt/openMSX # Add revision number to executable file name? This applies only to # development versions, not to release versions (see version.py). VERSION_EXEC:=false # Create a symbolic link to the installed binary? # This link is placed in a location that is typically in a user's path: # /usr/local/bin for system-wide installs and ~/bin for personal installs. # This setting is only relevant on systems that support symbolic links. SYMLINK_FOR_BINARY:=true # Install content of Contrib/ directory? # Currently this contains a version of C-BIOS. INSTALL_CONTRIB:=true openMSX-RELEASE_0_12_0/build/detectsys.py000066400000000000000000000103041257557151200201150ustar00rootroot00000000000000# Detect the native CPU and OS. # Actually we rely on the Python "platform" module and map its output to names # that the openMSX build understands. from executils import captureStdout from platform import architecture, machine, system from subprocess import PIPE, STDOUT, Popen import sys def detectCPU(): '''Detects the CPU family (not the CPU model) of the machine were are running on. Raises ValueError if no known CPU is detected. ''' cpu = machine().lower() dashIndex = cpu.find('-') if dashIndex != -1: # Hurd returns "cputype-cpusubtype" instead of just "cputype". cpu = cpu[ : dashIndex] if cpu in ('x86_64', 'amd64'): return 'x86_64' elif cpu in ('x86', 'i386', 'i486', 'i586', 'i686'): return 'x86' elif cpu.startswith('ppc') or cpu.endswith('ppc') or cpu.startswith('power'): return 'ppc64' if cpu.endswith('64') else 'ppc' elif cpu.startswith('arm'): return 'arm' elif cpu == 'aarch64': return 'aarch64' elif cpu == 'aarch64_be': return 'aarch64_be' elif cpu.startswith('mips') or cpu == 'sgi': return 'mipsel' if cpu.endswith('el') else 'mips' elif cpu == 'm68k': return 'm68k' elif cpu == 'ia64': return 'ia64' elif cpu.startswith('alpha'): return 'alpha' elif cpu.startswith('hppa') or cpu.startswith('parisc'): return 'hppa' elif cpu.startswith('s390'): return 's390' elif cpu.startswith('sparc') or cpu.startswith('sun4u'): return 'sparc' elif cpu.startswith('sh'): return 'sheb' if cpu.endswith('eb') else 'sh' elif cpu == 'avr32': return 'avr32' elif cpu == '': # Python couldn't figure it out. os = system().lower() if os == 'windows': # Relatively safe bet. return 'x86' raise ValueError('Unable to detect CPU') else: raise ValueError('Unsupported or unrecognised CPU "%s"' % cpu) def detectOS(): '''Detects the operating system of the machine were are running on. Raises ValueError if no known OS is detected. ''' os = system().lower() if os in ( 'linux', 'darwin', 'freebsd', 'netbsd', 'openbsd', 'dragonfly', 'gnu' ): return os elif os.startswith('gnu/'): # GNU userland on non-Hurd kernel, for example Debian GNU/kFreeBSD. # For openMSX the kernel is not really relevant, so treat it like # a generic GNU system. return 'gnu' elif os.startswith('mingw') or os == 'windows': return 'mingw-w64' elif os == 'sunos': return 'solaris' elif os == '': # Python couldn't figure it out. raise ValueError('Unable to detect OS') else: raise ValueError('Unsupported or unrecognised OS "%s"' % os) def getCompilerMachine(): # Note: Recent GCC and Clang versions support this option. machine = captureStdout(sys.stderr, 'cc -dumpmachine') if machine is not None: machineParts = machine.split('-') if len(machineParts) >= 3: return machineParts[0], machineParts[2] return None, None def detectMaemo5(): try: proc = Popen( ["pkg-config", "--silence-errors", "--modversion", "maemo-version"], stdin = None, stdout = PIPE, stderr = None); except OSError: return False stdoutdata, stderrdata = proc.communicate() return proc.returncode == 0 and stdoutdata.startswith("5.0") if __name__ == '__main__': try: hostCPU = detectCPU() if hostCPU == 'mips': # Little endian MIPS is reported as just "mips" by Linux Python. compilerCPU, compilerOS = getCompilerMachine() if compilerCPU == 'mips': pass elif compilerCPU == 'mipsel': hostCPU = compilerCPU else: print >>sys.stderr, ( 'Warning: Unabling to determine endianess; ' 'compiling for big endian' ) hostOS = detectOS() if hostOS == 'mingw32' and hostCPU == 'x86_64': # It is possible to run MinGW on 64-bit Windows, but producing # 64-bit code is not supported yet. hostCPU = 'x86' elif hostOS == 'darwin' and hostCPU == 'x86': # If Python is 64-bit, both the CPU and OS support it, so we can # compile openMSX for x86-64. Compiling in 32-bit mode might seem # safer, but will fail if using MacPorts on a 64-bit capable system. if architecture()[0] == '64bit': hostCPU = 'x86_64' elif hostOS == 'linux' and hostCPU == 'arm': # Detect maemo5 environment, e.g. Nokia N900 if detectMaemo5(): hostOS = 'maemo5' print hostCPU, hostOS except ValueError, ex: print >> sys.stderr, ex sys.exit(1) openMSX-RELEASE_0_12_0/build/download.py000066400000000000000000000045141257557151200177230ustar00rootroot00000000000000from os import remove, stat from os.path import basename, isdir, isfile, join as joinpath from urllib import FancyURLopener from urlparse import urlparse import sys # FancyURLOpener, which is also used in urlretrieve(), does not raise # an exception on status codes like 404 and 500. However, for downloading it is # critical to write either what we requested or nothing at all. class DownloadURLOpener(FancyURLopener): def http_error_default(self, url, fp, errcode, errmsg, headers): raise IOError('%s: http:%s' % (errmsg, url)) _urlOpener = DownloadURLOpener() class StatusLine(object): def __init__(self, out): self._out = out def __call__(self, message, progress = False): raise NotImplementedError class InteractiveStatusLine(StatusLine): __length = 0 def __call__(self, message, progress = False): self._out.write(('\r%-' + str(self.__length) + 's') % message) self.__length = max(self.__length, len(message)) class NoninteractiveStatusLine(StatusLine): def __call__(self, message, progress = False): if not progress: self._out.write(message + '\n') def createStatusLine(out): if out.isatty(): return InteractiveStatusLine(out) else: return NoninteractiveStatusLine(out) def downloadURL(url, localDir): if not isdir(localDir): raise IOError('Local directory "%s" does not exist' % localDir) fileName = basename(urlparse(url).path) localPath = joinpath(localDir, fileName) prefix = 'Downloading %s: ' % fileName statusLine = createStatusLine(sys.stdout) statusLine(prefix + 'contacting server...') def reportProgress(blocksDone, blockSize, totalSize): doneSize = blocksDone * blockSize statusLine(prefix + ( '%d/%d bytes (%1.1f%%)...' % ( doneSize, totalSize, (100.0 * doneSize) / totalSize ) if totalSize > 0 else '%d bytes...' % doneSize ), True) try: try: _urlOpener.retrieve(url, localPath, reportProgress) except IOError: statusLine(prefix + 'FAILED.') raise else: statusLine(prefix + 'done.') finally: print except: if isfile(localPath): statusLine(prefix + 'removing partial download.') remove(localPath) raise if __name__ == '__main__': if len(sys.argv) == 3: try: downloadURL(*sys.argv[1 : ]) except IOError, ex: print >> sys.stderr, ex sys.exit(1) else: print >> sys.stderr, \ 'Usage: python download.py url localdir' sys.exit(2) openMSX-RELEASE_0_12_0/build/entry-seq.mk000066400000000000000000000011461257557151200200200ustar00rootroot00000000000000# Declares a dependency chain of sequence- on sequence, sequence- # on sequence etc, where N is the number of words in ACTION_COUNTER. .PHONY: sequence-$(words $(ACTION_COUNTER)) ifeq ($(words $(ACTION_COUNTER)),0) sequence-0: else NEXT_ACTION_COUNTER:=$(wordlist 2,999999,$(ACTION_COUNTER)) sequence-$(words $(ACTION_COUNTER)): sequence-$(words $(NEXT_ACTION_COUNTER)) @echo Action: $(word $(@:sequence-%=%),$(MAKECMDGOALS)) @$(MAKE) --no-print-directory -f build/main.mk \ $(word $(@:sequence-%=%),$(MAKECMDGOALS)) ACTION_COUNTER:=$(NEXT_ACTION_COUNTER) include build/entry-seq.mk endif openMSX-RELEASE_0_12_0/build/entry.mk000066400000000000000000000024371257557151200172360ustar00rootroot00000000000000# Entry point of build system. # # Do a sanity check on the given action(s) and processes them sequentially. # Sequential processing is needed because for example "clean" and "all" cannot # run in parallel. Some actions might be able to run in parallel, but that is # an optimization we can do later, if it is really worth it. # All actions we want to expose to the user. USER_ACTIONS:=\ 3rdparty all app bindist clean createsubs dist install probe run \ staticbindist # Mark all actions as logical targets. .PHONY: $(USER_ACTIONS) # Reject unknown actions. UNKNOWN_ACTIONS:=$(filter-out $(USER_ACTIONS),$(MAKECMDGOALS)) ifneq ($(UNKNOWN_ACTIONS),) ifeq ($(words $(UNKNOWN_ACTIONS)),1) $(error Unknown action: $(UNKNOWN_ACTIONS)) else $(error Unknown actions: $(UNKNOWN_ACTIONS)) endif endif ifeq ($(MAKECMDGOALS),) # Make default action explicit. MAKECMDGOALS:=all .PHONY: default default: all endif ifeq ($(words $(MAKECMDGOALS)),1) # Single action, run it in this Make process. include build/main.mk else # Multiple actions are given, process them sequentially. # If the same action is given more than once, some warnings will be displayed, # but they will all be executed. ACTION_COUNTER:=$(MAKECMDGOALS:%=x) include build/entry-seq.mk $(MAKECMDGOALS): sequence-$(words $(MAKECMDGOALS)) @true endif openMSX-RELEASE_0_12_0/build/executils.py000066400000000000000000000036721257557151200201250ustar00rootroot00000000000000from msysutils import msysActive, msysShell from os import environ from shlex import split as shsplit from subprocess import PIPE, Popen def captureStdout(log, commandLine): '''Run a command and capture what it writes to stdout. If the command fails or writes something to stderr, that is logged. Returns the captured string, or None if the command failed. ''' # TODO: This is a modified copy-paste from compilers._Command. commandParts = shsplit(commandLine) env = dict(environ) while commandParts: if '=' in commandParts[0]: name, value = commandParts[0].split('=', 1) del commandParts[0] env[name] = value else: break else: raise ValueError( 'No command specified in "%s"' % commandLine ) if msysActive() and commandParts[0] != 'sh': commandParts = [ msysShell(), '-c', shjoin(commandParts) ] try: proc = Popen( commandParts, bufsize = -1, env = env, stdin = None, stdout = PIPE, stderr = PIPE, ) except OSError, ex: print >> log, 'Failed to execute "%s": %s' % (commandLine, ex) return None stdoutdata, stderrdata = proc.communicate() if stderrdata: severity = 'warning' if proc.returncode == 0 else 'error' log.write('%s executing "%s"\n' % (severity.capitalize(), commandLine)) # pylint 0.18.0 somehow thinks stderrdata is a list, not a string. # pylint: disable-msg=E1103 stderrdata = stderrdata.replace('\r', '') log.write(stderrdata) if not stderrdata.endswith('\n'): log.write('\n') if proc.returncode == 0: return stdoutdata else: print >> log, 'Execution failed with exit code %d' % proc.returncode return None def shjoin(parts): '''Joins the given sequence into a single string with space as a separator. Characters that have a special meaning for the shell are escaped. This is the counterpart of shlex.split(). ''' def escape(part): return ''.join( '\\' + ch if ch in '\\ \'"$()[]' else ch for ch in part ) return ' '.join(escape(part) for part in parts) openMSX-RELEASE_0_12_0/build/extract.py000066400000000000000000000071471257557151200175730ustar00rootroot00000000000000# Extract files from archives. from os import O_CREAT, O_WRONLY, fdopen, mkdir, open as osopen, utime try: from os import O_BINARY except ImportError: # Platforms that do not define O_BINARY do not need it either. O_BINARY = 0 from os.path import abspath, isdir, join as joinpath, sep, split as splitpath from stat import S_IRWXU, S_IRWXG, S_IRWXO, S_IXUSR, S_IXGRP, S_IXOTH import sys import tarfile from detectsys import detectOS hostOS = detectOS() # Note: Larger buffers might make extraction slower. bufSize = 16384 def extract(archivePath, destDir, rename = None): '''Extract the given archive to the given directory. If a rename function is given, it is called with the output path relative to the destination directory; the value returned by the rename function is used as the actual relative destination file path. This function sets file ownership and permissions like is done in newly created files and ignores the ownership and permissions from the archive, since we are not restoring a backup. ''' absDestDir = abspath(destDir) + sep if not isdir(absDestDir): raise ValueError( 'Destination directory "%s" does not exist' % absDestDir ) tar = tarfile.open(archivePath) # Note: According to the Python 2.6 docs, errorlevel can be passed as a # keyword argument to the open() call, but on Python 2.5 this does # not work. tar.errorlevel = 2 try: for member in tar.getmembers(): absMemberPath = abspath(joinpath(absDestDir, member.name)) if member.isdir(): absMemberPath += sep if not absMemberPath.startswith(absDestDir): raise ValueError( 'Refusing to extract tar entry "%s" ' 'outside destination directory' % member.name ) if rename: absMemberPath = absDestDir + rename( absMemberPath[len(absDestDir) : ] ) if member.isfile(): mode = S_IRWXU | S_IRWXG | S_IRWXO if not (member.mode & S_IXUSR): mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH) out = fdopen( osopen(absMemberPath, O_CREAT | O_WRONLY | O_BINARY, mode), 'wb' ) try: inp = tar.extractfile(member) bytesLeft = member.size while bytesLeft > 0: buf = inp.read(bufSize) out.write(buf) bytesLeft -= len(buf) buf = None finally: out.close() elif member.isdir(): if not isdir(absMemberPath): mkdir(absMemberPath) else: raise ValueError( 'Cannot extract tar entry "%s": ' 'not a regular file or a directory' % member.name ) # Set file/directory modification time to match the archive. # For example autotools track dependencies between archived files # and will attempt to regenerate them if the time stamps indicate # one is older than the other. # Note: Apparently Python 2.5's utime() cannot set timestamps on # directories in Windows. if member.isfile() or not hostOS.startswith('mingw'): utime(absMemberPath, (member.mtime, member.mtime)) finally: tar.close() class TopLevelDirRenamer(object): def __init__(self, newName): self.newName = newName def __call__(self, oldPath): head, tail = splitpath(oldPath) headParts = head.split(sep) if not headParts: raise ValueError( 'Directory part is empty for entry "%s"' % oldPath ) headParts[0] = self.newName return sep.join(headParts + [ tail ]) if __name__ == '__main__': if 3 <= len(sys.argv) <= 4: if len(sys.argv) == 4: renameTopLevelDir = TopLevelDirRenamer(sys.argv[3]) else: renameTopLevelDir = None extract(sys.argv[1], sys.argv[2], renameTopLevelDir) else: print >> sys.stderr, \ 'Usage: python extract.py archive destination [new-top-level-dir]' sys.exit(2) openMSX-RELEASE_0_12_0/build/fileutils.py000066400000000000000000000120441257557151200201110ustar00rootroot00000000000000from os import altsep, chmod, mkdir, remove, sep, stat, walk from os.path import dirname, exists, isdir, isfile, islink, join as joinpath from shutil import copyfile try: from os import symlink except ImportError: def symlink(src, dst): # pylint: disable-msg=W0613 raise OSError('This platform does not support symbolic links') def scanTree(baseDir): '''Scans files and directories from the given base directory and iterates through the paths of the files and directories it finds; these paths are relative to the base directory. Directories will always be returned before any of the files they contain. The base directory itself is not returned. Hidden files and directories are not returned. All paths returned use the OS native separator character (os.sep), regardless of which separator characters were used in the arguments. Raises IOError if there is an I/O error scanning the base directory. ''' if altsep is not None: # Make sure all paths use the OS native separator, so we can safely # compare paths and have consistent error messages. baseDir = baseDir.replace(altsep, sep) baseDir = baseDir.rstrip(sep) if not isdir(baseDir): raise IOError('Directory "%s" does not exist' % baseDir) def escalate(ex): raise IOError( 'Error scanning directory entry "%s": %s' % (ex.filename, ex) ) for dirPath, dirNames, fileNames in walk(baseDir, onerror = escalate): # Skip hidden directories. # We are deleting items from the list we are iterating over; that # requires some extra care. index = 0 while index < len(dirNames): if dirNames[index].startswith('.'): del dirNames[index] else: index += 1 # Skip hidden files. fileNames = [ name for name in fileNames if not name.startswith('.') ] if dirPath == baseDir: relDir = '' else: assert dirPath.startswith(baseDir + sep), dirPath relDir = dirPath[len(baseDir) + 1 : ] yield relDir for fileName in fileNames: yield joinpath(relDir, fileName) def installDir(path): '''Creates the given path, excluding parent directories. The directory is created with permissions such that all users can read what is installed and only the owner can modify what is installed. If the given path already exists, nothing happens. ''' if not isdir(path): # We have to do chmod() separately because the "mode" argument of # mkdir() is modified by umask. mkdir(path) chmod(path, 0755) def _installDirsRec(path): '''Like installDirs(), except that "altsep" is not supported as directory separator in "path". ''' path = path.rstrip(sep) if path and not isdir(path): index = path.rfind(sep) if index != -1: _installDirsRec(path[ : index]) mkdir(path) chmod(path, 0755) def installDirs(path): '''Creates the given path, including any parent directories if necessary. Any newly created directories are created with permissions such that all users can read what is installed and only the owner can modify what is installed. If the given path already exists, nothing happens. ''' if altsep is not None: path = path.replace(altsep, sep) _installDirsRec(path) def installFile(srcPath, destPath): '''Copies a file from the given source path to the given destination path. The destination file is created with permissions such that all users can read (and execute, if appropriate) what is installed and only the owner can modify what is installed. Raises IOError if there is a problem reading or writing files. ''' copyfile(srcPath, destPath) chmod(destPath, 0755 if (stat(srcPath).st_mode & 0100) else 0644) def installSymlink(target, link): '''Creates a symbolic link with the given name to the given target. If a symbolic link with the given name already exists, it is replaced. If a different file type with the given name already exists, OSError is raised. Also raises OSError if this platform lacks os.symlink(). ''' if islink(link): remove(link) symlink(target, link) def installTree(srcDir, destDir, paths): '''Copies files and directories from the given source directory to the given destination directory. The given paths argument is a sequence of paths relative to the source directory; only those files and directories are copied. The scanTree() function is suitable to provide paths. Files and directories are created with permissions such that all users can read (and execute, if appropriate) what is installed and only the owner can modify what is installed. Raises IOError if there is a problem reading or writing files or directories. ''' if not isdir(destDir): raise IOError('Destination directory "%s" does not exist' % destDir) for relPath in paths: if altsep is not None: relPath = relPath.replace(altsep, sep) srcPath = joinpath(srcDir, relPath) destPath = joinpath(destDir, relPath) if islink(srcPath): print 'Skipping symbolic link:', srcPath elif isdir(srcPath): _installDirsRec(destPath) elif isfile(srcPath): _installDirsRec(dirname(destPath)) installFile(srcPath, destPath) elif exists(srcPath): print 'Skipping unknown kind of file system entry:', srcPath else: print 'Skipping non-existing path:', srcPath openMSX-RELEASE_0_12_0/build/flavour-android-debug.mk000066400000000000000000000005561257557151200222550ustar00rootroot00000000000000# Android flavour. Copy all ANDROID_CXXFLAGS into TARGET_FLAGS # Otherwise, probe fails as it won't be able to find the include files include build/flavour-android.mk TARGET_FLAGS+=-fPIE #CXXFLAGS+=-O0 -g -DDEBUG -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC \ # -D_GLIBCPP_CONCEPT_CHECKS CXXFLAGS+=-DDEBUG -O0 -fPIE # Strip executable? OPENMSX_STRIP:=false openMSX-RELEASE_0_12_0/build/flavour-android.mk000066400000000000000000000003071257557151200211630ustar00rootroot00000000000000# Android flavour. Copy all ANDROID_CXXFLAGS into TARGET_FLAGS # Otherwise, probe fails as it won't be able to find the include files TARGET_FLAGS+=$(ANDROID_CXXFLAGS) CXXFLAGS:=$(ANDROID_CXXFLAGS) openMSX-RELEASE_0_12_0/build/flavour-bindist.mk000066400000000000000000000003041257557151200211740ustar00rootroot00000000000000# Generic flavour for binary distributions. # It disables debugging and aims for a small binary. # Optimisation flags. CXXFLAGS+=-Os -DNDEBUG -ffast-math # Strip executable? OPENMSX_STRIP:=true openMSX-RELEASE_0_12_0/build/flavour-debug.mk000066400000000000000000000003741257557151200206350ustar00rootroot00000000000000# Configuration for "debug" flavour: # Build with all debugging info, no optimisations. # Debug flags. CXXFLAGS+=-O0 -g -DDEBUG -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC \ -D_GLIBCPP_CONCEPT_CHECKS # Strip executable? OPENMSX_STRIP:=false openMSX-RELEASE_0_12_0/build/flavour-devel.mk000066400000000000000000000004711257557151200206440ustar00rootroot00000000000000# Configuration for "devel" flavour: # Build with debug symbols, no debug prints, some optimisations. # Debug flags. CXXFLAGS+=-O2 -g # Extra warnings, only works with recent gcc versions #CXXFLAGS+=-ansi -pedantic -Wno-long-long -Wextra -Wno-missing-field-initializers # Strip executable? OPENMSX_STRIP:=false openMSX-RELEASE_0_12_0/build/flavour-i686.mk000066400000000000000000000003611257557151200202370ustar00rootroot00000000000000# Configuration for "i686" flavour: # Optimised for Pentium 3, but compatible with Pentium 2 and higher. # Start with generic optimisation flags. include build/flavour-opt.mk # Add x86 specific flags. CXXFLAGS+=-march=i686 -mtune=pentium3 openMSX-RELEASE_0_12_0/build/flavour-lto.mk000066400000000000000000000014671257557151200203510ustar00rootroot00000000000000# This flavour enables link-time optimization (LTO). # This requires GCC 4.5 or higher; it's known to work on GCC 4.6.3. include build/flavour-opt.mk # Enable LTO for compiling and linking. # Don't bother with native code output in the compile phase, since new code # will be generated in the link phase. # Ideally LTO would be enabled for the 3rdparty libs as well, but I haven't # been able to make that work. COMPILE_FLAGS+=-flto LINK_FLAGS+=-flto=jobserver # Enable this line to speed up compilation. This is supported from gcc-4.7 # onward. Quote from the gcc manual: # -fno-fat-lto-objects improves compilation time over plain LTO, but # requires the complete toolchain to be aware of LTO. It requires a # linker with linker plugin support for basic functionality. #COMPILE_FLAGS+=-fno-fat-lto-objects openMSX-RELEASE_0_12_0/build/flavour-m68k.mk000066400000000000000000000003111257557151200203230ustar00rootroot00000000000000# ARM specific optimization flags # Optimisation flags: # overrule optimization level for m68k # TODO: is this still strictly needed? CXXFLAGS+=-O1 -DNDEBUG # Strip executable? OPENMSX_STRIP:=true openMSX-RELEASE_0_12_0/build/flavour-opt-debug.mk000066400000000000000000000002531257557151200214310ustar00rootroot00000000000000# Generic optimisation flavour: # does not target any specific CPU. # Optimisation flags. CXXFLAGS+=-O3 -DNDEBUG -ffast-math -g # Strip executable? OPENMSX_STRIP:=false openMSX-RELEASE_0_12_0/build/flavour-opt.mk000066400000000000000000000005711257557151200203500ustar00rootroot00000000000000# Generic optimisation flavour: # does not target any specific CPU. # Optimisation flags. CXXFLAGS+=-O3 -DNDEBUG -ffast-math # openMSX crashes when the win32 version is built with -fomit-frame-pointer # TODO: investigate and recheck when new compiler is used. ifneq ($(OPENMSX_TARGET_OS),mingw32) CXXFLAGS+=-fomit-frame-pointer endif # Strip executable? OPENMSX_STRIP:=true openMSX-RELEASE_0_12_0/build/flavour-ppc.mk000066400000000000000000000002721257557151200203260ustar00rootroot00000000000000# Configuration for "ppc" flavour: # Optimised for G3 PPC. # Start with generic optimisation flags. include build/flavour-opt.mk # Add PPC specific flags. CXXFLAGS+=-mcpu=G3 -mtune=G4 openMSX-RELEASE_0_12_0/build/flavour-ppcg4.mk000066400000000000000000000003561257557151200205640ustar00rootroot00000000000000# Configuration for "ppc" flavour: # Optimised for PPC-G4 and higher. # Start with generic optimisation flags. include build/flavour-opt.mk # Add PPC specific flags. CXXFLAGS+=-mcpu=G4 -mtune=G4 -mpowerpc-gfxopt -maltivec -mabi=altivec openMSX-RELEASE_0_12_0/build/flavour-super-opt.mk000066400000000000000000000021671257557151200215070ustar00rootroot00000000000000# This flavour enables some extra optimizations, though these options may # not work everywhere or may not be benefical in all cases. # Start with generic optimisation flags. include build/flavour-opt.mk # Add CPU specific optimization flags: # march=native is only supported starting from gcc-4.2.x # comment out this line if you're compiling on an older gcc version CXXFLAGS+=-march=native -mtune=native # Use computed goto's to speedup Z80 emulation: # - Computed goto's are a gcc extension, it's not part of the official c++ # standard. So this will only work if you use gcc as your compiler (it # won't work with visual c++ for example) # - This is only beneficial on CPUs with branch prediction for indirect jumps # and a reasonable amout of cache. For example it is very benefical for a # intel core2 cpu (10% faster), but not for a ARM920 (a few percent slower) # - Compiling src/cpu/CPUCore.cc with computed goto's enabled is very demanding # on the compiler. On older gcc versions it requires upto 1.5GB of memory. # But even on more recent gcc versions it still requires around 700MB. CXXFLAGS+=-DUSE_COMPUTED_GOTO openMSX-RELEASE_0_12_0/build/flavour-win32.mk000066400000000000000000000005671257557151200205150ustar00rootroot00000000000000# Configuration for Win32 flavour: # Optimised for Pentium 3 but runnable at MMX-Pentium or higher. # Optimisation flags. CXXFLAGS+= \ -Os -mpreferred-stack-boundary=4 \ -mtune=pentium3 -march=pentium-mmx -mmmx \ -fstrength-reduce -fexpensive-optimizations -fschedule-insns2 \ -fomit-frame-pointer -fno-default-inline # -DNDEBUG # Strip executable? OPENMSX_STRIP:=true openMSX-RELEASE_0_12_0/build/gitdist.py000077500000000000000000000066651257557151200175770ustar00rootroot00000000000000#!/usr/bin/env python2 # Creates a source distribution package. from os import makedirs, remove from os.path import isdir from subprocess import CalledProcessError, PIPE, Popen, check_output from tarfile import TarError, TarFile import stat, sys verbose = False def archiveFromGit(versionedPackageName, committish): prefix = '%s/' % versionedPackageName distBase = 'derived/dist/' umask = ( stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH ) def exclude(info): '''Returns True iff the given tar entry should be excluded. ''' return any(( info.name.endswith('/.gitignore'), info.name.startswith(prefix + 'doc/internal'), )) proc = Popen( ('git', 'archive', '--prefix=' + prefix, '--format=tar', committish), bufsize = -1, stdin = None, stdout = PIPE, stderr = sys.stderr, ) try: outTarPath = distBase + versionedPackageName + '.tar.gz' print 'archive:', outTarPath if not isdir(distBase): makedirs(distBase) outTar = TarFile.open(outTarPath, 'w:gz') try: # Copy entries from "git archive" into output tarball, except for # excluded entries. numIncluded = numExcluded = 0 inTar = TarFile.open(mode = 'r|', fileobj = proc.stdout) try: for info in inTar: if exclude(info): if verbose: print 'EX', info.name numExcluded += 1 else: if verbose: print 'IN', info.name numIncluded += 1 info.uid = info.gid = 1000 info.uname = info.gname = 'openmsx' info.mode = info.mode & umask outTar.addfile(info, inTar.extractfile(info)) finally: inTar.close() print 'entries: %d included, %d excluded' % ( numIncluded, numExcluded) except: # Clean up partial output file. outTar.close() remove(outTarPath) raise else: outTar.close() except: proc.terminate() raise else: data, _ = proc.communicate() if len(data) != 0: print >> sys.stderr, ( 'WARNING: %d more bytes of data from "git archive" after ' 'tar stream ended' % len(data)) def niceVersionFromGitDescription(description): '''Our release tag names are still based on naming limitations from CVS; convert them to something more pleasing to the eyes. ''' parts = description.split('-') tag = parts[0] if tag.startswith('RELEASE_'): tagParts = tag.split('_')[1 : ] i = 0 while i < len(tagParts) and tagParts[i].isdigit(): i += 1 version = '-'.join( ['.'.join(tagParts[ : i])] + [s.lower() for s in tagParts[i : ]] ) else: version = tag if len(parts) >= 2: parts[1] = 'dev' + parts[1] return '-'.join([version] + parts[1 : ]) def getDescription(committish): args = ['git', 'describe', '--abbrev=9'] if committish is not None: args.append(committish) try: return check_output(args).rstrip('\n') except CalledProcessError as ex: print >> sys.stderr, '"%s" returned %d' % ( ' '.join(args), ex.returncode ) raise def main(committish = None): try: description = getDescription(committish) except CalledProcessError: sys.exit(1) version = niceVersionFromGitDescription(description) try: archiveFromGit('openmsx-%s' % version, description) except (OSError, TarError) as ex: print >> sys.stderr, 'ERROR: %s' % ex sys.exit(1) if __name__ == '__main__': if len(sys.argv) == 1: main() elif len(sys.argv) == 2: main(sys.argv[1]) else: print >> sys.stderr, 'Usage: gitdist.py [branch | tag]' sys.exit(2) openMSX-RELEASE_0_12_0/build/install.py000066400000000000000000000101761257557151200175630ustar00rootroot00000000000000from fileutils import ( installDirs, installFile, installSymlink, installTree, scanTree ) from makeutils import extractMakeVariables, parseBool from os import listdir from os.path import basename, expanduser, isdir, splitext import os import sys def installAll( installPrefix, binaryDestDir, shareDestDir, docDestDir, binaryBuildPath, targetPlatform, cbios, symlinkForBinary ): platformVars = extractMakeVariables( 'build/platform-%s.mk' % targetPlatform, dict.fromkeys( ('COMPILE_FLAGS', 'LINK_FLAGS', 'TARGET_FLAGS', 'COMPILE_ENV', 'LINK_ENV', 'OPENMSX_TARGET_CPU'), '' ) ) binaryFileName = 'openmsx' + platformVars.get('EXEEXT', '') docNodeVars = extractMakeVariables('doc/node.mk') docsToInstall = [ 'doc/' + fileName for fileName in docNodeVars['INSTALL_DOCS'].split() ] print ' Executable...' installDirs(installPrefix + binaryDestDir) installFile( binaryBuildPath, installPrefix + binaryDestDir + '/' + binaryFileName ) print ' Data files...' installDirs(installPrefix + shareDestDir) installTree('share', installPrefix + shareDestDir, scanTree('share')) print ' Documentation...' installDirs(installPrefix + docDestDir) for path in docsToInstall: installFile(path, installPrefix + docDestDir + '/' + basename(path)) installDirs(installPrefix + docDestDir + '/manual') for fileName in listdir('doc/manual'): if splitext(fileName)[1] in ('.html', '.css', '.png'): installFile( 'doc/manual/' + fileName, installPrefix + docDestDir + '/manual/' + fileName ) if cbios: print ' C-BIOS...' installFile( 'Contrib/README.cbios', installPrefix + docDestDir + '/cbios.txt' ) installTree( 'Contrib/cbios', installPrefix + shareDestDir + '/machines', scanTree('Contrib/cbios') ) if hasattr(os, 'symlink'): print ' Creating symlinks...' for machine, alias in ( ('Toshiba_HX-10', 'msx1'), ('Philips_NMS_8250', 'msx2'), ('Panasonic_FS-A1WSX', 'msx2plus'), ('Panasonic_FS-A1GT', 'turbor'), ): installSymlink( machine + ".xml", installPrefix + shareDestDir + '/machines/' + alias + ".xml" ) if symlinkForBinary and installPrefix == '': def createSymlinkToBinary(linkDir): if linkDir != binaryDestDir and isdir(linkDir): try: installSymlink( binaryDestDir + '/' + binaryFileName, linkDir + '/' + binaryFileName ) except OSError: return False else: return True else: return False success = createSymlinkToBinary('/usr/local/bin') if not success: createSymlinkToBinary(expanduser('~/bin')) def main( installPrefix, binaryDestDir, shareDestDir, docDestDir, binaryBuildPath, targetPlatform, verboseStr, cbiosStr ): customVars = extractMakeVariables('build/custom.mk') try: verbose = parseBool(verboseStr) cbios = parseBool(cbiosStr) symlinkForBinary = parseBool(customVars['SYMLINK_FOR_BINARY']) except ValueError, ex: print >> sys.stderr, 'Invalid argument:', ex sys.exit(2) if installPrefix and not installPrefix.endswith('/'): # Just in case the destination directories are not absolute. installPrefix += '/' if verbose: print 'Installing openMSX:' try: installAll( installPrefix, binaryDestDir, shareDestDir, docDestDir, binaryBuildPath, targetPlatform, cbios, symlinkForBinary ) except IOError, ex: print >> sys.stderr, 'Installation failed:', ex sys.exit(1) if verbose: print 'Installation complete... have fun!' print ( 'Notice: if you want to emulate real MSX systems and not only the ' 'free C-BIOS machines, put the system ROMs in one of the following ' 'directories: %s/systemroms or ' '~/.openMSX/share/systemroms\n' 'If you want openMSX to find MSX software referred to from replays ' 'or savestates you get from your friends, copy that MSX software to ' '%s/software or ' '~/.openMSX/share/software' ) % (shareDestDir, shareDestDir) if __name__ == '__main__': if len(sys.argv) == 9: main(*sys.argv[1 : ]) else: print >> sys.stderr, \ 'Usage: python install.py ' \ 'DESTDIR INSTALL_BINARY_DIR INSTALL_SHARE_DIR INSTALL_DOC_DIR ' \ 'BINARY_FULL OPENMSX_TARGET_OS INSTALL_VERBOSE INSTALL_CONTRIB' sys.exit(2) openMSX-RELEASE_0_12_0/build/libraries.py000066400000000000000000000422371257557151200200740ustar00rootroot00000000000000# Some notes about static linking: # There are two ways of linking to static library: using the -l command line # option or specifying the full path to the library file as one of the inputs. # When using the -l option, the library search paths will be searched for a # dynamic version of the library, if that is not found, the search paths will # be searched for a static version of the library. This means we cannot force # static linking of a library this way. It is possible to force static linking # of all libraries, but we want to control it per library. # Conclusion: We have to specify the full path to each library that should be # linked statically. from executils import captureStdout, shjoin from msysutils import msysActive, msysPathToNative from os import listdir from os.path import isdir, isfile from os import environ class Library(object): libName = None makeName = None header = None configScriptName = None dynamicLibsOption = '--libs' staticLibsOption = None function = None # TODO: A library can give an application compile time and run time # dependencies on other libraries. For example SDL_ttf depends on # FreeType only at run time, but depends on SDL both compile time # and run time, since SDL is part of its interface and FreeType is # only used by the implementation. As a result, it is possible to # compile against SDL_ttf without having the FreeType headers # installed. But our getCompileFlags() does not support this. # In pkg-config these are called private dependencies. dependsOn = () @classmethod def isSystemLibrary(cls, platform): # pylint: disable-msg=W0613 '''Returns True iff this library is a system library on the given platform. A system library is a library that is available systemwide in the minimal installation of the OS. The default implementation returns False. ''' return False @classmethod def getConfigScript( # pylint: disable-msg=W0613 cls, platform, linkStatic, distroRoot ): scriptName = cls.configScriptName if scriptName is None: return None elif platform == 'dingux' and cls.isSystemLibrary(platform): # TODO: A generic mechanism for locating config scripts in SDKs. # Note that distroRoot is for non-system libs only. # Trying a path relative to the compiler location would # probably work well. return '/opt/a320-toolchain/usr/mipsel-a320-linux-uclibc/sysroot/usr/bin/%s' % scriptName elif distroRoot is None: return scriptName else: return '%s/bin/%s' % (distroRoot, scriptName) @classmethod def getHeaders(cls, platform): # pylint: disable-msg=W0613 header = cls.header return header if hasattr(header, '__iter__') else (header, ) @classmethod def getLibName(cls, platform): # pylint: disable-msg=W0613 return cls.libName @classmethod def getCompileFlags(cls, platform, linkStatic, distroRoot): if platform == 'android': return environ['ANDROID_CXXFLAGS'] configScript = cls.getConfigScript(platform, linkStatic, distroRoot) if configScript is not None: flags = [ '`%s --cflags`' % configScript ] elif distroRoot is None or cls.isSystemLibrary(platform): flags = [] else: flags = [ '-I%s/include' % distroRoot ] dependentFlags = [ librariesByName[name].getCompileFlags( platform, linkStatic, distroRoot ) for name in cls.dependsOn ] return ' '.join(flags + dependentFlags) @classmethod def getLinkFlags(cls, platform, linkStatic, distroRoot): if platform == 'android': return environ['ANDROID_LDFLAGS'] configScript = cls.getConfigScript(platform, linkStatic, distroRoot) if configScript is not None: libsOption = ( cls.dynamicLibsOption if not linkStatic or cls.isSystemLibrary(platform) else cls.staticLibsOption ) if libsOption is not None: return '`%s %s`' % (configScript, libsOption) if distroRoot is None or cls.isSystemLibrary(platform): return '-l%s' % cls.getLibName(platform) else: flags = [ '%s/lib/lib%s.a' % (distroRoot, cls.getLibName(platform)) ] if linkStatic else [ '-L%s/lib -l%s' % (distroRoot, cls.getLibName(platform)) ] dependentFlags = [ librariesByName[name].getLinkFlags( platform, linkStatic, distroRoot ) for name in cls.dependsOn ] return ' '.join(flags + dependentFlags) @classmethod def getVersion(cls, platform, linkStatic, distroRoot): '''Returns the version of this library, "unknown" if there is no mechanism to retrieve the version, None if there is a mechanism to retrieve the version but it failed, or a callable that takes a CompileCommand and a log stream as its arguments and returns the version or None if retrieval failed. ''' configScript = cls.getConfigScript(platform, linkStatic, distroRoot) if configScript is None: return 'unknown' else: return '`%s --version`' % configScript class FreeType(Library): libName = 'freetype' makeName = 'FREETYPE' header = ('', 'FT_FREETYPE_H') configScriptName = 'freetype-config' function = 'FT_Open_Face' @classmethod def isSystemLibrary(cls, platform): return platform in ('android', 'dingux') @classmethod def getConfigScript(cls, platform, linkStatic, distroRoot): if platform in ('netbsd', 'openbsd'): if distroRoot == '/usr/local': # FreeType is located in the X11 tree, not the ports tree. distroRoot = '/usr/X11R6' return super(FreeType, cls).getConfigScript( platform, linkStatic, distroRoot ) @classmethod def getVersion(cls, platform, linkStatic, distroRoot): configScript = cls.getConfigScript(platform, linkStatic, distroRoot) return '`%s --ftversion`' % configScript class GL(Library): libName = 'GL' makeName = 'GL' function = 'glGenTextures' @classmethod def isSystemLibrary(cls, platform): # On *BSD, OpenGL is in ports, not in the base system. return not platform.endswith('bsd') @classmethod def getHeaders(cls, platform): if platform == 'darwin': return ('', ) else: return ('', ) @classmethod def getCompileFlags(cls, platform, linkStatic, distroRoot): if platform in ('netbsd', 'openbsd'): return '-I/usr/X11R6/include -I/usr/X11R7/include' else: return super(GL, cls).getCompileFlags( platform, linkStatic, distroRoot ) @classmethod def getLinkFlags(cls, platform, linkStatic, distroRoot): if platform == 'darwin': return '-framework OpenGL' elif platform.startswith('mingw'): return '-lopengl32' elif platform in ('netbsd', 'openbsd'): return '-L/usr/X11R6/lib -L/usr/X11R7/lib -lGL' else: return super(GL, cls).getLinkFlags(platform, linkStatic, distroRoot) @classmethod def getVersion(cls, platform, linkStatic, distroRoot): def execute(cmd, log): versionPairs = tuple( ( major, minor ) for major in range(1, 10) for minor in range(0, 10) ) version = cmd.expand(log, cls.getHeaders(platform), *( 'GL_VERSION_%d_%d' % pair for pair in versionPairs )) try: return '%d.%d' % max( ver for ver, exp in zip(versionPairs, version) if exp is not None ) except ValueError: return None return execute class GLEW(Library): makeName = 'GLEW' header = '' function = 'glewInit' dependsOn = ('GL', ) @classmethod def getLibName(cls, platform): if platform.startswith('mingw'): return 'glew32' else: return 'GLEW' @classmethod def getCompileFlags(cls, platform, linkStatic, distroRoot): flags = super(GLEW, cls).getCompileFlags( platform, linkStatic, distroRoot ) if platform.startswith('mingw') and linkStatic: return '%s -DGLEW_STATIC' % flags else: return flags class LibPNG(Library): libName = 'png12' makeName = 'PNG' header = '' configScriptName = 'libpng-config' dynamicLibsOption = '--ldflags' function = 'png_write_image' dependsOn = ('ZLIB', ) @classmethod def isSystemLibrary(cls, platform): return platform in ('android', 'dingux') class OGG(Library): libName = 'ogg' makeName = 'OGG' header = '' function = 'ogg_stream_init' @classmethod def isSystemLibrary(cls, platform): return platform in ('android', 'dingux') class SDL(Library): libName = 'SDL' makeName = 'SDL' header = '' configScriptName = 'sdl-config' staticLibsOption = '--static-libs' function = 'SDL_Init' @classmethod def isSystemLibrary(cls, platform): return platform in ('android', 'dingux') class SDL_ttf(Library): libName = 'SDL_ttf' makeName = 'SDL_TTF' header = '' function = 'TTF_OpenFont' dependsOn = ('SDL', 'FREETYPE') @classmethod def isSystemLibrary(cls, platform): return platform in ('android', 'dingux') @classmethod def getLinkFlags(cls, platform, linkStatic, distroRoot): flags = super(SDL_ttf, cls).getLinkFlags( platform, linkStatic, distroRoot ) if not linkStatic: # Because of the SDLmain trickery, we need SDL's link flags too # on some platforms even though we're linking dynamically. flags += ' ' + SDL.getLinkFlags(platform, linkStatic, distroRoot) return flags @classmethod def getVersion(cls, platform, linkStatic, distroRoot): def execute(cmd, log): version = cmd.expand(log, cls.getHeaders(platform), 'SDL_TTF_MAJOR_VERSION', 'SDL_TTF_MINOR_VERSION', 'SDL_TTF_PATCHLEVEL', ) return None if None in version else '%s.%s.%s' % version return execute class TCL(Library): libName = 'tcl' makeName = 'TCL' header = '' function = 'Tcl_CreateInterp' @classmethod def isSystemLibrary(cls, platform): return platform in ('android',) @classmethod def getTclConfig(cls, platform, distroRoot): '''Tcl has a config script that is unlike the typical lib-config script. Information is gathered by sourcing the config script, instead of executing it and capturing the queried value from stdout. This script is located in a library directory, not in a directory in the PATH. Also, it does not have the executable bit set. This method returns the location of the Tcl config script, or None if it could not be found. ''' if hasattr(cls, 'tclConfig'): # Return cached value. return cls.tclConfig def iterLocations(): if platform == 'android': # Under Android, the tcl set-up apparently differs from # other cross-platform setups. the search algorithm to find the # directory that will contain the tclConfig.sh script and the shared libs # is not applicable to Android. Instead, immediately return the correct # subdirectories to the routine that invokes iterLocations() sdl_android_port_path = environ['SDL_ANDROID_PORT_PATH'] libpath = sdl_android_port_path + '/project/libs/armeabi' yield libpath tclpath = sdl_android_port_path + '/project/jni/tcl8.5/unix' yield tclpath else: if distroRoot is None or cls.isSystemLibrary(platform): if msysActive(): roots = (msysPathToNative('/mingw32'), ) else: roots = ('/usr/local', '/usr') else: roots = (distroRoot, ) for root in roots: if isdir(root): for libdir in ('lib', 'lib64', 'lib/tcl'): libpath = root + '/' + libdir if isdir(libpath): yield libpath for entry in listdir(libpath): if entry.startswith('tcl8.'): tclpath = libpath + '/' + entry if isdir(tclpath): yield tclpath tclConfigs = {} log = open('derived/tcl-search.log', 'w') print >> log, 'Looking for Tcl...' try: for location in iterLocations(): path = location + '/tclConfig.sh' if isfile(path): print >> log, 'Config script:', path text = captureStdout( log, "sh -c '. %s && echo %s'" % ( path, '$TCL_MAJOR_VERSION $TCL_MINOR_VERSION' ) ) if text is not None: try: # pylint: disable-msg=E1103 major, minor = text.split() version = int(major), int(minor) except ValueError: pass else: print >> log, 'Found: version %d.%d' % version tclConfigs[path] = version try: # Minimum required version is 8.5. # Pick the oldest possible version to minimize the risk of # running into incompatible changes. tclConfig = min( ( version, path ) for path, version in tclConfigs.iteritems() if version >= (8, 5) )[1] except ValueError: tclConfig = None print >> log, 'No suitable versions found.' else: print >> log, 'Selected:', tclConfig finally: log.close() cls.tclConfig = tclConfig return tclConfig @classmethod def evalTclConfigExpr(cls, platform, distroRoot, expr, description): tclConfig = cls.getTclConfig(platform, distroRoot) if tclConfig is None: return None log = open('derived/tcl-search.log', 'a') try: print >> log, 'Getting Tcl %s...' % description text = captureStdout( log, shjoin([ 'sh', '-c', '. %s && eval "echo \\"%s\\""' % (tclConfig, expr) ]) ) if text is not None: print >> log, 'Result: %s' % text.strip() finally: log.close() return None if text is None else text.strip() @classmethod def getCompileFlags(cls, platform, linkStatic, distroRoot): if platform == 'android': # Use the current ANDROID cross-compilation flags and not the TCL flags. Otherwise, the # wrong version of libstdc++ will end-up on the include path; the minimal Android NDK # version instead of the more complete GNU version. This is because TCL for Android has # been configured with the minimal libstdc++ on the include path in the C(XX) flags and # not with the more complete GNU version return environ['ANDROID_CXXFLAGS'] wantShared = not linkStatic or cls.isSystemLibrary(platform) # The -DSTATIC_BUILD is a hack to avoid including the complete # TCL_DEFS (see 9f1dbddda2) but still being able to link on # MinGW (tcl.h depends on this being defined properly). return cls.evalTclConfigExpr( platform, distroRoot, '${TCL_INCLUDE_SPEC}' + ('' if wantShared else ' -DSTATIC_BUILD'), 'compile flags' ) @classmethod def getLinkFlags(cls, platform, linkStatic, distroRoot): if platform == 'android': # Use the current ANDROID cross-compilation flags and not the TCL flags to # prevent issues with libstdc++ version. See also getCompileFlags() return environ['ANDROID_LDFLAGS'] # Tcl can be built as a shared or as a static library, but not both. # Check whether the library type of Tcl matches the one we want. wantShared = not linkStatic or cls.isSystemLibrary(platform) tclShared = cls.evalTclConfigExpr( platform, distroRoot, '${TCL_SHARED_BUILD}', 'library type (shared/static)' ) log = open('derived/tcl-search.log', 'a') try: if tclShared == '0': if wantShared: print >> log, ( 'Dynamic linking requested, but Tcl installation has ' 'static library.' ) return None elif tclShared == '1': if not wantShared: print >> log, ( 'Static linking requested, but Tcl installation has ' 'dynamic library.' ) return None else: print >> log, ( 'Unable to determine whether Tcl installation has ' 'shared or static library.' ) return None finally: log.close() # Now get the link flags. if wantShared: return cls.evalTclConfigExpr( platform, distroRoot, '${TCL_LIB_SPEC}', 'dynamic link flags' ) else: return cls.evalTclConfigExpr( platform, distroRoot, '${TCL_EXEC_PREFIX}/lib/${TCL_LIB_FILE} ${TCL_LIBS}', 'static link flags' ) @classmethod def getVersion(cls, platform, linkStatic, distroRoot): return cls.evalTclConfigExpr( platform, distroRoot, '${TCL_MAJOR_VERSION}.${TCL_MINOR_VERSION}${TCL_PATCH_LEVEL}', 'version' ) class Theora(Library): libName = 'theoradec' makeName = 'THEORA' header = '' function = 'th_decode_ycbcr_out' dependsOn = ('OGG', ) @classmethod def isSystemLibrary(cls, platform): return platform in ('android', 'dingux') class Vorbis(Library): libName = 'vorbis' makeName = 'VORBIS' header = '' function = 'vorbis_synthesis_pcmout' dependsOn = ('OGG', ) @classmethod def isSystemLibrary(cls, platform): return platform in ('android', 'dingux') class ZLib(Library): libName = 'z' makeName = 'ZLIB' header = '' function = 'inflate' @classmethod def isSystemLibrary(cls, platform): return platform in ('android', 'dingux') @classmethod def getVersion(cls, platform, linkStatic, distroRoot): def execute(cmd, log): version = cmd.expand(log, cls.getHeaders(platform), 'ZLIB_VERSION') return None if version is None else version.strip('"') return execute # Build a dictionary of libraries using introspection. def _discoverLibraries(localObjects): for obj in localObjects: if isinstance(obj, type) and issubclass(obj, Library): if not (obj is Library): yield obj.makeName, obj librariesByName = dict(_discoverLibraries(locals().itervalues())) def allDependencies(makeNames): '''Compute the set of all directly and indirectly required libraries to build and use the given set of libraries. Returns the make names of the required libraries. ''' # Compute the reflexive-transitive closure. transLibs = set() newLibs = set(makeNames) while newLibs: transLibs.update(newLibs) newLibs = set( depMakeName for makeName in newLibs for depMakeName in librariesByName[makeName].dependsOn if depMakeName not in transLibs ) return transLibs openMSX-RELEASE_0_12_0/build/list_system_libs.py000066400000000000000000000007401257557151200215010ustar00rootroot00000000000000# Prints the Make names of those libraries that are present in the base OS. from libraries import librariesByName def main(platform): systemLibs = set( name for name, library in librariesByName.iteritems() if library.isSystemLibrary(platform) ) print ' '.join(sorted(systemLibs)) if __name__ == '__main__': import sys if len(sys.argv) == 2: main(*sys.argv[1 : ]) else: print >> sys.stderr, ( 'Usage: python list_system_libs.py TARGET_OS' ) sys.exit(2) openMSX-RELEASE_0_12_0/build/main.mk000066400000000000000000000534061257557151200170230ustar00rootroot00000000000000# openMSX Build System # ==================== # # This is the home made build system for openMSX, which replaced the # autoconf/automake combo. # # Used a lot of ideas from Peter Miller's excellent paper # "Recursive Make Considered Harmful". # http://miller.emu.id.au/pmiller/books/rmch/ # TODO: # - Move calculation of CFLAGS and LDFLAGS to components2defs.py? # Python Interpreter # ================== # We need Python from the 2.x series, version 2.6 or higher. # Usually this executable is available as just "python", but on some systems # you might have to be more specific, for example "python2" or "python2.6". # Or if the Python interpreter is not in the search path, you can specify its # full path. ifeq ($(PYTHON),) PYTHON:=$(shell build/python-search.sh) ifeq ($(PYTHON),) $(error No suitable Python interpreter found. Please install Python version 2.x where x >= 5. If your Python interpreter is installed in a non-standard location, please set the environment variable PYTHON to the full path of the interpreter binary.) endif endif $(info Using Python: $(PYTHON)) # Delete on Error # =============== # Delete output if rule fails. # This is a flag that applies to all rules. .DELETE_ON_ERROR: # Logical Targets # =============== ifneq ($(words $(MAKECMDGOALS)),1) $(error main.mk can only handle once goal at a time) endif # TODO: "dist" and "createsubs" are missing # TODO: more missing? # Logical targets which require dependency files. DEPEND_TARGETS:=all default install run bindist # Logical targets which do not require dependency files. NODEPEND_TARGETS:=clean config probe 3rdparty staticbindist # Mark all logical targets as such. .PHONY: $(DEPEND_TARGETS) $(NODEPEND_TARGETS) # Settings # ======== # # There are platform specific settings and flavour specific settings. # platform: architecture, OS # flavour: optimisation levels, debugging, profiling # Function to check a variable has been defined and has a non-empty value. # Usage: $(call DEFCHECK,VARIABLE_NAME) DEFCHECK=$(strip \ $(if $(filter _undefined,_$(origin $(1))), \ $(error Variable $(1) is undefined) ) \ ) # Function to check a boolean variable has value "true" or "false". # Usage: $(call BOOLCHECK,VARIABLE_NAME) BOOLCHECK=$(DEFCHECK)$(strip \ $(if $(filter-out _true _false,_$($(1))), \ $(error Value of $(1) ("$($(1))") should be "true" or "false") ) \ ) # Will be added to by platform specific Makefile, by flavour specific Makefile # and by this Makefile. # Note: CXXFLAGS is overridable from the command line; COMPILE_FLAGS is not. # We use CXXFLAGS for flavour specific flags and COMPILE_FLAGS for # platform specific flags. CXXFLAGS:= COMPILE_FLAGS:= COMPILE_ENV:= # Note: LDFLAGS are passed to the linker itself, LINK_FLAGS are passed to the # compiler in the link phase. LDFLAGS:= LINK_FLAGS:= LINK_ENV:= # Flags that specify the target platform. # These should be inherited by the 3rd party libs Makefile. TARGET_FLAGS:= # Customisation # ============= include build/custom.mk $(call DEFCHECK,INSTALL_BASE) $(call BOOLCHECK,VERSION_EXEC) $(call BOOLCHECK,SYMLINK_FOR_BINARY) $(call BOOLCHECK,INSTALL_CONTRIB) # Platforms # ========= # Note: # A platform currently specifies both the host platform (performing the build) # and the target platform (running the created binary). When we have real # experience with cross-compilation, a more sophisticated system can be # designed. LINK_MODE:=$(if $(filter true,$(3RDPARTY_FLAG)),3RD_STA,SYS_DYN) # Do not perform autodetection if platform was specified by the user. ifneq ($(filter undefined,$(origin OPENMSX_TARGET_CPU) $(origin OPENMSX_TARGET_OS)),) DETECTSYS_SCRIPT:=build/detectsys.py LOCAL_PLATFORM:=$(shell $(PYTHON) $(DETECTSYS_SCRIPT)) ifeq ($(LOCAL_PLATFORM),) $(error No platform specified using OPENMSX_TARGET_CPU and OPENMSX_TARGET_OS and autodetection of local platform failed) endif OPENMSX_TARGET_CPU:=$(word 1,$(LOCAL_PLATFORM)) OPENMSX_TARGET_OS:=$(word 2,$(LOCAL_PLATFORM)) endif # OPENMSX_TARGET_CPU && OPENMSX_TARGET_OS PLATFORM:= ifneq ($(origin OPENMSX_TARGET_OS),undefined) ifneq ($(origin OPENMSX_TARGET_CPU),undefined) PLATFORM:=$(OPENMSX_TARGET_CPU)-$(OPENMSX_TARGET_OS) endif endif # Ignore rest of Makefile if autodetection was not performed yet. # Note that the include above will force a reload of the Makefile. ifneq ($(PLATFORM),) # List of CPUs to compile for. ifeq ($(OPENMSX_TARGET_CPU),univ) CPU_LIST:=x86 x86_64 else CPU_LIST:=$(OPENMSX_TARGET_CPU) endif # Default flavour. $(call DEFCHECK,OPENMSX_TARGET_CPU) ifeq ($(OPENMSX_TARGET_CPU),x86) ifeq ($(filter darwin%,$(OPENMSX_TARGET_OS)),) # To run openMSX with decent speed, at least a Pentium 2 class machine # is needed, so let's optimise for that. OPENMSX_FLAVOUR?=i686 else # The system headers of OS X use SSE features, which are not available on # i686, so we only use the generic optimisation flags instead. OPENMSX_FLAVOUR?=opt endif else ifeq ($(OPENMSX_TARGET_CPU),ppc) OPENMSX_FLAVOUR?=ppc else ifeq ($(OPENMSX_TARGET_CPU),m68k) OPENMSX_FLAVOUR?=m68k else OPENMSX_FLAVOUR?=opt endif endif endif # Load OS specific settings. $(call DEFCHECK,OPENMSX_TARGET_OS) include build/platform-$(OPENMSX_TARGET_OS).mk # Check that all expected variables were defined by OS specific Makefile: # - library file name extension $(call DEFCHECK,LIBRARYEXT) # - executable file name extension $(call DEFCHECK,EXEEXT) # - platform supports symlinks? $(call BOOLCHECK,USE_SYMLINK) ifneq ($(OPENMSX_TARGET_CPU),univ) # Get CPU specific flags. TARGET_FLAGS+=$(shell $(PYTHON) build/cpu2flags.py $(OPENMSX_TARGET_CPU)) endif # Flavours # ======== ifneq ($(OPENMSX_TARGET_CPU),univ) # Load flavour specific settings. include build/flavour-$(OPENMSX_FLAVOUR).mk endif # Paths # ===== BUILD_PATH:=derived/$(PLATFORM)-$(OPENMSX_FLAVOUR) ifeq ($(3RDPARTY_FLAG),true) BUILD_PATH:=$(BUILD_PATH)-3rd endif # Own build of 3rd party libs. ifeq ($(3RDPARTY_FLAG),true) 3RDPARTY_INSTALL_DIR:=$(BUILD_PATH)/3rdparty/install endif SOURCES_PATH:=src BINARY_PATH:=$(BUILD_PATH)/bin BINARY_FILE:=openmsx$(EXEEXT) BINDIST_DIR:=$(BUILD_PATH)/bindist BINDIST_PACKAGE:= ifeq ($(VERSION_EXEC),true) REVISION:=$(shell PYTHONPATH=build $(PYTHON) -c \ "import version; print version.extractRevisionString()" \ ) BINARY_FULL:=$(BINARY_PATH)/openmsx-$(REVISION)$(EXEEXT) else BINARY_FULL:=$(BINARY_PATH)/$(BINARY_FILE) endif LIBRARY_FILE:=openmsx$(LIBRARYEXT) LIBRARY_PATH:=$(BUILD_PATH)/lib LIBRARY_FULL:=$(LIBRARY_PATH)/$(LIBRARY_FILE) BUILDINFO_SCRIPT:=build/buildinfo2code.py CONFIG_HEADER:=$(BUILD_PATH)/config/build-info.hh PROBE_SCRIPT:=build/probe.py PROBE_MAKE:=$(BUILD_PATH)/config/probed_defs.mk VERSION_SCRIPT:=build/version2code.py VERSION_HEADER:=$(BUILD_PATH)/config/Version.ii COMPONENTS_HEADER_SCRIPT:=build/components2code.py COMPONENTS_DEFS_SCRIPT:=build/components2defs.py COMPONENTS_HEADER:=$(BUILD_PATH)/config/components.hh COMPONENTS_DEFS:=$(BUILD_PATH)/config/components_defs.mk GENERATED_HEADERS:=$(VERSION_HEADER) $(CONFIG_HEADER) $(COMPONENTS_HEADER) # Configuration # ============= ifneq ($(OPENMSX_TARGET_CPU),univ) ifneq ($(filter $(DEPEND_TARGETS),$(MAKECMDGOALS)),) -include $(PROBE_MAKE) -include $(COMPONENTS_DEFS) endif # goal requires dependencies endif # universal binary # Filesets # ======== SOURCE_DIRS:=$(sort $(shell find src -type d)) SOURCES_FULL:=$(foreach dir,$(SOURCE_DIRS),$(sort $(wildcard $(dir)/*.cc))) SOURCES_FULL:=$(filter-out %Test.cc,$(SOURCES_FULL)) SOURCES_FULL:=$(filter-out src/sound/generate%.cc,$(SOURCES_FULL)) # TODO: This doesn't work since MAX_SCALE_FACTOR is not a Make variable, # only a #define in build-info.hh. ifeq ($(MAX_SCALE_FACTOR),1) define SOURCES_UPSCALE Scanline Scaler2 Scaler3 Simple2xScaler Simple3xScaler SaI2xScaler SaI3xScaler Scale2xScaler Scale3xScaler HQ2xScaler HQ2xLiteScaler HQ3xScaler HQ3xLiteScaler RGBTriplet3xScaler MLAAScaler Multiply32 endef SOURCES_FULL:=$(filter-out $(foreach src,$(strip $(SOURCES_UPSCALE)),src/video/scalers/$(src).cc),$(SOURCES_FULL)) endif ifneq ($(COMPONENT_GL),true) SOURCES_FULL:=$(filter-out src/video/GL%.cc,$(SOURCES_FULL)) SOURCES_FULL:=$(filter-out src/video/SDLGL%.cc,$(SOURCES_FULL)) SOURCES_FULL:=$(filter-out src/video/scalers/GL%.cc,$(SOURCES_FULL)) endif ifneq ($(COMPONENT_LASERDISC),true) SOURCES_FULL:=$(filter-out src/laserdisc/%.cc,$(SOURCES_FULL)) SOURCES_FULL:=$(filter-out src/video/ld/%.cc,$(SOURCES_FULL)) endif # Apply subset to sources list. SOURCES_FULL:=$(filter $(SOURCES_PATH)/$(OPENMSX_SUBSET)%,$(SOURCES_FULL)) ifeq ($(SOURCES_FULL),) $(error Sources list empty $(if \ $(OPENMSX_SUBSET),after applying subset "$(OPENMSX_SUBSET)*")) endif SOURCES:=$(SOURCES_FULL:$(SOURCES_PATH)/%.cc=%) DEPEND_PATH:=$(BUILD_PATH)/dep DEPEND_FULL:=$(addsuffix .d,$(addprefix $(DEPEND_PATH)/,$(SOURCES))) OBJECTS_PATH:=$(BUILD_PATH)/obj OBJECTS_FULL:=$(addsuffix .o,$(addprefix $(OBJECTS_PATH)/,$(SOURCES))) ifneq ($(filter mingw%,$(OPENMSX_TARGET_OS)),) RESOURCE_SRC:=src/resource/openmsx.rc RESOURCE_OBJ:=$(OBJECTS_PATH)/resources.o RESOURCE_SCRIPT:=build/win_resource.py RESOURCE_HEADER:=$(BUILD_PATH)/config/resource-info.h else RESOURCE_OBJ:= endif # Compiler and Flags # ================== COMPILE_FLAGS+=$(TARGET_FLAGS) LINK_FLAGS+=$(TARGET_FLAGS) # Determine compiler. CXX?=g++ WINDRES?=windres DEPEND_FLAGS:= ifneq ($(filter %clang++,$(CXX))$(filter clang++%,$(CXX)),) # Enable C++11 COMPILE_FLAGS+=-std=c++11 # Clang does support -Wunused-macros, but it triggers on SDL's headers, # causing way too many false positives that we cannot fix. COMPILE_FLAGS+=-Wall -Wextra -Wundef -Wno-invalid-offsetof # TODO: Remove the overloading from the code instead. COMPILE_FLAGS+=-Wno-overloaded-virtual # Hardware descriptions can contain constants that are not used in the code # but still useful as documentation. COMPILE_FLAGS+=-Wno-unused-const-variable CC:=$(shell clang++ -print-prog-name=clang) DEPEND_FLAGS+=-MP else ifneq ($(filter %g++,$(CXX))$(filter g++%,$(CXX))$(findstring /g++-,$(CXX)),) # Generic compilation flags. COMPILE_FLAGS+=-pipe # Enable C++11 COMPILE_FLAGS+=-std=c++11 # Stricter warning and error reporting. COMPILE_FLAGS+=-Wall -Wextra -Wundef -Wno-invalid-offsetof -Wunused-macros -Wdouble-promotion -Wmissing-declarations # Flag that is not accepted by old GCC versions. COMPILE_FLAGS+=$(shell \ echo | $(CXX) -E -Wno-missing-field-initializers - >/dev/null 2>&1 \ && echo -Wno-missing-field-initializers \ ) # -Wzero-as-null-pointer-constant is available from gcc-4.7 ## IMHO this is a useful but not very important warning. It triggers in ## quite a few places via macros defined in tcl8.5/tclDecls.h, so we can't ## easily suppress it. So for now I'll disable this warning again. ##COMPILE_FLAGS+=$(shell \ ## echo | $(CXX) -E -Wzero-as-null-pointer-constant - >/dev/null 2>&1 \ ## && echo -Wzero-as-null-pointer-constant \ ## ) # Empty definition of used headers, so header removal doesn't break things. DEPEND_FLAGS+=-MP # Plain C compiler, for the 3rd party libs. CC:=$(subst g++,gcc,$(CXX)) else ifneq ($(filter %gcc,$(CXX))$(filter gcc%,$(CXX)),) $(error Set CXX to your "g++" executable instead of "gcc") else $(warning Unsupported compiler: $(CXX), please update Makefile) endif endif endif # Strip binary? OPENMSX_STRIP?=false $(call BOOLCHECK,OPENMSX_STRIP) STRIP_SEPARATE:=false ifeq ($(OPENMSX_STRIP),true) ifeq ($(OPENMSX_TARGET_OS),darwin) # Current (mid-2006) GCC 4.x for OS X will strip too many symbols, # resulting in a binary that cannot run. # However, the separate "strip" tool does work correctly. STRIP_SEPARATE:=true else # Tell GCC to produce a stripped binary. LINK_FLAGS+=-s endif endif # Determine common compile flags. COMPILE_FLAGS+=$(addprefix -I,$(SOURCE_DIRS) $(BUILD_PATH)/config) # Determine common link flags. LINK_FLAGS_PREFIX:=-Wl, LINK_FLAGS+=$(addprefix $(LINK_FLAGS_PREFIX),$(LDFLAGS)) # Determine component specific compile and link flags. ifeq ($(COMPONENT_CORE),true) COMPILE_FLAGS+=$(foreach lib,$(CORE_LIBS),$($(lib)_CFLAGS)) LINK_FLAGS+=$(foreach lib,$(CORE_LIBS),$($(lib)_LDFLAGS)) endif ifeq ($(COMPONENT_GL),true) COMPILE_FLAGS+=$(GL_CFLAGS) $(GLEW_CFLAGS) $(GLEW_GL_CFLAGS) LINK_FLAGS+=$(GL_LDFLAGS) $(GLEW_LDFLAGS) endif ifeq ($(COMPONENT_LASERDISC),true) COMPILE_FLAGS+=$(OGG_CFLAGS) $(VORBIS_CFLAGS) $(THEORA_CFLAGS) LINK_FLAGS+=$(OGG_LDFLAGS) $(VORBIS_LDFLAGS) $(THEORA_LDFLAGS) endif # Build Rules # =========== # Force a probe if "probe" target is passed explicitly. ifeq ($(MAKECMDGOALS),probe) probe: $(PROBE_MAKE) .PHONY: $(PROBE_MAKE) endif # Probe for headers and functions. $(PROBE_MAKE): $(PROBE_SCRIPT) build/custom.mk \ build/systemfuncs2code.py build/systemfuncs.py @$(PYTHON) $(PROBE_SCRIPT) \ "$(COMPILE_ENV) $(CXX) $(TARGET_FLAGS)" \ $(@D) $(OPENMSX_TARGET_OS) $(LINK_MODE) "$(3RDPARTY_INSTALL_DIR)" @touch $@ # Generate configuration header. # TODO: One platform file may include another, so the real solution would be # for the Python script to write dependency info. $(CONFIG_HEADER): $(BUILDINFO_SCRIPT) \ build/custom.mk build/platform-$(OPENMSX_TARGET_OS).mk @$(PYTHON) $(BUILDINFO_SCRIPT) $@ \ $(OPENMSX_TARGET_OS) $(OPENMSX_TARGET_CPU) $(OPENMSX_FLAVOUR) \ $(INSTALL_SHARE_DIR) @touch $@ # Generate version header. .PHONY: forceversionextraction forceversionextraction: $(VERSION_HEADER): forceversionextraction @$(PYTHON) $(VERSION_SCRIPT) $@ # Generate components header. $(COMPONENTS_HEADER): $(COMPONENTS_HEADER_SCRIPT) $(PROBE_MAKE) \ build/components.py @$(PYTHON) $(COMPONENTS_HEADER_SCRIPT) $@ $(PROBE_MAKE) @touch $@ # Generate components Makefile. $(COMPONENTS_DEFS): $(COMPONENTS_DEFS_SCRIPT) $(PROBE_MAKE) \ build/components.py @$(PYTHON) $(COMPONENTS_DEFS_SCRIPT) $@ $(PROBE_MAKE) @touch $@ # Default target. ifeq ($(OPENMSX_TARGET_OS),darwin) all: app else ifeq ($(OPENMSX_TARGET_OS),android) all: $(LIBRARY_FULL) else all: $(BINARY_FULL) endif endif # This is a workaround for the lack of order-only dependencies in GNU Make # versions older than 3.80 (for example Mac OS X 10.3 still ships with 3.79). # It creates a dummy file, which is never modified after its initial creation. # If a rule that produces a file does not modify that file, Make considers the # target to be up-to-date. That way, the targets "init-dummy-file" depends on # will always be checked before compilation, but they will not cause all object # files to be considered outdated. INIT_DUMMY_FILE:=$(BUILD_PATH)/config/init-dummy-file $(INIT_DUMMY_FILE): config $(GENERATED_HEADERS) @touch -a $@ # Print configuration. config: ifeq ($(COMPONENT_CORE),false) # Do not build if core component dependencies are not met. @echo 'Cannot build openMSX because essential libraries are unavailable.' @echo 'Please install the needed libraries and their header files and rerun "configure"' @false endif @echo "Build configuration:" @echo " Platform: $(PLATFORM)" @echo " Flavour: $(OPENMSX_FLAVOUR)" @echo " Compiler: $(CXX)" @echo " Subset: $(if $(OPENMSX_SUBSET),$(OPENMSX_SUBSET)*,full build)" # Include dependency files. ifneq ($(filter $(DEPEND_TARGETS),$(MAKECMDGOALS)),) -include $(DEPEND_FULL) endif # Clean up build tree of current flavour. clean: @echo "Cleaning up..." @rm -rf $(BUILD_PATH) # Create Makefiles in source subdirectories, to conveniently build a subset. ifeq ($(MAKECMDGOALS),createsubs) # Function that concatenates list items to form a single string. # Usage: $(call JOIN,TEXT) JOIN=$(if $(1),$(firstword $(1))$(call JOIN,$(wordlist 2,999999,$(1))),) RELPATH=$(call JOIN,$(patsubst %,../,$(subst /, ,$(@:%/GNUmakefile=%)))) SUB_MAKEFILES:=$(addsuffix GNUmakefile,$(sort $(dir $(SOURCES_FULL)))) createsubs: $(SUB_MAKEFILES) $(SUB_MAKEFILES): @echo "Creating $@..." @echo "export OPENMSX_SUBSET=$(@:$(SOURCES_PATH)/%GNUmakefile=%)" > $@ @echo "all:" >> $@ @echo " @\$$(MAKE) -C $(RELPATH) -f build/main.mk all" >> $@ # Force re-creation every time this target is run. .PHONY: $(SUB_MAKEFILES) endif # Compile and generate dependency files in one go. DEPEND_SUBST=$(patsubst $(SOURCES_PATH)/%.cc,$(DEPEND_PATH)/%.d,$<) $(OBJECTS_FULL): $(INIT_DUMMY_FILE) $(OBJECTS_FULL): $(OBJECTS_PATH)/%.o: $(SOURCES_PATH)/%.cc $(DEPEND_PATH)/%.d @echo "Compiling $(patsubst $(SOURCES_PATH)/%,%,$<)..." @mkdir -p $(@D) @mkdir -p $(patsubst $(OBJECTS_PATH)%,$(DEPEND_PATH)%,$(@D)) @$(COMPILE_ENV) $(CXX) \ $(DEPEND_FLAGS) -MMD -MF $(DEPEND_SUBST) \ -o $@ $(CXXFLAGS) $(COMPILE_FLAGS) -c $< @touch $@ # Force .o file to be newer than .d file. # Generate dependencies that do not exist yet. # This is only in case some .d files have been deleted; # in normal operation this rule is never triggered. $(DEPEND_FULL): # Windows resources that are added to the executable. ifneq ($(filter mingw%,$(OPENMSX_TARGET_OS)),) $(RESOURCE_HEADER): $(INIT_DUMMY_FILE) forceversionextraction @$(PYTHON) $(RESOURCE_SCRIPT) $@ $(RESOURCE_OBJ): $(RESOURCE_SRC) $(RESOURCE_HEADER) @echo "Compiling resources..." @mkdir -p $(@D) @$(WINDRES) $(addprefix --include-dir=,$(^D)) -o $@ -i $< endif # Link executable. ifeq ($(OPENMSX_TARGET_CPU),univ) BINARY_FOR_CPU=$(BINARY_FULL:derived/univ-%=derived/$(1)-%) SINGLE_CPU_BINARIES=$(foreach CPU,$(CPU_LIST),$(call BINARY_FOR_CPU,$(CPU))) .PHONY: $(SINGLE_CPU_BINARIES) $(SINGLE_CPU_BINARIES): @echo "Start compile for $(firstword $(subst -, ,$(@:derived/%=%))) CPU..." @$(MAKE) -f build/main.mk all \ OPENMSX_TARGET_CPU=$(firstword $(subst -, ,$(@:derived/%=%))) \ OPENMSX_TARGET_OS=$(OPENMSX_TARGET_OS) \ OPENMSX_FLAVOUR=$(OPENMSX_FLAVOUR) \ 3RDPARTY_FLAG=$(3RDPARTY_FLAG) \ PYTHON=$(PYTHON) @echo "Finished compile for $(firstword $(subst -, ,$(@:derived/%=%))) CPU." $(BINARY_FULL): $(SINGLE_CPU_BINARIES) @mkdir -p $(@D) @lipo -create $^ -output $@ else $(BINARY_FULL): $(OBJECTS_FULL) $(RESOURCE_OBJ) ifeq ($(OPENMSX_SUBSET),) @echo "Linking $(notdir $@)..." @mkdir -p $(@D) @+$(LINK_ENV) $(CXX) -o $@ $(CXXFLAGS) $^ $(LINK_FLAGS) ifeq ($(STRIP_SEPARATE),true) @echo "Stripping $(notdir $@)..." @strip $@ endif ifeq ($(USE_SYMLINK),true) @ln -sf $(@:derived/%=%) derived/$(BINARY_FILE) else @cp $@ derived/$(BINARY_FILE) endif else @echo "Not linking $(notdir $@) because only a subset was built." endif # subset endif # universal binary $(LIBRARY_FULL): $(OBJECTS_FULL) $(RESOURCE_OBJ) @echo "Linking $(notdir $@)..." @mkdir -p $(@D) @$(LINK_ENV) $(CXX) -o $@ $(CXXFLAGS) $^ $(LINK_FLAGS) # Run executable. run: all @echo "Running $(notdir $(BINARY_FULL))..." @$(BINARY_FULL) # Installation and Binary Packaging # ================================= ifneq ($(filter $(MAKECMDGOALS),bindist)$(filter $(OPENMSX_TARGET_OS),darwin),) # Create binary distribution directory. BINDIST_DIR:=$(BUILD_PATH)/bindist BINDIST_PACKAGE:= # Override install locations. INSTALL_ROOT:=$(BINDIST_DIR)/install ifneq ($(filter mingw%,$(OPENMSX_TARGET_OS)),) # In Windows the "share" dir is expected at the same level as the executable, # so do not put the executable in "bin". INSTALL_BINARY_DIR:=$(INSTALL_ROOT) else INSTALL_BINARY_DIR:=$(INSTALL_ROOT)/bin endif INSTALL_SHARE_DIR:=$(INSTALL_ROOT)/share INSTALL_DOC_DIR:=$(INSTALL_ROOT)/doc # C-BIOS should be included. INSTALL_CONTRIB:=true # Do not display header and post-install instructions. INSTALL_VERBOSE:=false .PHONY: bindist bindistclean bindist: install # Force removal of old destination dir before installing to new dir. install: bindistclean bindistclean: $(BINARY_FULL) @echo "Removing any old binary package..." @rm -rf $(BINDIST_DIR) @$(if $(BINDIST_PACKAGE),rm -f $(BINDIST_PACKAGE),) @echo "Creating binary package:" endif ifeq ($(OPENMSX_TARGET_OS),darwin) # Application directory for Darwin. # This handles the "bindist" target, but can also be used with the "install" # target to create an app folder but no DMG. include build/package-darwin/app.mk else ifeq ($(OPENMSX_TARGET_OS),dingux) # ZIP file package for Dingux. include build/package-dingux/zip.mk else # Note: Use OPENMSX_INSTALL only to create binary packages. # To change installation dir for actual installations, edit "custom.mk". OPENMSX_INSTALL?=$(INSTALL_BASE) # Allow full customization of locations, used by Debian packaging. INSTALL_BINARY_DIR?=$(OPENMSX_INSTALL)/bin INSTALL_SHARE_DIR?=$(OPENMSX_INSTALL)/share INSTALL_DOC_DIR?=$(OPENMSX_INSTALL)/doc INSTALL_VERBOSE?=true endif endif # DESTDIR is a convention shared by at least GNU and FreeBSD to specify a path # prefix that will be used for all installed files. install: $(BINARY_FULL) @$(PYTHON) build/install.py "$(DESTDIR)" \ $(INSTALL_BINARY_DIR) $(INSTALL_SHARE_DIR) $(INSTALL_DOC_DIR) \ $(BINARY_FULL) $(OPENMSX_TARGET_OS) \ $(INSTALL_VERBOSE) $(INSTALL_CONTRIB) # Source Packaging # ================ dist: @$(PYTHON) build/gitdist.py # Binary Packaging Using 3rd Party Libraries # ========================================== .PHONY: $(addprefix 3rdparty-,$(CPU_LIST)) run-3rdparty 3rdparty: $(addprefix 3rdparty-,$(CPU_LIST)) # Recursive invocation for different CPUs. # This is used even when building for a single CPU, since the OS and flavour # can differ. $(addprefix 3rdparty-,$(CPU_LIST)): $(MAKE) -f build/main.mk run-3rdparty \ OPENMSX_TARGET_CPU=$(@:3rdparty-%=%) \ OPENMSX_TARGET_OS=$(OPENMSX_TARGET_OS) \ OPENMSX_FLAVOUR=$(OPENMSX_FLAVOUR) \ 3RDPARTY_FLAG=true \ PYTHON=$(PYTHON) # Call third party Makefile with the right arguments. # This is an internal target, users should select "3rdparty" instead. run-3rdparty: $(MAKE) -f build/3rdparty.mk \ BUILD_PATH=$(BUILD_PATH)/3rdparty \ OPENMSX_TARGET_CPU=$(OPENMSX_TARGET_CPU) \ OPENMSX_TARGET_OS=$(OPENMSX_TARGET_OS) \ _CC=$(CC) _CFLAGS="$(TARGET_FLAGS) $(CXXFLAGS)" \ _LDFLAGS="$(TARGET_FLAGS)" \ WINDRES=$(WINDRES) \ LINK_MODE=$(LINK_MODE) $(COMPILE_ENV) \ PYTHON=$(PYTHON) staticbindist: 3rdparty $(MAKE) -f build/main.mk bindist \ OPENMSX_TARGET_CPU=$(OPENMSX_TARGET_CPU) \ OPENMSX_TARGET_OS=$(OPENMSX_TARGET_OS) \ OPENMSX_FLAVOUR=$(OPENMSX_FLAVOUR) \ 3RDPARTY_FLAG=true \ PYTHON=$(PYTHON) endif # PLATFORM openMSX-RELEASE_0_12_0/build/makeutils.py000066400000000000000000000076311257557151200201150ustar00rootroot00000000000000from os.path import isdir import re def filterLines(lines, regex): '''Filters each line of the given line iterator using the given regular expression string. For each match, a tuple containing the text matching each capture group from the regular expression is yielded. ''' matcher = re.compile(regex) for line in lines: if line.endswith('\n'): line = line[ : -1] match = matcher.match(line) if match: yield match.groups() def filterFile(filePath, regex): '''Filters each line of the given text file using the given regular expression string. For each match, a tuple containing the text matching each capture group from the regular expression is yielded. ''' inp = open(filePath, 'r') try: for groups in filterLines(inp, regex): yield groups finally: inp.close() def joinContinuedLines(lines): '''Iterates through the given lines, replacing lines that are continued using a trailing backslash with a single line. ''' buf = '' for line in lines: if line.endswith('\\\n'): buf += line[ : -2] elif line.endswith('\\'): buf += line[ : -1] else: yield buf + line buf = '' if buf: raise ValueError('Continuation on last line') _reEval = re.compile('(\$\(|\))') def evalMakeExpr(expr, makeVars): '''Evaluates variable references in an expression. Raises ValueError if there is a syntax error in the expression. Raises KeyError if the expression references a non-existing variable. ''' stack = [ [] ] for part in _reEval.split(expr): if part == '$(': stack.append([]) elif part == ')' and len(stack) != 1: name = ''.join(stack.pop()) if name.startswith('addprefix '): prefix, args = name[len('addprefix') : ].split(',') prefix = prefix.strip() value = ' '.join(prefix + arg for arg in args.split()) elif name.startswith('addsuffix '): suffix, args = name[len('addsuffix') : ].split(',') suffix = suffix.strip() value = ' '.join(arg + suffix for arg in args.split()) elif name.startswith('shell '): # Unsupported; assume result is never used. value = '?' elif name.startswith('call DIR_IF_EXISTS,'): # This is our function, not a Make function, but the goal is # not to emulate Make, so we emulate our function instead. path = name[len('call DIR_IF_EXISTS,'): ] value = path if isdir(path) else '' elif name.isdigit(): # This is a function argument; assume the evaluated result is # never used. value = '?' else: value = makeVars[name] stack[-1].append(value) else: stack[-1].append(part) if len(stack) != 1: raise ValueError('Open without close in "%s"' % expr) return ''.join(stack.pop()) def extractMakeVariables(filePath, makeVars = None): '''Extract all variable definitions from the given Makefile. The optional makeVars argument is a dictionary containing the already defined variables. These variables will be included in the output; the given dictionary is not modified. Returns a dictionary that maps each variable name to its value. ''' makeVars = {} if makeVars is None else dict(makeVars) inp = open(filePath, 'r') try: for name, assign, value in filterLines( joinContinuedLines(inp), r'[ ]*([A-Za-z0-9_]+)[ ]*([+:]?=)(.*)' ): if assign == '=': makeVars[name] = value.strip() elif assign == ':=': makeVars[name] = evalMakeExpr(value, makeVars).strip() elif assign == '+=': # Note: Make will or will not evaluate the added expression # depending on how the variable was originally defined, # but we don't store that information. makeVars[name] = makeVars[name] + ' ' + value.strip() else: assert False, assign finally: inp.close() return makeVars def parseBool(valueStr): '''Parses a string containing a boolean value. Accepted values are "true" and "false"; anything else raises ValueError. ''' if valueStr == 'true': return True elif valueStr == 'false': return False else: raise ValueError('Invalid boolean "%s"' % valueStr) openMSX-RELEASE_0_12_0/build/msvc/000077500000000000000000000000001257557151200165065ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/build/msvc/genconfig.py000066400000000000000000000041371257557151200210240ustar00rootroot00000000000000# Generates configuration headers for VC++ builds import sys import os.path import outpututils import buildinfo2code import components2code import systemfuncs2code import win_resource import version2code # # platform: one of { Win32, x64 } # configuration: one of { Debug, Developer, Release } # outputPath: the location in which to generate config files # def genConfig(platform, configuration, outputPath): buildPath = 'build' msvcPath = os.path.join(buildPath, 'msvc') probeMakePath = os.path.join(msvcPath, 'probed_defs.mk') # # build-info.hh # buildInfoHeader = os.path.join(outputPath, 'build-info.hh') targetPlatform = 'mingw32' if platform == 'Win32': targetCPU = 'x86' elif platform == 'x64': targetCPU = 'x86_64' else: raise ValueError('Invalid platform: ' + platform) flavour = configuration installShareDir = '/opt/openMSX/share' #not used on Windows, so whatever generator = buildinfo2code.iterBuildInfoHeader(targetPlatform, targetCPU, flavour, installShareDir) outpututils.rewriteIfChanged(buildInfoHeader, generator) # # components.hh # componentsHeader = os.path.join(outputPath, 'components.hh') generator = components2code.iterComponentsHeader(probeMakePath) outpututils.rewriteIfChanged(componentsHeader, generator) # # systemfuncs.hh # systemFuncsHeader = os.path.join(outputPath, 'systemfuncs.hh') generator = systemfuncs2code.iterSystemFuncsHeader(systemfuncs2code.getSystemFuncsInfo()) outpututils.rewriteIfChanged(systemFuncsHeader, generator) # # resource-info.hh # resourceInfoHeader = os.path.join(outputPath, 'resource-info.h') generator = win_resource.iterResourceHeader() outpututils.rewriteIfChanged(resourceInfoHeader, generator) # # version.ii # versionHeader = os.path.join(outputPath, 'version.ii') generator = version2code.iterVersionInclude() outpututils.rewriteIfChanged(versionHeader, generator) if len(sys.argv) == 4: genConfig(sys.argv[1], sys.argv[2], sys.argv[3]) else: print >> sys.stderr, 'Usage: python genconfig.py platform configuration outputPath' sys.exit(2) openMSX-RELEASE_0_12_0/build/msvc/openmsx.sln000066400000000000000000000033171257557151200207210ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Express 2013 for Windows Desktop VisualStudioVersion = 12.0.30110.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "openmsx", "openmsx.vcxproj", "{9563D96E-BBDC-410C-8BED-189102B0866F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Debug|x64 = Debug|x64 Developer|Win32 = Developer|Win32 Developer|x64 = Developer|x64 Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {9563D96E-BBDC-410C-8BED-189102B0866F}.Debug|Win32.ActiveCfg = Debug|Win32 {9563D96E-BBDC-410C-8BED-189102B0866F}.Debug|Win32.Build.0 = Debug|Win32 {9563D96E-BBDC-410C-8BED-189102B0866F}.Debug|x64.ActiveCfg = Debug|x64 {9563D96E-BBDC-410C-8BED-189102B0866F}.Debug|x64.Build.0 = Debug|x64 {9563D96E-BBDC-410C-8BED-189102B0866F}.Developer|Win32.ActiveCfg = Developer|Win32 {9563D96E-BBDC-410C-8BED-189102B0866F}.Developer|Win32.Build.0 = Developer|Win32 {9563D96E-BBDC-410C-8BED-189102B0866F}.Developer|x64.ActiveCfg = Developer|x64 {9563D96E-BBDC-410C-8BED-189102B0866F}.Developer|x64.Build.0 = Developer|x64 {9563D96E-BBDC-410C-8BED-189102B0866F}.Release|Win32.ActiveCfg = Release|Win32 {9563D96E-BBDC-410C-8BED-189102B0866F}.Release|Win32.Build.0 = Release|Win32 {9563D96E-BBDC-410C-8BED-189102B0866F}.Release|x64.ActiveCfg = Release|x64 {9563D96E-BBDC-410C-8BED-189102B0866F}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal openMSX-RELEASE_0_12_0/build/msvc/openmsx.vcxproj000066400000000000000000003142631257557151200216250ustar00rootroot00000000000000 Debug Win32 Debug x64 Developer Win32 Developer x64 Release Win32 Release x64 {9563D96E-BBDC-410C-8BED-189102B0866F} openmsx Win32Proj Application Unicode Application Unicode true Application Unicode Application Unicode Application Unicode true Application Unicode <_ProjectFileVersion>10.0.30319.1 $(OpenMSXOutDir)\ $(OpenMSXIntDir)\ $(OpenMSXOutDir)\ $(OpenMSXIntDir)\ $(OpenMSXOutDir)\ $(OpenMSXIntDir)\ $(OpenMSXOutDir)\ $(OpenMSXIntDir)\ $(OpenMSXOutDir)\ $(OpenMSXIntDir)\ $(OpenMSXOutDir)\ $(OpenMSXIntDir)\ /MP %(AdditionalOptions) Disabled $(OpenMSXConfigDir);$(DXSDK_DIR)\Include;$(ThirdPartySrcDir)\$(LibNameFreeType);$(ThirdPartySrcDir)\$(LibNameGlew)\include;$(ThirdPartySrcDir)\$(LibNameLibPng);$(ThirdPartySrcDir)\$(LibNameSDL)\include;$(ThirdPartySrcDir)\$(LibNameSDL_ttf);$(ThirdPartySrcDir)\$(LibNameTheora)\include;$(ThirdPartySrcDir)\$(LibNameVorbis)\include;$(ThirdPartySrcDir)\$(LibNameOgg)\include;$(ThirdPartySrcDir)\$(LibNameTcl)\generic;$(ThirdPartySrcDir)\$(LibNameZlib);$(OpenMSXSrcDir);$(OpenMSXSrcDir)\cassette;$(OpenMSXSrcDir)\commands;$(OpenMSXSrcDir)\config;$(OpenMSXSrcDir)\console;$(OpenMSXSrcDir)\cpu;$(OpenMSXSrcDir)\debugger;$(OpenMSXSrcDir)\events;$(OpenMSXSrcDir)\fdc;$(OpenMSXSrcDir)\file;$(OpenMSXSrcDir)\ide;$(OpenMSXSrcDir)\input;$(OpenMSXSrcDir)\laserdisc;$(OpenMSXSrcDir)\memory;$(OpenMSXSrcDir)\resource;$(OpenMSXSrcDir)\security;$(OpenMSXSrcDir)\serial;$(OpenMSXSrcDir)\settings;$(OpenMSXSrcDir)\sound;$(OpenMSXSrcDir)\thread;$(OpenMSXSrcDir)\utils;$(OpenMSXSrcDir)\video;$(OpenMSXSrcDir)\video\scalers;$(OpenMSXSrcDir)\video\ld;$(OpenMSXSrcDir)\video\v9990;%(AdditionalIncludeDirectories) __SSE2__;WIN32;WIN32_LEAN_AND_MEAN;UNICODE;_UNICODE;SECURITY_WIN32;DEBUG;_DEBUG;_CONSOLE;_USE_MATH_DEFINES;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;NOMINMAX;GLEW_STATIC;STATIC_BUILD;%(PreprocessorDefinitions) MultiThreadedDebug Level4 ProgramDatabase 4324;4063;4121;4125;4127;4189;4201;4244;4310;4355;4505;4512;4611;4702;%(DisableSpecificWarnings) $(OpenMSXConfigDir);$(ThirdPartySrcDir)\$(LibNameTcl)\generic;%(AdditionalIncludeDirectories) wsock32.lib;winmm.lib;secur32.lib;opengl32.lib;dxguid.lib;dsound.lib;SDL.lib;SDLmain.lib;SDL_ttf.lib;freetype.lib;glew.lib;libogg.lib;libpng.lib;libtheora.lib;libvorbis.lib;tcl.lib;zlib.lib;%(AdditionalDependencies) $(OutDir)openmsx.exe $(DXSDK_DIR)\Lib\$(PlatformShortName);$(ThirdPartyOutDir);%(AdditionalLibraryDirectories) %(IgnoreSpecificDefaultLibraries) shell32.dll;%(DelayLoadDLLs) true Console MachineX86 /SAFESEH:NO %(AdditionalOptions) X64 /MP %(AdditionalOptions) Disabled $(OpenMSXConfigDir);$(DXSDK_DIR)\Include;$(ThirdPartySrcDir)\$(LibNameFreeType);$(ThirdPartySrcDir)\$(LibNameGlew)\include;$(ThirdPartySrcDir)\$(LibNameLibPng);$(ThirdPartySrcDir)\$(LibNameSDL)\include;$(ThirdPartySrcDir)\$(LibNameSDL_ttf);$(ThirdPartySrcDir)\$(LibNameTheora)\include;$(ThirdPartySrcDir)\$(LibNameVorbis)\include;$(ThirdPartySrcDir)\$(LibNameOgg)\include;$(ThirdPartySrcDir)\$(LibNameTcl)\generic;$(ThirdPartySrcDir)\$(LibNameZlib);$(OpenMSXSrcDir);$(OpenMSXSrcDir)\cassette;$(OpenMSXSrcDir)\commands;$(OpenMSXSrcDir)\config;$(OpenMSXSrcDir)\console;$(OpenMSXSrcDir)\cpu;$(OpenMSXSrcDir)\debugger;$(OpenMSXSrcDir)\events;$(OpenMSXSrcDir)\fdc;$(OpenMSXSrcDir)\file;$(OpenMSXSrcDir)\ide;$(OpenMSXSrcDir)\input;$(OpenMSXSrcDir)\laserdisc;$(OpenMSXSrcDir)\memory;$(OpenMSXSrcDir)\resource;$(OpenMSXSrcDir)\security;$(OpenMSXSrcDir)\serial;$(OpenMSXSrcDir)\settings;$(OpenMSXSrcDir)\sound;$(OpenMSXSrcDir)\thread;$(OpenMSXSrcDir)\utils;$(OpenMSXSrcDir)\video;$(OpenMSXSrcDir)\video\scalers;$(OpenMSXSrcDir)\video\ld;$(OpenMSXSrcDir)\video\v9990;%(AdditionalIncludeDirectories) __SSE2__;WIN32;_WIN64;__x86_64;UNICODE;_UNICODE;WIN32_LEAN_AND_MEAN;SECURITY_WIN32;DEBUG;_DEBUG;_CONSOLE;_USE_MATH_DEFINES;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;NOMINMAX;GLEW_STATIC;STATIC_BUILD;%(PreprocessorDefinitions) MultiThreadedDebug Level4 ProgramDatabase 4324;4063;4121;4125;4127;4189;4201;4244;4310;4355;4505;4512;4611;4702;%(DisableSpecificWarnings) $(OpenMSXConfigDir);$(ThirdPartySrcDir)\$(LibNameTcl)\generic;%(AdditionalIncludeDirectories) wsock32.lib;winmm.lib;secur32.lib;opengl32.lib;dxguid.lib;dsound.lib;SDL.lib;SDLmain.lib;SDL_ttf.lib;freetype.lib;glew.lib;libogg.lib;libpng.lib;libtheora.lib;libvorbis.lib;tcl.lib;zlib.lib;%(AdditionalDependencies) $(OutDir)openmsx.exe $(DXSDK_DIR)\Lib\$(Platform);$(ThirdPartyOutDir);%(AdditionalLibraryDirectories) shell32.dll;%(DelayLoadDLLs) true Console MachineX64 /MP %(AdditionalOptions) Full AnySuitable true Size true true true $(OpenMSXConfigDir);$(DXSDK_DIR)\Include;$(ThirdPartySrcDir)\$(LibNameFreeType);$(ThirdPartySrcDir)\$(LibNameGlew)\include;$(ThirdPartySrcDir)\$(LibNameLibPng);$(ThirdPartySrcDir)\$(LibNameSDL)\include;$(ThirdPartySrcDir)\$(LibNameSDL_ttf);$(ThirdPartySrcDir)\$(LibNameTheora)\include;$(ThirdPartySrcDir)\$(LibNameVorbis)\include;$(ThirdPartySrcDir)\$(LibNameOgg)\include;$(ThirdPartySrcDir)\$(LibNameTcl)\generic;$(ThirdPartySrcDir)\$(LibNameZlib);$(OpenMSXSrcDir);$(OpenMSXSrcDir)\cassette;$(OpenMSXSrcDir)\commands;$(OpenMSXSrcDir)\config;$(OpenMSXSrcDir)\console;$(OpenMSXSrcDir)\cpu;$(OpenMSXSrcDir)\debugger;$(OpenMSXSrcDir)\events;$(OpenMSXSrcDir)\fdc;$(OpenMSXSrcDir)\file;$(OpenMSXSrcDir)\ide;$(OpenMSXSrcDir)\input;$(OpenMSXSrcDir)\laserdisc;$(OpenMSXSrcDir)\memory;$(OpenMSXSrcDir)\resource;$(OpenMSXSrcDir)\security;$(OpenMSXSrcDir)\serial;$(OpenMSXSrcDir)\settings;$(OpenMSXSrcDir)\sound;$(OpenMSXSrcDir)\thread;$(OpenMSXSrcDir)\utils;$(OpenMSXSrcDir)\video;$(OpenMSXSrcDir)\video\scalers;$(OpenMSXSrcDir)\video\ld;$(OpenMSXSrcDir)\video\v9990;%(AdditionalIncludeDirectories) __SSE2__;WIN32;WIN32_LEAN_AND_MEAN;UNICODE;_UNICODE;SECURITY_WIN32;NDEBUG;_CONSOLE;_USE_MATH_DEFINES;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;NOMINMAX;GLEW_STATIC;STATIC_BUILD;%(PreprocessorDefinitions) true false MultiThreaded true Level4 ProgramDatabase 4324;4063;4121;4125;4127;4189;4201;4244;4310;4355;4505;4512;4611;4702;%(DisableSpecificWarnings) $(OpenMSXConfigDir);$(ThirdPartySrcDir)\$(LibNameTcl)\generic;%(AdditionalIncludeDirectories) wsock32.lib;winmm.lib;secur32.lib;opengl32.lib;dxguid.lib;dsound.lib;SDL.lib;SDLmain.lib;SDL_ttf.lib;freetype.lib;glew.lib;libogg.lib;libpng.lib;libtheora.lib;libvorbis.lib;tcl.lib;zlib.lib;%(AdditionalDependencies) $(OutDir)openmsx.exe $(DXSDK_DIR)\Lib\$(PlatformShortName);$(ThirdPartyOutDir);%(AdditionalLibraryDirectories) shell32.dll;%(DelayLoadDLLs) true Console true true MachineX86 /SAFESEH:NO %(AdditionalOptions) X64 /MP %(AdditionalOptions) Full AnySuitable true Size true true $(OpenMSXConfigDir);$(DXSDK_DIR)\Include;$(ThirdPartySrcDir)\$(LibNameFreeType);$(ThirdPartySrcDir)\$(LibNameGlew)\include;$(ThirdPartySrcDir)\$(LibNameLibPng);$(ThirdPartySrcDir)\$(LibNameSDL)\include;$(ThirdPartySrcDir)\$(LibNameSDL_ttf);$(ThirdPartySrcDir)\$(LibNameTheora)\include;$(ThirdPartySrcDir)\$(LibNameVorbis)\include;$(ThirdPartySrcDir)\$(LibNameOgg)\include;$(ThirdPartySrcDir)\$(LibNameTcl)\generic;$(ThirdPartySrcDir)\$(LibNameZlib);$(OpenMSXSrcDir);$(OpenMSXSrcDir)\cassette;$(OpenMSXSrcDir)\commands;$(OpenMSXSrcDir)\config;$(OpenMSXSrcDir)\console;$(OpenMSXSrcDir)\cpu;$(OpenMSXSrcDir)\debugger;$(OpenMSXSrcDir)\events;$(OpenMSXSrcDir)\fdc;$(OpenMSXSrcDir)\file;$(OpenMSXSrcDir)\ide;$(OpenMSXSrcDir)\input;$(OpenMSXSrcDir)\laserdisc;$(OpenMSXSrcDir)\memory;$(OpenMSXSrcDir)\resource;$(OpenMSXSrcDir)\security;$(OpenMSXSrcDir)\serial;$(OpenMSXSrcDir)\settings;$(OpenMSXSrcDir)\sound;$(OpenMSXSrcDir)\thread;$(OpenMSXSrcDir)\utils;$(OpenMSXSrcDir)\video;$(OpenMSXSrcDir)\video\scalers;$(OpenMSXSrcDir)\video\ld;$(OpenMSXSrcDir)\video\v9990;%(AdditionalIncludeDirectories) __SSE2__;WIN32;_WIN64;__x86_64;WIN32_LEAN_AND_MEAN;UNICODE;_UNICODE;SECURITY_WIN32;NDEBUG;_CONSOLE;_USE_MATH_DEFINES;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;NOMINMAX;GLEW_STATIC;STATIC_BUILD;%(PreprocessorDefinitions) true MultiThreaded true Level4 ProgramDatabase 4324;4063;4121;4125;4127;4189;4201;4244;4310;4355;4505;4512;4611;4702;%(DisableSpecificWarnings) $(OpenMSXConfigDir);$(ThirdPartySrcDir)\$(LibNameTcl)\generic;%(AdditionalIncludeDirectories) wsock32.lib;winmm.lib;secur32.lib;opengl32.lib;dxguid.lib;dsound.lib;SDL.lib;SDLmain.lib;SDL_ttf.lib;freetype.lib;glew.lib;libogg.lib;libpng.lib;libtheora.lib;libvorbis.lib;tcl.lib;zlib.lib;%(AdditionalDependencies) $(OutDir)openmsx.exe $(DXSDK_DIR)\Lib\$(Platform);$(ThirdPartyOutDir);%(AdditionalLibraryDirectories) shell32.dll;%(DelayLoadDLLs) true Console true true MachineX64 /MP %(AdditionalOptions) Disabled $(OpenMSXConfigDir);$(DXSDK_DIR)\Include;$(ThirdPartySrcDir)\$(LibNameFreeType);$(ThirdPartySrcDir)\$(LibNameGlew)\include;$(ThirdPartySrcDir)\$(LibNameLibPng);$(ThirdPartySrcDir)\$(LibNameSDL)\include;$(ThirdPartySrcDir)\$(LibNameSDL_ttf);$(ThirdPartySrcDir)\$(LibNameTheora)\include;$(ThirdPartySrcDir)\$(LibNameVorbis)\include;$(ThirdPartySrcDir)\$(LibNameOgg)\include;$(ThirdPartySrcDir)\$(LibNameTcl)\generic;$(ThirdPartySrcDir)\$(LibNameZlib);$(OpenMSXSrcDir);$(OpenMSXSrcDir)\cassette;$(OpenMSXSrcDir)\commands;$(OpenMSXSrcDir)\config;$(OpenMSXSrcDir)\console;$(OpenMSXSrcDir)\cpu;$(OpenMSXSrcDir)\debugger;$(OpenMSXSrcDir)\events;$(OpenMSXSrcDir)\fdc;$(OpenMSXSrcDir)\file;$(OpenMSXSrcDir)\ide;$(OpenMSXSrcDir)\input;$(OpenMSXSrcDir)\laserdisc;$(OpenMSXSrcDir)\memory;$(OpenMSXSrcDir)\resource;$(OpenMSXSrcDir)\security;$(OpenMSXSrcDir)\serial;$(OpenMSXSrcDir)\settings;$(OpenMSXSrcDir)\sound;$(OpenMSXSrcDir)\thread;$(OpenMSXSrcDir)\utils;$(OpenMSXSrcDir)\video;$(OpenMSXSrcDir)\video\scalers;$(OpenMSXSrcDir)\video\ld;$(OpenMSXSrcDir)\video\v9990;%(AdditionalIncludeDirectories) __SSE2__;WIN32;WIN32_LEAN_AND_MEAN;UNICODE;_UNICODE;SECURITY_WIN32;_DEBUG;_CONSOLE;_USE_MATH_DEFINES;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;NOMINMAX;GLEW_STATIC;STATIC_BUILD;%(PreprocessorDefinitions) true MultiThreadedDebug Level4 ProgramDatabase 4324;4063;4121;4125;4127;4189;4201;4244;4310;4355;4505;4512;4611;4702;%(DisableSpecificWarnings) $(OpenMSXConfigDir);$(ThirdPartySrcDir)\$(LibNameTcl)\generic;%(AdditionalIncludeDirectories) wsock32.lib;winmm.lib;secur32.lib;opengl32.lib;dxguid.lib;dsound.lib;SDL.lib;SDLmain.lib;SDL_ttf.lib;freetype.lib;glew.lib;libogg.lib;libpng.lib;libtheora.lib;libvorbis.lib;tcl.lib;zlib.lib;%(AdditionalDependencies) $(OutDir)openmsx.exe $(DXSDK_DIR)\Lib\$(PlatformShortName);$(ThirdPartyOutDir);%(AdditionalLibraryDirectories) %(IgnoreSpecificDefaultLibraries) shell32.dll;%(DelayLoadDLLs) true Console MachineX86 /SAFESEH:NO %(AdditionalOptions) X64 /MP %(AdditionalOptions) Disabled $(OpenMSXConfigDir);$(DXSDK_DIR)\Include;$(ThirdPartySrcDir)\$(LibNameFreeType);$(ThirdPartySrcDir)\$(LibNameGlew)\include;$(ThirdPartySrcDir)\$(LibNameLibPng);$(ThirdPartySrcDir)\$(LibNameSDL)\include;$(ThirdPartySrcDir)\$(LibNameSDL_ttf);$(ThirdPartySrcDir)\$(LibNameTheora)\include;$(ThirdPartySrcDir)\$(LibNameVorbis)\include;$(ThirdPartySrcDir)\$(LibNameOgg)\include;$(ThirdPartySrcDir)\$(LibNameTcl)\generic;$(ThirdPartySrcDir)\$(LibNameZlib);$(OpenMSXSrcDir);$(OpenMSXSrcDir)\cassette;$(OpenMSXSrcDir)\commands;$(OpenMSXSrcDir)\config;$(OpenMSXSrcDir)\console;$(OpenMSXSrcDir)\cpu;$(OpenMSXSrcDir)\debugger;$(OpenMSXSrcDir)\events;$(OpenMSXSrcDir)\fdc;$(OpenMSXSrcDir)\file;$(OpenMSXSrcDir)\ide;$(OpenMSXSrcDir)\input;$(OpenMSXSrcDir)\laserdisc;$(OpenMSXSrcDir)\memory;$(OpenMSXSrcDir)\resource;$(OpenMSXSrcDir)\security;$(OpenMSXSrcDir)\serial;$(OpenMSXSrcDir)\settings;$(OpenMSXSrcDir)\sound;$(OpenMSXSrcDir)\thread;$(OpenMSXSrcDir)\utils;$(OpenMSXSrcDir)\video;$(OpenMSXSrcDir)\video\scalers;$(OpenMSXSrcDir)\video\ld;$(OpenMSXSrcDir)\video\v9990;%(AdditionalIncludeDirectories) __SSE2__;WIN32;_WIN64;__x86_64;UNICODE;_UNICODE;WIN32_LEAN_AND_MEAN;SECURITY_WIN32;_DEBUG;_CONSOLE;_USE_MATH_DEFINES;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;NOMINMAX;GLEW_STATIC;STATIC_BUILD;%(PreprocessorDefinitions) true MultiThreadedDebug Level4 ProgramDatabase 4324;4063;4121;4125;4127;4189;4201;4244;4310;4355;4505;4512;4611;4702;%(DisableSpecificWarnings) $(OpenMSXConfigDir);$(ThirdPartySrcDir)\$(LibNameTcl)\generic;%(AdditionalIncludeDirectories) wsock32.lib;winmm.lib;secur32.lib;opengl32.lib;dxguid.lib;dsound.lib;SDL.lib;SDLmain.lib;SDL_ttf.lib;freetype.lib;glew.lib;libogg.lib;libpng.lib;libtheora.lib;libvorbis.lib;tcl.lib;zlib.lib;%(AdditionalDependencies) $(OutDir)openmsx.exe $(DXSDK_DIR)\Lib\$(Platform);$(ThirdPartyOutDir);%(AdditionalLibraryDirectories) shell32.dll;%(DelayLoadDLLs) true Console MachineX64 Document Document Document Document Document Document Document Document Document Document Generating config headers... for /f "delims=" %%d in ("$(OpenMSXConfigDir)") do set CONFIG_DIR=%%~fd rem echo CONFIG_DIR=%CONFIG_DIR% for /f "delims=" %%d in ("$(OpenMSXRootDir)") do set ROOT_DIR=%%~fd rem echo ROOT_DIR=%ROOT_DIR% set BUILD_DIR=%ROOT_DIR%\build rem echo BUILD_DIR=%BUILD_DIR% set MSVC_DIR=%BUILD_DIR%\msvc rem echo MSVC_DIR=%MSVC_DIR% set PYTHONPATH=%PYTHONPATH%;%BUILD_DIR% rem echo PYTHONPATH=%PYTHONPATH% pushd %ROOT_DIR% python "%MSVC_DIR%\genconfig.py" $(Platform) "$(Configuration)" "%CONFIG_DIR%" popd build-info.hh;components.hh;probed_defs.hh;resource-info.h;Version.ii;%(Outputs) Generating config headers... for /f "delims=" %%d in ("$(OpenMSXConfigDir)") do set CONFIG_DIR=%%~fd rem echo CONFIG_DIR=%CONFIG_DIR% for /f "delims=" %%d in ("$(OpenMSXRootDir)") do set ROOT_DIR=%%~fd rem echo ROOT_DIR=%ROOT_DIR% set BUILD_DIR=%ROOT_DIR%\build rem echo BUILD_DIR=%BUILD_DIR% set MSVC_DIR=%BUILD_DIR%\msvc rem echo MSVC_DIR=%MSVC_DIR% set PYTHONPATH=%PYTHONPATH%;%BUILD_DIR% rem echo PYTHONPATH=%PYTHONPATH% pushd %ROOT_DIR% python "%MSVC_DIR%\genconfig.py" $(Platform) "$(Configuration)" "%CONFIG_DIR%" popd build-info.hh;components.hh;probed_defs.hh;resource-info.h;Version.ii;%(Outputs) Generating config headers... for /f "delims=" %%d in ("$(OpenMSXConfigDir)") do set CONFIG_DIR=%%~fd rem echo CONFIG_DIR=%CONFIG_DIR% for /f "delims=" %%d in ("$(OpenMSXRootDir)") do set ROOT_DIR=%%~fd rem echo ROOT_DIR=%ROOT_DIR% set BUILD_DIR=%ROOT_DIR%\build rem echo BUILD_DIR=%BUILD_DIR% set MSVC_DIR=%BUILD_DIR%\msvc rem echo MSVC_DIR=%MSVC_DIR% set PYTHONPATH=%PYTHONPATH%;%BUILD_DIR% rem echo PYTHONPATH=%PYTHONPATH% pushd %ROOT_DIR% python "%MSVC_DIR%\genconfig.py" $(Platform) "$(Configuration)" "%CONFIG_DIR%" popd build-info.hh;components.hh;probed_defs.hh;resource-info.h;Version.ii;%(Outputs) Generating config headers... for /f "delims=" %%d in ("$(OpenMSXConfigDir)") do set CONFIG_DIR=%%~fd rem echo CONFIG_DIR=%CONFIG_DIR% for /f "delims=" %%d in ("$(OpenMSXRootDir)") do set ROOT_DIR=%%~fd rem echo ROOT_DIR=%ROOT_DIR% set BUILD_DIR=%ROOT_DIR%\build rem echo BUILD_DIR=%BUILD_DIR% set MSVC_DIR=%BUILD_DIR%\msvc rem echo MSVC_DIR=%MSVC_DIR% set PYTHONPATH=%PYTHONPATH%;%BUILD_DIR% rem echo PYTHONPATH=%PYTHONPATH% pushd %ROOT_DIR% python "%MSVC_DIR%\genconfig.py" $(Platform) "$(Configuration)" "%CONFIG_DIR%" popd build-info.hh;components.hh;probed_defs.hh;resource-info.h;Version.ii;%(Outputs) Generating config headers... for /f "delims=" %%d in ("$(OpenMSXConfigDir)") do set CONFIG_DIR=%%~fd rem echo CONFIG_DIR=%CONFIG_DIR% for /f "delims=" %%d in ("$(OpenMSXRootDir)") do set ROOT_DIR=%%~fd rem echo ROOT_DIR=%ROOT_DIR% set BUILD_DIR=%ROOT_DIR%\build rem echo BUILD_DIR=%BUILD_DIR% set MSVC_DIR=%BUILD_DIR%\msvc rem echo MSVC_DIR=%MSVC_DIR% set PYTHONPATH=%PYTHONPATH%;%BUILD_DIR% rem echo PYTHONPATH=%PYTHONPATH% pushd %ROOT_DIR% python "%MSVC_DIR%\genconfig.py" $(Platform) "$(Configuration)" "%CONFIG_DIR%" popd build-info.hh;components.hh;probed_defs.hh;resource-info.h;Version.ii;%(Outputs) Generating config headers... for /f "delims=" %%d in ("$(OpenMSXConfigDir)") do set CONFIG_DIR=%%~fd rem echo CONFIG_DIR=%CONFIG_DIR% for /f "delims=" %%d in ("$(OpenMSXRootDir)") do set ROOT_DIR=%%~fd rem echo ROOT_DIR=%ROOT_DIR% set BUILD_DIR=%ROOT_DIR%\build rem echo BUILD_DIR=%BUILD_DIR% set MSVC_DIR=%BUILD_DIR%\msvc rem echo MSVC_DIR=%MSVC_DIR% set PYTHONPATH=%PYTHONPATH%;%BUILD_DIR% rem echo PYTHONPATH=%PYTHONPATH% pushd %ROOT_DIR% python "%MSVC_DIR%\genconfig.py" $(Platform) "$(Configuration)" "%CONFIG_DIR%" popd build-info.hh;components.hh;probed_defs.hh;resource-info.h;Version.ii;%(Outputs) openMSX-RELEASE_0_12_0/build/msvc/openmsx.vcxproj.filters000066400000000000000000003317511257557151200232750ustar00rootroot00000000000000 {27eaf1e2-f5da-477a-99a5-7be15006d6a0} {878501bc-d7f0-49ec-bc35-d2e22835aaf6} {a5140952-ef4a-4d86-b93f-8562cf838f1f} {2bd3c002-6e7b-444b-a02f-75e6e57aa28e} {99b1a5c3-9248-4127-bb7a-bffc6052fa1d} {2f1531a3-2bff-4bb2-b737-8b5bc263a5d0} {4b659948-74af-4e70-9469-71cf9e8ff90e} {0d4a99d7-3f0c-4bd7-94c9-73f2ad724887} {a36193d1-ca1d-4188-aefc-60d10a41573b} {db111771-419a-4e9f-a046-87a88f567ce5} {39b914e9-534c-42eb-8b1c-7078665cd464} {e010ea5f-02d9-420a-af08-577f6fac9b71} {4790c7a8-2904-4fda-9248-fc5357d9751f} {6bf182df-f9bf-43c3-9d2a-c0984b19716c} {234f2a7b-0398-45c2-9149-a918a0d564bd} {c12b54ed-4653-4ee0-b86a-e5c90a8b9b8d} {5db6c172-d415-41b3-b89d-876e94c600d7} {f34953a9-8bb9-4fe7-9d92-38dd046b936a} {dc7744b4-c613-43ff-8e2b-d89fb8b90f2b} {441fef5c-d4c3-4cb1-8bd9-27253f5a929a} {8fe6dae8-765e-428c-abf4-662e83a99385} {ce8126f9-f849-4f95-85bf-f6919a3a4e9a} {3b3e10a3-7923-46c0-b78f-46acd840d7c2} cassette cassette cassette cassette cassette cassette cassette cassette commands commands commands commands commands commands commands commands commands commands config config config config console console console console console console console console console console cpu cpu cpu cpu cpu cpu cpu cpu cpu cpu cpu cpu cpu cpu cpu cpu debugger debugger debugger debugger debugger events events events events events events events events events events events events events events fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc file file file file file file file file file file file file file file ide ide ide ide ide ide ide ide ide ide ide ide ide ide ide ide ide ide input input input input input input input input input input input input input input input input input input input memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory settings settings settings settings settings settings settings settings settings settings settings sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound thread thread utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video\v9990 video\v9990 video\v9990 video\v9990 video\v9990 video\v9990 video\v9990 video\v9990 video\v9990 video\v9990 video\v9990 video\ld video\ld video\ld video\ld serial serial serial serial serial serial serial serial serial serial serial serial serial serial serial serial serial serial serial serial serial serial serial security security security laserdisc laserdisc laserdisc laserdisc laserdisc memory cassette cassette cassette cassette cassette cassette cassette cassette commands commands commands commands commands commands commands commands commands commands commands commands config config config config config config console console console console console console console console console console cpu cpu cpu cpu cpu cpu cpu cpu cpu cpu cpu cpu cpu cpu cpu cpu cpu cpu debugger debugger debugger debugger debugger debugger events events events events events events events events events events events events events events events events fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc fdc file file file file file file file file file file file file file file file ide ide ide ide ide ide ide ide ide ide ide ide ide ide ide ide ide ide ide ide ide input input input input input input input input input input input input input input input input input input input input input input memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory memory resource settings settings settings settings settings settings settings settings settings settings settings settings settings sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound sound thread thread utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils utils video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video video\v9990 video\v9990 video\v9990 video\v9990 video\v9990 video\v9990 video\v9990 video\v9990 video\v9990 video\v9990 video\v9990 video\v9990 video\v9990 serial serial serial serial serial serial serial serial serial serial serial serial serial serial serial serial serial serial serial serial serial serial serial serial security security security memory resource sound video\ld video\ld video\ld video\ld video\ld laserdisc laserdisc laserdisc laserdisc laserdisc openMSX-RELEASE_0_12_0/build/msvc/probed_defs.mk000066400000000000000000000011031257557151200213060ustar00rootroot00000000000000# Hardcoded probe results for Visual C++ build. # Non-empty value means found, empty means not found. HAVE_GL_H:=true HAVE_GL_LIB:=true HAVE_GLEW_H:=true HAVE_GLEW_LIB:=true HAVE_LASERDISC_H:=true HAVE_LASERDISC_LIB:=true HAVE_OGG_H:=true HAVE_OGG_LIB=true HAVE_PNG_H:=true HAVE_PNG_LIB:=true HAVE_SDL_H:=true HAVE_SDL_LIB:=true HAVE_SDL_IMAGE_H:=true HAVE_SDL_IMAGE_LIB:=true HAVE_SDL_TTF_H:=true HAVE_SDL_TTF_LIB:=true HAVE_TCL_H:=true HAVE_TCL_LIB:=true HAVE_THEORA_H:=true HAVE_THEORA_LIB:=true HAVE_VORBIS_H:=true HAVE_VORBIS_LIB:=true HAVE_ZLIB_H:=true HAVE_ZLIB_LIB:=true openMSX-RELEASE_0_12_0/build/msysutils.py000066400000000000000000000043261257557151200201710ustar00rootroot00000000000000from os import environ from os.path import isfile from subprocess import PIPE, Popen import sys def _determineMounts(): # The MSYS shell provides a Unix-like file system by translating paths on # the command line to Windows paths. Usually this is transparent, but not # for us since we call GCC without going through the shell. # Figure out the root directory of MSYS. proc = Popen( [ msysShell(), '-c', '"%s" -c \'import sys ; print sys.argv[1]\' /' % sys.executable.replace('\\', '\\\\') ], stdin = None, stdout = PIPE, stderr = PIPE, ) stdoutdata, stderrdata = proc.communicate() if stderrdata or proc.returncode: if stderrdata: print >> sys.stderr, 'Error determining MSYS root:', stderrdata if proc.returncode: print >> sys.stderr, 'Exit code %d' % proc.returncode raise IOError('Error determining MSYS root') msysRoot = stdoutdata.strip() # Figure out all mount points of MSYS. mounts = {} fstab = msysRoot + '/etc/fstab' if isfile(fstab): try: inp = open(fstab, 'r') try: for line in inp: line = line.strip() if line and not line.startswith('#'): nativePath, mountPoint = ( path.rstrip('/') + '/' for path in line.split()[:2] ) if nativePath != 'none': mounts[mountPoint] = nativePath finally: inp.close() except IOError, ex: print >> sys.stderr, 'Failed to read MSYS fstab:', ex except ValueError, ex: print >> sys.stderr, 'Failed to parse MSYS fstab:', ex mounts['/'] = msysRoot + '/' return mounts def msysPathToNative(path): if path.startswith('/'): if len(path) == 2 or (len(path) > 2 and path[2] == '/'): # Support drive letters as top-level dirs. return '%s:/%s' % (path[1], path[3 : ]) longestMatch = '' for mountPoint in msysMounts.iterkeys(): if path.startswith(mountPoint): if len(mountPoint) > len(longestMatch): longestMatch = mountPoint return msysMounts[longestMatch] + path[len(longestMatch) : ] else: return path def msysActive(): return environ.get('OSTYPE') == 'msys' or 'MSYSCON' in environ def msysShell(): return environ.get('SHELL') or 'sh.exe' if msysActive(): msysMounts = _determineMounts() else: msysMounts = None if __name__ == '__main__': print 'MSYS mounts:', msysMounts openMSX-RELEASE_0_12_0/build/outpututils.py000066400000000000000000000020331257557151200205270ustar00rootroot00000000000000# Various utility functions for generating output files and directories. from os import makedirs from os.path import dirname, isdir, isfile def createDirFor(filePath): '''Creates an output directory for containing the given file path. Nothing happens if the directory already exsits. ''' dirPath = dirname(filePath) if dirPath and not isdir(dirPath): makedirs(dirPath) def rewriteIfChanged(path, lines): '''Writes the file with the given path if it does not exist yet or if its contents should change. The contents are given by the "lines" sequence. Returns True if the file was (re)written, False otherwise. ''' newLines = [ line + '\n' for line in lines ] if isfile(path): inp = open(path, 'r') try: oldLines = inp.readlines() finally: inp.close() if newLines == oldLines: print 'Up to date: %s' % path return False else: print 'Updating %s...' % path else: print 'Creating %s...' % path createDirFor(path) out = open(path, 'w') try: out.writelines(newLines) finally: out.close() return True openMSX-RELEASE_0_12_0/build/package-arch/000077500000000000000000000000001257557151200200445ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/build/package-arch/PKGBUILD000066400000000000000000000011421257557151200211660ustar00rootroot00000000000000# PKGBUILD for openMSX # Contributor: Theo Smit pkgname=openmsx pkgver=0.6.2 pkgrel=1 pkgdesc="openMSX, a great opensource MSX emulator with lots of great features" arch=('i686') url="openmsx.org" license=('GPL') depends=(gcc sdl_image tcl) makedepends=() provides=() conflicts=() replaces=() backup=() install= source=(http://downloads.sourceforge.net/$pkgname/$pkgname-$pkgver.tar.gz) noextract=() md5sums=('282acf2ea7bf67e15a7b8d961c9556a5') build() { cd $startdir/src/$pkgname-$pkgver ./configure make || return 1 make OPENMSX_INSTALL=$startdir/pkg/opt/openMSX install } openMSX-RELEASE_0_12_0/build/package-arch/README000066400000000000000000000021501257557151200207220ustar00rootroot00000000000000PKGBUILD file for building an openMSX package for archlinux/i686, contributed by Theo Smit . This PKGBUILD was only tested on i686 (32-bit), if you want to build for an x86_64 machine you can try to modify the PKGBUILD accordingly. If you're lucky you only have to modify the "arch=" line. Usage: - Create a temporary working directory and go there: $ mkdir /tmp/build-openmsx $ cd /tmp/build-openmsx - Copy the PKGBUILD file there: $ cp path/to/PKGBUILD . - Run the "makepkg" tool: $ makepkg This will download the sources for openMSX 0.6.2, verify the dependencies, compile openMSX, install it in the temporary directory and archive that installation into a binary openMSX package. - Install the binary package with the standard Arch package manager: $ pacman -A openmsx-0.6.2-1-i686.pkg.tar.gz - Now you can start openMSX: $ /opt/openMSX/bin/openmsx Please read the documentation in /opt/openMSX/doc/manual to learn more about openMSX, for example how to install additional system ROMs. ------------------------------------------------------------------------------ openMSX-RELEASE_0_12_0/build/package-darwin/000077500000000000000000000000001257557151200204135ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/build/package-darwin/Info.plist000066400000000000000000000036771257557151200224000ustar00rootroot00000000000000 CFAppleHelpAnchor index CFBundleDocumentTypes CFBundleTypeExtensions rom CFBundleTypeName ROM image CFBundleTypeRole Shell LSTypeIsPackage CFBundleTypeExtensions dsk CFBundleTypeName Disk image CFBundleTypeRole Shell LSTypeIsPackage CFBundleTypeExtensions cas CFBundleTypeName Cassette image CFBundleTypeRole Shell LSTypeIsPackage CFBundleExecutable openmsx CFBundleGetInfoHTML openMSX %VERSION% CFBundleIconFile %ICON% CFBundleIdentifier org.openmsx.openmsx CFBundleName openMSX CFBundlePackageType APPL CFBundleShortVersionString %VERSION% CFBundleSignature oMSX CFBundleVersion %VERSION% LSMinimumSystemVersionByArchitecture x86_64 10.6.0 i386 10.4.0 ppc 10.4.0 LSRequiresCarbon NSPrefPaneIconFile %ICON% NSPrefPaneIconLabel openMSX openMSX-RELEASE_0_12_0/build/package-darwin/README.html000066400000000000000000000053311257557151200222400ustar00rootroot00000000000000 openMSX README

openMSX README

Documentation

In the Documentation directory you can find the documentation for openMSX. Some sections you might want to read first:

Key Mapping
Lists which keys you need to press for special MSX key and for emulator functions.
System ROMs
Describes how openMSX deals with system ROMs. This binary distribution has C-BIOS inside of it. Should you decide to install additional system ROMs, I suggest you put them in ~/.openMSX/share/systemroms. That way, if you upgrade openMSX later, they will automatically be found by the new version.
Release History
Lists the changes made in each release of openMSX. Read this to learn about new features or changed behaviour.
Authors and License
Tells you about the people who made openMSX and the conditions for distributing it.

Running MSX Software

ROM files (ROM images)
Open the OSD (On-Screen Display) menu using Cmd+O, then choose "Load ROM...".
DSK files (disk images)
Open the OSD menu using Cmd+O, then choose "Insert Disk...". Make sure you select a machine that supports disks; C-BIOS does not yet have disk support. If you installed system ROMs (see above), you can change the MSX model that is emulated in the OSD menu under "Hardware... > Change Machine...".
CAS files (cassette images)
Open the OSD menu using Cmd+O, then choose "Set Tape...". As for disks, you will need to select a machine that supports cassettes; C-BIOS does not yet support cassettes.

Double clicking a file in the Finder to open it in openMSX unfortunately doesn't always work; this might be fixed in future openMSX versions.

To shorten the loading times of disks and cassettes, you can type set fullspeedwhenloading on on the openMSX console, which can be opened with Cmd+L.

openMSX on the Web

Home Page
Contains everything about openMSX.
Project Page on SourceForge
You can download new versions here, or report bugs.
Forum
Discuss openMSX with other users.
openMSX-RELEASE_0_12_0/build/package-darwin/app.mk000066400000000000000000000034421257557151200215270ustar00rootroot00000000000000# Create an application directory for Darwin. APP_SUPPORT_DIR:=build/package-darwin APP_DIR:=openMSX.app APP_EXE_DIR:=$(APP_DIR)/Contents/MacOS APP_PLIST:=$(APP_DIR)/Contents/Info.plist APP_RES:=$(APP_DIR)/Contents/Resources APP_ICON:=$(APP_RES)/openmsx-logo.icns # Override install locations. DESTDIR:=$(BINDIST_DIR) INSTALL_BINARY_DIR:=$(APP_EXE_DIR) INSTALL_SHARE_DIR:=$(APP_DIR)/share INSTALL_DOC_DIR:=Documentation PACKAGE_FULL:=$(shell PYTHONPATH=build $(PYTHON) -c \ "import version; print version.getVersionedPackageName()" \ ) BINDIST_PACKAGE:=$(BUILD_PATH)/$(PACKAGE_FULL)-mac-$(OPENMSX_TARGET_CPU)-bin.dmg BINDIST_README:=README.html BINDIST_LICENSE:=$(INSTALL_DOC_DIR)/GPL.txt # TODO: What is needed for an app folder? app: install $(DESTDIR)/$(APP_PLIST) $(DESTDIR)/$(APP_ICON) bindist: app $(DESTDIR)/$(BINDIST_README) $(DESTDIR)/$(BINDIST_LICENSE) @echo "Creating disk image:" @hdiutil create -srcfolder $(BINDIST_DIR) \ -volname openMSX \ -imagekey zlib-level=9 \ -ov $(BINDIST_PACKAGE) @hdiutil internet-enable -yes $(BINDIST_PACKAGE) $(DESTDIR)/$(APP_PLIST): $(DESTDIR)/$(APP_DIR)/Contents/%: $(APP_SUPPORT_DIR)/% bindistclean @echo " Writing meta-info..." @mkdir -p $(@D) @sed -e 's/%ICON%/$(notdir $(APP_ICON))/' \ -e 's/%VERSION%/$(PACKAGE_DETAILED_VERSION)/' < $< > $@ @echo "APPLoMSX" > $(@D)/PkgInfo $(DESTDIR)/$(APP_ICON): $(DESTDIR)/$(APP_RES)/%: $(APP_SUPPORT_DIR)/% bindistclean @echo " Copying resources..." @mkdir -p $(@D) @cp $< $@ $(DESTDIR)/$(BINDIST_README): $(APP_SUPPORT_DIR)/README.html @echo " Copying README..." @mkdir -p $(@D) @cp $< $@ $(DESTDIR)/$(BINDIST_LICENSE): doc/GPL.txt app @echo " Copying license..." @mkdir -p $(@D) # Remove form feeds from the GPL document, so Safari will treat it as text. @awk '!/\f/ ; /\f/ { print "" }' $< > $@ openMSX-RELEASE_0_12_0/build/package-darwin/openmsx-logo.icns000066400000000000000000001733421257557151200237320ustar00rootroot00000000000000icnsTOC @is32s8mkil32ql8mkit32)%t8mk@ic08is32U/Za/*jX2F82Td дn=ܹ ә q̲ͿuWU/Za.'_fR 2VF82T] n4Ǽ¾ h m̲yeKZa:moUL֡SeWL1BE7|.BUrR< NVzM=}gXPF; p^SH>= ĮU -VӸgs8mkc{_rIE HX0&2D8CXJ#il32q8Aʘ8[Z]lʭ='sV6@u) ˜*116{j,g(!I9P+- QP# 6"ʶȋrʢ*׀ӵ܇>dVO͢9րޞ ʣؙ >EgۚyO.8Aʘ8ZZ]l§:%ӬlQ3< ?y V*116{j,g(!I9P(-Ÿ QP# 6ԶƋhг &̸܇> ȨdV Ơ E÷v)¬Ѐs Ȅp >z2KqupX :!zx uՃqo nl GHkZki {J gf iY0'?A:dc U 3a_ urJ7d][[ [nqn=sWSSf^\AcepnePIzoUjdX0ix^ne`\Gdme wgb\WN)7W yimic^XSM@ {upje_ZTOI/c wqlfa[VPK>$%u݃ rmhb]WRMY^6 jOPl1+ ܇TLdj;W1 `2 +&#>k`R l8mkVdQjNN0=E>:4 aZe xFccR^ M :5No I^Y|x8@!it32)%??@_}w] Ʉ M ߆_=WGAe}bǴst(ր5S(ށs/bGZb5ObE]uuʅ-[rut 2sՊ̃zze OnOLd F̲6t 0r z״ f qLZpCNtJ"vܸ E(y9z)ڣ ZhLsLwZ 1=Lj}Q5frѳDg.xs{tV.`i>2 mS`VulHy`ۈo!&*)& [ 7K#smْنiC܃e "\ jל)w Ϝ `q!"lV֞WOq}& Iݲq+Cߚ xG@5N}cd\VMex%.րBލ9WmR ;~ MI XhqG| 0׋qN V{c<&Cz; ׸yh &ߴo W4ڴT pŮ 0H¬զQb [0ګ$j?پ{{{n YMML=d#lCz:̀|hʩH6TdKj Rjnt9fM穥xc-b}ڡ"}߁iV\؁׌ KY σ7 ޻B@/ׄ?n1_ORڄ mT afO4{ɬ1HR 64罅?bŌ gץ-؀KVΈ\=Fځ8V%c{GEmׂܱ85[ \{A֔%-OiyzjQ,1sp .rlmw؂'ބHم~M߂hQ؃WׂكHۀىR׀2׃٫܀"q֯QۄX?f8gFznV(5FXJ76??@_}w] Ʉ M ߆_=WGAe}bǴst(ր5S({s/bGW^5MaEWsuʅ-|}[rum 1q֋ʿ{{e LكٟlJI] A俧~ۏ3߃u -끿߂s s˪`߉ޔ kHUj?ImF oϭ ၅ A%q7s'ΚUbGwT 9Ljv _rѴ3 ss{oE ai>2 lH1RuiDy`܈p!&*)& [ 7K#smْنiC܃e "\ jל)w Ϝ `q!"lV֞QJr}& Cz̤h'Cߚ xB96N}[\UNH]ܼx%.ր=܀ˁ4PcR ;~ۀFBُ XhqG|ڀ؀ ,w֣׋qN V{[ڀ׀ v5ԗف؀׀ չt'ׁՀ ;z7؀րӀ׸y_؀ՀԀӀ"ϕߴoׁՀԀӀҁ ϩM-ڴTՀӁЀcͶŮրՀрπ*?¬զJԀҀрЀπ ̶VͲӀ΁ P)ګ$_ՀҀπ́˺ɐ@ـҀЀρˁ پ{{{bрЀ́MMMLсπ́ˀɁ>d#b<ρ̀Ɂzρ΀ˀȀ1̀|ā̀ʀɀǁuhʩ́ʀɁĦJ6J̀ˀȁǀŀdK\ˀʀǀŀ< \ȀƁ¸ht9Gi CǁƀĀŽXH 'ŁÀR|uŁÀ}`ԁĀ€EEf L€…V (޻>Á w?1^w=[ȧ'YP;~ mVOS?'yɬ24<a64细<bŌ Kx 7VΈ\=Fz)?%c{GEP)'dp[ \]/l.PjyzkQ.%UR !s_UOPWdw4[z{8L;?`4d<$}^R;@-J)Kj3YP>&3@6(( {~|~}|}|v }~ ~~}}||{z{{ ܡ} |yzzy䆀~wyxwvtuwvvwnuvutupŊyutsstręorrqqpn}}rqponpoonomponnmmjnnmmnlbǴsfmlko( 5dmllkf(a sellkkjlh~bGQ"5fkkjiihi~~{.Phjjiihgj~~}y܄xzjihhgl}|{{%PLǀihffl}|{{ztz7 ΅yiihhfem||{zyy' Zghhgfedk{zzyx jhgfeedcjzyyx|CDaYyݛzygjggffeddcbi{yxg#GBRkfeddcbfxx{('  /gdcbaaexwN(  bdcbbaa`fwv;$\cbaa`dvvMebaa``_^b uur  !dbaa`__^^autsa 3baa``_]]dvsrri+ a``_^]\_Vtsqqpt4 Xa`_`_^]]\[\[\rqpodEb``_^^]]\[[ZZ`O zqpponon^ vOY^]\[ZZYYZWpponnmmn@,ܳLT[[ZZYYXXQ]ponnmmlkn- SXYXWWV\tnml k[ PXWVU].qnml ka-eRUY9Jmmllkk߃ `  ]lhfeylkaJ nmg_*/9mls3iv `lpnggemC isqP hlsqpmejP 8{utJ yqomlkjhgedca`_]\[YXWUTRRPNMMPib`9"@}|{yv tsrpnmljhgfedba_^\[YXWVTSQPNNLJM9^_J,t~|{yywvtsqpnnljjhgedc``^][ZXWVTSRPOMLKIK)1k,g}{{yxvusqqonlkjhgfdbb__]\[YXWUTRQONLKJHO#",Cx~~|{yxvutsqonmhjhgfecb`_]\[YXWUTSQPNMLJIHL@ǝ+3|z|{yxvvtsqpomliihgddb`_]][ZXWVTSQPONLKIHFCT+*|{{zywutsrponlkjhgedba`^][ZYXVUSRQONLKIHGL(v+0|yzyxvutrqonlkjigeeca`_^\ZZXWUTSQONLKJIHHQ)+@tzyxvusrqonmkjigfecba_^\[ZXWVTSQPNMKKIHFG) 8+e~zxwutsqpomlkiggedb``^][ZYWVTSRQONLKIHGEA4I$zxwvtsrqomlkiiffdca`^][[YWVUTRQONMKJIF68Z,Ltvutrponlliigfeca`_]\[YXVUTRQPNMLJIIMP>@=.)x+Bxutsqpnmljigfdcba_]][YYWVTRRPONLKLMMR7A@5( Kٚ]tsqpnnlkihfedba`^\\ZYWVTSRPOMLI-@?9A*Zvtpomlkjhgfcca`_]\ZYXVUTRQPNLKKMS:}?>5-u!1wqqmkjigfccb`_^\[YYWUTRQOMSSJL9?ՀB=01!X[vkljlmgno`_^\[YXWUTSRQPQRS2H<24bEVxr6&XY`[\ZXVUTUTSF1R4/8 ։re%(?bx*(9)'9,(&6z'&1ָ'%+ߴ&%& ڴl%* Ů=#¬  ګ>D پ{{ MMaDd=z̀|.hP6$dK !Kt9 }!-/  PG ޽)=54 :Sm_rӬ564 絆1(>~Ќ /(0sֈ\<H4&"H{EF4  $[ ]  1SkzzlT2 t8mk@D~ }D EP MA IAMK  |K } | ~"q[6dyH6D^=DjO:6KB3D ,S|\*yys5_|~E t |^Y}{X7yuj'|(%-'g|,2F0%osuK%xaDo~|tNT/s=WkwRL6$3rG8rhic08݉PNG  IHDR\rf$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+@IDATx]`T>I "Ez"Hћ *OX~ vgDY)ʳg  ,tMzfٝ\6d{ٙ9{9S\ $H @ @ $H @ Pixwsև++kh%RP $(K# z Of8z-)\:B*p)ᓏN! @(%TJg+E+/ S[ \~%5Q:-Z΍K poG ;50[ %%qJcjBIirhWn [0>n8Ÿ=C"Kb?0źGG52\&Ώ)Nuäk<^Ag1p!=0*$P, ߳ſ `t+zaܥa7GŻ~NF"y; A=1!př$2.P*(~p(~P%Uwäa0W=h8t\6dxPl KV?0{©HYᘇg^n9/Qڔ?D.30WK3"@տ %qW_+0iSYD ؊Rﭔܚi@u{?v JtOg؟Kwץk8X$|"WLb%_|ݙi.I GݻtRWڷ WK7jȋm^RzLKȨsj@?I5L_PS1)yp7"*f~`@b]|=(U9uV[ӧQlE33婧:g@U 1lׯ,VOڵ-U:u*T HicGlذ]V,V|s|R7o{=eNg}̝F@S.p;F-ȑ7߫;zΡG,+j2xA2`@+ӻh޼D4iLZٲ;{x7F G$اp vhCP~#:ʫ"-Z^ӞsNYj̘pQuT_ArͥC:~BxF Ҷmmiժlݺ=5eExsMD"BH7 |rᢕ@`!*VCFꊖ[@!/%}dēYR%o'q6=GRSSiBCj9GA kB `;1WPq}VG5 toqG#h"jBn2vl_Z/ *WN6mjˊeΜUPt-500}"|G] EH AxBG67?%a2fRLj^P,XN.\^ҜQPK$ۚ=#u({fȻԁq(T]a6}`; 'I la3^**s箔իN16_lO 09lPbqh*|k*?J˖{?ᗗ[޽rJoдi5Yf̞Bmp'>i11Us(&a?D9g*UfͪI {7ٗ[+W .4pU~c. VpD*s(f@.?_07GsAO$pn_AZeѢu믫dvw@n㈕.&P1C?uoNЩ.FpzW ^kwb->aOV[ PW=#}‘7x:CucǺf!A口M( A0ɜh҂>@` \l୸gՊ[e˦bه`M:RbaAfsdP(pamu7޽!3;Qcwy|ly-×F]. \ FwCK:_!pwrFA C5 ezL88%P?mn]@T3KKKž'k[6S3dx,@cl_+9ߵ/Mw 5}P]5yy{/>P`B6?#N܌cO)K[2^^75L?^NF*BP`WzBJm+祔׬YѼ)hUe_!`S_D @vZ `*mJ_zl҄;PwBPY1/Kc@ 'Cf7lX @ZZUOx&wHF0aSX@t[[3mlkvj[n 7ocQ'ÅKxn_n_%5k jٹs'-/jՒjzp ܸ1BP]],fb85bnFA$| 6ύ?m3OOǶ\rˏ?(1s\y{4 l>hy_IrϏ6p{EלU^0Zm % oa Vbf\b37nSrW9qݺua"OqP*8 [~?[opK0%˅qeay'*0EP @̳BWnfKl:ѭRnFiT]z|gdFwO5[r kG?y2 | T2p%yq != %&[٬sUX>[mxѢ$ T"={܅7ڵKG9˽rɿIaىs8vJvC1_n-ONq~=w¡=6ʁ.*#=6C\\gB% WbzRnŎ├ J͚5qbp9#D\jj*f˩* gaאΑIjz?I_^;CB0̈ʋOɔOoYs~\a([99B PH93b|{ظq,^͕xu3IyD5BJd2ib+Dd|*B-p>}F]?Cc哷R~^m37<7eXIGO@! C'H:R墋.2ƅ㘟 zҤ{Z9lW0KW>%ϞBr`O!?@E V\5Sy++FG,]T=X~V{ݱ+gήL{o?x*eas ~0e3 % %( 7%V^)oD|qm۶SR0)W~*OVϽڀ@RT`n`'z[h8$24!Oi Jȷ$0ZyVZ+WW{e%rډ+*e*]$fr/~C5o԰]UyEC&dM;"o1/1ဟ,VSXtZ1@aI 0ltV9mvqWEx6MAzk'MA?KRv/+VajUgʊ3d޼y{NK~ +a7;OvnfGD!_r_3ʭq-[vwG"~UW1GL*7#-¹_Jwڛ%ʔɐsT.,'<309s<췻IG _NdJ++5VyNi;3W{;:xۇ ? h<3IzMK/{HM _]ĴK7cM%KtB}MxkEb߂@`-(]3ϘcןÒv>"qJel+k44 ICr}y رg+?U̝a?8s ixAH 0bԳ]̿j\pkveאU97ӆ׬Y+=:y^a B駟f%nz曅+?ƍğ*rUy)My\NFɁ_ @E@+q ˨Q Bth_O*VIjc}+lriP A&Aq~!YOYݵDi۶XIt!L䗁 fH$[ѿmZ_G5+rgrW} /i Mݢū^*eFfHڴ\ՀuK/a̘s77~yhܕ%q q/K̠i܊KPئ; $Vw?iִRe/ɬ}gN(Mv0v:d; @%|8kTD5 mwE76v]Fl2!/c=%~0_i*}*>ߕ&40Mc|'@!K`jM!Te5|&j v1 6^QB}.4?3FjUCI):D/-%`%𽁶C ɰaL}$itXuyt\{9yRڑi6Y`9 2;ٔٻ1G7 ^+p <ғ ̾?[krqK嗟NG Zd=䍷Mr1Y2⼰th\%'p{dJi-j,~;G?@&A?+V3 S-*[<!3d8d45[YYԮVTVO#+f¥Qz txn<=D;҂@ȿTQkV`ğM_A)rCy]Ѳq\pXLy4kDeG|ȤQ9:CP6vJZ!J^{`;ٷ($v"5Aa98-לVʼ%2 FPG O;1gw"!J z9ȿ0!C۬Xy3N͔׌[_^C.˨ۢ2̝ DMkE~o,a!J@Аd5D⤤sdގ"E ފמ2''9?JS,1 @`-(Z**VpfiNmC_ rxo g.u.  H x˴= N8אġy06hx WԮ%rejuDbh'MV^WUPXg)J|AV^aʴekT&r)FyL{T ӥ˓2wte^i!/9%5$lي. c GNa<}n,XIC("th*qb~dҿ8N-j!#Xbi _O~a&ʕsc7á@Vo}1 `f1Tg&#4/Ɣ"@rHNPrc% XXi~I]N:hf/FCFogD;QgoT'tPX @%5uZZ}0(?`*];֡VreWeh.{v/J8[!-]67@U0W>"r mKZ\%r\dƨ  ;nR8MC/5Ƀp$\iLjE|VĘ@931B9{d|WҮk<+"s9$a p*2Mvbe{#{kPԯ"ҬCe9S˒IBLw8*5Ӷj!{;P.*202ϖ,f_ qAtWr)P  >:*+u;wɲ}剧+'P ~Rr饮#J/.~n/K.&SVu ԱB[i͋5rRi٪s+l"r ZUc^*o)Ҧ%y mՙOgۀW7#轨|4>q~Pq?_/( Kr,u\?[?V(Zs|/M9Oz+Z-PBKdBD&5im؏_)[u7 Ia9#'<K"yg0qL'^_(9w n2;l a." &Wb{gpd|yT>/Eڶfڲr}LOONa& ;srULȇlI# 7ܶʜ^ Ug&z0$?(Kʖ8wRl82,Wv]JV~tN,;rwD\cMn^CڙL{h2*7sո\WאP.w.8 C@$c:‡ ϪWzUk6JfFniۡ%y wzxS ɿx~Nĵ;]9F{{)\^xPIgc3kϤ`3<2t+{~ r 3ičRf#l/Z*2+J|KD@wǭaY<_60JӡmHk9`8.#b߁U$~xQֿWd_ȏ_pRJ_c M@~hPGC C R|?zRZq֋!r,mP ӌwǓ8/vU)Ҹ!WDHf鎡uCUU?ѽ3-/)wD0`v0 B*K4F*{W`Ƴ+F$R_f]Z6/#MJfavC? 41[[o ˀ}`۱s32P<=2O5I}#P*@$<{v+YYaY49g?io"+ܶiBx 'Rzeb'w$0 Sz܀-_*>U~؇+ˀSF8\ك |* Ίw ,HѰ~Ҳ o 7sȯ!KkQ`e K߭.>住;W-})a*px6gAݝ#|u70 x;|T|OC`{OZ{9%Sz\&6+;Avas*8 @g/E;&Vjܸ鷒y=U3$/M ˈa8).@8W@6(`ng|w:R5$_~ lWhWz ѳÝSIժͻV)eap6f'`E0ʧ`?VNun\îo t.2Vr䉕1f[1>|1؄强RD^n˄K4OAih v#ܛҔg\{yH^oXᚄǞn( r:vl g ?^4&?@ʐ~7j (Du-Y`sI_`{PVR*4ÊKޕ/#o}C7i'׌+{ |!X䙿 |=hƼS?އ#Wl(Wʓr7 ːrr˿KjgCӦI׮姟˪UDGRd4tp2i}s}1] 0{'pVb8j8>b65o\z!cP g-U𕡐i%~"_tB{>+QSSf#NyjDCp׻/~tfׄeإ{rۑpY2bҢ2u\leK w^@8ak}.XGț8wА,4gTZ`1X8w_y#D>}=A --50ދ$KWX8'asgr؛嘾MY7ΝrmweR_/;w2l>e ^S S(0ya gtJ/[I5>Ŭaox?&v?dm#ϗٳn.g*/3Dez-T{L?h\"EeI̘!Cdž}fJk:u7o2eO8:!,\J??\/]PfR`Gb  |g)8AVLUnUpƵ8Aշwq22r%h<.ʧ8c +3߭*@g>ǝKKSǿH-y?{K.$a8dyd藰=nyl9! vY@%5"J%{K<8 ұ @ʂ  gg`ȑAK.:a ز }S{w`pṡ>].$%L=xqR9]H .]+Tx}^ĥ4 E  @.|]QY\eG4VI3D ?HYv\xFÞHv` jsCGE 8(ۥstW;sLr@]_=c@n*I9P$DRJ4Z%+(?QF~e4jٺ+WAd_dN +ha?\-aϠ F$Xaqpg8,E: J H?N2a™WbɅ!C {O;:,Y<]hHksn5]n&M<ś2fβ@cy! T /a'@Z #(rY =-`8@l.6H*qpxx4^XJӉ*~!~ʼyMd|wȓYKq@ŠX r#"s $PSr&qgxSX V,[6U|L(?R`P<ZmiiiW2#l8Ekӵ3Dcz ޺OvA9? OQ鶺%8q{l( is,ZDnmR ZF U[[tۆ⋩Zi6=Ǧ ܡp8CAC<ʯ-ii&s*qWwaOl N{ j8fJ >ׄpSxԩMaq2{_l\h,*c/DZ"w#'2q%*Vʾ =4s]+qt2>3_6Jc@aK͛у7ͫi1̞8w_=&CE.xrlؽ;LGU7ݟ{bhEW~+Upi-eڴC ի9C`308 OKtᶡn )mP MU"0Js}߁1'⅙J+I6$PreV\Nv̲rA.N Bzș >4)@[><(?7% B~+>X> pM:KIϞ`_K %i-#"Nd`ΰ;Х;G1h◔K:0s=wtJƏc+%ch\"7|v{k lə1ëYV.<]0Npyoތ=ů/B 9ʭ !<O<>8xZ˖-ë2tBq╏< TnR%_ߍ΂#>itqָ'\߿7JWƥ7Żq˙q qegG'#9Rnnνa" H+ V W<ÄpĹ+< f+k1RիWkp ;TwLX˕4U_8:aIŖtOp:nݵ] 8>67piOq fO/>@QJ 33o46;Zzgf^îa-{6bб`nAăJ147|(mw 0BU.`v+]O'tKg=[Zb %_c' WQZ~]P|N8gȄ{GxH#J4$eka*;PN^ #\=2h!P$ jέ,\]i.ޥiX- aJ6$8-Yʉaai!2ƉwqJS!e͂[mo<OJJ)+믿!' a9*#кqna=aRhtB9h}Up38Iaw? lR{ Kp%)ɰۯ5lXR05FˣO'tK/:8ȣ|8sC,Dɧx+ WiyIfiXPPsCo&N|AfS22'Yq۰i}aҡ{zNJ@;$ٱ'*D W}#;jT4ْg2j q|2jY4t']Z+0 n<RHp2ث y0s]LpO {<("3<_ʕTnI9;T:}4~] :W=sAWb}P99;CKw]7< +^ПG {7739V"+o[$ eKm"wNѸ4-Vuc'"X"hIv?Y J{cABn!nr1aW*¹*5ڍ+qϞb8>z{9VG(x}p;|2&-Gw-oOFJAѸ.{vxx4Wd`' D_E]hwVSO(UpxE-7xx8fOĭpܯerTp ++5Nq|.oKlIL!b i:+8 CqYp?g^mAj87%M7$ݺjjaoDq 7zh!OA>*{1tLp #K~e(]}v? T7j LB09}b-PJYB0LGq? m㯫<9r晝X|[KVPIk0z,D ;itT#%0k"9/ N+oJV1E6\Yΐ[x<$j驽 5LY;8̣#hxmuzOpLxwyy\^Ic`>lldn:ȗ)No N%|7 Pp0uTӧ w1_A嬲T%աeƌkw0F=>]ύXW:}\>ű]#nD<$1O{y/RPBOGаҴ ղ ogt{w#&(#aPn3'aֳHk9&> QB[`;@n.@WˏFN#<-ל|a̓*Ӱc@uQC@?'[,e` ha0╦%Z&&OBb0I d0 Zj8Up*90A H}o7pzjҖ}ݸq}9Rp($J0ea/,t[;Ӱ#h/)=,7W_S}ϑŋ됬e(%ƣ|J=SZnx;mX0<=w ' 8O:ȑGK7\xs̝˭ ](1LD \%wZHq`7n|R+"ӧϔE\(+*ܸ]Hv °WLuWkBSpX<&oc׏5wʧy+}u~a) z[ E\WOnBsq883-eKtХ?oe۶k'J]&Sc&/peq x(,qU@MG͋a?(Mq')^iUKiaf9T\rDќ5JkKT]_,_e9R^*O`־*4Z5NQI iX4@^ asjM(Y1ϓ8ð+ݥ)säk:ĘeEPA5l7!fLF :Y+H +pʕC|p+4I*G O& F~έuxGWOauYq<~ 㼯6'be$  OݭOR }?&4.]h)pٲE_TvJS*#8W*;1?3w@#MAW?' c-4,~2!p Q4npɃb_{R3ϼ%: nUYUJcq*ݼ4y|5 FZy]_yKGq.!= 1"~ 16 ᴐs*n'AYiVB<ʑx1|JS?'=IQxsçykn8 ݋f5ר\S47r݂`Y9*828VxA sZ<G+W)C~`8q+vPOSX-ſ?&*@4uV&L% NW>}%S:i 8Ż4ũ4UP6塯4qq.aưE 2n\@I3*0 @ {ѱ:]onXKWj%u`\_qauO(\\%YH3b9U|wj0O˯tOMG_(Mn8'^B߀M hY2Ir({ܼnqVEߎF%tīмӰ. +t1asqn9XT47y?%);<Uпmj2<`IDATTUŦTIsi.&y8MKfqU?1L4+sӸ|~^SO&_ FӔD4wQi&P~iiiҪJ1%88u&|8͏ti\xW>7=î#Jc˯%TN6(Z_S8{GoŊ|YZqJMߏcܥ3Lx O~ WG Qqy&i.hZ4?7ҕiϮ4jH?Up.i5:0Qٳts+b͚7YiJ>Op}YUiDii4?\ּ\>WSy&)̣ ?;)xC59u̚f[fv 9`QFR>vMJD>qW,U'ΏW:}Mø*`\.Ҽ F;01iיKSh!,_^Zlw"\ uVhEswi qG8}Ź|a3╮xٕ_\F;ʝl~I3W1Q9t'k:t% wds +?+UJœ7S~?Qy(ޛxUŻaҙ0!QG9_>Ab+P+[X]4/%;t邓f{9Q.Vɫ#.QO\MaJ┏FDxr3kWC̔IZ(I\yĢ8 (I:vI'Hٲ?!#*?*ְjOP:îW8Wx_]Ż8 w%q.^׵~i=~ux W:D1>)dxnxqSTQ0p@|_6 (NJW<@ӐKy\Ík~%mZa^j{kO?q1S}֡ .)'ٷIZX 59wf2oNYYduD#@N3_Iڇ,իT^F` rmNJVqv}7<.ƖőOxu87Oʛ_ 87g)4bb1IՕm ([VA?Z-u;`\z եEү rი,ƍxN,{xDkX UJw}؍V^TԀ=1ۨ./O Jx9=9i j-#'J?zcNٿ\D[B4 I#tIeLro tyTX̊ʌ2lڬ"h,mwoSA)j_-˖KK"JkWj?]ӑOi~?4+NW:=%VS؄aLQr#W){mn #0F89!hτf=v3tlbErV-S'{l᨞.C.AZn逗_~Yn6YPJ\G9xB)+Q{RNyh5?SDt !=oG.yO駟J))G!zmxsAfq i@%Pn # G EO ?^Y3ydEd Q|RdȐr57Jv혢À/7ᣝFŀR#fUJҴzi:]X5-}\Yވ#D:i2~xi,'σV>A#0=~)}s2> TagR<-,MyH3 а!3yxYGȿ'XK.&y|VɅ+Je녯;u!&;NOq*ӍixU~WJ0a9y׿CdG2l}м3d |;zw y0[LqqLٺ Uu3s3*θ]c I"6I6!912d1 75[Tm0Kr]wJ-4=;8—y8ͧr;@0]_V-X8!Q|q]87?2޿ tI>! d̞hd dyӈtOD/9G_ |r'Jgʊv8\@>S}c5uG+҉c)q0Jc$ʘ17˩z/ZbMv7],w3}Ns^/~X.LlY@ tN؎_'<OHO'N kzXlN ,YҗB3%q v&fi,rWoC`{,8(hko >E}q?}r~sKl -UJ*;jtJJ4}uf_}MGWCXcn4lX^/~/r' ]!33H7_]( 2Œ`:%5돧Dk#ü9F)w+-nC=&v-7ԭ[oĜL͛̕iTTs -̝SvธEfhhX~N4&|)PL(|Xv"ӧx*1s6ff![3cd!> $6HeXM+`Sϖ{o ?TXr!Ek CJaټΝQT\0l)0LeNݰ+0Q(reޒʻ+|2=2RbLnΝ2 WOW,ME{^zY?!Fyäf62e|A'(`uQPϞ*!74ia¶c㏗C9Yȋp# =*lan04U ,>%ęS 5E # ٪1JX<;;qSTƎE9Ï^6L..zYYx٥ȃ=%geWL΄)m0akrVXaz_|̛76Z-4>n+FOGe<˂g K{Yx1$*ee7lذ(* 1N`.Bqp5XmG~N * dB>ʌQL]xe@%AyD3r}Y&v$,׌`tfacџ-J GZرcƒ0.S˓ر KˬY?Paz 1e! Tx-%^_.ۧf=yj?xX0qaQJGJR\T,Kz(*}d-a׾=Cd|SϒU56׽Q~isw r۷g`NC2kNgX>=phHNkrSDӿ\98%[e)}jr M/185*џ0W]"oN ?,śK2_xxɱh}->\J4S(L['zz Ӥ7R:bQ 4nX⧨ @?;&6O;S| De!5ŋLn޼KzXX>|!EnAFL\q茹mEwb,eJqpF2H *Rj+R:ErU3w( ~L=5qe3SQ];E'؇^g@\/6q3HCnpIEiBt#THrA4)!GI2qCUӳf(3 D|`D~8ü $__d3 ~KLE7 n<9P[FuOYr4kfR3 gFLSʼn34~&n8l9i@&/ W.,?zxu:D^|Ey䑒4P M ZK @^A'S 'H c>Ȩ7IJXƯC$\,lNLkz@? #`@0T]-$w]oW\izP2%s'#Bi`փ=p2s IG16؎)vÞ`Q@X:x*>1dG GJ뚗D>a6KV8Tj' X$}X9 b䶬bymahH=yrXo~a@ c`Cc L6 z{'ˇ"2KG^;('z+W`c˭<@2+V,t 4LZr@Q5Xz6Y]ˆLcbE({LV ΀:n:K޸fY&ʨKA'䲛p^1# aICQW%& DضM{Msst1~AT/pp< lǡڴ Ʃ8 pDl+oZh?&ǩFHUpLɴz!˴1eϛP1|U9Q XCC>Wcip_3a4i I";wݲF˟1gϠxAQthѡM/;vr 4KL,ˀLE#n7cV"2y SkD^ q4oX{|E,εzȾ*F@BFF|t<$d qq XA/kj4ȃ:C7 7=0JCaX-knxU8<<^RMBL%,g ĐWcsG5Ï KE/ lڴfN)9M= +7SJ?*`Y-E/*:8~W^Mʫ1+7@Q&q1!xwtM0a")c| } `Ifö@&[hJgh~-YxLzx,c[l%`l ỶbEe(UđD̩_V**j:u-cFjAkɑQtf?%^Zp=0ɋ fpZ'j!6lذ!XHܟ쁿$].G:?]>`>􅖴( ZCydj oL_2j G%xA&B-Dl`p&l%aؼx@N:ѐGށZ1O8Q/f}_˵3c/K/ߋM@Mx/F"5xpb׎ʢ aT">Q' ʄeW>/U7^2Լ!0~>箿M{w'VTP܊%1&rȇS-?#lR(ar)lؠuTQ%XBc` z8MBәE+dKK睛l sI"N;sEdҭ[7N@7]v^0:t(>sSDj׮m>3e;*~P#O_ڃ371Ok+~'᥏x]xBnzv5[ W&ykz,Hf~]ҪmɦPh)`ԍ;VQ6Ib¶Pb[y!o[xj~Hgr54nBP& ɑ-O? TwG#*ȼ(?ٳg'kpy[<&lX]qV~g1grUFe0QMO(bv­yopt^2Oie:&a-fe{0H k=Զd~^>دjH:u$?m9 ~~n̿?@[.^-I@eWwr'ќ,g>ȉ8ዕ`6o}-#v{iP8^V|V-CEcIͯmIlF߽ilq?o]ia zH5Nj ~Sqf:&L y,'Tp'T9W67jr:CkyWyӒAb'8qgKrV<21D柀0Le$0DzXX~b/X\h߽d="nɐߏJݺudsJիWݻˈ#zaJ(Y񥮕+,y=y)X:nIPlrՌh!BFckmЄUOi]sZ3>EE⭹kh~ OY3Pp jk)k?_FxÕ+O#ѳgO7}1>6^7Y)ڶm%VLҼYPG8[*@diH3:0'i1gg꒏8<\i-aTf< &3imF'=>ۻ*s% ’v$ " "*kEx(>W-pT B*uA Q@A -R˾/BHȝy@6܅IΝ3ߜw.mdÆ ꘰f]te˖ŋ2P嫈b2J![Dɀyجp~|^9ă?A{E)T?@@+wv ZJ!fOo[#Ψ9gkiP?zaZC3VE7jj۶H\K/ -ZHڴiNr㐣|GZUU\>hS00ȃϚzÃm1 haT#MKPM3/S p342`18DҴIٱclW<8DC Ç WxqOQQM-Kd YpL݁TRܷ;)nղ{SۆrL; *j@9;Y$[Ʉ= ]iz =WRzP.A*p{"cĔ&+Ȱa }u`Jwbh5k֔k&H߄{q,hɛ; IzZǹx{bƩ,,? &pf,+6k: , zx V`OXFgF冒Qw2u!l߾]U((idĉɓb|xp+[VC]rՖ5GdBJ5hz,rk'_Y/IwM}xft LcAʅތp6Z%Ke1r73:Uy{p&# yꩧ pZt`qa #3FWi$<؜쒧q'yBT\ln.WmԠb$ͼFG Ls_(J0 qbyn#M z>L#O++i1 i4TdǸpŁqsIyxȱPOV+IcǎIXXT 4h\ |K~~&/IGdJL-N@c$ -5)`ulHͣ>aϏ>=Q$` e{2yǷNv &h2 ͈ܯKtLi+Pq1W`Oޫ4iK-w-Qkx^8,D\dKzu9󃴇Fh. J`QXnXOM©yV(·›U 2+A?0&ľ'MH_)LLl^a7bJΝxK#+ SNc̙3+07$mqN"zIln 4*`}`f$e`/flp䞃@' gbՂz 3YK9X vpPQ&TR$뉳b!nc# PB\R?@M˿ 0,D 9ϸ$ 0Id! &^Af5L^ 궔::ؚFTQ@<}foNLV&0m%+u=ҶC_;O-6|i֬<1 v>7Y{ʦM[}Ӣwl\r[ ƒA/%ԭHI)u2#wmnVlh tfSϴ0z`IK0Q-O8};3ޔjMU|_'>w"Eo)JlOH/#:'\QY%,?Rۍ]E]|"!$*IR6*<דc*Af͒#F(ceJ^lAGvueP$hM~AVm5.I[DU%@R@kQi_s_@Hx+`4[Tm(Tkp6iJ $ g2n7XbO111l_ʫNgq ON_[TWސyB\ovZbȅ 6->GgOkۅH*! 7?rD\~.i >"r`󒘘(55YgktyUQ8xptzkΝڧ&i?"/,ZD3Z䶛F-=a*L?OZqdܢ+D]->RQݶ{?;Po7Zg}VBѿK`M(./oup5_2erJٲ5Cfʼozh{5Vv_yrut%S۠"҄<yP~w*v*`ov73vM6? |w%CP;w[ Vx~*1 <ä\xO82 S4iT ͔Q?,zfԊN=Bfv76Mp 8w;S PIPJFk ^غup+5 /;d~?M{eON,4o_xxz_d<]t-W{F561J2مp3Wn@QfU馛~ WZ*τHW% rMc<=d3CѬ! C^|]?kHB,zQ%+ooWP>m3Qvw7bhyN`2z(ᭃW꺺R,潆|Ux wYB-]=\uq{u@ñ5kEWlk0iW$[[sF{נbʸ{2E]~2sY7E\!y]Lª֏7#p7enl& Ito87PgΜQ_j܉Q۟G/LV0Gey`޾W ǁL9 Ń33}Gfϙ+իUqME" 9{A}G .kAIz12?n)c{HHD3Җ2ә41?7P^czyI]xV,6r[e|y.C3U盐:=䓉ڵ1/… ܹsdeKzpc7ne IO.?C2-~M4[-йspHm;V'KPVo9o! `X$ $!!.VP?<;# sqrϺ73{.gHhQj23z U[kG Ʉn|*wDen)>x7 %2\e(M46?*X:Q^\C=׮?~&c_| ym!6$ '&v/[2`Z; T/uih-~ (sZSS*^bcB`T *1EQ dd\R~!dU#GQFIJ*`v޿2.i[( H\hjx!ڐ% SCmZ9ƼK2|~h~WK !@l `[0pi3z9TINNSh$uQ1i)B`8IMHUzc'Oɮ]e-˥K'e*_=7WnD(OxG}$|rO=:ח=X̀?_*Ž4d:C'I߷l=(W׼R%hUqM4˛CTb3!؍xƠׁSPE ˝޽o3&횜j=m]Oo1 0(0w dX%kN-rdd˥+:Nf`"=?"LHX"\) pRi*_~ȐL_wIak:ZK* r?J[KrSeY|]o1dNr蔚5mr+K*"nu*NJWkhˆ2/K&VMJ4츩g=fM/ۊfjdɋAN9 >I( ~ٸk:6Ƹ"K(M6ңͥٮv>-&sV r +/PyCGJeF nuZy@ +q7$aSɊ- /u*4*NPl5x*8Z5LNOТtʨ.& k70<!L9= `Nq1Xfw4>{ Қ]Q]Mcs5^Ωs~'kD@̏oO" 5D cE:oM׆B> XAp*wsJm`ش4TMD\2%B. `DziR7{ (d><7X2וŒ/|67sJ\<XZA'` CfXpkʹX΃ڜh /tmp/openmsx-log.txt openMSX-RELEASE_0_12_0/build/package-dingux/zip.mk000066400000000000000000000016451257557151200215660ustar00rootroot00000000000000# Create a zip file for Dingoo. LOCAL_DIR:=$(BINDIST_DIR)/local PACKAGE_SUPPORT_DIR:=build/package-dingux # Override install locations. INSTALL_BINARY_DIR:=$(LOCAL_DIR)/bin INSTALL_SHARE_DIR:=$(LOCAL_DIR)/share/openmsx INSTALL_DOC_DIR:=$(LOCAL_DIR)/doc/openmsx BINDIST_OPENMSX_START:=$(INSTALL_BINARY_DIR)/openmsx-start.sh BINDIST_README:=$(INSTALL_DOC_DIR)/README.txt PACKAGE_FULL:=$(shell PYTHONPATH=build $(PYTHON) -c \ "import version; print version.getVersionedPackageName()" \ ) BINDIST_PACKAGE:=$(PACKAGE_FULL)-dingux-bin.zip zip: install bindist: zip $(BINDIST_OPENMSX_START) $(BINDIST_README) @echo "Creating zip file:" cd $(BINDIST_DIR) && zip ../$(BINDIST_PACKAGE) -r local $(BINDIST_OPENMSX_START): $(PACKAGE_SUPPORT_DIR)/openmsx-start.sh install $(BINDIST_README): $(PACKAGE_SUPPORT_DIR)/README.txt install $(BINDIST_OPENMSX_START) $(BINDIST_README): @echo " Copying $(@F)..." @mkdir -p $(@D) @cp $< $@ openMSX-RELEASE_0_12_0/build/package-slackware/000077500000000000000000000000001257557151200211035ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/build/package-slackware/catapult.desktop000066400000000000000000000004461257557151200243170ustar00rootroot00000000000000[Desktop Entry] Name=openMSX Catapult GenericName=MSX Emulator Comment=The MSX emulator that aims for perfection Comment[it]=L'emulatore MSX che mira alla perfezione Exec=catapult Terminal=false Type=Application Icon=/usr/share/pixmaps/catapult.xpm Categories=Emulator;GTK;Game; Encoding=UTF-8 openMSX-RELEASE_0_12_0/build/package-slackware/openmsx.SlackBuild000077500000000000000000000167641257557151200245540ustar00rootroot00000000000000!/bin/sh # # SlackBuild for openMSX # http://openmsx.org/ # By SukkoPera # 27/10/2005 # # Thanks a lot to CAT for his Slackware package cration # guide (http://www.slacky.it/misto/tutorial/spunleashed.txt) # # Check out # - http://www.sukkopera.tk # - http://www.slacky.it # # Notes: # - This script will make a package including both openMSX and Catapult. # Please edit it to suit your needs. # - You'll need catapult.desktop, slack-desc and the openMSX and Catapult # sources, of course. # Get the current and temporary directories CWD=`pwd` if [ "$TMP" = "" ]; then TMP=/tmp/pkg fi # Set up working directories if [ ! -d $TMP ] then mkdir -p $TMP fi # Some useful variables about the package NAME=`basename $0 .SlackBuild` PKG=$TMP/package-$NAME VERSION=0.6.0 #VERSION=`date +%Y%m%d` ARCH=i686 # Autodetection makes this pkg i686 BUILD=2suk SOURCEARCH=$NAME-$VERSION # Set compilation flags according to the architecture #case $ARCH in # "i686") # SLCKFLAGS="-O2 -march=i686" # ;; # "celeron") # SLCKFLAGS="-Os -march=pentium3" # ;; # "pentium3") # SLCKFLAGS="-O2 -march=pentium3" # ;; # "pentium4") # SLCKFLAGS="-O2 -march=pentium4" # ;; # "duron"|"sempron") # SLCKFLAGS="-Os -march=athlon-xp -m3dnow" # ;; # "athlonxp") # SLCKFLAGS="-O2 -march=athlon-xp -m3dnow" # ;; # "x86_64") # SLCKFLAGS="-O2 -march=athlon64" # ;; # "sparc") # SLCKFLAGS="-O2" # ;; # "supersparc") # SLCKFLAGS="-O2 -mcpu=supersparc" # ;; # *|"i486") # SLCKFLAGS="-O2 -march=i486 -mcpu=i686" # ;; #esac # Add flags common to all architectures #SLCKFLAGS="-pipe -fomit-frame-pointer $SLCKFLAGS" # Print a welcome screen echo "+-----------------------------------------------------------------------" echo "| $NAME-$VERSION" echo "+-----------------------------------------------------------------------" if [ -d $PKG ] then if [ "$1" == "--clean" ] then # Clean up a previous build and go on rm -rf $PKG $TMP/$NAME-$VERSION else # Warn the user and stop echo "Previous package build directory found, please remove it and" echo "restart the SlackBuild script: $PKG" exit 1 fi fi mkdir -p $PKG for i in usr/share/applications usr/share/pixmaps do echo "--- Creating directory $PKG/$i" mkdir -p $PKG/$i done # Decompress echo "------------------------- Uncompressing source -------------------------" cd $TMP tar zxvf $CWD/$SOURCEARCH.tar.gz #mv $SOURCEARCH $NAME-$VERSION cd $NAME-$VERSION # Build #echo "------------------------------ Configuring -----------------------------" # ./configure --prefix=/usr \ # --sysconfdir=/etc \ # --localstatedir=/var echo "------------------------------- Patching -------------------------------" # Set installation directory to what we want SUBDEST=/usr/share/openmsx sed -i "s:\(INSTALL_BASE\:=\).*:\1$SUBDEST:" build/custom.mk sed -i "s:\(SYMLINK_FOR_BINARY\:=\).*:\1false:" build/custom.mk echo "------------------------------ Compiling -------------------------------" #CFLAGS=$SLCKFLAGS \ #CXXFLAGS=$SLCKFLAGS \ make INSTALL_SHARE_DIR=$SUBDEST res=$? if [ $res -ne 0 ] then # make failed, we cannot continue exit $res fi echo "------------------------------ Installing ------------------------------" DOCSDIR=$PKG/usr/doc/$NAME-$VERSION make install OPENMSX_INSTALL=$PKG/$SUBDEST \ INSTALL_SHARE_DIR=$PKG/$SUBDEST \ INSTALL_BINARY_DIR=$PKG/usr/bin \ INSTALL_DOC_DIR=$DOCSDIR res=$? if [ $res -ne 0 ] then # make install failed, we cannot continue exit 1 fi # Doc echo "----------------- Copying documentation and other files ----------------" mkdir -p $DOCSDIR for i in AUTHORS GPL README TODO do # cat $i | gzip > $DOCSDIR/$i.gz cp -a $i $DOCSDIR done # GUI echo "--- Compiling Catapult" GUINAME=catapult GUIVERSION=0.6.0_R2 GUITNAME=openmsx-catapult GUITVERSION=0.6.0-R2 GUISOURCEARCH=$GUITNAME-$GUITVERSION # Decompress echo "------------------------- Uncompressing source -------------------------" cd $TMP tar zxvf $CWD/$GUISOURCEARCH.tar.gz mv $GUISOURCEARCH $GUINAME-$GUIVERSION cd $GUINAME-$GUIVERSION # Build #echo "------------------------------ Configuring -----------------------------" # ./configure --prefix=/usr \ # --sysconfdir=/etc \ # --localstatedir=/var echo "------------------------------- Patching -------------------------------" # Set installation directory to what we want GUISUBDEST=/usr/share/openmsx/catapult sed -i "s:\(INSTALL_BASE\:=\).*:\1$GUISUBDEST:" build/custom.mk sed -i "s:\(SYMLINK_FOR_BINARY\:=\).*:\1false:" build/custom.mk sed -i "s:\(CATAPULT_OPENMSX_BINARY\:=\).*:\1/usr/bin/openmsx:" build/custom.mk sed -i "s:\(CATAPULT_OPENMSX_SHARE\:=\).*:\1/usr/share/openmsx:" build/custom.mk echo "------------------------------ Compiling -------------------------------" #CFLAGS=$SLCKFLAGS \ #CXXFLAGS=$SLCKFLAGS \ make clean make res=$? if [ $res -ne 0 ] then # make failed, we cannot continue exit $res fi echo "------------------------------ Installing ------------------------------" DOCSDIR=$PKG/usr/doc/$NAME-$VERSION/$GUINAME-$GUIVERSION mkdir -p $DOCSDIR make install CATAPULT_INSTALL=$PKG/$GUISUBDEST \ INSTALL_BINARY_DIR=$PKG/usr/bin \ INSTALL_DOC_DIR=$DOCSDIR res=$? if [ $res -ne 0 ] then # make install failed, we cannot continue exit 1 fi # Doc echo "----------------- Copying documentation and other files ----------------" for i in AUTHORS GPL README do # cat $i | gzip > $DOCSDIR/$i.gz cp -a $i $DOCSDIR done # Clean a little find $PKG -name .cvsignore -exec rm -f {} \; # Gzip man pages #find $PKG/usr/man -name "*.[123456789]" -exec gzip -9 {} \; # SlackBuild stuff mkdir -p $PKG/usr/doc/$NAME-$VERSION/slackbuild cp $CWD/{$0,slack-desc} $PKG/usr/doc/$NAME-$VERSION/slackbuild # Icon and desktop entry (We're using a custom one because the included one is broken) cp $PKG/usr/share/openmsx/catapult/resources/icons/catapult.xpm $PKG/usr/share/pixmaps cp $CWD/catapult.desktop $PKG/usr/share/applications # Remove broken desktop file (not in $PKG!) rm /usr/share/applications/openMSX-Catapult.desktop # Strip binaries echo "--------------------------- Stripping binaries -------------------------" find $PKG -type f | xargs file | grep ELF | cut -f 1 -d : | xargs strip --strip-unneeded # Set permissions echo "--------------------------- Setting permissions ------------------------" chown -R root.bin $PKG/usr/bin $PKG/usr/sbin chown -R root.root $PKG/usr/doc chmod -R 755 $PKG/usr/doc/* find $PKG/usr/doc -type f -exec chmod 644 {} \; if [ -d $PKG/usr/share ] then chown -R root.root $PKG/usr/share chmod -R 755 $PKG/usr/share/* find $PKG/usr/share -type f -exec chmod 644 {} \; fi # Copy Slackware package files echo "--------------------- Copying Slackware package files ------------------" mkdir -p $PKG/install cat $CWD/slack-desc > $PKG/install/slack-desc #cp $CWD/doinst.sh $PKG/install #chmod 755 $PKG/install/doinst.sh #cat $CWD/slack-required > $PKG/install/slack-required # Create package echo "---------------------------- Creating package --------------------------" echo "Creating package" cd $PKG makepkg -l y -c n $CWD/$NAME-$VERSION-$ARCH-$BUILD.tgz # Clean up if [ "$1" = "--cleanup" ]; then echo "---------------------- Cleaning up working directory -------------------" rm -rf $TMP/$NAME-$VERSION rm -rf $PKG fi # Package created echo "Package creation finished!" openMSX-RELEASE_0_12_0/build/package-slackware/slack-desc000066400000000000000000000017551257557151200230470ustar00rootroot00000000000000# HOW TO EDIT THIS FILE: # The "handy ruler" below makes it easier to edit a package description. Line # up the first '|' above the ':' following the base package name, and the '|' on # the right side marks the last column you can put a character in. You must make # exactly 11 lines for the formatting to be correct. It's also customary to # leave one space after the ':'. |-----handy-ruler------------------------------------------------------| openmsx: openMSX - The MSX emulator that aims for perfection openmsx: openmsx: openMSX is an open source MSX emulator that tries to achieve openmsx: near-perfect emulation by using a novel emulation model. openmsx: openmsx: openMSX is in alpha state, which means that some things work but not openmsx: all features are implemented yet. Many emulation features are openmsx: implemented, but in terms of user interface it is rather bare bones, openmsx: unless you use the optional Graphical User Interface dubbed openMSX openmsx: Catapult. openmsx: openMSX-RELEASE_0_12_0/build/package-windows/000077500000000000000000000000001257557151200206215ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/build/package-windows/controlpanel.wxi000066400000000000000000000023661257557151200240610ustar00rootroot00000000000000 openMSX-RELEASE_0_12_0/build/package-windows/harvest.py000066400000000000000000000135511257557151200226540ustar00rootroot00000000000000# Generates Windows resource header. from optparse import OptionParser from os import walk from os.path import ( basename, dirname, isfile, join as joinpath, relpath, split as splitpath ) from uuid import uuid4 indentSize = 2 excludedDirectories = ['.svn', 'CVS'] excludedFiles = ['node.mk'] # Bit of a hack, but it works def isParentDir(child, parent): return '..' not in relpath(child, parent) def makeFileId(guid): return 'file_' + guid def makeDirectoryId(guid): return 'directory_' + guid def makeComponentId(guid): return 'component_' + guid def newGuid(): return str(uuid4()).replace('-', '') def walkPath(sourcePath): if isfile(sourcePath): yield dirname(sourcePath), [], [ basename(sourcePath) ] else: for dirpath, dirnames, filenames in walk(sourcePath): yield dirpath, dirnames, filenames class WixFragment(object): def __init__(self, fileGenerator, componentGroup, directoryRef, virtualDir, excludedFile, win64): self.fileGenerator = fileGenerator self.componentGroup = componentGroup self.directoryRef = directoryRef self.virtualDir = virtualDir self.indentLevel = 0 self.win64 = 'yes' if win64 else 'no' if excludedFile: # TODO: Modifying this global variable is an unexpected side effect. excludedFiles.append(excludedFile) def incrementIndent(self): self.indentLevel += indentSize def decrementIndent(self): self.indentLevel -= indentSize def indent(self, line): return ' ' * self.indentLevel + line def startElement(self, elementName, **args): line = self.indent( '<%s %s>' % ( elementName, ' '.join('%s="%s"' % item for item in args.iteritems()) ) ) self.incrementIndent() return line def endElement(self, elementName): self.decrementIndent() return self.indent('' % elementName) def yieldFragment(self): # List that stores the components we've added components = [] # Stack that stores directories we've already visited stack = [] # List that stores the virtual directories we added virtualstack = [] yield self.indent( '' ) yield self.startElement( 'Wix', xmlns = 'http://schemas.microsoft.com/wix/2006/wi' ) yield self.startElement('Fragment') yield self.startElement('DirectoryRef', Id = self.directoryRef) # Add virtual directories if self.virtualDir: head = self.virtualDir while head: joinedPath = head head, tail_ = splitpath(head) virtualstack.insert(0, joinedPath) for path in virtualstack: componentId = 'directory_' + str(uuid4()).replace('-', '_') yield self.startElement( 'Directory', Id = componentId, Name = basename(path) ) # Walk the provided file list firstDir = True for dirpath, dirnames, filenames in self.fileGenerator: # Remove excluded sub-directories for exclusion in excludedDirectories: if exclusion in dirnames: dirnames.remove(exclusion) if firstDir: firstDir = False else: # Handle directory hierarchy appropriately while stack: popped = stack.pop() if isParentDir(dirpath, popped): stack.append(popped) break else: yield self.endElement('Directory') # Enter new directory stack.append(dirpath) yield self.startElement( 'Directory', Id = makeDirectoryId(newGuid()), Name = basename(dirpath) ) # Remove excluded files for exclusion in excludedFiles: if exclusion in filenames: filenames.remove(exclusion) # Process files for filename in filenames: guid = newGuid() componentId = makeComponentId(guid) sourcePath = joinpath(dirpath, filename) yield self.startElement( 'Component', Id = componentId, Guid = guid, DiskId = '1', Win64 = self.win64 ) yield self.startElement( 'File', Id = makeFileId(guid), Name = filename, Source = sourcePath ) yield self.endElement('File') yield self.endElement('Component') components.append(componentId) # Drain pushed physical directories while stack: popped = stack.pop() yield self.endElement('Directory') # Drain pushed virtual directories while virtualstack: popped = virtualstack.pop() yield self.endElement('Directory') yield self.endElement('DirectoryRef') yield self.endElement('Fragment') # Emit ComponentGroup yield self.startElement('Fragment') yield self.startElement('ComponentGroup', Id = self.componentGroup) for component in components: yield self.startElement('ComponentRef', Id = component) yield self.endElement('ComponentRef') yield self.endElement('ComponentGroup') yield self.endElement('Fragment') yield self.endElement('Wix') def generateWixFragment( sourcePath, componentGroup, directoryRef, virtualDir, excludedFile, win64 ): fileGenerator = walkPath(sourcePath) wf = WixFragment( fileGenerator, componentGroup, directoryRef, virtualDir, excludedFile, win64 ) return wf.yieldFragment() def run(): parser = OptionParser() parser.add_option( '-c', '--componentGroup', type = 'string', dest = 'componentGroup' ) parser.add_option( '-r', '--directoryRef', type = 'string', dest = 'directoryRef' ) parser.add_option( '-s', '--sourcePath', type = 'string', dest = 'sourcePath' ) parser.add_option( '-v', '--virtualDir', type = 'string', dest = 'virtualDir' ) parser.add_option( '-x', '--excludedFile', type = 'string', dest = 'excludedFile' ) parser.add_option( '-w', '--win64', type = 'string', dest = 'win64' ) options, args_ = parser.parse_args() for line in generateWixFragment( options.sourcePath, options.componentGroup, options.directoryRef, options.virtualDir, options.excludedFile, options.win64 ): print line if __name__ == '__main__': run() openMSX-RELEASE_0_12_0/build/package-windows/images/000077500000000000000000000000001257557151200220665ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/build/package-windows/images/SideBanner.jpg000066400000000000000000000214551257557151200246110ustar00rootroot00000000000000JFIFddDuckyPAdobed      8  "12B# !RbAQr3CaScs$t”%q⃓4Ddu&V "2BRb1r#3!AaqQCS$c4sT ?] !xX8= I#JāWBHy*V$@CRq zВGj'<}_nA_H$̷Y-N5'u׋.z~b==vիɹݒws#mC#\O2q>7ڸʓS;#E帮hw{R牅Lʍ\s줕4.sҹ˵..eP/e:wwgqznTjfnOmGoZ_y-?!SL%OBRV*ܕy<VvZvI]]nV+W.~)OS}OJ4ViGS(N29wt~<)tCNk!**qIN^!Pд zMŻ`l Ġ,xϽYmu8M3)owQ=Bw)&3Za*e1_F8צw!`+,^ O.N uzV2UWM|M~)0/3eSTP~O׷x HX-2\#d4KkϷRU܅|H*6:_W ~>HNSS%? ;.avG0ue&>}9}3\ڧ֟[F~r2 ݷޖR 91wl¬;Xe)l6¤jyJvaVHkb) 2R?iL^xܧqw*V+uod9 %tK)'T;qSnNu#UՌjSvq$e[_U*KJzQF>U}ߙZ癕([էR|ya,SłsL TimoKe#ҟǮExh!jˊύPڜeJ+r7O.o̱lԭ.'ރemdr| Se_9qYE ٶoBo(i ^-ڥk;ŧrÉW.kNUB㱐CXn҆ǎg}B`U9=}%7sG5OWv۩95DU5\76km{W/+QTujSvޓyW'oq,dU!ga>3_Kq5vcW>̎EjQ]fsWVSz?DUX"gϩK/ase߱S%HSv٘bOGϗIq,՜iѶ8?9RW9?W"-PyW;\ǃ+^abC)jSRҟUBq/egXpEsFSʸܝ+L}jw{7zm>Ϝ.i|?BT"rCm2GH6pЗT6N),9.y:mk^=І}gŷޏ({)m=༙\\gXwYifd~5LKn2ѵx|*NoRIxJ-(±ی*Kqk)[f;Iܵ_=%9aWD~WXi:)IK+Fc6{o>R{<=u`^8g>asYieba1VkqjeJJV6M}ͮIJV?{J{L. q-MqcgHuZB}^)JVy2wqqVxqmu [KNա^d*""DZd^~+Q4FSj}`>XYj/#w+D9]߉Ȭ"]eos.=童 d˪WsO+t,)Kִӆyo6y; դ!F֤m~#||a(wӶ*Sދ!bebP\E?|Mu]SvY+oj0zٺ9M |=k՗rm$fEci.iהhxO+0o> ߸ON-Z>ĭ5FvK?'C]=,b̈́ kuTezav\߆twX.ٍV4ۺM˵JE֌K§Y]|*Ώ4uKdC 0Su^ToqjKGq>5|h ooCg_29YQz q%Z]YKoY\{˯pCd0XWd-#jwն#~_ƇxWT%竰&C* q^X}mjv6|FssnlZx_Y5:ӆM+eDJJqq>3.a쥳/ޗzRXiKB煸.f,iazJulaIⷶ踭MrxfW*GiLyoSw5͟#LukUf)/GO}6Rjty~΍y\`,a%(UJ,jSPYt$ɍ+j~:joIq5q{M1j12cET?%:ymvϻ0k;9 Iꩆk-=J}6+M;~ݤƵKgC5#KM?l$9͖]6pўPlӓaףjb"K>c1 ႼA,h2ԷN${ akHvK\V$8n̮xT)&L<Va4J۪$̆P~/@JYZLʱm,*-f^pe%_ l,7SXR1uzKMZ a "])*jV+ө^:)4zO(U~JnCVM;]կ֖/Y'K%nS8eTwSe9 Ԫ#WcIu~gO#g_N\:|>E-{]뛊#mmUqD\ۼޖ_}SQ5;; r|yXܚKX1a׭)7m]#{&XT2|v0Ǯ{ɧ`_dUV$6ױZ{W٩ Ene~OmjFΒl#+m(ܤwwrc*Z=˳nsKIHɺVXԜw`K+7'pjx!ʎcV[qb'l4\z?5ᖹw#dR1yԘ/emdkg+ikɽ(6_r J@\eΕSo[Ksʓ_So [x56}wPu+ca>`_m8~]7HjWd17J\zl ^%=F~D;VMkb˘-{|t.vrʚXW^"NQk- T;-?CK#sO!\?kT5#[͎eJsyNfUg6ϰSY,uɝwuS[*N߿3H|;+\(lal0W(a7%KC+g4㻵[7'_1CbU-3xZI=-g+Y/6dUv6WVvt ̀vd5i%h/Df>[;;5^qΥe+Xjt|W%QVq_+KE佦ȍgf>uvQ+ێ)KN+NƖq<iWFWKGZ?7ҹr9j|3ꞥ+X:[yشJէ 8V_"RGUcHt.ˌǤG)_~/,.{6R}_ 8׶ÙVloӷwOEujr2z(ҕ7\)pᆟU^UKKw}IV6mk'[qTeƜ.8<{fxeSdZ%3xycpȱ(Sf=4_RPǟR=YŘ{z~"oNF+duDh};z~:.Oצd5#U {{RӰ8r˖↭/q~c)'ogePu˫YPOg|UT"ҤnIiwgJՎKJQvȸq<zr$'BтI9&{uZ԰*b_YsD)^ Wl JKțQ#gh[Д O59+%Isooiٗ٬(PJ;2(}lcj+->e8;]Rkϻt|fZuK-J6%?GĦ{kmFx)!nOW[Y3Z^0֭Gi7zZ(W?QjWU~w>^m[H[֓Es xlQXopenMSX-RELEASE_0_12_0/build/package-windows/images/TopBanner.jpg000066400000000000000000000041741257557151200244660ustar00rootroot00000000000000JFIFddDucky<Adobed       :!1"AQqaBRr#23s1A!Qq"2R ?b󜫌`[k<[eROwu;BN//Z6{2FM{r]8bYY qfF,ZOxӻ">:I5kWbk,o_wf$YYgRn5d|mz?y[M F] Z9^siz~x՚kW9S6lxrI;F-Xut;S[}V]VW]1?9ob oY~.Is{+cIr'EmNsm-poXSظtlc]aldJBz9F;^ڝ٭m֥[wuR+ejJ6Gڕ:cj^S~ϖ1{|")ywX+mdFr+z+QjǢ/VQU8aNY6@F*Ev̞ 6y}R[=ۭuh'sQ{s)}4u._c.o$Hl"|3;#~ J8hMH*u񵭸W"z{Xƪxu"-mDN1Ԇ#-IJb361filXUU=i:iIĻM{|sc^-Gٌh'˯1Sɔ'_\,61De\UԍkZDNVoۿFM_?:vvs\y"ӐY)kGGQƬbUOm[E0< }N^RZr5٨SZ&raeGc26=վ6 ǫnԓcQjj/#[E02y}T>]ڦѧЧDs8dlJᓾ~NNsڭ%m okY_i{ܔ]QU#}wi~yOWVH;;mkvR$oJTǵk>ʗ|G\Vz)މFe';@־+NǣV맵Pwu^ޓm[7V8g?_..Ə|?Sc9gҷWF>˻xXƽ}E11䘌s_(v:_#VdC\:Tqr;^;YUX7Eҵb'|٬OeCzi&O)otƺY&[[ F| h6 |,5wM|W1jOުkĻZz^>aۯYopenMSX-RELEASE_0_12_0/build/package-windows/openmsx.wxs000066400000000000000000000274301257557151200230630ustar00rootroot00000000000000 NEWPRODUCTFOUND PREVIOUSVERSIONSINSTALLED = 500]]> 1 NOT Installed WixVariable Id="WixUILicenseRtf" Value="license1033.rtf" /--> openMSX-RELEASE_0_12_0/build/package-windows/openmsx1033.wxl000066400000000000000000000026651257557151200233660ustar00rootroot00000000000000 1033 openMSX The MSX emulator that aims for perfection http://openmsx.org/ openMSX Manual openMSX Website Uninstall openMSX Catapult Launcher and GUI for openMSX Catapult Manual Core Emulator Catapult UI ZMBV Video Codec Codec for viewing movies generated by openMSX This feature requires [4] on your hard drive. It has [2] of [3] subfeatures selected. Zip Motion Block Video [ZMBV] A newer version of openMSX is already installed openMSX is only supported on Windows 2000 and above openMSX-RELEASE_0_12_0/build/package-windows/package.cmd000066400000000000000000000021261257557151200227020ustar00rootroot00000000000000@echo off rem rem **** Run this from the top of the openMSX source tree: **** rem rem Usage: package.cmd OPENMSX_PLATFORM OPENMSX_CONFIGURATION CATAPULT_BASEPATH rem rem **** OPENMSX_PLATFORM is { Win32, x64 } **** rem **** OPENMSX_CONFIGURATION is { Release, Developer, Debug } **** rem **** CATAPULT_BASEPATH is an absolute or relative path; e.g. ..\wxCatapult **** if "%3" == "" goto usage if "%4" NEQ "" goto usage setlocal set OPENMSX_PLATFORM=%1 echo OPENMSX_PLATFORM is %OPENMSX_PLATFORM% set OPENMSX_CONFIGURATION=%2 echo OPENMSX_CONFIGURATION is %OPENMSX_CONFIGURATION% set CATAPULT_BASEPATH=%3 echo CATAPULT_BASEPATH is %CATAPULT_BASEPATH% set OPENMSX_PACKAGE_WINDOWS_PATH=.\build\package-windows set PYTHONPATH=%PYTHONPATH%;.\build python %OPENMSX_PACKAGE_WINDOWS_PATH%\packagezip.py %OPENMSX_PLATFORM% %OPENMSX_CONFIGURATION% %CATAPULT_BASEPATH% python %OPENMSX_PACKAGE_WINDOWS_PATH%\packagemsi.py %OPENMSX_PLATFORM% %OPENMSX_CONFIGURATION% %CATAPULT_BASEPATH% endlocal goto end :usage echo Usage: package.cmd platform configuration catapultPath :end openMSX-RELEASE_0_12_0/build/package-windows/packagemsi.py000066400000000000000000000166431257557151200233110ustar00rootroot00000000000000from harvest import generateWixFragment from packagewindows import ( PackageInfo, emptyOrCreateDirectory, generateInstallFiles ) from os import environ, mkdir, system, unlink from os.path import exists, join as joinpath from zipfile import ZIP_DEFLATED, ZipFile import sys def _writeFragment( wxsFile, sourcePath, componentGroup, directoryRef, virtualDir, excludedFile, win64 ): print 'Generating ' + wxsFile out = open(wxsFile, 'w') try: out.writelines( '%s\n' % line for line in generateWixFragment( sourcePath, componentGroup, directoryRef, virtualDir, excludedFile, win64 ) ) finally: out.close() def packageMSI(info): print 'Generating install files...' generateInstallFiles(info) wixIntermediatePath = joinpath(info.buildPath, 'build\\WiX') emptyOrCreateDirectory(wixIntermediatePath) if not exists(info.packagePath): mkdir(info.packagePath) print 'Generating fragments...' # openMSX files openMSXExeFile = joinpath(wixIntermediatePath, 'openmsxexe.wxs') openMSXExeObjFile = joinpath(wixIntermediatePath, 'openmsxexe.wixobj') sourcePath = joinpath(info.makeInstallPath, 'bin\\openmsx.exe') _writeFragment( openMSXExeFile, sourcePath, 'openMSXExe', 'OPENMSXINSTALLDIR', None, None, info.win64 ) openMSXDocFile = joinpath(wixIntermediatePath, 'openmsxdoc.wxs') openMSXDocObjFile = joinpath(wixIntermediatePath, 'openmsxdoc.wixobj') sourcePath = joinpath(info.makeInstallPath, 'doc') _writeFragment( openMSXDocFile, sourcePath, 'openMSXDoc', 'OPENMSXINSTALLDIR', 'doc', None, info.win64 ) openMSXShareFile = joinpath(wixIntermediatePath, 'openmsxshare.wxs') openMSXShareObjFile = joinpath(wixIntermediatePath, 'openmsxshare.wixobj') sourcePath = joinpath(info.makeInstallPath, 'share') _writeFragment( openMSXShareFile, sourcePath, 'openMSXShare', 'OPENMSXINSTALLDIR', 'share', None, info.win64 ) openMSXIconFile = joinpath(wixIntermediatePath, 'openmsxicon.wxs') openMSXIconObjFile = joinpath(wixIntermediatePath, 'openmsxicon.wixobj') sourcePath = joinpath(info.sourcePath, 'resource\\openmsx.ico') _writeFragment( openMSXIconFile, sourcePath, 'openMSXIcon', 'OPENMSXINSTALLDIR', 'share\\icons', None, info.win64 ) # ZMBV files ZMBVCodecFile = joinpath(wixIntermediatePath, 'zmbvcodec.wxs') ZMBVCodecObjFile = joinpath(wixIntermediatePath, 'zmbvcodec.wixobj') sourcePath = joinpath(info.codecPath, 'zmbv.dll') _writeFragment( ZMBVCodecFile, sourcePath, 'ZMBVCodec', 'SystemFolder', None, None, False ) ZMBVFilesFile = joinpath(wixIntermediatePath, 'zmbvfiles.wxs') ZMBVFilesObjFile = joinpath(wixIntermediatePath, 'zmbvfiles.wixobj') sourcePath = info.codecPath _writeFragment( ZMBVFilesFile, sourcePath, 'ZMBVFiles', 'OPENMSXINSTALLDIR', 'codec', 'zmbv.dll', info.win64 ) # Catapult files catapultBinFile = joinpath(wixIntermediatePath, 'catapultbin.wxs') catapultBinObjFile = joinpath(wixIntermediatePath, 'catapultbin.wixobj') sourcePath = joinpath(info.catapultBuildPath, 'install\\catapult.exe') _writeFragment( catapultBinFile, sourcePath, 'CatapultBin', 'OPENMSXINSTALLDIR', 'Catapult\\bin', None, info.win64 ) catapultDocFile = joinpath(wixIntermediatePath, 'catapultdoc.wxs') catapultDocObjFile = joinpath(wixIntermediatePath, 'catapultdoc.wixobj') sourcePath = joinpath(info.catapultPath, 'doc') _writeFragment( catapultDocFile, sourcePath, 'CatapultDoc', 'OPENMSXINSTALLDIR', 'Catapult\\doc', 'release-process.txt', info.win64 ) catapultBitmapsFile = joinpath(wixIntermediatePath, 'catapultbitmaps.wxs') catapultBitmapsObjFile = joinpath( wixIntermediatePath, 'catapultbitmaps.wixobj' ) sourcePath = joinpath(info.catapultPath, 'resources\\bitmaps') _writeFragment( catapultBitmapsFile, sourcePath, 'CatapultBitmaps', 'OPENMSXINSTALLDIR', 'Catapult\\resources\\bitmaps', 'release-process.txt', info.win64 ) catapultDialogsFile = joinpath(wixIntermediatePath, 'catapultdialogs.wxs') catapultDialogsObjFile = joinpath( wixIntermediatePath, 'catapultdialogs.wixobj' ) sourcePath = joinpath(info.catapultBuildPath, 'install\\dialogs') _writeFragment( catapultDialogsFile, sourcePath, 'CatapultDialogs', 'OPENMSXINSTALLDIR', 'Catapult\\resources\\dialogs', None, info.win64 ) catapultIconsFile = joinpath(wixIntermediatePath, 'catapulticons.wxs') catapultIconsObjFile = joinpath(wixIntermediatePath, 'catapulticons.wixobj') sourcePath = joinpath(info.catapultBuildPath, 'src\\catapult.xpm') _writeFragment( catapultIconsFile, sourcePath, 'CatapultIcons', 'OPENMSXINSTALLDIR', 'Catapult\\resources\\icons', None, info.win64 ) catapultReadmeFile = joinpath(wixIntermediatePath, 'catapultreadme.wxs') catapultReadmeObjFile = joinpath( wixIntermediatePath, 'catapultreadme.wixobj' ) sourcePath = joinpath(info.catapultPath, 'README') _writeFragment( catapultReadmeFile, sourcePath, 'CatapultReadme', 'OPENMSXINSTALLDIR', 'Catapult\\doc', None, info.win64 ) # Variables needed inside the WiX scripts: # OPENMSX_VERSION to tell it the product version # OPENMSX_ICON_PATH to locate the MSI's control panel icon # OPENMSX_PACKAGE_WINDOWS_PATH to locate the bmps used in the UI environ.update( OPENMSX_VERSION = info.version, OPENMSX_ICON_PATH = info.openmsxExePath, OPENMSX_PACKAGE_WINDOWS_PATH = info.packageWindowsPath, ) openMSXFile = joinpath(info.packageWindowsPath, 'openmsx.wxs') openMSXObjFile = joinpath(wixIntermediatePath, 'openmsx.wixobj') candleCmd = ' '.join(( 'candle.exe', '-arch %s' % info.cpu, '-o "%s\\\\"' % wixIntermediatePath, '-ext WixUtilExtension', '"%s"' % openMSXFile, '"%s"' % openMSXExeFile, '"%s"' % openMSXDocFile, '"%s"' % openMSXShareFile, '"%s"' % openMSXIconFile, '"%s"' % ZMBVCodecFile, '"%s"' % ZMBVFilesFile, '"%s"' % catapultBinFile, '"%s"' % catapultDocFile, '"%s"' % catapultBitmapsFile, '"%s"' % catapultDialogsFile, '"%s"' % catapultIconsFile, '"%s"' % catapultReadmeFile, )) # Run Candle print candleCmd system(candleCmd) msiFileName = info.packageFileName + '-bin.msi' msiFilePath = joinpath(info.packagePath, msiFileName) if exists(msiFilePath): unlink(msiFilePath) print 'Generating ' + msiFilePath lightCmd = ' '.join(( 'light.exe', '-o "%s"' % msiFilePath, '-sw1076', '-ext WixUtilExtension', '-ext WixUIExtension', '-loc "%s"' % joinpath(info.packageWindowsPath, 'openmsx1033.wxl'), '"%s"' % openMSXObjFile, '"%s"' % openMSXExeObjFile, '"%s"' % openMSXDocObjFile, '"%s"' % openMSXShareObjFile, '"%s"' % openMSXIconObjFile, '"%s"' % ZMBVCodecObjFile, '"%s"' % ZMBVFilesObjFile, '"%s"' % catapultBinObjFile, '"%s"' % catapultDocObjFile, '"%s"' % catapultBitmapsObjFile, '"%s"' % catapultDialogsObjFile, '"%s"' % catapultIconsObjFile, '"%s"' % catapultReadmeObjFile, )) # Run Light print lightCmd system(lightCmd) # Zip up the MSI zipFileName = info.packageFileName + '-bin-msi.zip' zipFilePath = joinpath(info.packagePath, zipFileName) print 'Generating ' + zipFilePath zipFile = ZipFile(zipFilePath, 'w') zipFile.write(msiFilePath, msiFileName, ZIP_DEFLATED) zipFile.close() if __name__ == '__main__': if len(sys.argv) == 4: packageMSI(PackageInfo(*sys.argv[1 : ])) else: print >> sys.stderr, 'Usage: python packagemsi.py ' \ 'platform configuration catapultPath' sys.exit(2) openMSX-RELEASE_0_12_0/build/package-windows/packagewindows.py000066400000000000000000000063421257557151200242060ustar00rootroot00000000000000from install import installAll from version import ( extractRevisionNumber, getVersionedPackageName, packageVersionNumber, releaseFlag ) from os import makedirs, remove, rmdir, sep, walk from os.path import exists, join as joinpath import sys def emptyOrCreateDirectory(top): if exists(top): _emptyDirectory(top) else: makedirs(top) def _emptyDirectory(top): for root, dirs, files in walk(top, topdown = False): for name in files: remove(joinpath(root, name)) for name in dirs: rmdir(joinpath(root, name)) def generateInstallFiles(info): emptyOrCreateDirectory(info.makeInstallPath) installAll( info.makeInstallPath + sep, 'bin', 'share', 'doc', info.openmsxExePath, 'mingw32', True, True ) class PackageInfo(object): def __init__(self, platform, configuration, catapultPath): self.platform = platform.lower() if self.platform == 'win32': self.cpu = 'x86' self.platform = 'Win32' self.win64 = False elif self.platform == 'x64': self.cpu = 'x64' self.platform = 'x64' self.win64 = True else: raise ValueError('Wrong platform: ' + platform) self.configuration = configuration.lower() if self.configuration == 'release': self.configuration = 'Release' self.catapultConfiguration = 'Unicode Release' elif self.configuration == 'developer': self.configuration = 'Developer' self.catapultConfiguration = 'Unicode Debug' elif self.configuration == 'debug': self.configuration = 'Debug' self.catapultConfiguration = 'Unicode Debug' else: raise ValueError('Wrong configuration: ' + configuration) self.catapultPath = catapultPath # Useful variables self.buildFlavor = self.platform + '-VC-' + self.configuration self.buildPath = joinpath('derived', self.buildFlavor) self.sourcePath = 'src' self.codecPath = 'Contrib\\codec\\Win32' self.packageWindowsPath = 'build\\package-windows' self.catapultSourcePath = joinpath(self.catapultPath, 'src') self.catapultBuildFlavor = '%s-VC-%s' % ( self.platform, self.catapultConfiguration ) self.catapultBuildPath = joinpath( self.catapultPath, joinpath('derived', self.catapultBuildFlavor) ) self.catapultExePath = joinpath( self.catapultBuildPath, 'install\\catapult.exe' ) self.catapultPdbPath = joinpath( self.catapultBuildPath, 'install\\catapult.pdb' ) self.openmsxExePath = joinpath(self.buildPath, 'install\\openmsx.exe') self.openmsxPdbPath = joinpath(self.buildPath, 'install\\openmsx.pdb') self.packagePath = joinpath(self.buildPath, 'package-windows') self.makeInstallPath = joinpath(self.packagePath, 'install') self.version = packageVersionNumber if releaseFlag: self.version += '.0' else: self.version += '.%d' % extractRevisionNumber() # -----.ext self.os = 'windows' self.compiler = 'vc' self.packageFileName = '-'.join(( getVersionedPackageName(), self.os, self.compiler, self.cpu )) if __name__ == '__main__': if len(sys.argv) == 4: PackageInfo(*sys.argv[1 : ]) else: print >> sys.stderr, \ 'Usage: python packagewindows.py ' \ 'platform configuration catapultPath' sys.exit(2) openMSX-RELEASE_0_12_0/build/package-windows/packagezip.py000066400000000000000000000052261257557151200233160ustar00rootroot00000000000000from packagewindows import PackageInfo, generateInstallFiles from os.path import abspath, basename, exists, join as joinpath, relpath from zipfile import ZIP_DEFLATED, ZipFile import os, sys def addFile(zipFile, path, zipPath): print 'Adding ' + path zipFile.write(path, zipPath, ZIP_DEFLATED) def addDirectory(zipFile, root, zipPath): for path, dirs, files in os.walk(root): if '.svn' in dirs: dirs.remove('.svn') # don't visit .svn directories for name in files: thisZipPath = zipPath if abspath(root) != abspath(path): thisZipPath = joinpath(thisZipPath, relpath(path, root)) addFile(zipFile, joinpath(path, name), joinpath(thisZipPath, name)) def packageZip(info): print 'Generating install files...' generateInstallFiles(info) if not exists(info.packagePath): os.mkdir(info.packagePath) zipFileName = info.packageFileName + '-bin.zip' zipFilePath = joinpath(info.packagePath, zipFileName) if exists(zipFilePath): os.unlink(zipFilePath) print 'Generating ' + zipFilePath zipFile = ZipFile(zipFilePath, 'w') addDirectory(zipFile, joinpath(info.makeInstallPath, 'doc'), 'doc') addDirectory(zipFile, joinpath(info.makeInstallPath, 'share'), 'share') addDirectory(zipFile, info.codecPath, 'codec') addFile(zipFile, info.openmsxExePath, basename(info.openmsxExePath)) addFile( zipFile, joinpath(info.sourcePath, 'resource\\openmsx.ico'), 'share\\icons\\openmsx.ico' ) addFile(zipFile, info.catapultExePath, 'Catapult\\bin\\Catapult.exe') addDirectory(zipFile, joinpath(info.catapultPath, 'doc'), 'Catapult\\doc') addDirectory( zipFile, joinpath(info.catapultPath, 'resources\\bitmaps'), 'Catapult\\resources\\bitmaps' ) addDirectory( zipFile, joinpath(info.catapultBuildPath, 'install\\dialogs'), 'Catapult\\resources\\dialogs' ) addFile( zipFile, joinpath(info.catapultSourcePath, 'catapult.xpm'), 'Catapult\\resources\\icons\\catapult.xpm' ) addFile( zipFile, joinpath(info.catapultPath, 'README'), 'Catapult\\doc\\README' ) zipFile.close() zipFileName = info.packageFileName + '-pdb.zip' zipFilePath = joinpath(info.packagePath, zipFileName) if exists(zipFilePath): os.unlink(zipFilePath) print 'Generating ' + zipFilePath zipFile = ZipFile(zipFilePath, 'w') addFile(zipFile, info.openmsxPdbPath, basename(info.openmsxPdbPath)) addFile(zipFile, info.catapultPdbPath, basename(info.catapultPdbPath)) zipFile.close() if __name__ == '__main__': if len(sys.argv) == 4: packageZip(PackageInfo(*sys.argv[1 : ])) else: print >> sys.stderr, 'Usage: python packagezip.py ' \ 'platform configuration catapultPath' sys.exit(2) openMSX-RELEASE_0_12_0/build/package-windows/vs_menu.py000066400000000000000000000047541257557151200226610ustar00rootroot00000000000000import msvcrt import os #set standard settings (No XP support/64Bit version) compileXP = 0 #XP support 0 = off/1 = on version = 64 #32 = win32/64 = x64 version quit = 0 #Loop variable keep at 0 #Some standard strings location='C:\\Program Files (x86)\\Microsoft Visual Studio 11.0\\VC\\' #location of Visual Studio 2012 (express) VC files title=' openMSX build menu for Visual Studio 2012' menuval = { '[1] Update from Repository' ,'[2] Get 3rd Party Files\n' ,'[3] Compile 3rd-party files' ,'[4] Compile openMSX Release' ,'[5] Generate Installer and Packages\n' ,'[6] Toggle Win32/Win64 Version' ,'[7] Toggle Toggle XP Support\n' ,'[8] Quit\n' } def menu(): os.system('color 9f') os.system('mode 80,24') os.system('TITLE'+title) os.system('cls') print '-'*50 print title print '-'*50+'\n' for item in sorted(menuval): print item print '-'*50 print ' Resulting Binary settings:' if version == 32: print ' - Windows 32bit Version' else: print ' - Windows 64bit Version' if compileXP == 1:print ' - XP Support ON' else: print ' - XP Support Off' print '-'*50 print if compileXP == 1: print('The DirectX June 2010 SDK is needed for XP support') if quit == 1: os.system('cls') def execCommand(cmd): os.system('cls') os.chdir('..\\..\\') os.system(cmd) os.chdir('build\\package-windows') os.system('pause') def updateSourceCode(): execCommand('git pull') def update3rdparty(): execCommand('call "python" build\\thirdparty_download.py windows') def compile(whattocompile): compiler = 'x86' if version == 32 else 'x86_amd64' support = 'v110' if compileXP == 0 else 'v110_xp' compilefor = 'Win32' if version == 32 else 'x64' compilewhat = 'build\\msvc\\openmsx.sln' if whattocompile == 'OpenMSX' else 'build\\3rdparty\\3rdparty.sln' cmd = 'call "'+location+'vcvarsall.bat" '+str(compiler)+'&&' cmd += 'set PlatformToolset='+str(support)+'&&' cmd += 'msbuild -p:Configuration=Release;Platform='+str(compilefor)+' '+compilewhat+' /m' execCommand(cmd) def package(): packagefor = 'Win32' if version == 32 else 'x64' execCommand('build\\package-windows\\package.cmd '+packagefor+' release ..\\wxcatapult') # Main Loop menu() while quit == 0: x = ord(msvcrt.getch()) if x == 49: updateSourceCode() if x == 50: update3rdparty() if x == 51: compile('3rdParty') if x == 52: compile('OpenMSX') if x == 53: package() if x == 54: version = 32 if version == 64 else 64 if x == 55: compileXP = 0 if compileXP == 1 else 1 if x == 56: quit = 1 menu() openMSX-RELEASE_0_12_0/build/package-windows/vs_menu2013.py000066400000000000000000000047541257557151200231670ustar00rootroot00000000000000import msvcrt import os #set standard settings (No XP support/64Bit version) compileXP = 0 #XP support 0 = off/1 = on version = 64 #32 = win32/64 = x64 version quit = 0 #Loop variable keep at 0 #Some standard strings location='C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\' #location of Visual Studio 2012 (express) VC files title=' openMSX build menu for Visual Studio 2013' menuval = { '[1] Update from Repository' ,'[2] Get 3rd Party Files\n' ,'[3] Compile 3rd-party files' ,'[4] Compile openMSX Release' ,'[5] Generate Installer and Packages\n' ,'[6] Toggle Win32/Win64 Version' ,'[7] Toggle Toggle XP Support\n' ,'[8] Quit\n' } def menu(): os.system('color 9f') os.system('mode 80,24') os.system('TITLE'+title) os.system('cls') print '-'*50 print title print '-'*50+'\n' for item in sorted(menuval): print item print '-'*50 print ' Resulting Binary settings:' if version == 32: print ' - Windows 32bit Version' else: print ' - Windows 64bit Version' if compileXP == 1:print ' - XP Support ON' else: print ' - XP Support Off' print '-'*50 print if compileXP == 1: print('The DirectX June 2010 SDK is needed for XP support') if quit == 1: os.system('cls') def execCommand(cmd): os.system('cls') os.chdir('..\\..\\') os.system(cmd) os.chdir('build\\package-windows') os.system('pause') def updateSourceCode(): execCommand('git pull') def update3rdparty(): execCommand('call "python" build\\thirdparty_download.py windows') def compile(whattocompile): compiler = 'x86' if version == 32 else 'x86_amd64' support = 'v120' if compileXP == 0 else 'v120_xp' compilefor = 'Win32' if version == 32 else 'x64' compilewhat = 'build\\msvc\\openmsx.sln' if whattocompile == 'OpenMSX' else 'build\\3rdparty\\3rdparty.sln' cmd = 'call "'+location+'vcvarsall.bat" '+str(compiler)+'&&' cmd += 'set PlatformToolset='+str(support)+'&&' cmd += 'msbuild -p:Configuration=Release;Platform='+str(compilefor)+' '+compilewhat+' /m' execCommand(cmd) def package(): packagefor = 'Win32' if version == 32 else 'x64' execCommand('build\\package-windows\\package.cmd '+packagefor+' release ..\\wxcatapult') # Main Loop menu() while quit == 0: x = ord(msvcrt.getch()) if x == 49: updateSourceCode() if x == 50: update3rdparty() if x == 51: compile('3rdParty') if x == 52: compile('OpenMSX') if x == 53: package() if x == 54: version = 32 if version == 64 else 64 if x == 55: compileXP = 0 if compileXP == 1 else 1 if x == 56: quit = 1 menu() openMSX-RELEASE_0_12_0/build/packages.py000066400000000000000000000134651257557151200176770ustar00rootroot00000000000000from urlparse import urljoin class Package(object): '''Abstract base class for packages. ''' niceName = None sourceName = None @classmethod def getMakeName(cls): return cls.sourceName.upper() class DownloadablePackage(Package): '''Abstract base class for packages that can be downloaded. ''' downloadURL = None version = None fileLength = None checksums = None @classmethod def getSourceDirName(cls): '''Returns the desired name of the top-level source directory. This might not match the actual name inside the downloaded archive, but we can perform a rename on extraction to fix that. ''' return '%s-%s' % (cls.sourceName, cls.version) @classmethod def getTarballName(cls): return '%s-%s.tar.gz' % (cls.sourceName, cls.version) @classmethod def getURL(cls): return urljoin(cls.downloadURL + '/', cls.getTarballName()) class DirectX(DownloadablePackage): downloadURL = 'http://alleg.sourceforge.net/files' niceName = 'DirectX' sourceName = 'dx' version = '70' fileLength = 236675 checksums = { 'sha256': '59f489a7d9f51c70fe37fbb5a6225d4716a97ab774c58138f1dc4661a80356f0', } @classmethod def getMakeName(cls): return 'DIRECTX' @classmethod def getSourceDirName(cls): # Note: The tarball does not contain a source dir. # We only redefine this to keep the name of the install # timestamp the same. return '%s%s' % (cls.sourceName, cls.version) @classmethod def getTarballName(cls): return '%s%s_mgw.tar.gz' % (cls.sourceName, cls.version) class FreeType(DownloadablePackage): downloadURL = 'http://downloads.sourceforge.net/freetype' niceName = 'FreeType' sourceName = 'freetype' version = '2.4.12' fileLength = 2117909 checksums = { 'sha256': '9755806ff72cba095aad47dce6f0ad66bd60fee2a90323707d2cac5c526066f0', } class GLEW(DownloadablePackage): downloadURL = 'http://downloads.sourceforge.net/glew' niceName = 'GLEW' sourceName = 'glew' version = '1.9.0' fileLength = 544440 checksums = { 'sha256': '9b36530e414c95d6624be9d6815a5be1531d1986300ae5903f16977ab8aeb787', } @classmethod def getTarballName(cls): return '%s-%s.tgz' % (cls.sourceName, cls.version) class LibPNG(DownloadablePackage): downloadURL = 'http://downloads.sourceforge.net/libpng' niceName = 'libpng' sourceName = 'libpng' version = '1.2.50' fileLength = 826893 checksums = { 'sha256': '19f17cd49782fcec8df0f7d1b348448cc3f69ed7e2a59de24bc0907b907f1abc', } @classmethod def getMakeName(cls): return 'PNG' class OGG(DownloadablePackage): downloadURL = 'http://downloads.xiph.org/releases/ogg' niceName = 'libogg' sourceName = 'libogg' version = '1.3.0' fileLength = 425144 checksums = { 'sha256': 'a8de807631014615549d2356fd36641833b8288221cea214f8a72750efe93780', } @classmethod def getMakeName(cls): return 'OGG' class OpenGL(Package): niceName = 'OpenGL' sourceName = 'gl' class SDL(DownloadablePackage): downloadURL = 'http://www.libsdl.org/release' niceName = 'SDL' sourceName = 'SDL' version = '1.2.15' fileLength = 3920622 checksums = { 'sha256': 'd6d316a793e5e348155f0dd93b979798933fb98aa1edebcc108829d6474aad00', } class SDL_ttf(DownloadablePackage): downloadURL = 'http://www.libsdl.org/projects/SDL_ttf/release' niceName = 'SDL_ttf' sourceName = 'SDL_ttf' version = '2.0.11' fileLength = 4053686 checksums = { 'sha256': '724cd895ecf4da319a3ef164892b72078bd92632a5d812111261cde248ebcdb7', } class TCL_ANDROID(DownloadablePackage): downloadURL = 'http://downloads.sourceforge.net/tcl' niceName = 'Tcl' sourceName = 'tcl' version = '8.5.11' fileLength = 4484001 checksums = { 'sha256': '8addc385fa6b5be4605e6d68fbdc4c0e674c5af1dc1c95ec5420390c4b08042a', } @classmethod def getMakeName(cls): return 'TCL_ANDROID' @classmethod def getSourceDirName(cls): return '%s%s' % (cls.sourceName, cls.version) @classmethod def getTarballName(cls): return '%s%s-src.tar.gz' % (cls.sourceName, cls.version) class TCL(DownloadablePackage): downloadURL = 'http://downloads.sourceforge.net/tcl' niceName = 'Tcl' sourceName = 'tcl' version = '8.5.15' fileLength = 4536117 checksums = { 'sha256': 'f24eaae461795e6b09bf54c7e9f38def025892da55f26008c16413cfdda2884e', } @classmethod def getSourceDirName(cls): return '%s%s' % (cls.sourceName, cls.version) @classmethod def getTarballName(cls): return '%s%s-src.tar.gz' % (cls.sourceName, cls.version) class Theora(DownloadablePackage): downloadURL = 'http://downloads.xiph.org/releases/theora' niceName = 'libtheora' sourceName = 'libtheora' version = '1.1.1' fileLength = 2111877 checksums = { 'sha256': '40952956c47811928d1e7922cda3bc1f427eb75680c3c37249c91e949054916b', } @classmethod def getMakeName(cls): return 'THEORA' class Vorbis(DownloadablePackage): downloadURL = 'http://downloads.xiph.org/releases/vorbis' niceName = 'libvorbis' sourceName = 'libvorbis' version = '1.3.3' fileLength = 1592663 checksums = { 'sha256': '6d747efe7ac4ad249bf711527882cef79fb61d9194c45b5ca5498aa60f290762', } @classmethod def getMakeName(cls): return 'VORBIS' class ZLib(DownloadablePackage): downloadURL = 'http://downloads.sourceforge.net/libpng' niceName = 'zlib' sourceName = 'zlib' version = '1.2.8' fileLength = 571091 checksums = { 'sha256': '36658cb768a54c1d4dec43c3116c27ed893e88b02ecfcb44f2166f9c0b7f2a0d', } # Build a dictionary of packages using introspection. def _discoverPackages(localObjects): for obj in localObjects: if isinstance(obj, type) and issubclass(obj, Package): if not (obj is Package or obj is DownloadablePackage): yield obj.getMakeName(), obj _packagesByName = dict(_discoverPackages(locals().itervalues())) def getPackage(makeName): return _packagesByName[makeName] def iterDownloadablePackages(): for package in _packagesByName.itervalues(): if issubclass(package, DownloadablePackage): yield package openMSX-RELEASE_0_12_0/build/patch.py000066400000000000000000000224121257557151200172100ustar00rootroot00000000000000# Applies a unified diff to a directory tree. from os.path import abspath, isdir, join as joinpath, sep import re import sys class LineScanner(object): def __init__(self, stream, lineFilter = None): '''Scans the given stream. Any object providing readline() can be passed as stream, such as file objects. The line scanner does not close the stream. The optional line filter is a function that will be called for every line read from the stream; iff the filter returns False, the line is skipped. This can be useful for example for skipping comment lines. ''' if not hasattr(stream, 'readline'): raise TypeError( 'Invalid type for stream: %s' % type(stream).__name__ ) self.__stream = stream self.__filter = lineFilter self.__currLine = None self.__lineNo = 0 self.next() def end(self): '''Returns True iff the end of the stream has been reached. ''' return self.__currLine is None def peek(self): '''Returns the current line. Like readline(), the returned string includes the newline characteter if it is present in the stream. ''' return self.__currLine def next(self): '''Moves on to the next line. Raises IOError if there is a problem reading from the stream. ''' stream = self.__stream lineFilter = self.__filter while True: line = stream.readline() self.__lineNo += 1 if line: if lineFilter is None or lineFilter(line): break else: line = None break self.__currLine = line def getLineNumber(self): '''Returns the line number of the current line. The first line read from the stream is considered line 1. ''' return self.__lineNo def parseError(self, msg, lineNo = None): '''Returns a ParseError object with a descriptive message. Raising the exception is left to the caller, to make sure the backtrace is meaningful. If a line number is given, that line number is used in the message, otherwise the current line number is used. ''' stream = self.__stream if lineNo is None: lineNo = self.getLineNumber() return ParseError( lineNo, 'Error parsing %s at line %d: %s' % ( '"%s"' % stream.name if hasattr(stream, 'name') else 'stream', lineNo, msg ) ) class ParseError(Exception): def __init__(self, lineNo, msg): Exception.__init__(self, msg) self.lineNo = lineNo class _Change(object): oldInc = None newInc = None action = None def __init__(self, content): self.content = content def __str__(self): return self.action + self.content.rstrip() class _Context(_Change): oldInc = 1 newInc = 1 action = ' ' class _Add(_Change): oldInc = 0 newInc = 1 action = '+' class _Remove(_Change): oldInc = 1 newInc = 0 action = '-' class Hunk(object): '''Contains the differences between two versions of a single section within a file. ''' reDeltaLine = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@') changeClasses = dict( (cc.action, cc) for cc in (_Context, _Add, _Remove) ) @classmethod def parse(cls, scanner): delta = cls.reDeltaLine.match(scanner.peek()) if delta is None: raise scanner.parseError('invalid delta line') oldLine, oldLen, newLine, newLen = ( int(n) for n in delta.groups() ) deltaLineNo = scanner.getLineNumber() scanner.next() def lineCountMismatch(oldOrNew, declared, actual): return scanner.parseError( 'hunk %s size mismatch: %d declared, %d actual' % ( oldOrNew, declared, actual ), deltaLineNo ) def iterChanges(): oldCount = 0 newCount = 0 while not scanner.end(): line = scanner.peek() if (line.startswith('---') or line.startswith('+++')) \ and oldCount == oldLen and newCount == newLen: # Hunks for a new file start here. # Since a change line could start with "---" or "+++" # as well we also have to check whether we are at the # declared end of this hunk. break if len(line) == 1: # Assume this is an empty context line that had its single # space character removed. line = ' ' changeClass = cls.changeClasses.get(line[0]) if changeClass is None: break content = line[1 : ] scanner.next() if not scanner.end() and scanner.peek().startswith('\\'): # No newline at end of file. assert content[-1] == '\n' content = content[ : -1] scanner.next() change = changeClass(content) yield change oldCount += change.oldInc newCount += change.newInc if oldCount != oldLen: raise lineCountMismatch('old', oldLen, oldCount) if newCount != newLen: raise lineCountMismatch('new', newLen, newCount) return cls(oldLine, newLine, iterChanges()) def __init__(self, oldLine, newLine, changes): self.__oldLine = oldLine self.__newLine = newLine self.__changes = tuple(changes) def __str__(self): return 'hunk(old=%d,new=%d)' % (self.__oldLine, self.__newLine) def getOldLine(self): return self.__oldLine def getNewLine(self): return self.__newLine def iterChanges(self): return iter(self.__changes) class Diff(object): '''Contains the differences between two versions of a single file. ''' @classmethod def load(cls, path): '''Iterates through the differences contained in the given diff file. Only the unified diff format is supported. Each element returned is a Diff object containing the differences to a single file. ''' inp = open(path, 'r') try: scanner = LineScanner(inp, lambda line: not line.startswith('#')) error = scanner.parseError def parseHunks(): while not scanner.end(): if scanner.peek().startswith('@@'): yield Hunk.parse(scanner) else: break while not scanner.end(): if scanner.peek().startswith('diff '): scanner.next() diffLineNo = scanner.getLineNumber() if not scanner.peek().startswith('--- '): raise error('"---" expected (not a unified diff?)') scanner.next() newFileLine = scanner.peek() if not newFileLine.startswith('+++ '): raise error('"+++" expected (not a unified diff?)') index = 4 length = len(newFileLine) while index < length and not newFileLine[index].isspace(): index += 1 filePath = newFileLine[4 : index] scanner.next() try: yield cls(filePath, parseHunks()) except ValueError, ex: raise error('inconsistent hunks: %s' % ex, diffLineNo) finally: inp.close() def __init__(self, path, hunks): self.__path = path self.__hunks = sorted(hunks, key = lambda hunk: hunk.getOldLine()) # Sanity check on line numbers. offset = 0 for hunk in self.__hunks: declaredOffset = hunk.getNewLine() - hunk.getOldLine() if offset != declaredOffset: raise ValueError( 'hunk offset mismatch: %d declared, %d actual' % ( declaredOffset, offset ) ) offset += sum( change.newInc - change.oldInc for change in hunk.iterChanges() ) def __str__(self): return 'diff of %d hunks to "%s"' % (len(self.__hunks), self.__path) def getPath(self): return self.__path def iterHunks(self): '''Iterates through the hunks in this diff in order of increasing old line numbers. ''' return iter(self.__hunks) def patch(diff, targetDir): '''Applies the given Diff to the given target directory. No fuzzy matching is done: if the diff does not apply exactly, ValueError is raised. ''' absTargetDir = abspath(targetDir) + sep absFilePath = abspath(joinpath(absTargetDir, diff.getPath())) if not absFilePath.startswith(absTargetDir): raise ValueError( 'Refusing to patch file "%s" outside destination directory' % diff.getPath() ) # Read entire file into memory. # The largest file we expect to patch is the "configure" script, which is # typically about 1MB. inp = open(absFilePath, 'r') try: lines = inp.readlines() finally: inp.close() for hunk in diff.iterHunks(): # We will be modifying "lines" at index "newLine", while "oldLine" is # only used to produce error messages if there is a context or remove # mismatch. As a result, "newLine" is 0-based and "oldLine" is 1-based. oldLine = hunk.getOldLine() newLine = hunk.getNewLine() - 1 for change in hunk.iterChanges(): if change.oldInc == 0: lines.insert(newLine, change.content) else: assert change.oldInc == 1 if change.content.rstrip() != lines[newLine].rstrip(): raise ValueError('mismatch at line %d' % oldLine) if change.newInc == 0: del lines[newLine] oldLine += change.oldInc newLine += change.newInc out = open(absFilePath, 'w') try: out.writelines(lines) finally: out.close() def main(diffPath, targetDir): try: differences = list(Diff.load(diffPath)) except IOError, ex: print >> sys.stderr, 'Error reading diff:', ex sys.exit(1) except ParseError, ex: print >> sys.stderr, ex sys.exit(1) if not isdir(targetDir): print >> sys.stderr, \ 'Destination directory "%s" does not exist' % targetDir sys.exit(1) for diff in differences: targetPath = joinpath(targetDir, diff.getPath()) try: patch(diff, targetDir) except IOError, ex: print >> sys.stderr, 'I/O error patching "%s": %s' % ( targetPath, ex ) sys.exit(1) except ValueError, ex: print >> sys.stderr, 'Patch could not be applied to "%s": %s' % ( targetPath, ex ) sys.exit(1) else: print 'Patched:', targetPath if __name__ == '__main__': if len(sys.argv) == 3: main(*sys.argv[1 : ]) else: print >> sys.stderr, \ 'Usage: python patch.py diff target' sys.exit(2) openMSX-RELEASE_0_12_0/build/platform-android.mk000066400000000000000000000014561257557151200213370ustar00rootroot00000000000000# Configuration for Android, for ARM. # It *must* be called from Android SDL port build system. Toolchain params # like CXX and CXXFLAGS have been properly set-up by the SDL port build system # before invoking this make file # ifeq ($(origin ANDROID_CXXFLAGS),undefined) $(error Android build can only be invoked from SDL Android port build system. See compile.html for more details) endif # Does platform require symlinks? (it is used to link the openMSX executable # from a location inside the $PATH, which means it is not applicable for # Android platform)) USE_SYMLINK:=false # For Android, a shared library must eventually be build EXEEXT:= LIBRARYEXT:=.so TARGET_FLAGS:=$(ANDROID_LDFLAGS) # Build a maximum set of components. # See configure.py for LINK_MODE definition and usage LINK_MODE:=3RD_STA_GLES openMSX-RELEASE_0_12_0/build/platform-darwin.mk000066400000000000000000000023031257557151200211730ustar00rootroot00000000000000# Configuration for creating a Darwin app folder. # In practice, this is used for Mac OS X; I'm not sure all of it applies to # other Darwin-based systems. # Does platform support symlinks? USE_SYMLINK:=true # The app folder will set a hi-res icon, so the openMSX process should not # replace this with its own low-res icon. SET_WINDOW_ICON:=false # Compile for the selected CPU. ifeq ($(OPENMSX_TARGET_CPU),x86) TARGET_FLAGS+=-arch i386 else TARGET_FLAGS+=-arch $(OPENMSX_TARGET_CPU) endif # File name extension of executables. EXEEXT:= LIBRARYEXT:=.so # Select the OS X version we want to be compatible with. # In theory it is possible to compile against an OS X version number lower # than the SDK version number, but in practice this doesn't seem to work # since libraries such as libxml2 can change soname between OS X versions. # Clang as shipped with Xcode requires OS X 10.7 or higher for compiling with # libc++, when compiling Clang and libc++ from source 10.6 works as well. OSX_VER:=10.7 TARGET_FLAGS+=-mmacosx-version-min=$(OSX_VER) # Select Clang as the compiler and libc++ as the standard library. CXX:=clang++ TARGET_FLAGS+=-stdlib=libc++ # Link against CoreMIDI. LINK_FLAGS+=-framework CoreMIDI openMSX-RELEASE_0_12_0/build/platform-dingux.mk000066400000000000000000000012441257557151200212100ustar00rootroot00000000000000# Configuration for Dingux: Linux for Dingoo A320. # Set CXX before including platform-linux.mk (see comments in platform-linux.mk) ifeq ($(OPENMSX_TARGET_CPU),mipsel) # Automatically select the cross compiler from its default location. ifeq ($(origin CXX),default) CXX:=/opt/a320-toolchain/usr/bin/mipsel-linux-g++ endif # Use MIPS32 instruction set. TARGET_FLAGS+=-march=mips32 endif # Dingux is a Linux/uClibc system. include build/platform-linux.mk # Allow GMenu to identify our binary as an executable. # Since the file system is FAT this cannot be done with a permission flag. EXEEXT:=.dge LIBRARYEXT:=.so # Build a minimal set of components. LINK_MODE:=3RD_STA_MIN openMSX-RELEASE_0_12_0/build/platform-freebsd.mk000066400000000000000000000002761257557151200213300ustar00rootroot00000000000000# Configuration for FreeBSD. # Does platform support symlinks? USE_SYMLINK:=true # File name extension of executables. EXEEXT:= LIBRARYEXT:=.so COMPILE_FLAGS+=-D_REENTRANT -D_THREAD_SAFE openMSX-RELEASE_0_12_0/build/platform-gnu.mk000066400000000000000000000002711257557151200205020ustar00rootroot00000000000000# Configuration for Debian GNU systems on various kernels (FreeBSD, Hurd). # This should be the same as compiling for a GNU system with a Linux kernel: include build/platform-linux.mk openMSX-RELEASE_0_12_0/build/platform-linux.mk000066400000000000000000000011461257557151200210520ustar00rootroot00000000000000# Configuration for Linux machines. # Does platform support symlinks? USE_SYMLINK:=true # File name extension of executables. EXEEXT:= LIBRARYEXT:=.so # In glibc the function clock_gettime() is defined in the librt library. In # uClibc it's defined in libc itself. We should write a function that actually # tests which library defines it. But as a temporary workaround/hack we figure # out the linker flags based on the occurrence of the substring uclibc inside # the compiler executable name (e.g. for Dingoo we don't need to link against # librt). ifeq (,$(findstring uclibc,$(CXX))) LINK_FLAGS:=-lrt endif openMSX-RELEASE_0_12_0/build/platform-maemo5.mk000066400000000000000000000011431257557151200210730ustar00rootroot00000000000000# Configuration for Maemo 5 devices. # Maemo is based on Linux. include build/platform-linux.mk # Workaround for SDL bug 586: the Matchbox2 window manager will not give input # events to our window if we set an icon, because SDL sets the wrong flags on # the icon. # http://bugzilla.libsdl.org/show_bug.cgi?id=586 # http://talk.maemo.org/showthread.php?t=31696 # This bug is fixed in SDL 1.2.14 (at least, I verified that the patch has been # applied), but Maemo 5 is using an older SDL. SET_WINDOW_ICON:=false # Link to X11 lib, Needed for the code that configures window composition. LINK_FLAGS+=-lX11 openMSX-RELEASE_0_12_0/build/platform-mingw-w64.mk000066400000000000000000000012651257557151200214540ustar00rootroot00000000000000# Configuration for MinGW-w64. # Don't be fooled by the name: it can compile to both 32-bit and 64-bit Windows. # http://mingw-w64.sourceforge.net/ include build/platform-mingw32.mk # File name extension of executables.... AGAIN. # Workaround because the makevar parsing doesn't do includes EXEEXT:=.exe ifeq ($(OPENMSX_TARGET_CPU),x86) MINGW_CPU:=i686 else MINGW_CPU:=$(OPENMSX_TARGET_CPU) endif CXX:=$(MINGW_CPU)-w64-mingw32-g++ # Native windres is not prefixed; just use default from main.mk there. ifeq ($(filter MINGW%,$(shell uname -s)),) WINDRES?=$(MINGW_CPU)-w64-mingw32-windres endif # make sure the threading lib is also included in the exe LINK_FLAGS:= -static $(LINK_FLAGS) openMSX-RELEASE_0_12_0/build/platform-mingw32.mk000066400000000000000000000007501257557151200212010ustar00rootroot00000000000000# Configuration for MinGW on x86 machines. # Does platform support symlinks? USE_SYMLINK:=false # File name extension of executables. EXEEXT:=.exe LIBRARYEXT:=.dll # Compiler flags. COMPILE_FLAGS+= \ -mthreads -mms-bitfields \ -I/mingw/include -I/mingw/include/w32api \ -D__GTHREAD_HIDE_WIN32API \ -DFS_CASEINSENSE # Linker flags. LINK_FLAGS:= \ -L/mingw/lib -L/mingw/lib/w32api -lwsock32 -lwinmm -ldsound -lsecur32 \ -mconsole -static-libgcc -static-libstdc++ \ $(LINK_FLAGS) openMSX-RELEASE_0_12_0/build/platform-nacl.mk000066400000000000000000000005721257557151200206320ustar00rootroot00000000000000# Configuration for Chrome Native Client (NaCl). # Pick the right compiler. ifeq ($(OPENMSX_TARGET_CPU),x86) NACL_CPU:=i686 else NACL_CPU:=$(OPENMSX_TARGET_CPU) endif CXX:=$(NACL_CPU)-nacl-g++ # Does platform support symlinks? USE_SYMLINK:=false # File name extension of executables. EXEEXT:=.nexe LIBRARYEXT:=.so # Build a minimal set of components. LINK_MODE:=3RD_STA_MIN openMSX-RELEASE_0_12_0/build/platform-netbsd.mk000066400000000000000000000002751257557151200211740ustar00rootroot00000000000000# Configuration for NetBSD. # Does platform support symlinks? USE_SYMLINK:=true # File name extension of executables. EXEEXT:= LIBRARYEXT:=.so COMPILE_FLAGS+=-D_REENTRANT -D_THREAD_SAFE openMSX-RELEASE_0_12_0/build/platform-openbsd.mk000066400000000000000000000002221257557151200213370ustar00rootroot00000000000000# Configuration for OpenBSD. # Does platform support symlinks? USE_SYMLINK:=true # File name extension of executables. EXEEXT:= LIBRARYEXT:=.so openMSX-RELEASE_0_12_0/build/platform-pandora.mk000066400000000000000000000005261257557151200213400ustar00rootroot00000000000000# Configuration for Pandora. ifeq ($(OPENMSX_TARGET_CPU),arm) # Note: CXX is automatically set by the "setprj" command. # Target Cortex A8 CPU. TARGET_FLAGS+=-march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=softfp endif # Pandora is a Linux system. include build/platform-linux.mk # Identify the binary executable file. EXEEXT:=.bin openMSX-RELEASE_0_12_0/build/platform-solaris.mk000066400000000000000000000002331257557151200213630ustar00rootroot00000000000000# Configuration for Solaris machines. # Does platform support symlinks? USE_SYMLINK:=true # File name extension of executables. EXEEXT:= LIBRARYEXT:=.so openMSX-RELEASE_0_12_0/build/probe.py000066400000000000000000000304641257557151200172260ustar00rootroot00000000000000# Replacement for autoconf. # Performs some test compiles, to check for headers and functions. # It does not execute anything it builds, making it friendly for cross compiles. from compilers import CompileCommand, LinkCommand from components import iterComponents, requiredLibrariesFor from configurations import getConfiguration from executils import captureStdout, shjoin from itertools import chain from libraries import librariesByName from makeutils import extractMakeVariables, parseBool from outpututils import rewriteIfChanged from packages import getPackage from systemfuncs import systemFunctions from systemfuncs2code import iterSystemFuncsHeader from os import environ, makedirs, remove from os.path import isdir, isfile, pathsep from shlex import split as shsplit import sys def resolve(log, expr): if expr is None: return '' # TODO: Since for example "sdl-config" is used in more than one # CFLAGS definition, it will be executed multiple times. try: return normalizeWhitespace(evaluateBackticks(log, expr)) except IOError: # Executing a lib-config script is expected to fail if the # script is not installed. # TODO: Report this explicitly in the probe results table. return '' def writeFile(path, lines): out = open(path, 'w') try: for line in lines: print >> out, line finally: out.close() def tryCompile(log, compileCommand, sourcePath, lines): '''Write the program defined by "lines" to a text file specified by "path" and try to compile it. Returns True iff compilation succeeded. ''' assert sourcePath.endswith('.cc') objectPath = sourcePath[ : -3] + '.o' writeFile(sourcePath, lines) try: return compileCommand.compile(log, sourcePath, objectPath) finally: remove(sourcePath) if isfile(objectPath): remove(objectPath) def checkCompiler(log, compileCommand, outDir): '''Checks whether compiler can compile anything at all. Returns True iff the compiler works. ''' def hello(): # The most famous program. yield '#include ' yield 'int main(int argc, char** argv) {' yield ' std::cout << "Hello World!" << std::endl;' yield ' return 0;' yield '}' return tryCompile(log, compileCommand, outDir + '/hello.cc', hello()) def checkFunc(log, compileCommand, outDir, checkName, funcName, headers): '''Checks whether the given function is declared by the given headers. Returns True iff the function is declared. ''' def takeFuncAddr(): # Try to include the necessary headers and get the function address. for header in headers: yield '#include %s' % header yield 'void (*f)() = reinterpret_cast(%s);' % funcName return tryCompile( log, compileCommand, outDir + '/' + checkName + '.cc', takeFuncAddr() ) def evaluateBackticks(log, expression): parts = [] index = 0 while True: start = expression.find('`', index) if start == -1: parts.append(expression[index : ]) break end = expression.find('`', start + 1) if end == -1: raise ValueError('Unmatched backtick: %s' % expression) parts.append(expression[index : start]) command = expression[start + 1 : end].strip() result = captureStdout(log, command) if result is None: raise IOError('Backtick evaluation failed; see log') parts.append(result) index = end + 1 return ''.join(parts) def normalizeWhitespace(expression): return shjoin(shsplit(expression)) class TargetSystem(object): def __init__( self, log, logPath, compileCommandStr, outDir, platform, distroRoot, configuration ): '''Create empty log and result files. ''' self.log = log self.logPath = logPath self.compileCommandStr = compileCommandStr self.outDir = outDir self.platform = platform self.distroRoot = distroRoot self.configuration = configuration self.outMakePath = outDir + '/probed_defs.mk' self.outHeaderPath = outDir + '/systemfuncs.hh' self.outVars = {} self.functionResults = {} self.typeTraitsResult = None self.libraries = sorted(requiredLibrariesFor( configuration.iterDesiredComponents() )) def checkAll(self): '''Run all probes. ''' self.hello() for func in systemFunctions: self.checkFunc(func) for library in self.libraries: self.checkLibrary(library) def writeAll(self): def iterVars(): yield '# Automatically generated by build system.' yield '# Non-empty value means found, empty means not found.' for library in self.libraries: for name in ( 'HAVE_%s_H' % library, 'HAVE_%s_LIB' % library, '%s_CFLAGS' % library, '%s_LDFLAGS' % library, ): yield '%s:=%s' % (name, self.outVars[name]) rewriteIfChanged(self.outMakePath, iterVars()) rewriteIfChanged( self.outHeaderPath, iterSystemFuncsHeader(self.functionResults), ) def printResults(self): for line in iterProbeResults( self.outVars, self.configuration, self.logPath ): print line def everything(self): self.checkAll() self.writeAll() self.printResults() def hello(self): '''Check compiler with the most famous program. ''' compileCommand = CompileCommand.fromLine(self.compileCommandStr, '') ok = checkCompiler(self.log, compileCommand, self.outDir) print >> self.log, 'Compiler %s: %s' % ( 'works' if ok else 'broken', compileCommand ) self.outVars['COMPILER'] = str(ok).lower() def checkFunc(self, func): '''Probe for function. ''' compileCommand = CompileCommand.fromLine(self.compileCommandStr, '') ok = checkFunc( self.log, compileCommand, self.outDir, func.name, func.getFunctionName(), func.iterHeaders(self.platform) ) print >> self.log, '%s function: %s' % ( 'Found' if ok else 'Missing', func.getFunctionName() ) self.functionResults[func.getMakeName()] = ok def checkLibrary(self, makeName): library = librariesByName[makeName] cflags = resolve( self.log, library.getCompileFlags( self.platform, self.configuration.linkStatic(), self.distroRoot ) ) ldflags = resolve( self.log, library.getLinkFlags( self.platform, self.configuration.linkStatic(), self.distroRoot ) ) compileCommand = CompileCommand.fromLine(self.compileCommandStr, cflags) linkCommand = LinkCommand.fromLine(self.compileCommandStr, ldflags) self.outVars['%s_CFLAGS' % makeName] = cflags self.outVars['%s_LDFLAGS' % makeName] = ldflags sourcePath = self.outDir + '/' + makeName + '.cc' objectPath = self.outDir + '/' + makeName + '.o' binaryPath = self.outDir + '/' + makeName + '.bin' if self.platform == 'android': binaryPath = self.outDir + '/' + makeName + '.so' funcName = library.function headers = library.getHeaders(self.platform) def takeFuncAddr(): # Try to include the necessary headers and get the function address. for header in headers: yield '#include %s' % header yield 'void (*f)() = reinterpret_cast(%s);' % funcName yield 'int main(int argc, char** argv) {' yield ' return 0;' yield '}' writeFile(sourcePath, takeFuncAddr()) try: compileOK = compileCommand.compile(self.log, sourcePath, objectPath) print >> self.log, '%s: %s header' % ( makeName, 'Found' if compileOK else 'Missing' ) if compileOK: linkOK = linkCommand.link(self.log, [ objectPath ], binaryPath) print >> self.log, '%s: %s lib' % ( makeName, 'Found' if linkOK else 'Missing' ) else: linkOK = False print >> self.log, ( '%s: Cannot test linking because compile failed' % makeName ) finally: remove(sourcePath) if isfile(objectPath): remove(objectPath) if isfile(binaryPath): remove(binaryPath) self.outVars['HAVE_%s_H' % makeName] = 'true' if compileOK else '' self.outVars['HAVE_%s_LIB' % makeName] = 'true' if linkOK else '' if linkOK: versionGet = library.getVersion( self.platform, self.configuration.linkStatic(), self.distroRoot ) if callable(versionGet): version = versionGet(compileCommand, self.log) else: version = resolve(self.log, versionGet) if version is None: version = 'error' self.outVars['VERSION_%s' % makeName] = version def iterProbeResults(probeVars, configuration, logPath): '''Present probe results, so user can decide whether to start the build, or to change system configuration and rerun "configure". ''' desiredComponents = set(configuration.iterDesiredComponents()) requiredComponents = set(configuration.iterRequiredComponents()) buildableComponents = set( comp for comp in desiredComponents if comp.canBuild(probeVars) ) packages = sorted( ( getPackage(makeName) for makeName in requiredLibrariesFor(desiredComponents) ), key = lambda package: package.niceName.lower() ) customVars = extractMakeVariables('build/custom.mk') yield '' if not parseBool(probeVars['COMPILER']): yield 'No working C++ compiler was found.' yield "Please install a C++ compiler, such as GCC's g++." yield 'If you have a C++ compiler installed and openMSX did not ' \ 'detect it, please set the environment variable CXX to the name ' \ 'of your C++ compiler.' yield 'After you have corrected the situation, rerun "configure".' yield '' else: # Compute how wide the first column should be. def iterNiceNames(): for package in packages: yield package.niceName for component in iterComponents(): yield component.niceName maxLen = max(len(niceName) for niceName in iterNiceNames()) formatStr = ' %-' + str(maxLen + 3) + 's %s' yield 'Found libraries:' for package in packages: makeName = package.getMakeName() if probeVars['HAVE_%s_LIB' % makeName]: found = 'version %s' % probeVars['VERSION_%s' % makeName] elif probeVars['HAVE_%s_H' % makeName]: # Dependency resolution of a typical distro will not allow # this situation. Most likely we got the link flags wrong. found = 'headers found, link test failed' else: found = 'no' yield formatStr % (package.niceName + ':', found) yield '' yield 'Components overview:' for component in iterComponents(): if component in desiredComponents: status = 'yes' if component in buildableComponents else 'no' else: status = 'disabled' yield formatStr % (component.niceName + ':', status) yield '' yield 'Customisable options:' yield formatStr % ('Install to', customVars['INSTALL_BASE']) yield ' (you can edit these in build/custom.mk)' yield '' if buildableComponents == desiredComponents: yield 'All required and optional components can be built.' else: if requiredComponents.issubset(buildableComponents): yield 'If you are satisfied with the probe results, ' \ 'run "make" to start the build.' yield 'Otherwise, install some libraries and headers ' \ 'and rerun "configure".' else: yield 'Please install missing libraries and headers ' \ 'and rerun "configure".' yield '' yield 'If the detected libraries differ from what you think ' \ 'is installed on this system, please check the log file: %s' \ % logPath yield '' def main(compileCommandStr, outDir, platform, linkMode, thirdPartyInstall): if not isdir(outDir): makedirs(outDir) logPath = outDir + '/probe.log' log = open(logPath, 'w') print 'Probing target system...' print >> log, 'Probing system:' try: distroRoot = thirdPartyInstall or None if distroRoot is None: if platform == 'darwin': for searchPath in environ.get('PATH', '').split(pathsep): if searchPath == '/opt/local/bin': print 'Using libraries from MacPorts.' distroRoot = '/opt/local' break elif searchPath == '/sw/bin': print 'Using libraries from Fink.' distroRoot = '/sw' break else: distroRoot = '/usr/local' elif platform.endswith('bsd') or platform == 'dragonfly': distroRoot = environ.get('LOCALBASE', '/usr/local') print 'Using libraries from ports directory %s.' % distroRoot elif platform == 'pandora': distroRoot = environ.get('LIBTOOL_SYSROOT_PATH') if distroRoot is not None: distroRoot += '/usr' print 'Using libraries from sysroot directory %s.' \ % distroRoot configuration = getConfiguration(linkMode) TargetSystem( log, logPath, compileCommandStr, outDir, platform, distroRoot, configuration ).everything() finally: log.close() if __name__ == '__main__': if len(sys.argv) == 6: try: main(*sys.argv[1 : ]) except ValueError, ve: print >> sys.stderr, ve sys.exit(2) else: print >> sys.stderr, ( 'Usage: python probe.py ' 'COMPILE OUTDIR OPENMSX_TARGET_OS LINK_MODE 3RDPARTY_INSTALL_DIR' ) sys.exit(2) openMSX-RELEASE_0_12_0/build/pylintrc000066400000000000000000000215151257557151200173310ustar00rootroot00000000000000# lint Python modules using external checkers. # # This is the main checker controlling the other ones and the reports # generation. It is itself both a raw checker and an astng checker in order # to: # * handle message activation / deactivation at the module level # * handle some basic but necessary stats'data (number of classes, methods...) # [MASTER] # Specify a configuration file. #rcfile= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Profiled execution. profile=no # Add to the black list. It should be a base name, not a # path. You may set this option multiple times. ignore=CVS # Pickle collected data for later comparisons. persistent=yes # Set the cache size for astng objects. cache-size=500 # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= [MESSAGES CONTROL] # Enable only checker(s) with the given id(s). This option conflicts with the # disable-checker option #enable-checker= # Enable all checker(s) except those with the given id(s). This option # conflicts with the enable-checker option disable-checker=design # Enable all messages in the listed categories (IRCWEF). #enable-msg-cat= # Disable all messages in the listed categories (IRCWEF). #disable-msg-cat= # Enable the message(s) with the given id(s). #enable-msg= # Disable the message(s) with the given id(s). disable-msg=C0111,W0142,R0201 [REPORTS] # Set the output format. Available formats are text, parseable, colorized, msvs # (visual studio) and html output-format=text # Include message's id in output include-ids=yes # Put messages in a separate file for each module / package specified on the # command line instead of printing them on stdout. Reports (if any) will be # written in a file name "pylint_global.[txt|html]". files-output=no # Tells wether to display a full report or only the messages reports=yes # Python expression which should return a note less than 10 (10 is the highest # note). You have access to the variables errors warning, statement which # respectivly contain the number of errors / warnings messages and the total # number of statements analyzed. This is used by the global evaluation report # (R0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Add a comment according to your evaluation note. This is used by the global # evaluation report (R0004). comment=no # Enable the report(s) with the given id(s). #enable-report= # Disable the report(s) with the given id(s). #disable-report= # try to find bugs in the code using type inference # [TYPECHECK] # Tells wether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes # List of classes names for which member attributes should not be checked # (useful for classes with attributes dynamicaly set). ignored-classes=SQLObject # When zope mode is activated, add a predefined set of Zope acquired attributes # to generated-members. zope=no # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E0201 when accessed. generated-members=REQUEST,acl_users,aq_parent # checks for : # * doc strings # * modules / classes / functions / methods / arguments / variables name # * number of arguments, local variables, branchs, returns and statements in # functions, methods # * required module attributes # * dangerous default values as arguments # * redefinition of function / method / class # * uses of the global statement # [BASIC] # Required attributes for module, separated by a comma required-attributes= # Regular expression which should only match functions or classes name which do # not require a docstring no-docstring-rgx=__.*__ # Regular expression which should only match correct module names module-rgx=([a-z_][a-z0-9_]*)$ # Regular expression which should only match correct module level names const-rgx=([a-z_][A-Za-z_]*)$ # Regular expression which should only match correct class names class-rgx=[A-Z_][A-Za-z0-9_]+$ # Regular expression which should only match correct function names function-rgx=[a-z_][A-Za-z_]{2,30}$ # Regular expression which should only match correct method names method-rgx=([a-z_][A-Za-z_]{2,30})|(on_[a-z_][A-Za-z_]{2,30}_[a-z_][A-Za-z_]{2,30})$ # Regular expression which should only match correct instance attribute names attr-rgx=[a-z_][A-Za-z_]{2,30}$ # Regular expression which should only match correct argument names argument-rgx=[a-z_][A-Za-z_]{1,30}$ # Regular expression which should only match correct variable names variable-rgx=[a-z_][A-Za-z0-9_]{1,30}$ # Regular expression which should only match correct list comprehension / # generator expression variable names inlinevar-rgx=[a-z_][A-Za-z_]*$ # Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata # List of builtins function names that should not be used, separated by a comma bad-functions=map,filter,apply,input # checks for # * unused variables / imports # * undefined variables # * redefinition of variable from builtins or from an outer scope # * use of variable before assigment # [VARIABLES] # Tells wether we should check for unused import in __init__ files. init-import=no # A regular expression matching names used for dummy variables (i.e. not used). dummy-variables-rgx=(.*)_|dummy # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins= # checks for # * external modules dependencies # * relative / wildcard imports # * cyclic imports # * uses of deprecated modules # [IMPORTS] # Deprecated modules which should not be used, separated by a comma deprecated-modules=regsub,string,TERMIOS,Bastion,rexec # Create a graph of every (i.e. internal and external) dependencies in the # given file (report R0402 must not be disabled) import-graph= # Create a graph of external dependencies in the given file (report R0402 must # not be disabled) ext-import-graph= # Create a graph of internal dependencies in the given file (report R0402 must # not be disabled) int-import-graph= # checks for sign of poor/misdesign: # * number of methods, attributes, local variables... # * size, complexity of functions, methods # [DESIGN] # Maximum number of arguments for function / method max-args=5 # Maximum number of locals for function / method body max-locals=15 # Maximum number of return / yield for function / method body max-returns=6 # Maximum number of branch for function / method body max-branchs=12 # Maximum number of statements in function / method body max-statements=50 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of attributes for a class (see R0902). max-attributes=7 # Minimum number of public methods for a class (see R0903). min-public-methods=2 # Maximum number of public methods for a class (see R0904). max-public-methods=20 # checks for : # * methods without self as first argument # * overridden methods signature # * access only to existant members via self # * attributes not defined in the __init__ method # * supported interfaces implementation # * unreachable code # [CLASSES] # List of interface methods to ignore, separated by a comma. This is used for # instance to not check methods defines in Zope's Interface base class. ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__,__new__,setUp # checks for: # * warning notes in the code like FIXME, XXX # * PEP 263: source code with non ascii character but no encoding declaration # [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=FIXME,XXX,TODO # checks for : # * unauthorized constructions # * strict indentation # * line length # * use of <> instead of != # [FORMAT] # Maximum number of characters on a single line. max-line-length=80 # Maximum number of lines in a module max-module-lines=1000 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=\t # checks for similarities and duplicated code. This computation may be # memory / CPU intensive, so you should disable it if you experiments some # problems. # [SIMILARITIES] # Minimum lines number of a similarity. min-similarity-lines=4 # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes openMSX-RELEASE_0_12_0/build/python-search.sh000077500000000000000000000003411257557151200206570ustar00rootroot00000000000000#!/bin/sh for name in python python2 python2.6 python2.7 python2.8 python2.9 do $name -c 'import sys; sys.exit(not((2, 6) <= sys.version_info < (3, )))' \ 2> /dev/null if test $? -eq 0 then echo $name break fi done openMSX-RELEASE_0_12_0/build/sizestats.py000066400000000000000000000063611257557151200201470ustar00rootroot00000000000000from executils import captureStdout from collections import defaultdict from os.path import normpath import re, sys _reSymbolInfo = re.compile( r'^([0-9a-f]+\s)?([0-9a-f]+\s)?\s*([A-Za-z])\s([^\t]+)(\t[^\t]+)?$' ) def parseSymbolSize(objectFile): text = captureStdout(sys.stderr, 'nm -CSl "%s"' % objectFile) if text is not None: for line in text.split('\n'): if line: match = _reSymbolInfo.match(line) assert match is not None, line addr_, sizeStr, typ, name, originStr = match.groups() if originStr is None: origin = (None, None) else: source, lineNo = originStr.lstrip().rsplit(':', 1) origin = (normpath(source), int(lineNo)) if sizeStr is not None: if typ not in 'Bb': # Symbols in BSS (uninitialized data section) do not # contribute to executable size, so ignore them. yield name, typ, int(sizeStr, 16), origin if __name__ == '__main__': if len(sys.argv) == 2: executable = sys.argv[1] # Get symbol information. symbolsBySource = defaultdict(list) for name, typ, size, (source, lineNo) in parseSymbolSize(executable): symbolsBySource[source].append((name, typ, size, lineNo)) # Build directory tree. def newDict(): return defaultdict(newDict) dirTree = newDict() for source, symbols in symbolsBySource.iteritems(): if source is None: parts = [ '(no source)' ] else: assert source[0] == '/' parts = source[1 : ].split('/') parts[0] = '/' + parts[0] node = dirTree for part in parts[ : -1]: node = node[part + '/'] node[parts[-1]] = symbols # Combine branches without forks. def compactTree(node): names = set(node.iterkeys()) while names: name = names.pop() content = node[name] if isinstance(content, dict) and len(content) == 1: subName, subContent = content.iteritems().next() if isinstance(subContent, dict): # A directory containing a single directory. del node[name] node[name + subName] = subContent names.add(name + subName) for content in node.itervalues(): if isinstance(content, dict): compactTree(content) compactTree(dirTree) # Compute size of all nodes in the tree. def buildSizeTree(node): if isinstance(node, dict): newNode = {} for name, content in node.iteritems(): newNode[name] = buildSizeTree(content) nodeSize = sum(size for size, subNode in newNode.itervalues()) return nodeSize, newNode else: nodeSize = sum(size for name, typ, size, lineNo in node) return nodeSize, node totalSize, sizeTree = buildSizeTree(dirTree) # Output. def printTree(size, node, indent): if isinstance(node, dict): for name, (contentSize, content) in sorted( node.iteritems(), key = lambda (name, (contentSize, content)): ( -contentSize, name ) ): print '%s%8d %s' % (indent, contentSize, name) printTree(contentSize, content, indent + ' ') else: for name, typ, size, lineNo in sorted( node, key = lambda (name, typ, size, lineNo): ( -size, name ) ): print '%s%8d %s %s %s' % ( indent, size, typ, name, '' if lineNo is None else '(line %d)' % lineNo ) printTree(totalSize, sizeTree, '') else: print >> sys.stderr, 'Usage: python sizestats.py executable' sys.exit(2) openMSX-RELEASE_0_12_0/build/systemfuncs.py000066400000000000000000000024211257557151200204720ustar00rootroot00000000000000class SystemFunction(object): name = None @classmethod def getFunctionName(cls): return cls.name @classmethod def getMakeName(cls): return cls.name.upper() @classmethod def iterHeaders(cls, targetPlatform): raise NotImplementedError class FTruncateFunction(SystemFunction): name = 'ftruncate' @classmethod def iterHeaders(cls, targetPlatform): yield '' class MMapFunction(SystemFunction): name = 'mmap' @classmethod def iterHeaders(cls, targetPlatform): if targetPlatform in ('darwin', 'openbsd'): yield '' yield '' class PosixMemAlignFunction(SystemFunction): name = 'posix_memalign' @classmethod def iterHeaders(cls, targetPlatform): yield '' class USleepFunction(SystemFunction): name = 'usleep' @classmethod def iterHeaders(cls, targetPlatform): yield '' class NftwFunction(SystemFunction): name = 'nftw' @classmethod def iterHeaders(cls, targetPlatform): yield '' # Build a list of system functions using introspection. def _discoverSystemFunctions(localObjects): for obj in localObjects: if isinstance(obj, type) and issubclass(obj, SystemFunction): if obj is not SystemFunction: yield obj systemFunctions = list(_discoverSystemFunctions(locals().itervalues())) openMSX-RELEASE_0_12_0/build/systemfuncs2code.py000066400000000000000000000014521257557151200214120ustar00rootroot00000000000000from systemfuncs import systemFunctions from outpututils import rewriteIfChanged import sys def iterSystemFuncsHeader(functionResults): yield '// Automatically generated by build system.' for makeName in sorted( func.getMakeName() for func in systemFunctions ): yield '#define HAVE_%s %d' % (makeName, functionResults[makeName]) def getSystemFuncsInfo(): return dict.fromkeys( (func.getMakeName() for func in systemFunctions), False ) if __name__ == '__main__': if len(sys.argv) == 2: rewriteIfChanged( sys.argv[1], iterSystemFuncsHeader(getSystemFuncsInfo()) ) else: print >> sys.stderr, \ 'Usage: python systemfuncs2code.py CONFIG_HEADER ' print >> sys.stderr, \ 'Note: Should only be called directly on systems where the probe ' \ 'does not work.' sys.exit(2) openMSX-RELEASE_0_12_0/build/thirdparty_download.py000066400000000000000000000054631257557151200222010ustar00rootroot00000000000000from checksum import verifyFile from components import requiredLibrariesFor from configurations import getConfiguration from download import downloadURL from extract import TopLevelDirRenamer, extract from libraries import allDependencies, librariesByName from packages import getPackage from patch import Diff, patch from os import makedirs from os.path import isdir, isfile, join as joinpath from shutil import rmtree import sys # TODO: Make DirectX headers for MinGW a package and make the DirectX sound # driver a component. def downloadPackage(package, tarballsDir): if not isdir(tarballsDir): makedirs(tarballsDir) filePath = joinpath(tarballsDir, package.getTarballName()) if isfile(filePath): print '%s version %s - already downloaded' % ( package.niceName, package.version ) else: downloadURL(package.getURL(), tarballsDir) def verifyPackage(package, tarballsDir): filePath = joinpath(tarballsDir, package.getTarballName()) try: verifyFile(filePath, package.fileLength, package.checksums) except IOError, ex: print >> sys.stderr, '%s corrupt: %s' % ( package.getTarballName(), ex ) sys.exit(1) def extractPackage(package, tarballsDir, sourcesDir, patchesDir): if not isdir(sourcesDir): makedirs(sourcesDir) sourceDirName = package.getSourceDirName() packageSrcDir = joinpath(sourcesDir, sourceDirName) if isdir(packageSrcDir): rmtree(packageSrcDir) extract( joinpath(tarballsDir, package.getTarballName()), sourcesDir, TopLevelDirRenamer(sourceDirName) ) diffPath = joinpath(patchesDir, sourceDirName + '.diff') if isfile(diffPath): for diff in Diff.load(diffPath): patch(diff, sourcesDir) print 'Patched:', diff.getPath() def fetchPackageSource(makeName, tarballsDir, sourcesDir, patchesDir): package = getPackage(makeName) downloadPackage(package, tarballsDir) verifyPackage(package, tarballsDir) extractPackage(package, tarballsDir, sourcesDir, patchesDir) def main(platform, tarballsDir, sourcesDir, patchesDir): if platform == 'android': fetchPackageSource('TCL_ANDROID', tarballsDir, sourcesDir, patchesDir) else: configuration = getConfiguration('3RD_STA') components = configuration.iterDesiredComponents() # Compute the set of all directly and indirectly required libraries, # then filter out system libraries. thirdPartyLibs = set( makeName for makeName in allDependencies(requiredLibrariesFor(components)) if not librariesByName[makeName].isSystemLibrary(platform) ) for makeName in sorted(thirdPartyLibs): fetchPackageSource(makeName, tarballsDir, sourcesDir, patchesDir) if __name__ == '__main__': if len(sys.argv) == 2: main( sys.argv[1], 'derived/3rdparty/download', 'derived/3rdparty/src', 'build/3rdparty' ) else: print >> sys.stderr, ( 'Usage: python thirdparty_download.py TARGET_OS' ) sys.exit(2) openMSX-RELEASE_0_12_0/build/version.py000066400000000000000000000060521257557151200176000ustar00rootroot00000000000000# Contains the openMSX version number and versioning related functions. from executils import captureStdout from makeutils import filterLines from os import makedirs from os.path import isdir import re # Name used for packaging. packageName = 'openmsx' # Version number. packageVersionNumber = '0.12.0' # Version code for Android must be an incremental number # Increase this number for each release build. For a dev build, the # version number is based on the git commit count but for a release # build, it must be hardcoded androidReleaseVersionCode=4 # Note: suffix should be empty or with dash, like "-rc1" or "-test1" packageVersionSuffix = '' packageVersion = packageVersionNumber + packageVersionSuffix # Is this a release version ("True") or development version ("False"). releaseFlag = True def _extractRevisionFromStdout(log, command, regex): text = captureStdout(log, command) if text is None: # Error logging already done by captureStdout(). return None # pylint 0.18.0 somehow thinks captureStdout() returns a list, not a string. lines = text.split('\n') # pylint: disable-msg=E1103 for revision, in filterLines(lines, regex): print >> log, 'Revision number found by "%s": %s' % (command, revision) return revision else: print >> log, 'Revision number not found in "%s" output:' % command print >> log, text return None def extractGitRevision(log): return _extractRevisionFromStdout( log, 'git describe --dirty', r'\S+?-(\S+)$' ) def extractNumberFromGitRevision(revisionStr): if revisionStr is None: return None if revisionStr == 'dirty': return None return re.match(r'(\d+)+', revisionStr).group(0) _cachedRevision = False # because None is a valid result def extractRevision(): global _cachedRevision if _cachedRevision is not False: return _cachedRevision if releaseFlag: # Not necessary, we do not append revision for a release build. return None if not isdir('derived'): makedirs('derived') log = open('derived/version.log', 'w') print >> log, 'Extracting revision info...' try: revision = extractGitRevision(log) print >> log, 'Revision string: %s' % revision print >> log, 'Revision number: %s' % extractNumberFromGitRevision(revision) finally: log.close() _cachedRevision = revision return revision def extractRevisionNumber(): return int(extractNumberFromGitRevision(extractRevision()) or 1) def extractRevisionString(): return extractRevision() or 'unknown' def getVersionedPackageName(): if releaseFlag: return '%s-%s' % (packageName, packageVersion) else: return '%s-%s-%s' % ( packageName, packageVersion, extractRevisionString() ) def countGitCommits(): if not isdir('derived'): makedirs('derived') log = open('derived/commitCountVersion.log', 'w') print >> log, 'Extracting commit count...' try: commitCount = captureStdout(log, 'git rev-list HEAD --count') print >> log, 'Commit count: %s' % commitCount finally: log.close() return commitCount def getAndroidVersionCode(): if releaseFlag: return '%s' % (androidReleaseVersionCode) else: return '%s' % ( countGitCommits() ) openMSX-RELEASE_0_12_0/build/version2code.py000066400000000000000000000012521257557151200205120ustar00rootroot00000000000000# Generates version include file. from outpututils import rewriteIfChanged from version import extractRevisionString, packageVersion, releaseFlag import sys def iterVersionInclude(): revision = extractRevisionString() yield '// Automatically generated by build process.' yield 'const bool Version::RELEASE = %s;' % str(releaseFlag).lower() yield 'const char* const Version::VERSION = "%s";' % packageVersion yield 'const char* const Version::REVISION = "%s";' % revision if __name__ == '__main__': if len(sys.argv) == 2: rewriteIfChanged(sys.argv[1], iterVersionInclude()) else: print >> sys.stderr, \ 'Usage: python version2code.py VERSION_HEADER' sys.exit(2) openMSX-RELEASE_0_12_0/build/win_resource.py000066400000000000000000000014401257557151200206130ustar00rootroot00000000000000# Generates Windows resource header. from outpututils import rewriteIfChanged from version import extractRevisionNumber, packageVersion import sys def iterResourceHeader(): if '-' in packageVersion: versionNumber = packageVersion[ : packageVersion.index('-')] else: versionNumber = packageVersion revision = str(extractRevisionNumber()) versionComponents = versionNumber.split('.') + [ revision ] assert len(versionComponents) == 4, versionComponents yield '#define OPENMSX_VERSION_INT %s' % ', '.join(versionComponents) yield '#define OPENMSX_VERSION_STR "%s\\0"' % packageVersion if __name__ == '__main__': if len(sys.argv) == 2: rewriteIfChanged(sys.argv[1], iterResourceHeader()) else: print >> sys.stderr, \ 'Usage: python win-resource.py RESOURCE_HEADER' sys.exit(2) openMSX-RELEASE_0_12_0/configure000077500000000000000000000017121257557151200163470ustar00rootroot00000000000000#!/bin/sh # Probes the build environment (libs, headers). if [ -z "$MAKE" ] then # Look for GNU Make; prefer "gmake" over "make". (false ; echo "nothing:" | make -f - > /dev/null 2>&1) && MAKE=make (false ; echo "nothing:" | gmake -f - > /dev/null 2>&1) && MAKE=gmake else echo "Using value of MAKE environment variable: \"$MAKE\"." fi if [ -z "$MAKE" ] then echo 'Error: no Make found.' echo 'Please install GNU Make and put it in your path as "gmake" or "make",' echo 'or put its location in an environment variable named "MAKE".' exit 1 fi if [ "gmake" != "$MAKE" ] then if ! ( $MAKE --version > /dev/null 2>&1 \ && ($MAKE --version 2> /dev/null | grep "GNU Make" > /dev/null) ) then echo "Error: Make does not appear to be GNU Make." echo 'Please install GNU Make and put it in your path as "gmake" or "make",' echo 'or put its location in an environment variable named "MAKE".' exit 1 fi fi # Invoke actual probe. $MAKE -f build/main.mk probe openMSX-RELEASE_0_12_0/doc/000077500000000000000000000000001257557151200152045ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/doc/.gitignore000066400000000000000000000000521257557151200171710ustar00rootroot00000000000000/*.swp /*.log /*.dvi /*.pdf /*.aux /*.toc openMSX-RELEASE_0_12_0/doc/DMK-Format-Details.txt000066400000000000000000000344601257557151200212000ustar00rootroot00000000000000 This text file describes the DMK format as it is being used in openMSX. Originally openMSX only support simple sector based formats like the generic dsk format and the xsa format (which in essence is a compressed dsk format). These formats contained a simple dump of all the data in the sectors on a 360/720KB Double Density disk. These formats however could not be used to store track info that was commonly used for copyprotecting disks. Copy protecting resorted to tricks like out of order sectors, partially formated tracks and other trickery that was made possible by customizing the actual track headers, sector headers and CRC checks that are used by the FDC (floppy disk controller) chip when reading data from a disk. When Wouter,one of the openMSX developers, started to look for a format for openMSX to store more complete (read: copy-protected) diskimages his eyes fell upon the DMK format. The DMK format was originaly created to store TRS-80 disk. Not all of the TRS-80 specifics in the DMK format apply to the MSX standard so not everything in the DMK format is implemented for the moment, nor will it probably ever be . For instance Single Density was never used on MSX, so no support for it is implemented in openMSX. The original page containing this info can be found at http://www.trs-80.com/wordpress/dsk-and-dmk-image-utilities/ DMK Format Details ================== Files with a .DSK extension could be JV1, JV3, or DMK format. The DMK format is a format developed by David Keil which allows for the storing of TRS-80 disks in pure form, which would allow for the representation of copy protected disks into emulator images. The DMK format gives the ability to support any FM or MFM encoded format that is reasonably close to the IBM 3740 or IBM System 34 standards. DMK format disks are easy to discern because of its 16 byte header. Emulators made after 2005 should handle the DMK format. The DMK virtual disk format is as close to the way data on a real disk is stored as possible. There is very little added overhead and the data is easily examined and edited using PC based hex editors. The actual design is really quite simple and enables support of ALL the WD-17xx controller functions and formats. While the design is simple however the programming requirements for this format are much more extensive then for the JV1/JV3 formats. Disk header: Virtual disks have a 16 byte disk header which is initialized when the user creates a new virtual disk. This header may be modified before or after a virtual disk has been formatted to change some of its characteristics. +----------------------------------------------------------------------+ | Byte 0 | If this byte is set to FFH the disk is 'write | | | protected', 00H allows writing. | |--------------+-------------------------------------------------------| | Byte 1 | Number of tracks on virtual disk. Since tracks start | | | at 0 this value will be one greater than the highest | | | track written to the disk. So a disk with 40 tracks | | | will have a value of 40 (28H) in this field after | | | formatting while the highest track written would be | | | 39. This field is updated after a track is formatted | | | if the track formatted is greater than or equal to | | | the current number of tracks. Re-formatting a disk | | | with fewer tracks will NOT reduce the number of | | | tracks on the virtual disk. Once a virtual disk has | | | allocated space for a track it will NEVER release it. | | | Formatting a virtual disk with 80 tracks then | | | re-formatting it with 40 tracks would waste space | | | just like formatting only 40 tracks on an 80 track | | | drive. The emulator and TRS-80 operating system don't | | | care. To re-format a virtual disk with fewer tracks | | | use the /I option at start-up to delete and re-create | | | the virtual disk first, then re-format to save space. | | | | | | Note: This field should NEVER be modified. Changing | | | this number will cause TRS-80 operating system disk | | | errors. (Like reading an 80 track disk in a 40 track | | | drive) | | | | |--------------+-------------------------------------------------------| | Byte 2 & 3 | This is the track length for the virtual disk. By | | | default the value is 1900H, 80H bytes more than the | | | actual track length, this gives a track length of | | | 6272 bytes. A real double density track length is | | | aprox. 6250 bytes. This is the default value when a | | | virtual disk is created. Values for other disk and | | | format types are 0CC0H for single density 5.25" | | | floppies, 14E0H for single density 8" floppies and | | | 2940H for double density 8" floppies. The max value | | | is 2940H. For normal formatting of disks the values | | | of 1900H and 2940H for 5.25" and 8" are used. The | | | emulator will write two bytes and read every second | | | byte when in single density to maintain proper | | | sector spacing, allowing mixed density disks. Setting | | | the track length must be done before a virtual disk | | | is formatted or the disk will have to be re-formatted | | | and since the space for the disk has already been | | | allocated no space will be saved. | | | | | | WARNING: Bytes are entered in reverse order (ex. | | | 2940H would be entered, byte 2=40, byte 3=29). | | | | | | Note: No modification of the track length is | | | necessary, doing so only saves space and is not | | | necessary to normal operation. The values for all | | | normal 5.25" and 8" disks are set when the virtual | | | disk is created. DON'T modify the track length unless | | | you understand these instructions completely. Nothing | | | in the PC world can be messed up by improper | | | modification but any other virtual disk mounted in | | | the emulator with an improperly modified disk could | | | have their data scrambled. | |--------------+-------------------------------------------------------| | Byte 4 | Virtual disk option flags. | | | | | | Bit 4 of this byte, if set, means this is a single | | | sided ONLY disk. This bit is set if the user selects | | | single sided during disk creation and should not | | | require modification. This flag is used only to save | | | PC hard disk space and is never required. | | | | | | Bit 6 of this byte, if set, means this disk is to be | | | single density size and the emulator will access one | | | byte instead of two when doing I/O in single density. | | | Double density can still be written to a single | | | density disk but with half the track length only 10 | | | 256 byte sectors can be written in either density. | | | Mixed density is also possible but sector timing may | | | be off so protected disks may not work, a maximum of | | | 10 256 byte sectors of mixed density can be written | | | to a single density disk. A program like "Spook | | | House" which has a mixed density track 0 with 1 SD | | | sector and 1 DD sector and the rest of the disk | | | consisting of 10 SD sectors/track will work with this | | | flag set and save half the PC hard disk space. The | | | protected disk "Super Utility + 3.0" however has 6 SD | | | and 6 DD sectors/track for a total of 12 256 byte | | | sectors/track. This disk cannot be single density. | | | | | | This bit is set if the user selects single density | | | during disk creation and should not require | | | modification. This flag is used only to save PC hard | | | disk space and is never required. | | | | | | Bit 7 of this byte, if set, means density is to be | | | ignored when accessing this disk. The disk MUST be | | | formatted in double density but the emulator will | | | then read and write the sectors in either density. | | | The emulator will access one byte instead of two when | | | doing I/O in single density. | | | | | | This flag was an early way to support mixed density | | | disks it is no longer needed for this purpose. It is | | | now used for compatibility with old virtual disks | | | created without the double byte now used when in | | | single density. This bit can be set manually in a hex | | | editor to access old virtual disks written in single | | | density. | |--------------+-------------------------------------------------------| | Byte 5-B | reserved for future options | |--------------+-------------------------------------------------------| | Byte C-F | Must be zero if virtual disk is in emulator's native | | | format. | | | | | | Must be 12345678h if virtual disk is a REAL disk | | | specification file used to access REAL TRS-80 | | | floppies in compatible PC drives. | |--------------+-------------------------------------------------------| | Track Header | Each track has a 128 (80H) byte header which contains | | | an offset to each IDAM in the track. This is created | | | during format and should NEVER require modification. | | | The actual track data follows this header and can be | | | viewed with a hex editor showing the raw data on the | | | track. Modification should not be done as each IDAM | | | and sector has a CRC, this is just like a real disk, | | | and modifying the sector data without updating the | | | CRC value will cause CRC errors when accessing the | | | virtual disk within the emulator. | | | | | | Note: Modification within MSDOS could however be done | | | to emulate a protected disk in the TRS-80 emulator. | +----------------------------------------------------------------------+ Track header: Each side of each track has a 128 (80H) byte header which contains an offset pointer to each IDAM in the track. This allows a maximum of 64 sector IDAMs/track. This is more than twice what an 8 inch disk would require and 3.5 times that of a normal TRS-80 5 inch DD disk. This should more than enough for any protected disk also. These IDAM pointers MUST adhere to the following rules. * Each pointer is a 2 byte offset to the FEh byte of the IDAM. In double byte single density the pointer is to the first FEh. * The offset includes the 128 byte header. For example, an IDAM 10h bytes into the track would have a pointer of 90h, 10h+80h=90h. * The IDAM offsets MUST be in ascending order with no unused or bad pointers. * If all the entries are not used the header is terminated with a 0000h entry. Unused entries must also be zero filled. * Any IDAMs overwritten during a sector write command should have their entry removed from the header and all other pointer entries shifted to fill in. * The IDAM pointers are created during the track write command (format). A completed track write MUST remove all previous IDAM pointers. A partial track write (aborted with the forced interrupt command) MUST have it's previous pointers that were not overwritten added to the new IDAM pointers. * The pointer bytes are stored in reverse order (LSB/MSB). Each IDAM pointer has two flags. Bit 15 is set if the sector is double density. Bit 14 is currently undefined. These bits must be masked to get the actual sector offset. For example, an offset to an IDAM at byte 90h would be 0090h if single density and 8090h if double density. Track data: The actual track data follows the header and can be viewed with a hex editor showing the raw data on the track. If the virtual disk doesn't have bits 6 or 7 set of byte 4 of the disk header then each single density data byte is written twice, this includes IDAMs and CRCs (the CRCs are calculated as if only 1 byte was written however). The IDAM and sector data each have CRCs, this is just like on a real disk. Modification should not be done since doing so without updating the CRCs would cause data errors. Modification could be done however to create protected tracks for importing protected disks to virtual disk format. Examples of disks created using this technique are "Super Utility+ 3.0" and "Forbidden City". openMSX-RELEASE_0_12_0/doc/GPL.txt000066400000000000000000000432541257557151200163770ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. openMSX-RELEASE_0_12_0/doc/authors.txt000066400000000000000000000047061257557151200174410ustar00rootroot00000000000000Prime Developers: David Heremans Wouter Vermaelen Maarten ter Huurne Manuel Bilderbeek Arnold Metselaar Patrick van Arkel Max Feingold Sean Young Alex Wulms Albert Beevendorp An actual overview of contributors can be found here: http://www.ohloh.net/p/openmsx/contributors Retired Developers: Eric Boon Joost Yervante Damad Herman Oudejans Reikan And before altering (or completely rewriting) we used code from the following contributors: TMS9918A: Sean Young VDP command engine: Alex Wulms z80: Marcel de Kogel SDL_Console: Garrett Banuk AY8910: Mostly taken from xmame-0.37b16.1. Based on various code snippets by Ville Hallik, Michael Cuddy, Tatsuyuki Satoh, Fabrice Frances, Nicola Salmoria Y8950 and YM2413 emulator: written by Mitsutaka Okazaki other YM2413 emulator: written by Jarek Burczynski TC8566AF disk controller: Based on code from NLMSX written by Frits Hilderink SDL surface scaling: Taken from the rotozoomer written by A. Schiffler http://www.ferzkopp.net/Software/SDL_gfx-2.0 YMF262: code taken from MAME, written by Jarek Burczynski YMF278: code taken from MAME, written by R. Belmont and O. Galibert cas to wav conversion: based on "cas2wav" by Vincent van Dam With many thanks to: Alex Wulms Marcel Harkema Jon De Schrijder atarulum Patriek Lesparre Manuel Pazos Tetsuo Honda Albert Beevendorp Jorrith Schaap Jan Lukens Daniel Vik SukkoPera Maarten van Strien Laurens Holst Filip H.F. Slagter And a special "thank you" goes to ??????????? for providing an original 'Way of the Tiger', a tape based game with its own data encoding, to test our tape emulation. ... this list is not complete!! ... [please add] openMSX-RELEASE_0_12_0/doc/internal/000077500000000000000000000000001257557151200170205ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/doc/internal/ChangeLog000066400000000000000000000011131257557151200205660ustar00rootroot00000000000000We don't maintain a traditional ChangeLog file anymore. Instead we try to write detailed (git) commit messages. If you have a clone of the openMSX git repository, you can see the commit messages with the command: git log Historically we did maintain a ChangeLog file: pre-2009: Change entries are stored in the ChangeLog file (now renamed to ChangeLog.old). 2009: Transition period. We switched from ChangeLog to commit messages. Unfortunately not all developers switched at the same time :( post-2009: All change entries are written as commit messages. openMSX-RELEASE_0_12_0/doc/internal/ChangeLog.old000066400000000000000000016441611257557151200213640ustar00rootroot00000000000000!!! !!! This file is no longer updated (and hasn't been for a while). !!! Instead we prefer to put changelog messages in the git commit !!! messages. The only reason to keep this file around is that in !!! the old days the messages in this file were more detailed than !!! the commit messages, so this file may still contain useful info !!! when you're investigating old changes. !!! 2010-06-06 Manuel Bilderbeek * Released openMSX 0.8.0. * NOTE: this is probably the last entry in this ChangeLog. From now on, just use the SVN commit log to keep track of changes. 2010-01-10 Manuel Bilderbeek * Added Generation MSX id to software info topic. Use this to link openMSX games to the Generation MSX web site. 2009-12-20 Manuel Bilderbeek * Made softwaredb information available from Tcl, using sha1sum as key. First step in making better use of the softwaredb, but mostly meant to make it possible to replace the automatic ROM info printing with a command to query that information on demand. 2009-11-08 Manuel Bilderbeek * Added saving and loading of replays (in reverse feature). Thanks to Wouter for doing the difficult work. 2009-09-27 Maarten ter Huurne * Corrected palette colors on V9958: The V9958 uses 5-bit DACs for R/G/B, shared by the YJK colors and the V9938-compatible palette colors. The R/G/B palette colors are converted from 3 bits to 5 bits in the same way the V9990 does. Thanks to n_n for capturing the output of my test program a real turbo R. 2009-08-02 Patrick van Arkel vampiermsx@gmail.com * Made some major changes to the ROM database and added tags to the field. Included are also Generation MSX-ids to have a common 'key' to bind databases. 2009-07-16 Manuel Bilderbeek * Added session management script. When you do: set enable_session_management true your session (all machines) will be saved when exiting openMSX and loaded again when starting openMSX (ignoring the machine/extensions you put on the command line). You can also save, load, list and delete sessions manually from the console. Default, this whole feature is disabled. 2009-07-15 Sean Young * First commit of Laserdisc code. Some of the outstanding issues are listed in doc/laserdisc-todo.txt. 2009-07-09 Manuel Bilderbeek * Added tabbed_machine_view script, visualizing multiply running machines in a kind of tab widget. 2009-07-08 Manuel Bilderbeek * Renamed 'msxonly' option to 'raw', which fits much better to what it does. 2009-07-02 Wouter Vermaelen * Major reductions in executable size for serialization code: See the commit messages for details, here I'll only list one line per commit and the size of the executable in each step (stripped devel flavour on linux x86_64 gcc-4.3): 7200kB [initial version] 7140kB MemBuffer iso std::vector 7060kB made serialize without ID default 6920kB string="" -> string*=NULL 6918kB XXX::serialize() tweaks 6899kB outline OutputBuffer::insert() 6861kB __builtin_constant_p 6590kB beginTag(const char*) 6292kB move error handling outline 6171kB moved more of loadVersion() outline 6120kB use const char* in PolymorphicSaver 6109kB moved more of PolymorphicXXXRegistry outline 5781kB give ArchiveXXX classes a non-templatized base 5780kB moved ArchiveXXX::load/save() methods outline 5726kB pass attrbute names as const char* 5710kB outline attribute(...) calls So combined this is an executable size reduction of over 1.4MB. Apart from this, compilation is also noticeably faster: went from 8'00" to 6'23" on my machine (user time). 2009-06-07 Manuel Bilderbeek * Apart from full screen (which was already implemented), also hide the mouse pointer always when input grab is enabled. 2009-06-04 Manuel Bilderbeek * Added Sanyo MPC-25FD, MSX2 with diskdrive and one cartridge slot only. Thanks to jltursan! 2009-06-04 Wouter Vermaelen * Fixed IE0 related VDP bug: When there is a pending VBLANK IRQ (bit 7 in S#00 is set) and you switch IE0 (bit 5 in R#1) on, there is immediately a IRQ send to the Z80. Before this fix openMSX would only generate one on the next VBLANK. This fixes several long-standing bugs: - [1849488] Game "Adonis" plays sound way too slowly - [ 739150] Zanac: title corrupted - [1847275] Penguin Adventure doesn't work on some machines - [ 658430] Galaga/Bosconian slows down when you die Thanks a lot to 'n_n' for providing a z80 trace of a Adonis run in meisei. Comparing this trace with a openMSX trace allowed to pin-point this bug. 2009-05-23 Maarten ter Huurne * Always build application folder on Mac OS X. This is the way we release and supporting both app folder and systemwide installation only adds to the maintaince effort. 2009-05-05 Max Feingold * Added a Win32 implementation of LocalFile:mmap(). This should make file handling on Windows imperceptibly faster. * Implemented correct shutdown logic for PipeConnection thread using async I/O and events. 2009-04-24 Manuel Bilderbeek * Final step in un-uglifying Tcl scripts using Tcl namespaces :) 2009-04-19 Manuel Bilderbeek * Added Sharp-Epcom HB-3600 dual 5.25" external disk drives, thanks to FRS for contributing it. 2009-04-09 Wouter Vermaelen * Added support for taking screenshots without OSD elements: Restructured OutputSurface and child classes to make it possible to render to an off-screen surface, so that it becomes possible to re-render the current frame without the OSD layers. 2009-04-04 Manuel Bilderbeek * Implemented enough of the Arkanoid pad to make it work. Use the mouse to control it. 2009-04-03 Manuel Bilderbeek * Added pointer_hide_delay setting to control visibility of the mouse pointer. If you set it to -1, it will be always shown. 0 (the default) means it is never shown. Positive values is the amount of milliseconds it takes before it will be automatically hidden after it got shown due to mouse activity. Docs are TODO. 2009-03-30 Manuel Bilderbeek * Added a fun OSD VU-meter script, see script for details. Thanks to Wouter and BiFi for their support. 2009-03-26 Max Feingold * Secured sockets used by CliServer on Windows by requiring authentication and authorizing only the user that launches the openMSX process. 2009-03-24 David Heremans * Added new emu-state icons for set1 and replaced the default ones in share/skins. 2009-03-20 Manuel Bilderbeek * Added throttle setting to the new OSD icons (thanks Wouter). 2009-03-11 Manuel Bilderbeek * Convert ancient history_size and remove_doubles settings to modern settings. They influence how openMSX handles console history. 2009-03-09 Manuel Bilderbeek * Added pause_on_lost_focus setting. After enabling it, openMSX will pause when your cursor leaves the openMSX window. Useful in some cases. This was default in fMSX, but in openMSX it is off by default. 2009-03-03 Wouter Vermaelen * Split Y8950Adpcm sample generation part in two: - There is now an emulation and an audio part. See comments in Y8950Adpcm.cc for details. The emulation state is now no longer dependant on the audio-thread. So in UR, the scope part keeps on working when the audio is muted. - Also made some minor adjustments to the rest of the adpcm code. 2009-02-24 Max Feingold * Updated VC++ projects with a Python-based script that builds config headers using mth's Python scripts. This automates one of the more annoying manual steps in the VC++ build process. 2009-02-24 Wouter Vermaelen * ZMBVEncoder motion vector search heuristic tweaks: - try vectors with smallest euclidean size first (was Chebyshev size) also give preference to purely horizontal/vertical motions and to a lesser degree purely diagonal motions - start search at best motion vector of previous block (before we always started from the center) - use different set of possible motion vectors, search further in pure hor/ver/diag directions, less far in other (random) directions Combined these tweaks give measurable smaller video files (5% - 25%) and also run faster (2% - 6%). The 2nd tweak (start from previous best) has the largest effect. 2009-02-23 Max Feingold * Created a WiX-based installer for openMSX on Windows. The output is an MSI file with the usual wizard-based installation UI. 2009-02-21 Wouter Vermaelen * Refactored disk classes: - simplified class hierarchy: - removed virtual inheritance - merged WriteProtectableDisk into SectorAccessibleDisk - Disk now inherits from SectorAccessibleDisk, ATM we don't support disk image formats that are lower level than sector level. But even for those images it makes sense to support reading/writing sectors. - moved patch functionality up in the class hierarchy, to SectorAccessibleDisk * Added syntax to insert a partition of a hard disk image: : This is a preparartion to support disk partitions in the nowind interface. * Implemented hard disk support for nowind interface 2009-02-11 Wouter Vermaelen * Initial nowind implementation: still need to work on the nowind openmsx commands 2009-02-09 Max Feingold * Ported most significant inline assembly code to VC++ format, where significance is defined as 'being noticeable in kernrate'. * VC++ binaries now benchmark 15% faster than mingw32 binaries on my system openMSX svn VC++ x64 - 10.72 seconds openMSX svn VC++ x86 - 11.19 seconds openMSX svn mingw x86 - 12.62 seconds openMSX 0.7.0 mingw x86 - 13.96 seconds 2009-02-07 Max Feingold * Reduced VC++ W3 warning load to ~250, with help from Vampier and Wouter 2009-02-05 Max Feingold * Rationalized the following definitions throughout the codebase: - ASM_X86 = build assembly code on x86 and x64 platforms (gcc's inline asm can target both; VC++ doesn't support x64 inline asm) - ASM_X86_32 = build assembly code on x86 platforms (assumes ASM_X86) - ASM_X86_64 = build assembly code on x64 platforms (assumes ASM_X86) - __x86_64 = x64 target is specified - _WIN64 = Windows x64 target is specified (same as _WIN32 && __x86_64) * These definitions should act that same for both mingw32 and VC++. 2009-02-01 Wouter Vermaelen * Implemented 'ld a,i' Z80 quirk: See comments in CPUCore.cc for details. Thanks to n_n for reporting this issue (first discovered by GuyveR800) and writing a test program for it. 2009-01-29 Max Feingold * Unicode filename support on Windows platforms - On Windows, standard CRT functions and 'ANSI' Win32 API calls expect and return ANSI encodings, not UTF8. Unicode support on Windows is generally achieved by calling CRT functions that accept wchar_t and 'wide char' xxxW Win32 API calls. - openMSX uses std::string and UTF8-encoded strings throughout. Consequently, on Windows we wrap all system calls that might touch the filesystem in UTF8-to-UTF16 conversions before calling the 'wide char' API and (if necessary) converting back to UTF8. * As a side effect, this checkin also deprecates openMSX support for Windows 95-based platforms, as well as for Windows NT 4.0. At the time of writing, only Windows 2000 and above are supported. 2009-01-25 Maarten ter Huurne * Use Python to generate "Version.ii". * Use Python to generate "build-info.hh". 2009-01-18 Max Feingold * Fixed bug [ 1206036 ] Windows Control Menu while using ALT With this fix in place, the ALT+SPACE combination no longer pops up an annoying context menu on Windows. * The solution was to register our own winproc, filter out the offending WM_SYSCOMMAND events, and delegate the rest to SDL. 2009-01-20 Wouter Vermaelen * Console refactoring: - Share much more code between SDL and GL console variant - Use TTF fonts instead of a .png file with the ASCII symbols: console can now also show unicode chars (if the ttf font has them) - Added new setting: consolefontsize - Removed (now unused) classes: Font, DummyFont, SDLFont, GLFont, SDLConsole, GLConsole * Reverted part of last CPU refactoring (2008-01-15): - Omitting needExit() check on some instructions can causes acceptance of an IRQ to be a couple of instructions late, see comments in src/cpu/CPUCore.cc for details. - But even without this part, the threaded interpreter model is a big win (more than 10% faster) over the old model. 2009-01-23 Max Feingold * Checked in VC++ project files that build dependencies and openMSX itself. The project files are in VC9 (Visual C++ 2008) format and should be usable with the Windows SDK's msbuild tool, as well as with the free (as in beer) Visual C++ 2008 Express Edition. * This checkin also brings the first 64-bit Windows build of openMSX. x64 binaries are fully functional and I'm pleased to note that I found no significant 64-bit portability issues in the codebase (inline asm aside). 2009-01-17 Wouter Vermaelen * Initial implementation of the VDP sprite collision coordinate status registers: Not yet 100% correct (see top of src/video/SpriteChecker.cc for some TODOs). But at least 'Ace in Space level 3' works now. So far this seems to be the only game that actually uses this feature. Fixes [2510696]. 2009-01-15 Wouter Vermaelen * Major changes in CPU emulation core: I've worked on this patch on a local (private) branch for several weeks. Actually it are several related patches and ideally I should also commit it as seperate patches. However developing on a private branch means (at least for me) do a couple of commits, detect a mistake in one of the earlier commits, make a new commit that corrects that mistake and so on. Usually I do the extra effort to extract a clean patch-series from my local branches before committing them. However this time that would require quite some extra time, and as you guessed already I was too lazy to spend that time. Though as compensation I wrote some extra doc about the inner workings of our CPU emulation (see src/cpu/CPUCore.cc) These are more or less the steps that are combined in this commit: * Put instruction selection into one big function: this allows to 'goto' between all part of this code (required in later steps) * Move opcode fetching (initial byte) and time accounting into this function (preparation for next step) * Allow to execute multiple instructions inside this function * Use gcc extension 'computed goto' to implement faster dispatch * Added fallback for compilers that don't have this extension (VC++) * Simplified 'exit-test', see doc in CPUCore.cc for details * Some smaller time bookkeeping optimizations: - IX/IY instructions updated clock in two steps -> merged into one step - Some helper routines took a 'extra-cycles' function parameter. Turned this into a template parameter to force the compiler to treat it as a compile-time-constant. (Actually gcc already did this optimization by itself most of the time when the function got inlined, but now the optimization is guaranteed.) 2009-01-10 Wouter Vermaelen * Implemented difference between AY8910/YM2149 reading of registers: - thanks to enen for doing most of the investigation on real MSX machines 2009-01-08 Wouter Vermaelen * Merged (part of) mfeingol's work to make openMSX compile with VC++: - code still compiles with gcc, but i couldn't test myself with VC++, so probably this work is not finished yet ;) - build system is still TODO, these are only changed in the C++ source files 2009-01-07 Wouter Vermaelen * Released openmsx-0.7.0 2008-12-20 Wouter Vermaelen * Updated to cbios 0.22 2008-12-20 Wouter Vermaelen * For carta command, check for errors (rom and ips files) before removing the currently inserted cartridge. 2008-11-25 Wouter Vermaelen * Added '-repeat' option to 'bind' command: - When the '-repeat' options is given and the key remains pressed, the command will be repeat (just like regular key repeat while typing). - Used this feature in the OSD menu. 2008-11-01 Wouter Vermaelen * Added possibility to take screenshots of only the msx screen (so no OSD stuff and console): For the moment this is done by passing the option -msx to the screenshot command, this will take a screenshot of size 640x480. There will also be no scaler or any other effects in the screenshot. You can use -msxsmall to take a screenshot of size 320x240 (-msxbig is a synonym for -msx). TODO is the syntax of this command ok? Also still need to document it. 2008-10-29 Wouter Vermaelen * Optimized VDPCmdEngine: The core of most VDP commands goes like this: 1 transform x,y coordinate(s) to vram address(es) 2 do some operation on that address(es) 3 increase (or decrease) coordinates to loop over the whole rectangle (mostly only the x-coordinate changes) 4 repeat if there is still time left The coordinate to address transformation requires quite some shift and logical operations. In the next iteration all these operations are repeated. In this patch the vram address is calculated incrementally: we calculate it once at the beginning of the loop and then adjust the address when the x-coordinate changes. We have to take care of vram interleaving (screen 7 and 8) and that several pixels map to the same address (screen 5, 6, 7). A second optimization is to simplify the stop-condition for the innermost loop. Before we broke out of this loop either when destination time was reached or when we reached the end of a horizontal line (implemented as two seperate tests). Now we calculate up-front which of these two condition will occur first and iterate that many times (with only one test). I measured between 4% and 8% speedup on total emulation time for the Space Manbow intro demo (renderer=none, sounddriver=null). 2008-10-25 Manuel Bilderbeek * Added Sony HB-10P and Sony HB-55P, thanks to Rudi Westerhof. Also added Sony HB-20P a few days ago, thanks to MSXKun. 2008-10-16 Manuel Bilderbeek * Implemented rough, but measurement-based timing for V9990 for commands LMMV, LMMM, BMXL, BMLX and BMLL. Code by Wouter. Original measurement programs by Peter Mastijn. Measurements by Wouter and me, using Wouter's more generic measurement program. Fixes [421861]! 2008-09-25 David Heremans * Vram debuggable changed: First step towards creating a debuggable that provides a logical view upon the VRAM based upon the current videomode. This is the behavior an ML-coder expects when dealing with the VDP. The old chipbased view upon the vram is now called "physical VRAM" instead. 2008-09-21 Manuel Bilderbeek * Added Brazillian OPL3Cartridge (MoonSound without wavetable). Still mostly untested, although MSX-Audio games (e.g. from Compile) seem to work with it. See RFE tracker item 1815450. 2008-08-28 Wouter Vermaelen * Added 'probe' debug infrastructure: see 'help debug probe' or (doc/manual/command.html) 2008-08-20 Wouter Vermaelen * Made Disk and SectorAccessibleDisk write-protectable: Added WriteProtectable interface and refactor code so that really all write methods check this write protected flag. 2008-08-20 Wouter Vermaelen * Split MSXtar class into two parts: - DiskImageUtils: this part knows about partitions and format - MSXtar: this part knows about files and directories Rational: before this change, the interface of the MSXtar class was a bit confusing, especially the usePartition() method. Depending on which MSXtar functionality you plan to use, you either must call it, were not allowed to call it or must call it but ignore exceptions thrown by it. To simplify this, I split the functionality of the MSXtar class in two parts. Now MSXtar doesn't know anything anymore about partitions and formatting (of course it's still possible to work on partitions, but that's now abstracted via the new DiskPartition class). 2008-08-16 Wouter Vermaelen * Fixed some UMRs triggered during savestate: Before savestates this was not a problem because those value _are_ initialized before they are used in actual msx emulation code. Though savestates can read these values earlier. And if the variable was an enum, it could even lead to an assert. Thanks to Vampier for reporting this bug. 2008-08-16 Wouter Vermaelen * Diskmanipulator changes: - Teach diskmanipulator about multiple active msx machines. Before this change 'diska' always refered to the 1st drive of the oldest machine. Now it refers to the active machine. It's also possible to explicitly qualify the wanted machine with syntax like 'machine2::diska'. - Improved check on whether the specified partition exists or not. 2008-07-24 Wouter Vermaelen * Changed our Unicode code with UTF8-CPP code: http://utfcpp.sourceforge.net/ This fixes the TODO's left in our Unicode code. But it's mainly dome as preparation to serialize the Keyboard class (because a 'type' command in progress doesn't survive a savestate ATM). The old code in the Keyboard class contained a UTF16 encoded string, but the serialization framework can't (directly) serialize that. 2008-07-24 Wouter Vermaelen * Optimized de-serialization of arrays: Directly deserialize the elements in the correct position in memory. Before the element was loaded in a local variable and then copied to the correct position in the array. This also fixes a bug in AY8910 when detune/vibrato was used, because copying a AY8910::ToneGenerator object is not allowed. 2008-07-20 Manuel Bilderbeek * Renamed Midi{In,Out}Native to Midi{In,Out}Windows (untested!) * Made save/loadstate commands a bit more comfortable, so we can easier test this stuff :) 2008-07-19 Wouter Vermaelen * Enabled savestates to memory (as opposed to XML files): - Currently available via the 'mem_savestate' and 'mem_loadstate' commands, but this will still certainly change. - Speed seems to be quite good: savestate MSX1: 125us MSX2: 310us TurboR: 734us boosted MSX2: 3770us This between 200 and 400 times faster than the XML savestates! So it seems to be fast enough to implement a 'reverse-time' feature. 2008-07-14 Wouter Vermaelen * Serialize pluggables (WIP): Current code serializes all pluggables even those that are not plugged in. The available pluggables depends on the platform (e.g. the Midi{In,Out}Native pluggables on windows), the openmsx configure options (e.g. CassetteJack), the openmsx version (more pluggables in newer versions) and even the state of the host machine (connected a USB joystick). ATM loading a savestate only works when the exact same list of pluggables is available. Maybe we should only serialize those pluggables that are actually plugged in in the emulated machine (TODO verify that pluggables don't keep state over unplug-plug cycle). ATM only the state of the pluggables itself is serialized. We don't yet restore the actual plug state (which pluggable is plugged in which connector). 2008-07-05 Wouter Vermaelen * Merged Rom{4,8,16}kBBlocks classes into one templatized class 2008-07-01 David Heremans * Fixed two bugs in the diskmanipulator: - one preventing correct 360KB disk creation on some machines - one valgrind warning about variable initialization 2008-06-30 Wouter Vermaelen * More serialization work: - Philips_NMS_8250 can be serialized now, but still very few other machines or extensions are supported 2008-06-28 Wouter Vermaelen * Added HIGHLY EXPERIMENTAL savestates: - Only very few things work yet, but I was able to resume 'Road Fighter' running in a MSX1 (actually that's about the most complex setup that already works). - There are two new commands (still VERY primitive) savestate loadstate 'savestate' will create a openmsx.xml.gz file in the current directory. 'loadstate' will read this file and create a new machine based on this state. You can use 'activate_machine ' to switch to that machine. 2008-06-18 Wouter Vermaelen * Require extension hardwareconfig.xml files to list devices inside a tag. We already updated all our device configs for this over two years ago. 2008-06-17 Wouter Vermaelen * Implemented TMS99x8 VRAM remapping (reg#1, bit 7): thanks to: - enen for providing the technical info - Quibus for testing it on both real and emulated machines 2008-06-14 Wouter Vermaelen * Implemented 'tabbed' machines: It's now possible to have multiple msx machines in memory and freely switch between them. This is done with the already existing 'activate_machine' command. Only the active machine is running, the other machines are paused. This may not be the most useful feature, but we already had the 'activate_machine' command for other reasons (e.g. machine switching) and you could argue that it's a bug that it didn't already work like it does now. Basically we already had all the infrastructure in place. Actually enabling session support required relatively few changes (mostly mechanical). Also having this feature working gives more confidence that the design is right. 2008-06-12 Wouter Vermaelen * Simplified machine handling stuff in Reactor: Before we had some (complicated) logic in the Reactor class to postpone the actually machine switch till the main reactor loop has had a chance to run. The reason for this was that the 'machine' command (or other related commands) can get executed via a code path that runs through the current machine, meaning there are stackframes active that refer to the current machine. Only in the main reactor loop we're sure there are no such stackframes. A consequence of this approach is that for example in this sequence of Tcl commands: machine turbor ; diska mydisk.dsk The 'diska' command is still executed in the old machine, because the reactor loop has not run yet. If there is 'some pause' inbetween the two commands, the 'diska' command is executed in the new machine. At the very least this is not very intuitive. Taking a step back, switching the active machine (which has active stackframes) was not the problem, but deletion of that machine is. So instead of postponing the whole switch, we now only postpone the deletion of the old machine. The Tcl commands now work as expected. Also did some other refactoring to make the code easier to understand. 2008-06-12 David Heremans * Fixed a small bug in DirAsDisk: Read-only host-OS files became truncated in the MSX dsk image. 2008-06-05 Wouter Vermaelen * Fixed compilation of CPU code with gcc-3.4: Compilation with gcc-3.4 was broken because of a compiler bug, gcc-4.x works just fine. Worked around it. Though I still like to demote gcc-3.4 support to second class. 2008-06-04 Alex Wulms * Added French version of VG8010/VG8020 and corresponding unicodemap files 2008-06-04 Wouter Vermaelen * Fixed V9990 vertical scroll behaviour: Based on MRC forum post by GhostwriterP (page 3, post 12) http://www.msx.org/forumtopicl8574.html 1) Writing to high byte of scroll register only takes effect at the start of the next frame. This is for both scroll A and B registers. 2) Writing to low byte has effect the next line. The next line that will be displayed is the line number just written to the register 3) point 2) has no effect on the sprite vertical sprite position (this was wrong in the initial openmsx implementation) 2008-06-03 Wouter Vermaelen * Added -clip option to OSD widgets: - If this option is true, then child widgets are clipped to the bounding box of this widget. - Used in the OSD menu script to make sure (long) text lines don't gun over the end of the menu window. * V9990 display enable only takes effect at the next frame 2008-05-28 Wouter Vermaelen * Reduced code duplication in CPUCore by templatizing various stuff: - For example, a lot of instruction methods only differ in the registers they act upon (e.g. inc_hl() vs inc_de). By making templatized helper functions to read/write registers, a lot of these instruction methods can be combined into a single templatized instruction method. - This reduced code size by almost 2000(!) lines. - The generated code is also about 100kB smaller. I'm not sure why exactly, but I think because it doesn't need to keep a non-inlined version of the templatized instruction methods in the final executable. - Speed of the CPU code has not changed. 2008-05-27 Wouter Vermaelen * Tuned R800 refresh (still WIP) 2008-05-26 Wouter Vermaelen * Implemented dynamic IO port allocation for Panasonic MSX-AUDIO: fixes: [1231256] Panasonic MSX-AUDIO not detected for games 2008-05-24 Maarten ter Huurne * Fixed AVI recording (ZMBV encoding, to be exact) on big endian CPUs. 2008-05-24 Wouter Vermaelen * Fixed R800 page-break behaviour: - did a lot of measurements on a real turbor, see doc/r800test.txt - refactored CPU code to match this (WIP) 2008-05-21 Wouter Vermaelen * Reading sectors from a disk image via the SectorAccessibleDisk interface didn't apply the IPS patches: This had as effect that accessing the disk in the emulated machine did show the effect of the IPS patches, but using the diskmanipulator on the same disk ignored the IPS patches. 2008-05-19 Manuel Bilderbeek * Final fixes on Nettou Yakyuu mapper, including enhanced sample player. 2008-05-17 Manuel Bilderbeek * Added WIP version of Nettou Yakyuu mapper, used by the game "Moero!! Nettou Yakyuu '88". Thanks to hap/enen for the research and blueMSX implementation and Wouter for teaching me about this stuff. 2008-05-16 Wouter Vermaelen * Fixed crash in DirAsDsk / DiskManipulator: Occured when a diskimage used a different end-of-fat marker than 0xFFF. In fact the whole range 0xFF8-0xFFF should be interpreted as EOF. See: http://en.wikipedia.org/wiki/File_Allocation_Table#File_Allocation_Table 2008-05-03 Wouter Vermaelen * CPU time keeping speedup: - We want to have cycle-accurate bus transactions (memory or IO-port read/writes). So for example if in one instruction there are multiple transactions, they should happen at slightly different moments in time. Also the time of the first transaction is not necessarily the same time as the start of the instruction. Initially we implemented this by incrementing the 'current time' by very small steps at each sub-instruction. We actually only need this time for very few transactions. The vast majority of the transactions goes to ROM or RAM (IOW to 'cachable' memory regions), and there the exact time is not important at all. To speedup this time keeping, we now only increment the 'current time' with the whole instruction cycle count. In case we do perform a transaction to non-cachable memory we still first have to calculate the current time. To be able to do this all transactions have been annotated with 'cycles since start of the instruction' information. I measured between 5% and 10% speedup on the ZEXALL benchmark with this change. 2008-04-30 Wouter Vermaelen * Optimized check for pause in CPU code 2008-04-29 Wouter Vermaelen * Use switch statement instead of jump table in CPUCore: - Is about 10%(!) faster when using gcc-4.2, more when using recent gcc snapshot. With gcc-4.1 it's performance neutral, gcc-3.4 was a big slowdown (but we care less about such old compilers). - Some history: I had already tried this before, but at that time it wasn't a win. Recent gcc versions are able to eliminate the check for the 'default' case when it can prove all cases are enumerated in the switch statement (here we switch on a byte type and provide all 256 cases). With this optimization the switch approach gave a small speed gain. Then I tried some additional tweaking (force inlining some core functions). And this gave a much bigger gain, even on older compilers. - Because we no longer use a jumptable there is no advantage anymore in using static functions (with an explicit 'this' argument) over regular member functions. This makes the code more readable again. 2008-04-08 David Heremans * Fixed a bug in the load_icons script 2008-04-07 David Heremans * Added a new 'fancy' skin 2008-04-06 Alex Wulms * Review unicodemaps for MSX models with 'international' font: - There were some errors in the maps. Especially for graph and shift-graph key combinations. 2008-04-06 David Heremans * New load_icons script: - Added suport for frame.png - Added time-before-fade-starts * Some extra comments in the DirAsDisk 2008-04-05 Wouter Vermaelen * Added share/skins/Vera.ttf.gz: - also added support for compressed font files 2008-03-30 Alex Wulms * Add support for Philips VG8010: - Add new unicodemap unicode.proto_int; the VG8010 has a keyboard matrix that differs completely from all other MSX models. It seems to be some kind of prototype of the keyboard matrix - Add 'GRAPH locks' processing logic to the copy&paste routine in the keyboard driver (GRAPH has a locking behaviour on VG8010) * Fix DEAD key behaviour in keyboard driver in KEY mapping mode: - In KEY mapping mode, RCTRL key must be mapped to row 5 col 2 in the keyboard matrix (DEAD key on 'international' MSX keyboard) or to whatever other position if user has loaded another KeyToKey mapping file. Either way, it has to follow the 'Key to Key' mapping logic - In CHARACTER mapping mode, RCTRL key must be mapped according to the DEADKEY position in the unicode map files (for the MSX models that do have a DEAD key) 2008-03-26 Wouter Vermaelen * Fixed Z80 timing of accepting a IRQ: - Accepting a IRQ in IM0 or IM1 should take 13 cycles, there is no extra wait cycle. For IM2 it takes 19 cycles (also no extra wait cycle). - Thanks to enen for testing this 2008-03-25 Wouter Vermaelen * Added -fadeTarget and -fadePeriod properties for OSD widgets: - This allows to smoothly fade the alpha value of a widget without further interaction with this widget. (I mean you don't have to manually change the alpha value in small steps.) - Allows to greatly speedup the OSD icons Tcl script. (In the GP2X port, it took a lot a time) 2008-03-16 Manuel Bilderbeek * Some small improvements in the cassette player: - clearer errors when both a wav and a cas image fail to work - properly flush the recording when switching to play mode Still needs more work to properly handle 'zero-recordings' after switching to play mode, see comments in the class. 2008-03-12 Wouter Vermaelen * Replaced C++ IconLayer with a Tcl script 2008-03-12 Wouter Vermaelen * Added 'user_setting' command: This allows to create openMSX settings from Tcl scripts. The main advantage of settings over regular Tcl variables is that their value is preserved over different openMSX sessions. See 'help user_setting' for more details. 2008-03-08 Manuel Bilderbeek * Added -doublesize switch to the 'record start' command, to record at a size of 640x480. Note: this is usually a lot slower... 2008-03-07 Wouter Vermaelen * Fixed bug in OSD rectangle drawing: As an optimization rectangles with alpha=0 (completely transparent), were not allocated in memory. This caused problems when we needed the dimensions of this rectangle to calculate the position of child widgets. Fixed by refactoring the transformation code. 2008-03-05 Wouter Vermaelen * Added 'openmsx_info realtime': It was already possible to query the emutime of a machine in Tcl, now it's also possible to query the realtime. * Added '-scaled' OSD property: OSD widgets with this property (or child widgets whose parent has this property) are automatically scaled according to the 'scale_factor' setting. This makes it possible to design a GUI independent of the host window resolution. 2008-03-03 Wouter Vermaelen * Extended OSD stuff: - widgets are now hierarchically structured (the 'osd create' subcommand has been changed to allow this) - child widgets are placed relative to their parent widget - there can be an offset relative to parent in pixels (in the future probably also in 'scaled' pixels) - or offset can be as a percentage of the bounding box of the parent widget 2008-03-02 Alex Wulms * Several updates to keyboard driver: - Rename all keyboard settings to start with kbd_ so that they are grouped together when listed in alphabetic order, like in the documents.html file and in the tab expansion of the various commands in the console - Add support for a few keys that are specific to the jp106 keyboard model (the most used keyboard in Japan) 2008-02-25 Wouter Vermaelen * Added 'MatraShockware' mapper type: This is a non-bankswitched mapper that uses a AMD flash ROM. The flash is wired-up in such a way that writes end up in a different region as reads. The effect is that the flash' autoselect function tells you that a region is not write-protected, but when you actually try to reprogram that region it has no effect. The game 'INK' actually uses this as a sort of copy protection (if this check fails, stage 2 goes back to stage 1). See also this forum thread: http://www.msx.org/forumtopic8329.html 2008-02-25 Wouter Vermaelen * Added new mapper type: plainflash It's plain mapper type (no bankswitching), but implemented with a Flash Rom, so it reacts to certain write-sequences. According to this forum topic http://www.msx.org/forumtopicl8329.html the Matra INK game uses this as a sort of copy protection. 2008-02-25 Alex Wulms * Several updates to keyboard driver: - Implemented auto-toggle of KANA-lock (and CODE-lock on VG8010) based on the character that the user enters. This is only done on MSX models on which CODE/KANA has a locking behaviour. Though, user can switch it off via auto_toggle_code_kana_lock setting. - Implemented auto-toggle of CODE/KANA-lock when pasting characters (either through catapult or via the type command in the console). This is always done on MSX models on which the CODE/KANA locks. - Implemented auto-toggle of CAPSLOCK when pasting characters. This is especially required for japanese models, which use CAPSLOCK to differentiate between hiragana and katakana entry mode when CANALOCK is on. - Added a mnemomic DEADKEY to the unicode maps, to indicate the position of the dead key in the MSX matrix (it is different on german MSX models) - Added setting keyboard_mapping_mode, so that user can select between KEY to key mapping mode and CHARACTER to key-combination mapping mode. - Refactored keyboard driver; unicode mapping data is now in its own class and it uses a std::map to store the mapping data in stead of a (sparse) 64k entries matrix. 2008-02-23 Wouter Vermaelen * Added low level OSD Tcl commands: - It's now possible to draw OnScreen filled-rectangles, images and text. See 'help osd' for more details. - Font rendering is done with SDL_ttf. This is a new library dependency. - This is still very much work in progress. 2008-02-13 Wouter Vermaelen * Added 'horizontal_stretch' setting: - amount of horizontal stretch can now be configured (some users repeatedly asked for this ;-) - removed horizontal_stretch enum from display_deform setting - as a bonus horizontal stretch can now also be used in combination with the 3D deform mode 2008-01-28 Alex Wulms * Initialized key_ghosting_sgc_protected parameter in all hardwareconfig.xml file: Initial value of the parameter is derived from keyboard code at ROM BIOS address 0x2c; for Japanese machines, it is set to false and for all others to true (this is based on tests with some real machines) 2008-01-24 Wouter Vermaelen * Introduced PLATFORM_GP2X #define, don't call getpwuid() if this #define is set 2008-01-23 Wouter Vermaelen * Conditionally compile 16/32bpp and scaler code: GP2X port only needs 16bpp and scale factor 1 2008-01-21 Wouter Vermaelen * Removed workaround for pre gcc-3.4 compilers 2008-01-18 Alex Wulms * Added key-ghost protection for SHIFT, GRAPH and CODE keys: This exists on several real models; these three keys are connected to row 6 via a diode (one diode per key) Have made it configurable through hardware.xml. Default is that the protection is on 2008-01-17 Wouter Vermaelen * Refactored OutputSurface class: - Made all members private (was protected) and added protected getter/setters. This makes the interface between base and sub classes explicit. - Renamed getFormat() to getSDLFormat() to make it clear that this method still works dircetly on SDL data structures. - Added lock(), unlock() and isLocked() methods. These wrap the SDL_LockSurface() and SDL_UnlockSurface() functions. It for example allows to check that getLinePtrDirect() is only called on a locked surface. The first two changes allow for easier refactorings. The last change is a step towards enabling the use of HW accelerated SDL-blit functions on the GP2X port. * Move calls to OutputSurface::lock() / unlock() to a lower level: Every function that wants to access pixels directly should now make sure the surface is locked. Similarly a function that wants to use blit operations must unlock the surface. This allows for the minimal amount of locked<->unlocked transitions. (Locking while already locked or unlocking while already unlocked is very fast.) 2008-01-16 Alex Wulms * Added German keyboard map (unicodemap.de): It is needed for Sony HB-F700D. Please note that currently it gives a key-ghosting effect when entering { character; on german MSX this character must be entered by pressing SHIFT, CODE and ( keys SHIFT and CODE are on same ROW SHIFT and ( are on same COLUMN Hence the < character gets 'ghost pressed' (in same column as CODE) Need to figure out how this works on the real machine. Does it contain hardware to prevent key ghosting? 2008-01-12 Wouter Vermaelen * Optimized SCC::generateChannels() 2008-01-11 Wouter Vermaelen * Optimized CheckedRam for the case there is no umrcallback set: In some cases (e.g. in manbow2) it still caused some overhead: Internally openMSX divides memory in 256 byte chunks (see ChacheLine.hh) as soon as all 256 bytes in a chunk have been written to, read/writes to this chunk happen without overhead. Usually this happens early in the MSX boot process or startup code of a game. However manbow2 has 2 or 3 chunks that are read very frequently but only partly initialized. Solved by treating the memory completely initialized in case the 'umrcallback' settings is empty (no callback function registered). 2008-01-08 Wouter Vermaelen * Fixed reading of wav cassette images on Windows: Some versions of(?) windows can't open the same file multiple times in read-write mode. But this is exactly what happened by using the (now removed) File::getLocalFile() method combined with SDL_LoadWav(). In the past (2007-08-29) we already solved a similar problem with IMG_Load(). We solved this by deleting the File object before using the path returned by getLocalFile(). This works for regular files, but not for compressed files. I like to gzip my wav images so we need a different solution. To solve this I removed the old getLocalFile() method and created a new class LocalFileReference. This class makes sure there is no open file reference anymore after its constructor finishes and that there is a local uncompressed version of the file for at least as long as the lifetime of the corresponding objects this class. 2008-01-07 Wouter Vermaelen * in CPUCore prefer to use 'unsigned int' over 'word' datatype: Results in smaller and faster code. On x86, instructions that work on 32-bit are shorter than those for 16-bit. Code size reduced about 10kB, code is slighter faster (depends a lot on compiler version and optimization options). ARM CPUs don't even have 16-bit operations, only (limited) 16-bit load/store instructions. Here code size reduced about 20kB and is about 4% faster. 2008-01-05 Wouter Vermaelen * changed SoundDevice::updateBuffer() method: As parameters this method (still) receives a number of buffers, each buffer should be filled with the audio-output of one channel. Before this change the existing data in the buffer should simply be overwritten. Now the new data should be added to the existing data in the buffer. This new approach allows to let several channel buffers actually point to the same underlying buffer (in fact only a muted or recorded channel will still get it's own private buffer). This allows to (mostly) skip the mix-channels step. I measured on my core2duo CPU and this new approach is slightly slower (a few percent) than the old one. If I disable SSE2 optimizations in mixChannels, it's about the same speed. However on older CPUs without SSE2 and with smaller caches (like ARM) it's definitely faster. I guess because this new approach is more cache-friendly. 2008-01-03 Maarten ter Huurne * Removed execution of ~/.openMSX/share/init.tcl on openMSX startup. We have a "scripts" dir which can be used instead. This avoids an infinite loop if the user copies the openMSX internal version of "init.tcl" into the user dir. 2008-01-03 Wouter Vermaelen * Created 5 new commands: - create_machine (returns machine ID) - delete_machine - list_machines (return list of machine IDs) - activate_machine - ::load_machine - These commands allow to implement the existing commands 'machine' and 'test_machine' as Tcl procs (not yet done). - This also allows to test extensions with the existing 'ext' command. - It also allows to have multiple running msx machine in memory, though ATM we always show the video/sound of the oldest machine. 2008-01-02 Wouter Vermaelen * Added DivModByConst and DivModBySame utility classes: - these allow to replace a 64-bit by 32-bit division or modulo operation by a multiplication/addition/shift. This helps if the divider is a constant, especially on CPUs without division instructions (like ARM) * Always use 512kb flash for Manbow2 and MegaFlashRomScc mapper types: Flash RAM saving now works in 'Lotus F3'. 2008-01-01 Patrick van Arkel (vampiermsx@gmail.com) * Added Original ROM tag to database and added tag handeling to RomInfo.cc. * Changed list.php on http://romdb.vampier.net to include original release tags. 2007-12-31 Wouter Vermaelen * Fixes for videosource setting: - after switching machines, the list of allowed values could be wrong - doing 'unset videosource' crashed while MSX was not powered on 2007-12-26 Wouter Vermaelen * Restructured MemoryOps::memset implementation: should make it easier to also add non-x86 asm routines 2007-12-24 Wouter Vermaelen * Optimized sprite drawing in mode 2: I still like this code to be reviewed. I believe it's correct, but sprite mode 2 is tricky sometimes. * Fixed error handling while parsing setting files 2007-12-19 Wouter Vermaelen * More YM2413 stuff: One of the previous optimizations triggered an assert, so I had to revert (part of) it. Now I reimplemented it in a correct way, it's even slightly faster than the previous implementation. * Made some YM2413 methods ALWAYS_INLINE: Gcc already did this for x86, but not for ARM. And it is measurably faster (also on ARM) if they are inlined. 2007-12-17 Wouter Vermaelen * Send out an update event for newly created settings: - This way catapult is informed of value changes for destroyed/recreated setting (happens for some settings during a machine switch). 2007-12-16 Wouter Vermaelen * Refactored BooleanSetting: Don't inherit from EnumSetting anymore. This requires slightly more code but allows to better tune the code: - openmsx_info setting now returns 'boolean' (was 'enumeration') - querying the value now returns true/false (was on/false) - as a bonus the new implementation has less #include dependencies 2007-12-14 Wouter Vermaelen * More YM2413 optimizations: - swapped inner/outer loop, now generate all samples for a channel before moving on the next channel. - made a special case for channels without AM or FM modulation 2007-12-13 Wouter Vermaelen * More YM2413 optimizations: - only update noise value in rhythm mode - made Channel::global a function parameter iso a member variable - moved 'fnum' and 'block' from Slot to Channel, merge them into one new 'freq' variable * General SoundDevice/MSXMixer optimization: - added SoundDevice::getAmplificationFactor(), this allows to move a multiplication (or shift) from the sounddevice to the mixer. In the mixer there already was a multiplication (for per device volume setting) so those two can be merged. 2007-12-12 Wouter Vermaelen * Various YM2413 optimizations: - made lfo_pm and lfo_am local variables iso member variables - we used to store the last two Slot output values, but actually one the last one was needed - inlined advance() and calcSample() methods, this allows the next two items: - calc_phase() and calc_envelope() are not required when channel is inactive (there is an exception for two rhythm channels) - moved calc_phase() and calc_envelope() into the different calc_slot_xxx() methods, this in turn enables the next two items - the egout member variable can become a local variable - rhythm channels don't use FM or AM, so we can use simpler calc_phase() and calc_envelope() variants - small simplifications, removed dead code * Made keyboard event parsing (for event recorder/replayer) slightly more robust 2007-12-10 Alex Wulms * Added unicode support to event recorder/replayer logic 2007-12-09 Aernold Metselaar * Fixed small bug in diskmanipulator: Extracted files timestamp had most signifiocant bit of minutes set to zero. 2007-12-09 David Heremans * Fixed small bug in diskmanipulator: Extracted files timestamp had an offset of one month. 2007-12-08 Wouter Vermaelen * Another YM2413 optimization: Before the Slot objects kept a pointer to a Patch object, possibly they can share the same Patch. One of the patches is used for the custom instrument, so this one can change. After this patch we keep the Patch object by value in Slot. A disadvantage is that if the custom instrument changes, we have to update it in every Slot that has this instrument selected (though we already had to check this for other reasons). This change removes one pointer indirection from frequently used code, in total I measured about 2% speedup. 2007-12-06 Wouter Vermaelen * Various optimizations in YM2413: Combined I could measure upto 10% speedup. 2007-12-05 Wouter Vermaelen * Optimized BitmapConverter::renderGraphic4() and renderGraphic6(): Created a 16x16 table to lookup two pixels at once. This helps a little on 64-bit machines in 32bpp mode. On 32-bit I couldn't measure a difference (positive nor negative). It also helps on 32-bit machines in 16bpp mode, as is the case for GP2X. There I could measure a clear speed gain. * Greatly simplified ResampleLQ: New implementation can only do downsampling, but that's good enough, even when output freq is 48kHz. 2007-12-01 Maarten ter Huurne * Dropped support for GCC 3.3. This was triggered by link errors when using std::basic_string in the new keyboard mapping code. Working around this would require a large amount of extra code to be written, which is not worth it. 2007-12-01 Wouter Vermaelen * Fixed problem in BliBuffer: In some cases our mute-optimization kicked in too soon, (low frequency, low volume, short audio sync intervals triggers it more often). This was audible as low frequency, low volume noise. * Previous changes in keyboard handling turned OPENMSX_FOCUS_EVENT and OPENMSX_BOOT_EVENT (also) into msx events, this is wrong. Event recording/replaying is still wrong because the logs don't contain unicode information. Need to discuss this with Alex. Maybe we need to log msx keyboard matrix info instead? 2007-11-25 Alex Wulms * Initialized keyboard_type and has_keypad parameter in all hardwareconfig.xml file: Initial value of keyboard_type is derived from keyboard code at ROM BIOS address 0x2c. Initial value of has_keypad is guessed from MSX version via a simple heuristical rule; it is set to false for MSX1 and to true for MSX2 and higher. 2007-11-24 Manuel Bilderbeek * Added connector update events, to keep track of which connectors are available. 2007-11-23 Wouter Vermaelen * Removed EmuTime parameter from MSXDevice constructor (was not used). 2007-11-22 Wouter Vermaelen * Optimization in SpriteChecker::checkSprites1() and SpriteChecker::checkSprites2(): Before we checked line-by-line which sprites are visible for that line. Now we check it for lines in a whole range at the same time. 2007-11-21 Alex Wulms * Added docs/keyboard.readme: This document explains which new parameters have been introduced in hardwareconfig.xml and in settings.xml 2007-11-21 Wouter Vermaelen * Optimized MSXMixer::generate(): see comments in that method for more details 2007-11-20 Wouter Vermaelen * Big optimization in AY8910::generateChannels(): Because the PSG generates mostly square waves and because we emulate the PSG at a high frequency (224kHz), lots of consecutive output samples have the same value (typically 100 or more!). We used to emulate the PSG step-by-step, instead now we calculate the moment in time when the output will change and output with that many equal samples. Because of this change PSG emulation itself became 3x-4x faster. On total emulation time this is upto 10% faster. This change was actually done as a preparation to use BlipBuffer more directly in the PSG code (because BlipBuffer only needs to know about changes in the sample stream). But on it's own it was already a nice speedup. This also means there is still potential for another big speedup. 2007-11-19 Alex Wulms * Removed conversion program (CVP) marker from russian keymap and removed few duplicate entries from spanish keymap 2007-11-18 Alex Wulms * Fixed unicode map jp_jis, introduced jp_ansi, ru, es : - The unicode maps are now derived from the MSX key matrixes on the MAP. See following URL: http://map.tni.nl/articles/keymatrix.php - Updated harwareconfig.xml of Sony_HB-F9P_Russian and of Panasonic_FS-A1 to test maps ru and jp_ansi 2007-11-16 Wouter Vermaelen * Optimization in CPUClock: Before this change we had two member variables 'limit' and 'extra'. Now they are replaced by 'limit' and 'remaining'. The variable 'limit' still has the same meaning, the variable 'remaining' is equal to the old 'limit - extra'. So the same information is represented in a slightly different way. The advantage is that the expression 'limit <= extra' now becomes 'remaining <= 0'. This is slightly faster but it's used in the innermost CPU loop. 2007-11-14 Alex Wulms * Processed review comments of Wouter on Keyboard class and made several other small improvements 2007-11-14 Wouter Vermaelen * Optimized cacheline handling in CPUCore: - before it was used like this: const byte* line = readCacheLine[address >> 8]; byte result = line[address & 0xFF]; now it's like this: const byte* line = readCacheLine[address >> 8]; byte result = line[address]; So the cacheline doesn't store directly the address of the buffer, but the buffer minus (address & 0xFF00). 2007-11-13 Wouter Vermaelen * tiny speedup: Added non-virtual method RawFrame::getLinePtrDirect(), the base class already had a virtual method FrameSource::getLinePtr() that does the (on this level). In some places (like SDLRasterizer) we know we're working on a RawFrame, so using getLinePtrDirect is slightly faster. For consistency I also renamed the (non-virtual) method in OutputSurface to getLinePtrDirect(). 2007-11-12 Wouter Vermaelen * Optimized ResampleBlip: This is not the blip algorithm itself, but the preprocess step that transforms a sequence of samples into a sequence of 'sample value changed' events. 2007-11-08 Wouter Vermaelen * In various places performed the following two optimizations: - Prefer to use int or unsigned int over shorter types (char, short). Results in both faster and shorter code (both on x86 and ARM). - Prefer to use unsigned over signed types. Especially when the var is used in division or module operations (more important on ARM than on x86). But even a simple divide by 2 (or any power of 2) is slower on signed types! This is because divide rounds toward zero, while a shift-right operation rounds down. 2007-11-07 Wouter Vermaelen * Added 'machine_info device []' info topic: - Allows to query the device type of a given devicename - Preparation to be able to query rom types * Extended 'machine_info device' to also return the romtype (only for roms of course) 2007-11-06 David Heremans * DirAsDisk has a new sector cache format: - Forced little endianess of numbers in the cache. - Made sure that files are mapped on the same sectors. - Invalidate the cache if files have changed size - New files are added afterwards and do not conflict with the cache. 2007-11-06 Wouter Vermaelen * Fixed manbow2 mapper type: First 7 sectors (1 sector is a 64kb block) are write-protected, only the last sector is writable. Created a seperate mapper type 'MegaFlashRomScc' (existed already as an alias) which has the complete range writable. 2007-11-04 Alex Wulms * Introduced unicode character handling in Keyboard class: - Before, SDL key-presses where mapped to MSX key-presses. Now, unicode characters get mapped to MSX key-combinations when SDL returns a unicode character. Otherwise, SDL key still gets mapped to MSX key 2007-11-03 Wouter Vermaelen * Optimized AY8910::generateChannels(): Swapped inner/outer loop. Now the outer loop goes over all (=3) channels and the inner loop over the samples. We (potentially) duplicate the calculations for the 'shared resources' between the channels (envelope and noise), but the inner loops are much simpler. In the cases I measured there was a significant net gain: it only takes 60% of the original time. * Additional AY8910::generateChannels() optimization: - Also use the 'silent channel' optimization for channels that use envelope volume and envelope has reached zero (and stays there). - Simplified the initial tests in generateChannels() 2007-11-02 Wouter Vermaelen * Optimized MSXCPUInterface::setPrimarySlot(): more efficient way of updating the not-allowed-to-be-cached region information 2007-11-01 Wouter Vermaelen * Various CPUCore tweaks: - mostly to eliminate branches or to produce shorter code - I couldn't measure a speed difference, but the code is 2% shorter 2007-10-31 Wouter Vermaelen * Made normal and '_fast' variant of Clock::getTicksTill() and Clock::advance(). Use the _fast variant on places where speed is important and where it's safe to use. (Since 2007-10-03 we always used the fast variant, but occasionally that's wrong). 2007-10-28 David Heremans * DirAsDisk now only saves 'CACHED' type sectors and the final sector of 'MIXED' file caches in the '.sector.cache' when the disk is ejected. 2007-10-26 David Heremans * DirAsDisk now dedects new files on the Host OS 2007-10-24 David Heremans * Fixed DirAsDisk detecting moved/erased files on the Host OS 2007-10-24 David Heremans * Extended the DirAsDisk functionality: Due to popular demand.... Got sync from MSX disk to Host OS correct for create, delete, rename, grow and shrinking of files. Tested with Philips NMS 8250 and FS A1-GT who both have a completely different approach/sequence to handling these actions. Still a lot to do and test but we are getting somewhere at least. 2007-10-23 Wouter Vermaelen * Fixed regression in SCC (introduced in 6430): update output after a freq register write 2007-10-21 Wouter Vermaelen * Fixed detail in SCC rotation mode: if deform bits 7-6 are 10, wave form 4 rotates at freq5 speed see: http://www.msx.org/forumtopicl7875.html 2007-10-15 Wouter Vermaelen * Initial version of Manbow2 mapper: this is work in progress, possibly the way the flash is saved will still change * Added MegaFlashRomScc extension 2007-10-08 Manuel Bilderbeek * Added Sony HB-F1XDJ config, thanks to msxrestarter. Function and use of the big firmware ROM is not clear yet, but it should work, as it uses the Halnote mapper. 2007-10-03 Wouter Vermaelen * Optimized 64-bit divide-by 32-bit operations: - the x86 'divl' instruction already does a 64-bit by 32-bit division, though it generates a trap when the result does not fit in 32-bit. That's why it's not (directly) used by gcc. In certain (time critical) places in openmsx we know the result fits, so we can directly use this instruction. 2007-10-03 David Heremans * Extended the DiskManipulator: It is now possible to export a single file or subdir from the msx image 2007-09-29 Wouter Vermaelen * Fixed Halnote mapper, based on info from blueMSX 2007-09-19 Wouter Vermaelen * Fixed bug in stereo blip resampler: in case only one of the two channels was active, we left the other channel undefined (now we fill it with zeros) * Fixed bug in CPU code (probably introduced by me on 2007-08-28): - limitReached() bookkeeping was wrong when the advanceTime() method was used. This only happens when switching between Z80/R800. Fixed now. - This new implementation is also faster. Before the CPU polled the scheduler for the earliest sync-point (after every IO and memory mapped access). Now the Scheduler pushes new sync-point data to the CPU. 2007-09-14 Wouter Vermaelen * Fixed TR status bit in V9990 (was inverted): - TR=1 means ready for transfer, thus V9990 is *not* ready to execute (part of) the command, it's waiting for data - Apparently most SW doesn't bother to check this bit (since the V9990 is fast enough). However Symbos does check it! * Initial implementation of V9990 LMCM command: - maybe not correct yet, but certainly better than no implementation * Fixed odd/even pixel problem in LMMC and LMCM commands 2007-09-13 Wouter Vermaelen * Mute individual PSG channels: - already speeds up the PSG code itself (though i only added code) - also enables speedups in the following stages of the audio flow * In SoundDevice::mixChannels(), special case mixing of 0, 1, and odd or even number of channels, also unroll 2x * Added SSE2 optimized version of SoundDevice::mixChannels() 2007-09-12 Wouter Vermaelen * Use resampler for SamplePlayer * Process samples per 4 in SoundDevice::mixChannels() On itself this is already faster, but it also paves the way for future SSE2 optimizations. 2007-09-08 Wouter Vermaelen * Fixed harmless(?) UMR in Y8950Adpcm * Don't take sqrt() of negative numbers (in case of rounding errors) * Fixed crash while using breakpoints 2007-09-06 Wouter Vermaelen * SpriteChecker improvements: - get rid of expensive division in SpriteChecker fast-path - duplicate SpriteChecker::checkSprites2() for planar and non-planar modes 2007-09-06 Wouter Vermaelen * CPU code tweak: - slightly better signed overflow calculation - use RD_WORD_PC to read two consecutive opcode bytes 2007-09-05 Wouter Vermaelen * Big optimization in CPU code: - Before we used function-to-member-pointers, now we use function-pointers to static functions that take an explicit 'this' argument. This doesn't look as nice, but gcc can generate faster code for it. - In the 'zexall' test with renderer=none speed improved from about 106s to 85s on my system! In a 'Space Manbow' test (also with renderer=none) speed improved from 54s to 48s! 2007-08-30 Wouter Vermaelen * Optimized YM2413 and YM2413_2 idle mode: After profiling we found that even a completely idle YM2413 was at the top of a CPU profile (while running PSG games). The only thing the YM2413 was doing was updating the internal noise generator and AM/PM/evelope counters. Because this is so expensive we disable that after some idle time (for now 200ms). This means the noise sequence and AM/PM phase will not be exactly the same as on real HW when the music resumes, but that's most likely not audible. 2007-08-29 Maarten ter Huurne * Destruct File object before loading a file with IMG_Load from SDL_image. This should fix sharing violations on Windows. 2007-08-28 Wouter Vermaelen * Split CPUClock::setLimit() in inline/non-inline part: gives a nice speedup in IO-port intensive applications * Tweaked calculation of current CPU time 2007-08-27 Maarten ter Huurne * Do not cache the result of native system detection. This makes it easy to use the same source tree on different systems, for example when the sources are on a network drive or if you do both 32-bit and 64-bit builds on an x86_64 system. 2007-08-27 Wouter Vermaelen * Created release branch for openMSX 0.6.3. * Various optimizations in CPU: - wrote specialized (simpler) code for instructions like xor a / cp a / or a / add a,a / sbc hl,hl - split cpuTracePost in a small inlineable part and a non-inline rarely executed part - optimized time book-keeping in the read/write memory paths * Small optimization in SDLRasterizer (+ cleanup): - don't render the same VRAM line twice in case of (single page) hardware horizontal scroll 2007-08-20 Wouter Vermaelen * Optimization in SDLRasterizer: - if possible directly render to destination (instead of to local buffer followed by memcpy) 2007-08-11 Wouter Vermaelen * Made diskmanipulator command also available on machines without disk drives 2007-08-07 Wouter Vermaelen * Added -nopbo command line option to disable use of OpenGL Pixel Buffer Objects 2007-08-07 Wouter Vermaelen * Show build flavour in the version string (not for release builds) * Fixed crash on exit while removing an extension that created extra external slot(s) and occupied (some of) those itself. 2007-08-06 Wouter Vermaelen * Compiled with -Wpadded and changed the order of some members so that the enclosing struct/class requires less internal padding: - I did the same thing on 2007-04-24, however this time I'm compiling on x86_64, so pointers require 8 bytes (iso 4). There were also a few new classes that needed some shuffling. 2007-08-04 Wouter Vermaelen * Enable -Wold-style-cast warnings by default and fixed all warnings triggered by this * In CPUCore only do unaligned memory accesses if we know the host CPU supports it 2007-08-01 Wouter Vermaelen * Added psg_profile Tcl script 2007-07-31 Wouter Vermaelen * Optimized cassetteplayer audio part, also use resampler 2007-07-30 Wouter Vermaelen * Some fixes in ESE_SCC 2007-07-29 Wouter Vermaelen * Commented out win32 specific sleep/timer code, it gives problems on dual core CPUs 2007-07-28 Wouter Vermaelen * Fixed [ 1622018 ] New persistant directories created for every run-time switch * Use resampler for YMF278 (MoonSound wave part): also added a trivial resample mode for the case that input and output sample rate are the same (as will often be the case for YMF278, as its native freq is 44100Hz) 2007-07-27 Wouter Vermaelen * Disabled OpenGL SaI shader, see comments in GLScalerFactory 2007-07-26 Wouter Vermaelen * Fixed [ 1730148 ] config checker creates big HD images: image is now only create on first use (read/write), so not yet when the machine is instantiated (=config check) * Added experimental PSG detune option, similar to PSG vibrato: - control with the PSG_detune_frequency and PSG_detune_percent settings - next step: experiment with the PSG detune/vibrato and hopefully remove one of the two again. Maybe also remove the PSG_*_frequency setting(s). 2007-07-21 Patrick van Arkel (vampiermsx@gmail.com) * Updated to Version 2.1 of the cheat defenitions after adding a ton of new cheats provided by Mars2000you. 2007-07-16 Wouter Vermaelen * Added proper MSXDOS2 mapper type (before we used generic16kb): - implemented by BiFiMSX, I only fixed some bugs 2007-07-14 Wouter Vermaelen * Implemented 'Super Lode Runner' mapper: This mapper is special because it ignores the SLOT-SELECT signal; it reacts to writes to address 0x0000 in _any_ slot. This violates the MSX standard. For a long time we didn't implement this mapper because it would add a (small?) performance hit on all memory writes. However, now, since we implemented the watchpoint infrastructure, the mapper can be implemented without any performance impact (no performance impact if the mapper is not inserted). 2007-07-05 Maarten ter Huurne * Added vibrato effect for PSG. You can control it with the settings "PSG_vibrato_percent" and "PSG_vibrato_frequency". Good values are around 1.0% and 4.5Hz. By default "PSG_vibrato_percent" is 0, which turns off the effect. Thanks to Wolf for the idea and his feedback on experiments. 2007-07-02 Wouter Vermaelen * Use pimple idiom to move implementation details from hh to cc for YM2413 YM2413_2 YMF262 YMF278 * Fixed regression in YM2413_2 core: Was introduced in revision 6372. We used a 8.24 FixedPoint datatype, but this goes wrong because our FixedPoint types are signed. Solved by using much smaller resolution FixedPoint datatypes (this is even a simplification and a small speedup). 2007-06-30 Wouter Vermaelen * allow to use bindings on joysticks that are not plugged in (into the emulated MSX) 2007-06-27 Wouter Vermaelen * Created new directory 'utils' and moved several files into it 2007-06-26 Manuel Bilderbeek * Added Software and Creation Date info to AVI files. * Added Creation Time info to PNG files (Software was already there). 2007-06-23 Wouter Vermaelen * Added experimental BlipBuffer support for DACSound based sound devices: - heavily based on http://slack.net/~ant/libs/audio.html#Blip_Buffer * Added BlipBuffer based resampler: - Selectable via 'set resampler blip' - I'm not sure we want to keep this global or if we want to make this resampler default for specific devices. But for now it's an easy way to experiment 2007-06-19 Manuel Bilderbeek * Voice ROM of Keyboard Master is now also searched in ROM pools (sha1 is hardcoded). 2007-06-11 Wouter Vermaelen * Fixed bug in SCC peek: - peek of deform register also triggered a reset of that register (like a regular read does) * Implemented peekMem() for ESE_SCC / MSXSCCPlusCart * Implemented read/write cache line stuff for ESE_SCC 2007-06-11 Wouter Vermaelen * Added (optimized) clipping routines in Math.hh and use them in several places: - idea for int->short clipping is taken from Blip_Buffer http://slack.net/~ant/libs/audio.html#Blip_Buffer 2007-06-09 Maarten ter Huurne * Use resampler for AY8910 (PSG). 2007-06-06 Manuel Bilderbeek * Added a dummy port of blueMSX's YM2148. TODO: actually fill in the code. It's harmless, so I checked it in to have a base for a real port. 2007-06-06 Wouter Vermaelen * Access all CPU registers via getter/setters: - as preparartion for performance experiments, this change on its own has very little impact on the generated code * Alternative implementation for CPURegs: - see comments in the code for details - this version is a few percent faster for me 2007-06-05 Maarten ter Huurne * Added support for DESTDIR in the build system: If you run "make install DESTDIR=/some/path", all installation paths will have that prefix prepended to them. For example, the default location of the executable would become: /some/path/opt/openMSX/bin/openmsx 2007-06-04 Wouter Vermaelen * Another CPUCore optimization: - Made optimized 16-bit read/write routines. Originally this gave a slowdown, caused by gcc no longer inlining some bigger routines. After forcing gcc to inline the memory read/write functions, there was a huge speedup: upto 15% in a CPU only benchmark!! Forced inlining is responsible for the largest part of this speedup (very roughly 3/4 by always inline, 1/4 by new 16-bit routines). * Experimental IM2 support for YamahaSfg05 (untested) 2007-06-03 Wouter Vermaelen * CPUCore optimization: - in RDMEM / WRMEM routines, keep cache line address in local variable instead of reading it twice from an array. Apparently gcc was not able to prove that array didn't change during a call to T::POST_MEM(). - Gave more than 10% speedup here in a CPU only benchmark (zexall with frameskip=99) 2007-06-02 Wouter Vermaelen * Sound recording (seperate channels or combined) now always records to sound, irregardless of the 'mute' setting 2007-06-02 Albert Beevendorp * Added Password Cartridge implementation. The value between the tags in the hardwareconfig.xml may vary from 0x0000 to 0xffff. It's used by the BIOS to read the password from the cartridge if one is set. This implements feature request 1411507. 2007-06-01 Manuel Bilderbeek * Added an LS-120 SCSI device. - Mostly as demonstrator of a removable SCSI device. - Could be used to make a SCSICDROM easier to make. - Can also be used to determine common stuff between SCSI devices. - TODO: change 'lsX' commands to something better. - TODO: remove massive code duplication with SCSIHD/HDCommand. 2007-05-31 Manuel Bilderbeek * Removed all dead code from SCSIHD and attempted to move device-specific stuff inside the class. I'll try to make an LS-120 device soon, to demonstrate a removable device. * Note that yesterday we also added ESE SCC and WAVE-SCSI. The latter doesn't seem to work properly, though. 2007-05-28 Manuel Bilderbeek * Added initial MEGA-SCSI emulation. It's not working completely OK yet... Please help debugging :) (Thanks a lot to Wouter and Albert for their help.) * It seems to work after all: make sure to do a physical format of the disk first (using SFORM-2.COM). * Lots of cleanups, also by Wouter. * Added ESE Ramdisk (a.k.a. ESE RAM). This is MEGA-SCSI without SCSI. 2007-05-26 Manuel Bilderbeek * Created common superclass for SCSIHD and IDEHD called... HD. This makes it possible to let HDCommand work with any kind of HD. Now you can use the hdx commands also for SCSIHD's. Note: (off-topic) diskmanipulator can't handle corrupt disks. Note 2: diskmanipulator doesn't support the partition table formats used on SCSI harddisks yet. So, until then, SCSI devices cannot be used from diskmanipulator. Note 3: implementation of HD class could be cleaner... (it's using a protected member "file" now...) 2007-05-24 Manuel Bilderbeek * Read initial size and file name from config XML and create disk image in the persistent directory. * GoudaSCSI is now usable! * Implemented LED events for SCSIHD. 2007-05-23 Manuel Bilderbeek * (GoudaSCSI is basically working, yay! But please do not use it yet.) * Introduced abstract base class for SCSIDevices and added DummySCSIDevice (and made use of it). 2007-05-21 Manuel Bilderbeek * Added initial GoudaSCSI support, ported from blueMSX. NOTE: DO NOT USE YET! This is very much WIP! It's not working yet and contains lots of shortcuts and hardcoded stuff. So, again: don't use it. But please help improve it! :) (You are warned!) 2007-05-19 Wouter Vermaelen * Removed old (unoptimized) implementation of the high-quality resampler: sound quality is the same 2007-05-18 Wouter Vermaelen * Added fast/low-quality resample algorithm * Added 'resample' setting with possible values 'fast' and 'hq' to switch between the different resample algorithm 2007-05-17 Wouter Vermaelen * Moved int<->float conversion from sounddevices to Resampler: - preparation for fast/low-quality resampler 2007-05-14 Wouter Vermaelen * Share resample coeff table between Resample instances with the same resample ratio 2007-05-12 Wouter Vermaelen * Additional speedup for the Resampler: - unrolled the loop (+ break dependencies) - added SSE optimized routine (mono only for the moment) - also added SSE optimized stereo resampler * Fixed memset4_2_MMX(): - we used to pass a pointer to a struct on the stack, but newer gcc versions optimize this struct away 2007-05-11 Wouter Vermaelen * Experimental speedup for the Resampler: - code is very quick-and-dirty - still contains both old and new implementation to make sound quality comparison between the two possible 2007-05-10 Wouter Vermaelen * Use 'SCC' instead of rom-name as name for the SCC sounddevice 2007-05-09 Wouter Vermaelen * Optimized Resampler a bit: - used 'x - trunc(x)' instead of 'fmod(x, 1.0)' - but it's not used in the innermost loop so the speed gain is not that big (it is measurable though) 2007-05-08 Wouter Vermaelen * Added SSE2 optimized noise routine in FBPostProcessor * Restored parsing of the and tags in the soundsection of hardwareconfig files. There can also be a tag now (that overrules ) * Removed volume handling from sounddevice code: - it's now handled (together with balance) inside MSXMixer 2007-05-07 Wouter Vermaelen * Removed _mode setting: - will be replaced by a balance setting soon - TODO what about backwards compatibilty? * Added _balance setting 2007-05-04 Wouter Vermaelen * Added support for SSE2 in HostCPU * Added SSE2 optimized routine in ScanLine class 2007-05-01 Wouter Vermaelen * Reworked internal sounddevice mute code: - As an optimization is was possible for a sounddevice to indicate that it won't produce sound in it's current state. In that case the mixer wouldn't bother to call the updateBuffer() method for such devices. It was also already possible for a sounddevice to indicate that a specific channel is completely silent. Now these two approaches are unified: sounddevice should only inidicate which individual channels are muted. If all of the channels are muted, the rest of the sound code detects this and the same optimization as before happens. In this new approach the updateBuffer() is called in any case, so it's now possible to both correctly update internal state and benefit from the muted optimization. - The glitches that happened after unmute on resampled devices are now gone. But that because muting is effectively disabled for these devices for now. Although this new approach does make it possible to solve it and have the mute optimization. It's just not implemeted yet. 2007-04-30 Maarten ter Huurne * Fixed MSX reading from SCC: frequency and volume block is write-only. * Fixed SCC reset behaviour: only deform and channel enable are changed. 2007-04-24 Wouter Vermaelen * Compiled with -Wpadded and fixed some of the warnings: - only fixed the cases that were solvable by rearranging the order of the fields locally in one class * Fixed cassette input bit (PSG R#14 bit 7) on turboR (thanks BiFiMSX) 2007-04-23 Wouter Vermaelen * Extended mute_channel related procs: - mute_channels without any arguments lists which channels are currently muted - unmute_channels without arguments now unmutes all channels - new command 'solo' mutes everything except for the specified channels * Small optimization in SDLGL-PP simple scaler: - pack several scalar uniform variables in one uniform vector variable. Removes one MOV instruction from the compiled fragment shader. 2007-04-21 Wouter Vermaelen * Recording/muting of individual sound device channels works: - it's still quick-and-dirty, but i wanted to have something working soon - use either via the _ch_record and _ch_mute settings or via the record_channels Tcl script (no mute yet) examples: record_channels PSG record_channels SCC 1,3-5 record_channels stop record_channels stop PSG 2 record_channels list - TODO: documentation make recording stutterless record silence when complete sounddevice is muted (muted manually or implicitly by the device itself) * Improved channel recording: - Channel recording is now also stutterless. - Recording continues when sounddevice is internally muted. Unfortunately explicitly muting the device doesn't work now anymore when some channels of the device are beiing recorded. Fixing this requires some more work. * Added tabcompletion + help for the record_channels script * Extended script with (un)mute_channels procs 2007-04-19 Wouter Vermaelen * Converted sounddevices to generate output as seperate channels: - This is very much work in progress. There is no code yet to take advantage of this split. The code in the converted sounddevices should still be cleaned-up and optimized. * Refactored SoundDevice and ChannelMixer: - merged SoundDevice and ChannelMixer classes - made SoundDevice aware of input and output sample rates - added methods in SoundDevice to record and mute individual channels. Next step is to actually use these methods. 2007-04-18 Joost Yervante Damad * fix build with gcc snapshot 20070326 2007-04-16 Wouter Vermaelen * Implemented SDLGL-PP RGBTriplet scaler * Implemented SDLGL-PP SaI scaler: This algorithm is not well suited for a HW implementation (it has a lot of branches). So this scaler will probably run a bit slower than the other HW scalers. 2007-04-15 Manuel Bilderbeek * Added emulation of Yamaha YM2151 and partly the Yamaha SFG-05 sound module. For the latter, I included the config of the Yamaha CX5MII/128. TODO: MIDI and keyboard, so only few software titles for it work. Thanks to Daniel Vik for his support! The code is taken from blueMSX and MAME. * Added emulation of the VLM5030 sound device, which is used in the unreleased Konami game Keyboard Master. For now, the sample ROM should be located in share/keyboardmaster with the name voice.rom. Thanks to Daniel Vik for his support. Also here, the code is taken from blueMSX and MAME. 2007-04-15 Wouter Vermaelen * Extended resampler class for stereo devices * Speedup SDLGL-PP simple scaler * Use (stereo) resampler for YMF262 (FM-part of OPL4) * Use resampler for (both) YM2413 cores (MSX-MUSIC) * Use resampler for Y8950 (MSX-AUDIO) 2007-04-15 Maarten ter Huurne * Released openmsx-0.6.2. 2007-04-12 Manuel Bilderbeek * Fix for MSX1 VDP's: on these VDP's the VRAM pointer is always updated when writing to the VDP registers. Fixes some MSX1 demos like Planet of the Epas. 2007-03-23 Manuel Bilderbeek * Integrated David's new commands.html into the manuals. Thanks for converting it! * Removed commands.txt and references to it. * Get rid of in-manual revision history. 2007-03-22 Maarten ter Huurne * Universal binary support for Mac OS X: make staticbindist OPENMSX_TARGET_CPU=univ There are probably still some issues left, but the infrastructure for building universal binaries is now complete. Thanks a lot to BouKiCHi for providing the scripts on which this universal binary support is based. 2007-03-15 Manuel Bilderbeek * Added Al-Alamiah AX170 (prevously added Yamaha AX350II). - NOTE: it is not certain if these configs are correct and I have not been able to verify the ROM dumps. They are added only to have some working Arabic machines, do not use them for compatibility testing or something. (Have fun!) 2007-03-03 Wouter Vermaelen * Force a FormFeed when a printer is unplugged * Better algorithm to calculate framerate for avi recording: - measure time between two recorded frames instead of two frames from before recording started. - old method could go wrong when minframeskip != maxframeskip 2007-02-28 Manuel Bilderbeek * Fixed a bug in the tabCompletion of the enable command. * Added a small help for the enable command, pointing to a new documentation file: doc/openmsx-control-xml.txt. This text explains the protocol used for external communication (CliComm). * Removed deprecated update types (deprecated in openMSX 0.6.1, but they were not used by any external application we know about): break and resume. 2007-02-24 Wouter Vermaelen * Fixed(?) data corruption when using 'diskmanipulator import': - I had no idea were in the code the bug was, so i started doing clanups/simplifications till I understood the code again and found the bug. - Turned out the dirty status of the FAT chache was not set correctly by the writeFAT() method. - Another bug happened when a there is not enough free space left on the disk to completely import a file: the file was imported as far as ossible, the FAT was updated, but not the directory entry. 2007-02-20 Wouter Vermaelen * Fixed hang in altparty demo: - Demo polls FH bit in VDP status reg S#1. but when line interrupt register has value 229 this went wrong. Reason is that for this line the duration of FH=1 is partly in the current and partly in the next frame. Old implementation only set FH=1 in the current frame. 2007-02-19 Wouter Vermaelen * Fixed SDLGL-PP simple scaler factor=1 deinterlace mode: - in the previous implementation this caused a (floating-point) division by zero. This could give strange results. - Now we disable scanlines when there are more lines in the input than in the output. This is also how it works in the SDL renderer * Fixed bug in switching between Z80/R800: - switching _very_ fast (by OTIR) between Z80/R800 triggered an assert that time goes backwards! - problem was that we record the switch time at the moment the OUT is executed and then still finish the rest of the instruction. So there is a slight moment in emutime that both CPUs are executing. - solved by only switching between two instructions 2007-02-12 Wouter Vermaelen * Fixed assert on exit when a (real) joystick was still plugged * Small Z80 fixes: - timing of LD DP,{HL,IX,IY} was two cycles too short - number of M-cycles (and thus also timing) of instructions with multiple prefixes (like DD CB xx xx or DD DD DD xx) was wrong - off by one error in high part of 16-bit port number in ini/ind/outi/outd instructions (complete bc register is put on the address bus) b register was decremented too soon or too late 2007-02-12 Wouter Vermaelen * No longer include playball samples as data in the source code: - instead read them as 15 wav files in share/playball/playball_0.wav ... share/playball/playball_14.wav 2007-02-11 Wouter Vermaelen * Set version=0.6.2-pre1, release_flag=true 2007-02-08 Wouter Vermaelen * Implemented proper resample algorithm for SCC: - does not introduce aliassing (graph+9 in solid snake sound test works properly now) - because there is no seperate filter anymore, those filter characteristics based on Sean's measurements are gone. Need to investigate this, because it was not clear whether this extra filter improved sound quality 2007-02-05 Wouter Vermaelen * SCC filter fixes: - wasn't correct when there were multiple SCC cartridges - replaced FIR filter with a cheaper one that also matches Sean's measurements - it's still not alias free, but fixing this requires a whole new approach * Added interruptable sleep() method in EventDistributor: - sleeps for a given amount of time or till a event gets posted - used in the Reactor mainloop (e.g. when emulation is paused/breaked). Should improve response time in the debugger 2007-02-02 Wouter Vermaelen * Allow case insensitive tabcomplition in Tcl scripts: - requires a make install - changed newtrainers.tcl and cpuregs.tcl scripts to use this, maybe it's useful for others as well 2007-01-30 Wouter Vermaelen * Replaced filter in SCC code: - used 4x oversampling + low pass FIR filter - still WIP 2007-01-30 Manuel Bilderbeek * Added Sony HBD-50 disk drive extension (with proper FDC mirroring) 2007-01-27 Wouter Vermaelen * Fixed mirroring of FDC registers in NMS 8250/8255/8280: FDC registers are mirrored in all 4 pages, rom only in page 1 * Added comment in NMS 8245 config: FDC registers are mirrored in page 1/2, rom only in page 1 It was already implemented like this 2007-01-28 Manuel Bilderbeek * Implemented mirroring of subROM in all pages on Philips NMS 8250 (and derivatives)... TODO: find out which other machines have this. 2007-01-27 Wouter Vermaelen * Removed enum setting info topics: code was already commented out (see 2006-11-04) 2007-01-26 Wouter Vermaelen * Make FilePool return a File object iso a filename: - (g)zipped files won't be decompressed twice this way 2007-01-24 Wouter Vermaelen * Added FrameBufferObject utility class: - was part of an experiment to implement a fully shader based hq(lite) scaler (current one is still partly done by CPU). This needs some more work, but the FrameBufferObject part is already useable. 2007-01-23 Wouter Vermaelen * Fixed bug in SCC: volume of 5th SCC channel could be wrong right after waveform was updated (e.g. audible in Quarth) 2007-01-19 Wouter Vermaelen * Fixed PSG noise frequency 2007-01-16 Arnold Metselaar * Print warning if bit 0 of VDP register 9 is set to one; on a real MSX2 this causes the screen to go black and the computer to freeze. - closes [ 1354347 ]. 2007-01-13 Wouter Vermaelen * Synchronized TC8566AF code with blueMSX version: - seems to fix [ 1630933 ] "Formatting on turboR goes wrong when not booting with a disk". 2007-01-11 Wouter Vermaelen * Reimplemented 'soundlog' command as a Tcl script 2007-01-10 Wouter Vermaelen * Extended the record command to optionally record only audio or video: - 'record start' accepts two new flags: -noaudio and -novideo - the soundlog command is obsolete now 2007-01-09 Wouter Vermaelen * Improved 'record' command: - syntax is very similar to the 'soundlog' command, see 'help record' - in the future the record and soundlog command might be merged, with an option to select recording audio/video/both * Implemented gfx9000 video recording: - active videosource ('videosource' setting) now gets recorded 2007-01-08 Wouter Vermaelen * Avi recording fixes: - no longer require that V9938 has the exact name "VDP" in machine hardwareconfig.xml file - print warning when a change in framerate/samplerate is detected during recording - fixed bug in conversion routine while recording in 16bpp mode - during recording frameskip is kept constant at minframeskip, thus for example with minframeskip=1 you can record movies with 25/30fps instead of the usual 50/60fps 2007-01-05 Wouter Vermaelen * Implemented experimental video recording: - still very much work in progress, but for simple cases it should already work - user interface is still VERY primitive: the 'record' command starts recording to 'test.avi' and only stops when openmsx is exited - changing between PAL/NTSC during recording doesn't work yet - changing sound sample freq during recording doesn't work yet - recording is not possible (yet?) with SDLGL renderer - generated avi file uses the ZMBV codec (same as used in dosbox) this codec is well suited for video-game like gfx and allows real-time encoding on not too old machines. On my 3-year old system i can do real-time recording of simple games, but I can't record sphere in real-time (due to moonsound emulation??) - the ZMBV codec is not very common. In mplayer (linux/windows) can handle it, VLC version 0.8.6 not yet (development version can). For windows you need to install zmbv.dll, but note that there is a faulty zmbv.dll that can only play 8bpp (openMSX movies are 16bpp or 32bpp). - doesn't play well with frameskip yet, as a temporary workaround you can set maxframekip=0 for now 2007-01-05 Wouter Vermaelen * Use PBO for faster texture upload in GLPostProcessor: - depending on the system this is only a small (1%) or a big (10%) speedup - when PBOs are not supported we still use the old code 2006-12-30 Wouter Vermaelen * Use Pixel Buffer Objects (PBO) for faster texture upload in GLHQScaler and GLHQLiteScaler: - 'Space Manbow'-test is about 10% faster here, using hqlite2x 2006-12-29 Wouter Vermaelen * Various VDPCmdEngine related optimizations: - in VDPVRAM: only track time in DEBUG mode no need to notify bitmapCacheWindow and nameTable in VDPCmdEngine: use Clock::fastAdd - between 2% and 3% faster in vdp-copy-intensive MSX software (I tested Space Manbow intro demo) 2006-12-27 Wouter Vermaelen * Optimized inner CPU emulation loop: - there were 3 tests per instruction, reduced that to just 1 - total emulation is about 3% faster in Space Manbow test 2006-12-23 Wouter Vermaelen * Try to upload larger texture blocks at once in GLPostProcessor (was one line at a time): - I measured upto 15% speedup (scale_algo=simple scale_factor=2) * Use array arithmetic iso pointer arithmetic in BitmapConverter: - can be better optimized by gcc (and compilers in general) - though, impact on total emulation time is not that big. I measured about 1% speedup 2006-12-22 Wouter Vermaelen * Removed charDisplayCache and bitmapDisplayCache from SDLRasterizer: - I measured around 5% speedup in screen5, around 20%(!) in screen0/80 * Make TC8566AF use the correct filler byte during format: fix copied from blueMSX 2006-12-18 Wouter Vermaelen * The -Wparentheses warning is now also enabled in c++ mode in recent gcc versions (4.3 SVN version). This for example warns when parentheses are omitted in places were people often are mistaken about operator precedence. And guess what .. openMSX got it wrong in a couple of places. In other places I just added extra parentheses to silence the warnings. 2006-12-17 Wouter Vermaelen * VDP code cleanup: Changed users of VDPVRAM::readArea() and VDPVRAM::readAreaIndex() to use VDPVRAM::getReadArea(). This method is safer to use, it also simplifies the code a bit (IMHO). 2006-12-16 Wouter Vermaelen * Fixed VDP bug introduced at 2006-11-18, s#0 was completely cleared upon read, only bit 7 must be cleared: - fixes VDP detection in sudoku.rom * Added intro text in openMSX console * Removed dirty checks from CharacterConverter * Added VDPVRAM::getReadArea() and use it to optimize CharacterConverter * Optimized CharacterConverter::renderText2: see comments in code 2006-12-14 Wouter Vermaelen * Refactored sound code: _ Should only affect SDL sound driver - Never generate samples from the audio thread: In case of buffer-underrun we used to still generate the missing samples from the audio thread. This helped sound quality a little bit when CPU usage is very irregular. Unfortunately this also introduces a lot of threading problems (race conditions). So removed this completely, it also simplifies the code quite a bit. - Fixed a potential out-of-boundary buffer access. - Fixed bug in sound-genration-timing to real-time tuning algorithm. On some systems this caused a floating point exception. 2006-12-11 Arnold Metselaar * src/sound/SDLSoundDriver.cc: ensure the left argument to % is positive, fixes distorted sound and floating point exceptions. 2006-12-11 Wouter Vermaelen * Implmented end-of-tape detection: - was implemented by Manuel, I only fixed a remaining bug - when end of tape is reached, the CassettePlayer goes to STOP state - main motivation for this feature is to avaod staying in 'full throttle' mode after loading (in cases were the cassette motor is not automatically stopped) * Fixed crash related to proxy settings and machine switching: - e.g. 'openmsx_info setting ' would still use the setting object that belonged to the destroyed machine 2006-12-09 Wouter Vermaelen * Fixed explicit (global) namespace qualification on proxy settings: - proxy setting here means a machine specific setting that's for convenience mirrored in the global namespace - the toggle/cycle commands triggered this problem 2006-11-29 Wouter Vermaelen * Changed memset and memset2 from functions into functors: - Compilation was broken recently on system were GLuint != unsigned. To fix this we need template specialization. But templatized functions cannot be specialized. 2006-11-28 Wouter Vermaelen * Split Cassetteplayer in Cassetteplayer and CassetteplayerCLI 2006-11-27 Wouter Vermaelen * Fixed auto-run-cas-file: - type command got executed twice when a cas file was inserted via the commandline. Could also happen when two cas insert commands were given when openmsx is paused. 2006-11-25 Wouter Vermaelen * SDLGL-PP renderer speedup: - Upload textures in BGRA format (was RGBA). See this paper: http://download.nvidia.com/developer/Papers/2005/Fast_Texture_Transfers/Fast_Texture_Transfers.pdf On nvidia cards this is faster, most likely on ATI cards as well - I measured between 3% and 5% speedup (total simulation time) 2006-11-22 Manuel Bilderbeek * Added Philips NMS 801 "MSX Compatible". Thanks to the blueMSX team. 2006-11-22 Wouter Vermaelen * Another SDLGL-PP hqlite optimization, OpenGL shader part this time: - Before this change we sampled the color texture three times (to get texels C4, C5 and C6) and blended these these three colors each with a certain weight. The weight for the outer two pixels is never non-zero for both at the same time. So we can as well sample the color texture only once and make use of the HW texture color interpolation. 2006-11-20 Wouter Vermaelen * Added missing line width conversions in FrameSource: - fixes crash in Gfx9000 parts of calculus with SDLGL-PP renderer * Use optimized memset function in V99990P{1,2}Convertor: - only a little faster though 2006-11-19 Manuel Bilderbeek * Print originality status for ROM dumps in the database. Rationale: see e-mail on openmsx-devel of today. 2006-11-18 Wouter Vermaelen * fixed [1188517] Dragon Quest II intro logo corrupted: - We had this comment in our code According to TMS9918.pdf 5th [or 9th] sprite detection is only active when F flag is zero. but there was a bug in the actual implementation. Dragon Quest 2 needs this. - Bug was caused by splitting storage of VDP status register 0 over VDP and SpriteChecker class. Now S#0 is stored in VDP and SpriteChecker uses get/set methods to access the sprite related bits. 2006-11-16 Wouter Vermaelen * Speedup SDLGL-PP simple scaler: - taking the average of two pixels and multiplying that with a constant is the same as summing the two pixels and multiplying by that contant/2. - 7% faster here (in 4x) 2006-11-15 Wouter Vermaelen * Optimized SDLGL-PP hq scaler: - used same tricks as the hqlite scaler (see 2006-11-14) - share more code between SDLGL-PP hq and hqlite scaler * Optimized hq edge calculation: - applies for both SDL and SDLGL-PP hq scaler - another 3% speedup for SDLGL-PP hq (only measured this one) 2006-11-14 Wouter Vermaelen * Optimized SDLGL-PP hqlite scaler: - optimized edge-bits calculation: - change position of the bits so that it's easier (less shifts) to reuse bits from the left and top neighbour - calculate two pixels at once using 32-bit (iso 16-bit) bit manipulations - I measured 25% speedup on my system (for 2x and 3x because these are CPU limited, 4x is GPU limited here) - TODO: a similar optimization is possible for SDLGL-PP hq and SDL hq/hqlite. Although there the speed improvent might not be as big * Added 'test_machine' command (see 'help test_machine'): - initial version, details on how success/failure is reported can still change 2006-11-10 Wouter Vermaelen * Applied pimple-idiom to MSXMotherboard: - will require less recompilations in the future: no longer when there are changes to private member variables or functions 2006-11-09 Wouter Vermaelen * Added machine add/remove/select clicomm update * Extended 'machine' command: - without parameters it returns the handle of the current machine - on a successful machine switch it also returns the handle * Added 'machine_info config_name': - returns the configuartion name for the specified machine, the config name is the name that should be used in the 'machine' command to switch machine 2006-11-08 Wouter Vermaelen * Implemented watchpoint regions: see doc/commands.txt or 'help debug set_watchpoint' 2006-11-06 Wouter Vermaelen * Split CliComm in machine specific and global part: - machine specific update events now have an extra 'machine' attribute (e.g. add) * Manage disk/hd/cd names per machine iso globally 2006-11-04 Wouter Vermaelen * TODO temporarily disabled enum setting info topics: see comment in EnumSetting.hh * Moved 'machines' and 'extensions' info topics from MSXMotherboard to Reactor * Shared MSXDevice state should be shared per MSX machine, not shared globally: for the moment only fixed MSXMemoryMapper and Printer * Renamed 'machine' setting to 'default_machine' and create a new 'machine' command to actually switch to a new MSX machine * While switching MSX machine, first create the new machine and only if that succeeded delete the old machine 2006-11-02 Wouter Vermaelen * Put machine specific commands and settings in a machine specific namespace. There is also a corresponding command/setting in the global namespace that delegates to the current machine: - preparation for cleaner run-time switchable machine and run-time machine checker - setting code should be refactored one day, it's rather complicated now 2006-11-01 Manuel Bilderbeek * Send CliComm events for CD and HD changes. * Report full name including path for CD and HD images. 2006-10-30 Joost Yervante Damad * fix support for kfreebsd, "kfreebsd" was detected wrongly as "freebsd" causing build failure 2006-10-26 Arnold Metselaar * Unified advram-p and advram-s to a single advram, - use "?0" to specify: subslot "0" if expanded, - src/CartridgeSlotManager.cc: parse "?0", - src/MSXDevice.cc, src/config/HardwareConfig.cc: handle the case of "?0", - TODO: get rid of the special values for slot numbers. 2006-10-25 Manuel Bilderbeek * Restructured media-related commands: - basic idea: all media have the same command structure: + mediacommand subcommand arguments + default subcommand is 'insert', which can thus be omitted - if you want to insert a medium with the name of a subcommand, just use the insert subcommand first, e.g.: diska insert insert - Corrected help and documentation a bit and optimized completion a bit - TODO: completion can be made a lot tighter, still - commands are still (practically) 100% backwards compatible 2006-10-18 Joost Yervante Damad * fix from BouKiCHi for install-recursive.sh * make MMX and SSE code also work on AMD64 2006-10-18 Wouter Vermaelen * Split Mixer in a global Mixer part and a per machine MSXMixer part: - when there are (temporarily) multiple machines in memory, each machine has a MSXMixer while there is still only one Mixer (that loads the actual sound driver) 2006-10-13 Wouter Vermaelen * Removed JoyNet: - it didn't work (on practical speeds) anyway * Split CommandController in MSXCommandController (machine specfic) and GlobalCommandController (and an abstract base class): - changed code to use the approriate class, but ATM both still do exactly the same 2006-10-13 Wouter Vermaelen * Split openmsx_info topics in two: - machine specific topics are moved to a new machine_info command - general topics remain in openmsx_info - for backwards compatibility the machine specific topics also remain in openmsx_info for one or two releases, but they generate a warning - openmsx_info is also deprecated, prefer to use openmsx_info setting (already existed) - adjusted Tcl scripts to use the new commands 2006-10-12 Maarten ter Huurne * Integrated Windows resource support: stores icon and version info in the executable patch was made by Albert and Manuel, based on wx Catapult build 2006-10-12 Wouter Vermaelen * Preparation to separate command registration from CommandController (is itself a preparation to allow multiple machines in memory): - moved tabcompletion code from CommandController to Completer * Print possibilities for tab completion in multiple columns, especially useful with all the trainer_** procs ;) 2006-10-10 Manuel Bilderbeek * Renamed FileManipulator to DiskManipulator (classes/filenames) 2006-10-09 Manuel Bilderbeek * Fix crash when uninitialized disk image was used. TODO: check limits more accurately (now only checked for 0) TODO: separate partition selection from boot sector parsing 2006-10-05 Wouter Vermaelen * Various changes to reduce #include dependencies 2006-10-04 Joost Damad * remove bashisms from build/install-recursive.sh * debian/ updates 2006-10-02 Wouter Vermaelen * Empty the sound buffer in the SDL sound driver after a mute/unmute * Make 'after' command part of Reactor iso MSXMotherboard: - after events are now kept over a machine switch - added two new after events: 'boot' and 'machine_switch' * Extended autoplug.tcl: - also auto plug the cassetteplayer after a machine switch 2006-10-01 Wouter Vermaelen * Fixed race condition: - was easy to trigger on a dual core CPU - shared_ptr is not thread safe on it's own, see comments in EventDistributor 2006-09-30 Manuel Bilderbeek * Send an event when the cassetteplayer is added/removed. 2006-09-30 Joost Damad * build on arm with optimizations again * add flavour opt-debug, useful when searching bugs that only occur on optimized binaries 2006-09-30 Wouter Vermaelen * Optimized V9990 P1 mode rendering: - WIP sprite rendering can still be optimized a lot - also optimized sprite rendering * Optimized V9990 P2 mode rendering 2006-09-29 Maarten ter Huurne * Reset loading state if a disk drive or cassette player is destructed. 2006-09-28 Manuel Bilderbeek * Added BOOT event to be able to do autoRun on cassettes at every boot. Also needed for Catapult (would otherwise only work once). 2006-09-25 Wouter Vermaelen * Added support for (recursive) subdirectories in RomPool 2006-09-23 Wouter Vermaelen * Implemented PlayBall mapper type * Only flush DiskManipulator FAT cache when there were actual writes: - fixes a strange error that doing 'diskmanipulator dir diska' on a read-only disk gives a write(!) error * Detect invalid PPI mode selection and print a warning when an unimplemted mode is selected: - those other modes are not that easy to implement and they anyway will just hang your MSX 2006-09-22 Manuel Bilderbeek * Save firmwareswitch position with persistent data 2006-09-22 Wouter Vermaelen * Made Command's also Recordable (was only SimpleCommand's) * record 'debug write' commands: - record/replay now also works when trainers are used 2006-09-21 Wouter Vermaelen * Made 'hda', 'cda', 'type', 'keymatrixup' and 'keymatrixdown' commands recorded * Changed format of event recorder to also allow ':' characters in recorded commands 2006-09-20 Manuel Bilderbeek * Removed SDLGL2 (superseded by SDLGL-PP), as requested by Wouter 2006-09-20 Wouter Vermaelen * Added RecordedCommand class to make recording certian commands easier. TODO refactor previous recordable commands to use this mechanism * made 'reset', 'ext', 'cart', 'cart' and 'remove_extension' commands recordable. * Reimplemented 'plug', 'unplug' and 'cassetteplayer' commands as RecordedCommand's 2006-09-19 Wouter Vermaelen * Refactored (un)plug and media changed events to MSXCommandEvent * Use MSXCommandEvent for cassetteplayer command 2006-09-18 Wouter Vermaelen * Use events for (un)plug command: makes (un)plug recordable * use events for disk changing: TODO: - do the same for cassetteplayer - use different string representation for events because at the moment the paths can't contain ':' characters 2006-09-16 Wouter Vermaelen * Added 'extensions' and 'machines' info topics * Extended these two info topics to also return the meta info when a specific extension/machine is given as 3th argument 2006-09-16 Manuel Bilderbeek * Added MSXEventRecorder/Replayer: - you can record and replay with the command line options, which require a filename argument 2006-09-15 Wouter Vermaelen * Fixed deinterlace for V9990: - force two consecutive frames (non-skipped frames) in deinterlace mode 2006-09-14 Wouter Vermaelen * Fixed V9990 cursor Y position in overscan modes: - in overscan modes the cursor position is still specified with the same coordinate system as non-overscan modes (while the display line coord system has shifted up) 2006-09-12 Wouter Vermaelen * Fixed Y8950 sample ram reads: - first 2 reads from sample RAM return bogus values. This behaviour was implemented, but it has a bug and returned only one bogus value :( - fixes sample RAM detection in 'Almost Real' 2006-09-11 Maarten ter Huurne * Send an event over the control connection when a disk drive is added or removed. Can be used by Catapult to update the list of drives. 2006-09-11 Wouter Vermaelen * Fixed AUDIO in 'Thunderbirds are Go': - 'Thunderbirds are Go' sets the Music Module General Purpose pin 3 as output without specifying a value for that pin. By luck(?) in that case on a real machine the value is '1' while in openMSX it was '0' before this fix. * Forgot to sync V9990CmdEngine: - fixes regression in blox 2006-09-07 Wouter Vermaelen * Fixed bug in removing IO watchpoints * Improved help-text / tabcompletion for the debug command * Changed checked_cast implementation so that it also works on older compilers (gcc-3.4 and below) 2006-09-06 Wouter Vermaelen * Split UserInputEventDistributor in 3: EventTranslator, EventDelay and MSXEventDistributor * Automatically unpause after a power off/on cycle: solves a bug, but this behaviour also makes more sense * Made MSXEventDistributor EmuTime aware: needed later for event recording 2006-09-02 Wouter Vermaelen * Added priorities to event listeners, higher priority listeners can block events for lower priority listeners: - fixes the problem that hotkey bindings are still visible in BASIC 2006-09-02 Joost Damad * simplify arm CXXFLAGS; fix m68k build; override in cpu-xxx.mk doesn't work 2006-08-31 Joost Damad * do not do -finline-functions on arm as current gcc generates faulty code 2006-08-31 Manuel Bilderbeek * Added MEDIA update event for cart-commands 2006-08-30 Wouter Vermaelen * Added checked_cast and shared_ptr utility classes * Pass events around by shared_ptr (avoids making a polymorphic copy) * Added Event::toString() to print events and InputEventFactory::createInputEvent(string) to parse events * Added Event::operator<() (to store events in maps) * Extended HotKey class (name is not 100% accurate anymore) to also handle joystick/mouse-button and focus events: examples: bind focus:0 { set pause on } bind focus:1 { set pause off } bind mouse:button8:down { toggle throttle } bind mouse:button8:up { toggle throttle } 2006-08-23 Maarten ter Huurne * If default machine is broken (for example because of missing system ROMs), fall back to C-BIOS config. This makes sure openMSX will start up, allowing the user to select a different default machine on the openMSX console. 2006-08-22 Wouter Vermaelen * Enable detection of more OpenGL extensions with glew: - see comments in SDLGLVisibleSurface for details - might fix the problem PingPong is seeing: accoring to 'glewinfo' framebuffer_objects are support on his card, but not so according to openMSX 2006-08-15 Wouter Vermaelen * Use 2D textures iso 3D to work around bug in ATI drivers 2006-08-14 Wouter Vermaelen * Transform texture coordinates from (N x N x 4096) to 32N x N x 128 because some gfx cards can't handle that big dimensions 2006-08-12 Wouter Vermaelen * Implemented hq as a OpenGL 2.0 shader * Use external data files in GLHQScaler and GLHQLiteScaler: - work around limitation in MSYS assembler(?): Assembler messages: Warning: .stabs: description field '11004' too big, try a different debug format 2006-08-10 Wouter Vermaelen * Implemented hqlite as a OpenGL 2.0 shader: supports 2x 3x 4x only 256 pixel wide, non-interlaced MSX screens 2006-08-09 Wouter Vermaelen * Fixed VDPCmdEngine status register 8 and 9: - implemented by Alex Wulms - status registers 8/9 are documented to return the result of a SRCH command. However they simply return the value of the internal 'ASX' register. In the end this register does contain the result of a SRCH command, but it is also updated during the SRCH command and even during completely different commands. 2006-08-08 Wouter Vermaelen * Fixed indeterministic behaviour in CPU emulation: CPU could react one instruction too late to an IRQ, depending on which other sync points there are (see comments in CPUCore.cc for more details) 2006-08-05 Wouter Vermaelen * write printed pages to "dot-matrix" director (like screenshots dir) * made printer image resolution a setting: between 150dpi and 1200dpi. Default (300dpi) is a compromise between quality and imagesize/speed 2006-08-04 Wouter Vermaelen * Added Dot Matrix Printer Emulation: code mostly taken from blueMSX 2006-07-31 Joost Damad * add support for powerpc64: ppc64 cpu and flavour * compile with -O1 on m68k by default 2006-07-30 Maarten ter Huurne * Released openmsx-0.6.1. 2006-07-28 Manuel Bilderbeek * Escape strings before sending them to TCL's splitList, fixes diskmanipulator with spaces in file names * And reverted again. This breaks splitting completely. Needs a better fix, but it's not trivial. 2006-07-21 Wouter Vermaelen * Fixed loading of keybindings 2006-07-20 Wouter Vermaelen * Implemented 'Removable media status notification feature set' for IDECDROM, slightly improved 'request sense' command: reading from CDROM (also when switching ISO images) seems to work now, although there may still be a problem with the volume id * Set version_number=0.6.1, release_tag=true 2006-07-17 Wouter Vermaelen * Reenabled saving of keybindings: on exit when save_settings_on_exit is enabled or when explicity requested * Fixed saving of 'renderer' setting: when save_settings_on_exit was enabled there were two problems: - after testconfig run, renderer=none was saved in settings.xml. Fixed by introducing 'unsaveable' values on Setting - every other run the value would fallback to 'SDL'. For the renderer setting we made the default value equal to the saved value (needed for the 'unset renderer' command executed by catapult) and settings with default value don't get saved. Fixed by having a seperate default and restore value. 2006-07-14 Wouter Vermaelen * Don't assert when multiple Panasonic MSX-Audio's are inserted 2006-07-13 Wouter Vermaelen * Exit the CliServer listener thread in a more portable way 2006-07-12 Wouter Vermaelen * Don't listen for incomming connections in -testconfig mode * Split RomInfo in RomInfo and RomDatabase: - allowed to fix RomInfo memory leaks 2006-07-10 Wouter Vermaelen * Implemented proper locking for Reactor::motherBoard 2006-07-08 Wouter Vermaelen * Fixed top-border flicker in cbios/SDLGL: - CPU loop was exit'ed only one instruction after the exit-loop request. Normally this doesn't matter (Schedule points are still executed cycle accurate). But for _very_ long instructions, like the way we implemented the HALT instruction, and the repaint event it did matter. * Fixed(?) the undocumented TMS9918 mode1q: - I didn't test this (i don't have a test prog) but the old code had obvious bugs, I fixed those 2006-07-07 Wouter Vermaelen * Implemented general purpose IO mechanism for Y8950: - implemented for Philips Music Module, Panasonic MSX-AUDIO, still have to investigate Toshiba module 2006-07-05 Wouter Vermaelen * Fallback to SDL renderer when switching renderer didn't succeed (e.g. when switching to SDLGL-PP without ogl2 support) 2006-07-03 Wouter Vermaelen * Added lighting for 3d display deform: Corners (especially top corners) are now a bit darker then the middle-lower part. The effect is subtle but it looks more realistic IMHO. I couldn't measure any speed difference with or without lighting, so it's enabled by default 2006-06-29 Wouter Vermaelen * Renamed 'monitor_effect' setting to 'display_deform' * Fixed GLSnow effect for non 2x scale factors 2006-06-28 Wouter Vermaelen * Implemented 'horizontal stretch' for SDLGL-PP: - is another enum of the monitor_effect setting (rename this?) - the 3D monitor effect always has horizontal stretch applied * Only enable SDLGL-PP renderer when OpenGL-2.0 is available * Moved SDLGL2 to 'non-release-only' status (like SDLGL-FB) 2006-06-27 Wouter Vermaelen * Implemented blur and scanlines for SDLGL-PP simple scaler * Implemented FMPAC SRAM disable bit (bit4 address 0x7FF6): Thanks to BiFiMSX for investigating this 2006-06-25 Wouter Vermaelen * Made "monitor_effect" an enum setting (suggestions for a better name are welcome). Made normal (non-3d) the default. Fixed a bug when started openmsx is started in normal mode. 2006-06-24 Wouter Vermaelen * Implemented 3D monitor effect for SDLGL-PP renderer * Enabled glow setting for SDLGL-PP 2006-06-22 Wouter Vermaelen * Various RTC (RP5C01) test mode fixes: - r#14 bits 0-3 are for seconds, minutes, days, years - in test mode there is no carry from 'lower order' number - test mode also works when timer itself is disabled 2006-06-20 Wouter Vermaelen * Switched to non_power_of_two textures instead of texture_rectangle in SDLGL-PP renderer: - ATI cards don't support text_rect in GLSL, but npot text should work - for GeForce5 class HW this change is bad because it has no hw support for npot textures while it has for text_rect 2006-06-19 Wouter Vermaelen * Made rectangle_textures available on correct resolution on vertical linewidth borders: - fixes most SDLGL-PP glitches - TODO cleanup, make it work on ATI cards 2006-06-18 Wouter Vermaelen * Use texture_rectangle for SDLGL-PP shader: - allows to fix the border glitches - still WIP but horizontal glitches seem to be fixed already 2006-06-17 Wouter Vermaelen * Added generic register logger (see also psg_log script): - typical use case will be to log soundchip registers - can also playback the logged regsiters 2006-06-15 Wouter Vermaelen * Implemented all HQ scalers for the extra gfx9000 resolutions 2006-06-14 Wouter Vermaelen * Refactored HQ scalers to reduce code duplication 2006-06-13 Wouter Vermaelen * Speedup hq3x (same technique as used in hq2x): between 3% and 5% faster on my machine * Speedup hq3xlite: between 2% and 3% faster * Implemented hq3x and hq3xlite for 512-wide mode: no special implementation like in the hq2x case, just do hq3x followed by scale_2on1 2006-06-08 Wouter Vermaelen * Fixed HQ2xScaler-1x1to2x2.nn data (bug in generator script) * Improved quality of hq2x in 512 pixel wide modes (screen 7) * Speedup hq2xlite (~5%) by using the same technique as used in HQ2xScaler * Improved quality of hq2xlite in 512 pixel wide modes (screen 7) 2006-06-05 Wouter Vermaelen * Mask unwritable bits in V9990 registers: fixes 'shocky' scroll in some power basic demos * fixed "openmsx_info isexternalslot" command: guess_title script now works again * Oops! Socket code couldn't handle multiple simultaneous connections (or two single connections quickly after each other). 2006-06-03 Wouter Vermaelen * Only lower part of AY scroll register resets scanline position: fixes GEM08 * Implemented priority control for V9990 P1 mode 2006-06-02 Wouter Vermaelen * Fixed bug in V9990 blank screen behaviour 2006-06-01 Wouter Vermaelen * V9990 P1 mode does not have the strange screenplit behaviour (see 2006-05-29) 2006-05-31 Wouter Vermaelen * Implemented 'cd' command to change ISO image * Implemented -cda command line option * Fixed V9990 commands in P1 mode: Actually I just tried something and it seems to work very well (it fixes a lot of stuff). Still need to figure out exactly how the coordinate system maps the VRAM addresses in P1 mode 2006-05-31 Maarten ter Huurne * Initial support for sector loading from CD-ROM. Still missing: - commands to change the ISO image - setting the right status flags when a CD is inserted / not inserted / changed for another CD - correct calculation of packet size - audio track support * Implemented Read Native Max Address so SymbOS can access the HD. Thanks to Daniel Vik for telling us this command was the problem. 2006-05-30 Wouter Vermaelen * Fixed assert when switching scale_factor from 3->2 when openmsx was paused. 2006-05-29 Wouter Vermaelen * Fixed vertical rolling at 256 line (is normmaly 512) for V9990 P1 and P2 modes * Fixed vertical offset in V9990 when vertical scroll register is changed on a screensplit 2006-05-28 Wouter Vermaelen * Fixed striped screen 6 borders in SDLRasterizer 2006-05-27 Wouter Vermaelen * Fixed sprite disable for V9990 P1 and P2 modes * IDEHD no longer resizes an existing HD image, the tag is now only used to create the initial HD image * IDEHD now knows its own name (preparation for dynamic hd image) * Changed naming scheme for HDs: just give letters in order (not interface master/slave) this is more similar to how disks are given names and it also allows to give more logical names to cdroms (future) * Implemented 'hd' command to change HD images at-run-time (only works when MSX is powered down) * Added -hda command line option 2006-05-26 Manuel Bilderbeek * Specify the type of PSG for those machines we know this for sure 2006-05-25 Wouter Vermaelen * Use EventDistributor iso Scheduler for MidiInNative, MidiInReader and RS232Tester * Don't use Scheduler to exit the CPU loop * Removed ASAP from Scheduler 2006-05-24 Wouter Vermaelen * Implemented sprites in V9990 P2 mode (can still be optimized) 2006-05-22 Wouter Vermaelen * Implemented differences between AY8910 and YM2149 (two PSG variations): - add AY8910 or YM2149 in the tag in hardwareconfig.xml Default remains AY8910 2006-05-21 Maarten ter Huurne * Split IDEHD into generic part (new class AbstractIDEDevice) and HD specific part (class IDEHD). * Initial implementation of CD-ROM emulation. The only things it can do right now is convince the Sunrise BIOS that it is a CD-ROM and print some debug info about ATAPI commands. * Included a CD-ROM drive in the "ide" extension. 2006-05-20 Manuel Bilderbeek * Added Spectravideo SVI-738 machine 2006-05-20 Wouter Vermaelen * Implemented V99x8 with only 16kb VRAM * More robust error handling in rs232-tester 2006-05-06 Wouter Vermaelen * Implemented ninja-tap: Thanks to BiFiMSX for disassembling the test program 2006-04-30 Wouter Vermaelen * Disabled ninja-tap device because it doesn't work yet: ATM it acts the same as the 'normal' joytap device 2006-04-29 Wouter Vermaelen * Fixed bug in breakpoints: when a breakpoint was hit we still executed one instruction 2006-04-27 Maarten ter Huurne * VDP accuracy fix: corrected length of horizontal blank. Thanks to Daniel Vik for investigating the Parallax logo code and for writing measurements programs that helped in determining the correct horizontal blank length. Note that the start time of the horizontal blank is not yet fully correct, but having the right length already fixes some problems. 2006-04-25 Wouter Vermaelen * Fixed assert in MemoryOps: assert was too strict 2006-04-25 Wouter Vermaelen * Fixed truncate() function in windows: - instead of extending the file with zeros at the end, it wrote those zeros over the beginning of the file. Oops. 2006-04-20 Wouter Vermaelen * Implemented io and memory watchpoints: - for the moment use like this: debug set_watchpoint
[] [] debug remove_watchpoint debug list_watchpoints Where type is one of 'read_io', 'write_io', 'read_mem' or 'write_mem'. Address is in range 0..255 for io ports and 0..65535 for memory. Condition and command are the similar to the set_bp command. - I'm not 100% happy with this interface. It may change before the release. 2006-04-17 Wouter Vermaelen * Fixed V9990 SRCH command: at least paint command in power basic seems to work again * Fixed inserting wav/cas images from the command line 2006-04-15 Wouter Vermaelen * Implemented infrastructure for V9990 command timing: data in timing table is still _very_ rough though 2006-04-13 Wouter Vermaelen * Fixed(?) V9990 BMLL command, it should do logical operations: not yet tested 2006-04-12 Wouter Vermaelen * Enabled support for single sided disk drives: TODO update hardwareconfig.xml for machines with single sided drives 2006-04-11 Manuel Bilderbeek * Added new 'bootsector' setting. You can use it to let DirAsDSK use a DOS2 (default) boot sector or a DOS1 one. Also put the boot sector data in one single (dummy) class, yesterday. 2006-04-10 Wouter Vermaelen * Fixed bug in V9990 command engine: LMMC command was wrong in 16bpp mode * Optimized address calculation for 16bpp v9990 commands * Fixed V9990 cursor color 2006-04-09 Manuel Bilderbeek * Added tags for extensions as well. Please check; most likely not all data is correct. But it's useful for GUI's/launchers! 2006-04-08 Wouter Vermaelen * Made command that gets executed when a bp is hit configurable, default command is 'debug break' * Allow tag in extension configs (same as machine configs) old config format is still supported * Updated all extension configs to new format 2006-04-07 Wouter Vermaelen * Further optimized hq2x: also reuse edge flags from top neighbour (again about 5% faster) 2006-04-07 Manuel Bilderbeek * Print a warning when a DI/HALT is detected, which means a hang. TODO: add warnings for other known hang-situations as well. 2006-04-06 Maarten ter Huurne * Speedup (approximately 5%) of hq2x by re-using edge flags that were computed for the left neighbour pixel. 2006-04-06 Wouter Vermaelen * Speedup switching to different color_matrix * Fixed assert when using the same extension multiple times * Fixed asserts when using advram-p and advram-s extensions 2006-04-05 Wouter Vermaelen * Reduced compile dependencies, should reduce amount of recompilations needed in the future: - split SettingPolicy.hh -> SettingPolicy.hh SettingRangePolicy.hh - hold BitmapConvertor CharacterConverter SpriteConverter by pointer iso by value, performance difference is not measurable * Use functions of Math.hh in GL(2)Rasterizer * Made it possible for TCL scripts to implement tab completion, see share/scripts for examples: you again need to update the installed init.tcl file * Implemented experimental 'tabcompletion' command: only ment to be used by external programs * Implemented color_matrix setting: This color matrix can do any linear transformation from MSX RGB to host RGB. See share/scripts/monitor.tcl for examples. 2006-03-29 Wouter Vermaelen * Made it possible for TCL script to provide a help text, see share/scripts for examples. Note: the system init.tcl was updated, you probably need to replace the installed version * Added new 'about' TCL script. It searches for commands (built-in or TCL scripts) that seem to be about the given keyword. Useful if you forgot the exact name of a command. 2006-03-27 Maarten ter Huurne * Merged SDLGLVideoSystem into SDLVideoSystem. The actual differences exist in the VisibleSurface subclasses; these classes only function as factories. 2006-03-26 Maarten ter Huurne * Fixed two compile errors on Intel Macs. At least one remains though. 2006-03-25 Maarten ter Huurne * Added new scaler "TV". 2006-03-24 Maarten ter Huurne * Set maximum of scale_factor to 4. Only the SDLGL-PP renderer can actually reach 4x, the other renderers will fall back to their maximum zoom (2x or 3x). 2006-03-21 Wouter Vermaelen * SDLRasterizer optimization: - replaced memcpy() with new stream_memcpy() function - about 3% faster here (simple scaler 2x) 2006-03-21 Wouter Vermaelen * Optimized clock in CPU code: - do part of the time bookkeeping with 32-bit additions (iso 64-bit) - gives between 1% and 2% speedup in typical PC usecase (scale_factor = 2, no frameskip) - gives around 10% speedup with scale_factor = 1 and frameskip 5 2006-03-19 Maarten ter Huurne * Made scalers switchable in SDLGL-PP renderer. Only "simple" and "ScaleNx" are currently implemented. * Ported noise effect to SDLGL-PP renderer. 2006-03-18 Wouter Vermaelen * Implemented run-time switchable machines: - use set machine - there are still some rough edges and I also still expect some bugs. So please test. * Fixed bug: keypresses in the console were passed to MSX just after switching machine 2006-03-15 Wouter Vermaelen * More preparations for run-time switchable machines 2006-03-13 Maarten ter Huurne * Introduced new renderer "SDLGL-PP". This uses SDLRasterizer and does post processing in OpenGL. Since it uses SDLRasterizer, it is more mature already than SDLGL2, which uses its own GL2Rasterizer. 2006-03-13 Wouter Vermaelen * Turned MSXDevice creation in a 2-step process: First construct object, then call the init() method. This simplifies error handling (exceptions thrown from form constructor, see comments in code for more details) and it more easily allows passing extra parameters to device construction. * A MSXDevice now knows to which HardwareConfig it belongs (machine or extension) * Extended cart commands (is now similar to disk command): - cart (without parameters) now returns the name of the inserted cartridge (or possible an extension) - cart -eject removes the inserted cartridge (or extension) 2006-03-11 Maarten ter Huurne * Added support for pixel buffers to GLUtil. * Added utility functions to GLUtil. * Introduced new experimental renderer "SDLGL2". It uses OpenGL 2.0 features, including fragment (pixel) shaders. Currently, it only renders SCREEN5/8 without blanking/borders. 2006-03-11 Wouter Vermaelen * Dynamically register cart commands 2006-03-11 Manuel Bilderbeek * Fixed Gradiente machines. Cartridge slot B is in slot 3 and not 2 on Expert 1.0 and 1.1 and the ROM size was wrong for the DDPlus. Thanks to Gustavo Seidler for pointing this out. 2006-03-09 Wouter Vermaelen * Upper 4 RAM blocks on turbor may not be writable in dram mode (not yet tested) 2006-03-05 Maarten ter Huurne * Added utility classes for dealing with GLSL shaders. * Fixed UMR in StoredFrame. * Fixed MegaRAM mapper so it can handle 2048K (256 pages) as well. Thanks to Gustavo Seidler for spotting this bug and to Adriano Camargo Rodrigues da Cunha for finding the cause. 2006-03-04 Wouter Vermaelen * Added blur_1on3 MMX implementation: total emulation speed with simple 3x scaler is about 10% faster * Added "input_port" and "output_port" info topics * Implemented "iomap" command as a TCL proc 2006-03-03 Manuel Bilderbeek * Implemented better completion for debug command 2006-03-02 Wouter Vermaelen * Added "openmsx_info slot" info topic * Implemented "slotmap" command as a TCL proc 2006-03-01 Wouter Vermaelen * Preparation for run-time switchable machines: - send event when a MSX machine is loaded and change titlebar when that event is received * Implemented blur for 3x simple scaler (WIP): - only 256 wide modes yet 2006-02-27 Wouter Vermaelen * Fixed MicrosolFDC to also support DDX_3.0 * Added DDX_3.0 extension: Code and config contributed by Gustavo Seidler. Thanks! 2006-02-27 Wouter Vermaelen * IconStatus doesn't need Display * Moved IconStatus and Display from MSXMotherboard to Reactor 2006-02-26 Patrick van Arkel (vampiermsx@gmail.com) * Removed CRC32 values from the rom database and added a few more sha1 values 2006-02-24 Wouter Vermaelen * Replaced PollInterface mechansim with events * Moved InputEventGenerator from MSXMotherboard to Reactor 2006-02-23 Wouter Vermaelen * CliComm now uses events instead of Scheduler * Moved CliComm from MSXMotherboard to Reactor * Decoupled Display and RealTime classes: - instead Display now send events, RealTime listens to those events 2006-02-22 Wouter Vermaelen * EventDistributor no longer needs Scheduler, instead it cooperates with Reactor to deliver events: TODO turn (some of the) ASAP schedule points into events * Moved CommandController, EventDistributor, CommandConsole, FileManipulator and FilePool from MSXMotherboard to Reactor: more classes need to be moved but these were easy 2006-02-22 Maarten ter Huurne * Fixed GLSetTexEnvCol on big endian machines. This bug was visible in the border of SCREEN6. * Look for GLEW in DarwinPorts or Fink dirs. * Fixed bug with palette initialisation in SDLGL renderer. This bug was visible as a black screen when using an MSX1 VDP. 2006-02-21 Wouter Vermaelen * Preparations for run-time switchable machine: - made seperate EventDistributors for 'DETACHED' and 'EMU' listeners. - Reactor now owns MSXMotherboard * Fixed HotKey for key release bindings 2006-02-18 Maarten ter Huurne * Use GLEW to handle OpenGL extensions. http://glew.sourceforge.net/ From now on, the GL renderer requires GLEW. Note that since the GL renderer is optional, openMSX as a whole does not require GLEW. This commit is based on a patch from BouKiCHi. 2006-02-18 Manuel Bilderbeek * Log warnings to stderr, in stead of stdout 2006-02-17 Wouter Vermaelen * Implemented noise for GL renderer * Tweaked order of command line parsing: extensions should come before filename parsing to handle stuff like openmsx -machine msx1 -ext fdd mydisk.dsk 2006-02-15 David Heremans * Added SukkoPera's Slackbuild scripts 2006-02-14 Wouter Vermaelen * Implemented noise for 16bpp modes * Optimized non-MMX noise a bit 2006-02-11 Wouter Vermaelen * Added 'brightness' and 'contrast' settings 2006-02-10 Manuel Bilderbeek * CheckedRam now calls a TCL proc that is defined in the new umr_callback setting if an UMR occurs. E.g. try set umr_callback umrcallback umrcallback is an example implementation of such a TCL proc; edit it to your liking. TODO: find a way to automatically skip the UMR's from the boot sequence. (Although the feature is already very usable!) 2006-02-09 Manuel Bilderbeek * Introduced CheckedRam class. See the class documentation for details. TODO: make it useful by adding a usable UMR detector. 2006-02-09 Wouter Vermaelen * Fixed bug, diskimages with spaces in the filename: - since a few days the disk images passed via the commandline execute a TCL command to insert the disk instead of calling a c++ routine (to avoid code duplication). However this went wrong when there were spaces in the filename. I fixed this by avoiding the 'assembling tokens to string' and 'parsing string to tokens' steps. * Handle errors in the startup TCL scripts better * Speedup noise effect a bit (~10%) by using prefetch instructions 2006-02-08 Wouter Vermaelen * New video effect: noise (experimental) - try "set noise " 2006-02-06 Wouter Vermaelen * Fixed setting initial diskimage via the commandline: - as a bonus -disk now also gives an error on msx machines with no (or too few) drives * Don't loose current disk after a failed 'diska' command 2006-02-04 Maarten ter Huurne * Made Konami mapper more accurate, using info from: http://bifi.msxnet.org/msxnet/tech/megaroms.html * Made Majutsushi mapper inherit from Konami mapper, since it bascially is a Konami mapper with a DAC added to it. * Recycle line buffers used for scaling when openMSX is paused. 2006-02-03 Wouter Vermaelen * Removed 'slot reservation' mechanism in command line parser: - The order of -cart and -ext command line options is now important - This may break catapult in some cases. Combination of slotexpander extensions and other memory mapped extensions (or rom cartridges) can fail now. * Better error handling (in particular cleanup after error) when something goes wrong during run-time insertion of extension: * Better error handling when removing extensions (e.g. trying to remove a slotexpander that is still in use) 2006-02-02 Wouter Vermaelen * Made FAT buffering more symmetrical: - if FAT sector writes are stored in cache, then FAT sector reads must also come from cache - I don't know this caused problems in the current code, but now it's certainly more robust against future changes 2006-02-02 David Heremans * Decided to keep the FAT buffering and adjust the writeLogical sectors for it to get correct working again. This also repairs formats of existing dsks/partitions etc. 2006-02-01 David Heremans * Fixed a small problem with the media descriptor during the format of a new dsk file. But at first glance the entire FAT buffering need to go, since it might conflict with different sized partitions on a HD 2006-01-28 Wouter Vermaelen * Made MSXSwitchedDevice more robust when inserted/removed dynamically * Handle errors in MSXDevice configs better: make sure device is not left partly registered (memory or IO) when there is an error in the device config 2006-01-27 Wouter Vermaelen * Added new cpu-status update event: running suspended The old 'break' and 'resume' update events are deprecated * Small cleanups/fixes in JoyTap stuff: - got rid of the "ERROR: Connector still plugged .." messages - removed JoyTapPort class, it was exactly the same as JoystickPort - (minor) removed (lastValue == value) check (no need to do the same optimization twice) 2006-01-27 David Heremans * Added JoyTap and (incomplete) NinjaTap devices The JoyTap is a base class for collector/combinator devices that sit inbetween the MSX joystick ports and the actual joysticks. 2006-01-25 Wouter Vermaelen * Implemented 'list_extensions' and 'remove_extension' commands * Converted some fatal errors to normal errors, they can now also get triggered by run-time insertion of extensions * Recalculate visible pages when a memory device is added/removed: fixes assert while removing a running game cartridge 2006-01-23 Wouter Vermaelen * Enabled run-time insertion of rom cartridges and extensions: - new commands: ext [extension] cart, cart [romfile] * Removed EmuTime (and related) constructors that initialize time at 0: - makes initialization time explicit in the code - fixed some (all?) wrongly initialized timestamps e.g. the new "ext gfx9000" command caused an assert 2006-01-22 Maarten ter Huurne * Added new platform "darwin-app", which can be used to produce a redistributable binary for Mac OS X. Thanks to BouKiCHi for getting static linking to work. 2006-01-21 Wouter Vermaelen * Reduced build-dependencies between source files: - moved most inner classes from hh to cc file 2006-01-20 Wouter Vermaelen * Changed 'locking' of devices (eg. ADVRAM depends on VDP): - it's now specified with tag(s) - can now be handled by generic code * Moved ownership of MSXDevices from MSXMotherBoard to HardwareConfig (MSXMotherBoard owns (possibly several) HardwareConfigs): - another preparation for run-time switchable extensions/machines 2006-01-19 Maarten ter Huurne * Added the option not set the window icon from the openMSX process. This is useful for running from an OS X app folder, since the app folder sets a hi-res icon. 2006-01-18 Wouter Vermaelen * Preparations for run-time removable extensions/machines: - keep mamchines and extensions as seperate entities in memory - unregister 'expanded' and 'exernal' slots when the extension or machine is deleted 2006-01-17 Wouter Vermaelen * Refactored CartridgeSlotManager: - allocated external slots are freed again when the MSXDevice using it is destroyed - preparations for removable devices at run-time 2006-01-16 Wouter Vermaelen * CassetteJack cleanup: - changed code style to match rest of openMSX sources - fixed small bug: new[] needs delete[] (iso delete) - more robust error handling * Refactored the way how MSXDevice names are made unique 2006-01-15 Maarten ter Huurne * Added "make app" for Mac OS X. It is not yet fully functional, but most is there already. Thanks to BouKiCHi for helping. 2006-01-13 Wouter Vermaelen * Made MSXDevice::getName() return a string by value: is not performance critical and simplifies code in MSXMultiDevice * Moved common code of MSXMultiIODevice and MSXMultiMemDevice to new MSXMultiDevice class * No need to implement reset(), powerUp(), powerDown() methods in MSXMultiDevice. It's even wrong if they ever get called. 2006-01-11 Wouter Vermaelen * Implemented [ 1320568 ] Preload entire diskimage/ROM: We don't actually keep the image in memory, just read it and discard the result. However the image will stick in the OS cache (only tested on Linux though). This way the "don't preload when not enough free RAM is available" issue is addressed. Tested with both CDROMs and floppy's and it seems to work reasonably well. 2006-01-08 Wouter Vermaelen * Fixed interlace + multi page horizontal scroll also for GLRenderer: with deinterlaced disabled in it's the same fix as for SDLRenderer 2006-01-08 Maarten ter Huurne * Refactoring of GLRasterizer: - only rasterize sprites if the line actually contains sprites; this can make quite an impact on performance 2006-01-07 Maarten ter Huurne * Refactoring of GLRasterizer: - partial preparation for output windows other than 640x480 - removed deinterlace code, because the idea was broken; later deinterlace will have to be re-added in a better way (like the SDL renderer has) - store bitmap data in a single large texture rather than one texture per line; this is better for performance 2006-01-06 Wouter Vermaelen * Reenabled 'sdlgl-fb' renderers in non-release versions * Made 2x scalers work with non 640 wide output surfaces: preparation to enable 4x scalers later * For symmetry also made 1x and 3x scalers output width independent 2006-01-05 Maarten ter Huurne * Split OutputSurface into smaller OutputSurface and VisibleSurface. * Use PixelOperations instead of SDL_PixelFormat where possible. 2006-01-05 Wouter Vermaelen * Added support for SDL 'world_' and 'undo' keys. 2006-01-04 Wouter Vermaelen * Report error for key names with multiple non-modifier parts (e.g. ENTER,A or PRINT,UP) 2006-01-04 Maarten ter Huurne * Moved ownership of RenderSettings from MSXMotherBoard to Display. 2006-01-03 Wouter Vermaelen * Fixed combination of interlace and multi-page horizontal scrolling: only for SDL renderer yet. Deinterlacing first needs to be fixed in GL renderer (also use data from previous frame) 2006-01-02 Maarten ter Huurne * Created release branch for openMSX 0.6.0. 2005-12-31 Wouter Vermaelen * Fixed assert when openmsx was quit with simple pluggable still plugged in the printerport. 2005-12-31 Manuel Bilderbeek * Temporarily disabled reporting of filetype of first file on cassette, because this breaks Catapult, and Herman is not there to fix it... This way we can release. TODO re-enable after release and fix Catapult or finally start on the Qt version of Catapult! At this time, I'm talking about line 407 of CassettePlayer.cc. 2005-12-29 Maarten ter Huurne * Allow installation of binary to /usr/local/bin without overwriting binary with a symlink. * Check whether the C++ compiler is working. It checks only if the compiler can compile "Hello World!", so it is not guaranteed the compiler supports all C++ features needed to compile openMSX. But the complete absence of a compiler is correctly identified now, instead of reporting that all libraries are missing. 2005-12-28 Maarten ter Huurne * Rewrote "make install" to use the "install" utility instead of "cp" combined with "chmod". The problem was that the "chmod" step did not work on systems where the installed files are spread over multiple directories, such as FreeBSD. 2005-12-27 Maarten ter Huurne * Added support for FreeBSD 6 by making FreeBSD 4 an exception (it has GCC 2.9x as default compiler) and using the same platform Makefile for all other FreeBSD versions. * Added support for CPU reported as "amd64" (mapped to "x86_64"). 2005-12-27 Wouter Vermaelen * Force PSG portB direction to output: - we already forced portA to input - some msx machines have fixed port directions others not, we should make this configurable per machine in the future - for more details see [ 1390211 ] joystick problems 2005-12-26 Wouter Vermaelen * Revert cleanup in XMLLoader.cc, it breaks making a connection from openmsx-debugger to openmsx in windows * Disabled sdlgl-fb16 and sdlgl-fb32 renderers: they don't offer anything over the existing renderers and sdlgl-fb32 still has endian problems on PPC 2005-12-24 Wouter Vermaelen * Made S1990 registers debuggable 2005-12-23 Wouter Vermaelen * Reverted bug in earlier 'cleanup' in VDPVRAM: - we must use baseAddr to encode 'window is enabled' info because the implementation of isInside depends on it - fixes crash of KV2 MSX1 on turboR 2005-12-20 Wouter Vermaelen * Synchronize CPU and scheduler irregardless of how CPU loop was exited (normally or via breakpoint): - fixes assert when MSX is reset while CPU is breaked * Prepare openmsx-0.6.0-rc1 2005-12-18 Wouter Vermaelen * Assert on exceptions that pass through the TCL interpreter: a config error in the ADVRAM extension asserts now (instead of calling terminate()), we're still investigating how to fix it * Worked around error from above, clean solution needs dependencies between devices 2005-12-18 Manuel Bilderbeek * Implemented AutoRun feature for cassette software - only works for CAS images, WAV is TODO (more difficult) - experimental; default setting of "autoruncassettes" is off - also tries to autorun if you insert at run time - doesn't work properly on machines with non-international keyboard layout, because the 'type' command doesn't work properly then - please review and feel free to improve it! :P 2005-12-16 Wouter Vermaelen * Properly fix fnano2 bug * Implemented horizontal scrolling in character modes 2005-12-14 Arnold Metselaar * Use another bit for planar mode when ADVRAM has no 'enable' function 2005-12-14 Wouter Vermaelen * Made 'enable' function in ADVRAM configurable 2005-12-13 Wouter Vermaelen * Draw scanline in 3x simple scaler deinterlace mode 2005-12-12 Wouter Vermaelen * Implemented deinterlace mode support in RGBTriplet scaler 2005-12-11 Manuel Bilderbeek * Updated the manuals for the new scaler settings. Please review! 2005-12-10 Manuel Bilderbeek * Pass cassetteplayer status to CliComm 2005-12-08 Wouter Vermaelen * Implemented 'openmsx_info setting': It returns a TCL list with: - first element is the type of the setting string, integer, float, enumeration, filename or key - second element is the default value of the setting - third element is optional, for integer and float settings it is a list with the minimum and maximum allowed value. For enum settings it is a list with all possible values. * Fixed timing of slowest (no extra wait states) R800 IO instructions 2005-12-07 Wouter Vermaelen * Fixed timing if 'IM n' instruction on R800: should take 3 cycles iso 2 * Added '-script' command line option: - makes it possible to execute additional startup scripts after the default init.tcl script 2005-12-05 Wouter Vermaelen * Split scaler setting into scale_factor and scale_algorithm 2005-12-04 Wouter Vermaelen * Readd delay on turboR on ports 0x9A and 0x9B: - fixes previous fix for assert on turbor with advram - also prepared code to later add delays on other ports 2005-12-04 Arnold Metselaar * change advram to emulate a newer version that can be switched on and off by software * fix assert when using advram on turboR 2005-12-03 Wouter Vermaelen * Small ADVRAM changes: - read device ID ("VDP") from config file - bring coding style in line with the rest of the openmsx code 2005-12-03 Arnold Metselaar * MSXDevice(readIO, peekIO, writeIO): change type of port address to word * MSXMotherBoard: add methods lockDevice and releaseDevice to allow interdepencency of devices * ADVram: new extension 2005-12-02 Wouter Vermaelen * Handle scaleBlank() via Scaler::scaleImage(): - allows to fix the "[1057836] Infinite plasma logo" bug - also properly handle scanlines with non-black border color in deinterlace mode. Previously display area didn't had scanlines while border area had. Now none has scanlines. 2005-12-01 Wouter Vermaelen * Also do realtime syncing when started with none renderer * Fixed deinterlace in combination with frameskip * Use DoubledFrame to render interlaced (non-deinterlaced) frames: scalers with odd scale factor are rendered better now. For example low scaler also shakes now 2005-11-29 Wouter Vermaelen * Added line scaling in FrameSource: - fixes the 'double screen' bug - improves scaler border effects (but still not completely) 2005-11-27 Manuel Bilderbeek * Made cassetteplayer status query machine readable. Just use 'cassetteplayer' to get a TCL list, similar to the diska command. 2005-11-23 Wouter Vermaelen * Added 192kb VRAM support in VDPCmdEngine 2005-11-20 Maarten ter Huurne * Optimised variable zoom factor version of 2xSaI. * Used template metaprogramming to instantiate an optimised fixed zoom factor 3xSaI scaler. 2005-11-19 Wouter Vermaelen * Keep icon status in new class (IconStatus) iso in static data of IconLayer: - fixes power led status when openmsx was started with 'none' renderer (catapult) * Basic 192kb VRAM support: - only CPU-VRAM interface, no command engine stuff yet 2005-11-19 Maarten ter Huurne * Added the variable zoom factor version of 2xSaI. It is being used to provide 3xSaI at the moment. The current version is very slow though. 2005-11-18 Wouter Vermaelen * Optimized scale3x (c++) 2005-11-18 Manuel Bilderbeek * Updated the manual. Still TODO are the new zoom/scaler-method settings, which are not even programmed yet. 2005-11-16 Maarten ter Huurne * Added a C++ version of Scale3x. 2005-11-16 Joost Damad * yeah that was one year ago since I did an openmsx commit :) * use word for casting a pointer to an integral; fixes build on 64bit systems * test 2005-11-13 Wouter Vermaelen * Remove all pending after commands on exit 2005-11-13 Wouter Vermaelen * Automatically unschedule SyncPoint when it is deleted: On lots of places we forgot to do this. Will become important when we allow to remove device or switch machine at run time 2005-11-12 Manuel Bilderbeek * Attach to throttleManager, not to throttleSetting. * Added keyword 'explicit' to declarations of constructors with only one argument, to prevent undesired implicit conversions. Please do this everywhere applicable from now on! 2005-11-12 Wouter Vermaelen * Allow multiple tags per device * MSXRam device is now mirrored: Together with change above it's now possible to create RAM like this: 0x2000 This represents a 8kb RAM that is visible on both 0x2000-0x3FFF and 0xA000-0xBFFF * Directly using throttleSetting is wrong (should always go via ThrottleManager), so make that impossible * Still found a singleton, HardwareConfig. Removed it. 2005-11-11 Manuel Bilderbeek * Removed old add/removeListener calls * Removed SettingListener class 2005-11-11 Wouter Vermaelen * PluggingController related cleanups 2005-11-10 Manuel Bilderbeek * Introduced a generic Gamma based Observer pattern, template implementation by Wouter though. Still needs to be used more properly for Setting/SettingListener and ThrottleManager. 2005-11-10 Maarten ter Huurne * Map all even numbered joystick buttons to trig A and all odd numbered joystick buttons to trig B. Before, button 0 was mapped trig A and button 1 was mapped to trig B and if those buttons were not present or not easy to use on your joypad, you were out of luck. 2005-11-08 Wouter Vermaelen * Renamed scaleMxNtoXxY() methods in LowSCaler and 3x Scalers: old named didn't take overscan into account * Renamed all scaleNNN() methods in 2x scalers * Use DeinterlacedFrame in 2x scalers 2005-11-05 Manuel Bilderbeek * More intelligent 'loading' check for disk drive; if there is no head movement for a while, we assume loading stopped. Works much better for software that doesn't turn off the motor. The timeout is set to one second for now. This may need some tuning still. 2005-11-05 Wouter Vermaelen * Allow more general memory specifications in hardwareconfig.xml: - base and size no longer need to be multiples of 0x4000 - It's now possible to create devices that have 8kb rom and 8kb ram in the same 16kb page. * Fixed fullscreen switching in windows 2005-11-04 Manuel Bilderbeek * Implemented an experimental "fullspeedwhenloading" setting. If you turn it on, openMSX will run at full speed when it thinks that the MSX is loading from disk or cassette. It's still a bit dumb for disk as it syncs the motor status... Also, some stuff like RealTime and sound drivers want to get informed if the speed of the emulation changes. This is not passed yet when openMSX goes to full speed due to this new setting. Default is still 'off', of course. Please test this and improve it :P 2005-11-04 Maarten ter Huurne * Renamed scale methods in LowScaler to reflect ratio i.s.o. line width. * Finally introduced DeinterlacedFrame class. Right now it is only used by PostProcessor internally, but in the future the scalers will use it too, as a FrameSource. 2005-11-03 Wouter Vermaelen * Fixed assert on 'set power off ; set power on': also cleaned up reset handling code 2005-11-02 Wouter Vermaelen * Only build SDLGLOutputSurface when OpenGL component is selected 2005-11-01 Manuel Bilderbeek * Optimization in RGBTriplet3xScaler: don't really call scale_1on1; if we already have 256 wide lines, there's no need to scale 2005-11-01 Wouter Vermaelen * Use OutputSurface iso directly SDL_Surface in Scalers: - preparation to use other than SDL framebuffers - OpenGL framebuffer code seems to work, but is not yet enabled. Performance is about the same as SDL on my system * Experimental. Enabled OpenGL framebuffer renderers: - use 'set renderer sdlgl-fb16' and 'sdl-gl32' to enable them - possibly we disable them for the release * Fixed bug in MemoryOps: - need mmxext for streaming stores 2005-10-31 Wouter Vermaelen * Fixed assert when switching video system (hq2x -> hq3x) and leak when switching video system on gfx9000: * Small optimization in Simple3xScaler::scaleBlank() 2005-10-30 Wouter Vermaelen * Fixed memory leaks in VideoSystem/Layers stuff: - simplified layer ownership rules: creator now owns the layer iso transferring ownership to Display - added Display::removeLayer(), there was only addLayer() - VideoSystem and Rasterizer objects were never deleted 2005-10-30 Maarten ter Huurne * Moved the switch(lineWidth) statement from PostProcessor to Scaler, as a preparation for renaming the line scale methods. 2005-10-29 Manuel Bilderbeek * Implemented Simple3xScaler with scanlines. Blur is TODO for someone who is interested :P 2005-10-28 Wouter Vermaelen * Implemented border effect for outer left and right border pixels in RGBTriplet3xScaler * Fixed Scale_XonY line scalers for case when destination width is not an integer multiple of Y 2005-10-28 Manuel Bilderbeek * Implemented RGBTriplet3xScaler for all widths using the new functors 2005-10-28 Wouter Vermaelen * Added 'Streaming store' tags on line scalers to enable optimizations in Scaler2 and Scaler3 2005-10-27 Wouter Vermaelen * Transformed scale_XonY() methods into functors * Changed signature of scale192/384/640/768/1024 methods * Factored out scaling algorithm in the base Scaler classes * Optimized PixelOperations for 32bpp * Implemented 16bpp in RGBTriplet3x 2005-10-26 Manuel Bilderbeek * Implemented adjustable scanline in scaleBlank() for RGBTriplet3x scaler. * Let rgbify (in RGBTriplet3x) use pixelOps to access RGB components. Makes it almost work in 16bpp, except for the spill threshold. TODO: fix this and implement other widths (as soon as this can be done without loads of code duplication...) 2005-10-26 Wouter Vermaelen * Changed signature of scale512() method * Replaced Blender with PixelOperations class: - puts all blend functions in one place 2005-10-25 Wouter Vermaelen * Moved scanline code from SimpleScaler to new class Scanline * Reused scanline code in RGBTriplet3x scaler * Made the 'amount of RGB separation' configurable in RGBTriplet3x scaler 2005-10-24 Manuel Bilderbeek * Implemented scaleBlank() for RGBTriplet3x scaler. Still no effect for non-256-wide modes... (so still WIP) 2005-10-24 Wouter Vermaelen * Improved Manuel's RGB scaler (still WIP) 2005-10-23 Manuel Bilderbeek * Added RGBTriplet3x scaler. Emulates a Trinitron effect, including a scanline. This is still very WIP and experimental! Thanks to Wolf for the idea 2005-10-22 Maarten ter Huurne * More preparations for cleaner deinterlacing: - PostProcessor calculates destination Y (in addition to source Y) - Scaler::scaleBlank signature changed to use destination Y - Scaler::scale256 signature changed to use source and destination Y 2005-10-22 Wouter Vermaelen * Put common code for HQ.x scalers in HQCommon.hh * Optimized MSXtar: - cache FAT in memory * Implemented scaling in IconLayer: - OSD icons now have the same size/position in all scalers 2005-10-21 Wouter Vermaelen * Fixed low and hq3x scalers for v9990 * Cleaned up Scaler code (mostly for non 2x scale factors) 2005-10-19 Wouter Vermaelen * Added hq3xlite scaler 2005-10-18 Wouter Vermaelen * Added hq3x scaler: - is *very* slow (will try to add hq3xlite later) - there might still be problems in combination with gfx9000 2005-10-17 Wouter Vermaelen * Use unix domain sockets iso internet domain sockets for communication with extrenal programs. * Updated Contrib/openmsx-control.cc: made a openmsx-control-socket.cc and a openmsx-control-stdio.cc version 2005-10-11 Wouter Vermaelen * Worked around SDL CAPSLOCK problem: SDL doesn't send proper keyup/down events for CAPSLOCK, instead it sends keydown when key is pressed for the first time and keyup when key is pressed second time. As a workaround we must make an assumption about the duration of the key press (i took 100ms). 2005-10-10 Wouter Vermaelen * Implemented tilde expansion for ~ (*nix only) 2005-10-04 Manuel Bilderbeek * Renamed force_play to motorcontrol and some small clean ups. 2005-10-01 Maarten ter Huurne * Fixed IDE on big endian machines. Commit from Bussum MSX fair :) 2005-09-30 Wouter Vermaelen * Fixed help command: - info topics were visible as commands in the help command * Fixed reset during pause bug 2005-09-29 Maarten ter Huurne * Fixed screenshot colours of SDL renderer on big endian machines. 2005-09-28 Manuel Bilderbeek * Started implementation of new cassetteplayer CLI. - needs a lot of clean ups, only the CLI part was changed - record subcommand not implemented yet. Needs modifications to WavWriter, to be able to append to existing images. Consequently, readonly-handing is not implemented yet either. 2005-09-26 Wouter Vermaelen * Fixed input delay for MSX keyboard: - Split keyboard events in host and emu events. In the future other events should be split as well - Currently the console code translates host keyboard events into emu keyboard events (only when console is disabled). This should change. 2005-09-26 Wouter Vermaelen * Fixed EmuTime parameter for ASAP sync points 2005-09-24 Arnold Metselaar * CassetteJack - fix output when MSX output varies very rapidly - simple interpolation on input 2005-09-21 Wouter Vermaelen * Automatically plug in cassetteplayer when the MSX machine has a cassetteport: - done with a TCL script for the moment because the list of plugged devices is not (yet) saved by openMSX 2005-09-20 Wouter Vermaelen * CassettePlayer stuff: - cleanups (bring coding style in line with rest of openMSX) - fix wav ouput when MSX output varies very rapidly - update wav header 'regularly' so that wav is already usable by an external program before recording on MSX is completely stopped 2005-09-20 Arnold Metselaar * fix in WavWriter class; totalsize was wrong * CassetteJack, CassettePlayer - simple DC removal filter for cassette out signal * CassettePlayer - setMute() when recording - stop recording on eject, insert, rewind unplug and destructor 2005-09-19 Wouter Vermaelen * Made WavWriter class: - removed common code from Mixer and CassettePlayer - code should now also work on big endian machines (not tested) * Enabled wav writing for CassettePlayer * Clear secondary slot registers on reset(!): - fixes reset bug on Sony HB-F500P 2005-09-18 Manuel Bilderbeek * Added a new -testconfig option, which just tries to load the specified machine/extension/etc. config and then exits. Can be used to probe what machine configs are installed correctly, including ROM images 2005-09-17 Wouter Vermaelen * Implemented 'magic key' dongle * Fixed assert on reset (happened most often on turbor) 2005-09-14 Wouter Vermaelen * Made RawFrame SDL independent * Allign every line in a RawFrame at a 64 byte boundary 2005-09-12 Maarten ter Huurne * Preparations for cleaner deinterlacing: introduced FrameSource as an abstract superclass of RawFrame. 2005-09-12 Wouter Vermaelen * Added EmuTime parameter to peekMem() method * Implemented debug reads for FDC's 2005-09-10 Wouter Vermaelen * Fixed some SCC details (needed for playing samples on SCC): - stuff was discovered by NYYRIKKI - see comments SCC.cc for details 2005-09-09 Wouter Vermaelen * update events are now enabled/disabled per connection 2005-09-07 Maarten ter Huurne * Set environment variables though TCL interpreter. Fixes problem on Mac OS X where OPENMSX_(SYSTEM|USER)_DATA is not updated in TCL, causing "init.tcl" to fail. 2005-09-06 Wouter Vermaelen * Added listen_host and list_port_min/listen_port_max settings: - valid values for listen_host are 'all', 'local' and 'none'. Default is 'local' - listen_port_min/max is a range of port number on which openmsx will listen for incomming connection (openmsx session will take the lowest free port, it's a range to allow multiple simultaneous openmsx sesions). Default is 9938 - 9958. - TODO better names for these settings? - TODO documentation 2005-09-05 Arnold Metselaar * Added support for interaction between cassette port and jack audio connection kit - added CassetteJack - moved the generation of samples from Cassette to the Pluggable. - build system updated - description in user.html 2005-09-03 Wouter Vermaelen * Large singleton cleanup (only a few remain now): - singleton classes had a lot a inter-dependencies, it was not possible to make this change in smaller steps - these dependencies still exist, but now there are explicitly visible in the code (in some places it looks ugly now) - TODO break some of these dependencies 2005-09-02 Manuel Bilderbeek * Fixed a bug in the disassembler (thanks for the anonymous poster for telling us about it); it now uses stringstreams iso char arrays. 2005-08-28 Wouter Vermaelen * More singleton cleanups: UserInputEventDistributor, CommandConsole, RenderSettings and Display are no longer singletons 2005-08-25 Wouter Vermaelen * Moved implementation of 'toggle' command to a TCL script * Removed CondVar class, it was not used anymore 2005-08-22 Wouter Vermaelen * MSXtar cleanup: - don't use CliComm to print warnings or errors, instead report them as normal TCL output or TCL error msgs - more robust error handling 2005-08-20 Wouter Vermaelen * Added resume update event 2005-08-19 Wouter Vermaelen * Implemented peekIO (debug IO reads) for MSX-AUDIO 2005-08-14 Wouter Vermaelen * V9990 no longer internally renders 640 pixels wide lines. The PostProcessor now scales the lines to the correct width. * Extended Scalers with scale192() scale384() scale768() scale1024(): TODO find a better way to do this 2005-08-13 Wouter Vermaelen * Use PostProcessor for V9990: - this means scaler, scanline, blur settings have now also effect on V9990 display - TODO all V9990 lines are internally still rendered 640 pixels wide 2005-08-12 Wouter Vermaelen * Changed 'issubslotted' from Debuggable into InfoTopic * Added 'isexternalslot' InfoTopic * Added 'guess_title' script 2005-08-10 Manuel Bilderbeek * Added -prefix option to screenshot command * Moved getName from ScreenShotSaver to FileOperations, with new name "getNextNumberedFilename" and used it in Display and Mixer. Removes code duplication between those 2. 2005-08-10 Wouter Vermaelen * Fixed using precompiled headers: - include guards should be unique in the whole tree - split Icon.hh in Icon.hh and Icon.cc * Fixed KeyJoystick: - UserInputEventDistributer can now handle multiple listeners with the same priority * Large V9990 speedup: - added faster VRAM access methods, specific for certain display modes - optimized logical operations in V9990CmdEngine with LUT 2005-08-02 Wouter Vermaelen * Added SimpleDebuggable class: made code of most Debuggables a lot simpler * Simplified SoundDevice class 2005-07-23 Wouter Vermaelen * Fixed bug in EventDistributor: event order could get swapped for events close together in time * Scheduler now keeps sync points with same EmuTime in the same order as they are registered 2005-07-23 Manuel Bilderbeek * Added -nommx and -nommxext command line options, to disable MMX and MMXEXT usage, if available. This is useful only for debugging purposes and may change in the near future. 2005-07-22 Wouter Vermaelen * Implemented resolution changing for low scaler * Renamed 'SDLHi' renderer to 'SDL' 2005-07-21 Wouter Vermaelen * Made Scaler interface independent from scale factor: - low scaler works now, although still in a 640x480 window * Moved Deinterlacer functionality to Scaler: - preparation for low scaler deinterlacer * Implemented deinterlace for low scaler: - no MMX or SSE optimized version yet 2005-07-20 Wouter Vermaelen * Removed SDLLo renderer, SDLLo renderer will be replaced with "low" scaler soon 2005-07-20 Maarten ter Huurne * Split off PostProcessor from SDLRasterizer. * Fixed bug in memset4_2_CPP. This was the cause for "[1237004] OSX Tiger GFX bug". * Fixed endianness bug in memset_2_helper 16bpp support. * Used a different GCC 3.3 bug workaround, because the previous one caused bus errors on Mac OS 10.3. 2005-07-18 Maarten ter Huurne * Introduced RawFrame: a class which contains an unscaled frame. This is an architectural improvement which will later allow bug fixes, cleaner code and new features. * Removed NATIVE event listeners (replaced by DETACHED). 2005-07-18 Wouter Vermaelen * Made CPU breaked status queryable with "debug breaked" subcommand * Use glTexSubImage2D to reuse existing textures: can be faster in certain OpenGL implementations 2005-07-16 Maarten ter Huurne * Optimized PNG images. I used the tools pngrewrite and OptiPNG. * Optimized PNGs some more using advpng. * Optimized PNGs some more using pngcrush. Thanks to Wouter for the tip. 2005-07-15 Manuel Bilderbeek * Updated the IDE ROM to version 2.40. 2005-07-14 Wouter Vermaelen * Added "issubslotted" debuggable * Reimplemented "slotselect" command as a TCL proc * Added "pc_in_slot" TCL proc. Can be used to set a bp in a specific slot. 2005-07-13 Maarten ter Huurne * Fixed the new icon on big endian machines. 2005-07-12 Wouter Vermaelen * Fixed auto saving of settings at exit 2005-07-11 Manuel Bilderbeek * Thanks to Wouter's improvements to CasImage, I could boost the baudrate of cas images to 5520! :) Any more and it blows ;-) * Don't use allUp(), but just let key up events through to the MSX when console is active. 2005-07-10 Maarten ter Huurne * Introduced UserInputEventDistributor, as a first step to get rid of the "native" event handlers. This distributor forwards keyboard events to the console or the MSX depending on whether the console is active. 2005-07-07 Wouter Vermaelen * Fixed linking problem for gcc4 * Fixed inline assembler for gcc4: we need to hide the MMX/SSE registers in the clobber list from the compiler when the target CPU doesn't know about these registers 2005-07-06 Manuel Bilderbeek * Renamed 'eject', 'rewind', 'force_play' and 'no_force_play' to '-eject', '-rewind', '-force_play' and '-no_force_play' in the CassettePlayer. * Added completion for these options. 2005-07-05 Wouter Vermaelen * Refactored SoundDevice code: pass estimated EmuTime and EmuDuration to updateBuffer() preparation for DACSound improvements * Improved DACSound sound quality: - 2x oversample signal (and downsample with 4th order FIR filter) - use time average to extract the (unfiltered) signal 2005-07-02 Wouter Vermaelen * Implemented stripes for screen6 background * Fixed "load_settings" command 2005-06-30 Wouter Vermaelen * Moved vdrive key bindings from openMSX code to vdrive.tcl script 2005-06-29 Wouter Vermaelen * Only save key bindings (settings.xml) that are different from the default key bindings. Added (un)bind_default commands. * HQ2xLite cleanup + small speedup 2005-06-27 Wouter Vermaelen * Mute sound when a bp is hit and during turbor HW pause * Implemented HW mute bit in turbor PCM device * SettingsManager is no longer a singleton * Don't save settings that (still) have their default value: - this way the user no longer needs to delete his settings.xml file to get the new default values when he upgrades - TODO: this broke the load_settings command. I'll fix it after keybindings and keyjoystick are refactored * Added second keyjoystick: Names are now 'keyjoystick1' and 'keyjoystick2'. This is not backwards compatible with the old name 'keyjoystick'. Do we need a compatibilty thing for this? * Made keyjoysticks configurable with settings (iso directly having to edit settings.xml): Setting names are like this 'keyjoystick1.up', 'xx.down', etc 2005-06-26 Wouter Vermaelen * Mixer is no longer a singleton 2005-06-25 Wouter Vermaelen * Implemented stripes in border in screen6 2005-06-23 Herman Oudejans * Fixed problem with saving SRAM to a machine that wasn't used before. 2005-06-20 Wouter Vermaelen * Unrolled inner loops in CharacterConverter: - rendering text2 mode is between 1% and 2% faster now (on total simulation time) - gcc only unrolls loops when the option -funroll-loops is given it's not automatically enabled at any optimization level because it sometimes also creates slower code * Changed result format of "diska" command, updated vdrive script * Don't use default alpha value in SDL console: When the background image had a contant alpha value (or no alpha value at all) we took a default alpha value. This behaviour is different from the GL Console. It's also not very useful because it prevents using a background with a different (constant) alpha value (or no alpha at all). 2005-06-20 Wouter Vermaelen * Flush PrinterPortLogger data 2005-06-18 Wouter Vermaelen * Fixed problem with event distribution: Current implementation can't handle new subsriptions while events are being distributed. In this case it was easy to avoid but in the future we may have to change the implementation to allow this. I also added some asserts to more easily detect this type of problem in the future. * Reimplemented "palette" and "vdpregs" commands as TCL scripts * Added Vampiers "setcolor" TCL script (slightly modified) * Reimplemented "v9990regs" command as a TCL script * Updated cpu.tcl, made a proc to read/write individual registers, see documentation in cpu.tcl for details * Implemented SRAM syncing: at most 5 seconds after a write to SRAM the content of the SRAM is saved to disk. 2005-06-17 Wouter Vermaelen * RenShaTurbo is no longer singleton * Debugger is no longer singleton * Use references iso pointers where possible in VDP code * Made memory mapper registers debuggable (MapperIO) * Made V99x8 palette debuggable 2005-06-16 Wouter Vermaelen * Large refactoring, get rid of some singletons: MSXMapperIO, MultiIODevice, MSXDeviceSwitch, DummyDevice, PanasonicMemory, MSXCPUInterface, MSXCPU are no longer singletons. Instead the (single) object of this class should be get from MSXMotherBoard. MSXDevices and some other classes now have a backpointer to MSXMotherBoard * Get rid of more singletons: PluggingController, CassettePortFactory 2005-06-15 Wouter Vermaelen * Added basic syntax checking for expressions in conditional bp's 2005-06-14 Wouter Vermaelen * Implemented conditional breakpoints: TODO add more documentation and examples 2005-06-13 Wouter Vermaelen * Turbo R hw pause only pauses the CPU, not also all other devices. 2005-06-12 Maarten ter Huurne * Only Schedulable has access to setting and removing sync points. * Look for TCL in "lib64" directories as well. Fixes TCL detection on Fedora Core 3 x86-64. * Split off Reactor class from MSXMotherBoard. Work in progress. * Decided that the "reset" command and the "power" setting apply to a specific MSX machine and therefore belong in MSXMotherBoard. The code that handles that command and setting already assumes that it is called from the Reactor instead of the Scheduler, but this is not yet the case, so currently assertions can be triggered. 2005-06-11 Maarten ter Huurne * Do not initialise SDL if openMSX doesn't start up (-h, --version). * Initialise SDL video when the first screen is opened. 2005-06-11 Wouter Vermaelen * More accurate Clock class (when freq is a fraction of 3.58MHz) * Also complete "-eject" and "-ramdsk" in "diska" command 2005-06-10 Maarten ter Huurne * CommandLineParser is no longer a singleton. * Removed -nosound command line option. Use the "null" sound driver instead. 2005-06-10 Wouter Vermaelen * Small speedup of simple scaler * replaced classes with only static methods inside (e.g. StringOp) with a namespace * Implemented hard/soft pause bit in turbor (bit 1 in port 0xa7) * Removed support for old romdb format * Removed support for , (use -romtype instead) * Use tag in softwaredb 2005-06-10 David Heremans * Added vdrive.tcl script: This scripts allows user to cycle through a uniformaly named diskset. It enables blueMSX users to use the same kind of diskcycling as blue's v-DRIVE if binded to the ALT+F9 hotkey 2005-06-08 Wouter Vermaelen * Removed romdb.xml and romdb.dtd files: These files were already obsoleted by softwaredb.xml. OpenMSX still supports the old format, after the release we'll also remove this. 2005-06-07 Maarten ter Huurne * Updated C-BIOS to 0.21. 2005-06-06 Manuel Bilderbeek * Implemented new icon by Eric Boon (32x32 version). * Cleaned up Icon code, to use a slightly modified GIMP C-Source image dump. It's now also easier to put in a larger bitmap. 2005-06-02 Wouter Vermaelen * Fixed background in screen6: only use lower two bits for background color TODO use stripes 2005-05-28 David Heremans * introduced a quick-fix: The minial bootsector in MSXtar is, at the moment, created for a 360KB disk. For now the minimal partition size is forced to this size as well. 2005-05-25 Wouter Vermaelen * Removed 'useFile' sub command from 'diskmanipulator': the new 'virtual_drive' replaces its functionality * Refactored DiskDrive code: factored out disk changing code into DiskChanger class * Added 'virtual_drive': this drive is always available (even in power off state), its main use is for the diskmanipulator stuff 2005-05-24 Wouter Vermaelen * Merged Maarten's 'close socket stuff' patch: - should fix exit problems on OSX 2005-05-24 David Heremans * Enhanced the 'diskmanipulator import' function: It now accepts a list of files and directories as arguments 2005-05-23 Wouter Vermaelen * Renamed 'eject' and 'ramdsk' to '-eject' and '-ramdsk' * Renamed 'filemanipulator' to 'diskmanipulator' * Removed diskmanipulator sub command 'usePartition' 2005-05-22 Manuel Bilderbeek * Literally reVamped default console background and font! ;-) ConsoleFontRaveLShaded is default font now, ConsoleBackgroundGrey is default background (see poll). Added some others as well. All graphical work was done by Patrick van Arkel. 2005-05-21 Wouter Vermaelen * R800 timing improvements: CAS/RAS optimization only works for internal RAM, timing tests in R800-ROM look a lot better now 2005-05-20 Wouter Vermaelen * fixed R800 memory access timing: - accessing internal RAM (or some ROMs in DRAM mode) takes 1 cycle - accessing internal ROM takes 2 cycles - accessing external slot takes 3 cycles 2005-05-19 Wouter Vermaelen * R800 timing fix: - retn reti instructions takes 5 cycles (2 more than a simple 'ret', that's unexpected if you look at the Z80 timings) - tweaked CAS/RAS optimization a bit, also optimize data fetches, but still don't optimize between opcode and data fetched I have no idea whether it really works like this, but the test results are better now 2005-05-18 Wouter Vermaelen * R800 timing fix: di instruction takes 2 cycles (ei takes just 1) 2005-05-16 Manuel Bilderbeek * Added Sony HB-F700D config. Interesting characteristics are: 256kB RAM, German ROMs (nice for German users), broken_fdc_read=true 2005-05-14 Wouter Vermaelen * Made WD2793 code more robust: Something _could_ go wrong when a new command was started while the previous command was still busy. This might fix the drive problems seems by vampier, mth and shevek but it might also be something completely different. 2005-05-14 David Heremans * More FileManipulator stuff - get feedback on curretn situation from 'useFile', 'usePartition' and 'chdir' in the same was as our 'set ' commands work - fixed small bug concerning non partitioned devices 2005-05-11 David Heremans * More FileManipulator help 2005-05-11 Wouter Vermaelen * Improved build time (~15% faster full build here): - split EmuTime.hh in EmuTime.hh EmuDuration.hh Clock.hh and DynamicClock.hh - don't #include in openmsx.hh in non-debug builds PRT_DEBUG macro is not checked anymore for syntax errors in non-debug builds now, but I think that's not a big problem 2005-05-09 David Heremans * Added support for linux style partitionnumbers in the FileManipulator 2005-05-09 Wouter Vermaelen * Fixed linking problem with gcc-4.x: explicit template instantiation must be done _after_ template definition and implementation * Improved timer accuracy of MSX-AUDIO / OPL4: thanks to Daniel for figuring this out 2005-05-07 Wouter Vermaelen * Large MSXtar / Filemanipulator (and related stuff) cleanup 2005-05-06 Wouter Vermaelen * Implemented missing WD2793 feature, interrupt on index pulse: - fixes 'write protected' message while formatting read-only disk images 2005-05-06 David Heremans * MSXtar Work-In-Progress: - limited disk/partitions to 65535 sectors for now to solve the 'create disk of 32MB' problem. - improved bootsector guessing in the MSXtar constructor and commented it out already :-) - check against directory removals done by the emulated MSX - fixed some compile warnings 2005-05-05 Patrick van Arkel (vampiermsx@gmail.com) * Added trainers.tcl to the scripts directory - This file will enable the users to 'cheat' games 2005-05-03 Wouter Vermaelen * Console refactoring: - simplified code / fixed a bug * Use Tcl_DeleteCommandFromToken iso Tcl_DeleteCommand to unregister TCL commands: is safer when the user uses 'rename' on the commands * Correctly fill in the 'LBA size' field in the IDEHD 'Identify Block': patch made by Adriano Camargo Rodrigues da Cunha 2005-05-03 Wouter Vermaelen * Fixed -nosound option: - also marked as deprecated 2005-05-04 David Heremans * MSXtar Work-In-Progress: - new chdir,mkdir and dir command. - registering of IDEHD now done by the IDE controller which adds a Master or Slave indication to the name - enabled '.' and '..' support in some MSXtar routines - while cleaning up the code I added some verbose error messages in case of failure or wrong usage 2005-05-02 David Heremans * MSXtar Work-In-Progress: - partition info per DiskContainer device - new format and useFile command. - renamed getDir/AddDir to import/export - IDEHD import seems to work :-) - 'create'-command works but IDE sector 0 is not fully correct yet. - extra documentation file concerning the filemanipulator 2005-04-29 Patrick van Arkel (vampiermsx@gmail.com) * added a new rom database after some cleanups 2005-04-28 Daniel Vik (daniel@vik.cc) (commited by Patrick van Arkel) * added support for windows timers * fixed directX sound driver for win32 * added RealTime.cc (Please talk to Daniel about line 96) 2005-04-28 David Heremans * MSXtar Work-In-Progress: - the IDEHD is now accessible for the FileManipulator. - partition support is comming along nicely. - you can already do a 'getDir' to export an MSX-IDEHD partition to your host-OS. No importing yet - untested create dsk image code with partition support :-) 2005-04-28 Manuel Bilderbeek * Fixed a small bug in IDE HD. 2005-04-25 Wouter Vermaelen * Experimental directX sound driver for win32: - I don't have a windows machine so this code is NOT TESTED 2005-04-24 Manuel Bilderbeek * Added EXPERIMENTAL AND UNTESTED build support for the following CPUs. DEC Alpha, ARM, HP PA-RISC, IA-64, Motorola 680x0, MIPS (Big and Little Endian), IBM S/390. Everything should work (endianness is OK), but we have no systems to test on. Let's wait until someone complains that it doesn't work and then fix it (if needed at all). 2005-04-23 Wouter Vermaelen * Extended IPS patch support: - support IPS files with internally overlapping regions - support IPS files that extend the orginal file size * Added sound_driver setting: currently only 'null' and 'sdl' drivers 2005-04-21 Maarten ter Huurne * Made openMSX relocatable on Mac OS X. It looks for the "share" directory first in the directory containing the executable and the search goes up one directory until it is found. 2005-04-21 Wouter Vermaelen * Split Mixer in generic sample generation part and sound system dependant sound output part: - preparation to support other sound systems later (eg DirectSound) 2005-04-20 David Heremans * MSXtar Work-In-Progress: - using the temporary addDir/getDir you can now import/export host-directories (and its subdirs) into a dsk image. - Creating some extra classes to later on facilitate IDEHD registration with FileManipulator, and add FileManipulator functionallity to work with dsk-images without the use of drives of a running MSX session. 2005-04-19 Manuel Bilderbeek * Added support for Sparc CPUs. 2005-04-14 Wouter Vermaelen * MSXtar cleanup / fixes: - fixed compilation on windows (not actually tested): the type field 'd_type' in the struct 'dirent' is not generally available (not on windows for example) - lots of small cleanups (mainly layout) 2005-04-13 Wouter Vermaelen * Refactored V9990 'renderUntil' related code: should fix pixel accurate rendering 2005-04-13 David Heremans * Got the needed routines to add files/subdirs to a dsk image into msxtar code.The code compiles but doesn't work yet. Going from an all-in-memory model to a one-sector-at-a-time model introduced a lot of partial (and untested) rewrites and some quick hacks. 2005-04-11 Wouter Vermaelen * Implemented V9990 vertical scroll rolling in Bx modes 2005-04-09 Wouter Vermaelen * Improved SCC sound quality - during (very) short periods of silence, waveform counters should continue to increase (major improvement) - copied filter code from BlueMSX (minor improvement) * Enabled pixel/line accurate rendering for V9990 (WIP) 2005-04-09 Manuel Bilderbeek * Switched the two YM2413 cores again. Okazaki is now default, because it sounds better now. Regarding drums, some sound better in Okazaki, some in Burczynski (e.g. the cymbal). Ideally the drums should be switchable and the normal FM should always be Okazaki. Thanks to Wolf for judging the sound quality. 2005-04-04 David Heremans * Actually trying to get the format routines from the original msxtar into openMSX. The idea is that this new class will perform the real work, and the File/DiskImageManipulator will invoke msxtar as needed 2005-04-05 Wouter Vermaelen * Fixed / cleaned up FileManipulator and RamDSK code: still TODO: - proposal: rename FileManipulator to DiskImageManipulator? - proposal: change "diska ramdsk" to "diska -ramdisk []" (note: ramdsk vs ramdisk (or just ram?)) - keep ramdisk contents when disk is ejected/reinserted - automatically format ramdisk on first insert * Simplified all SectorsBasedDisk subclasses 2005-04-04 David Heremans * Filemanipulator: tab completion. 2005-04-02 Maarten ter Huurne * Refactored AY8910Interface and renamed to AY8910Periphery. Main difference is the discovery that peek and read actually are the same thing on this interface, not just in the implemenation but in the conceptual model as well. 2005-04-01 Manuel Bilderbeek * WIP: make Okazaki core more like 0.61 * Swapped default and alternative YM2413 cores, because Okazaki core is very broken at the moment. CHECK points need to be checked by Wouter still 2005-04-01 David Heremans * Next step to integrate msxtar into openMSX: The creation of the RamDSK class, to allow ramdisk like dsk usage Doesn't seem to work yet though :-) 2005-04-01 David Heremans * First steps to integrate msxtar into openMSX: The creation of the FileManipulator class 2005-03-31 Wouter Vermaelen * More YM2413 fixes (ported from v0.55) * Swapped default and alternative YM2413 cores: is easier to test this way, if results are good, this might stay the default order 2005-03-29 Manuel Bilderbeek * Tried to adapt Okazaki YM2413 core in openMSX to make it look more like the original code (version 0.55, from NLMSX, thanks Frits). WIP! 2005-03-28 Wouter Vermaelen * Handle TCL events in openmsx event loop: - makes Patrick's webserver script work - preparation for Tk integration 2005-03-25 Herman Oudejans * fixed -control stdio on windows. With this option, the StdioConnection was not created. 2005-03-21 Wouter Vermaelen * Split CliComm in CliComm and CliServer: - fixed assert on startup (CliComm must be instantiated in main thread) * Added cheat.tcl: written by Patrick van Arkel see share/scripts/cheat.tcl for a short explanation 2005-03-19 Wouter Vermaelen * Implemented step_in, step_over and run_to as TCL procs * Try to listen on ports 9938-9958 (iso just 9938), and print a warning (iso crash) when none of these is available 2005-03-18 Wouter Vermaelen * Fixed crash on missing argument for 'debug set_bp/remove_bp' * Implemented "debug disasm" command: with this command Boukichi's 4 debugger commands (offered as a patch on openmsx-0.5.1) can be implemented as simple TCL scripts 2005-03-17 Wouter Vermaelen * Tiny SDLGL improvement: - use glRect() to draw rectangles with a single color iso GL_QUADS * Experimental: enable window resizing in GL renderer - For some reason it doens't work yet in character modes - I read that on windows you need to reload all textures after a resize. I couldn't test this but if it's true resizing currently doesn't work in windows - By default the resizing is disabled, to enable it comment out line 110 in src/video/SDLGLVideoSystem.cc 2005-03-16 Wouter Vermaelen * Fixed MSX-AUDIO sample RAM detection: UR always detected 256kb AR always detected 0kb both are fixed now 2005-03-14 Wouter Vermaelen * Fixed minor(?) rounding error in SCC 2005-03-14 Wouter Vermaelen * Split CliComm class in CliComm and CliConnection: helps to keep the c-macros defined in windows.h more local * Only accept connections from local host: it's still a security risk (hostile local users) but much less already. 2005-03-13 Manuel Bilderbeek * Implemented soundlog toggle command. * Implemented tabCompletion for soundlog command and partially for after command * Stop logging sound when frequency setting is changed. Alternative: ignore frequency change when logging sound. 2005-03-13 Herman Oudejans * Fixed win32 compile errors TODO: - 2 warnings - better integration of wsock32 in the buildsystem 2005-03-12 Manuel Bilderbeek * Added basic code to record openMSX sound to wav file. It is not used yet, but one can test it by uncommenting 2 lines. * Added soundlog command to start and stop recording of sound. There is still a lot TODO - see the comment in Mixer.cc 2005-03-12 Wouter Vermaelen * Added socket support: - openmsx now listens to incomming connections on TCP/IP port 9938 when a connection is established the same communication is possible as with the CliComm stuff over stdio - For the moment port 9938 is open for everyone: security hole! Sockets should be explicitly enabled and by default only for local connections 2005-03-11 Wouter Vermaelen * Fixed various warnings: - doxygen warnings - compiler warnings while compiling with -Wall -Wextra - previous compiler warings with also -DNDEBUG 2005-03-10 Wouter Vermaelen * Added HQ2xLite scaler: Resulting image is close to hq2x but can be calculated a lot faster See comments at the top of HQ2XLiteScaler.cc for details. * Changed include guards from __FOO_HH__ to FOO_HH: Identifiers that contain a double '_' or that start with a '_' are reserved for system headers. Currently our include guards work but it might give problems in the future. 2005-03-08 Wouter Vermaelen * HQ2xScaler speedup 2005-03-05 Maarten ter Huurne * Released openmsx-0.5.1. 2005-03-05 Manuel Bilderbeek * Added Sony HB-F1II config and corrected HB-F1 config. 2005-03-05 Maarten ter Huurne * Added support for Checkmark FM Stereo PAK. The stereo effect is not yet emulated, but it works fine in mono. Thanks to Albert for the config XML. 2005-03-04 Wouter Vermaelen * Also accept the enter key on the numerical pad in the console 2005-03-03 Wouter Vermaelen * In deinterlace mode render always two consecutive frames: When every other frame is skipped deinterlacer worked on 1 new frame and 1 very old frame. Fixed by rendering (not necessarily drawing) always two consecutive frames. 2005-03-03 David Heremans * Replaced 'fallback LEDs' with variant of set1 2005-03-02 Wouter Vermaelen * Last minute db format change: type for normal roms is now structured like this normal 0x4000 ... * romtypes should start with capital (e.g. 'Normal' iso 'normal) 2005-03-01 Wouter Vermaelen * Don't send LED events when LED status didn't change: fixes the slowdowns in sphere 2005-02-28 Wouter Vermaelen * Fixed warnings while compiling with -Wextra (all harmless) 2005-02-27 Wouter Vermaelen * video code was not exception safe, fixed it: - when an excpetion was thrown some objects were not deleted or even deleted twice * Accept sha1sums in db in both upper and lower case * Fixed debug reading from ioports 2005-02-27 Manuel Bilderbeek * Also check if entries have a child. Fixes Synthesizer. 2005-02-26 Patrick van Arkel * Updated the softwaredb.xml thanks to Mars2000you for providing an updated version of his rom list. 2005-02-26 Wouter Vermaelen * Renamed new db to softwaredb.xml * added new db: thanks a lot to Vampier for creating the new db and tools 2005-02-21 Wouter Vermaelen * fixed cassette WavImage DC correction: forgot to properly clip signal 2005-02-21 Wouter Vermaelen * Replaced 'plain' mapper type with 'mirrored' and 'normal', makes it easier to implement the new database mapper types: - for backwards compatibility 'plain' is treated as an alias for 'mirrored' - it's also possible to specify the start address of the rom like this 'mirrored4000', 'normal8000', ... (start address must be a multiple of 4000) * Fixed parsing of romtype for 'plain' roms in database 2005-02-19 Wouter Vermaelen * Update for new romdb 2005-02-19 Manuel Bilderbeek * Updated the manuals a bit more, regarding C-BIOS 0.20 and support for Mac OS X and OpenBSD. Maarten, please check and fix if needed. 2005-02-18 Wouter Vermaelen * Implemented frameskip for gfx9000: I mostly copied code from V99x8 renderer, we might want to factor this common code out in the future 2005-02-17 Wouter Vermaelen * Added locking to EventDistributor: There was a race in EventDistributor (long time already). Today I got a crash because of this race (irreproducible), so apparently it's hard to trigger. I hope it's fixed now. * More P1 mode performance tweaks 2005-02-16 Wouter Vermaelen * Fixed sprites in P1 mode: - Erik did all the hard work, I just had to fix one line ;-) - Battle Bomber works now! * Fixed color of backdrop color in P1 mode * Optimized P1 rendering a bit: just some tweaks, algorithm needs to change for really better performance (Eric already has some ideas I believe) 2005-02-16 Eric Boon * Initial implementation P1 sprites (~15 fps :-/) - HiSpec Snowfall demo shows wrong sprite patterns 2005-02-14 Wouter Vermaelen * Fixed more compiler warnings (all harmless): compiled with gcc version 4.0.0 20050212 (experimental) * Initial support for new rom database format: - both old and new format are supported - support for new format is very minimal, for example lang="xx" attributes are ignored 2005-02-13 Maarten ter Huurne * Added build support for OpenBSD (3.6 RELEASE, to be exact). Thanks to Ariane for testing! * Updated C-BIOS to 0.20, including new directory structure. 2005-02-13 Manuel Bilderbeek * Rerenamed ROM types that we already agreed on for the new standard 2005-02-11 Wouter Vermaelen * Fixed compilation errors / warnings when compiling with gcc-4.0(exp) 2005-02-09 Manuel Bilderbeek * We forgot to replace "brokenFDCread" by "broken_fdc_read" in about 17 different config files, before 0.5.0! OOOPS! 2005-02-08 Wouter Vermaelen * Reduced the 'black flashes' in SDLGL renderer: see comments in Display::repaintDelayed() for details * Fixed switching renderer while in overscan mode (bug was introduced by me two days ago) 2005-02-07 Wouter Vermaelen * Added 'none' icon set * Fixed a (the?) 'pure virtual method called' bug: an Alarm object was deleted in main thread while it was executing in another thread 2005-02-06 Wouter Vermaelen * Fixed CPU tracing (patch created by Maarten) * Fixed(?) sporadic crash when switching renderer on MSX1: - this is quick fix, needs more work after release - needs lots of testing 2005-02-05 Manuel Bilderbeek * Split Philips VG 8020 config in VG 8020 and VG 8020/20. Penguin Adventure works on both now, when using the right ROMs. Thanks to Hans Otten for his kind assistance 2005-02-04 Wouter Vermaelen * Fixed all warning when compiling with icc * Fixed V9990 palette initialization when switching renderer (sdlhi <-> sdllo) 2005-02-03 Manuel Bilderbeek * Added extension for MSX-AUDIO 2. This is an Y8910 on the alternative I/O ports (C2/C3), without the MIDI part of the Music Module 2005-02-03 Wouter Vermaelen * Fixed harmless(?) UMR in AY8910 code 2005-02-02 David Heremans * Fixed a minor announce in the load_icon tcl script and added extra SDLLo checks and a TCL hint from Wouter 2005-02-01 Wouter Vermaelen * documented icon related settings * Fixed 13-bit MSX-AUDIO DAC 2005-02-01 David Heremans * An extra set of LED images. * Enhanced the load_icon tcl script 2005-01-31 David Heremans * Changed default led settings 2005-01-31 Wouter Vermaelen * Implemented V9990 SRCH command * XIMM bits (R#6) are ignored in Px modes * Implemented V9990 BMLL command * BMLL is always done on interleaved VRAM * Fixed V9990 VRAM interleaving: together with previous item fixes scrolling in power basic 2005-01-30 Wouter Vermaelen * When a new settings is created and a TCL variable with the same name is alreday defined, then take this value as initial setting value: fixes icon stuff defined in init.tcl when openmsx is started with enderer none (-control mode) * Only use one .filecache file for all rom pools: this file is put in the (writable) user directory 2005-01-29 Wouter Vermaelen * Extended "disk[x]" console command to accept IPS patches: diska [ [ [..]]] 2005-01-28 Wouter Vermaelen * Allow IPS patches for disk images: only on the command line, not yet in the console * Allow multiple IPS patches on the same rom/disk 2005-01-27 David Heremans * Updated the LED images. 2005-01-26 Wouter Vermaelen * Don't save "mute" setting, at least not until we have an OSD indication for this 2005-01-26 Manuel Bilderbeek * Updated documentation. TODO: MacOS X and C-BIOS stuff 2005-01-25 Wouter Vermaelen * Machine code executes faster is loop is aligned (on 16 byte boundary for athlon CPUs): I measured 1.5% speedup because of this 2005-01-24 Manuel Bilderbeek * Made a separate extension for MegaRAM Disk * Updated ROM types to new names in romdb.xml; also commented out some useless ROMs, because they only occur inside machines or extensions 2005-01-24 Wouter Vermaelen * Fixed Clock<> constructor: rounding is not needed(?) it's even wrong * Fixed tab-complettion for consolebackground 2005-01-22 Eric Boon * First steps towards V9990 P2 mode * Some P1 fixes 2005-01-21 Eric Boon * First steps towards V9990 P1 mode 2005-01-20 Wouter Vermaelen * Fixed some valgrind stuff (UMR, FMR) 2005-01-18 Wouter Vermaelen * Implemented V9990 IRQ stuff: not completely correct yet, but good enough to make the xor-demo work 2005-01-17 Wouter Vermaelen * Removed more #include dependencies * Fixed compilation errors on icc * Added missing #ifdef ASM_X86: should fix compilation on non-x86 machines 2005-01-15 Wouter Vermaelen * Refactored XMLElement related classes: preparartion to fix the 'order of construction of settings' problem (e.g. inputdelay) * Reenabled "inputdelay" setting * Implemented "load_settings" command: only the settings / keybindings / keyjoystick that are mentioned in the new settings file are changed, the rest stays unchanged * Implemented "escape_grab" command 2005-01-13 Wouter Vermaelen * Various small cleanups: mostly use auto_ptr to break #include dependencies (for non time crititical classes) * Also grab input when "grabinput" settings was already "true" when openmsx is started 2005-01-11 Wouter Vermaelen * Lookup original filename inside zip/gz for filetype detection: this fixes [ 1072797 ] Detect extensions if i use a zipped file too * Implemented rotational delay for WD2793: fixes [ 905031 ] Graphic problem in Peach up 2005-01-10 Wouter Vermaelen * Fixed gfx9000 border color 2005-01-08 Wouter Vermaelen * Z80 speedup: union optimization is actually a pessimization on modern CPUs (and an optimizing compiler) * Fixed [ 823686 ] console is included in frameskip and speed setting: also fading of console and LEDs was wrong 2005-01-05 Wouter Vermaelen * Changed command line parsing of IPS files and rom type: - There are two new options "-ips" and "-romtype" these must immediatly follow a ROM option. Examples: openmsx -carta USAS.ROM -ips USAS.IPS -romtype konami openmsx USAS.ROM -romtype konami -ips USAS.IPS openmsx -cart USAS.ROM -romtype konami openmsx USAS.ROM -ips USAS.IPS - The old format , is still supported but is deprecated, it will be removed in future versions. The temporary format ,, is already removed. It gives parsing problems in case of filenames that contain ',' itself. 2005-01-04 Maarten ter Huurne * Fixed initialisation of clock in autofire circuit. 2005-01-04 Wouter Vermaelen * Split Display.xx files in Display.xx and Layer.xx * Moved fps code from (V99x8) Renderer to Display: "openmsx_info fps" now also works for gfx9000 * Icon fade parameters can now be configured per icon image: for example it's now possible to never fade out an active FDD LED * Fixed crash when settings.xml had empty or invalid consolebackground * Added some LED images (drawn by David) and a script to load them 2005-01-03 Wouter Vermaelen * Implemented deinterlace for V9990 * Better frameskip implementation (skip more code) * Don't draw V99x8/V9990 when V9990/V99x8 is active * Made special videosource setting: don't allow to select gfx9000 source without gfx9000 extension 2004-12-31 Wouter Vermaelen * Fixed V9990 palette read-out * Completed V9990 YUVP and YJKP modes (palette part was missing) * Integrated blueMSX' HBI-55 improvements. Thanks Daniel! 2004-12-30 Eric Boon * Fixed dOxygen warnings 2004-12-30 Wouter Vermaelen * Fixed bug in V9990 cmds (address masking bug): underwater demo works now! * Support V9990 screen enable/disable bit * Implemented cursor in V9990 Bx modes 2004-12-29 Wouter Vermaelen * Added v9990cmdtracing setting 2004-12-28 Eric Boon * Improved V9990 simple scaler Fixes 'no V9990 updates' problem in M$ Windows 2004-12-28 Wouter Vermaelen * Implemented V9990 LINE command: MSX3D works now * Implemented V9990 CMMC command * Fixed V9990 cmd engine bug that could cause blue colors in 16bpp mode 2004-12-27 Eric Boon * Introduced FinishFrameEvent for V9990 2004-12-27 Manuel Bilderbeek * Rom type naming clean up part 1. TODO: romdb.xml, class/file names 2004-12-27 Wouter Vermaelen * Implemented "openmsx_info romtype" command: only the code, no actual descriptions yet * Split rom type names in standard names and alternative names: preparation for rom type naming cleanup * support for write mask in V9990 commands: gfx9000 parts in calculus work now! * Fixed "debug break" command 2004-12-26 Eric Boon * Improved V9990 YUV and YJK modes: copied bitmap conversion from V99x8 code 2004-12-26 Wouter Vermaelen * Integrated Adriano's MegaRAM diskrom patch * Fixed bug in reading from compressed files: zipped IPS files work now * Fixed (harmless) assert that got triggered on exit when IO ports were shared * Changed invalidateCache() method: - 2nd argument is now size in bytes iso in number of cachelines this loosens the dependency on the CPU class 2004-12-25 Wouter Vermaelen * Fixed crash on missing LED icon files 2004-12-24 Wouter Vermaelen * More V9990 stuff - implemented interlace modes (no de-interlacing yet) V9BMP shoud now work completely - made palette debuggable - read palette registers hack to make calculus work - stubbed unimplemented cmds - implemented PSET cmd shifter: shifter part in calculus now shows something, colors are still wrong because of missing WriteMask(?) in commands - implemented CMMM cmd (but not verified that it actually works) - added BMXL and BMLX cmds (still have bugs though) - implemented horizontal scrolling, not per-pixel and no rolling yet, but enough for page-flips 2004-12-23 Wouter Vermaelen * Tried to make the gfx900 parts in calculus work (not yet succeeded) - implemented LMMM command - added very basic HR, VR status bit support - bugfix in LMMC command 2004-12-22 Wouter Vermaelen * simple (slow) implementation of V9990 LMMV command 2004-12-21 Wouter Vermaelen * a few V9990 fixes: - unimplemented cmds should still finish - implemented CE and TR status bits - YUV, YJK conversion formula's were wrong U and V are 6 bit signed and resulting R G B values must be clipped to 0 .. 31 autumn.g9b (YUV image) still looks wrong though 2004-12-18 Wouter Vermaelen * Cleanup/optimize V9990BitmapConvertor * Store V9990 palette as GRB iso RGB: - format in V9990 video RAM is also GRB, emulation is faster if both use the same format 2004-12-16 Wouter Vermaelen * Turn off all LEDs when MSX is powered down 2004-12-15 Wouter Vermaelen * Large cleanup: - prefer not to use the 'using' statement in header files, in implementation files it is ok - removed unnecessary #include statements * Use FilenameSetting iso StringSetting for all filenames: previously it was only possible for already existing files * Fixed UMR in WD2793 formatting routine 2004-12-13 Wouter Vermaelen * Fixed TurboR DRAM support, TRCAS works now 2004-12-12 Manuel Bilderbeek * Removed the annoying [alpha] notice. It's clear enough we're alpha... :) 2004-12-10 Wouter Vermaelen * x/y-coord and on/off-image of the LED icons can be configured * icon fade delay and duration can be configured 2004-12-09 David Heremans * replaced the ips-patch separator. It is now also the ','-sign and updated the documentation 2004-12-09 Wouter Vermaelen * Refactored setting code: - redesigned class structure: settings no longer need to be non-inheritable 2004-12-04 Wouter Vermaelen * Fixed (+ cleanup) IPS patch code 2004-12-04 Herman Oudejans * Made a quickfix for romloading in windows. Using another character for ips-patch separator would be highly recommended. ':' is used between drive and path in Windows. 2004-12-03 David Heremans * Added more code for IPS patcher, but it is not yet functional. 2004-12-02 Wouter Vermaelen * Added fading to console 2004-12-01 David Heremans * Preparations for integrated IPS patcher * Replaced the test OSD leds with something more fancy that shows the alpha channel of the png better 2004-12-01 Maarten ter Huurne * Moved coverage and z-index from LayerInfo into Layer. This also fixes an undefined memory read when Display is destructed. 2004-11-29 Maarten ter Huurne * Fixed base port of debug device. 2004-11-29 Eric Boon * Fixed broken build for non-GL systems * Improved V9990 Command engine a bit 2004-11-29 Wouter Vermaelen * Major CPU cleanup: replaced macro hack with c++ templates * Reduced size of internal mixer buffer: - should reduce sound latency a bit - sound quality seems not affected, but it needs a lot more testing. So please test!!! 2004-11-27 Manuel Bilderbeek * Implemented a fallback for the detectGeometry function in Disk for sectorbased disk images. Fixes booting of SVI-738 CP/M disk, which doesn't have a valid MSX bootsector :) 2004-11-26 Eric Boon * Add V9990 Command Engine * Implemented LMMC 2004-11-22 Wouter Vermaelen * Merge romdb.xml in user and system dir 2004-11-20 Wouter Vermaelen * Changed buffer managment in sound code: - Mixer now allocates/deallocates buffers iso the sounddevice - preparation for run-time changeable frequency/samples settings * Changes to 'frequency' and 'samples' settings takes immediate effect, no longer needed to restart openMSX * Value of 'samples' setting must always be a power of 2 2004-11-15 Wouter Vermaelen * Only send events on actual changes: * Use different image for led on/off, fade out the image if it doesn't change for some time 2004-11-14 Wouter Vermaelen * Experimental: alternative Mixer implementation - generate audio in main emulation thread - CPU-timed samples sound much better now (turbor PCM, PSG samples, ..) 2004-11-14 Wouter Vermaelen * Initial version of OSD LEDs: very minimal version, it uses /share/skins/led.png for all LEDs. Icons are still shown at fixed positions 2004-11-13 Wouter Vermaelen * cleanup dasm code: * fixed 'cmdtiming setting' * use events for LEDs: preparation for OSD leds 2004-11-10 Wouter Vermaelen * Improved WD2793 timing: added 15us delay between data read/write in sector read/write cmds 2004-11-08 Wouter Vermaelen * Enabled "cputrace" setting also in non-debug versions: - There should now be very little overhead when tracing is not enabled. Before it was 1 if per Z80 instruction. 2004-11-08 Joost Damad * updated debian/rules: fixes by Goedson Teixeira Paixao 2004-11-06 Wouter Vermaelen * a few small improvements * clear ram on power off/on 2004-11-03 Wouter Vermaelen * Moved speed and throttle setting to GlobalSettings: as cleanup and as preparation for Mixer changes * Changed float to double everywhere: On modern CPUs doubles and float are equally fast. However mixing floats and doubles in the same expression is slower because of the extra conversions. In some places we need the extra precision, so using double everywhere is the easiest (and faster). If you have large datasets floats can be faster than doubles. Not because of faster calculation, but because of less memory transfers (float 4 bytes / double 8 bytes). For openMSX this doesn't matter. 2004-11-02 Wouter Vermaelen * Removed Disk and Tape BIOS patches * Fixed cassetteplayer noise 2004-11-01 Wouter Vermaelen * small VDP fix: missing sync() on r#7 change in GRAPHIC7 mode with screen disabled 2004-10-31 Maarten ter Huurne * At end of HMMC and LMMC, TR should not be reset. * Reading and writing the COL register should reset TR. * VR status flag flips at start of left border. 2004-10-26 Eric Boon * Improved V9990 displaying: - proper PAL & NTSC imaging in Bx modes 2004-10-25 Maarten ter Huurne * Non-maskable interrupt (NMI) implementation: - was half implemented already; now it's fully implemented - moved edge detection from old nmiEdge() to new raiseNMI(): an NMI request could get lost if the NMI line was raised and lowered within 1 CPU instruction (a mostly theoretical case) - IRQHelper can raise either IRQ (maskable) or NMI - this is preparation for ColecoVision support * Moved MC6850 from "sound" directory to "serial" directory. 2004-10-23 Maarten ter Huurne * Removed slow_drain_on_reset RAM configuration parameter. All our current configurations had it set to "false". And we don't really know how the drain works nor what should be considered "slow". * Removed from the configuration XML files. 2004-10-22 Maarten ter Huurne * Added build support for NetBSD. Based on a patch submitted by xtraeme, see bug 1052115. 2004-10-19 Wouter Vermaelen * Reused MMX optimized routines in Deinterlacer 2004-10-19 Wouter Vermaelen * Fixed SETetisDongle: unused bits should be 1 iso 0 * reenabled "I/O ports" debuggable, but renamed to "ioports" * added MSXDevice::peekIO() method: - Reading from ioports debuggable no longer influences emulation state - Not yet implemented for all devices (not for V99x8, V9990 and MSXAudio). For these devices peekIO() returns 0xFF 2004-10-19 Joost Damad * sync with debian/ * updated manpage and control file * support for cleaning up CVS/ dirs for "install" and "dist"; still commented out 2004-10-18 Eric Boon * GFX900 emulation - Bx modes & belonging palettes - start of SDL Rasterizer, dummy GL rasterizer 2004-10-18 Maarten ter Huurne * Released openmsx-0.5.0. 2004-10-18 Wouter Vermaelen * forgot some "up"->"release" renames * Added MMX optimizations in Scale2x scaler 2004-10-17 Maarten ter Huurne * Separated frameStart and frameEnd for SpriteChecker. Fixes asserts when using "none" renderer. 2004-10-16 Wouter Vermaelen * Improved TC8566AF timing: - based on code from BlueMSX written by Daniel Vik - fixes corrupt gfx in "Swiss Demo" and "Gazzel" * For the renderer setting, take the value found in settings file (if any) as default value: - this fixes the "Catapult always uses SDLHi" problem * Also recognize zip files with extension ".ZIP" (uppercase) * Fixed reading 1st FAT sector in detectGeometry() routine * Fixes in Command Line Parser: - stuff like openmsx -h -machine turbor and openmsx -v -ext scc works now - code is not very clear, should be rewritten after release * Created V9990DummyRasterizer: - fixes crash when renderer "none" was selected when gfx9000 extension was plugged in * Added scripts/cycle.tcl * Added workaround for "renderer none bug": need to investigate this after further the release * Renamed "up" and "down" key modifiers to "release" and "press": names conflicted with the "up" and "down" cursor keys 2004-10-15 Wouter Vermaelen * Fixed coredump on missing arguments on the command line: e.g. "openmsx -diska" 2004-10-14 Maarten ter Huurne * Added default key binding: ALT-Enter for full screen. Windows users are familiar with this hotkey. * Fixed bug: search for TCL again on every "make probe". 2004-10-13 Maarten ter Huurne * Disabled "I/O ports" debuggable: - it reads I/O ports with side effects, which can trigger assertions - the name "I/O ports" is not suitable as a file name for "save_all" We can re-enable and then fix it after the release. 2004-10-13 Joost Damad * import of debian/ * some non-intrusive changes to the build/ dir * Contrib moved when installed to share/ * bad timing of changes :( * not softlink, but symbolic link 2004-10-12 Wouter Vermaelen * Correctly restore saved cpu freq settings: - When z80_freq_locked was saved as 'false', you had to do set z80_freq_locked true ; set z80_freq_locked false to really get the wanted behaviour. - Thanks to patatof for reporting this bug. 2004-10-11 Wouter Vermaelen * fixed scc and Panasonic_FS-CA1 extensions: mappertype tag was moved * DirAsDsk fix: - check for null ptr from localtime() - this fixes (not verified) a crash in win32 when DisAsDsk is used with files with weird dates (1913-11-16 or 2023-08-07) 2004-10-10 Maarten ter Huurne * Fixed assert in SimpleScaler when interlace is on and deinterlace is off. 2004-10-10 Herman Oudejans * Fixed getting user dir when "My Documents" is set to the root of a drive in windows. 2004-10-09 Maarten ter Huurne * test release openmsx-0.5.0-test2 (Bussum 2004) 2004-10-08 Maarten ter Huurne * Fixed crash if openMSX exits without starting emulation. * Ignore old-format settings.xml at startup instead of aborting. * Fixed help option if no settings.xml exists. 2004-10-07 Manuel Bilderbeek * Renamed save_settings_at_exit to save_settings_on_exit * Renamed FrontSwitch to FirmwareSwitch everywhere in the code 2004-10-07 Wouter Vermaelen * Fixed crash on exit in FreeBSD: - was order of destruction of global objects problem * Fixed Screenshot code in 16bpp: conversion routine to 24bpp read 2 bytes outside the array. Although it didn't actually use these bytes, it could trigger a segfault. Fixed by using the SDL convserion routines (probably faster as well). * Don't plug printer logger with an invalid log file 2004-10-06 Manuel Bilderbeek * Renamed frontswitch to firmwareswitch 2004-10-06 Wouter Vermaelen * Only allow existing machines to be selected with machine setting. * Fall back to "cbios-msx2" when the selected machine no longer exists. * Removed warning for missing settings.xml 2004-10-05 Wouter Vermaelen * saving_settings didn't work when file didn't exist yet * updated all machine configurations to new file format: thanks to mth for writing the convertor script! * test release openmsx-0.5.0-test1 * fixed sprite color bug: in planar modes the sprite color table was not read correctly (bit masking error) 2004-10-04 Wouter Vermaelen * Don't use exceptions to report unconnected drive in ready() method: - this was quite heavily used in the MSX boot sequence, and caused 100% CPU usage for several seconds. * Increased speed of type command * Moved location of tag to a more logical position * tag needs an id attribute to give a warning in case the SHA1 doesn't match * default binding for pause key must be "toggle pause" iso just "pause" * renamed tag to * Check the DOCTYPE SYSTEM part when reading in xml files 2004-10-03 Maarten ter Huurne * Updated configuration converter. 2004-10-03 Wouter Vermaelen * Fixed simple * Don't autodetect mapper type for roms in machine configs * Oops, the guessLocation routine in RomPlain still used the old config format: this broke the firmware on some machine (e.g. Panasonic FS-A1F) 2004-10-03 David Heremans * Fixed Tetris 2 special edition dongle 2004-10-02 Wouter Vermaelen * Force PSG portA to be programmed as input port: some programs (e.g. Match Maniac) wrongly program portA as output, but on most(?) MSX machines this works normally (e.g NMS8250) * Fixed initial value of mode settings * correctly give "write protected" err msg on read only disk: without the head loaded delay our WD2793 detected too fast that the disk was write protected (too fast for the disk rom software) * assertion in Clock was a bit too strict: very low clock frequencies (~1Hz) are not allowed (because we use a 32bit variable iso 64 bit for speed). But the assertion was already triggered by frequencies around 100Hz. Renshaturbo circuit uses such low frequencies. 2004-10-01 Wouter Vermaelen * Improved disk geometry detection heuristics * Fixed save_settings command: - when settings were loaded from system wide settings.xml file, save them to user settings.xml file - when settings were loaded from a user specified settings file, save them to that file * fixed quoting in tab-completion: the chars [ ] $ must be quoted in TCL * use xmlFree() iso free() to release memory from libxml 2004-09-30 Wouter Vermaelen * Removed the MSXConfig::loadConfig method that was resposible for the media settings bug. It's no longer used now. * Fixed type commando: sometimes the first chr was missed * Fixed "-setting" command line option: Settings were loaded wrong when the -setting option was given. Was a problem in the order of creation of settings (no settings may be created before the settings.xml is loaded). Current code is a bit fragile in this respect. Refactor it after the release. 2004-09-29 Wouter Vermaelen * Removed settings.xml from CVS * media settings (diska, diskb, cassette) were wrongly saved: - once a "save_settings" command was executed with a disk inserted from the commandline this disk was always used, even if in the next run a different disk was specified. - should media settings be saved? Current (fixed) implementation does not save them to not break catapult. But should they be saved in the future? 2004-09-28 Wouter Vermaelen * Moved default keybindings from init.tcl to HotKey code * Updated commands.txt * Use built-in default values for KeyJoystick in case the config section is missing in the settings file 2004-09-27 Wouter Vermaelen * Use #ifdef iso if around asm routines * Added normal-MMX (non-ext-MMX) routines for every ext-MMX routine * Made a setting for "user directories" * Simple scaler 16bpp scanline optimization: - added ext-MMX routine, improves speed from 69s to 51s 2004-09-26 Arnold Metselaar * Let Keyboard and KeyJoystick release all keys when the console becomes active. 2004-09-26 Wouter Vermaelen * Don't print "couldn't find file" warnings on startup 2004-09-26 Maarten ter Huurne * At end of HMMC and LMMC, CE is reset immediately, but TR is reset the next time S#2 is read. Fixes Andorogynus. Thanks to the anonymous poster on our forum who pinpointed the change which made this bug surface first. Even though reverting that change was not a solution, it did point to the HMMC command timing being the problem. 2004-09-24 Maarten ter Huurne * Fixed assert in Mixer on unmute if initial state was muted. * Load settings file specified with "-setting" from UserFileContext, but load the files it refers to using SystemFileContext. Fixes console look when openMSX is started with "-setting" option. 2004-09-24 Wouter Vermaelen * Take default settings value when saved value is no longer valid * Integrated blur scaler and simple scaler: 2004-09-23 Wouter Vermaelen * Implemented TC8566AF format command: based on code from BlueMSX written by Daniel Vik * Don't save soundchip mode setting: it gave conflicts with the mode setting in hardwareconfig.xml 2004-09-23 Maarten ter Huurne * Initialise I register to 0, instead of 0xFF. Fixes Pennant Race bug. Thanks to Daniel Vik for finding the cause! 2004-09-21 Wouter Vermaelen * Added "save_settings_at_exit" setting 2004-09-20 Wouter Vermaelen * Initialize the msx sound devices even if there is no openMSX sound: - solves UMR (and possibly crashes) in case openMSX is started with "-nosound" or for some reason sound is not available 2004-09-19 Maarten ter Huurne * Cleanup of layer system, continued: - put common code in new VideoLayer class - implemented "videosystem" setting - implemented SDLV9990Rasterizer, which shows a test pattern If you start openMSX with "-ext gfx9000" and on the console type "set videosystem gfx9000", you will see the test pattern (SDLHi/Lo). There are some bugs left, but I had to commit to keep track of the many changes. I'll try to fix the bugs as soon as I can. * Do not save "power" and "pause" settings. Fixes starting from Catapult. * Withhold blocked events from other NATIVE listeners as well. Fixes "set console off". * Delay reInit when power is turned off. Fixes "set power off" in R800 mode. * Initialise lineContent in SDLRasterizer's constructor. Fixes asserts when switching to SDLHi when paused. 2004-09-18 Wouter Vermaelen * Rewrote saving of settings code: - All settings are now saved by default, only exception is "console" setting. But there are probably more that don't need to be saved. - settings.xml fileformat has changed again. All settings are now saved in a uniform way. - Code is a lot simpler now * Default rom pools: added /share/systemroms and /share/systemroms as default rom pool locations. The use can still add more pools. * Fixed crash on "save_settings" when there was no settings.xml file loaded 2004-09-18 Maarten ter Huurne * Cleaned up layer system: - "alpha" was not really alpha, renamed to "coverage" - explicit Z value instead of toFront/toBack - preparation for V9990 layer The cleanup is not finished yet. 2004-09-18 Reikan * Converted the macro __WIN32__ to _WIN32. Please use _WIN32 instead of __WIN32__ now. _WIN32 is more popular in Win32 compilers. Although we require gcc3 for now, it would be safer in future. 2004-09-15 Maarten ter Huurne * Added new type of event listener (DETACHED). * Fixed timing bug in VDP command engine: replaced "clock.advance" by "clock.reset" when starting a command. Start time of a new command is not ahead of end time of last pixel of previous command if previous command was not finished yet. Fixes assert in The Ant demo (found by Jorito). 2004-09-15 David Heremans * Added the joystick protection of 'Tetris Special Edition II' 2004-09-14 Maarten ter Huurne * Cleaned up renderer switching in the new layered display system. 2004-09-14 Wouter Vermaelen * Don't assert when there are two Moonsounds (not really usefull, but in a real MSX it's also possible to have multiple Moonsounds) * Changed settings format for volume/mute settings: * Added iomap command to check what devices is on which I/O port 2004-09-11 Wouter Vermaelen * Implemented RTC mode as a setting, is now saveable * "set " now returns the actual new value of the setting (e.g. "set gamma -1" returns 0.1 iso -1) 2004-09-10 Wouter Vermaelen * default machine is now configurable via a setting. Of course this only has effect when openMSX is restarted. Although in the future we may switch machine between a power off/on cycle. 2004-09-09 Wouter Vermaelen * Z80 updates: - fixed P-flag in OUTI and INI instructions - calculate DAA table * Blur scaler: - small optimization in normal-MMX code (non-extended-MMX) - fixed bug when scanline=0 (recent optimization requires scanlineAlpha <= 255, not <= 256) 2004-09-08 Wouter Vermaelen * Created "frequency" and "samples" setting: Changing this setting only has effect the next time openMSX is started (will be changed later, but it requires some refactoring in the sounddevices). * Implemented "help set []" command: Since the switch to TCL it was no longer possible to query the setting descriptions. Fixed now. 2004-09-06 Wouter Vermaelen * Blur scaler speed-up: use ext-mmx instructions "pavgb", "pshufw", "pmulhuw" * Fixed bug in Z80 OTIR like instructions (bug found by mth): fixes equalizer in FDD1 2004-09-05 Wouter Vermaelen * Converted more extensions to new config format * Allow saving of settings with 'non-xml-tag' characters 2004-09-04 Wouter Vermaelen * Allow machines (extensions) descriptions in both /machine.xml /machine/hardwareconfig.xml * Converted a few more extensions to new config format 2004-09-02 Wouter Vermaelen * renames in settings.xml: "renderer" section --> "video" "mixer" section --> "sound" * volume and mode (mono, stereo) settings are saved now 2004-09-01 Wouter Vermaelen * Refactored settings: - creating saveable settings is easier now - settings.xml format changed slightly: tag name is now the same as the name of the setting. So if you're not using the default settings.xml file you need to make at least changes --> --> --> 2004-08-31 Wouter Vermaelen * Renderer settings can be saved now 2004-08-30 Wouter Vermaelen * Simplified console code * All console settings are saved now 2004-08-29 Wouter Vermaelen * Fixed Uncaught exception on "openmsx -v" 2004-08-28 Maarten ter Huurne * Fixed bug when palette index 0 is written and transparency is enabled. This bug was visible as gray rectangles in the Gazzel intro. 2004-08-28 Wouter Vermaelen * Added 16bpp MMX simple scaler routines * Proof-of-concept: saving of settings (settings.xml) - added a new command "save_settings". There is no autosave at exit yet. - for the moment only very few settings are actually saved. Only consolecolums, consolerows, consoleplacement 2004-08-27 Manuel Bilderbeek * Added initial version of the openMSX FAQ: not finished at all, please edit! 2004-08-26 Wouter Vermaelen * BlurScaler 16bpp improvements: - use look up table instead of (complex) computations. Is almost twice as fast. Also used 10-bit precision iso 8-bit, so less rounding errors. 2004-08-25 Wouter Vermaelen * Don't copy HostCPU * Improved MMX code. I measured between 1% and 2% speedup: - a block of memory reads followed by a block of memory writes is faster than interleaved read and writes. Probably because write combining works better this way (larger bursts on the bus). Loop unrolling made this grouping possible. - Rearranging instructions (especially mul instrcutions) also gave a nice speedup. This was also possible because of loop unrolling. 2004-08-24 Wouter Vermaelen * Changed blur scaler for 16bpp: - do calculations in 24bpp mode to avoid visible rounding errors (not tested) 2004-08-24 Maarten ter Huurne * Moved screenshot code from VDP to Display. * Added extended-MMX versions of non-scanline scaling in 256-wide modes and both scanline and non-scanline scaling in 512-wide modes. Still only 32bpp. * Sound device cleanups: - Removed "user mute" which was unused. - Renamed "internal mute" to just "mute". - Mixer checks isMuted() instead of updateBuffer returning NULL. - Make sure reset() is called before registerSound(). Right now this doesn't really matter, but it is needed once we allow runtime insertion of extensions. * Refactored PSG: - Code in C++ style and easier to read. - Slight improvements in accuracy, probably inaudible though. - Some optimisations, but not enough profiling done yet to see whether they really matter. 2004-08-22 Wouter Vermaelen * 'set consolebackground ""' now removes the background image * Don't use MSX pause LED for openMSX pause status, instead send a (new) pause event. 2004-08-21 Wouter Vermaelen * Fixed assertion on "openmsx ok.dsk wrong.dsk" * Optimization: don't use TurboRCPUInterface on non-turboR machines 2004-08-19 Wouter Vermaelen * Fixed XML configuration stuff for IDE 2004-08-18 Wouter Vermaelen * Added "-nosound" command line option: - main raison to add it was faster profiling. Audio thread takes 50% of the time when running (far from realtime) under cachegrind, even though the sound was muted in openMSX. * Initialize SDL AUDIO subsystem: - AUDIO subsystem was never initialized. Apparently this isn't needed for Linux / Windows. But some platforms probably do need it. * Fixed "after time" and "after idle" console commands * CPU cleanup: removed historical cruft * Merged CPUInterface and MSXCPUInterface classes: - get rid of virtual method calls, and (possibly) inline them - code was written like this to be able to use the same CPU code also for the CPU in the MidiSaurus cartridge. If we still want to implement this, we should templatize the CPU class with on the CPUInterface instead. 2004-08-17 Wouter Vermaelen * Made tiny optimization in DynamicClock, however this gave a speedup of about 1.5% because this class is _heavily_ used in the CPU code: * Increased minimum CPU clock freq to 1MHz: - the new DynamicClock implementation can't handle very slow clock rates (slow meaning ~1Hz). But even before this change a clock of 100kHz didn't work correctly, I'm not sure why. 2004-08-15 Wouter Vermaelen * Added MMX optimizations for blur scaler 2004-08-14 Wouter Vermaelen * Added new 'blur' scaler: - this scaler implements the same blur effects as the SDLGL renderer already does (but then in software) - only tested with 32bpp, can someone please test 16bpp? - can still be heavily optimized using MMX assembly 2004-08-13 Wouter Vermaelen * Extended Scale2x scaler for hi-res modes (screen 6 and 7) 2004-08-12 Wouter Vermaelen * Extended hq2x scaler for hi-res modes (screen 6 and 7) 2004-08-09 Wouter Vermaelen * Removed CoRoutine stuff from CVS, it's no longer used 2004-08-07 Wouter Vermaelen * Allow multiple times the same extension (make id's unique) * ROM title (from romdb) is again visible in slotmap 2004-08-06 Maarten ter Huurne * Avoid syncs when VDP state changes when display is blanked and the border colour is not influenced by the state change. This allows the scaler to draw border lines more efficiently. * New feature in build system: version executables. See VERSION_EXEC in build/custom.mk (off by default). 2004-08-03 Maarten ter Huurne * Fixed ROMBAS mapper for 8K ROMs. Eat Blue! now works if you load it with "eatblue.rom,rombas". * Improved ROMBAS detection. Eat Blue! now works without forced mapper type as well. 2004-08-02 Wouter Vermaelen * Fixed some bugs I introduced yesterday * Another bug fix: HALT instruction was broken 2004-08-01 Maarten ter Huurne * Initial build system support for x86_64. No optimised compile flags yet. MMX code is disabled on x86_64 for now. 2004-08-01 Wouter Vermaelen * Removed CoRoutines again. Instead reversed the calling-sequence between MSXCPU and Scheduler: Previously the Scheduler scheduled both CPU and other stuff. Because of this the CPU needed to be able to pass control back to the Scheduler in the middle of a Z80 instruction. In the past we solved this by either: - Splitting all Z80 instructions in atomic parts and made the code resumable at a sub-instruction level --> very complex CPU code - call the scheduler (recursivly) from within the CPU code --> too complex CPU <-> Scheduler interaction, we had too many bugs related to this - Use CoRoutines to pause/resume the CPU code --> was only an experiment, stopped the experiment very early because I think the current solution is much simpler In the current solution we call the Scheduler from within the CPU (iso the other way around). Pausing/resume CPU code is now done by a simple function call. New code still needs cleanups, but I wanted to check it in early beacuse the CoRoutine stuff didn't compile yet on win32 (although it was easy to fix). * Use "string::size_type" iso "unsigned" in STL string manipulations: on x86_64 sizeof(string::size_type) is 8 while sizeof(unsigned) is only 4 2004-07-30 Maarten ter Huurne * Added CPU detection. Read the Doxygen comments of HostCPU for details. For now, debug prints are enabled, please check if dected capabilities match what you would expect for your CPU. * Added extended-MMX version of SimpleScaler (only 256-wide 32bpp). On my machine, it can scale 50% more pixels per second. * Added extended-MMX version of Scaler::scaleBlank. * Added extended-MMX version of SimpleScaler::scaleBlank. * Got rid of named operants: they're not widely supported. GCC 3.0 can't handle them according to the docs. GCC 3.2 can handle them according to the docs, but crashes (on mingw32). ICC cannot handle them. * Made pixel accuracy default. We usually have the most realistic option as default. In the past, pixel accuracy was too slow in many cases, especially when VRAM I/O was causing a lot of syncs. Those were eliminated a while ago for the most commonly used display modes. If you encounter a program running significantly slower in pixel accuracy than in line accuracy, please report it as a bug. 2004-07-26 Wouter Vermaelen * Experimental: use CoRoutine to simplfy and later optimize CPU / Scheduler interaction. Current patch is very minimal: it just adds CoRoutine without any optimizations or cleanups. 2004-07-25 Maarten ter Huurne * Removed support for 8bpp. It never worked anyway. 2004-07-24 Maarten ter Huurne * Split SDLRasterizer off from SDLRenderer: - SDLRenderer deals with EmuTime, is colour depth and zoom independent - SDLRasterizer deals with pixels, does not know EmuTime * Split GLRasterizer off from SDLGLRenderer. * Merged remainders of SDLRenderer and SDLGLRenderer into PixelRenderer. 2004-07-23 Maarten ter Huurne * Removed all SDL specific code from SDLGLRenderer. Maybe I'll rename it to GLRenderer in the future. Preparation for GLX renderer. * Init and shutdown in VideoSystem subclass, instead of init in RendererFactory subclass and shutdown in Renderer subclass. Applied to SDLGL, other systems to follow. * Applied to SDLHi/Lo and Dummy as well. 2004-07-22 Maarten ter Huurne * Changed scanline drawing from a post-processing operation to an integrated part of the "simple" scaler. As a result, drawing with scanlines is now equally fast as drawing without scanlines. Probably this means small computations can be done in cycles that would otherwise be wasted waiting for memory access. 2004-07-21 Maarten ter Huurne * Introduced display which manages multiple layers. Currently, the following layers exist: - background (TV snow) - renderer - console In the future, a second renderer will be added (for GFX9000). Also, OSD features are possible (such as status icons). The code has some rough edges, but it works. * Along the way, fixed console cursor blinking during pause. 2004-07-21 Wouter Vermaelen * a few small fixes 2004-07-19 Wouter Vermaelen * merged MSXDevice, MSXIODevice and MSXMemDevice in one class MSXDevice: - virtual inheritance can be removed because of this 2004-07-18 Arnold Metselaar * added Unicode.{cc,hh} with minimalistic conversion of UTF-8 to 8-bit Ascii * Keyboard.cc: Fixes to make the type command work properly - utf-8 -> ascii conversion in Keyboard::KeyKeyInserter::type(), - fixed bug in Keyboard::pressAscii() (index to asciiTab), and - corrected asciiTab[] 2004-07-15 Manuel Bilderbeek * Added tag with some child tags to XML. - should be added to other hardwareconfig files later as well (is mandatory) - optional for extensions? - there's no class associated with it now; it's only used to put info in the window border for now - child tags "manufacturer", "code" and "type" are now mandatory 2004-07-14 Wouter Vermaelen * Fixed MoonSound bug: When DL=0 go immediately to sustain phase. Fixes 'timpani' instrument for example. 2004-07-13 Maarten ter Huurne * Fixed nullpointer dereference when ABORT command is executed in "cmdtiming broken" mode. 2004-07-13 Wouter Vermaelen * Simplified SHA1 interface + cleanups 2004-07-12 Wouter Vermaelen * Preparartion for insert remove at run time: unregister memory / IO ports 2004-07-11 Wouter Vermaelen * Config file change: FDC type is no longer a sub type * Fixed RenShaTurbo * Config file change: grouped and tags in a new tag 2004-07-10 Maarten ter Huurne * Separated CPU and OS in build system. 2004-07-10 Wouter Vermaelen * Fixed loading of extensions * Fixed some bugs in slot structure config loading * It's now possible to insert a slotexpander extension 2004-07-04 Wouter Vermaelen * Fixed exception-safety problem in CassettePort constructor 2004-07-04 Manuel Bilderbeek * Added simple DC filter for Wave cassette images (subtract average) .wav files from MicroWAVer should work now. Please test this some more with other .wav images (e.g. from real cassettes) 2004-07-01 Wouter Vermaelen * Another config file format change: - use 'primary' and 'secondary' tags to specify slot structure - this proposal is still being discussed (especially for extensions), so this may change again 2004-06-28 Wouter Vermaelen * Fixed assertion in ClockPin 2004-06-27 Wouter Vermaelen * Get rid of ordering requirement in turbor hardwareconfig.xml 2004-06-26 Maarten ter Huurne * Replaced EmuTimeFreq by Clock and DynamicEmuTime by DynamicClock: - Fixes accuracy bug where fractions of ticks were discarded, causing problems with high-frequency reads of low-frequency timers. Thanks to Daniel Vik for reporting this. - Reduced the number of operations, especially operators. - Added Doxygen comments for the changed classes. * Improved MoonSound BUSY flag timing (not completely correct yet). 2004-06-24 Wouter Vermaelen * hardwareconfig.xml format change: replaced foo.. bar.. with .. .. 2004-06-23 Wouter Vermaelen * FDC config format changed: indicate number of connected disk drives iso drivenames 2004-06-21 Wouter Vermaelen * hardwareconfig.xml configuration file changes: - replaced .. with .. 2004-06-21 Manuel Bilderbeek * Fixed mix level (volume) bug in Moonsound, thanks to Albert and Wouter 2004-06-20 Wouter Vermaelen * bug fix: first destroy renderer before creating a new one, bug was introduced when using auto_ptr * bug fix: YMF278 is a stereo device 2004-06-19 Wouter Vermaelen * Fixed ROM loading bug when no SHA1 match was found and no filename was specified * A bit more robust config file parsing * Config file format change: - put roms in new hierarchical tag * settings.xml configuration file changes: - replaced .. with .. 2004-06-18 Wouter Vermaelen * Compiled with -Wall -W and fixed all warnings 2004-06-18 Manuel Bilderbeek * Fixed Floating Point Exception in Y8950Adpcm, which was triggered by 4Trax Songbook #1. Thanks to Eric for finding it and Wouter for providing the fix :) 2004-06-17 Wouter Vermaelen * Large configuration file code refactoring: - get rid of Config class, use directly XMLElement - no changes in config files * Use auto_ptr to manage ownership: - ownership is better documented - safer in case of exceptions 2004-06-15 Wouter Vermaelen * Config file releated cleanups (no changes in config files) 2004-06-14 Wouter Vermaelen * Added Date class to convert dates to and from string: there are library fucntions that do the same (strftime, strptime) but strptime is not available on win32 * RomPool cleanups / fixes / enhancements: it's now possible to list multiple pools in the settings file 2004-06-13 Wouter Vermaelen * Implemented RomPool: still needs heavy testing and cleanups 2004-06-07 Manuel Bilderbeek * Rerenamed "MSX-AUDIO MIDI" to "Music Module MIDI" 2004-06-08 Wouter Vermaelen * removed IO port registration for Mixrosol FDCs from the code, instead it must be specified in the config file (TODO) * Explicitly indicate ownership transfer in Device factories 2004-06-06 Manuel Bilderbeek * Moved mode setting of the RTC to settings.xml (converter still needs to be updated) 2004-06-03 Manuel Bilderbeek * More config file renames: - Audio -> MSX-AUDIO - Audio-Midi -> MSX-AUDIO MIDI - Music -> MSX-MUSIC - MSX-Midi -> MSX-MIDI - SCCPlusCart -> SCC+ The converter has been adjusted as well. * Fixed a forgotten rename of Rom to ROM in the MSXRomCLI. * Added specific extensions for the Konami Sound Cartridges (Snatcher and SD-Snatcher) 2004-06-04 David Heremans * Fixed the problem with filesize zero in the DirAsDisk feature 2004-06-03 Maarten ter Huurne * Simplified code which stores and draws previous frame in SDLGL. 2004-06-03 Wouter Vermaelen * Fixed creation of internal datastructures to match the new XML format * Removed deprecated device PanasonicRom 2004-06-03 Manuel Bilderbeek * More config file changes: - renamed old parameter brokenFDCread to broken_fdc_read (needs update of the converter, please fix this for me) - renamed Rom to ROM, PanasonicRom to PanasonicROM and PanasonicRam to PanasonicRAM and updated the converter for these changes 2004-06-01 Maarten ter Huurne * Wrote a converter script that updates old configurations to the new format: share/scripts/convert_hardwareconfig.py 2004-06-01 Manuel Bilderbeek * Corrected ASCII table for 'type' command for international layout Thanks to Albert Beevendorp for doing the hard work! * More config file changes: changed device name of FM-PAC to FMPAC 2004-06-01 Wouter Vermaelen * More config file changes: - removed constructs - removed support for , config files are incompatible anyway * Fixed assertion on "type {}" 2004-05-30 Wouter Vermaelen * Better names for ROM debuggables. Still include the (unique) device id to avoid assertion when the same ROM is inserted twice. * Added optional "sha1" parameter to rom devices. For the moment only used to print a warning when given sha1sum doesn't match with real sha1sum. * First step to new xml format: bar can now be written as bar the first format is still supported, but will be removed soon. In the FDC device the parameter "type" is renamed to "fdc_type" becuase of a name conflict with the device type. * Removed support for autocommands, they were deprecated in 0.3.4 * Added Vincent van Dam to AUTHORS: cas to wav conversion code is based on his cas2wav too * Moved IO port registration from DeviceFactor to hardwareconfig.xml: This is an incompatible xml format change! I only updated Philips_NMS_8250 and Panasonic_FS-A1GT. I hope someone else will update the other machines and extensions. * Also got rid of ... constructs in settings.xml * Removed duplicate code for case insensitive string comparisons 2004-05-30 Manuel Bilderbeek * Added channel mode 'off' to mute individual sound devices 2004-05-30 Wouter Vermaelen * In one executable only one interpreter is used (TCL or other), no need to have a virtual base class. 2004-05-29 Manuel Bilderbeek * Cleaned up names of the debuggables 2004-05-29 Wouter Vermaelen * Made all (S)RAM debuggable, created a base class for RAM * use auto_ptr to indicate ownership transfers * use stl bitset instead of an array of booleans * don't use exception specifications, they do more harm than good * don't use global objects, they are constructed before main() and thus we can't catch execptions yet * Command speed up (mainly for TCL scripts): Take CommandArguments objects as parameters instead of strings. This avoids int -> string -> int like conversions. This optimization was already done for the command result, now also for the command arguments. 2004-05-28 Maarten ter Huurne * Released openmsx-0.4.0. 2004-05-26 Wouter Vermaelen * Renamed "keylayoutbit" setting in PSG to "keyboardlayout". Also instead of the values true/false it now takes "JIS"/"ANSI" 2004-05-23 Maarten ter Huurne * Fixed glitches in SDLHi when deinterlacing. * Fixed pause in SDLGL in monochrome character modes (SCREEN0.40/0.80/1). * Added several missing documentation files to "make install". * Some other build system improvements. 2004-05-21 Wouter Vermaelen * RS232 interface in Sony_HB-G900P has 2kb RAM 2004-05-20 Manuel Bilderbeek * Added about 14 new machines 2004-05-17 Wouter Vermaelen * Print an error for duplicate drive names (iso assert) 2004-05-15 Manuel Bilderbeek * Implemented scrolling in console by a whole page, using SHIFT+PAGEUP and SHIFT+PAGEDOWN 2004-05-15 Wouter Vermaelen * really fixed TCL_STDOUT redirection * fixed passing of binary data in console command: was needed for "debug write_block" command * updated "save_debuggable.tcl" script with "load_debuggable" and "load_all" commands: This is no savestate script! Most of the times it won't work! 2004-05-14 Wouter Vermaelen * Fixed redirecting the TCL_STDOUT channel: this fixes Adriano's bug (saving to file in from TCL script) 2004-05-09 Manuel Bilderbeek * Fixed double SRAM names for Matsushita devices 2004-05-06 Wouter Vermaelen * If init.tcl file in system dir is missing try init.tcl in user dir: The default init.tcl in the system dir still source's the user init.tcl file. 2004-05-05 Maarten ter Huurne * Various small improvements to build system, including: - install dir is easier to customise (build/custom.mk) - fixes for compiling on Mac OS X (thanks to Jalu for testing) 2004-05-05 Wouter Vermaelen * More V9990 work 2004-05-02 Maarten ter Huurne * Different final probe message depending on probe results. * Refuse to build if dependencies for core component are not met. * Support for components: a single label (like COMPONENT_GL) controls whether a component is built or not. (in preparation for forced disabling of components) 2004-05-01 Wouter Vermaelen * Implemented a faster DC removal filter, thanks to dvik * Implemeted V9990 palette reading/writing * Unified Ascii8-8, Koei-8, Koei-32 and Wizardry mapper types, also fixed some mapping details 2004-05-01 Herman Oudejans * fixed [926293] Win32: specifying driveletter without path doesn't work. 2004-04-30 Wouter Vermaelen * Use 64-bit type to store realtime: 32-bit microsecond counter overflows after about 70 minutes. We also saw openmsx crashes after 70 minutes... 2004-04-30 Maarten ter Huurne * Fixed sprites in overscan. * Corrected clipping for scalers. And managed to slip in some optimisation as well. 2004-04-29 Maarten ter Huurne * Optimized top and bottom border drawing in SDLHi. 2004-04-28 Maarten ter Huurne * Integrated probe fixes from Reikan. Probe should work on FreeBSD and Win32 now. 2004-04-28 Wouter Vermaelen * Implemented VRAM reading/writing for V9990 2004-04-28 Herman Oudejans * Implemented 32x32 icon. In win32 the icon MUST be 32x32 and Linux doesn't care about size, so 32x32 for all OS'es (thx to Reikan). 2004-04-27 Maarten ter Huurne * Fixed per-line administration of scale factor. In Psycho World intro, bottom text was scaled x2 instead of x1 when openMSX was paused. ANMA's Relax had a similar problem. 2004-04-27 Wouter Vermaelen * Fixed Koei mapper: sram is not visible in 0x6000-0x7FFF * Fixed RealTime: still sync music for high frameskips, but properly handle syncpoints * Fixed SRAM saving: filename had a double extension (file.SRAM.SRAM) 2004-04-26 Eric Boon * Add Debuggables to V9990 2004-04-26 Wouter Vermaelen * Implemented Koei-8 and Koei-32 mapper types: based on BlueMSX v1.4.0 * "openmsx_info fps" now return 0 in when "renderer none" is active * Fixed bug in min/max frameskip handling (happened for example when minframeskip == maxframeskip) * Fixed serious performance bug: Sometimes a RealTime syncpoint was not removed from the scheduler queue. So it got fuller and fuller and openmsx became slower and slower. 2004-04-25 Eric Boon * Introduction of Gfx9000/V9990 2004-04-25 Manuel Bilderbeek * Fixed Philips NMS 1205 extension (MSX Audio MIDI was missing) * Updated manual for upcoming release (please check!) 2004-04-24 Maarten ter Huurne * Build uses same compile and link flags as probe. * Integrated part of the mingw32 probe fixes from Reikan. * Implemented probe overrides for FreeBSD (untested). 2004-04-22 Maarten ter Huurne * Enhanced system probe: now it also scans for mandatory headers and libs. It performs all checks and then reports what it found and what is missing. 2004-04-21 Wouter Vermaelen * Fixed [ 933628 ] assertion with accuracy=screen in "puyo puyo" * Fixed OR-ing of unaligned sprites 2004-04-20 Herman Oudejans * Suppressed warning in windows (warning: couldn't find file: ""). * Fixed 'no window when return from full screen' in SDLHi/Lo (windows NT4 only). 2004-04-19 Maarten ter Huurne * Fixed small bug: I forgot to replace "#ifndef X_DISPLAY_MISSING" by "#ifdef HAVE_X11" in RendererFactory class. 2004-04-19 Herman Oudejans * Fixed 'no window when return from full screen' in SDLGL (windows only). 2004-04-18 Maarten ter Huurne * Dropped autoconf completely. This also means autogen.sh is no longer necessary. Thanks to Herman and Manuel for testing parts of the new system. The new probing system only looks for optional parts, not for mandatory ones (such as SDL). Also, there is no option to disable optional parts yet. Possibly some bugs have slipped the testing, please alert me if you find any. 2004-04-15 Maarten ter Huurne * Build system discovers TCL compile and link flags using "tcl-search.sh" instead of autoconf. 2004-04-12 Maarten ter Huurne * Changed BIGENDIAN implementation: determined by looking at CPU, instead of running a test program. (preparation for cross compiles) * New version format: "openMSX x.y.z" for releases "openMSX x.y.z-devN" for development builds, N = ChangeLog revision * Pass information from build system to sources using C++ types such as bool and std::string, instead of macros. You should re-run "autogen.sh" after updating. 2004-04-10 Wouter Vermaelen * Don't allow one Pluggable to be plugged in two Connectors at once * Also send update event when media is ejected (diska eject) * Experimental: delay input events (currently by a fixed time) to avoid key misses 2004-04-09 Maarten ter Huurne * Added support for compilation on FreeBSD 5. Thanks to Jorito for providing a working environment to test on. 2004-04-07 Maarten ter Huurne * Several changes to the build system; the main visible effect is that all generated files are now in "derived". You should re-run "autogen.sh" after updating. 2004-04-07 Wouter Vermaelen * Generate update event for media change * Implemeneted "openmsx_info connectionclass" info topic: - "openmsx_info connectionclass" returns all connectionclasses - "openmsx_info connectionclass [pluggable / connection]" shows the class to which this 'thing' belongs * Copied "oversleep compensation" from old realtime sync algorithm to new algorithm: - this helps to keep openMSX running at a reasonable speed when it has to compete with other CPU hungry processes 2004-04-06 Reikan * Added link libs to platform-x86-mingw32.mk. Thanks to Honda. * Reverted AC_CANONICAL_SYSTEM in configure.ac. We need it to let automake prepare config.guess and config.sub. * Removed check for tcl8.3 because makefiles don't support anyway. * Added some tiny checks for libs in configure based on suggestion from Manuel. 2004-04-06 Wouter Vermaelen * Simplified RealTime class: added Timer class (had to rename existing Timer class to EmuTimer) removed RTC synchronziation (linux) because it was complicated and didn't really improve anything * Force a realtime sync 8 times per second (real time): - now music stays more or less ok with large frameskips (set minframeskip 100) and very low speed settings (set speed 10) - disadvantage: auto frame skip is much less effective for value above 6 or 7 (set maxframeskip 7) 2004-04-05 Maarten ter Huurne * Minimized usage of auto*: - only run configure checks of which we actually use the results - use "sizeof(bool)" instead of "SIZEOF_BOOL": a lot simpler and the generated code is equally efficient - dropped libtool Thanks to Reikan for his patch from which I copied at lot. 2004-04-03 Wouter Vermaelen * Changed frameskip setting: there is no explicit frameskip "auto" anymore, instead the framskip setting indicated the maximum allowed number of frameskip. * Don't skip frames on small CPU usage spikes * Renamed "frameskip" setting to "maxframeskip" * Added new "minframeskip" setting 2004-04-02 Wouter Vermaelen * Implemented different frameskip and real time sync algorithm * Fixed "[ 915747 ] sprites are not visible on display line 0" * Skipped frames are now reflected in fps statistics * Made "frameskip auto" the default 2004-03-29 Maarten ter Huurne * Made new ("alternative") build system default: GNUmakefile is picked up by GNU Make before Makefile is. 2004-03-28 Wouter Vermaelen * Added generic info command to query the possible values for EnumSettings. 2004-03-28 Manuel Bilderbeek * Added info command for 'accuracy' setting 2004-03-27 Herman Oudejans * update events are now disabled by default. 2004-03-26 Wouter Vermaelen * Implemented simple subsription mechanism for update events: - openmsx command "update " - for now all update events are still enabled by default, but when catapult is adjusted I'd like to disable them by default 2004-03-25 Wouter Vermaelen * Only the init.tcl file from the system directory is now executed by openmsx. However the default init.tcl file does source a init.tcl file in the user directory !!! you probably need to redo a "make install" because of this !!! * The default (system) init.tcl now automatically sources all .tcl files in the user and system scripts/ directory (it prefers the script in the user dir if it exists in both) 2004-03-23 Wouter Vermaelen * Fixed unexpected exception problem in combination with NPTL threading library (Debian unstable) * Generated update events on setting changes: - maybe we should supress these when not in 'control' mode? Ideally we make a control these via a publisher/subsriber pattern. * Generate update evvents for plug/unplug 2004-03-22 Wouter Vermaelen * Don't instantiate CassettePlayer when there is no CassettePort 2004-03-20 Wouter Vermaelen * Implemented a very basic fps indicator * Don't send fps as 'update' events, but make it queryable * Fixed overflow problem in SoundDevice code 2004-03-17 Maarten ter Huurne * Do not copy captured GL frame back the the frame buffer if the same image is still there. Thanks to Herman for finding this problem. 2004-03-17 Wouter Vermaelen * Fixed debugger assertion: - scheduler did not notice the CPU stopped early because of breakpoint - the whole CPU <-> Scheduler interaction is a bit of a mess, need to clean this up someday 2004-03-16 Wouter Vermaelen * Screenshot 'feedback' was only given for non-GL renderer, moved code to a common (gl / non-gl) code path. 2004-03-16 Herman Oudejans * Fixed saving persistent data in windows (SRAM, cmos, history) 2004-03-15 Wouter Vermaelen * Proper XML escaping for results from comm protocol 2004-03-14 Wouter Vermaelen * Reduce memory usage in Y8950Adpcm: - found by massif (part of valgrind) * Fixed HBI-55: address 0 is read only and contains 0x53 * Changed (un)bind command: - a key can now only be bound to one command, this solves the "toggle + toggle = nop" problem - if you still want to bind multiple things to one key you can define a proc or bind something like "foo ; bar" 2004-03-13 Wouter Vermaelen * Another config refactoring: - split MSXConfig in hardware and settings part - merge Config and Device class * Use ugly macro iso template in NonInheritable class: - hopefully this works on all compilers, if not (or if it's too ugly) we can just remove this class * Compilation fixes for icc * Extended CPU debuggable: - IM can now be read at position 26 - IFF1 and IFF2 can be read as bit 0 and 1 on position 27 * Made SCC debuggable 2004-03-12 Wouter Vermaelen * Implemented "type" command: - replaces KeyEventInserter - TODO documentation correct ascii->keymatrix table better name for command? 2004-03-11 Wouter Vermaelen * More config cleanups: - don't build and parse xml in memory anymore, directly build correct datastructure - temp broke KeyEventInserter, will be replaced by a console command 2004-03-09 Wouter Vermaelen * More config cleanups 2004-03-05 Wouter Vermaelen * Made Y8950 (MSX-AUDIO) debuggable * Deleted debug view related files: they were not used anymore and doxygen didn't like them * Made MoonSound debuggable * Made MSX-AUDIO sample RAM debuggable 2004-03-04 Wouter Vermaelen * Made memory mapper debuggable * Made YM2413 (MSX-MUSIC) debuggable * Made AY8910 (PSG) debuggable 2004-03-03 Wouter Vermaelen * Implemented R800 refresh delay: - every 180 cycles stall for 20 cycles. Are these numbers correct? I have to different contradicting sources for these numbers. (180 and 20 are in between the numbers of my sources) * R800 IO operations takes 3 cycles: - S1990 adds two extra wait cycles cycles 2004-03-02 Wouter Vermaelen * Improved R800 timing, implemented CAS/RAS optimization: - when the high byte of the address of a read is the same as that of a previous read, the R800 doesn't resend this byte and thus saves a clockcycle - I vaguely remember this only happens for opcode fetches, is this true?? 2004-03-01 Wouter Vermaelen * Made openmsx_info command return lists in TCL format 2004-02-29 Maarten ter Huurne * Fixed bug in command engine: when switching display mode while a command is running, the command's progress was lost. This bug was introduced in the refactoring of 2004-02-17. * Improved rendering of transparent pixels in Graphic5 (SCREEN6). It's still not perfect though: real VDP can render even and odd transparent pixels in different colours, just like the border. 2004-02-27 Wouter Vermaelen * Fixed 2nd drive detection bug on MB8877 based machines 2004-02-26 Wouter Vermaelen * Also do tab-completion for build-in TCL-commands and user defined TCL procs * Fixed sprite collision bug: sprites cannot collide when isDisplayEnabled() is false 2004-02-25 Wouter Vermaelen * Implemented "vramdump" command as a TCL script 2004-02-24 Wouter Vermaelen * TCL command speedups: - avoid "int -> string -> int" conversions on command results * 2 new debug commands: - read_block, write_block (WIP) 2004-02-23 Wouter Vermaelen * Made abstract Interpreter class: - TCLinterpreter is (the only) subclass - all other code is TCL independent 2004-02-21 Wouter Vermaelen * added scripts directory * added multi_screenshot script 2004-02-21 Herman Oudejans * fixed FileOperations.cc for win32. setenv doesn't exist in mingw 2004-02-20 Wouter Vermaelen * Set OPENMSX_USER_DATA and OPENMSX_SYSTEM_DATA environment variables 2004-02-19 Wouter Vermaelen * Implemented "after break" command * Path calculation cleanup: moved all path calculations to the FileOperations class 2004-02-18 Wouter Vermaelen * Implemented master volume setting (range 0..100). Changed volume setting of other devices to 0..100 as well. 2004-02-17 Maarten ter Huurne * VDPCmdEngine refactoring: replaced macros by templates and switch statements by virtual method calls. (committed by Wouter) 2004-02-15 Wouter Vermaelen * Improved CPU timing, both Z80 and R800 2004-02-14 Wouter Vermaelen * Implemented "after frame" command: - now it's possible to write a multi_screenshot TCL script (i've already made such a script) 2004-02-13 Wouter Vermaelen * Made CommandConsole SDL independent 2004-02-11 Maarten ter Huurne * Implemented firmware switch for Panasonic FS-A1FM. Use "set frontswitch true" to boot internal software. 2004-02-11 Wouter Vermaelen * FDC code changes: - code cleanups / maybe fixes (at least more robust code now) - changed track0 signal value for not connected drive 2004-02-10 Wouter Vermaelen * Refactored EventDistributor to be SDL independent (WIP) 2004-02-08 Maarten ter Huurne * Added support for compilation on FreeBSD. Thanks to ag0ny for providing a working environment to test on. 2004-02-07 Reikan * Fixed error when reading init.tcl on win32. 2004-02-07 Wouter Vermaelen * Changed default init.tcl to only bind unbounded keys: should solve the "toggle + toggle = nop" problem * Implemented 6MHz (5.3MHz actually) mode for Panasonic MSX2+ machines * Refactored RealTime classes: - algorithm in RealTimeRTC was broken, refactored the code so that RealTimeSDL and RealTimeRTC share the same syncing algorithm - the algorithm itself still needs to be improved 2004-02-04 Wouter Vermaelen * Handle unknown tags in control protocol more gracefully * Same for parser in Contrib/openmsx-control.cc 2004-02-03 Maarten ter Huurne * Perform less renderer syncs on VRAM writes: - skip sync if written value equals the current value in VRAM - only sync if scan region contains written VRAM address (currently only checked in Graphic2/3 modes) 2004-02-01 Maarten ter Huurne * Simplified rendering interface and code: - unified putImage and putStoredImage - introduced frameEnd, moved some of the old putImage code there 2004-01-31 Wouter Vermaelen * Changed the XML format of the CliComm protocol: - the new format is not compatible with the old version - the example parser Contrib/openmsx-control.cc can handle both formats (except for the LED updates) 2004-01-30 Reikan * Fixed strange build error of FileOperations.cc. It would be becauseof "gthreads vs w32api". * Fixed broken "Makefile.am"s. * Adjusted configure.ac and Makefile.am to accpet libtcl84.a on MinGW. 2004-01-30 Wouter Vermaelen * Made cpu frequency switchable at-run-time: in console: set z80_freq_locked off ; set z80_freq 12345678 2004-01-25 Wouter Vermaelen * Use DATADIR variable set by configure: not yet well tested 2004-01-25 Wouter Vermaelen * Removed obsolete "alias" command: for backwards compatibilty there is an "alias" proc defined in init.tcl * Use PAUSE LED also for the openMSX pause command: patch contributed by Tetsuo Honda * Debug device now prints time in Z80 ticks (3.58MHz), instead of internal ticks. This way we can change the internal tick resolution without changing the debug device output. * Dropped support for MD5 hashes, they were already deprecated for a few releases. 2004-01-24 Wouter Vermaelen * Integrated TCL scripting * Execute share/init.tcl at startup, marked AutoCommands deprecated 2004-01-20 Maarten ter Huurne * Bound quit to ctrl-break by default. 2004-01-20 Wouter Vermaelen * Implemented key modifiers for bind command: example: bind CTRL+ALT+DELETE quit 2004-01-19 Maarten ter Huurne * Integrated FDC fixes by Tetsuo Honda: 1. FDD LED on Turbo R FDC (TC8566AF) 2. Disk change signal 3. Drive detection (1 or 2 drives connected) 4. Empty drive behaviour ("Disk offline") 2004-01-19 Wouter Vermaelen * Changed RAM in FS-A1FM mapper into SRAM, but unfortunately this doesn't solve the problem (openmsx-Bugs-826066) 2004-01-16 Maarten ter Huurne * Released openmsx-0.3.4. 2004-01-15 Maarten ter Huurne * Added Scheduler::setAsyncPoint, which works like setSyncPoint, but is safe to call from other threads. Fixes asserts with "-control" and MIDI in. 2004-01-11 Maarten ter Huurne * Added "dist" target to alternative.mk. Now it is possible to make a release tarball in the alternative build system. 2004-01-11 Manuel Bilderbeek * Added a "boosted" MSX2 config: - European MSX2 config with lots of extensions, see README in its dir - still experimental, needs testing... 2004-01-10 Wouter Vermaelen * Fix VDP bug: - out (#99), xx / OUT (#99),%11xxxxxx is NOT a register write still have to figure out what it does (if anything) - fixes "SNOW26" demo * Fixed midi-out-logger bug: - once plugging failed (file not writable) it kept failing - same for RS232Tester * Fixed "Jump Jump" part or "UR" demo for SDLGL renderer * Made EnumSetting case-insensitive 2004-01-08 Maarten ter Huurne * Implemented new deinterlacer for SDLHi. 2004-01-08 Wouter Vermaelen * Fixed crash when trying to save screenshot to read-only directory * Save screenshots in ~/.openMSX/screenshots (only when filename is not explicitly given) * Fixed bug in Y8950: also reset IRQ in reset() method 2004-01-07 Wouter Vermaelen * Updated control-type selection code (written by tcm1998): - generate error for unknown control types - "pipe" is (currently) not a valid type for linux 2004-01-04 Reikan * Fix SDL_Video subsystem initialization. This fix boot up unstability. * Renamed win32 native midi. out: midi -> midi-out midi* -> midi-out-* in: midi* -> midi-in-* 2004-01-04 Manuel Bilderbeek * Implemented "info scaler" 2004-01-03 Maarten ter Huurne * Separated Scalers.hh/cc into Scaler, SimpleScaler and SaI2xScaler. 2003-12-30 Maarten ter Huurne * Fixed Scale2x scaler: one pixel too many per line was scaled, which lead to out-of-range memory writes. * Fixed "random pixel" bug in pixelacc SDLRenderer. The width of a display rectangle was rounded incorrectly, leaving a 1-pixel wide gap in which the previous frame was visible. 2003-12-29 Maarten ter Huurne Various alternative.mk improvements: * Automatic generation of config.h. * Changed OPENMSX_PLATFORM format from OS-CPU to CPU-OS. * Integrated support for compiling with ICC 8.0. You can select ICC with "export OPENMSX_PLATFORM=x86-linux-icc". Note that if a header file is removed, incremental compile will abort with an error. Workaround: manually remove the dependency file. 2003-12-27 Maarten ter Huurne * Fixed console drawing in SDLLo during pause. 2003-12-23 Wouter Vermaelen * Implemented automatic filename generation for screenshot feature 2003-12-23 Maarten ter Huurne * Integrated Mac OS X patches from Jalu. * Fixed rendering of sprites in SCREEN7. 2003-12-22 Wouter Vermaelen * Fixed reset problems (again): reset could go wrong when it was given in the middle of a CPU instruction, now wait till the end of the instruction * Integrated Joost's screenshot code: - no automatic filename generation yet 2003-12-22 Maarten ter Huurne * Separated Settings.hh into one file per class. 2003-12-21 Maarten ter Huurne * Removed RendererSwitcher and FullScreenToggler. They were useful back when openMSX was multi-threaded, but are useless now. * Various code layout cleanups. 2003-12-19 Wouter Vermaelen * Fixed a lot (not all) ICC compile remarks, mostly virtual destructors for base classes 2003-12-17 Wouter Vermaelen * VDP pause frame cleanups: always store the last frame (was already like this for SDLHi/SDLLo, and also for SDLGL when some effect was used). This simplifies the pause code. It also fixes the image when breakpoints are used. And it will make the screenshot feature integration easier. * Fixed ICC 8.0 compile warnings. 2003-12-16 Maarten ter Huurne * Fixed ICC 8.0 compile errors. 2003-12-15 Wouter Vermaelen * implemented "debug break" * added a hack to make the "boring scroll" demo part of "Relax" work 2003-12-14 Wouter Vermaelen * remove VDP sync points on reset 2003-12-13 Maarten ter Huurne * Optimized hq2x scaler: - Fixed inlining of methods. For some reason g++ cannot inline if the methods are defined as HQ2xScaler::method and , but when using HQ2xScaler:: with an if-statement inside, inlining does work. - Split the giant switch-statement into 4 smaller ones (one per output pixel). Makes a huge difference in object size (123K -> 33K). - Many small optimizations all over the code. 2003-12-12 Wouter Vermaelen * Implemented cpu debugger: the following commands exist now: debug set_bp debug remove_bp debug list_bp debug cont debug step during a break the vdp output is not yet correct 2003-12-11 Joost Yervante Damad * removed obsolete README.WIN32 2003-12-10 Maarten ter Huurne * Fixed SDLLo: rendered image did not show up on visible screen. 2003-12-09 Wouter Vermaelen * Implemented "keymatrixup" and "keymatrixdown" commands 2003-12-09 Maarten ter Huurne * Integrated hq2x algorithm: http://www.hiend3d.com/hq2x.html Use "set scaler hq2x" in the SDLHi renderer to activate it. The code can use some cleanup and tuning, but in most cases it works. * Fixed bug: lines were scaled by the wrong factor when a frame contains both 256-wide and 512-wide lines, such as Psycho World intro. 2003-12-08 Wouter Vermaelen * added a 'safe' getCurrentTime() method in Scheduler, and use this instead of the getCurrentTime() method in MSXCPU. Renamed the latter to getCurrentTimeUnsafe(). See also comments in MSXCPU.hh * Only quit scheduler loop when it's not being called resursively, see comments in Scheduler.cc 2003-12-07 Maarten ter Huurne * Integrated Scale2x algorithm: http://scale2x.sourceforge.net/ Use "set scaler scale2x" in the SDLHi renderer to activate it. Unlike 2xSaI, Scale2x never blurs. 2003-12-06 Maarten ter Huurne * Create a scaler object once it becomes active, instead of creating all scalers in advance. This reduces memory usage when the number of scalers increases or some scalers use large lookup tables. * Made Scaler base class a template. Preparation for later changes. * Removed upscaling from SDLRenderer/CharacterConverter/BitmapConverter. Instead, all upscaling is done by the scaler implementation. The only scaling remaining outside is the downscaling (pixel blending in SDLLo). There is still some cleanup to do, for example deinterlace should be re-implemented outside of drawDisplay. 2003-12-06 Wouter Vermaelen * Made memory in (sub)slots debuggable * fixed bug in wait() method of CPU (order of statements problem): fixes the reset on turbor crash * "cassetteplayer" doesn't crash anymore on non-existing filenames * use HAVE_LINUX_RTC_H instead of NO_LINUX_RTC, makes it easier to port to non-linux platforms 2003-12-04 Wouter Vermaelen * Fixed "Back to the Future" hang: romtype was wrongly detected, in romdb.xml the romtype was specified as "PLAIN", but for this type we still need to guess the start location in memory. Changed romtype in romdb.xml to "PAGE12" * Added high-pass IIR filter to mixer: this greatly reduces clipping in applications that leave a sound channel at a constant non-zero value (e.g. catman) * Fixed rendering of undocumented modes in GL renderer: "Back to the Future" (tape version) triggered an assertion because of this 2003-12-03 Wouter Vermaelen * made VDP status registers debuggable 2003-12-02 Wouter Vermaelen * Implemented IO device multiplexing: - several IO devices can now share the same IO port. When CPU writes to a shared port, all devices receive the write. When CPU read from a shared port, all devices readIO() method is called, but the return value of only one device is returned to the CPU. - this fixes the "openmsx -machine turbor -ext fmpac" problem 2003-12-01 Wouter Vermaelen * made vdp registers debuggable 2003-11-30 Wouter Vermaelen * made memory (64kb currently visible memory) debuggable * made IO ports debuggable * made vram debuggable 2003-11-29 Wouter Vermaelen * Added example implementation for bidirectional communication with openMSX (aka CliCommunicator) in Contrib * Implemented "debug" command infrastructure. For now only the CPU registers can be read or written. 2003-11-24 Wouter Vermaelen * When started with "-control" openmsx now automatically selects the "none" renderer. The launcher should send a "restoredefault renderer" command to enable the display. 2003-11-19 Maarten ter Huurne * Extended Scaler interface to scale modes of 512-pixels wide as well. These modes are scaled in the vertical direction only. * Implemented vertical-only scaling for "simple" and "2xsai". * Minor cleanup of SaI2xScaler class. 2003-11-19 Wouter Vermaelen * Implemented "restoredefault" command 2003-11-18 Wouter Vermaelen * made alternative YM2413 (MSX-MUSIC) the default 2003-11-17 Wouter Vermaelen * Implemented per-pixel-alpha-blending for SDLConsole and SDLFont: - if whole image has the same alpha value, we use per surface alpha blending to speed up blitting 2003-11-16 Reikan * Quick fix for console input after switched renderer. 2003-11-16 Wouter Vermaelen * Major singleton-cleanup: - circular dependency where already fixed - now also fixed construction order. All singletons classes that are used by some other singlton must be constructed before this singlton is constructed. Done by constructing all dependant singltons in the constructor of this singleton. This also prevents new circular dependencies in the future. - because of these two points it's now save to destruct all singletons (before some were never destructed) - instance() method now returns a reference instead of a pointer - I'll post a more detailed explanation to openmsx-devel soon * Fixed hd image file creation: - automatically create or enlarge hd-image when the specified file doesn't exist or is too small 2003-11-16 Reikan * Fixed "hollible slow down when open console in fullscreen on SDLHi" at Win32. 2003-11-16 Maarten ter Huurne * Implemented scanlines for SDLHi. 2003-11-15 Manuel Bilderbeek * Moved initialization of SDL Video to RendererFactory. * Added Ren-Sha Turbo device to machines which have it. 2003-11-15 Maarten ter Huurne * Modified Scheduler to only schedule ASAP sync points during pause. Also made ASAP resolve to CPU's current time instead of target time. * Delayed start of new frame until unpause. If a renderer keeps state to reproduce the image other than the pixels themselves, this state remains available during pause. * Made SDLHi renderer react to scaler changes while paused. 2003-11-15 Wouter Vermaelen * Split EventDistrubutor in EventDistributor and AfterCommand: - together with the "quit" cmd move this removes the circular dependency between EventDistributor and Scheduler * Reversed dependency between Mixer and Scheduler: - Mixer now depends on Scheduler (for mute when paused) instead of the other way around * Removed circular dependency between RealTime and Scheduler * Split Settings.hh in Settings.hh and SettingListener.hh * Removed circular dependency between Scheduler and MSXMotherBoard: - MSXMotherBoard no longer needs to be a singleton - I have to double check, but I think this was the last circular dependency between singletons 2003-11-14 Maarten ter Huurne * Performance fixes for SDLHi. Thanks to Reikan for finding a solution to the slowness. 2003-11-14 Wouter Vermaelen * HotKey is no longer a singleton: - not required anymore since everything is done by bind cmds - fixes circular dependency between HotKey and EventDistributor * Moved "quit" command from EventDistributor to Scheduler 2003-11-11 Manuel Bilderbeek * Added DummyRenderer (set renderer none) * TODO: - only register the renderer in the settingmap in CliComm mode - move SDL video (de)initialization to superclass of SDL based renderers (feel free to implement this! ;-), to fix problem that the window of the previous renderer doesn't disappear 2003-11-11 Wouter Vermaelen * Fixed crash when trying to define an alias with the same name as an existing command * Split CliCommunicator in CliCommInput and CliCommOutput: - first step to get rid of circular dependencies between singletons 2003-11-10 Maarten ter Huurne * Integrated part of Reikan's SDLRenderer patch: - added missing SDL_LockSurface calls when accessing surfaces directly - do not blit from a surface to itself, instead use memcpy (apparently DirectX doet not like blit source equal to destination) - make SDLHi and SDLLo double buffered 2003-11-10 Wouter Vermaelen * Fixed crash on resursive aliasses: - these commands alias foo bar ; alias bar foo ; foo generate an error now 2003-11-09 Wouter Vermaelen * Refactored MSXMapperIO: - MSXMapperIO is no longer a singleton, this solves the destruction order problem between MSXMapperIO - MSXMemoryMapper - possibly the crash on power off is solved by this - machine configs still need to be updated 2003-11-08 Wouter Vermaelen * Implemented "info time" * Implemented "after" command (see doc/commands.txt) * Implemented noise effect (power off) for SDLRenderer 2003-11-07 Wouter Vermaelen * Refactored Scheduler: - there is now only one scheduler loop anymore - fixes the 'still schedule a bit even though emulation is pauzed' bug 2003-11-05 Wouter Vermaelen * Implemented 'alias' and 'unalias' commands 2003-11-03 Maarten ter Huurne * Added inbetween image in SDLRenderer, to allow postprocessing. Hopefully, this will also reduce flicker on Win32. * Integrated 2xSaI scaler algorithm by Derek Liauw Kie Fa: http://elektron.its.tudelft.nl/~dalikifa/ You can activate it with "set scaler 2xsai". For now, it only works in the SDLHi renderer. 2003-11-03 Wouter Vermaelen * Implemented 'snow effect' for GLRenderer (try set power off) 2003-11-02 Wouter Vermaelen * Implemented "power" setting: display is still wrong in power off mode 2003-11-01 Maarten ter Huurne * Added "createsubs" target to "alternative.mk": this creates a Makefile in every source directory which builds only the sources in that directory. 2003-10-30 Wouter Vermaelen * Don't continuously redraw screen while paused 2003-10-29 Wouter Vermaelen * Removed "-config" cmdline option / .xml cmdline file-type: were long time ago replaced by -machine / -ext / -setting 2003-10-28 Wouter Vermaelen * Added descriptions to sounddevices: can be queried with "info sounddevice " 2003-10-26 Wouter Vermaelen * commands can only throw CommandExceptions * fixed possible resource leaks caused by thrown exceptions 2003-10-26 Maarten ter Huurne * Fixed clipping code in SDLRenderer::drawDisplay and drawSprites. Matra games (which use overscan) have a stable image now. 2003-10-25 Wouter Vermaelen * Moved command classes to new subdir * Added info command infrastructure * Added "info version" command * Added "info pluggable" and "info connector" * Added "info sounddevice" * Added "info renderer" * Fixed bug: SDL must be initialized before we parse the cmd line, because the "-control" option creates a new thread * Added descriptions to pluggables: can be queried with "info pluggable " * Added descriptions to connectors: can be queried with "info connector " 2003-10-24 Manuel Bilderbeek * Added -v cli option to get version number of openMSX 2003-10-22 Wouter Vermaelen * Added "-control" cli option to enable CliCommunicator: - commands are accepted on stdin - output is in XML 2003-10-21 Wouter Vermaelen * Converted CliCommunicator to take XML input * replaced PRT_INFO macro with printInfo(), printWarning() or printUpdate() 2003-10-20 Wouter Vermaelen * Added truncate() method to File classes * use unsigned iso int for file size and file position 2003-10-19 Maarten ter Huurne * Moved some low-level GL stuff from SDLGLRenderer to GLUtil. * Changed GL implementation of SCREEN1 to render in cached blocks instead of lines. * Abort VDP command when switching to non-bitmap mode. Fixes assert in Psycho World stage 4. 2003-10-19 Reikan * Call SDL_Quit() also when exit by error. This will fix crash(Win9x)/hang(Win2k/XP) on error exit. 2003-10-18 Wouter Vermaelen * Fixed VDP command timing: - the updateTiming() method did a sync() with the old register values (correct). But also updated the timimng variable with the old register values (wrong). - fixes "Coding Melpots" part in "Metal Limit" * Replaced all PRT_ERROR macros with 'throw FatalError' 2003-10-15 Wouter Vermaelen * Added "incr" and "decr" console commands 2003-10-14 Manuel Bilderbeek * Added provisional FDD LED emulation: - works only when the emulated FDC uses the DiskDrive class - LED goes on when diskdrive motor is on (not always correct!) - IDEHD also turns on FDD LED, when there is a read or write action - all possible FDD LEDs are ORed with each other * Implemented boolean setting 'turborpause', which sets the pause function of the MSX turbo R. TODO: pause function of other MSX computers like Sony HB-501 and MSX2+ machines (setting may need to be renamed!) 2003-10-14 Wouter Vermaelen * Continue VDP Cmds on display mode switch: - current implementation may not be 100% correct, but it's better than just aborting the cmd (fixes feedback bug) 2003-10-13 Wouter Vermaelen * Autofire code cleanups / simplifications 2003-10-09 David Heremans * Forgot to commit some autofire code * Adapted code to new Commands interface 2003-10-12 Maarten ter Huurne * VDP fix: fixed assert in SpriteChecker. 2003-10-11 Wouter Vermaelen * Added CliCommunicator: - external programs can now use stdin/stdout to pass/read commands to/from openMSX. Very usefull for launchers or GUIs. * CliCommunicator cmds are now scheduled so they get executed by the main emulation thread 2003-10-10 Wouter Vermaelen * Turn LEDs off when openmsx quits * Refactored Commands interface: - commands should now return a string instead of calling the print() method to display stuff in the console. 2003-10-09 David Heremans * integrated renshaturbo from Alex into openMSX: fixed the namespace in Alex code 2003-10-09 Wouter Vermaelen * Fixed VDP TR bit bug (fixes Ys-2): - TODO HMMC cmd can be simplified a lot 2003-10-08 Maarten ter Huurne * VDP fix: force renderer sync on sprite enable change. 2003-10-08 Joost Yervante Damad * added initial screenshot support: the .png part is working and I tested it by hacking it into SDLRenderer, but I think someone more knowledgable of the openmsx internals should actually hook up the code 2003-10-06 Wouter Vermaelen * VDP fix: - Mode and blank bits any have effect at the next line 2003-10-06 Wouter Vermaelen * Fixed command/debug console settings 2003-10-04 Wouter Vermaelen * It seems MSX-AUDIO sampleram is mirrored. This fixes sample loading in "Revolution". 2003-10-04 Herman Oudejans * If the moderegister of the debugdevice is written to and bit 6 of that same register is set, newlines will not be output. 2003-10-04 Maarten ter Huurne * Split off SettingsManager to separate source files. * Split off SettingNode and other namespace classes to separate sources. 2003-10-03 Herman Oudejans * Remove buffering from the debugdevice. 2003-10-02 Wouter Vermaelen * Implemented 'vdpcmdtrace' setting * Fixed VDP commands that had SX/DX >= screen-width 2003-09-30 Manuel Bilderbeek * Added the Debug Device to the User's Manual * Small additions, fixes and clean ups in the User's Manual 2003-09-27 Maarten ter Huurne * Released openmsx-0.3.3. 2003-09-24 Maarten ter Huurne * Major rewrites and editing in Setup Guide. Thanks to Manuel for preparing the file for me. 2003-09-22 Reikan * Add MIDI support for Win32. It's missing to know association between parameter names and real device. Ok. we'll call it as TODO. 2003-09-21 Herman Oudejans * Made the debugger optional. If the settings.xml doesn't contain a debugger section, it's turned off. In this case the debuggersettings in the console won't appear either. 2003-09-21 Manuel Bilderbeek * Made error messages more UNIX style and thus more uniform 2003-09-21 Maarten ter Huurne * Workaround for bug in shutdown sequence: MSXCPU::getCurrentTime is needed by audio callback, but MSXCPU got destroyed before audio was stopped. * Finished Pluggable rewrite: - all Pluggable instantiation moved to PluggableFactory - all Pluggable destruction moved to PluggableController * Added "install" target to alternative.mk. 2003-09-21 Wouter Vermaelen * tab-completion improvements: - fixed bug while completing tokens that contained spaces - keep the formatting of the line the same as the original line, completion behaves a lot like the completion in bash now 2003-09-20 Maarten ter Huurne * Have VDPCmdEngine pass accurate timestamps to VRAM writes. In the process, replaced opsCount by currentTime. Should solve PA3 enemy box problem (missing bottom line). 2003-09-20 Wouter Vermaelen * Fix the TR status bit in the VDPCmdEngine: most programs don't even check this bit so they weren't affected, the ones that did were waiting forever 2003-09-19 Reikan * Win32: use SHGetSpecialFolderPath when available. I hope this help on Win95. 2003-09-13 Wouter Vermaelen * The ';' character can now be used in the console to put 2 commands on 1 line. This is usefull to bind 2 commands to 1 key and have a deterministic order of executions between those commands. * tab-completion now works when cursor is not in last position 2003-09-14 Manuel Bilderbeek * Added run time configurable stereo settings. You can now set the channel mode of each (mono) sound device to mono, left or right from the console, using the set [sounddevice]_mode settings. * Integrated VolumeSetting into Mixer class. 2003-09-14 Reikan * Fixed SystemContext error in FileOperations.cc. This affects only on Win32. 2003-09-13 Reikan * Introduced flavour-win32.mk. Do only relatively conservative optimizations. * flavour-uow32.mk contains more aggressive optimizations now. * platform-mingw32-x86.mk uses flavour-win32.mk as the default now. 2003-09-13 Wouter Vermaelen * filename stuff cleanups 2003-09-12 Manuel Bilderbeek * the file from which the WavAudioInput reads is now configurable with the setting "audio-inputfilename". Default is still "audio-input.wav". * restructured AudioWavInput class and cleaned it up a lot 2003-09-11 Wouter Vermaelen * immediately respond to frameskip changes 2003-09-11 Reikan * Added workaround for Win32 on pathname/path-delimiter. 2003-09-11 David Heremans * FDC_DirAsDSK: Figured out why a format didn't work correctly with my default emulated msx2 (NMS8250). Now the code can handle formating, didn't check on other machines yet 2003-09-09 David Heremans * FDC_DirAsDSK: Have cached sectors saved and loaded correctly, now you can use the entire fakedisk for writing as well. * Small bugfixes 2003-09-07 Maarten ter Huurne * Have the "plug" command handle PlugException correctly. * Changed Connector interface so that subclasses require less code. * Declared PlugException in Connector::plug as well. (yesterday's change added it to Pluggable::plug) * Replaced C standard includes with their respective C++ wrappers. 2003-09-06 Maarten ter Huurne * Added PlugException, which can be thrown by Pluggable::plug if the plug action fails for some reason. * Declared PlugException for the methods that can throw it. 2003-09-06 Manuel Bilderbeek * diska/diskb/etc without argument shows the currently inserted disk now 2003-09-06 Wouter Vermaelen * VDPCmdEngine: correct behaviour when registers are changed while cmd is executing. There seem to be a few glitches left though. * Fix VDPCmdEngine timimg (was broken by previous patch) 2003-09-05 David Heremans * Corrected destructor behaviour for the Disk class 2003-09-03 Maarten ter Huurne * Fixed bug: when CPU has to force extra device scheduling, it should schedule all devices until the I/O time, not just the devices until the schedule limit timestamp. 2003-08-30 Wouter Vermaelen * more VDPCmdEngine optimizations * fix VDPCmdEngine register status at end of cmd, should now also be correct when cmd is interrupted * implemented most willStatusChange() methods for VDPCmdEngine 2003-08-28 Manuel Bilderbeek * the file to which the PrinterLogger logs is now configurable with the setting "printerlogfilename". Default is still "printer.log". * the file to which the MidiOutLogger logs is now configurable with the setting "midi-out-logfilename". Default is still "/dev/midi". * the file from which the MidiInReader reads is now configurable with the setting "midi-in-readfilename". Default is still "/dev/midi". * the file from which the RS232-tester reads is now configurable with the setting "rs232-inputfilename". Default is still "rs232-input". * the file to which the RS232-tester writes is now configurable with the setting "rs232-outputfilename". Default is still "rs232-output". 2003-08-27 Wouter Vermaelen * small VDPCmdEngine optimizations 2003-08-25 Wouter Vermaelen * Initial version for Panasonic_FS-A1FM mapper: - internal software doesn't start yet (should it even?), but at least it doesn't crash anymore 2003-08-23 Wouter Vermaelen * Improved tabcompletion for plug command: - only complete pluggables that will fit in the selected connector * Made it easier to switch between MSX-MUSIC cores: - use the parameter "alternative" in the device config section 2003-08-23 Manuel Bilderbeek * Made the keys of the key joystick configurable. TODO: make it possible to have more than one key joystick. * Already some cleanups to make it robust against bad configs. 2003-08-23 Maarten ter Huurne * Rearranged joystick code: - Separated JoystickPort class into its own files and put them in the "input" directory. - Moved JoystickDevice, DummyJoystick and JoyNet to "input" directory. - Split and removed JoystickPorts class: the I/O was moved to MSXPSG, the instantiation was moved to the new PluggableFactory class. 2003-08-20 David Heremans * Rewrote code to be more sector based instead of cluster based: This also simplified the code, also made first steps towards more correct dir-entry handling. 2003-08-20 Reikan * Added Keymap-change and KeyLayoutBit. 2003-08-18 David Heremans * Added cached diskwriting to FDC_DirAsDSK. Regular file operations seem to work, formatting doesn't yet. No writes to native host OS files so far. 2003-08-18 Reikan * Adjusted as a first step to support win32. 2003-08-17 Herman Oudejans * fixed a serious bug in the commandlineparser. -diska (and b), -carta (and b,c and d) work again. 2003-08-16 Herman Oudejans * Added priority to the commandline parser. When the -h option is invoked, there won't be any machines loaded. 2003-08-14 Wouter Vermaelen * Basic support for .zip files, for now always the first file in the .zip is taken * Added filetype detection for filenames of the form "..zip", e.g. "psycho.dsk.zip" 2003-08-12 David Heremans * Small enhancement in FDC_DirAsDSK: Alowed writing of the boot sector. The altered boot sector is stored as a hidden file and is used when creating an FDC_DirAsDSK instance 2003-08-13 Maarten ter Huurne * Reikan released C-BIOS 0.17; I updated it in Contrib. 2003-08-12 Wouter Vermaelen * Now any file can be gzipped, including files loaded by SDL and libxml 2003-08-12 David Heremans * improved FDC_DirAsDSK: - now empty sector are generated for unmapped clusters - altered filesize also scanned for when accessing FAT sectors 2003-08-11 David Heremans * Added more functionality to the FDC_DirAsDSK class: - fixed an offset bug - made changes to files on the host OS visible to the emulated dsk file. Now altered filesize is detected when accessing file itself or the DIR sector and the DIR and FAT will be adjusted as needed 2003-08-11 Wouter Vermaelen * Read-only support for .gz files 2003-08-09 Maarten ter Huurne * Put all openMSX code in namespace "openmsx". This avoids name clashes with C libs, for example "Font" being defined by us and by Xlib. Those name clashes occur earlier when precompiling headers, but otherwise it's still an accident waiting to happen. 2003-08-09 Wouter Vermaelen * Fixed FileContext owner ship semantics (fixes memory leak): - the class that creates a FileContext object should also destroy it - when a class needs a FileContext parameter for a longer time than the duration of the method call, it should make a copy of the FileContext object * Added audio input pluggable/connector for turbor PCM: - for now it just reads "audio-input.wav" 2003-08-06 Wouter Vermaelen * CPU cleanup / speedup: * FDC cleanups: - renamed some classes - added a base class for sector based disks (those that don't store the information 'between' the sectors) 2003-08-06 David Heremans * Introduced to the FDC_DirAsDSK class: - it is now possible to have a directory given as value for the '-disk(a|b)' option, this will construct a read-only disk that contains the regular files in this directory. Content changes in those files are immediately visible in the emulated disks. Sizes changes of files are not incorporated yet! 2003-08-02 Herman Oudejans * fixed compile errors when using gcc version 2.95 2003-07-31 Wouter Vermaelen * Implemeted a RS232 tester pluggable: - writes to file "rs232-output" - reads from file "rs232-input" - the BASIC commands load"COM:" and save"COM:" seem to work * Made sure that classes that are ment to be pure virtual, truly are pure virtual * Added rs232 extension 2003-07-30 Wouter Vermaelen * Added MSX-RS232 to device factory * Gave MSX-RS232 16KB ROM 2003-07-29 Wouter Vermaelen * Added MSX-RS232: - there do not exist any RS232 pluggables yet - code is yet completely untested 2003-07-27 Herman Oudejans * decoupled de console and debugger again. It caused some unforseen site-effects. * Added settings for the debugger in settings.xml 2003-07-26 Herman Oudejans * Re-enabled the debuggerconsole. * made sure the console is turned off when de debugger is called. * added a disassembly view (still static). * added the same settings for the debugger as there were for the console (rows, columns, placement, font and background). 2003-07-26 Maarten ter Huurne * Refactored settings code. * Moved settings code into new "settings" directory. * Cleaned up the linking process. 2003-07-23 Wouter Vermaelen * A few midi in related fixes. Still needs to be tested with real midi hardware 2003-07-22 Wouter Vermaelen * Added midi in reader (reads from /dev/midi): - still completely untested 2003-07-21 Wouter Vermaelen * added joystick emulation for mouse: - when you plug in the mouse with the left button pressed, it behaves like a joystick * added Semaphore class * protected Scheduler with a Semaphore: - it is now possible the register synchronization points in a different thread than the emulation thread. This will be needed for the MIDI in pluggable 2003-07-20 Herman Oudejans * first steps toward a debugger. For now it's just a static hexdump. * Temperary removed an assert in de settingsmanager because the consolesettings are init twice. 2003-07-19 Wouter Vermaelen * Fixed bug in MSX-Audio sample RAM 2003-07-16 Wouter Vermaelen * Fixed some MIDI bugs * Added Midi Out Logger (logs to /dev/midi) 2003-07-16 Wouter Vermaelen * Implemented Midi-In and Midi-Out ports for MSX-Midi: - completely untested, so it probably won't work yet - there are no pluggables for the midi ports yet 2003-07-12 Wouter Vermaelen * A Pluggable is now told in which Connector it is plugged 2003-07-08 Herman Oudejans * debugdevice now works with stdout, stderr and normal files 2003-07-08 Wouter Vermaelen * Don't clear SCC wave forms on reset * Added MSX-Midi: - except for sending and receiving data it works, thus timers and interrupt generation work 2003-07-06 Herman Oudejans * added a debug device. * Details for control are in docs/debugdevice.txt 2003-07-05 Wouter Vermaelen * Started implementing MSX-Midi 2003-07-05 Maarten ter Huurne * Fixed colour of blue intensity 2 in Graphic7 mode. Thanks to Grauw for reporting this bug. 2003-07-03 Maarten ter Huurne * Fixed bug in scanline effect, which caused glitches on ATI and Matrox cards. 2003-07-02 Wouter Vermaelen * Implemented stereo/mono and 8/16-bit conversion for wav files * Implemeted "force_play" and "no_force_play" for cassetteplayer 2003-07-01 Wouter Vermaelen * On turbor use big 4mb rom also for MSX-MUSIC ROM and DISK ROM 2003-06-30 Wouter Vermaelen * Fixed cassette player bug: - float was not accurate enough, use 64-bit fixed point * Made rewind command for cassetteplayer and tape patch 2003-06-29 Wouter Vermaelen * Made casstte player audible: - you can turn it off with "set cassette_volume 0" 2003-06-28 Wouter Vermaelen * Implemented automatic .cas to .wav conversion in CassettePlayer: - not all .cas files seems to work though - there can be a delay of a few seconds while converting the file, need to do the conversion in a helper thread * added peekMem() method, will be used by debugger to savly read mem 2003-06-28 Manuel Bilderbeek * Cleaned up the machines and extensions dir: - added a subdir 'roms' when needed - added the Id tags when missing - filename cleanups - fixed some typos - added SHA1SUMS files in each roms dir, when available * Added a few extensions 2003-06-25 Wouter Vermaelen * Implemented Listener interface for Setting class 2003-06-24 Wouter Vermaelen * Converted romdb.xml to use 'title' tags iso 'id' attributes 2003-06-23 Wouter Vermaelen * Implemented SHA1 support for romdb * Converted most of romdb.xml to SHA1 2003-06-22 Wouter Vermaelen * Added new YM2413 core: use with the new (temporally) device "Music_2" * Implemented TurboR VDP access delay * Added SHA1 class (not yet used) 2003-06-21 Wouter Vermaelen * Implemented 'fallback' mechanism for files in settings.xml 2003-06-15 Wouter Vermaelen * Implemented undocumented flag bits for "BIT n,(HL)" instructions, ZEXALL test still passes 2003-06-14 Wouter Vermaelen * Improved performance for tab-completion: without this patch "diska " could take several seconds 2003-06-10 Wouter Vermaelen * Implemented HBI-55 data cartridge: according to some BASIC test progs it seems to work, but not yet tested with Sony_HB_75P internal software 2003-06-09 Maarten ter Huurne * Released openmsx-0.3.2. 2003-06-09 Manuel Bilderbeek * Updated HOWTO (please check) * Updated/cleaned up machine configs: - MSX2+ machines have a memory mapper of 64kB (tested on real Panasonic FS-A1WX) - Changed ROM names in 8250 config to match the ones used in the other configs, using a 32kB combined ROM for basic/bios - Added missing CVS Id tags - Removed unnecessary DiskRomPatches from machines with TC8566AF FDC's - Changed FDC type names to new conventions (see 2003-03-29), note that the old ones won't work anymore. The names of the FDC classes should be revised though. 2003-06-09 Maarten ter Huurne * Implemented user-specified gamma correction for SDLLo/SDLHi/SDLGL. Find out your PC monitor's gamma using the measurement image on the following page: http://www.bberger.net/gamma.html TVs use a standardised gamma of 2.5. So for a realistic picture, set openMSX gamma correction to: PC_gamma / 2.5. Or you can just try a few values and see what you like ;) 2003-06-08 Wouter Vermaelen * Don't abort on "cassetteplayer " cmd with invalid filename * Print warning when initial font or background images can't be read 2003-06-08 Maarten ter Huurne * In the command engine, only act on display mode switches if a different command engine mode is entered. * Fixed various GCC 2.95 compile problems. 2003-06-07 Wouter Vermaelen * Print error msg when user gives wrong diskimage on the cmd line * same for cas and wav images * Fix core dump while writing to read-only disk images * Don't put empty lines in console history 2003-06-06 Maarten ter Huurne * Added FloatSetting class. * Introduced new setting "gamma" for gamma correction. Not yet implemented in Renderers. * Refactored EnumSetting: - Split into type-safe inline wrapper class "EnumSetting" and type-indedendent implementation class "IntStringMap". Forced template instantiations are no longer necessary. - Made strings in string-enum map constant. 2003-06-06 Wouter Vermaelen * Refactored consolecolumns and consolerows settings 2003-06-05 Wouter Vermaelen * Merged Arjan's YMF278 fixes/improvements, plus did some optimizations * Take default MapperIO type in case the machine configuration doesn't specify one (all MSX1 machines) 2003-06-04 Maarten ter Huurne * New console font: ConsoleFontRaveL.png, this is a larger version of ConsoleFontRave.png (easier to read on hi-res screens). * New extensions: 512K/1MB/2MB/4MB external mapper. 2003-06-04 Herman Oudejans * Background and Fonts are not reset anymore when changing renderers. * Added 10 machine configurations, made by BifiMsx 2003-06-04 Wouter Vermaelen * support 8kb roms * fixed SDLFont, sometimes the font was completely transparent * fixed UMR in Timer class (caused Toshiba rom to hang openMSX) * Implemented fake "per pixel alpha blending" for SDLFont 2003-06-03 Maarten ter Huurne * Fixed SDLGL tile cache when character and pattern table use mirroring. Solves glitches in Dr Archie, Relax and probably others. 2003-06-02 Maarten ter Huurne * New console font: ConsoleFontRave.png, based on Bitstream Vera, but renamed due to license requirements (see http://www.gnome.org/fonts/). * Re-encoded existing console fonts to make their files smaller. 2003-06-01 Wouter Vermaelen * Implemented MegaRam * Implemented PAC (like FMPAC, but without FM and without ROM) 2003-06-01 Herman Oudejans * Added a setting for grabbing mouse and keyboard input. When input is grabbed, the windowsmanager won't be able to mask keys from openmsx and the mousecursor won't be able to leave the openmsx window. 2003-06-01 Wouter Vermaelen * Fixed mouse (hopefully) * Removed "loadsram" and "savesram" settings 2003-05-31 Maarten ter Huurne * When pausing, store last frame and keep plotting it during pause. Also keep redrawing the console, so it is usable during pause. Current implementation is a bit of a hack, but it works. * Implemented afterglow effect, try "set glow N" in SDLGL. 2003-05-31 Wouter Vermaelen * VDPCmdEngine cleanups 2003-05-31 Herman Oudejans * Changed console save file into ascii. When loading the history, empty lines are ignored. * Removed error when unable to load consolehistory * Only two identical commando's directly after eachothers are now removed if removedoubles is active to improve performance. * Remove settings for consolehistory load & save. Default are now: - Always load and save. - Filename is now fixed as 'history.txt' 2003-05-30 Wouter Vermaelen * Refactored VDPCmdEngine, use VDPCmd subclasses for each command instead of function pointers 2003-05-30 Herman Oudejans * Implemented save/load consolehistory 2003-05-30 Maarten ter Huurne * Implemented Text2 and MultiColour modes in SDLGL using one texture per character approach (the approach Graphic2/3 already used). * Implemented screen accuracy. * Do not sync during a skipped frame. 2003-05-27 Maarten ter Huurne Dirty checking improvements: * Removed all old character dirty checking code from SDLRenderer and SDLGLRenderer. * Added DirtyChecker class, a generic VRAM cache administration. * Use new DirtyChecker in SDLGLRenderer for pattern and colour table. Caching the name table is useless in SDLGLRenderer. This is a work in progress, but the current state seems to be stable. 2003-05-26 Wouter Vermaelen * More RealTimeRTC improvements * Some frameskip tuning * Keep frameskip setting between renderer switch 2003-05-25 Wouter Vermaelen * Use RTC Timer for RealTime syncing when available 2003-05-25 Herman Oudejans * Fixed a problem with the consolesize when switching from SDLLo to SDLHi or SDLGL. * When the console is resized, the text in it will be rebuild. * Fixed a bug in the cursormovement (broke with the text rebuild). 2003-05-24 Wouter Vermaelen * Split RealTime in generic part and implementation specific part. This decouples time synchronization from SDL and paves the way for more accurate implementation (like RTC) 2003-05-24 Maarten ter Huurne * I/O to extended VRAM now goes to limbo, instead of main VRAM. * Changed VDPVRAM::Window to VRAMWindow. There is not much added value to a public inner class in C++... * Changed VDPVRAM::VRAMObserver to VRAMObserver, in its own file. Own file reduces the risk of cyclic header dependencies. * Renderer gets VRAM updates through VRAMObserver. This is a preparation for later optimisations. * Make sure no sprite checking is done in spriteless display modes. * In a spriteless mode, or when sprites are disabled, the entire sprite plane is skipped. This is more efficient than the previous checks for a "no sprites" situation, because it occurs earlier in the rendering process. 2003-05-24 Herman Oudejans * commands can now be on multiple lines in the console. 2003-05-23 Herman Oudejans * Fixed an error when processing the commandline and one device exists twice in the configurationfile(s). 2003-05-21 Herman Oudejans * Double commands in the console commandhistory can no longer occur. * Made 'remove doubles' and history size configurable 2003-05-20 Wouter Vermaelen * Implemented run-time tuneable volume for sound devices 2003-05-19 Herman Oudejans * Implemented search history in the console * added Ctrl-C to clear the current command. 2003-05-19 Wouter Vermaelen * Added MoonSound: - code was largly taken from mame - fm part is quite good already - wave part still needs some improvements: - some waves sound weird at certain frequencies?? - missing features like: pseudo-reverb, LFO, vibrato, ... 2003-05-18 Maarten ter Huurne * Released openmsx-0.3.1. 2003-05-18 Herman Oudejans * Converted a lot of spaces into tabs. * Changed int into bool for zoomSurface(). * Fixed indentation in zoomSurface(). 2003-05-18 Maarten ter Huurne * Removed "ConsoleSource" directory and moved its code elsewhere: - actual console code moved to new directory "console" - command code moved to existing directory "events" - circular buffer code moved to directory "src" * Moved input devices code to new directory "input". Maybe more can be moved there in the future. * Changed "cpudebug" command into "cputrace" setting. Also fixed the duplicate "cpudebug" command on a turbo R (dual CPU). Note: this setting exists only if "cpu" dir is built in debug mode. 2003-05-17 Maarten ter Huurne * Removed EmuTime parameter from Command::execute. Since most commands do not interact with the emulated machine, they do not need EmuTime. * Removed EmuTime parameter from EventListener. The only reason this was needed before, was to execute commands. * Removed code that is no longer needed in the single thread model. * Fixed event handling during pause. 2003-05-17 Herman Oudejans * Implemented configurable file-extentions. * Lined the help-text up, properly. * Renamed lines/rows/columns/width/height -> rows/columns * Fixed a uncaught exception when the SDLConsole is resized and the backgroundfile can't be loaded. * Fixed -h when the specified machine can't be loaded. * Console now resets the scrollback when any key other than page up/down is used. * Fixed the blinking cursor in the SDLConsole. * Fixed Ctrl-A/E when numlock is active * Fixed a few small bugs in the CommandLineParser. 2003-05-17 Reikan * Changed to single thread model. Now fixed SDLGL renderer fault on XFree86-4.3.0. 2003-05-11 Maarten ter Huurne * Fixed colour of font on GLConsole overlayed on Text1 mode. 2003-05-11 Herman Oudejans * Fixed segfault in the SDL background scaling when using 8bpp. tested and working in 15,16 and 24 bpp. in 8 bpp it doesn't give a segfault, but since the whole display isn't working, I can't see if the scaling really works. * SDL backgroundimage can now have any pixelformat. * SDL Console characters are no longer blended. 2003-05-10 Herman Oudejans * Fixed wrong initial default size of the console. * Fixed range of the consolecolumns-setting 2003-05-10 Wouter Vermaelen * Replaced old YM2413 code by new one. Volume still needs some tuning: - relative volume between music and drum channels - relative volume between YM2413 and other sound chips 2003-05-09 Herman Oudejans * Added resizing and moving to the console. * Extended the console editing keys. * Added keyrepeat to the console. * Added setValueInt() and setRange() to Integersetting. * Added SDL background scaling. 2003-05-08 Maarten ter Huurne * Another GCC 3.3-pre fix: passing signed byte references as unsigned byte references. Typecasting to "(byte &)" instead of "(byte)" works on both GCC 3.2 and GCC 3.3-pre. 2003-05-07 Wouter Vermaelen * a few fixes in new YM2413 code. Still not made default because drums in illusion city are screwed 2003-05-06 Maarten ter Huurne * Compiled with GCC 3.3-pre: - Fixed missing "#include " directives. - Fixed some warnings, suppressed others. 2003-05-05 Wouter Vermaelen * release openmsx-0.3.0 * added "mbstereo" extension 2003-05-04 Maarten ter Huurne * Swapped the threads: event handling now occurs in the main thread, emulation occurs in a worker thread. Inspired by Reikan's UOW32 patch. This removes a lot of differences between the Linux and Win32 ports. Also, renderer switching and runtime full screen toggle should work on Win32 now. * Bug fixes on thread swap: - Fixed compile error on GCC 2.95. - Workaround for incompatibility of SDL_PushEvent. - Print exceptions thrown in emulation thread. * Minor bug fix on video init attempts in RendererFactories. * Merged Reikan's configure.ac improvements. * Fixed magnified sprites in sprite mode 2. Glitches in Eindeloos are gone. * Fixed bug: display was disabled after switching renderers during overscan (for example Unknown Reality title screen). * Fixed bug: possible writes outside screen buffer on overscan. Crashed Ark-a-Noah on SDLHi. 2003-05-03 Wouter Vermaelen * Trojka starts playing ADPCM samples without initializing the sample volume. The sample volume after a reset was set to minimum, changed it to maximum. 2003-05-03 Maarten ter Huurne * Removed EmuTime parameters from Settings methods. * Centralized GL header check in new GLUtil.hh file. In the future, GLUtil.hh will contain GL utility classes/functions. * Replaced "renderer" command by a "renderer" setting. This also makes sure the user cannot switch to a non-existant renderer. * Moved "Rom.(hh|cc)" and "SRAM.(hh|cc)" from "src" to "src/memory". * Removed obsolete "cfg" directory; configurations are in "share" now. 2003-05-02 David Heremans * Added the Contrib/cbios directory to 'make install' * Have make install create some nice softlinks * Added md5sum files 2003-04-30 Maarten ter Huurne * Introduced Blender class and use it from Character/BitmapConverter. * Implemented narrow SCREEN6 sprite pixels in SDLGL. * Implemented blended SCREEN6 sprite pixels in SDLLo. * Refactored ROM related classes to get device ID from hardwareconfig.xml into RomInfo. In the process all mapper type code has been centralized in RomInfo. All of this just to get "slot 3.3: Firmware" in the slotmap ;) 2003-04-29 Wouter Vermaelen * Fix tabcompletion for absolute paths 2003-04-28 David Heremans * Fixed make install to /opt/openMSX and create extra link for binary in /usr/local/bin * added (no)debug to main makefile 2003-04-27 Wouter Vermaelen * Implemented more complete filename completion as described in feature request 713442 2003-04-27 Maarten ter Huurne * Added a console background to SDLGL when no image is used. * Minor cleanups to console classes. * Take horizontal adjust into account for calculating HR and horizontal retrace interrupt timing. Fixes some of the glitches in No Waste, but not all. * Fixed text colour in Text1 mode in SDLGL: reverted a misguided optimisation. (in GL, full intensity of int is 0x7FFFFFFF, not 0xFF) * Updated C-BIOS to version 0.16. 2003-04-25 David Heremans * Allowed JoyNet to accept a new connection if previous connection is closed. 2003-04-25 Maarten ter Huurne * Fixed sprite colours in Graphic5 (SCREEN6). The turbo R boot logo looks OK now. However, narrow sprite pixels do not yet work in SDLGL. * Implemented horizontal scroll high and multi page in SDLGL. This implementation is based on Wouter's horizontal scroll patch. * Implemented horizontal scroll high and multi page in SDLHi/Lo. Note that some horizontal scroll features are still missing: * Multi page scrolling in combination with deinterlace. * Scrolling in character modes. However, the current state should be enough to run most games and demos. 2003-04-24 Wouter Vermaelen * if an extension exists both in user as in system directory, prefer the one in user directory 2003-04-23 Maarten ter Huurne * Added update calls for horizontal scroll settings. 2003-04-22 Maarten ter Huurne * Partial horizontal scroll implementation: scroll low and border mask. Scroll high and multi page to be done. Space Manbow works fine in MSX2+ mode now. 2003-04-21 Wouter Vermaelen * Improved DACSound quality: - improved quality at the expense of an (extra) delay - samples in Swiss Demo sound acceptable now - modplayer sounds better but still not very good * Updated FMPAC: - ROM is not visible while SRAM is active 2003-04-19 Wouter Vermaelen * Added alternative YM2413 core (implemented by Jarek Burczynski): - still needs some work before it can replace old core - volume needs some tuning - performance optimizations (silent channels) - bug fixes(?) - if you want to try it copy YM2413.cc.new and YM2413.hh.new over YM2413.cc and YM2413.hh and recompile 2003-04-18 David Heremans * Fixed a bug in JoyNet, but need a program with a good asyncron protocol to test it 2003-04-17 Maarten ter Huurne * Separated drawSprites from drawDisplay in subclasses of PixelRenderer. 2003-04-16 Maarten ter Huurne * Moved display coordinate calculation from subclasses to PixelRenderer. 2003-04-15 Wouter Vermaelen * Reworked SCC - Manuel Pazos's SCC detection routine works correctly now - Optimized for common case. You mostly write to SCC and hardly ever read from it 2003-04-12 Wouter Vermaelen * Reworked consoles: - splitted console in "core console" and "console renderer", the main advantage is that command history is preserved while switching renderers 2003-04-12 Maarten ter Huurne * Created util.hh: currently only contains fillBool, which used to be duplicated in SDLRenderer and SDLGLRenderer. * Removed system.h: this file has probably been obsolete for some time, but managed to escape everyone's attention. * Changed SDLGLRenderer to use SpriteConverter. 2003-04-10 Maarten ter Huurne * Moved the VDP and rendering code into new "video" directory. Thanks to Manuel for suggesting how to solve symbol dependencies. But I still don't like recursive Make... 2003-04-09 Maarten ter Huurne * Merged SDLLoRenderer and SDLHiRenderer into SDLRenderer. Template expansion replaces duplicated code. From a user perspective, "SDLLo" and "SDLHi" still exist. 2003-04-06 Manuel Bilderbeek * Clean up in RomInfo stuff for empty ROMs. Note that I'm now assuming that an empty ROM is always an empty SCC. Is this a valid assumption? The problem is that `forced' ROM types (as with an empty SCC) are not put in the ROMInfo. So I can't check if it is really an SCC... 2003-04-05 Wouter Vermaelen * Added Korean80-in-1 mapper type * Added Korean90-in-1 mapper type * Added Korean126-in-1 mapper type * Implemented "keyjoystick", joystick emulation via keyboard: - use like this "plug joyporta keyjoystick" - joystick direction is mapped on cursors button-A space button-B left alt - TODO mapping should become configurable - TODO you should be able to configure more than one keyjoystick * Implemented V9958 CMD bit: I don't know any program that actually uses this, so it is not yet tested 2003-04-04 David Heremans * Getting everything in /opt/openmsx installed, no links yet 2003-04-04 Maarten ter Huurne * Introduced SpriteConverter, a utility class for the Renderers to use, similar to BitmapConverter and CharacterConverter. * Changed SDLHiRenderer to use SpriteConverter. * Removed PixelRenderer's protected fields from SDLGL and SDLHi. 2003-04-03 Maarten ter Huurne * Fixed remaining reads of uninitialised VDP fields. 2003-04-02 David Heremans * First cleanups to prepare for a 'make install' 2003-04-02 Maarten ter Huurne * Minor cleanups in SpriteChecker. 2003-03-31 Maarten ter Huurne * Turned DisplayMode into a class (makes code easier to read). * SpriteChecker no longer checks sprites in text mode. * Various minor cleanups along the way. 2003-03-31 Wouter Vermaelen * Don't abort (only warn) when there occurs an error during processing of autocommands * Converted "frontswitch" command into a setting 2003-03-30 Maarten ter Huurne * Removed fullScreen parameter from Renderer constructors. * Made Renderer constructors private, only available to their respective factories. * VDP subsystem reset methods are no longer called by their constructor, instead they are called explicitly by the VDP. * Removed EmuTime parameter from VDP subsystem constructors. * Rearranged VDP constructor to remove overlap with reset method. * Fixed uses of display mode which could not yet handle the YJK bits. * Fixed a tricky bug that could occur if rendering was performed in the middle of a series of updates that all occur at the same EmuTime. 2003-03-29 Manuel Bilderbeek * Moved other configs from src/cfg to share/machines * Added some more extensions * Changed settings.xml to use cbios-msx2 and added skins/ prefix to ConsoleFont.png * Updated the HOWTO. I wish the make install was finished, so that I can cut 10% of the HOWTO... (it's getting complicated to explain...) 2003-03-29 Wouter Vermaelen * Fixed crashes on missing arguments (e.g. "openmsx -ext") * Added extra FDC names: National --> MB8877A Panasonic --> TC8566AF Philips --> WD2793 The old names are deprecated, as soon as the machine configuartions are changed to use the new names, the old names will be removed. * Fixed crash on missing console font 2003-03-28 Manuel Bilderbeek * More refactoring of Rom objects to add RomInfo: - all Rom objects now have a RomInfo object - get information that was in the romdb from that object - slotmap uses this and prints real Rom info, when available; later, the GUI should be able to use this too, in some way - note that all MSXRom constructors changed :-) - note that it's my first big change, please fix things that could be a lot better and tell me about it :-) Maarten, Wouter and Joost: thanks for your help! * Added loads of machine configs (thanks Atarulum): - when we decide what to do with the patches for the FDC, I'll add the others 2003-03-26 Wouter Vermaelen * Fixed RomPanasonic SRAM * Optimized VDPCmdEngine for common case: no command in progress 2003-03-25 Wouter Vermaelen * Fixed VDP Command Engine bug: - on every sync() the engine processed at least one 'pixel', when you poll the VDP very fast (R800) the time between two syncs is less than one pixel. This screws up the bookkeeping. - Fixes MSXView startup problem, Relax demo, ... 2003-03-24 Wouter Vermaelen * Another directory structure change: moved machines/ -> share/machines/ extensions/ -> share/extensions/ etc/romdb.xml -> share/romdb.xml settings.xml -> share/settings.xml * Create share directory, moved a lot of files around 2003-03-23 Wouter Vermaelen * Reimplemented Panasonic memory system: - in TurboR rom/ram are tightly connected (dram mode, ...) Memorymapper, RomMapper, BiosRom are now implemented as special as special devices - TurboR hardwareconfig.xml updated - MSXView almost works now, without "set cmdtiming broken" openmsx wait for a never ending VDP command. Still investigating... 2003-03-22 Manuel Bilderbeek * Refactored ROM mappertype retrieval/ROM Info (WIP, to enable readout of ROM info in the console.) 2003-03-16 Wouter Vermaelen * Implemented timeout for reading mouse: - this fixes the '"plug joyporta mouse" in settings.xml' bug - current timeout is 1/100s, I've no idea of the timeout value of a real MSX mouse 2003-03-16 Wouter Vermaelen * Various minor optimizations / cleanups, mostly better cache for unmapped memory regions 2003-03-16 Manuel Bilderbeek * When a ROM is found in the romdb, the info about the ROM is now printed. Later, this info could be stored somewhere, so that the GUI can use it to show a ROM title, e.g. 2003-03-15 Wouter Vermaelen * Added HALNOTE mapper type: - Doesn't work yet! Only 'normal' rom switching is emulated. The program also writes to pages 0 en 3 and it selects rom pages > 0x80, I've no idea what this should do. * Fixed format command, apparently I broke it several months ago. 2003-03-13 Wouter Vermaelen * Implemented "vramdump" console command, only available when compiled with DEBUG 2003-03-10 Wouter Vermaelen * Reworked MSXRom: - made a (small) subclass for every mapper type: * Moved MSXRom MSXRam related files to a new directory "memory" * Fixed rom-cartridge SRAM saving, path was wrong: - it is now ~/.openMSX/persistent/roms// 2003-03-05 Wouter Vermaelen * Implemented "Harry Fox" mapper type * Fixed ASCII8 mapper type: Annimal Land works now 2003-03-03 Maarten ter Huurne * Fixed bug in PixelRenderer: finishFrame was called every frame, even with frame skip active. This also fixes the special effects in GL with frame skip active. 2003-03-02 Maarten ter Huurne * Introduced RendererFactory. This should help in making the renderer selection a setting. It will also make it possible to query which renderers are available. 2003-03-02 Wouter Vermaelen * Converted "frameskip" console command into a setting * Implemented BooleanSetting as a specialization of EnumSetting * Fixed path resolution bug: all files were also searched for relative to the current directory 2003-03-01 Maarten ter Huurne * Updated C-BIOS to version 0.15. 2003-03-01 Wouter Vermaelen * Converted "throttle" console command into a setting * Converted "pause" console command into a setting * Converted "mute" console command into a setting * Converted "console" console command into a setting 2003-02-27 Wouter Vermaelen * Converted "speed" console command into a setting 2003-02-26 Wouter Vermaelen * Implemented free slot selection: - this is only a temporary solution, it will very likely change in the next release - when you specify -1 for the primary slot, the cartridge will be inserted in a free external slot. Very usefull to write generic extensions. * romdb.xml is now searched in /opt/openMSX/etc/romdb.xml or in ~/.openMSX/etc/romdb.xml * removed -fmpac and -musmod command line options, they are replaced by extensions 2003-02-24 Wouter Vermaelen * extension directory now has the same structure as the machines directory: - for example the scc extension lives in ~/.openMSX/extensions/scc/hardwareconfig.xml * Implemented Jon's SCC amplitude measurements 2003-02-23 Wouter Vermaelen * Implemented command line extensions: - you can load configuration files located in ~/.openMSX/extensions/ /opt/openMSX/extensions/ with the "-ext" command line option - example: ./openmsx SDNATCH1.DSK -ext scc+ * Removed -msx1 -msx2 -msx2+ -turbor command line options * SRAM of rom cartridges is saved in ~/.openMSX/persistent/roms// 2003-02-19 Wouter Vermaelen * Implemented "console_font" setting: - change the console font at run-time * Fixed SCC+ 2003-02-19 Maarten ter Huurne * Implemented "toggle" command, which toggles any BooleanSetting. * Replaced "fullscreen" command by "toggle fullscreen". * Support key binding of command including parameters. 2003-02-18 Wouter Vermaelen * Reworked File / FileContext classes: - separated "searchpath-resolution" from file opening * Implemented "console_background" setting: - change the console background at run-time 2003-02-16 Maarten ter Huurne * Fixed a bug in the VDP, where display-on was reported before the sync with VDP subsystems was completed. Can cause the command engine to run too slow, among other things. 2003-02-16 Wouter Vermaelen * First step to persistent state directories: - persistent state files are saved in ~/.openMSX/persistent/// - is the name of the directory where we found the hardware description. Maybe we should get this from inside the config file? - it's not yet possible to specify , so for now it's always "untitled" * Default machine can be specified in settings.xml: - see example settings.xml, this will load ~/.openMSX/machine/nms8250/hardwareconfig.xml * Added "-machine" command line option 2003-02-13 Maarten ter Huurne * Made "full screen" into a setting. The "fullscreen" command still works, but will disappear in the future. 2003-02-10 Wouter Vermaelen * Uninline cpu rdmem and wrmem slowpaths: this actually increased performance a little * RTC uses generic SRAM class for persistent data: - SRAM is now the _only_ class in the whole tree that writes files - parameters "filename, load, save" changed to "sramname, loadsram, savesram". This means all configuartion files must be updated. * Implemented SCC+ subtypes: "Snatcher" "SD-Snatcher" "mirrored" "expanded" 2003-02-08 Wouter Vermaelen * Fixed MSX-AUDIO mapper: - it has 4 pages of 32kB instead of only 2 - it has 4kB RAM - is mirrored at 0x8000-0xffff * Fixed Y8950 interrupt masks 2003-02-02 Wouter Vermaelen * Implemented Panasonic MSX-Audio mapper type (untested) 2003-02-02 Wouter Vermaelen * Compiled with some extra warnings enabled and fixed the resulting warnings * Renamed MSXRomDevice to Rom (it isn't a device anymore) * Default values for settings.xml: - all sections in settings.xml now have default values if they are left out the config file - don't abort when settings.xml is not found - but I really advise _against_ using these features 2003-01-31 Wouter Vermaelen * Implemented "user directories" (see settings.xml) * Fixed "specify other config file" bug * Relative-from-config was broken, fixed it 2003-01-30 David Heremans * altered a code comment and added some extra config examples 2003-01-29 Wouter Vermaelen * Implemented home-directory-substitution for local files: When a filename starts with "~", this character is replaced by the content of the environment variable HOME. I'm not sure this will work for non-UNIX OS'es. * Implemented system-directories: - The files romdb.xml, msxconfig.xml and settings.xml are searched in the "system directories". For the moment these are (in this order) ~/.openMSX/ ~/openMSX/ /usr/local/etc/openMSX/ /etc/openMSX/ But we need to discuss about this list. Note that "." is not in the list. 2003-01-27 David Heremans * Increased usefulness of error message in DeviceFactory 2003-01-26 Wouter Vermaelen * Automatically load "settings.xml" on startup: - you can specify an alternative file with the "-setting" command line option - For the moment "settings.xml" must be in the current directory * Moved hard-coded HotKey bindings to settings.xml 2003-01-26 Wouter Vermaelen * Fixed Z80/R800 switching again * Removed "sync_interval" parameter from RealTime 2003-01-26 Maarten ter Huurne * Relaxed over-zealous assert in PixelRenderer by one tick. 2003-01-25 Wouter Vermaelen * Fixed TurboR assertion: targetTime wasn't set properly when switching CPU (Z80 <-> R800) * Added dummy MSX-Midi: Illusion city works now! 2003-01-22 Wouter Vermaelen * Reworked MSX-MUSIC / FMPAC: removed enable/disable logic from MSX-MUSIC 2003-01-22 Wouter Vermaelen * Renamed "TurboR" FDC type to "Panasonic" * Fixed "cpudebug" command (I broke it yesterday) * Fixed MSXMatsushita: this caused FS-4700 build-in word processor hang * Removed ROM files from CVS 2003-01-21 Wouter Vermaelen * Allow muliple commands with the same name: - for example both Z80 and R800 could have a "cpudebug" command at the same time (not implemeted yet) 2003-01-20 Wouter Vermaelen * Removed SearchPath stuff * Relative paths in config files are now relative to the location of the config file: - this together with the change above probably means you have to rewrite some/all of your config files * Moved some #include's from .hh to .cc 2003-01-16 Wouter Vermaelen * Implemented 8-bit unsigned 16-bit signed DAC * Implemented MSX-AUDIO 13-bit DAC: untested because TurboR (needed for modplayer) is broken for the moment 2003-01-16 Wouter Vermaelen * Fixed MSXF4Device: FS-A1FX (re)boots correctly now 2003-01-15 Wouter Vermaelen * Removed possibility to use other than XML config files 2003-01-14 Wouter Vermaelen * Fixed Kanji12 * Removed some unused classes from config directory 2003-01-13 Wouter Vermaelen * Implemented Kanji 12x12: - not tested, I don't have correct ROM * Implemented front switch: - use console command "frontswitch [on/off]" - not tested yet, will do it tomorrow 2003-01-12 Wouter Vermaelen * Added powerOff() method to MSXDevice * Removed getCurrentTime() calls from destructors 2003-01-11 Wouter Vermaelen * removed MSXCPU::getTargetTime() method * added EmuTime argument to the execute() method of a Command: this removes some of the calls to MSXCPU::getCurrentTime() * An empty SCC cartridge no longer needs "/dev/zero" as filename, just don't specify a filename: this also works in OSes that don't support /dev/zero * Removed "readromfile" parameter from SCCPlusCart: if you don't want to read a rom just don't specify one * Better(?) rom placement heuristics: fixes zanac 2003-01-09 Wouter Vermaelen * Fixed CPU on big endian machines * TurboR PCM update: - It now works in Seed of Dragon, although the frequency seems wrong. The game also hangs when you start to play. 2003-01-06 Wouter Vermaelen * Fixed CPU HALT instruction * Semi-fixed CPU/Scheduler interaction: - seems to work, but I still need to take a very close look at the code 2003-01-06 Wouter Vermaelen * Large CPU rewrite: - we never perform IO at a timestamp greater or equal than the earliest sync point - 1% - 2% slowdown compared to previous implementation - ZEXALL tests still pass - still need to verify if the timimng of the instructions is still correct 2003-01-05 Maarten ter Huurne * Fixed mode number for Text 2 in VDP::updateColourBase. * Recalculate pattern and colour base when switching modes. 2003-01-04 Maarten ter Huurne * Implemented colour and pattern index mask calculation. * Fixed remaining Doxygen warnings. 2003-01-03 Maarten ter Huurne * Fixed Doxygen warnings. * Fixed I/O port handling of Matsushita switched device, which I forgot in yesterday's changes. 2003-01-02 Maarten ter Huurne * Moved I/O port registration out of VDP and into DeviceFactory. Eventually, all I/O port registration should move to the machine description file. * Moved I/O port registration of all other I/O devices out of the device and into DeviceFactory (or FDCFactory in one case). 2003-01-01 Maarten ter Huurne * Workaround to prevent out-of-order I/O with the VDP. * Added asserts to VDP code to check timing constraints. * Fix for sprites in planar modes. * Fixed behaviour of port #99, #9A and #9B interference. * Colour and pattern base mask calculation are now done in a single method each. 2002-12-30 Joost Yervante Damad * updated AUTHORS 2002-12-30 Maarten ter Huurne * Let VDPVRAM::Window notify its observer of base/index changes and windows that become disabled. 2002-12-30 Wouter Vermaelen * Autodetect BASIC ROM cartridges: code mostly taken from windows port * Don't abort when "romdb.xml" is missing, just print a warning. * Added a lot of const qualifiers * Implemented TurboR PCM device: - only playing (no recording) - sound quality is really bad (like all our DACSound based devices) 2002-12-29 Maarten ter Huurne * The VDPVRAM::VRAMObserver interface is put to use for the first time, by SpriteChecker. Cleans up the VDP subsystem synchronisation code. 2002-12-29 Wouter Vermaelen * Released 0.2.1 * Better rom placement heuristics: should solve bug 658430 point 3 (rom files without "AB" header) * Allow filenames that contain a ',' on the commandline * Support roms that don't like to be mirrored: - solves bug 658430 point 1 (BASIC prog at 0x8000) - can't be autodetected (yet?), you must specify romtype as "PAGE[...]" (examples "PAGE2", "PAGE01", "PAGE12", ...) - romdb.xml should be updated for these 'problem' roms 2002-12-28 Maarten ter Huurne * Cleaned up sprite checker synchronisation. 2002-12-27 Maarten ter Huurne * Created VDPSettings class, moved cmdTiming there. * Moved limitSprites from config to VDPSettings. 2002-12-23 Wouter Vermaelen * Support for second MSX-AUDIO: - add this to the AUDIO section in the config 2 - alex' modplayer doesn't work yet :-( 2002-12-23 Wouter Vermaelen * Tabcompletion for setting values: example: set accuracy p * Implemented "cmdtiming" setting: Switch between real or instantaneous VDP command timing. Should only be used for debugging purposes. * Implemented YJK/YAE (screen11/12) rendering 2002-12-22 Wouter Vermaelen * Updated RealTime class: - this should improve the cases where emulation is too slow, but openMSX doesn't claim 100% CPU time * SDLLoRenderer inherits from PixelRenderer iso Renderer - SDLLoRenderer now also has "accuracy" setting 2002-12-18 Wouter Vermaelen * Made some IDE stuff configurable: - master/slave device - HD image - HD size is not yet configurable - see someconfig.xml for format of new config 2002-12-17 Wouter Vermaelen * Implemented MSX Bunsetsu Henkan / Jisyo: - I don't have much info on this device, but at least BUNSDUMP.BAS gives the correct result 2002-12-14 Wouter Vermaelen * Fixed assertion in VDP when computer was reset while in PAL mode 2002-12-13 Wouter Vermaelen * Implemented MSXDeviceSwitch: - IO-ports 0x41-0x4F are (possibly) shared between several devices - IO-port 0x40 selects the active device * Converted MSXS1985 to a MSXSwitchedDevice * Converted MSXFS4500SRAM to a MSXSwitchedDevice: - renamed to MSXMatsushita - implemented PUT KANJI function (IO-ports 0x43-0x44) 2002-12-09 Wouter Vermaelen * Implemented correct mirroring for National based FDC's 2002-12-08 Wouter Vermaelen * Implemented mmap() in File classes: - map a file direclty into the process memory range, the OS can perform some optimizations this way: - only read data when actually used - faster swapping - ... - I'm not sure non-UNIX OSes (windows) have a similar function, if not there is a fallback implemented in FileBase (we also need this fallback for FTP, HTTP, ... files) * Simplified/updated MSXRomDevice: - simpler interface: you no longer need to inherit from MSXRomDevice - use mmap() to read files * Implemented FS4500SRAM (suggestions for a better name are welcome): put this in your config file: FS4500SRAM true true FS4500.SRAM 2002-12-04 Wouter Vermaelen * added SRAM class: This is just a small helper class that offers loading and saving of a byte-array. For the moment only MSXRom and MSXFmPac use it. 2002-12-01 Wouter Vermaelen * added CRC16 routine, can be used by FDC / FDCBackends * fixed TC8566AF: completely forgot to send step/side/motor commands to the DiskDrive 2002-11-30 David Heremans * Still working towards raw track read 2002-11-30 Wouter Vermaelen * fixed auto frameskip: probably still needs some finetuning 2002-11-29 Wouter Vermaelen * Fixed FDC bug (DriveMultiplexer) 2002-11-27 David Heremans * Working towards raw track read 2002-11-18 Wouter Vermaelen * FDC rewrite, introduced DiskDrive and DriveMultiplexer classes: - this is a relatively large rewrite, so I may have broken something. I wanted to commit rather soon, so I only tested the Philips FDC. - WD2793 Type I commands are now executed the same as in the flowcharts of the technical datasheet (verify step not yet). 2002-11-18 David Heremans * a small WD2793 update: quick implementation of multi sector read not tested and DSK backend needs to be changed for correct behaviour! 2002-11-18 Wouter Vermaelen * Mapped TurboR keys to left and right windows key: I haven't checked this, so possible I swapped left and right 2002-11-17 Wouter Vermaelen * a few WD2793 updates (David please review): MicrosolFDC partly works now: in BASIC the commands files load save work, but starting a game from disk doesn't work. * converted "deinterlace" from a command to a setting: - you should now use "set deinterlace [on|off]" instead of just "deinterlace [on|off]" * converted "accuracy" from a command to a setting: - you should now use "set accuracy [line|pixel|screen]" instead of just "accuracy [line|pixel|screen]" 2002-11-16 Wouter Vermaelen * renamed BrazilFDC to MicorsolFDC: doesn't work yet, I'm investigating... * cleanups, small fixes in WD2793 2002-11-13 Wouter Vermaelen * Implemented 'PUT KANJI acceleration' of S1985: This fixes the font printing problem of the FS-4600F internal software. AFAIK the FS-4600F now works completely (or at least as good as NMS8250). You need to add this to the FS-4600F config file: S1985 2002-11-11 Wouter Vermaelen * National mapper type fixes: - FS-4600F internal software seems to work, but I'm not certain the 1st and 2nd menu option work as they should. Can someone verify this? * Fixed National FDC 2002-11-11 David Heremans * completed timing in WD for writeTrack command: Timing still needs some fine tunning, but not much * implemented writetrackdata in DSK backend: Formatting with _format in BASIC seems to work Need to compare with some real disks just to be sure. 2002-11-10 Wouter Vermaelen * fixed PhilipsFDC: IDEFDISK doesn't coredump anymore * use old style file functions: should work with both old and new gcc 2002-11-08 Maarten ter Huurne * Improved scanline emulation: the dark line is now an interpolation of the visible lines above and below. * Added horizontal blur: emulates the fact that MSX monitors and TVs are not as sharp as typical PC monitors. Many game graphics are drawn with this blurring in mind and look poor on a sharp display. Note: scanlines and blur are implemented only in the GL renderer. 2002-11-03 Wouter Vermaelen * added VDP reset() methods (not finished) 2002-11-03 Maarten ter Huurne * Initial version of generic settings is working now. Currently there is only 1 setting: scanline alpha. 2002-11-03 Wouter Vermaelen * minor cleanups * fixed a few uninitialized variables: - discoverd with valgrind - some were harmless, fixed anyway to keep valgrind quiet 2002-11-02 David Heremans * Added two calls to the FDC backend: These should later be used to enable the writeTrack command 2002-11-02 Wouter Vermaelen * Basic TC8566AF disk controller support (TurboR): - Based on code from NLMSX written by Frits Hilderink - TurboR boots now! It still has a lot of missing features though. 2002-11-01 Maarten ter Huurne * Scanline emulation in SDLGLRenderer. Very rudimentary in this stage, but it works. 2002-10-31 Maarten ter Huurne * Changed SDLGLRenderer to make it a subclass of PixelRenderer. * Minor cleanups in SDLHiRenderer. 2002-10-27 Wouter Vermaelen * rewrite of FDC infrastructure: - the existing diskroms are too different to handle them all in one class -> made FDCFactory that instantiates the right object - Implemented support for: - Philips based diskroms: were already supported - Brazil based diskroms (needs better name?): IO-based were already supported, but I never tested them I also didn't test the new implementation - National based diskroms (needs better name?): also untested The three diskroms above are all build around the WD2793 (or compatible) controller. - Configuration format changed slightly, see msxconfigFDC.xml for details 2002-10-29 Maarten ter Huurne * Minor cleanups in PixelRenderer and SDLHiRenderer. 2002-10-27 Wouter Vermaelen * Implemented mapper type "NATIONAL": completely untested, will have to wait till this weekend 2002-10-27 Wouter Vermaelen * Replaced all file reading related classes with a new class structure. This structure: - has a simpler and more robust interface - is easier to port(?) a large part of the WIN32 patch deals with file reading stuff - has real filetype separation: rom, disk, tape, config files have their own searchpaths 2002-10-26 Wouter Vermaelen * Implemented "CartridgeSlotManager" 2002-10-25 David Heremans * "frameskip auto" completed 2002-10-24 Wouter Vermaelen * Cartridges are only inserted after configuration is parsed, this allows in the future to have configurable cartridge slots * Added PANASONIC mapper type (for example used in Turbo-R for MSX-VIEW ROM). Untested!! 2002-10-23 Maarten ter Huurne * Fixed bug in sprite drawing of SDLLo. * Fixed bug in initialisation of horizontal adjust. * PixelRenderer now offers left border calculation to its subclasses. * Bug fixes and cleanups in coordinate calculation of SDLHiRenderer::drawDisplay; SCREEN0 no longer fails the asserts. * Cleanups in SDLHiRenderer::drawSprites. Should be applied to other Renderers as well. 2002-10-22 Wouter Vermaelen * Rewrite of "CommandLineParser": - CLI options and CLI file types must be registered now, this allows to move the implementation of, for example the "-fmpac" option, to the MSXFmPac class where it belongs. - This should keep the CLI more up to date with code changes and make it easier to support new CLI options / file types. 2002-10-21 David Heremans * "frameskip auto" implementation started 2002-10-21 Wouter Vermaelen * Replaced "Simple64Kb" device with "RAM" device. This device also supports random sizes, so it is possible to make 8Kb machines. 2002-10-20 Maarten ter Huurne * Moved subdivision of area-to-be-rendered from SDLHiRenderer to PixelRenderer. Also changed subdivision approach: before, displayPhase would draw display area and the borders; while the new drawDisplay only draws the display area and all the border colour drawing is done by drawBorder. 2002-10-19 Wouter Vermaelen * Implemented frameskip command: TODO "frameskip auto", skip frames depending on CPU speed 2002-10-13 Wouter Vermaelen * Some IDE fixes: Harddisk works now! Following is still hardcoded: master: harddisk, size 1GB filename "hd.dsk" slave : not connected I'll change this in the next couple of days 2002-10-16 Joost Yervante Damad * removed compile-time disables for now 2002-10-13 Maarten ter Huurne * Moved absolute X coordinate to screen X coordinate translation from PixelRenderer to SDLHiRenderer. 2002-10-13 Wouter Vermaelen * Implemented Sunrise IDE interface: only interface, no IDE devices yet * Implemented very simple IDE harddisk: IDE-BIOS detects the disk IDE Master:LBA,Mode 4:OPEN MSX HARD DISK IDE Slave :not detected. but the disk can't be used. Probably because the HD isn't partioned yet. 2002-10-12 Maarten ter Huurne * Split off some functionality from SDLHiRenderer into new PixelRenderer. PixelRenderer will contain the common code of the pixel-based renderers (SDLHi, SDLLo, SDLGL). 2002-10-12 Wouter Vermaelen * Fixed sound on high endian machines (untested) 2002-10-07 Wouter Vermaelen * Horizontal adjust is only updated the next scanline: fixes some glitches in the "threedee rules" part of "relax" 2002-10-05 Maarten ter Huurne Last minute Bussum fixes: * Temporarily reverted name table base fix. This fix needs modifications in the renderers as well and I don't have time right now to find out exactly where. * Rewrote PhaseHandlers to specify rectangles in VDP absolute coordinates and using exclusive limits. Fixes crashes on MSX1 and excessive draws, maybe more. Unknown Reality shows only minor glitches, so we can demo with pride! 2002-10-03 Maarten ter Huurne * Fixed bug in VDP: name table base was not adjusted for planar modes. This caused wrong rendering in SCREEN7/8. * Disabled "inside window" check for now. Parts of the code are not ready yet to use the VRAM windows properly. 2002-10-02 Maarten ter Huurne * Updated C-BIOS to version 0.13. 2002-09-30 Maarten ter Huurne * The Great VRAM Rewrite, part 4: Introduction of VRAMObserver interface. Not used yet. * Minor VRAM cleanups. 2002-09-29 Wouter Vermaelen * Read/Write memory cache for SCC+ * Unified Xanadu and Royal Blood mapper types 2002-09-28 Wouter Vermaelen * SCC+ didn't work at all, fixed it. Fixes Snatcher and SDSnatcher. * Fixed RoyalBlood mapper type (not sure about Xanadu) * Fixed Majutsushi, bad sound quality (DAC) though * Implemented CrossBlaim mapper type (untested) 2002-09-28 Maarten ter Huurne * Updated C-BIOS to version 0.12. * I forgot that the cbios.xml used TMS99x8A. With a V9938, C-BIOS can run many MSX2 ROMs as well. I created two separate config files: cbios-msx1.xml and cbios-msx2.xml. 2002-09-25 Wouter Vermaelen * GLFont uses 16x16 character texture instead of 256x1 2002-09-22 Maarten ter Huurne * Updated C-BIOS to version 0.11. Boukichi sure understands "release early; release often". :) 2002-09-20 Maarten ter Huurne * Updated C-BIOS to version 0.10. 2002-09-17 Maarten ter Huurne * Updated C-BIOS to version 0.08. 2002-09-08 Wouter Vermaelen * Added "deinterlace" command: switch at-run-time between stable and unstable interlace only SDLHiRenderer and SDLGLRenderer 2002-09-08 Maarten ter Huurne * Updated C-BIOS to version 0.07. 2002-09-08 Wouter Vermaelen * Enabled pixel accurate rendering in SDLHiRenderer: - for some reason the NOTONLYSNESCANDOTHIS part in Unknown Reality is not extremely slow (it is when using SDLGLRenderer) - remember you have to manually enable pixel accurate rendering with the command "accuracy pixel" * Enabled pixel accurate rendering in SDLLoRenderer 2002-09-08 Joost Yervante Damad * mv configure.in -> configure.ac [autotools recommendation] * configure exits on failure to find SDL_image library 2002-09-07 Wouter Vermaelen * Correct blending in all non-paletted modes, no blending in paletted modes * Implemented "accuracy" command. This allows to switch at-run-time between pixel/line/screen accuracte rendering. For the moment no screen accurate rendering and only for SDLGLRenderer. 2002-09-02 Wouter Vermaelen * Dont't stop when sound device couldn't be opened, just continue without sound. 2002-09-01 Wouter Vermaelen * Added some Turbo-R specific devices: - MSXTurboRPauze: without this BIOS gets stuck in pauze loop - MSXS1990 (minimal): Needed to switch between Z80 / R800 - MSXF4Device (needs a better name): to distinguish between cold/warm boot and Z80/R800 boot-pass - MSXTurboRLeds Turbo-R doesn't boot yet :-( 2002-09-01 Maarten ter Huurne * Updated C-BIOS to version 0.06. 2002-08-31 Maarten ter Huurne * The Great VRAM Rewrite, part 3: SpriteChecker does all of its VRAM reading through VDPVRAM::Window. Only CPU does non-windowed reads anymore. All planar remapping occurs outside of VDPVRAM. 2002-08-31 Wouter Vermaelen * Move common code from SDLConsole and GLConsole to a new parent class * Added R800: there is nothing implemented to switch from Z80 to R800, so for the moment R800 is not yet usable 2002-08-30 Wouter Vermaelen * SDLGLRenderer: small cleanups when interlaced, display odd fields half a line lower 2002-08-29 Wouter Vermaelen * FDC supports 2 drives 2002-08-28 Wouter Vermaelen * Implemented pixel-accurate rendering in SDLGLRenderer: - scope part in Unknown Reality is correct now! - some parts are _very_ slow now, implementation can still be optimized a lot and speed should also improve when VDPVRAM is finished. * Implemented "vdpregs" command to help vdp debugging 2002-08-26 Maarten ter Huurne * Updated C-BIOS to version 0.05. 2002-08-26 Wouter Vermaelen * Implemented partial character rendering for SDLGLRenderer: - fixes top border while vertical scrolling in Space Manbow - fixes Psycho World * Implemented vertical scrolling in TextMode1 in SDLGLRenderer 2002-08-25 Wouter Vermaelen * Use SDL_image to load images (.png .bmp .jpg ...) * Implemented GLConsole for SDLGLRenderer - old console didn't always work for SDLGLRenderer - a lot faster - per pixel alpha blending: ConsoleBackground and ConsoleFont can be true RGBA images (current pictures are .bmp, they don't have an alpha channel) 2002-08-22 Wouter Vermaelen * Implemented R-Type mapper * Fixed vertical scrolling in text modes (except TextMode1 in SDLGLRenderer) 2002-08-21 Maarten ter Huurne * Added C-BIOS 0.04 to Contrib directory. * Added config XML for using C-BIOS to src/cfg. 2002-08-18 Wouter Vermaelen * VDP code: - trivial fixes / cleanups - low resolution (SDLLo) rendering improvements try SCREEN 0 WIDTH 80 only 24bpp modes yet * Z80 fix: IFF2 should be reset while acknowledging an IRQ this fixes music in "Break-In" 2002-08-16 Wouter Vermaelen * major cleanups, minor tweaks / fixes 2002-08-16 Maarten ter Huurne * Fixed wrong use of sizeof(array): it returns #bytes, not #elements. Caused a segfault on Sparc. 2002-08-14 Wouter Vermaelen * Fixed SD-Snatcher loading bug: - disk has a non-standard bootsector (sectors per track and number of sides) is wrong. Updated heuristics to detect 9 sectors per track and 2 sides in case disk image has 80*2*9 sectors. How does the BDOS read this disk? Perhaps it uses the media descriptor? 2002-08-13 Wouter Vermaelen * Split MSXMotherBoard in: - MSXCPUInterface: handles communication between a MSXDevice and CPU - registration of IO- and memory-space - slotselection mechanism - MSXMotherboard: keeps a list of all MSXDevices 2002-08-11 Wouter Vermaelen * more Z80 cleanups * commands can now also be bound to key releases (was only keypresses) example: unbind F9 bind F9 "throttle off" bind f9,up "throttle on" this disables throttling while you hold F9 2002-08-10 Wouter Vermaelen * Z80 fixes: V_FLAG was wrong with SUB/SBC/CP instructions * Z80 fixes: XF and YF flags where wrong with BIT n,r instruction * major Z80 cleanup: - greatly reduced code duplication in CPUCore.n2 2002-08-09 Wouter Vermaelen * More robust Thread class * cleanups, especially in JoyNet * fix bug [592594] MSXFDC doesn't work with single sided disks 2002-08-08 Wouter Vermaelen * Z80 cleanups / fixes: especially fixes for the 2 undocumented flags (bit 3 and bit 5) * New config file format for inserted disks / tapes * Applied Manuel's patch [592591]: romdb.xml is searched with FileOpener 2002-08-06 Wouter Vermaelen * Full support for .XSA disk images 2002-08-05 Wouter Vermaelen * Enabled SDLConsole in SDLGL renderer: current code is only a temporary solution for the following reasons: - use of SDL_OPENGLBLIT is heavily discouraged, see http://www.libsdl.org/pipermail/sdl/2001-November/039852.html - transparancy doesn't work in 16-bit video modes (an SDL limitation) in the future we should make a native GLConsole 2002-08-04 Wouter Vermaelen * Allow multiple "md5" tags per "rom" tag * Low level tape support (experimental) 2002-08-03 Wouter Vermaelen * wrote doc/commands.txt * cleanups, small fixes in RomTypes and MSXRom * added some entries in romdb.xml 2002-08-01 Wouter Vermaelen * EmuTime cleanups: introduced EmuDuration class - the subtraction of 2 EmuTimes results in an EmuDuration, this used to result in another EmuTime - an EmuDuration can be added to / subtracted from an EmuTime, this results in another EmuTime * DACSound: - totally discarded old implementation - new implementation is extremely simple, relatively slow, has poor sound quality, but is bug free 2002-07-31 Wouter Vermaelen * Made "check-db": a primitive tool to compare a romtype database against the build-in rom autodetection algorithm * When console is up, keypresses are blocked for MSX-Keyboard: this implementation should work in all cases (hopefully) 2002-07-29 Wouter Vermaelen * cleanups * Support for "ROM database" 2002-07-26 Wouter Vermaelen * Experimental support for .xsa disk images * Removed "automappertype" parameter, you can still explicitly specify autodetection by setting "mappertype" to "auto" * Added MD5 class: will be used for mappertype detection of difficult ROMs 2002-07-24 Maarten ter Huurne * Added debug option NOTIME_EXECUTION, to make VDP command execute in zero EmuTime. Useful for tracking "no/partial images" bugs. 2002-07-23 Wouter Vermaelen * MSXDiskRomPatch supports 2 drives again 2002-07-22 Maarten ter Huurne * The Great VRAM Rewrite, part 2: SpriteChecker does most of its VRAM reading through VDPVRAM::Window. Updated doc/vram-addressing.txt with details of sprite indexing. Proper support for planar modes is still missing. 2002-07-21 Maarten ter Huurne * The Great VRAM Rewrite, part 1: Renderers do all their VRAM reading through VDPVRAM::Window now. See doc/vram-addressing.txt for background info. 2002-07-14 Wouter Vermaelen * Implemented "bind" and "unbind" command 2002-07-14 Wouter Vermaelen * Moved event related files to new directory * Added Keys class: translates keyCodes to keyNames and vice versa 2002-07-09 David Heremans * Improved portbased interface to MSXFDC 2002-07-08 Wouter Vermaelen * Enabled console in SDLLoRenderer 2002-07-07 Wouter Vermaelen * Reenabled SDLLoRenderer: - just a copy-paste from SDLHi and adjusted a few routines - console is disabled in SDLLo - not very well tested yet 2002-07-07 David Heremans * Added portbased interface to MSXFDC for Brazilian MSX's 2002-07-06 Wouter Vermaelen * Moved cassette related code to its own directory * Cleanups 2002-06-30 David Heremans * Added index mark to the FDC status register (not tested yet!) 2002-06-28 Wouter Vermaelen * Initial version of DiskImageManager (WIP): - insert disk from commandline doesn't work yet - MSXFDC compiles, but not yet tested - MSXDiskROMPatch only supports 1 drive - FDC_DSK backend only supports double sided disks * Insert disk from command line works again: - just a hack, we need a better configuration format for this * MSXFDC still seems to work: - config file changed slightly, see msxconfigFDC.xml - David please check my modifications, especially the error handling code 2002-06-27 Wouter Vermaelen * MSXRom fix: map 32kb ROMs in the right place 2002-06-22 Wouter Vermaelen * Small Mixer fix, cleanups 2002-06-21 Wouter Vermaelen * Enabled console background * Console font and background filename are read from config file 2002-06-20 Wouter Vermaelen * Moved thread related code to its own subdirectory * Added unregisterHotKey() and unregisterHotKeyCommand() methods * Enabled "renderer" command 2002-06-19 Wouter Vermaelen * Console commands and HotKeys are now executed by the main thread: - this removes some (potential) races, so some bugs like "This crashed once, but I can't reproduce it" might be gone. - this is a rather fundamental change, it might still contain some glitches. * Removed all registerAsync__() methods: they aren't used anymore and they shouldn't be in the future * Introduced condition variables (thread locking): used in Scheduler to implement pause * Added unregisterEventListener() method 2002-06-18 David Heremans * Basic timing for all FDC Type I commands 2002-06-18 Wouter Vermaelen * Tab-completion now works for zero-length tokens 2002-06-16 Wouter Vermaelen * Use exceptions for command error reporting 2002-06-15 Wouter Vermaelen * Simplified MSXRomPatchInterface 2002-06-14 Wouter Vermaelen * Scheduler fixes * Removed MSX16KB device: MSXGameCartridge can do everything what MSX16KB could * Renamed: MSXRom -> MSXRomDevice MSXGameCartridge -> MSXRom * Moved common code to MSXRomDevice * "filesize 0" no longer means autodetection, instead use "filesize auto" (or just don't mention filesize) 2002-06-12 Wouter Vermaelen * ConsoleCommand parser now understands quoted tokens or tokens with escaped characters. Examples: diska a\ space.dsk diska "a space.dsk" diska a\"quote.dsk It should now be possible to enter any string as a command token. 2002-06-11 David Heremans * basic FDC fully working: Fixed minor bug (all my tested disks work now) Implemented small speed up 2002-06-10 Wouter Vermaelen * Commands can now be unregistered again * cleanups 2002-06-10 David Heremans * basic FDC implemenation partialy working: DSK backend working All Type I functions working Type II partial (readSector OK, writeSector doesn't check for write protected images yet) No Type III commands yet 2002-06-06 David Heremans * First step towards an FDC implemenation: Basic structure is provided MSXFDC as good as finalized. 2002-06-05 Joost Yervante Damad * preliminary auto* libpng support [David is learning auto* woowoo!] 2002-06-04 Wouter Vermaelen * Implemented "speed" command, try "help speed" in console 2002-06-02 Wouter Vermaelen * MSXGameCartridge cleanup 2002-05-28 Wouter Vermaelen * Fixed PAINTER.ROM: mappertype autodetection doesn't work, start with ./openmsx roms/PAINTER.ROM,64kb * Fixed MSXSCCPlusCart: delete SCC in destructor ignore reads/writes outside address space 2002-05-27 Wouter Vermaelen * writing the same value twice to an (output) connector doesn't change the status of that connector, so don't call the write method of that connector twice (Joystick, PrinterPort, Y8950Keyboard) 2002-05-23 Bas Wijnen * Mutexes added around PRT_* macros 2002-05-22 Maarten ter Huurne * SDLGLRenderer uses block textures in Text1 (SCREEN0.40). * SDLGLRenderer uses block textures in Graphics2 (SCREEN2). * Rendering sprites in SDLGLRenderer is a lot faster now, because textures aren't immediately destroyed after drawing. 2002-05-19 Wouter Vermaelen * small sprite rendering optimizations 2002-05-18 Bas Wijnen * Added files for Xlib renderer. Mostly unimplemented. 2002-05-15 Maarten ter Huurne * SDLGLRenderer uses textures. 2002-05-15 Bas Wijnen * g++-3.0 fixes 2002-05-15 David Heremans * JoyNet cable using TCP/IP implemented 2002-05-13 Wouter Vermaelen * converted some ints to unsingned ints 2002-05-13 Joost Yervante Damad * fixed some files mising in the Makefile.am 2002-05-12 Wouter Vermaelen * made "diska" and "diskb" commands 2002-05-09 Joost Yervante Damad * File, HttpFile, LocalFile [work in progress] 2002-05-08 Bas Wijnen * fixed console-key-pass-bug and some minor things 2002-05-06 David Heremans * Skeleton files for JoyNet configuration 2002-05-05 Bas Wijnen * Game Master 2 is working with SRAM and guessing. 2002-05-03 Bas Wijnen * Created src/FileType.hh for g++-3.0 and some g++-3.0 fixes 2002-05-02 Maarten ter Huurne * Calculate pixel precision coordinates. They are approximately right, but not exactly right yet. 2002-05-02 Joost Yervante Damad * File, HttpFile, LocalFile [work in progress] 2002-05-01 David Heremans * Skeleton files for JoyNet device 2002-04-30 Joost Yervante Damad * s/-fhuge-objects//g * verious g++-3.0 fixes 2002-04-29 Maarten ter Huurne * SDLGLRenderer is working again. * SDLGLRenderer now uses CharacterConverter and BitmapConverter. * Removed double buffering from SDLHiRenderer. It didn't work and without double buffering more optimisations are possible. * Renderers use screen position instead of EmuTime for internal update targets. It was like this before the VDPVRAM introduction and with the cleaned up implementation it is possible again. It also fixes a bug where too many lines were scanline converted. This caused no glitches, but it did waste CPU cycles. * Also made SpriteChecker use screen position rather than EmuTime. 2002-04-29 Joost Yervante Damad * small cleanup FilePath [sorry guys!] * more proper cleanup 2002-04-28 Maarten ter Huurne * Some VDP cleanups. 2002-04-28 Joost Yervante Damad * FilePath stuff for future FileManager 2002-04-28 Wouter Vermaelen * Tab-completion updates 2002-04-28 Maarten ter Huurne * Removed re-entering of SpriteChecker::sync and Renderer::sync. Display enable/disable changes now go through VDPVRAM. Various other cleanups. Most sprite glitches are fixed now. 2002-04-28 Bas Wijnen * Added includes, std:: prefixes and unsigned char casts to allow compile with g++-3 2002-04-27 Maarten ter Huurne * Display mode changes now go through VDPVRAM. It also performs planar reorder on single-byte reads and writes. 2002-04-25 Wouter Vermaelen * VDP Text2 dirty colour table check 2002-04-24 Maarten ter Huurne * Fixed a bug in SpriteChecker and also simplified it. 2002-04-24 Wouter Vermaelen * more accurate VDP command timing: 2002-04-23 Wouter Vermaelen * Enabled VDP command timing: recalculated table, no difference 50/60Hz anymore 2002-04-23 Maarten ter Huurne * Re-enabled SDLGLRenderer. 2002-04-22 Wouter Vermaelen * tuned relative volume Y8950-ADPCM / -FM (thanks Manuel) 2002-04-18 Maarten ter Huurne * Separated sprite checking into a separate class. Works, but with glitches. 2002-04-17 Joost Yervante Damad * added tools/ dir and bin2c tool * added --disable-SCC * added --disable-FMPAC * added --disable-MSXMUSIC * more "--disable-XXX"'s to come ;-) 2002-04-16 Maarten ter Huurne * More VDPVRAM and related cleanups. 2002-04-15 David Heremans * Enhancements to the developpers FAQ 2002-04-15 Joost Yervante Damad * extended commandline parser to support keyinserter 2002-04-15 Wouter Vermaelen * Extended KeyEventInserter so it can be used in Tilburg * Fixed YM2413 drums: playing the same drum twice now works 2002-04-13 Maarten ter Huurne * VDPVRAM and related cleanups. 2002-04-12 Wouter Vermaelen * DACSound cleanup (still has the same(?) bug) * EventDistributor simplification: use Scheduler for synchronous event delivery thanks Maarten for the idea * Made Scheduler thread safe * #include cleanups * Y8950 generates an IRQ on end-of-sample _in_EmuTime_ (was realtime) * Implemented Y8950 keyboard connector: just the connector, no keyboard yet * More accurate ADPCM readback (thanks Maarten) 2002-04-11 David Heremans * Royal blood works, use type ASCII8SRAM2! Xanadu resets to Basic, Hydlide2 hangs. 2002-04-11 Maarten ter Huurne * Introduced "renderer" command to print current renderer and switch to another one. However, switching from SDLHi to SDLGL hangs SDL. Well, at least the renderer destructors are finally implemented. 2002-04-11 Wouter Vermaelen * Finally (?) fixed Y8950 samples: "Arranger 4" sounds ok now * Implemented ADPCM readback (untested) 2002-04-11 David Heremans * Royal blood works, use type ASCII8SRAM2! Xanadu resets to Basic, Hydlide2 hangs. 2002-04-11 David Heremans * Added (not tested) support for ASCII8SRAM type to MSXGameCartridge. 2002-04-08 David Heremans * Added support for Hydlide2 type to MSXGameCartridge. * Added generic support for SRAM to MSXGameCartridge. * Enhanced CommandLineParser to support SRAM filenames for games. 2002-04-08 Maarten ter Huurne * Took character scanline conversion out of SDLHiRenderer as well, its new home is a class called CharacterConverter. 2002-04-07 Maarten ter Huurne * Took bitmap scanline conversion out of SDLHiRenderer and put it in a separate class called BitmapConverter. It is necessary to reduce the size of SDLHiRenderer, because its source had become too large to manage (over 1250 lines). 2002-04-06 Wouter Vermaelen * small update to doc/developersFAQ.txt 2002-04-05 Joost Yervante Damad * added --enable-interlacing ./configure argument see also ./configure --help and src/config.h.in after rerunning ./autogen.sh 2002-04-04 Wouter Vermaelen * Console cleanup * Console is no longer a singleton, instead there is now a singleton ConsoleManager. All Consoles must register with the ConsoleManager. * Made InteractiveConsole subclass of Console: SDLConsole inherit from InteractiveConsole a logging-only console inherits from Console * Bumped version number * ConsoleFont.bmp can now be in any configured directory 2002-04-04 David Heremans * Added more debug info to MSXGameCartrdige. * Fixed an error in SCCplus cartridge * Addapted SCCplus cartridge to use FileOpener 2002-04-03 Wouter Vermaelen * Moved ADPCM part of Y8950 to its own file * Fixed ADPCM bug: addresses where a factor 8 too low, only first sample was played correctly * Made console command "fullscreen", PrtScrn is bound to this command * Console command "mute" takes on/off parameter * Console command "pause" takes on/off parameter * Made console command "console", F10 is bound to this command 2002-04-01 Joost Yervante Damad * FileManager sync [WIP] * a first proposal for a FileManager API, I'll implement it soon if noone objects [or at least something like this!] 2002-03-31 Maarten ter Huurne * Cleaned up VRAM implementation a little. Still plenty of work to be done. 2002-03-30 Maarten ter Huurne * Added new VDPVRAM class, which manages VRAM. It is far from finished, but even in the current state it should be no less accurate than what we had before. 2002-03-30 Joost Yervante Damad * made CommandlineParser xml entity safe 2002-03-28 David Heremans * Added MC6850 and sampleram to '-musmod' CLI option. * Added article written for the Dutch MSX-infoblad. The png's are ment to be printed and where not scaled for webpurposes. 2002-03-28 Wouter Vermaelen * Added dummy MC6850 (ACIA used in MSX Music Module for MIDI): "FAC demo 3" now detects MSX Music Module 2002-03-28 Wouter Vermaelen * amount of sample-ram is now configurable 2002-03-27 Wouter Vermaelen * implemented Y8950 timers * implemented "throttle" command 2002-03-26 Wouter Vermaelen * fixed crash when a specified file did not exists: for some reason C++ doesn't throw exception beyond the constructor, even explicitly (re)throwing an exception in the constructor doesn't work 2002-03-25 Joost Yervante Damad * made VPATH compiles work, fixes 'make distcheck' also * bumped version no 2002-03-25 Wouter Vermaelen * implemented RYTHM sounds for Y8950 (MSX-AUDIO): untested! * reworked IRQ handling: CPU keeps IRQStatus (was CPUInterface) this way CPU can react directly on unannounced IRQs (eg: Y8950 end-of-sample IRQ) 2002-03-24 David Heremans * Added more helptext to the CommandlineParser. * Made the 64 kB roms Manuel send me working. * Enhanced the autodection of MSXGameCartrdige. 2002-03-24 Wouter Vermaelen * Y8950 and YM2413 cleanups 2002-03-24 Joost Yervante Damad * fixed configure.in * continuation of 2002-03-23 Joost Yervante Damad * more CustomConfig framework * started on FilePath CustomConfig * this is WIP, and is not finished yet! 2002-03-21 David Heremans * Again a (total) rewrite of CommandlineParser. A simpler and cleaner design then the previous one, and easy to extend for other CLI-options. 2002-03-20 Joost Yervante Damad * XML::Escape support added to libxmlx * usage is explained in the header * if you make xml that is feeded into libxmlx, you need to escape it first, to allow entity escaping for &, <, >, ... * cleaned up libxmlx dir, libxmlx is now dual licensed GPL and LGPL 2002-03-19 Wouter Vermaelen * updated config files 2002-03-19 Joost Yervante Damad * started on CustomConfig * added src/config/ dir, moved initial files 2002-03-19 Wouter Vermaelen * various Y8950 improvements, not sure adpcm part is completely fixed * fixed getName() for Schedulable (eg VDP) * fixed virtual inheritance constructor invocations 2002-03-18 Joost Yervante Damad * small fix in src/Makefile.am, I hope this solves David's compilation problems 2002-03-17 Maarten ter Huurne * Fixed graphical glitch when switching between PAL and NTSC timing. * Minor clean-ups in SDLGLRenderer. 2002-03-16 Maarten ter Huurne * Implemented blanking in SDLGLRenderer. * Implemented side borders in SDLGLRenderer. * Implemented sprites in SDLGLRenderer. * Only compile SDLGLRenderer if OpenGL was detected. 2002-03-15 Maarten ter Huurne * Improved line rounding in renderer sync. * Fixed side border widths in text mode. 2002-03-15 Wouter Vermaelen * Fixed SCC distortion (was unsigned->signed conversion bug) * [Experimental] First order high-pass IIR filtering in Mixer * Tab completion of filenames for "disk" and "tape" command 2002-03-14 Maarten ter Huurne * Fixed ASCII16K MegaROM mapper. Eggerland 2 works now, but is still autodetected incorrectly. 2002-03-14 Wouter Vermaelen * Fixed tab completion bug * Fixed printed CPU T-States (only printed value was wrong) * Fixed crash when RTC.SAVE or FMPAC.PAC where missing 2002-03-12 Wouter Vermaelen * convert CassettePort to Connector-Pluggable structure * MSXSimple: - converted from MSXIODevice to PrinterPortDevice - renamed to PrinterPortSimple * MSXPrinterLogger: - converted from MSXIODevice to PrinterPortDevice - renamed to PrinterPortLogger 2002-03-11 Wouter Vermaelen * made Connector-Pluggable aware of EmuTime * convert PrinterPort to Connector-Pluggable structure 2002-03-10 Wouter Vermaelen * Console cleanup * Added console prompt * Tab completion for help command * Fixed uninitialized variable (enabledSCC) in MSXGameCartridge * made MSXConfig::getParameterAsXXX() methods throw execptions again 2002-03-08 Wouter Vermaelen * Split Console in Console and CommandController: commands can now execute without a Console * Implemented help command * Re-implemented tab-completion: - completion till longest common match - if there is only one match left a ' ' is added - option for context sensitive completion (not yet tested) * Implemented tab completion for (un)plug command: name of connector and pluggable gets completed 2002-03-07 Maarten ter Huurne * Fixed Generic16K MegaROM mapper. DOS2 works now. 2002-03-07 Wouter Vermaelen * Added plug-connector infrastructure: - only very basic support yet - changed joystick stuff to use this 2002-03-07 David Heremans * New internal structure of CommandlineParser is working This will make it possible to have extra parameters for cartridges,disks, or in a more global context. * Small fix to MSXGameCartridge 2002-03-06 Wouter Vermaelen * split console commands in tokens 2002-03-05 Joost Yervante Damad * simplified auto* support for OpenGL see src/config.h after running autogen.sh to see which vars to use. For now it searches for or and for -lGL This should allow easy conditional compile of SDLGLRenderer. 2002-03-04 Maarten ter Huurne * Added SDLGLRenderer (work in progress). * Added nms8250.xml to distributed files. * Fixed compile warnings in VDPCmdEngine. 2002-03-04 Wouter Vermaelen * SCC cleanups and tiny tweaks 2002-03-04 Joost Yervante Damad * added auto* support for OpenGL 2002-03-03 Maarten ter Huurne * Made sure unused bits in GRB palette value are zero. 2002-03-02 Maarten ter Huurne * Implemented fixed palette for sprites in Graphic 7 mode. 2002-03-01 Joost Yervante Damad * cleanup & documentation for libxmlx * tightened interface in xmlx.hh * tiny configure.in fixes * bumped version number 2002-02-28 Maarten ter Huurne * Fixed enlarged sprites. (David, you can fly your heli now! ;) 2002-02-28 Wouter Vermaelen * moved sound related files to new directory "sound" * fixed MSX-MUSIC in CTNG products: a real MSX-MUSIC is enabled on power-on 2002-02-27 Joost Yervante Damad * doxygen cleanups * updated homepage 2002-02-27 Maarten ter Huurne * Fixed even/odd page flipping, it was overzealous. 2002-02-27 Joost Yervante Damad * simplified auto* setup for crosscompiling * added atoll.c code for platforms that don't support that function * crosscompiling now works, xmlxdump.exe works, openmsx.exe doesn't work yet * for instructions, see README.WIN32 * fixed faulty CHECK_FUNC in configure.in * atoll.c is now only used when needed 2002-02-25 Maarten ter Huurne * Implemented even/odd and interlace display. 2002-02-24 Maarten ter Huurne * Fixed port C read bug in PPI. Now keyboard works in SBB promo and CAPS is off at boot time. * Implemented VDP name table masking (R#2) in bitmap modes. The 12 scrolls part of the Source of Power works now. * Implemented blinking in Text2 display mode. * Disabled SDLLoRenderer for now; it is not actively maintained. 2002-02-23 Maarten ter Huurne * Fixed Graphic7 border colour. 2002-02-22 Maarten ter Huurne * Implemented VDP overscan. Can use some more cleanup, but it works. 2002-02-19 Wouter Vermaelen * PrinterPortDevice now receives EmuTime: this is necessary for devices like Simple 2002-02-18 Wouter Vermaelen * moved CPU related files to subdirectory 2002-02-18 David Heremans * added simple and a printerlogger. Two devices that are connect to the printerport on a real MSX. The first is a DAC to play samples, the other logs all the data that a printer would normally print. 2002-02-17 Joost Yervante Damad * added atoll support for platforms that don't have that function [code taken from newlib, a libc for embedded systems] 2002-02-16 Maarten ter Huurne * Moved "display or border" calculation from renderer to VDP. This is a preparation for overscan and for accurate VRAM timing. 2002-02-15 David Heremans * Nicer mappertypes for the MSXGameCartridge: As requested by Maarten you can now specify easier to remember names (like ASCII8, SCC,KONAMI4,...) for the different mapper types instead of the numbers. 2002-02-15 Maarten ter Huurne * Added "palette" console command. 2002-02-15 David Heremans * Nicer mappertypes for the MSXGameCartridge: As requested by Maarten you can now specify easier to remember names (like ASCII8, SCC,KONAMI4,...) for the different mapper types instead of the numbers. 2002-02-13 Wouter Vermaelen * CPU speedup: CPU doesn't check the IRQ line after each instruction anymore, only directly after synchronization points. This also means that all irq.set() instruction must also set a synchronization point. TODO: check this for VDP, Y8950 and document this * VDP bug fix: writes to register > 46 (non existing registers) overwrite other variables 2002-02-13 David Heremans * MSX MUSIC/MSX AUDIO Stereo now possible: The xml has an extra parameter allowing the Y8950 and YM2413 to be activated in mono, left or right channel mode 2002-02-12 Wouter Vermaelen * Fixed FM-modulation error in both Y8950 as YM2413 2002-02-11 Wouter Vermaelen * Fixed some bugs in Y8950: it is usable now, both OPL part as ADPCM part but there are a few bugs left (YM2413 has the same problems) relative volume of opl vs adpcm might need some tuning 2002-02-10 Wouter Vermaelen * Made "cpudebug" command: must be enabled in CPU.hh (#define CPU_DEBUG) * Y8950 (AUDIO) updates * Implemented auto commands: automatic joystick/mouse insertion is now possible 2002-02-10 Maarten ter Huurne * Moved SDL dependent code out of VDP: - Renderer instantiation happens in PlatformFactory (new class) - full screen toggle was moved to Renderer (not its final location) 2002-02-08 Maarten ter Huurne * Fixed secondary slot select bug for real now. The cause was uninitialised variables, the previous "fix" turned out to be no more than a workaround. 2002-02-08 Wouter Vermaelen * Large Console cleanup: convert code to C++ split code in SDL dependent and SDL independent part moved all console code to directory ConsoleSource * Support for 9 joysticks * Stricter syntax checking for joyport command 2002-02-07 Maarten ter Huurne * Fixed ASCII 16K (type 5) mapper. Re-engineered Zanac-Ex ROM works now. * Made MSXMotherBoard console command inner classes private. 2002-02-06 Wouter Vermaelen * More cleanups in ConsoleSource: made SDLFont class, replaces DT_drawtext 2002-02-06 Maarten ter Huurne * A few small fixes in joystick implementation. It works now. Thanks Wouter! 2002-02-06 Wouter Vermaelen * Console cleanups * ConsoleCommand inner classes for Scheduler, MSXMotherBoard * Implemented "disk" command (similar as "tape" command): works only for drive a: 720kb disks * Implemented "joyport" command: joyport[a|b] [unplug|mouse|joystick[1|2]] * First cleanup in ConsoleSource: use hash_map in CON_consolecommand instead of self-made linked list 2002-02-05 Wouter Vermaelen * Implemented "reset" console command. 2002-02-05 Maarten ter Huurne * Added MSXMotherBoard::IRQHelper class. * Removed IRQ helper functionality from MSXDevice. 2002-02-04 Maarten ter Huurne * Took advantage of new raiseIRQ/lowerIRQ semantics in VDP. 2002-02-04 Maarten ter Huurne * Fixed bug where devices in non-expanded slots would only be visible when subslot 0 was selected. * Implemented "slotselect" console command. 2002-02-03 Maarten ter Huurne * Implemented "slotmap" console command. * Console::printOnConsole now accepts multi-line texts. * Fixed buffer overrun in CON_Out. * Renamed DummyDevice's device name to "empty", which makes more sense to the user (who sees it in the slotmap). 2002-02-03 Wouter Vermaelen * Removed class LoadFile: it was only used by MSXRom and it was only usefull for that class (two classes merged now) 2002-02-02 Maarten ter Huurne * Implemented memory overlay behaviour in SCC page (0x3F). 2002-01-30 David Heremans * MSXTapePatch uses console: Now possible to change or eject tapes using the console 2002-01-29 David Heremans * Console enhancements: -calls CommandHelp when tabcomplition has already found a complete command * DiskPatch can now reports problems with disks during initialization: This should help Maarten if he has a 'black screen' :-) 2002-01-28 Joost Yervante Damad * made libxmlx an 'utility' library, this implies that openmsx links this statically, which makes for easier debugging, no more 'wrapper script' for now 2002-01-27 Joost Yervante Damad * Made SDL console autotools enabled 2002-01-26 David Heremans * Console now usable: - registered as ansynchron event listener - Scheduler has now two commands registered 2002-01-26 Wouter Vermaelen * Fixed double delivery of syncronous events 2002-01-25 Wouter Vermaelen * Exception cleanups * Y8950 adpcm support (untested) 2002-01-24 Joost Yervante Damad * added support for streamstring xml config per request of David 2002-01-24 David Heremans * Altered SDL_Console: - C++-ed the code and files - dumped unwanted function - now registers objects from type ConsoleInterface to use for callback instead of C-function pointers * Scheduler has now one (1) registered command with console: - press tab for autocomplete :-) * Added cartridge to help debug ARC protection cartridge * DummyDevice warns about unregistered I/O port calls 2002-01-22 David Heremans * Added an SDL_console: Not usefull for the moment but you can toggle it with F10 No commands possible and double key registration but the effect is "cool" :-) 2002-01-20 David Heremans * Added DSKFMT-implementationr:. Formating of a 360kB dsk file doens't work. A 720kB dsk file can be formated single or double sided. * Autofill of empty,newly generated .dskfiles: No auto formating however, maybe later. 2002-01-19 Joost Yervante Damad * openmsx now uses new config code * features: - supports multiple config files - it's possible to create alternative config backends - no more libxml++ *yipee* - uses libxmlx, right now still linked dynamically, this means that ./openmsx is actually a libtool provided script, soo for debugging use .libs/openmsx and alter the LD_LIBRARY_PATH environment variable if needed - support for save not yet added, but it will be easy * xmlconfig raises exception on duplicate "id" 2002-01-12 Maarten ter Huurne * Compile fix for Thread class: explicitly convert between Runnable pointer and void pointer. 2002-01-12 Wouter Vermaelen * made a Thread class, interface is SDL independent, implemenation uses SDL * made a Mutex class, analog as Thread * sync MSX-MUSIC and MSX-AUDIO on register write * made all sound related classes (except Mixer) SDL independent 2002-01-11 Wouter Vermaelen * Implemented RTC load/save on power on/off (this is not the same as saveState) * Implemented FMPAC-SRAM load/save on power on/off 2002-01-10 Wouter Vermaelen * MSXMemoryMapper <-> MSXMapperIO cleanup (thanks Maarten) 2002-01-10 David Heremans * Added FileOpener. This implements the needed routines to support "rompaths". It also provides easy to use functions to open files in the other devices. 2002-01-09 Maarten ter Huurne * VR resets one line before start of display. Fixes Almost Real Copper Bars part. 2002-01-09 Wouter Vermaelen * Prepared CPU and Z80 class for inclusion of R800: - moved as much code as possible from Z80 to CPU - separated timimg dependent code from the rest of the code - ugly "#inlude-hack" to avoid the overhead of virtual functions 2002-01-09 Maarten ter Huurne * Fixed MSXMemoryMapper so that it can handle over 64K of memory. 2002-01-08 Maarten ter Huurne * Red and green were swapped in Graphic7 rendering. * Apply planar reorder also on CPU reading VRAM and sprite checking. Fixes Hydefos intro, all Graphic6/7 rendering should be OK now. 2002-01-08 Maarten ter Huurne * Preliminary Graphic6/7 rendering. Works somewhat, but not in all cases. * Fixed transparency bug; Psycho World sky is blue again. 2002-01-07 Wouter Vermaelen * DiskRomPatch cleanup: I wanted to fix the PHYDIO routine, but it was already correct (the result in the B register). Maybe it is now more obviously correct? 2002-01-07 Wouter Vermaelen * Z80: fixed HALT instruction (rounding error) * more #include cleanups * getCPURegs() interface cleanup 2002-01-06 Maarten ter Huurne * Implemented rendered line caching in bitmap modes (SDLHi). * Fixed GETDPB in MSXDiskRomPatch: uses static table based on passed media descriptor instead using values from boot sector. 2002-01-06 Wouter Vermaelen * Z80: reverted buggy optimization, fixes undeadline, track&field, flickering in space manbow 2002-01-06 Joost Yervante Damad * small fixes * XMLConfig skeleton + some code 2002-01-06 Maarten ter Huurne * Text mode extra-wide border now drawn at once (SDLHi). 2002-01-05 Maarten ter Huurne * Implemented CC and IC bits of sprite mode 2. Now OR-ed sprite patterns are rendered as they should. 2002-01-05 Wouter Vermaelen * #include cleanup * Changed PSG mute routine: PSG-samples in "time curb" now work! 2002-01-05 Joost Yervante Damad * small libxmlx improvements - should handle comments and pcdata correctly - started modifications of msxconfig code, first in a seperate temporarely program cfgtest, to avoid hindering the other coders 2002-01-04 Wouter Vermaelen * Changed interaction Mixer-SoundDevice: Mixer now calls the updateBuffer() method of each SoundDevice, even if device is muted (device can update internal counter). If the device was muted is must return a null-pointer. 2002-01-03 Maarten ter Huurne * SDLHiRenderer needs to sync on VRAM updates after all. This solves the disappearing boss blocks of Knightmare stage 1. * The VDP (sprite checking) needs to sync on VRAM updates as well. Hopefully this will solve the Knightmare stage 2 boss problem. 2002-01-03 Joost Yervante Damad * libxmlx sync 2002-01-03 David Heremans * Improved tape support, tested with Time_Curb.cas file 2002-01-03 Maarten ter Huurne * Changed pause implementation in Scheduler to make it thread safe. * Fixed PAL timing in SDLHiRenderer. 2002-01-02 Wouter Vermaelen * RealTime syncronizer improvements 2002-01-02 David Heremans * Added tape support, not yet tested * Restored old filesize behaviour in gamecartridge 2002-01-02 Maarten ter Huurne * Improved VDP accuracy. Space Manbow still flashes, but less than before. 2001-12-31 Maarten ter Huurne * SDLHiRenderer uses double buffering. * Prepared SDLHiRenderer for RealTime sync, but if I enable it the emulated MSX slows down (even though host CPU load remains low). * Added FDC values as patches to nms8250.xml, fixes disk ROM hang. 2001-12-31 Joost Yervante Damad * lots of small C++ standard compliancy fixes * Z80Core.hh -> Z80Core.nn * more disk support, still partially works only * disk support now works completely, but not yet for all programs [starquake, on mania036.dsk works completely] * fixes for slotselection [thanks Maarten] 2001-12-31 Maarten ter Huurne * Render accuracy is now one line instead of one frame. * Implemented line interrupt (horizontal scanning interrupt). Timing is still a bit off though. * Implemented horizontal display adjust (vertical not yet). * Implemented HR/VR status bits. * Prepared for caching of rendered lines in bitmap modes. * Command engine cleanups. 2001-12-30 Wouter Vermaelen * Z80: correct inter-instruction timing for DDCB and FDCB instrcutions, 2001-12-30 Joost Yervante Damad * more disk support, it already works a little bit, but the slot switching still needs to be added 2001-12-29 Wouter Vermaelen * Memory reads/writes can now be cached (if possible) measured 10% - 20% performance gain not completely finished, especially documentation 2001-12-29 Joost Yervante Damad * synced in libxmlx skeleton [W.I.P.!] * header file include cleanup, this should avoid alot of useless recompilation 2001-12-27 Wouter Vermaelen * Scheduler now uses a heap instead of sorted set * SyncPoints can now be removed * SyncPoints take an optional userData parameter, this parameter is later passed to executeUntilEmuTime() method * MSXDevice does not inherit from Schedulable anymore, devices that still need to be Schedulable must inherit itself from Schedulable (eg VDP) 2001-12-26 Wouter Vermaelen * added getCPURegs() and setCPURegs() methods in class CPU 2001-12-25 Wouter Vermaelen * small optimization in Scheduler: no overhead when unpaused 2001-12-25 Joost Yervante Damad * added --enable-profile and --enable-release to ./configure * added "debug" and "nodebug" targets to Makefile in src dir [toggle] usage: "make debug" turns on debug for this and next makes "make nodebug" turns it of again 2001-12-24 Maarten ter Huurne * Added "unsigned" to uint64 definition in Scheduler. Before, Scheduler::INFINITY was -1. * Implemented PAL at 50Hz and NTSC at 60Hz. * Restructured VDP timing routines. 2001-12-24 Maarten ter Huurne * Added render routine for Graphic4 mode. * Added support for 212 lines display. * Added partial sprite mode 2 implementation. 2001-12-24 Maarten ter Huurne * Added render routine for Graphic5 mode. * Added support for transparency control (R#8 bit 5). 2001-12-24 Maarten ter Huurne * Finished integration of command engine. It runs, but without render routines for the bitmap modes, it is not possible to visually check the results. 2001-12-23 Maarten ter Huurne * Fixed a design flaw in the Renderer interface. The update methods were called *after* the changed VDP state became effective. That way, delayed rendering operations cannot be completed because the old VDP state is no longer available. In the new interface, update methods are called *before* the new state becomes effective. So the old values can be retrieved from the VDP, while the new values are passed as parameters of the update method. 2001-12-22 Maarten ter Huurne * Fixed dirty check bugs. The "inside name/colour/pattern table" checks were wrong: too much was considered inside, therefore too many characters were considered dirty. Also the attempt to make Text2 use the same dirty checking code as the MSX1 display modes failed, instead I wrote different dirty checkers for different display modes. This was bound to happen in the future anyway, because bitmap modes will require different dirty checks than pattern modes. 2001-12-22 Maarten ter Huurne * Added VDP Command Engine 1.0 from Alex Wulms. Adapted the code to C++ and to openMSX conventions. Only functional change is SCREEN7/8 pixel lookups (two defines). The command engine compiles and links, but cannot run yet. 2001-12-21 Maarten ter Huurne * Implemented Text2 display mode (SCREEN0.80). * Prepared for blinking (not finished yet). 2001-12-21 Maarten ter Huurne * Applied gamma correction to V9938/58 palette. My guess is that the gamma of MSX and PC monitors is slightly different; the palette precalc now compensates for that. 2001-12-21 Maarten ter Huurne * Implemented palette feature of V9938/58. Can be seen by running Nemesis 3 using NMS8250 config. 2001-12-21 Wouter Vermaelen * Fixed uninitialized variable in RP5C01: this caused a delay of several seconds at start-up 2001-12-20 Wouter Vermaelen * added MSX-AUDIO: no ADPCM yet, not optimized * LoadFile and MSXGameCartridge cleanup 2001-12-19 Wouter Vermaelen * Removed more indirections from YM2413 2001-12-18 Wouter Vermaelen * fixed CassettePort: exceptions don't work if you compile with -fno-rtti, removed it 2001-12-18 David Heremans * MapperType guesser can be turned on/off * MapperType now configurable in MSXGameCartrdige. * Integrated MSXKonamiSynthesizer into MSXGameCartrdige. 2001-12-14 Wouter Vermaelen * sound on/off (F11) and pause (PAUSE) now work together * fixed "pure virtual function called" error 2001-12-16 David Heremans * Extende LoadFile to autodetermine file-size. * Renamed MSXMegaRom to MSXGameCartrdige. * MSXGameCartridge can now handle smaller game roms (<= 64kB) 2001-12-15 David Heremans * Added volume to the SCC. * Used F11 as HotKey to sound on/off: 2001-12-14 Wouter Vermaelen * Fixed DACSound, KeyClick now works 2001-12-11 Maarten ter Huurne * Optimised MSX-MUSIC mixing. * Removed indirection on ch array ("Channel *" -> "Channel). 2001-12-12 David Heremans * Added the SCC+ cartridge 2001-12-12 Wouter Vermaelen * Cleanup MSXMusic / MSXFmPac: "CALL FMPAC" now works! 2001-12-11 Maarten ter Huurne * Compacted the SCC mixing and muting code. * Added offset to lookups in SCC::getFreqVol. * SCC::ch_enable is not masked upon write. * Added "currentChipMode = chip" to SCC::setChipMode. * Changed size of waveform array to 32 (was 64). 2001-12-10 Wouter Vermaelen * Implemented Z80 inter-instruction-timing (DD CB instructions not correct yet) 2001-12-11 David Heremans * uploaded SCC soundchip: This chip can emulate the SCC hardware in the 3 know modes - Real SCC - SCC+ cartridge in SCC compatible mode - SCC+ cartridge in SCC+ mode * Changed MegaRom: Megarom of type 2 know use the SCC soundchip. 2001-12-10 Maarten ter Huurne * Implemented indirect register write for V9938/58. * V9938/58 can now have 16K, 64K or 128K of VRAM. * Upgraded CPU interface to read/write more than 16K of VRAM. * Changed video mode naming scheme to match V9938 data book. 2001-12-10 Wouter Vermaelen * Cleanups in MSXMemoryMapper, requires config file change * More config file changes: PSG -> volume parameter MUSIC -> volume parameter PPI -> volume parameter -> key_ghosting parameter Mixer -> frequency parameter -> samples parameter * RealTime cleanups: - MSXRealTime renamed to RealTime - RealTime is no longer a MSXDevice - moved parameters to config file 2001-12-09 Maarten ter Huurne * Faked enough V9938 features to make nms8250.xml boot. 2001-12-09 Maarten ter Huurne * Fixed compilation with Z80DEBUG enabled. * Fixed single-width pixels in SCREEN0 in SDLHiRenderer. * Re-enabled resetting of dirty flags at end-of-frame. This was probably disabled during debugging and accidentally commited to CVS. 2001-12-09 Maarten ter Huurne * Introduced VDP class, which will replace MSXTMS9928a. 2001-12-09 Joost Yervante Damad * Rom Patching Code skeleton * MSXDiskRomPatch skeleton 2001-12-09 Maarten ter Huurne * Fixed uninitialised struct field in SDLLoRenderer. * SDLHiRenderer renders doubled pixels now. 2001-12-09 Maarten ter Huurne * Introduced SDLHiRenderer. Opens 640x480 screen, although it still renders low-res. * Changed configuration: - new category: "renderer" type selects renderer: "SDLLo" or "SDLHi" - "fullscreen" moved to "renderer" and renamed "full_screen" plus it's an actual boolean now - new parameter: "limit_sprites" in "msx1vdp" * Setting "limit_sprites" to false actually works now. 2001-12-08 Maarten ter Huurne * Improved Doxygen comments in MSXTMS9928a and Renderer. * Small optimisations in sprite rendering. * Renamed XPal to Pal: it hasn't been X for quite some time. * Introduced sprite buffering: VDP stores sprite info for one frame, so Renderer can retrieve it on demand. This removes the need for the Renderer to call checkSprites, making the VDP behaviour completely independant of the Renderer used. 2001-12-08 Joost Yervante Damad * Loadfile now handles patching of files 2001-12-08 Wouter Vermaelen * added several missing reset() calls 2001-12-07 Wouter Vermaelen * MSXPPI is no longer a singleton 2001-12-06 Maarten ter Huurne * Small initialisation fix in MSXTMS9928a. Thanks to Wouter for spotting the problem. 2001-12-06 Wouter Vermaelen * removed init() method from MSXDevice: initialization should be done in the constructor * all MSXDevice constructors (former init() methods) now take an EmuTime parameter 2001-12-05 Joost Yervante Damad * MSXRom continued [reworked, no longer inherited from MSXDevice] 2001-12-05 Wouter Vermaelen * Made MSXIODevice and MSXMemDevice both subclasses of MSXDevice: these 2 classes take functionality away from MSXDevice and put it in more specialized classes. They also offer things like automatic slot-registration (and in the future automatic IO-registration) * removed start() and stop() methods from MSXDevice * reset() method in MSXDevice now takes an EmuTime argument 2001-12-04 Joost Yervante Damad * added MSXRom subclass and LoadFile mixin, as first step towards ROM patching support 2001-12-04 Wouter Vermaelen * correct use of register 0x7ff6 for MSX-MUSIC * MSXMotherBoard is no longer a MSXDevice 2001-12-03 Joost Yervante Damad * fixed Config *getConfigById(const std::string &type) * started on disk support "level 2" [not in CVS yet] 2001-12-03 Wouter Vermaelen * Made a new EmuTime class (for details search mailarchives for "[RFC] new EmuTiem class"). This gave a large performance gain, more than I had hoped for. * SoundDevices now unregister themself before destruction 2001-12-02 Maarten ter Huurne * Integrated name/pattern/colour table base addresses and mask into a single mask. This is probably how the hardware does it. * Fixed dirty checks. King's Valley 2 is now actually fixed, blanking just happened to make the problem go away without fixing the bug causing it. * Moved to "pull" model for VDP - Renderer communication: VDP sends update signals when parts of its state change, Renderer can get current state from VDP at all times. 2001-12-02 Wouter Vermaelen * fixed pause: - now works when paused - after pause emulation isn't too fast * correct inline in Z80 * MSXMegaRom cleanup, minor speedup 2001-12-01 Maarten ter Huurne * Fixed compile error in CassettePlayer.cc: PRT_ERROR contains exit(), which needs . 2001-12-01 Maarten ter Huurne * Fixed bug which caused Z80 to fail when methods were not inlined. Problem was that ld_xix_byte() and ld_xiy_byte() called Z80_WRMEM with two parameters, each of which read an opcode, an operation which increases the program counter. This is wrong because the evaluation order of parameters is not guaranteed. 2001-12-01 Wouter Vermaelen * added FMPAC * MSXMotherBoard is now also a CPUInterface, this eliminates one indirection in readMem()-like methods but these get called a few 100 000 times a second. I measured 10%-20% performance gain 2001-11-30 Wouter Vermaelen * made a helper function registerSlots() * made a helper function loadFile() note: config file format changed! 2001-11-29 Wouter Vermaelen * Added a CassettePlayer (no record function yet) A CassettePlayer must be plugged into the CassettePort and you must insert a tape (= .wav file) into the CassettePlayer Currently you can't do either of these operations. 2001-11-27 Maarten ter Huurne * Added updateBlanking to Renderer interface. Fixes redraw problem in King's Valley 2. * Various cleanups in MSXTMS9928a and SDLLoRenderer. 2001-11-26 Maarten ter Huurne * Introduced SpriteInfo in MSXTMS9928a, this is a prelude to sprite info buffering (separating calculation time from rendering time). * Introduced VdpVersion enum in MSXTMS9928a. 2001-11-20 Wouter Vermaelen * Small CassettePort update, implemented very primitive filter 2001-11-25 Maarten ter Huurne * Sprites are now rendered in the screen buffer rather than in the display cache (which replaced "canvas"). As a result, sprite drawing code became simpler and the display cache needs less updates. 2001-11-25 Maarten ter Huurne * Render on demand: New Renderer can render as many lines as requested. Many lines at once has lower overhead, but if necessary it can still render one line at a time. This code can be converted to pixel-precision rendering that has decent performance. * All change tracking (dirty flags etc) is moved to Renderer. * Interface between VDP and Renderer is much cleaner now. (no more public fields) * Got rid of "tms" struct. 2001-11-20 Wouter Vermaelen * added basic support for CassettePort - CassettePort has no filters yet - no output (record) yet - !! no CassttePlayer (must be plugged in a CassettePort) !! 2001-11-18 Maarten ter Huurne * The drawing area (now called "canvas") is now an off-screen SDL surface instead of a pixel array. SDL calls are for blitting and drawing empty lines (top and bottom border). * Cleanup of change tracking in VDP. * Cleanup of Renderer access to VDP state: public fields replaced by inline methods. * Cleanup of VDP fields: slowly getting rid of "tms" struct. 2001-11-18 Wouter Vermaelen * updated KeyEventInserter, it works now but it is not yet useable, needs to read text from config file or something 2001-11-17 Maarten ter Huurne * MSXTMS9928a and SDLLoRenderer respect SIZEOF_BOOL now. * Introduced Renderer: pure abstract superclass to all renderers. * Changed SDLLoRenderer into a template class. All supported colour depths are compiled in. A factory method selects a suitable colour depth automatically. 2001-11-17 Joost Yervante Damad * added sizeof(bool) autoconf macro * rearranged configure.in to avoid problems with simple checks being confused by the extra CXXFLAGS and libs 2001-11-17 Maarten ter Huurne * SDLRenderer is now SDLLoRenderer (low-res: 320x240) and was given its own files. Also removed MESS history comments from MSXTMS9928a. 2001-11-16 Maarten ter Huurne * Split off SDLRenderer from MSXTMS9928a. This makes it easier to support different renderers (320x240, 640x480, 8/16/32bpp). In the near future SDLRenderer will get its own file. 2001-11-16 Maarten ter Huurne * Disabled 512K mapper in cfg/someconfig.xml because it conflicted with the MegaROM. 2001-11-16 Wouter Vermaelen * cleanup EventDistributor/HotKey 2001-11-15 Wouter Vermaelen * Z80: interrupt state is only checked right after a sync point halt now "burns" CPU cycles --> large CPU speedup 2001-11-14 Wouter Vermaelen * an MSXDevice is now associated with an MSXConfig object as soon as it is instantiated. Before this change it was possible that a MSXDevice (mostly singletons) was used before it could access its parameters. * AY8910: fixed uninitialized variables 2001-11-13 Wouter Vermaelen * fixed HotKey: method find() returns a normal iterator, not an iterator that goes over elements that match the search criteria 2001-11-13 David Heremans * openMSX now stops when using the WM close button: * Correct close behaviour: Set a sync point so that if the CPU is scheduled until infinite we still can stop openMSX. Wouter will look into the HotKey event distribution, it is temporarly fixed using if comands. 2001-11-09 David Heremans * quickly add a fullscreen flag in the TMS code: extra parameter in config.xml file use PrintScreen key to togle * openMSX now stops when pressing F12: however make sure that openMSx is full screen when you press F12 2001-11-07 Wouter Vermaelen * reworked CPU related classes: much simpler structure more optimizations possible in the future * Z80: correct wait-state handling 2001-11-05 Wouter Vermaelen * Z80: undocumented instructions like "res 4,(ix+5),b" implemented * Z80: correct(?) timing for undocumented instructions 2001-11-04 Wouter Vermaelen * Z80 cleanup/fixes: fixed DAA instruction, bug in Penguin Adventure should be fixed now correct 'undocumented-flag-handling' for most instruction 2001-10-31 Wouter Vermaelen * fixed bug in Z80, some instruction had a negative T-State count!! Road Fighter works again 2001-10-30 Wouter Vermaelen * added MSX-MUSIC, code largly taken from Mitsutaka Okazaki http://www.angel.ne.jp/~okazaki/ym2413/ no "suspend" function yet 2001-10-29 Joost Yervante Damad * finished migration of CVS and mailinglists to sf.net * thank you sourceforge team!! * http://openmsx.sf.net 2001-10-27 Wouter Vermaelen * fixes, improvements to DACSound (untested) * updated KeyClick, the "click too short" problem should be solved now KeyClick now uses a DACSound object to play sound, playing samples on KeyClick should also be possible now (untested) * pause sound while pause emulation * SoundDevices default now to muted after creation, fixes a race in Mixer::registerSound() 2001-10-25 Wouter Vermaelen * SoundDevices can now mute themselves, mixer doesn't ask for soundbuffer of muted devices * added KeyClick support, PPI had to be made time aware for this doesn't work very well yet because a typical key-click-spike takes about 34us which is shorter than the duration of 1 sample 2001-10-23 Wouter Vermaelen * make DACSound compile again (doesn't work yet) * add HotKey service * openMSX can be paused (pause key), added to demonstrate the use of HotKey. Problems with current pause implementation: - sound must also be paused - realtime keeps running, so after unpausing openMSX tries to catch up and runs too fast for a while * MSXKanji now supports both class 1 as class 2 Kanji * Added MSXPrinterPort and DummyPrinterPortDevice (= no device connected) 2001-10-22 Wouter Vermaelen * Changed Mixer and SoundDevice interface (David's request). Now each SoundDevice must do its own buffer-managment, this gives more flexibility to devices like DAC's 2001-10-21 Wouter Vermaelen * small performance improvement in MSXZ80 2001-10-21 Joost Yervante Damad * added some basic conversions to MSXConfig##Device let me know when more are needed * updated msxconfig.dtd to reflect soon te be added non-device configuration entries * added support for non-device configuration entries this is not thoroughly tested, but should work 2001-10-19 Joost Yervante Damad * started TODO file to collect ideas/things todo 2001-10-19 Wouter Vermaelen * documented SoundDevice interface on David's request * RP5C01 can now optionally sync with host-clock, but this limitates some functionality 2001-10-18 Wouter Vermaelen * implemented test register in RP5C01, corrected 12/24 hour mode, month-wrap-bug fixed * RP5C01 is now emutime synced instead of realtime 2001-10-17 Joost Yervante Damad * added src/cfg dir for storing config file templates * started in KeyEventInserter 2001-10-17 Wouter Vermaelen * made improvements to MSXRealtime * small fixes to MemoryMapper, E6Timer * improvements to RP5C01 (RTC) 2001-10-16 Wouter Vermaelen * fixed bug in Keyboard (uninitialized variable) and made minor performance improvement in keyGhosting * add 1 extra waitState after each instruction * Corrected MSXRealTime, speed is now correct. Deviation was caused by accumulation of rounding errors. 2001-10-15 Wouter Vermaelen * Scheduler can now schedule "Schedulable" objects instead of only MSXDevices, this is necessary for sub-device scheduling (e.g. counters in I8254 2001-10-15 Marcel Harkema * * Set the MAINTAINERCLEANFILES variable in Makefile.am files (for make maintainer-clean) * * EventDistributor.hh should include and not (which is an internal header file) * * Add {XML,SDL}_CFLAGS, {XML,SDL}_LIBS, etc. to CXXFLAGS and LIBS in configure.in (and remove openmsx_LDADD line from src/Makefile.am) * Include , , etc. instead of , , .. [I might have missed some... please check your code] * [patch applied by joost] 2001-10-14 Joost Yervante Damad * Made EmuTime printable [read streamable] for David 2001-10-12 Wouter Vermaelen * Simplified MSXRealTime implementation 2001-10-11 David Heremans * MSXTMS9928a and MSXRealTime updates 2001-10-08 Wouter Vermaelen * update parts of audio-buffer when registers have changed * fixed tone generation in AY8910 (overflow in calculation) 2001-10-09 Joost Yervante Damad * removed all automake/autoconf utility files, use autogen.sh to install them locally 2001-10-08 Wouter Vermaelen * added a real time synchronizer (MSXRealTime) * fixed keyGhosting bugs in Keyboard 2001-10-07 Wouter Vermaelen * renamed Inputs --> Keyboard * forgot to initialize keyboard matrix * moved method keyGhosting() from MSXPPI to Keyboard --> method is now only called when * keymatrix has changed and * keymatrix is read * extended Mixer to support (pseudo)stereo 2001-10-05 Wouter Vermaelen * Made a very simple mixer, made AY8910 register itself as sound generator. 2001-10-03 Joost Yervante Damad * updated automake utility files 2001-10-03 Wouter Vermaelen * "static const int -> enum" cleanup * Reworked EventDistributor, it is now possible to deliver events synchronously and asynchronously. This makes the EventDistributor more complex, but it eliminates locking problems in synchronous ( = almost all) client code. 2001-10-02 Wouter Vermaelen * made Philips mapper-IO behaviour * read mapper behaviour from config file, this needed some restructuring in MapperIO and related classes * Fixed TurboR ST mapperIO 2001-10-01 Wouter Vermaelen * Z80 converted to a C++ class, still needs cleanups * IRQ handling cleanup 2001-09-30 Wouter Vermaelen * second part of Z80 cleanup 2001-09-27 Wouter Vermaelen * #define -> static const int cleanup * first part of Z80 cleanup 2001-09-26 Wouter Vermaelen * reimplemented Scheduler, total new algorithm * made some changes to cpu-device's these need some cleanup, especially the c/c++ mix (Z80) 2001-09-25 Wouter Vermaelen * added locking to class EventDistributor (maybe std::multimap is thread-safe and no locking is necessary) * added locking to class Mouse, there was a small race that could cause some mouse-movement-glitches 2001-09-24 Wouter Vermaelen * implemented Real Time Clock device * implemented joystick support. Cannot be used yet since there is no mechanism to plug a Joystick in a joystickPort yet (dynamically nor statically) * implemented mouse support. Cannot be used yet, same reason as above 2001-09-23 Wouter Vermaelen * made sperate thread for event handler other classes can ask EventDistributor to receive specific events * adapted Inputs for new event model * respond to SDL_QUIT event 2001-09-23 Joost Yervante Damad * various small fixes * started working on making the code work with gcc-3.0 * added autogen.sh: use to regenerate build files * added m4/ dir for own autoconf m4 macros * added fstream_templ.m4, since in gcc-3.0 ifstream is templatized on type, and .read() returns char, not unsigned char, soo this needs ifstream in openmsx, but in gcc-2.95 ifstream is not a template. * added -fno-rtti to compile-flags since we don't use runtime type inspection anyway, and it increases binary size see also: http://gcc.gnu.org/ml/gcc-help/2000-03/msg00064.html 2001-09-22 Wouter Vermaelen * cleanups in most devices * small bug fixes in some devices * Implemented memory mapper all mappers share one MapperIO device reading from mapper ports can be customized 2001-09-21 Wouter Vermaelen * Z80/R800 separation cleaned up: only the class MSXCPU knows there are 2 CPU's, all other devices should talk to MSXCPU instead of MSXZ80 or MSXR800 (nor ask MSXMotherBoard which CPU to talk to) Removed some code marked as "ugly hack" * added support for Kanji ROM * cleanups in MSXRom16KB 2001-09-19 Joost Yervante Damad * various small fixes around the code * the 16k rom still needs a C++ makeover 2001-09-17 Wouter Vermaelen * implemented kana/code led * implemented joystick-ports. This is only the port, not the devices that can be plugged into the port (joystick, mouse, ...) * interupt handling was seriously broken (CPU jumped to 0x38 at every di->ei transition even when there was no device that had raised an IRQ) Fixed now (I think). Also moved interrupt related methods from class Scheduler to class MSXMotherBoard. 2001-09-16 Wouter Vermaelen * implemented sound generation for PSG (code taken from xmame-0.37b) mixer is not finished, so you won't hear anything yet 2001-09-16 Joost Yervante Damad * added endian-ness check to configure.in * removed -DLDB_FIRST, and made it dependant on configure result * added sizeof long autoconf check, to make Z80.cc also work correctly on 64 bit and 128 bit cpu's [untested] 2001-09-15 Wouter Vermaelen * started implemention of MSXPSG (no sound generation yet) 2001-09-15 Joost Yervante Damad * reworked msxconfig.cc by using the xmlhelper * xmlhelper is a simplified xml interface 2001-09-13 Joost Yervante Damad * added xmlhelper class, which I will start using to make xmlconfig code cleaner * made msxconfig more standards-compliant 2001-09-10 Wouter Vermaelen * removed double initialization * CPU is no longer a special case with initialization * fixed various bugs * made Inputs independent from MSXPPI * implemented CAPS LED + support for all other LEDs 2001-09-09 Wouter Vermaelen * print more usefull debug messages * use DummyDevice instead of the general MSXDevice for all the emptyDevice's * registering SP must be done by Scheduler, not Motherboard 2001-09-08 Wouter Vermaelen * some fixes (yesterday discussed with Davy) 2001-09-06 Wouter Vermaelen * fixed a few classes to make them compile again * made some minor addition to emutime 2001-09-05 Wouter Vermaelen * print debug information only if compiled for debugging 2001-09-05 David Heremans * sync : PPI, Z80, ... * Z80 should be completely correct now * started integrating SDL keys * VDP integration Sean's code started * subslot selecting and mainslot selecting imped * MSXMotherboard slotlayout filled with dummy empty devices to avoid 0-pointers 2001-09-03 Wouter Vermaelen * In class Scheduler, use "set" instead of "list" or "sortedlist" * Split MSXPPI in a MSX depended part and a reuseable 8255 part 2001-08-27 Wouter Vermaelen * made a more complete PPI implementation * made new device: E6Timer (TurboR) 2001-08-26 Wouter Vermaelen * removed linkedlist.hh and use stl list instead * made template SortedList and use it in Scheduler this compiles, but does not link yet 2001-08-25 Wouter Vermaelen * updated class Emutime * make use of emutime 2001-08-23 Joost Yervante Damad * msxconfig: added and * msxconfig.hh: externalized nested classes * msxconfig: started saveFile support 2001-08-10 David Heremans * sync : PPI, Z80, ... 2001-08-09 Joost Yervante Damad * reworked msxconfig code, now more C++ * added support for multiple slotted's 2001-07-07 Joost Yervante Damad * added class property to tag in msxconfig * also slightly reworked that code * bumbed version to 0.1.1 * added SDL auto* support 2001-07-06 Joost Yervante Damad * merge David's code in CVS this includes: PPI, Z80, RomDevice, Motherboard, ... 2001-06-26 Wouter Vermaelen * more updates tex docu file 2001-06-23 Wouter Vermaelen * more updates tex docu file 2001-06-23 Joost Yervante Damad * finished readonly MSXConfig interface 2001-06-22 Joost Yervante Damad * added MSXException exception base class. * created MSXConfig exceptions * started using them. * make distcheck now works * openmsx.tex is part of the dist 2001-06-21 Joost Yervante Damad * made MSXConfig code -> this code needs serious cleaning later ! :) 2001-06-21 Wouter Vermaelen * updated tex docu file 2001-06-20 Joost Yervante Damad * initial MSXConfig interface/singleton skeleton * first touch with libxml++ * links smoothly now with libxml++ and libxml * !!warning!! needs libxml++ version 0.13 (debian ships with 0.10) * see also http://lusis.org/~ari/xml++/ 2001-06-21 Wouter Vermaelen * created tex docu file 2001-06-19 Joost Yervante Damad * added autoconf support for libxml * added some $Id magic tags 2001-06-18 Joost Yervante Damad * Emutime tweaks as per Wouter and David 2001-06-16 Joost Yervante Damad * added autoconf support for libxml++ * various small tweaks 2001-05-06 Joost Yervante Damad * made a nice and clean CVS/automake/autoconf/libtool * restructured source * versioned files 2001-05-04 David Heremans * openmsx: initial version. openMSX-RELEASE_0_12_0/doc/internal/MSX-cassette.dia000066400000000000000000002032161257557151200217630ustar00rootroot00000000000000 #A4# #Cassette out bit# #1u# #22n# #4k7# #4k7# #100# #Cassette recorder# #22n# #10k# #470k# #10k# #3n3# #10u# #2k7# #2k7# #4k7# #220k# #1k# #+# #-# #+5V# #+5V# #+5V# #cassette in bit# #cassette player # openMSX-RELEASE_0_12_0/doc/internal/audio-flow.dia000066400000000000000000000253251257557151200215540ustar00rootroot00000000000000}[o9nhwotg>9y [qtZ YI:p~!Y.uQDrIUf1U,~֕=G_rtq-~xͿxo}ÛO߾+{+4}Fӷo2OAox3d=qz}o>L~}X-?/6je2w7o7s|aa5⣟>>-gvSeH縿lF=AW<vUB֏lQcͼxiJ)Xm⊼}77{~\Wٺ*r9N|9wرil^<Swjv|h{] |B70y_fϳi gu0^>[/gݯ"Frb*NW!g(iOaozy\SSI{ɷj?ThV{ nn֓du?~7;[Z* q1zZnou>|ۉwBBFטX;KrBjq`RDϰ@O3@}Z~}_cS vߧK6%Qe(LY cĨyܵ{-eΫjZjoF ]}b]/dq86Y<=9U/zv7=G3 UcgL'PٖS.iӪN7 jF=;SQ3An"qPznŘJ^Kp@?Oɕ4fQpl@ ҈8 d8HOJ+@:YE*ͣP-f,1çtZ--b@a0``N3$wp\ʡ&˿܈pn}#}tF7`7kWe4N Fo4%B06ŬjtI[O ]:HsNϹ)y5U)sEڷ̥lv̮E{טl/?}D$Kv?uj>}$q*Wnq zTyDdzˣyYl)B^@x7R:7B^yw.nd@#9,GRı)rZ\^ y7Mb<2) )فăUB.C5 b  9@H* SPeV 8Q'*aNTlNLEB斣NNĉ:YRP VDR9bY8n+G FP1* ~$@8888aX*+u4"V(&Mw1i 1i:4j "%)PU6ەe3lZRM#%)P 0IZ,ұ Tv T3:VZ1tVnV\eB斳VTHY+- S+D)$٘#oE4S꧛%'`W5ͩAXLB*4X[@XΞ\&e2.s5_Ɂ+N܀*an@P5 2߀zi+΢]@W|j+&suݯfatfBsܲvNE4lQ};lgtWXf + }jpeHgG7=|'/mo))mK?& c(9{[y/+^ԞhKثk~bukwKn Vܴ,z;n1/ϧaĚ7yZj;~?.|u"TD=WBmATS9ٔCq~X%˲*g cPm~m6D6Ol>Ѷx|׷"mnO_ꯟ(ͯzN: y,REd^KԋʩkM/R/ s%^:2^z"23a3WFТ( 8e,R|\$[ F@BLHeTq`nѐrX- H:ydlX#):϶fZ,8]fP==D0*gVBC.cZyIkuE-#8MT[(xkr`\yF0%1ݽH6k(h!$Rd6y?*}PJ^.tQC nSl3eȥ>! M2z4^?gH#AG9{dn9&I\`DOX!)X&^+_֟޷JfU3ʲiQ.^jr3ZfK?#G~v3M>C~oS93Ydd&Kjp2"zK.*j^{EuZ"ޱwjjޯsU \ JW$*n%|s+FjεWò jT{,++j[{(TUsUg-źbWVަxUu )ג5=\q!+g>]#+W""Xŭ\Q>pګ3aLz`mj%^EV{e=++j[{(\UsU bj,-^9oʢ9^ګN^)$R^jVUuEZkN5ګ:/3$U@핢WR{┮@\8vWww \JYhW^1_{eګ\{ٹrR1j8MZ{S+N[[\{5+P{e 멽R:R`WVԶ3ګ ګZ&n 'WVަx'k_KVW~3.P{ ̕+1rG#>%j3A01rRx-*$-Y+jmuj¦kfH4V{ei 9x{W͹*^ueN J(W% Q핕R{uDW(Z{uv)^y!j4T{u,/LR_4Iг'} wkIs~9-:DF_BϞom(9`.!qsǵ μ-˥ӯ Zy$rh$ø {=ϞY-:[c SB=`h]7vWf `cKۭ0Rx);{*/Qp [R}n7 MNf ?1dU![4$HjEdvt#"XE06{B5ՙg xp1^K֜\G03 Sa& ,_ VLf:%['Rq2ۢ>+"NQ_ 8tDh+z!#d4=.tiU.tT(RLG4 VHGn&:>rF8d;kІeKFe6lߓЮ僓28DԓS(ݚnrBPH٣8xs(?"DösH0T w0?$O`)GQ9Pu(.֋+p13NFE[+LgTZ&/oC~Iu pC¦mK"8]W=U pURmB۬|s[30sjWBbXjR#{HF|+/,*=vu]kk ܇U:o!lFO(Dc~ˑI;nPlQV ϕZrk>Ft5OsԺ:QCDkQxaͪ9UPsi9YqqNٹIJI"F21'mF=' Š~Μ`I$#h S;1dc) k)p/Lwh~Z6˂\hPqw(G} Dc\ ~ye~lz7m)\FZrǡ|OT͠}jU!./$V'1&}Y4r#]]Z ё5a5^cFw|. `Z8h*W-w(u^Nt+7s{\Y9[+ѷ]ѻba:z:yz߬je:z>.Wxŵ4~S[kKkzUZ8CJ[MAHRb/͕;44nEB(x,~Ɛq9gP,ॢ s6_OWhym#k |O(Ql !JK.J8ȴ!Xl R5T%5 b@`HoXx#>#f&00)!/!"!z@PH G(M0.T(]Iӣ%9**3 c_`\nUc0w#dt$p f& =v0J3UL$"dg:p!АA/`p,X`0Ԋ@DOF2PRyPX0`¨`) BP;Ǿ ;`ݪ O8S SJd#f\], u;F58vxpF0 e1Nuԭ.?Q 2t= mXj䴮Sgb*esRs}:AB\VHu iJ:K}q`oDʨ5HWCjvV& __J+NAWdzeQ7/t~dqLƥvī]%a.҉Wqdz`ơߗc.`y72sawĻ%fPk NԔW2W.]I| Yykc~j$pj^LChҐhŠw׃IS "Eѵ:9pUBCU 9F Uj|YoTHYdO0``i+81u!#IKAu!\>~$ pG1|GvA1ֹ*F|Gj}GF@}GF:u<p'"H NNQ;ZQ[t #v#CNN,XcIzdq=u€YA3Fݵ7 ##-m;Jw9)61 ƤPc2 11d96&cCdsNcshcrecspLYfMYNLY;mLMW1LYE VU+b쫚(Rg>˳b&; Hwv_G9 !w4ꎷ9Q\}FbƼxsZKmxj?ɮ=Or\K?*$~W/\d>{XlG1ɓ&/@+Lrʬty.@Z#!\D3Mv&9p&5[2E ro[(g{<:W1ș"&q!2"'Rj9qY>,t\$O?']_KќEPiWpxqP1DD[fZ5x! Ys|١R;J?#G~vfziЖ'iu}1"1Q؇쳹ؑv5b&H'P,Ymwq:2αl4<6ұzPuP6uLG=C;Y )D\ٳ`NJs{zw2$3iy@P=v~F&HpFq٭Y-X4pI%2bnbmu$D`x+XY/O_bXGvս]|ʲ>[}~K—O|Վt33*i.D OZ"AX5뮶T?XCBpLLiV5Olfm$>qS}^}=]C ǙZ߬#Hn;ҿKIp ݥ[`2^Z`֗nvloYVa'0 ͍xsvx10(x9hmڀs't夭wAk[d t[FIp) _Õ{9GhK]O7je_'t%R;nQg%efvHq)R#-q ̥fgA/7}}<*yz/G>[~saG >6 E0HdV]x`cP㩂Œǃ)\l-,$%B :)S+Mu^x0SW""9Kt dY˚ն:8x#d#;Q>ӊ8tf1Ț5}^svEޮ/mJ_sn&>ɹ]e+ZM(Q"lv"h`~a5 \EAۋYfٙUs@N \|<ۚ&kᏆOTL Y$`rRa!E-1j.c>w2ԕ"#"C8"y\sd!& @2 a CX:yzc;+m2PV|2g>46l646u^{H^Csou RpYcz׊T^F^[GMޘ6qM^xHxmKxmk!C8.91%_#!~y ypa}Š$Ёe_ݺVOPHmHvc^K_r?][BIۗwh ay(<,xNIP3\wHbiN΁9i791~uQJֽگ t| nՂOp'̂]i'o䌝 t2 r9Җ=S|e_EQ@$2;8 drL灬X&LFdt= 3@ _009|q 8EIEʂZ}\7/;=7^qD#]*u01z0Ǡ[ԉjE*QzE+d֗0aA^Xs`%ho hstm5D ;*"]lIg1~€vC9sZX vءdt%bZ*" n'Zג8?j2>iq%"qkc } U](D #L=ã#GO6T4ݓ? EƜ B;)U"9vji;]i\RӑHM) ˔qmMc9~q_3]miF JZ+Fe^QXYyyJՍۺ3DGv۩jx-N}ZF;Ng 6]xS|c{XM=fopenMSX-RELEASE_0_12_0/doc/internal/events.dia000066400000000000000000000075521257557151200210140ustar00rootroot00000000000000][o6~ϯ0\O"D9[l`=]K$F II_/I9ɒ- s4p<$(ta <_6ĕZY\s'=Rqq`Ǒp=;*LeEiWھj$kA/i^͊DbeKKT֚&W ݆q(/9%J"ݮa5L4cՉVܻkwM_kT]:OgQ7w ]C.nuF] W1&VK{G? Pyu 7#"sILkٹR%]pbN0]Uxr +xȎک!j!(J\n5 A>G>" @A[9qa/ ll׍G,,u: 9+#6Y:3W /Ce\T.Lx|oXGETmPW"v =wh}nj#zh!KEގS>soWFR8BT%51)[ yuMcJޯsܐ W]pL@O.nrovSGU7H#]nϿ;VgBGC<+V+r0|:?-7m*8.u)U,Ͼfeވ]w^ L6Ya?}hq懠_u4Z4 0*><^OGE\%m k24ssGKܐsfVWMu6ry,~r|>̺YܿYȯ ru T- K Э \9`R NqA Rv|fFٲ "}Dw/6 .,KyC_&lZ LV誨>j_-1/ĬYr@ǣwNAgYvA }ݼ3NkН¡[w@%uD>N]8Ҭ8km# =D"iWyokwPiLj)wfҠ!\0i0 y*LhedE=2<㗿ů+0L&7uAEKh܊ޮgET#эS2 wlZm})G ^]vwDŞz:= XA,]+Z t`Ukbn^Pnr20 (PJ|75u[jnbh+ba`Bt`n 9 A>yHF'ʝ`B2y @ 6 :HzĞs{n}42ij9^Cqb"9@*ER𵗳66e4vYIΠmb3V* v5rs2Ә tyL{OF/{hlu)*ku׌9~­o "c9@ 7rzh YI1ρ`|6вD{ZȦ4|jaAIkMlJHChEhS??5 #LYrj!#u[/*BcϪY-)]6!٪; ~="vK;Yq`3*#,jope A7]VZlKfecەnD3j!m@7( :$sHH6h+ddr$wKc P9${$ {c]MVrkѫLɖ>D;Z=e{ĭR$+U+ӭOB>2>yJ> tYg+h߳mmV(jө^϶j^sJ6cBO{}a ~6/O-z./JT-C@Opd\с!2%_R (EЫ#p<-|jJPPK$ Dy& +RZr6vBcOėpmlMm~x|&BK/ &F}V>Z}-fФZ6chfC(B*,o3o@$߼:)&|=-&">U*$\Noo]pl?8neut_V>PEQ> i rA㘔(S @vՐ]"j'Gȁo"Lk;9?F|I0O@WL q#Fi'螶IU%*ԕG'%:Ўp(%S8/"1p6JQ$[N%s!N݅N)v@ ^}[ hA :2rw0jesmE@ݎvb_Pw.YfdzB* openMSX-RELEASE_0_12_0/doc/internal/exsphl.txt000066400000000000000000000074471257557151200211000ustar00rootroot00000000000000 EX (SP),HL ========== This instruction does 5 memory transactions - 1 byte instruction fetch - 2 bytes data reads - 2 bytes data writes The goal of this test is to find out the order of these 5 transactions. Obviously the instruction fetch (F) is the first transaction. There is one read from location SP+0, I'll call this R0. One read from location SP+1 (R0). A write to SP+0 (W0) and a write to SP+1 (W1). R0 must come before WO and R1 must come before W1. This leaves the following possible orders (please check that I didn't miss some): a) F R0 W0 R1 W1 b) F R0 R1 W0 W1 c) F R0 R1 W1 W0 d) F R1 W1 R0 W0 e) F R1 R0 W1 W0 f) F R1 R0 W0 W1 * Z80 In the document z80cpu_um.pdf you can find the number of cycles per M-cycle. For the EX (SP),HL instruction it lists: 5 M-cycles, 19 T-states, 4+3+4+3+5 note: this doesn't include the extra wait-state introduced on MSX. This can be explained like this (note, I'm guessing here, but it seems to fit well on the number of T-states). M1: fetch/decode opcode 4 cycles M2: tmpL = read(SP) 3 cycles M3: ++SP ; tmpH = read(SP) 1+3 cycles M4: write(SP, H) 3 cycles M5: --SP ; write(SP, L) ; HL = tmp 1+3+1 cycles fetching opcode (or prefix) takes 4 cycles in all Z80 instructions reading/writing memory takes 3 cycles on Z80 This corresponds with order c) [F R0 R1 W1 W0] * R800 On R800, the instruction takes 7 cycles if SP+0 and SP+1 are in the same 256-byte page, 9 cycles if not (see r800test.txt for more details on this). So it seems there are two extra page-breaks in the latter case, order a) and d) need only 1 extra page-break (of course it's not impossible there still are 2 page-breaks, or one page-break and some other extra cost). In any case the timing doesn't give us enough information. We need another test to be sure about the order. We'll do this by setting the stack pointer to a memory-mapped IO region: In the MSXTurboR, in slot 3-3, there's a ROM mapper. Writes to the region 0x6C00-0x6FFF will switch the ROM in region 0x6000-0x7FFF. The content of the ROM is like this: page 0, address 0x6C80/0x6C81: F6 50 1 C3 3E 2 08 07 Now execute the following program, with slot 3-3 selected in page 1 (0x4000-0x7FFF). ------------------------------- org #C000 di ld (save),sp ld sp,#6C80 xor a ld (#6C80),a ld hl,#0201 ex (sp),hl ld de,(#6C80) ld sp,(save) ret save dw 0 ------------------------------ After running this program the registers HL/DE contain these values HL = 50F6 DE = 3EC3 Register DE contains the ROM content after both writes (W0,W1) are done. The value of DE comes from ROM page 1, so writing of L=1 (=W0) must come after writing of H=2 (=W1). Both register H and L contain the content of the initial ROM page (page 0), this means both reads are executed before either of the writes. This leaves two possibilities: c) F R0 R1 W1 W0 [FRrww] [FRRwW] e) F R1 R0 W1 W0 [FRrww] [FRRWW] I've also indicated the page-breaks when SP+0 and SP+1 are in the same or in a different page (again see r800test.txt for details). Possibility e) would require 10 cycles, possibility c) matches the measurement of 9 cycles. * conclusion Both Z80 and R800 use the same order [F R0 R1 W1 W0]. The result on R800 is based on measurements, for Z80 it's based on extrapolating T-state documentation. It would be nice to also actually measure it on Z80. The order [F R0 WO R1 W1] would have been better on R800: it would only require 8 cycles in case of a SP+0 SP+1 page break. Though that would be incompatible with Z80. Also most of the time it doesn't matter because the top-stack-word doesn't cross a page boundary too often (even never when the stack is two-bytes aligned). openMSX-RELEASE_0_12_0/doc/internal/line-speed.txt000066400000000000000000000130201257557151200216020ustar00rootroot00000000000000!!! THIS INFORMATION IS OBSOLETE !!! This document describes an initial improvement to the openMSX VDP LINE command timing. Later it has been improved further to be nearly identical to the real VDP. Those improvements are described in the 'vdp-vram-timing' document. So this document is only useful if you're interested in the historic development of openMSX. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Speed-test for V99x8 LINE commands ---------------------------------- This test is inspired by this forum topic: http://www.msx.org/forum/msx-talk/software-and-gaming/line and more in particular the post by NYYRIKKI on 09-08-2012, and the pictures he posted the next day. Very briefly his test draws in a loop lines (using the LINE command) from the center of the screen to each point along the edge of the screen. _While_ the command is executing the command engine COLOR register is changed rapidly. This results in color patterns that give an indication for how fast the LINE command is executing for different line directions (see the pictures in the forum). I took this test as a starting point and changed it in the following ways: a) Run in screen 8 instead of screen 5 b) Start drawing from pixel (0,0) instead of from the center c) Change the colors faster a) The color value of a pixel is an indication for how many times the color register has been changed (in my test it starts at 0 and increments by 1 each time). In screen 8 we see the lower 8 bits of this count, while in screen 5 we only have 4 bits. Even in screen 8 this counter will overflow (we actually need 9 bits) but getting the actual count value is easier in screen 8. (As a verification we also did a few tests in screen 5 and those gave the exact same timing results as compared to screen 8.) b) When drawing from the top-left we can draw lines of length 256 pixels (longest axis) instead of only 128 pixels. This gives more accurate measurements. From NYYRIKKI's original pictures we can see the pattern has 8 axis of symmetry, so it's OK to only test one octet (only 1 direction in X/Y and also only X-major). c) NYYRIKKI's original test used OTIR to rapidly change the COLOR register, this takes 23 Z80 cycles per iteration. I use a long sequence of INC A ; OUT (#9B),A instructions, this only takes 17 cycles per change. Multiply this by 6 to get VDP ticks. At the bottom of this text you find the source code of my test program. We run this test program in 3 modes: 1) display enabled, sprites enabled 2) display enabled, sprites disabled 3) display disabled, (sprite status doesn't matter) See 'line-speed.xcf.bz2' for the graphical output of these tests (enable/disable the different layers to see the different pictures). Next we fit this data on some timing model. This is the model we used: time-of-line-cmd = A * pixels-of-major-axis + B * pixels-of-minor-axis. With A and B (yet unknown) constants. (BTW the old timing model in openMSX had B==0). In the last column of the 3 pictures we can read an indication for the total time it took to draw this line. After taking overflow into account we can multiply this number by '17 * 6' to get (approximately) the time in VDP cycles it took to draw the LINE to that point. Note that we can only (easily) do this for the pixels in the last column because the pixels in the other columns are possibly overwritten by multiple LINE commands. So now we have 256 measurements to fit two parameters. When doing a least-squares-fit (actually a least-squares-fit with the additional constraint that the result must be integer) we get these results: display=on, sprites=on A = 135 B = 36 display=on, sprites=off A = 120 B = 30 display=off A = 109 B = 29 Next we change the implementation in openMSX for this timing model, and then we run the same tests we first ran on a real machine in openMSX (you can see those results also in 'line-speed.xcf.bz2). Compared to openMSX the real measurements show more irregularities. This means that the timing model is not yet complete. My current best guess is that these irregularities can be explained by fixed VRAM-access-slots for the different VDP subsystems. But very little information is known about such access-slots. Wouter ;----------------------------------------------------------------------------- org #C000-7 db #fe dw start dw stop dw start start di ld a,2 out (#99),a ld a,15+128 out (#99),a ; Select S#2 WaitCE in a,(#99) rra jp c,WaitCE loop wait1 in a,(#99) and #40 jr z,wait1 ; wait if not inside vertical border wait2 in a,(#99) and #40 jr nz,wait2 ; wait if inside vertical border ; at this point we're at the top of the display area ld a,36 out (#99),a ld a,17+128 out (#99),a ; Indirect access to R#32 and up ld hl,LineCmd ld bc,10*256+#9B otir ld a,44+128 out (#99),a ld a,17+128 out (#99),a ; Indirect access to CMD-CLR ld a,#70 out (#99),a ld a,46+128 out (#99),a ; start LINE cmd ; Change command color register as fast as possible ; 17 Z80 cycles per change xor a out (#9B),a ; 0x000 inc a out (#9B),a ; 0x001 [ repeated so that we have a sequence of 512 OUT instructions ] inc a out (#9B),a ; 0x1ff in a,(#99) rra jr nc,ok halt ; ERROR line cmd was not yet finished! ok ld hl,(LineCmd+6) ; ny ld a,h or a jr nz,break inc hl ld (LineCmd+6),hl jp loop break xor a out (#99),a ld a,15+128 out (#99),a ; Select S#0 ei ret LineCmd dw 0, 0 ; dx, dy dw 256, 0 ; nx, ny db 0 db 0 stop equ $ openMSX-RELEASE_0_12_0/doc/internal/line-speed.xcf.bz2000066400000000000000000002071471257557151200222560ustar00rootroot00000000000000BZh91AY&SYYz!ʇ@PPO}H۵8^_mݻn}ƀ: <;u(FfN[y֛)J` ǶA*ݘZt/. QZV uFO]m%R(ʺ>WmwpB٢/Zf{jힱeܽؽغmU.zqݽTpQ!UP Fz4Pc':a@ * h kADlʕ^YkMd(QEBmu`h(%C>e.د{ޞΟ0&qk`r >gIUQ8ٺG {yn ۶2zSRh (>:AaWMQ hޯ@ץ8yglk=ō/U 惡lxn`) ЦN]T.\]wIV!)N(_`55 hgtݵE)TJ<\BmY3ݎgd7݇}Xϛ}ڤ)$ @wԊ(Tԓ﹮MC}zpOGB{秹{ز:sww4Y;8hD*ow^͍taFZToxY[f`t۳篖Sw =eux ywfq ֭:noukfOFO}-=oxz4k6;zöfF6 "ԙXWg Pm ۃWm-.kTvֶ:w}Gۯ?{ _?}~o [@>F 8r~O.m#@}C(FRuC G=>Ǿ?_A@Rߪߗ׏z#Agw{_o<r@ ?w?;2 2|. U |Gfwp'bd_I$ko#hTiAke22UTvpY-;"N_=Q1"HI˹-IxHtKҚ&B9S/#J$*N0*+2W 4jˇ(#ȣ(iTPpa\8V$#:Ȋu199R#1LHRMgTCKT=vSNͥTW Mʪ9ATEʨ(dRIK oqyYTʂjAPQTUPPD3d@SN%rM\&LXHQ\%#HQQ!2LQTG+.EsRңDT+ĊC!s_x^~{!I?HEʲRCZr.Y"VXA[+.EQQЊբEI((tϒSNRQ*AWg9QeIQZ*'T8d:U*\H,Er*.˪tJ Y¨!:ʈȸsȢ8~)`#S (""Q *Q$a.DDU"L"Ijf*6Ʃ?KNY;7؄T-H T7xDbBEI9QYh]1@HO4|<$K&u$ #4:gVS.hZ&}F*(;j4m_H/I|C, T'$=OZ yۑq/i]MS0!5ǩ7QwSHCvjҥfI'(3j.iDsbqmyQfO'۽CATFBEAc?}Ӈ:JAdOX{oRdI ҨO:Y>Ɩ$t+GE\΀D>#oߦ7Ҵ2Q̙ 8ZA{}dulX_Fd틯o뾿y7燋; (Hۭ'4` ARP{qgg=?SJ ;HjP/ӦgڞpE 0#X/ENݸD$P HgӁ&+뵿6'xʛOks3e<~wO:Arz7"q:M^3dѵf|>ZXI݋:rƍJߧs? \k/ݛ>/$1Ԙ5 ToQ㧷uN!wY=!-==n^NЎo_Sdw?W؄6q|cMޯ7]珏(|={QvE捯  ;ʣs^ݝ6lJ#ķϳc$P u&UOU!;@e}bN2'lWXZduMs[`$Db)={|֜c+j4M2HTc 6=_ *IpotC\Q>tƒ*aDAv98Q{;ܬ,[w"(8"4OWB$bI IHCAevG V5Bd.8B]"}SuܞI@i^F!<>21QUj)bT#jIft |Mνn~)O"ܒ.E=sA/;6>G8G0\ʙd6}=6FT6ڄq9ӑbk]wGPxE=6;r<bV*VCXU *\R.˯r9 "QCq@y( vC:C&*'qF!ZAZO0`G?cIi;&+fW$f{Ûvęݛ$,f"l=wy B3I˻ҋܪ,R v 1h'svy݃~9G̗)P `CěFQ>Y<{&CIqysNQdc BP1"ʙ#^=]wO͚qg~z A$﯆u{3`׳xjam7>2i%Λs}vEn۷)9>;z+;ŝ` +>Hg8/tKXg;I?.[5x}zJ3ⵢ|1GFY(j-'"3U[4bt0˻lXw9ӱ"HvH7U : sGrnï8u#Xu-L&NFMKW:w33K Hp*$ 3ܛИ]l"~tE_ rThtr"z.s8._ '&nw>8 0LϸE0{t6à&P O!׮;N}'HU/R~51 -f=qUUlTPtbם':/QbFB}t;uNlj4;P?C5{'ݚ B WTIdzE)AOϝvƞЉ!xpwLԢL`q9s(  Nx$_ o H?8M"_]}]ڮ(PPX%ҧ>O _H~/x:g=Q;fhb 4B*K= =7/>^^_^I>=w[=3n4/'}9xQ;9Vy|X߬SoIGF,@XiSc$)4 >?71><,oY |3B@I)=3gI:'lwѨ&6hЕſ(OxH1Q[-``Uͯ XĂ:f0V/5!CpP<Ït˙/o෥;t`{_<>G?ozZu>~ήr "p?8 0(UfH?S/ȓ"hUUb1QO9}>_CM=uV+omu8O'._}~3=~.smE&ar'i ;G |_)BA 6wʐݣ .GK#{ü<{Q#A4wH%,srUUw_GA=wzyBN=IS:\f }ybaHc|㤂grg 8!!ZAE%A B]^poy8S&{'![w'a/PN4YF‚ <\y"QTUyxŞ^dRr 'aOhJL`w7pBH ѥ!4c>ݹN3GժhΠ $ݻ RuIũvyvʰm>iЯE  #H$R~HqyZUAvL媞An'9yO?f~3|yseF΄#H_;u {.DDp" u׃}t`%Hw; TPI ˽5pL1|{ۥ~;;ܤ 7/y(C* Oxw 8u<Dqd<Tw5<`RI@K!UnubnN6 Y,YWOfjhyn^$B)~{ލ#Yx\p,K>4wy" oi Dc~ЁHQT(IQu6۹x:5ޗѐ.#ZX' \apjmfE]IϢj֝`SIgOk7SHn@7)x4j`2F94ޛpqNI&4K,Buv! Z(8q=V$`,`lϠ|~N/#aM}L$49:uuoxlc9kwKrp^F!^/沊Мx`SH^@p䦀JnHW'$B9$CM8!g[ppͣ,ϨSĦv UF\)B )ER(R.JњiL4dr斅* PTk j]ߌU]Ur(JPQ=IM4|2 B E(UA%kJ+P@0Ej'JH02G8pDqq2Y|/Y\d\[3ddpEYH,NsĄd]cĒ8WYztTWxy<7Nl~KV!R%Zu8o/ S=_)"ðtB.yǺ89is|0F.Û6_a.t)En)d@a ֶV|z&L) 96kȶ5msE) "O_1_zv\~=bĖ+KqO= mIQ+;g:3oԮ][= m#ڹ-$A&ˆ)M 8\y&U@6GSiNU{>MʵٻP]ts@*+5Ar.NuU*+PW~4B#$WLJIwQʦBj{)Y!TUʉ#ګVbRX0lܹD?D|Pz\S4PTp\V"Cut`ŒKCE\Q0v`ӺV]BG)TA"$8!!RQQDcɒU*"*!EYEUZ!˺^\9 ȊѪJ(((&&.JEMuF7lWIr *M\h*j+ױDȼ7rJ*"LaL(TQv\娑` BZF (`EM RQHRh@`h@QHX&P!&C!TQ+E+L&BT]DbȺ@YI!*)b#Z*J(eHybEO"WP;J&JP4.Ɣ4:S`+hB@BIih%Atk)ABXr*:E)0% PR,yM*PPҪd}vƒ5@DtBܢ œEJ $(BPcTDmX4@T.rBBg.D5ӗ vȪ$j(al&zy7vPQu/r-TNrsA:^m7Y\f9^%(0PQ@1h%t9((QE=-ԩYH;t+ˤܪl“.q^9$pں$-yۗ6_jEg)l˶T[FMt4%! {3E%ь $4WWfRܦ]_$Uwخ_g?6J4^?ͅS"T^U{Q"9jA"jy7>\?m`<`Ȏ$T2$3IRM 68vҴ#'IA@tJA@p;u 4N JhdȠ҇!tJQ\0:h\\ iӁ$4iMJ@ÃiM;" Bd".l"iHԘ]+(LvP$BH֝h,͔9Z!)NB!At*&в,ZPz.**V"rNCrhyHE1DcLEE۶9\~<H|}Llq!ԢߪGD@p0؅HJʖҦ:vNG`rI;T8(K_DYt}\ *JM#R" zj>[VLea!WW ̥mC˳Ypѓ`lX{Wj+lg#}^Ϻ=ɜB8-É΄uY,t9MMv_j!R21)'2$)lm=?!x!nۻ ;N?{o`7 'Jx˩L.˦(]u*$+quY¼! 7g{(348w2gvvUs eGOkb:s\*(;m-/cɷ8C#N\r<Le˾\J (=ѣl*\d=rtHI* Z1J9:T']V`2 SAtNt [+i(I8`vӔ @"4 TudZ*tDP~Pi?p{ O.>H*Dad'pDBeE x܎Ni.*FMh?J Lv?^̥幃oXjVsՑ#IL/s)J5I!Ԯ3N8s^X,?*VT)j)">OJya1?OsDޡ}epPo7+8HesB|>g̴?N/u >W^}>UWky +a(C$4rEh` zsGl <Sy0ϕtK @ Mgc@ voJ#KSj.,hIO y7DuJR 8E,:~emm^m< A_=F4PwwEtf\+Z}kye~ f1tc%f]ѱOV^k;EG[fK1 MTvnb*Hx;e)0w 6}٠YՀAWF0Ŕ,fUO \;2œ,لbzdH:xwS}(ة5m.ۜ_P< fP&k~Q9R#ՊTU/q7cTh> FA]e4gՕ?YF#a!97(j%f 1c^'1^uhb$ xL ,`lT=ɑّNV'b|p2Ӥ/2y;adyףy>$pR! 8Cg0>]<c˱iG5ǽ湖ПM8YߖkN`6NMtҡl f*.#dQtےve]]v~O) +ݑX=I@?g!vq4[OCz;kϕ>wG],QS mseݣYr)K%K?ź*P|!Sz&LPNGDo]Hz'WATHR(.VM?:I* *%rAb i(&)f(T4lf&TEQ!]gSC􈱥4ݻCsEfpF&%bE#=/;ߤ{CݪQj , ,F ]dqxAA@  N|D}$ ;KRT{%BR|6妦PDD ,r@UUk΄C=pT/VDw;դS.ւ*jXGg4ZD4+ fj&N.A{ cHh^47#1P˦ )H'8Hüֲ9E+rLFNF"p.p/Eȩl3;A UAQ 4Hm#͙Hlð!gDkr3L^ƚ$ dj$\*r=FsWv!AרtR ]ÆB4A \5ʧRx^VmTSEMQPyLIEaLh6/u f'8CXhAK{iL)g"(^V^!!!S'/E**e4{˄Y"D FuHҮ~Pg;>&ɡ(F( 2B!)TUl4 /[x_:PEȣ#w@BV" Pre`H .绹ѹ>yҟ}LREUp3vU'ۧ9ef73sծ8X@\J+XǮLuD597G\HNr6/vr/(ܤutيZZSNgL_%S.b^AQM\ACȨzˠd6&Gl %CD4}~[\|KrG DybrEAE*kEGLȪh;'hhzl8`ٚZ N !hx{s T9^!UmG(k\,Ыz:pĩ=L`V7&LD#%`9ZAsn8LoبQ='eAԒZ6gӋc@\cӀrG& gIէzir{EٹMvJj'AweZ]y^\`v*h*H8qÍdu 52;J!x 젊xދ0GУz_#wDE}P=:@!/ETQͤ9j'xxWHn+^:qSJӤG3*bzxv#U ,LGh wX &(b)no9dKM]sQ0TU@G:\rE^`8T+&.av1e$GJ SkM>܊"Jtunr ӧI4ㄜsZ={#/5*!PFLO ëons㧢EEp.lX^#偠J)i{!NBSE5K)5T QG RC'< Q4(!B Rrt9;) SH`ZT())S@AɠE+(L34i4K`(Fs y Gu vE+J0rMΧJGJ($bQH:)t @&"MiCZEɹ'JȚvy]ZNBehM$NO%{I(v9DDsClQJS2P4MSTiJG-R%%4]%+H_[+TD CH4ĩJ4  JA49@(PdSr@ЂbT)ZTjGAHsbthV{ rQ`(CG_$ҀiBJwy<it-`( )BDGF4Ioc401p+mmB1faȣ[#B%#Q+%!O5DtWFV͊"({* ;.j"MSBhO#@ RQ!t S@] BP>^܆iPt.SxQB) >XNII P i^Ƚ쩤i쎐!)QM"iJAӑ*uMP!$ $@qOҎdiJhi@S#G@4))h䎆S3S4:Pl%О!4!Cw(x2>BR} Les.j| rW>1LBQGb>C< R'`<ҔN%l slU R!4B|H iU8 mhUZ4(5hhV\NJ' KD)y !iS7JNY`v͝H~>1DQL-Dp#%|Ԃ hJZAL TZQw\)@zG4 HkJknXA)i~NȔ4+Wv4BhZ>M tʇa)@#iDP9vV  J:(T&&\N@0"B y U(r1@F!_%`{ /*;O 4H(C9 RܧMz 3 ղ>AM !KJS D2J] xȧ%JG:^B.RJN%2:@M{PD{ H(@4M9R:EALD òU*PvrϕIE[.^W^`Xi AN J vB"HF BhR BJP@(*aO6;BG$c=)(((N;-4{BP͇'SR%tD4H:D:ʼN@! hP *4(4%(:T9 i PJ{'`=RAH$U5J# @;=PxMTRǖ>m݂(9/gQy#a)8C\_7.4)AHo<]=:4MGv+䜪!X yBJ1@8QA䔩J%T>V,s+ j & =+P)@!䎕iJTNl"Qԗ`rq%Tp!HyoJ- >;!{P4SFhOdiGTУt"u4aڞr_1>L`H%(biC'R#pQuI2HĔ Ps "bո2W;vZ O9N=:MugtSrB D36i"SO8dhPm}A6tE-% ƣ꠼N]mH|8]! 5|iDJ =PNH JPMbGq8'- C|pQc< 0&8x7q&|-7e4&O0:Td $SD@\ pD`m;bW?"c1TJ >hoϗ8: C<| ȊX#~\a Cw2ΞC7ù34]a\ΝlLPȂ`Ȧ9_n yżuz@(َ@sP',w'< s{ gdaҔggvq&Ak&d:=٧`ízO#۷DC& ;ñ$z ^8w*+*"]pPArK uc`7.NLO/3T KT2gMzvj<3̑WbӲv` *myP}a$4؞ 5LA ߟHO@VFȁ D h S|@λwZ)!59!th$~OWȶACy^C{c.c>Sq@'qXYw +۶vKLSU^\7xXfQ6aP.!քk<ɏA:\A@!C>o>]Ojf'~w\HAt #դ%7G0ys*".O<P78!C@xI^ǻYػwbL2`G ι tsɇXay'!:“gƾdyW0RxkRqS38\P.{ߝizpgc(kT-|9+ԇg:o;}E)>^~rcaNd\6 4{<%׹Vpb qbF//Bzs)l D׷<t( xih)yIr'q49Aw1[%/ ^ X `Aq!fo}9;Uu+3w{l e j)"{4I8mx`rdHEm)0:,4x>wQM/-zG\?~<~4?i/By=$ߓ}8/{̜K{NBRH]8^ɤVx5BH`O'}0y  AJA}/L&6-O{HϞo=:䋣Z+"ȉ >@hhBQC਀Κ6.Tp A{+NӆE 0f+TwyfY-*nEӔ[h2IHW^67=9rOIihI; R}q&&~Tǹ<Kb%cц`JT@O{H"4CuۜNL38fCh"ku' | h}9*'!;u!@|q |{>mCOw9yқ %3lyf_Ϟsfe0*\ruf`LOl!jw{{'{ (RĴۄTIJ0s|rAhh|RO v_E)`ݏ](~snʘmP&vde:Pǟ=shJй3"^Lm;vۘs\xlLfҮ:\Ǧ6΋Auo]NQ:':?S=Yc'z]WI%t:/CG y`̽:픽kuWJ]xL'/o <>? ))N`q @?纀( RI"9ΞߝG3:CuyϹqN1ާ7SHdrI|sKgH{ntop<^;<<<qpA2%D—Ͻ";}~dO+yg=\y8P\u ~bVf)O?MG3؋B>Q9>i_>3z 'B]6Df@d:*5JD>B~GGZI5"G}F_xeFDl#n?n!c+[T6'$ &|ymjyW?z u2R wpd# UAW<+2e`du]坐k\]$+$?'sMݞe"spyaN* (4:rߓ=VQ{4 8}N\~w%^jvCwwt_ojYz96wxwsS !ζ8K~:p[}aMw7c=Wt}ﯴP4 @J?ZߖU2FS3ЙytU/uVqrȇ" G{Gm~~rtKO6(׹<,$K\9kKW5#a˵RaQ$NDd_ClqwywEߌ_{݇>%N& qd΅]sAY궩rrKډdqQXY!$բId;^#{Y:[*:eP#Ĉ\񜐔_<^*F~921݃d T4CC[]&g51Mɛa;xK߮ãN@E҄LG{>cEMR_@б e{ <>n~;8г/] 28Df@rW k[8s}yp\li0\4 (y4A@b'h )É\D}\ñ^HR`$eJzabѓC%AO7g7Lsm0Q|&&7#"E.{r&t'FEI0mnB?'c̽[#a1XyskXB$q2b'kOG.aLY. t-}ތqG{. է?bΡ{ DP $Vf )Q@TgP(!rӂl֞`RH U2H;t iЊK L#&c kp+:=|]xݩړLɼ^T& ߷($d]B@db֪IF5G50 "OxC n, *4"?%h>_*)'{X.3b4H3's}?ok~wn'`p. fct+*ASHRr|.P|wl7wΏN}~Ow|~9T!i: xN8CG ϙ_>{6C3̽W_"s+c˝gIE M>\ ߕ3{GF9nYg]@:;;ftA@Alhkt|/bOJU"*!_"7M>c0oPOO^/c?xhx&M Sv>g\ޚ)yB̦vnO(f`3N/6\)ӷ7E㧓33nK䇆99yL^#&uM#ow{xpFhxM;;f04r1P+rsw{%vzn; a+u4hm;r|t3M*sݷǭ]gGXtp z;I]}uCo~s:8gss+l=oG{rv4ѝgFY;s;~p3'C;0S;nw:;f鷰vmη_&x=~Pn/_xpg}R, X KJ|ޏwo?|t߫|b I -:"l6ZtEr&Dz`Boz[Z=U]Ѯv6?=oRgz6F'>}K7eGadu4g72IXHt5ͮݗ]i"M g0au'=1ų;!38AKAhjT#W,)49hVGZֹooHPr.$K]W]!cl;s^ *-͚mݒ~VjklFU͑</6*l?oVuK*Gm"d꺇fMXtvY1(\UDdN$Hi:t.F$9\ixPJ+VxfS^Y80{='`\3li!Q;\*AeQDstHGr nlrxIxx@SD=mfjAUBf #*#$%¥H- fumS˞.؛q[kW1ut۲d?̿?5SNr_-eae~(frLZ܅,8#"0`t]ضn7&⾔m_ ^QT3ӬȼV^bJv$Mdy;8%J|t_nG>s?tm??~xGnOk}NSi9`IRp==C RC(:TDNJ2;pi6K%׹pAzI݋%z*LP{ݩ!Oy &k36fjif-h0{$ "K\E%9#MPDrf{S>_~zOy|:EB.8 QpyA+G[ֹ{+ ܃_#X1wURbL&Ă.IēwzZ0XU$G'n&#BS2y<-kbsWքcBG}ه= OdLqAXy{7=+y$j82L-pq઀"D>5أhb$(TRt#z5*AD&:86BL*b 5DRc02A$$D:66\nh&9"am3G7l!VY \6!eMl!9o()eCq1k+bUbA9Uyih&*V$t3:"rB0.V,S#ZK$Q0CƝS3L"OL̨-Aq`r"(=Vq*;h&.ŌyRg H9AhMTD@fQh)Q" +8T\SBU%M3  ;JGD'HTrT͔B:JD"J$j( NX؅Q 4,4:`iUW*,JQfL$(4a0kXRGw ug"tRdQĊ$UEPP(LQ .\5e *b*(EM%AMp,Ü#F!UQUDW(5HC9T\,RHTd # 3KI:e,(52( )0I.˚$kUQJmD"(ȔJ.FI슢*PE&EG&rLNbT&XTJQVaLª*9(UQ$Zf,IRUTƒZISz9r[(*U$2̐ "9WDڽ\-d^Ԉ*I1]q"sV3/!*(c+5P:TYm=Z@EX+`ntV֜Q69Hj5ȩs' 0RM0kGJ  '><||ϖď77xMUTRW QknoyrZ!:m_ sg5OnUi/ rH(|vE!*̡@Ub/@0U x>O;>7YJ[ CqEVHtUC~nN$wo+g&qUH`*#jQ<=_܇vd\MqaPvO3W,AD-aJ̮{h#[4RY|(őuٽ1<^ń=+4_\%O < *ڞSw![.]q8hU‚q;DLغ }B^?CkɇAToXOPܪ1h}s؏OQوUPG2%rP&0$f'Y˹QL" ,&N0At~]y JyǑ]6BUFc&xHmyrO͐>DP~LA„_J)=UcDz A,) !P t4:UR+ʣ\ *b]2WCG!Z9#~w7$D?*P;=uC]ƭ\+zSQ@&Bb62PxDrtPHae}6)bna";488pTb&bk5VM]ʂ#β)*(aAT0|t"KB1"~i"0~IۡBeL"=_3Θ0PNq|]>'Hi*$C lw,oh[r9Kf ~2QKw6{6{["^ȩs)xyZ ʊ-TEUS#q#>^xj*}#-JN">VQUw:zg/9}z kV2)2BK]=7l>ўR64KTcRi-j~/^cAs˘O[?'}8b9![Y,Z>{hST,VH(4h&V]Q˗)\,.qI.6R4%U HVy5\&]UU!\R"lu 3dbHLyPN.t0blEyAr^pょLӮT[ IILTDhĞNq9Q]7, }S9YBYjwkJb( FOG>؏p!apߞJ`iilPO4!RCP4 BUV$>)%ѕDBHC~ hCUIR3E79)YR'6xK.<7Jqwd=(FH Ϭ)`,ssFIMD:(Д%T5Ai2'ceV/{( >,EH s4M>]) @#ERRR:)<44QE~F6MDQT|{M1{xq5h) YFJ@&FIhZJF"% P0}s e" I+Z{'s)) T)%E/Od@`iC*Sׁ.BB~<(ʂp&)O4}x <ygByS´McMTTB5`9M)oDBE51M=,EWt)EW6Q'ei BT&BIIݥ{#I"QiDŎyo,*B* ;S&pnK͛q}9&˟-{?!SSDTL'B@Yedl+]) }Sۇ0PF"ivG-C)TAEMrQTC,E Bt+0 I#j+ƽ_%.1r*D\ DDS N&\p/f|} D>,s"zDPJ=FeA~_ẙ90DME_jp}NpLA~˘lS#\ŠEG~ׇ51k#E50R!@伹USO#{e}Ov!U;.4i.`i|p}PIvC,C"VTPOO?^1B!?;w @`O"{~tT) n܃`~/#ad?_(bXeE1֪a8Af; S "c**s*C9G4T!H6#_>A!8 GKP@U;Djbs+J"E(MjZGEP铉AgAUQ4kB({;lP>t;t&j{ԙ'j1yNQ>#ݶdq ^Ey\eaNjeXKQ_{3ԸՑȠ( " _[1_k`:sTqs*eʢTP?P%׏uT8UT ?WJ* \9vV7"TɪyC@;.pˇc3$"T ȿGB"2\uqtZ?jji:YϷF;`~N¤E g "'NwN9p*zQʕQEDTEQE)14P?^BL)>?JE26=D=OhȢG>f] ֑T> |dD:B|3tF(M"I&8t,4FSIT6)J>U}O)%T.l.GED Jl\NX3[JHG> 2X[lTfC*`"OM@5l[#CR]\E1 $UH|sha4bj 34E"P- ,}XԀxf4D(ցiHH 先CM" N-)@  xe#IE24RIT7W¹>)S' )N"a;-\؊^ɉf<3kuhŸK*y%eR-xL9%yEQEG?:P0Ң9vfv.n]Q'lcm;oQ \yu>䪧t5T3+$` r)Ш:Q.i|&MݔtU6:]^^?Q-\MaM5CCMhI*rD8TDr(!M?s\"#Lʨ*l$ˮ$$VYמ~kweEu~ ߗD;\Ll]p|(\ظ"Dnub쀤?7yƓ| iv,ZJ}\z+R)*Wᒏ'~]OSMU!/d44w.'Hh T4?hKwyޕ'#.>\Ƣ*UUAvFZHjQp5k7*qM7.fЌL;=w'}܃L2 PPXq=Us+<هr_WCO&.zVb+\jU*W*ɛ"~*NzO‹ tgtpxk,Mjq\/d{v#2X*?+fܛ[)XX5a)zBLCS&v\r\x%wN 'd3ݴr&r< /o76m0`\R@Kv?nu 2{ 9!H%M֍HTO͕i4R4.ŐҜ1;4jja'B IV'͑FhDd~vf.D"bTCvMº 0"MN8?:W\({rCGd/'I*OۏBA> J)Hx` HSIV.ն B߁~$P/te{'}=^ {J]4 NJ\Pɺ! ΝJI@C+_2x%~lAZT)$P8\nBqDѷqB6Wl'PiJWOs !@"??H#iTE7Ϲ{ܮV$4 _?"= S;`{7'υ~GЁ@/'' +x}=hOBO~OvdzS*l>5ZS. 1$vAsNyMW]օBb3%rDDef?i7K.r.30$4(EWu'J$G) z)4쳴=3Wr Wg\OxV}wTl%)HOan,iBpxݸfBrtQ4:q$C9Gs!+s*.ݥCN (sC Ԓ*.S~ji90): Q3+8'3VNa܅FwcOu{d5Z6F[26mFiJ*\cg׹לi9mM(od>N]z=[BsXpSk^CQLɫrJ ;O$%PҤ;l?w \S5"A6_2q 2l.2C x&pOFE ̃[KYΩ VMhKrBRdKIǚ$ƺ$67ȋ39 }RIgu,DYv8>[j.\ȕ5ry뭹좈^vYiYBGeyx{%GSÏ}(DDo .TX7 o8~g拀)HWWA՜ip`֚ZgHa4~P!!>o^G[ ۸xTg `$0$cGaB:{$`NdMWӧ֮S.ls,fV|E!_;9ɚ8guiۤH/7$cgiEJ"ˊSlXM9Q~cxkͦT2(<#B+ 3dg6&/9Tw%wIgRjdᄅvTR2TNҼ<"DJRL_Y[D=Mrj$s'H2khB|N" Yu]ғ"d™i6(H;>=ʠ"W2Xy"p+ÇG'$.V@u+Mg ݔ 䡉@ЌD0A N"TtatA P% 2@ @L#IT I#!NA!8R*E1()Ҏ{Wvès#ɤEЩ\@avn+*\s YyrN_{N&r]8p 'u9tv8JK5 *$Жi'O=. 4 H٘(i) D@A nq*9W .z0Ubk͹oT|!jJqܯaNtӃ fq s(@ ]PIEֲiGWZvL $F0v0zRH31*I*yUT lMFxR9V+k0QFCe7#/IHB2Xu3}?w=}zN#O"O& Dkpk<1E$=@ ?RD(hE F)};Ia2Lfgd ;t t`FrIZΎj^"}4[BTM,Nn-E5P#n$[f }hі,UP n( 'q F#a(L% %XDHʣTYj:\R RHVikw3 7OA*WY#Idi[PJ֑m(׀ mEuu%""XaGR , kR2RVɻx텚DgI3AbYi{Y> ?O|NV藆=Dar ^q-` @ Bui8DBA&x*ฉʟȦCFH)m*$U RϢϛ%PH?lů ?i}*Au_K˕\=HvBDY_fG퉃 L\3 ;;Ïݴy4b^E>R |)}=̶RƞJJ!ӽ8dDL}=pZB+"Ƚ>]1}JX}VLM{;'P'!R\%t6łr=zgr[372KmhDjǞ{tz(.9CV@^T9 tYIeHb}VehnMyéo;:xtϺ>l$xCv1)$lQM|yZSm<}*x'p,d{aX'Ң#̐~lf,q-~Oâťu R6osx89b1UW"*(g gZd3P; rܟ=|gP?EݕB=(D<=-sinoNEȕf.RKZ3>!?p1TP52B*hYa:?[a-F!po r5msy;syQvi$}{=J#P-!%ҐBE;B*o7IS[2--?](߱o^ܹJ\ Gst&ac?Wb估ء5T*} VW f WZEz$jT\#vra\{7˘ kwy?;EXIjd~|dCill>!8S"|Zns^6NCcvN\XED1A,k1@Cʨw"g&zEUOݵ})xvjԛ&D?V$ζ&Nx 5KԶs9.1=9C)HE]cvaPJޓ_}E%fA#U.EIfCQ KZZJ4B@65ll ҃SNGX"^BH(Wr@xU[: P.k)IڤO2iVmȰd՞2Hp̮.e\~9ăU] NB띲= CyȤD=пG1M4W$R٭iA?Nv)m5@4C@^` AAMHsjhTCHQ /x{ a0a me hBТ.LbC HHnq0P`hD?y'  #J FZ]SiUBP>yʅ\+$(*LIUB1-d.kV TiiM4 AfQEN"efFN?QA{M_~u8abe'{u5 Ў\uݧn T9 9QIЂXQq̆z)C7\$8R! %&[6JLGl֯1*gdmM{l1۴EAg'{;B&l/kU8Ŀ%6\%ϝI$BO?US!i)mZfjL-R{sd;8+g= kT"|eXӨ &6Jо+&Pܮ Ӌf`xYhӓlָ\mP*arȹǾ;߅:Vv2} oi+Wd[eLwm[K\]h#Ց̻l~ e}ՆO[ jI2e>m^JK>)tCGVjz nt%/^NC/$sFgVwzc~?sGZ? J? X4@Al6xFb)GKNƌ)@B ˭p5C Յs-^kw;9D!.$Q:Z,x.Fj JdcX!%ѴF6Ttm4鉟!=^]`e}͘M-"xh˞J f/H=fE_Ό˄ۧs_[T>>>LXټǺ֬AL e.ZgC,o1yI`Uf~<<] >oOQ9*y4`-as ,WNsnEґ Jr1ʑl/t#gV0ot3s u.?.y*dy|Q58%B%f\_Wxc_^6$Ey\DR)21rnuV4qF~)WcIgntOJL(^LJgaj&kDlB"Ӟ61!@)]@*eBA+%"& AЦt4HU! hJ&u4%A .jHHKw! &,ر&Go)=&LpJDp ޲=qčw"hNwWJ1BR9sjC5M\m`hSqU<;ױ'<>{SIW:sjkc) W+R 6Wn9$ T-eCߌ Kv,P#1hkAPBRQmMe}ڱlz\t< fE-(T c'(icrHdFU@+Axف6>Vyy p.UKKl30fhj2Su4ZCUlxkiyĢdCmGMNi I0AtFc+`*J%$$TN !`J,"T(`lJ54ԙ$$ѐf59U ^;ÊrRq)ƺ뮱:k.}V{*IS<)|<M'&+צ[lXkJ.A8a"循P^N)I*]62ITH2CI&(BSR@l,$&RăU *K#2cA T"Fi&H3f&XAs.mg/E-InPS"lk>@"@h$i2 C&RK&EI8:DKW]t$]CiKp&+ X`V0Ab`r .@F&KqCp%_۬s_y"AY';+y +^VKOI#/[߯S&Um2TA}IskW<Y+m*vHF;,\vyUV ،Mwv,)&\fdɹ$mUG&ȽԅI!gDf9v3E[+dZZv6.2suvo91N" W4FP*Q&?ztdj;L7un F%J"G˥^ƵIo9ml<}<+-o?Dy#1?_- 6դ])$rddx:bDEdNIhVC=ej}}>67lYh_x^DZz?r, fu;>@$W1r1p.O?m !4uZQ 2*ΪPH!Q<=)+)6\rg9L*MdT!QQ]P訲(4HMRKҊM*U\ʬSZd+;J8YĊtU DQQM4dU[0PTkJb "YaijmiDE2sE\)0QZȠ*S`j(*2"fi(f\䡊jU)3RPz=a$YI֙F ( PĈR "UFjh9%[ȔVS:$Uapxr A>C'(=Iptz-.rސ>/9gB.R\09scI)Zs>rTUʊ#DU.\-Zv!H`j-("JjZr(N)( "tːUE Y!QDD\Z`Xm u[ABAF$Ff"!˄J,2H$s0(ȱAHC%%".G3M%E9) X6DPH*EP+R(ʈiDKIH5IU(]2ZFU®IhQEٝmLH2YR}}iQ/9փc &b96Q(`#s*L][ɷ56mĶ'vfLL]`w\HPav\ )q#Cs|BWaHYM %@^n*Vvاl틩M|Nhxvz0: ^I!Sؘbj"Avx6[H./] sa_y9۾,M (K%uwݏӛۯZrHv=Nz7Ѧy =_rB1Q'C PrZKc|5W䇭vNx܁ Z bR Odm7~oܖc>6ǺMQU5#S Tǥg^5R $'qu(5}{wD„ԺIsONӏVJ<|@x<'DSAPZU Pr~C~&ډOvyUxav{Oe\=wq =vk~C6T&.'*?^H>9.*TIU\sFW . '.UPSrBˑ^E^aUQTHEpPr"RpƨWov_]h?G1_V BP?Br(B&NV%LlR~_>C=X'S>NiEW6 m.m#uW lQ B!]DQDM̂e9LSiCH rG! 9G:feAt*!Q'KTHLb _vWz:WUJJ(?+ʓU M C/ʬgL;5^GVO~ |_}$PF#ytQr DTU}=2ʤSvG60#Rgp\x"?΅UTjj;yoJb)).łΦS*܉J(aϪTDM"OL _jR* ,';J?컔yp(HUv(*Uphh(h] 5Gn*fD*UUAUe _„hy$Nu!E4ªR2JT0! < TR9'{IhjLj3P&TiTKQLBI4A'J9迉q5DDJj't(%8*}T(i}Sۇd\;(( /"*U@YЮHSJQC}OkΊ+N*|Pr!{rT." J("" d9f$ʹÌU':%'jtv4NňGeET~l,W9 Hm+R @H|Sޟg&-ϊ{н}kHLU'tD{"&4IJh"=H|CJx$ `SPJ{rSؑ|ĔPI?#sh"R-#Ɗ=y_/U=8_%;(݆e)bj&BXHq5;L 3(~/=]~SH1m/=\B70@"g,\ ePbQKjr(9Ot$:J8# TM4@T5,.I(.cE =9z<:r9E0"S*A\ƎF"!((ILr4BE4dQ94RN)T(Y,H { }~T'dEh4vEb$M* H*SFҕCN,TT5RPĻ%&A~tv }+ 'IƴD"/0AEEC *ء1AAP@UcNۙW𡠂ytH=9$eWR (Nx6ᦹ0͑L((RET)U ?Gl7}O<8\>TG>fN{yDTAOPZ?sDPEUEmCGe$cugg=ʜ  6ڮyʙ3#+s*I*HjH3%ED՟nUm/gGiHx{[ɞ~EgxoY*EepND_QQWvPfMbq!i~^~I6M[b14b@5A" ""օQʊ#0D|]nr2񡠡Z*C]s*$(C0db"Z{iNch<%4% jtP>̠/% 'H` l6"@<O {'ӁC*{' P:$CG4) F- p"T9\JhT$](/ {*BJ/$]0TU !Idr9EVg1TM$(B JÕN$B[ Xr y()At809Ē OB(mGlsm V3HBJE DO$H%{ VGJ|pDy BEp}hDݼ9'C eUyxZ.s$rP^Sjmj^O`Y(stB(EVK嘔EHBH?##kΎ rpH\Wi쇜{^!YS#4PC"i2hW[8+n/{S.rtO~C>HR9VD8kTJ%G:kH=R봲5`Y5625=AXܖȹ 4'7ߜ' !mksG _jy>zrxea_LE/xΰbԦ [otфAf黤_Ӓ C!&Lh(V76kW|}5*쌕ȨJ.h+V$~ǟGӮ:)akK2-bhfW41[pBD\dW)f?t3Ԡ~'>ʇQCą^@~˜"zqG*I$Zrx^t*J]$2\<ȹEr(E+l3VȚl ~L8)]n'b$7![y9QGjDT)ߢ'[P_>z=2iUWż&6oʶE=5(%jմ.4TĢ&l rd1sPkh̏w\4>OE*ՖW\}5'Rɓ\d^;?A. N~&e;)&}*tvҒx& ]-$"LL;ooF2c~Kg+EvMbknVdm ^%<͇RgE XϿBS}?x(`= @&3$%9K%Bg`?n6 |:~\GDjD 33~Gm_4_c$DX* H- |C  ϘWHR !DBዮG{G<㶑VϦIF0Q 8 R)0-\$QAǻHD8HNl#"fj\*e#uP* #"{kgΙQ*_DO'D_jll_ ސq RjjI k *QYx"": #U2B@wt:^x/Ґ}G{{D{$ֶ(Ia*Guβ}Qi _iJ+ݙV=ᤉ^nEz8i l5DZF̊9;< "w^y< ; )$HC!V"ʫRE@Db].di& M (&$ĭP!$R!MhMgiP›nC 2ą<@L5iumw/94!D( րivųu]9x@򢵦BҎa JhDGEbzB`AFPtHdMa5/2n22w"@|*yi$[9e#: vm]T@XUy rCb&RqA8z $`'jsQD&IP~k/q@bЁ ^b^H4u,y?.U|+ɼxAe9:]LrJ7<>241Urv8!Ό{~'RSk#>Ǹe:e$_3Z O V+!rUjbQCPz{׾q(B"+2gKP觚Xl2faؓGtipF5J0ˆ Ng99ʠ9B3+Xӷ80AI$d;F^GlGj@tklMNxhˊ$(bNy:U iy8x)cJ!P-NcwWzq+\=iEp+@/([JrlC=D㎴G`6JR_0x\y44Ҕ  nEp+*yx,18%WHiBBS KǾ3WAZ:!#"9\{=zϴnmnw#ֳ$UQ`)Gwe}'W/$/C(| 슢8RE.TUc:Zh؞D'e*rZ钤fV̙d籼$nޜNHiTI&(LJR, "@g̊* rLP2` D.ir(؄A>ta0wـWRRER$,f ȟcVEQFĉƐ (1BktGvD>}"`1bWSH B @Tu1$E@rmp%\!5` `SMh5=ݭf0ױ:ՊNSk P!@ٳeu`V[&)`PPAAb& Vh&IhmUێ5ep _RnA% &wM(frؑ_6#V1VCZzyv~ksw7v>?ȟ=:%$4 Pvɕ#DcKA dmB!+hR>:Gb6;`6@F{\GFJy+_(ﳊQG;`ta PƱPR^ ~8'?EIAVF"A#ĻޞA$HM*@B6gRdrN)~3Tܒ<1s],447m$~AϧreܰRHe2ԋ$DJ(^_2㋃G* Ɨ6 i(Ǩm`z'%w͖Am\@.Q*dᔙ/$OOm$$ŵkggR,d@VrڌH^)H" _cN%<ɽ#MlNޯ7~B#Vᶠ*YcT/du; |LxI}ωOEc?7uHI1ZJUb8_ iꁯ}d3&{~Tw,v'$Pϫn΋>\zaL?[߿P^N |ExÌHx30[0<'dˊϾL'K=w<͚AiՔ\-c╪-!ABXTp=,'Kͮ1p,N;o+gyؼ9)=Y|4N8pZs };XtG3! @G>E(T\R';vZ;'ڄ= ?ܜNQ)~l##iGoWp8Π-{=>{79y!We`R]Ci-;ri4?vNǏ#8O>SP{+ӟOb[͜/TLQN$c"*`6`oegW>Bmu: C8;"~ ;nNNg%M+s\\ XW3 累My&OD7Xuw 7ɜ&*F+vhEE.fqԂ<48HVKxt}?9W W2 ¯*>S""$WxRD:Glm r )&碣KfbĚE致*CmQQ؋7V70u0/ 6&` JJ ) ` (kWN Bi d#GerùDULLQ5n{ OO:t&aI)59γ 2v$9N9Ah4$],.&G)![RT^m61^{nMև q&LW8 ŗg E1S%LNNd28M4rME<(Rp t!0SIyLt ;A]Η"*JR)k!mB) _0GOdhMMZ34qle n3 Ԡ# ʹV\s (PRlʥ 77JX pTpOqak v+;QJe2g|ӗ=yF"H@99[<ʙOE.:W뫚7 =}D{[R/&isA V\gp W5diѓ&Z !!:dw)&IҜ8C9)\qsJU-wvi /'KKl2{n-+Q#liJNfd\A-1euN\O8LJQ8à4P( J Q;9D `Tn #}1AGI]Ytta[PP  $wo&XT!P(*c.)sPIDdqIw+Ǟ"V{yr !ZSmlS̽r2gijx3Їn~㏮͛[s!:s=){>lv{ Gɥ AD|΂ R@#AnET$b0#z8*D"F)QHG)IDSM qcSxI&Cz#*J8MoqPNn@A!B;\d @0&0H8a(ulF4 3Kg22ʠRIJ%289X27k,|K_`{(R&σvvj& fxZ–XZЪ+^Y%mR QX6l\X.i!CFluM˩%A( QJ{Z0ZH4TԚR^Nb1 8Hz8Y\^[`vF`1ue .kquyPj(L5У! yG24.l:-0\4xbp(J_hXN&Bj7^4:62GTXN0J`*ALX`% v0H"̟1fH:^-+K 09^\ }L>} h{ sv1p_'~ 59ɦsw'yf=j>A۽xqCy/v@.AaH2-XA*D5B &F.y~oN'NS9<( mHF7aCI@A%`$v邠 PcI=05+W GshWP`M-fM&<5ir_.PU QADQRV|s=}Jc߅t= 8E )'V-$GlȸzmN "tW{|>n^RVn*V% Hp^Rw{:ap8 B +=I#(yT^Vӫ;܀v,l][7'_7{o|V=>9`"!eG>(r?~o<>[? ?46{sCp.*`syA&\a֭?e-{Lק#35I2i*#rtɻMѫ \+jE";5-UaO`t:z!I${ bXaxZz iB#vN4Usq8gZkW<XݻSx{_bH5n`<ޞܞy8(8vx]م|s'/ äqog!TU|{=z=}lzNגtyˀ.EÞGktGw*f効ahni/4S/}<`҅[ Ņi~lrM(#GMNYBj@Pb(ح#іOo܊x(K|O#7xGe]=M'pt.:ohMMB5*<ۻi͎I=x$u}(is{gԵrBdAnpU \xx[r'HC'M7:F1ˢn.Pى!yպ$cGi3Sв9KzpK[<MC `l1La ٜվw+(>ٺo'|^E3fN@PDlz+@%"}@½b`(Kssazȭ$|יg {ZW7tszN5XT8n fk\Q;3.'J#]Qf)'{lڋ*3+gw[>||4$)m"qܦӶ2Vb5L#vQeXIvwroSOa\=AEReb"kA1;skuFpM¢Nd̫n .Q^6հ??2 zdpmE^2)rf^C=s){6桇ȪS g#&[JQcNy~kŔ~#w8kQTYl+Ա/B*3i*/Xs~9a?=ЮVq h*<9ΗzBOQ߷KU} ȽYQkԾg]T4dv.A6oZVޞ !DM>~É tnwfhu/cvQD*q %8h$%˻o;$ȶK{\YиҚvN61J9:x\8͏f똮,8|}g EYlaK\2kׇ篝'!ũvǜ:Lv.ل c%"N]'&rw.|'hɁ_*vA4-D*>%?xȉ 9T>:AEP "*֤;.ʎ^͜zWU9̯=mbAN*ԍXI\^ ػ"ƻfN"9dڤ<&/(*hA 1P'*d`eQ{UY@?+_># }yӔIP0 #Hհ֔#)Z }{CpI@iyrh4Ieeʠd!$Qp! ۮPU %c1;@D2D0< \v%U(G/(E 8Vtu6oyQu!{V "! 'CE937C=:6sffz.H߻dw~f|On"AZydIg.{ۧEoiQLY˒\ 9!T.^*r𢨃vRzXs!.sUI껁qŽFJ=Z (" ReqRJF˥pU4 }1ӹG A:4~r9O&-SIEi(:l_,b䥴Tg&a %hGyϐS@A4z}aSEvW7֊i&))ZEC2P>N̢i>(x/~a b)j;D$TDIAMQ/X j!Q݌ _M(JD LUQ$CT1D5 ,A%I)0PSM 2PU_#؍TO )t BTJP7EN 6WRc<8PzC(?25w/T:@)(Tϟ=H@]۳bJQޗp}p}J3 {p{Px*lG($!o1Z"؝3v75h(Jne@=<> X())i)?S\(鄜}TL>UuϑL8p?DrtP;q~TrO<~\{|`KERTQY&iHBjd(фı 1GJ|r_$ODȱ@J"͗! IJ5HWYlH(")(ha(U:e {b652zˑ^c={R U%J(\멜iB\܂(͊CI~ :NE-mB&88FH_>u8Ɍ6Xr tgaT(id"Be4p!}JGҀ%T:J'C|FG*wL0D 20IG *"JRU3%0TTNӈVГq03vC@ ;QCjȁ BL/c"0}MRESI@T7aBPUJgSgQMyrp@(КvjÑ +_L׵P|r̠Jh*T[RM d!EV8gK*9Tw5nB:v b9? 'K6:S*&%^"Ha+/.BbRa] Ą@)Ea.RIICAUJC$*4,R$&(Q&yP,*'p}!"S@?pRDJ&\TCHkHTD#K}쀜EJF<4ij@)P>p L0 2"| eP'R &RD_ZNS*)j\9Q3"3zDEQWh\(}'{/nUEE"-.`B: E-76#՗cOHHWUTo *zi鰣E佰easjY#dADd6q+=_K "G4"PY"̋Dϓi+Eg 2ٖTQovEEۜ8ӲurY_ 0 Efg?b$DSĘ}I2<_6='kI/3",h-qrYFMG*r<.{ƻ34'JĊG?TEtڜ閍F&cVEgNvPBfXUU9̆KuҪDBR!iIoQMd3OKla2E`}{0~j?f|Kwz_ż9W~:!\-H3aR݌:ߓ^8VV~%p_vAOhjQ'A\A9ȡwP+㪬˔6|Rfn%›W>7'!sm&EUP4kTIoG PHAe$dr3*(g mӐnp IN TW)3']SFP*'i $>.ȸ3B@bnܚuqХ ӡb_}xOzYMr&!%dS@m]q%ܜUApS ^E=kJP(},"&lRE댇dL\ txa22 2>ʦ$*BGV\ f<{ՠ$E]&S(!;8jL (I 7H,~{D>ZWdY598U0L" ('BV: E\e$Dn;.l"CjP!ݞP6X&yg4K,TN@ c' s h$TiJIDQH? RUDa red U< _f8Dp=4I r)P+Z9yzXSӲE;;Q^C1ru= >[Iz^qeT!&X?&:R5m(ɟ^"W#s[-r+("9-#ն"jA<;DkVmJdZ:MYNd+\UOi& )l y3n ;I栮-NQ{ߧ™k] JڠgLL&t߶RrY(*(*8p G̉ϕa^|<ݼ9tN[i$YdJ]l;bk~3տ#x%t\yjC7LcTr%YefA6lWKM&-7/'GtQTRDA݌N\c!6o#4AvW]H|֟W^BGii QJ*0I$/vO]4Zgejsؠ]ltZT R+\,̙9;y8tj\b-q,zycǧ;ԮLs$,cUC*LU}wZmi.Q :RM~^x%A}\wT'O6"BwXGhCo"C8{k^:!0@c/'n;t,mz)ɶՊ%UTdRg&PRSV'JQ6ꚦգ]bʘfj%8ݲqFЊX)q[6-&;$ vd^WiTz>xWDŽ_XT d4\(JW}UÅ)`Xuԇ)XiTUZDI'vqh-$4FPдS!*)t `^JP4GR@ Q2 2@. H: B  f +m@Ѝ1$D=r`m#Oz zU0'iD.hq-5ԝN+ wg9vbj4i(T$E|tIV-օ9As8-mfRB *vH^l9}A[;-G]ɤ&1[g(RPVh}9(AD|y:b!5M 4Q!44Hr`&3t53F -)@. \;i' JSª*4y;U豊ԂU\uPr ,LT%#hF(6@RH&ByttK{Y 'jO%Rl͝щBrV*&,ֱ%#ܛP$}3AX~Ǜd" iLfmƞQ`ȷ٠hzR竿2G⎖۝'4G[2$;͉*9@Sj.BKxGё_}?MȹvQwCwskf亡b5UsnȌig$Qj<(P҈ô fLL@ p̻,f [ronkCxriy]9A&Ҵ!@LU%eW *}bOp$Hrӂ!KMhU2S j*#˨}<E" ,BuhP$<$<"HE헳6Đ$]“(! {reؐDZi-D`n %SCIE%1$4Ql"&!yatPp%$9*WNCYKE B*/hv]J=)6A:B ɐwAB:]*{G?#tubF>$AwP\<"d*b,m٥9|z #nzre/3ô[-BF;iʛmHjS0]CF]-Nԋ3lȿg3Oμ< *Gܕ/Q9hWED4\QC"a&Д¢#8"=D„6xiTU" *U%%Bct)cnC0CX"Wml>գvLVfm+0/ҰMIPL1L 9mɍI$QG+ AR 0¹+"=,b֥pZ"uD XIqɣ'bK֨P`ڂ 3m1X6#HbfPEqbDw5FRd1 jHn.A"\ژ SZ].ZbA@.aa{ذZ!~l5 k`$'r#vZƴSnJ6m6ѷjmiŭ88d3:CDAh4v7^Fܗ( Ҁ*`A ژ&!116 h ԛQ@@ lԊf3&X -)( e$#BD5%);A5A vsގ_Dzk?اnNo' iF G!WS?/]GY):@GH p**ڝ( r ^GG 2}3>>`Ȓ318n~MuBՠN$\x&Nht:HNJ1Q8>]T"G"m;R ~2:bL۴]ʨmҸGoNO?/#ōvƊCߓާnQ:][k{tdՑj Ww{оkJb0cd{9"d:_"t2© I?0T($ڱ >ɶc(B0 *-|$8v-!?r l].U4~{Hz+<%egYOD 1l#P?> MV5([1H9!ɤ8Z1U#E*jT$ihAve$>}Maq'&S ݅Hċ',9 VM]u1/FȶgTuȑLT 5/7HR/X6ʸ ٶFuZ(^LDmHOl6#8ʊ+,†r[rAa1BaZߟms…X #XhJi!ҟA"TpP<9{!L_m29.dܑew؆Ee-RDERWq2O zHx5/td]쏣 qwdjɑp,2\((  ^X<>dҁFmJTOH?%|dDRPP PRԩJ!PS~ $6@~)~$]kZ4&B&!֍v :lbtvk&T;xG]}@P!M&/tq;i=O¡\ya+=b>laG*)$<m抭IKNHz,,z^EjͭbI3U ? ˙'KT1$js=g;,(Bp.HFO_5>}eO/cwݽɣJ"45|Au a& fN< LՐ:~zY!4'6wAG{>z ,&  y;: NO7<~//np"M45C6Rw2)yXVs6߱n<'(Ƚ;(NB mjU8G"dQ/qc&Z&T(H0iV]ˎX8"ڸ@g?u ;0|E#&*V*( J r@#~\?'s<sd`gH{(zY`G_L[-D;18dTIp'" 0(."RoeG1-v FfDᆻh^4Ƒv!/*|*;R(0aZ%-hDvigRBShTUDEDêUL$+(#"8PQqTU UDQD\ʂTQ(H-%U!Z6EPdJ!4#$!VB(8RfQrDKD4-$VlG %0ЮiIXr HTK`@ѩ rTsg**""šUP\+s:2JN"( #A4# #CPU emulation# #frame rendering# ## #sleep# ## ## ## ## #key press# ## #frameskip = 1# ## #no frameskip# openMSX-RELEASE_0_12_0/doc/internal/openmsx.tex000066400000000000000000000462741257557151200212500ustar00rootroot00000000000000% This is a very early openMSX design document. The general ideas remain valid % today, but some of the details have changed. Especially the parts of the text % that talk about implementation aspects are outdated. % %TODO: % fully translate in English % fix many(!) spelling errors % corrections/additions \documentclass[11pt, a4paper]{report} \title{openMSX internals} %Add your name here \author{David Heremans} \author{Wouter Vermaelen} \begin{document} \maketitle \tableofcontents \chapter{Analysis} \section{When does a device need to be emulated} A MSX computer has several components: a CPU, audio and video devices and many others. All these devices need to be emulated. In a real machine all devices work in parallel all the time. Of cource an emulator can only emulate one device at a time. This creates some problems: First problem, some devices need to emulated \textit{constantly}, e.g. \begin{itemize} \item screenrefresh by the VDP \item soundbuffers for soundcards \end{itemize} We can approximate this by emulating those devices very regularly. Second problem, devices can communicate with each other. When two emulated devices wants to communicate we must make sure these devices have advanced equally far in time. Example: CPU changes by means of I/O-commands the palet settings of the VDP on time T, therefore the VDP must calculate its screen up until time T before the IO command is executed. From that moment on the VDP will use the new palet data when calculating the rest of the screen. So we need some sort of synchronization mechanism. \section{Communication in MSX} \subsection{General principles} In a MSX communication happens only between the CPU and another device, never between two non-CPUs. All communication is initiated by the CPU, except for interupts. Communication happens at a specific moment in time but is not instantaneous. \subsection{(Un)predictable communication} Communication can be either predictable or unpredictable: \begin{itemize} \item Predictable communication: Most interupts are predictable, example: The VDP knows: 'if my settings remain as they are right now, I will generate a VBLANK interrupt at time T' \item Unpredicatble communication, examples: \begin{itemize} \item CPU executes an IO instruction \item CPU executes an instruction who reads from or writes to memory (!) \item an external interrupt (vb RS232) \end{itemize} \end{itemize} Note that \emph{most} unpredictable communication is initiated by the CPU, this is important for some optimizations (see next chapters). \subsection{Communication with multiple devices at once} Important example: memory mappers (IO 0xfc \ldots 0xff). Writing is not a problem but reading is, since the result is not defined in the MSX standard. But some programs expect a certain behaviour\ldots \subsection {Non-standard behaviour} A device its memory is addressed by slot and address signals. Normally a devices should check both these signals. Some devices (e.g.: ????) ignore slot-select and respond whenever they see their own address. \chapter{Design} \section{Communication} \subsection{Initiated by the CPU} The CPU can read or write to a memory location or to an I/O port. The communication happens at a specific moment in time and might take a (short) time to complete. So besides to obvious parameters (adres/port, value) and return values (byte read), we also need to pass and return a timestamp. \subsection{Initiated by other device (interupts)} \subsubsection{General} All interupts must be planned beforehand (by synchronization points, see next section). This is easy for predictable interupts, but impossible for unpredictable interupts. When a device raises an interupt it must pass control to the emulated CPU before it can further advance in time. \subsubsection{Unpredictable interupts} There are two possible solutions: \label{unpredic-irq} \begin{enumerate} \item Polling. %% Bij deze oplossing zetten we zelf de SP om de bv 50 HZ T-states. Blijven we bij het voorbeeld van de RS232 dan betekend dat er om de 0.02 seconden gevraagd gaat worden of er een byte klaar staat. Als dit het geval is dan kan de ge-emuleerde RS232 zijn volgende SP plaatsten binnen bv 100 T-States en deze tijd langzaam laten toenemen als blijkt dat er geen byte meer ontvangen is. Methode heeft het voordeel dat je zelf een vorm van snelheid begrenzing kunt inbouwen door een apparaat steeds maar een lage hoeveelheid interrupts te laten genereren. Tevens zou op deze manier het mogelijk eenvoudiger zijn om bv een RS232 connectie te faken aan de hand van een file met een 'opgenomen' connectie. Bij beide methodes moet het device er voor zorgen dat het er rekening mee houd dat een geregistreerd SP wel eens tijdens een DI zou kunnen vallen en dat daarom een gesette interrupt niet noodzakelijker wijze word afgehandeld. Dit wil zeggen, er kunnen bv drie SP geregistreerd worden die elk een setInterrupt() uitvoeren maar dat slechts na de derde setInterrupt de CPU deze pas afhandeld!. \item Converting van Guest OS interrupts naar MSX interrupts Het zou ook mogelijk zijn om als het guest OS een interrupt krijgt dat deze kan ingelast worden in de datastructuur met de SP, aangezien deze al in staat is om SP's tussen te lassen in de bestaande structuur zou dit geen probleem zijn. \end{enumerate} \subsection{Communication with multiple devices at once} Most important example: memory mappers (IO 0xfc \ldots 0xff) %% For this kind of behaviour the most usefull principle would be a master-slave approach In het geval van memmorymappers heb ik dit als volgt reeds geprogrammeert: \begin{itemize} \item De classe MSXMemMapDriver(=master) registreerd zich bij het MSXMotherboard voor de poorten OxFC .. 0xFF. \item De Driver houd per slot/sublsot bij wat de mappergrootte is. Tijdens de initfase vervangt de MapperDriver alle verwijzingen naar hem in de slotstructuur door verwijzingen naar verschillende MemMapSlaves. \item Bij OUT activiteit zal de driver de gepaste slaves vertellen dat ze nu data moeten lezen/schrijven op andere plaatsen in mapper van hun main-/sub-slot combinatie. \item Bij IN activiteit is het de mapper die de waarden kent en de juiste value aan de CPU terug geeft. Op deze manier is het simpel om de pull-up van de Turbo-R te emuleren, of de conflicten van het lezen van meerdere mappers in de driver te implementeren. \end{itemize} %% Dit idee ga ik waarschijnlijk ook gebruiken bij de CPU, op die manier heb je een CPU-master en twee CPU-slaves bij de turbo R. De master zal zich dan ook registreren bij het MotherBoard om zo op de CPU-switch poort- van de Turbo-R te kunnen luisteren en de juiste taakdelegatie te kunnen doen. \section{Synchronization} \subsection{Time-active and time-passive devices} \subsubsection{Time-active devices} This are devices that can request to be emulated at a future moment in time, such a moment is called a \emph{synchronization point}. The reasons for such a request are: \begin{itemize} \item Some devices need to be emulated regularly, e.g.: \begin{itemize} %% \item VDP moet regelmatig zorgen voor beeldopbouw \item soundchips moeten regelmatig nieuwe samples klaarzetten \end{itemize} \item Some devices can raise an IRQ, these IRQ must be announced beforehand with a synchronization point. This is easy for predictable IRQ's, but must be estimated for unpreditable IRQ's (see section \ref{unpredic-irq}). \end{itemize} \subsubsection{Time-passive devices} These devices can be emulated at the moment the CPU tries to communicate with it. Example: \begin{itemize} \item Keyboard \item RAM/ROM \end{itemize} \subsection{Scheduler algorithm} A few rules: \begin{enumerate} \item %% geen enkel device mag in uitvoering 'voorliggen' op de CPU. Dit omdat praktisch alle onvoorspelbare communicate (IO en memory) uitgaat van de CPU. Als een device op een bepaald moment voorligt op de CPU en als nadien de CPU tijdens zijn 'inhaalbeweging' communiceerd met dat device, ... ja dan klopt er iets niet. \item %% elk device moet zijn eerstvolgende synchronisatie punt aan de scheduler melden. \end{enumerate} Now the algorithm: \begin{enumerate} %% \item De scheduler neemt het vroegste synchronisatie punt, en \textit{probeert} de CPU tot op dat punt te brengen. \item Als dit lukt \begin{enumerate} %% \item \label{alg:ok} dan wordt ook het device dat dit synchronisatie punt had gezet tot hier gebracht (dit lukt altijd) \item \label{alg:nok} als het niet lukt (CPU heeft een IO of een memory instructie uitgevoerd). Dan wordt eerst het device waarmee gecommuniceerd werd bijgebracht (nu niet volledig tot aan synchronisatie punt, maar tot waar de CPU gekomen was) en dan wordt pas de IO of memory opdracht uitgevoerd. Het zou kunnen dat het device een (nieuw, vroeger) synchronisatie punt heeft gezet, dus het algoritme begint weer van vanvoor. \end{enumerate} \end{enumerate} Note a device may not register a new SP that comes in time before the CPU's current time. (Step \ref{alg:ok}, \ref{alg:nok}) \chapter{Implementation} \section{Timetracking} Every device has its own native clock frequency, for the emulation of one particular device it is easy to work with native T-states. But for the global emulator it is easier to work with some least-common-multiple frequency. The class 'Emutime' offers such an interface: devices work with native T-states, but they are internally converted to global emulator T-states. All operations involving time should go through this class. \section{The implementation of the scheduler in openMSX} The MSXScheduler will keep track of the T-states. This 'scheduler'-devices is 'owned' by the MSXMotherBoard. The MSXScheduler is a generic object that can be reused by other devices. If for example someone would like to make the VDP as a collection of internal devices each withs it's own function and needs some internal scheduler ... \subsection{Scheduler and timetracking} The MSXScheduler keeps a simple list for timescheduling. This is a simple list of the next SP and the device that has set this SP. The list is ordered so that the first element always is the first SP that has to reached. The scheduler will follow this simple procedure: \begin{enumerate} \item Set the target T-State of the cpu to the first SP in the list. \item Let the CPU execute until the target T-state. \item Get the device from the first SP in the list and let it reach its T-state. \item Remove the first elemnt from the list \item Goto step 1 \end{enumerate} Attention. The first element of the list in step 1 could be different from the first element in step 3. This is because of the fact that during step 2 the device can do some I/O to other device who can set a new SP that has a smaller value then the set target T-state. For example, enabling line-interrupts from the VDP could change the first SP to be reached. For this reasson the scheduler, when inserting a list element before the first element, will call the setTargetTStates of the CPU. this will garuantee that the CPU will execute until the first SP element of the list. Note 1: To get the extra time-info during communication between CPU and devices the calls (readMem,writeMem,readIO and writeIO) pas as their latest parameter the current T-state of the CPU. This makes it possible for devices to "catch up" with the processor if needed. So the graph of Wouter could be implicetly included in the emulation by passing the current time around during device communication. Note 2: Also non-msx devices could be integrated in this list. for example the routine that draw the actual screen on your PC could be called from the scheduler. Also you could make a sort of dummy device that would be called each 1/50 second and would block emulation until there is really 1/50 second past between it's previous execution and the current one. \subsection{Device Sync points} Since each device has its own natural clockfrequency the scheduler will run at the speed of the lowest common multiple. In case of an MSX2 the CPU is at 3.5 MHz, the VDP runs at 21 MHz. Therefore the scheduler runs at 21 MHz. However we could make a MSXDevice that uses a clockfrequency of 42 MHz. In this case the scheduler will need run at 42 MHz, however the CPU still needs to run at 3.5 MHz and not at 7 MHz. To overcomme this problem the scheduler has a method called getTimeMultiplier(nativeFreq). You can pass your native frequency and it will tell you by how much you need to multiply the devices native T-states to get to the frequency of the scheduler. In the first case this would result in the value 6, in the 42MHz case this would return 12. If you ask the scheduler for the current T-state you will need to divide them by this value to get to the native T-state value The most used SP setting is from "now" until x T-states later. This can be achieved using the method setLaterSP(getTimeMultiplier(nativeFreq) * numberOfNativeTStatesLater , this ). \section{For interrupts itself} If a device generates an interrupt it should call the MSXMotherboard.raiseIRQ() method. when a device doesn't activate the interrupt line any longer it will call the MSXMotherboard.lowerIRQ(). The emulate CPU will after each instruction check the IRQ-line and respond accordingly (EI/DI, interruptmode,...). In the Motherboard the IRQ-line will be implemented using a counter, if irq-counter is zero then no interrupts otherwise the irq-counter should contain the number of devices that are generating an IRQ. \emph{A device isn't allowed to call the raise(lower)irq twice in a row!!} The MSXDevice classe contains a standard method setInterrupt() and resetInterrupt() which take care of this restriction and call the MSXMotherBoard if needed. We could make it mandatory to set the interrupt as an SP. In this way we would only need to test if the IRQ-line is active each time we call the CPU from the scheduler. However for most devices this wouldn't change anything, they still need to emulate up until the SP point and call the raiseIRQ(). Any comments ?? \chapter{Implementing devices for openMSX} Before you start implementing a new device for openMSX make sure you have read and understand the way openMSX handles eumTime. If your device is able to generate an IRQ you must follow the rules described above. \section{The life cycle of an MSXDevice} \subsection{In the beginning} In the beginning there was nothing.... And then a lot of things happened and after a few billion yeas it all resulted in openMSX. And internally in openMSX also a lot of things need to happen before you get a running emulator. This is what happens before a device becomes aware of its existance. \begin{itemize} \item The main loop creates an MSXConfig object and let it parse a configuration file. \item A MSXMotherBoard is create, this object can be viewed as the bus of the MSX and keep tracks of all the devices and slot layouts. \item The devicefactory creates the objects that are indicate by the MSXConfig object. It loops trough the objects that are summed up in the configuration, each time creating an object of the indicated class and adding to the deviceslist of the motherboard. Each MSXDevice also gets a pointer set towards it personal configuration object. For people who need a more vivid description of what has happended up until now: We just create all the needed chips and PCB's and tossed them all on a big pile. \end{itemize} The life cycle of an MSXdevice follows the following basic steps \begin{itemize} \item Object is instantiated \item The device reads its configuration and prepares itself to run \item The device waits for calls made by the scheduler/CPU. \item When destroyed the object nicely releases it sources. \end{itemize} \subsection{Instantiation} The life cycle starts with the birth of the object. During this constructor fase the MSXDevice can set some of its internal parameters to default values. The general rule being that this initialisation must be fail-proof. Meaning that during this initialization we can not try to malloc some memory or read a file or something else that could go hay-wire. By following this simple rule we can safely asume that the device factory can contruct all of the desired devices with out bailing out. If we need to bail out this will result in the termination of the entire emulator. If for instance the total system memory isn't enough to even create an object. \subsection{Initialization} Alsmost imediately after the device is instantiated, the first method that will be called is the setConfigDevice(MSXConfig::Device*). This will allow the MSXDevice to read its own configuration during the following initialization. Remember that the instantiation should be simple and fail-safe. Now it is time to begin the more dangerous internal set up. At this point all needed devices are instantiated, only there configuration isn't complete yet. Now the motherboard start calling the init() method of all of it's known devices. Know you can try to read files, allocated memory and (since all devices are existing) trying some initial communication with other MSXDevices. Be aware howver that this device itself may not have yet been initialized. During the initialisation the device should register its place in the slot structure with the MSXMotherBoard. Also registering I/O ports is done at this point. One of the reasons to have this method seperate from the instatiation is the fact that later on we could decide that each device should be able to be re-initialized during run time. It would be advicable to write your code so that this is possible. For pointers this would mean that the contructor makes them NULL pointers and that the init should check if the pointers is empty before trying the malloc, and constructs such a like. \subsection{Start} One of the first things the device should do in his start method is asking the motherboard for the actual emutime. Remember that it should be possible to insert a device during the emulation. So it isn't smart to assume that the start method will be called when emutime is still zero. This is ofcourse only needed if the device uses time \^\_\^ During this start the device sould also set it's synchronisation points if needed. ex. a VDP should register it's first following interrupt. \subsection{Stop} \subsection{Reset} Remark 1 : creating of MSXDevices --------------------------------- the sequence in which MSXDevices are used is as follows --if are embeded in the -block this device will be visible in the slotstructure of the emulated MSX.Multiple blocks are possible f.e. a simple 64KB RAM expansion is visible in one slot,one subslot and the 4 pages of it. --the< parameter> block is parsed and every key,value pair is passed to the setParameter methode. This method is used to set internal parameters of the MSXDevice, f.e. the amount of vram could be passed to a VDPdevice, or the number of mapper pages for a MemMapper. -once athe configuration file is parsed and all devices created, all devices in the deviceslist have their init() method called. In the init() method the devices can allocate desired memmory, register I/O ports using the register\_IO\_Ini/IO\_Out method of the MSXMotherBoard. -at last the virtual MSX is started, the motherboard sends a reeset signal to all devices and starts emulating, if the user requests a reset, the reset method is called for all devices and the MSX comenses al over. The reset method is used for warm-boot, so for example memory should NOT be cleared from most memory devices. \end{document} openMSX-RELEASE_0_12_0/doc/internal/r800-call.txt000066400000000000000000000206521257557151200211700ustar00rootroot00000000000000Some time ago (March 2015) Laurens Holst (aka Grauw) did some measurements on the duration of various R800 instructions. For his results see http://map.grauw.nl/resources/z80instr.php For the most part he was able to confirm the results we obtained earlier (described in the "r800test.txt" document). Though for the CALL instruction he 'sometimes' got a 1 cycle difference. Now, half a year later, I finally got around to investigate this in more detail. I used the following measuring method. Alex Wulms once created an MSX cartridge with a 3.57MHz counter on it. Writing to IO port 0x20 resets the counter and reading from IO ports 0x20-0x23 (atomically) reads it. So this is much like the MSX-turboR E6-timer, except that it ticks 14x faster. The R800 runs at 2 x 3.57MHz, so that means we can still only measure up-to 2 cycles accurate. To work around this I always repeat the to-be-measured sequence twice. Here's an example program. org #c000 di out (#20),a ; reset timer call func ; repeat to be measured call func ; sequence twice in a,(#20) ; read timer ld l,a in a,(#20) ld h,a ret func: ret Initially I run this program without the 2 'call func' instructions. After the program returns, register HL contains the value '5'. This is the time between reseting and reading the counter. This value should be subtracted from all future measurements. Note: usually these test programs give stable results. But once in a while you get a too high value. This can be explained by the R800 refresh stuff (see "r800-refresh.txt" for more details). By repeating the same test a few times it's often possible to avoid this refresh stuff. When re-inserting the two 'call func' instructions, I obtain the value '17', subtracting 5 gives 12. So executing 2 'call + ret' sequences takes 2x12 cycles. So a single 'call + ret' sequence takes 12 cycles. Later in this text I won't repeat this calculation, I'll directly give the length of the (single) sequence. So this confirms the result of my earlier measurements. See "r800test.txt" for details. There I also show that these 12 cycles decompose into 7 cycles for the "call" instruction and 5 cycles for the "ret" instruction. The interesting thing is that Laurens 'sometimes' measured 8 cycles for the "call" instruction. Let's now make the following change in the program: ... func: nop ; <-- added this nop instruction ret So the executed sequence is now "call + nop + ret". Naively we'd expect this to take only 1 cycle more. Instead we measure 14 cycles (2 more). I also tested this program: ... call func nop call func nop ... func: ret The sequence is now "call + ret + nop". This does take 13 cycles (only 1 more). So it's really "call" immediately followed by "ret" that is special. I measured a lot of other sequences as well. I'll summarize them in this table: sequence measured decomposed remark a) call, ret 12 7+5 NO penalty, call-ret is special b) call, ret, nop 13 7+5+1 NO penalty c) call, nop, ret 14 7+3+4 penalty on nop d) call, nop, nop, ret 15 7+3+1+4 penalty on 1st nop e) call, reti 15 7+8 penalty on reti, reti is NOT special f) call, nop, reti 16 7+3+6 penalty on nop g) call, pop hl 12 7+5 NO penalty, call-pop is special h) call, nop, pop hl 14 7+3+4 penalty on nop i) call, nop, nop, pop hl 15 7+3+1+4 penalty on 1st nop j) call, pop ix 14 7+7 penalty on "pop ix", different from "pop hl"! k) call, nop, pop ix 15 7+3+5 penalty on nop l) call, nop, nop, pop ix 16 7+3+1+5 penalty on 1st nop m) push hl, pop hl 11 6+5 no penalty, n) push hl, nop, pop hl 12 6+2+4 push-pop is not special o) push hl, ret 11 6+5 no penalty, p) push hl, nop, ret 12 6+2+4 push-ret is not special q) rst, ret 11 6+5 NO penalty, rst-ret is special r) rst, nop, ret 13 6+3+4 penalty on nop s) rst, nop, nop, ret 14 6+3+1+4 penalty on 1st nop t) rst, reti 14 6+8 penalty on reti, reti is NOT special u) rst, nop, reti 15 6+3+6 penalty on nop v) rst, nop, nop, reti 16 6+3+1+6 penalty on 1st nop w) rst, jp(hl) 9 6+3 penalty on jp(hl) x) rst, jp(ix) 10 6+4 penalty on jp(ix) Details: a) For simplicity I've shown the decomposition 12 -> 7+5. In reality the situation is more complex. The full sequence in the test-program is out ; call ; ret ; call ; ret ; in When we take page-breaks into account, we get the following for the 4 middle instructions (I'm using the notation from the "r800test.txt" document): fffWw FRr FffWw FRr + page-break So the 1st "call" instruction takes 6 cycles, the 1st "ret" takes 5 cycles, 2nd "call" takes 7, 2nd "ret" takes 5 and the next "in" instruction has a page-break-penalty on fetch. If we simplify this (artificially attribute the page-break for "in" to the 1st "call") we can say "call" takes 7 and "ret" takes 5 cycles. Notice that there's an even number of cycles between the "out" and "in" operation. So this inserts an IO-penalty on the IN instruction (to align the R800 bus at 7MHZ to the external cartridge bus at 3.5MHz, see "r800test.txt" for more details). In case of an empty sequence ("out" directly followed by "in"), we have zero cycles which is also even. So our calibration method (subtract 5 from the measured result) remains valid. d) I'll give another example of the simplified decomposition. The full sequence is: (out) ; call ; nop ; nop ; ret ; call ; nop ; nop ; ret ; (in) The middle part (without out-in) decomposes to fffWw Fx f fRr FffWw Fx f fWw + page-break 6 3 1 4 7 3 1 4 (+1) Here 'x' is an extra "call-penalty", see below for more details. So if we artificially move the page-break-penalty for "in" to the front we get 7+3+1+4 for call+nop+nop+ret. This same simplification is made for all decompositions in the table. c) Compared to 'a' you'd expect this sequence to take only 1 extra cycle (just like sequence b). Instead there's yet 1 more extra cycle. In general, when looking at more sequences and/or replacing 'nop' with other instructions with known duration (not shown in the table), we see that we always get an extra cycle whenever a call instruction is not _immediately_ followed by a "ret" or "pop" instruction. We'll call this a "call-penalty" cycle. We could attribute this call-penalty cycle to the call instruction itself, thus say that the call instruction takes 7 or 8 cycles, depending on what instruction follows. For most practical purposes this explanation is good enough. Though if you look in detail, the fetch of the next instruction has to start 7 (not 8) cycles after the start of the call instruction (otherwise we cannot know whether there should be a penalty or not). So this call-penalty really happens during the _next_ instruction. It's likely that this call-penalty stuff can be explained in a simpler way if you look at the full-pipelined implementation of the R800. _Maybe_ a call somehow/sometimes introduces a late pipeline stall. Though because no details are known about the R800 pipeline, for now, I'll stick to this weird sequential explanation that a "call" instruction somehow induces a stall in the next instruction. e) In sequence 'a' and 'b' we saw that "call" immediately followed by "ret" has no call-penalty. This is _not_ the case for "reti" or "retn". g) h) i) A "call" immediately followed by "pop" also has no penalty. j) k) l) Though "pop" has to be a single-byte instruction ("pop af", "pop bc", "pop de" or "pop hl"). "pop ix" or "pop iy" do have the call-penalty. This is similar to how a multi-byte return instruction (reti) also gets the penalty. m) n) o) p) From a stack-usage point of view a "call" immediately followed by a "ret" or "pop" is similar to a "push" immediately followed by "ret" or "pop". Though the tests show there's no penalty for push-nop-pop compared to push-pop or for push-nop-ret compared to push-ret. q) r) s) t) u) v) The "rst" instruction behaves similar to "call", so there is a penalty on the next instruction except if that next instruction is a (single-byte) "ret" or "pop". The only difference is that the "rst" instruction itself takes 1 (only 1!) cycles less than a "call" instruction. TODO implement this stuff in openMSX. openMSX-RELEASE_0_12_0/doc/internal/r800-djnz.txt000066400000000000000000000137741257557151200212310ustar00rootroot00000000000000It seems in some cases on R800 a djnz or jr instruction take one cycle longer than expected. I discovered this purely by accident: I was doing another timing test in which I varied the number of instructions inside a loop. For a specific amount of instructions I got an expected result. This happened when a djnz instruction was the very last instruction in a 256-byte memory page. In the following test I'll investigate this in some more detail. Test-program: org #c000 di ld b,0 ds 248 ; vary between 248-251 out (#e6),a loop djnz loop in a,(#e6) ld l,a in a,(#e7) ld h,a ret By varying the value of the 'ds' directive between 248 and 251, the start address of the djnz instruction varies from 0xc0fd to 0xc100. For each position we measure how long the test takes using the E6-timer (this timer ticks once every 28 R800 clock cycles, there's also approx 18% overhead for refresh) djnz-start-address | real turborGT | openmsx-rev11821 | openmsx-fixed --------------------+---------------+------------------+-------------- 0xC0FD | 31 - 33 | 31 - 32 | 31 - 32 0xC0FE | 41 - 42 | 31 - 32 (!!!) | 41 - 42 0xC0FF | 52 - 53 | 52 - 53 | 52 - 53 0xC100 | 31 - 32 | 31 - 32 | 31 - 32 (there's some variation in the measured E6-ticks, we list the range of all measured values in this table) Translated to clock ticks per iteration: address | real | rev11821 | fixed --------+------+----------+------ 0xC0FD | 3 | 3 | 3 0xC0FE | 4 | 3 | 4 0xC0FF | 5 | 5 | 5 0xC100 | 3 | 3 | 3 There are 3 different cases in this test: 0xC0FD and 0xC100: djnz instruction takes 3 cycles, this is the normal case for a djnz instruction which does execute a jump. There are no page-breaks: all opcode bytes are fetched from the same 256-byte memory page. 0xC0FE: This is the unexpected case, for some yet unknown reason the instruction takes 1 cycle extra. Note that all opcode bytes are fetched from the same 256-byte memory page. See below for a possible explanation. 0xC0FF: Here both opcode bytes of the djnz instruction are located in a different memory page, so per iteration there are two extra cycles for two page break, thus 5 cycles in total. I did a similar test for the jr instruction: org #c000 di ld b,0 ds 242 ; vary between 239-242 out (#e6),a test2a djnz test2b in a,(#e6) ld l,a in a,(#e7) ld h,a ret test2b jr test2a jr-start-address | real turborGT | openmsx-rev11821 | openmsx-fixed ------------------+---------------+------------------+-------------- 0xC0FD | 63 - 64 | 62 - 63 | 62 - 63 0xC0FE | 73 - 74 | 62 - 64 (!!!) | 73 - 74 0xC0FF | 83 - 84 | 83 - 84 | 83 - 84 0xC100 | 83 - 84 | 83 - 85 | 83 - 84 address | real | rev11821 | fixed --------+------+----------+------ 0xC0FD | 6 | 6 | 6 0xC0FE | 7 | 6 | 7 0xC0FF | 8 | 8 | 8 0xC100 | 8 | 8 | 8 Again 3 different cases in this test: 0xC0FD: Both jr and djnz instruction take 3 cycles (when they do jump) there are no page break or no extra penalty cycles. 0xC0FE: Now the jr instruction is the last in a memory page, this shows the same penalty cycle as a djnz instruction. Again there is no 'real' page break in this test. 0xC0FF and 0xC100: In both these cases there are two page breaks per iteration (but at different locations for the two cases). So 8 cycles per iteration in total. Possible explanation (this is just a guess but it does explain the measurements): This behaviour could be explained by some (very limited) pipeline behaviour in the R800: it seems that the decision to cause a page-break on the next instruction is already made during the execution of the current instruction, after the last opcode byte is fetched, but before the destination address of the jump is calculated. (Though when the jump does actually go to another page, this is also a reason for a page-break). It's possible all R800 instructions have this pipeline behaviour, but it's only visible in the djnz and the jr instructions. All other instructions either don't jump (the PC simply increments) and then it's not possible to externally detect when exactly the 'must-fetch-opcode-using-page-break-decision' is made. Or the instruction does jump but it already causes a page-break for another reason, so the possible extra page-break is always masked. The list of instructions that cause a 'jump' is: djnz, jr, jr-conditional These show the extra page-break behaviour jp, jp-conditional, jp (hl/ix/iy) There's _always_ a forced page-break after this instruction. (The exact reason why is not clear to me.) call, call-conditional, ret, ret-conditional, rst, accept-an-IRQ All these instructions (or events) push/pop on/from the stack. When switching between data read/write and opcode fetch there's always a page-break; otir, otdr, inir, indr, ldir, lddr, cpir, cpdr (When these are repeated PC is decremented by 2.) All these instructions do a data read or write and thus there already is a page-break when switching back to opcode fetching (even if opcode and data are in the same memory page). halt I'm not sure about this instruction: it's possible this instruction constantly decrements PC by one (after it's been increased). At least this is how Z80 does it. So it might be the case that a halt instruction as the last instruction in a memory page 'loops' a bit slower. Though this effect will be hard to notice. At the moment I don't know how to write a test to detect this. Suggestions are welcome. As you've already seen in the measurements above: I implemented a forced page-break in the djnz and jr instructions when they are located at address 0x..FE. After this fix the measurements in openMSX match the real hardware. Wouter openMSX-RELEASE_0_12_0/doc/internal/r800-ei-timing.ods000066400000000000000000001076641257557151200221160ustar00rootroot00000000000000PKb=l9..mimetypeapplication/vnd.oasis.opendocument.spreadsheetPKb= content.xml]ko8w_@c/d^l/<X`(YNk[L&8؉hLP<:|xHRy<"__ t~|X\8R{|f<[ej ~^<):Yee^lԥ.)я׉su&lr}|ubYI ڏc\.GP_mjr6=ȋ1Zk.X֩f8[fr/1*cgUZ߭hh* JH5y\F3~}9Z>9MN *tf?JWaOzU-ˤuJf6-ty}:Nqn}d´$YEJ #\ZOZ>Q|6su@ ֐\VE.M-Um 7˂Bj %b61Sx1v4Ol4e]Õ&luumH=&X-OF2t~QN͢Jx>%yK>=Ǟ S4W顬KuiUy9g`iA]aj #n>|Ҏm@_Zd<.u s&_ _XyrU\hF"zDhW9#r8nǺSP_-{bcתlqs[±4| Z~*]%S8+F&=<[V^5٢,m} fU>ŨXhV1Bnd<6e5[MReڿMQ|[AG!וL 3c/>? UdYZcT,)ͭO^ѹC|=DǕ;Tr<_'#;Av?aş}'CcZyeC~9pFRIlU7yy7ލ,ek웳o82OsGb}W+B(9iz<󜫼|ӇQTĨZS"-tdm2_gm>xaswG\˷6MuWLSiFShA[&Wꫯ~Vo ;׋ӿե5IN*Մ @Hs{ON6o&?B;_P!)L(?u".h\c҄aW.i+{i<.pB K+^FA/ DzV> /^cbL |.i^E=E ak,` z;gJz4` GPiQ#qEdiFq6k$ G$'\ݮ 9&&ԉf q|3L~E}yNnIdFD&2'd'd+̐G GQ^χ$5Zh½(lɜVd0U% )IȎd2cr,2L&Xc-QO:79i+!hK4'""? ?m5ތAE5&ԉnӐi h"4녒a̤ 2H FI @F(es!z$鉐@F)B`EL7 *26`"9XȚSهh|)"2>#FĈ#ll#PDn&;<$@Q17{,Q?ȐH<I dWJMْ~ph$e!.! i2 D Q =>yN|h ,a恃#/Df ҟ% ̴ 2mDLڧ?K4>6?NBs2#Jb6LeX!eφx?!f0#0 d?Df1~آdWwIh '1R`m6^h@%L Bp0Ohм#BؙH螓h4`[jHր#Mb|7ƨSrz2W!M]ao.'J+EofMSvq5F.vh4mk@0Vh{ BY2 =i'0,)R gPh4; % ' h"?4{(YBD؄ENBi1KZNgel۠EPM[`GP靨rD[3d 4s8e/6^hNVi7J}cBc SX=yW 2I<2fzf!FK4ldoW?nߐ o*4ڄOwޢb?rHCGRR2E͗ܩ+z#QneIJPqDC`:YrdO C%qq_٫MA:p *iS tr7B鬓8Bؙ1$)L%h4`W$4;TJ8WKp/4uWMCJh$p4 hN4;ipãAh\d|JPa&g6>h@RСF;j^0Uo+z+4B 4Rsh4`[i^|JPm6 ^hNiPI5҄mB+wb7N!4Q\bFSVh4@mk*iCBګM;ʡC%v Ԉ 9XSqEot7'Ce 8gjơ#YRh4`_MNv)hwq")N,ی9tnbV?Q3ω "HP4;,'o gȩn90ߥE]jNK> !.:Uh@0UH(eW 2Rw2##pM̹I 쒄L[ Sf ]hYիe {[GBk{4N;teЧ%(~|2I79 ĵBP0Q/Zt"3m3R"Q s] rF7F]Y0& c^fÜu:XX92'(5J^9hd&Șc;SI[ҏ.˴0-ఒ8W$&罖,=3)fCvgnY)w w"fPբQ2YY^Ͳ _#QB$^)&zZ7>?+?\/fխ~:0lqs[x!IS5y=ݔLjn[_US:Qmf(d}=[}cg?E7]R"__4ybYfqm~ujf?hG*f*[WekC:6_-]o|;Ta]fM?|Omdly,3u})³7co>/m:S7}5M%4):=B롽qG;!>)WGB< qD蒱Bt~9Qc]ݡ&DO qB Y ҞB^˄DGb%7=#bxo݁)^% ́٨{t[vY87 gOAE"ӏ>uw߾juw=Fy ʻ\ݺ>{u߀)2;# 8'~dI97!qƤc @xS'Gug$js;Qo-$_Ib ,CI$Gug\j{G|ҤB_(_\ۏΛ9gy y;/ fo}!3Nz0H~;LdI975;Nq>̓tbz~Qݛd̦=ioӦNsS3th>gt0S'Guojfʥ&7SGiS'Mʹ!t< QݛG+= : *~SOΌNss3Й SU ztO3s'Guvs8P(Bh9>n.@D귝w^e`Dh=:`dfn43JRfvfFF}ҤZ&ĥ% wg*00`?̗*f>]bxsvF !~S_@G41H$`zBcJUNJ39x}%d9;. y;/G0 V Qϸl LH[c]BI\s֑Ef ~z.'n[+//9ecc37q/R`.ح{~jkRt˼܃9CMWHu]D0yGs/›8᯸S'ѵD=|$=+>8!  :=V^CLNOO0/hNePx2867xuT<;rYV%zz~yGs/›8᯸S篎RdDr_&7q/c_q_6r*+܃9CMWWw=+>8!  :FuCyGs/›8᯸SSЩP^_ќġ&epl+nkqC?UO ͉L o^džOf'? 釡܃9CMWtsM駠܃9CMWM}8~={W|4'2q(Cx8!  : T9_͉L o^džOm۟@KV` ^_ќġ&epl+ndu7 l=+>8!=!f-G岣Q#(ސ W<utNQ"yGb%V;n껉roYq3b}MBѳrZ.?S.5 2G$8/+DB@LǼwcw*iMdo(ڑ`(Ax./Ctk|O"?n-GWh}m>:(n\Sy~[+2k+>ˊm>̔KMvo>`'iRM\߽N\v>9{S3S.5ٽ:L:iRM/(_$2 p#?N&c6mL{3u46uҤC DŽ=:9#rfJfN>3#r y;/ rp>A,̇)Kd]-5}D|F]v^̐y`'iRMt?҈G6urTf.^b?܇+T2ThKa̐_vdfOs3PL$K!fs{fO;t$5tb&aԏg8y(2 ݠà͡\jof̐,"&`UO zJf4ڥ!J꾄Jh[h3Lwrʡﳄ᪙~GB67ryVMEϞ.! 侄ӒYBK_S:J_{ %Gu_BKB4yytsx}% @'CgA/NV QǪ兪}t Y'%Uh_*gu9R[bhD?{K_%?gK=Hir9`c 1f74_̒op8tre9OKC;Tuhȯ}셒/Ih["LHj99>K>e_gI~r^B*?-G=|ryT>=PrT%Č1|ryLU JG_R=x}% {{rlS 3']By 1W=Ao)^(9"Ƶ`[ ^7A9pNbOC?> ]oS*o>3']By ӕ`h_!?[/,3Wj/!f\!Ꮽ[*:B~9>KT6=+{*{e<t: tvkeI~r^Bخk#Rh{(Kt_`I_TX\9pNbГz:m+{9jzC\xl=']By t kFӯ셒?̑[ρ#>ٹV9r~yIsnnzzsUB]0`LOc9E!7 VfvN1A7CA_6X=i @@@:[ A20`D(BP{LzқC9>;O~ }Ot<5V#׹jzZ~jRVO~v^?󄸯@%q#X+wj/Ab$V )fw>7W.8'^g 9<m}zʩ U3]#͕̕)wjm\%ϕ䱜}KB?;/Ab"?) JoNMS{%} 2@3t*FNF;0:: zitʭ :zt2kD-_ֻ3r.9 i|!n/.S( {^rT%B1->I(s^ {rxz9 % [S۸qnj }o~OKB?:/b#6۳wЌos?`wmCB_6عOH9IsF*Z QCWCZ50qF[bT^ `Zz L:iRMeBm9t^ ރ753fi;fjM4)o)\!DICTS`e0VbT+&#$+s;of`IrnU߮Qmoyu4 s{N[I3yUyߛWI:x=BHC}8G=/(ul}DG 4xmWMj+VЪ{b[ghS!ztA/4]P,2='ɉ87qzO'*h3xa-SQhzҷ~+FrMÆ?O#r{zMQ,2='ɉ87qDMn0}y4=O"??HN8OT\V%4}%~?HN8'*hӱTŀ;|MW퉰h$'p 0l'*hjk` 4Z?۟h$'pYck4u=R hO4p8oR(2='ɉ87q m]?4u`__JFrMlm?0F@L|FrM|[FM^T|y2Qd{O4p8o64uyoMuk0u{$U8oV: \&7`* M{O4p8o|#G4uwQB(?Û8 DMNsj QJH'?Û8 Ds0%c1#ݐg'WBS!F+1nMFvB|[ Ěb! ,>e)D蒱_N{)ra?P^=fEF3SSb㋺gT((|*ޟ]>[iUwO/o}b?wNVKʻ^R0s76 ({끣u)Cqq@[0.l.WǏXZz%m(An)(nQCXN9S\ax!~S,佧cQڂMхzS&t^ة_{(K_VL7ʉY[BuIXyI0Sm à/*13PQݗ ` XTZXN9>KzG2꒰7qັ@,; 1s{%}I0c'h=*-RZb,x}%asnF]v^t{o3*CI$Gu_w]0ݡ*]f7)XN9>KD2꒰ k|(q?Iة_{(K }z;ޭ~aFNϒ0ќ$,$UOjO 53PQ[wi8 L h2;< Up ;R/&Oʹ `[q5Hg;vbĺ`7|.vBKbץc],ˠMC3PQo*&Bv> +'Mʹ+|>.(_C~=Q!, s{N~;ό_K_)Z7x}CvBK>E'7M)13PQݗNJi{ҿ*XN9>K"CvF]v^Sn.'|=`dfn$/ 2>?r}D8쌺$,$ H`?ߘ{( 0cX |oI975!Db}Oc{0urT_,0*tQJ0SN<3dy y;/ ugL>LyOo_{(K~6Uzh('^gIQ3= =X{to=DrT%!b wg2}zb&}@J9ΉQ7N/WB5rήKB8/g;'uR/Wk,6/Ќ=JrTnLB~fv%TNeh h?8yN`!i@>KBk8{0{rTfLDQЭжп9er_0XA7@n }+'/XL΅~}C$Gu_* g`zz|`&%tKtTd&g-t =z #jh^t)Y/t>~8/|-{(K= zb&gПAGC5r}3ԇ6 =ztSj7zft)Y/L_A+׎_}Z:`J)9R_@ߦ0If{ȁs}3|Cm; uNwv7AMr6 op1nRJ?3>HwC2ع ;~4)fUKGs0=:93> erL~=!FNCSWC_0rJM_9s 伄jhaw}Rp#`h0s{%} ]3G_5G/#(=]it,!fr|:Z=ey(;:I9OKX0ó˻;X5MOC %GuhLuzٹC+&drF};guЀ =:93I)]  :ȉ_x6?)ГAB-Nv7r[_ Z3GoKZbVsK'B'CI%Gu b W7B7CW}AoD%|EN`Hs7sR;GA5{0{rTk#ľt!?tq8'^PWA?SN32!2/Kf0ց|!t+hNI~pi9@诡``z?4),8oo< zU[ ރ753΃ 2ع7S3@v0mI975u_$䋡Cg`u0`M͌= ]tL:iRM@ A ]wރ753z+tiS'Mʹhe4Ox(X^@ =:9{S!{u+s;of`Irn D֏:}m ރVjL# vk-a!imݞ7~b.OjDr[9W|4'pq/c_q_x66?)g͉8gWC7!.Mwg-_ќqxa  :pD+"ghN8<0^džO ` Q4uW|4'pq/c_q_اgZP[_ќqxa  :} d֧P4uW|4'pq/c_q_.B;W(z9xq88᯸S篙7_iE0u_7cGs/2867xyZhN8<0^džO>ظzq'g]hN8<0^džO^`öBlZ.|ֆno_3epl+n뽅B4k)ě6i%޵0sDl47Opq/cS篁ːί?PY' +>q88᯸S~WqD!JVh=^~hN8<0^džON^)B:|_3epl+n.h<ѐV^蚾6$NZzQ'9~SsRNӳjNAN}Ν0rO@cff T扅j~0t_W!~qԾ:_Ƃ"yK3{hEed. ձˎ}~Z;KcOi,;iءu:6?;NkǞtdv=,{e3ݱC9{oKٱZckWvGʎ=MZ;#uv ulvme^.M[ݱ]C5{PڱMuv ulvlc_֎]wlPHo.;$)K; F7um< N˺e߷#}G1s})˾_4 }|UNIO(~mYZ~@nsmd?3$}Ifww0%}v Ok6L}Ípwݏ8RϞ&i R6^>Jc}ߵҾﺗ}q־ncvK1(~Ҥ}ۉ( }0w/Ζ}4=}eӊcoIUW#"lK6 XÓ_oR2<%9dfZEt >r|xo3I*Óc߸q+/:<ī|xIuiZEzx:*U`JWiGSuߣw [ygz۞m㦖w X%筫:iϔpko'Km iaL)#80#_h`tJ˗#X紎e,.#:c.m#H;7@a[)Nǯ&/u;By6<\`[+"*7"))0#5#9U1ۛ<എWM9U=16#8iA?6GKY{^WW:3GkVݹW{~WS`Zi9N>2N@'<4l߶2GW qts(:9/ +%WC\VK/[_nMG]Q@ $x\x.6H߄r?(v/Ǩ3S7G3pjIc5!>Z#p88qs]6Hw5ZF-DlڴUDx T撇UF)Aт %C R}o_$u[Kg0M_t>n)I䪒QA8N008`T톧W68[ NVQ;`s}KY%78Blp)i醖jYWJ`;_$C N}@LX)gVcSXp.\~Z|[ո̠'äCa3T4iR^fDL+j!\W4EMV|ѵOgxz\M .7iW"zƶ-&_ӢT-`ݘ+>7ЇQAPED#̢q "{-j)P!b,TݩOU렩A(sCBҠ=%V-7jpȵ8:,ԫiFUcCR>9w=e0N|޹J宯V{Fנ~3iTzn2$ =hpIåڷ4&B1+vݽ(HDlCSB d"r; 2my1eTF f4#ۄ<1>t,%A0ig%Jbq|Pgw0{3O(BF@}pQ>ôNB}m}uj\c5Er֏fSFs ERiJ5S^zCGb~c04(8f9ks؆?ֆuT_f/T8GݧR+&.$}VSc><~ Z2\!L{JٓTU|VpxJ}&i! ?/qYW㱨_Q"a :ZPڅ-#9ȁG3*NTL;!FyYKik&+?㘑϶xFI^ ÛLH\;>:'/Axk;;v E9b4"J;P*ꀠ^iWAH3W:VړG^֝Y F'-&?d;5Z¹)qW9!/V/I2,Rc$;EQ۫\)r̃Υy*s0_6b3{.b/|fj"0?R~ZL"t8l~&^.}c?[ Z< LfQkiY1PA;B,z{{{{wCB'/] c ZnTl;mCw^cgS"}cԾ>^01릁T5m${U zDNj-<9;gVaK.måW*+4p /.׫upU=>mYL&мmW&ЮKg?_ۿPK(rPKb=Object 2/content.xml]YoH~_ah)} );%_ռݒ<$r쯏ꪯ*~~ٖw)][WY{.:tx=?e+[/2w5 :16oKsy}vOX5iYp1#=Q1/F94/.7/۴} [?÷`]E^d;t'uzɶ9+zrYuUkP֛@Jm\΂ޛyg5<#)l3jӧruΏ5Oaz8חϳ,t7(10e͕sh)~ X AWSZ4v昚gMeң&Kl^U{d-v)˓"]I&uIݷ#^$Z5&V@wF=ٷK?FtO>"s 2gry SW A.:5 ȅQo$6F0準KzN#O:7 |ˊt[`F0r2f:JoTeb߿pD Yw?-ή'^Nէzg'#炋H#%}co>2mfN΀Uva~;z}o(X ]v?x1I d%%k7S_ "Nf ;n5euz)Dw,.2aJ80ini,]_HۜT %`s) [ׇ0%!<]_Hx2=LH,] \rF'!L~Y+!L u=*Bl$X:_JXphY@ YF rTat!yR?fV<43}}a0f)hC4Y~C| Ð|7>$&ÄаDcQ3̇0ˌ$A\,!ſ)jωu LR&4|I~wOUWOVn?A,>D < 3s.䑯ּKԉZX_[q iR<"J8,9nW !D8;'Z4nKX1o[Q}+CWԅT*qɇP%+#đ!< k) nKXޭ,_) EBf x~nKX;KAfk%/iwN湄`$"PrHNaB5^ǘσ>X C7{kQZ ka BxrSKI VHk_7f)@#iEQ>+"sщ~zPKd sPKb=Object 2/styles.xmlKn0=ED@ZAckHD^3㥩WD@<['+9}%RqIڦ~ҬX:>5f5P)RO"ip5 b^Xg9K@6Ċ=j+:-!д̪?Q\jږ2 C6l2햄9`>smu'ޙ!y]s_gL_Ew mD|6*|v=K=qCы zx|4q9qD*{F]˯e h@w)=ۦ?uB+&7n^jK~KjiS6n,ӯ1B,Vm% E0jÎܾPKĞBPKb=Object 2/meta.xml=s ~ fG1Cu蚥{U1Mz:ǹ0VjU'(\DSFւr]P.c?,]G%YibTBmG=MѺ3wR}ùLӄ kB9 kX,8]7GZߌ| Ha]otk8* |BX|bzFѵ݅ SѦg.z>šBD «K`bxU[-"Tk9m80ٝOQ;Hҧd2ʎK;TPK%L2fPKb=Ѕmeta.xml 2010-07-17T16:09:572010-08-18T23:59:04PT00H43M25S2OpenOffice.org/3.2$Unix OpenOffice.org_project/320m12$Build-9483PKb=Thumbnails/thumbnail.png]Ye@T&DiNPa)iZrAPϽ߽g׳ֻ @]Mcaac%u`a1\+)# Оc˺X7qceiH;16A+1)eb|V^\;FzFLq[=%=E*q,_u.YdwumނՎ4 OI/MХ|+.1勉>w 7.k%IoΏh\}C?sJ=pic?Y%ӞK.=m&7ovjfWbۦk6D34$ p{;V6_3;o_ƨtBq{x}w _Lf!eČ'.?&Fٗ%S tZ4i&}bC7^T=> 7}2Gj{XI'y jY1I.nIS{z9CXo3:l' Ihmvs?9#DU΢L;=WX &eِLxц&3M_< 8DMi2آ[j=u>˿)PcMg4+|T|@ձLJve03EޱARx=pyR.?m0e لGt[Ottop tv 5< q4 ֒z ߾g%xҼnԴ8/d>࿘x8Y\!A]~Qu_}PŸ1JP$_ a*EAɥEɥlv?3$H5ɢwRaiEe_vsm';P"|nx*8/{l'cw yK_[ku-mFA_+k-/W_Q(}DhSMߗnvKgO'U߯U,$4>{4aغ]l73bKlM(8B8
    pRV.[,A|ߠ3O/EC=xvNv4Po1R/wx]! 4c4: ]E,տ@3B~C6M^^sM6@ybA{ D Q'vdO(㶁.>BoZ&8YVGu**nA5'XxL%N mf_#4K.#gv]X=F:ԳmCjVS/\M= Կҩ{X)anFVc KrվVs-wѽ8p7ʈe?^PKH=o:^Nl|^#eIַ{@CFTSm+4?kajA a@[g8?2ˡkQ{OUWV{w5K|{p^TsXEVox\ۤ/X6UF]W>KzC< ] I= Je4nF_o-1uJa-,jA/h'֘hm $j X+ݸ Gm׊9: ! J\go-I|,mrߤrI4WΨ{ x~g_8=/Qgw9PN39<+"D.Ns>$גަu]-r3*puPA 4#E@w:Lq;~jۇ^p[`I-lx{E vHF_#؋"_{7[Djn5.HIn\%}gKSuB_X|8͔!P -r3]N>Hű]R j,UٗƮvQj}3%eZ,E;cko<$`tu#ү iR}{mAWI-~JI,#޻kC6l6AmMZw39%ZFu&՘¤مGm:3+BۛUFFc™N:ń>*"=c ^HhS"q \(FEEW'ByjhxU&TzFܾ1[z$0$ c] -KB /Ld0G {ۜ#~#cqd3?,*' 1^4.4oy082A5*B{:Zw}va:s1FZ^#p!X4<߅%vz ԇ 衊f]ջ%#_ٸ9ڪYLt.b x`c{7ʓ|Z#ۼxQҖ3L2*w-|_(pS}SXV3DO^1 ͻM+dLF+NĠi"sCuNBDcӽhϺEs8wUh{JiWcw.&g·vb 2 ]CA$vo4셜]Frb dzY,cɢQJ$fO5:fźN"c UlLI|-Trd~WbsO QSFªB ۷@̮,_\Z콶/x- ?Uvx[$KTiK#^ ]E01c\08\4f6V=ǵ}Or qݱ?P{jU\u J^wӵ=BG6͢7&?竻1AuXI&SH2mtY@~kiqO}q0c;:-E±ug|w>8{*>'t>Lͤgbvsg lXrcq'a+4cM{?YI:C{DG{VsR\qf-Ye6 bL2o]kPE e?io+6ѮRuv|0pMrS|#m|b 9т|/d31y|ӏH-NTs˳]1܁l.x&>?xȦl YCx(Ǝ >&d`J3<2ӿh'ĞL^^3i0j*3c'Da` @WQ ׌'Py CQyƌΓ'i-Y+Qy%ͥ듓2N'-cp(˶3[ᘏʘlj54q@m]}guUy}4ͯ#Z#cPn^]z(^TCHT)!Fz_?wPyC(|% OLYE&&2u>Wvm,55u1^->cBhFlO?;-*ě~bzm]_W!!g=# { *Jz]өM?|GioQLg >v"hA_4B G mOVX*Z5!۷G;KMv^͑V $$J zT͉Q}SozM޾deac'ہN';r@:r@v49,~]xE,`l_\*aQjtڡwGnH`͸Q% d~mM,tQoH);@*.mڰVeQvx  Ii<Zaن"lb'.?8D%"ra0V(C̿+H#y.BO_"v9VW{7YTsq'fM%N4 lsѵD+WYX%:@)ca@BJ,߈?,\8pmgkJVa`5ѳHxpx{ӖlZz=tna(l.뒊fI!5" S*`)+ihX!U1iژ(cQnD 6t}Vԟ Pg:đbb#z-P/\Y蠈~:SA-xorq ˶ k=Lg<,PGT4G7%^v!H1~ד^7ꎱ$O R -̶!f)fø.Lm2[a.9nʯ2ܗ#Og\b]hChDr(\׭}ϵw=Iņ Qӗ<~6f#Tg#a9IɌ\8 CPW Xm+lvBHa@07.֟>DHL%5-M޻s׵Pn 3UD: xPrD$Ulf!eHD~ޕosLbl(Wh3 9X5H2dȵ7ReP5D$(h?X! Aк 1d+ѥٓ)&5,5'´bW\~J:,p v4GRazP6]kxA/ĭ {toqf&Qf* .tEM"J_^s F&aۨe%QcB6H} ׸]kv*7؟a0b/T*-W6%)N mJHan,Sa A" ܗ􀬔54 jTBU܎eɨHQfbK9L;dLy| HAhjW{#\F "A& 5>=iO?lB=(9k{!%7tI(g!_ʺUi E4Y*6fs r>Ϟ'-NA^}^6eRYB$0~ʶm.ߞSr1w_fѾF4'J8*iR8s3hdo&{5TT50xV1d|<-Vr8}''T|.F>(#ļ1fx#z8?`:yHZ`s ̓ѭP ]U"-pr8 $ tRNx~gz`BSJW\z7vN#e&2sKU].&(R>x {0p Q`%y8kҫNs荌늭l] b`*%<{ԕuZPK5 PKb='Configurations2/accelerator/current.xmlPKPKb=Configurations2/progressbar/PKb=Configurations2/floater/PKb=Configurations2/popupmenu/PKb=Configurations2/menubar/PKb=Configurations2/toolbar/PKb=Configurations2/images/Bitmaps/PKb=Configurations2/statusbar/PKb= settings.xmlYQs8~_!\8kfM2 0@Cә{I.OߊwAH|hp}ʗCnv=ލ8łzEpՕ";z9N:<"$J<C2gK<2JбfsXڽ3;ͦztYT:{">$Gv Meq^t3t Mg8vmhiΚ)jVѺkscd m)W~ &X(3R_{Gᯀ.WNӪ݀]}x?o 6Ż%U<͵sS*SŃ7y}]^ DBEI.ݫ)ϡW(707dT}D_h Lfo)}J$CO:THQeGXV֡i =Wiu[~ȰQPasZISܙ|*%9b s0nYD?Ѥ5ԤpEfVI,FȐ_ݫh)Շ?Šp\w)m$řrOƟ4;4"@? 6*?/%7I=rvI~X:M > ;kZp %jS ޏf!pv0A-0Υ޺b?ylaklઇIU܃~q̂M W' CdLZ"s==7KOnPKx" PKb=l9..mimetypePKb=0d%, Tcontent.xmlPKb=)(%3ObjectReplacements/Object 2PKb=(r Hstyles.xmlPKb=d sNObject 2/content.xmlPKb=ĞB@\Object 2/styles.xmlPKb=%L2f^Object 2/meta.xmlPKb=Ѕ_meta.xmlPKb=5 ?cThumbnails/thumbnail.pngPKb='Configurations2/accelerator/current.xmlPKb=Configurations2/progressbar/PKb=3Configurations2/floater/PKb=iConfigurations2/popupmenu/PKb=Configurations2/menubar/PKb=׃Configurations2/toolbar/PKb= Configurations2/images/Bitmaps/PKb=JConfigurations2/statusbar/PKb=v! settings.xmlPKb=x" META-INF/manifest.xmlPKopenMSX-RELEASE_0_12_0/doc/internal/r800-refresh.txt000066400000000000000000000315411257557151200217120ustar00rootroot00000000000000I was investigating whether the 'R' register behaves the same on Z80 and R800. I initially wrote this program: di ld hl,#0100 xor a ld b,a ld r,a loop: ld a,r ; (2) ld (hl),a ; (4) inc hl ; (1) djnz loop ; (3) ei ret On Z80, for every iteration of this loop, R is increased by 5 (+2 for 'ld a,r' and +1 for the other 3 instructions). On R800 R is also increased by 5 each iteration. Though sometimes it's increased by 6! The increase by 6 instead of 5 happened every 18th or 19th (alternating) Iteration. One iteration takes 10 cycles (taking R800-page-breaks into account), so that's on average every 185th clock cycle. This number 185 is very close to the number of useful cycles in a full R800 refresh cycle. Currently (2010-08-16) R800-refresh in openmsx stalls the R800 for 26 cycles every 210 cycles (so that leaves 210-26=184 useful cycles). So all this lead me to the hypotheses that a R800-refresh cycle also increases the R register by one. This would be very useful because it would allow to measure in more detail how refresh works exactly on R800 (the current model in openMSX (210/26) is not 100% correct). So that's what I'll do in the rest of this document. New test program. This records sequences longer than 256, it's important that each iteration takes the same amount of cycles. So I had to replace the djnz instruction. di ld hl,#0100 xor a ld r,a loop: ld a,r ; (2) ld (hl),a ; (4) inc hl ; (1) ld a,h ; (1) cp #c0 ; (2) jr nz,loop ; (3) [ some code to transform memory block #0100-#C000 into the differential of this block, so it's easy to see by how much R increased each iteration ] Each iteration takes 13 cycles. Per iteration R is increased by either 7 or 8. The difference table (a small portion) looked like this: 07 07 07 07 07 07 07 07 07 07 07 07 07 08 07 07 07 07 07 07 07 07 07 07 07 07 07 08 07 07 07 07 07 07 07 07 07 07 07 07 07 08 07 07 07 07 07 07 07 07 07 07 07 07 07 08 07 07 07 07 07 07 07 07 07 07 07 07 07 07 08 07 07 07 07 07 07 07 07 07 07 07 07 07 08 07 07 07 07 07 07 07 07 07 07 07 07 07 08 07 07 07 07 07 07 07 07 07 07 07 07 07 08 07 07 07 07 07 07 07 07 07 07 07 07 07 07 08 07 07 07 07 07 07 07 07 07 07 07 07 07 07 08 07 07 07 07 07 07 07 07 07 07 07 07 07 08 07 07 07 ... These tables always have only two different numbers, in this case a series of 7's followed by one 8, then again a series of 7's followed by one 8 and so one. The actual number '7' or '8' is not important for timing, only the pattern of these numbers in the table is important. So I'll create a more compact notation for the table above: 14 14 14 14 15 14 14 14 14 15 14 ... (these are decimal numbers) or even 4*14 15 ... This means there are 4 repetitions of '13*0x07 + 1* 0x08' (length 14) followed by one time '14*0x07 + 1*0x08' (length 15). So this notation indicates how many iterations there are before there is a refresh. For this test on average there are '(4 * 14 + 1 * 15) / 5 = 14.2' iterations between two refresh cycles. Each iteration takes 13 cycles. So that's 184.6 useful clock cycles between refresh cycles. Next I realized this test program could be speed up one cycle: ld c,#c0 loop: ld a,r ; (2) ld (hl),a ; (4) inc hl ; (1) ld a,h ; (1) cp c ; (1) jr nz,loop ; (3) An iteration now takes 12 cycles (R is still increased by 7 or 8). Difference table looks like this 16 3*15 16 2*15 16 2*15 (this sequence repeats all the time) That's (3*16+7*15)/10 = 15.3 iteration or 15.3*12 = 183.6 clock cycles between refresh. Then I started inserting extra instructions in this test program: ld c,#c0 loop: ld a,r ; (2) ld (hl),a ; (4) [***] inc hl ; (1) ld a,h ; (1) cp c ; (1) djnz loop ; (3) * NOP 13 cycles per iteration (R increases 8 or 9) pattern: 8*14 15 cycles between refresh: 183.44 (8*14+15)/9*13 * 2 x NOP 14 cycles/iteration (R incr 9 or 10) pattern: 5*13 14 cycles/refresh = 184.33 I noticed the start of the difference table was a bit different, it went like this: 9*13 14 7*13 14 5*13 14 5*13 14 5*13 14 So it took some time before it stabilized on the '5*13 14' pattern. For the rest of the tests I didn't look at the start of the table anymore. I only searched for the 'stable' pattern. I ran this same test again, but now the (stable) pattern was 9*13 14 that gives cycles/refresh = 183.4 This is very interesting behaviour, depending on some (yet unknown) initial conditions, _the_whole_test_ runs slightly faster or slower. This is interesting because it may give a clue to why there is considerable variation to *some* speed measurements on a real R800 (see doc/r800-test.txt) while for other tests the results are much more stable. (Also in the current R800 openMSX emulation, the speed measurements are always stable). I didn't repeat the previous tests. Maybe they also show different patterns on different runs. I did repeat all future tests (there always seems to be either only 1 or 2 different stable patterns) * NEG (like 2xNOP also takes 2 cycles) 14 cycles/iteration (R incr 9 or 10) pattern: 7*13 14 cycles/refresh = 183.75 * 3 x NOP 15 cycles/iteration (R incr 10 or 11) pattern: 2*12 13 3*12 13 cycles/refresh = 185 183.75 * IM 1 (3 cycles, like 3xNOP) 15 cycles/iteration (R incr 9 or 10) pattern: same as 3 x NOP * 4 x NOP 16 cycles/iteration (R incr 11 or 12) pattern: 11 12 cycles/refresh = 184 * LD (HL),A (4 cycles (with page-breaks)) 16 cycles/iteration (R incr 8 or 9) pattern: same as 4 x NOP * 5 x NOP 17 cycles/iteration (R incr 12 or 13) pattern: 4*11 10 7*11 10 6*11 10 cycles/refresh = 183.6 184.73 * BIT 0,(HL) (5 cycles) 17 cycles/iteration (R incr 9 or 10) pattern: 5*11 10 4*11 10 6*11 10 cycles/refresh = 183.90 184.57 I still need to do more experiments, but some *guesses* so far: useful number of cycles always seems to be within (183, 185]. That's a variation of more than one clock cycle. Maybe refresh also waits for an even clock cycle (just like IO does). This could explain why there are two stable patterns for some tests (in one case you have to insert an extra cycle to align to an even number of cycles, in the other case you're already aligned). This could explain why there can be variation in speed between different runs of the same test. And finally it explains why the documentation (e.g. atoc or even the turbor datapack) talks about half clock cycles for the duration of the refresh (in 50% of the cases it needs to add one cycle). Those docs talk about 21.5 cycles refresh every 222 cycles, those number seem wrong to me (don't match measurements on real HW), but at least I now have an idea about that half clock cycle. --------- I implemented the above guess (refresh waits for even clock cycle) in openMSX revision 11643. I can now more or less reproduce the results above: The number of useful clock cycles per refresh is also in range (183, 185], but the number per test is not the same as above (the 'stable patterns' from the tests above are different). Also in openMSX there's always only one stable pattern, while on a real R800, for some test, there clearly were two different possible patterns. New test: above we measured the number of useful clock cycles per 'refresh cycle'. Now we're going to measure how many cycles the refresh itself takes. The idea goes like this: by observing the R register we can detect that there was a refresh, so by doing a longer test we can count how many refresh cycles there were. We can combine this with measuring the total time of the test (using the E6-timer). We can also calculate how many 'useful' cycles there were in the whole test. So the difference between actual and useful cycles must be cycles spend on refresh. And finally if we divide that by the counted number of refreshes, we should know how many cycles a single refresh takes. Full test program: org #c000 di ld hl,#0100 ld c,#bc ld e,3 l1 ld a,r ; This loop waits till there was a refresh. ld d,a ; It's an attempt to get more stable measurements. ld a,r ; Note that this loop will not terminate in Z80 sub d ; mode. sub e jr z,l1 out (#e6),a loop ld a,r ; (2) Actual test loop, same as in tests above ld (hl),a ; (4) One iteration takes 12 cycles inc hl ; (1) ;[***] ; extra instructions here (see below) ld a,h ; (1) cp c ; (1) jr nz,loop ; (3/2) 3 cycles when jump is taken, 2 otherwise in a,(#e6) ld l,a in a,(#e7) ld h,a ld (#be00),hl ; store total duration of the test ld hl,#0100 ; Next we do some post-processing on the data: l2 inc hl ; First calculate the difference table, same ld a,(hl) ; routine as in tests above (though not shown dec hl ; there). sub (hl) ld (hl),a inc hl ld a,h cp c jr nz,l2 ld hl,#bc00 ld de,#bc00+1 ld bc,#200-1 ld (hl),0 ldir ld de,#100 ; Next we calculate a histogram. ld hl,#bc00 ; We expect this histogram to be all zero, except for l3 ld a,(de) ; two entries: the iterations with and without a ld l,a ; refresh cycle. inc (hl) ; There is a single other point in this histogram, jr nz,nc ; that's because we don't calculate the difference of inc h ; the very last iteration correctly (we'd have to inc (hl) ; sample the R register one more time outside the dec h ; loop). But for now we ignore this outlier. nc inc de ld a,d cp h jr nz, l3 ei ret Test results: * no extra instructions: real machine: histogram: 0x07: 0xAECE 0x08: 0x0C31 0x66: 0x0001 -> belongs to 0x07, can be seen by the pattern in the difference table, I won't show this entry anymore in the next results E6-ticks: 0x5B75 total cycles: E6-timer * 28 cycles/E6-tick = 655564 cycles useful cycles: 0xBB00 iterations * 12 cycles/iteration = 574464 cycles overhead cycles: 655564 - 574464 = 81100 cycles cycles per refresh = 81100 cycles / 0xc31 refreshes = 25.985 cycles/refresh openMSX revision 11648: histogram: 0x07: 0xAECD+1 0x08: 0x0C32 E6-ticks: 0x5B78 --> 26.004 cycles/refresh * extra instruction: NOP (1 cycles, increases R by 1) real machine: histogram: 0x8: 0xADCA 0x9: 0x0D36 E6-ticks: 0x6318 --> 26.011 cycles/refresh openMSX revision 11648: histogram: 0x8: 0xADC8 0x9: 0x0D38 E6-ticks: 0x632A --> 26.144 cycles/refresh * extra instruction: IM 1 (3 cycles, incr R 2) real machine: histogram: 0x9: 0xABCA 0xA: 0x0F36 E6-ticks: 0x721B --> 25.636 cycles/refresh openMSX revision 11648: histogram: 0x9: 0xABBC 0xA: 0x0F44 E6-ticks: 0x727E --> 26.25 cycles/refresh !! Big difference between openmsx and real MSX !! * extra instructions: EXX ; MULUW HL,BC ; EXX (1+36+1 cycles, incr R 1+2+1) real machine: histogram: 0xB: 0x882D 0xC: 0x32D3 E6-ticks: 0x17D33 (of course I couldn't measure how many times the timer overflowed, but it must be one time) --> 26.042 cycles/refresh openMSX revision 11648: histogram: 0xB: 0x8801 0xC: 0x32FF E6-ticks: 0x17E80 --> 26.669 cycles/refresh !! Big difference between openmsx and real MSX !! Preliminary conclusion: Refresh seems to take about 26 cycles, this is also the value we currently use in openMSX. Though especially for the 'IM 1' case there's still a relatively big difference between openMSX and the real hardware. Needs more experiments. One other difference between Z80 and R800 di xor a ld r,a ld a,r ld c,a ; Z80: c=2 R800: c=1 ld a,r ld b,a ; Z80: b=5 R800: b=4 ld a,r ; Z80: a=8 R800: a=7 And of course once in a while the numbers for R800 are (partially) increased by one. This difference can possibly be explained by a difference in the order of storing the result to the 'R' register and increasing the 'R' register on each M1 cycle. openMSX-RELEASE_0_12_0/doc/internal/r800test.txt000066400000000000000000000570521257557151200211630ustar00rootroot00000000000000 The test program, in short: Execute the same instruction (or short instruction sequence) number of times and use the E6 timer to measure how long it took. The E6 timer has a resolution of 255682Hz (3579545Hz / 14). R800 runs at 7159090Hz (2 * 3579545Hz). So the timer ticks once every 28 R800 ticks. ----------------------------------------------- org #c000 start equ #0100 ld de,start ld hl,begin ld bc,begin1-begin ldir push de ld hl,loop ld bc,loop1-loop ldir pop hl ld bc,(20000-1)*(loop1-loop) ; number varies depending on ldir ; length of the test-sequence ld hl,end ld bc,end1-end ldir jp start begin di out (#e6),a begin1 loop muluw hl,bc ; test sequence loop1 end in a,(#e6) ld l,a in a,(#e7) ld h,a ei ret end1 ----------------------------------------------- Below are the results, it's formatted like this ---------------------------------------------- [ prologue ] instruction-sequence [ repeat-count ] result: cycles: [ instruction break-down ] notes: ---------------------------------------------- Prologue are extra setup instructions, they are only executed once, only a few test have them Instruction-sequence is the actual instruction that's been tested. Mostly this is a single instruction, but sometimes there is a pair of instructions. repeat-count indicates how many times the instruction-sequence is repeated. I tried to repeat the instruction as much as possible. But to fit longer instruction sequences (longer in number of opcode bytes) in memory, the repeat count is sometimes reduced. raw-result: this is the value of the E6 timer at the end of the test. Note that you cannot directly compare this number to other test because other test might have a different repeat-count. cycles: here I calculated the number of cycles required per 'instruction-sequence'-iteration. I calculated this by comparing the result to the 'nop' instruction (a nop takes 1 cycle). Note that the duration of most instructions in almost never exactly an integer multiple of the duration of the nop instruction. A possible explanation might be the refresh (only happens at the end of an instruction, so for longer instructions the chance of a refresh is relatively less). But we need additional test to fully explain the refresh behaviour. Instruction break-down: Here I tried to split the instruction into micro-ops. I used the following notation: f opcode fetch, no page-break, takes 1 cycle r data read, no page-break, takes 1 cycle w data write, no page-break, takes 1 cycle F opcode fetch, page-break, takes 2 cycles R data read, page-break, takes 2 cycles W data write, page-break, takes 2 cycles x extra cycle (for various stuff) i IO --------------------------------------------------------------- * Instructions that don't do additional bus transactions (read, write, IO). nop [x 40000] result: 1629-1645 openmsx-rev11643: 1637-1638 cycles: 1 [f] add hl,bc [x 40000] result: 1633-1645 openmsx-rev11643: 1637-1638 cycles: 1 [f] cpl [x 40000] result: varies between 1628-1647, most often around 1638 openmsx-rev11643: 1637-1638 cycles: 1 [f] note: same result for cpl, daa, ccf, scf, exx, ex af,af', ld sp,hl, rrca, ... neg [x 20000] result: 1637 (most often 1637, sometimes 1638) openmsx-rev11643: 1643-1645 cycles: 2 [ff] note: - measurement on real HW is MUCH more stable for this test compared to previous tests - same result for ld {a,i,r},{a,i,r} - all results below for the real HW probably vary as well, though in my initial tests on the real HW I only noted the most often occuring value. I should re-test, but that's a lot of work. im 0/1/2 [x 20000] result: 2453-2360, most often 2456 openmsx-rev11643: 2459-2460 cycles: 2 [ffx] di [x 40000] result: 3267 openmsx-old: 3267 openmsx-rev11643: 3273-3274 cycles: 2 [fx] note: I added 'openmsx-old' tags where older openmsx revisions were better (possibly by accident) than the current revision. ei [x 40000] (VDP irq disabled) result: 1435 (!) openmsx-rev11643: 1434-1436 cycles: 1 [f] note: This is faster than nop!! Both instructions are one cycle, but it seems there's no refresh delay after a 'ei' instructions. At least with this assumption the theory matches very closely the measurment.... Implemented like this in openMSX and it matches the measurement very well. I verified that 'VDP irq disabled' has no influence on speed: I mean even with VDP irq disabled, the speed of 'nop' is the same as before. ld a,ixh [x 20000] result: 1637 openmsx-old: 1636 openmsx-rev11643: 1643-1545 cycles: 2 [ff] muluw hl,bc [x 20000] result: 29354 openmsx-rev11643: 29360-29361 cycles: 36 [ffx34] mulub a,b [x 20000] result: 11419 openmsx-rev11643: 11425-11427 cycles: 14 [ffx12] * Instructions that do a single read/write operation ld (#d000),a [x 10000] result: 2449 openmsx-rev11643: 2446-2447 cycles: 6 [FffW] note: page-break when switching from opcode fetching to data writes and again when switching from data write to opcode fetching ld a,(#d000) [x 10000] result: 2449 openmsx-rev11643: 2445-2447 cycles: 6 [FffR] note: same as above but for read ld a,(#0100) [x 80(!)] result: 20 openmsx-rev11643: 20 cycles: 6 [FffR] note: opcode fetch and read are in the same 256-byte page, but still there is a page-break when switching from opcode reading to data reading [ ld hl,#d000 ] ld a,(hl) [x 40000] result: 6522 openmsx-rev11643: 6522-6523 cycles: 4 [FR] [ ld hl,#d000 ; ld bc,0 ] ld a,(hl) ; add hl,bc [x 20000] result: 4054 openmsx-rev11643: 4054-4055 cycles: 3 + 2 [fR F] [ ld hl,start+XX ; ld bc,2 ] ld a,(hl) ; add hl,bc [x 20000] result: 4055 openmsx-rev11643: 4054-4055 cycles: 3 + 2 [fR F] note: same test as above, but now the data is in the same page as the instructions. There is still a page-break. [ ld hl,start+XX ; ld bc,2 ] ld (hl),a ; add hl,bc [x 20000] result: 4054 openmsx-rev11643: 4054-4055 cycles: 3 + 2 [fW F] note: same as above but for writes ld r,(ix+o) [x 10000] result: 2854 openmsx-rev11643: 2848-2849 cycles: 7 [FffxR] note: between opcode fetching (3 bytes) and read there is one additional cycle to calculate the result of IX+o ld (ix+o),r [x 10000] result: 2854 openmsx-rev11643: 2848-2849 cycles: 7 [FffxW] note: same as above, but for write ld (ix+o),n [x 10000] result: 2861 openmsx-rev11643: 2848-2849 cycles: 7 [FfffW] note: Has the same number of cycles as 'ld (ix+o),r' even though this instruction is one byte longer. The IX+o calculation is done in parallel with fetching the last opcode byte. Z80 shows the same parallelism. note: Even though it has the same number of cycles as 'ld (ix+o),r', it is consistently slightly slower. A possible explanation is that there are page-breaks on opcode fetches in this test, since this instruction has length 4 instead of 3. bit 0,a [x 20000] result: 1637 openmsx-old: 1636 openmsx-rev11643: 1643-1645 cycles: 2 [ff] bit 0,(hl) [x 20000] result: 4055 openmsx-rev11643: 4065-4066 cycles: 5 [FfR] bit 0,(ix+o) [x 10000] result: 2861 openmsx-rev11643: 2848-2849 cycles: 7 [FfffR] note: I always found it strange that the format of these instructions is <0xDD> <0xCB> (offset before function) But this allows to hide the latency of calculating IX+o. The same happens on a Z80. * Instructions that read/write 2 bytes ld (#d000),hl [x 10000] result: 2854 openmsx-rev11643: 2848-2849 cycles: 7 [FffWw] note: no page-break for the second data write ld (#d0ff),hl [x 10000] result: 3264 openmsx-rev11643: 3260-3262 cycles: 8 [FffWW] note: of course here there must be a page-break ld hl,(#d000) [x 10000] result: 2854 openmsx-rev11643: 2848-2849 cycles: 7 [FffRr] note: similar for read ld hl,(#d0ff) [x 10000] result: 3264 openmsx-rev11643: 3260-3262 cycles: 8 [FffRR] push hl ; pop hl [x 20000] result: 8956 openmsx-old: 8950 openmsx-rev11643: 8928-8930 cycles: 6 + 5 [FxWw FRr] note: push is once cycle slower than pop, probably because push first has to decrease SP, while pop can directly put the value of SP on the bus * Read-modify-write instructions (single byte) inc a [x 40000] result: 1628-1642 openmsx-rev11643: 1637-1638 cycles: 1 [f] inc (hl) [x 40000] result: 11393 openmsx-rev11643: 11392-11393 cycles: 7 [FRxW] note: This instructions is slower than I expected, but it can be explained like this: - there is one cycle between data read and data write to get the time to actually increase the value - when switching from data-read to data-write there is a page-break (even though the address is identical) But see also the 'ex (sp),hl' instruction below inc (ix+o) [x 10000] result: 4080 openmsx-rev11643: 4076-4077 cycles: 10 [FffxRxW] note: similar as above, but 3 cycles longer: - need to fetch 2 more opcode bytes - need to calculate IX+o before data read can be executed res 0,a [x 20000] result: 1637 openmsx-old: 1637 openmsx-rev11643: 1643-1645 cycles: 2 [ff] res 0,(hl) [x 20000] result: 6523 openmsx-rev11643: 6522-6523 cycles: 8 [FfRxW] note: similar to 'inc (hl)' but opcode is one byte longer res 0,(ix+o) [x 10000] result: 4083 openmsx-rev11643: 4076-4077 cycles: 10 [FfffRxW] note: similar to 'inc (ix+o)'. Opcode is one byte longer, but cost of IX+o can be hidden rld [x 20000] result: 6529 openmsx-old: 6528 openmsx-rev11643: 6521-6523 cycles: 8 cycles [FfRxW] note: similar to 'res 0,(hl)' ld hl,#0000 ; ld de,#0100 ; ld bc,40000 ; ldir [no repeat] (src != dst) result: 11393 openmsx-rev11643: 11393-11394 cycles: 7 [FfRW] note: - as expected, 3 page-breaks are needed - there's no extra cycle needed to repeat the instruction TODO make a specific test for this ldi <-> ldir ld hl,#0100 ; ld de,#0100 ; ld bc,40000 ; ldir [no repeat] (src == dst) result: 11393 openmsx-rev11643: 11393-11394 cycles: 7 [FfRW] note: same timing as above, even though the read and write addresses are the same, there is a page-break in between * Read-modify-write 2 bytes ex (sp),hl [x 40000] result: 11393 openmsx-rev11643: 11392-11393 cycles: 7 [FRrww] note: - This result was unexpected to me: based on the results in the previous section, I expected a page-break between the read and writes. - 7 cycles is really the minimum. There are 5 bus transactions (1 opcode fetch, 2 data reads and 2 data writes). Stack and instructions are in different pages so there must be at least 2 page-breaks. - I've confirmed the [frrww] order, see exsphl.txt for details. [ ld sp,#xxFF ] ex (sp),hl [x 40000] result: 14660 openmsx-rev11643: 14634-14635 cycles: 9 [FRRwW] note: - Now we forced a page-break between reading/writing of the word. So two additional cycles. - This confirms that first two bytes are read and only then two byte are written: the order [FRwRw] would only require 8 cycles [ ld sp,#01F0 ] ex (sp),hl [x 200(!)] result: 58 openmsx-rev11643: 56-58 cycles: 7 [FRrww] note: Stack pointer in same page as instructions. But there still seems to be a pagebreak between instructions and data. ex (sp),ix [x 20000] result: 6529 openmsx-rev11643: 6521-6523 cycles: 8 [FfRrww] [ ld sp,#xxFF ] ex (sp),ix [x 20000] result: 8153 openmsx-rev11643: 8152-8153 cycles: 10 [FfRRwW] * Jump instructions jr $+2 [x 20000] result: 2455 openmsx-rev11643: 2459-2460 cycles: 3 [ffx] note: one additional cycle to calculate PC+offset jr $+3 ; nop [x 10000] <- nop is skipped result: 1229 openmsx-rev11643: 1229-1230 cycles: 3 [ffx] note: - even if opcode fetching is not contiguous there is no extra page-break - slightly slower than test above, because code is less dense and thus overall there are more opcode-fetch page-breaks jr $+2 ; nop [x 10000] result: 1636 openmsx-rev11643: 1637-1638 cycles: 3 + 1 [ffx f] [ ld a,0 ; or a ] jr z,$+2 [x 20000] result: 2454 openmsx-rev11643: 2459-2460 cycles: 3 [ffx] note: condition is true, jump is taken [ ld a,1 ; or a ] jr z,$+2 [x 20000] result: 1637 openmsx-old: 1636 openmsx-rev11643: 1641-1642 cycles: 2 [ff] note: condition is false, jump not taken PC+offset does not need to be calculated, so no extra cycle djnz $+2 [x 20000] result: 2454 openmsx-rev11643: 2457-2459 cycles: 3 [ffx] note: - whether the jump is taken or not does not matter for the program flow - 1/256 of the times the jump is not taken, this can explain why this test is slightly faster than 'jr $+2' (but it's well within measure error margin) [ ld b,0 ] djnz $+0 [x 2000(!)] result: i 63014 openmsx-rev11643: 62707-62708 (1) openmsx-rev11643: 63013-63014 (2) cycles: 3 [ffx] note: - Only repeated instruction 2000 times (iso 20000) because otherwise timer would overflow. - The difference between (1) and (2) is that in (1) the djnz instruction is 2-bytes aligned, while in (2) it's mis-aligned. This matters because it will cause a lot more page-breaks. Maybe some of the regression in the other tests could also be explained like this? Till now I didn't pay much attention to instruction alignment. [ ld b,1 ] djnz $ ; inc b [x 10000] result: 1228 openmsx-rev11643: 1229-1230 cycles: 3 [ff f] note: djnz only takes 2 cycles when it doesn't jump, same result as for jr jp $+3 [x 10000] result: 2028 openmsx-old: 2040 openmsx-rev11643: not yet retested cycles: 5 [Fffx] note: Two additional cycles are unexpected. It can either be explained as two 'x' cycles or as one 'x' cycles plus a forced page-break. See also tests below. [ ld a,0 ; or a ] jp z,$+3 [x 10000] result: 2029 openmsx: 2041 openmsx-rev11643: not yet retested cycles: 5 [Fffx] note: condition is true, jump is taken [ ld a,1 ; or a ] jp z,$+3 [x 10000] result: 1229 openmsx: 1227 openmsx-rev11643: not yet retested cycles: 3 [fff] note: condition is false, jump not taken extra 2 cycles are gone [ ld hl,start+XX ; ld bc,2 ] add hl,bc ; jp (hl) [x 20000] result: 3260 openmsx-rev11643: 3266-3268 cycles: 4 [F fx] note: Similar as above, 2 extra cycles [ ld hl,start+XX ; ld bc,#0102 ; ld de,#FF02 ] add hl,bc ; jp (hl) ; add hl,de ; jp (hl) [x 10000] result: 3241 openmsx-old: 3240 openmsx-rev11643: 3201 TODO retest on real machine, I have the feeling I'm not doing the exact same test now and before cycles: 8 cycles [F fx F fx] note: In this test every jump has a destination in a different 256-byte page. A single 'jp (hl)' instruction is still responsible for 3 cycles (though the cost of the forced page-break is pushed to the next instruction). This confirms that the two extra cycles in a jump are one 'x' cycle plus one forced page-break (in case of two 'x' cycles, this test would show yet an additional cost for the page-break). note: The 64 backward jumps in the first 256-byte page and the 64 forward jumps in the last page in this test are not executed. call $+3 ; pop hl [x 10000] result: 4880 openmsx-old: 4891 openmsx-rev11643: not yet retested cycles: 7 + 5 [FffWw FRr] note: This is again as expected, though the two extra cycles can be hidden by the data-writes. [ ld a,0 ; or a ] call z,$+3 ; pop hl [x 10000] result: 4880 openmsx-old: 4891 openmsx-rev11643: not yet retested cycles: 7 + 5 [FffWw FRr] note: condition is true, call is executed as expected, same result as unconditional call [ ld a,1 ; or a ] call z,$+3 [x 10000] result: 1229 openmsx-old: 1228 openmsx-rev11643: not yet retested cycles: 3 [fff] note: condition is false, call not executed [ ld hl,start+XX ; ld bc,3 ] add hl,bc ; push hl ; ret [x 10000] result: 4884-4887 openmsx-rev11643: 4885-4886 cycles: 12 [F fxWw FRr] note: 'RET' probably behaves as a 'JP (HL)' (two extra cycles), but these cycles can be hidden by the two data reads [ ld hl,start+XX ; ld bc,3 ; ld a,0 ; or a ] add hl,bc ; push hl ; ret z [x 10000] result: 4884-4887 openmsx-rev11643: 4885-4886 cycles: 12 [F fxWw FRr] note: Same test as above, but with a conditional ret that is always executed. As expected same result as above. [ ld a,1 ; or a ] ret z [x 40000] result: 1644 openmsx-rev11643: 1637-1638 cycles: 1 [f] * IO instructions in a,(0) [x 20000] result: 8066 openmsx-old: 8126 openmsx-rev11643: 8152-8153 cycles: 10 [ffI8] note: - 8 cycles for IO seems a lot, but this makes it the same speed as Z80 Z80: 4 cycles @ 3.5MHz R800: 8 cycles @ 7MHz This is needed to keep the timing on the external cartridge slots the same. - Besides IO port 0, i checked a lot of other ports, but not all. I did check some random unused port numbers and at least one input port from each IO-device in the turbor. Only the VDP ports are different (see below) in a,(#98) [x 20000] result: 44286 openmsx-rev11643: 40713 cycles: +/- 54 note: - Can be explained by the 'intelligent' VDP-delay added by the S1990 Need additional tests to accurately measure this. - Only IO ports 0x98-0x9B show this behaviour - TODO this still differs a lot between emulation and real HW [ ld b,0 ] in a,(0) ; djnz $-2 [x 1] [no page-break between in/djnz] result: 126 openmsx-old: 132 136 openmsx-rev11643: not yet retested cycles: 13 [ffI8 ffx] [ ld b,0 ] in a,(0) ; djnz $-2 [x 1] [page-break between in/djnz] result: 145 openmsx-old: 156 openmsx-rev11643: not yet retested cycles: 15 [FfI8 Ffx] note: - we forced a page-break between the in and the djnz instruction by properly aligning the start address - 2 extra cycles compared to test above, this confirms the decomposition of the in instruction is [ffI8] (as opposed to [ffI7 + page-break]) out (0),a [x 20000] result: 8065 openmsx-old: 8125 openmsx-rev11643: 8152-8153 cycles: 10 [ffI8] note: similar to 'in a,(0)' out (#98),a [x 20000] result: 44285 openmsx-rev11643: 40713 cycles: +/- 54 note: similar to 'in a,(#98)' Theory: R800 is connected to a 7MHz bus, but IO is done over a 3.5MHz bus. If the start of IO is at an odd clock cycle (so not at a clock edge at the slower bus) there is an extra wait cycle required. in a,(0) ; [nop] x A [x 8000] result: A=0 -> 3226 A=1 -> 3274 A=2 -> 3872 A=3 -> 3932 A=4 -> 4525 openmsx-old: A=0 -> 3248 A=1 -> 3255 A=2 -> 3893 A=3 -> 3913 A=4 -> 4557 openmsx-rev11643: A=0 -> 3261 A=1 -> 3269 A=2 -> 3914 A=3 -> 3926 A=4 -> 4565 cycles: A=0 -> 10 [ffxoI6] A=1 -> 10 [f ffxI6] A=2 -> 12 [f f ffxoI6] A=3 -> 12 [f f f ffxI6] A=4 -> 14 [f f f f ffxoI6] note: When there are an odd number of NOP instructions between two OUT instructions, one of the NOPs can be executed seemingly 'for free'. Or in other words when there's an even number of NOP instructions, the OUT instruction has an extra penality cycle. These results could be explained by assuming the following structure for the out command: 3 cycles [ffx..] followed by possibly one extra cycle to align to the slow 3.5MHz bus, followed by 6 cycles (= 3 cycles on the slow bus) for the actual IO. * Internal-ROM ld hl,(#xxyy) [x 10000] with yy=#00 -> no page break with xx in normal RAM result: 2855 cycles: 7 [FffRr] with xx in ROM, DRAM mode result: 2855 cycles: 7 [FffRr] with xx in ROM result: 4080 cycles: 10 [FffRmRm] (m -> memory wait cycle) with yy=#ff -> page break during read with xx in normal RAM result: 3265 cycles: 8 [FffRR] with xx in ROM, DRAM mode result: 3264 cycles: 8 [FffRR] with xx in ROM result: 4080 cycles: 10 [FffRmRm] note: - RAM or ROM in DRAM mode seems to have the same speed, including page break optimization - reads from ROM take 3 cycles, they don't become slower in case of a page break. So it seem there are always 2 cycles to set the address plus one wait cycle. - I did the same test with the 'ld (#xxyy),hl' instruction (even though writing to ROM doesn't make sense). I got the same results. [ ld a,128 ; or a ] [ BIOS ROM contains nop (x 11) ; ret m at address #1EB2 ] call #1EB2 [x 10000] result: [(D)RAM] 9763-9770 [ROM] 18757 cycles: [(D)RAM] 24 [FffWw F f f f f f f f f f f fRr ] (<- 23 !!) [ROM] 46 [FffWw Fm Fm Fm Fm Fm Fm Fm Fm Fm Fm Fm FmRr ] note: - this confirm 3 cycles per memory read from ROM - we measured 24 cycles for (D)RAM while there should only be 23, this might be a measurement error though: to calculate the number of cycles (in long code sequences) I compare it with the duration of a NOP instruction, for long durations differences this may be inaccurate * External-slot ld hl,(#4000) [x 5000] result: 2857 cycles: 14 [FffRmmmRmmm] ?? note: 5 cycles per memory access, close to 3 cycles (@ 3.5MHz) on Z80 but I'd expected it to match exactly. It's strange to have halve cycles on the external 3.5MHz cartridge slots. ld (#4000),hl [x 5000] result: 2857 cycles: 14 [FffRmmmRmmm] ?? ld a,(#4000) [x 5000] result: 1634 cycles: 8 [FffRmm] ?? ld (#4000),a [x 5000] result: 1634 cycles: 8 [FffWmm] ?? [ ld hl,#4000 ] ld a,(hl) [x 15000] result: 3669 cycles: 6 [FRmm] ?? [ ld hl,#4000 ] ld (hl),a [x 15000] result: 3669 cycles: 6 [FWmm] ?? [ ld hl,#4000 ] inc (hl) [x 15000] result: 7337 cycles: 12 [FRmmxWmmm] ?? TODO: RETN, RST CPI(R), LDI(R), INI(R), OUTI(R) HALT (how to test?) * refresh org #c000 di ld b,0 ld c,0 out (#e6),a loop nop ; variable number of nops djnz loop dec c jr nz,loop in a,(#e6) ld l,a in a,(#e7) ld h,a ret nops | real | openmsx-old -----+-------+--------- 0 | 8088 | 8036 1 | 10717 | 10716 2 | 13336 | 13382 3 | 16079 | 16042 4 | 18740 | 18726 10 | 34778 | 34753 cycles (without refresh) = 65536 * nops + 197375 openMSX-RELEASE_0_12_0/doc/internal/turbor-vdp-io-timing.ods000066400000000000000000000772501257557151200235400ustar00rootroot00000000000000PKo=l9..mimetypeapplication/vnd.oasis.opendocument.spreadsheetPKo= content.xml]r6ߧ3;4#v'4g&dہHHbC\^;)RlHɞi8ppwqDnY&B\u!,y&?1iqEi0Va㮪Ř J,C!44>[ }z~-'0JS.hEZ ]2Ӻ.$W|ʀOM[9>aEq){J#LL)O2Fr52 T47I1-Ϫ'ӢpC˫#\[3'`&U9gl.wUx!z42po-rK]2eFJg(."yO)PH#Z˳M-1`(3dQgW2Ce]ҨxABAE|0UB>cv S~[@ 7(vkLx|ͬ`Z7Lx,@ח6ϵfT3ZYEuW@k=Mtچ EiJ(n`JbG&OJF\h\!Bma=`*тr"V3/{CE77d?@ظ=-p y⻑{ M# [6&yˇ/*}Qm t&4D!( rHN(ITb\k:䆈9_D[2hT|A.$0ٽ'wp^EKx*r늳6>BU-lyI4 YBbF"`;, nꧡȳ t:K= E6ῒoIm´;[_KoL$x?„baf6Gֲ}yᓰ,4Tv%o5pV߰vŲLwFZTp4i P: C='3DQl7bWCqmO8=1絨 \<_C _\`=mNt9-c a1bOCqmOX<_3=a5-QPECndu]p82a1U:;lGBcЦYMcC>&Vs67s)l2v!@Q}6Bb}*>sPPYL'3G{#5w=οk S 6v^`j4kO~!2=Y3O xTl,:Z4m)s=;ku 3qMFhXSv45_> ΪĶJmͥ&C"Rj%[ {Z[:Оٱe):j*m ;םeeNLYjkkT&:kNi;[,2 V? ^U;8CRCks_sX3mdNIF$Cc '~Ps] r3Uf/fˋCӄ lGB1L\2#qL0c=$,\*-ej(L'Dd&s@G-m,n|̊Kʵsㄉ`QJ9+FP|0$77oh.,3oUM Mʕ XF_T7gbm{EqA^ӈJ嗧J]=}N3: IaJjbN F6cfBB["P =]Qd{"Ɍdl11/=9Oo8~Y_ETܩ Y_I&T@`s&Xyk} r.4b_%8E1nY"3΃j@$ 8&g@0`PyTd4ue~a4S;`ϼɠA^ݏ#h&# TRզ=Uu>FL+X-S uPuVW\Uc,sCI̗p2xb,Lhd6z荫Hۨe嫪z%|STo6+yUUUۊmE"|W_Q !^uS~Lବs&*Ó9?r5me+ﯿ>v@Jm.1T2tKv) SgSeV}EjhctinYoa>cLo+ַ5Lg m$edbM-q3HVo"XSK  XSK O9P2$+5 YIB2}LЉA=3Z96Z TL֔Fd#o%XSK<7q)L% қ76H&Mc̖L% қۖH&McF2x4Hō+$ қM1ģA7\Ms߂Ms{Ms;itMsmMsfMs_Mcސj K|xKL?M z?S[c(R]rLyɃN/Y3]k'IB<8M;HKe+"~TgT' 6!!_BW!Vם &/ݢ]~7sK>;x(}BynN;H'=_)vDhK]RsDoև5>}@ȸ]Ɏz:mSOש1ɵOj_[ǦP+ ``j{T F>vBq,}`6sԬ*w"̈zގƂcmRMrt"pqB Q߃Qݛ3vfcmrnjY-g+ ``j{T Fŗ f3OjXk}l{;VtL+lrn!37ߚӯ#M=LHNQ`;y35riS&%BeB׬/Hp0{0={SC4+Fmrn7!SB̜Iv^>C=ս4i3o> Mm* iMؗD6_a*֨M}bZQX0mjۤA9# Blow>CF0{0={So"׉ C0QԛbmrnfB[wZLm`̥`Dz7SǃiS&xVL_"F;& M. W vfjӦMʹA!~?QwJim'0{0={Sq<rH+cHP69ЀVÉxSI40{0={s7$`G=oDҏMιN\LO /BR U}GuobڟN1w`G=o# MmsS@.%%mk {R ``j{T>Y}I$WYzL `ԶI97u9&U+h;vDv0{0={Su i;i:mrnj}vRMF*?F}GMЬNwk$ /O/=DzJR3=Dgz.x8a!CNm= ^D : zA%Gs/Û8ᯤ3篭 xA%Gs/Û8ᯤ3O0eN͉Lo0^Ϝ #];L/DP_ќ&8i+ix҂AKGeN͉Lo0^Ϝ>h<à+eN͉Lo0^Ϝv96^/͉Lo0^Ϝ*#WF%Gs/Û8ᯤ3sT|ggTIQ_ќ&8i+igey(Izy>܃9M qWkSd_uI͉Lo0^ϜTq>ޟz^ ECjor(ak=,9z(q}51뫳;7q/Ijmse'jLy'|9)=+98a' % 9]3_c? zǤ(hNepxB44xuR]I7,DX_ќ&8i+iŲw/]Oz|Z=+98a' % 9u+;rۇF |)=+98a' % 9MSHzy {Wr4'2q8xӒ=йW\\$ >"}r}qQ]w%MʹS0)6@1Sۣ7b:bIz.)`G=h8js&܋ u?&SxDA3o)^ukEރ⑜9%-͜x$& /3Y='ɉM ?0f?<r2*m?'ɉM# ͜x$ V/[,?&Nf?-f-X;J'ɉM4sA%,6ՋR='ɉM# ͜xhVC(VW(m'ɉM# ͜EFfV5R?HNpo,YG$ Zˆ9Oކi&{iy|8OFʓ= ē2gʓZCZ<gtYFV~KuB #-"4USX_#'h<'FWA {=Lʹb."!J`ޱˎ Ųխcd߬uc7t&.]޳vg}vL{`>;ӷުC>}6wĂKٴMj_[Ǧަr@9"%5;"ܬ׬|輸!AJ6YE8}DZ7޿S;gJzO%oލn~ 70].bz'𭈾irġ.n97%R.CpjPw#̨aGuF;y;y:L&GP|_ڨ@7u f3OjXa2:&cE+>V&/[ʤnSۣ7dQܛ]mrn=`OguM=p׹F=o MmsSVyBpu yF}Guoj0%7S MmsSo@qfhSۣoV0/(m4<5=cmJ{2#y;V L+lrn( QoVF}Guoj(QQ`ԶI97x/3uO–:yJTF}GuooS&bF:zC VsSc00DڜP69澐/}hSBZ[m` H_s쭗pL6)GXR{]Fz)r Sۣ75'G(f(`W0mjۤ@mSh] O&Bzr Q߃QݛaNА&NԞnhqz\zGrCxi'.huÍ۶ X?HNp# ͜0-|1O<8!FZS 9*̓ZX?HNp#ebf?<1] s!2{O< 'Hkj|A3,x,]1V.c#9!PQ)ރ⑜8!F 9 CJ1(ϥX?HNp#͜xlQex$'A8Nf?> _hNp8i+iS#CT+CQ.phNp8i+i]C&ʮБnJ_ќ 8 qWw=Tvͅ(]s@%Gs/Cx/I_Ig_EѲuzWr4'B8B44xt)oյ=+9!x!NJp=! @zo Ϥa'r 9Q: l|*SQNɧ>_"{myM})U<]v'r{q1!?^Vi]VG "X+pըW/y6}m} =6띗{hh``j{T#kI6 MmsSh(Q'ZI/9#08R[75wp[(Bev(fj{0mjۤV7X[jizV=սgψ~CHnmrn胿FwU;¸BҀF}Guoj0UۃH0QϛL6)\{G{% c^7Q߃Qݛ-I:v?0QϛL6) QWeGrCxiÒ/hÏܫ͹W rz4^Ԑk#9!6lPwJTlp{%? 'Hk|A3~uz vp>_Mz5~sHNp#yXַ͜ԣ|C߷Sɮ? 1Қ%_ 8FHW[܏ɏ-#9@^ٺz .9\Yewg.m?۶mSdߘնrniݶh1[d폜~zm?=7/m%Ym[붟mx5YնF}smmO&ӳ|nݶh۟DGR|5kmO}>V$,j jO5@`^ߜE.7!r<4kG`,}ymX#4(3[r jJVק` 6n Y݂؂Sc|-ni-n܂7 |\p>6AW >V.{A>h8٩xv\^ Xq>hdvvrlQáݶ5FfOfs {S낖cd]^ H[+om߇ ֟ ԢxZ .֯VȽ^i|P}:jHݷ_ p^zmlGrvt8!FZg@:])8ҕʩ 8{ '@(s:JP^dًG]7p;Bpo{5 ӫDaH8Mkm)'_PW?Tպ}Ϣ^rppOzEkR('0hi>NcZNmoA_jp,Zʕ W.3^W$]V BL-p"|>@'OR/Ĺdڬg.׊~K1O vzK)fRz>PA 11/wQE ]40.7(wE.hhnE.#v/L'Ș张K. zWnU{S9)z¾i@"- QV)2oޮLi}{ՉIk8DLsLP~{^tFEz?AUidӁdmj!^̟ }лugnvB>81oD1rht"ׇՕ~K Dz:X/p*asj䏈{8=|8r}x'Q]͗qN[:?}=qiU'- o?ݏ?NjE&O_QYTԵϪD~ .BW\g:h'x}PKǞ\*VPKo= styles.xmlYmo6_a(C)9iaŰkPeR@Rv_#)ʒ%+Z ^>|R1Qh3Z"aE>]nDW9-4RpVnsTX Z$j(iVmVܖAO6؎,~IS 8mb((yI4;⁳:j]0\ GĘrjS8GcsT mRQTNhj)\4EmN|ѵog dz\&mٜ^װiW2zv%+'_ӡBT#ݚ+[(|/G1qøȇH\΄GKs鳚aIK!ucH:;&U:Szh&d \bH[Hctѩe/>h\iI e¢NZK}k5:-P5*"I2Il|H%4jsRYgC:I2@z@5;( BRDt y# k.Ǐ~E$TwpR-[74 ^w۞ДT^sm-)7컭*%Mj]۫YN`ájHQI$$)}1P %LiRΟ(ll_ޯT8)dF .]p* Hq)z< w}s0m٩f;OMqō6fX-IAx(у?#?ɽyB%T8(UʖmvfXhxcELt~ǔtP.P_"!*1va4%\vd0TGƇn52C=<\آeEm4 goEN e@hhRp-J=9;8!G* ˒uqU/M A_+n|Nq'Y"H 1 u͖5<WRKa(z[7o ^poMưgwZn&EUEHS) RDi9iԜla9)/fL\7Cʒ|NpxI}%LpBZ>/qY_l^Q"&:ZTPڥ+gȞ%Ǵ3:؎XLe[!VyYGk'SwWJg1#keR˧+7J mw|tʏJA vv {L.]Zp.\[U5,r̮I >\|hLg?XzΊXHIi0 iE E߆wF{!ӻS]q|9Q u46Ҵb977~Y:.+ '`|[8S0'zMqӜܥ~0fanZk=&Qx9Ǭ2֌Am{~#sRg0\ѳsLo Y0 Qxe qf7?μ`b /WacPtj15m};,m[bg;/_PK8rPKo=Object 1/content.xml]r8SnR"AR$URN%9Lrڽ$$qÿ%)KӾþ>6L-q$U4&e oDx%~nǿQ?-r,~m"|8WM7yF<8;)ƃ3-Z[K7{쩛/n>]Jij>;1t1ZiJtZ>QJuð[6t-$ɺtr{`-nQ~H5#L<rDL$ (屪S0zٌf?SP+ʙ]gB(OzGMUuiVBJ^J W>=qj>%63t HQts]T R;D̷S-tsCq>N&n1FD}il RmMŢ8 TQT\ 7p(jDqP *. sDR$i *.؍,"7zuGs fc{z3 TUb`.v_,=a3 nv7U2pYL(:@ !&]*W)hT]tpEVy ׳O? zx`dB<ճMHbዀEuM \(] g.gd0&~{ZWA7ՃGܑ D?q D26o 4csp.Xw5&aÉT8'{As sk8:'P\oTX'8fPE A(h VϛO\'(:ٸiS3mTצRV nTGSG':zRU.mN :ƞ}> :Uیb4cDGu7Qe%L0 s!җ{'o$yzue~+jJ1:J9J3RŦǞA9ytO DWGg3< p6%>TSss& uPNOd/7"EtRD ~2Ȳ|eUPN@RiKJXTg6r-̐-xpNqMiM3C76[ae&: c 25Dk|"'vR_hjZ͟W$.PK5Z PKo=Object 1/styles.xmlKn0=ED@ZAckHD^3㥩WD@<['+9}%RqIڦ~ҬX:>5f5P)RO"ip5 b^Xg9K@6Ċ=j+:-!д̪?Q\jږ2 C6l2햄9`>smu'ޙ!y]s_gL_Ew mD|6*|v=K=qCы zx|4q9qD*{F]˯e h@w)=ۦ?uB+&7n^jK~KjiS6n,ӯ1B,Vm% E0jÎܾPKĞBPKo=Object 1/meta.xml=s ~ fG1Cu蚥{U1Mz:ǹ0VjU'(\DSFւr]P.c?,]G%YibTBmG=MѺ3wR}ùLӄ kB9 kX,8]7GZߌ| Ha]otk8* |BX|bzFѵ݅ SѦg.z>šBD «K`bxU[-"Tk9m80ٝOQ;Hҧd2ʎK;TPK%L2fPKo=ܳkmeta.xml 2010-08-19T19:25:022010-08-19T22:03:30PT00H04M52S1OpenOffice.org/3.2$Unix OpenOffice.org_project/320m12$Build-9483PKo=Thumbnails/thumbnail.pngyeX7ŭŋ[{[;RkP%h tϹg\3s3Di)ࢡ(B}=BFGRMpjBCcR}uԝ鸶z+][bE,,? EgLr)m4 m 7ݞU B5Ŷg'΀n[݀ufV.ԁ\Ӻ.oq,O s7!\*`,"J:!Ƀ"A8ǟۅbB!m%kABӄ@/0@4U5?OđE8L1 cbfvC.Uu9zS_كa´$d.Rnu%s.P7ARil(f\דF ?PD~{F@ySAlu9M{~`YC?Vǘfsoڗ)jL.|esmڢO2zw|jȜ^Ҫ|HϤ~, ȦT[:voKd?5A^(I#n0):KQU<246]9U_xń~DS7u Jz:h0$]>{W)n~hЊe<NPET/&D;dNݞUq>0p#O`6e; e*9귆`g&+hlY)MU,-/xWnT+. ACsmb1T{u ِQ g"~>DӶnޫ\>J7 v%XU9_&&\ ml{'?U%9sPR[NnBcEV8{SvxmƘR e\j E6\zv9S~ˡrldH]`ʈ{YW⮭$C{]!ȴ|֙Sd|3_{؝r* ]={V"XR4ܟ P Ioz ӥ{s, ~huq4ȤvF~/%Ug5˛9.w*bf\HK_r_f$Zhr`u}М77rn5Fxvg J$6l O^n' J&]#r7Xg3 9bb D|UR\BO7 2i/l`qcr$7sb:iJg^5DW9pU:ֻiggu7 )\Dz^qY{HZVwZ<2XI gF )"zA'Fw)fSRl+֟>pBӆ jdW"#&m $DL:8G\K9pk\7jeúx찓V-flʣYc1P%T 0RLƝ<ޖ&"GRx/y{LVlk|6- "mNO-b\kcVJ<iuO[~o ͔JWڸV+/nM^_^1Z;-?:JN1<=tVs7Ȗ5=kIyͲX&VHmPMΰf8#Ԑ|̹'L>rdy i!$_N0%)5VO}1d?+ICw*jR=qE*_ 1Ү zB;+> Iȇ?=&*L1I1[w<]e!c˿,`W%ITy4+q guj{w YYS2T qv=#>gredpz\ÝI󈸟a\e6tQ Zc9Ki-+^,R5_On=mMm%o.nC?Z$ǃJ GNgy1҇WBF/^xU<\^WKr"9ӕlhQEpBW:k"0}:TDv75zDu#VD8x Vb<,2Tɳd&D`TYYg=(fWtK쀽au5clJF& /䢽tq>b/t3C~ŏg')`?_J6ig8,="?\u-d LEC6+])RBnj땈C;gNSFSQZ3+q3,,qY+x9=@X5}3,ԷU}Re9Я.Cq<4p]Y "U$[څoJWj8 wTkQ-) a9mNW6}@ֵw1êkfFɍ4w,tC8ߞGXHw'HyF_>/4oS0 jZyȨx[- M^5gO;Nz }2>on_CunB}S8! ͧ|۩/'Zvܤ\NUwZR_8zk/vyE׽0ue]Tm7?žقT"Kڬzmn=WMm ߷4.Tb:;M1|a%. CQ3~0妈xU!tq{/eF͂~£b"O+9I`H]ZHr1ptu}dyXOV,%r+Q|8qhgz] {s^1Nl߂ZȄw`n%Ոj[nTK@iv.wCmZ:tAgTOdn|P9T86?/`Yӗ-񏻣`vFa^Sگ$5Ky^ h؋?R0XpOnع]1}7υh'cPa`I };O[_ җ[QDð3u-6#t8H1xODrZv{D"b9Ӻ#헡m[baaJ= ,㔗R]Ri r00O˿ oaZſ~BTϡiِ[YgρQK|9&ܗ¼$TI`eOD~eľCX#M?=8ǗR@S#&fv~= \?'P,3AϩA}5ƔP`^a(teZN 4z$n.CI,ck}ޛ`oPj$ݕR7hr#P`V*{^& $0aXg[t~5ʜ5bSd,Q{FQSqerb-t5)ߒuV0*,TdtEUY{5My9&FR.L.Nby|/+e=;PhQm /Ed1t=ȡ) NI_*tEd HT9o0.Zijʌ8-i~ǜL%z@vgTM0|y6`/gٴg?Cs0:,:@$It{JY\.w3·}FtbxORfJ1~-kU/S̏0XmV Y6ΏX:N[eQoD*Su+˪Sq #$Q-$ Tف&iZSe0"BihK$,?Ձ L=Yvf#DP&B7e!hVMk|TpzjN9EPN1>=%d|Yks^^GG-L*QL(BwgfxPe~AI[h F˲~cj2R*:mzжQ}awagi']X#wBջ5 q0ze.+}gnS`]|1vעoyRBE1/hi1L*O {|*b ?b;~fB4UR$(E_WÙ]EC#v,jX%u\>-EcYelV@e5E'8^<.=xYR%on^ŃzT,KmuE2R_+ZEdh"XP܋w T vUxՋ"e_= n>#(6&.d .* 'b4Oapeד"$9pcQ$D,)c3T.eTE[\|^ 2fg»-E8cV?!l;|Ar%j1N7ӄf?IhBn*ZezW|u1ߢ}yhȯfL7*\O"8j2jOp{y%^ 9ơ Aۿ2'Ҕ΋V -bXx)EIX-#.2fVV8bFz}Xb!p^(@ĩ`vڴGŸp&(Azhe-ߕ;G+#N<8 "l8\# o?Rex8Fκٹ\ZƋPyaA֑ o}uvڿяY֝ th~фsy) ,{{.˰nwYT`ݳ+m`ѦݞWZ7K^s(su 4LW2EXX^dآE:Qô?FlcRܒQ,yb3'_eWXɬ[bXb|Y:iM"?BJw{TJ|Q?upKln%yˁ^J:)C*<ܳu1Iˊ[1ЄqղTЂ+5+9& hd ˉm%ڧ y/ 46%Ua~M5+nOs ?-C0P  d\h-T"'u~\4N14ߤb[lJ:LxR+6PE95*!PKX) o!PKo='Configurations2/accelerator/current.xmlPKPKo=Configurations2/progressbar/PKo=Configurations2/floater/PKo=Configurations2/popupmenu/PKo=Configurations2/menubar/PKo=Configurations2/toolbar/PKo=Configurations2/images/Bitmaps/PKo=Configurations2/statusbar/PKo= settings.xmlYQs8~_ݱsi8k'M20q 0@C0Mgc$}v?}+?!)58[F{jv%ы+A)=Evr.lxdE;H$'HGyel'5=yd?Rq|Pz^:O/骮l)D|2,ȜIq/ouvNƶy d?] Hb=N\Yڤ?E*[|=t`"1A {/A|Ke պ zhڧ_]K]? ~廔%u_ԠFN#!QܢJײhfsʠ5CBA#W!o5r)yڬ} OQPݯ $F:AEE N/('bk1-V$ N@Վ;ӊ>&Lj$Lk$lkH[ҾŅq}#OOyZ͵5M1ȄZoK i;K ^WyyW{%'B bP( ?T^?anVqN[5KH0/bZ84Й`!He{W}w/PKSwF!PKo=META-INF/manifest.xmlVn0)zKL{Bf+QT!rF$k3nwena7$7?ۋM{MQwzy =O)Ơ>/4HLi#](hIKH#w];`nR^/`H+֡_P3XӪ.KR8^ =kmh3\ .C>!ϖVz;=,aLQyuR09ˠJ98Nƴ%2ǯs/~r˟PK>ȶ PKo=l9..mimetypePKo=6? ~q Tcontent.xmlPKo=Ǟ\*V ObjectReplacements/Object 1PKo=8r 7styles.xmlPKo=5Z >Object 1/content.xmlPKo=ĞB'JObject 1/styles.xmlPKo=%L2fLObject 1/meta.xmlPKo=ܳkwMmeta.xmlPKo=X) o!&QThumbnails/thumbnail.pngPKo='qConfigurations2/accelerator/current.xmlPKo=qConfigurations2/progressbar/PKo=&rConfigurations2/floater/PKo=\rConfigurations2/popupmenu/PKo=rConfigurations2/menubar/PKo=rConfigurations2/toolbar/PKo=sConfigurations2/images/Bitmaps/PKo==sConfigurations2/statusbar/PKo=SwF! ussettings.xmlPKo=>ȶ wMETA-INF/manifest.xmlPKyopenMSX-RELEASE_0_12_0/doc/internal/v9990_command_timing_test_results.gen000066400000000000000000000305221257557151200262020ustar00rootroot00000000000000; This program was used to produce the results of v9990_command_timing_test_results.txt ; Written by Wouter Vermaelen, based on the code of Peter Mastijn ; Change the parameters at the top to specify what you are going to measure. org #0100 FREQ equ 0 ; 0 -> NTSC 8 -> PAL ENABLE equ #82 ; #82 -> display enabled / sprites(cursor) enabled ; #C2 -> enabled disabled ; #02 -> disabled enabled ; #42 -> disabled disabled di call init ;call set_p2 call set_b0_2bpp ; change this to select mode (set_xxx) call tst_LMMM call tst_BMLL call tst_BMXL call tst_BMLX call tst_LMMV call print_results xor a ld ix,#00d1 ; set screen mode ld iy,(#faf7) call #001c ld ix,#0141 ; init palette ld iy,(#faf7) call #001c ld ix,#0156 ; clear keyb buf ld iy,(#fcc0) call #001c ld de,text ld c,#09 jp #0005 tst_LMMM call cmd_common ld hl,#0020 LMMM_0 call wait_vblank call LMMM_1 call wait_vblank call LMMM_2 call wait_vblank_cmd jr nz,LMMM_3 inc hl jr LMMM_0 LMMM_3 call setres jp wait_cmd tst_BMLL call cmd_common ld hl,#0020 BMLL_0 call wait_vblank call BMLL_1 call wait_vblank call BMLL_2 call wait_vblank_cmd jr nz,BMLL_3 ; end if still busy inc hl jr BMLL_0 BMLL_3 call setres jp wait_cmd tst_BMXL call cmd_common ld hl,#0020 BMXL_0 call wait_vblank call BMXL_1 call wait_vblank call BMXL_2 call wait_vblank_cmd jr nz,BMXL_3 inc hl jr BMXL_0 BMXL_3 call setres jp wait_cmd tst_BMLX call cmd_common ld hl,#0020 BMLX_0 call wait_vblank call BMLX_1 call wait_vblank call BMLX_2 call wait_vblank_cmd jr nz,BMLX_3 inc hl jr BMLX_0 BMLX_3 call setres jp wait_cmd tst_LMMV call cmd_common ld hl,#0020 LMMV_0 call wait_vblank call LMMV_1 call wait_vblank call LMMV_2 call wait_vblank_cmd jr nz,LMMV_3 inc hl jr LMMV_0 LMMV_3 call setres jp wait_cmd wait_vblank in a,(#65) and #40 jr nz,wait_vblank wait0 in a,(#65) and #40 jr z,wait0 ret wait_cmd in a,(#65) and 1 jr nz,wait_cmd ret wait_vblank_cmd in a,(#65) and #40 jr nz,wait_vblank_cmd wait1 in a,(#65) and #40 jr z,wait1 in a,(#65) and 1 ret setres dec hl ex de,hl ld hl,(resptr) ld (hl),e inc hl ld (hl),d inc hl ld (resptr),hl ret resptr dw result dw hexout digits db "0123456789abcdef" text db "LMMM BMLL BMXL BMLX LMMV", 13, 10 hexout db "0000 0000 0000 0000 0000", "$" result ds 80 init ld a,#00 out (#67),a ; MCLK = 0 xor a out (#6f),a ld a,#06 out (#64),a ld bc,#1763 ld hl,init_regs otir ret set_p1 ld a,#00 out (#67),a ; MCLK = 0 ld a,#06 out (#64),a ld a,#01 ; P1 mode out (#63),a ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / sprites enabled ret set_p2 ld a,#00 out (#67),a ; MCLK = 0 ld a,#06 out (#64),a ld a,#41 ; P2 mode out (#63),a ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / sprites enabled ret set_b0_2bpp ld a,#01 out (#67),a ; MCLK = 1 ld a,#06 out (#64),a ld a,#80 out (#63),a ; B0 2bpp ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b0_4bpp ld a,#01 out (#67),a ; MCLK = 1 ld a,#06 out (#64),a ld a,#81 out (#63),a ; B0 4bpp ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b0_8bpp ld a,#01 out (#67),a ; MCLK = 1 ld a,#06 out (#64),a ld a,#82 out (#63),a ; B0 8bpp ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b0_16bpp ld a,#01 out (#67),a ; MCLK = 1 ld a,#06 out (#64),a ld a,#83 out (#63),a ; B0 16bpp ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b1_2bpp ld a,#00 out (#67),a ; MCLK = 0 ld a,#06 out (#64),a ld a,#80 out (#63),a ; B1 4bpp ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b1_4bpp ld a,#00 out (#67),a ; MCLK = 0 ld a,#06 out (#64),a ld a,#81 out (#63),a ; B1 4bpp ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b1_8bpp ld a,#00 out (#67),a ; MCLK = 0 ld a,#06 out (#64),a ld a,#82 out (#63),a ; B1 8bpp ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b1_16bpp ld a,#00 out (#67),a ; MCLK = 0 ld a,#06 out (#64),a ld a,#83 out (#63),a ; B1 8bpp ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b2_2bpp ld a,#01 out (#67),a ; MCLK = 1 ld a,#06 out (#64),a ld a,#94 ; B2 8bpp out (#63),a ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b2_4bpp ld a,#01 out (#67),a ; MCLK = 1 ld a,#06 out (#64),a ld a,#95 ; B2 8bpp out (#63),a ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b2_8bpp ld a,#01 out (#67),a ; MCLK = 1 ld a,#06 out (#64),a ld a,#96 ; B2 8bpp out (#63),a ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b2_16bpp ld a,#01 out (#67),a ; MCLK = 1 ld a,#06 out (#64),a ld a,#97 ; B2 8bpp out (#63),a ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b3_2bpp ld a,#00 out (#67),a ; MCLK = 0 ld a,#06 out (#64),a ld a,#94 ; B3 8bpp out (#63),a ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b3_4bpp ld a,#00 out (#67),a ; MCLK = 0 ld a,#06 out (#64),a ld a,#95 ; B3 8bpp out (#63),a ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b3_8bpp ld a,#00 out (#67),a ; MCLK = 0 ld a,#06 out (#64),a ld a,#96 ; B3 8bpp out (#63),a ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b3_16bpp ld a,#00 out (#67),a ; MCLK = 0 ld a,#06 out (#64),a ld a,#97 ; B3 8bpp out (#63),a ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b4_2bpp ld a,#01 out (#67),a ; MCLK = 1 ld a,#06 out (#64),a ld a,#a8 ; B2 8bpp out (#63),a ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b4_4bpp ld a,#01 out (#67),a ; MCLK = 1 ld a,#06 out (#64),a ld a,#a9 ; B2 8bpp out (#63),a ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b4_8bpp ld a,#01 out (#67),a ; MCLK = 1 ld a,#06 out (#64),a ld a,#aa ; B2 8bpp out (#63),a ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b4_16bpp ld a,#01 out (#67),a ; MCLK = 1 ld a,#06 out (#64),a ld a,#ab ; B2 8bpp out (#63),a ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b7_2bpp ld a,#00 out (#67),a ; MCLK = 0 ld a,#06 out (#64),a ld a,#a8 ; B3 2bpp out (#63),a ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b7_4bpp ld a,#00 out (#67),a ; MCLK = 0 ld a,#06 out (#64),a ld a,#a9 ; B3 2bpp out (#63),a ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b7_8bpp ld a,#00 out (#67),a ; MCLK = 0 ld a,#06 out (#64),a ld a,#aa ; B3 2bpp out (#63),a ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret set_b7_16bpp ld a,#00 out (#67),a ; MCLK = 0 ld a,#06 out (#64),a ld a,#ab ; B3 2bpp out (#63),a ld a,FREQ out (#63),a ; NTSC ld a,ENABLE out (#63),a ; display enabled / cursor enabled ret init_regs db #00,#00,#82,#00 ; r#8 -> display enable db #00,#00,#00,#00 db #00,#00,#00,#00 db #00,#00,#00,#00 db #00,#00,#00,#00 db #00,#00,#00 ret ; not used LMMM_1 ld a,#20 out (#64),a xor a out (#63),a out (#63),a ; SX = 0 out (#63),a out (#63),a ; SY = 0 out (#63),a out (#63),a ; DX = 0 out (#63),a inc a out (#63),a ; DY = 256 xor a out (#63),a inc a out (#63),a ; NX = 256 ld a,l out (#63),a ld a,h out (#63),a ; NY = hl ld a,#34 out (#64),a ret LMMM_2 ld a,#40 ; LMMM out (#63),a ret BMLL_1 ld a,#20 out (#64),a xor a out (#63),a out (#63),a out (#63),a out (#63),a ; srcAddr = 0 out (#63),a out (#63),a out (#63),a ld a,#04 out (#63),a ; dstAddr = 0x40000 xor a out (#63),a out (#63),a ld a,l out (#63),a ld a,h out (#63),a ; numBytes = hl * 256 ld a,#34 out (#64),a ret BMLL_2 ld a,#a0 ; BMLL out (#63),a ret BMXL_1 ld a,#20 out (#64),a xor a out (#63),a out (#63),a out (#63),a out (#63),a ; srcAddr = 0 out (#63),a out (#63),a ; DX = 0 out (#63),a ld a,#02 out (#63),a ; DY = 512 xor a out (#63),a inc a out (#63),a ; NX = 256 ld a,l out (#63),a ld a,h out (#63),a ; NY = hl ld a,#34 out (#64),a ret BMXL_2 ld a,#80 ; BMXL out (#63),a ret BMLX_1 ld a,#20 out (#64),a xor a out (#63),a out (#63),a ; SX = 0 out (#63),a out (#63),a ; SY = 0 out (#63),a out (#63),a out (#63),a ld a,#04 out (#63),a ; dstAddr = 0x40000 xor a out (#63),a inc a out (#63),a ; NX = 256 ld a,l out (#63),a ld a,h out (#63),a ; NY = hl ld a,#34 out (#64),a ret BMLX_2 ld a,#90 ; BMLX out (#63),a ret LMMV_1 ld a,#24 out (#64),a xor a out (#63),a out (#63),a ; DX = 0 out (#63),a ld a,#04 out (#63),a ; DY = 1024 xor a out (#63),a inc a ; NX = 256 out (#63),a ld a,l out (#63),a ld a,h ; NY = hl out (#63),a ld a,#30 out (#64),a out (#63),a out (#63),a ; FC = #3030 ld a,#34 out (#64),a ret LMMV_2 ld a,#20 ; LMMV out (#63),a ret cmd_common ld a,#2c out (#64),a xor a out (#63),a ; ARG = 0 ld a,#1c out (#63),a ; LOGOP = 0x1C (TPSET) ld a,#ff out (#63),a xor a out (#63),a ; WM = 0x00FF ret cmd_common_2 ld a,#2c ; not used out (#64),a xor a out (#63),a ; ARG = 0 ld a,#0c out (#63),a ; LOGOP = 0x0C (PSET) ld a,#ff out (#63),a xor a out (#63),a ; WM = 0x00FF ret print_results ld ix,hexout ld iy,result call prthex call prthex call prthex call prthex call prthex ret prthex ld hl,digits ld d,0 ld a,(iy+#01) srl a srl a srl a srl a ld e,a add hl,de ld a,(hl) ld (ix+#00),a ld hl,digits ld a,(iy+#01) and #0f ld e,a add hl,de ld a,(hl) ld (ix+#01),a ld hl,digits ld a,(iy+#00) srl a srl a srl a srl a ld e,a add hl,de ld a,(hl) ld (ix+#02),a ld hl,digits ld a,(iy+#00) and #0f ld e,a add hl,de ld a,(hl) ld (ix+#03),a ld de,#0005 add ix,de inc iy inc iy ret openMSX-RELEASE_0_12_0/doc/internal/v9990_command_timing_test_results.ods000066400000000000000000001107001257557151200262130ustar00rootroot00000000000000PKO9l9..mimetypeapplication/vnd.oasis.opendocument.spreadsheetPKO9 content.xml}s77*?[fvtSEIJVbؖJR&_% 5tgw@5o߿}pէoI`ջO?W_]}/Wt{7>7W|7W׫O񯾁~귻M8|?m_u? զ||]ů:ULn~~s/m/7O{baWw >]sqwoz}↉ًt?xv57ϳݩ"m>U;/f|StwvbU~pyӿ~\ޭn~o/>M=~Nc/'|ZD#nqߜ>|n8𢡊~~=r!N$X~^\a$s磌o G.Ϟ9nCx"ڟxzn~i_?(>_uޮ/>'~}}:ꓗ͏O\_޽7a#~оY]|ӳϯX]n_}Z_:^ M3ZOrN'ÁIpTAp8G3s8n-ǁsAbcM9p5ȝRZDWPEH]A Tq`Ŵ8}WPOp󮠢ʡw㒪Bc,/+4X^PW(X^PUh,5cq1Yw{s#d7}SW?Z]=x9_Vk&dpf+xq{y~}_mw]^{姟zO:`$~G:xxzzvⷫo1yhx5;گ~>zV6Շ&@ڇu}Ѣj?뻎(ŧu&aCj><NwrhRwCOW/w_s<ُW 7~}Ň3`0F#/O EÖ~6zuqz3?'7VNN R2rq9N8-;;2R ^m=>}S_HQt"^|e_w^*^zpn`W_JD׋D6 m߿Y>8:oC}/{vtT{ 0O87xxx)ط|ģ&ޑ&@:0jq{>`jPXjb&[m&.aaGM<&f5xhE20h hSp=ͮᮡv )zDCŁDC5tCPQ*ə""gBYPAڤѦqJՠt(D[d|2"5"1H A..Ew i_?`jPXC]7P *C],Ad\&IE .P+ᆉgjCQI7t * bV_∾C*mjB@r4CB© yF嚇3r{j$\Oʙ THmEV@)\5ԁhpwG @jPxJcqc55Pc̉8Vޚ2Pcq5Pc\A{UiDtp8qkujP:xDLCC#±lkj 5mKmjc~aijQ{=5_&7Ux5-TuC#WҸ"+i(bUvG@tp26n`˄0 }2ɘ{(\U=.m, uCԾЇc~c%qw._ݧŽq_V1{O6d]͞$6ER]"LdTՃ TA00!#8'Ҁ+@4Ten}9Ue_ơ>BTX!`@m>T\*WTb\8TjP粒eF]x a\t0cv4 :\du@j_@ J Vݫrv RT[;Qtkʵ弡+h](p+#Րj( R  rQ\tqԠtpPneRv !up:u!;q@i8PT @ Q V:5'̞QThX1QU ؕf,*\$ ̎1 x~8(h:B;a_n-Mac/\֐Qe`}?Fd0Nv ԤJe 7%I)5ȣ>n:x[{C:xڇdi 5(\6t@L5`mF$Ȩ19t{j:h6: xy Yvv"ɯs,^!]jV׎>+d>z/..>~y, ݤNW7?|fQLFrg/[K}b;: \x@ 0IRKRoy}/,z wN;{_{_o׿Un>z|?7? gh7Azkq18,ha1hM1к{hd, wg9@@ Q%!ZhsryZǬLu?gD6QAWh @@_2/&@@ߠ2 ,s{011R 0@`D(LqPsb) ZYZ.R@qL+4k<4Б<81lLq,4}V`1lӞ`UJq0"89Y⠠T⠖K>P8|,$+4$k<$4Б<"9%2mLrbQ"ħC 4$MkAaLC` H5!Kz0RFB]Blٛf&?P,-AzIUOzX$=hI 8ay:Oݹ]gr)nQpiL:ZumQؘcK|?Ba5Z1) BԬЧ*}36 +&'CuH r(I/PDJ(ESn-%rhH P43eUW_ !fպQJ dV[iZ1[dGF#';24dGFZHvPG!;;Xc Eve^[i:rYd5+d݇VVLA=[dGE';*4dGEZHvPG!;;\cvB@Mvp%ȩ'G(l r XOw0 Ʀ'eȠ=}*m;K#z%7=BwbEWrt'V[C P_2 oьĊx" t.rP,j;"rIV(l Jia *l}ܼĺ ]}8݉PӝX{8݉PGӡ;^CU{kZOwP^XpƠeV+P*¬JAUy5E;yY+݉5_qk0; #jq0qDQq\5"j ݉)! 1) YMY)atnBa+݄[1 \1 5!Da5M_iH $u*o`"B}c , !;;[Z/pEKaV* UJvPR2E_(lj&;:qX+ I iN܄[MxN܄:ҝA(Թ.(l@w0j;YY`;SPOV-m;<4dGZHvPG!;;\r}PHjBqV8 p(7 [M(p!weCm'y@5'@Mw0`ՍnBwb FQ4mN,* TAuCelx45*g)RW%Ba+V`JwP Tt5/fUKwbFQ0݉EPӝX8݉EPGӡ;;\㨒}eQJ[wPt) Zd_a*Y6s 7/ j@w0ت; #ϨW2xN,ÈZ2xDw̵tnQpk:4eo\Ca+\%$(dF7feKHP8'Sjb&/T.w t]5j<1CvvBS5d,A⪔fea'V8b T-ZNŐJX 8ىuPX u$;5Wk(l[Pj%;YY5db޸V-qɎd 8q젡';.4ԑt\_P rq & o\Ca+| n^q 7/+-b FQlMjk<5#7x4̼5*W64A+V+A+{ [ieqV0 |al O,ˆ["x O,ˆ:A@+{ [561k`JxP%3ⱟ\˲we'aU wbFYɬYd#CiS/$Yk[/dGG+W."C]Fovu.2P%ۊf`VblS$6+{ (5IHl0P0 |q8."C]FovuH.P zUmV+AIQԋl['cIO,X=EzͮES/#6X_U`n=[eI[IbqV8 oՒXQ &=,cP.#7Z ٻLM/gOOҝIT9( V f(l2䌚Yq+7) %nfP?3K̄1umT"9nfr,yERƒP]H y"9P_ XZޜYFP9ۗCe2K1pf9̔8* UJ%=rUI,PW,Â[҃IjfKzbY( TIJPxIO, 5EGb CAro Է=l) [53 ,L{P34873="Q= #1H{PG MNbi 5`S ,PGa*>CnAֳ0:K=) if&?Pt&`G|73=&>Jt?+k"+Da+MwPo  ZFt5/ Լ,Lw0ت;XD *Nwb(h!JɳTm^^aGP_WuZ0 KQ}X1M+IQTbl AzLdU UP.#7Ggl\A'=b;a+VAAzPR&=b;Ab+VABzX$=r|"B}]=efWP ͠ʹ U>[ь6$6WE>HJYTmFr}J/'h4bes#WC]DP TQ¤$8ߦHlION؊fKzbF/)XBC]DP dTilvj%=.KzpJmHlՒX &=\cP.#7JCz/ N$z`Щϫw5;yp??}'鵫9/L0/|ًooVOg~e|}syɐ؎37y K%K-Kƾu籟k쀽M/ᒔe>Y%64;B Q(!^GףxGQ|`[ccӈy1O7pe;e E̳y1ﭝZNr p8dm!`GlI(Y,gM}rxZ\< p1T Z1,"EļY.j2b^f,,sοcv56ݶl-drm 13ZѸu;A?`L*Lj/TDhht# OB M6_ Y@B 1/4~ uX n1/"Ėu?/#eļ̙3A i2 jxA\a|113 `z;"#s=&\v"b^{א Fˈy1P_fM.zYe_A!mq11s׬sN?E0 3J$iFI;b1@LOQclDe1"``@9$cޫu!3m 1(oΧb)r p!E}yQ(>ʀ>A7M2L  TVOQ$q8g}D:EDس%!]ѶPIS@$g}@ $Q.asԚWES@,gI@O$Nr`'9KBu5B 4 D9KBm)z ʰY {wE-4f)O^rmv*i["OnrxTׅbȓל'9^a֪tC=$40͐Y h8 x<prȓ'7z`\'7:On%֙bȓO'zƒp."O>u|9KB%ASɧΓO=cI++7/EȀ]eLGt8rh G!/zMɢU^t 2phgOux2r4& ([-8w;Ouoj\"3VYW!PWeɵ.k=cul6F88O <OWO$Y,ku5FBϓ]$/{갰Ԛ<_$؋{ zBMc]$z=2;8Mx0nb"~RxZ& 2p! ) >ʀk2qB 1) 0͐Z HQ dͶLne嬎T "쓜O+k*3<0O23dֶ*RA,guV5 Zp' y΂p XT<"ysԑ `/e2n8TAuWA =+ UQP< U0N.*SLaWkC) ARB)@R3@iL2]JGl xZJe 6Ԝ5P8 U.HYPe/Ԝ5P8 U.HXT&ǹLs|uqvm 5Ksxv*]&/:^Q29eraW*\&9^<͠5Q[Qf ,,8q J+W]5T&_LS23VGS2sD'ִLns b5G cMe (&U96SP ӵY2taFS L7^IiaZMCSLwf!sD8 >IO,إ-)V@Sk~rZ~؋{`/ꇽL rgk5[KUWxZ #7& (pd8ʒT%J.г_]I$q$U[?CTr$ςXa'I$I *yU\%yEv y}^?EH^&{34Ə x틠bp\UZHqCu䬯[ϓZur-Kǖn歂E?GNU*J*d5UyGw6MUô-!m[h2q4܀ZdϖhLګ5- U _)Zi0k!))M ^hF ڲ-A[?Ђ;ANU.m!= q5T`ڽ-AMUh:ln{suswpwuoޮnoW__p{p9piu_/cx\mЌ?^F5^5WϵvgDjOx@5y W1Z1UCC 8]qη{ЍlntHкw;,Y=1ic -L,DZ6rwSJ֧"sQJe+45fq4 s`ZqNz_c5nL#lGF.μ5m'ZYδkQcV/* (z5n,\:Ij|][30&B"7g~ۖXIFzM-J0iЍhQR%+N^➢ڮ[hJʉTO6Wx{js[qBJ8ݶSTUۊۈ ?>gە&>PTUۊ@~Ic`|K/'* .rJ1eahNIjϫL|lLi`'L*ORa| I -{UIPe?6!6|(eR#AڶPlm4'cKL2AϠi|p}Ps~?P Gyx}p&BkF  v9^X=༁KBl7:ꊳ^'hTcf}1vYn/ 0ٷ2z!0'q+Yc';7\X999;aNI:Ap~zmo3 tX5f'F+=sKrb^B# Bp\i`Bk_l;qE1Jlgj& OWZ(|ffްdgʅy9*zH,=e]ݹ8hOO4cJC_{h;fxZ[ӷTUۊcxcq\9@}K{"S;͝a6 jϋL4rH;!ԾYdERsmm{"3УLOױPY6`l"n 'j ;f5J0hXm&ٸõ;Zv.-7I8芀wwL^-u1?ߓxb[+zEg^= FjOVL%P_q+ s:+)=&oaR^&cnᬄ2uqc5Mb_.sM땦o2 ϰM륇C[w[MF/G%'6Hias"=[~s<6Bq8~yANє:^tH0GkYi| Ǐyi PK2#XhNL#Xm!4BLrBtoz|(BclH=hae$ylTcBk'ÃΈ-p44^M4P z?K𡅶r ! IN6sKA6H-eEIxqwBU#<"?+TIX VƼzӹ їQx$r{yhX` zOV{_iNB,93վWZHF~QI -d<&$Xm!'4'ThtʸnOwCVM. hB(foNfhϵ6FǡfhLi'TZf0±Nt2/KTshۖ0f"?Jj׏JѧG4c[ye1qϚ:nrBьm!7wjhX {>mőh29˞z}3&VH+LӘN)N>#Xf4Sy:&Cxj;DlPbIHm2BTG316p9huo,̶r/͈Qz畞qJOXp艜ܵ: k%%9⏗Zsmgn~8R) $&iL ЌKj[H&ȹ4gphxaG QrfqHLHc`doE>Mȧ]3M8~w#)ȴq8gAX5!էYB>YM7rk!&Þ$'9CZ4TZ.]suswpwu<|ۃo1ws|߾^:枎usmg1:Kd ΍^T?"o =I]xRP8+ao(18p e!޶5(1v7~*c}K/1N7k 3½ g:8&~%!90דP*:^`Zg8)n:n:FR`(αs]1HpT\纱 Q ;׋,%rTIHsAwl߄iH`vJ+QS8zFudBKB**iE3a^B2 ^f'Im| xFhJKD BwĄ2 7ՅGq7ٙ ė xja Xv Ctaq ҝ@>rs<Ñ5u&zMns ',Lg:/wB7a@ah)Ɛ3ݳ0p׹rxWEj}݄Zҍ0PawJ a\Wgg|,Jm'0ZJ()V~QjZzȸꗲbD )!r.rMS°2CNv1':ږ28d3ǝ1mK֊OPKJ 9ۆvB\gX+r!9*v,8g.w)YƕDt1]D )6 P&DJZ`m+b GDӉ-%/sWqRJ(hstcx4^h*FǃO4u 4S^2aziuL[%4Kf;ѣ[$ǭW ۤέW^*n`&n d+J[QRlз~0l~0l ~bW]dA3ra&L L}8ܞd.2~[Ov 3)1@Ώ:m~c0l~0l~f8 S owX]&:Lӄii\u^&L/0meqt\M?Y{׫O\]뛫ջח>\n>\~Z׋74yi^[~LӼo{'_s;ZJԏTsǠ5coJ#̻vb\#1n|K~N#y=DFX,c|$<\똖ڿ}K\G#4pn$=gV|ۃoͶ.vnk&(!ycvВ,GV, ^(i5Fjq^>VIcRX*B꾷['Z 0gfPLvn{n{43n8ݽONhN3+84fJ{b.܌mp[=\9%.!aNBxv 3JX$1Y]d nc { iDLlsFTDKtwZK}f>hڃ@\N7)=5ZYcW=$1gao g]yFVf̪ .@q|xå%T/n-7Rm!s]{XǍn1 a0v4h  O+1xvhr {1z3Z]OL7R䌮{öHډKm203sFև@3*#=gTacf=4 9]kM8uN{FYfBYHlD}@NI ܴ-ۮ}KǾ;F! 紞;I6=:SN֘sht0ɲ 6zVeoBܒ8x=poh9 [ENX'e׏uR:)bf ,u; !>X5Q ku#[Z2fq7cܚ6&l<ֆkAwkh_a,e{n,}Ov\J_&(qO屴7;*˄er;|zs]%F~g ,xtѝ 6s%eY"q&"f  Zctv~B.;dCNx糄||8Gp.21Fct1Fk4uګC\ s)ixGahpES`Ԥa.CC/q(P68ua$Tf1rlҶ| 7\[ۣ*}Ls[We݆_QXtExh2s.NykQ~]Ρ(AS*g5G 7}@ TC29hIq?G8{=aArO!*RHDW%?ODI%0*?e`x | xmm[qgpj4 Ŕ~Z *!TDAcڎ@#U=.Zv e,g79_SĊYHy͔S}Up  :}pp[f8c Q|g;:g1)0/ABfwYKgQ]˯w&0S@Z֙a\aɄ,e@_WZ2P~RSpYB>e{lR9 n_m(U4v2 RͣNnx iП*):ZT3IP Ĩ)$[8ܰ4k9At61+b(NQ4WJDhuk'pګƝ)gbq=Ydon_=̗T(^&z;A~=e?K#ݒA@_-(cF6RK'WQ"٪,jM=}`>2r'pY C70GRZ=}ZJyah^ϒժ kRzxɟPKPKO9qͷmeta.xml OpenOffice.org/2.4$Unix OpenOffice.org_project/680m17$Build-93102008-10-14T18:40:042008-10-15T21:32:345P1DT0H46M19SPKO9Thumbnails/thumbnail.pngy<%iDEV,){ G) 6 $CDr-a,#EflYB3}a|9w?kzfn)mdcaaea?|;+?r`Ѿ1ji':]yaqBvϱ8©gBj^]G%I԰ WơGL/ep@g|J<+E+EdfiUtd{FA@}y/̵/,*mCn`{YצaaKʲ$$lD | Ӽ{B\1ė34A~Ve3)ݟIy/w <;-zc>ţ20bUښWHW582x2o@P6#T'kUN12~ڝSA gtV]H!W#maKJ/=inMYߢ# >Rq.iiS\)6v B̗Nu{isZzPJe ȭ&-Sol|e4ɢK>CowA ϔۈhQn YGG.vKOyt*4>Si3?C嗒ɬXF-Sd?x!2Nǚ)52]˩uNR9bBY;a(mq]{\,_@amףvCçtwɔ$7[1۶UO-4֒3U>Mp l"I[ks^S]/l?ҵ(.S κ`ϤFzJߦt2oˑ) .ڡ8 Y'<^6P=V6i! }τ4(rx-VENS߰92[b?#`;"`߄>9l4y=?3iױrkp[)bGK$E|D3)Q|}:Xe2tyI聞<ǺL` `S$iYӏuJ)swelerN|C4\!?ug}X٫Gy7x_J"UFI9K3.+lL 7V|rj! _U@="+P\@ Y杍 O]\nwQ-uknU3U.6/Dձ!siTE_E˸J=ǣyG c<FZJ9joTBmg$TW طVeE:܋zĕ*i ~;ݝeؙТNس}bɜpX,!b|EIj̝ͥQs{ڋ?n;|xhBkiBzl$E k A]>X61k.r[&~MWU626꼿> t֕xBڛknHZĩ߷Q7 J8yv2Z0և8̥ܸ;C2Ane6n$^4d=ty7Tjˏ7Eo9r Z_nx{+R[e1`$yH; rVTGf6`E+}j#>KL4ғW~*';6]ehiBc z Nli.~xw3aq`^ɑ}bMFs> ?LC19Z3]KgK9KiotcgTY^Θ\K!B6C؆]RDjIqZdE R\(§fIQZqpd;{BaN̺O)No5fx hQ^@N}0U#0r߂#/9=8:nSl[/)VR9]Dݒܞq檛D!^;9Mٓ&f(Yk @{Fe t]yKyՠ='5|qBݦ!?o.i\࿦%WSQ}bݫBd犠q3ɤ,<*3WWŢ0,b\ [2%%CDfi=e݉ܧ0ጭN3ښMgiXq(Ҍk*d !4oC힫VJu6 -v"hL78 EWUL^=F;)8_IUس*Ti[FH0jSDXBcڑW)%n]ۓ<+-\Α/F¶CPO'9V*vNÉ"Ӓ Ş|/0".v]Zbl"X$g=~GFBsѶFrbkdW4ͫdFga~_Bِ-6 "ێGrrݖ2(uJ d/}kj*ߌjc?ϋֽËލiR4; 笩Wk9=k\ qfnH9r] k jVEIo9@H^Y R. J?FCkO:+7+4'̜e,YD Lp- gks+ˮݸ爆w#mF ۪[\a{nD9nϭj Mx!0|vTlb333-Z.Q3+s:*hg[Vz}dFN:ĵ9OݳmpN܇gl$_sԯo]TH=FDm*/^Ol+難/x̔xї, '@\ot!zX}DZb:x}|PRU-N櫃@Uaa1UWPq?Tu!$W·n2Wt%sM lX zѼDU2t]40s{m:ʄP|qiVu\VDcVWsͅtjrlRYѳ7DoRc)l˄pG8طWܚtV뻱wO<>?Pmb7E6cr(<&ʾG$N-ks$ʠwZQ:D&T=l9ѐݍ\r5 ӝMߤQuY5’j{q`~I7C-3t0jo*]G%N{ZqcWG_]j| 1J3bCsV$9P#g.KƏ{6c(_a?ss`Y;xG;)`ZQ)*fϤ B3 8K]z׵;q'רq|cy٨#uakt6vR/Gpe6"#.$_u?MĉD\,mFxNy1b<uT2,)z<!V^NaJ3EyOUr4BO}X6fXo=GYrݨS xoTFضr=Y1%z@U lXaaja6K3.kW13ԕʑnɑauodb6?t" n7s|s)4 _>H΋8>xl'{M-Q:rËk";WJX0{LZGjP#i' 騄hhFyn&XA,`[/H?skL_CuTʄTe ^N-ςS#cV)H6قE  = ‹A!EiyET/ @FhqMy T*$yg/ɸeh`pE|,fI o)ܚ8Ӓ( ÙqL# ırlkN;lC X"Шox.CH1_ shzΟy[!YBW*=ӒQlyxT*' |u˕̴w71- 4&1I^zryDٓ2ڬ$Hwxr e9jB_'oŠ+tom/4co2=87ti77ͫDu5ͫm s3(EMᏥ»G;% -zz2S=We(DX_d-jW/'o"\'3S?9KFWQK}=qcMoVoLyKR(:[hٖsгY ;zH?&*b<j)n7;3:$L㍀rOU6h@@wj>T (#V 0PY]Ql %=`p=9E_ |9u,c`#^ݳhnM]^`{ۑ J::ֵL:ٹ,Of2 *HΦu|Rf,Q&7 CUA?) q5g18`U(zS`r`Yk;"p|u #<7]* x%&/E [.1F"8*Dh4rwCxVoi~r5|8PQȭᥕ?P3{i4kwu]>_brTlXSd˰5l'.Bu[o[]o.4t`TUXidžV|"-F5{QCY=sf&y.umUM:H妼uPЏNpD4tC(!Uk\=ԃ=.māӲ`eD9$aQm?Xb@,q}x{D/HF02v!f?%+#WXY/{̜ygw) 2_ƴ-I7 f@kSF:`Z^C$h˶Ծ΀J1B~L4D4Q'X_2exwkǰM.DV͙cT*FiL0EAV!NPC6gwWIWEg+p8ݯڮcݻ%$R=&8BYJ !zK_JGq*ݥm0MyĎEX»*`<~{%3HPĒqJW0zV>SCx5Oɷ!&o3Mw,JJO0)>xQ_L!X) ak$ɗՐeJ_؂*mj"r]f ̝w81mG]WP=wL'eM^yx7䕂4pBv-&cF(^(et]][}Ia>iGߚ%Q9&5O jSWs[fQ83T']RG]H8~b3@ǥ#4Iơ3c'{h91Xo׫%[ $E>0Ak7WXPl]dKNF DRis⁴c<fmƒ'򛁱RʷM3 Ã}3nM:tYT)D3 HYΑ|7Z['k 8?<H>TŦ}60I{M!7k*HojFʕ1윚!/jjzsU^s\@#~rn01:Ħ^?:a}4 X!A(4ExUz#G5 ˂X੿5+Wʎz]a^Ьrn  J%N[^2zD9~]YƼ`hdlNDa>vͰ>r\ Eٶ L[3?A`|y:A|QPd ԵN-6s"'qA4c$-4 /@1a#SNNT:VV۫,L@.}w+ q? .]SY~M|竦NR9pg%6[TA H4/"׍n|w$KҾ$%9K@0'`Z*_S~/K,k4ީ̉3ʉؔA|m$Dv"81zEg IJeZΒd O_lBaN7o& Gbe w5Pp] M8nG0@1PKO΀ȹ!PKO9META-INF/manifest.xmlj0 }{m6t:Jj\ڷ_R֟m=! -y>XS1Փ()j7sQjt9(}.Fjv`jVZEz4iU!:޵U** "+KƔx)CNˡ41aN. ͎0z%u I>UpYxuOxERhpX(Uq%>"I!ySHv0z$Bߎ,lb4Q؜Uҕuy32e4/oqPKɇ6CQPKO9l9..mimetypePKO9~ȴ`#1 Tcontent.xmlPKO9 Jastyles.xmlPKO9qͷ gmeta.xmlPKO9n[jThumbnails/thumbnail.pngPKO9'cConfigurations2/accelerator/current.xmlPKO9Configurations2/progressbar/PKO9Configurations2/floater/PKO9*Configurations2/popupmenu/PKO9bConfigurations2/menubar/PKO9Configurations2/toolbar/PKO9·Configurations2/images/Bitmaps/PKO9 Configurations2/statusbar/PKO9O΀ȹ! Csettings.xmlPKO9ɇ6CQ6META-INF/manifest.xmlPKopenMSX-RELEASE_0_12_0/doc/internal/v9990_command_timing_test_results_raw.txt000066400000000000000000000211621257557151200271210ustar00rootroot00000000000000V9990 command timing tests: LMMV: line (0, 1024)-(256, 1024 + ),, bf LMMM: copy (0, 0)-(256, ) to (0, 256) BMXL: copy #00000 to (0, 512)-(256, 512 + ) BMLX: copy (0, 0)-(256, ) to #40000 BMLL: memcpy src: 0x00000 dst: 0x40000 num: 256 * Result is the smallest number so that the command does not finish in one frame. For LMMV, LMMM, BMXL, BMLX this number is 256* pixels. For BMLL this number is 256* bytes. Raw results on a real Panasonic FS-A1GT in R800 mode (shouldn't matter). * NTSC / screen enabled / cursor enabled LMMM BMLL BMXL BMLX LMMV B0 2bpp: 012e 005b 0132 012e 0171 B0 4bpp: 00b4 005a 00b5 00b4 0108 B0 8bpp: 005a 005a 005b 005a 00c3 B0 16bpp: 002d 005a 002d 002d 0061 B1 2bpp: 01dc 0091 01e1 01dc 0235 B1 4bpp: 0122 0091 0124 0122 019e B1 8bpp: 0091 0091 0092 0091 0133 B1 16bpp: 004a 0093 004a 004a 009b B2 2bpp: 0134 005a 0135 0134 0171 B2 4bpp: 00b4 005a 00b5 00b4 0108 B2 8bpp: 005a 005a 005b 005a 00c3 B2 16bpp: 002d 005a 002d 002d 0061 B3 2bpp: 01e2 0091 01e5 01e2 0239 B3 4bpp: 0122 0091 0122 0122 019d B3 8bpp: 0092 0091 0092 0092 0134 B3 16bpp: 004a 0093 004a 004a 009b B4 2bpp: 0131 005a 012f 0131 016f B4 4bpp: 00b4 005a 00b5 00b4 0108 B4 8bpp: 005d 005d 005e 005d 00c6 B4 16bpp: 002d 005a 002d 002d 0061 B7 2bpp: 01df 0091 01e2 01df 0235 B7 4bpp: 0124 0091 0124 0124 01a0 B7 8bpp: 0096 0096 0097 0095 0139 B7 16bpp: 004a 0093 004a 004a 009b * NTSC / screen enabled / cursor disabled LMMM BMLL BMXL BMLX LMMV B0 2bpp: 0151 0064 0158 0151 0190 B0 4bpp: 00cc 0064 00cc 00cb 0124 B0 8bpp: 0064 0064 0065 0064 00d7 B0 16bpp: 0032 0064 0032 0032 006c B1 2bpp: 020b 009c 020c 020b 025b B1 4bpp: 0139 009c 013a 0139 01b7 B1 8bpp: 009c 009c 009d 009c 0148 B1 16bpp: 004f 009f 0050 004f 00a6 B2 2bpp: 0151 0064 0155 0151 0190 B2 4bpp: 00cc 0064 00cc 00cc 011f B2 8bpp: 0064 0064 0065 0064 00d7 B2 16bpp: 0032 0064 0032 0032 006c B3 2bpp: 01ff 009c 020c 01ff 025b B3 4bpp: 0139 009c 013a 0139 01b7 B3 8bpp: 009e 009d 009e 009d 014a B3 16bpp: 004f 009f 0050 004f 00a6 B4 2bpp: 014e 0064 0155 014e 0190 B4 4bpp: 00cc 0064 00cc 00cc 011f B4 8bpp: 0067 0067 0068 0067 00db B4 16bpp: 0032 0064 0032 0032 006c B7 2bpp: 01ff 009c 020c 0200 025c B7 4bpp: 013c 009d 013c 013c 01bb B7 8bpp: 00a1 00a1 00a2 00a1 014f B7 16bpp: 004f 009f 0050 004f 00a6 * NTSC / screen disabled / cursor enabled LMMM BMLL BMXL BMLX LMMV B0 2bpp: 0156 0067 015d 0156 0193 B0 4bpp: 00d0 0067 00d1 00d0 0126 B0 8bpp: 0067 0067 0068 0067 00db B0 16bpp: 0034 0067 0034 0034 006e B1 2bpp: 020b 00a1 020c 020b 025d B1 4bpp: 0143 00a1 0144 0143 01c2 B1 8bpp: 00a1 00a1 00a2 00a1 014f B1 16bpp: 0051 00a1 0051 0051 00a8 B2 2bpp: 0156 0067 015c 0156 0193 B2 4bpp: 00d0 0067 00d1 00d0 0126 B2 8bpp: 0067 0067 0068 0067 00db B2 16bpp: 0034 0067 0034 0034 006e B3 2bpp: 020b 00a1 020c 020c 025d B3 4bpp: 0143 00a1 0144 0143 01c2 B3 8bpp: 00a1 00a1 00a2 00a1 014f B3 16bpp: 0051 00a1 0051 0051 00a8 B4 2bpp: 0156 0067 015d 0156 0192 B4 4bpp: 00d0 0067 00d1 00d0 0126 B4 8bpp: 0067 0067 0068 0067 00db B4 16bpp: 0034 0067 0034 0034 006e B7 2bpp: 020b 00a1 020c 020b 025d B7 4bpp: 0143 00a1 0144 0143 01c2 B7 8bpp: 00a1 00a1 00a2 00a1 014f B7 16bpp: 0051 00a1 0051 0051 00a8 * NTSC / screen disabled / cursor disabled LMMM BMLL BMXL BMLX LMMV B0 2bpp: 0156 0067 015d 0156 0193 B0 4bpp: 00d0 0067 00d1 00d0 0126 B0 8bpp: 0067 0067 0068 0067 00db B0 16bpp: 0034 0067 0034 0034 006e B1 2bpp: 020b 00a1 020c 020b 025d B1 4bpp: 0143 00a1 0144 0143 01c2 B1 8bpp: 00a1 00a1 00a2 00a1 014f B1 16bpp: 0051 00a1 0051 0051 00a8 B2 2bpp: 0156 0067 015d 0156 0193 B2 4bpp: 00d0 0067 00d1 00d0 0126 B2 8bpp: 0067 0067 0068 0067 00db B2 16bpp: 0034 0067 0034 0034 006e B3 2bpp: 020b 00a1 020c 020b 025d B3 4bpp: 0143 00a1 0144 0143 01c2 B3 8bpp: 00a1 00a1 00a2 00a1 014f B3 16bpp: 0051 00a1 0051 0051 00a8 B4 2bpp: 0156 0067 015d 0156 0193 B4 4bpp: 00d0 0067 00d1 00d0 0126 B4 8bpp: 0067 0067 0068 0067 00db B4 16bpp: 0034 0067 0034 0034 006e B7 2bpp: 020b 00a1 020c 020b 025d B7 4bpp: 0143 00a1 0144 0143 01c2 B7 8bpp: 00a1 00a1 00a2 00a1 014f B7 16bpp: 0051 00a1 0051 0051 00a8 * PAL / screen enabled / cursor enabled LMMM BMLL BMXL BMLX LMMV B0 2bpp: 0169 006c 016c 0169 01b9 B0 4bpp: 00d7 006b 00d8 00d7 013b B0 8bpp: 006c 006b 006c 006c 00e8 B0 16bpp: 0036 006b 0036 0036 0074 B1 2bpp: 0242 00b1 0247 0242 02ab B1 4bpp: 0161 00b1 0164 0161 01f6 B1 8bpp: 00b0 00b0 00b1 00b0 0174 B1 16bpp: 0059 00b3 005a 0059 00bc B2 2bpp: 0170 006b 0170 0170 01b9 B2 4bpp: 00d7 006b 00d8 00d7 013b B2 8bpp: 006c 006b 006c 006c 00e8 B2 16bpp: 0036 006b 0036 0036 0074 B3 2bpp: 0248 00b1 024b 0249 02af B3 4bpp: 0161 00b0 0162 0161 01f5 B3 8bpp: 00b1 00b1 00b2 00b1 0175 B3 16bpp: 0059 00b3 005a 0059 00bc B4 2bpp: 016c 006b 0169 016c 01b5 B4 4bpp: 00d7 006b 00d8 00d7 013b B4 8bpp: 006f 006f 0070 006f 00ed B4 16bpp: 0036 006b 0036 0036 0074 B7 2bpp: 0244 00b0 0247 0245 02ab B7 4bpp: 0163 00b1 0164 0163 01f8 B7 8bpp: 00b5 00b5 00b6 00b5 017a B7 16bpp: 0059 00b3 005a 0059 00bc * PAL / screen enabled / cursor disabled LMMM BMLL BMXL BMLX LMMV B0 2bpp: 0192 0078 019b 0192 01de B0 4bpp: 00f3 0078 00f3 00f3 015d B0 8bpp: 0078 0078 0079 0078 0102 B0 16bpp: 003c 0078 003c 003c 0081 B1 2bpp: 0271 00bc 0272 0271 02d0 B1 4bpp: 0178 00bc 0179 0178 020e B1 8bpp: 00bc 00bc 00bd 00bc 018a B1 16bpp: 005f 00be 005f 005f 00c7 B2 2bpp: 0192 0078 0197 0192 01de B2 4bpp: 00f3 0078 00f3 00f3 0156 B2 8bpp: 0078 0078 0079 0078 0102 B2 16bpp: 003c 0078 003c 003c 0081 B3 2bpp: 0265 00bc 0271 0265 02d1 B3 4bpp: 0178 00bc 0179 0178 020e B3 8bpp: 00bd 00bd 00be 00bd 018b B3 16bpp: 005f 00be 005f 005f 00c7 B4 2bpp: 018f 0078 0197 018f 01de B4 4bpp: 00f3 0078 00f3 00f3 0156 B4 8bpp: 007b 007b 007d 007b 0105 B4 16bpp: 003c 0078 003c 003c 0081 B7 2bpp: 0266 00bc 0272 0265 02d2 B7 4bpp: 017a 00bd 017b 017a 0213 B7 8bpp: 00c1 00c1 00c2 00c1 0190 B7 16bpp: 005f 00be 005f 005f 00c7 * PAL / screen disabled / cursor enabled LMMM BMLL BMXL BMLX LMMV B0 2bpp: 0199 007b 01a1 0199 01e1 B0 4bpp: 00f8 007b 00fa 00f8 0160 B0 8bpp: 007b 007b 007d 007b 0106 B0 16bpp: 003e 007b 003e 003e 0083 B1 2bpp: 0271 00c1 0272 0271 02d3 B1 4bpp: 0182 00c1 0183 0182 0219 B1 8bpp: 00c1 00c1 00c2 00c1 0190 B1 16bpp: 0060 00c1 0061 0060 00c8 B2 2bpp: 0199 007b 01a0 0199 01e1 B2 4bpp: 00f8 007b 00fa 00f8 0160 B2 8bpp: 007b 007b 007d 007b 0105 B2 16bpp: 003e 007b 003e 003e 0083 B3 2bpp: 0271 00c1 0272 0271 02d3 B3 4bpp: 0182 00c1 0183 0182 0219 B3 8bpp: 00c1 00c1 00c2 00c1 0190 B3 16bpp: 0060 00c1 0061 0060 00c8 B4 2bpp: 0199 007b 01a0 0199 01e1 B4 4bpp: 00f8 007b 00fa 00f8 015f B4 8bpp: 007b 007b 007d 007b 0106 B4 16bpp: 003e 007b 003e 003e 0083 B7 2bpp: 0271 00c1 0272 0271 02d3 B7 4bpp: 0182 00c1 0183 0182 0219 B7 8bpp: 00c1 00c1 00c2 00c1 0190 B7 16bpp: 0060 00c1 0061 0060 00c9 * PAL / screen disabled / cursor disabled LMMM BMLL BMXL BMLX LMMV B0 2bpp: 0199 007b 01a0 0199 01e1 B0 4bpp: 00f8 007b 00fa 00f8 0160 B0 8bpp: 007b 007b 007d 007b 0106 B0 16bpp: 003e 007b 003e 003e 0083 B1 2bpp: 0271 00c1 0272 0271 02d3 B1 4bpp: 0182 00c1 0183 0182 0219 B1 8bpp: 00c1 00c1 00c2 00c1 0190 B1 16bpp: 0060 00c1 0061 0060 00c8 B2 2bpp: 0199 007b 01a1 0199 01e1 B2 4bpp: 00f8 007b 00fa 00f8 0160 B2 8bpp: 007b 007b 007d 007b 0106 B2 16bpp: 003e 007b 003e 003e 0083 B3 2bpp: 0272 00c1 0272 0271 02d3 B3 4bpp: 0182 00c1 0183 0182 0219 B3 8bpp: 00c1 00c1 00c2 00c1 0190 B3 16bpp: 0060 00c1 0061 0060 00c8 B4 2bpp: 0199 007b 01a1 0199 01e1 B4 4bpp: 00f8 007b 00fa 00f8 0160 B4 8bpp: 007b 007b 007d 007b 0106 B4 16bpp: 003e 007b 003e 003e 0083 B7 2bpp: 0271 00c1 0272 0272 02d3 B7 4bpp: 0182 00c1 0183 0182 0219 B7 8bpp: 00c1 00c1 00c2 00c1 0190 B7 16bpp: 0060 00c1 0061 0060 00c8 * P1 LMMM BMLL BMXL BMLX LMMV NTSC/scrn-on /spr-on : 0031 0031 003f 003a 0067 NTSC/scrn-on /spr-off: 0049 0049 005c 0053 009a NTSC/scrn-off/spr-on : 009b 009b 00bf 00a3 0148 NTSC/scrn-off/spr-off: 009b 009b 00bf 00a3 0148 PAL /scrn-on /spr-on : 0050 004f 0064 005a 00a7 PAL /scrn-on /spr-off: 0068 0067 0081 0073 00da PAL /scrn-off/spr-on : 00ba 00ba 00e4 00c3 0189 PAL /scrn-off/spr-off: 00ba 00ba 00e4 00c3 0189 * P2 LMMM BMLL BMXL BMLX LMMV NTSC/scrn-on /spr-on : 0063 0031 0063 0063 00a5 NTSC/scrn-on /spr-off: 0094 0049 0095 0094 00ea NTSC/scrn-off/spr-on : 0137 009b 0137 0137 01ba NTSC/scrn-off/spr-off: 0137 009b 0137 0137 01ba PAL /scrn-on /spr-on : 00a0 004f 00a0 00a0 00fb PAL /scrn-on /spr-off: 00d1 0067 00d2 00d1 0140 PAL /scrn-off/spr-on : 0173 00ba 0174 0173 0210 PAL /scrn-off/spr-off: 0173 00ba 0174 0173 0210 openMSX-RELEASE_0_12_0/doc/internal/vdp-vram-timing/000077500000000000000000000000001257557151200220415ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/doc/internal/vdp-vram-timing/dram-read-burst-2banks.png000066400000000000000000000555411257557151200267300ustar00rootroot00000000000000PNG  IHDR!R pHYs  tIME ,'bKGD̿ZIDATx]wEֽ * bU AQd( *ָkb"0+"F̈d$$7of|LwuO;3=o귋>s[%@J@l24\:vGܧ ߾R #༞VUCUCUCUCUCUCUCA|U~Cq"q5Ry|UBHGV$3gYUZU\+:G7{~Wt tROg}Rg{uHO~:Jֹ\#{ 9+M˽Y&Z{N4r>ϲl(۪>ĶVAU}mUc[է"öͶc]Js$@_I*LvAj^EUݴp_ǢG#p2ZdUesj̪ͬǖYjE|՚W1S!_WR2}UW z}՜B}U%JEWUo?_[9[l?_XPo_  =^*W*~]:_ef`v'|UJd!!$YF>"'m`%6:<&jd~m-V[UϺU{>6qԭ:me??IBJjH~¯/|q5AhZ㬪UH~%, W-Xq4Np&#S $@WUJsjM4i?㲪-;t)G)$n׼ %j_6Tڙ lv`C#]PɆ걁gKNbCP#": wBJL'P_^v'vBzpLҕzPp˃ ŗ5#AKqu~ 9=R1(lV|s=reܖl*bU 6@@0`s  \Vs4p@=ֺ=ha&,qBJWX pHh\~s y@?j *@ԃS ޿ :̿EflZyW|WMKUaQj' }x3o[BzA2BէUɂ ,;JU€64nfoj {Cu'7U5I'W: !%:OV+UVդƪ{j0 `xQ*X2@˲@aUM $hr߫ ^k1ڪ/ZشUţVgڃ]]'ﱩﰩopo7w|{k՟QU׮ܝlvdC6T۰zz$P^rx6TO j0T3jjxR􆴶*UV* sMqh'?GVEWWU `URJ<ZPT{=n f6P¡YP/ oUj,倿# KY8 $[%t*AW2Su2;0K+G=Ć}l ;P P ;P <{uGP}:UGp*髊lWL_U8F *LUEdW5W||Ui^Y%aUvJ3\f"ZUϾTZ|U :auVg?wtے۴(UۖF PQVj*V-IlVհ~*]bf2d/JUl*VUV%۪Jª,GEUp@S6T': P} P} P l~Ɔrj?6Tڞ P= lƆlvbC'DZzj9@U3UaVijjjj%HVi]ad*V˞ZՅR)qvPM/8Sy,}{tfћݣ?`v!=W=,K_S |n%U+ _Up=~H)]+{>*W}*=z' P= ٙSv[t̳W!]+mOFl8(V7`P.V;S_ۣwK$,c;Nf,'ǏұM}2_b AxlpWX.:1`qeªF[%*UVUV M$-ZU!=dx%Rh0'HG>—8O[ڪjj1Vim2 PJUF-Q%*5dUQ~dU?g/~ɪ 6lՏ #.VǸժhd"]Pm yfЍڑ #:T P.1r;v!ghU WBr*˷ j5*o 竦>"UӫUQA -X)UPVKMDPrU- @بVUH( 'UU+k K={8N*oEV ģR*_[ JiUAR$mXԪI@*C52@3*%TQA X: ^S}z,B*PʆjgcFB3GE;GnlaCx6TOaCr)4^j;psh Te=eK ~3ibgncgfgW55Vh(VkXUQBmTHmUūKJ$ەxVZ-!Z:(!z3-Lkju{X2*qűQ=_BnۚJfܨdc %[ŲRF أ?WkD@bVUiSեbU_) r(TqPPbTV%fZPͼX)`s1#Y(T> UU.rCU*WZ%P!Uؓ,ۃU`V 6TEj^hFtϐ)j 3 1覂Wݴj7ժWشr {KM++VdV}ZE+]VNaV}^Z{jZ8_]( zjjѷwp } tXm,^U*+m_35l]fMx q=Bx z<]& vAUPM%D 5+}˖8oDx/ܸD+⬰O^?ժ (r_)VX9W bOh_+vUc^C{PXA%*H\nY nΦ^qkmH:s2AUe+J1;_UE|6l=~0^۪zT.>vI1V/FA_k>߳zxb- bV?!$c)ZIKj-Hk_wV{R_z|Qx.,4zRTJ ڍ գyk@^[wgCh6TڪSVau= U,P5*Vt`; q="7#Ek,k {G̊l`/luJO~u/̪̪-;n*`|Uh{ѥVa6(;m-VPMF P5@(-T~'eT8  μD LPeɪɾJXdx25ĢdU+ UW`3TimLP5t J ՓP= >PvF;]C&x_\v*Nx^z߈l ­Ȇ6T.%+پl_<`O}|ճؾ_U6_uW1W=^n^Ua Ye@03Y0TPvxg03ɩ`d >*5ԭbdDvS @C f@H KƬu~|e+JƬe+"`UөEje+Rhme++0_>!l̟,4۪ gĪJq /HYrbU&fKWAJH ԫiNwHDm6WUp|UU牤X~R"16XZԝTa(DQzJīo XE4_^U K bί~B;<p ; tߊ*T/};:X.XG񊫱/3AUGbq$v}Mn"?J1SIª]!j$a)/<:J_2er^_\W}(GSesRhmSL̠oާC >g4# DQ*wo-` ` 0Zf3lfU:hw]g"Yf3RA*#ZͬJl_̪4YbVaV^-fV̪tbYzR!X{B]!%p_EժlවM+3jy8*JnM+3+V.`>-@S9Bj"{j:z-p҈{(T{ڞ :ڜ զPXg9 TzH[ȁjK6TEs@3}PZkH^Ѷ HݻXP]3U|U4bZ7_5TӷFD8-jK k1 %I`UT(b@gP؋D?qִ{ڏݱ׭v/TmKR긗DP]b n s 7'q@F;,e+jPQ@X4 U@0bnD5[޼*y]ů[D*;r6-X)Ta|9VnՊSS^KnUz:%sR,MUu*.4Y|Ve[~v`m4>:j/;7C[wUcUyl:2Zbv-UζCTPUfUApZ3V,_Ôg V UVeCIcVU! +VYm3'&iLG* 0XM쌘H•E&$S_)\7X/%T?Q"~U4UCՠo1A&jaaa*`g> )IsoX ?Fhsx ȽHX3*s(*AIVnVGvjNVU&p%A[em^+kU(ߪ YB ,SwUԾjU(Q|_5c*M}8|mUMLEp\M* *W硭J!r5dSl`ʋ?im<NܭjL.]iho,5 TT!Hw:*zXV9Z,uJVy( [ HUUnUc\]"9 fC86TfC]Pʆj76T{ڏ P= ճz&P= l̆`6Tz:簡6T*xWnUiU׭"mQUe¬*InX*:e?I4T5TTk +%S\a|U@pJ@b@L$^j1E,Sŀj 0( 0kyU-־UW]UW}Օl_u%U]ɦd+پJbUV}յ@W^Xvg;fl{a58ta/e;'i&j&jVŤnUU1X*mUU* Uh@;VU[ղQy]?Y+y'st #dr>ɪ;lFwx vG Zc3F `+o2Is1z\vc@lcC6T/eB-hUyU N5{;DU0FwL5Vym J"` XFs2=XtH > ARIf~Pt@g܎x; fVAWE`V32`􂺚Y5W A*#2r2=B*v?*=* eV%?R*ER2̢*;c dV}E0_vY'GUi ãBjz>!|&ߧ U}8zV5U%@1Z)5PIQUUw&~#hUA@$¬k'_U[kdUSbUPziUs)D A 򵪛7T jY Vl?rq@UօBaPjVX 1{a o(0+C\G^UBHCo-X*dׄBUƫ k*T/Sa*م 6TFVl3'IXm!*/>Q+kUbX񡚘] +PP@+WE^PT"XWjZUZm- | 4yYU'A@>PEETΫoڪb]e,9WB}U_5==gW5+jU|U UsW|}UjpwRG{&IH<}"A|2G#" @z ?UKل- g@gC4YG{7UQkm<'H~LJr8Eަ16{y!wwnc ʰfs]+)"HW=yw?]wӿ20_x?p*E˫ʧeU %jBjp3djӝJ̪U52q2VUWV*@J.3Њ *TVUdULkyUqh$6NMgnyT%Zjշ^Z ]G=|.kujf-$wO[*k:>V"+q')EQ*ǽ$զ|C[]/ZwHBB~C_c<b_w+P2xRHm#6]bE2 س &:TpF2TU) 6C<^U4Jx2h9Tف0_9@]e%-JyR:jfi-d)BRbJ@nw3/rU`\25QTWut;Fm HԹn"XUOVݗͫg6 w$㰪g8tl|S?15䴪 ~I5 ox$6AcۿcHyҀq>}+>C!),sNg}5=nݹ~$AM.BUe ].}sr: sڞyen 4j❱>f,TdWH7LaRkH7GC^3뀩K]M.bR !*TО1Rb@-{yqYõXCX 1 (n[O40oOl;GQm%mZ2 t YR5hBohG7sNN[㊴G/$h&ܸq)(&6¤րX ǓlfuYH#Db֘p Zc% F G# шjNj?)DJYm9[B&PCc>yf$NYmN;f/盭[\U;:v{rPvI^K=~2LΦ1V0Ǩz> HLoL*ci{<H`ĥh Cn3R}F?NnCJ샍Hf*J&@E]zr: @Ӷƹ M= tr.P5(Y:' GS/N&Tey6F,''A U+M4;Vm u^u0&C[4A]@;$Υ_s|Uyxo],D>svJ@$ZfP|UZWK~nozj{|;ѶY[VH-+ Dڲb>wˊ|{_@fefi {j ҵ:@=ݡFPZ?fUX~My[UgHVu~'>F/V5%|mo#XUd*W22\R=3VuI{V.!gDŽY]aU{{[Us{5O3t*s'۪WϞU=79GXݾ ?۪ĶV)شG[ezߦm [ڴҀՊVF[%@\m{4Y+#pV^ t-U[Cjͩ{1 sb@+/] (Tݱ+@uZ 0CPjbX P6P5R֕] f' 4T@B Wn՛Uw`>qjzЗt=^C<U|PC5*T@fW` $tf&TT7ќ jIB t5VsVͤ o[pj @Łc,>+ȿr5L -əɪ*^mnMuҁV}<{H$1Z$KIDXOMcw1V5VU:wij%jmUժ۪PEV|=-=w}ZU]~ r×1 w/ զ**Ȏb1*DTUkߎ\;Ɣu2ډ >lDU9"mTNKbJ=ZO1:{W 5F1|e+̟58j4jUPjhV5`תvdU#Z5yjecIZU",تFhU#8*"|W|UʨfmXcU` ݺrFVhjVj_U5UZMU9m G?vEr[Gv]=> @wv+;Ѕdg3'3CbUG}R}8 ZK`+7e[`([( `Ry8'F|H)aMS]oGdhqQ1x([nəc@uG"d=9mi-Ig0T*_b iP#PRƞ+*EG sꅕW [FbVyխbU^u&jj Utc7Ysd %^(M<2ZTR-d",\pPaj]RWajkdF@KBU:䫭IǪ 6@`[Ul*mU VU];Pm͆js6Tڄ զl`CڪlW.*m mªKX\X5UUs'S1J.`.<VsaU]:"xaU:V3|Uª^XUsªK @z/fTVBYN  i *] Z+5A(&s'{-m[ z1z{SpvC. ػyo6=؞ߣuhQ=Zcq-#]6?q{ɝUsZar.nj?3{Lk1#:>cEA5a@#}={ѱ=:{taq,g*[V K@[UmU0jkZUiU18V=qiU3=VՀYXtjYՌaUyx# M겭IZ}Uh@WM}ytvxUVw0/t8t9$ӾdU`uQ4JCUC#Z$QCUC@}9L:iUZNmw < \y*\kq]Sp'N=ί{NH)0;gծh=77Gإ? 4RJtɹ ^|۵;$݀"9ePT Ɗff0@ VJ)V1aVb8"[+ 2,ª]zAa&^i3V[W|ղiKZdH. t1v =:PK9^;BPh 8) pv1zuzo>O_.WSk  t mU˴lB+ZR-UBCUCU׭vC*ڪhJ`U9Vl@36 p0Дhas:9*pγ) ؅]u$ NeS\g.yzw=H$ͯl[>az<, ;/ =#wvITFWB;VI䌖9]o8ִv5nw_:'ca~̩[%0 j`!jjjŪjjMt_@6Ѓ¦t-cWΝl@o6?@5*`d( P[UmUUVU[bZUaUOvXՙVcUmaU[vXu;ê>DvX54<ݪ:UGqrǐ= QdUCǐ`ZlVU*̪&E[ѪJj[> xϥUYUmUՅ 3EzUimןt1 Ђ U U-XѾUcUuӭU;JUL@@Ulm`:eXحG;vVE[fhќݣ GG%Ŷ'[U=Y*U$@M* UWvڪjڪjѬj 8ǰUڪlmU[ [[u[[Ն:Vbkgkn[UUYUz+`W4_US5TkT;v UQ3h4T5T Thv-UyC5`@U7FuvJ8qNnn) 준1N(QU cXY[u<(۪c[Vjwf{מu7(dDz'Q3ZM#YUʞ&oZϒwM$*A3m{c|Fl: m G?v6ы2ҠΝ1֏^tumk~ :6Cí{`620]tW0XXs9=ί{?NLRKKntNQf^Og+(mICzOZإJ)%TǮ}k0(Ra%d4_Y53Z&4RƳΟXG!|պ+ɪH|UQU|j@!:n)"/m vt PN%19>J;@;9*w/@4DA@b: A 9fHЧ:ױ%v XoM1{a(x=?Ip5]=OFGI0oVPR _5~VU*TJJZhҋ"W@591Be XOUP2@3 ܂dƋ- }P†j@'T+n ?@'T3A'T[C;P6s1UU9H*zJ@LUo(J))V%H*-J*Em}[[%AkhzJN[U Gg@sL7^āEGNT\{=9c:WGF < @C92@JFU~2ZII+} EF2@zIDJ4h`ٍZ]ʇZXOFvE"Ojuf*Io_5Z--Z=h.[Qlq5$X5fPŪyY$"5ߪ9)I\mV`Q#E"/&}1T)j=&pe{=Y:֔N+{C8awJcpe'^鵣!!\~Z/, * +RJ 5 Qr*嫚Ar?s<y+MzP ßi۟=lw`vgF:$^иw܎tg/c[VV5YL@ ?䋵 H!%UmXCr .ܸZAR%KxXզ RRBdi2,=bwSVMU5KcL`vݦ]^OHl: tau!zՂİ&pMR[(I;f` {~U{e/YsjHϧmkmCwC;(JU; ^YVr#mWkꋓ!Z`h੹1IW X4A %;ж@ ![]J뛮;xGOwAbjmC{ڣj1huُ_u0{ՑiG DxRj̬2uORSڐ+LHHlfUza'ՆL:HhEVUi6$a[UHA t(@YE/+e*1N:=?Fg\~BiUCp6v %#Z Rn~?.,a6ganl?Խ!]˂m9d`9} u~6J6$; ZRs37jۿ<%K9*qQ݉AtV%8Æg6>Gxn_O{94 .u %_Su8 ~yGwVOG=EzHHO$@=0 qn]%Hmep G^/G8IFVYV1Ko8KbYlg齶N< ih90`XLbMJkZmêԧĂsêAfU2* ~V"YPHª&D>j[͜uw*`@5ǎyhh`@U /J`y :4 T*m{ЯDDa I4^:9dPÆjg6Tj)%Zޒj@VT(KpsGc @fɃj6ME~j@[ts:UcPp@&f QUbYO3+QCl>ٺϙr&2%,Aڪd=w27C A=䇰 -Sn^䡭?ްɬ}D2K ߴf.՚o 2RֳQ7g?ּ7leBRʍ!>b V/QavEgV=}u2:pHSqNM}f^*w%=3Yu*kc% <~ PPZ k]O3JV ~ѥk_-g=!0i7ka^Lvk+k/`0gc|4[+KeUJ'^Ux%0Uϯ8w6p= ?? x|1 VËZ YA~5"P]uw(:dCg+ey{Nj5u"]U&z_}6y787 @5dǼ+mRAK9uZ,ޫOm!2T*^c[}kv)?hamm >JK)Y$Nxlf2OM1m2";_w./|fy~|-j&H$pWgKsc5FiH oyyKhV?0|8b/<~+r2ՔM3c7aשs;y~`qjVh vO!qM g1 à zz: 1d)[\G)#b@]Rb : H\Ym3ːc^U8~$( :+w2u: 6㋝ЗjV"5c{ffc%P ~8U^`*c%A Z[ewݪNpHn<؄*vҀ=#T9 Axi}uV&ut*+b2Ms:_i3.$C#$մ!r&g @z6]͇-'o%UTz0J1 YI+ 4N"<$Cxf%`  Qs~4O"Ekp!aB]`16ޡI7;CHc'+Uݪ-DE5'ЙvDLjKv,dBhE LWn5hAUmy/=M@W?.p!&] \@Dw+Uyn=D8yӈ7_x+U_[eUz$5Gcv r.@> ~9cnqq&g}2Tև@ ?Š*A;[Ҟ-XiHswίX5EȞU0 Z{w2gAؖmR] gGU?Prൻ&KUJo`e`w*VenW8A_v *pEP#@\mWOVI P{rU%ʤ@s khxjT. !?ͭdZ2I)t^07j޵P1kTϟhTP !fk92(U ZU jVmVYU*U5J*PUumwmUϨDVՕ@}0UǪdޞV'KU5ҴLZUFAVUIPi( 1-A)+Ԥkw 0(*9{XUסx|=W5F֜kouYj*j\HF:4r)*ML^x~`5@\SvFS}k@iKT tO,1$X3Ye"M WvIJrt.yt쨼5'%z%ۣ4݅ҟ r\Mݙ=B*oI_W|}vIjMRMV9NۇNt:xwNw@Pf&ЩIKbؾQ*W#+]{ٮ,Wmr5*6[c\Z1T?"* ]u9c\C+_Hݐ9{^'U}Փ KYT%` Qg]QIl"Z0`-HHi)pWRx0B[$α `.H)%n֙p2* Vê8d5 {V=:"lFUՏ>⫪wz|=z{taёݣ3GWv  +ڪTrQbU3(UODQoUbUgj|@CUC$ '[ؑ-l2@t[V6CT_އq6G}VPuC$eGD*r|'<-A*j`ZՐUVՀurjBTtZ*Ǫfz0[*IX$V{t[ x*Ҝ8@jUhj H_IENDB`openMSX-RELEASE_0_12_0/doc/internal/vdp-vram-timing/dram-read-burst.png000066400000000000000000000366451257557151200255560ustar00rootroot00000000000000PNG  IHDRF#Q pHYs  tIME *D'bKGD̿=6IDATx}wXPQYcl1."XScX5%&A`X+6PKT4*e㽙3o}ovc4ޛ;3w3{~.`ff'3Cٜc$)kf]~w\pNaxJ;gipOn}~_f0Jn~}P8!ܫoS>zl_{n\Sr`_#0$Q1zaS ^bhF0:.FMIơk?$ފxH ÒuAƆ#L8߲d쇜 sP.54% X) @5$5* 5`ІnZ<e7{.sY?{ͨ4 Q@%hy _L #بQQg+ݠu,zs`t+^--[^DE?$a'_.]Daġ=Y÷#G?D|-z[,n_bőŌ v s d5+u=Y1f6цύO;5=psVi_T^ڶn\D:Aq @®/֣+ffLuC:\{`愉=^k7>P=D ҽj\fA7B, 9({bk$ ;4v/q;6>fICl Cl$fpӇ]Ib;m2bCl=LMb;&;q]Y;0D Ƈجd!+wtiQeg!bCnLfW,67enYl#2bW|]=/Ii(TvcC,'Rvb{>% w0,- ~A!6طOIٰbT> 3R3#Lʐ7J!Ğ#CYfC{!{8~, #abUa[ };P bhF#0:&HE Qq$Z#&:e`q)a+Şe~[]Ri&(33$`=bkXkTϜh&eF5y+1,7(Jd`1TFeaFűQ?ql[,cjqlthG:ZE|7[$nqc -p{秋1+ƌ F #D̙]ziPh@/*k 0*SivDf!.BXʆ\QPiV:kYZuN\$Qo$5SUҏC,Qv 0(OJXrUo9Qe ebWְ:460 <v`T.#:8:sPQV6E,Db3{@(sE@o#hlXQ$p(؈0B#"{! Tj"Z:5:5.QStwmcS ٿ"}V[{T/S#V[-ujxP6R6WPZ4djkO%Fzb èFaFa[ A10rl5bMFYtls³Wjk >5$8J%"rjkyV GQ%jkHi@@H])ڇ%Akr> ]^ׂFS)r!jq _q~-LWM+> h"3~ tc^OA1K`ldRиjJAݗ{ ՀWlG+ۥ1#"!f%=O%ǿ bvةI$vj׊_NCVQʱ"vj:cv_9p/͜ϳȶd _e?*GUc?*wxBIݏ cacLlj8`}w?Y5*-Ҥi9ToOotXߨXߨXhb}!FHpM hlhSHbzϸGNׯ mhk-بbÈ;0[SmK~%7Gc;lMk5[9 Ș&6 .v I Fd`Qs=4,N?F,N?8#ӏ,N?20bq&1m)qߞ֞֞֞Km[ K[KFC0+сb'۾bèF0&d:5SoJ [70jq0JJ{Yv рyPy#~lJhިrV!]gWN$#~ŐDH Ybq1ń֨kVXĞtw0k[lOdjSC-k)1\܏q'Z!y5MBZ.+QWSSE0xAHDžѡM{2ُ-Qd?6mmũ[#Ǖ6nj)] m ]:mM*3mM!Lc93 SQT#}W2I>b.j0Ք6[fZW)(rQ Av-V-06\ aI5:\lZ#ߎQ@Zbk485Z&#hvE sBatFHXi0:O ae`k1~F[i0L ߋa Ԓ8{Ҽ%YPqyeɴ F6F )X¼QEdި`{(ښ捊ElhռT+#Wϫ)Vېh߿Jiȕ%^#B$:z>5-@GZ?hmY#, -R1FyUHmTBJ 0߹ (Qk(VB*V ӏok%$J1\l.@b0)@(橩WعEdr Av!6uYEc`B&e%/IN%Sv½G+ҏ<ۿZ#|"8hӏVll`jug`v,Qk5DW=z4FKCa*F0z-F gځ"1H}4-mDb!;!v:s.M"K>,}a2UFdb]Ik*0bK`sM<7u`l5Xqql4:Zh<؈ G ӌ o_űѐhAr.xqltRsz wq}fxWV/4h{wFD(HU ~-١0R6Y0:+F"(~)ѹ0?0@ 0c|x{jgRRfEApl' J,g(Vr ԬpE} [ 4עĩTU|m {U'uKQښ;5Zw=D[Ðǭu,^ (`TGKs alNѭR~q)?` 6H6XU36D]<(E\ee?nt=m-)$~1SFv;ֈ},Z* ֹZ5:ҝYKuJyWsFxʮmtb :yڜQϽFeo3S voG>Vc001=d3Ί6k,iOQݫp֮3. eC^]Drj!8b]FYhN-m{btjiG!ة!J?kf ~Z>ةۛi2. ލ=zoVQsķ{7ݳ!#>{ƘTXk([O57tcYbkw5}]Ynbk#ik[lј 䍒|挨mV¨"+_\5 yj+_k ϐ&f#^sg&  Inw$Z}œ{eHI!ѺL:Zk>J,1$ZŗhDu9ε6cQnc/7ߨ1S{7zk ZF`ߡ0`WFQ3&ZdB]֙!?P&{в2 )RDךm=?#m@S÷2Y!*E*3rFf+:Z+3h*;l-p'Ȃw瘈EB  $[$t"a}^5bWF'QJS0:% B}Z8G ߈at8oT۲!6U[ҵfu]lKb[caTu]lKb-ʭDjkaUB,W A\o46 'm kdqjFƩqV 5 A5heFk=¯>Sp]PI +kX7Xf1y)ŎbW퓂(;"(b|`FaFaFaFaFg`jH "ݥ#5tE:C8Rb~p(2XL%bzة,Y.Q`Q%%DS[[6X# *7# Fqh8o4T7&F'g!ot&F'$5$FNjIbktJl.mF\lki֨\l+ )XBk%. *)!N?B~8ؘ%L?U6.ӏTGbn[#ka5k'67HFY҈*ŠfcGd9Zjfw[aU!O{bOķ>E{ ?ScmJBlCDuX3qj^GߴTC ?I>(rfJ F>EȢW&6J?r- ڋNnl}ıQNܷ\]cQsıQ^űQ>.6bRoEq&_V 8z1|}]?1糏_͐g[YO.[C_='Ƴ %{mOqvsԩ#.CClb~IpGj4f5pK~#hl6+# I'I};F֫d!0Q.E7H۩2h6XӬ.*8^eFƹw'F\h[#cZ#ugG[  3}rٳFg!#ڼ~F1dѴI?I&aajqި")̆M0Zq#3I ;pW]M@`tf(]߶3\TaT:wť:0#jx6zz蟧y(t߮xaYt<06],n^wXwˠ 14U 6+=Fi֖tM>=y+okOHA/ 0Rыk߰3X}QԶwxSVmZFt|%ͯ8\x6+9&K-6]3Ͳ7]h{v#"0EZ/*ΨPzSQ0ߢC݂C`^_6wEw>x?G #^O`gHj%5C-aG-uw\ ˆ”DZoѳiF_/;Ѿs}f]y)C`;pĽ=jRpjHm`=&XP_"|/9/^E7~ mQWWj =xE=Xz1P|U[բڮuH-W__覤YT nsb;0vkn#;Q ;arR XCKc7sT,~cV+2v#Sb ujo<=v-vdw垽߄55_q0y V1w;m75o X 6f=ӏM#OX3F.nնm-u-=Aa+deuĎ`d7 Fʡ8QOb @JEk ښ&,yhkZ, T^sSݱ*tsZ 0=ᡭpa?+j7]7.k[#Oo#FNWe.vfE(ڗm}O`d+yxih$E y7yn/[10*H[#j@%hJ\_% BW+5 ڦٙl{WA^^ ?gNVW#-[ؑвZA9 ~ZoHmx6 FȾhua({K(z ,=iK(z􀅰_kZ̋\+30bt{9!!r!,]ھ}anm,$u[l"=ӏJ,5U}>\5oٿ"EFkJƏQk.o@N4Y+!-FjYb+U2k@b7k@ۄFF Bl= =iNecbLbkxbSF`;[&ObbC  E ) FgExή <9*4FH=aBl{3[S*p9F\PkFbQ.v5j\(B1FaDƦ4k`d`V1~Lҟq#'6Gc52X#c52!QCjŐZ1jŐŐŐZq1V\ C֋!j1èF]0,b'b htEEQD!f"o*kdF4?mX;\P)nh~h(1JzB&FYSY-|0 hS3NVq<[q)8D0qQLj[3o@ 'ےgZdŢ_f\.g!#GӏӷGn$ UE Q(#Ʃó]@"‰"Ј"' (⹍!>#########ȃ 3EQ#xkt8./t{=U=R\=L\(.4`qiv4{4{blivR ˬGq6ưerjb[FV5’xv~gJ44;ܳҷ/iQbuPqMÓiL#(%ڄJmr;Ї@GGs0z?~4c9M,t S3N-S `j0N9RF^8Nmb'/Ӈ= t&`c5{BLݕ03020}pe!z1M0 b^`bk!- "4Nd4ĎЋ!k!6g9 ZY9DEuuuu#%;D,wdh{_(\/N[0*64ybjq}Pnt~4 U 8:Mt-]HELJi(чQ-nWܢE?qA-Ɗ[L ꌼoF^=]gy]Cԝ 1\l6FM)|+'80b34W #.WFZ$_#1YCp 1#s+Ԇ@5-FсbS`S!vjdxr#u빴o-Bf5Z$𫨫 bKi!H"fhkfQ[FOl0F-a"EXNG!7tkx"K~~Aڏ4G6\s0Rb.sUAeUVb.sF\RG_|-,nEOq Lؑ!~4~#ڗ>ou`ٲH#a`T9'>i?z9h_cJ1)03C׿O]QZ"!4x_ҏy?ۂWXCg:s3qE?cX@?^9RSgf͐ 0O=E4К!C$ F#nY3qN#֩fXn06ׇC~D!6r?`_bC샓Bs~BC#![ 6*bFh{ g_RaK9 ı+IlD*\?X5 \${-^1$ZgDkK} +hU}eF#NF-oT;NeoF1O7jN` ]KȺ5궠>֩AvN%ֿK hkmZ!UPiZ5vgyj_Nȶ5u -&.;+i'XA!]l+tG-e>.ήSIZ1Ns!,sHFȅf9u2V GR\lT:0Z"3{\`/ Wv6m VŤ7kr,tQ^.Qh[s͠bNmtA64*7K9[f8B3>\캷){Y.6r[sUwԸ-?3;1?_=J2؏Vh$8hvXuR.GQ s~Su%I|;t/|KM+´qd(RxXߋ5/3=;i6bJ(rK00ЁN'K{\6?w6ΙԱ}^@OT2~R6SYP9\Sy}Rv̒-N?4O?a`GcѬɖ9cҪfսw 8)wnT'V=,T&c#q Q`@E>WݩR&vp(1i/8t pC#fhj;/vmڴڔ&aM+7`\{rF_NLѪmִ;hs8\7Q\紬N`Ľ^xEwŗ؁~ (КU69̺)Znh5h„鎠78 FI{`\L }OnҜJjLj0qC"lF=/?h%~I?bkj_C) Q t@`y ?9=l3bNA&ꔘ-N:n_z`t('ڿml7yl;e-NmwR 6mXCr$B?Z?¨vZr8ExU'm)mqVFJ #D[<7 |.q|WuOOu܃ ǷZś~87xfolet+s{H)?@gxȨ[Af3?׽V vR)hש)Se]|3ُ{FJcҸ,;~s+huM1Q~AFմ,D}$h}C `T &gϤ}JK?D͚(Pa@PolEz9EF00`fJt N]u)fH[ǮZ#L</lҡ1KFJ0OX:JyBK;jqwY}I\e6kpv_6J7Mwh xm pFlnꟾJ^\|ZEY{h Y#{g3ܟ:ݷ#/mlw |GԂa5B5;]IK!ֿVN`\z&_1(~qbלQ->Q \p(=a!pM>*W{ W}㶞.xgS'( _Of,;~]z#GSl?P\E*ҏ/g=KbISR5S.v . _љS6OcW@?0ZyK,W"nnj #!*\#B1Z`qk F p3XϠhpP07)hU@s *}( o7< ^%`@q:Σt9saĠOLx`s` 336 93l`TfŸVPGÀCiKNtYDD vaQH~veD׉N?؁E%D+hHtGMwt( =\KNwtЧf~Nw] d -uu4YVDi}.yMwikI;wQ{0tG.ڋ? ʳG8K[s'Lw:uY#ǯPaW>!"5CZS!F8hn_w * #;2Q۹)7d]lhvm]^k*Â-p,F%pj+Zk֨[S#eZnbkt}GJQpjŶSm.O)|͐4tU2]s ZVpj(~}ILN&Qߚ4[kN̨I^sm&~I4-Vp3>S'is :bhbuz.#PoQNkbY85MD?ΥhkT;;kZy5 ߮Lj3#`DVejfrHcdNwl(tGc5\8I6 lfA GjT?Eɬ~L>RSn#B ;{(wY!#5K8Rsu Lցb!:ed ! BaX& ,F,P|]@}C{e90qlLW΀zS+vjYW/ujNڏƩV.F5F劍S3N͌Ԍ5j h[}֨-F֨[!bk4Flʯ(aV*` uwR1P70202DCi)D Qʫ#)S++L)Q0„iJͭg_ftp0 {\UaW &0@am2{g44I,L݃(ac5j52! MmBLΈKmk!`x7(C"Kw>P$;?c!pwb!YaIENDB`openMSX-RELEASE_0_12_0/doc/internal/vdp-vram-timing/dram-read.png000066400000000000000000000231311257557151200244030ustar00rootroot00000000000000PNG  IHDRe  pHYs  tIME (66%IDATx]yյ>FH@58<OdQscBWPT0.1c((" "3U{Twխ:3]= T׭U_垕 ?aD_Ou šmaoU|.^`̄uطQ s뙞[sW1}\=3Lmut(qb /b a>/d0'3dPk5y[_C>/CbS`1E SD"5EE(HT l."^Qd@wL/T׫ά!D/}Ls2jջYs,WEGi [y:w ZFDF+s S<~5Z0fF`AYOߵ0Fg PULbvxXS˰*tx,3͚>j1fOq#;`7XC9 (hV95fšᄵi֊| ):K酵 kH%}$Ja"P7tzveg-@B,/hg8.I=kv9Z&]q<;kggmkwn} kZ 5Ik#!狅2xXX ?xiMNJ8J D?1@%@h3x|2PˬX ֘Jb@;j0TYQb.BBjMw,p?L-(N(@FkBdH*2b`(x{bidC@U;͠7XU'/A[1 Qnp#Zi3<G*ҏF?AF?KTH0}BQ^fpF*"feǏ" f}QU>kZ(˲a2QCg>u|&&(.L4xM  ~`n OYPhV&FsbB0%ο|CšB=ySfV`MBʚ'!eMOp5=-fMiM @#b#@KT@ q\F~\@N/K5d 13[qҌ!&1{8$cvYRVP!I7f[ -1BWr ֻP#~DDt@#dqk ;2| KL5z?]y/3~х6ӬK.X @0+."/v ΂E~*Uz/kg=+w o0hgGow*!YYU'3;Ŭv1k=přUXt k:dӿ.U\}R#@ڵ3!Й`δn+Йy s)ХgM=~ս{:K37#?Dڄ&GL h)s(\Mh.w x'm6 tE@t9hAX>}Gs l=唻۝ 0Soe9t#U({hy"Wzod:wj5l iq]@}EC3cQzmdEQ R\\Xsm2D7US;"w#ǚ-|Ի~Q"mIJ9 ,0M}.#(Z,?”#TC"sΩ~QX W/7^:K7p<gE{DK!oQao*.o]f+1qTx:* ]:~%˸GkVˁoX@p7tX$v -;^;^',;|"1L D?15b j@1LU5U2 fMuitJUZ/UZ'U:Xܫt0JkŽJeJ5kkº iM@-b!b 1:^ZMbt5]-fMWYubtSXOGuxp"LB![fpmrB+Xuژ )s=zfuw DIvZXG+V}f6W35НHK8cE@9#OG)w(0 '{>Z”/EƤvQ.U4("E=&R\ر@ЖƸQ:W_h\F:PwQ3:@4x%7ЖGY3 d2YHFklvq*Bg}Z |AM[%L|at@]Z C{W@x0T8`Jt W;DϚ@[̸g>Ko-zLQYt n%0hmU^q]x(novNڟ:0\7${ `B8Xct@-t5EwK F|)uQ* }_q+wˈ ?hZ # VXGN5zuwXZ750@?S;r "C  @\,J:>lNrvMr찦5⚽30#wmpѷCCȚLh50ˈPۮCw]pPI]2'Y;~jUc$|+2 1:4"wW;m\S<|< Fyx}EnQjc9Ѱ]ueM)oXSJ̚R:PHxO7t)1kJɅu_yH[{0(4VD˸1(EpZ\_z"zRDNR[("j1(^U.DޟQU" Go0s{3ѺJrJ)]*U\Cۃ*!RJ]&%γw `~fHě,8&( RAVS8|dž5Y⺰}A&1)UX "jul~?o|=?>c#YKBXZaMFS@+>"BamȳV03v5v"YE ˍe^\YfME(^@X"Bka} ERQ0u1YG 1@J j1K}(U[l.U]l-6֤;3O"6וtXM5i 4 DQ}DxQ+G+G><1E%q"Syen]mpCHqYk* u]ɥuR?hs"yg"Z@hsoWN=ܖ{L,g!kAšQlG6ӮR9<&c yj,@)ePF0no{}揝+5]N,b,)P*zO4vL-s2&x_aY32||]' |l69~yl6Ys]`źhǾuXS>&LEa9|6tMl%fMJl(/(#}J?rsQ_ak;z:+ D)##}GGXY*Mޭ齍 EOԄ)oChV:&>-cV۳o&z)IMSw)d[d.E 4 H 1|*RF xLӴfWWiQ~S@+1$x ָ[dҬ=@yC=lg6~BD33@0\7O~Gdj5:GO-۰[_UNr;VuK vtt঍!뾻W^61~A3@,-nN.=e> oOCDŽuBan# $9 6S*Qe(C8q)㸠{bWGF,tdg_fL8P r5D2kT74߃ Y-֤ƪNCjuGnB .+SYQHF f 4#aY? uIK{ɌԜL&Nk̼+^K0Aّ{,N ksB03Y@X{3G0 vΟy.UGa%tq7S,GN}#;to<`Ƃ9{yj'³3_ƈsc;^λ3q>iHpȇ6t*eˌ ֤<s{fha# 2q>s8:k3<J% {2}օG#o,& kY-?mH?vX=_ɈȘ4(ik!c(s Ӈr )Vn`7ȎsSpйBk}av-:@rx䚡5X_۴;V%x{ӿEn[ ֚60Q~aV3rqγֵuQ6 *f6Hb 1Fb0:'EY礈Y5ZQm5EhB(6q4%6q8@4 b z.b  .5jk94Ehv⦱u⦱:4+NCd:%oBf:yUYSBn<8"̐]J}]vmh3mUhMzj8ϺuCfp@h'֤2B&-# ))%YlkjۚRb R%"Yñ}31eG -0 ~B38\ni 4 BΆQ23`j H7;Ǯ?<٧PF1b#b[Pow7Z`;W7Iw1{hC1f@XSK !E?kLdhMF9Ai_1~j,/m D1CuQ?h^ ^,tViJOmwU*0@k'`8"[{ꡛn *qޣxϠYSQYS7ԂS,!.l'ހNx/&k9 "51XC3zҏL DYSWx>1 Y" Z(²$,+uL2v]&.uk8u8u8u8u8uk(u+}.J+v_m$7ˈ˵V@%Q+XP֓wm6&k3xQz yZ"̨u»^:w BQL F*bb )00*b~~zv-C@wő1xhM˻ gP  8PDYocĎ!m}Q^MrU$Z,^qh(DQv+Ŧ:Agv%dקo0#ԯ-A*!wS3pOڔ* ma<@DIm"hgc(RQDq _;@+6(g*Չ@ڔҬ):d6>눻NbW&La^fQkeu͈&La^g{oc@ |6f{f8лۄ;Uk[5Ӈ U2)^0 lMF]~*62lkd'Snxr+Ϩ7G{̘y3x޺tmF&wɓOvY{許1s|+=zΥ&\woE\{&yߡNxÉ1N:MxG&MB_0 6 `0.VϘVf|{±'H i)@IeM)򣈳~m+nߤc0;G41甇Oq8p038!cXX7Ӛo%I $ )J}`6 `\Co\{:Wt.RíƐuظ:t >C^[UyrvˈO &\7QS.}lM'L 5{DI39%z Je%FtkIu#~ 45*gupUg;5^睾m $\|1N>ȁ``5#8miR.ԐW}0ޡQa&={}2 B'ٕXI]x>CZk~i)/|#HIAo$K"z8pVr>c-_ѾV=B27&Ai`;@0EZ33onJrbH s}SCTt*v]̈́Gmpy7RR/f!)J+iɢvP/E ,E 3]wkJl],㤄"Ц\)H@^xi3/^4e50: _vƝR,YG<@ۛlyuƎO>IxN2@wY0[P8aNaGu-ơ}Ԉ=64C^8/u$zG n$;YUOuLMF|៼ nqV wcA`'ir$"n 3j047@Ҥ.!Ed5~GN. _o%<c%?]f;̮ .̪ >fc(g-? 䩩*&3=f<+r 0c'Һ&_dq\G ou0,Y҄6gm3g61DL}3k2 RXSO_iSNWLcŚbg My֜FtymujӒ躯ysfw z+Y" VQwxo0iy4TJԆmݸPJݶ 2by"t#ʠT50Zт C5efĥ@qJ/qJ7q%nJȽĉ*UE.ҮYn[)Bi/Y֚4E")FL=CL5b$!Nj #f35NW 6k3x yҨYiX>#oX>\9ղg(Z k-=ZwxKw-K/8$^)IENDB`openMSX-RELEASE_0_12_0/doc/internal/vdp-vram-timing/dram-write.png000066400000000000000000000143551257557151200246320ustar00rootroot00000000000000PNG  IHDR}*N pHYs  tIME #9IDATx{|* "T[rQV[Ep *VHI"**xTKxCVP.JՊPUC?;3;gw>lv7w993l:vh#;_>;>g|5p`sbL>3}=ҿ>N3 ŧ,EܞM.ѐUST2eCHm[0v-jk}">ܚu_1 ~/~\#pޠ[lUXUg|Rzsq|P[^EQrMeXvew=*d.[YFz:3n,0} h.N|Ԕ# ι/>^}|_-˰rt/Kof˰~39Vz[p\˰tdQ/zS#=N{9p2(Kп bC9vľ@c0QT@)9^iyv h@O p-]Oү `:Mu)heF-Jľf ]""CxA3SaK ju PL?1'~x 9 " " z v$z=\қqܱK VӾv3'=TH6痙\ h6^)V;08_=h !^3⪞ˮp=L=fv/m)pݙCޠo@t͸tO&Az&aXνv=bȪeÁ m:I =li MOu1l}k_)ƾ@>j⩹EIo. GJf@z:'U+%OVLS}9nHzKNV* my0{~r)ՓOʻoxɫ-OIGx=r?(>3}c5dU0>Q0z7j?y 6ߤbn"U}teL:fdX+!㬘%/C#^T'p*r>Jۨ/WeO@|iQDfMOwI7dwSv<aDr2o2пW:ˈ׉FIru 1ԗ!p|qPG鯰il,nL?AWWAN(}[ĜLӈsV\2C!@ x :{Ef3 "rC> i!UR{ #t>Lt\@hn)#ݾ2Pjcy~E{/:p+ZǧHxe*'/CAA[ܩ@hkC!Bfz:˰i>ynFdGC`2A --m`>Ǩ죣n>  TG_ذv'^Ǵ'@GٕZ g-["(k!@Tk"'sss[@!PߠZ)`KnɈz~0 )+.^|[m>0CWtwXh? F߉ytgHr2*2 }Gvϖݮp縢fK֢on~*oW\&ULǾJ6œ)GENDs.㚧2z-wʴseG?Vr[菐λ#*0Z)vfN©d&0̹<#dL*Y97D7 s oZ;T7C7W),4׹L?^KM&Dd2Cg]+[ךZժ7|{+js:)>3}gZsL鳃3}v0}j";_>7*ui>8ot ӴZ_ZO;^+f˷u5+͖RQ5F]֎!w5JkT1$kG 藍#?#AUtC ǻr$8cm}ii @Gr0}39WCr0}[`qLu^n'c,P%O=F3v$K~-38On+XymƎk c6 Sw$uu2n7#_ k%y/ecD4wbQmJc' 99mS9KB_JW@Z?]N'ʳh>PM}Icᙟڎgî|jRmr$弃@Lw%~I$;<St$eU_4 u@(7s0 u ߟRႯ> >tsvXV.E>Y<@q@IVxo\-(wƛr#,z"z#PWWWȬu1A?0cr\CSGa<%'2uülhoiGCiJ4_'ęEhv 큿h,y8[NЬڛ>&[Nz;9rNO= N[ `a@ӵv(h="wM]myutEqSA{n 7LPuzxlKP-a.aN`{z7 n8!M vnꍪp9nm6>c_8[տ*ϻҿ0뫬d"sώfL_OBOGr0}=6]'8~6y9owLnw$[*aҚ>-m/9^kW#^Yi3%ÜɻZh쳘>g1}bLKGv8H?^6>;>gԌZF(#ӐV!?)NwB-$]j%8ͮy/N[ޙnunP+Vvd, [%@돸Euqq%u68e:6! ]ak %>qT@ޝ:1יSP!_A~U2:m2q^C2dүha#}mvTX;Jy!~W XGj63Ou)G8}]@`h)Ep'wI'x PVaq7P=5 dʮKc?v޷ Vߺp-^@hK 4޶<5}.N p&H=mP(Ou/3UU'G5[9Q!Uki:HDk"#RWJyJge}3seީ:$Gc] mi4V9LP3&)OS*A#ޥWcXU˻T [$ٺ,K;΢21Ʃ9[a:u#S/ϦVFFKX_vtn~YLߔf䕟ԏ( 52pӕk-d&U-r<g1}㡳׽CzI;Lq];~b]6cRpӜ0 rvXQ+3}>gLp>h쳘>g1}bL|K #ti\9Ig!M@yv+_<.?}_re!,*83j-SPy.ȱ.בWGБAS O<\MGY:{uKs5~FRM\ǹDMJ:2}5) )Փ9U(̷,%z~czm0%0}Gr+uS;.*xh"-GT/y\WOimSAA< _Ȩ w:\ǹk `< u:~l?@ k]=#b2`ݕ1N \A..]7z47sQyK6r{#GoWq@wh6&ϛEg<5J*S˓߲pLZ e=4Z={U-O¸M?wVǡv\5Qy?pY+ C E<mwD_϶ ߺyNNzG"_^W@탧C;!UY`.%\:[">#YlZIP!@cYqrdz]f^oXHqMiB w:Ǎq}w@1 OWX㷻s9=`8"Q g$ː;#-O!X@-/|?^#-dɉMFڇЊMs xE!\u}S9#+;}ߎ <\|J{tqu%:iz5( ҩp $7hX[/r)"ݺݠeڑ "h,.ž7'd%#J@Lڼ{Z ǑEc_u9c] 0zS K~[t]Z]l֖Ga[1u'KؗQOkJ"~(aS] ;>gL߿G{iO3}g,>3}/!GIENDB`openMSX-RELEASE_0_12_0/doc/internal/vdp-vram-timing/gtkwave.png000066400000000000000000001501551257557151200242260ustar00rootroot00000000000000PNG  IHDR85o pHYs+tIME2&~tEXtCommentCreated with GIMPWIDATxX߀λ +b/aӳv`Cޥ{$tz}+$ &y'f2;ٙٝ}w2P`@_TTX\,B4JAhU9BP!B] Qh^HA8m5%tr \ԗ %| 0_hZSG7D 9@]=gUB9"u]hf/ L1DVs5S9S*!}:(; w{Wvwv PyB/vVyGh:wSw=.W׻DѽYq.}v pF rH;c"X~`$~M6SVֻ`p]0+@ψm %xU ߧ.\0.w$>bĝ.4w'w;noϛUC)2xQ`|b-MTPizbh1x&bMTu@m$5a5\{yw8u% WHC9Rq&@挡$&?Uy2u{eq_k?q/ ee-~}ܑ(X#q^*vwؼZ)&f =UBWz˺Mi/.#o/ۨVD̏0:4cqw\XĽ8,MB #Ńp#񏗌[r?jSy'.2Nyݶl)ݣC*aN6w)EH[~E-i^Qپj+',҈'U܇I9k%l'9jEBLYyh&E7‰b.SkټUNӭ;e _^}TnIx,KҹxͧHen+,i1fbS'rHQ;`=OXǺT =XVZߗ~^ֶ/nZ8eİɫT,(^Ū+>鍣3p^g-`_*tTE Zl.fǴ-'+fr<\#EN}SQkqۼ"vä-.E"ɋnt̾Fte>FB;UsMzOz+Yu-N0Gi2Y][o?iw盭ǻ,ӧ5 4zI8nWF{V@[%f݈ r/XZG1nz`$u^ƫh)f Yji[3n4*XQ6)*klM6%pvK71Zq1-^N 8g*imFX#$wvėwCZx^m` "`opt䨋dqx&5ׄB 8'V%/8ĝ8Sv˓")ɦQm=-P"}'ܟxɔxK'*( Ysi>:'KIq'\3iG\ezi{ϊ{5ЗVrR,?yl[uq*'#oxwvŝN#os~Eū(G(h&RNjFȌX"M[bJQmRwKҨļH'}]Rb֧ydJ^f_ $vEEw {'.qtQ4 La:dߣ0QRJRbԜ-u;+9wgʠ\U T$N.'{YVMf>fEݾd:tbمUd$dfzTwq4qr8R2O~n[uKb^0]f(ɉO$4ţ(1IMNBk36$qg>&]{$}kw*3zѡ a7z|yKS߫C FqGVS:2q@|i{͋Lj}7)WALm'!GS;}oT6*_lP8^4 ]\Õb8 I|i6|O ɷxlӋGy}7)~7^tUY$\ԼvѻԻI[":IKJKI3P.~:*RT~Tc [xhpCyɭ|΄ðYe;tǗ2%qGl|9=o tXǿũ\B RSvKJӛLT̚$`0߉.]]ɽXk.n-2jsGJM/muְ:tӗw8̞CT\<WG-t_KJt彳.-١{^*У_i=]v'3@ w߬/=}7eȹyw z)e}: 6ቖ$|"d(;/]+<<8}_ p*yw=zKqM%#ܦ8@yy7äq< σCQqt0uql8=(>xU=!>>4$Fk F <kkA*ûw^n9+3XШo|lIvrR4"yuNU\CՃ DPu1P/uH&+;G@Pq773KMIhDY 5H?O?ߗ?VX2TTT8BUUi1E.YYfNPPM&33 02$ꪊR K$Xt6(áIK(+"LQ9CHdX#~k]yncLaS̺IdZID ީ~P]EE{daA[C1![ITmko| n-PHhs{zxTVV:88`E<=>466-46$ҥK'O># q_ xrp4(;#Qk++ folcli6(Cy{f:x[zKOK*7?]* g!9npmΤcUWugTvhmƸAi۩\14*U_{:)jB<Ml.ȾĂkI8_8PЃe鑼~PsssE7N7CQԲ2Sm#lF9}&nx@3YS#RCEeӫMҧϿ=cRsSv/JWPsTi|HLHHOqqqGoQ$()1llsZ{e_s@2Ja.\شiӮ]f͚geeeccc'Nxcǎ:yRdSQ^^âW>ilmmЦڡ` 5꿄ʲïV98FYͧ'}H/yn`'x)Z(/YV# Q9T:4$@"t4-n&FJ=fG'Y|,Qn]^CH*![ޞ#n^q}w]'>ot=]@oFjD-=m&q+d)SMx6ϭ"hKZ΂Ё_ UUU<ʤ?!څxxeE9C6L!\=N? 2xE.Oryzg~!]uĝH(644@2ΎD (АD$(HΎ4On(=\Θ1C^^྾ȉBYYYdd$J0f̘I& ^#5UO;;[$VUUՖMƎ<=MkDq8k\K(mThfťŠ7.y0Xlޣ<QlKaa%p>oC βi$[޲8W }&T}>9%>%[U?aemףP`1\<op%Cw=TztN7EMr,ڱGr+,Ht?<&&ZtqG:%55y9蕿zbޝyWтj͠1Bn;teuz;;c &>Q) EZZ 5HsYHЅ}lr„ ӧOG=8V@ (sCCC#V0000`!CBеGXhHdDDlL4צե&ښQWFRĽ7h|Ыsǵ˟~XܓSSuxF#ÕGߓ*J0zpᆨs3V7j DVEF]ĤXŽ6sv]:̙:,llMQhwWV˂矩?t]xo1o:d7a*!w,c1O=U==T:a(+QAxG,=c$: Fzq۱Ky IG6xtCY@ܻZ:9"&ĝ{ף};CǤϸ9~q:u/Lgd̽3s^dۉ_md=*2u!ޢHPȭ]Ǐ?sLԃ4eeeHYcU{jo*ڪڡT[YY5U(v?6|{A~;߄,B }EccC}ۺڪƆXG  /,,0wKTg0ףY=k"OmcK,SAwϨ>^{:duҤ8')O4C DwjR SDmK&ĥaj?q7`YNZFNq1gb67z9)c}ȿyĤwhdh5BU7Vbu} LQ2 Tʰٻ0aao^vU3"SDZM٤QwN} oe¬ ];%۴|gwֱl.RcF5X75qڷ+$!79cgԩQv7mߡ{>=xъn<|_BmYԦr閑V=+~; mtaQhߐ^(sܾjӧSͳdP=*Ss]nEp#[%[BٹrØE[q, B q'Io 622DEw~nw经7n֬YNm {VyCC !-$6hD "kdffefԦDPSu-}<JW8yх{Ol=@i1jCwrnLL-=ƏǮ>}h2B,tmG}B#ȾBZNѶ|L,}|ssmLXu))DzEw^[ᵕ܆;KT某)Uzce'C+]3VF"[DYg_Q.ϙПGۊ{}٧>9e% ['[sũs->׹υfj꽩uHozJǩ1Fo7FJ[:Q/k7;p۷;ln? }Lo0-!la-M# K"Ļr&<_h݄ٗC >9mitl3g*svI8"&,8JLx$'#+bhE .bwh_*Vرc̙ٓ:ŝLPigL HymGV^Ζ)vL.BZҒOI,HUڒJuʊҪ*|EuH㲚nttuiJN!Tjk˪Q>JZYUE .S:F<$H[gY>rEῑ#~~+A%(„7F-b?,d{Yȶ5fk'JIw )0rOOĵ`gtoai1^%Y-{{KהNvl纳F",X>Eh6ixU爺IuB|*WɞQX3TLjoMW8]cK[Gg-u0pIE<5'7Gsk)P%Hgփ[6*{duE/hY'gY0vƯp= IH ['JIJW86ДfKHH^p86ky䂓iyhQ,q~{ޒڸtj6 xe̱+/eֱƓGTX8ceI"Z9?m]\ ?Gs#@> S:۲ܶ?!$^5{$!X 3=NFl|ʜYmFewAsEQ$)9yFS]()d3݌ ۆ /Q N2ƽ {nNc$yyy-세צ9[q=z4E"$2Y;NG^V^Н 2JqЬc0yzzI 'QAK蕀,-˩<~*Pˣ{OD-oemeL%}Rv2LQoCfn+ي!ݒyj[EPw}na^.+EFlpBsk[D<{%]-9M]Ϋ\~D[8Y=qcFzqM~{ztRnmU?y[?P}@V Yrs5G&aO,_&K~혦뜮璁^_9j'=E .OR}p8X2]CSgNOR8$k'>Sczςbr'ŝge%ˤ%6#0O̤T5n^]0ar *ÖtNR1I̼TۛĽ>x^UxnRZ"S*yIf7՘ü[t6c9jٟP$~ˤ%#m^ƪt '3,yȻyZ\?4"fǎ^O[Us~b윸ac1J3RWWrt̫->7g,P_gVSx4L8kAee%I >]g}Z##3䯇cqsmDR2v&8Xhfr|ytLۯ_M;3WjZBq8]mM-P9_v5t< W; cGϿMlOQ*94aۤVy<[tQ*Շ?P$x Öb0&Nq>ϣ`@W;nfffcc#JP_wvjeG5Ä5jܹM $)&X(-+CΜtim jbz֗Z1mm5eܜ^c9{=q_6r-iJvK(j\֙;z= 0"eזRzLyhQ{!(KIᇦs`'p/ríya(47kC67D(?* }/BsWdH`vcuA}&JօSIvڂ=-?QkS\2`ǀ 24hL!M?7C :4軃kз{w{SgW}}n)(N۠rmMiړG݆fO0Ž9smnYƈ"N‰;vߝU`S7tp\ݓ>I$G>]]k7%sm(LX()[rm\3nƭl zB.Ȉ''',͛7>ޢ{| |}-nGzLwww;sj vksiXЋĝ}3ۈ-cҡS)r5#!GӉEܪ1' ңo~OX|Fe?{ve4*/P gK+OUŝk%j$ޛ͈k_!K_bFG2Y=uw?W僧;G:pN p}gp NfcD<2xd;~Y3 vuĔgN&PB26~!ȼb\։w9t{{%cx0g.̘$Mul4an(Ɠn̓f_,n2M)|i$TͩwgN?㼊&:Z6~z,9܉۴Crô7νe{kyDwa%&&"Oe> -$%%I$mqoo{wp;*CQL Z`<[[%횾e,{١3Ƨ/<1rGGd,Cϫ\V}+:&ZP DjTڇt*R5cy.w_Y/Qu4& .ےC#V8u/F&h'jID7z'/qa /$_{zm@捜{s֚{սND)%0OWbfbrI/v*{{mfn'ΛS[;Soo`=|mZxy' SizvT\#gT鹄#)!%AեꟉ*S;+u,L7>s'd;hsЀLkxWK߂3MlzF)y! LKR)4|ԋ]%M^ⱼúQoԩ{  {FM)Itö+ QgײnA[qZ/u%)肑tr.]RZ: &D29dSW{@ ڰ6"F/k@۔+f3tmwUW]ɭSL[u(ʄ}oi#fmQɣ 2SGy@<⎼?TPP(((ZbÇ= (dV6֎bP̾ݓϞ=Eكy8(Hܘz⎲c=cqgm=ƖvvAPaXDcR+i?FsKܺu-IHz"{BB+/]'7[fJr#Tr tgw9 lS;Ed8QR &10I~K<~Уw-s Y:p;xJ}Q=Yޱȹivw=~gYЅދS7yf9+)L3Q)}RSqF{{?ˮi!8|tET:U,teN<84h{. WYNσH'ԡu}J2` QoW:+uiph6j3OoAi9Iy Žg.N7n܀0m4|Q|YLa[>0a)S;wvh#m=Tw ncmk{s[ĝ9)e?g"vrr551ᵩKA;{c*BNN655n>>BWʦ%&-5Uf]}}LlCu¶42LJp;aˆ683fpNAVg{Uuuk^OJKKMMM8zφF^6*7'v({e6W EE8Nkwpt@ŧR(J x>(8H3lSO_mhXIm=J&aj H#cl*>!]r9 vHFkU Uͳl ;=mbd(_Q#IԘ F{3n=OqxJAz`3w#GcOJ:dЏ|3ӟ[>흴y@}f/c{o69ܛ)w?NmK~۷Uhia>k$on\> dl.]2me=CÇH+C0ԩ;(jjH [ccu5::/xe(//a1O?"qX'SKA+LnvKK +KKs "B!YZYVVUzzy<}%Йp~KO2sDܒK>ئ.?|S%놓n*X҉ T|îCUw\Up_w9a%Sf_.{7?F {1@?ϙ3_^^dɥy|UJJy V-2JDE1 qa!|2[p +9r +7nРA(Z<ʳ۷++ʟ={j`1%Hgѫ վ_ƶv++KX Plb]ss742KFFIa?jl %ΚWu=HW|TwrrŲ| d;cv%+n,t.~r)45ad5?d0!?ѿ:[;ߏ8.R|pz?{~,XԔs 2#9ZS1a&.)oݾVG̀$%%w}⹸j-\( v]?LLJtvqW>,((psw{m~!&-3B8{'Y9ٵ55''ȩ TptA:lՑTixsNrl2qŝMh{W9߽ &&Ʊ1e>"<5a_z^}F/rĘ>]:Ћ[NN1y99Osv"_.u{wNR倸; ; *B"qXl_;.$")()ViPrsQqO #TۻJKHIJOQulG|I A!y UUe> *BccCzZ *T?)2BY8ĝdلW,q$|v+ݧ]1knDvVfeeEYWrVŨPns'Z\cm,,h<; t=UPݿ" peK߻bܸa}vvGK)㓐Kiv{PP@]]tVBUWVV#w735Ao#kb( sR=FBJr7lǃ AUUzz<^xY;dbvŇqAwwDwDCC-Md)jI[1Ń=(E62gޱ 42qS)>ol%+t=WY5:@ȍ8azL1xZ,q/@ dZli)l;;dz::|ڡgN"BC40EA (i"H-=lqe1 A8)gʘ/Qă# uä$O#R2# V`vPxL-c췏`v2۬gG#w:ࠢq)xqT fN,OX5%Y4CI~髎–˜V੾OAu1{$;+hYPG!,sky#%FqĕYB-ӱdꭖKe&!%I+hZ>E6ay/Bef NMbOE׶Y$q']Q1~weŇR;`zv;%ќדw$Pڈ;)d9k`T/yFeZCo6:ۀ>ė:b>i8*L L=sb|Αw1t,tޱrg%fIIal=H9p),t;ͩ_WwOc~n]M-ñcm׷Zρ~xpٴ1?43sFq MYbIq#ߊi㥤5-ob { M^KIH%mF+N:[쾶˴{f';/B$,.di3;t7/~Fa&Kygo~?CO=gTL+aV)NNv5WƜi3lFQT4^9&^Ad*5YBD*;2UJRfŁ23.Pzr4f#x@/_Nn9;>O⒬O0[s]%_}N2M%]ҡ[f)8*\C΁@?zqZ+M`߄:b)N#4>~ }=dĔCF1X2Sx;@GŝKQ^v T7bC)m _B _JMo{M<_TTb srr"##<<<yS䧫fW޺6j‘EOffW)Zܟ:M~+|?{Z "=_ ">9Ծ[9tM6 ([|շ͟q+ovzGlJMG5{ Rv LMMyѫ=jd0n\G6 wwt^q" Ws51T4{}'%Cvg|͜ʜ{<@vK.'GÈ;;{YaXʇN{|8fɁ_"5y%w:!ƻݹ{[_Tp|vz5.^qw&X9[Ûc _~,;r䅳!N9pqgу͔7bͻ9k,P_RÙYXp?u#o-a;;k+p/V-D7oIya =ÿmN/CvGiոHӋ !}VR,-XL<2'2 kȎ1ӎES$cΆ07=wSe\j]?Zg&{O?+h6k*+Yc+cXܡh;jG)|7_ !}xRWUŜ0H`qvζwvv|ܽkݮ$"3"ݼPB!ތ̎9p0WG|tZV޼z\>[ ^z"4Lty︭1PPZu_&P/{;<= {;+6UܡݿqG|=6&Ǐ^NNT kdycVopYrYv1C|a!\Z6,RO]nϜBHFYaR Q֧o~F^y"  d/o6r~vd~= }-7}Psqv:۟FˮL,v*ĝmj55\{o9U^$.ܳ}( )1_8ĝGr !.xN8?|?Jcp }5>s{ۀ&$Ge.!sE{zv)9; v\ܡݿvL|wpܚڽ߿oD֎ޖVTT w H$R Hә=➞I]b<>Ylm ^h;+wV)jkPP,2~p!>MsSQQ^WWWD]V߈v?HΟ%1`r;6d_0d>o#R@!)4=hzR `ĝ/04Oބ5ru|%8dhMHQ@o Wq'PZqĝrR/ cY, !<[+45-ܟ UA7jɗܓةRΔdu,CE]4l 6=4-:{sL׋@awX ^Bc>l=(dgA)us*;MMM5{ddKmG?|# -͇l A!wVܣNʍ^u'*11LЪ]F ;MMM/޿wmUv]u{A15cb"A!q{{<=--1@򉬿2vɑ'1 ;MMM/Өd u5{w_zYRB{:S}G3+w:N.RdgAܡ9ޠQ{[ӳOӜHD5jTB(>z)Jٲ;wwXV{Uaϟh>~~##*+_<;wwAa7XVyq*(B[.w_APM>^YQUQ<;%lqpS)d6 @A!)4=hz  uqOdz)[?lտ ~LzXIDb F;^#hoD$. (߯YlI ϋ;5CEȝ QuHQzƀlF8/M?v>ݏtN_4'$.-kw ЭCqŝFXq'PʠgC5➊I]|.ߗe4Dwf)X/*Eqqqqĝto#4*M;q' @!qg8/8r}8Å(~!%<#b0+?ίlqn;  t̙\l-#f>0E/skY6[,n2Yn>y,rEmm%'^#> ЭCq^$qG{4NX,΃%dÖD( [mGf z\dFh2ЭCq(33_1GiΛVΗLc{m?x<0X \Sg7pgw;I.QPFjjXNmںZVpÍMP!@!@$b`$}fr9|D c; pZp7>6;j'lH+&ԋcÜ}6q45NP - {d?NWjupQ;{a(hKy.Oҕ^kJZ/DejkwwpGwE-g+JxOkR3mq)e-_xupQ;;uwjpGԚ*憡N S/Ϭh"s4Ui s=t(RYU;v;w;w wnkMv.IWU/YsnݻA^0q.AVsܱ[wcP;~9pwcop/WP8ܕ%;ʮN$;;n2%d'cpokzu<56] {Sa,usv=oo T:we({TXw̸0Bף.qWQis2c JKK݅Q]^6LY0E/RWS"ۉ6yd%"Md  xGcABS oKdPZ$fɾq=ctI[jo=cJso(t= ]z~p'!OP*j骐H38(t= pG=#(t= ]rP+rV ][}9N]A9̰hw #iln!x4pOu̞rfWӮ8Su bq9€-(TEwpwp $\9jpGwpwpnwLX%9wlߦP䵼?-" /$[XO nYg_/}zH+39bXk\,g>_w>q<Y8}a wy߷,ܩVM44&;;no:L-;Y*ܭRzypdvy%|˅EyeE!bjYd#ă)|cO|7T+;<-)bZ)N2Eƴr- c;5KZܑ w]~UY ;uw@(a|}/s~NO7T+OrAwwvSp7^UpGz6.{gU_ 8;ʣ (bmzϮ]yq~;EOъ~2+Q ;(; {`c 86gGSD u[L0}߲Pgw"VהE55{ۘ)< gJwpewp܍ #qoc]굖?%|\d>>vL̸;퀻}ÝdʺwAs9=zK縗g4۲SZ9&7ҟr}Ӕsu''wpewpˤFnHYZRVD 2@2. XPbX_UF2=|fJ.];[./Ϭh\"s4Ui s=t(RYUZ) װ$U咫g22;;F;ISi/%ʥ<WJΞ9} A: qR8ȸ$%E9 <2&Mi"y}b$_ǽu|Eذ_󧗂,Xֿtq%Ue9ֽ4W 3Wm t6w [^] &Gfawpwp?+B]b|VKI%ew&78#/^HHM Bd YLafI yYYdLH(H0(SVM44&;;no҈rcA iu35jfpW$7Gv ͒E!DR\LL[qH3\9; ?; ;;=NDZ-- HmRcQQG\ e2ɘl^펍jM/;z3lfOKm[O9H+;w eʖ,"[KmA2pjǢHO9y˕x%Ij37GuuI P;bp'j:zxBE8~,h:w[GAb KJ 32CL …j"/=%̸c73$%ETENcyHWq;+Vr+wR0bLAAF)..w(t= ]Tv]lB{Va;!} oڱ}[;Ww"ݫ'6- {-Cu-7_kiwʥcdm8 UUnV6!GQ4E6ܛp?W^!Cxgٞc6'Bwla[yao,3-e_x{tCB?^:%dG~ہ :%߰s X)>2%1pLJo ?3Iޮ/ݍ,1pجp5 ]bF{ψ%p/oL.Qf 7.%' |Ӈ`N^"I`y,{B>>fKq+p?t&ޅ4c9b+~j/~z@m۬xcmm6&a aߟpn2կghJ/d^\2cY"d9oJ6L;qGYU퐁DS{%Iyd41WN+6_ qtSol}m47TUx [}ݩôipoԹɚgTA.\Y0SLr'ԼS:Krc~ӊH ʘ)yd}h ۫H5;^O_\py_R~ɽ=}'1%w~ {mg9"6*ce;wt= pp'ΡbsKE zcwވ M~Ӛo9pjVC9;LA4KG X}o6ȋ n5qJl^p@wW7c¹ '"qK Nb-7~ofTv1-:7]pܭw{no 1bCϸ[:5ߔ#L[qo z>G$3}ID_ vzLBBܪ_} W}z߬^7{֮ꕔN]W^9՗~~Qi{F;Gkk,kǽ1"D5nA%; pw&TpmQA#i|N{it~qOWD]ط/;αT\,._p?Ч}GR?=?qCsO19ǽUO0})|>;=z F';<Fsw*# |zʯؾm2vow#Y3pdvy%|˅EyeE!bjYd#ă)$/98E'2UUkϟn|ipG(pwPKYLʂ즇^~ƫշ7&D:bȾtW_-;UF̾op֟?}#i6$̄ o!+zO]hDSowc?_jwWgl]̼cāN:x'B۪y߯Y2R*).j"g=eF2wiz w]~UY ;uw@(a|}/6Yopol2 pVB]l|Vpȓ.$Lj^ #Rz=) _~ދ]! '6ǡF0W Y\xV^vyK>V^=o@-49P/*֦W:Is˵vMFQwG{^o :wѽw}&lLmZ:٧;o19 ۠g8#RnA" p/9L}U!dž9lhj43be - {YF]|+Ni5!w kkxwv EY1k&6[(;l{ФV3pWLI4̵^.+hkw(p:$2*ԢpEgfdI>l@:]d D 0R <7.22Ș4!TUyy$Lq9e)Ͷ,VΧɍ_NZA6Q(4 !Va4q eTN4> ڄ{r⅔KIɐ:܋ IrPց{Me{ |F]̻v苷],U-vs_YѸVErwiy~{06WQV.\i 7w(pܥTw]B-OC)ۘq/.*$1~Zkjt+†귟?heyƲ}+җ,CU9ݠ/N?YU#ٛm;wt= p)pWʤT0U$)U HB՜BA+x 7]%faE]1t8[bpG;wH2#LU<pGݰt(z#vs=/eR*ԢpcMr#99%U3+*bD p7^Uܹf4]j:MoE&ʵ(Qzep5 1܍WiwY-U&9}A:廭ýTNq/1:  .O'/?^V@ ,1L,4X~_j@*2Y3pc)<.!;dLQӐubc*+yyywd%"M(QzeoEM#dMZM= .'#?O$w~bww46/uؐo׊ű!l\?7T=Ov1}|go='5`;?6c̚U?x9_֜jj^NXw{e$Wt,>pw 7G#006z>ܱ 95;ܭ\閉C~>{Gr Ny+8۔KfV5>KrCw qD{@؀ 鸹 'PI5;/gA^yN-KvT{fn0쭿c'߽ggUSa2[qu7wFO8cgeqM˭n4>ōLcM˜[m|ߎL5{e9E=o xUY5Gp] Zc<畑tVATlp/')PQwaSp{܍_k/e .=-6F"z+C|I$Z?FyYf#^6A=`Ș+H;s@zsǮs .VC풚Sp46 'jq9?VObqMit{ޠXVLJMpw]nbn(TE j6X=) :9}̔bV8&1Npir)/_y}WO/g/,k>;oGϊ&iGR?oo aľ_(m富<\'p S2IZEFk;JZnܱOwc')+rX?|[\e̍C^LPo'|11lYr&6a|_pt=}TQk>+~9|)ye!ic>pwu6M_3@dMZM<3F$^6 1~Ф)N~;j?хrW^ߋWا;ܱ,rXGn_T HgUt2:$QK-v܋cÜ}6q45NPd-˩;æ;+rTc9sRX*ps+]2 H_n T$'w+lzUpaSp-Y¤r@̿.j f[c~JYI4ʼn ^zyarp>pwU'QU٥q IeituvwUb7M3_ rzTuwpMwp p~&W.B-Zw1rFRjA]-t2d D 07Ș4qٱ t]z %}/ w9̮0Q0lH,l2;w2D.0> wிL\J"˩;æ;eR*;ا;ܱvp Ɛ'gܼAn$^ 'jq,ofʤT̾prdLQp9b nh~9GfQr3}P3ܛN1F$BY-U#]gZ&z+ ]BףwynpI%TAsIdR1jy];qJɯWi+TtC[3(̽(t=gܻ6:Cg ^zYpQ8z4ψ[3Jug2e w6{-T;0f>y$2 hw\.5]b:DM Kp׷ieYd(t= ]?K"[xҁgTp+O[:4iMVxğ%5u>K&pg̯?KcZIYeѽ#{ YY>ՕC>' <$bjYSpZa M!a7c@oݩ4v?ڻSQiML goK,a1S3h~u>Lr.Y|7O,JhtEYh;p$Tղ G-(RVx_Evhzߝ%.QW*7|R; p;\M V{qZBQw0LO:r+ ʠwpGN}wpmFOq!P v9M.˽_?^'pG;;ɍwzpOcdĒ&hwG7 psAm:0gMM1yFl1?rzmՙ iӛmw}+*+UQi6; p<3ygTte$ QL&upWLI1̵^.Jr0gm|ӯvw ܩVXwcQ;wފ:LF TN4> pG f*g4S#wyM1>.Ub7Mג\8MU 7a|yڋ'Հ; pww f|DrF:qBÇPNHd8Q4\FB}'Z| w6F2&Mi,y ۫{EƲ}B>}I2q~+yӋmTd%"M(pQ;nwpoaxc|s+rpTԵKI˩; pwp9p/ϣ7yTEpG;= *;!.%1w"jpoBGJʮތM 5{)9H%b9.(=`,f܍hwL*6]l:DMf?ƠX1] &z4&0Bף(qEe"oP#nK;+nUAXw/5MKL4u9i 9uM<'o[J1_`u#. f1 ]BwtO}g=dyfM]—KbiJrN~jw(t= ]r5}pq9Hɍ &pY翁WG|wrCu~_5͸k};}iޡ{ B}]i zxOhШ,R[R=R=!`s˥*fpgRh$ct9LEIpZQVFYd(t= ]ӦN]O/7m2\-Fܖ ,{>>nx0؅rqȉghLΠ;|`z{]miյ*^zIDqs0~duZw~̸;2 T*B-{CnWҽ:; N cк˸I9T?D\&B-Z + Ϲ~T|b9k =U"!+SiSwcD,c]_]\̽avi֣GRէ쿧θSR7^p{;[yfz͎ǽ5xf\ލFǫ4{iOLcM˜Ԯ>.>9ӥZ9DcÏʮVGNA ܅T: wݴ8;w1fzq!!ٗS1nְEMq3O//S{av;~ #ŁOp@AN}PC=`̜On8]"RM#5F5 }ibE$w}+ i4 (p?q,;* aCɎQήNi uA^36_֔.]ގ>3hJiO.8vU~\. ܵ=#ȋ#RwDoA%;; p;w/ z oXC~ ς;![MtZa3Jj,v=?"үtHF~yt'yw8IB>j+^- yxЂ"eMpVRwpwpQ(@HȧB-v!uށSwRwpwmpGvw]a'`B˰ 7_Kwpwwܹ1Fzw2m)Yj45ad;W,uA86gGSD u[ܜJٽmxцQfSPUQi69pQ;noeڵ1<9%3/*Q]wD$S pW*r%{Y|F-1?u ő;; p; +b,c pg(k]?rL.ҏY> ܅C4̜MS'J[ThD[dwpG]hv]ؖ ڄ{VA:2]z %}/P)~2ً60g]V_Nwpwp{;uiGZwTpwp{ܥbwpw Va <*"ᾦsܻYviͺ[pGw(t==W.5$h)9l1Q7ȍ8pX}S IDj8j99bF2&Mir=W,Yl'{VM44͒&:  0m.RɚgTAᮌ8Wy4w*ĮNjty]U wO*pwS{CQzo]JBf< h9ܙJnɌ:Cg ^j,iɛNTjWևՀ Xje߿g2~9Z4{65 pg쬳gɘvp&M(Qzp.A[po ^y#Ϯ#LhCkq'i];mԪo1zOߣr+A3b֌R]٧LY80pݬ ^ /N]޼X";$EB>pGBףq4Ӗ EZS4ޅvP*{=˩8w_s;{POXq$T3o&:6s3Ί,1\QM3{#-UeLgaLowXu)ӏ?6c<:5rul9wA; pw u',ք;11GL x̌dl!=e7!7\u}/֚NSx|qp8>MwpG]`.m1guuZ1}GoVix[\]چ{Oks|v8/xZ|mi1۹WxjMEݓS Ԉ4~9ZRk{FGmX%k{П{}Pt 2Q;!LJ"JPb# Rɍ2׽lrͽ0o`V=xkhsݟΠ}s˩_Qީ̪r։o:(xAĥ@c sw7ӥp1a{N\\ X1u J  Li;s攕+Cs]$wmnۮoFɴ5% H,l2; pRo w'TŮ{ql&FÉ<#po`-L(ԟ;c sw7gwn.qt1ܕ^kU2z'*"24%5c sw7weSZg4SRoecwpwpQp~zpGU y\6jJ[ERpIx6jpuƎۄB^%OJ_ҷo6pwwLBbX;<EZwY\Th/v/>ɀ;(pApg1sq;zpX=ssL*pϾ/2F2&Mig;sB%HT+Si6pwVa49܍?ow6* p6F(䧧^'7b(<51;EܳDHƤq|fLן7"Q9}ҠX>] &z4͢&etyAuPwC:wY8lpgeՕpoTNf3 z(̸(t=1x> r <Ɍ;p7MK:_pFnAA{%pow]8ߴDDSʛ^Szɞ`:ne;yAP8U}ʠ0Bף(ǜq"۸j2?5yt\SE*SeP8(t}7TB" Pփ{]J? z=仃\F :i]8- KcSHXJc|ǻ_}EFmf7 ^jʘC&l' KV7=WՏO{P;8܍{CVՖ0z6z܈ivʂs.:tڷ}CO>H}/ޗ9Y_ "o /82tɭvYGÓ^SnHDUPV~ϰGYd8Qp7~99 QHt&'+*T>NlQi6Vsy OQmu==`]a4lp'kR*On坛5;^qy7>z ]2b?y3A7 czRWNkit!o=ƢlwK㲩P;(p{%"!1Bfp'>>c܊a'-/9{gf39ܟw^7?}#?d~?np{wo?;K㰩P&pGFC4Y4)Gp׷icaqw ,{ã}8;(}Pcbڂ{Cgc׌W$5섭KWk=1_4cwJ>ϋQlw.Vgy{Z]0yi>/Q~SZ ;wT.b 7 6`xz@n?`,ox Jn4Π 6VRcS^6~ͥC6w>M^- yxЂ"eK掙b_mQqF]"L;A 4;w0$R heP:ETpݩzG)tO 9_ ;:pGN]&QQwvC)N~;j?хr,]ݷ6j; pwtQEw#p1}űa>8 'bb#%A #b qwpG(ՃrH|jL wZϥW2z'*f+ ?ݽ}B;Q\&>d7v:spGY d8QSOm xTE%DF#_!j{wD+te)Ͷ8V{`qZA6Q(4 (eoEMb=W.bwY-U@eJȍw~`wd],Si>FܳZͽO0(K /Ϭh\\8MUnȂǮs +UҮw_x5Ue#[aDOYdwpG(;,,freTZ=*hZ̓Zd@j 2ep/).Wd,wI)ӗ,CU!Lqǰ_,ؒ9;wpw^#cT=N8mMJ=?v )?&`wM<6j7sш9ǕƠ1wkFS,nwpqGQzT7P@p+O[:4iMVxğ%5u>K&pg̯?KcZIYeѽ#{ YY>ՕC>' <$4݉?O毃_/ǀ{{C/`g!ܞˮ:A n2e )2$|lG|` w Rg|뿑UE-H8dQZȨ+:LWZ| }ZSW+EJY- cƽCEϵz]ԡzCߠ>μadؠ7q ;+dĬsE5X,Φ_!1u~N5}<uǦzL93_M'5+*pwwpwK%"GZSu#c> CzF7S7!7\u}/ֶXmxyAdpqwzpӊwz༄R90 yPUUY*wƄB Qzbpt:ETpݩzG)t>?crlopol2; [=np瘅̶g޺ 8;&G&wlpwzC{ ?*P;[^űa>8 'bb*GϪ9; ]S.qR&zeV̵^.J CLmJa *;;Ϭf[c~JYeoژ;B(TpX/l: "s4Nyw\?mvpw ;6P.2]z %}/ȋjecGwwt= ]<ܻrv˩; ].$TE;;V#Eך~j bPz*YwOpG; pGaW;_cp'uAA-φD,$ TwM~56M]*o{eN齿VZЗ4ۘY_!d3(̽(t=gܻ6O}g=dyf'9f^/ҵ2pGQzc],P6rVo&2z\P=w]9eM3nA~N`_wzEªP_Wg;?ޭ&uѠQY6N?Q]5H#pQ8(t=n+θn}3pG]! 7<ΏqF YױxNߖ_]b׫|OIG>' KV_ez|$xi-7Tcpj8t5 ]ѳ`}Z= + Ϲ~T|b9k =U"!+S.>9ӥZ9D1A|u{^;R8Dyת!dpwpw.|*ԢUq+ t(j쭟q\pzyڻ>Rxޜ OvC;;swpoTt޾q}~>lx(1u~i1m1k+uÚRv֥+s헇ҝcͿTi<-IՅ$oQcOaX>' Vgy{Z}%A`Pt ;pP܅|[^iY26 wB 3>fXz~Dׇͥ_}s鐀|>*vz59_N;jYd#ă)n ;;6[C1fQnw"*w:pwC6p\*")p8]W=o@-49PNB]*\pӦaǡbwY; pocÜ}6q45NPŀ;;6[CpGvwebZfY\wpܱ*; p3g4Swpwl;  n3++2-NS;lw eSܫuɐdpXֿPW/YFuܩwpwC. Hx\jpGwGTpwp{܍|pwpwpGNྦWקkpuha 2wpoյzCQzfwAnAA{o]( s9Twe~o)m&_ 1Si/i Ӽ 2wPze3] wG3{/KS'|^"; pG9 ܹT _}vaF_0;M3inV}37/{S}\i zsf>efmZx pGBף(ǣbCC~i?w;OYRSW-Ziw}ZYk=E\U;3_]I<1s0sNBIt{q컣ދ/{6 _p7R75>-}S67cOށ5i}78fs\f)$9Tŧp6>ʔ%3,KFVX#iEi2tG2]j jM37JpܻR{ůCx)۽.]yTrW\ݡ%pT3o&:6s3Ί,1\QM3{#-UeL_!eLowXu)ӏ?6c:!١ =Gss?r1;?\'lv} gaˮAƀ;)w/xט>Σq4ήw tumýN}QB'qZU~ֹ>Zf94K(5^[svwk]+3Ȉ=bs9~pwpw; wXZWWӟP ̪ς{mn9tZ߰opԖT+J;U>!8 'bbr_ə :^TSYꚚAppwpw;wO"P;[\pW*r%\?83 Qxe{xe)Ͷ8 p)'ON9;;=|.ޮR-vs_YѸXmqF+=4\ru Wԣ2\Upwpܱ8|PHZ,>|؀Bu@wWd,wI)ӗ,B-᾽: ;w"); p/;;npo;;y$B> 5>]^R .z3 ޞݎyt=$wqG= ]BףlV]PQ=WnAA{]8ߴD[uO}g=dyfMe{_/}Xjާ׵LvjNWpeX7"Ԁ;!GQ]wMpY翁WG|wrCu~_5͸k};}iޡ{ B}]i zxNj6ϟhШ,RfO>zB=c/N6q{r1(QzUp簨X}]wcI 70.sZ?x/^=\=E+OIG>?KV^^cl1>)WכCթ 9VIVCԀý h.Z7h,>QC}7T飊phL}MήbXa=`%j#>M,VcfyNz %OzevL縧uߗi zQs0뚤%T6=2!t@X>p$wdPT{3zl=gwӦa1[wYU;7kv83n|42<^MXݳ M˜iߌ[q\|r+aKm<&sf*R=W֎MdJm/ǮVXSSxuX)pUX,'EEý$:d#=h Z [,Jw՘>nq OV(pQ;npol~Z=YP,\4/hA6smI{WfKu?9#>l !j"~o~3V5 27 1Ep˥gOG?Ϟ$6 A%U?ȭ*uܦϫ ʤ\}4; pww| ͢B-Z(Tភ+'2?u4>6U_NN<ttLjSSˍ/}zOe֪땞8wD6_ىJ*w;Ť˩‚kW<~ӱŜ=!>g2n(W5m wmE?"/ƨw]Ʌ}Qh8C:'B^^+/K;t;_ukMZP@ KY"" TDDuGFQ}#@X $}'d턬;{'=`͝*9IӝFz={xr2}a;qCw]D<%a j:>lBI?~PNcddgWl1&qT7/9/X<@mD ~9qCw;an!%wnh,:_\۩7?}gi/ngߝ,Aw;qC&;c o͘I]W>+7xȌ&~3U&P \?sVdmq;⎀C!]6ޡ%SgWwvN+kڟ^9.no~3 ⎀C!;}G;;u1B'qϸv50z>grĽ%ݮ-mAS̋g1ML;⎀C,lCke r wYUr*q# s]q6 dܛ7o\ eM<UMw7qCw;755tPv~wO~@ɉ 1qqV*q1dm$R,ZvHpRb⎀!PzJo.  ŽAikgI%mmm55cw}ĝuX kb_bfUݶd9;MnooUkWXiߡy )N,)׼-jCgZBL\?],f;#p ңNc\qf} ? ZE)<]ےɒE_YӜbgf*Ww^tg߮Oj 0AC(=GXKBnOR֪0U<-կ۾>!&3vyw=9ˉu&vyMK9g,u&#)ԑWΑmYj0r-W>Z-J=@g[2xȮmPWܻ*C^p}9Gֻjٛpk%WYs\qC1CQ);⎡ú],3+m^őWm=́z](w̸ٲ5J=T9.Źu8=oԡnI/B!!qamW< [ 53N]uBy]|iS=-eZopyjfQCGGsmiԑЎ2s_H66S|v K.97bٔ5'Eڴ=oohmhUGnAKu}O|ܿ9#9WjwaL,HxޔŇmc%4u{wز|{RU;t;*qG@1tX+LF+ H';B/1!nH j5<'''"qr2OqR_4]rmʦ+!Ľiݕsn~le/_ qǘqG ;1W0&qYY55U]PO0:QV(Dfor۷ov^9ĝ.)*I2]}\E>Ǯwe3^Olס(1io~;t;*qG@1tLwH z'7z>Q ˸%ܜ,B*qk]_O0.= !FֻW8GS1CQ);⎡cŝ0%+#B rqqP]r(ko ʴij9=?z`IswS! PTmkif]xo!qǘqG ;gU*tobTܻǗP%Ž4y:%9]:a?WwYUda1JAwLk f6WnS,wlъT;t⎀c\;KBB/g`^B1JAw V&"!T%@!v?1^svw?wq{K;C@(=ڮMw;pPqm1:qaֆ%N Wqko#;@!L# C~^Nˉ W232 A%EF@!L#,w`3➝TVVDlP|r!wpJ@{/6#~>D"Qgg6T>.4, q#0#PzJPܙVĝl6CB!L#,wX`3 kAERB!L#,wDqG8 w}{oL#O܅ ].3)߿ըĝg9w@(=b.qi5+decINvĺڳi}1⎀C(=GX&rjR1HKf32q7%w@(=b^UUy#<jk4 ڦFJ9ы*rT> /,JђiK7 )>ң3X>cF!w;)#PzسgN;y硣=r'ש23C܇~=) ?m>nO3ӬQ5{-qC(={qˤM )I |џ!(0A-&sW==zOn& {q#0#PzJowwLi"΍M7wΙS ]߉zfgrq;qG` G>J*ϹyA; X8GB!w;S8G |ms}~%+{{h?}Ⲋ2"~b_/V;r*r*S8G#qal Ǫ[bZs㳜YN}* qC!LC͑O3}2C!wpJ@Mkeqw;qG` G1iN9:'7͛1aBr;.ŭ[ wpJ@Ê;cZF&777C"Ætwwr>GK ;)#PzİKNN/rNeCRSl@ܫ+!b01!qG` Ga^ܛkgP)mmmu5.ҳ2_[ *\U͆C(=Gw\FXĝ #6JvVF~gRN;FFL#lK9w(=G@C(=G@C(=C@(=@(=@QzN=6&0\"_KIN;)#PzL& O06;Reggdeps qG` G(=ĽBAq-^&&ēZR1qsrr$QBG"sr!wpGؓBra\.ڇw^90Id݇=LU\\@$#Pz݈;qY%kPǏS|^v5T,p 5D  ylqG` G(=žĝhmm}k^"~VFB.8Ʌ|]*ge9 q#0ag>$2&>\"H$#Pz}qfK%CR6ϱZ{١u&e7%_>?tNZ.v!#0#PzJq%EuuUmV ucxt LnKw`D;f4^ZRTRR~YiZ{]]aeךDƒ/@G,Yh3Q`4 kIM"q #ҒؘMiUDRvCĝu!y=T>`akkﱁ0D qQv0.pb-vwiFwt<6fqvbruΔOLA(_` |'5^{7mtY"pD;=OhD*Er;,IqGoFGRmN3y{z08O?洐V88~;i^[]5ac.a.n|ϞK9X IDE~ݶ(P_]OeX#ITӾFr?Ě=X1s}h@FFIApٳɞT\+sV,4N)+W/&=޹u_駉D)pvw [xkꂗ9;7_8ڐ6OJJ!qOMM>qi_pΝ=sıx0(9243n_R]2'=8r%秧S 7n!X,+Y?Y^%Iu7ׅӻ,%aeO{W=b ׸,&KAE-۳iqX tlk.={6I[߹!PDy~%6#JÝWcRV/[3{W 4Uޣ}k?fr(nTsqҞ%s'ۻ7Ѱ,߉wO?x8que%m3 "<DEF}%4$#N`N i5±rXhf¹MܫTJe_p]{@RpN|6L_uiH߿|qbq=7z+=^fOϗ3+|G~[:|Î)G2:ҳgS=]ywW L+2Ό;Wcsk˿fITpeo\E*ԻX︈{jZʠrNiokU׍D.ڕK崜8qG&r㝘c>[si|}YG~sK[xP#Úu"arܓǏ>tfhni'+ߗ!aƫqCGIw3)$_WJJC|ltHpCC4*j@piĚ};W#V`(~9:#9beG f/_r?Spz˺2wg4f=XzRy{0k?Wf'"77d?MG7O2g.ѣRfhX%N_sDtP0Nnsll ִUeS'9ѬUUVVWWiZ~VQJQR*ehhs %rNeC. M44坷rC*"}}cSq !c_[85 &һDTڼ觩s潧{sǶ}}_K9ySXSlrѰ,_ =6&A(BL}%ѠUDYYש4X* JTՐWU;{z|]PTeʤXO;]Y62Pv7^`/NY .dW\ )=-K!ǏyҸ rB*P3+GG%͛N t.oܰX%RqIDS| Ϸ|.ǖe6_ =>.^(}ur>_ŕ 9|]={}].Ki$bwoSIאĄ8j)֏W56SRf}Ȕ=<Ґ!ߎH@.ޏT,~q( -X 2"<|ؖ eB*O|,^>_a}wIM*%Z(d֖EdqƟeFuZaWϷjKߗW Fm** jju%eFM+ _~>r)>qZ<2s;INNrN;V0qK37kR5;ɟFBnݑ%5;9L%--NNJ1>ћuZ2cAR1#aJ9̰&-6팷ۆolvȍ7jPRSA?JY܍|f0F/C*2'>k߱qB71c+F܉yhHp`mMaDXs6诬PXD4)1A*!LBI)B3SnnJx"-%)*2ד>3*j@pW*ͪf&V"7'^&)2 M# 2nIvfF%iSZ{I֟$2|-i)U>0=ZU~h/N8~sІyfFzJi2ߑlJʌr)gffD_P$ņ] !Ab0$8J ͌4?^瘛ӻL]c \&!U"a2CJjZj|\BݣNZ"V$2|tj] ܘl‚|H`OIEJʌ_ ).* x*eXhw`ÔyeCI9T R:d)B+`@$IENDB`openMSX-RELEASE_0_12_0/doc/internal/vdp-vram-timing/line-speed-emu-8.png000066400000000000000000000245221257557151200255320ustar00rootroot00000000000000PNG  IHDRæ$ pHYs  tIME;!(%tEXtCommentCreated with GIMPWPLTEQ++Q++QQQQQvvQvvQQQݙQ++Q++++++Q+++++Q+QQ+Q+Q+v+vQ+v+v++Q++++Q++++Q+ݙ+++Q++QQQQQQ+Q+QQ+Q+QQQQQQQQQQvQvQQvQvQQQQQQQQQQQQQQݙQQQQQQvvQvvv+v+Qv+v+vQvQQvQvQvvvvQvvvvvvQvvvvQvvvvQvݙvvvQvvQ++Q++QQQQQvvQvvQQQݙQQ++Q++QQQQQvvQvvQQQݙQQ++Q++QQQQQvvQvvݙݙQݙݙݻݻQݻݻQݙQQ++Q++QQQQQvvQvvQQQݙQ[TbKGD%IDATx]G$GĚZ WUfk!/ANAK|C y jy jguoGV׀;2jGdx8x=裏>#w}]w?qM7on᯿?o曯?^}W^y{gy晧~~衇|~wq~_wu^{5\o믿?_|g;o[_~^׿SO=O?q~Q觤~zI~I맮~o~Zttugwoo, ;|1v16Y|7~"Ex ~1n9Ns̰n^]e0t5iwk躵*٫Iu]9^>sL ]2c%'n- '-P`1?hBC E{($8_\Aw-'nRHOf/o!Z O-:!z7[H?`7AmXO,{(`wAS eKl p v(`KJâ 3>g-'sN6CuП!+#$bna*l>V1 @lXrg1 AbaǘNy*x쿌n13}2JL%\Ae gN5`?q>0ax1lO\p39^J58T-!=s*1 eY5 ČSd >;$7ޏK0 ] (Y@g p@c{r]`Qx .[ 5߃^d~^d&p׀LvD8,̚nFbT04oY !B +R [>QHP>gBHۤcX)$() !1x'cл*!Ayܖ(t YB[BH[p{ѲҬ ПXTIHPMTn5S"$X7 !LO^='$()-ĢI S)r%/ n&$ zK"Q8$$}B/pПXHyH@H*j`٠?f!B=+dw./˿dH0$"gխQ]WK!$V= -S' 5 e$A!$Am=.vs%eM߀ F$$ij+-$?+ /B)6%ϱȨPP]CBf8&~*1e6‹K+( vfBB{Zj+; Hb{ՌYЛ@K#!FxSVrd7nc]"`?/ VfO,r* l,eo&%n3"BN.0 (k0aet6jPy@,T0r!J;RՍZ !q=doJ7>4nhޖT6,З0/ȁ+.*-z"5N|QK"UqW1S[!Ǽ(m:ȉ?`B꾼o FnS:a`n׋6reNޑv 0R*-a睱 =` q :iI7ܶ7EyʘtÀ:s`%8suP F5Dp *[̆nƭ1)o))\!y!7!IɺpP w7Jˠ%س' !yhb.SK!$A (MQXB`x2IUA+׃t7)k_(1GP u7)m]\ Bžu1~F$JTp{A;N&z ϑڇQ ҡ*[!i"ՙ0Ԧ[r6*҉BӐVM- $KS~UF-kθZم}r͍Ǡjpl߫r]BG*TՔx;LuBڝT><@*xAg.NH9I04T '$ͦxohE#7\) !M`䭮cDsIxàXE pK )!\"ٿqP*O!$Te#K !`ēd ",طӉ0 nПANN=T0rn9܂=Җ F< /O`.S vR-}N9$?x˭9kZM oP!spE<" F}H Dt+T#'!?!{z'x yN;HD"OB|vyeM$iBӖN6Kg ҉'!?ظ3 |)_k !p:vqBȉKB DDN<>U'! 6 %K!_YPmީR VK!D8\*}GTl*^´*$4 |Z! (}QxbɥX,(H|QИ V@Zm6(;OXv!_yyqr *ťƵqB')./l1"VQCǓy R1()k+k3l^$SEYKoIqVIJKG !`l//Q 6T̵@ K~]@vIܜ~B)y^vPB-H N2,zBG!D+rRژ۠ sPMQiM#$]?$+0^A!'I?#,rBf*J>UMqWx֠ F\rBz>z`/q+Do#Sy\'LE& ŭ\*ȅΨk;q)Bz*bCG*J S`rV!D x$dsޙBZ{0B !6!STp{4IH#OX!c@!D'DB QqG$(sJvL`qo;l}9; BZzQI pҋ9zB{7лNkTp{|w/<,R.^ҜġS%^b vwOpAPU*EXcTmmZ 'H3*$H.Mxe*إ}@m;كe*x+!HdJOt_` ϢV^0v !`\CdRsT0rQ?XDUBx*9u0PI~ ,e=B@#?x]P#O2REPmw9B<:b F,[ z=gX%4PUNw !KSL !:<&pi"#rSBlSHYJZOҩ|/àߟE_k5_Gص/$G.H̫ѿ7Е?V+ g\Xiمxйr_ ؃^_+w.ʞZOSU` O0+>z?p{Pri&h3G8ڼսOV.̓=Lu,Ee$PrWJj][GZ)}W\؃qsL0XqeIAA?86_HM&ݷbV V3 WMvV.}oWCbko)f*;86)ZN䟫%QTr(ْ9QTpJp<@PS^Z߉PS0O _n0ťPD}J .N)[, r w{308Z|@ a!, A@|t3p0xTy]bi_QX*xs`ͥ8a7D<p/1 &Zp mSx* %<{ke R)a2:T<lv&0lS>C OgGw4 4D ϖ9 `ǖ`,% ,XYpSp䏏!]*[ | T<d#:&[*ȓ#a'q4H3*H 8&Q9 !z(ZUFa_%f FBPbhA)*BH-׶]F/ŶFpP%f *AKyXH=*8E!Dƽ+0] UH>#kT`0 I:Y9tR7 Åiz*_tUHm .>^G(KT@[Yp#$>B+zbP/`:* Լ ߙEb|$Ms#EƯX7٩EǕmL ,VaÞPO;BRqkwnqqt++q=#+B 5%Z@Rt` NU͉|"/t=IJt:)"+Tp=T['Ѡ Y螂eŘKL! yL8CTpX!D9  @08vPT2>zQ: T0BHsvlHvO~ @PLkP{($H}$[[I $Q#?rwސ lV*Bo;%{ Af5Uw餂= !(Ԥ?d>bѩ=uj&]!A-TR, !(䤿#$}B#"eV @PzHߦ,9) *P<" B !5)|[ŭƒjN8YXaALUb*XJH ]T0 N PR11iB*Q{IJa^%  &}"R%&~/hhJLx4df_$ c -TpBHuJ8rύŠXh !(]oqsN?RuӲ@w1:- z#ch Av%7.$(}Bt$,<>b#*RI_H@;t H,}|H@ ЖE)PH@HZX/hju!#K@z"M;$ }Bz-/_<#?$}hP9IZESP>r`Z4yüJV,5#nMa69#)$1ǝWAvϦRO+p콒Ze\MfL:KZ9! #cH5D /b П!ׂ(uIc0>2n+rL1u8}d q {bqdP%&HsYƱ1i"TW&DY01ކcT%&Dw )Ze1XA _s 0E`:eXy/cWf9hW}B2HRs8;#ՈY]_moI>R, rWzG:bη$ ݚ:_ )'S|=#B7vq&@! "čt:@C6H7'Bj!A~xߺ("[@C5?O/ʢ#c?rD$ҝnk>2FC oH?>b>`>|ݱcT}LE WE-*>b(^hz1Q$$ E3*BυQf߳CڠX)BJ(a“CX4 !pkM ZEІ >V4}sڎL, >r-<d #QJRS4oc~@58y hAC[grG#П[4囜yineU NS$9#L !씰:X *AU cT/χ |v;MA&BnǹyJJ]Q;"Yԣ/RYZ8-Z>bQ RvA*B"J*1,unW{hE%*xRnW}E:ǜ_ob,>bQ WHE[}ĢVw?ƐE*PN Mu:Bq >UeR@`{XU4šA C1<*-Poz >$B'%}+Q+߷طQH  vFU*cmH  nV(#Gm/@#c0RB RH 1t(8 k-$g6@ I.DC@#cpQ#J}iP@QQ!BiXHT $[@"#lBPJ9!HG,8Ĕ}̂GI!DtFBAC ^GS X82/KER4r@MO$Ҙgu%F|hB4fq'\[+ri =nN'QwXH}Šw\K˩9,d>cC6&. ᠏X&ʕDո&k)\p@ɒ*.(/rVIH XT eLJ!DWQ9BWpXwsAvĕqϙ" 1j+(Y}d 6}(ƈ1>2F ͨ]'s 딥)>Q-ecșB26N8gDuWH.$Л/Xl, {Bf1T>br^3s-ՠX$ z@^3f$}B6wnig*1zr΍A^ĨB_Gc!$ 7KSSC|!G#^R?o#^!ć9C堏X*b_@=! G,"+!DR}d#~BG!z67d !$7$0% !h)%$}BnA#\V6 ,>bQR X}"U!d^ު!G,U)$0qzYBkX+B3ΐ#~w W"$W(HB<6P㛞X}3+hĺ$#S⃑?b1*حX}W[X#B@*,v-n/=e>ǩ`]q}La0"3R@ɇT]}d v`#q18s0" }bFDsW@vG,i1IENDB`openMSX-RELEASE_0_12_0/doc/internal/vdp-vram-timing/line-speed-old-8.png000066400000000000000000000065651257557151200255310ustar00rootroot00000000000000PNG  IHDRæ$ pHYs+tIME ]tEXtCommentCreated with GIMPW@PLTES//S//SSSvvSvvSSS///////S///S/SS/S/S/vS/v/S///////S///S/SSSSSS/S/S/SSSSSSSSvSvSSvSvSSSSSSSSSژSSSSSvSvv/v/Sv/v/vSvSvSvvvvSvvvvSvvSvvSvژvvvvS//S/SSSSSvvvSSژSS//S/SSSSvSvSS//S//SSSSvvSvژSژڹڹڹژSS///SSSSSvvSvSSژ;#sbKGD۳U IDATxU EP : :"M@ ! AK Ee (HGR>˲,[sf ;gwbnb|} \S؅R b;Qx4kQ^4rxyMn'5g4i~B.KrW;瓳8\I.N$7up 9+EO` x}Be2\/L6$5  d[12Xg)Md"Cp% H&p$H}eO+ 8R]yIq5Gj`_RZ-"=)&xU %EIx$aRIEr~G ēp&rLU$gPZK"p WzC`UMblK `VT%ާ iO7wpߨ`S<uwc^:Co 9\ Mc 7? "0$ "X0, #4 #X<4 0,,L(l$ " Bb@=@9@5 @1*@-J@)j@%@!@@@HP@(( T q@x P( BU:e2u*" 80( #3CS ;c 3s + #    7'3'#7GW5g%w mH8$p8 1B!BEy9BYA `@0 t@S@Ӆ@3@@s -(ZT(4@hhaЈ@c 55%%%%!j;2guPN@s:y3wP ,@2 ee ʚ99c({P "@2e*zT8xP*@R UL!:T9ez@HjPJX*` hH8a$(R RRRRRUuH]U* U@JV)R]qH}E  @F*T2Uvd\Y  dɄ+ 0r ey e@ rPI: Gu@j rX dD$F侱(@cPF @a 5j y(`sPfPfPئPP>#9eWpIENDB`openMSX-RELEASE_0_12_0/doc/internal/vdp-vram-timing/line-speed-real-8.png000066400000000000000000003577241257557151200257040ustar00rootroot00000000000000PNG  IHDRæ$ pHYs  tIME7%mtEXtCommentCreated with GIMPWPLTEQ++Q++QQQQQvvQvvQQQݙQ++Q++++++Q+++++Q+QQ+Q+Q+v+vQ+v+v++Q++++Q++++Q+ݙ+++Q++QQQQQQ+Q+QQ+Q+QQQQQQQQQQvQvQQvQvQQQQQQQQQQQQQQݙQQQQQQvvQvvv+v+Qv+v+vQvQQvQvQvvvvQvvvvvvQvvvvQvvvvQvݙvvvQvvQ++Q++QQQQQvvQvvQQQݙQQ++Q++QQQQQvvQvvQQQݙQQ++Q++QQQQQvvQvvݙݙQݙݙݻݻQݻݻQݙQQ++Q++QQQQQvvQvvQQQݙQ[TbKGD5IDATxlKI%vدGf@?2{OEHD~j +L!ǜGy*Dm=vU<2w`\cMB8Z ׭^皩0 3`j(+@Ì e\y, ݢ9|IX 4ר֖jE3 D0_e~n pw? Whf0oLd_މgf 9Ňi3|9Z1.272JVee0Ϝ$,R)p.Ea(1A*J-x8#9e(My'N\ΦC =@q~?2B3׾\sYjfF}- RKE *7;+qb{uXQZ'X~+d6?zOvEHiKCDA9 CVt @?M"5o T &ueF囿GFte@_Ovqc{ ɥdTr%KQruD@֞d'dZW X2dQɂ#zgC:8}ˀ㥵ռ%~oXJ)7I]%q4P 0_ \0oj |muz>s s !21py_Û1:c7̶V;̆drkJZ5)0@/>AC!~VZXl`ʌ0nY@x0,d<+kudm[AP;uir9) 0 Ir#ܐ[n#$<]Y(lE/( ;؇ GkG63vGV zei4rCi@)/ pm_ D12/CX̍p ${sZj*vzc7po@V VXXCFJJpAxcD׷Rbz$~ŀܴ "RJ=C3- e'cT8 Z_֏;Q9_9ܲO_b4vE^@-9cwdc2A#r0-_=a`hWVs]ʚxH$CEqddtޚ&x%-oqpv\ZZ1Z}PIZ".M:O;OH8, Mx=SQT"Ð]̒!'z9C1J)`$f5a < Q%i>$9K< tEtlh+j)E{GӍL m#t]v5V͖X9צh*@'S]xNn[ގѮ!!@""Cu@50^1 b)բD?-\|+xrwƠzG5oa}8FчD*~.[νD VCR|*N*|%^`\sb:2_@2 kؘ '9߷]m9H=B(3ZX.S܍|5j.ӽJZ-.QBo# &BfK`:+`dq3j:`e^[( Kmm1-;Bik/WRCez€eʔm, :^?Z%r~ӵR\a1[HqIPoߵFRzB4n.K4Dl|e:)~_Ӡa=qgN㒹@EUiעԞƀgp駁,jgþzd$Q?]ȥ-/DlX-\z * n|l?,/^@w6Yj^Xk=LCG 𧟓P}w-e  3 3& g*7߬U@mWG)$A3_X\g])%&&9HCλ!^<LVRZnj1*_X\潖Wedi x8J8ٴE_6"Ԁ55q2>\\jTEv/;x whngX+H",|76׵"ڢk6XqP vuzx%'vH9J 07|qFHdf(ПPQ o޵tI~u(.k]aQVIZob ڂy(SɩD`Dz}V/P7(xi^2/grw9DSCԀٲ@(L̦gFW5-59@/ZX`U_s*5PVTAF 絙e Ddwqlm4xԤFFLխX]Tz`2谴YsJhy*×MONL}"vj W'0tTc4ӽKٕȵh,{Yw<%L%.y$nB0Tdg'_[As+ߏsQԨ 8{O{~dYE?XbS-.#Vh6jFFku[؝V˘#6XcÀRvښ7075BRWTº$D2܌"|wØ}G/xt +`Pe}-3a4wmvu Wqsrn.y7fqKݙCYrZ7&lm,V%$beK鐜td4j5Dɘ!KdEY, ?;;A3+LG׿|¼˕-_s#ru x h ؍s6)q Zk7SLXGT+5b~^8@$gxo1p \'RpfOpr5zZ4 \Ro\f9XM|,yZ "AIF`y(e_F%op ) '. WϻëD]1rOQXpET>܄s9`f,W^:sz3clM!VJ.lkV@ڬ D-re>~lEjen-m# / ;+'#+uw$FG].*[/lǩ}ZЫ"z?JcV 6o 9jUN\nKOR #&r8;[2Gv͂e|PF%얐OI`t̾; @3Z;ocR2 V?a Ӽ;`7;zF͛ wS xx֚o^a!ɲlVˡUce/ 2ABO\ia!OaOQ4/PU&̣ 抄 >Բ*`L\tDD!cβVs(q?Dñ#Hݓyu/} 8N` (~}7;a}6j~c(ё`nnZv((ZJO!:omqJ2j&'e7gQ!fQ+C-.8zBG_&ne{da?8d:qgD*Q|4Ojpˆ;RC<.|ᇷJ`Ʊr#rA=c@0i՛c^=d[yoռ=T P?Xj!`iW(P+| (1~owGG-.^^DB,Ně M-´ )@2Dfk-yOW|ՇĦpV oq CMDvN7o])j {.EW[Aq]&ĠXsEME]j/IXvq?x# Ygo|C yt$Jk,ojc8ez 5рJjDiem%q CA$%!Z6գ^צYTI-"zr.p[Jy? sb:ЎS2j9OmMMZ{6&c ՖxX+3y nDP-bsƹto`%8hn^/cv 阇dz&qďQsQؖڂ%KϨRȆ?.էRnrpsx !*{TgC]R\^`;f H!Ho5˘Ms+܉Swʳd)YˌZE%S`M=%FE)\-JtQ(;!j$d*Fxv7͇gt\7%-b2;ezJINa(.\"@ 1m5b{"9b(Dm͛@`!Z۶bÀ(ϊO^}*;GTKnmSׂw-,A@p*7r)'Xs ,9VWf񌈞hcUA% ǍbPΥ't\-5z&%l-Su}}YJb[N?֎M!zjiŸ8O5S%>_K.2HO"u5mQ 0@CB1~Eɿ B^#hhVk92zh47Ԉu6e"GeV]ymk'4}>{8|ATxc]Z5# wOBmfMVS) XIiv'nUPox2#Ȓ:)W2zhEGlj#dIO%PT[r nyB d Q=ޯo%OjyↈCm5SxϗB.vrrupEfn%_FߺAɨeњDvƥ2&q@Ϛl>|^ KߑI 7F,'Y#$HЧe/.11/E]P!y%>\LA.Ǩ$fFqѼ m X ]ɃjqZY5V]4S , .W?ߚ?5% -} 3P r~滭.W2c£Eɧ@v:[p/*E(YZ1N9W2%CÔբ%78S48!Q.-Yam* @帍fkO_ժ0]h [6;9ACh[A 󊷒em;e-BѰ VRm9G=a@9ACR6p@m9 3w!û{b{Q/N~A;,'%D\zզ(uD+"2r5h S,#FfÛ4IJ-@h0guX9sMJc%tmj .%6.B]=JY d% G Оw?^`nz?9LE-cKh{{oS9ړѵyO٠XV +f!vhӑ`.=Zq*c|Jł(=yHBp\E+}mKѐD7mvs˵(,]!Ѽ5G>EB"'nC8:EA8i\A }z.lkݕa׋!` !Gtzwn 8.[ЗkH{mss)QiPS 9xڐi6TT| 6dWeS02P0Ϧ[KF t0#2p3ޫ_E-iL@w͋jSlzUM㥽8RcӂvPGP PXeˡb It Zg&I Os?5EO˹Z-[g |2&sȇC׻r} F: Fy 55A ޑuC@Vi<#z.% 8p=[}è\rysWSHMah2[D I;5^Q`I J 1{ ֖Y|F3غL]MS;C ,%^V@*r[s 4Pa)K"鰚 PTj8=fSvXLB8 @G'k]Q1Dq\C.%Xz9mil+ DP9IU zO/AFF_Mo_4rn QRM@| ^>$A9Z}(`736py̒=EO#%v\Tt8<:ƍ&Zvcka/9,m/jӦ$J?j~Big ,vyû;͜n-O09zgroKeIwȍv@%F1<_Ku8$Ա7(m=4+[mb@!rI? R r|!#n4@<X2/Yğ֭U5\ 'kO `ŐJ_&ZD38: ;78%oth]Pϫy^ibI84K*J|a\wV=T+ <ƣxXѵ"Rf>Ue[K)?g81l92ADCezz2 gh 2U8Elk0]l%rSÚo79,ԕqZVJonU 2n"-7G`Z[ܔhKnykdS2jhdžL h6\@u$xs8{ć79l]p5DT]p2AT%)ְYTpZ57&v`E8v6>mFzn٥j{88[c! ‡Cf<$$k;e@;{@2V7 ݏ7 U fp` oŋ$qwkC4H}S\x7؝ Λ% @K//i_UqIY KԾJa'&xy=޵!X[;vLqyoQiyڔ@0E 8kCJcC ɠSIv[<P;n\feUKt !;/KLs,w?ސ[ćfFDߒ*L= M3Gߒߝ@Kcl]obh7#zD "[ i&QSOO뚺ۙ,ҷ ",.7xX@CHR_\/rv<Ԗ Zü v-s kty.,s@V?8>Qj _eܪ^0۟n6 Ha'zJe뚽 }1Tnw-T.v8)dhҠ5j%Sk- -Ġ`IQ{.8hXF ى `pjW $q9t%P;"VJ+q͝[zA?Hlj0qserQwvpY.,(մ`D5DC|Vmeh烶)ADzQV `XS)$`A0d۟-bKCWI/GgYs#8FΫqh%eHQs)%g[2(֧*EO !Z@V,хncyuX-Z.ruKNc_"v돔kV kRXcm,I#1pk"kN]5@0ESU+ ts7UqvU.EVv_{榢"¼<{Ϋ$hL:efs>[6l/`ݐDֶ`L35@ glǔnG ` ]Ջ'_5qpT S߽0swU(:KB&o`/&"YGk eQK3NvJIe@ 6&+]uʎUJT6QӍ)<=^O[?8aht&y|.뉗 [j2:Ѽ2zUOȕ-T<~pg*żr㣱j1bj:DR޵=괷Q/Fos fuSQ0C RǔSM FI,eI\p0I Quɬ㶷bܥqm <]c͋Ć)dbsjN\3GnwZ*+z " jL盱7Ի|\ҫ0ӦO,qٮw(+vNȅxUOR[O~u `jse$jyl 쨣JS"i . sF> Os}JiKL 2 B|زϘ @ԪZk /v@y͓Y%`-1UlHG\ >29acIENCK ]6 _y~/d8%~hE>_:o <ؖs2`:v-һj_2H2kW3#yMO]|JC#NdHmx(` a9"GB>=zܴ ?}^a *B;R%:Ȼ)b~!z3W/BK$LJB7V'e͗dA՚Wc&*\+´ib7Deb4: /Lodꬬ{pe?*ٻa%?"0ybRXc8𡛑i,ŸDV}Oڞ*כqDTIܘ[7CIf;la掠: {ć*;⇘I v<%*k?fÊ|"=dڛ'W~ u:| zy7/`b#Hue,LI}VAE{/%ϟjGQL2Jz?քćO_xˆ"b3- G- N&$F٠HUY&/G\?eNI޼z2H%.S vL˙; )|Jzc>&þW5'jUjZ{ ${b=/s&]Z߸07~ԋe8W,)k. ]Oy"im]{JuۻH(ƞ^'& >mfѓVG `IO$o4C-ux6i`Yӣ\9R-V O}8,ؐrW,-P[(̛ 1)o oO-?]7rRT_=GD}\?K78A?F12-jOwf2ufܰ)zzSvGbr9g۽2 > `[^=XxMiwL%#_H5֫uJR2!Nz3vzr?DvR;Yez~u:-R?f62.ʺun3o2dcXO]%+S7/uR{пf߽שCkvUGm<P2}FL6n*Ԅf]JHo$qy\fB)aQL DJLg:*mK u^%+ vwsQwV0Ew r9aް$˶ťfh|hUstL0t*'t$KD%@i-6{=U:n,(ߑR͝ ToxUu껷:{Z<~P xI﷗&^i%#GtJvݲ?USGq 5nO3B $(0{ag%@|SKlf"U1L?v|e 0L!{\|иxfG}Pۍ?`Nӆy}QVkǗ ؁)AR=Gc)#`$gOs0~/uރI"ŎNy$˾ $)$/YaI՛>ؑ#R&O0#zu0n% BڑL3Om@QAd2Ui}=Lz<#MMTyD0!`= H~4=8KҦH4A ?*),9 qӮ /^-' 2b^GU-V>^qK7:vdUXCSo$L8_z=GՅ^e@]j$lx_\ڀ<퐊*6a @) z~nޚ_A |IC&߿Ukx` TvDBMG2WKᖐ΄mIp)&)W!Cn}j' e/߂ 3&doռx^U b&U= n p3)n)UؚW=$<yQ!C+`m*SL:Q"&0Z&VX Lj!-6Oho0=c0W2|k t8n-Gz̄&yMa4"+ 9:Y'$`\v1?̲Yʐ0W'>KE@P;|o!ES/oHHWkg69!Fh:U)бXZVOG@&ԋ م~U=E`* 5 e<*XqXȱ]/D|uQ0‚`t~pcnR3Iު3r!D>JKܞhXR?gC/O6oRV>6Ugv\]E׻Հ?M]i>t֥>ȑdl!yMW=fTܚj!:#U߂q혂CBt+fFY2Kߗg5ԜDuB AoP&TGn;j7_G\e\w)|4UW-$А֩2R؄JmR zń1>'ԸY8U>UppP^#S H~Oh{?OjԪDkϼo#'Dt{W?6T=Hg0Ñ3$r +Uv״[I )HE[5ՕVLGA\=5HuC [-UKJ~e4=T裑HXW?#߀ "S v$_=7^wHxuQXИ$^8@-G1z#$oa!l:B %.czTɃeV[Ӵ^ZSGpRӯ))mHzf6v;*mB}Gdj@Xz9%q=wW0`^G`V|٩ e]xFZJB֤t}._;Gk2==>$ndaicX1 j`PSˊ3rR6H:CjuR^ܙJ^G&@TٚhzysL p_ L7(RXּҦS߫cjWG 1Bp=Mj>NiϹsѶ#9\/eo {Ց|m^0 !i7U  f&L:^mk)+:)B$yyL 7zV> zOwIT<ۨ opVS| A$tcIhp\@`+mͭISlszl /p^R蔁ˍ|nOz~)TK )hs/PoϧXyj̱~ߺ]I="GzxVc{皶iQ។ M*N?cسmoH6nl&\-cZg {U#@a֊`Z2=uaCWxbYY \dJۧ@LG^BAeN?>3IQsG85Wx%吂6%s5V& ](22u9%Q]x۲ {[{j ]glْQK;[B =c^fL{_aM%۴񮦳J%3&d<. VE-|P^NY"UzVCЩxߌ7&Ԏm T]Qs 9<;:E*qB kq1cML+&\¾KRh0_]]_ qTI0Ò!4j= WSm0U 7"Rj,6٧VڤlEf|o֫TR5-tiQ-\ qjJ=<Õ?NzPWߪpo- C.>f2ء sUe~ O@\j@QF44f'qr*#55i,mYGjVDd}woOaLx00 Rc䷽}E3q(=U= zq}VD4gjSJYy vK#le(`؞Z¬Զ:41ۡ5Qr]2|W~}bj9ӷUҪTJD;]O)qibcYq^ZA.t~<&!6+qa,ds.q://o`B kzIuEm =&2Cʔ2&ӟ+ Y*h'~u*J"-=+F<cWbףOVha7,EkKJd˵Uzxl$Ⱦƻr]@qo$u$^Yё„ d( Hì!12}Wez5"! $QuolfEe3kJ2 uGU&օ}Q6$EԄf}+$3g5j{>(N%@ rvW:7ݼ2i^J(*[ZzHaK7 _O*b g6/12"o?;b)Jnr3*BH< z Ku:ޙ2v0`f1&EsԎ ɩRȑp\T>ܳPW_w02Zrӵ'=ҙ g*WwL\q6Kú!8Q@#CLVk!9>͚md=V__NLɊS?q5ơ<%m!RR9Mvezl6o>vx좒?*z)yg-ac{W(jq}cmԯ0;`@B ;*${ղmR;K %@~Pll9ʴzd^P/a{jb)$cd>ܹ! d/#2ӟŎA> -:uh)1;xȤܩ$8)Hxdۜ/\KgExl+# 7]Md_Wt-SyY*DKJ4*1w?{_B=:9Xb8Z<y%cdKmdҎw VAoxQ ]+8ВUӹuԤ.`6ۨJ >_D!_x?(^XU8J@EjG$W՟ˆio-%ҼM;Sh; ^GM:λpM0S%,޻JGLoHKl df{A^э״˾\9'U+=\7B4,- 8B!JEҸ< u_Ljt j? Dd2RT] @h&Hh D@nU':O10:_:/ ^2pI%ER*Sdu7m%,'SJcr ZGK%;|2<$aT_%e.E@$-VF$ $gzy&n,ĊS憰)-<|_%60\U{_` tXZ JAR79R.>̜r>twzӒ8&8&iz#ק;&Q ْ4:$HE_ `ڦ̪$f:UcT`&Xý>씌.7Bɜ!=_%4)eҌ;Mkɴ/wצRG*N-9u՗L< ZK}O պRvz@sǸpjWUWb- OB [ Dz]b=1u&կm)J&s#oDҽ?&y!P(ߡNe<72 ǣ& IgvTAK+C<P3'p=NEjwSjB q-YO?5z|\ ^唬8Ma۩)y$uI6r,p^$aY\!˯۱UR}N @9ڏ9&He7X22Wt2mJ0Ps86^Oz8_k$mSH`D%+OG%:zG[TkSSޕlPUXr"5Yշ+vOCxzdWdn)ܧ3dOPƱl.g֎}?Ǩ'ezVzDOQnM{d'ڬGwQ!;SwQFm+HRH~y<?Wװ䏗 ʥ|TڥH/pz, qS/HL-9zq&Mo`M 7ʆgGBC68@nk::*^>-h7ȡ<ڌ-9O$ޗ~Y1j uҲI\Ji\=ٓ鑞$[Z/8m7~< S߁#T :Xi?od5}io7\C )-<'#~]6R\.y'gΧO5@;MHb6~QRZ;_O2!S*+*ْuIRY`xzx[ZW oW2U{n98J$17d滳im28޼-҉lԵzL㵴n5``tb!uKuyu#2nfJɧ[-YPOC)~r] _[ i)A@ :jm=^Rh9WOUtCґv"PnJc>> Q$ P&vҦSW߇sWR+u)J  Oϑ1g0ü}Tǚ/~] Œ~tT<ϐd+{gSV>S95 7cY#2M8LUT.\%]Ksq W~Ȗ۞IszNFn^\ >̲A+\47H0Q5ZS8LOwZ WbΖFunk5jmmюKas6}B k3 ?U*(˟B1wЪO \ބ)hn p;㱯- fșQ`Hk9Y8pCqF_#!L?C 5v WS.hȨ_G_}f JCGgv f 8f &EzenNW@-(Xױ](񆯳\܀Ԗ #>m(ςߌ@v{L+! ў9|k5_@]97(+.>q~d޵3rkvyo4J6L~GNQфɟ"K]9 %!S}bu`OY(v]"иyLq')3D9VbID8Z*F׻Ȏo٣' x`?`1PD* FN(Ƨ'P~%ݭl]EQ.Hg6 l)$H a͛qD({VQ6,pȪa[YRڄ*.7o]bХa ?rڨv2:|ʊ)w%șqu+2r*CԲS/0xtzE پ q!)_ C2 5}U(GR#̌#RG(-Qt(}B/35-!n6625Ѐ$]Sھ4otM6I_Joฌ([w;լXӧ:kkY/=44EO>X?݁vJ$أ n(!|clxcffu-=g׋ Fu' JJv@9]x ѷKҺ2?]1 9 8 eA׏&lWn* -: #)H[ZG\a.lWC0JVnK6ωtЧ?9M0<^~gߍm,y pVJo%}]h_ߢ4"k:fH~gϦ?[1Xs{F q xC6 YoSk%(S`׀Қ⌊xϭPSbF 8`]^;y0a?j'Z`, k> F[Zw Ū-yrF+@Ӓʦ*^e~MV=5kX[|Vӆjme~2ζUmuXVvEp杷TM rܧpE>55 q+6}u3zF/ 0P;| leNniGA{ dЈ6!6FpY a v ʼnaro)IJb sl]{yX{.C=c$~&&dZm BpoctۢV8Ғ Jvgh!ѮKkzeR-ڪec@NXE=t;ui)\PPL^g͎ AqB3hYB멙e,D8d5?ĕMm'sg $F&iZϳ]Ғ<5Ik#>4 Q_\7 zg^科>?K/![5W:Daеna+%XF $K{yY'^"=*[b@P;.ܻ7x i>ͅqe_򾠡(Y1'j'PλJ6wxHYbi;C 2m-HZ?<1!@rdd' IT.Xnx_)Do3QG/fyq@QGӫ D1P pdh+d科Y}~U&qt$e(#nux*Foj,O:6cpWൾ]]e3$ Mt٥ 2^=ϔfFۅ;s,USPLh.|95n6zgbGg9zvH\/@Fb*镽N1*-%3x%Q7笢QFt@B#M=qہ\Oow%lowŜ رCF0jV')y3<[#|/ƟB53vz߇kQFӈbu8M e{vl"4 U[˫.m+o,>mQ a%hVOKB !CCΟP ٩ ops%0%Q>s5g Aƹơ-!fqF{Po~kG jO[m@/ D>(31+QLO ,=`D;ן ;m!n`rB(wו ]qW'YYoՒt}p0?!~B) ve -eqPozk3t$Y"m N@u13|ԆUݢIdsʼz9rh 9:NyFyO$T~~U`僪Q~0H^/F5QUU 'U+=Fex&V`?GPr[=j$p5gTa;>AQ{=>`inlsXjos(! r9f"S|偑D*>>'c +rZ-진A4zmeOnLƔ Cלe">?2/ܮdn2]n]?&24ZJH[ֶp5U:uY'V:0V $iJfS^[3K!QaYLڊr|Q^q)ZP]+Q}Phޞxݏ$QgFN*ryT{F 7(ډ9OР9 ,[( id%s[w`_|'Ƃ14VCԊHS5;&HCۃ%*{rj i"UC'R&֝vio۵mV;ڒ~@Yt:Z">}O/GgҰ%`+x$wDC9@y ݜ" ֓L >_/'O[?r &adO\JyT5z!-9Q(6(‘D^GL 1jJ~y{[ Rļ0E ӮX!;_gHܽ=Q72ڨurYG8dU_ye5Q68JkGak[حmj}!:]3G5R#po[a]8aWsV4(}:!Hq(M)|J?]jL|-G/ƨDV78$Skn GyR$XB'ZMv HkUq@v`#R'vy%uLG ϻDX*`_2yg{@ MTߜ9 ܒ7ՆC&Y Fyl ?&뫏B:} *޷jg|u~yWflI~M@!Eߜ| ݸdENG)rJFd`B>lALX>KMlF&YrZXOQk&Pmtðp(::= @?LM2i wB!+v夕<niW}+02 9W^ZT8/M#C3rՁ3|vYLEe9lf%X0S3 JV0޳ޗ؅tdC;^ȝ׋ Y&HY7zq@o<3$h[4}z'9!\P62 QHQG0% fZ4ֳZ? {1"k5uxQUpZ2:pMrj+K $&Nh P@i7[U 3[ۯ]PPCժgax:8ܪk-v$QVLe?걵jbiB$Y@S:|Ьz._@`+[it+xw\?'E?Pgad<ߴЕ5!<8h3Wiu3?:3 W6eHl/v1@Z A75al _u 0yE8wWxXpuKK+[UV4 +}><9Z;AYqJNo|*Z[Zy.曫7A8D;r/47|(|jaȿ+a<~8kD(ZjB jgWEy#?WY,7Lӻ%<>kh*kQe¬a휷7g{쇘|A s%!a&طa!-m'6 Zg`oe,0q?}kЦjxeYCP`Q&hɁV=y>u$U٢l(KͲݦD3kǦ6|]}O+:þG~O67C!yM~H)I /i#SsN_M>ig<#KրuO뀝Ψ`NI34QeY^o) 8WPKZzm촼z þb('& i.ٲ޼eL[2060NmqXGF ;Q,bJ&7 Wzl:=Wu`k4oBȭegeէVfI䢍fK{$bi߮ϓ&rW_q1 %53? I,> 1~}v11*da\;ckOzBaP~5>#DRyPu;0Q1+M 5ͅ%mݦihCޒC`_ էHXcㄵ:|IBnsp+LxTu@N@Q͆9U_emiMmQnb+]V@^Vw8rlV-h]\ƿ 6?z~?<HLhH{_##gjR1N8_N8@1n6p}?c: ddy߾zۏӾBaNv eD.㟍;N@C7JgE4`ZXYDArۏ;^.Kwnۆ P7(~lGne~[70޵=l5#U4GV2XVӊ2]?nA:SIEϫD2o^3t _/yKoC@[8Q<1L5vݣ2j2 5 ]y v|0-P sX10WZ3A&?BN+a0m5}9n>V>ګ;]qwŹ]t +  :`8ꖁJ9L5M+.chw>߻|륁n/ߊU_dX-O)G=g])\1{Ff5e% E̷Ǜ}Y8@gye 09FK18"1 io  HIzo#EGz;`>-cM6iK;vq: [![^ʺ {ć8ޙVrO=Vg(ӭ\]d_;??;֊mvIm~L:Rc(b5+Sho-c6هH"!G鳄sI'0h0|ѝ2d@Bo[-e,(9r>WXF1Ge=ӡd^a u|$+DKh> +T:LY-^a'!TM$ᆤK^p-B+&,mVkے!V5a5gS b !co)A=Y\ Vf^@g}qE&H@#z #9r*w1NL]Ri (=$Ch={5엳o(T2ae)6S3j-ꀢL~n n%KUzX0s[(Ӊ:6[}QfV~b 0&ζ]6h:2Ӛbm-٘wHm޾bʦc?V+g>YF+H.!z>˺j3:<#Ι^bt8 >1΍dZqW[zy `ǡ8I&P_N-lM1ΨQp[Q×0[1X_GY ^ɚl;ۂn9_%)xև/r{oo3CX65Ci=k!C1C};_s3ETr 2M!C};߾Iwvw]YI@Cb٦rTp08bif.@;G~%`u˯dTJ@G;0  Sn0t g]Hc?ݽm灏.XC2S=lg|`)jj׿[SKkEn[[G>~N4)̿:P5q=;ʀ&Rc+kR>[yi(Z#- )BY!E8F6EksDZĹQꔍ/ZxSDPh`?[M*Fk>3N\2z# \"RȿA <.Wǹo'Gn,}[EPa4DU@ښrSox[ܿ|O ZYw4v986a<ݧj' Vp[٦Ys\k_g11褚w[ >`>Iw ͭVh.Kn 痪e07>v2 4tdЀ~c(S=6Dk}*o~΂* K,o{%l>x]$FXaTFҪFr's"W#%d۝q;“h}y|XXk-qu]"PyNCҧCP6IQe#-en,5F۱9ÝM7Y!PˉY5v?ʆ:KZ );Xc1GsL@ݟ}3\J77$ +zK~zY$/:s( J"#9J5yḫ7F`RX.1o5L7:ibXFZBb6W%,QDPeT7p "V-0F'ʓBQ` X@zc%K$ILb`w{q|eO4N? /ȁlOMew9{إ0.*#34qLDu :Z[ g4fx/j8uV@,f޶b?ݶfU9h RN|Zk}_` NYF<ΐ`R(} ӡۡ]~XQd=X1ro5{Ox{eCD}48r-@e*-?۱(0iDHh8?݀v|-XY?5!*Zfek1 ށOUNṫ|`e/͵cjg_]ǐbNd@_hWp+Άf\^6-jTg"~uwYu5S~ܜ/gAw}AyǍm7펭lXe;)1!!\3C]8T8~yX7=B80.%r s?:HG#㻫qٟK0@x@Leul1F18QwIO,q{Q/ LK۟*,CZXۤ[b%bs?י*е!U/΂X `}S:LuFsFTg^kxc(Q~vϚHw|{3궃n +"o[J  U#s"I 4߅QB %':F(C<ώ-9JG Ukw32 e5  !mw@BGՀ/_JبT,Ƃ? ,o-_9j?ݷ 6Ƒ^o>nnq8Ѳ:\Bގ6UNef˶U+6 -tgX% %c ퟄ֭2Oj# #S9S.{]gCzFg]N0PH>d؏Iα-.,@H>w@wqtTC@wU .IX!eO'Vh؁&/ SENSu ?F!CW'_ 6n~_<* ??Ѫcstym<}&nhb, o7~64!d e? wd4|D+xſ;ajHG: m"f7:LKgs,mU,jv4oF4c+{;9gzӡ_K9rKE>3p3_ ߔ3y$:F*2j8;h~3ۏY[>"8MZl} tiX} v'-W hG> ~ML%|V 9h+@@vvIݫ?Z Ҫpju4ј|z ~;1>/6l>~ 9Fv-č=)Ŋ êhUkk[V5`#@ 9?~ `a75pnʪ/V~gȜPhձnv}oB7ͼ +wukLܹ?PtHG]bGّq`2C8R^lߤxQƕ3Fa9|ެk$qcZ|0=O\!r-vKZsB̤<V*el{DZ ?r|yw2HrajYq.Шp`t VħƣN9=># wl+xxxx?g5d iqD:oe%MїHv0@o0r|\K պkK4 y||ywIb0$myD5cMԒѝU}IWX N d}k1]O,$NEQp-k~~*uj]oh;m)+!IBOeYf|Di T"aCogZB!MPFMT/q19J|H+r,3Jd=#4>DƧ8L% :ZprQ!ugc(H1|JL`bɜ( ~_'Ɨs݌ Bo)Y2ݐMZΚ:'(_(e3o%TBHskpq//U k507V`!띰;_yE*|E"%GЛ(쒦k2}?q|3и̌ Qڲ39@PІD xHh6;.V8' uʼ LX#Z2~er =DU֫ *oxހ'v'k e-˔5LN&ksQt[ݿt+J |+b^ l+%QHbxʽVeiy~$?AWz9DO)4C̣ڏ+%i艮0K@!!:Tc98[sۉYaq?^ ,BÖ/|+lnU`ƭrY@nŮ8vS#h(اrgkl^YVWp#|JKOp<{Zs#Uu h#&ߴ4:G] ,LFSH !}kKC/P0pc`` `x8WH(D$lhl஥ 珻,oW |{[_|aٹЬ+:`QaQw6|/|/ZۂFmw= uU5>;x/ݛX9,ӭ4&xqshbH"C[*бY8'E Ai)1m#I3뀀_04>v2Pk8CnL-A pYyb+,vvޏ2Db8X%d^8[!@+PJ#Gv(M{5_s\<&̞z=*[?q?ue [2GXȠ Ds:3(I]3!pu4n ڡ(es(RuS !7^Mv}rusTK#W$B^Cٶ5λ@[3mtS$V#LWsnJMnlެz\>}אL|4Ȯy|W2uPԿq\ns% % ׌_+vf*>rC>OݠQhZ/+ !F l5P kK9@eփ>t; %`؟79Ee䃵࣢ Y/-&P6r+Q;oTQlρgv} gٜ r^FmeݾY??dg7S>ϙbĴs>>L .ͬSTd%6}r!T1e/:"CԱCه̿Q8-Zw˰LE(&"g5Lp+))`'M'L'bzpYV bNxeC(p eeksn/TykThfY.Vcid/e]l>K3YAYo具e颕49ogd(:4WS5 UyiPs2(Ni!YBm}C(Y6_WU!+s6nX`n79pb ;QTb@GB^s6s^ ˮ4殱tF):sȨ~%!Ͼ*` ^ZuS(i^iX8CamY3!eSoek'f;irFVfК◼AxY+6Td\ͭ:hHS+mnjAvlӭ[ _7d^MsPw?]@2r,\̷Pྎ'ȡp~7gԐSyC$cu">5 HHnށr Xl9@X6 ϼQ&5 kNXts~l!^EZ܍eKV!5kVs[U2^my<|jscw\ϼ%-B|:+2o2𹮁zr?u qe@΂y"=_Y(;uW  /4I"jհăIr+84*~{"ZZ V+[^+l:llhDr2, }6'BL*wc}"H$[O5!rA"-?AWqB <(pƩs8x$d=IfJַq6:!HVGx;2%eZQrb/ZHO&8)-n@[?UVx 5: UMT!cY+u?7yW}ymՀjL΂&<ᄀe;nꆥKRN/>_ ~ptS 5w;djj&|Joe49)+;?v!XYӊWRdiϡ)Q]0#5y5,G 3MwLG/L C8KqʉcVٻXcK˱CFi )  㥝+(V8MSMX{:@+XH!#[ R်׭UT! $>+OBPuigE!KZѢɖ; oݯko; ^`nL+nZxVBi~lYϡPW -V?z*$͞l\|aȣ/W w6GM%z+8"Ɠ㝑sU2vȷsd,6^?(֡cJ?> X?iWSbNJkb7-jR[K5e{米QYE0XFε:Qo-co-t͖;Eo0 V;׾2joL4ؾUprqm{vq}[+$EZkSz8 EZU^3#? AYFw=D#\8lW@9V] 9s9)LEp a >pFR2vF>n|Q 0pN[;&.x7d@]ځ(\ADfmh7=V["NR >5'o2;Պ~jyXz^27껓9ae+5{\aASȧ4Wx\bЏRϙ2=rQe$9e.wZ!h6G/! &GbCop P\5O{HoOw<3 )٭jK,aiFHoNm^c_P$Nl[rEO"SxJg$bh$;sjQ!qvЎ0x ;^pcq;8rУe`zj3X|@xB .e)zMč}wh+"24˦b)G[PiFڞ XY{ mWftlC-8:ov%Z/51f ?lma;Xy;E9d+|tu-h)9#!F#={AOTnRqխ8"y(x;>$5ς81 X_)ܞ"nKDg &h5( Q.Jb< aVf>zE2,!sXޟoOP/ySڮG,m-4}ҵ)V.Wbho5km1]D[ٜ,]CVQ(S9=ԡ!i虑d.,C'Hj #wzkWoe ITsdD{_kDI$x^<9[uQ>@`%F;Nlf |vDA"Mv"rr9 d@ Ж5-?76%[dUoVBYUn48YY},Oƽsc;I&ۍҹ^nt7,ӣ|S]C`P]]ek:KTGd\rW?z̩u-z1r"yV;?aX_G2R>~qDf87G0-c9|k>!iˀB5K[&:2sمk\S fiA$Qb\nFVsBĶ4jrx+x '}+le7DZ2q7K޸R.dX@tK[0t66 8`z.]f2蟐ȮY>#qg)Sddy8XDog〠(oH˙q>u:pv!0r 6TsJ|x2)e` ըG~ XҁpY, {[K؇|bLFR\A3YuayApヷͭX/^Ď~\w@3inoW7Y 5tITڸV@q nL)zNW MQfwM̮) C3?~iH R q Q/и}4@>S)C%T[}`m_,f/8aߌBoxB/+f BGHlUmju;C,suK|Y IuUV ˄Unj4^+~YϚOk/h 9`^i/[K89v#[bab,|V3 (@#j@b2ޥYf.KJ34FBڳG8[v]psY7<08W#|snBdC=bQ lGCg{$' Y"ZY Kf 0>cځwQ)mKi|T`M;, iAG^;L }?g]ߟn5U7'+v ۭQ~٬nnpq ӛ/#BV[2" 2ổq($4܏-dNSB3X_Q{ h(GjЏ ȕ1 \znDOH6wolV`'Fr\rtKEW wTie/peAʈV%%Vg51][^}=l8٢ $q3 Ų[FǬ/7[1UbwSʱ7`+ͣ+ŚO"Jz#lf(vYGsXcۏEL+ ;BϵqCj D OEb}ed?f>D p>Xv+{=ޙ|,`t`M>()|֌eۀk Q&N1/:9V D>%/= QB†2kj5($UCD!LUDsa*wZT-YѴk%`lUݰn`fnV‘b}ah~ 4"w{7DYi>5GM_‡-O$>РCS<#~l'J%I}`ÞvA꯺%yԭKcH v= i>jD}v:>|Y% {ޚf>mB߂C㣑Hh8/R!y+uJ#C:v7D隁 <~ fwȗfm'NyoN͙%p-'`EWkX-s厜r^恤SX Hc68)Gn]&8vaͦ@;|5a%0BvHP$ϴޗ8Zlcv>̭a/)n*>6xHVo7pmDZo;Ѭ|qߚ[&Nal|4-+8^zfT5Թ*h*%>gXVjy~XWow8!,G2ȅČbxD 2'r}ˈ@FzKVk WR {Qjymڹ_B$[ZX6)%+y?Æfȇc Ǩ&f>Lh wEZU#~{{QNt6;ۨ%OOT[_8r+1UkRxBF+_a9sJ >N >F%EΒdb F~E"S쑥R 4F|(XG p:X^5(QFX ԋy=$k`3\cy68;{9?jE@Y{>FE/5#n;m>½lOϷ8tZ_(Yj7 !rSpš@湮CEh1eR`L":3)9pŸ"We|]K<<&Ub!o,7[+xkE3-@I~`,EfÒ];,Wiof̃@[nա‹k%kGtцQ̷i+7͵@~chc# ga܊ƽ_m51d5oy 8M qHൔSïq;V 98˯3y*.ܼ>Rāv+N,x}!#CVu L҆  vk~ZB@^a@_ls . &OЦIHӍ`|Ǵ=ms~h/w2jsY iúH j۶^*Am|v?RHaY$$SLV"EYa@W\*º"R.k:d.\޳Aj *! LE"8%;DO "-_ļu8j|`lf_oSX=~{Elz5LقF9K=覑sw;`%@C ;)džfsM]%h!=9E NߗswXdLc[٣@ѿ_=sA+7ЩSKJjIEjY*<Tdu+0'8K["ݏk\m"Pɴ;-T@C Hh(ma;e5sc A^6DQ`MOZҧTy[rSn~Y[y8X9kBX}|iR{fXv?c sJx Q*&(K&|%-? 2S-oP0&dIz"ܐ=|yt;2.w:5toʸ1z| [x:֌|Fuy5`iܟQH?v/ˢ.y@} R%u]Ed(Ybəs^ 脀8O` ,k Hʼn\L3It,)K,CJǃ(8ɒ#p=\G駴N4pʎo. PG>`bunKZ^(?6x~%%:}:s]gW0\GOd{ ^SP\t D2~L&f@T*LܣN<Ӡ@]8esZ;,y')_ ~.>OϬY@bvT l'2,g>?LlZPQN)90Ϯi7(;L6F3 WۜH>%aÒNV跽Ygۿ{OkϕSU95g.)U:^Yw੐lS+P`w9yq|}-\5! q-422zOvf B2WO&n"_)/F6gRb APB]T)0 "e]g_ZMHC7/!f24:kN[j)?޵}nM o>F۝K6_{QߝWi}f~؍࿜} z%u&ĜZi֙K3W{ hҩԛڜR)@]@8fYہəq!xPϫ &k D4 =^2I'WC#2( [AZmO`¦ meV{~$@ OXE|Y^;L&*jcs߀1)w`ϻ\e y4|6水n"l<ȚGO$裼mETL2տZtT9wH qVCIRLK׺Ay:=ppZ_g\E=6##1/Ly-0JOVdQ9P9@X8k}xzF69W3ٖ߿Z 4X2^8*(nJWCOܼ^=?^W0֏ h[om@6nUBZ|r8(O/;:Tɽm^~09_ ѿ34=CHDbUW@U*qeT{•ҙZ@Q;8qYu'o(s:ߡ˾D9W"Y:  E.1`-I.aH54ƛ(o,Oʁh_ǣa*Owa ލQd ;OV,7c% hAZ{hwUS3zxr;ӻxB|ifgIG.j#uT*X\=WZuSe+@\ yQ- <ϗ4|?&I L\鿷S<1l!`p&0"eMIKS&$nxH:,(L rv I/y }u1ġ~D7`opP73 }+j@Mx1JBѶ,lD8<ڪXeڰǠc^3ĜYF BzT@9LD3ׅu^v+b5/%p '#@([iinM3HjK[j&k{KHX*(HHO͂)svuv2j˅--{YLc78hޅ 0Yj=F֬W(܈+n`7|<%\dl~_9;['^fZBzIN3OuBJ/ZH,W99CW@,6c] kKpN䌋jN=)Gg{7q0ʙͣ 98- _RmVp )W:m5Xi=`V(O{vs1G_(7#}5BwC 5OZJ:ݴ0-YF&?-tI:f锁8l4ȃ57sW㞷ʩ8Ue$`~]RyRq '~\Xbk u+WCl,R8ӣ'<YY5 (]OVАb@8lI49,ї tgќ܇G ؝ :|ۍ);nVh^,9j᫱? lX7ñ C=914` t+Qqrs3wEoʋ1-V+aB̷4IZP#\T ࠧrj:K-1M_9%jc*(*|7B$4qMLyکt,&O) V^dg  ish`zaOj!F>^xWp =:OIY[zr m' 9)oħsV&0|[ ^ʞέ~N>ݘ&V?Dn -y0q4F3bSU"8W:3Rl`*$Hub2~/"9Bdy|:(kߒ3 69s̳&b!ElZ 8KU/J[W/ݰ벟 M}3)v>(O19h*W 0ò~8hnsзsl3"W ȕnW=WTNJ $ORDd!h"]<ϙX'XHЍ;x~r"U*glJSr2N"Ch֫hR|(u^ꏔ%V e7GC׈yf ݾkK ttn݆A4& NIE<~SN2ȋ9 ٜ!͗ߔ -&H%Zŵ4󩀪ywY}<.Loɶ+ET5oɧj08>+'/DeѦe;)Ei_c5M ".1;,MU$R(˾|hFkS ռz!5AvCGy}[oe3ɶ=ukaX%Iˈf/㣓H-KS2Ik \gU]@]Z,'tTgRH0jy 8r L]<ϯД'9'/,xYBW ӮUB%L#f &|/K!sDrR/C0|uݙ +@󛜝%߼,؁MDvK[Gk[=[wf̝2Z|;}amk-AjkO$|m)^^ZPRPZgF%E6Rܩ@h1$b">B.M \=/ rybB03'|! <9z %n{J S$,W ٭YxfI`?e>H8 [t`':>~<މnh{t: ݷf"Ӻ/~}a:tq}afg-cj22 iɗO#f9ʽ%I :}e-6ڏ (Q#ZD)7lj81mQdƔבL&!M!ĒT!_` T- 8觱d"CL›ʲoOn^԰S߹F0m?cDqd{O>uv;yi_[lts+將Vo)r P+J]<3V~ǁ$K™Y-u!(~PJus(pgN)rK~x$p)V[Yn8 Ty"ސ֫?G@ &>^P^FVcXD3'Нֶ ց[op:Kc@#I־߾5vvֶ}{3aqE/5}5oi}Q[/xX r#}؊f((mghᨁ";nsX3WSލ xМj P2I%{HG+Q=ky`qkLL A纰p+d0H[S +-&sd m*G i7V\2.7Ɲ!ҐAPa(Wf e G n[3^`{c6`}[o5'~=zGq.Lwv7G~NkF /yȮf7ABU4rպ,եT¹/:uCIw@_?SYb|U*341=uG9ϫ(`SOhb 8keY( ǝ "oxO@Z-+l0_=oyh$У9~J`Ӏ(yῆmj;:Qbm~}n(-Ak^ק7 7Oum6Yygrr}k," 4sc IcyXUmCG _0rIg^(,$HP9HDXA|!TMYx9ZWyU(ԗOs\隆|+/I0'.R.CGż oik)s Bcw8(Ęa# maLZAN cmf7vmk}{1a+?/-I\(gj,c?ѥ3-'eB:o.˅ĜPByΐ"!| 1 :|T4)#02|a[Z%Q+K}GIv{plAu4Eyxnrv>AǸ=ox{Ynw|{>[Dn>M;IN|V)@zv~^κ{CLQDHqZWL^*cN<5`s yH8usA0; \[}4 mC ˋe<7!ޱn`W_-a{ng]GVO7vƭʵOG|5dj{t;T6Ƿ\57?B$CGEOԱPNp!0/2t_g -༞'5 QH'4MRV1׵8홥CeLIje,/\hW4 in9`l[5:dv~b@t۸]#}t{`p0|8EqNsv,Ty63߽~URu 1o?/Q g1nm1{|_ki7%V[krwk8j U3t+P.Dn]s,ȉ)TȕZ&>d t"e-4F94ZQK>@_ޣ=E@y[: zت'?0J,U0fӷ}9JwjG[[noΓnm5yZ߼}; KA^{J*\UaF!R7m x]+ZYy H*k X2kÿe65c !?];ߧrp wQp'*@]ԻF8PaЫ'2ny\$43qC-*pQn uOػo@yv]e*pNk.|hѵ[ s;`߃yݼ{Œ;GUwU}` ߀JbStFz!_e0xk]S|%"dq4Y9cY}4N`*0ze}D !oKIBNP$<|E8qLe6؀.|J}lVo:4GdBηeN#!o/ސoH݅?oDIq7Tf+QR*yesU*Zg"}>D8+ +WQx hv jZH` ”@Q( _2Lc`M(&Ƭork@m )Gq}B-~:;_,(nmzOp#os?ו=T Pm kx.#A\?KCm*WE@(cWM_ qfL@ \{Ym*=:8t f@GXx$X^Rqa*arz,g4XY\+/:|p;[ꎝksap· /'=(U7 =U惓ݍe<43}ùݼ߿'*d}]Д^I n"PkY`%ddh*G:wkF-<8 N15Q0#V[<#PNBFB Ph%- ( m% B1Dںmqw<~s5nopãWMfFm?>Gj耉~'o~ۼSܹitsUk)ڻ z]O?'] #(.rs<|ϒ(aЩMk1s30nUAD-g2p<p~]8,/)wJ+5J=Tܾ4MDgZq 3J+x kk9 e n`Gx@ 8>n Cxq@4 %{wkI~zxUY. }W o@ !Z=/ 7ϩy3̅|VE D8=?&3+$YU&B,eiQ% $4)ij;LIpttVMd"V/gUnBJ9Ȇ#1؂{ %ϋ$@`*3t5VaeW/gЍCI;;3OX`lZHʯ,r|S}odK6O; _ﬦOqL ՟3+$vl8F6@_[r2PQ1C^͵ݛWR?5W6WQ)?+AƫNTQ؇"\h%R@k28Q@$.(-`AfuQ1 UOoueC$~(,---ؚ1}0A}mكfVK}&x`jbE]g;x.`6DeNӉCjM>cٽ xjT;#uQSshgSRI5 WA 1IM8ZI<¡QiX!+dk&,*05QA+%< Kfnr44/l6tYNƿ_?9;~=]_ǢIM^3*ץr<1h r1}sr.gC欣Z}2@[Oֹ+7Jj $ ."cKDA*7(e'`l~ XdYZ@$S= )X,]fy5&- *^)#;:ɮ4+dFk7ҿ GԨ`t^+a$/L18a0]r;?*o:CQTswrz#^BO'- U HGoKȣbKW‰ߘ8D>Dծ#McCVm4T>Ts<quGShBo1Bs` op4a8ف]zg斀Xx SoA r˱7'b*A,۲iI mf DC6<*ZAЀ[tafʔWs{u/*OmC^)evW0. 2ۑm APl駸<+7koR=9InwfǷ0zEk<|MXzPJ}4z0qΎB}DTp:T b&@# N Z]@! !A-N0Y-mA&K!#> iOCl >bwES &_#><=LF j/TϊUO!"|DsT:Á ! jz  LEk#bţGB=0O!,T`5e wSD F`Ǩn{ǽ]WxGcHo2-<*1018@R]j]?M\wJ;Jq|wQU.6nա~> {9b9;\!NHd`ƃHac{eXl(5z֒+'rH=D0|ãˣ>fzgPg2qv{;leVPj򿒞85<=.@o%MMympJ?HzIƇɟj{$5h QUQ~k P'z]!LQ01WKhN[4_2p7,P- +2 Q,4onNik|Ё;NEū3}R|KfDGo\%>HƻQO\EB~۽POA W0`ǁpuoԏo.GճQu4Wk?~v\( +>#$rcuoAE1_P!-i\STR$Z - )^r6 gk_.m=x, q˖~ }>xŬp.s~\G~dw93D1+X [ 35Mr t(W=DM_Qs*=`ǁ"5rxTITbe^zHCf/m6=_[X1,ZA(f+0onaIܛM\zKdb6ᓑ&GCUèJ&sAo#0@=GA1(K1`RUMٻ 5BE1|UpS5ٕ͸B3_ ڴxuVWs3 H FcxZFG"ߢ*?"+ 82A}u_4{]^uK82 &-yX"W@/*[a 5ك?G?-,i>BҰsZ\Vy߽;_%gc*}{>aF+DX1 '`4p6XA=͢ `@;؆%- jx+2<@($b|ےʣ{@Yc(MK%&^ecᴍ !#ulcov_s']X !%1:Vm" P_mb 8rOz:nb. IU^g Y}wq51sMx |<8*aKmVd0zA*^WD[1 RZ5AX>"Ef_?$1e4wsFZ5{Ɵ5s]>^wuuG/y_TgfWy$VA{*P@6c,pit!߷} egR 2bI(J%R%6,`l!ӝ7FlXF)0b8|2~ *Of81~VڼuR~,!z)qivuɩkp>84y?S\t1qF@Sh 'jı)bazqHU sÇ LM8kcðFі/p σΗ@7 /lg360mN>R:SE[5Ý;L0[n7!äy-G.w|~op7BwjyV Zf"91byP]5aŁ. E!>rFFdOc{{2cP32Zd;ߐ lo'~4sHaۭƘxmpc ۀ#K=ͻ~Ijt$C^b  ߺw * x@!OsT{֌a b"0[ D:eceTo|TjMNc^6iՌLG1S6u\0!˯GmopVf F4opA#|xRSB^Ѥq^&\U s s6T'p6L.if t畐*")>MޥҥҜ&5ޭ9ב$<wxb!$ّBhհϊg!>}b18B4]3osAoH w5$0BWv{ؘTd|>bb7+Z/,L! t &$$Xp?"#hﵻzn#cߣXQQ0IT4AƸ.#v"fG8fozQ '| :;61IX(H_+>piD00_bmSXxL `Ml)B!IY/y. n!&Q7[)y% c 4D.ٻM]15_4MI(,̤«tOڮ]GaX&k|ͲOC;C0fx DACBT%=a4(F1q0|xF# |dN2o'קEGiIUgbo:~;~7%QnL.'54et}Wͱ\U=xh/1I„.ck(F1`b)Cünc.W`$݁%%Ab%3h7ynj .D)A>oXq`%=8"ԧ0wʹ'|A5$n1х2+ 1]?t}ץ@'7*2bcg.ձL>KCok$Hc{%dc J72jɗAɲc8mQK&?HD }KЇO4"r7ŝ3}~G,0eF/K~_Եd ]A9bXhK*Y!^?yv>l&0X bv20X0ȮM\[Y&̯DCroloFiʉc<Ll.O=$? nÍ_ 9ē&p>eNg] >.FߘL\1UA3f0mQ/>'Mӣz~ScKJs։cvv@B].bͱfg>}M(<~R r؝MlF'EH_?]`[R.>>a!6_T9G_b)A6%w1B91}>pAaǜ4 I]@e,5RmA0x`c&! `/s<Gu ɟ&{?+ϯ#!%b= p6K3p> bu8f{>yqޑ 5H3}BR4OjYpL(!Pj#LFj -lg1G ݑ@!| aҝG]eif6*Ol'&/4{y9>fн_0ʺPfft:@Xd>}^_.E//|q#=#_b͸ nKQ4&͸İQ5h@a,!W<:jdty_b;|g8;0`#35a l/c /aYB# wp_2@FxŎCH27 OXLJ" +V>193D;1Az^T<]C-"?^*/2sr~/$`T0x]pթ'7->_ۃ89FN@`RnUH"3_{˪z~ Bޞɂv+v.5P˻oNF6ѽ N} ??++~otkt/5&pvw0Gyw޿l2.vb\)hY%5`~ZOweXxWxʬ 7{V97?lwMo>w|;8Ew0/y<t(hݹ}ݙH;1ǚ-ٿG0?ELUwys UvuJSH5H`aFp03_~vv7p<^'⛽vC}1ƛTg@44Dn~4:ՊhUpR*&]gPw>@B#%{ U` [`OPO-(q3f"HP=*ZA; &7LjnoCsQ|8uX_8QvGm=-lS?a)6_^Liz}*z#t\8\=4K؛5No1:,݁F(>ۉ')$@=LJ3'Rlx2a+m]RNJP{b,3oGSϿ@RsG ` vc> h#/.|/Gp]J!?WQ%>HPn'a{TyhG$GT swTA8l^)$ `lU*CI'$A H/}h`(yV云*e[@eH UڊPێl,flIҗ/6K4aGj$?. >ø.kFC/+b6Sgr<ϑE-9` ]dLz BP9P+lPx9{RZʍ@HPDB ܘZ&I WeТ! F9UdYZ@$ C"YKOBeV&҇Ԭy&Fy\$dKrLmW"J?ov{|v'CjM9ݫ){~/؟p] Fz<>Hu$”{$d1=Cc@\HdcZ2V|lW`K`T酉 ZY,gG [sG>Ƅ}ĭx r|;!/*&mΚϿ1L]K =uC"~6x'-4R~W:pIguT51E }A6P Q] *=*^K+2=b-@bpW*(xX6% GVͪ#+aY_}qB'p*_>^ULo{t>Bgt*&%}ԝS9)>zҁJnjp2t gJA&\2f-V$e^0 %7r / ^fﬦ3mC.`WQN4/t_Rͨ.v/DUSϻ0Fu" G BU{v4KUM pz y'UCUdi'S:T!nHtD&,0@aZ ,Ɋ,@ c7 fXZ 4mK^mL ;*dJcQ0A-nT^f pZ&߻RT}T,>(~\ClTk۵@DުϘgjс>h'zB=>oT; p'l%"Wp-u]1TG L@R QPba`b^  Tli-` KeӰx++ QDKs%ma,̖Al~ _SWxU %.)?qF0u {*:QҢϗܼz'=ywT Z'RUsc{jc_;ZMg4+8&4Б(*7'YrFI Q2)I_F$dQsC X|д$10dn*3 K1*dIm);Y'IeX^YY]vƯ#k6U쒈 䃅߲: TDhHn1.y5U-򣻨._ۡ!~hjA9P#w<g?iD_(«vWwAJ6oO:L+}`E9FtRl 9@hEm,Ryߍ<_`~QY&="X'^qִ$+CD5Dj<EЯ 0k4 Q poցo6;AVr~x\~?U}1hu_*PF(xUhSY`;TE0m[jP&6 1! Ԑ_BE"܁QgY)*[dyKֺEV($2X%`d(d0f(@TbYRikbe27&[yK96.Sr3.E],p1סvicrwo~6j<*]:p^'+GUѣ&hSzD-/3S1]CMD$R,-qrCF,2 @-[vSuˆ72&,, 倉P&<li9tCkf_n4$^Se&8\$-^"˨EUSV~X{;C @O hg_Oo) /n6Ł:,8pb?j.XP1WY$$ À>S>1 8EuPb̆+0-묠ZB1j,%j⃑ !^O&hy呧vW#A0E @phׄ.E};ew]v n\K w}ηXL40OUd}Zw3b 84}FeU$x-wpԓy#\@=ZnwoLL 7L-a# @p^؈&Km [ђdD@ 8Lcln 7|U^Sevsț13Gv/ԍuvv;_8WO1؅MCqt|Tؽ׽Aj~ xY1?P1D@!7SS@'Eyq@2z6gnAȄ!2k_{bh{X$z&6FB~b9WYXܕ{PbkgQ-Hd|SJ/xhWsܿ\ u6]~V0zV?NK= q~pgn8b_?ߏ CT@gD{p?QX3IyCn#)h`Ev+זsiA H_ƬsC6H/hEMaJ`%OL+H-A"&+|^A#_TO[܀2_e`Z&O`.U |9.0 zjv_4.٠#ئoOBatx,4OoaYOLj*#FЁϪ&)$q?Lw&QŸqO~=TSPB=PP]h:Su`j,7]z0 (j8;҂L D\*1Rq3[ e (=P+3hLk[f>-k[0> %衾Rd^ Kܕ{naJf%l-*cH<7f ,^+l F~bJtk M.qټհ ;Kh2GD=q zq] CZMUduO&V*7HoyпE4EA];,()"+l @QE`V$^#zWlypK=рOk)SsdXFa_gj'9`̖0W2AɶuB*x\Dj[7 ߏ}Vc}mpyoR;u3b?{Vl!v8 K8TS&#5oRm8+SO2g@ԶzK]1 פ&Bb"ԳTB2$,b/ȅ̖h]L 7KY!a.VX̖6=xYT.I(|X3d y$+̈t]aI]Ľvs sc`õv;ZV/7ן#I;OG{xt8$s?j:#$?-dMpoy7؞!DA"\-pĹ!+Xtc!*deak,R!hM^2 Su C,d$%!5 -/j5ö}3Yޘښ/(<'02{xIsh&ߚ&ȼOYGsUށO=1iRK͟@~&T:\+ #'Q)J` HUg]϶yĚU 1$!9X[CD*(28KC9)>@(yo PJ[W(X3)d%4V=,Ð-<`34F&+Z^ߒ>olN0oh&2 A'k5!V6P/a^O^v:Ǫy}IG_lO1=,}هu_ uCUpq6{ܹވj ) c3X(YMQQ`BAK$PZ2]݀(ʦEk --ۢ ՀD"/k)Ô0-oj"LSISG9W2eLtcZ6nLՆ[&Fu-CW6ux^nM=k?\wZikӡy%zOlҡ8T&ysP}wGuAx@Zg{9REocV[TEdO]Z *'B,lQ&Ѳ`b!U%ސd Pr/[ %&"V2ඔXOyy̍Q` K\,LYw`,7}pe>,s̈́U6]%_&=2wHwI:79aޯo@=x6{=U؁c/G BMG+GKuHAU':z.ذ5<>Jn fؾkPA/Twɍ2z$&+dµ]]lIw>b ߱{}zR{vI:b)Ñ~s ExyXp?UQ{+U d 7AGs`>=}hĜMki˵Sc EXjQ?v euK+qr\SdS}{# ժ>B;QL,PZjBbV: շb"ê/1q$/u%?ܜ&*Gź':VcY\7]B$=|ꑬndRļֵpo*! !`k"V̰kw?~jHpc,&uMͰkő݄`l{~6%9d0M|j Fyg8`2 <Â.S]jҞoaN&Znq>w`S@3Z+VQixV U*)-񧕭lkH^b=wACx?bJP|i[h*t"`H}^K#^PeE89Xﲘ>q́NyXf* | 4A#844хn묳 { 8Y0:2nOkSxm> n>w񆆙/X{hQ TTm *ʧkP3ULV[`$P}OOD;p7gʲy`Sh'/iY+G%!YsϾI=ѐSSC]޷>5uS.SӄI ׌w>~8tp0Εu>$a|uhõQ !f`Dh O]XG]+Qhi MÑV%%BK e0Tٞg LC|X k ;MÚA4n9I~(J@l6Jp9G!+M/:H Lag>erza}eq L2M=9`ʋ1cuay}꣧ҟ`"{W ! beBL1,+ 8wx% T8t+<š"|łAX4xy:ǣf簬VU *Jj돤fjOX=扟?32Њ5>,(Vr*e n~g1?_elSˁk1:bhu\%)珈%jC=0bw5CQݺFCi.fPI`4a%U| ҏ@/XhzxYn",T*櫤zi74p?)hMOb/+`UPqE 5!oH/ χSV6l~Spͻ9 :NuR׼^=^+s>0"!~yMJx >k3bDw+-cҋӓL a469Fm?UenotϯJPo@V ؝ *|y[MZ8d&ENeXjجg.&ۃJտ&?8Vh(3cdfWyv*+L1{z蔨. FZE]P`6o#͂8}#مabe#E^{<{kgK8jvr~#%L -m0lq \Wڻ ީfa8'FfJU~XGUS5 UQńT62lZ.*3llΚq&r3dCZ=/Zי\:}g3ǯ] O:Ӛ# 1R=m]e]׊C?Ƽ2խietƝX>+@qG4ȹԈ$F:97,U[U:.,$: Pc2Z]h{EBhj!Ոϵi#Ȫ< kvws/>}$ib eI j sz.]CZA1·s?s"-"DP} )+,*}]TQEy]`yX3- lbdDЫb/@1M(&X.d? d(YUH U;r.XE,n@z%' MG8Y)9+[%7}Ő \,Hrf ׺SD;Oy%ϵ`A=EABnIHM \SCÿ.v23[%L< ,bȒMz1Z)3TXŗ`` rCIW*|R]Mlˬ8 9KY P{&L9PkukbA4މlXXVpO c,pdJG"l֧bLИ|T3nz.h^+bI)ˌ&~1/ ʺ:Ё0C8_*D;cOFJ: ˁY\K-f 'RNIÇP&&XY$s,(Wʧ|rb?7K"UblP݃ViT@VV׶ J GUhesmo|<ٜͤ4\>}!Jj(l7QOѵ/͡K4Wi^+e}։0>5rrZ΂c~)j\ xk2(L2fC>΋e"eb/gUP58b,ƔUc*Uu)D!fCI $''|4Tths=|#BuU:(o<>ـUl՚U@V6nV]},_6eX@kB9F`i|o͜Su:{o|VGltVЈ3 aЈq(28c2!5?G॑_@ya^1'G_!LS&_6v27p_  ^T{DePܼd b`M%MmmOjcsj<:*X˫U~\8b] ||氰5֒(4喷i'E%.55x X:F냁sǹ;gz#(N0!`hpXE價ǁ#ծڄ=tse XP+4>eꌁ|wjCH|O" S O󙋂ꤘW"^sVbA,KW|)Yl ӈu&K$Ӈ˞Cv% aLރ1!*~p}ѻ f~Lttoއ+H.|!@ |Lϗ+o̲6@ Bu ,PPz ,G狁$(P&:٤{}@'K[Ql9Ѐ!D lN /F^aJd|Y]7# q_,HdK^2BᇕF6}j %Dc f[J{L)Dao:+xDћg'!` G#Q5ّ}:۝z^˂ :vR~g3LCk{`v`v)a9*< `eϤ<ۦA|'|ҡU% rf̊ j"М7HLTQ'9@4µ?Y V@xvVE2?ih)JsJ [7Ӑ0[~ʪ穰ѓ)3 *֕2޵>.c)}Hx[t꽌!Bq{vR#o.25ʅ2}.m(e)&Nh…wr0Cwe—/̐L N(` YPȀ@Tl:}1LsiaZqه\Q5߁'s.}^V) Lq-]I~B]]DA ŏYF>@,ZĀ^ 9gkFJ_ˌVb 4 5M>(8}%i n@ :@ oZnax|'g'vh@*~Ѡ./T*|X6'Q[r5X[]–?=Y)ac#A^/6Un -?d|ҿ +0[[.z,)QCX׼y1=Vu5;?@b$y/W81#OYgL%/5L&zOlYQL NN]3(?QWCX' X'pQ>N̷.\&ƪ,e%LM1Uo 0?0~ړŭ:/SP-~n@njqoT΋xW[i;@;` 2_=w_'gN\ߤ-(FMe]WB[<2q~^Oq?3 v͋Jɗ6pbD^pa&|c~2ɐ &iC*Scǩޮ!ZB@|/ CY_ ,nLi;Gp "n0ijq, lE[_Ua,='l'Bb_s1{²m{+b)z$ `oed3Qt|X;aADBCH<_`u2m >bC!ʌ1q{_ 5#Q15c'}/BG ܅@^Oe %#sT#S" @ZbI,vX¤ UtNo'Fȗ2`u6bBۗYҌb=L nI 1\ۗ //64`WU퍵A&rcH՗'gnܷb;g"ڝ!BքMbP}fpN~Uq d1ξbkIt3&4KJ~,3I FY%ynT2VwY`l)0V;9C30+|5?@|8P tCw2&hb)5*ڊ:Q-`:*W2r>Ӽ]W:`g4ln"'PV^6mJ/dqoN^$cfW:-?;5 KMF_Zc^A|{ w*| FEpk;8?Z C}Hp]†2S:wk9bRҿϪ)_c⚑}‰3zl]>㛯.1>I+[;`Z+f{KbG$@9.'!(s\[!Q@Ďs >c6%GN+6˭`bMe-r7B u̯ zʇ'7p-=Gt.a ;}+܉r'5,tiw>b_Ϙ:_JN=גL|_Рrfψkb-z`8x9Wgtd;k G ^8%Ȯ 'Q8ÐAA2fH9In9n6q@K@9@BV TlAԅ0ׂ叺qTJhm4v=_o"U='ͮVᛙr@Uֽo2 d< gGV)#X:gYn]+e hh }J, J;Ōq)ًs=u##ysz;2;F-aGj#V1H`:vo@JB*8 6%l(-bUPf`#TՙY*fsaq)SW }4Rj;ͦ]ClRt"-[ٛ=[^U>Y@g%͕1'k ,:VΩʉ_ q(U?ڪYf>{pBCFu|x d1St!H6woސY` k1RAu6`2'ϤZ#'>//71[R(VG]5\[̬GY WgYjxbрEcՊ,ܜyi%K |-gͥ+ÚhU߀A> x am?Y wΝ0+Y'$gz44 cFZ45}D8,1boo٩~,9џ<1y- p!9z7 Œ#N񚷦lgCg YT Y`2f9X[ pJ55t@"]yTIJuu!m>(Ykݺ^nu92ZjdٜEXnF4_ܪ,Xެb~V&z LD‡VfKrMi3==k[1Ly 4f$9p-~Yf#[1bA~[>4$GaSXLX9>mt ァvp$SV ~uB/ *gqzݮnĄJj+㰇rX*c7gوbna>PЬ;`l0,A?ekaHlm q;Xa,g</c&+SbS?,)̻U 33&:y{d'-[סZNf8 2ug2S,)0ɀ}M0`J*vF>||62p^gPlp+X5` pN[Hy6`_ĎVMn_o2nPRз}߫{ *l{'bՊqY˃MUa 9~k UCS?2u-{~1D#x+C[\b= d&VP|Xf#/H ]N$$̀Θp s7hB]$Qf:puF5/Ie`q|uBBkQq<+:3M(VU{7[وo{co@V۰J l) s+.YN:7'Y;g]3\А:&}(ch^91Cyy ޻Z8v8€`[ ^]cv^clN;ap:.}^d?`&,, 4DˆkytlfVX')L=maH)lAP;χ|gsֺ*G#+kԃ{ƺ]HH`ǒ!lwL L33浮3Cӧ{>;Cf )gy7Ss==D8ꌾ V>Li L`C/~|Rx"/ ̠7XF3:DHvuNfy*1ˀ<m9Lm 7A|:d)28@}|QP.Qlަvp1~N=!V.yw4b@oZ+mt,1lNTo` >_ʌޝM^Z?¢ 4Ůu~G YB{ū?zZO-8}̇[1?>@q(Vt]%q8bTѧchpoN,AV #^!X>H^8q-ڷ2DgUe16 '?܂mH!|},\A9ܰ[x"euVWIܢg?YMVꌅ@ ~zi;Ь3=mg[| JqyoepOs`7ݮ~%3_SJr8Kg_ǿ43Сmdra;HSq&pZ/}s'gX(a]1rF2uܷHL\b"EK AC|A#Jk Yor|C[KTdblo:w! 35Z+Y1:]6vU2e-somQ!z[-vi7KV&Z_۷ʒJټ%'FadGypS=y=Š"t2)15z^ȹ_'>yA u*w/<$+}$WMNBZI{ p >5^unUםV)Q/SȘ5Pp98ɷdԳn @:YrBj}U^ Yr.5?Tm69r3{`,l[6mw(K$AI CB=>cfKj\:M &A,,ozo>-3> gXe\C_xp}[S<\z3a$9V ,۵Ćk!0f*C4ՓZ u&) *eS%9B!U>XkTM!0@j8g]y*n-&lz(ȝuCU2 QvlFiaI?:`Ox&xMQʦ?s-Wn}_uc57"uӸ1/K׌r*N^3O9?A>HR[@=/@P9L|Ik. C``_,3`a5&m@Yp@6|8V&1Z_,Mi4O2a~_1}s7yŦ%,`qsm;lζc1Q{4{?h-Jg"Tw_>˘1_1{>D$ ƱR"=Əl&`L }ڵb8;ړoX I,6Zӱc">A=^. `S @ 4EF{> N:: kdp.XlcW#F4H5bG[a5HQ,/!=Θ% ?oq^ 1CS??o1.e0YKB/-,wG&MUp'!8Es+bY)CDv횑(aywT@ ۫L M4P z(x1D{JdwW3łyn A5 05> y^:6ׁYlYwxh*/+Pƭ4d_ьjNJ$&vkZ([LWkPИxp?#+*v *H|sg'zW)@ㅫ?v^W/a'ed~͇]3SA``E'T .E쟵壤Vʂ[&@e_Q``>jqXw{(.H<-2Oc=Mz >I|UwZ=FM]X4}|'cc@-4N{wsh_ZJ%=Y'k=# ?xf7!yxmisF\;ܼZ 7UJ@fQ#F0ψ_%@{)OYfKe"24>c-Q1û7s+iCXLBg&/0n%\ʏP9=u8q U=*}"Rg(Dk\GY+٩@ZjRQT h > 4ƪ<zMa<*}S8s; Xs 04Dc$b똶Awk_^}zذ=ZUxѺT/:W`߁@#z«wwqHopUjV]B!Wy,Ӻ< # *.0 Xxm@ytN `9;$u*`̀/'mvVOhȥ(̇@/V›!{C$ Bpp`* Y,sqE?[2 Cd\Bz^m'7o,9nlW̆Abl?19fIHgʋD?էk `>cLxOF:R:JЁ:bWtWA=MQ]Q_x/!EVz 2ԃd"b=D/ .-`Bœ'`y`pdp*vd89-,̚i іMDsiis1eD1f7PnB_ml/80\WtwP-=pKX1?]O3BlQ5i aGtUH`qvTsA'D-GlLJpr5%dY.DQry^Q>8>2>4-Ԯ]@3XCQk y \1gk5MA5}D@fhd"J% ޹ kqGbn0ǒQ`Xet--_X=n d|\ eq&h Hp8Ԥ@_ګ~!zs#*Uk?w%0F&ӫx2ԓjϼWzVG;-/5rx&b F  ގ ϩFN[])EB'1zE"B>!pRم;(_B^TcO1<4"Z(gF} a;;+R 7Mh܌V>9lntt+?xu=d;K=ޢתDti:GtW1c`q F!3#Dx?^Î>YjiDnL~V*c= 0r#~X Pn. BOJa1yvƉqbIXHvI|0`W1 W;s#elcI6f4~ZDDnEF^]<3g ђ?S犴4Ear&~+M'Cd=G , -1׮{sQ}l gRqcǬp:|-abZET^2*Ą_?*|/~Ỗ045fcEcι-.l䌵4E s`r .?*,Oqfa0}@ UWL٢'FA}|Xq0,SA@1<`f>Ɉtt̞e-=%}63ӧ-["6 3/Ux>Og72zh=*z틚vTM4Hc:&dqv_EΎ=/&Bx/o=nyֻ 6b ~99*rmZ}%8]- Z[ &6_g @@PP-P^^M@ eՏAQ:/ Е\h2& $f#p,JYva ر 78KÜf5$z[Y75_}H u p}9M5Q֝#6=w1%0 \\狙 _@|l̍ޘ&R'|k:鿍}1vIy߬A=~9/1@(v o*B{E4vS juB*썭T8f ܥoOsf_>!77z\`@\}w_> ?Lzz\4DG.p𠯑GZj fowaHo-Ԭ[*R0puABPJgmfC4: G0Hհ-ٳ1Y?DY--P_{WwI63T1tǗzWARE_*ӫS`/v7Z?P1US5N5WVUK8{bs;c|YM%dIK=,#O_ˠ^`}Vʰ57 v'd0?5k{_Nd 5ɰ(D왚o|ɨLj-"gZoMw> Kt(3u]u]P8cdADo|#oC#55~EUHGw zj/RӨ~0]8:UI*2UCX,/QU"ưc_aowO{PhWk=A58;n^t"-!f;l@f´ߜ3u`=uΞ-2?rpq7š(,0 lǺ$f9SQ|%Qn+ Y'?Pl^tt1mAǮGP8>K,NTA?|ނ UU<]p*`w[qdeK Ƚ [c m 7@=Mh& ڱQs'+?@t)|8촛7z2> /3t`M96\Jej93 ؐhY :V-~;l_D 7f^9 |3|͛^u^a|zq#џ_U(F &{mV=:%gΗxoG$npj+:Bi=xDSz&c:DsmS 1^}mݩ[6M1uWwsS+G1Ţ`Ȍ}Ofp{4gB( on>&[gҬGZ2>&G(|zXWm_2/?ssޫqieٟ!qGP^Ej:}ٺvbR\qw~9 0PQ=ZW7Gz=9h5~ 37 HbŒH(_6XB/.i9Zo13V}V- i[X|({-zIJdD7 {4 ={&Œ+6Vzű21?QQ=/w8xӾrnUU[UG8G@o!v;Bņl׎=g绶Sæ̵rZp0hQXN)_O}\@5 M_! PCb/Xcl52L7AyC&NGsi2ʬ)r,q _xs+?  2 NwEˎz*<=, -SdM#0[Q= ozz1oBEj7"Nvc9yŭK4-O!IԪյʇJ=QىKj,>o/_c&ȿ.d(~:۝m9\؏K-& 詳%Jk,U* mǦeDe)џYL,y9׹F2Fn|egur+7fKK˜Od먻ĎWBeűSQ E.hz ףPtK1 㻫vo@o3T;@ʆ(rYzKܪ@W?OdW)gy{٩O뼱& @XX~Ͱ(6]L[t >GŚs@Yy{FwcZ҂ &0Zz1j[qYBLY7ȭB:|?oq]G쁣#zP] ]ڽ]=2j%λ2R7dcn̈8N}NAn'~ 86Aޛ&w M*w?3 io&ZrZ!/T,;f"B0j&)kirτE|jyuYtR)Ҟ&[ܽ t+ltsuK!nayV4k;ܞ.wy|4>ԛ0SDCЯCƅKh{W bwDjOUw58a/9k {fMD~}!4nW8G(jB{=Nb lߵl@9&I ڂvL葄.ANڋ/"e~SӇd a٢6y3 f: w!vl >5y5ؼJQX!ƢH)ҽĂFx{izSrwȀaYčTɶ%enZliWĐA3j4h@__4tC5po~4:RCU>\bag=G² \;no<*rLļWZ's 0@13G+u4jl=#+S6 (f|0S!xk%-j3ErdY0 =2$cQ(ޝ7M9Y n'FZVTܘYG* !|MؓT5P\ %6.A&1>ۡ_Sh{Wt ;$ ШbMuD?47y=^)uj*c۱l%1Esy^#@ qӈԳ: ^sK`m8uS۽'<Ĉ'4Yzx6^g0r+ށd2\bIKz\&2jrv8J@GS @'Ub]J#,FYlXRE, m[Hѷ|l%cJvWxgO`Ӌzh1-=PW֏񒣲@囋+JT^pСej۽^2u}5J;;cQq[s8 T+T^5y~^6YaDz`v@~#(S4P!w|5,0f53^( !ʔtӅ UgYϛWkrt/0DlX̋p+/O2_]e_E.U 780W4A&pv1o0^7-sazX? ]B#Ru^pDY]A,V4@ uI]{f~+`]/K54pv<FO׿YŒo3}ԏYujF6Vy9uB6}-|(2x3aKZMZny5ؐj8!ؐ)kW0Ɗ؞ `)b J1 0PZ6j1ׂ&@l۱75i" YF4B<.gCW!,VfCHMD`[ݮ!OLd?w;^/1CjWCqt瘈9v[%y?ڹC_Jk[m?6Xq'`i Xf=/@UZK"8D\^V5@ӝە$?f<%'"ȴȕ8˲-VT8z9"5z!RqChYv_E%lj Awl~^>Xؼ|8/ĿUR=x5^xKhZSs.}V5{/dgm"UBTGbH}fGO#;cNCB KրF~ #qyXF1ZYai849RGZgl&Y"zbnO-h,%0_1@ޛ {nw؉v# T/V]mM} gzDK&Hed%sm37 TZs@9Ͼ3 PVۣk*F Q峎 x~aǨ@ V~\=*b~o>Rk" NT@:Y_4Ee` D+'R|d+@O強^S PoG c΅@@s_ş lq/<@qVǷ~Šw!R?rL- jֲٻ(fN-5C hXC2qv $9?if*? k``Hí$l`Z6>ە7*ygDJ2oUc%:g}wIoR19ޡBݷw֘sk _v4]@;gI> <1o tl6'GO`B.8F 584&v}Ԅq>_5O -[;N~0~eMkziiDwzpz9:WZ'a}=N޻sd"hr#R|\]`:<~I?A'"Y*MX} Eg^:V}e=n|c\eG@Y-POGhEwK#W3-VZ3Zn @/?5ep1/+6fڳۏ[+H }=iD*s#ނjjj\1κsů>GkQ1hɸӛب[൭$J拢70фy  6oװ?JA0 |L{\ C?W'dfYSżrV&Xo}f4p߲AN@M1zzcnNJF$Sy5meI(m|+ KK%~ |?O>{} >ސS߿C9LRVS=GW /%* Mi <{00=jskΞA&<$hnP8[[K#7Qmͽw-2e14ͳhVSw,eF.x|(;kٚxG X\&/-۱(FKKb%gK$S K>@nZ^2& &E cɹ2~V2U>LF7In&gOg~~nJoP4u!Zh(:j\W}V0[zDc^(M.1RGiy][Eܐ #7+~8C4\F=`= 琐G= mۼAdOrzx;%] G$"f+(8;֒ȴ X-LR#wiqC4[ 6|Q[%x2KO nR~Z(F|sKs+pch;q]=33?v9!zhҽ8%g=Fx)qӘz &2"\ԻټޫrG1jjwa^rg ;[>%\ª`)f(:l#3}C%O% c [#vh oxn;珙_KXZRޡL T;{5O yiӈ"8 OCb.y ~C3%*8D&>eV{W?ULLj߄yW!!![v^Rg~Z UWT2'>2P׳sKbDw2V^3! Kѫ&!r_A'EYfnB'vjlf9}qbhPX [P@葋E-cHjϠCZnI- ˗-mג#K%Rhadx(Zshg$uK3}Fs*Hlԝ%q[?gB=Dk7z׷IOzS=d3QR D8ǀ֠ڂ?,~DW G4RrFezm=Dт"ZfByyF`uHޢeq%svԪl l3&L+p V5Nh  PL bEM gXF4m>b˖:ȍgpl&{4Ie]\|,%,'H$7lyԠm?rB\76,iIT`̈ ڍcx8&y|1!Go:.y/UstYA+qίgc`U~~(X,|s}BlM[FWeGoOHBOeQ:vc]Ƌ`Ӈ]H4 t")|5:.&- 'NܲF7u8 |}lٜB+fNӱ93,HL}*驱Խ)/_ =mXPY8ɕfnyH#RCА^]=,ե57]1B;r6^v9# M ;+,2[{jYIrN2zjYc={LX>J9k#32!DhԂu0G #D1)%9<{S ce5,u_JaƖ>6J]odɑd瞣.LF#tMR8H[֌B) {uȌUqTm,؍DV(\ w7;ɬ&)ԀFQpWK(C76¶zP;@^vBR*|C6J-FIh^[:(*iOţr&6Ow*}Q)Byn^x$y Oh б/.Yy܎7.eЭWz ҇ )v"7WD؏ J6O[v}XYmy}G0w@QԄ 2:>42a/Ўdx*InxEc?,RUf-iN^|pP^H/(MYC zanZBd]cQs}Le#JAa+tvLU)H v47]/@ex# ;PzJ爒bѦ½M-`m TlJЯ ?''A$KMJgp=[24[RLj̏XW?].H<>m *:ƹ uN")9"5opiQD2.4q;ײWUs#o =w+rbNj>l:?.EaV P}^+dfWs]):\NRhUR5gHFr3GVD%NQ&߮\$>м֢jKs^?(~0|:zR(KEZ dtrZ-k(J(q:3蹝RDuWJ~K^fa?tMT-~>LVP`Źb×6*jC~J;_c[s;elQBNalZ CKo>%h,4K.weCm{ =3V[Ri4IX `uZ\I5+FY^EqUʋ?H=2(y`_Qѽ98l*7= EDf"|UgfPDJW#i*xJ(OybQ\nSrw/B@^Ŏ}BL1*w< w((ȢMWwElsӍ.uVԱێhˣR6TBW_VRO"*n׺xUhlރD2kDx?˿,I,j;XG]3**+KOJuË-Hzڸ&xb?m55T&0kx唯PyVD^Dtϛ{a|aT=m-v<jBI\nc3#PlG\nPM2i_s˚& _T{ \8maְۭ]0EM{tfuɕ *$``$xKX/Z~bGuZ rh =F ?*8S## я"g[ӬD(?q7^VcI)G:.%sy[^Sk $;@=M r [m~t||E&(r-JV#f vă %Q-4L6\!qh_Y:de(%6 uѧ{˛ ŖXy6oj>M F26uQC`oRa1wFv_kI`V~Gzb{ ItUHAz+QU( Go%?s Ԋ@y&$jW׾ BB32,E NnR~"p75xQ*\5+]'҉X>`ׇX(HMЕk\~ݶFOI&D eOgv4Ooq!($vu멑zÐ]k-Et_Ո,ցk! ;8j<>Ӈǭ~1 a#y _?O`f? O?pla6at5,s۬9 *SŊD6X7= 2ׯ_ HrcڎKg$M~ 4U&'}k[cݕRg vE;/㌒,YtN*+ U-Y(!YL<-lUR^T[}ǮOv} ,X|pr'W5ݵoB07{N.)]wFٓ\( 23ZT@,גqy9XO[c"Kc?hcj/!Ξg2B/mf:Iac^b;R7;>ghԺ(^n6}|n SKRCWнo\UڼƦ&ǒ&4.,L nhԢܢl˫VѶ~5ox2ҶVݭ#+ړ&PT*VBF/-xr bhOi*P: ē}dUΧcOOSlDX!9ҳs%h#FB*ȯ(/VG8Zq!uSS ,Ƈ`E<T>ber\\B[bt_%[@Bv9 щ^Œ!M]Q2ܼ꣑U[&#.CPqWgD;pu&m>%MT,?T~.k`fP6^>lhq}T9J8PQtZH XLc5td:ӂU^Fgx?tKX|d@1[SG 7`T _%Y-^*N4JrHE?OX[@Fv2s:*ZG@0Ԩ2ReGbף-uG(X xYVTmZQ{]?#g%idoZ%CXѼ9Ι^ZsMO & 16+pyYn<[ʹoe Rzs6z F X%T4دQ)h(xk' |44tf-V.Xӿ*-4xfp}vSQˆI|epMœy~6fۚo@@Z[^򘼻U !YC"Bﮕ^݌TҤmxzrᥖu7G,yf\.a,Þ?j=5 /QѼZ_vz9zmtn XO j%ey~Tm7KE'S2PY-h[c6ʻ OC2R~/r&E5G26X홬yNdQi3QpzmkOΤaĈW(湽IQ:]jЊEʞB|_n]k>0Yzmo^XM5Qv )E\>Z/ ]z^<}˾8 _Uubfa"6T/>Zg8z4g2F=܆il`9GX6s ֖Þ4g}@lYȣ][o(:9 ;(B{mHl9YزKsHX2]|*clTXƚJ1kY`D0:v Iݱ$J[uoyY7:}Ճ ,( GExqC ? ,kL\>_c㛗[ DV$XR32r^RqBP,d`0 3۟H`1 ;Km ^vhOO(,A! wg΀n8S,">Y4r2ɂ(E\זUc~r"WE8~䷮56M'wog6qCQv{Q/E734W(B=FƳ\?9^pGVs.D:pf;>yR&V[0A kOzQصЩ%l~z RkVhܜ0=AaY(PPŋ_m6+Y[ 4Z@tqZk+>Z` k,t yz&w zmiϚV@^qwnޥʿi5;]{ PzåW]CfҧW,O(=k_wh!?"fž,$Ƿ/܎(l'nV[Vr"qWv$:Z"t(~2xV*}3|5 unA^;_6X q )c( U(Q5O;yi = o,>"Ťߋ˗[gun&w<u/iЧ 2cuw}K0܇RFA__K[@?#^"6~vn-A'Q*Zs33̩M@FJ:8s1`p{chm0<ofBT@tk}uhXL,A۹AFVns0E-bvss@2rDG ښ rn3K8 ﯻwa}Եdֻ˿L7x&4^*ʣ\Yc0}x?l[c菨F^~%sVvr)Lʁb`Z’:_*`Aʋj(bX-5\NcQv`l'W&(؞] /E=IE1D_*аdy`aK\5:~rz( a;%,F RCzUM. _T\!ލ~T޵W<[xѺIu| .TQ#{^ c?~OGI '(8*' *g 'È1x1:/⧠ WH#^ y/u==Nl)[իl5nD V=pWg{R*gՇwϳ=ل)ӿ;QI~rWrFA [+$7Ħ]8A~@1;\%Y5^Ow]SX&DPݰ֥ 7Dx&F(Qe8*}JGCx u/\؋,16Ӑ2lu(;@y֋݁K=1Xv`yY,Pnߤk{w6o Y>+Q{6J 뿋/wD! Qm_1 kQ&r=X_l]:;Xi{ lЏ'UdTV5xwR a9s~/U/|Gu;J.IN]*~ρy1Զ~&pβH ӻ}H^b:ƥA2 X:W##u9س5^@>H[x>#KK90PSkKN5aF&jqP %!%vKɹBrIVhǿdBR&d'6Fgwң]LNF#:zKE{eTU^׿ȥS_+/q[jRUYd%X\B@om=rv{oNз;6JoybQ.fV?w?>(hd8TxY'0-ZŊ@h-qzx(d[h6G;E~2Q .wm?/_mΎzקo怯m xrae U_,DQ!n F s7B |žDMpBgl\ShܲcuKw:{A TyYBlVw#[7m?R7 rfTn|o^AݟA{Uy1@u Sej)mk2qv rJa֖R{G^^[J1r|;RH)Gфf}jXg휌Q y, G fo ƻߖ~+0߸R n}) .8M), :ﮢ&D~*̬4!V5دٽTlViG\lK%׍%BfU^FPw)QKdHFN-*Hl@%f8hPsӥ(bGf6A@ qM¥GXI\R9}w1` oHSĄ$,k_0fIq&cPp@qCmBG xW~HgO> 7G#NhǤ2 q/RY^dP,9AOϕvN-n(:0I@%ު0K()han!&d:se K3A!@ h0I$Iu޸L3n!VU5^S%h}VH)r^7K OiP,Hl7bcӧMhC c}V5!yŒn`{r5(&>=y 0wB 5~`*>3 dΦF "eCyW:u6$l*L1BY9Od^,5XwU\qq"C%L(I8IVx$6 }ClSc)Dyʥ5њI?T|_QzfMB}Z6( X|3 h'!S:˂0+@pL@^Yzt@ޒ1\>dAH&$-okA?P,E n-<X"ϒ0)pրo7586HpAhkvϬ|PC $&C7UѾ΄Zڸ 3;W;w.4Mϻ)o~Z#+$1o<$@A,^OLHqWKGj^\k⵮tMU7kSڟۃmoX yImI#ǐ d/>7|292S05zҙ:țfjA|9S,󊥇f$aĔԂS$%y٣6wp1E28 Qxdt &y$t}(™\A3U ~ 8nYվʹ%͇FjU)cX^P(~oP7酀eAC+Z@5I8X>Ѣ#+i4`7JyչpJ/-EKkjP!f6am)7ȎLdG  9ohd? ow$ӇS>z8%E^49\@@,[Xv r(YZ6.9a=Y*ϔ{}@Қt#f[0ˍ(" &tۍZ[YߓEU*x۽){lm?VǾ׳yIyqpͶӇ8pYdpTL( ,S[i5 %o5;5D]v|bAI >> 3z>^u bBPo0InnmN:?};bizP5p,C<2+r*Lo jG#LY<&@̧㺬c<o頼V2anXQyAHƖBoVeR鿗7O5|'x +Eca]) =Ile5k=ǻc $hmy>R < {/`it!膐)>̙*e>M;X0=|=M\ovx`^Q Q|s$!3vVk@/Sꔫ%ʂrn nP$h!=i<K_K?g,IG"ȈˀUf.}ԫOaü*qIe PP{]E0  J)BM $B‹QSX( Ҳ>sBLGq"!zcMW-Ӆ#)=Nts:ՃLJ•v_Ag*)`H' zk}Y<)a:. /9EiiSwrҹ5GP^D$`Y_wǚ˴ElfIvEY [ISQo\MyFpGn7#eMZ0En0}}xq\i?4"&xi"R<qۧW)_ ۭ)fCy7a6xiuAK@ve }"1u.fS_|f$B8s]@LI bh1~ۦa߰4Td_q_A͚uH:}{"Y_ WVB[;@`I:[p$ڐ}@pK`|S7JQl^$Hu^9VfluA| P?υxGo?, X_ׂ)oew1@M&daް$Q,ۂI]T}ڠ쨢|SL9T ÉO nC"ZA~; 5:g[> 8.-2}F$ޕ^QU&o[/+(ۛ6HCPz["0ߨuE\0ʴ?_ciUV%jT4Sf ;Q2c]oZuwBTb{0&:2+T^lJBIͼwE඼yQ! bL- pnDꑯ_&S8?dBlHJ&^F #YzPk;1ReZ~r:o:z6H2H5m9 {c* %ގ ާaf-){($jn}2sy\s0=NCBժ94YbqБ8V;/ؐ|W,Ds 24Y^j(̫K[LJe5PXhuhx 4%䣪}Cvrj+6ޏ;/ %<X`kSdh  H0Փ`J<7YNy%zs쩮6sWlue=` avV0zPZ:嬲~&8na. f\ع5M h/G d8pڏ'+ ^`*sra5L|? ntH\ E_ /y{^A_; ^WB_-[-*"wk|CQ/?XAp]x ' G;K,X-`C&a.f4) QV,H L֛ZhjmoBF-Ւ΂/Q`X~"~Qx({GFp .,_'V[PTt:UmTjHA1KꫭXs")Nw+ IV¸P`B9^ꩲxX4'ŘsH4.0jcۿZiE^60E){׆g Ķd( JG^H3ivf0iKR]:(<`Ii+iJ(gqv>²GvłMWA/_>@Y<?\RF|w(~?U\6y^Nhⅸ򰘐jki u)a2)I&I*8!V]l9 Z]ϳeB_ޡ :hp2kBgdV h(y<Hy{Ϋ7]^? Wn<;3=@;zޞ*sDT^.ZB:hwKXÛ' +R<0]*R)3Ǻe/J{mCV<ㅿ3'o%y>^# ~t3VG3 V{;i8Ư  Ac}G/6/92I'\PҦݮUay ՗˾W|ZE{iL /48>حj% vOEjti@ܚʔQDs^-9ғ,aquerY' MeډI`r}rjʾi+niu.ʖ2O@ґ S0EG/w.P: %oePA?\jp u2-Dx@I.b6ћbD;6 lhUJ YʔmS:J]!m ?_35$^+}yY 54ItHLmcʰEL(_ŋ yN/zIpwLIݷvZʇ$H\iT'\|:d04$f#SsBۭ{,"z ='@۟[>^ɱ :g*nh&?fJ~r63첱= N;i&$1J ;DgVŠZz"Nh\%_bdPTD?BV9K=n<˴{V'Z8@pЁ@[jn\x@-?oul@;ۍ rX]@ocTPzs I aN `/Ϸ <K2ʴ[2s8 /F%~(/Wzh$Ho~B n^ʻ4_|QlvW1od;:@dь^q/cVڐ`PaxB 1OxtÌt@c_LTp0~.D<̕ӿf[ ^NhGRH&^}ڛ )JJ>5C'@[xL. :@x ^ꆷ[nԶy:|8mIH4GZ yh LAf,p&yػ2Ym3 yZLoPPpӺȔioJlNP48T/L˺.+._%C~)8,QPl(T,k}FDv!ko*dl;%MOy;ӧCX- Sp= wiJv5?߲ӱjz!t\5(@pYhW/B 0_3^>){d/`¦Xġ۶Hyt0o0 I jnH݅vPguP*[ 2)ܾͤ$Rd_VzBU{^z[fO2\e!hn3Dȡ\X4CEKyHz6VzBI <GR*G7y7u+ ]4B\Bun[;349"ie/ 5R5߮ݴ̈́Ÿ<_@(ܲ:}&[է: OxbsCcg$ﭾfjh{[cZPbBa-'A/K&H@5-#}e<R/_v^׃ HA߼K*Z1V' Ą2J@z8U '][I01Q 8P:JOYVςw`NK)!!ɨ_gs+Ƒ7F=aݸ֮vMO;DA^#-9cfk&1V{2"y grgH쒕Z7Z@^AA^:>l}ԂH┗/ޓƱ)3ADP T&ͥS$3+f.AtąQG5yf]Fb#I@uۘG51(Yځ.4GN4:Gd9KQ9 RBʤcßం?V ` H{/Iի<0ٮy{& 䙸6JmF\A!Ouzjz.4\*[a bQ8tE>-ۼ=:@_p(<K xb&h!x[pۋ~Xe{b*3 /zd>j `jzoI YinƪJ3jJOWS_x9(6l7(PqTxj &3@Q,RjALDIUy9rco ˔:eH6;uP} ܴ\IMk@qB>3N"ЮW/B-s9Yi)=38,9W/H,kY3 me!A3^?z >!qS1;栱HXr!5V+R _oyA˺2ř/4q+! 'ec?;6)I|6IwT/?K£Z1Ljjكtx}{g>o <50mHi>.DɭTgfOY#Xg|Y3-Q_>325] f"*NJka%@o_=79zzXSFgˍ5\̷mFJxaU:0S^0X ՠGվ)4Pv$5 ia!=_X'I !F!g>}{_>cted%5H:7.'LoGa^#  m6 eyzZn?k&V\f2[r {=`[|I?f!zHxCx C8z!GQ[Z{6Cᄝj Y$:Of̀KSjoޖIX87FP IJ6C UZgy4/:XN - A ( |'}?;ov}Ev\$lԞX΃!np)Aӟ? -C)|<`o=#^nW۬^v ʃ~O_xÆOdSBt !fN n{>n 4i|U}HgNLwk'8ᬬ{c*xEQljLJۂLO2VU q& &4LAB!R@X|aDů y.dU(z= \2_`P@}' TgTcegG$J_kԑAo.,\: ohY[/9,Q bnkdn',7(R#sւ-ucMHJ/Qw#b$(4 ͤ2icSvcXG @N<0gppA/邾.jOCU`^~WcH!i2!?ސV<-3y]#YSb!;uɑJ5mSx44啥yx Q6h4NlQaٴT͚lL!(DAU hOwen3_0bmƹ-X*FZ%pĹ<* 2Y BO7քEIiJtjjAP=4=Ҫ=vgkB$DA$] zP2~0bXH34%zݮiFJ@y..QkO Sgۿ?^LL le5̜B4[xP~~׋)DyW$0ʇ8v?]E xTF x> eh~pyw\c(Yrn K&DTˠEfr*hL)FC1K[K -E_ŴjU'.+]8sx  @Uz&p| %>㸇Q]tE@ll˜]Z_=CTÞX^ڗP{.s{ `zKp\X,%B4sUGAUjˈbCngt8V4$x8{ $?Ax[,eyKWD0ԠxR)@i8OMSsԫe81 ishH077)}|H?\K%h*-k =ઋ'#ɄhH겛3  FiBNh({gryC;F&'\JfA[:q75`|}cOτ>gL[e/u&ػ'p71;D \`hJaK>qF\ad!<`;`S@^sr5cp6DpPBnB+q,K\T\t@p۩6*mOS^R/japjB2GNz,~rzHpU 3ma~Vba19A8C9!GZɉJB r.%s<H{:h;`'[e#}v2 E&K-ѷ ?p"'O9dA+L{kAf/WV ^'[(5؍f&[ %2Z]meEߪ_kFB OXT+5E/4q8L ysfr<ݏB3?~LAc@w$JgG`&BԒ zÁA2LZJ, 1ZyZ" OD;Ƽ`,QF9L8Ԅp`™R%FX:`%H `%d|5-dxwSDh(84Ja D=@\^NרOIGmR)a'mC~[&Ȃ*K:œ@A»lFC_Bzun\u[ (˕"|vsλ)Ԝr0 Lq'(89.ta -Jpzhdt. 7#La(S.ȴ 24RĠ5"MwvG*0-WVጚ]`gEY4RQsyU, M-(<]UWS%0UZԣ4/T: _ Ȃ^͆@98w-w&bϟ؇(pvzdnݜˮ*qErKkC!u҂R!At K& J(M`N]lt-PR8gAZʀ;v (޽aDDeel' t 9Y\Dݵ?r>1Y;8QAb>p3o"BXnH:U̓"_e`0W%{Bk r~hKA@Ȑw?r @תp*fH(@R{.k]WJL'!FF,q]GM&>kC?XJuea P793 } Wٟ߮8e goy_ Վ OQ0Z1j>~XOL_e tA(2 |r< gbne0"޿}JaH3m?W(0 U 7K qM\j|u)5H2DM<-?8g{ M++9"x-aC9kE82tE-e-Kb2;m`"!`pO1MzEˢ,wyf2CD'?s@uHq}JTcuPVA$Fp Fu&, SKq`fNDo@&5VlBq0 +E3N?;?v(ŀanRRb0 Gu5eǹP]vK R_v,{ v"Ht1_Xqߠ@أ+xo˱qY?9_(<^@k :=.DѶjY" .B:_! $dҵql}#dXj ;ZNLpӭ]_0_C)l  &o(N J&< R2ju՞0@DdIrh]4/4Z뗟2$ΙJѢ@6JÉ݌a,[J' s샟A0 2t܍rK_~NQ]*ˀnOúyx \\ը& JD@E+R#U2Uy0/D_!i | [z$q冈o~^ф' N1.`T6\JF%ZJQruO#1gssL= F-5D.$0$8 };XwKyW~yhEa cg!5{!۟q4_~Fзa5YjRƜQP4oei\2ROZqy],qNa(1oGEeM9#_ ,ے?V2\iޱ.gVBZ:IENDB`openMSX-RELEASE_0_12_0/doc/internal/vdp-vram-timing/manual-purple.css000066400000000000000000000004761257557151200253440ustar00rootroot00000000000000@import "manual.css"; p.version { border-top: 2px groove #8070C0; } h1 { border-bottom: 2px groove #8070C0; } h2 { background-color: #C8C0FF; padding-left: 4pt; } h3 { background-color: #E8E4FF; padding-left: 4pt; } h4 { background-color: #F4F0FF; padding-left: 4pt; } div.note { background-color: #E8E8E8; } openMSX-RELEASE_0_12_0/doc/internal/vdp-vram-timing/manual.css000066400000000000000000000031261257557151200240320ustar00rootroot00000000000000body { color: black; background-color: white; } ul, ol { padding-left: 16pt; } ul.toccat { list-style: none; padding-left: 0pt; } ul.toccat ul { list-style: disc; padding-left: 0pt; } ol.toc, ol.inlinetoccat { list-style: none; padding-left: 0pt; } ol.inlinetoccat li { font-weight: bold; text-transform: uppercase; margin-top: 0.5em; } ol.inlinetoc li { font-weight: normal; text-transform: none; padding-right: 8pt; display: inline; } p, dl, ul, ol, h5 { margin-left: 16pt; } ol.alpha { list-style-type: lower-alpha; } p.image { text-align: center; } p.version { padding-top: 1em; margin-left: 0pt; font-size: 50%; text-align: right; } dt.toc { font-size: 150%; font-weight: bold; } dd { margin-left: 16pt; margin-bottom: 0.5em; } div.commandline, pre, code { font-family: monospace; letter-spacing: 0.1em; } code { margin-left: 0.1em; margin-right: 0.1em; } div.commandline, pre { margin-left: 32pt; margin-top: 0.5em; margin-bottom: 0.5em; } div.note { font-style: italic; padding: 0.5em 4pt 0.5em 16pt; margin-top: 0.5em; } div.subsectiontitle { font-style: italic; margin-left: 16pt; } div.examples { margin-left: 32pt; } p.todo { color: red; } a.internal, a.external:visited { color: #000080; } a.external:link { color: #0000FF; } a.external:active { color: #8080FF; } table.border_enabled { border-collapse:collapse; border: 1px solid black; } table.border_enabled tr th { border: 1px solid black; } table.border_enabled tr td { border: 1px solid black; } table { margin-left: 32pt; } th { text-align: left; } td { vertical-align: top; padding-right: 32pt; } openMSX-RELEASE_0_12_0/doc/internal/vdp-vram-timing/openmsx-rev13227-GT.png000066400000000000000000005507311257557151200257540ustar00rootroot00000000000000PNG  IHDR{C pHYs  tIME )\L#tEXtCommentCreated with GIMPWbKGDAIDATxڬ}ɕ8l` '9_J7NuVr$1'}lu~lvNډ ψ'2ױȈ'DF?V~2 ;a2 ;a'D>OX># '<"?'=bEx{yGzG0"""#lGx;"VZauyĊ\+|c4#w-²?;-ܙ;s{nv>;r[n{vgxvgVg۳l{wm{gl#v{νͷrھv}Xzږ====lmmkxl=u_q =vsOw[s/7~s-Wwsݷg}qq;Q׫3iӯy]<_y.ɷfo[;}{wokYoކ/qQoV+$vrγlŖXZhkת0\XS7.na+"lE^!tGs{$CDœ_dB ΄'bTDf1ޓv,' {%'D<cAP j[~ "VAv^Lf'W_K,Wߟ;bG"7wDyN/WQWݟl{hqvĎsom "!;vS:d_3=ҍ9 =mo9:nnc!w|;9Gp۞˝<d p-W,``7wuQe_,ra{a;̱Ѹ7"kSsޯ/뿊IX^1 'QZ·aсep& `H;fAg$4_wu.;\~<#37^O!TW#O?DDqp>xI1חW*DD ` $@n!QՐU{N]WO]4? 6M}\ő PBy(겪g*7y{إ{G|va%6=MvοNpoO䀬4=n>7qT=[;`ˍ߼5⸹|Q=8ӤmOWǣ9Iu&Ȋnc~؎arqvF(ˌ 8;,ym 羰:(W^;=rE:g;sc;CƇW@;A澭N pwC#X5N8#x}a'b"H }8`ȭwd^ q{X'LX%F:LQ vl ~ ~K UۑTywDd~H¹3 {ݧ-嶮NFa(7j%VI.m>CC.J_m@sk]W?'ѫj?;j[2P=*,&O]'wQ AuTOK_Qx]x> <*+8 #`úBD\+5 !'yHe{+/HO>|hl_aNvnUP7QؽH2*3ՙ .YHjﶧM>p)깧P%nsNNs cGwWCK 'IBM_5p' @~z3A76NЉ)J xשdPa;Bұ4~b'_qM'yWU OqgtɎ<ݣ: tyĎ\= [bwџǻ-#Ekqt1=w'XU719 ݎn^g(͂?_]ݍ>>> RDSpoUԯ.ޫ5hsͭu[U4qC ڶww 6+<7c[7p ,wi3W`PGG>֙ 0蟋ðwwL,qO @O0oGu\h[;_bU8fN'D(,9~ZeJ?U-aIV"t䪢[@J[~#p#ۺܬCV^itak$ HZI uX53DW^=u O$ްcv̎cQ̰u*c/,+z׼w ;Euh@?a>{sdeO?UB-D*:xIC`>C:֐?_8s]P_ΪzjUjk%[jFeؑ>Ş4N۶pW?uUɪOO2<'fEpo"\sr=b{Mzb)c^N0cT~<ṽ5߈: Xt2oe &0.nOcO8 ,K"x6O; DNZ͞][@lan1awğ)Ψ<;z{&UZL; v.a]GӢ"gJ8=u;خ;Ǝ#V8;5N=5F?iF zWqGrlv@>)eb${J,ᑇݧs1 @&`Xcrw0Zvf"\l rs@Rg:3Q=|UOG |:EG:8"_ֶ^~7}Npi4^ܓ[U`Y n`|'{~V Р8nɎҿ^ƹm1I0 si/1l7QZE]FsYZ!̅E:~|.x=avJp9Sy'Fdzs }v+|UtOAu9{~ X0*;3Hv|7xu-Kgv'YDv3s]Oj`rʶ>H!y)wL,; ^kfј۾}A]oA'A<=hu<=!yo[ >]z>Y,VH*Lry9kf~?/{8\cA*[nd Ɛ&lԤ?i.gG)0D]6iHl@to|7!eK0;}h`zRF_^J }`h0k^P6LCrG:)@ПovOB<!5d]qI =Y#{7>;2&jLWݓ>ܾVDN};}3"K9i2@ih]9ӫpξa@s U'JՏ& T8@"J+TNw3W,6D(gtoϲ;h;f±s7B4¤g7ژWJ֖q~Qo yM80V  <3dYT[kq sxkw YI0v:2=dGB F/_{XDW$&O!d'4ˊ|$TpH&s0 0?г]>$nd Wz DxKQ:e 0$ `IO:ڈ.%^Uq 2aH௻8^đD/#'2%zF^8[4a\ -3A^k{T[CYP\?n>,_zKP?"58@OGY\`YMFs@jE8 7.`0Ow?{b0 z2 `潮1Q^<@, ,r!hQE27|@<;N͸9s_fB'h}Oiՠ4b@7Ss :8Ñ%V/(r,#9õ :c;5 'ߐKϧ }(C7rqf:OVX$Vֽٳl(!HM<@N~7gN;wKb.4M>lp\&=Pv7A$:/ij|w&%Jd(K[GBpN5Xg9)xB^E?n}47?1Tځڟ͟ 4F#!MwU\Q__gD'l$TN#ɀN <2l!XO$*֭  oR&Lgw_C8%h}Q ) N!μ;WU $HNib?0X4@ Fx '&>4Z9bvuV `#dyw91ѳ\Kچ^u!@1Zp)XE<}|!x؝S6^w&b+A 8&+>_wr߸~<* F<,-nW,;%(<<ˀ 4?=py~?E+a@`5PwgNnA.' }(k\~9[p?0 1M_sC'W@HC$\-PnbB:b5"^;"zx\&>~215[nf j;mT e]rb@~8P\P` Z^g&q&PXSȔmVSؓ Q%4`l &[5%׃[)$ % ?sfܪ*O9S U[roczOͧKH? FIuV5 (tq &Dj:ZW#W}u?=rq˛˳zU~G*58r@Q"W ;@5D'>.WQW~?^>s&P-Iv#"xkcu 10.U jdQ'<h/k;!AQ/LݹUwLxG5o9#(QEVpp:W%s h%TT$ix(!Wx&Ώ]b jn}MBF) =oM>yt[IC2I87_SE~>* Jr ^t&-R֍isL)MR0: ~m?f33 ^iBR<~4y(a#3O @g vYa]w@}B5`5eGew`;(p@ ?+pџ15x@D9ɛbD!ԦQHJ{m71pE$jލJq#=0g")E@V *ipEA$ ‚w9Ԯ륋\Ke/U[SQfs4GAN[];t~(떟U\ojA99V,@& 3S2+ggQsdscFXr @!k턜sN3[p7\: $^@A[kl88ߤ(k Lc6k=x:"O-=uU)ܞUD6k{VW8.geL;E1qps{VlNE^/JԄLYE&( c\\3qI@:r RD^CG\vNRDU7ͅ%=Zh| !2ܣ(e@XHn"i2C-laB$3:VVX 8edok" Gw[Қ(ưw=!^.W_ܸeFUdP(4NI7sw0Q[RGKzVDs}JsP:5T8D O хxmCC/JR4D9l'1-L(ϠFFx:s@s C͆x>YuJz+lwg8b@\:0 ~?/>A)Lܸ{2T3N& &eP&%0D;l, ^\=[Х eh.{ i3[sV5-BD"5%P!e*ܬG)hC`"1;U5B! ᧇ?qwc@tJHȗKQ]-C$Nd'$+JX#` ìȧa0־i~-~-IoRzK~=## qBB.強FWz罌A,]czWyw?=sAǚ~UV<0">]{։ѮE+}iPM^P賊>8QM_~\!X#fsƦru$9$M ?a@N.E'vM)lTAŽg,rfU_C!gN sZO*8V}b0$3i-a&%d0qAta&z#4):?G}Ѹ0&EVﲝW)_Ejqs yghUI {i{|~ {<C%q6OsE=at5WؒKi`Q1!RuC NN m{+3iN|OW?_ktrCyV>Fԟ Ufp UyyD-@(SszC+ uĔEENY%=ʢ%Ijn}L5Pl#,آBDXdqڟB*'q#"M==sNa֟uc23o)@(Hb_ z*Թ$nB8EƣLޕ'X"DJ{3px/Uui#|/½0!;:rQ T rHi3wI (cO 1 N@8bj? c!@'ϊ X'yEePY;$B!Z1aZN61M:BӐ{h(ڿP3>!G@|r9DwqAL2\&sy eH]n[/8iyE+1qCu!#tH(h6 T5 OE>w1{.k=} 7.jgy^y@N]eDgP%Л;W~.,MavP?o8Z?ZVq+F;b`RbU~>Td` a?GRpT6tTq7QSSM(bԳ`!H":ecvD\d_9H@C1Ak& l34UrTO4 b_<"aM%"1>W-! ւ~m/cZA5 #t[?Lj>FvEc&;i'CUmJ3aӵHJһD#>^R]@iH:8il~ ]ab}қM:y:'Ge}Fyk7%Pڅ*""Ϝ =+I5䟗71("$lG6h)lT`^C,],kcq_U=X6ȑ_g՞Gu 9xx!ENL >,SE Q*֩.1^]\PP+Pٍi9(Cl/" FStK6N\:`EUnXq(sSL*!RpXn~}1iL_hb~3JW/q<"t77\K{>[>C"G 3[y9[`=U>_J{2LD3YWr*$T;=n^Z UPό4>G$`MΛU@ -?_1[W!lube`$gs@Պko֯--hO7n2 754Ir`Tp#1<=`I_)##2 "Ǒ5gĿ=bQTvKlhjTEh / 'q!#+XPϖk@\0|?% ecuwZꮄ,vj=p/{ ޮaSr,p0֌^m ,i(&rCR>x]X7GoV>ӕ8=buܯΏ߅G[i+%Q`!51UHt˟xxQuzկʱ16/qlH u~8(OEx޼ L\>p^것B'jM ,.}gkY7qm}mb{Od cA6iq@UE4Z*ENKǮ>3#'1@zg4U]u*(j*hslZSE}͏ؗx޺ WߢD>5 @V`wp#ubl rڪЏ Bl#/ԄpP J5l`j0 @U_-2p1q0_mMdHgϏƃ5Av]EP9`dF#{YO'~:%XoL%05-y EnR@8rT/;%M9rjeVdSA*hLC? E7W]_  ԢR̫[X-}E_}:f(z+nF?X˗KٸWU&xqw_{U7b;նqSq $&w7A⛏3{R tUV|si;V_ ˔-b2>ᩂr#|<)Ɗa qi)RE5h9oWfp |gXsK1nkar@/Lu_W<˲^]탸_/G_`O˴dn Uhw-r@jP  N_+1< @"RDKYTe|8u>oA%}f5V?G]  Ydž8< k8L~oq|6x~$|`76HjY ֌лn ]p^RqչWjP#e@ж ĿlU4ΠK_e(鯩DuH3)Q9mͻbVW6Qe@bO9EN\ ކPkk5z{`ކ #"#|KYPzpqY8Jg'#W>sHBGzpS|bMv獻x[-iʛћ6N1X#\\36G'ZrR3Kd1 ݬ.<`]-7߼–UEYyZ4hEM!ݪP(*ebcv_<מ(hTza6ٞ]w\\s__~.ұ1=umTigGHUR{=)2!Xo@Ȩ8,;,rQeSi {f~$AF&b]gN5! $ @9t?=J?oz})3IUiW: =_U(TEB h?) :^:Mm=_8 I~=_K Dዪv t{sKEEF C3fhP/*?ˊW(U f ?|yZSߖhiX>#"((-}@Ds+kJ,Fp_}~Ec0mz P|X8GcQ`?֯%*/8ETDR?19{RκOq?@6[l2W;:%Кv . C!`&Њhe԰ѻ',!a>?8 #aLO$~C|h8 ^fbO؛V}bKns#x߆k-#tMT21"~ DpMzV!J/}ƫ|S*)*`{`}\8%:3fWkhMqw58OeioumTBrAd -ɷ Ԑ X@|"8$2ORoy"~¥ W)\`.>Dbg}ѢP,)I -=#Kn {4b;7H[CauH1 ۸%Fh`KH))=MG? /@}Di{ :?8(ej:[^筱BH qG㈔O o3WfR=w$?ہ@vDۉN9JIqY\L]-d/X 0]KVl˜AC=t]R Wwx-=g}{U+3XVٖJ:IDj c.9dC47tFtᅘ~؟zA KmK `I2·%'KKUlu]K+Y&I[ ng4]U˾tDd:6.~g`0x2`X"qVp7a0: oI q?A l@1q;:}?}EJG7}m2Qe5W|0GH a^#0ccebU3XN_)|TgrMe 7S$y,Dy/Ay񥢲X+~@_&t= O Ŋ=t{]U:`Kgb[HЧ_mD{̘>Thp-󇚵\q_xX~>ms' L#wQNih! HB~|N29y-3e#UQ n ݸ U!^m/ǎB2>6ʽɈX3̙듮|˨'/WT̄)6Hջ!@`gl={$$ʊK^n}V~yioq;W:jB:=I; )}hdmʼMW,.v3FH88j"m/G lbgwfe9 3*= z;5R>rsD?h2aQmUdJzz_!#wޤ_Ucq,%ҀsP [|5' аx*pߏp~\Qc0~89zg!$x'u-؀]@31:s)z a_D%< GA奈W(.暲 HD za2Ћ0)%Fh }wp[އі@`Cc$ ./0CWd5;n̏~~R3<@nwCiHwʫ=$`rzM].hxgPfKZy~-#r @t)6hqF.GA4:u,󙸟_;77Yݗ4lZf+kWQD? ň•/9o>:U1,v>2luW0gܧo%88̢SڒZƯEſ{˸p(\%)Kn޸u =X.Q TWhI杽ޝLrpA41<ǁO;\b@1Jy wHS~.[*VN_|X>~K(#ES#yw $nf*b'ns2gT6 )cxCk~`а'2M4..d GJXwNw/f_Ux[JU82Q>f8 \meZ8[s4Ш|}>cwI PuRO['Q{qi%#ƞ$q.qOR"J}P٪.!d`f2B &^۟ں@9"L 9&asVOJ!]&ԡOC@VDu y!D7PaVL\gĕ3.o|؛oFBz+$["~c2opyh;o=[NF}xO3-t"#$jݝj 傳5}plhQ@' Nq kA-1` ~pr*TbӼavA61d ޙ# yĨ)q@FER(z4 %ePchI. .b9*Wgiq55. Q%R#.2K8ؾOzRUM]l3'z[M$hY#Jf *>dxNUOBK;$"@=54)g3HȰ s@JCpύoQ?W g`L1s"ר!Se3s)#]~\!Ȅo2$K=DЖEp*X=ʋcPG@Uub[^Vݡ";?t${*Xxfː4h-ot36S>qlw4 zV> 7^ӥ}u~LM& 5]L1")Cu=?|-*I41.K/>

    Ō &,e:fr$#+80|5o@ŬH211xPXԮ Q= Za߁#W܊T0Q@[3 rl~~" M`9o3"jtްAtvL>0LǼlZfe/˭Qm>GfbDgk4Sd  ާq!*=(q~K607-s涊n4$\btC6W˺oi~i:mCP_{z,l YZ>C?v\]p>>77HWy\))F 2`f_PW_:lnk|kT q@°W 3أl-Ʀ:*F !}>&"W|: -V(ao膊Ս}. |r"Cw,;?5,%?yiө#ZX5_@V8u8At&QP !Re!.>.ל>\0$aX.i tІ,x#F#u?-"εϠu`Ha3. 7q}uSطS7mfz+}wz4NvUg ^"1/ ({Y8`Mu<eE/;DEj <M֩eBKƢ C}LC> WXן+lRn?N+@__3@¤|z6\z:6yy'$)E#/Hkm dP8]W8LQ/AޠW䉔tBn)ts+){V7wL[Ƚ=lJd'|!"4{9竊Ӎ wY}jGk%0t DIsfI`ok>4JM؂(@ %{Y‰7{wMIo*G#,g7! #lbI.#d+^z6(M-hSV㙔y-SI&rz>W Nw U;6Uze?1!i\M%Wȴ,_{7zz۬J u>pHHfŁWך{OlNEF s81g0Gts 87Y˱R=v^{/hITYrXMƒrcpFx*K^NHX(dG57a}R@ALvMT>0ЯX4*%6aX&[.lB ޳f=lQ'2t10%) )۪f9٭ iQp$"YJIJtИG74#o} %JX~^6"HIAs߄9 h}bptm-6>pRR=Gyq*²کVѪa$@jGkt%0ԺT&F(7 G'{@1h6ZqZmg~2"~W)#\#@].̔1t-\NtU G]`ޛ=zx;OtYF+:"oE( ׃BH)$0oйƔY*lyٵU\Sjlk9v7r3džW@I‘ }X1dPJS},sk@pP;f~;-߲`: ֏%+O]>nؘ_?)9Zs#>~d!` @-_41 9#V5 LgSmUX X%t+26CqntDcp_]!,~zu]S/X}50_j+[h@x)@W`OML4'ϗ !gD"ئz_НY\CӋI:dD4^HDo%r*R8k!$F6O@8b]GEѥo"$2b@uXۃaWlSzd'sOЧOS8I! eէr,y #'́.?4+!hun X^ADiK9VFmJ]dn Kq.+}@i!Az\ȓ$W;Ҳ@R70e%C]67݄ޮcYw4 *>Kpd Aq"/\I+t2|_OY0l-|]X2 Uu`h!ƷkxZ"J+fɫB36Y!x.nj$_Ⱥـ7NUoؚ?0~IkjpH1*jMBr)A'^fɵ*`s] >T+5^0#M`lԂ!i*{Ao;x _-ߨA4?܆"GDm"菁/c Ő dꪛ>?^20D.? m=QyΤ4ք$ Ԍ!%w !\#ָ(ݱ QWWyQ]K!vfs[Nfn,_~_>XQ`@9~T@q}@º@I @yee)EB "C#d0anՓ!hSxNL (z |r^c熱Z88h6HEU m ͷhS#+9{Pbge1B .Xnfw[pApO_~ k}i5#!^~3?d"͇tb7.*'QB$aMKǺuch#Z(n( 6`rZ1sz5W){m"U0DuB 96LsMQ(Zws  fHObWXçckPf\8m[xx9@)!B\StCpԨc,9AYrC|hrsbUKn|:,@?"/n'8$-X+){XlT\lBLļ0xǔP*<R݉џ;3t9CC'@;.F 8xIymJgS|<?}+TSy=.F7ٵ7T\{K5Y. i>70#L bo&w343LŰ>=tGH3>NV 8.W50f |;#rFطzWNsR׶ODQel痁RAN7ل6,#Uvɉa>~DsBV!Cq6ã 6}wܿ%ߩ Qpo0 (d\ڦbO>C. Xfx%ygre+g 3\B ];A 8AX 40C*Z+5[e~wrqVGק\dzhn_bq_vkL, /ZS26+E50uS ٿW!S!ؙa}5yfdQf 2QkPLj͑8/֟yDz<=<,r6$dXD^(v.#8cL'F.q X诽g nC?wfTc6`z7!>.U!%m8E WܸyLU\ZV(6*CTzAL^oa;U)CB 4x@:|ûqTsV|#5zm>wlb44>W_?Ugd;Oʧ-FukTŝ yu 7(#n s53_ϳXN ;u3n[}רZk.(Ց0;*ѻE|!]F&q coڭy, }LmDC;JᴆbQgLx#}+*Tu22]GBS+p#1zX,Zb%Zc[R&l1A*yb͵U865h"+0' 6=vi]cZFS=Ps .F{Zb<tT %bB#+U.>w8D Z;rxy?Ƈ fYLoq-dKg/Z@=!c&řr6uQ UG{*J+GzHp#78go?̖M1~[bz1OQE.E74h+/G ?.&o;+WU^-Iz ~'9<' E g括&ˢזXnuQ>3ͦ_OC/$np(03&Luz՚ (B&+F( E8˻>0WH͊wo+%Ƭk1$+?V >7s`f ^@tz[ndꐐh\5>}Ĕ_`.R: P0uC?LO C(4!PcؕԄpq.U=~FcB*XNޫpȓe=sQ)3T63̏CD&j<9R uU3ɪ|[h+YzsUx=VuĀ}m=4z1׶Oy𱶃 z2-H#FdPY`ǫq3|,٨_ט[Iת q 'PY v^ZD l=g dXDe}Ms:#9ѹ3 04ڈߧuB T74XPF~NaU'9;P@Z@Z c䂛q`)D{w4/؟j|Bjե-vYb~%zϥмȞSnK2[F}l3p&L՗3.s - fȏM{e%uعpAfv]6\y<ˬÁcO|tx!<'d>| zo28Nɘo"TFDR"t63Ը,H.d"UӢ6 k+*:fS|spȰp74p |-p1.vO7F4ɋT7ܦth xcsOH)N&pU.պRlPPO86G;)oD)?cy8IÍegq!2=ތJ\sޣ6qW͵ )5]!flZ/󸔜&xHNĦxh/9u6ϫfv>~$zA;= Ʒ.׈X|e _|v.KTǟKZ#}Σ;*NVo,G7,3GAȲ4<TbSH(3pBbHm%"gYK(,p͹4Px^屠;_`\?:1溦l9f2-!e:wK⮃vϯOKw(Ëbμ3X"aB`a'ˑG|Bo!WwHϒ&p|s+od#r5jx D:/{dƞYT7ei9ֽ|#(;uQe[F3. LIʆ.}?q:J3pY[`&<BS!U X<"߀R9Dpf [ [A d~'LnB~@OKh۝>X>te^gbW`l4Bǒcqk2g2[ :#3ĺPx1AM.'^c\~/AIKSFP̃ĐZ[zuYA$;?+kCZ<)o5q>@)fc 8fm9p|n-@o a0 5~( fq7(k_: WLLcB0&G?JacooJO@ N-Sq984PѿfTK gBϣds-i5:BumlJ?kW8x2a4}K`lHUb1)y_pl oe?]'<^ItlFٯ7 7-e7Y,rRa &]g! yq]ew Yb>rJB&gf,6]`e` '$d 1dZd(fԺZj-\3اϐDBF@q`]Σ(f04!яv`@44x&o; 4W*942|6XQ^Yp]^!*P>J``3˾_׉\[@ r@Į(;A<3YEO ).ټ&Z{}/=`>qwYĽ_atnP,viڙ!d͉'@;{88.nġSez*}V_VZ=?wn_`! ' syJ&KT+Vhd}W]. btqfX^`>°9!5.gRZB&>H<۔>wo{ڵ4ӌq4'CDzt;߼3]~CxnUG<nX2gRX6DnV*dVգKKjkB@X/\KPt Q:&u :NgAMkޜqSÀ(f0Ӕ a>U~*"m& IS[(fڮ4cHS!t>Oٙ[ZMD] \mR&sr6`RdJjg 9ӣ4tz;CexGSriR/폷ô@O8FS`b"i~?n+3KO0:9WN;q_0TՖL}LRZ rO9:IIJ5cccy@…:vV[D@5+4Xtk=HX~4@ X{h; @G׿܁=C ⠑Hd4M|ӬdGܬBsTaKed5㸞v`m}9jwIjMkC_Ъ,m;9+AޱOh7/,-co6d1Oiz4#Y+bzQk% OXQ4|+}!ISNQ#)Rj3qYPf!gkb\_7 * #5=dw ޭPǥN[Z?xA)$7䅤0rgIֵ)&0*AʼZKY3<g| 2\ EȞ`0,~(ZDk\@@]rCW*C@2ّJ[;^tJBo>~QNׇ.cmHr"(5KNbMqfg>g%I%jƌRn8 bk7aIufp{铵hk\i3_obk5E1=*W%pW'>տz5 }Gp0W袊ʒ:ytVG  DG([:ƋgY7SsGpF/V!LH|S["S) .;Œcs%]`u`gh{^)1ۋ`jZ%GM*!2 ,wo.tf HN(?ɝ eNCdsvS muu|N"dq{j=5eB!lw )t4_ɠ&ɈCQ=hvIϟ}peZdG}{hR2lۑFxC+^I= ~nu.G|Z}l?6ҊO֌@mh1B<8 X͇v7|9̟KBKe:dfB'l+2ssŝe#Jĵɝ)!qkT0Ar NȠ޴ לj؛N/q6,2/(KxB(,bP ф賒uWRbX-xr_Ŗ,sGg#"(cXi` p Mv@-0 )wYiPq|w*m_5`Dٚ$?:+_&cÑЅ`h$!Lja0MFtt:x`/JXlNDZM26}Nt{*[ld>O5%r! }%>=igItNƝ^~#μ3.Q|~?Pz`KBw YjN%$x?oEBSRN9%CگAr4$&5֛ E.=lD5dd`N%4 \Nl {!^>dE03a%q3߰|a#[0A YC` O?kc \.XaN8č}v3Tr4$ 0nH1`pd;g d+g- KS 9Y:.[{:64'K.hfwC9t\(N I$W -,YQ&}r{l+ }3;.1X}b|\ZzPUAjy৪]閉X j hiK/,h.俯 Ұ^HLdRCÃ߃}m=*x@ I"6t,%|Lwv#"9C-L ȹPƚ{Wt(qSHu 2A16рPXІ^Pɒ$'g L\?2a~G/#( `\|p@|wOeaw~rwr}mw;<_77tSXćРvMnt^kӴy{$>sk\9!P'ߪMsco~ǐ|ns)ŽK1tSn'ͱWAtq~w$9Fӻq_m_6Q?FUq_5a 1z2toԳ60I̭-'M8 hvaS?@'Yǁ4Z X3̉y@2 .>P$WkF= p(AӁ0],W0L-`;+FJ}jlB$j`ANVm* A5^Pr7j\AOļ%|2>4;{ELCV_[G@iL̦Jn༢BN3"/3!,~S\Gc65E3&m쯛TN LNp  02UAt;:ށ`D;<[%q%Y ]D g,LpIv\) \@`Z`L1`mX :AGHv<?hXL2ulk|- S%' ™|S(||+<g{jQ/g7<;Lf¡/R;Kʪh5cT]L v5 >(u9ׁJgv Q N߽2is-0nMHX 'c֧^,,"xK64neC_ˀIۧ垵'QyE/(EJL=T|L؏gK%I:{=P3?w6CʅH ׏%".qp:"*b7˙q}ŀ8HꔳB]r`y@GIDZRmRIuUP*]ia \` f_G w6 R'9p*Kd ct[/8{$@WPN-WJy;4<=fI\qcA >${Rx,o~p0`U O gpEc$ӈD'2ut{ocj]?kN߫?Okg93Tw\`A{zG!\& mn#g=d"C T,wpMV5@DkOLcBր$', (!e%؟D>^j܃,xDq1%uh|xPԹPj c32&5֩pWUfZrYˋ;߄\ ԷWR 㳅|S~w@fkQ]JIhՇff@쾊p`RWeth桷,D4Kio)|րLmY# i$pr=8+8eGjS&ò<Ǘ$zC6ԃ}DEeD|)㯛~I+-Q"dȜ"(\$@AUs4 4%X3tF 2pAe pb%w]P.IPϐx HOT^.>"m:6_FjZ fgyyKTϙU,cŠ32%⢨cO8;D2uƜEɤf}nw]&H[!}˹ƎN L$vX" 0sCE@l\`mfE,_mIcBϢiǔ*7K&(AP<5N) :BNO޳8>+ y~K6=С8O; e/ICP|x%֏ٲӎ`KaݿbDud2F5V ҉f3Hd9XR"+w~4yZ㉷bDHVI@4LtV=3M]Xҫc @l?2* z'aѡe&SG/1p3;=0M~.:`6%w޹{3enիW5)$BgW*!k#cg:w_G;|U6VNܼD5 Mq6qti<`OS9+&vZA0f<(jߋ?| T;p)4 Ue#|GӸ aD}$\l-jw,ͤ8N\nϑ%ZFs4 @Z {)=L)MA _CZ8#nvSC03NL-3JMaIljg>DI# {.R08i )"*C~@0b fmR` z1́^O8`pAt7O.z|Z[΂$Q|iJ>2iƹuI٘e P| -WsxI B["$geo!  O7Z>BnroF~|ל#S_hYvx!ž%:hG)|ŃESw?O_2I }'+EVvnlsS_Wvwlx'{ .?5\-:WvMD pTZ ^pcv-7B|z\Q }v3gO>OZ't<~ d4O/Gބ~[8ϷF0Dꭳ>Q7>:s`?pTW)|(u >Rྨ{$-Ӱ] lW%}WMog0\ B-C7]4`@3LDjٸ8Q5 9(#X}"8DJ<}a w/#[@ 8\zK4 ώ[)Kj=p,H䯸GɸFfΥ@B^HNM܉8{Q$v%E_t}@͞~z'|ܐ iR(I=NjKKVH5\=tiغpyٯ ŧ?ǴSOOb pil4FՂg5Ð7$bM_o,݋ERY#KZ ^~-c&Ό:wYħ#c'SSЍq!{;CiN] pNL +KB?MոBG'&W:bÁq`6d qcAhR|/$+3{/̀ɥ ;&']!S~7 T5'6/P*24\45v>L_7m/OdnNAeiŎ?.{.nqT)Re< V+|>ao={D>C VČB>>ޯj[D6 )f:tXU!vd-:F58/3.dhCw[x0VRbŇk@hq2š A_9bI1$ xpp*@C08~t_ @O uBߍ^vİP(")/c0u~>"_ I LKB #?9`Atv.Dchu1ťإ?nE D֪JMYSMrhW9S3qZ) zS> @Y !0W?E:[kd9+6AvO;[x k?q?~?^k'Z" UjOEH^vILG =QW4L`> @@ :'f _;K4@˔2^Ѭ1ص:E D-r/ds&&L8 Q2ѢD?4ĖIq@`cvDB (^XcAz 00's_(:Ϟ VZ 0I;M$b/Dsx/0lN>LtB:4ޟ):a1.; @RBkYl3%DX[D?@>FA&w2 ѢibGYzzC3oIorb0`|XijzVq(p=He _\!}E5"u|JhqM N WrǞ [xX(F( H7UDE갃%L-l*'#Rn*4B`py5Ǵ* I$ 7   {&ɦ AKp6`Dm¥` !=ħvܯcnV> .U䞨Uf!Luފn59}#'25϶*R5VnX|E- &m Rj\F0לk(ЍzB={ ~ZywDZ1ҟ'IYj RQw&y\#Ucx3Gb{Rg*sOvB/.d23Ozj"yuHXBLeq^!Әa-Y:)P,A>Q4U.TX2+VK >v;UsE @Y@  D cp Y| sq߂7HaJPfqh4l Ņ@k)%qt~x8" :u1z2@ѱj,$ 7~ gJKYT7|㮈I^r~W'nɣB2TVCnQ-Ts @K?e`.wCP Ic%y9)VFe?xwJ w]`tvoT4Wc"Og*RvWw]|iS^k<Պfs]nmpTLCZ!wЯKHrKc6{`gWS_߮''?C#8-eH3G ?^#;]0 CuAi^t 6q:{UC~YTcjˏq\;ՙng#tI/7)k.K}BIst n0X*D űҸxp ' 4;#3\VˆfNdĄT& Q C!pаL{YP՟08 [jVrȠU@ƳvY_rngu`a)S_5Xw[_X)ntop&QqzSg7p8U(F-n'-!U%?XYXgVNk5$s(3Gm߼<⨼t¡wt~ ?'zA~?7p}+Egvf昁g˶kR>2q /3Y'\RKi40hDeL寢GMn:9T+V%^DY\f}݁bE%@<%t+Z|@qK-`BÌު1XJ?] ~ {j+ӣj~Syߖ6:2G*x͟ 4AYg$Gt"=Ľ\\Kð>wI3sKj7wJX n:"x^cLHn`ֽlJj=i#eePɓ#\,QI>ʯh^b2yWm'ǯ/g22&r, %J~J?_)45IK` ~ƀbȖh_i&|!،x>+2eDCbmr&+:і@WɶtrCod7b(( zB"H4 N+5~`2(.D^|PΧ 01h(?>kjQfHϧBIp2psevn $Y{}SF##X`BnOBb;D`;W ~ L$;ݶ[qx C1!u T(WTXȺG9[fۅ2$EGݖ{:?4R쟲O E߯._a/h[%'130 ;~i"-qItz}F)-! პXM$V>Yw噍뉊5[r꺿I3آq|A.1Z@/@(J.PX6<*6v$l\-cT"}Ow1h"I3"?k0$.fq @+) ̞\@P"4 (l/ NK￧\r]|V7(70Zy~c;LBWk%.~uv*;_PpueςwkE`Gc{zS=aOb%X8! 4B,CƑ'󩊈y yEN( ^\~:>?q6]e :a3gˮp[g_2-[#)הbcR#(h˃3 :HN#e"4$a c/cp, ZBld`CVQB_h:b?w,W x oj gìb z4Z@.9h}w0HvH~~zx[vOP\3xe4t]%g7M+V%#̏X:.vO÷Ge*2cL1`+Kk;ϼKx+ C`4))7r> (^D~ĀiRL1@(=C6~ `l`<\4WPA5 ng@ 24Y3?9i1(_^C`U3hS 6 t?nNQ$B2H _hѩw?{{Ĺ @/-\ ~JY5B:ԕhO8zUpk&)cH A:NL{D\.Ի 3Qv_+8Ms9:/MIc  r}ottp/ORܼa j h8  B'gr&0Xf7^>`p= .3ߟ䇓\ HL؟C ?1AAI=7 %z=7RCσDTs`'%^0Nn{ >ʮ(\gO_*3s[)۽Dxk }kͦ~4< ¬, 67wBmgnEf_ [.ޯ8J9kµ:ŮƊy7 M ~EBl_ oLݎfl~(ԯ' @;:4 t|(;Jk/6)tKw=`HB~!I ] ;@z;i{BVv'(yT1cޔ6U@kaR~@oc*egmj瘮5~p_B.ʓ`H&ۯ(^ gc&ȷ t"WS-'4g o3XN!UPrT0CEArC-YV!DpkˁR@(4h-+ 5Ԛä!r,)bh(j}3Ўr4+='K7>k @ :@:@c36}iQ0=F OW*ǃ`X`P\rӤo˵ Ɋ} ~ڎ{9zLb0oLE'1sK&`d&2B+MD.1)!M 64ZCы(v ~~St]耊C4?ωk?'ɽ=Z8+.8OٽF<QSDW|AZ4)O{ѱ.-(>R%}Ӣ\ÀYYH)k(mTʛj.mF4i?m96r D*_IT`S<ߦbn]~gA6(8ƿLx;oGÎ<a$pI) M51/v dT@ElSb3(;}54طpC l3 LWa6lYΩRT7Kωn^?tb)f(,o~jlCGccj}+v_VxywȯfVtA7nz&J9lhsB׀j=1is)X :"e">[@! ; Q5%$qf^72vK]j2έFms"Cwr_P]+f-Y)܁OJ `Q5 @,L]KX OIV ̟T|I  C`4 I>4S_$UzPh~߇k sZ``W @ f 0HKA϶")\~Յ߄O-iYn_hq1hwA>.&\6m"͢98x5w\&6Bh(RPM CZ[~ʃpbr s&hU?vBQ,O@2ywkhLuir/2AC2#xR؛,&~(aVsvF›ۖ3*fzJ25Y.ې0}켾OW:?Ƚ@EVZ0CńM*nNMvY4Ս8~w՜ |;)"N}LmE vԼǹmD5Py9eCycW80yJr>~-C@ oy86gP%g-#=;w29x}/Dq\pFo %О @bsl#0L*,O.*2 @|%CN\* )``ۮƿԀdOSl"0ހ^_%5CM[R~ nBL5}g0rhb!4(NAMמ ^LD!~e8e[7 S (mUC\AgWT#,@9) ^T&/؀VҼ]: ̻;F4igL %/x]ly3zS"":&/kp#*vTe!jع&EkFy_"VI2`Ҕƒ#j a) +YN Ӓ78i#鹊I,> @$*"Ӣ+ c7! j,BԘ6>uLAF5 G7X ނ04pQGF sΟүYQ6J)ZWQ;?ϱ9t 9VZ#dO|b=ev7YҌ?wHHP|"˝bB=arYBL„D^wg ehnbJЬLv'5WQ @y\ Ej o0N%.0%etvLP-z\`7v]b[#d{4@D0^ 9 & Sj \\}D~ARgEz)ߊƟ`Nz{905,5.ѝ%$3-HlCⳣO0ɡD{&ѲAv ZЏi%.;\oﮉqT.]~NHK*m&Shs|NOkq~"}>Zc!M w>aBbm>GĒ7M:U_(kS ,]8\lܴPx% ZkЍa\\kgY*<#b%&BU]pP&DVzZ=)^4[v9z5 ̛0pɌNw~R ,ap<w{N tP @Ѓr_0 ]q JAoPisP2/'pY .qdzrv Y`o0YP9C=ߞ{RY/l2uuH*Daŧ IP~k%pa[LI)[UC8O\C\,K̻{1X }罆OUO"Pc.9?e)XU|~:YVMj@S.@c5%bJvMP ,eA s, ?ٔPG v{u,Y4 XeC5 ӏ\ŠX/ҎAun<|‚ L@:P gk O$гH AqwY 08G|A@|dC d*[}چft& SXrj&f1@h\m,ݘalD{f8rcW` Mq+/ līA9g9] o%cH>8ë{t,*NgGo"N}h(8i_P>5E g0BWӣH:T.!5s 83rlԧvakB2{u7}fT^T亞_aKB!\vAh @$>ޛ ;{ɂLd=JaX[V˱$pW<+=(" Qw@@=hQR%J-I LkN 2&k7Ns07O3[e BskbL ߠ:Z!3y1}Gf^g䁩kV|k—77eLKO IMU9s(Tm+]5ٚ4ֵbqGN͝ q锇}TXcq,N3 /r" ׻݃b1TN>8&Wg6DUshītlh;m bW63wzŒcLBÐ:kPOGOc!Ra8h|;{8 >M@ 7x3bP=q+C7 z`bZ戟p;A+ dί٤ @@ŀ(a4E!A_#Ÿs (o䬙Hdv[2frLu2rbۦzIc9W{|vMmS&ǹRF]*i@p>&jtm*ĩ!?5 4xzS O|tR T řB*8'1fcdAםϥ{ 2=W=1ppU& 5 Z-pDzykNA-4V`( ]`h8s.  ʦ0b!0 gko_0HE5!Ap~3`"Xf (= Iwa(o--q7j״\c`XP!<./bO?3ѝqkʨ6ċ睗DSTNv*],GiFm_SRNTكWE94-~^{@S|84)rzؒ2y!H2 \ޢyT1E\46+8dȜ$P)Fsn%pSj?#OT>dJ(5 %o4Z6l\M}M6@0g` #w CCp$f!*DĈ` Kh@wUXo&pm#$ OhLi\a(SE2po= \pw+9o  1 2=5\4 ~x0wbޙ E :?MӤl?zt:; d^@n"Un*|u|TW){P9N_;ݸ?SC_CC%g_>y@ )=xL )E\U02 `3v:^Cq$seg9FV3) BL]n{Z@ }$ B鷦r@%DB .PYX3:HP !t-!4PUC@m' C A?=_{xpA @ӠTu;t6~CJζgg->\W1-Yx8} ؄OȤs{J;)H4|ݟ2ȵ?ԁ[4<<~V !|Xl*w';}p *dkapSpU=2]:ycȒ B0ϐ_WXK!b]K%.RZ2{ܢ^k!O Z3.} b_oD$&iAo)Z (cLSfDh?08ց@}VA. @@u0oKԪ'@= p|!aPLPTJ58! ~,gvO6&3j0ViQ#F04vp( f x Q6ͺCAdU 6G5?nI ,S,aϩ{Żî'xj*[_/ZwlŐp]<p*~>=)MpvuLo3ߝ}79%76(?]YYC\? \IvoCBJ:Ĉob.KG,}lob +`,&! Vyl_ɞ˙M 2`C`].!{s j< `@ n Ď'ZZʋ/1(EA_LPx-T: Hq;w4H-7Fv7Ů40 ^a*rPBb@G)Mh"lR?Sf O K_y~O5O?5/ qH;ܝ{>Τb)d9UDcH?2\%qYU>Wq6[} {=`C_rױu\ր5?R3]8k\bg9!!S.xMS9Hx}.hk}Uu !@-f2&)Fq Xxi>k"\X %@}c>XqT'0^00urHq@̉$"4 t&#>(@Uw~8r  _dZ \&8poWXP@w"X">G. X<-:f?@i gr`mҍǰDLE#(bUt~ ߯w )щo"wR!T;^ C'=kU}.;a6Y<.TYos8Nuuax f.n__h/hXp$b=,E| 2s`SC`Zߗ\]:HlNWo5t~g: HOAP<-`}w"P-`\ N`6 4D ZWqłКN>1 `$`Y8;`0ecS>J}%U+gj{rŸMy&L&}p: B\Px<Q*P$,[ﯩ,%~gߟ G,p ;-`g_ ]~k(r xiBh<JQjH15; vd#' +ʼn9č-O㯧ǫeX:Zc TG m"Re\_AʌWQ{^!-#ZTy@Ox+A{> P1!fHlH i$lo0xOaE AK=]%!#`}H%V38P: Y8c-ٿp~u, HpGPiAe,̀A JB2`Ķ%X/lCoLKQʂKMtgf(me8ޘeJ]. @]ǸiCU+ m|T駡Iwv 1Y:i?&CN<􇯗g^4d Z`bL6Ӟ8w$8Щ) ^[Fs|Y ?E /t )(TA XVK=Bk@ @RopLUhy'Bxԃu8 <BkŒ #• *y_`Q08A,x}CjFB> tWOKjBRlk3^] #;l8hw4HR8(ldSW:O 'F#I!'n#Obk:T>C@W{jp )M8 V%j)ĚR`kaH_~Ov|ڼ%Ym\:A1?ѫB Lq*g_y{ R\!DR5~F&-  Z'u!2a'0.܎h(&zH4F_0I 4r`@vša8L^9u ̔ TBȠ `y{;)_uxD @Ɂ(MvcN}@^e̽϶.<|~ D$?ZeUk]5cIκhKHnBZ=<ǾCɈ&܊0)kr(zTjcZ_{p} ;=3:n'Q2C]Z(7z0+erp ,\=ex{ A4S]-^z\#G.1nurIo5׀a5Ŕ Z`IWv1۫NelT~l_Z7RBxdӏje3nrqv;ng5ۣ &3T\m3^rόvBCs0? Ư'T+zyT+UDtPg?DFY,tE燸 sĵamӅɼTc=^9TچaA&y(pc̛$^.k@ =3@jTd4ӕ]HAuzX{|#,0pd):2fJ4ܻk0 ?N'0 ׂ @B$e'bB $%K8'&-$h 'k %*F.r/B.!*!J[b]ip~BB{Cf^h!wrT*ߗE;"]bX;#*p FP`*ݤ isg7M|+p8)`VbLD҈?5?ϣv +b~JKypؼD ? 3 *uoX)=Is.8~xrQk@㙾PJ<@I3JZcxbB\-$\K!͢Co9sj߽?ϟ^N`9T@}% t1 !_ w-  Oe?Yoj Cop:5̈'t*&=>O t+zax;mzBMs\C L/h3BO2ɓa-? ~V 6_횒x7ؐ VFPÀ ^H ࿞|-rrxR)|8@U)GHI٭.lBҴKąkzV lX*s\Ӛ=޶8]/"83. [pP;b-`kMM ð5{0WDCw"Iϼ0 8@^'[ /!ж`P+% &jIiGUh0> 'Ÿ? % ]ݚj_ 6+ѭZ9d, |MS3Ǿ[.бq=`տ\"#X ı |=*U0!0ysth&8k"PQ{>J:/jyypQ`^ܺPOOi%7Er $rM8 T";B.%@9x1cGޝui*]r=-&DR$Oga_ ~C9y`GO,!3毁=~y:1n8h\LL.PU h[;oLgRlZ×A<VL!$Մ(i6NzStrˊ)Yu?ҔP~ulu#d5{.2Ȩ3QZ/TY 7)O8r$xiqd;-Ҵz;3@NqQy) 41?Vg)J!`8t2 2"c?`" -;X:H?4i޿*E;7 z,|h|5~; 9 0ёLδ;C{ %t\H[8-ޙnPd>IOo`4g4|>+0: F't~u `OpQN{pw0i/-,$;_9OF/0gK=紵wsa+X3s`PϪR U-.'j/'_52@ݤRvL=ښ(b Ȟ^LE)}ys^Rk(!ʇ݅Y qP`)BHCXu"Hp·i_qz"wU%|\W߆5%b- ;,, 4Ec9*+3@RL48T^4P ¾hC~ϵw~Zܮhfs-] l؀f--O} ~:.0h0u0"20aXO8xbpoOgPpB-R?ҕP*r 򝍿+ YPаNI;$dk2>MlWc^4ڭst3\D>OXO?x`wuP`L~q$b4[o <"I0&.TQetG/'wygN5j/_ZRKc1N46LТC;~#łyAZʇYd:%O+ ?J&%,]0傚e F B-͏oϯ'|c'0P vm(`01@ ƑŜ>OrHk 0wC %9'xR1#oTgU&/pD0 y77dMɚ)Xx&=b]U^2=BQ߸4(\j>Ur?6Hd#šn`G7E6_FQB/|+HtOthh=NǬ'?nk3\CoHڌu.^ ar;$:"r3298 }5>d`H`#IC#8)03KetDh{,qӝ'z zY$?߭72$:a4g/\K L\$@0R&DM90 "Q1q~i/w^{LLV: AL͖Oju9\, Nq!yiƠa{H6)GMt !w{g٧װ$WF5 #߰>g.dF[p,y0!5saw: '_˜6Ol\d?k#S'yU Dtc%b&Ɋ61 O| p]NLğu]fM.,DrӀZdatuE{nzÞ KW~ŜIZCƑ2|JĖAԋx0bCNO\9XNlhvR;|q8%2׈i.op":) ޠ^0A#:p,x dI_WC1#AZ$B_4U,)hKDk0o%uQg6(@"O-j&HH޽RH{6 Q5 p]# D ~W i{&P;(yCxSROԚ6yp(/Y;|/aib}O%O*5)S KD5'9da:k;4KߣIԀc$9fmzeJ06 O -M8C,ʜ {R,rx~:xRyb@E]r;.F0 ʛ1*@ ({0LaliL|R00lW@dDwO"tM}kﲄsGCZ!@r[_t_瀚 :?U(|T@t}eGN!y:Hl\iP4d9"`CGWJw2 zE_Fy{Zi\#bGNdV;0O@8N eOIKx`"P @k@/J8,N4S@mjf#ިV P\VZ[o+]>W3y s`_~ L㊈ ,4&I_\wGx$Ġ>[xoiuѕOg{wY ֐hdmKn.^z)O ~*  *5D!`z?WCK\!`LnU * z :gkH>А;yƖ)b2Ξ[mj"?@݋"'$.fT!$lBMo;[0{ w+կ1|%6 c)/U !u?bƴJ㮲w m;3Jytwz-uNNtoz}.#?Q}};̻G7>@B v;HhO/b!op2+_cLrLL1|qX!Oy"h@0ý[My< d6'h>ˈjL\.q@ v0bA3Y _5':!`nq km4SIpK ?) htN?q9 0΄ OtL * r x!ll L@MPrs\⇥;Fc_—"IV'ҷx!c2k !Ѥ= &+% $RѾg6ɟ AtH: @rLNl9Z@C h.<gB # 6$FYE p?~h{xj?fh/ydB^PL?nS8 rH* @2nAOMMh;R'!,Q"@PAy%MM(#F0`A50p_ `Fz-gk  ΡO`_\΁F0N./߆,6'. 4Q+JQp@t#WP[sXo[n}LlMtm25r&XB(=&xdk͍ߏF'ט#p^_'fH&#"ZՈ qr s93sHbZVn,E\= YC2$?j@JQpcA,2ۧ2!J!C`APq}Bq  4;pN cuAo@seǑ VSYvMlpBA3r@5Q7@` @  ?@Dr>/Lca3`W`7BO4 3 =ϋ% nac]2\]G `nu1:a(fV|=SD~\{o(}S&?Ii7霮 9coP噘ha寽[$D/S \\jC{y㊴"mZ­5..Q}^H&eAJrY|;QRL,w'{wɴ_| I`Y pFâ1}g `+@lR7:*mqkԯI!$@@|ޡNN;?%^60&,%3 -50MOhʂHA@}!H.^A L_'tL-WZ`R!9rzk\S&މ$IbN@K?hTCUsZ&I bKovs<V3 'I>_W"򖅖ӻ5ZGJJh!.%=޽{`@vK5K  ȇ(!uz uw"!Ȋ(ĪW[\)$SjC@+2a *@B 4Sa&`AM]a5 "]% 瀐\0@djvu4$L @#;"؅?G<Ÿi78^; SO)gL+`G/Yǿ(W5uDտL8n= `obK'2& A`B_81/nSm?,B}Km,G9l3e"Y͗>sqmu;\}:<|GOTa4({h^62eH`Et!lKtV"9N^uC‡C5#Y* 7Fs3Uj&GO ~sW#C$l .P%:twr@X42H`J0¾ #?`XQ`(Hnt. pOOL@  i2/Y.í>nC @G^JCܵg#nEWUxخ-EO4N.|rޡW7&^‡OthW@24=^ߐyh1ԃX"[k!|&Q#?P mPP KYVd 6]X[,ak>VB_ zY=7ԳnjF?)*VCO?7_0lΤ/iq,@;Hk34m0iąb(ֺOn򓶦2=A'"$4f‘$D@Ns$C Ae0G<<HH.+8WׯLaB± c=(P LtK= q&( Ɵ2vc%V  'pQ.͟|WTHg@ـ:f+IE*DVml;i0聎1RDx'klv/00UA(DTFlj`HШ2ZFBq7<\s/.*#t)e'b>&fnK& Yd U\~@,i+8lrymHD}ļ'Òj$K%/N DdU12! s0A>͋8 `N\\YF Q {$ILq.P% x1H($@ \Дt-Q׊~#Fpж @C c$& ?xs r@3:~&ƿ58&0r $`న3Hvdyj9&G!~QQ*#=nխK᭘s~7 Ý>o p8/].s4W9uYK@S̄ec,l"oJh/t\5;&^`.6qMiM#m\eMT}ĮI/6 r?=K.00 pO*@ƾ_PaIHlIc0#@uw݉lHc7PnA Ώ7ą+gЃA} &}Ψ-OXX(6*V`~H9H}\]2|Ad \hQgXPGXӤ~)5uR\ #,DxC@HJZޠxVʊk+@z;%x#d)m DKL`oǃ>/sO~'|b\jk/կ5BP`89 4CgS% 4IMɯOmBx(SZ:qHBk/м˲nE*ِWۀ A;?p9%Ay $䡌QpYS݄8I (nW3@#l]7@*U`ZQ0c 5[ٟN"+_2h54@$!S34.gч9 @"]]KA?=)+Lsm2~6s4\Wba{}_>)1Sl ]6pgmǥeԞzCW9D"cȻ;䇆h* }R@&X !{ ̵BhЇ,QqviB4AN%,zž@ʅW;r' @'zPCA@jL]>ޗ\xS>hgGegHyo"Я`Og\0 `ݽ߄84@m(wxoTf~i^Ip@3(yZ@(@ A Z {P0u;c2N [9C3?;r]˅8lda=y+Kf?6)Aw]⚩aNYeݟ>IIto?QN=-iLtpHI1BJ!/csL.|OQ !Bb hZ?+$V1@9I ?[@wovXCCFP$@ ~;n ) "w a= V-P\xpyJBx8DƝ !pc|Zz=wfPkR+OXw~V RmP A +`Ch>BKo^y/1#{`1EY!tAߐ`L>SfkW0)69[ l_mv9n~=]o h `H|qx,3@&CryCfɁ "叶&E,Rh ( "Uh T|;4"0A{K1x@ߞه#]3!JmF9Mb8LQ@`!DT}a8hs%$ "2{c?7?r)kKX5`į1@r M \?h j1(Jwj̇! 0u!00pD!{E(Fm%\xHS6&sby.&a[Um';-ȱ.֢_6?[>r'ڋTB~J6 _G~5δ { \bli#8znx=DAa1S_X'ƀMBhr @%r{x/s-C` @%`UV>p\͒+xf @HF3(^ifXɢ&/(y;$ W0aG @$Qݿy ,_|Ii$ sh֠Ey@%Af,|GH@P YERp @WHṔ30ɀ,@|:?cU(2a oёp^Bl]z߉VO.[ގ ¡}m|=̻#TAo_Hd Oʋtۼ=7L> To,L:vBJ i^! .ЏI *@A2!:*>[*!02y@ avp8#  @+#6@?!* z__&[8F&` 8 +]F3NA@)Z Ɓ2 `!7{ E p d%/@@Cj@ 3AřΈ) "hDs龺zs#_>R%K ?#c$Ls.Tl/SOohM= fF` NW, pN`t&z_ ux+}sgc(K-#:_ޔRM'u!I8&Y)|]1vJpbP|ԍ`Z ` b-ƛQBfloXs q\7t=+8'#L @0KA(8 H,i8Lm9qh060m$xQסm6 i_2ЀDԶ'B.P_5Ċy@_` n9YĒ{aA(4-`\K 73.aInCJnJK:v- XL9.-+'ܯ]6-]֤EǝfLN^`cq!گtdZ`5L`T(K!ysXlsrJc8Eund;(4bcG]La.h :2ޮ@\nJv w R y0 i0b}ۂ ]C xADkR@;Z 4 P [KՍL_& {$Gرﰀf&8pϵԦv3/{-Zp|&DhH_?3'_ޜz+l9ʃrJEtK={kZpqrI b.{JB܏l=t4skC@"@GgHa(p.'>a^PSX-JB\hN$\C\e<8G-_X *\ ᾩ:?C(Q4SWC` X@?4e_5@ a[`c 9g6!0GX@ ݀i$7_!<[FǯbJ΀d{k͸(yd+>pOK;&2֧ɣcL[A|ywܘ>ܮS-!/ܧ7Hk'pP%Eq C@P:Nb k8(&E: 瀧!,,X8?lOQUTlt1V cph .E֮nQֿݗO~w@Љ.ÿI/?S` D0q#幱PZ;6#N@.pyclцJ:%gbk&{XF5< D zRYi|:DŽ3H/%27G*c\A$Ԕ`@w/hvV0ddcDC#Xِ@@:N!̀Z{) U}piJ0)~QE^ҁ|C 70,. xr H=ϰ5Uk`,3_*ᢏ@<|{;@`y630X@wnH0IfÙJ#fw0DV_d 07^Z#fP*cfi g˸(|~Lu,Sk NkI2j7m2˶ z}2\I|㎵2'g|a) (3mI[\!;Ra^CX&Z@80{oy]> bA;$@!(GA1&K{ASI##LPl|C @gq~H@=[\1 pt{Ώ53Lч51$ƿO1|d38.G/ `T`q-7ROOgd~…)s_V- ~Q Of?"0Wҽ ^jc&M̵Uy]$= 3b'9DiC;?:b*S\q*)Q)uc9~RA'>k,r$ ;q=bT"~ .-9ˆ$4`r̍C.Nfrӱ bPfgUA3@jIWyL6?2֢O*AӥejCp PЀ^ϮޗZSSҠ Kꝁhz6jFdNFP5 F􁽳V^i,&[:ۤ,RXixT&ofD]*ܧg1S]\!u Q%.8sKCdPeyGR| BI#="Q+o{JK-iQlluP̉ KbxraJ*+"`K7` q f`lh! @S! lU P..z;/`NR7zaH,6Ñ|У/K+$IOSgI R.mm(1pY_b^ ȐHnPsw;IaIBtC7}b1_[1>`flw rF>hw>cmX7W|[%h+si Y1-DC4ӷ_VDک-500ˏ= i u81TӐJ/p$**(20 :MDeĪJ B !% 8 ;XH6@gA0q`y>wyhM'bIh^P+UApO!A#b$Lp (a dt充t~qQ|@z.kWdzhu~/|+5 X%MT@]R5CjcgЇ \ƿaH'T8F0mH1?g[B̢FȵY~tH{GbWز-bUUZ}nJs]J$Ĕvnht˥߯^]̼2 5=fSr PWMS-9OgٛC+؇ס1;$af.&1ȇQ@u&: ,I4)0A2({eMþm90/>?rQ)4 n2 rx8ayokZ5fT h"OzV'nCZ ~-_uV($E&~f8QZ@2 S>. #&I$ A5v\;U@ $D4](fUdm:l'.ܽ6 OBnHri=a`}'Сun|{21gm6T-k9tcVMIQ %̮x}Ҍ0D(Ar&a>˖%0sK8L@`xFC4ج;  PX 4,! ar RWpW<\J||h ˜Dz)˩i}fBSWuH Q} _2Ԁjpw{/?#Ӗkr=A͟ZG @_ZOƈm#X'tT@MK$@`nX 6MbF,}$.tng}[hC~$_$G):*зb[{(TYUXMmBnVx'N/XJPɋ 3>Xw=Ɓh(N{߹(u00DhȲ༆ P<\u, 5q8|Ț P zdkG@|I3V) ~p@nUe;]/g {G0@9 @?0Z?#āy ۤ3aUZ9H'@@@d2 fQSS;4CHBE;)^?&d{ɸ*~jfhc\_҆ -n-C-aX:%)'!К<8 )K>xk-[5&0tD7\t ϽS$ Nk/aa O)& 2G؆}ۿ}^ɐQṕwg5~$d[4B?)́CNC נ ā=!0~8xC]`l5z I Mj߄~|+umȦdsXMzg}z.}&nZv׎ަmGs{?gYZ k*-laJERodp. +Mq /h&yy<28=$z@#,* XPALIJxDb pqeIIȁm "#drQ{X U];6.ꣵU\򁭞c)_%@F7M&I/Q\Z@ i@/h@.ÉJpԀT)@ -٠ Br`Y1[@Bgp6n:!VIGd_YY>)s/pe 5t-v!;&Ce'DߜJ,GN!@d<yoy%دкH?u#q !0 I?31-a5|&x!@k!!0upRy#j@@C3-9h f0V&pc{5I41l?æZB+?7G _\xGE&.cM S#ć,^oRx,q,pG$~~!81Lc$Ia0g08:=^HJp`]?ԟǣoF4 Tc:$NIkJ0⃋1瀍 {8 ~&" nHLCxpSt*p h()Ȑ) Z>/qG`Bܿs~rh@C $#c%'NI& w\_6 $` 3O!0)t{pՏ c9^ &zSFsロɂ\Wd I}ڣc[Rh;cl;S9Tk/镚H.Ǝ Ɉo8/6 v\'`$S&cqUUjJג0у.!\"6K/4Aa:K ێdDL(+]EYcT7dP zAlO5WRwV p{;&'@' =+4N3XfbuG ИkN _To> 4RkD}w)l2 f' [pkCĜY CT'cKp ]}Ojr]B`]d0ƿ :r,*m1C bA Sz  /OuI1+h߭ܫ\Gݳ)u &)atRȆy`Sor W2xFMTT 1*a[:j"pQf+(%J)绔Ls V!0/$R4!`@l1hVԬ#p*DC7ho9s32k "dM u).P sHB}e 8( !T~!0XyXwLrE4_ :9pm?՟ ?D;p-\(UW ؀ )` ȣuq7~˔w5->#H9 k2^$fT,*qӭZBGBEnCgT ΐ'}'Hu%M3=, G SG6I 5eʥM_b N!;@itFq\.{W /+c ԅ <yT*=w\Ma7p4طd9 jD H8G2Z9]BX+Wu,h 9hD?~;?.8ߦLmy5Z* en_o<6`R#!e?H ݾ@;?tdί'IpʀâϤY@.ټia_zgj"Q ^הo@}F-mwmŐM2.2$=4X]T #.~7*#xژצ#UfcZm?[rTʍ'JqNsB @fJ!'a), rM @]տ vY#- ކPhFW p:&hNWX@P5W ˁ{NgT2Ɂ@ACpc4ߢH꿷Gw<U`χAAޡ *C2ܷbr/u Xw̹a#eF"Xy˅ fH@5>g-h>Q%Z+rxH40sȠ|`A4X.`D:|# CdB6i9@V~h UHKdجS?Ю`nw8̑I?1VUhِ"p~__28)@8edT TB$>S@&/s?:O?L}nK\q(c2C$I=h8@%`!RIw?ܱNǃ*R}5 hǻ^{6*|^ӹRRTwԉ8_M 1'9R,6 I!vGK& JPXB4}y ""_˾g*@Kxqg7[tCV 3<&X]nҮrX\hq*_ÈS0% p9ˆ>@{p:l"&F _ÿߝ-! E o P'"_˱KI@|P"IML]Ob[n!Y& FdtWpJlhj .-@,$?.5aQk[(9Z 6Ǟ;wg[uö>߇F.+2K\&iò 3F'lac^N[!0e(PT :0jZxșy@H ,2fBqB {80.X}ºVA#.@1h\&, _w*C1N?lwp Y`\'80믥B| Y 6Ķ`΍%?t`oyh~: ]6KHAWpAxEwg |LZGJcxM^D]DZ@%gF0` ~=X.[' (Ze9O_btO]`p3t~ II/c j~$@.9I8<rh!F0c|R6kq>tSdtCۏ{Ts]„w>q'䥦 MS} ܵvo $O@0.t"F% jC#ar90(X" 卟R xc~FW\z50$F a# .[>*к?I?wG @L~瓿>TZ@ʁf?d䃘~O!_j#KǿVՀfGPU@0uU &/U*D61%rBz/Wh4|U!eV/uH'!ؑԎ0CB/b!9 p%@}+A;~?BpOFP"$R30Xi }`I/; C Z}%HOP,4W- UfW'rxgئNmi@@տ_bPMK'ICK"s_,{-g@W] 2໑pdgx@.SNξ5q'r6\6q)$1|Z˺8X`2-ȟ/{Fo7D(*KA1ưj @aJRh!P.xU' tyAC.A]P! }R4t<7|ȁ0[ q?v@|4dFvS< 6%1=G-C ]?43 ~mA5X v|"=r64w9 3}/kduPJ V `= l W@SsKC/1nIO<ێسVmezRw5Gqn[|:L^ 2*Y Ț\&m!h _ (I/' LAq J^`).O [n+)) <|>Pt~-4'RU(ޫ l>k }? 񪳵_-(]8`l'hNt~1j TnF;gϦ 6*Ȃw@@xk8pPk$2*XcwKLD&Pǖl]K%)wcM/V|@ qE返0ƫ .enUQ"(QzKUI@\KL`^  P`8 Z@>pQp89 op~@.=o &oX8PI8 ) op<';%>0~ @+{ h@ > /GG[@9NmZi$?女_&ML )`^}L_S#si߅id(N`mxZ#{?P bas/7b6n]F^w{xG9Q.vM^Zy& AG?qQ~Ns },p9R=>؋cv?U-P)AC8 8U]`  ;X qf~GB | {0?akM$&l;zc7)- 4F @44xIh hs0m Gxaq$tK@M{ZcN}P)S3>m+/_?ƿH`KLmhQl F&90ì0jv?KL"&3m86/j,W[r_%31*&.q.k!aǁ@^@4qD` 噸4'8;`vp\-LTVxyxH$58&jAy "]xp/(Qa=h 0cz:XhO/;>~_W_3+/ 1 6QAjX"2)9c CLW b> j?=-F<_n_*}<&D\!_'X11GvVB.bp Sd 7>WfRBuzO~r)c< ḟp@)ِ8VάyOs^ R] ϭ' f6`d,D%qh8” o/C`)+(gc 1? @| Uqp_v.UcNP&@ .JE 0 P{GAh]Boz/D9B~,NS%]<<:zBz]Hm?g/d 1Gwst,B߮N a\'L#jAZn|k&D65I Pe /X@tX"!5q 腦8 u) ?6.?f`x.)|TQpŽF(hZ@ oT@? `b% ? > ;_'[>K gGOJ8&*#A'χ`q: o^ R`n'pjwa2GȰczVKBC\3 "6Y5"RO! ϏoZ/iI v"zx8fv2w!hGpX13.ppP2Pxm?A-*NYH|638jc 8!90?uP!ݱQ8zm>WpxG%"RBA!yvm!'e漪miIkB 1;1@*v0~\|C*a )f\N!\q'( Tmrx g`"R)t*ɛ5 ` S9\-m5rk QVx-t)3/YNlG#pA&}U0Kӂk tai3BL'1 "J XPِlؼϣ@'cP3B ^cl_+!L(@+O"u@ vЁtB|@`:$@Pw~ <P տR&\?~?!@@8}|b8v\%@LT3 ]r` !bs `- M.6~x,}Ilل .6y=`K?͓A/d QɁ'L x$2P'VǯWr ;X`Vu„j=R$F@GB16v` w<?ɉt9oa]'#%XQcg\x?}S8a~(: @| t48 wN?G8@@(m6f3$dM};UIv @2, Д/c C(Ӿ,pI61i\xKԪO[JKm Q d |_h5 zg}{# 5^F)@?`qxL.QB:^Y0 \ gY]RbLp@Sv;<ćtx(@ .>*ɃA,_Wr8Hp ~Զe K 5ᾱDpr@KFg,C@z5 bIz0!wgkmPk9֎ɊގGx/ F{սKza6)|2xٟr 3G1!|7JPC@|@H~> |T悅 89m{0@\i0U 9PjG;%&xK cP0O𗐀 R(p9 > YT4[?n@#w)Aw nY|G?lb T_&PS! T5 7s]f.%*o)!)w V14Ļһ@ ^>̦~oW7Fw\{ƾ鞤[4pqD8{rh3AM( $2TLvWc|| Q=8dL~3?ObPbZ{"* A_8Q8pNdFzCKq|N[@APe$FS%hB s:̝{'sp%H[`+ԟ`,}0]g0 >$ZP6H_J- nQoUxWB'w 7 "ֿk% R =@huB]m%tXvi9#h\KBH<2 $C22NI +W,!s7=F@vNs 0KlX#p)E@qx8#&@,H)" a!QF@{.{Pp X׉ث{5 & ^`phdād `mTd҄_8.0BA߬;ןa}7&?A0&CY`90ʼnF,"RƉ 0Hk[_{CLqO mr_Ë@a3Wڶ'wNx}O.m) }~4]?`ʩ# )D=у:@!b!@)(8#V27 acY -PP!᜼``ȩHb0,mʄ,1_ ?eA@# j@8@Q?%0om{C@3m-◴{_,r_!A@Ҁ+:"ile?R7Ch΃9eq[|m$eϗN)5sFk@E*xp mq$@&oxHbB#MYAqG<+RAe3rN$A:u !PQJd N @1r\M+#&1=pD> R2 V@@ok h/AuNO(x/? JдB)޴w+M >9+G#1kǶ5-[pZn(ZWLu:M/\(, n@' Ȏo;eC"~41BI\5pM`=0xBj9"U=&Z -do@.0#20x !JRUv$\/z<mұ}.b w7 {%m- |أf@JOnF S &?>ik#_ܒy(W xFr>p{Qb:~92 iEJ!m➡olC9Blj=A(O}00bvD*( lOa)$DR:a0UG FDqbT8(Ai`@cĆ;AXp`CzY|~MSHdBۘ >'w*yq xWO>.1 Vr0dQ4H#F) xFj,/>!Y/#{))0.wv;g³779u5fqWsN|~ Ai@ _|;ŋ\FR6 xm Um|# 7H, T# B0#*Th %4>~Ny!'2 lrkg%!@ )c# ZPR Z8Lt4Ą # wB2dm#$'sRwL 4ϧhV^^N @:W(@*efrܬA$_I1`68-XǚCl[ز;>z|\+uΉR@y=d牶 ςG #Rׄ-t/m# (N` H>&:@x9] L!v) p>Gp\7;Hy`Akq,ي#x3 o\yϏa"OW=Ц]q{ wF_UE[x̂W < Ry= Q< J kx XMEJ0!9S2L+ʹ`i Ժ@В6QF2hD20l B^o\aխoW'i0Z o  1ea*2G#  X(D2P :.YC ?vABVCG.=rk-w('j/..n*oacg`A*9آ X,hСBd hSÚC<xo6yףRbB4t&0y,` 0lm&`,(n@cmPNsw.P9+XF2hX9@Q$otP`̀Nl 8Q&~,s¿24J7-~"3?8;Ht$d!R.1(ӂۃ:Teh紇a9\Lf1᫴dqiHͫZH= QZ@VAyh{Ą(h&)#+;"Й}h_@T@2~uGG2 0ϒ4 !J_@@ ZdeD2p\`Ed$$\-  O(LxKg6dc@I 0<_!ԟNP@@$~A@S=Gh+t+ 1Y絆iqj$VkAfqK# H%@Py?wX;%!<и˨!0_Z/ u|G@*-LGP c8( A%@Ax0{DS `l_̄(aB,{_+cAG$#Ľ}2_7B\U~Z?Zܛ#@e,!Q>cSԀ;sd|[l?[fH XsS8L9fts`Lh;%_("C`NqijYa0g DbPEBk LM5l;+ h&7e s2 'U,HappJw_rp_O%ѧ23L=%x|X.e`%7P>VvtKp'3?pgWs ?gP0Rb,#-C)nVxS~rpk٭a~a$?5;Yk~Lnؚ@o80a(T@(S @IgP3't Pn 0=0oo\\pt;_k iq?76MOchNK|zV[g9n A)1|`McIY" t E% F1Tqܿ@  c$@ĎgIzvQRN9B|;]_q;R r1X[ LR@ w71S^ (y Y `J}(Y tk%7&MvrGX3 ˀƇ%"`!P5 QϤQ p#5J,PprO9ςGTVU?eCOj]~r~;?{w'?l}c1М,Ϣ@s`JR0΀k*yd<z~׈ÏbOh(|_/cB פH\լ][0iDBNd%(0H4*:E V%0@A bƹ`,he]X6:A JvH\@^4ϹMg~Y(;iyeGO}gg)9u @o#?iPNPg@ӟĤ#MٱPNogp>2Q}e;{k{$ 5@;*C VI kS:XNSr(;HcBb#iXnfD?A0F6Lz(U 4/A8`#BK31YEO4z3S>;+p*_[6?_0e@V%C_(}{@2HEJS K`!NBn"@u| pCOw|S~1[$0?ҿ`}\M!#~/[]%7@P^+"&:JP{!:?<j0@6!J@>7WWpIXGMC> 7~ k@'`Bx~ٱy>6WеԀ *DEH|LhW}NT7@ ?Y)z|%}SzOķ1yEy^~' 5eX?e `R4+' e@ %#}(e" t1$ _-m8_,4|"M!M59y8a!J{` h d9$Қ%ؚ#l$OC+uv11Ƅ{0|p_m(w{0aW<DPGoeO-\Ũ_Go?H9 _`r?_ZK`B@{yD:`6" /ಐ"gt)g+W/W: @4>\ B]4@gS k%%ޖ +X0)?4cp1Tї&@iaAY/|q¬e < |I SBy Sg=GsE/w&? 8_G>Ob/:>I&,? Wv~Y[5QIp@1I "&N‰'Hhyv?\JG?Er/^ޱF1OJ<*~|r_C>4|kUܣV1w6qLa@nP.pJiQ_7ID-)9O@Nا=izZ_@>%C;m@a{oֽ$L>om4TyˉZ`//Z"_Gd?"׶If֕,QCGׇν0!tt4|Ќ5Bc "*ܚsSŴF!F|SP" .TdςmlSS KjLeCX >` ;s ЋI]PĤuU%yzk;Ym_ϷL>)r/\ veO?q_5gW/wgD[L>dPGHR:=x999dH\ ƵJ&2p ?M̈́ C@2UL tD}}%* ՀKZN(ֽ8>ր>@/|A0R w[_w@2/No!}gpo6#}߆_>~tG` J!1=`2 O ֯%\< 4FUK(2 !@ C@Ԁb f2X4.@mpYPDH3|@G\Pkqи x+b}ą^ }N}繿]a=a=w6|v&@?>Eh,~Ղ_.:yzܿa DVSczgcA1IC}c<g<gh K7]m4?<, vOA8(׀)S>B 10s h _O|1w:|︷~ȇ\nR싡9?=*&uOt'~5?'-dbq|Px^w#!Ϲ\F7YPу֘0x$T- BG@PW[d|ߚ&g fqMvskk@7Z\khɧzOI7'_O_w9,>W'i~~r闓ߓ?N~${ %;l[(%^ߥ Ք,i<-ݻRX¾9+x KN@{QA7a<B Ќ nGV1 O5#,Rп!0-Ctӿ=/Yo7ރ{``ɿfw} }v/[_/$ mw k[n/׋YrRe\BhhZ~]<>k@ i'fAm'AF 0s%p b6(B\aD|@ō5vO_+7F@#o [-G2? O'`F?#؟CA P(I Gap8?AEBEBEBEBEBFBO;?-{wf}v0 6KuEo2* b!e-|6 JADH*Xr_/`)΀x[E8xXA B i :kn pUo!`>_jZ$@狍pޫ@bUwz}o?;~?t|ceX=$mۧ: .7@ƻ?$>CR FOoM`Ac 6x)huWPDԀ!j!2f;'ς`Q (kFS3ԞF@0b @~Oh=cLVɟ1hGk/ns;z ybѰ}}0c}"?OѶ]M??~ϓ2bn_Gw `w=O)k+6߷vٴ >"c(M'/~eFCas!gXGR A܄ю+tH 2"iӿCJ 0$ # @ica$͎0̬GM֨N%| ?ϧKoN)49HCc~?<`wg۪ ӿCP [Ha?=_ T]yNRȇϨ+ӟv?~Lmd:c{sPN .aA6u=pnASO2΀^$F1(@aV^) zeP`8n G@,Dpg G ԡ_X=1s,?([?b#ÈL|~Z8v }[ ?!ZԇS'ރOVnOA ;6覯K"ӿs} 8T:Bekw?pǂ쎈|%.A mpB(57=1=1Q0<"BWhn!uپL4(E`P V-6(Z"TP:ӅA^~3M?a bm+ŗ )Wuk;:fgoQvkX[>ߐ6* xj? ~O{{`%} 2YυwQAH -}v2`hrf@GzA BA׭!\&B0!zf_- C .}LA#XK(AmP1*xލdCUSDbP2Ћ}C_4YyJ+F@g7mb5 }Uo?1`HTwtڏ0F~e/Nϯ@t/wݧH!&- ;®]X.Q:FD2bVOL>@ CIh*gpP̐ChBP1oq<:ʄDy)C~Yp~rN7ɬ>B7yv|| ~tBO?]w6grإ Wtȁ}PMߜ_OS>2 Mn, v&.PzC q}## f ۅzŅNPMa7d b #j„HM# {`\? '/3݃ hɃ #E =BWJ_wvxſhO˯[ P0Hlwzlo]1dP?$@?Ҵ&g1W6ίH0r r+A7 !@9L~rMB24v֛2&vEx8|x7}10Q"~8aT({8oZ DK -,T /Mm&{<@iĠX):Ϥ)k$B < =pF(]qWn5w{>o/P?IwoJ3Ey< ߽zT>PBe|0oo^j|~ t vbJb{doyqﳻG|xpoNq(4@ "!b@#"! c !O A[8J(cTd MqOV{kY!@[ a! [ 5aaB Qu }LېƬvא;u55O9dA^dӸ'GWt?{`8(/~I?X @jmPR1g84xRIӟ{Nx{E"vȉӻ> %?6R~10<ekh@ey?;.V*;\4~YqmpSE8qd>鷏9DSO8ꀠ t9 dz/‹6X fd+yBK`'4LC'&|~`(A-vl+ǙKj4-,"[F|ef}h֛ֈq8Pe("_ߓ ]& tbug.I6ҞŁgApT}߰pk|(>pK/ظ 6y/S@ ;XU&DlS` 0Cd5+u.fe%xJ=Lp$D,O1 GAI1kuR8Bbͮ_!Pj4,J}W/5|2x`{ĀӼ\_yvgO~ m;B(&5hg?`N(ίl+GD'ȗ |2##&yo}FSޣyFҸ~˙ͰthN`YS k@؃? ntc>+V p> vH8!ri)vd6QD1mB 1NP'ʹ$J3k:q?Av~T@HVd?fX@W|W<  @\wLe%(\Eꓸ`~+ycID_%~ @}Z&?h8@ Ёi@}jfD[ n9ů=0>ֻTuwҨp&VJ ΢,CȣL!9)Q<g%D:% ~K1IQ)3gЕ_ݟ3 e3R@5vO{&`HrX WBi% {#;Jؽ-OmBIZ\/<|Gz`xwg5zruڅ>bc<\585*2[3%f~`m~`Ve엀x!?092@vҰ kj3bñT'_!F@&YLlp*x)<&2Tqh4dy|Oh#􇛞KL([w2s @3OH2$7N'P|IfU@cX/bL46|~,ioo^x6gs85zqärvq P xqరR >Ae \@%Re UZCUƂ%1 CKE1辀*`Sj@zph>#e@Db/C?s8 0*ĀcfI_Zѯ !cQRA^F5`zs_2a4 !0Ĺ؟*؟Vki c`Z~L=JvqL`N-oaa>̄H0{ɲ -7cAh9}&Da `6P #D9Wȍ@B ONItL`P=0+<KJ58-}kDEop2ݯK_u~4bNݯ9p X @ *>n-_[w[SyG,`v=|apxgi߷A\ J-"V:" !ӧ@: T@:!MaK d KMZ"s>gO-=#L&Ge;X3Lvzүqq5!OZDH)Dnxv8&NZ-`J$Bm@` |{\>N @;G7oggHC |~(_??~ap5.Mɏia5B( .@9(2*\-*XC puPС|MTc9 w‚b9K,@y~, 3(Nr#G)Q UeqO#e6': e lFwk?*ߐ1WJ ' 8H,Fr )NVg:offD37:3c_s `vS蓒ONx8lN*vW XΌqzΆK@o$'DШK`k 3@ gC"+X !O3N&`o0z%9 S )?%E@>g=<$` 5hv_{73h U M\Fe?} x2B(k(SM~?t74@sguPd 9,V@G%hg (Y@TfON|vRgU[<<I&O!f262>A"gHj~`X^v80`@ ꏗ!zP#74XC+pE5K!B)JJP]٢F, %hi"u5Gl'TH[ObJy£+^tG3|yS/g@ ԟa H5G|xn`N+`~[!d![`BiyojMlr Cv&6A4F +tBxcBXBXk+cDh >/H\ޛmyi9s>60E[H $ *r5<6γ {%Nrx$kp+aK8ߣ|7[_)?UL~|kr~ N?/ĿXd[~ z͂R g(q!hȝE|؟_#{Tj<@Nh`7T>!,h@s`hB[&\h.K7KD\⍦e) ҽ}7SXiHq@~ &Ă= A":Hm2h C7შ$jhOuYKƏ'/2 - oZ @w9s9!{:Yk=0xax9?{d?4m; SR`5@]c$@ ٟ=~tƅ6d>4tCō8nF `!7T· bg% E@Ж@JB=ȷp>+k ̓H#Os 5PP@#zUPR$"<:]A79c$ xu6^L=r0M ڹ xH_$@bq'^5pFn/Y<  80ppw 0gtwh//C\wygI=' .Avɉ s!ܵw!b(PiN6@BOJЅ@8 Act@X@ _W<;[k0A핌B{SR> F2P<(Ac@ k/<ԍC% D7 it#*@rmg /goɫ4F29x.A x?[@ 8?X?x$r ZO% gcpB@w|؀ h@[0:`!<K[^p2Po83A 7yG)-R&o Q2ĭ@lT8q|@>%̓uG At_ Z]//Wq/2rFw8F@g!{`!&D V'ZbmP'%1>n9'?[1L4[.FP*LK ĢqSI#b =TG/  ydDP,Jdo`0ȿ(?0F2>V| pe%lo@1J(@lpk;s6p/9E:|I@!F61gǏCϟ.N1X.Lrp@a&C 9uj5¿4o) hAnhH?_݁z+o ۀ`JL!O)#u\g [C'): o@. a#X\j> "EЪ{@w^0g2hXp·e8,H%#  r)H _7v1㰑>|pyRʶ3:--.߼si:O V>-台4KA`]Z( j_de yz>M]/rY^x{@'}ySso/;H1=t*i8O)IOP)# dPb>|na`U{˚Q6s<=?BĠ#&iiV*AN+FhKH @Dv0p<3Fv0y(Pc{dy`y'lTC+EМ/<zI>Qs;R>+(Ul} 8w|;X~x> G}*b]#m[ K9{C_0v_j@;z0*0@?e0~h;JG.}fBo.B>@ʈ_b1Z g4m3%XC!56k,O<@[+널 Ѓ븂b9F8%:H(ɝ2Xk2+i fO)i:n.I9/s'^||&B$fWC w#[6 `бw;τW.Uh<\p8 9 [A@7C .#&dh%9 €?v@# Z 5`# tEʆ|`P8k"N5B@xN6AWm> bɓ}! ڧ?ao@H V! ѕtwLcz$(B`Jpr 0@rsj F.ys>vqt+}Q?9MW A 4NA`k  )9^_ :?O `q^Bws ;~4>]B v9n'$ܻuv l[  |H# aHh"mS80@4_ `D0u1O1 %=(F@95fAB{`S`b ,#]<.{ˁ}E.,B1BWL(iJ;mqs* iIR}!yc/Y'P!`o1 _% !{!CvDט& &/4΀hS`-GAC`p (7 =W@+0cg[q80'pc_ja Af[$~(hG %A^0CʓEҏa  RvGHF%VӘyX pܘьHI/\d~q_.dBŲ|N@@)OCc# KKSE,-1 *AЏ=KįmP>R+2|A5'Nb&p@y+b0lҦv)uIyؙ_QnsNWt >T@*xς/&7'G nHi  )8 ?!_vji0``e`\/%v 芀ttD EiskH8)ЪXt]ps`_iOh@c` %kؒ0C j px D)LlwH$ x\ȠNգJ@1HI,bJD|ERBd EHF3"p< NM:}m} 'Ҿ4_SBI2е| 7' ,`!5!!i}ozI8?%`o4H ZjSBnގ1:.q&)@;HB;  M.T.0^"Z(@,ݧ?@V"Z1a aY_?Y<,LaR:6\ ӟC" M&`keqb8;]->Ԁ9V& 9k!Ma$x8FZT}k `W4F`B(u!xB﬉ Dsv.5)K^ XMc&1mX9X?0>9s`9A֘ L`d?Pn(f5{q<ߎ@cb O67nk:@jg8<o?(k0./{@X^$a {BB"iD. Yc- UhI ރrǤ&D#B@thEAy كB BeqcPAk|L9&0A9h.t`=U~†W礰sء$ -o6I`mo,֊'lO+]t=4n)*sQ<_P`cV"񭖓JAi!# fGWv7l}i+,z6N+rg{oXBA,EЂ@%jZ}*$0Ou:I4( X/ @)ϭH.{ -R8 d`*'$ $ :!Qϧ piSiJ)1 1g JfHbo@M2TZǙP\I!;S6$G5gxBnu@Nq[/Qfl:^2YON/X)}r^i&$*or#ra;S +G"~#Fnk@ oBGx*?-g%bcoc)~׽Ls@X:!)=O| @}.oC^N hNch@mel?|h-.? B/,Q @Xd[Mȣ + }^} q  tC "BH8o0d"!ҙ? E`beA_4.K]RpfK6vWo7p{9eGѥc+Ə)~]@A~GQxn=4?+qN#u/.M4޶m#+N@Mpog0 Q0@oPCy<'#n9킃Nb0Z`ȓ >%{ʶ냬bkjp{;-{  ~A(%)P1K! j/ @Ckm?g>-]vcXek 5wnF2s,'i"{:ǍLXxn-KzI|ݶp?-qEXE/F(K, CZ5S%(\`F M"up4)_`k6`ݯF_+ w>`Fcʱ&'T@= ׿Xo&q pи4P|(G@fmdc-PRUYJ@7_3 h}#XJa jJc YT⍂EhMv0Y KBfQy^܈_#/I3O2694lEG!$Yڹ3BL>iz}H?I@d`  Zw@ ZV $4APa($ P*+AiAP̖\#a(@SbD`!!\mR C5@Za=jv? )fiXv\pxp x2(]5}W`) p'}-6`+o #BApF:!ij8bc qPҀ|_-H@&`W ^+ A @+!6T3aL(كu*a,L.E Z^>ˀ 2B0೿Of0gǭ*X#Fi ypx|!>)G,CGv FI]Alx2Z٫ŦesP& 7q,_.}BNķ1V8r4F@Xhd(q+G m4L8ʯЕf-F{4>c z aYqk\뿮A?ȃ%rVq 1߰ ^-'rwϡ`hhsL| Sބ hFUI`81 xi,a `M.0|`@A,.cwJ;'0\n(Ɖ&O>+QD~8Mj:|*>Jc<X.CdMHsa/4۱M8V&i](h5?' spou/@ucBdT-@'<]'0?ְT-. qW<XSEuXWI<1,h?5ےM' l%D&Ԙ$pпK[, $R@,F󔱧ZeS邶$4B ˀ5Zß`p,_ "F8fl r"_T +#qn:xY; =kZ '?|"T#cHgq*ud%Z{'g:g}ORv~ᒢȎ-,op6s;Yvq?o< p m m=6&? 0c %vN/ Ɓ^d;tq/ ;YٿT|~G,s@??LRlۢ-c!׀5Z# ->%^ I QR\ VRN…"m0wg9ƨ`^Uz\Ƨ[$L灵n/; DAr gs9-R(#l) Lm{iH,/oXe`Y , > N dt}C|o,gCZ (Pʈ2`q?0b<~|#c+[e 9Քm홿3=W?td4-.~H[$:i)PjtiÀcJ`1Y@H=\槼4ֿF oB>6/¿N R( EA[IF#A}Cg? +(05#0/IBI=dPɧFn &(i)#ls99HħH_M^lC2xe@@L ^lDІ#m5 r 6YVf^Ƭ.舨+T&f<|֣ f):&Ȩ*/^hd ~d/(@#UFy_?>ٿ{O"98|ci, s64?'d&9L`B$ECoa,QPgl 2 Dh N8 /y5_JP0Yp[eS`uACWh&p-=(|* !:?٦%#c%a >H)z\RIh^\1.eNЮx0n2} Ov5CP8z,wJ`=hLah<"k# H ka!u@30TPXÌQ xxXэ^ t(`8}o;@* /Hu`v#KX jL_' ^YP.()f'K?~;<Fd, V( ؊YfG+M^wk<ʷ8-pVjbK>UOjS=iKP#,X^GTf$ZV[kG)/`fSKiy fJG'@ Z5h,AMmqKʏS%G)6cs=;̹9n|Gh "-$<)(m> u b 5  "k 4‰po,%[dϠХ5JN GXP2c(]~m֎ .PCd,GxTs!*\NƜJr[GQ4k2ԸpZXsO+Y;$?*$r-E=7|u>}r:Ai_4pxeA8?Fg`-(` 3M3~Qz8` yG>p: \6yƨ )Aȹ& ~W`vcjT90j"sKlj~y SgZ3M]?sԼ5ZEyΝ˲`1Y6`@ 448IOp<*^t>"^* h-bP$KPTb0P_?o~eơ 0%K yVcKPmO􋁏sҷZ^r`?t |a"YJigCpP>5lŀH)A CU(|h 6PH#7XύB"-|ae*\Cua09Ub{)AA`Aj8#:e0z`LU:y|MNF-ٸQd:2/M/zVbß`Sd/>ߟG@c`8+w»0'?JqI._9f?Lҧ<ӭ41²*I,C+@l}-B%0@5())l-;pB$ń-?H/ɢ >`46VbZAon :FT (Mehv lI;:&E<ُ1L0$Ll ΀eeJN{]+:fJU~&:z+y|U@%,kSi n413bmӃ@ (i_ZS 3l="N{N2?8/m(Т X0  k%;FNɎGӞO!\R% Uݕx08%F.Ab a"u@:Aڄ8(WP@7;U7L~6(=}'܎$ (gC eJ@V:|i,jt4!<`G"qW]:yJ' $Q핼eg[m.-s->1%c鸧oǁO,kw080EtG#0ȂI@R $Hp{);FX? Xh&vF|lɿ?T@h S:?+l08\"`hYܷ"I~4ze 8}1#bUņx1`,{plxUh-Vbߎԣ H 4Т@-ZfA]*&pBI@\f{|7ro[ e&O8Ȋ?NE)'U*ʆv3;1RbQW7DiBxi& +DcpUy @S)20gT|ꃷhNvgJb?M|#ÿBCg"\= f ! c61XW韘C2yp_ 9mhQ_H82%L>!qNq"/Hu2?-* a Z M h&>MY J+c0g݁<8KBp1DM dڢiaL(4 )&"YqPOQV4pPLCy-~߽O'7X監T6zIlbX_b@\N\cȧ#!5 `H(txV,fއDO%T%OY8OW~wb/3.gqρk?4DgN/t&æ0(к0p 9EUGXwW!9b7w@TBtcf1l&^C 0vr 6hT@`ӻ6X%h~ +a@J p$sEp2-'`TTcǚCΔS!yq/E7q[kGpkLbHD.lm|cmOIIyM@ 7FA'206ڳV%ؚZ/]&ƿOj\&Ļy^ O g1@B/?Tr\>S~̤8Q%OjXok Z!b;LB4)1s `OG W1 \ )3 p ȣGԆy MpH$\l룀C Z' `6V:\݁jvU}W?D"% --Py '"@x90G X2 D4'3.lTzc?ovhl &4 P~poɂ690ǔ ~|-, ĺy'%$sWJ45@tA`GAUNi CXX @Q :*AQ dAێ0@ ^0@(8'^0B] 5  8zڅPԀ{C.Ԇ֗I!RXƩ-#N#3޴a3m#"JdnD]0RDD& x)IHQSo8-]S4<.PqL7-vK'u{|ck:{2X; hV!NF4&O_hfH(A.T'z`?3$eK>5}^F}潽ó; ^ʇO@M1Lm cG"{0A`0 "FDC b8 0BRÜ|P&hJ9] <(a QY BkЃvB %h^Ã+q&9PPe`$" $5܈h"f,HEi̞Ɩ"OKNK%~ X׾?g 3ˏ 5f~SmJ{ʳ3?zw& jvA CfOXp8tMP-&Ƅ Z0rI >@%?\4^Dlzô)LqXh0A"JdP0@`Tz %&@\SN0dpljV[]W7Flh%m8DBo 4c*vPV,q䲽jeLqye[-N[~^yW p0 - P_<[k?{ `#:޽@42ޡ5`Fx1@ HFDC@@؟?sJ?_DodIv탭 9Z=U,RA2z.: .^?0frн{\0hobTep sG=bah#t|d,NN, x {c5<~D)BC CPkVy@gULLtP{RU}\(ld3DJOՖU?5z迩Gu?FAs?42"տ w&.ІX}_Pؐ# րDƾolha2$C Y?>e<>q։`" W Ap$#Q"4[@Z{(`rO\OViXL.XP΁+Ƒ @j@F,De* !`, <>!b6}@4q`4 +BDz0V+Ț8\STqJQ.%@»v. ]~(CnO w?58Uؼ9KI86io\>>%=Eh+Ip;~PεZIFƿ7v@Q5c5D@O&jJ߰O(^ߊ ` hs"ԹekU%D3G\ ݯ5VkXOp;,Y 0E _'G!thh S>DM8&11` w]IƁ0  (tcrޫHxp4par{",P46=if\zJ eˎT1'*?V,{&P%E̙iMHF awGzbMh5yS[_?ؼC~6R`BI$kB/-`51{r;oKnF0+x׉ݺ_g4<@2097~rU>+ίd7 @B)f*RXQg*`sua$\';'0F$ Ȅoo|4Iq{4F)K?A56d;nڹ= .@w~~b%gz{xB`fW Ϥ67RueH%8L?ĈuutZJ2̊`DhxHY>PߎQgX LN^scGypdB8\%6@s% #{[ vt1/ 2 N `?V#H@C{x u| <4+Rl+4$ 6J-%FǴdc1/] -c(cBwތ^W^cXqQl2PQtn~4<7&KzzGt_cc+{h'XQ3V@ (p-`#Ã{z[wS{d 'O1f  lrBMwP?q`Wۧ0)vxH ̂Bo#%FQ!e&Pc*XPKɐL5w8Lb na׀#`τu QXaXJ+b&(لFKp̯1'0.C P$s6K" SD6(ńLNZ|>Ij_##S:ҷc I#pJU^ŝX]&$:pD |0 8 z&H80<3l aEP>&YEm7}5(^Pg4Er=皢R"l?GXrB&'wMȦuWRn }Z{n:vO6i%_Ě&#[n %ٺ󏏏PBR?a=FE^^WY~I#=HpI@@s>MMq@)~|BCz&v_A?zHRb<Рv_J?i0ʝq$qIX_.[ .R2PA144, .1\t# Cs"SlDcKMy%II8Lj> 3k< 1 .%F<% ,:$e0|; "޷a(j=$H[2epZ@e@֥Q[!-Cs [)ž)dݟTY]ޮsI[?6 {|@[װࣔ 3aVҀs$U(rտ[#N00&+V_XmP{ [ha*:)`p C0B >*")s24IX\88@9):ފyRUiHoH+Q`{4Nsܞh:t p(2S*P!%Pډ5υT@&  P A)PdJM >gjz d奓V.!oh5hRriRt \=[OaN.?GP u9pK& 1!p~V=_1wƿ"Pj 6T w~'u DqlA {ΖldӐt#ЮN{v?c$6f'N 8hLPbbtL`6X !C{ʀ|E32Пِg08f:M8hãcz2P"@4!poƋv2rY/cH2 uzE,.N4Z^9@v_Js 4Fs+CRV8-ۭo;=tL)i1둶~S ~?bk?f  @eT@Hd8 y,3%,_' 8 yB ] \8ܿ`A;?hs" X/`6s2F#6'01dDiPY L??g P5T@!^4p6ЈFj_TvYe4N08Ch.e:rMíb71 gUhHBb p̦Д;E1O! {8RaeLYa1$k|bG2" tƹH7C3,.=lz>wN\~Õ$q8@ ^u#K_eV\9pɁ$qWl9j@6b_&>d/zd MG`?hC޿ӧ^!$ZL  밂8ӃU - !s|?)`\L:?Ktx`AÃm(#^486K2 )-i T0A,*AO>8piրD^,8D. g~lҋ~QJabIAʸ:{N@`I0ŵgM2WIꬿP<_6}U鮸"DO5yl6vs}g2|iTQXL/(:@ZhGGҀ6Ԕ!ͼ,y+~ 'H"iq8׽ YcC8|8uj3Qo9JMe,V=3 mDPz v0,i/&' ;#̔ku8pf:8@2kM=` gC`j |uC5цxw=*ЄпTa'&峾c&J"P?y8MyJI` `cFM0@.ㇼ$5}Zb'f6)Ϗۛ9D9dM$%+Qo>DsK|;CEe()eχڲ7y`Wwbi t~~Υ,(~OH4KAُ(ֱwLu0O$2_ca4aJ^=D mJdc|Ax8$$sccD0!$HL=]+ :+/ éՀKp0yp #XA[/5 nGfdCH ]GDzd`JۡX"=SDP`0(`a?bJ 6Q#G3&o H>}<^ԙq7db!D&_6GLiG4~"wsGS^y12kpC aAi=e+!51֥,0 >[_r @ &vt" ޡ/gY;efZ.5eوr5JLiS\3jmo 8x)3 L'zP}԰h0m@C"}-a NF65>B"ڳ|$=` @P @@!271$64bνmؑ)_)a̰@!f|gm0ե<#X:C.! v(Kwq$dZ6L>IjO(Ǩwf1|m,㻊80|p]>ts\$4Yu՟!0}q@9X>s@gEIA 4q\񹵋#{DA<#m @&ؽ Ba 5+>Q!`@)O|f/&IY*W/LŒ>?`a 5` ;vt~|}0͆@o6xy8F #ؙ8P{Zw8wFXq5~JUq>$YԆ+==D ~2NpVNB)]DldI2˭/vkqu?QƴUJsmҿ4Bdk.0mIkD黸An2u)6<'/(>].G czjXAaappgrJ̈́ `b${ d3{j?_v}Z >g|48Jgtିrʾ*P@<]dP/ks/&'IRAG7@KtM:sԥ@31 FDp Z h]#kE{"MBJO-d m(lol/vHaY1U zS5`3ǘ9 FhI {4VV8K)ဳ M^Q͙X&y' 6Y\1uz= k.7k~n?3ݣ`kM̀4nUn& Ł̀lzF…3Lү J_4H`t磎,_.}ut`}jx%46%7t@(p PC` Ly@yѿ:?A;`!PID#!a/ċ؀9·? 5g)@G (Kl(u $U440)NXv`AMn[*R3m;2!Jk,eY$M$DE DzbwD{ީ 260MxٯJupq0bQicAnQ8W^:RY/Pmo YmG$0p:%"Y PP90k @@MA&Ii/g.wX CD s|oM6?A#6{c[`ƒ3I vXnؚ F0b2%%rDBuhxԑ pXq Gm#S `%` L5@! EI`ЮQ2MmˎҴÀ>I~,;EKzNZ,*x A4ص8S1?c=!-~2N;-G?GW%Vl{t&'6WwȁAP)|$%7^!P9]82p@SMO&[M%(sX74LL:I0Y./@<0&6H{AlmnB p?c;7XЇ'_2(+ &Ԁ:,g 49= ~|/ h QEA Q_C 0f` |ǁ4n=tr0!V xބQ; T~(d&DFGH2Kݠ|2Hi8A&=g=L$+0r`;ʆ3/mI44nmM6n^ .2 GNcUaO~Hv5TiMDb?%4$;"a'w)P <.#2P&>ɁDx sd10>mA4O#CR 0G/Ȯ9"_<-K(@n叠}skw5B@ߧs`?r"%$2`WFuO;?9ж@a5 B|‚s⠣< mZja&3(A?Xyt|&OHLGܽ 4m{}CRcBC %;>`B8諾yplWi8a bF< ($r8BygG<@ Y08,hAzЮO{'Xml{Pn!N jz;!Ze bGhA2$X=^.$ k 0K1CBڌr0qlƑ;4W@;@X 2"w/U1 NG,!SZתZ%?/ 2x!Ť^_2 ޶V: ,Db ໃ+s`B-CbG4|9dP;N0BN)KK4%(Z@ MppO4j baLjP! Wgċ#^8螮&5 j 4@ ĠMDS@u'&~w'QZ 8 PhC0G5 ( NOG(esg\#`- MFtXȥؕу&iɱf`t ]?IXq ZD^+ \XDSLcϠ WPZR)/ůIgY9e驚*5@PΘ=," N} Ǡ`A PM(xHb?ؽC&8hԠ ,JB5s` D#:0@NR!gG&t>"HYgߣ iSb[k LT%1rB)r'E<;R@QCY h毡TšF2cUsoݭk&?+so!Ѓ?HߥW<M~ftn"HۍOޒ;A΂' h_ŀd0j(4Ŋ!0e `j&L!fy+RR NCue!c::RZQo^'*d;H8ACҌ0.0/?p ;,6 O(=hN$n9\R΁If`x#"8  PРKUAC/qA2c}UCj=K9 -fu$صkf;*tҏ85ԕNu3 zM55RZjEزO|@]!b[L_} 32O%P?5ymqxASZ v>$ xzw G-,P(>nR]S] _C0,0p49O%/!-ߐ5ZXG"P N1_ɑ"wa_9?a <PL k0.ppoWJgH%l.XP#G PZ Gg] 41&À(>-L}ܺ{BRmVQ?#H1rRd:>;Ci9 v舵D:vq8\NMz%{6fYÑ߳7F`/?S]>\ēZ n7Y 8n0W1ۧd0?.]3*!.X"P]@4|a5 `/qHLU,8A 4Y[$ %#,vK$-4?c0z/Lp +YHn! ٣d B& p|qR[@@$#g| `#SSWb2ToZL>wHt[Q,[ -$YB$DGwMfj"Md)A򶈫O麾!)Z9Nazѧq˺ f9Z,O󬜯*gǰ? 5St4X`pa08A(>Ww(@3]L7/AA H=P#ܞ|!2:c7yNӸT)I VZṁ 2] d쎥p@@72K[a&0\xk,h`PV1T o9B ڠҶH(Á]|r׀)ur(|..{$3Q=HxadɁ!țEb]ut4"I_(WNшɤ3d(;B"!d- aG ϩs]yĮ%`|c>~]BXL =iߏ*g? a/ѥ_WDA""B0L c7lw΁5~8~O"֙' V*h{MrÇr}v^؈kY1 4¾`TSA3TソK*_"(j;XfBb(odz7컴Y@u`=kZ@[** &P k $(u3D &D W <C}W㗡L!R&t)4ZK,GrDU;-c皩v9n]#mk׶)ą:WJƖ}8j$P?g+n۬ ~>/!v~{ OK@Nt%3 q€k)uFƫo@Tɛ73u $! `4y^zA ֋}es(*^ /g2=ߐřS`P,!!>.hGdcaR#H% CDp.^ Ye5Ik9r; m(T-b.A$DҨB eP t(& 5sx@ W㽍prM dNh( =h!b#@DDjyMAMhjzeM]f$7I 4m5?OcgqE3ӝqcx̬<цuɸ"g.c^_7\~iPJ*Lr3bg"X߿孍-?!PO-%(NGW $#'/ FRItAp@λc8@tߧH8LP}`NLuVw/V:t^  頁2j@9PmʂBL,&Nw?C@SDO'ƫn9<|,|6ku0$,+2WRVA~5͔_VؑTc4?'Δn4یfdKTc*e~Hb >FܩO"g<{?/=O]30}fɌwN˩JpC;A: tĸ4l$(SP >rN!w~ =ʛ|9V>K`:r8N&UVx~ԇk_|1{/cDØ{6G6)X* ^%i*R'#InH>QOݿ)Ϝ׌QRHr>ʅG/&Fc)`R?E-aA"xbݧ%X@ň9p %>% ̭?Q gr"/濻~(`(4`&rϱ8sіe< HBm /W$`X 8h)a9U, v[x8Kx pRF01vdSTP_*toD1hޫZ""J9h ;7mT0,g7γǺs%5r̀} 3n~vT>@q| g1IҖ{Йoh+`{W2X?&(n8LLqL4];X搏/ =7lSPe֗.O4ݮ ם ." 8P5ݚys\`FzVVCE/):]A|ߚJ'hHd=+ W\  #po, Iz<*j?t#OAf LgE0Ma #"`y G‚L )힇O5 p7ʇSyOswȾGXRê{_ "KkZƸ^"'>Y jp=#>RW_^zrq7A[mj7Q۳9!:}js4PﲦxFP!b_)\ wSʟ 8)'@!XDcZpS_rꋶOSj(ct( ^Pa ӹ`8~j;r~;<\c1RyG_=G(ʯ^"ѽO!h`:3! jMLA%XU# .ETpD=h_)8.2PC?oz y k*.PCn]W2´ f* 3 Pz!:#b݅)N08$v(C_@RY qnMG4$$Ԁw:BHړtuU2q,tpcq7l[ 2lm#+yГc^ʅ}-=VGaNJɈ0@?PӟVs%t# @W# LP-nxQT(,Yln:G>k#HLT]˺ c\;]uȊ6C xVB$elUe\l cW/'x~d07f.hgMA_rUIU?vj WSόd]!ZN[^UUq^6r!1:|l54Z X@ HF`߫?jBlDB"p f"t< 򂥹(ZBbPp9 9 |_"&{}0a?\b}5tǟуr_`hT |7P}¹PJNs3uVb5+߶0ڢz2 M4ߚRF6D)ѱ 0u%)**]zÁ@Q$fYۑx5&cw> 7L̽uITs2\MI.+~Ey>NĮZ噽6HLZ|\$o޹hycҸ0c:g5c`ms9̧Wx %r@~j%a|XZ"r@6. s@~E]QyH ;N_l8pϤɃ[^'0pn3l,`jvNn6!!9,dFsv (TmC@1Ե=v'&*uG(z4 *N_)ȽCKSiܱhK G'PEʍUJX=fc<4l :ѝEHg|!p<^hغjs0gEמoFGqF\*-wz\׮?;Yt{ZpOk*8^V| #_;De ߰v͂D0XwA,&N:P8y"* C2/7p՘l8v:;A&D'T:k;4gCHH0@j8L=j.4tf-8C ipc&\s`A3DR\bkYf3[ܢ䱤T:+:ɽ^0|g)21pDl r3r >iȣM$I7}G?L$3C5{;, *Y%q τ-2A3]\xhvXp5nhyb@ETH+bE,`!5 j@`4A*:oȻW2[@ػQ\R`"ʢBD`ǔa\g3sA.0*}ʈ%1?ab,+WcbO_)Up5kE3p- HT:!G=.ELzn{_1ro5N%,4ty7 ,e'ՉcB#l z}/}qXe.ls3뺆oC?\9Nd +~IF BnwhT@w:L(Pi0\$&˝ yCah?XPN@@>E>s@T8&S ~$ ėrX`C.ݼ;? o@7l&G `6@H  CgAT'r=1ppu% cH*e FȢlrV8-6Irȝ9dI\qMZ~qѴe8_R5!TPM"~wђ$ΘrC2),qm~ǟ9|"iþ̱* Gy|D>įҽ} Օ] e}ޞѿ?HA  @`וNs"8u;KdMD|U(AQ$@|׺8|8PEe)ND8߽;h|xHgꘜaac_@Wِu0, p4S!"ԯ4I9~ ^!tOLXjC %`Y@8 :&]9$X! aTqSC>|Ų  s`27**SG OH%0c2%y-Gh H҄5oFv(V џ! /!i-~Eټ; ],L b o̷u {:ޡR`qde<@iWxk??ܩq3@" 0\t t1 ߱%C@"n$!2L K/xHT Ͷ1N`J>9XP8^݃-kX>h5D",fH,jK$10 滛'$q. VcmO",(D3,^ H@ pi<12A@ v-%tO'l p  V, L"e ˯F<j@J ;1XzdsyH_c΄UV+gk0LvO[z-0lZM mM91^<\N!k%S{.Z %i242ԼTzӝ덿.W1yEW_mL wvAHE @ǚ`w(ύ*).G LKeQt O/y |wI"pXthECQ5͈Osd%S:? @$N}=@hjGx Աࡊ%A)tCbj: @y^#R)ThLLwR;$xNQ/?drG.BF>Z)ׄy|g D]PC=eJI9(h6!칣?l!XA.o;+)%~#FɋxEqf>H}vuĒEѺ/?TV^\S+^&ۭch@ӓ>#ڹ)sGg@@@P3R-{<8p@` U L<ѵϊO>x~-la P R3@BgE0DDL&̄_+xP {a"!E9>p$( 8GPl HBSEԽ mG w ; Pz2x$٨ Mb6ՖL#YkBy:$g"M;g.Ax}+d0eޟR6<~KښVFpST!oK`!? OՀk)ߑl՗ #׏ۑ:@ƋW8 X 81ВXtY]ȗhA/vA8S!/QB`@ hԞCG1b_dpLʀp#!%)!󒎗m6}.b"-Vw!?JvJA>XZ5(8*s* 45EGd4`ׯC@ lyT  @z:l_x,ad`(`!?3Z1jhB/2>33$XýG5N) aiI(,F!m p 2j P 'p( " \\G ĠVhʠA]C@ 0uAAC1.;~eqVc`c@w|pd cE>o`:0EkP T@ֽ Zs,';;CEbht`S25ƿ|g"(5mwiWB弇bªAT:^ / R뺒ˌ׎46_a|b߷'d7YomX(ʁ(e.0Qeۋ_,/?OdOe1x3hAd?Bvtgu90pEU4$P.ʃIHZ r!`T|e i0p@̠*1!2P{-?<$.ն(pg}ҙ5cAiB*+{F%(¤*4hX)EJ+rװ+AbM[LXT@ wp`ͺc|?5J|d:rIa.kvL91v\gb@X]c u똌 u}h g9{fs/Obkl<iЛIv"a)LX('^/m z%tE&L:?>\$2D~L0#vS.CfE~?<#QV C#]/\ rB @e6Д"Xeo;>{WCܼ PL~I,`Őw~!0%I.GʮDZ*) Taj1EՏ7@PE~;} P ~`m'@&YsS$ [Oth'Ԯ20E Zyrp`@8 ݑ@s;sOoTq@m@S䇭LCw5HK;NJ>?<ҚxP2=vi| g4rk*R(ˎpO j U?-'Lݺ8x};7A|4' ǚK6I -&[oR%iWo]&dy;Et&O zTY U}c3 ^1t  bVX`ߡoMŠ1qMr_a`p<̕ y4.:ۉ~1 C-`K@mh֫mKPՀv/a~I0:3Wȃ1rd b0iyp\Z0 6>Dј>&`|.;AGHig5PsgPT%= q\l?<\J0^/5|IM?` H^/ỏwoM7>%e#m̅f`UT_?c(Cj@㯇& s@Gw@!POj_ ΁&Y&8zb:q*2w?S]Bk[]ֻ{OQp-Bnf"YGېm>ջ eOg-Ӓ_>I@;< hD5  ~u1hn'PV v'r\P @E TGpPY_O+A h# q @z*k@3@\`CAfWA!O| P4` `Jt\~cG{ߌoHuIr[+dWkhOMFRʉ?G$%wFX PV|ԛODs||!.]ֆ*`.ph寊D1/L_sȏ~RnOҥϊ>>^kkѸg0E\P d`]tܭI C@GK읤EYU[r:xpωANg٪ $cviV4pW $рY1A3uј&\CB']E>j9 ]'mgoy!t]0H]>3O-{ób>T 7i>.}.!bkےA\7)ϺۘEAb7i8`4T#(+%`eC$$WQK~9-*\IK;43 Qv>@!`6?OWtB`L~6)AΜ ( cTo7\\`RI2:^t<= `ti@:!n9q̭摚Xt7/VL8qTDHu#M<~, #DP4S;9jap tω!m6YAkhKB5Яd/t;*[S>tro[_oWz^gYoe%w)H-(y%Tƅy.^4V{Dc'F͋C_9B0]j!ZjKVfכ W;1X"!Z_sA!@@|` <eǓw"X`|@;FKBX6D`! 5=3@4X waPg{'I2;!ю^񣩅xv,aqlPlfHCG@\` tbruyy/9u<֦L;)`.B@%~No7$cP1m1Hpg5g;V.b3}#g"=&AZ@bajn|[(5 Nbc8<F*\l(Yt$;GR|;¬U@_DW͟z[9K" :QIPpF+yv{#?s|gb1K(1c,PR˨곙HiP) Dϐ:C0~#d a}4jo\֭J i\gwyƦ>oQ#gT|s1*TU^?n^RRZ4~\Nڽ&M_I.\,^̝K'];r'YI)Z[o{A:߈Id = y ;2`ZMh!K"iӫ(@j_ 22 b{M~.Eɂ?2]  FB;z0: P~`6 %  b.( K(dj:J' f p!DKDchԆTد:x&nP2>,)mc֞JŘlblQvFWr+;=$%t}ݙť+tHd&Ci,!JtRx8DhךĒFg;Kdh)u9)c*\ ;!{Qx \s[?7o/hOݽ)7dbi{^w{yyg(qM#gtQhk@Gv#g,8路8 M{,?%n9P>m tߑBB]:Sd2Qhz7:!s٫, /`{RrP5 t ߶{+Dهa`?Z'{o櫈K |A˷HTC-f*!LLt7q*r[<7V~ÅaQ-߯ Ǡ jP.0̰Eϔ~] C84EU|rɁ2hoWp=+<{R! C,jTùa +Al1MtD7HLT)\w`0hC*@ Zdѐ*Y$([>%Z7w04:rJ9e27F3POkpN*)1rG]kU#Bh0#t*WLӻ3)Z;dV#'ҏSqV:R~MyiVDR b7{ȯi!\p[n9]nk&[gR;#29l w,n*>͔RkT% wܢ){A^N+h*IKZk$=:g[[gH5apN[.Sڽ9v˯GzA(`ma@` e =)ldt1ӂ(DT84>T4gq)K>en%vfKp*TuD93h2l'$r\ZPP3!~ jnK+ =OQh?h0@1V͟9_p=O @p0cWt<- @8hPz-e|z( o zf†Ph&}`6!9ր0`Q#.y܊ @Ln] )`~08f"ʽ0M[ձ T(m9 1 6HiV6w4zgyA߂UMKv-)ԛ`J =o+,e"̅<>3Ee7GO͘386foj_/-%U/NP_\|@6 ~!4S9erیK٦W=&I|3ѡ;#ϗiݗo[v6 2\}u]Z6l_#yu$%aF #.0d?_͈0bB;SI>cK_-g3/@q@aŠɠdsȶ  h m~Cu4AYnLº =G׹q$#CsE?n&x)"vqn*#$K]0n׹dtScM$n1'h/ڕ΁QoE L{,e Bn mHiCI:$=*&/?bP>46 ]Fo۵8Uк i(dn}Omn {‡"o{Oe:>lG>AA 0WGTj"HQf#$A_A-v(F / }}3h(/X01w 1v;@9[P:xP(z"b"k8֙plOtZE,T$77!}St?Ji1?93>۴K)!%X;L"nUPUkj[q(51سڹ}8ϲhl)t:$~DڟjlCѣ;?UL[}u`Ht?M;^;-1k`..ɮN(_g yZ+ћ<9 c"}|{O0 YA)!s` !$fqnŧݣxXNbPB ^5(_-a<c$@GlSs@@XHV"CIhAAP>uMAb'iG3+U 6 Lt9L\)C&nY 5' LϘ\Fv#,pg"(tڈ0+5ƾs͟"f %8Ov s?>qu~ T7Az$I}?`nS]H-#Ip:F3qT=ž;riG 6z3@1>Ѐ61ki*8@+va2(~xj3ҁJ84cɲ$2{K\VIM@b@+xRٓ~;W@LgSEl7a8_% >gL3a8cKs[54>M< \E3O=ׄ Qʇ܄lKVC$2X6kM lRz+$$4lь=mgBw-dRf< mi+WYߖwwt2|8le.`g?mGN\T )p$o+)(o_S_Y}mȁzBy( F~kYJTn=h 90oQ}&dmx>mR@ބt )Iq'JO`_5f`sǦkP'g#Oڰm0OHwDX#P!A˺z}\L4_)[B'׮88cxNC==z% 0#Ala!32NyNs)A]o!YPQV?S sAVs(XC@YEd{L4':85 D<u ЗRm he P](])Y??$ %(<@f~{ALP7c_{2$ IB^\\^l yϏ>{3O-i~GI8PGQibLd;Ϊ.'\..3$γ3eN Ԍ?j}9B $f5d HJtqP5zlG#|!^wΞW~x/y5 pYB#suۧ^-YV>091(NK?A֙D0Y`t2 tӚد O)TP/"J+ YP:TRbz (]l&68 dE&KOɆDӇ4 წc]4x{_pA(b΄u$a$ݙ80*.f|7sǛy;t1t*XFm-zTo 퉶>5ֵ_N6-/K]fgN O*8QJ(7/iQ)iM޴Y,_z/XBWpZ{8NHqݭ{:5v\xp! h;NJ$`Z X&"l;wp3 Z`bɐœ ȅN9a+$ 1 @~y5|ʱQ>\}>pSc. #viIey@E ]FqBrJ -w758U*䜸tEPLHKf dZ^;˲ ;E!̌#GyK<PF*h- Uj7 XY}M䒖wr?TE˨Fs,Gorho LQR@a"i7'BBc@p(mIi|\1x"S gfP6BZ9Ms4v}?5 {9KО?n]pdVSdam]Ĺ+1M'Λ/L}MDW E%Hqajԫ)bW MpYO { $G_yc.{0GTZa'S* ?&uDs p4Rl,- lnA(aNqh(8Vpi5HUxp-71]@!NO׌#e1 PLx WwkvHg jB<c̷yHMяƦNꪰ<1g|g sJlӀ8xMlb*]ŗ'ބޯ0?^ގ ^hPFޥ3Uجf#[,=|gV):zo_,a1I(G>ymIe3FxPvqΚ+$ Š*~V[U ez,z1(xJC H zo?n k ][Z38DbL !w 4 SV¼-3p2' kվC׀^ p" 2KupϧڄlkTj MdzޛKp88saƃ%DڄH]cH}<9S t)k=jG"ifi TT?).aV ɫ *, G(peX,k,t,} | \3 ' @>1$g@A5PLZ9 HV:JZ @ qeyH J~u6x.}C=I 4;Y h'"*!+$@ wlg{Nt5q4vͺE @>!KMlaZu. w^r%HVHqki\mflWaW& O޳ߐlJ0xμ,/?R7+UQ?_]K1j͖_]:ی,E>@+{BOu; gLi$or/}vN`(w?8(4VS#~AO 5}+.&MĮ>\`L ¯d (ZRih@8-*j@SPl0 `[@L,xpbmWbЫ#ʙ\`Д3@fA3ĊMaCԌzky6G_qAc<]!z y2;^Mތr.u(C$Fr6& -fbU {ƮMq !ƽgfϐM2 {\)ia&O$ǯkجB rX(BOl`ӡB3žR]<3NrΑ޴;+ƔV;?ױwB6s3.PJ .h~}jW` chg։0tPh%1Y8`{.!`p׬d-&*BSJk>0Ю!Hq1Vˍm"T8Du-C׀2 h-_z!b&.B3qE}ęA=|z>WR)]]@Ki4e1G}Enɫ^hieӀ ځvI`KHg6e'xvl,3_WXțI[{s]|}$["Sݛ6fJ/yXڞPޖ~!=س.~V 9U~Yu@*"x&36ě7*81(58ZOAݞ' k'!aoPz H8v0!oV*(p@ٹ`8gv3eU M-`/x6ysWFьNkqJG >si)E4Qx`tc|0e`,ۑO񈅤@Xz$@Izhr՘`3]F,5Co Eݧ|RR$$kx6[̞I-k[e/k."}~R?NbP 0avrY*gX.9pJm4( I!?~Akc#bB_k~[8oCL|Us?q^mM圦~o*b?/hZ)ޜ㟄 3;Jn2cD U Ua )8~{ F@oJa7FЄh D> A@4Y@ʃYjnW'"ba͟3`tvFGXй3/DE*@Nr ?IиeG,2;8C j=RH xk{~o Ng磵P|EJ6[4sM8B5fUי Q )=O=!\msEJwnpx^?F2IQ~+_W*mlCy9H5њ |37 ]35=L{ݴ߻:F9N30Ԝ>e1ڢKK{UN*. ga]e~ZIp%# 0l(/$8T52^/'Ͻ߇- c6 s$'Z@HbK͟ Crc7np>Shz,| ^"?hrlиgsDj46:YZVCiIr5KAbx6Ȑ;Ųф= ^bN/*[O5Ԍ#S/MkTT*-4F-ZSmWߜ=AJ>a)];+|LI1} A[xNw,&rjzkLzLb57yR x OAo)}ywC<#*PqT4}K"TfLU@ص'6V `'򄿌RM #:` 6R;ybC(gl.+p3}a ;kHy<|&#I͐qt\}vH(V6\ ! 9u83`qן!  JQ1Ovo[@:cg;IB+ -ITTEEwiMʯl0^NF|^ڽz>+MH:p~b}> SA 2ҭvo9ao`⫯)˷ 6v9vo=KNw1wPigktHlwB.dˍˀ? ,)'p^me'5.`L XO >Cx8:0!,c j[d A$ilB,0~s4akpdr$ t# p] |O>-0#g}Ün|3|CDrg~[+|F &6d1,]j50;M{}HSa].Rh 1DҬ_>y{8PHUX:7g[# %?n2Imkw@)"u=֜ͤÞ+ez氨~o0YƅKQjP[dcG[:ZoнoQl50nhH @ViJ?@EMjV=#80 ({88cMBZ`\[rVX-X\Np)mŒq S bMr~ҟL{&`W@ 2>Txup.{!2 N=" Ky0%ރ3C>E. @R j(@BfHΎҗV=^UyDiYg,]6:}6RpQ6xmy{|{Uץ޺`DFJ[L}G"N'+IT?n|5Y`|y.Lq>V3/~S0e" 3S95`!e~#ϻ} ǬI=Fש/[u.oCX=tWFXDaؽ'*""Ԃ>5ա1S!㎞TdvOPm97kU5-d;%e t f>[jQbB@)wC+! #%LLϫCPj*,'kx7EP5eQ2Ue 0c; J5J ӯK.˲S@#(k x-pI㙠x'Gs㏍Fb`ˌk@T2PK M*UZlb:&F]52/$pҹ3Sle5zpiȚ:؛o?[މ[4^6ycz)(k+ooT)EfK C_7,Cn[nO7bVpZ;g9dѧ|4 )oLB'XdXvK<[y7}o7zTSIM?Kc ( !6Lp tH:(AlcY9 R}mׁ6\ Wp 2yN 1y$! l4G6SDl D˓FyV nCtu˃:bYre"%J!2J+`{_7'arkaޛfa#>}o(NlxzQS1y$(ZDƓ+|=[<:WpX}zSC?P%y& 8ΩR$΁vWZPca?,wxX `$MZ@i'_0TAK?p"qttq~@F FK. BИ@&9$X _ռN!!%27r#Mԛɫd9oW{ *JHj%rnUuEfTYmS:5h sWHޯC YͥDm PԥH e*L)M xh!V3ٖAն-mc]3{mP^6L) lJF`j@z>@AIRlH5} Vv,_\ጝf|L{\Z1ຜL[@ d1?{O= o?‡KA}|B`1C'Ug*@O޸ F[`Ĉ!{>$u7 vEdޣ`H b gHWw_k&6tdGU7Ÿدb18&~@l#\!3uZaw?ad4nVA)pfٙ9F.[ tk&OB{6߯% Xl3duBj}=o7.69ۍ[PM'p1ՃOKkfx]"g^}_`a+.]aUθh7T3!Y*B _!p =zD`{n0]X@0{A p (:jd&$Pdt> g٣/kL8OI^P)*'<4gt0D$& z! \VeRAMAǤ%ӊ , ^'`FFd3FF?W` I鯥 9< HomhbbhiL ԉ/b!Sͯr8KRQJ= ܕ'5G05bgiQ^*B b9f u}T( X$~n;X2 hqYTf!ƕH}րW. n-}N3ѬԿ^畾ސQ?M[ (ąw}2cYܱ &4@l >zHs,bD8H-gYJ`TY?9p:@ @p2?֟Cԟ OAPSEPPRTYTVs`z j8zLQ!\91"1) LGͱ @ !q` e #E>Mz7bbF|Q(k蓽#%ndyֶm4mQ;[O(|h8}⺼7Y}"a*Wc@ _TT9=]3q6R)?£ިdi"`~O<ԲϏk7mu 'T|2UO:ﺹ ׭}ܙo}1] vSSm/cEyOccł9'%Cʅ~VZkj?w6ٷ3a'4ʁ-efP `es,IsLDo(9@bsK[1if8\ v-P}\d {7/XDg"e\n`Y3AOX$gD5ELE)qxf}1D dޣA ASh0k1'ֵ\$LAS"29( A+ZO4/RiOi: 3V\-]5b+0E-8ni K+iRnPt+|_WIuwty'>n/N}C=w}NO.;3<=d VajfI,;? tMe)J^On1=F0*FMb(L*@2 Q0?pcAP98Sa(P3ti,Qz2f%4(qģ>*R%pypE"z)Ā#̰AccF2 yOcĤf>a`hٮ6Wcoh8t=`ĵD.Ntڝ Vl?_ȯz7W+8׻5rOl@e[~|'T>'䖣_(1!K!)L}Ax= >KBlV}s|M !t]+ ?uD"4XQ~CDN#|8%`T3ϪjW!m6RBq|9RWdtozVޗu& s RYgOYCMܲ<)gwmHGնdz@>Î@ >ˤ A7JzY9^+E/b>=1H wY~k6F0r 1. r@"ڝ|u:p@ 'pFP U\z @Te]V6]CBP&$C dHu~/h pBGpop=-kkfhwܯiLў#BEeIM+KAP~bh-~u%˞F` [5[MN.IA嫑ŝ;c+^o9H`]?oOorvR;q %:34n{N[FJߐx-H^7?6"levF Z*R{]P 9ߏ qGǤ,%U1hCozI@J5~<Rl, pwZ>y+3@Йրr8DA0 ̣qh@ZcD4lp>wD aXvP+.9l5dgA0N yolQHRHR "K崙jN RzHd:i sx r D0!X %Tk5H8ȸ_u457ZIn ꯁIȔ#cX*$^\!-yLsl~{3ŝc9Ő_[v]&>7|(6X1$6݈)y7I>GD0ʑ! ^KT"P̔g4RJ\ 45ZΏ_2 ȑP2 <rÂ!//DË+[F$ e$w EX ZtP'b*I&Z3e<.S8c6^G#)؉P+U.&VMl(d={`3Y@_v9 uYsz; A{@.3O??^&hɞ cI&_Z>/l ̬^8,o=ܩyTm{ l/n wl1~>ԝz9u{/`*?=_AJ@V" A@V=?]B515p@8](K (a}zOտff2L aTYD'pBXEӻd" #7;UAkg 2@X1XVqq3yu~lј1DN灩9ē"ڍ7QJ݇\;vL&- ΙW*^~k\ /+*9GR>>˃z}SEqI e97 dPKHs ϧ>TĕҲ ss>o#$:/~ǯnC} d ;0 ;yfٚK7d"Bql!E{$)1ħnE!y&v呛_oQzix ̞Gmc d⧸dma?yfIo/7F$@ dw3ܷمba8,^7N+F=32ƿu!:~PnQ5IYʩOß8#:B>=oi5R3<A~)j T:ByT.>8&~0 B]:U?4E|WN уUOd81]9L+I0uv.q3oO:l'84?_/}l)"Y;)kr!su#q5ѮIHtJΣ*`kv0ϩ>mx ]^7iQk곉s|_R:8"m"7 +,Xz" mDzJAM."l^PY* w||ba}qõdm͕ `#jPވДFP~5J @3[ wWhD/j"?ar&%I kO#hE~h9HhiHEY09YĈ.fl]\ ę9c/mߨ"Ӭ QEpS_!=Ugk~(<>L}ezIL~҉ xꃩ( ~{J{F΂FaˠIؐMiҬMDTG(Po{AN͐G^}Obt@dA?3`kz8CQiV"'8=N:Y W3vzAT(\1o7QiGQL)Auu#S&bu~ gȹ&<Ďr}(RDxZ"" مmT!E&VLRZ:@vv,N\T% KjF˱`]S2L*,ьGL&r^V/-W7bj:)ћyD}E^ŷhRޚ799Q\NV*pg"EyesG%G3wM{%8YϞ=}%+yZ⹞ı#Cf@qPX^SCd?;*X~g{ eLIF U[ipJrV̉ߒPrтBXk=@2FPbV : x:Sbj$B2Dtf 8+ɻ?-ӹ1 ql6Isu< } En[>U lkWR3DχL{Sx\dh(>""uIߥcKglSV]i'dq 3Lgd;.Wy$5{.;ΟW _"KT:b'Ϝ\ƴ8M[ex_\k2@(\M'J:T%b3nMXh^.C}wkr&gϏ9DFQ}Ч3ocW@a7>t &S݉D3*ʮ?c7QY/h$jcǁijL{ds<(F ( t(nQi0+UM ]M30h=` !@׀mh"0O0ZkR  ̼Y/Wo *{`hRڈ9ǵ7xpƟރp8HS r;̿2KHSDX\ѸǗ c V {Jj0[Zʙ̥SL5DQ5A TvefώG*~/Dj|R $_m}G$uϐ[T-ʛx3-ڋJ•S9\ez\*:83S$=(1Rjd{)^ߔ,P<^]q H= dvetCA;"sd`е 3gg*\QdC u]Cr'kUOlUH:Ys%kDҤ_23]}%ryU.?A#9|`q³UBNgLc_%ժJeksҕ:-.ak=+u3" oTPobvq()\LH=yC)sF.ž?73 ۡjD dVOjQ<}Inl[FNAyt`+wul&vEfiQ4Mu>տ[x U]6D# + Su]LAR+&p D;OsFQӯ!ls@0$2z(&FH.\ ! f*7x=lj.td'->-Nnmy-U@^'M F BkAnJŒŢlg#ӂmgN9%sMF uL2ѲOogz Б򯠛Ap͢: ZC_:›UJWFӽ/G^|c:~sla :+\ ٨4al721g t/폗{(!Gj!&orLAp^7,ܹ1⟛6xKcHٝLwΕf}p.ƞ>盦Bƀt6O̲ [ݡVg_?R,Z3̒>3 *ѯA=D]aƉ3qP>잌NDY28tzA΄VGpPx+!b=vqmrLn3_PoFyo\8$,iECQqdLI=-4Yq(?g,ʢF7!ѩ ^*h'vїub*CBJ?/O2q(LiG ׸->a%_K|y3 WH 6ݯEPଞ M RP eMr] WDmI76Gh~Su,}W[9}HT΂:ޯeq`mӮc:3% 1q+Y0I$K F@;Yΐ{X(PgDEr5@V'#+cp􈸷t`Bѝg Sտ/Y6.'H֞ѥ)8,g3(^>Fe-魓,ԅ8}&Rtȝ8]c-^ P /KJpBRȹ1oWXɺ@_t .@ȶC7lOtG0NXCQk }7ɠxZ˷D%|!6&k `˗;pl5َX妍g䘼ƫk9M|x=BaK'Vxƥ{@snO/k=h?T]tnk_Y[^_.\+y&ޱ$4jd`BS&q &f,Ga$ѫrX64 z@?\xOYH{:Ja otn4) R*9yҠWj7:UIM 7̀.rXϝX $=wZL5@t+x%RWDDڌp l+Ҥ'I5 <.7'#ŵM8z|zhNɞSMK %Jb߇Z֕X39mzDu)kSJi]g7&>@EXstDM\cpi[Uxñ`X@=7?`ÁYPӆ͂c-w/qzH0jV;ְx]O4tAvOX+):3EusPW?̉Ľd۝q'fz$;#zmbdQg1N[&Ds1 s N4*ÙZxcl"vdA>@'{dvi4clalO-BOe*|[z(5B PAQkrrPryojrw_7xg=_z|>zw{gWp?۳ƻ%O&w#!яbo;n葯!ByẂov$.*/X<k*$ϰ$EꢄQ vz -kOq?\3Zm''Ez>ȭ늳Gc_`Z4ex,]q۹[CW)vr&`2RS f8L4)c!,y#9+دI$eK~5V 4̤8b#Rŝƚ:Js4Dp8K?I/zkbuN*wm4<-}Gfc-e o˻z}eNȤ$#aG=_=vM8 =`|ƣ7R{o=% Na;㦺;kaA‡"v0"%=}"'_gD=VQR\hj:FcjA6\nW (]  . nhjxܶS]6c?P X}WpHI l(:Hj}\8# u˰뚃xS_cPqރ4(5+ c`ռᒾl-쒗1K2[DgxXxs罊;υe_mU$vx}aO _A*{}iؼtuMO sW3Gn{H٤"! 8Uo>&{IF~Kdpw {l+㳾6ŧsˍX-U6C@J-W[:iC/Eau v.5*tMKÈ QBC``nEg3o>aL:[LjFP8MVF6.  !]H f"8CVPoKtuO;)i8-MjB W-q'E (4 ~짼Aq-,dl-W!O@Ls\F}h"f::0e|7O[Ĵŷ &u (6 ='Rzm8W))j}pl,4OPccؼm tc;jw??,7/gmRd]q.ЗisUVL W^ thۍ̯WVԘBQi_AO{(-J-tC^z%2apCɏvp cVD'Yno S&r0%:T_Ğ۫&5NSQkK֮:+2sARZ22{ .bS+QI8iFĀ7@v~B vf؞>켔UG % 4T6/әr[C{ 2ܰ:?=t9+Pq68,MV}wB>I1 kQ[Bvj<PJNN׭C OSեNs\+Dpئs|z$H$ [u* He3Q,xj<@BnWc2`g"ſ׫ƹiBM;F]ZrlC$)ߍfv>M0pmnҙ};@o)rFI|ĞCV2 H&CZɃma"&66]2G*pbCZL-( SaVU a;MqJln@ֱ?s z8_3] Q:7`jh&TTپԍe-Dp:@w Ym"3_w8OpΧ/d"[r2 wݽq%xꞪbH#'ҋvԥpG6Yʨҿ0IXO@chų:*UkhgV@D_xpt{ ( M/0)`m471+MS~f(ܘL 9:}ԛ&R7ouxV5q!xjbJ5ɬXml؁a-ԅq׶sx/T ƴ+@ բERo 8<%sf?=.ޭ^b2gbKPT}K~u: 2Mu2n{\Ǽ ݖݕ@lg]B[6<.z|ƪŧRy4^of,$:6m c淉?g!dk}2a뿠dG_BIF@Wg.Xi~\B >!Vg5vѓ-EG=ih#'q= 4 >atB4I@]dzsCbiVeqM@#|B$ P;0rBF+zdD"[@e b鴽 ilD.b.DNP3\&~8j|𙍔Z%*8c&ޓˠĎ K}ao"aw [W| ›t>;[{բ͟s~vo $EBx) rw~|vgn{BEiLu%!X;7)䧋7Sfjh.@ ~NO A$C9 J@NQMfߒ*" z;߃MMNIy6qd~@N.I 7Wg?ET8Vl:?C{f?r衊Қh@ ѯ&kRHc D0U\j'k:wʱd@iXqk`kOflԛটUZHAnoA?[1],Z8uLV1[ODz,8"<.xVj4s왖NrZ y,[Rѩ.OmRZ5m'KAd (А پ(NXa%#pDSzQT\dk ''j(<)ẁa`DzXI?#~'M2ā*!VG~y.Knk%]N.d'򌭌Bm:slz1)] E+4}Cΰx\~]E IFr;DsΠWו{`K4>[Iݘ)@&C2KB0J ϭŻ1cv/ lۛ$s~Fy_Tk4jS m)g,jNoZ3ٱ`66l#տ}XҊ^-IXĶB?a  bCKaFG0[ANj .3 I$].>5ah_g͜ ͝‚j`z )Ԃ#3-6=MfĤ!RM~ύq]G ,W'֞kHlXLC}?dcGs&*L0]$oh1zR,͉L @r׵"ݓaoKoa΢l p?pV!ln W=ݥ27${5zZqx)u#%z#;jb6 R.^*970OpҙL㜖9=sͳ,oP-ԚUuj'LA#wiuMVnWۥg7}ęvQ~nh@kgA*pqymn0}+jlSA/u(id BW"‡Qau2~$p!7Lg9G*[ 9h9Mi2tzE9LR6k-gJJZ<JԾ3 \+Jḵ~ސP^lcLX2&_kv$9+<Є@Pk?w8Ȭ91XW6IWOHBNa;e2m㲼.Ώ\jQK5x(f|bc֋z/ FWl =8˝ %B;kg #{g)udY^(W?uuoS,}l߲_s:2T5@"Vw PWДԋHwKCW#K  xi (Se$mb8u[  s3x™i1#"9Abs=e73xq8P|rX7! 樕@YIҐ9DüeF#S{eVy)*8H_*DMa.Vko]qL !O2ϋ=mwJ+KŽ\O8n&s㨮;@W5Tw&=#ۂnpb=,sA\we> lޫk}ֻI;SgwƲBW_tտ<1?=OGp@aw# i Qۇ w')YtLأ!7D}\Qei]vػ0 :D~!(PtF%6D{]0~ %{De,X`JF$EQ'|(j_AB; T_Wp4Q2&R᱑ΣKj`qcxcUK{Y+ 5|FP^!)IXɍR]X5Jޜ:;#vfQIŸ!i %C%7J Y%4%+=S28GP# o cB`_ F;[izEHEmOgVvx=Äel0NU!ᒁg`>S|PSP"/fhxTZ4b+ Yma˂tgI^Ǚ!x(Ecd(]7m\F-Ucτَ᝘3;Ucڟ(y5*R^% `k L2+LiSH r+y23 Z5hK>Ug#FT%)K&<~nc >iT?Y p/?up@C3\  64 ;A8wtG{Khϒ"P;fL[ jFY!UWXP2 a,A'Z!X3U<)FėΈDfG%ZN _; qcm?W?y,ͦh "S~ MKaC%s*Sr<[u ;wא<]^pC5M+wE%%coe,ML?oQr~Ɗ'E<#TϖBtvC-_ Y8a~m/].8!a{-Y?k//y@[6Q2n',߃0L -kXi2FP\ aղ jWl$Z>Jڃ]cZP {@5fB y(oѬhe 섓ljX!wb^7m:u /$ NcZqb[ B~XR+ e@Ou Rx=qu)E?>L\>۩`>gRT>\Tyw8f>(uBׂG%T:2-tx8?,V./$6zvF[CBq'JϘBbq 4*8^H@~v(@ _ʮJ0W'V۬0{?Zb4lԤUF$_4^8S&wU6g2bl'+vRn(sGGIn8:-g 3*'S28pq9fIGMc7qgs)粔_RL\Qfo} ObR<Z=. ~eP+7 ]\OV(Q7*[mR8<}'ݕa˃fSZ \EX|د'?| pΌkעN5ԴTET9<0%qWd m!yG𵒟[VS㡤i 25HryԡAxA)^!&S=1l Dj碧VqfFdgqFMFe1¥Z^ZMX+˞EY2 ڭ_oSBr~͢-%t=[dr Gy .kwKlFcMIYoM}BE$2_C>hO .QIaЎKn 2ı.ل}Է.Ww3mD,ndYI=nH%E#K6ؠt{+|Zp6yy5öq8݆1 XҶ@h{0]VQ)2Mm|74m?Sp?WW>W/ _nj||(b@˖L7]Io箖8\3aJYCy =a$ Q 0}L8rc"︤d \0,jZM(BA}*$x16^kxı D ^gjd. u;#S u";T?[d3 l9Ϣ& *B%՝3b%C܎\W!sI :W;rz ] jҦM2|d$?CN%Rvc9hb̬qgfw0~("ػgTCMꦞ{ui786$CynV*zvԋ,+ĘeYw1 b)iZ$TbIJC:=T ]1oSE 5_χ"tRV̭@H@{EpGnv jP]M(Uݧ*zdT69"6nI1ՙ j:&cSXߣ\3J N` +mD N"$>蜨1mщE+P4R&BdtN[Ey5s+\VRqvW-r~PT:%V<\;vmHS,ٹ Ɖ`*&}N-E:=:4$*0ZadY(#,IJߖiK_x:%2C읗YBc3ئKq +6bjN9}x~?? \~ψIXVHMk\Z8)J~|[wt ;x'F@OW'41|R>m6\o֏*1L >QP< Cן:ֽ[K_?B ü]0{Ã?`D]vN#d6L\i=NhL2>ʘ{1b)CD9]` >:뺞7k^2X3,JA&L15G\hy0CM2_ihTWe~JCs RB+BHfvJ;cE4hq>%˖5dM.#Z8dϙdVk?}>#T}hS4X&Sx8lC(4M\سn{1ϩ1Kw^K>ǀv5XoZwb]ɚ![3M& Ufp|P 6XVxC{|YO1*W686F/9KOy8iU3PtBI~6 ~YR>>gt]ԃ$w~ ,6gxSfBK.{ BHǎAo5!9ʇ/)<..Sr2vX bO^Q4IMJZj sq9=O1ou)X)ٷ<j,cŚ͞Rt5Ú,[ OwCnaLg#^^~}0<v*L5Ysj2اW&/똝xԄ'0ubqq֒(s~L[dXgv} 0@d5J@0 hڍ.^ Ze$e \0qz^JlX96GƼ՟u31[69 {fs|+'A+u.x"AǐakjWZ|,m|H?;`}BEJ1Ր](ڼ0/mm 1Ǹ1=̭@cU vaN1=|.y7n럇ߏk+)+ݒc IƐxE~r +Z@,a sd<>]|kFȣ*vqg#e9,UU^3 `lK<dacBq-YHvv}3h?]ؿ & 1 Ji_$7/(  W~:P sj"|%=ԗ\$cL5t@eb|;$223 lV' Z s!53m -Tp r #<>̂6 Zuc"_k1żdq1/r{@/Ê1~q(=!Ԗ9+Η=_0[f]jZyugeP[LV~>8Sϧ1"_!_o¤ZBj X-MBQ|1_m0&ߒg< 'D[9R-Mk0dM:w`G4sHI4Q'7<$ {q>U(4XfA->#< Э&Ǟ=60H7GI+ 4fg֣Y7,fv麲 CBPSa"a3с"1h}w{xK;i M6^2 P#bUrj?񍽼$Lb"2}L"i $*[FyY .*FM%l`34|&\W#%! `iO:%UR{&Kg#^46! r#XW /JRwOk<g(qu[FgMAS\"$2h˄O|o#9/^3qGg&.NWv&]Z'ܿnv ̆^SNԷA8Bqù~5Yb8T"tZ[c, 2KПi&!kPa^Lވ@hpC<5e7i6sИ;PcCtZғ"?IaaCÞzWghx=@0噱O9}.Z's4} ˅JdI{7$rCu[ѫ ({6{T#*xk0(Q rxc e:bl7,b$#~AƟf;:E/YVI33*>Ầ4+RJbܒx?2Y wOz2WݷW5?vA&;ĮE)p@d q|+ofv0M|kv8'I񗽾uyyLltKαq?Fju+K~>ɣ. . D4D=iOƬf HYO=9&I!EČ%^ v;Xki1fإfOe3k,~խ^M@`@  •`Q.GESBg#+5D50^sʆ"%P^DyQVM3jƊnX$8n!2$ZH 1RMTiTCfh0'?S 1d[:R& ͚:6rJ.fIFgǍX{:&YQ XD("Տ"ң+xK1Cq{o'YcK$ :'˰A}a=rdgp~,^#,i.!E,aR@.)Ru ,M璬IdJmWG`א,b/ 1kVy:NLS  !3 g~(!wl XHP\$~H^mGJhtk|VVͶ͞iP[ nbYghz0cG)6E we %dF%.ɏa A8azL?nG+ic( BT KD՟!,y H%.{cVp^-mB}sퟯiN#I:cYXÚ c#lk~ZG@y^uJjH2Yԛ,(iƩCU ܻ&!ΰ`KIT]!_̈́8kWڏ]QӏTp; Y@ @`s +Յ8]?˴.P [4&݃l!y.4sjGAn<7m~@3XaIɡ4{zթcݨŧtan]LvR* eNdͷA 'q4cLLjuq{ڎeP,IZ=`䰧ط١vIīF|֪q|/+SǤ-h|gzP Qn y^23ʬ`f}|Y"Ҿgg&L3^0u?.5Ƅ@iUϺk R_6GsddR*0? 5?>Xq?v )PJ'hTT,L".=\dvEVm#/ Z2HͧKfW f;ٺ8,c?Y_/xs2}zP/HQ,Xg FU%}4$i&}}# |%mHUn"T'lxݜ꫈UNLBz@]e!\sUq5LPLaIa:W*tG֨rkTrk[A704KOɋwF'JyACXX#] A6e{ܡ􉚔"n D̠dMgp} s(wNQfLrH!Q8s tFDZ)#+3a [4}\CL:0(a w'oUR ZӜGuӿ/ }u$N~O"HM0qSCRA(Njg5\@l+ U_n .jyao3T*85/]Tzӧ7{wZH)x:O TPPmT1ڼe&Gd2嚗(樢B5tԹRgD>adu{,_N/J?pl[&$$MnJ <*1y[1tKC;T!èbe K,݀{Z^ ,i|Z6|ݶZ{sh>rTHDp+6V_B05=.1qa-A#ǰaC!ńɦ u!u vvviB?/e mv>r/k ۗ,sM=ܴ9(yfϗnQ gA:f\Z# `^>|! ]=X.esZPۉn] װ&oG0ij#3FmLw qzD}L]?k;l #=wIA73Ӟ1,,+`;AD\-E,(^"E8\.d +2op j| [lCڟ+ p&'szLGo޸ޒCnD@fGlM#^HM՜ŃIʣO`p!Ŧdhg;MljYZ|2k_o<=?/^uŏ}RyK@U(I@.Ίehџ%gan7jN0V|v۞אiO^ kj;)0n`Rn sؽ,7yy!QRfu"]#Ŷ uRdfgaj)=1!Zktߠ~ÉPĤ¥1ԜEψ |7tWl/ @+[hҊs5)m-`rK!f o{'l/FYAc}2ovF.DZglkVͨ]UV~ME= 'puǯRbs-lH8~i:(*=fba3b;& AO枏)Q<>u_W,>?.T6jMă2Ũ`h3|I?6YsqLk*> Y]#'ǛP:_5> R ]:`)8P"شZ{Wz{HIFb 8?)Z|ǯF}V` "& +x REqL,Y'\y&ZZn*?ib4.-4 3)oDH3MG@zD̑XŇň3"N#gBqb14^.VB󠾌9IGq_]#$&)#*b~c!'\S:`0TN]hW65nӄr=>:pf;>JǑLOD~Q.ߨTWz3یV M%s/nq2.˓f:{4~=>uTWF?X0:m֪=d&H_*b.M8@l(: j \'C͊ XRkf"B9&ntfZghv&({×jZ)rYSPB^?c-ވH LAY~u$ QCUY&c&w;GU^~ '?v%xd$p2sMvqb.ƮLW%&ғˎxl3>~?1늒IENDB`openMSX-RELEASE_0_12_0/doc/internal/vdp-vram-timing/v9938-probes.jpg000066400000000000000000001424421257557151200246440ustar00rootroot00000000000000JFIF,,Created with GIMPC   %# , #&')*)-0-(0%()(C   (((((((((((((((((((((((((((((((((((((((((((((((((((, x<1$1 RBtހFPG g;4. FQt6嘴c *oϮP JT#IZ.J V p qmcfyШH#c*lMq7]QЪU,3oSP`}G(}0U`Ay-3G!D *p8/ ꅻݞ*5u50uԃ%5z4JJ!X̘^DF5~mY x<d/Q8@"E@mުPA8F5gm2` X"S'\%\gC! l!x8Qp" p< .uQ Ƌ2+ŲEmсa2L9)Ŧf2x+k kZEQB)^(x *XtNYCmdiG3rNȸu@*Zy {ukj]7AF)(@-q?@Pd*+}bYMI&*M7_=.6q͢pRL)|2Y#z,qPӣf%]}:Q <Ti̾"}7dU[r;i[/&=/g ATj(o*Bw'fk;FS6Ӓ!ugTBu!kugiy(wlaYJ<]x;jCgzB^hEǩ%מ0z,>šfs[uV~0dٚ(X`T0R+-0=̋kk?ũ΂7lY},oAygءJCY~ ï;*&qEa8ܝ *pRxj}R_$_l'C2Tl&۫_en4EjgXV-w3]Oծ^{;Dd# &酓G̺0RQ!gb!Wf,5xjSke{-hZYkbkӾ!̈xWjכO#A/9 K;ћNMVht `/ v#Ͳ󨲫!k4&#Uh$ъ38w mA\!U[*zz?,,wӣyv 擨f%,5aLPRѨ:Sy#@MHj+96KBjKjTuSrlBч-CXB*@;rn|GЌJ &B+z)B{_*~x2,%WSURBYڱIt6iwjpmȪ"WsPЮͥЗo>uNNwGitۚR @fB=tt$qxQqQ.Zvq nʯ^UE1YeWwk}RAU,˖\_(GY洏[2BTSƠ\exӃeήFMeaA7 "n!jš";c˛۹VmOns]}gS O`8fU'?wuEx fg[6NHc FKJ*uf :޺.ު2Uu6@94=hG vSbz?"m@/]d#/d\CضS=Ogcn6sqps@;rs-^/Eo i)*)ؕ(ѲKz+3hC'EQ`e"UZDLyuV)@DG8)ߙV} ۵ҺF;4+4Fj'qFYtW2`>'DTo /82sFgVɿ D/Z\be'VnH|\5٩YiXrǺQ 6S}8gjߞ,=QGF/lR +xtl\C re: 4T-@T J9}ZVazS$={eS>2];V y1Y<4U*S`V\1:MSjӈf蟇[loJTb)l#Ёe[5YIgEvdERc\znkjn=419]j+8J1GD.%MEuJU *chbR^sqEeSO>^(u } dZ*;~͇3o+ʢV1l]2jJЕL%EEGGA+/lu\*UНxkWFޛ`+eZ͆w-WO%va<.5S~Z.~/rv|ٸI Uy[jG`NF@>B\$7'Cdt^foLN#qm ŭ;T!~%{][m\/'ef(.^$|#BX%wGGu4WeUJZV:j[jW7Y[U狧nQWD]\۴^ GnmWƊok;{N$C i b5KNqa+RBAa륬zu.#g5O*O^9*A7u.Z9&-?a%dsk3J.믮1R S|nnYHq "Qg X6tH6u]&*&YG[ie: CTbUd bLTLڦsAwup-RIgmIꓰ8Z!+%rEgug|XN,Z(' m]k1Fd[[&J9.A).ƀs+< Q^CLkнZN,΢Ml3d[ Ė3]OD C6Oƨ69ɯRy>}kraOZMh@˘"\0+EHp#EYG=•Xq=qO8ubRֲ[i^@O!h>9#Șe+qTǾll6'<ZòũU10X=D'LX7nJXZD\v>6#,: dܿv'hǻYx1%Y+Z$p8p) !uϜ) NL2s2qfɏKˬ.p"dǮ 96 +UOIN,5t.7m"'(ڛf(;\WzyVS|u x=9L/}LkKsy%s&bqMWctzU*l @ )g 8c$# 9Bd,UYgDf^5MWb`׎lu3'cGS= Od~c?~wR] $ oZQ*wJN8uAV,q}Q |rK ;Y%O8cHfqpg3f2%RXxs9KaO?_HGLeXg'BfI"?B!EZtw][ uUgt1w9匙&b0mL2Bbs9vmYW);+ʌFqcd礬!0zHGw+]q:jєנLL"u20u?R̠XibMX,q\Xu٘+uW+͙sbMNx+AAdqyragPA>3Q}e3.Gi.lF^>22t:D5?0y!ԫKG bG:}ZC *Y4k/]1Lvg{Q%bV3g AFq_[az??QLbai@fffK#&~;ebg#}k%E1gYf&>JUƢnRlWv-We"?|.bv&X&ړ/l)38Q p+& ADdO!%.`ƆKr,ל-YR-y$BH7A^ %G>8M 4 NX#io+>,:с`K(,vƝky1>(K"@#NXM@s:G^8~= !|j:J;B#:/!pАƧC?80M,$I5?ı v&NGJfU$5die7UGjŅ'EaXU#-fsLtvv#Lc +/OpS KNԄ3 R0z0Hb8q}[umds/ߜ}pK Γ"kM' G1+ ZMN2ۆj#%vq :0|0Xџf/`,a>}XL,Nu*2~e>fhA]dAW]zHkXctOdtx9UV?l >,BFZ}E_jTKyפ8ʥ'm[*xN!Lj Y$K "B}d15m[ǐqְȮ&N ` lOQ8BW׆Eq.\N[zDZig0.73+!c"Op l8/G5"\V x3#'*G3#ַ'`09ȜK>N|ugK=O㿹8"rW;aCj{hOYiSsc5g?2&WLa8zzKp1<)ˮ% [+dkE:;6@00š&LL`9^?J!(1`N$O.?馁іۯcffgq99:F_^:'!gO$(u1k'Q% LA^Y_ڑAX'5v5k^>[3V=C?RF⬄ȴlߗxZb2Rcd:p͚vH*>ŸZEE;兇 GFIo 8, +yKX/Ȍ$JKjpgQKx]ߧB8FT}'tɀBb!#n'阵S8[5PSTXcɛŞ~?^<93AGE! ĩ*V3V?Y\"FLȲYHZ`A tC`>}0% w.0@]us8uu6v0~vIZYJ0-ۢCnЯPx2З[βX]{q r|Rj5i |% PxI  2g" #"F O'EG:uZ)ᭉn&F*LCBE6krnZ.uaۈ|J[wwLwW_^*X,lEzP9Pf%v 5E&KHg[М2~筕GLfDR\ʰw/1,mU!p-?*ZT,ӟvb)sCX1uRu(g!a`*Ys侢2iNɔDJֳDY1;wZ?񇨕W3?k^!&ˏlE؞*me@9XsrԪDžzN$׀YLV Pc* 4**uc?x,ucXNTDŽTqؖᒕelin귘Z,],e+g-38X DzA_ۥ-M{O_vhXṁ8.s=m%aX 53 nK$:8?]+$3=D 0"?1裴ZzAHvFq:%d*t_NIGjRb,# FXdVx2 i X6:Ti`8\SSTS-GOa,0b X2!1 "A2Q0Ba#q3@Rb?q;9SٖwSY?p[׃ p1>swOjjM--+?3Xgp?G<4,n8?w >^mmun {~VezaqG1,VܽB13Yl~/n:Y|LF ~<D]Dax\]"S>'2w.ɨT?  #^w㉈'Ҵʫe!i<ͥp?1Vky{3zv g1[>BT<ص(?3I~&cO׳b~`^ca< &?D>'Ŭ~`nQ[.&fBim.Ҵ2GqYCljP`^3UȟhJNk``B*K+5i?0TP:=N[Acr:}GWs(>*1rec3q>~ }ӛ Af;n9W>D.vkWN6sbLZ@5ma?`&s> #iSjs VRv|(0 KmeaɊ!?"mA60C3?EN%Zb*nQ6WoM!1he} y+0Jk& +4Kk.ns4n9DXɖm9>SeiF}yiu? |̴^ Ye \|baWc9oBב_Yb:b c| nm#lc ; gY䟉ʫi&͵d9=z l1RE~R{1RcU&n0O09Cf<9ܤ<!9]0pw?ƞE{P -Gi4ga`'}DP_e$b=E ?1n=CN'*T6g0ɛg9:_&sLv37s JǣXNA1Fn<l7ɃF`$O_R(^UQ ܣ_7QKmkNL2(g8I>/Q_#o'wFx1OŴ,  K̼x{\VzI^nn"U2=WT>fu( (աw̺kd,++{agz:fgx/jj*-rلxmp4ԊůoS,O[|zTWӫz4{zj/76~#d2 |ú31> @'l \r#6g HU ynV<~Tn 31Why><-&4 s'ш|hyJg|+|Zzqɂ}@U%U]jO)Vju-m@bigӫn9"ia8PzX\sz6}Val"#3왘':*cr`9]Nxhz-?v > T*q4-DC]uf1ok9_FqϾ7OX~1*1MFRyϋ=5S"ձ iSn"j+{8?Ls̯Pϣ?* F8lVO>`7%}s* e(D.~!>)c1ll`X2q)x4_ q^ZG3[hT6"еז͇1$%9ym@Yj-%AG8. V0MxjKؘjWqzt{N/S6Yc W2 ?s;vf#MV+ܵ}ReGv NNl3y\|.8x[g&sJk^ܘ+QlAهSXY.r\6?2ඏ,`NDW*r'XŌ {صͰijdK6eo|YCpeXǩcWՀciTFYZ"BΦ%$Nf18͏Ԧ?Ǣq9S+߈cBDF,OӒ̫Jnx6.bX7e/l]C!2;YLv 2c8| =Q5E7^˪Ϲ|4a57 w [Wy_k݅ &&و&;g.etٵ# s/0 MRZ71)FsyʄD},`83Ob@؉aMB*?ԻHkwopUEZzĪjmkN/f-߆SkʅXi;F;Nb2FB&q92 LOP" ෲ!nnijD~藲bjB,PgV`P}MnsJK-,B:z|n'1}y\L܌ e* 4eǹ``rIN8e%ycX"l" Nb8>EYe,҆M½D~ =8H]}DjͅL(ooZ{+˵en~e@48aFOJVM@ؕ'.hsjRTF<O<ݨp"w\=6-T+5@|GQ,ճρ)v bVyRBrs-eo)fU^q`MvrY#F;DN77̻ES?išw|EEx'2w 2` 1ۆ,PjU9BhaFfW?1x[i@$f!ȏ/[@QĽJXvDˎeCQa  ͦgTM Ȏ@3bWaqFeV*r%lO;p`> 3!1 A"02Qq#a@BRC?kZti*VB Uبcrk%= ?jѐ/7[`p" 9@+@\z;Dja19Jm_^P'uOWj1Vv_?"4hȚDZ#RLfǧ;7Z8~BMZz{#&ׯ>uMS<';# G]tZ{-Y͓sEt~њxMFԂ~ۧ-?Z|g̩J17 ƞ6Pq;|6'!rXvvɲprSB.9],v5JYL&WPQF#n4 qW,1`|S׹Xh Yȹw>բS3?V'f(| W1 e?NG~V2PȴKH(4cw9./wV'guҚ (֗j.lS1ƽa5wD?|qJV^SɪPCww9[tKݤ G¿S#GY٨Q9O KŦxN2nGӇ+~r/򝈍0'<6y{ F(Zx)HnM6j}V7A QAETlˏ0=K,OgUv *hఓ ~w~q_o`?Cp,6=v"GwD9hyi_ӰA]qQq ħ%~!yX\!aSAM+`\| Gt,w)1uG!J[ o'pb3# ,çWX;ϕK,w+X܊L\sw(UeV = rW11!1–(q3 7)X ?:7][jB2S%rselī AV(n,U̻e?nXpXj<{u]4Tj/W+%0Qqy]X;7HBrׄb.gwu6߲rry6zR6na(>^S+, ؗTYZq7Dm p2j~|8B753:!nN :[!,/_* ݖ>M>e啔 SZ恉0D ";;{<vO0ׅ ' w`nS%$ }ۍGLFK&P} `o tC,w8fتz]9.?D7Xj9ǻϲI7F[:{e,e&yEăSWnt,A׈(rn0Mw@vjW1n 1ԸfV*X\;fCWW,nˀO-N1_2'yenV<3T?pNNs%an ١,u.sL7)g`Y<]Bm L|O2b %Y/.u5>+o̮wee.i@h?EV\2a2rQ̬N {+)q._J(O41sYHhØM,oVѥ:yJ %rknNIu]t`(##pTzHˤuk"hV¼+jS\ܯ+>k}h랧\u ZM| ^iFB)yݎQv׀zQ=5RA=Z"d?Rt qi`Pcq A\+Oqis|el+?B$nW\]P}ZjW2+]T )冕cmhStXN&30hXG.o2<cy.l\T6!T*j m54W.@k\\ȫu~b)llTt5!b7L$ $Nh3It)1];8rdZ3*˽!nnsT95:S.vn+ꤣns*pTDNgu cŻT:Ȁ V q^+ޙGu1A#ԜơU #5ss |hLu|U.;uz$(,@ *ۜ_ICnDsWW]r4ibt6*FF۩x]c~`.jON*p@3c0~*Ci1M?1t]MO9L>hR%F<; fnԤʼnrzDF&YIdsr@c U˽,̜nӒ~efur~Jy_ԮP;ʿ^1;*Ĵlxlf ˸QWaUHܨ,&z-uWgwkNw-Hy[ښ{8dNe" l8 wU*SMʇpMU$*5,-@|NFaL랧+ % ,u 5E? `_8$dx[!π"/wNdl7갷=j=?5e2*>찴";tiN-m@@JpыyvCv- C8NSsj{q=T947(kVe[ w@8@XiNFgǥ@b UaD !0k3oİik=ο|1?Y =QD-],4~'V.| avu[uqMu]rT0bJcKp~@ e@J}U.!\sFLDDCAmSGnX9/Rճ4br C5{7ݏ3XMh,t@@MޢuOZMKyn\&,9uYtLo.OƳIRN;|koRmcM5{WSeuH_tCMk'9߆1b>\q%Tyuɘ!ΕĹ٨&^Eą ugsX^_)ED46+J{͛piŅn㪜1 Jno2/xirædGj-0ӀH;jPAm_M ͂fQr-ҚQxίvŬ8[T襹.پ6N*zs뢖.A?]Qؿ&3 ~NU_A oTptbc=ΐwFFn 8gXIBS X9 \KKN[ FkĖ+YKdJ70G@|mu-e,X]Ø+3,*b[uׇ92u/c;SSiD-'sCL^,!5>ݣXNʓHYV'ZM_t]ɇ<$Y:'&"/ Ы8K] \9x><>'Ku0s^u&&'0_?uquaM0'(jQ.,(<"wjq%%{X 3e=G YxU+WvBuT 2+L+Y FʰC`oDžalԦMZXz\}{U/P0z[CZйyGpKdj՜8`fyUV*n'*֩ruJ̩VN{nCl.ZX\dּ}LCAB;Flkzi kjluDSl4XNjbB@Y @:a;as,qLAR.2~!' [uEdzšT aO8)L jw}?$;8NšUJ <"`iWU,:-|S]UjIgGfcOvccc{}5E &lVuPeSb~z ԼN zI%I]ۀs.>{iY[}eL,@e8aCOdwϋ /1=+h?iQP2wQL> i]5 '&;v S:Zb; t\^>W\{HS (4&.kj9s`v! 4 7WG8t"1g\a`4'SyHVWswݣɣur ::&X9 IܠE%x+_M/ ["r;.[:2:`s9`p,:iW0eUt{=o 8P;eGw" ,-y&gdxDKʴWwʇ 0.Jn6l˽Na1<I cteѺ 1HNh{Sd4ap4 v8:})΢٥{.Tִ'~U)M.P~j*1=xC&|h wt)A]0Zv)8_W'`%e-Ղf>Tan/JT܍ًhӓP8u|G3^P2LhA ;rw;Dkut@: \wdD|\uY\܀VP\XTWڍDW;S=5UXWBSu?t[iwtO @\< }#~k<6e sRq[\Bp n/Oɸ@=)Db5\˿ɍ2E4,82Q 0e{GGBV+QV]DM? }֕C%!a>] t){h~ՒpUN'UYW3@& -i[9!]As4TyvEփ8sU m7 gt `>(7'baՋCbV@PuXz/ 4'RѶ@/*qlȚZ,*C+K9ȭZW8 |~҃US%V.ȵAT9Qɽ7QcꡪU*Y۶Qqc4-|; qgEaadiv{;"E|P{YO1]fM ȸ<yg.ɥ8N݈t|:t1"9NeM`,:pQL,ͧD(uyMg.{r2;m{U^E\a ؆kM+1+;+Sp&CZSԢz.P WM?S£qbuzS>>)b@s}p3,ֵΟ;NEsɦyq1УZ#&Hp􌏷T8i>@-$_ؽ|pi9^RSw7h~x#\EQ!xU>Ex!VF!Wtt^ ݊j!r2E)Qc\s ͭt˰VK3NZ)6 %G鬦!#D᎞pwCfOebaPon-"d.Hf 83XLENk݅ζ#nG.nkSQ,'Spa6jثrTj݄W4݈(2=z(䇳+hȶ3M!v"7@X(# y?m:bMm*zfw<0oS}(gVxȚ3E`TbiMGa q=:*sc"tNm#cs)7 z>a n*hr5Lv!^n2 XW(ߚOBۀNQ!F(r ', w?oz4#5TjҳG,O#u>bwPy%;=V@퇝Wcx|-<-ɧTflzդ)/MMQvNUY kрt BBԜE;AYMĒQ8h K~K8沖9^+c8IWf{=l7jNҨ1ZFX%P0TT%%hPt+yi~vZ{[ QR\%Cy[&~U{tLDx uhNAAk\9ʦ>\-B1w}a 5-nnW,lt+ y-Xgeݶ]E㝨T/a2EaOӍվŊRbso .t*'4Ia%23u]ᄻX*s vjii2 i;B' hURq2Z#OKN ,j.ERcgdn3)q>ߎ??OG ~n]),1ZM|w~H:rws!0mokd;C!@UQXS`SYqr8"df~c?KB}/BǿRUMzqqpL3n 2 ~[a33 x[0vSrQӏ~dq E]z@ՌXT2HL3˅%.Dn\B\Hx喗+&!l gYg)fp ?`j U|wqe}ycؐճ/s(ߑZŽexB9 =`haQ/}ĖHl\SrLΥ@Q阜{b=9mEAWPzir f%1c LڥQR~Բ8Ed):ypGdȢPAcP~Z*m9mi|`Vjry\B:51V>ʬк/~Gq <-#47,F\?ZmJQKcV7muu~5725Og4FDSpkqH2Ά`_ c>؛y:"rKkKlkKklT}r*2.qǩ9SJ1f㶏D`77RayS^Y4c44p=#{,Hz-35KYR(dp }w A~&QebtF\  Z~&\s'pg@eI~2i9q@~|L't W?2G^X̠7.c_0!g<*0K8ܳSM:`b~9t? IJWe䗘dj0Fڅ`%PjS9\w"1pRJѫ#q ͯt~`/,3f 1$XK/u2._ N1}M*--}hr_}_<:$i l8ͺ5E㗊Y#晋q?f_G1ԥ+^-Z^΀{F e"9si̝,DSn72Xq>ӨV1 EFHt *("Kwu=h[EA^2et0A51)J&ye+0/kScS%ð I &VX̼*EtZYmԽNyPD Df/,l:xeoԴ̯}XmA uq"9GfNy zQfB9C 4=WzDKp:tGEև7~b䥝G+#h`Mn0wFcv DTPG 577k3)~|@0d/z'X3/D\-b֦۞` &>DMb^_-2pRY`l߉,K3*(r?|KYC~SƼv3Q 20՗:bM]&q (i%%|&!uT)b{RV~gJԫ WxQ³0*?omO,6$06*Rʺrq5Cn;FV԰z|9k>aҐHuə /B6u\ V hD0DbԢԳwE튰 XV8T3 h"B6㼆Hs_XAŧS- 4C#?,ha [#jpF#i}7F)b˸]1fߘ_V;ec{$yL +PL.O'Id98e=5|ϋ!C"3yd1 ӗS&Vּjv=w `v@G2㯦[i jrU{;_gWx5{fğb3eceyyKњF*-rrem1ZTGXwۺ+GQF؋,S"31ʴ*KQg*>:h1~XV\~n/:|9orgv,fifXxذU})9Z';;{Y@8BzbVVX=vQnhC2 V΁N< =_$vR7YޒN_!o cG(4:7n47C%,J.CQb\S:]FL -BPpJ>ǹeWR y"+Zbe`%52cT)@EWGxWzcZ‡ϣ&?p!ר4e]a:y1@hvF’)*qyNL@I{w!k M^CeO7j?)E~hӵv2V`Juu} :e()D3؃. b z9-nv& b~pw D(e= '" ,+tSJo`^:E2q =+ĻTᳩ,*M'L[(a㙂D2%/0F\ b~guQ yH|f('_,Vdk̫^^Nh9,{j"]9q\M1N6莏q(Vb7J A,'f4r-DA`)*qW03c( Lg+X-=hzy?xy {^P}öbn2X}F)-Կ+"&35 hP%|A|mXژiuEp$;L j+V9YT[ V_u(%GP[½~])<8ߑ [ M%*^-Z)/,=wmkϙ+{h0/pfT]ip1; S0{cPWC;=/%w)Y |>')*QE?b/M֖ci299/rgV}em$C"@g%~3ߕDKot儆JVdG+ĥK{#]`xw_*n̑H=%8Ks-։آpPLeN9W‘.NNPPEB^EmѼp~ q =%K c8D16_bJ䘂ˀ ~f7^v1R-7z&18~xc]'īռT e&uRWyK}:KwMa@.'{b:BQ6 UM)Xkiv [ܱHLUf[vDSl0iYQ{m|}:r8Yd-x:KHhh;!K%1NVAG^@g/(n9^j4\ G#'#^]h.F_t4)8:UƕBnj4ۡSRc^ehЃKgjL'bcy IRQR7Z TZVWØ8|Bv:e;^4F7yoQ=HP#w US协P]S(,f>% w|}5y3 eʦUІ_!\p{K_q+{㿘\3y77RXuobU@{QŵDŽ"iwZۓkaD3,3p'&JW3ti !fɝ͙P*4*:161K;0>N [^056N|]B- Ǟ#EӁ0MƱUa,Y}a93W&`O*y0;߈C\2 d{AOcK5sO݌E'𕢼fď 9pf3/7G`v X Fo Ȋޛ',#&EH:ȜN"#c)ȧ]9 8_RxEo>ǟ2mwW^jj'tR0$ijᇕVrFg]J3rk1!C-`uxs_X|]u3T{ƒWOT q:wf}1joD`̘ P-A]au"wVy`ȶۉ9ӓ1 oNJ )pFƂjӠO MUX꿨t304)ߎ%!X_БUE+ q鎉NMN(CyZ9Ġ譽AYO)F>(U`q|>Xq4XhoQoxM)}q'ZiLKBy^>"Yg.qV<d2c;xC&j 4ڬ% Jj !zHË Ylܲbcۗkrrj7 0Ԩ~61/3ȎQ3heGׇ2n84?LXs%$3fޠ}]b_lsh&B:2p6BQ1%Mūpgd@^;[em/CS6|!B;cdQ-7INhJL4Ӊބ}/ʵZ#Aq0Ӧz!]Ba%&,%1^ ꃋm*'rUlAW%cl^fg6pO"v B<1冫'(s2WOñM~է}S%*팻r~yp_xy=.>$5_\{T7 cYs+l+jW13iA,5lG),v%pT./AD11֞&9G̩v~)3i6\F֋e.!G*V:D{)txG~KdU]v|J`Y"73@,y1[nuhA9ۃ|l ݪ9V)hܳ ۧڪYsG7^Ȉ&SQnKUi&IZCtCs^T͟x|OOSScSB4;9dEmj%j&-l%REm(WlT&_+Qvݳ/w}L*=߳/Zۗb"4tu ]-Y~h8UܐK1nd 5ުµ"y)Ap_~ǂ>#ֹN,9Qr"Rގ~D]z&ׇP߲s&rooim3ԧ^Qa#+|*tIN2fk?5E&=S.HA6,"xM&ܐ^RLYc:Ml`RVaqk4ݡ{{j0~Z;e5ˊJƑI`UKbz0О)QLk3Yh6&ig!4z3fRSƝq*UNթ[{Fr !H }q$.`'5SƧWXCaTQGd%vqTs|)W~zV<1zG\}n\q`..?8L0fX*j=Ejs3k#&5G}ѐX% ~VDcd?3}ؒ+}''OˇPX-f2 wd-e~c}1Uk3zL~[Q~pj{ϕ @~^1aw5 @08e˨#ĿKMhh=f.NImX .,a ey̤Z7[qQXR-Q7wcͭC֪8mR7J<oC|qPgCVN=&u35ڳD*/CkeߴAafgpwf.Ewǘ2LQ#/sG$pR#--G{(;;@OÈYk=#c [q05J[F_7utlM؏?8*I\3[Սʪ$UuQ)R-( $ !~H QVq }Ꮝ}X%ⳤ%wC^P+/ c%Fa-3x7ĩOIIlju\Leb6L*X7ozN4cZKm'n>P ׬¶e,\H c40,Nf+Bf\u/FBھpbVQiؖ Eek0Y:-gZ*,O$wDٗW1B30j%. Ng1ܥ;*Q/=.FX"q(]P7A؂fQeCQ`[/+iD*.]sĊZiwt? Sr.?2&v \LZ_QyE䬡 wH,yq~f3zJ%ymM\'&`z%&#tlAUQA|)>~ Sx|h˼8!7j9"EKu;`5sĠIJz+Ϛ`wMIC"\Za*wx4W!ukCMwVal>Ǭd3;Gn }Dpo'GP*oPNg: E 1/Zn ұ,=⣙(e, w0; # ģA>?0%û/  `re2=؁U)Ե;JuP4/h+^ a؂Tc /kF3Dcs~`W-l]D)\\>C>`Yg@W0{Eٻ@ F&kpu}U,!J#( aUF=c_ AFnfpe茸il0;b^Rf%Sǡ+|}%ٕD 'b!p}bQ}zZ4 )៿hH@%>P㫳¾n1Yd!c`a0\Mh&(|sS6 Ya1yceCy&0NF[}v LRq߼cWo2l=b6 p[}jpOD~"Z< YchF:O#o| "JԱ"tʗ/-]Kd'KՑ0‘ yOx٘ ߩ1f=Y `FcH72n\XWbbv%:` !mHp~i`jyT6Hf$h"66.$+=#͂.fY~Bg6N5ڙ D3) Qa=HCD5\eI/=Z >]HTRzBxs0H3ƌğ8R|g a(W|M_B攚~P; pVy7]hM/EW Uo֠nE} 0)_@rf"|" ci$-9rQ@l r㫊'z ,@5 Y`/! ~BeeѲ1 ,ǘߣ [Ρk/o!atb:Me;~bQFN R޻S&yh*[_%όqO@̪GPx\H\cQlǺ UW?ra&8!e^W *CGyYVP{zaAllv+Nl{V#{ԭE !.nY(d)rf+[S'kW5~m5kL~'?q{ҥCc U㘎1Js$~fYTC {Qǽp aQQqE"|J.K>eR5 s'yOD|)y'|B8J],ך0Dڵ~fPpzۨ '  a-QQq &}70퇏( ebbhtVAS Mխ ILYf'2bƟ0G1;5(3; qT~X)Cqw-<tBo(vcmyao%P˷g<fl۷j택_̼:ꡎ!&qnRm b_x;Щińڕ*j>:] Ą$2 ʹȥ4F"mgGcEƂcJ-[l2x~\0dp㏗) !?l%A 5Ah{FIeJZIdx3@10X0BRJᨚAfbV?4mϼa1.cP*{C27n4cxcx3gRy?pX`6䎠*K_OFJA@c虈3 O'̄VE)3diJ.4Hm-k0xT^}&:BL9Q5c_HʱI!6T3s! qJ{[k\[C f3jRb!hT ZԄG(gԃ2_25CfqԸ,#:,2pIx蓉x&wmPHYax`iP_"5A:qs4ߘVEʂeDQgPs V6Cj\nb:1[q4 sq 0 7e{1,OZm806TP<+0z 똺8g' 3%|[U2= 7ԩfDJ8"JV-8 y*C{!1d'1H5 KR S %V 04j)!1AQ aq0@?q,c5"!bW$ZT38\,aP-G+oJ)K5-ɸ"1#, [p:Zl4-B S?-Fg髚;.w%ŸM; KF&y*!v̮T9 cf!xr}U*(Fz#2b@r!y!qKV#=$*bc cj/hPMԅX ?;@bO1=MVDz3>Q#qU[UQ_b腋{_P` ̮Ҹނ_j0/i-՜ː>mQ/ߘu/*o7C0W`Eg=/R_.K]Z`*cᇉmwSD;W"=:O\ـ+fe^5Tn/3 ZwB-~&+f:lF}Y>xq)7{oRe94@hc x2Σ.{Pj_7.Y/*-YЧ*mc9`QALJws ݖ}W. =h@!><åkU#lxb^fATԲ7LX ^"J:xCЪ켐tGi!^T[tgyW{`<7TV@3WFleeKG@X9:drts~Cp<L@OS yܸtJ2$|vm0brkkWu%->"S>aJ`|cזU|!_0ִ,695 |c)";e>*+x.{LfeJ`M ЕlJXRפU VVׂR)bo+ ɖb9j|*{6J:J #cK1.(jPO+_v A˓"|Ba}nUX[&$JTWX \\3(X xLw&^ؾ0;Uq*4܍J' )p‰L&pY~~| fu0 K"RPgc|"t?v:EOk3Ը7 ;j+<\ up f|G ܣL Uf^X܂qÇ5)Z|2y} noPQ7NaulR0.'! /&2b4sZFm7>x103KUxyR}㌹%twS+YInCbeeJ &=#Rɨ: "p3nI=p54qQqiSݏPsU?q [kKI/,p2X[.3d-JiDBVTPڀų#n f`4iQ|rb_oq1fC0.b$s;F:Ep e0>Z OVO!=v~џ#b q.Jh'\BWߴXꛄZE_*T˶W"y[n-F$aP ."JĩQᔤ3Vy5>a j Fx qe.4sݒ鉛8@GmMk:++fbJa>'hfMeqp.Qu(2D]3UPA5 ]fE%#8gc0)Gd8z-£P̯1R191,*UjXb`4`;{`]bܱc0@U#l=|/ľփ~̡(_ Ź` "!W bEl v;ѩb}DPjbQIp:,0Aϖbߟ>[˿x>>w _3 *S=yH[*d *T"Vb`D@*p@RbIVkLĩYl7{ȱTE\jMJ¶LA}xjd`+RǨgռ QJB0D ERXģʀ1Q*a H"DMycXaWL]"%^inUpʁlCpRp:. #\r% M1Cʨcr+%tCssДL.1!WUN=-6`"po3+d9Cs4n5,-a7.=-lǠZ Ȫ.U䝑Qe9mAuH,I=FyLm"MWVPjH7=\Iч%߷7/,^AF =5)T-"kl3xN<0li.RZdHف2^Lu^%ʙwHK-KDt<25t!qS <5@-\MKϼo˭焚I"d怌f6[)[d3#Ks ZSK`P6D _<85u6CQ7\KJ)& A7bV7DdW \!vH &vCy6=b"^^e11BT)-_kif&*[ gX}S-#IB\kN.įL#d4 `'E*9B6(`C%wRKn`Apq.-00Lʼ/C! .SBah΋eƋ.>b!)sr3.q9sfqY&!1AQaq?Y zu [ق1pKIR0|bT%; !w8HALr>X!]qԛ̜Lf$֧"3~"AJ%*^y`lP'^L?;2x<4 p!OeNe@MY!56b8ld{T%C"8N>@`= (v}7`s | }d2DXd1 >?Js'U#C$B1D[oz,x2#=~^x6;&3!Xi/<(Er/8ȺپIX:aL8#u4P|F2| n)NWONWs_?]8"$-8@Jr2D#X??c&}1iOZ`V. & i1MYVI8,XƜ/4nNH8p`m[ɀ!=xarbvHJxX8SH޽E '*Qy=j  !GUy_6VEvx1e5I%+$cQ|e6bb_%1O]d59Y*(DxDUAḟ$}CxIްԀ #󋬃zwP!)<<B/b)qe]ql&gSn P~s ;/ǝdHNI%Ñ*¡I @J?Jσn2edߏߜ 8A)8ʣZe):x`>d D:ڄ=ܶ-O1LJ/yp@R3A.ږ>^cIUyM~еec'CrF&ShنMЫ#\i),dگI00wӋv˃K``Y^rgs^(F?,Y5IajhN@B[k4ТWp.zo(2 pȯx3ho A@^ܴsw93KGۈ H- ۏ'QTR4S6A6>2\fnx">w8d Ė ad|(`i_рiUµv^ח$ qS+puBGsq2vWsDzl:$X^[_j1MxeeF+KC4;X9px4b%}\_ыZyoP|,:oYʆsz4Z!5;3 @˲3[lVK͊1 r}А 0EVe0[+=US$^`<Y `0hycˈT^PL}V*EJ>n*pıXJ (r.94('%hoER<g`?qW( .ć񛳰^9lj|0ex[+ADμȢܻeʿp*:'wXv1(U %b"DDGY~:M^<^8úA|CːVz<4 bf8@@bW4ͨ=O 2ԉf0D &yđ`KĻ .ZH$Q",|a^:9H׷/ƙƜVx$s@M ' [og!:JȬMR!u09`T"gW!@?!0DGn4_c~!U$NK8&[8)&$;K{HւI奤!O#=8UYJ+MT#>[<H.Ez`?1x_'DY)F|8t5s2 ь#($BiP_-chw̱y|cQ1\K/nb B"/%D ~Ĺ +LP1#ktRg4VjZ~!8OxJrqvsՀFPח8%?>>Zm~c#W|+PC3NcT RK(Bs.@SJwpYd?zu&Dbd3 !X^~\!Rhws!X䩇74su9;J|k 3=5'd# n*s p)za R.Q+[}X$#"X$ A,'c#,eUlb $8n@{mÇ>)D2 1|aXB D`o2o(6G7uz20xRyoG(cIKJ"04w>X橓^FNQZƹǮxZ8pA`w_O"k ZX  )r@Sž )Kl[$P6^p0'E9,cki ?"*BD%.6w@3[ Mi7ìc+g+w`=;/AC,} @FJ0'+':ŰDb{)yc[.ڬC#K dxqdg* jJoWVL"2$q[N&nva%ey&=2}F`1?b>C͉?r.1 x%0*т.F6"vH2Ym 坐g SÔ@mV`J>l&V@xPT1n(Ddh,=zGdQe#aeWߣ V-r$# 9Ոhh?sJvC{P**tycl'oq*'X[vN QTy aLe|+.`Gc zv!z::&ֲN>,NGNHbl 0N@|k$d6 ]0EaL?)SϜXa& AyxTJ`Fؖ&F3dJJjg]> ?9"9@ayח:\0p{#MypCT@hh%CSxuC}XZon_}dbTI=9TKhw:pjTDڐXPNbNf7pdΆV Nùn2 8u4,7C]K8+\A"Wd  .^*H)֧4Zxx>H~/90ZO QCb (RX5#I" q8*Wo]+^¯ɪ 0}(6'8D8p*# V2 &\-JܶzF9ߠ&v*;z0Q'1e2<&t=r 0D8) 0p!X=L= b]Lڀ5N )gdx䡺@j{TB2L9olgB kHb"'yHȞ// $lX#9sm-)UhHaʠ1Y:Ϭ,*Ԓ|Bp0d>8Tܗ~S0< L!̖]㴺~ 0(w ~--s'؃yق)=9 lNQcR|O7{I X0JL>l#񄅁cPO>80=O sAc[}`%rqIB8" a3>pzH^b© ɰ۳8 ,XK윜X2{}d@1٧Ç$[˔ %q2F8BcPpJ%HO92{(7|M[,4ijJaFm,Kauͪ_ s2Qx C87lEqU|#HlNd?R <4p$|( b 9L"epZDԎ'kd+(M5 trb\/ĩ@E E>t"zǞX^d6=T2NSy,Lo!ē${d\j 1s,Y(j2ļ'#}`l~ԍL(XG !kˈ` 5gIgS'ָRa0H+KI /HQa÷uM?a^dbKR[Dla6Otڰ;&fDZw1p)a$E*(aIg)4:"IbFnbd'r2!"[b_A0bnPXh d fK07}n&G 9L]gNAh/3q8̄t) G .'D8XED1+7B=}Gg$>733-ߡH*dċ+Q19C <g  T )F;r#992aBL?ycNݮ* v_@Hٿh]ā018VtU-HႹ3ÄZ:y'kU%*Jō ϜId  %FM|BQH`bțz!'.bnUH1ƴ4Jn5󍥠T(~r/#Vwrc6CwJ9POGqšEL 3hХ$FA 1zX!OQ$8m ㅭY1#Rݲ? ࠟx J?\\bg%$jII?8^vd=v7\wsߜXA?"}LYx h < |Hqq6lDVq.(M>2(=J''P+ǀW'EzU2)+5CaihO[qXu010r,ChafO3[E Ƀ[az14N4'mw4 }qgIs&,L}$!AL'\^*w)>h##ΣAN$F~y َـD$IA  ÓyrJ'n`N ndwEʳ`4$*cS.1B4)$A )s[?CJQ7v@Pe %Xjvb AEm#h󗱽ʨ|6[:Y MC':Ϧ!T|Zyl RU1P)jaX; K$e4bPEJy Q( Bvf1&( !8xW$ɉّ P8 %vp=ߜ(#1p(,Gɩ"4:DC1=O#4H_n[ys̤/R)sxAuf+af2.d:MjGB;T=cE|;-@Z]eҩaD*V9 4vٮr33N`CWdmt| Kpp& oq(j'ez檗XVL?.:Bd$ i"xIXBE!/ f&y.T@h .'cbzܩwB.b LųQq"7+%1o$Q a&?q)1jό6mKfؚɦ^~2𪠴kQ?X%dg&Ktߩs"i#g92Hq{˅ 11ӣp '  K2w^$p6@γiTJ/#G&,/zx=N.ٷ1.AC'q@D+}V-`6ɴ2= r|1Z)/*?584xP- RQkS IR]aBLѷgix\+3lD@t87$R i|#B Bؘ %b!aS1"lup0#FBQ(' R4 2ʼ S%=IiYb˪_Z~0UKjY.uHyVU(i`EeqlFcyoheg9ҝ5io).sX J&.D "Dİ'˂A W fR"^.<$ tw5"U"Rcu$p[Hh~qJ!HGIEgZD[#'xjYIG1a'!hYJKLˆܥyS[2 Z<Á w h^X4D g,AtZ&#%>d jAefŐ6f*1@M`ACdmoX T@+s`KˉC/TJ}6!`1pOȓg#l y68ȶ'r<C?S׺?( 4Qv`Bƾs%FXTq-&Uqyx R&\qbE6Er19Ȳ${f;e`l1t(0x10^CHc`"X(IY f0< 8ii-+HekPJ@d#đqI|4\r_wV[$1 %Vj53*9:q";@n<"d""S{%6H/M"ȓ#K}`I27H"CĚ%ns#Xtw0S'NAU.大*CaO|I"O:`|oyيwZ[0@ =;¬*Ӌ~Ç cd2yr9Ƨ|ᐊ$~2pY$PHbtk6z @'baxA8).> "xl@&Lz[]FoKAEBLAq#xnpU=>dQ3); KwCodm E  L*{nG!,D2` HZ@HEuxU͟E" z׏>,%mU;DNpIUM\*y| heyDf Cw;!+ 2u[UD#K$(TK.[Wg!S}6d4f Y?-?8!>chG%S2lMgՙ } 4t dkNj8Ch2L/b:WLF,']ߠBŲP5.H\~qOƱO 0Ѫ lˮ37`sOW4  R : S78Fx-KꊪQV $XW\/_8I|Ψ OTd">@TyR: , *y56oF1BDr a Ae1'J \#0~#FhL%+x{;;|/K\C֜D|oBcҟ&k8eݩyzB)2xqԠ;_Ê"9YwRCl>vDM N?xlThX,-:A}bൿB؂Yq? Z"F*4-5P1 V9938 VRAM timings, part II

    V9938 VRAM timings, part II

    Measurements done by: Joost Yervante Damad, Alex Wulms, Wouter Vermaelen
    Analysis done by: Wouter Vermaelen
    Text written by: Wouter Vermaelen
    with help from the rest of the openMSX team.

    Follow-up

    V9938

    This text is a follow-up on the earlier V9938 VRAM timings document. If you haven't done so already it's probably a very good idea to (re-)read that document first, because this text assumes you remember all the earlier details ;-)

    Early 2013 the openMSX team made some logic-analyzer-measurements of the actual communication between a V9938 and the connected VRAM. The goal of those measurements was to improve the emulation of the VDP command engine in openMSX. That goal was fully achieved: to the best of our knowledge the timing of the VDP command engine is now fully accurate: e.g. recent versions of openMSX now generate identical pictures as real MSX machines for the LINE-speed test picture shown in the motivation section of the previous document.

    A nice side effect of these measurements was that, next to the command engine speed, we also obtained information about what happens when the CPU (Z80) reads or writes the VRAM too fast, and what exactly is "too fast". This behaviour is now also implemented in openMSX, though only (yet) for the V9938 bitmap screen modes (the information in this text should allow us to also implement the timing for the other screen modes).

    Because the focus was the command engine and because a V9938 can only execute commands in bitmap screen modes (screen 5-8), our measurements were mostly focused on those screen modes. And especially the analysis of the results was initially focused on the bitmap screen modes. In this text we will now look at the other screen modes, even though there is less measurement data available for those modes.

    TMS9918

    The second half of this text looks at the TMS9918 VDP. We only made measurements on a V9938 and as we'll see below the results cannot be extrapolated to TMS9918. Luckily there are other sources of information available that allow to piece together similar (though less detailed) timing diagrams for the TMS9918 as for the V9938.

    results

    Similar like in the previous document, we'll start by presenting all the results in one big diagram. I strongly recommend to open this image in an image viewer that easily allows to scroll and zoom-in and -out (so maybe not a web-browser). It may also be useful to have this image open while reading the later sections in this text.

    At the top of this diagram the results of the previous article are repeated, these are the V9938 bitmap modes. In the middle are the V9938 character- and text-modes. And at the bottom you see the TMS9918 results. They're all included in one big diagram to allow to more easily compare them.

    Horizontally you see the detailed timing of one display line. On V9938 one line takes 1368 cycles, on TMS9918 it takes 342 cycles. The line is divided in different phases (indicated by the different background colors) corresponding to the left- and right-border, display cycle, etc. Notice that the text modes have different border widths than the other modes. Also notice that the lengths of these periods are not exactly the same between the two VDP types.

    Because these horizontal phases don't fully correspond between the two VDP types, I had to make a choice in how to align the results of both VDPs. I choose to align the display cycle (the period where the actual pixels are shown). Though this means that 'cycle 0' is not located in exactly the same horizontal position for both VDPs. But this doesn't matter: as explained in the previous article, 'cycle 0' is anyway an arbitrary choice. Just keep it in mind when comparing the results.

    For the V9938 the diagram shows the RAS and CAS0/1 signals, for TMS9918 that information is missing. But that's OK: on V9938 there are burst and non-burst accesses, there are idle cycles, dummy accesses, etc. So the RAS/CAS signals do provide extra information. As we'll see in the 2nd half of this text, on TMS9918 the communication with the VRAM is much simpler, so the RAS/CAS signals anyway don't contribute as much extra information.

    V9938 (MSX2 VDP)

    Character modes

    In this section we'll look at the 3 character-based display modes: Graphics Mode 1, Graphics Mode 2 and Multi-color Mode (aka screen 1, 2 and 3). As we'll see, from a VRAM access point of view, these 3 modes are closely related.

    Graphics mode 2 (aka screen 2)

    refresh

    The VRAM needs to be regularly refreshed. The V9938 does this by regular reads (from all banks of the DRAM chips). Those reads are located at the following moments in time (VDP cycle counts within one display line). Remember that one display line has 1368 VDP cycles and that cycle 0 is semi-arbitrarily chosen within the line (so only relative numbers matter).

    284412540668 79692410521180

    Note that these are the exact same locations as for the bitmap screen modes.

    character rendering

    To be able to render each character we need to:

    • Read 1 byte from the name-table → Which character should be displayed?
    • Read 1 byte from the pattern-table → How does (this line of) that character look?
    • Read 1 byte from the color-table → What are the colors of (this line of) that character?

    There are 32 characters on a line, so these 3 reads also repeat 32 times. The name-, pattern- and color-table reads are located at these respective moments in time:

    214 + 32n + 0214 + 32n + 18214 + 32n + 24with 0 ≤ n < 32

    Notice there's some room between the name-table read and the pattern-/color-table reads. The address required to read from the latter two tables depends on the result obtained from the first read. So possibly there's extra room at this location to give the VDP more time to calculate those addresses. In fact there fit exactly 2×6 cycles between the name- and pattern-table read. This leaves room for 2 other VRAM accesses:

    • The first of those is used for refresh (once every 4 characters) or as a potential CPU/cmd access slot.
    • The second access is used to read 32 sprite y-coordinates. These are required to figure out which of the 32 possible sprites are visible on the next display line.
    sprite rendering

    Above we saw there are 32 reads to figure out which sprites are visible. In this section we'll see what additional VRAM reads are performed to actually render the visible sprites.

    In sprite mode 1 there are maximally 4 sprites visible on a line. For each of those we need to read the sprite attributes (this is the x- and y-coordinate, the sprite pattern number and the sprite color). And we also need to read 1 or 2 bytes from the sprite-pattern-table (for 8×8 or 16×16 sprites). Similar to sprite mode 2, the y-coordinate is re-read (the sprite-visibility pass already read it). The VDP always reads 2 sprite pattern bytes (for 8×8 sprites that 2nd byte is ignored). And even if fewer than 4 sprites are actually visible, the VDP always performs VRAM reads for all 4 sprites (and ignores the results of the redundant reads).

    • The 4 sprite attributes are read (using a burst read) at times:
      x+0x+4x+8x+12with x = 1242, 1306, 6, 70
    • The 2 sprite pattern bytes are read (also using a burst read) at times:
      x+0x+4with x = 1274, 1342, 38, 102

    Next there are a bunch of dummy reads:

    • The burst that reads the 4 attribute bytes is extended with 2 dummy reads (often but not always these seem to read the x,y-coordinates of the 31st sprite).
    • For every (burst) read from the sprite-pattern-table, 16 cycles later, there's a second dummy (burst) read of 2 bytes from the sprite pattern table.
    • Right after every read from the sprite-pattern-table (4×2 times), there's a 1 byte read from closely before the start of the sprite-attribute-table (where in sprite mode 2 the sprite-color-table is located).

    (As you may have guessed already) all these dummy reads can be explained by looking at how the V9938 implements sprite mode 2:

    • Per group of 2 sprites (4 groups), sprite mode 2 reads 6 bytes from the sprite attribute table (using a single(*) burst read). Sprite mode 1 reads 4×(4+2) bytes (in a single burst). In both cases that's 24 bytes total.
    • In total, sprite mode 2 reads 8×2 bytes from the sprite-pattern-table. Sprite mode 1 reads (4+4)×2 bytes.
    • In total, sprite mode 2 reads 8 bytes from the sprite-color-table. Sprite mode 1 does 8 dummy reads.

    (*)Actually, this is not true. But I believe this is how it was intended: the 6 bytes are located in the same DRAM bank. In reality the V9938 performs 2 burst reads of 3 bytes each. But because there are only 28 cycles available it has to use a, strictly speaking, invalid DRAM timing. See previous article for more details.

    So the timing of all sprite mode 2 reads is (almost) identical to all the sprite mode 1 normal + dummy reads, only slightly shifted in time.

    I guess all these dummy reads in sprite mode 1 are the result of chip-area optimizations done by Yamaha engineers. In the current design the same state machine can generate the CAS/RAS signals for both sprite modes. And similarly a large part of the address generation logic for sprite mode 1 and 2 can be shared. But unfortunately it does mean the VRAM bandwidth used for those dummy reads is not available for CPU VRAM access or (on V9958) for command execution.

    dummy reads

    Next to all the dummy reads related to sprite rendering, there are also dummy reads at these moments in time:

    1822002061218

    These are the locations where you would expect respectively reads from the name-, pattern-, color-, and sprite-attribute-table if you'd extend a display line to 33 instead of 32 characters. This is similar to how in the bitmap screen modes there is a dummy preamble for the bitmap data and a dummy postamble for the sprite y-coordinate. All 4 dummy reads read address 0x1ffff.

    CPU/command-engine access slots

    These are the available CPU/cmd access slots:

    3296166174188 220252316348380
    444476508572604 636700732764828
    8608929569881020 10841116114812121268
    1334

    This is very similar to the CPU/cmd slots in bitmap mode with sprites enabled. In both cases there are 31 available slots and the largest distance between (the start of) two slots is 70 VDP cycles. Only some of the slots are slightly shifted when comparing both modes.

    Graphics mode 1 (aka screen 1)

    From a VRAM access point of view this mode is quasi identical to Graphics mode 2. It does exactly the same number of VRAM reads as Graphics mode 2 at exactly the same moments in time. Only the addresses in the color-table are a bit different. So it does re-read the same color information for all 8 lines in a character.

    Multi-color mode (aka screen 3)

    Maybe surprisingly, from a VRAM access point of view, also this mode is quasi identical to Graphics mode 2. This mode doesn't have a color-table. But the V9938 still uses the same VRAM access schema as it uses for Graphics mode 2. The reads from the color-table are replaced with dummy reads from address 0x1ffff, but all the rest is identical.

    So unfortunately the VRAM bandwidth used to access the color-table does not become available for CPU-VRAM access (or the command engine on V9958).

    Text modes

    Now we'll look at the two text modes. We'll again see that, from a VRAM access point of view, both modes are very similar.

    Text mode 2 (aka screen 0, width 80)

    refresh

    In all V9938 bitmap and character modes the refresh was handled identically. For text modes it's different. Now there are only 7 (instead of 8) refresh reads per display line and they are clustered together near the start of the line, located at these moments in time:

    74829098 106114122

    So apparently 7 refreshes per line are enough to keep the DRAM content intact. Too bad the other screen modes use 8. Also using only 7 could have made command execution slightly faster.

    dummy reads

    There are 2 dummy reads from address 0x1ffff located at:

    230238
    text rendering

    The text rendering itself is pretty straight-forward. We need to read 80 bytes from the name-table and also 80 bytes from the pattern-table (the pattern-addresses depend on the values read from the name-table). For the blink color feature we also need 80 bits (10 bytes) from the color-table.

    Rendering is performed in 20 groups of 4 characters. Each group starts reading 4 bytes from the name-table using a burst read, these reads are located at:

    g+0g+4g+8g+12

    with g one of:

    246294342390438 486534582630678
    726774822870918 9661014106211101158

    Next we read 1 byte from the color-table (at cycle g+18). This gives 8 bits, so we only need to do this for every other group (in the other group this access is used as a CPU/cmd access slot). Last we read 4 bytes from the pattern table. These must be non-burst reads because potentially bits 15-8 of the pattern-address for each character are different. These reads start at:

    g+24g+30g+36g+42

    Note that combined all reads in 1 group take 48 cycles, and that's also the distance between 2 groups (in character mode there where a few spare cycles in a group). So it is really required to process the characters in groups of 4, otherwise burst reads aren't possible and all the required VRAM accesses don't fit in the available cycle budget (1 narrow pixel is 2 VDP cycles, thus 4 characters of each 6 pixels take 48 cycles).

    CPU/command-engine access slots

    These are the positions of the available CPU/cmd access slots:

    210182634 42505866166
    174182190198206 214222312408504
    600696792888984 10801176120612141222
    12301238124612541262 12701278128612941302
    13101318132613361346 13541362

    There are 47 access slots, but they are very unevenly distributed. Often the distance between two slots is 96 cycles, and one time even 100 VDP cycles! This means that, even though there are more slots compared to bitmap/character mode, the Z80 must access the VRAM more slowly in this mode! So to be safe there must be 20 Z80 cycles between two CPU-VRAM accesses (see previous article for the details of this calculation).

    Text mode 1 (aka screen 0, width 40)

    As mentioned before, from a VRAM access point of view, Text mode 1 is similar to Text mode 2. Actually from a VRAM access allocation point of view it's identical, only the actually VRAM read addresses are different. This may seem strange because Text mode 1 logically needs a lot less data than Text mode 2. This is because over half of the performed reads are dummy reads:

    • In Text mode 1 there are still 4 (burst) reads from the name-table, but the 3rd and 4th are dummy reads. In our (limited) measurements the address for read 3 and 4 was the same as for read 2, but with CAS1 active instead of CAS0 (but doesn't really matter as the result is ignored).
    • There are also 10 dummy reads at the locations that are reserved for reads from the color-table. All these read address 0x1ffff.
    • And similarly there are 4 reads from the pattern-table, but the 3rd and 4th read address 0x1ffff.
    CPU/command-engine access slots

    The available CPU/cmd access slots are identical to those in Text mode 2. It's unfortunate there are so many dummy reads in this mode. If this wasn't the case, the available VRAM bandwidth for CPU accesses (or commands on V9958) could have been a lot higher. Especially because the Z80 already cannot access VRAM very fast in this mode. And also because, as we'll see below, on TMS9918 there's no such timing constraint for this mode.

    Stuff not measured

    There are a number of V9938 cases we don't have measurements for. As already said in the beginning of this text, the original goal of these measurements was to improve the accuracy of the command engine emulation. And we only had a very limited amount of time the day we did this experiment. Re-doing the experiment is certainly possible and not even that hard. But it takes a lot of time to setup, for, as we'll see below, not too much useful extra knowledge. But of course I'd be very happy to hear from other people who do want to repeat and/or extend our measurements!

    Graphics mode 3 (aka screen 4)

    This uses very likely the same timing as Graphics mode 2 (aka screen 2), but with the address generation logic of sprite mode 1 replaced with the one for sprite mode 2. The timing of all VRAM accesses, even the sprite accesses, can remain identical. The existence of this screen mode might have been (another) reason to design the timing of sprite mode 1 on V9938 in such a strange way.

    Character modes with sprites disabled

    In bitmap modes, when sprite rendering is disabled, the VRAM bandwidth that was allocated to sprite rendering becomes available for CPU/cmd accesses. Likely the same is true for character modes (and text modes never have sprites). Unfortunately since we didn't measure this combination we don't know exactly where those slots are located. But it should be possible to make a very reasonable estimate.

    Text modes with screen disabled (or vertical border)

    All non-text modes behave identical when screen display is disabled (and the behavior during screen disabled is identical to the behavior during vertical border lines): the VRAM reads for screen- and sprite-rendering are gone and replaced by CPU/cmd access slots, but e.g. the refresh accesses remain. In the 2 text modes those refresh accesses are located in different positions compared to bitmap/character modes. It's not known whether:

    • Screen-disabled in text-mode is the same as screen-disabled in the other screen modes.
    • Or whether it has a dedicated schema with the refresh-reads in the same positions as in the text-mode screen-enabled case.

    This might make a difference for the exact position of the CPU/cmd access slots. But because there are usually plenty of slots available in screen-off mode, this likely won't matter (much).

    TMS9918 (MSX1 VDP)

    All our measurements were performed on a V9938 (MSX2 VDP). It's very likely we can extrapolate the results to a V9958 (MSX2+ VDP). But for sure we cannot use these results to derive anything meaningful for the TMS9918. Fortunately there already is some interesting information available in these documents from Karl Guttag. Especially this timing picture looks promising. Combined with information found in the TMS9918 application manual I was able to deduce the stuff below. This wasn't easy because that timing picture does contain some (confusing) mistakes, although I can easily forgive those mistakes because drawing this stuff by hand is very tedious ;-)

    general (memory) timings

    The TMS9918 runs at 5.37MHz (1.5×3.58MHz, 4× slower than the V9938). One display line takes 342 cycles (as expected, 4× less than on V9938). One memory access takes only 2 cycles or 372ns. So compared to V9938 each memory access takes slightly longer (on V9938 one access takes 6 cycles or 279ns). The TMS9918 never uses burst memory reads.

    Graphics mode 2 (aka screen 2)

    For the actual arrangement of the accesses I'll refer to the big timing diagram (see top of this article). Most things are reasonably straight-forward. One notable thing is the arrangement of the 32 sprite y-coordinate reads (for the visibility check): the first 8 follow a different pattern than the last 24. This is done to not have too long periods without CPU VRAM access slot. Reading the other sprite data (during the horizontal border) also shows some irregularities. Like on V9938, the y-coordinates of the visible sprites are read twice, but apart from these 4 redundant reads, there are no dummy reads or idle cycles (unlike V9938). It is not known whether the TMS9918 performs reads for sprites that are not actually visible or that those slots are available for CPU access (V9938 performs dummy reads).

    The TMS9918 application manual mentions in section 2.1.5 "… CPU windows occur once every 16 memory cycles …". This confirms the above.

    Graphics mode 1 (aka screen 1)

    I couldn't find anything specific about the timing of this mode in the above documentation. But because it requires the same number of reads from VRAM as Graphics mode 2, it's logical to assume the timing is identical.

    Multi-color mode (aka screen 3)

    The documentation also doesn't have specific timing information for this mode. In this mode the color-table isn't used, so one possibility is that accesses to the color-table are replaced by CPU access slots (not the case on V9938). This is confirmed by the following quote from the application manual, section 2.1.5 "… in the Multicolor mode, CPU windows occur at least once out of every four memory cycles …". Though when you look at the sprite-accesses in the horizontal border area this quote isn't true: there's still one location where there are 15 memory cycles between 2 CPU access slots! On the other hand, it would be possible to distribute the sprite and cpu accesses more evenly in the horizontal border. So maybe that hand-drawn timing picture is wrong? Or maybe it doesn't correspond to the final TMS9918 design?

    Text mode 1 (aka screen 0, width 40)

    Again see the big timing diagram. There's nothing really special about this mode. The following quote from the application manual confirms this arrangement: section 2.1.5 "… In the Text mode the CPU windows occur at least once out of every three memory cycles …". Note that reads from the name-table are not immediately followed by reads from the pattern-table. Possibly because the addresses in the latter table depend on the results from the former reads and the VDP needs time to calculate those addresses.

    display-disabled

    On TMS9918 there's no register that allows to disable sprite rendering (there is on V9938), but it is possible to disable the whole screen rendering. The documentation does hint that the screen-disabled behavior is the same as the behavior during the vertical border (just as on V9938).

    You may have noticed that in the above TMS9918 display-mode sections we didn't mention any refresh reads. Also note that the hand-drawn TMS9918 timing picture mentions something called 'refresh mode' (but nothing called 'vertical border' or 'screen-disabled'). So I believe that on TMS9918 the VRAM is not refreshed during each display line, but instead it's refreshed during the vertical border.

    On V9938 each display line performs 8 refresh reads (only 7 in text mode), so it takes 64 lines or about 4ms to refresh 128kB VRAM (and only 2ms if you rely on RAS-without-CAS refresh). On TMS9918 each vertical border line performs 32 refresh reads. So during the whole vertical border the full 16kB VRAM is fully refreshed multiple times. Though between two vertical borders there are 192 display lines or about 12ms. So DRAM chips connected to TMS9918 have to be able to retain their content longer without refresh than those connected to V9938. The TMS9918 refresh schema does make more efficient use of the available VRAM bandwidth (only do refresh when there's plenty of bandwidth available). On the other hand the TMS9918 schema would make something like the V9938-overscan-trick impossible (overscan = show display lines everywhere, 'skip' the vertical border).

    MSX1 CPU-VRAM access

    So what do the above timings mean for a MSX1 Z80 programmer? In various fora (MSX or other) you find discussions about how fast it's allowed to access the VRAM (read/write data from/to IO port 0x98). The general consensus seems to be "at least 29 Z80 cycles between two accesses". For example an OUT(#99),A instruction takes 12 cycles (on MSX), so you need 17 extra cycles before the next such instruction.

    This value of 29 cycles seems to come directly from the TMS9918 application manual: it says in the worst case there must be 6µs+2µs=8µs between two accesses. Translated to Z80 cycles this gives 28.6 and rounded up 29 Z80 cycles. Though IMHO this result isn't very satisfactory. That value 8µs is only given with one significant digit, so it could just as well be 7.5µs or 8.5µs. Rounded up to the nearest Z80 cycle that's between 27 and 31 Z80 cycles. Many people use the 29-cycles rule and apparently that works fine in practice. But you also see reports that only 28 cycles often(?)/always(?) work as well. It would be nice if we could measure the exact value.

    That value 2µs is also mentioned in table 2.2 of the TMS9918 application manual. Other values in that table seem to be accurate to ±0.05µs, so it's possible (even likely?) those 2µs can be read as 2.0µs. It must be an integer multiple of VDP clock cycles: 10 cycles is 1.86µs, 11 cycles is 2.05µs. If I have to make a guess I'd pick the latter (this still results in a total CPU-VRAM access time of 29 Z80 cycles). Though in the rest of this text I'm still assuming the larger uncertainty interval.

    Sometimes you find discussions about when it's allowed to go faster than the worst case requirement. Here the consensus is that in the vertical border you can go as fast as you want (seems to be correct, see below). Sometimes you see suggestions that it's also fine to go faster in the horizontal border or when sprites aren't used (this seems wrong, or at least only partly correct).

    Anyway, in the remainder as this section I'd like to dig a little deeper. Now that the exact VRAM access allocation schemas are known we can say a little more. But unfortunately some details will remain unclear.

    • In the worst case (Graphics mode 1 and 2) there are 16 memory cycles (32 VDP cycles, 21.3 Z80 cycles) between two CPU slots. The application manual also mentions an additional CPU-access waiting time of 2µs. Though as explained above that could be anywhere from 1.5µs to 2.5µs, this is between 8 to 13 VDP cycles or 5 to 9 Z80 cycles. (It's not clear where this time is coming from, maybe something similar to the V9938 'slot-reservation' delay of 16 cycles). Combined this gives between 26.7 and 30.2 Z80 cycles. But unfortunately this isn't more accurate than the range we already found above.

      In this post dvik suggests it's OK to use tighter timings when "sprites aren't used". This would mean that the memory slots that are otherwise used for sprite rendering are given to the CPU. But what does that mean "sprites aren't used"? If it means there simply aren't any sprites visible, the TMS9918 still has to fetch 32 y-coordinates to figure out there indeed aren't any sprites visible (and then it can maybe omit the reads for the actual sprite rendering). But this doesn't improve the worst case timing. Another possibility is to explicitly disable sprite rendering. TMS9918 has no bit in some register to do this. The only possibility I see is to have a sprite with y-coordinate = 208. It might indeed be the case that the TMS9918 stops fetching sprite y-coordinates in this scenario (the V9938 does not), but without further tests I personally wouldn't trust this. It would be nice if someone could confirm or reject this theory.

    • In the best case (the vertical border), there are only 4 VDP cycles between CPU slots. Taking the uncertainty of those '2µs' into account, that results in a minimum distance of 8-12 Z80 cycles between two VRAM accesses. The fastest Z80 I/O instruction takes 12 cycles (on MSX, taking the extra Z80 wait cycle into account). So this confirms that in the vertical border you can indeed access VRAM as fast as you want.

    • Text mode has maximum 6 VDP cycles between CPU slots. So rounded that's somewhere between 10 and 13 Z80 cycles. So it's likely OK to also in text mode access the VRAM as fast as you want, but we can only be certain if we know a more accurate value for those '2µs'. It's worth repeating that on V9938 you can not access VRAM as fast in this mode. Keep that in mind when writing MSX1 software that needs to be upwards compatible with MSX2.

    • Multi-color mode is unclear: the application manual says there's a CPU slot at least every 4 memory accesses. But as explained above I don't believe this (it's true for the display area, but not for the horizontal border). If it were true you only need 11-15 Z80 cycles between two CPU-VRAM accesses. But if you do take the border into account you get 26-29 Z80 cycles, thus only slightly better than Graphics mode 2.


    2014/08/09, Wouter Vermaelen

    openMSX-RELEASE_0_12_0/doc/internal/vdp-vram-timing/vdp-timing-v2.png000066400000000000000000016151241257557151200251640ustar00rootroot00000000000000PNG  IHDR'fkaFgAMA asRGBPLTE33sssssQ̨LJNJ5BF#N#4s4ޯNN#GJF¹NQNLLNc{괽Ļ`d_PdjJ\cEFEgjgM`fsysNLJbbtztTVScgcx~yf.h.6DH^u}z[`[XX'ņ}}gg%%%nX[Wnsm>MRqwqaacJJMQSO>>^c^8:8rTjqX$$hh/h++YovջTTDU[jmillBB:IMGGP$$//)[)؛DDxtru»񤬤M""r33ssy99뺺éQQ˴b^]44úǾԍQe&U&2?C-/.<!!Du.u??i\c{iiQ QAGGW''9'AA[[BBB??oo 90]MX&'٫yiT:H $4$.#$dsZ!HX$TɁ$2ZG$K a @HABq A@sd `h@H $jHp\h4@ %@!B88H"!eFqfeRȩ,uN6&M"W$.rD[bE\8 $@HV>w$@HV!8 A@@@!N\cW87"jaGQՎ@H $d!C%D\87R ǵ $l67>D(BIH] $8@!!2vֿ㽗8sT RQ-VߑBdˆ|$ $$Uh!$mM8Հd"  95? ,/$k@H +Bq A@I$đ6]Nq؋,ڣHez!":aw$@HV ֟ āB8qq;1SC.EO SMԩ4)"ιMn>u@H Y9Hg|V՚;s5#.@j ǵB  Y$48xss@H $d !8 A@@@$Y+ܗyҮ7ϊ菚p0z($c'; $d_~&".@q4@ YT/jH#$ $@r !8@!!B#ufBr$3;TZM-0,o9Q:D $$e.BH $$cg&[Fq@._q$N2d!!lq@|H $$4Bq A Agهz{,d6̗业F ~Ɵ,"JhOL) @H YeHRDGH6ǵuGhdx oH6$I5.@Hj@yt@!B88H"!$rͅ5As#&{TJ l9dM?.j1&d̗ZU+`: $@2uk c !Zp\f@ Fr m\$I/@H $J@!8q  cvW#?HΗvQJ$qF2(?.s5hKʔeW$XOؒɟf$@H9lsRHV(J188$s,1!JX,HD]T@r 1Ԋl1@H $a8@!!D#=cf),[8_JtVP4O14&zp8k[rDۇ }Qb@Hɒ@2oIxh%#.@H\2%!5?Hn#$2#( \'fAHcis@Hd~Bq A@Gy>a# u[VH~t<z }%qUw]|h?ov>z=ak!ߵi襔2/1Q\2"K]i6#-*̦UaI ${1f%>%i`".@d,y(oΗ 2p\qL1zEBV#I -V9@ciZ$@H$;a^ϡD3=8q ā*[gz/!B/[GFJm]f82~Ne>°?ḫ.!bèM³zOC[5 $@JCf#.@ q}~Ip\3=Phw p H ƪMBd I#a1@H $⌸oqv58 @w q# 8̳[չ.| zBܭ]0gL.m\)p۶伡NeXBD@yM TrfuA'$@H Yr:qK焈 2$~gُ0nvcI_QLKIU{4Bb, $@r !3>~Y @̝ !83l{ B܂*!@'kG$li (%H?ǕI2.Փ}!ҖݕԺ*o$-[#we$@H|Yx ^͈ $B(N{qEyMX\y`s$s<$jK~BB6I$59F$KF(@H $‘]Xq1Y0!.M8VL޻5'.y>?0 ̷ߟ~>zur+~ȑv|)an}\ԽٗUaW V7uqoBr޼Pz׊nM[o_]Eľlݦ#Sݩ 1JL2yzR;CI xJK 0~#J&} ][t?@H ͇ XHV oUi_eo3;4|[aYpwY|}/OgzݷІi/!cF,tNPh~Főĕz,y $ Iiw$@H,$I/@[ȈN7-/j$dW?>s0-S霅>iavP`}2{i4@[Uy'l~r}ԑ#{y}UюMv7S~Tsܾ=߻~#[ʕIu}\{RwO?w,0Rvu|+SSB\sϝA!N!D Ǟo]|y[𱯿wO|Q*Si +hW[\޾!qKEc>yB]DY|4|/>f!ۭ+IƍϋtkWL.T![n (!>uQ ,%}ik@4 'Ry>M<"JL6 6n i@H $Q/Seq3tqU$+f^kq͟KۭΟ3Ғa{sy⃽Rg9|pK-8.}G2rƯfH:J|tJ$^l/@PHH5 H$_JKK5@Hmd>MKbF<*#:%ĝn܎~+dWcU% s0-9 VSBܱ֕|d(8VVv+܇%={><-|V~Y#O{9^ts3G~~EhkW|8[=S?!8i~jI ھ ??]}??>Z^BkX³H ^d ̹oP aEzڸ s~$@H-$ʝ !JxVv^tC}Ϯҫ˕]*AE h3TBPjJZ_z+\+_@!nu qϧ}^zfy5?ur}qvt^ϵ s@R'ŗw+ܥ^Q婩~ٹ3@ q'O_*%PbWP|t&/ב+kBP݊b]e"߼VJ q2 [˿\;QqNo_^x!dk߭Αҧd]$O$W=6}4{1n=ɛߛ/[VL] p\&]Ƣ@ e_L#=%шuWh}"! М`9@Hɾ@H"ĵ71B\]W+'ĝo 6 !-qAUBq,w..y?'|7$uw➹3|ٱ[/+g!8mW?s;ƛ~_._+,M^ݪ闱}Ckg[v!A|x@ q.R#)3mid aCn1dgnvNGD6rO2LsO19GI $}v,iዂ G\dU!W?o0C5\v:}fq3rVqdY ;˺7A)Gp $f&Hn7$<"@H $CBʱENx9q 8CBB㖲l8߁JF+k=,.#Q*qI%5$@Hn'$)aM8!tz8>K.WvU'ĵ]B\g!BZoXq ĭTdR̷?>/TcT,yH[ŵN)IBq !yŜ~!d ۺUUƗnݪڧte(hW'LoZBsd򓝐-cڛkk={wIw;;đ C6J8VI[KO mX)L.G@HmD~&y^komB]W+ 6 !-Էqq ĭLO?wc/?qGs]߾'On\;G"wJ]ɍO=wV֔].]_@!N!ĭ?\/ߪXh_?zy'U,s_mNnLµtʕɅvL5$[$!f=ㄼ: f$&WyI" $t>}'c6$@H&cndJ|Oc5".@L5J*ۭΟ3ҿͦ_~7OC^Vr(Ù+/O[=wZqK &gQN*ZO$'(0U%mKt- $d*@>9) H$WhIf(Kj44 $}Dxv<~B4'dWcեʮꄸ6M H,DH [qq ĭ|uj;~Um|{x6#OK3#;GbSytq~)=v}wN '~q i{B+ !Eitپ>}\ m[U˪G{ڔ3nBR^ۆ۲ބy?UbuX_V![nGy!h> -O-(1T/&5[>jCq#01FM#"^yDfe󧔄$@Hh)b fVCqU[g˕6,"~$ĝ+p/O)!|{+L߄^qQH~ƒ삧o*nժ]hHI&yPjnl; ܐo*qp $@r` Qg>sNߴk'dW#եʮq-A@Yb qC @B f|*B*O[EЋգSSt7b $'J2mMfS?#̦JL/1j2˼&N@HRA"2u%i)ز.JD\d!7|]4Ya2O&~l$gz02WI>(h I*$I6#Y"3RK$y4N,|)@H $CB{"x9%Y' !gog A^ ?sB˾wQzFf_]"מ(c  9 )$"9wX$(rcy  $d//+H  EOHVhofjiJ;.7 O'ȣAr"QQjϔ).%H I4;N0q:- Y$ }cw!@H $J'0i^ ?gA[q ĭ)wI!n)hgO !l94 gbȝ3#%L*a̳%ilfmݑ3A2(W ՞r^ $ Iyq`hۺph#.@w q=YO—"ZŔ9WӜ#Pv0i}JyrcjZKKڷa"Hn$|)ӶѨޅ,@H PR~Xd{'ܻrtgB0@!ne|m[|!n)B,B2ĵxZƕhsԼ#exKX + DFقV8 q2LI A@HR@R=?FG);!C}".@ʐPJS qO~ݶ%.esVqw+h %bBBĈ @;t;_~N[HHQ&Nqۓk3(Zײ$1AL7r̈i*y2 hT3r\I@H $CbZ[ sq[ёCqUĕYɝ+,D!HGJB#qlE j)WU}w@H $Dp/2`-q)2,H <$vףV~eeO~;r++hV9ۨ@d!r$Wֱ2|2=( b1H $, a{ $ĭ!!B/r&r 6U_DzIe:O-Ô1lͽA@Ĵj2ɖT3IrJ $@r!!窮)g1r>E\Y14q\d5ʤd<1+Gc:\2b&)ȴs$󂄋"}zq Y<$ hєԩ $@0Hȴ'.)q ā!BBd䑩"5W>i)2N,HYOiXޜD$iNDv=d =Bfco dlY@H $İwN-c%匸 =PnG;Z$xDtp$A^QT_&6` SNFo6cz@2؍i|pf3 $ĆnڻqH0H $,ׯ QBq A AF⸸޲Z4BꊲkK.[6NO#ˬ.mU )oK*$y@H $&x)80OL $ 8ڇhCHldYZqQ2}"R^:TzRڍ!n%!dAmǖJ9N^'5-l+@u@H $rMs,i=T YmH4NJR^!p\#F򙥗\yDPb{x3E[ D ٝcS=ɼlE%ϭ O $@2'HlӀQI āB8qqBi 2LølMtI1rR)NY^w7'o?d^:܎l[3eE'襼&gHc,? BBR2[3,Ld ^Nڰہ:ml!@H $d.f|"9k Bq A@Cq!ز7&GXaVQF'C"$׋:ќ\I^]MK&H $;$K7SV? isH 6 @fxXװ:" 0|e~(v(JLѱ;+c;x2AR"1y{y AgVmgqGQ#u|m}\h9L^2L@H ?"3yh=4ǎWqD80մ-)^ʺ݁Rq68 qY>R4Ȱw)K '{1cFuЗܔY@H`G!20H^(jI3{@H $ەX_RU&'$OA!8q  82M3\5mnr$$VSm>룂_v6B,9 2AD!@H $b:zx3U$`*$j82gx;79DqEJ䛌Hn?ry71Z@ңӈg'w~1FVlwε~思DC;@H $d,$ql6ErW8@!!B/Vf$$4A0noa7:}_mҒxt*i}CdP޻iH0H$L@A[b8F{7$joD}MI =oިOH $[^'Sۄ q ā!BB$g1cLLKض<ƽs/Z^B%B2vpn}2?$~K\X/94d@،g7&ABܼdQ|+@@@@@;8MRMZ36b'(DzrfvH9HotL{Җ dC[Nϙ&4k8T\sgĴ4{'!8iF$d$'$\ #}[ -($vHW2$S=~n$겟8k/q1H cɜ{ Qr%It\U9gdr<ڛ]R#  QߥJr\Di"س=GKQL܏%,$&1DHy@2&9hnO&dL]v?"p 2θr(3-D

    (HGI H쯘$sn` IL:pX_r^[ԂƴLȂ$XɯtjP(Kן<͝77w.m:7ZLkvR{vgŵ7>75*Uq{;X2%[Yvv?yʣ/#Fw86M#EA76$iI,LǷOkH_uܓ >4zm Ծ}feS_z'+5O ZV_lԺME;.|o' UyJJ?'Y[옇'yۨͬB5%6ڊ~c[4=ZQZ-V 6zfz\ M eʻmml;csY赽uɥ髳yg`Ȯ%}(i Ik{[ԭeтmy<_fFZ|`x[zs[k.mM ƒFoy,XRUqRMv:Ӌ3{n56VP,)tV]MBtU2m W$կP*]ok$TPmFjx%VlYHڮ!bXR8[8^*DAZm^ےa4zULcIexh+v m֐| z]okb5z1H=gKzx #3AlFO-$86zC"T:\/$*~U*a!X=ay)@:D#jeK0UhԔiP7XH{^cPo^RB[ ˴ Uo;>ej]bH'9\at+ӓcֈ0Sōoݒ*C˽CCU(QӖha.DZVSMzzB'U9>J/lVҭ=kiԢ l2>K ^'%5Yexm7 a+u$}k ==W[ eNMtG[BQg+%4YOdżmtMBt%goݰgT^Hol5S3gT9DE뽭OLF/dLؙqǗ[7鎧_ߺg@@hk?]׎yjFU&dq,ұ>=PQc֯<~`&ĕ.qk\)^[[۝R{ڍyn~W^t ʅמݽ(q?y1z9DU~=@=p2 ̎zU%Ǣs&lӂզΧYEk7jԲl7[E_@/'XE'Zלy *eI"T1 Y~{殸U;=QLBjMe[>V3U6MV6l+-w,V7{Vfjm3Sxsza384MUzű6 un%?ͬu+l:m5J0dCIfA+mjʫZtSgĨE %E4gͮ5sҋ =mu*<۲uc-8kzV!u-cf" ޖe$57zDxV~UdŽ48ۡz\hT6t%%+aӼR-lI m5>*;k]1h`;eYE mXTw[u-qf!C۸Iuk9[X[bV٠aAh8KLAwrqo"nC6lVMFv,M o0 quҞ<^ۼq#GF+ӛ!jOvZ/H6zu^u qϟ9zABVquzg޻zi!nE*!+oK $t _޿ 9˷XZF}ζ2VUNGmVfޟ>Zeg-^yWW\0*vь᭟d{/޺+{WԚzjٓ/W&NL/tvskE,H; Y3UHHM ["LYWf- PR*dUEzY1TAcԺiV1Q*dꋆZUV&-ȍ~4í.^VM;VSJT V^[Qv6VޘꝝTN߶}dy[K6A"Y&mKhWΖkU"V3̉M%l"ޖgQ8[}H{F8UccjJS\ےyldױ ٪94̦[B]= uΖDCGmݍնhy[1fR[EPۚXAm5m"=HʩfΛHqoKA.mI :B3=c2.t5zPdSN-xotR-lcagz}Mk8aC_ߌƶ9y3MgxhK,+!ƺߴn1g+nv~ox[5cǠXl9Buzņnh;dE.meG Kī.JߴNGJpT׭q\_z$ڒ 1j5x[̶G" &Uc5j-Iqc\pEV=Ŷ35*q`f!A nI8\R!fj%Zv[Zoo*n[גZqCrj9\a8j?ĉ Ju'd(ܪ˓^t)`UWZԗ"5neS[-$="*-z3+r[ls:WGn9QGo؃H&mI֓۸[FXEdSGm*[EN ,ӔjHSړ75*A͝74a=Gϟ'Ϝ?a޼fz?sΟSoA-eYՋVqӒ^ 4l4t8l-S:k"Fα"c^nMқ ; dYWL02Y[=)bJixq# O4;ӈngJt'owDZwW8'HC(S+9LI3=~@v,l,1iڛ%(ħAFMb#5VU]ᘫR鑧ud%8x.D ӈGyޔj㤇 _N(!PsJ JٺH4bɐG S_tJN)CL(iXX4DγP5!brV4FLԔVaݫh=u|ױ0HZI }(yA# 3?$b4d,!˾%)3U{֝04}ҵ_7L=E?7!88"č%,qЈHhO8FѻO9SJlZ4 M %Fs1%݀#-RzU)rBy^ZdV9cӠ)-+cɸ@/.G/ܺh%(06:%>)-XegOiՔhl%YHIY?rL50"{wjwh>nb$> B{O F 4e|$1J )a 3}2#sH{B8:ڷnۡ|훛ؗkZ_$(.ޚ4!s ma/rq -P>~cl7>?u>>)z߾g AǷgOvzEBUm8dU`6}̤}}R/$fvy*תi)zQРR=Vzj?Ifm+-ĶүiP M3jAk+qh Xا M3jݴyb[~PɷjQ'a)n|}1k3kG^=+Լ@Qhɵ.QimeXlj6{tVoӭҺI6B-=$ΖԠ{[v_~m9L޶-B-VA>`Fb ԓm|^*mW㽭fqPޖΖ:4C]m ;ْmgjjבV_u 6HPCxqݦQJ?nYZΖD=1<-iP\m+ΖʒmWV㽭X\}mؖfBh@LV8[jg m;z:ٲ6m|ViJU;66 jaqm):jЖ:YԜ-Izжi]ae[jR [TSvl5K =9ZD.j6X+k.2gqcmv4F=56bncz5*=o#^4jVqpk+ZB[1Fn[hޖQSne=}lekaw]=9ev04z戥UMoLR3㎐ m~߱6gzuf9D(*fղl7YE~ѩԫ 2JWW5V^d|[=3wE I瞨KJeӌXTz m%H=(&ۃ-Lj+4T MVfzUzLSb'1LYYhf*$bV!.l*ՃŽYey5m7RK5an-VmRuf^ MsaVWlO&=*`:[+=5C!AVm=C[RKO*5ْt/cwxg+qUHA1^PcCnv}gtgK`T-_iRTRWmZ3^/:ΖLԠ}㵼EEbV U 5j]g>]a(n1}(Fh={Qpj@8o˿8m Z09XEZH૩x[Nl[z |ڦ(SuS=۲SZͺ@=*4,myЖzBB>n %4bЖ3g-me9}Kߒs35R@FԍQTKKW9nGMRGgS[b| -=mG-j뢝';jm?ĵHB漾׻_:zv&@ 5SU?}ǵwLvJR-<~#!w|"u{7ӿZws7y#ߙպ7{PRsA•?Lv).8y_&L/>Pd#Gk?O*啳 (aʇ70{`6!w} Wc.]W[mB\0G_z/ B .] _r t qo?8o7`o]_Ǐ_WNYK7/~s5!Л=zS]e`">9.|'{~g}XOKq㗞ޡ_ ~W\w_y:b&޴Lڟ3/Va+ZP*Tz Q0UeԈ)¤YJdl[ڴT+'$DZMK[%Th+]= #^?3dBӔJ.1m%fi!ZV$=CjdVn$ :m \*+.4M*<^г[jer`qo+xpuJYz[ gNJ=Ə-ZVz[il󉎳5TZ3m#Gz;XWYqoۓPlMeZ9mѳmjKj孩yۡi5{7mdo+2܂JVy[N fgV;>cc[bi*5ά7 :4 nhkƨfۊ.mg+G=9P84 %yO$2KEoۯi&qm8=m;FJܦ~-+ZYm }!a894Φ(ar8lAusg[H,$D#3EˋǶjBpLzBI 0-)֔lӺ4A m$XM0/YK9=RI*AM'G= آ[<r-CV zRBZwWUżgU!u3=T;`36O淊Fą[ZBGM^`ZEvmk5FۨILpl#W(Zm@1lӢ[C=%j%\~ȳTw6oȑ;SZ %92^͑Rm4OmVW;*_L:92G39/`S:=;V BJu; h/#!۳a^Qպ$=칧hzG %B Wsw&T2YgZ;ĕ٬R^9{*V\Yy.Lp{?YO!%w _vW/za qO]~[R*ռ nޥ"<B;Gk[fg~'`R;/;+;9[{ͣ;oTw*l};tK%o$~`v],/ղ'w˝鷶 D޺+{Wjv&Ӌ/=ۇѫԔZe>xw(J`a&Jl6+k*ZBY\]U='X%dP'gg*o+ѤhQ\4ŶLV SCj=(y4VsՓ V, M Qwiml|ն33Pckw~ΓՓMߑB-WG> ѼZOJljj:%o˦o %8mWFCqƓKΖ_+Sm|r~5Eg:`FmKroiui=j[:my j0 6VK2Qh-A\%ZhW]9ƚl#2vo;3x5!岈UqBlE):35UtVqQgK,m?c@Ŭ"]2SUgMAvXdo;5Kj>nv z%u7ճ3jP ĭ΍-2➹~ͩ6O}fPڙȑz/}6,5ەӟ\f&^둊fwQ5;2L.o_~y_&f4U1WGZSeJr@U+?LVͣU_U[W^?ǩJoW{|$_qWO?*U&wT W_ PPKrpK'<=OWM67"uXg/޼sѯ _*8K§^P{s5UٹqǗ><?GϟúԲ_]i;7iuOq?zl'^V2:ߍ^(Sj/\V j**[G3 jߝUնtDV^x2*@PΣ^5˒\me4V!^*^Pa qjiF"nW\,jKʦ)X֋Z"rLqߝ33`zu]*iƭBRqAi+$:[}OmToAPcDeF5w9JO$Ζ@ EK'USt=ژ%T%żmƹmG V1me!Xos UH(J5%oKlYMc-vfmL*ޖ{ =Ζـhg4TOf}Vb[FR@VYb[NM@F6[ s8Nq%4]oK^;SS{ To;tԥl^YRBhDo)=f}Jm&VS4eгǶG|oL\cPtrh=BBC۸I=+2ezBpD jgKⴅ%I-a!u!a~Z-˺l4ٲ=[*-qzZB֞ZQc[W3pٝ._n=m>DqM=ߒlf^ zFb 6`: fYKhm˚%Q϶W3G,+m|˳fz[jY9>ْXZKhԅ[V~ezzP˚1oKRB6/$)Ր vn{z֔jP YP9+3T&Rk|'T2\t1oC+39o.|ĉv"@OhCfjzrY.,6HiNA%)?93/BdLY巁a|Z.oLO)ִHk{A:^IJoLړ|=Fc@\r6XJ|f=;w_1#J|H{ Jeݛ~%h:^$_xD-9˔d]ՠy0&ń'̢ոHZ|FFȌ!٨-%V)8$N4{Nnj%݄Rս]*AiӧsbONBt1Jg5LؙÞfJiӮ+:5;k*Ks3%TԾF-xURZdϋȬH)6ՕQc;חi:KǦQ78JiAz@dys ĭ<>U Sn\P i,OS"i@{&ϛHNVQ<]Ɛ!96\䊅yxU-)zG[/;yoDՒȖW*܃,dHߐ*6>K~ K ͏LzdJsiVs*-̒^!Cۏ$_LOi(~9J"D2cW"ۊm,9Ԉ&Yݟ|ғdV6TajhD#BTUtE4HPɁO_ LY&26?đeR"/t!D!ӹ2Д쥨52ao&ֲő15u 2'"~d!V.Hj3N厧=#nk$M1G$g?G0qy(czڐuHaCӚq̔Mga>xᤒ,>& ﹘/^5Z`I\3H2`GNM7N*Jwtr8bx+90Mä]B"{[׊/ Bq A A qRnB{s{7;MgN8zrOYjҦ̆Di +xFm0?zw#7RX#--Ol|'st<4(A9mOWJ-ͭљ=Y[!6e-r߶7.쓐5LITŶ`i}5ѬsB|7"=k6=WHk 4XB3ozFr T^!'1`n)ڠ$!n-}%J^S>M11ȿo!aQ}nŮR_-He|t}Gc;Em.nxC.pD<347'>8ߟ д-kTs*}k77_7b>vvJdܱ8s)q ā!BBd8F`7Re=2vG۞'MOXlGʤҤGlFe{:b-]C#9kf=6- sZmVH+ k&0m2wRRZ HQ #&F|L92񈵄5/br.X?-2ԡβ ˜Dyv5dkڎLyxSI?m0cMF~4!~# i>z۴&Fb'In$W|ኩZ.HI,IP r ?,Jʬȴ:Li*l`7?ir 0GRqf&oShT%WNTrœDmť{jۉg8ܜH#Lպ=KI.SNl~)Ɣ)9vӰXɐM.e<7YgM]Zj۹Foc6%t0[MOHi^np馑plܐ`緎Դ;mv`g$c:,%پ^,VIi*i%sC]uH| 춗zE~Omll*׍6kd;NVXwGKc}Vq-o;UUmjX4Ͳ2e7A- M3iJle}v5E6O[VNh?y>S<޶ʏ76O}o*YUMSZD\MyEAk=zU6"hyc)oX_7 ;[Υn6kv[kWfc4T_M3l/mưEkjybKqa[^GVOa&d`VZak*=Kdp3m_w˘EXFaMS®57=bcB{,?!ېv5y4#agm?76s,%V8n?ƒvK6 ~0fin jzki7[j@^uXފmP 69dV^47b^j7M>%T\7ۈUlcG?{?U¼Rɲ j"=3 ]VK6KvMsX2SFEHm5ʿm;O30(ިf$Oʱ͍ eDnwÕvCbtU=1Ul}M%;_lU''"X"Q" *B i ?b!3P'`\hu b8Ws57\̅=^z眮}Zg?kwOm,Vcb}Y[/䲤׹/z$X]f9yj,)UhCH; ^ K`-ͧgQֽ̃֒`cؤK8{w%d/H@P%]L#z,磗F.H-@G3/Y#oecX*H]Z]8,&=ܑ;$@}j`]]^ :X;zF[2{j{ңKdIhfڠƒUHeT70>( \|wTr,6ٗXRO˽,E-^%{,:IpPa]\KX-ƒ {{ThuQ.XdYrX]p^p-0˒@<-[L/ /#q oo%%T/o52\՝ҳ+W J`Z'`P+qhb2tzIEfK*w70NHcg+Z,7l";[_%iX%u/&魘BI(T*Z9(Isn7V#*Kod_ ~q0|{َ?v`r}dl<vl%N/{H0_/S{?=C]`r|9N-7JNg;Je:J~i5Ql\̏|G5³G~|%~u">/RWk>,/y\0\M0W\k+O:DX;Ͽ--nMk7[|$dɋ2kf݌Yɒ;~1Kdʒ7%&Ò_͒M]1K?%+e3ƒl͒5ٲ:.9ـYv$, f{NYDd&d V~{ٲ[܁sdlYbn '2Keـ}Xʒ벮^k[I䄌%,yblY"{'d=/s$,[[m$K_uʯN%; HSK\x1%SWٗi]+OK2R@(6ɫ&R"+sWU7qLp`0۹^JT}_Թ`IV~L0oN\lr=kٞz6|,3|=w.sG$Fɚ{M8J2?`lv4=6N'^}뿘.=/V7VYqx9Qn^~IT+Np}M0_Zq}5okK>Œq޿,y6cg v,yYͷ,Ǘܑk RnYrY"3NYr Y.oB,<)6GSdM,nVc$X2eu]K6%wb {HKΘ%B^,;ʘ%e˒s̒,9j%2GB,a5-)-dɉlYr4[VYT#Yv$9@|ZS `dLSKvr>edobnD[]ǔ%K|iKdŕX^q NqYU**HKW*SA>JTW6T ⼁'%xj DqDqb R < .^Iq^ b ĥc r$  %*Dq9` T *.]UPq*SA0JT$ĩ NxRAxj DqDqb R < .^Iq^b ĥc r$  d+ ,QAUX$?_ NǸ $TA Tv .'Aĥxj DqDqb R < .^Iq^qKT .= .KTDq#Aq^ nNDwDq9` *,QA\X;ĩ Nq)AT] " T8` H*SA,80+.A'c Rc *,QĥxRA\ ,Q5%*K%*A,QAHPA%*,,QA\X KTq*SA\V ⠥  Nq*F ․8O*O-RAxRA\< .5 1@,QA\j' < !b ĥc r$  q^ .,QA\%*Kt8ĩ .7 KWA$TA 2MqT < .SKTT%*K%*DE KTZIqO*DE KZM+%*Y2KT׼,QA%*,,QA\X KTq*SA\V ⠥  NqC0JT$ĩ NxRAxj DqDq b R < .^Iq^qKtDqDqX8%* *Dqs%C%*KTWa r!Nq*K 8Uq U8!%* T' <  L+yq@v_7'YQ] ]q.QA\N6yڵKiݱ'/Λ_\?4rc]3_T\w|ql욀 U \ĥ;Jn'1Jut<()x/$ceD |#BO< ݧy뭿뿿[pA\]ım5 cJI@U kW) #%f̒ǒ`Y@3[F*#7`ɥܲz3Df"H%@IP+lҼu*¸=(43yS3?n7>Ǎ83gr7y/X| U ~,+ + a8' cgӬ@fqA2 .Qr;Q&^DFI~%A+ b`b-xa8D}OVҏqivM@ʷU U b^+%+ Ks'Vq eW\+"<@G<1K"KN%SgȒ^nq+ɒ ΋\K%r˒tϒ#Mv#Mh'AȒ5ٲĵK&R);\%䠌%5AyaAϑ,K%| eDh˘%r[lYr9XrY% N`dZ Xr"[͖%% .C .;lhkM1KR*JwVj}jC?(c ‚8 u)Kؒ+u r;vrab/t*S8u?dt͋#_p_?X/2H, J+2*K:ccAЬ xO ˖%%Z%/ Y.ɘ%&xdjdȖ%Geie ~djf,a;#SbI@e)fSΝ{RMToؑ|ȑ#S#S#RK#S%#ScJ+2*Q<82uhԽժd 82Ҳ&``q8Vq .e³l@x6FxRV>`s8Vq@x6ZqivM@* .sn %%k:Je:JLd yyn6@7"Z8IQq ΟA\]ım5 cJI@U kW) %f̒ǒ`Y@3[F*\K%r˒tϒ#Mv#Mh'AȒ5ٲĵK&R);\%䠌%UA?q/Wb#y_?cO^W]\ d ^\wu?y2| y"v7!ٴjx2} yMVA\Z!8oBq/Sq鍒I5y%2%O&2Jj^<<7"b!k'E%,K 8o*+y"bV+57! z,Qt|3wF4V>޺ص2`+]SzZ7Y](>l_ ULbaI`ኋ}MZydɿ-Q#lm(KJ,y,e< eId,m<+M$1K~f=%{5%;e ?SZ%G,a/`֜䁦dT>Y,%AޅfCĪL 1<4wͶgĒ Lk[9&`h,9exdWrIvQmЎxřF$g#Ӿ)igIV%94J |Mwa1Kɗ[$ViLYy5of˒,MVk7eARNL&d>!i!Iы2 Nq*Ӥ8M*ӤIqg1XfMI2Z+ѳvv.,0$7c!zI4,5iDqI b͓)7L}SSxSHwg>RZ7X/KiF$5bU*~h&F~sGW-K] lfͳŕ4ٺʴ$K?qfKs$$Zɶk$fb yčĤk`qIkp Nq*Ӥ8M*ӤIq!.F_0kL,@ΞeKZZ8 aȲ%6]fn決 R`7X&2vd6Mcd7s26/oX537&G\1,Ĭ 6JLZI}IV/cNMatW&_sEW٘6Oy޺;52fIKӰkߘ 2ȢWc%|+@tD:- 3lbDo 9blWKgR]IV')9"6_\&%lI8WJsK͕6dVf Z΀-][%e",-5i\Իu5 C,1ychpVa ~,iDK6mq;]K, dfI=EYWh]aIkKׂ,%3Mb%۰;JNR LuZ,9$dz]b;]s$[5FqKt ZYe |{A.ZL.S;`xkqPqKJM%U}\x2r|wkJ|pé8STkH ¶I!o7|/GBNja;u3 {w6b6J=;]gIaכ$Ava!u{.#;~]ӎF {Zi*5+aYG*ۻ?a;u̝qL zx0ٵvhL&6@_h"5-O`ﴝ9vQ+uc.s*6Y#\xT=DzV'؆H 'MjeT}K}_?zֶɶl _dg/Z=k܀3Fg\II @ `%TqxOgͮ3@ b7oϣd̒ŽTȒTwQ,Fړ,ِ KH(KqXmxRYYrwS= %<*bI!y W|,1eI1 PJEb䀐%2  .iKLtId̆E{҇;ӓIdQXeؓ3$gj%K9Kt:Z9y(%[fɎd!dnj@3hOz| U7^Nwa[6졲=iKK YdI:LR}-gKLԞ$dɃ1GDKH,'Y2]IV`6-sz~lv2n[p΢,  ˁ_6{]2 o8 Rr/$e77O 5q}?龟矻:W)A1DN_l0"PÅa%48$7;}$N6wGN~!qLm4|vv.΅|TaN>وs#wx?!ݹ#Tؑb.? 5\[u{{wESz/O!k⋰h,'icC`H4k'7t2 9kδ\K-/l!!QС[<S[2R! 鋩KJ6߼qT |.&RyDnDi;"95̲GD!x,10gI!KB@9s'K K%2g)I97IY*S,YmKL,p%/fMVlo`CO|,teI}5aJWD^w#JWޗinq5%!_2?1!XWCj,]+mN`# H!X!>j kw2N:h6@9]+x6x"\wuuF^ ;h]tyLT9ͲZA X|*C-Wx$X ` 1DQ!R2 <KKJq%CiDRRRcI|B YJp+PjD@=֍T!D$"D yZ:uL.wu!ز&ր7W.Dֺq-CD0;I5u.Wf5 <<[1jN,O LLͫX*lUWToXb"zO@rƼ,1H 7$xXA4%l8,e RnэmnA+ܺq7KЭ%`eh,&M"[B(76%Yr!q }尟%`mNY#Y%5@.?Kྂ`Ex, W%57nU<d, G`Ij)KYbBąU%a P+^XU@.q 2+#-d n x5pgAGl9DN,YM4%lf]Bd=#P@7^ˈa,GdU}^,%u,Y%jeɒ22yvIjVqk.Dfơ8X+K,1YK?%@.KBg#K:Ij,q!ju3KNe <'XmA͐Z@l. p;ݷmY( pH87?Luw3 Ns :maYɁo oN|[T";=<{jj_ȼ_ qW']nqҥHiFV+<( uU|vz#k9xwR;=wC5yn?ETwR$ݽ$%SwUV OO[jNNjgWoWE;R'Vi}U1Eh# ;t!N[&j$ۑ_\݊ Ȑ?izz~̵CӶe:l4o{]y޻%DΉ,_Ϡ onSKAejDcD#PBc\@҉npőB,6N'3yk@x0䫰y~`ѲkEm}n,)Df ;Z`, m^fBQX2I`qՁ%Z 4Fʊx,N5Dx|ZX, Iͫ'`"ĈY.㵔%EtILdaH%,.`0!Y2dX"q!K0{Y,1Y_˸L]eI%bIǒɒPv1̒qXG%n'uuwEF5™.&Xm8K,)zT]ިGΟ${b}z{DGƜoG9q7{(y\H )LУ?wwf,qA%'XӛH,wU\6$~>RN)_)ϣo;],JIl?qj \%|p'.rS@Nob[]{{pI QAP#V[O$'}[߃oOI&Z~>,FyvhXS]Y5j6xˤ΅:BGm.Rsu6YO/b u.D9 6w'0pWpOn.:; ꠟ^4ވ""5¡3`#$},ap{B`qg: 1`)P]r٨s!~xڏZ!%DO/QĮE!FW'EbIfɊ%Թ$KC!,'e$$KP]K6DbI 64%3(ys'B{>(ΒZa%H,Œ3fI`v_`IZ)ʒnKY XBSKLcX%Hd@\DK!X2;^Gx,BeR'Pv %)ݑ.BRΒX~+ G}$590k%x,vdE,!$!i~B=.܂8ʊYWEn?iՍ9td|#7|[x~Bq"44}mycN#{Q;{Q"'SJq,JF}J8JH)Y%.SB#NZ]pS@Nԥ= ٍg^"vz^;gB8JIʉ-VɳO-VII9u,/qkXލu/Q*>~ԃ})Es C|vC^~Wu1uB5QWm\ʸGpUwci pef}h>mC`@x=Lp%…CnTN@ 2pCnRBRZp$pC-{7ׅŝp?SBF(Qq;Nu#,0wLr/ECe  2I%&K"6q j6(jMRe ~!v$j%J"(5,y\*|<bIAȒ@$NfɃd` xB=+XM,yK,Y>!dnK*d`cOX]JvɒvCu(KY90{w !X]ʧAߥ O`IJнxwP eI`ɆTYbhO]Kd,CMDYbwcoŒBtlVrk_d%iDg&Q_I%gD}%uiK,%/ +q,YO(NbIej'A d!Oz ɒ~K Y &Xħᕅ Yhw,w;]H^Y?.^R^FZ3p(X9,>+|e ) P:; wߓJhn O%Vy[`g G1CYrFdxY"K M%Iibĝ) ],1䈅`]DYqV!X.wBeɒ@_b WB;^^׬*a *)b2@^@nKHBYd;Kv 2I;v ʒBvv@PG>g L j^DG\b H~ x.!Xz%8K.Τ%Ы<,eZKxY;^`9DK/!K%ZYO")5'8dQKP:Ʀz;xB"dSp^'bNgh;Q,!Y5WXA!=f zp=| No- ⨘3vXXwaсoRR Rڏ ( ;u}-Y^8b;j{5֩Z!nJ:%l9 ^eӵ@5*#:wNZ%vzYm~ (I9*%)X$&= a@w\'ϵ'>  ϵ' 6"&o=~SPJĹSU V2KD)9a)XKdq.D4B!r2[ujZ;B3k8b %Nuc"ձrBDOlawMUx`d-b$C*h!غő:>K~ЄJ<K(H}.Ũ:) U+$LtN=Kbl44E` !)<FF ΅ YB`4Ka?KI%reȷ>Lo&a!؇]JRa"b;%1.K! 6wd@ڤBaI9qi%Cid,^+K.$ܺQYVV+ :~]\h8, ~ pXƨm]`*.@ko ,ON%`z.9ԃ^%%pX`IɒZL@b aA[$zb Qj{5]$YAh,1,VfɸkVoVjK֩:ϴ|g9?K]Xb,AjeeDhŒ>8%0o%m%kgկ,>ԭ4Qn^(%F[bIb$ oO[N8=^M:`['YRaK %r$K'Dmig8,Y8S6?Pw> $1 vIqs) qWqe;qY&uwE;gRۤQ]%vz[paNoD&vz6#nوwG8B{1~;o7H'>֮?pI9)&bY UNoFV/&<%6"&,q@gnG`! ߬ǚ*~h?{ywLLޯGllGP/76-{?tD'w*8 Y}< ?ҲZ =}06ՊCHq4673%IRr\w WTz\+ĹHe@5157z,߱{2M8q)jcVIiwLK0[-8"iE˒\<ܗf  59ȒYn,!Z!6Z1b<,ZĒĒBd}ǡZe@i%&"K X2N=P#X4,Xf`mdV(CH$$ K?0.% ad֪!X9%B~`Ive`g]!Xhs%"TGȕ fn .Յ=;h DFH-V I9*!)?EmLd_J?hg)}{j7"- ?x!X0x`1!lT||aY\dqUWD( 1G'x E\N=|~67ÓۋT+5ȧ`@ӿV 6N{r\l!Oc^DtM+` Ii(kfVӋi!Xk0U+nހI%3*Kc\5],,@>+eKF0>9ĒkSd0N/J%.^dZab K%X9ǁiKJ?L%vn0k"?KX*`!Ke, ",`ghYecDK³C%Z氄O,# ,jK ދg0YB"\,`"$Ka2,1NR[S, :`YĒKjhK2v ` t$, 9 ,@Y%4YR}X%$ıCy7r$q3ϿFdǓ;^Sb)Es 8S²Imj aXF p/vU˄#lc m9WwuǾ ݙ ` m*Q{y.-'ٺl՟<WR:kE/1V*qgV8RX۽/<.rG¾ T`Wzۏ `ymVw7޽d~2 Vȧ` T+Cd_AaT,a嫗Cʯw}084iUfZiU~tWj@ք@_MZi Mʇ㾮k˦np.AʙbPU&f9:Fn#Zy ـv۾j!džV}ÓR6/$PXfB%XTXV;Fx u΅KWžh{LK,9y5v *, w1Oϒ6%Tq5CjZf )t Bf%./{c,8|wԅ*KZn%3͒ a,yaSl-Lk-h%֪c5k:{%KazZh9˺jW]s%`#Ybߡm$D]%| ng KWcIx52+,8%%qh@bOaRa;hvv%o,),-.퐗%X_Zyh~̛ӳq$X(CW75`K,=5J,+Btl@x%ny;p"&]8%.%,=1YvɣLd1nNZ .!Ywswz{H?qx̹6_w&#2sK2S*Kt1ER1 [jqXnݩa 39ISpqERضXVl bE2i2gjsǞZ)"Bf>mE0 \!]<]wc` ϓMY}FqVB]wzlȶXEקlC }%c}PM"]+zXCQ7(_X%@bd!(':!kn&lGKFX:vfD7U+ZYwvVv`]s5,ـd8U+<,jehGRc.Kc,My^%&O,L6!%<%䎳dY+>f5%,/Y Ē(K#3C燰D,Dx, J,YdÒng%)K`fP ` T+$Kk3Y//,RtqX>HE/<,%ٰ$]b쑱Ax\g:,J)*ejk$n,V+R˒mM-`7cItcV [mCwަYH*,Aҙd3H=#!]q` ^YR%3dI)]'Βd_9bIO;ZYwma) G!,%[L%$,(,YD%q׺d΋ Kn#Pu-g0F}\ ␘s?sq%L8R>? <1u0eZзKqs)H+4icw$[y'p$L@=[I$C[42,0z5d4L{L?n%iHf &}j%:`@׌,g? 21#0ƀJ]FȋʖhTZ@6pa|-fkk$IV& lS$ծ)J P>dgdT4u͌k$k=kMzBq5Kk43iyzd 4ijZA&M*MfH'pos$ei,nN1ÈW`TIKb}E\+qwn$~ .aH N2`bY+"XrR_GIu&(ʆ%!mIwΚDiֈ;˪EY{$1ff̨VL6"|Gf%-fP,1 X#hbGB&Da"Z:iK u XYl3\@JK/sʒ.s/!NB&'tmX"J&O7U#JU8iRA&iҤ8MbA,1L6 J..AC&*aF`J49ַ79jRr|YrY+JBW+jo7Ϭ65n;qfu6KřBִ@4|FL4`fɰV\4AlYd#(]%wXkϐq{w$4.v42e 2M6 7^{li|(aKܮ=n&kfd*Yt'&`ItV`dܟ!QvV[}]{62G.e}La12$#/iJD1=qhxM@N,v+%1#D[V儰_B~ aI:&=q^vBIn:뼜KR{Mv^v6|'l;)vNy%R\6::Wq5omTcbUTXjrJM^W>[}^^ysw*?~SSZwGJ_>sJqC'WWmμ'J۷aк3z9{3zRjrIF(VG)c0p56&@p*sx3]z](} 5k BR wZrtT%pUky}%J tn״Ѐ|`v4B;輪qY:Э@XnyGfS+Yc1cgZvMdvl:zxݪ`1`g`} tFXttlpg(,7րJmv,Vݱzqv@@RiBly::jK OtL%,f%V9 Kj $8W&%j<%V׬ϯ`XJ{bI}@XRk5-n-k]fY1fIx~xWdIgϚN2֒x9dy,XbUfxr.apA]zGKֿ]lp kÚG+GQΪ^Uʡ] ؔfp ή5YeN%a%lolT$Y"$lÄ5}nGX28 -tyH&?K[ HF%5 aԿl;x;,Y?X6J`vH{Tm23YQ[p>T&:0eI% -sMDžnC?%Z+Ys5+p. h׬J«^.0hzewsp,%:*fP}άWjtV  0KZSjx A-6K*:`;(A/`kBN:cpJ[)` iYOݩYmKl / ";:uM9$7!7 dh!lоm[[zȠ݂?doɲ;%<] 9zm.8NVn\Vqs"(zz}Jco/ZcvD_yzSŽ^Gi;[W'ox{U\Z.~pzٗ94 ʭ]k7^=zM0kŹʻv_k~Ew޳_vb|CcNʭy֫Zbl ok?M}uz`]Con&9Ž^㗭׹N2ZQa_?l=v`gep~E[W{do)~o%7fW֘kSzb7lzCW.nmػv:bg/sx~uuoV4swKUtc#x8c^;칣X5o^P_f}^^FlwjF]sjk7*YYFGGY2ڪ}C֯Z;z=ateΖ=Y~j=Q_`;xUJmݹiz3+׃.8=~ٍWjQIʗȖ.n[2G]sz5+ζ'Y+{tl]Jb͚Ӌ%'eM%KYs.m'FvfI" }&uWWo؋v+˂%Wr'Y8K%]bV <93vIsr<ɰ̹oq iBXR[Xy= GBT6yKB+Q04z'kΰL?%YHdŒMܯSK8Y2:%uWB'>d?KV%V8,yu$8vYRM(hݝY5f5Ȓ)m{Fp˒%#!Ē&KY2%uD==Uh:`iNm^Axk|ţmF{Jmyt>KBNi%Sz$tt?a?^NSrO|f[ܭۯXvV֯NC_O}}#S}ǧxV~"/+W.ˊ;w?e[>]&N#<.+׬lWEY++ovͿ~;2Oβ| |M=Գ^9sD ̻s#S}xҞ-׬lom9^q9{sa٩wdx5o?-A^q?t۲}Z^>cxo_JV+Y[W +onn|(x92}X6Z[dy@6`/IlAgO3v7+ba\t˄dlZY /jEȒtY2)[,]{=鲤CȒ3MɒdIwB,0,q[:GHSY4,٭,˖%]rXhlH%NkXh,Tdϒ⯥ij0K4KuHTldc,Y*Kj^>׏9[wXL%ɰlYZ.Y"^%ހu|%$uJ+K\J%ka88ߕS߽ p^K&?eh1f\^*$,NLmkͩ?O<ՙ3_u8Gt'rZeUGߝs>O)\Guߊj wXL[+_3}WDqk#W db>pqx;;۳zW|G^o=7X:8y~&*΂{??sk<,nq7Ӈ}Kdp?v'y®3ZL{raQqG&Mr V <yAQq'Ɍs=|nY0][rV&7;]uK!մK59Qµv?k;30}al:$7ĩ-d vy"')KbɇZg^%E۹טI)KzeDB,ab$3$,y"[̮.5KPcYIʒIY)Kv)Kig{^\ .ʃAIh((> )EES-d!!ЅוJ4QbJW}K[j馺 t5O~̙֑rOsdYnFn'f A7e2* P!@fߢez{$7;FC$<ާ (R~xJ# PP `x]m.B~+LoBwHJc-. 4aIa{k'yJ3vC8E W4!!wF4[QVGXЍdp1q%th\u"w‚j#MAՌ҆?yZ rO3 f_W֑MiWTsfXK1΀KXܛg#K?Kh\K3^&+,9, TlkT,!@XҺKFXF.TX"s'M2X$*h#\Ò`aI%SKDx X XB4s%SK열#dNCՁ%p2$bIX#b藴xkD,%6M,qKnph')8{YJo?ɝ9fvij/wg7jjNJռ[S'3 =lL :pFfz;Y"%Kqm!\Y]#ez[t/݄ra>qowui\Y7FMLow m#B t)Ļ~"*2ӛYn>l cBB:d@S6~\7c"]mt 5C!^UE46q$tȌ R{ok ~nS\D.wX~ManӸ `- a n簺Vz@Ia[nq;+s0(`ڨ:}psgƕgV p(>X0!#ܺ7B)劈%Xia"@YHx*\XHPd7’Xbɤ$ z%/,b'KhXGĒ.0[;|Œ^"L%*!X]P,m% KWXSN@#&U%; Kld/%+!l%țT,YKAW%iH~ KjN:{D,5XK<6Xc,1q-i?&m?*!q䴥Ӣ7QB( {v9B @U{568]9Lo'R굺54F%C\Fs8?qfz7NBhWOdg06\?`}t"ʰUT󲚒X=@=m!jH!O@~0et_a ֧؋Bf94wd@p1k ysIjD@ fr~pulZlܔJ-`I!z m^nݢ 9WЪ;`UZ&W^ Gh\Y WzB C`$%i\wKufv b`o4иrMX ܦzl %IW|ƒ80؞i9C,i,KK V+-h,Kˊ%4Z,06BU"Lc%hnKi\lO`Ղ%;bKK:KFhKdv:IXbA) K|ƒiĨ XH0#J b z#!SX%bɒXX&E.+T_w2'\QXb%A"$a/Y % lJ3 v6, W_Mn-\\?He,)HíW?HjGp'=y*A. 22[&1 P: @U}tʷԷ55YvZMo_,N@L: _}#e {NNLyȝʕh-ro6i\wBr#+q0$#vsё x^J )_NvH"wmhݯ؀816})zEb0}GoB ={6 .KG#bhl-DFVGJfƕVlbZ@ۯ &'B\ 7`ɥVHBs% ف9XNDj.XZiِ&q%60ut$ɂmF&Hĕ~<6X 3'bI+4W@JW,,%bIS%1g4r^a 7tV>, KՁ%s%T$hK%şXق6X%H!ɱ+X@QX"` q#a_ۜq/ TazŒbI’ W$ntrg4t%H e|YgÏjfh reF\I%/uW88!'w| pH a ַ!Ngq/Ӵ(ER֯ R ivdC籫w2lFS%J :[bƕˤLo0=t{CƪiR 5F.[vx&m\& ~/EԇCD7rfJ5yY:dÿK3)[h" wÕ%J\Dš': 9=xD#ߍ {jk[fQq88A-f 3T;hƒTѸ2?RuےĒ1s@Jb}1~'' W$^W+Kb[Zkm@ %^aZ smX6X*t6?K2 |cgoӆIŒ9wd;Y%h%a4ˊ%_ q %_KzKVŒW.`I 6ͯ6_LĒ1DAZh!HXSH`IsU`Id% 6;$AKy IRUbN^LŒI%ahDK[XT,Y~Z>c,%CDx%kX6X%X %KL7Ъ90W+,1Ƣ4,qaw9/aA) K=v9C;}~-.^V)7 Y@L߅O2˽ܥl{4[!\i\K_ɽI>4% amv\ 'tf/:ɯwU}5>lq&%tR4r{ȓ)j_1H}b_4r˕W'X~[bAtAޝvÜ?ϕ5Tdz\'G+=^seA3M5'3nI+M;h}٘Sؑq%b[ ?@5GpAjƣ4 6ڊOjf4[|5C%`r %3627;zK$LA,zքXFĒ KFXPUX"i +487X#;`\$b quW>,RͲa/*$yWKDDaX2IS͎&4WK;q +頰V~"3Ly%f~FXrM u%4%^+Ձ%*u㗘nfҡlqK`I/R.q]@8O_|=}J "n!΍0a* +6K{7">԰y/I"|>Xd\)[SZP;!B{g{4ƕſ%>kL#0)['.a7E7 mtz/ ZR> d 耸0ui}NK}Nk]@gT&+A1>C=Uş Wr4!${zݻ\ĕ$?4RpA5\QA/E5,Q(^5R\Wi3B4oj#1jfki|U(Y.u CsJǒX b$% xa a]Dq K0%PXrgpRX2WiXR(|ҧ4WX)1ؒ2EQ\//L=,A%:1#%4`$|~A a%KbJb EO>c J/%4,|ƒ4+U%װCE,_y%/o$$AfOQ,Ad6 KfKr~I~x Čq%2b ~S , d/0~1zk~lۼW IН_hI\"n<~-HHx׎$ lIk P$%uKNӸO r߻9hqe)@R8+ \iq ЯVLb Qf&iB_)4Wqän ph\\,ۃK4rmcIÏigX")XQ̓VxKI%13%\NFIŒ q ]I!iXGX"keeΔCC,jέӦIdDt4%ǒKl~} 4b/|#a*H&KD$>j` &8Aʀ prbɎXtK:iKĒ^"\"=/Ϡڦt%d&0^62k9¬cAR6FplOFgpy h|⪽1q?jj~s_{kqqH\ .P Ӭ.ѷ\hi\Z&q8‘ç۫!̺k$IƓ돆\ p~RmHp7D H?>#;FЇkY|8vQ&?/SRK4<.^m?%0I`/T̐_% J@rѰW40 Ku4/C&"W$R+upң=\U7u`5\DF5 F / " j8]IJ` 2H,KxH\m%S5 NJ9X/T\cIX,#5e8,Hl /A+)9߰9’ffc4,sK4T.Ĉ~ɴX䲔v%5Lo;k0f7sD`[|Ŀ Gz*XZ>hsO" _G*rO5%k,*4*_>_\F3Wp'j4xejz03涹|'g/ⵃɑlNK|mBjB\kG(_|Jpce*_a\k6m#\冀5-XdTϝ\J'2Buru\₶Q(R;^ #3kG`w*3kjTU`XR0*HaIcNQXrSG$?I%Z4 1 HQ>cɁ9K %`IsAf{a:ꛓ Yvb 3A1юOX2АاcIW*KFZX2/pKb_gJ%D,)8R~%GF$ވaIf6WڷI6>`Ii6_˄%(d<ܝ_bO=,pgMc$_$7D%2Kb:,b.`nN >_W_'o72(ŕ|KXRmF%t,nJW2C+XmJj/{\M)Kdv@!,p:$Kl \qk2[z 1qy̦Kξg|wF~׎_q♳S-}F|? 8O* N8UTbwBTrVwiMJU *^F U)լRKU9 KWL՗3\7TSlUMJ++0OLfEc~S*ҕOFhL}@)[&K`Cpcr~W%q$fN3+ܪ8 TQTQEĩ cַj}fm>c_\1]DW"Ieѻ3}1:Lf;J a̙N =pQW(Nj?1ULf1CS+2%%>/fya MW<|\&fq(,;8]8ݷ>'Il϶lT2XUM @byW;6I\Oc> ,wV%1 #3)Gwby ޸AHT@** N** NR@|4&l>1 0_01deB&q$UWK쵦ݰ&a;q'+ Y;?0;R`\rykR5M_ = MGؗn,i4KPXb%4S14#{Æwy9m$t yn%* ֍[ce28}NkXrլ8,q9D,#+EZ>{kns0?Xm8\<ʴ*Eh';)2@^f0,Kټ01k&/c<~3|̪ Sq* Nd/["/_~ KXR/M5I' ^3#wV}~uBE.n;y EnB/4W޾vߔ+BE:͕ *gռ_=+U&.o*ڔܱPoWU9+;nǯ`>g B¯WLa VAvmIF6y,~M=}l\^m*IBWO]%E(O*܃mXr)wBP(tK35͡ϝ}I4 k?g<D rOrqmt̮%:0;!_Q8xGȀ쯧x\ + jk3f!p+>AL(E 9S 2h/f9'XWBͭ&\ e`-%cs*+/,|En9QXtwD d:z F3ZgCMӎ<(cF8!t 2TT5υt,a=!!A5i°r%o֪iƕPV+rW`\fMA5XB5X}cHiXl Lj,Bg!Oݔ63մC,16_?AXa+XRT d gRXRD,n%pDc%b%6je0%!+"tŃx4HX"p-ċ(FpXit,11X4+% KT`+KB_bQĦ@ȋXiXIĒc.aIYb/xXHŒ KDo$ÒRᅌN/}Xb`y4Œ:%gOob`4Z%<@nd5]A5C%X3R, `Ig'n+d%8XB_YaIg@H0WMC7"bnYK’Ro+CL6B!^bjbѺD7XA:qa.,"Κ>oJbIgH,Nm=JAB}Mׁ[{\pc \FBniP 64biEVMVї֯x-6:g;~s[-SH2Nep>)gi9+Y[f =~d˵kkoAxbo}~7WxԵFy5Prw_f;@ݽ=|g޻k˧޻'3oAgxGx"I&SbzM?^dB]W]ח+#sPOa{\t Kw_[N"_Ar;E\^ugd[\U$wΣAȱ8hoQg)"<[u~h,إSPbvҿ_~Fӥ_ ~߃'o, ff'pcPn3 "W^=U7liϕ;8vDF@/զPa1*k 4gS@k¯fHo6&7rg47_P]LKZhGp2\?\Ce}}6id7+u0[8 q?9 и2*C4LT3O#CuK"f[9$bdWQ\;9FBĕYp`;:h\!bI%M$ T[,܈XSXKfŒ(I5cEveb@KZ5Œ%X2.-:Œ搯r$WZ=Œ^aXs%K>cIfK*/KEJιƒM5[ D,!n$`ĭMi*$i~IwEbɎҸR%XBܔn=T5[%piAUMc <#7)`,nrw K(vEpP^*~㧔W9 ze/5~kjgFz$Dacn Yopք8e\!E2EL-)C\ yP<6$34r0{54\x6`B)P2W"H^k oƕ%,9xmk+]HrJi\AfTv,N};9N3$I5#aLyl:i9LSM*4< l"i%=k"f%Q_Љ{aIs`I[+^cti\a’Vd *D /I^[Ab X ’cIBD %n$L"X"wTȕ*%b4܊~XͶиRXP%X:X"|}ہܔ !7i\I҆IŒ%9d %ºcɕ,Mpm+SK.ܰ9q;p sJ@ܼԙ-\ZGT@\]55R-ꄔmȌmua2nz rw #ͨ\yyJ&PÒ{; #Ȱ>BEfzF~&3r6q[02~ ?Bfz۸)L*ͦB'Dr}C3L7Pz0K➤_+hx@[/ċȋϟzɕ8_hc3lLK|sh­|侑*y3@"'\ `] cCW0hi db&x{yGUMB8`'$r ŞC" a-^.%&c$/cmf`I;ܸNp!cɈ,sKX@piQ_D6R,KŒ,WK;8EbKz $XD’du`I{$\~I}9Qd:v%a*tk,%T$+PK_,QX"BŒ9 xdd"! `UeӴDKBb+$!`(6Vsw$]KJ^ _'m-ϧ>qG_(q7dHaT@\T6C\ǯ>`Hbap)":yNcR+anS4rK'~'qp"sԓ"Uě!B%L) HGvq>'Yw֕a6AWFB;rݼ/;LDS(+`c*9 $v Єđp4\뛢0M5ۚi\H\YH=o] a)LJx6̝^Uƕ i>jv/%~aj,r%NaWZ$RXBTs%T;V KZ`XX`ƒvRd_, bI?`MWV`WNs K%>cI2D 7IK&=ƒ`X\%_B$YV,P,ɸ̙/ѱ}Mw:$VXFBou`I~X T%Xy$M^mJ" W˜ +D, ;D@<iYھkI9u$~ߟj=_SyF aĖ-cCj'LouHI>(lL |)]w+Thu ny Mnĕ%W!ezRɥ`'V [\3G*ܴͮJj^ q MhWl%>чk61 |y="{H6"pMc Wn JcS 7"^3^ c\^xZr{EQȧ. D+Q1[b*}Om \ c/Յh\E WF3H]i3hՄ" Hr%Wp^TȤօ"hr `r$`XGx!m…p%VeUǒiKH,K&K{}`I.K.Z< X`I_,9XvKKKi~\1Xr_,K L.RX2CÒKXrVz/=KnӸ/䒿X_3DǫK:KhXT%X+L 4KT;ӕ97  Z5ۻ3i`gfXnuzzy4A;8?`SyR@{3[0.H1sxa ʕoojjʝ?džTHlwomt Hj ̛ڣ (i"'$U9Y; )⺰!"6i6T-xߣC,)H#7Մifhݞh>ϓ} M,H_goq#.6 i^AN7a .iO~:`Mlt\FEsC5[i&hBXhG9#օ>5ÌwwEW4\Bfx Gh2ߙ`K~ci݂V&vqS,qTPM* ߎ[aI$yז9T,h>cKʉ%ShլtkbId__2y\T'*’<0/IV_x]Xr;F9zTWZhBbI+W}ƒX$꫓ױ/4Ts|Ղ%;4L%ErXr=bI'اEӁO}XYn M泅Ԍܰy_@y"WgηPa0Ԫa* *WN2=D4l蘪I&x ~oi\i|MʅVIʵeWim-p. nm$ C@ Dbgi~r)ě*M.61J}!j.,ːE~a?hfx ~G Ѽ}8} /o6 qU5^r%%/$Qiq\Ϡ%'WXrBxMI:sG+߃K|0` :r^T5>sX\$Vͅqy<I53D]iU5}4JT&=̾~W$ǒ m3 _Xr%4] 6@ĒǒXUX=ӄp)@2Kz͝p%}ÒFŒΊK"%W藬%r䀈%c` `j,iGߖ`YnKfK¾bxX+L/Pd%$,i, s%~I;M5! 6 wDse6TXިXte8}$76l19%(4r3uRB:3yZ@xŇ/ 5M@:R2eF[$rTv\^ҸyKz4Ғb&zs>OѤw9Іq_~c,M0: ,+?W'wCr.Brh_.Pq~skk~-oG_Kn RK;O XRw7xׄ;Ksxr{;L@_{TA+M%V\ G2n "fjBӌ^ !2JJƕ0.fhƕ$+xjT,IĒNH2b KM%,aIx%} K 8Ja P>"4Ѱ6zBmÒKh;DS^_ _,Y bɴX_CĒӒ\3 ;!L"+^Ӹ2A#O#w&7wiBxwl8L23홽/ӺM $M@ |} q2lkYbzMtc9l_Yn7 ʠ, Vtd VsIV<Ö'T/ o.5 a!Z@O.LRIid;vиohmo_Kmit4+}^o9ǿ*~;^ `_ԺdΧYqgy%ja $X_N,&KX2g,2r`M5iXp,a KU◴ϰÒ۴şXtKh$`I4%D,ɐ vOTs* K2D$7VM+Q7Db#AK㜝`!7|ƒ)W&hNY9ÒɄow6yƒ4K|۔v%CbɠX_BŒۜ:&$^}JKK4,!%W׏%N+&̹12S,Q #+>yd!\~|˯0o]erߘyO7a\I]#q= pʿvy&ӗ {gT g|O_ T$2-}vWB@\HyD"bӇ Ht>*-CܐD>sU!-ze߉äqI!s@|rq2@O.x~V`3ϕ! \W8Gmuؐ\VCO¿3z_x3&s3ϕ)WnoI8|v\e+ > ߙk#nm 9NI ߙWMCo1`ứɓ%/L{%03C8]KA40?2XD4MT,q&+en*FQ~cEi%%?Cwġ&]K4ƩU% $`I7,ѧ1_tT%XK"U%D, !dIEmXjrKz2=AS*K.c&eNl\KhB .s*K =y,ḲNdaɬ01E^5~ |g'~7SIpEKS܍ `bhr LتffU/sps?8_{F1.>+SU1{dn鄔dd}Rۤ{Lz{4{CsE.MC4roN `)x#B X_ゞ7T}yAb{4"C}"BY^"@OZIi!jghG!a8KN]@q#sfnG?MdH\k%HD.ӥŹ]D^ Lhi? WҤ /TsۅdP+"qoS iܼ qi+ $b&yFfC4KtD.ItJjʨ `X2&Mc2288ONʕ*QDh/%F\K$Jc~1E3XB@n$n]ou$> 7CޖNvsu_"!]ݛnQ|(~ޓWPImmj|L"q.]?k* vH[J}K b}ppFFRGOr$V'O_ %;k?|Iae{^Gdox,Oޡkױ)\+|(zDN~b ?J-\վq tD{fi+\])A'ץ B%5r{VbD/\  7w]Q Ռv-Wxm9WGV4TG +jߴWBѩ| бm;-+\Ngohfac*7loO^H 6>!3k.9Q3T3њn~-JJ@`iXIŒ5Bs[5<; ӊ$85_j'V"Ld 9,K r%D, X[ayc r\H`I`g~񗟎.hxz>cNiXX2GÒ p\I*K2[XD|-%%TSK7QĒ3$oA%gTK~XK&eTXK~ s/Ò|KseC+αd:Gj,ьm|Źc'T,!l`I'KOGƒ6Xr%C2˜Ŗ:6:, n=")a%qd]fSZpX2(( Ħtk)}Qܕ2g=issjh$O..αBd\}/syx9ݗΧNcTxG }{ }]ӏRauSĩbw'VWUe)ĭJ$[^,Ľ-e7J 7U~!l_+Nj*#ŀ-0bkԡ pDa1xP_1njv@f! nS&޳b@' mdt VfKCHpjz*GbV;ϑ*^|,1 rD\/M25/=ıNBqaN×@skC*g,:x2/h  U`"cI܀dTcfBF5ۊ2˩ʔUS\XQqTaX%L+p\3–{AX/,y*xUprC5T2nV9H t} ;Pq* NĩTQqT!2fݙ#$ H1h֛:LIgj䊭x) 3F]QH',bfZy\c b~`c @wS5: FBS>=+Xnla9XªW5-֌xg"T: ւLW+t,2yU`xҲs+}Mz%UY[m˜Z KUe4}lCY9zC3P}?Cb1d,~"*@o=[ur;{q/zz ^o#Ao=[P|@!e8;b:[ٲOl?VOXzuդYoBzGݜޚ[Sz+凒m5uCIyrCIV?ءav^wQuҡrCiӉ8JzkTš<('[ "I(Y+ȉwmH9ydEeA$" Eڑ[H$Eo! IL$SHH_`&HKJ8JHE]g"3$+ " C "D'Ҥ ՏEI_CA$䉫1`H K" "bI ĒR EYI?"+I$X-DtcIK9K,$; ,Œ#,i.':ÒK, ZXIbɔd AJ" " ZL\"I%" E$$h" CX‰$XXbf`N$Q#,Y@cIX$KBeKDDK8dJr^+KX1ǒuşHcIKZ$$?4aIU’9@/,|HbI\J$~`DƒK0,,K%aI/ #$\XD2gm%A0qEK⎰ +KX"\EMii,$()nJ#d%AxN`% {,:Ò K|($&K-w|xotл 3[Ȍ88{7:{w +Oo1[rAo\Eom\+ r!pzkCo魜dv=dWEX[sPz+Fo傜^\}zr!Oz+m"͖_"  ʖPdׂr # Z{`(|P"[)nfchIo&ˠrMz#7~[5G2 ʖڲ* J.Jo,b8O`(C9C Z$O%8rz+8yr$[d[oBVnYEoeFĭ[/#  P, ÷oі-9Wo\9seBz+t\\yNaz+$Vy魜5rCTo\sCi[z#熒sF mX|| %t %!7$ʀ4G"4UHZ"98g" +IYg%E2Z"Q$!Eb5dR*ErP9"i@Yd}ļI/+pKz"D9ET9"1<Is`I:&s z*Eb8%+VY$]9"i"n$E@$EpV$x+)x'2YɹBXI H<* bұm+1 qpH<!q@$Ϝgη\9{ or`(* N2UU╩ au$kbĥ]Nཕ`/e"DbSuO΋Kc3xkiokSђcK3LtR~xdr@K^M\<]3jjVgf"5\_'[Og>>4vsdfVKGnff>#jeܨk l wMl__?YOu͛*j뀸{RqxT=^ 2`~?xaOIS*JWF}53dUh%ek+XU<0Rj \V\Rr[ mtgMG,+Q`On` \̣gÓ\,T+a^#~Љ2_o,8ŬZ=9c,mj,/ # =8惌`t op:q7<\b,eExp5hpni^N A lY뤖Ѵ mʎ7~዗n&qMlֽLt /M[xՃ{vk6.bٟcOnG]zדZ~GW.]Gw{|ϙ_{L@XsU_@a-@3\w? -RZ$o)q qk)q@ƝC!No0`w eu4$z]c&[Oܷc[IR-'CL7ўQ.<$&eee6|^\VceR:%50 ͱv"͕47Np$x[;} &ɐGhr8;>lB{<34X VzaXsJ$z:ʺ #gPNQaf"q~`UsOhȓ8d<ؒ600#9 K17fylzs<“cX;gc mߨ)ɈGR%j0.c0fMYD2reY8L(f Lngqz,f.N/ WBVztb}fMn%I&#F6 Ck86 &x hFuP7Q $mGu{gf;# q~mZ0䝌6 t55S5?7?#~d)!)m?RzwR?5Ff|"/;܀0Cý_u}w>OښV* Nĩgq6^Lvn>Fϴgm]\;VQk,S~ SA@3jyW df~: ;~黙MRv'(dwyLҳcACj,33A;^eB,a2sB yJv ]ҷ q0fdwѧKJ7~o0Ґlz3KjtWsHv7p 'c 2DĖy`M*t<%LC#n2lb%A"؎I?d6Y@턂 _gV`|gzfun3[3۳#IX'#Ϛ0Yq!Ye#&n1 E{\6f?-Z {CK-,a6Ӆג [LnA),A L&jTx =. f }] t}Ċq?dĂt!h|j"Z@N LΞ,[0LІak%(V$xv{7! 1s_Lraɥ7aZb %")-؛vp4ȢBF8Nh᳈olX nN4߾%&2 .D.0H_@/)i+}gPg5,r+aV$ǗkK3fn|؞X; *ӛȍ/ngM;!/;_X]L|]n]T@ SE* Sc8U|c] &A_F(| h&eALG?V_i۰3~Caô=3<;J5ݰcv"a އ @]g43LW foI7YZ 3bB< .X|C1sk`Ι0s;KQ Ňatk>&n> `fVBӔ8 c쮦egӸHl1 jf^vu&\7Z]"VD),7M6bi,1&쭄OAEfEa2LT73f2 ه"41@ఄY/_BN0K2S~ٯ,&=E %45Y8YK"pS 2|T M 7w[u1?&~\q7ZJIVN-@,crG>ԱJHn 3U!} bVlfFֱ:E#3yBnEIDUk^X7uw1N{ET,|wd&tL3N02󙌾`R^*̙\8a.+}Pgg΁@Ʒzʊ?1'a^~{b"9gXUXe\1M0 < WpF{݆<:b/M0xA;coL̙ٝy1Hb-KIUX  goLeN@19檥HR$ &̝8*<2#X nf " ;u~7ԡ Sq3jjD?{Kb/0ӀEywbŭ/f-{K˅V[캽Rvhof^L,Īd?Yxl-%,:7X|l ŕw{غkI9 I%sT*)^R_4H Cb |ܒ?z%!-kHX! jT(ԢR?U|@+q<_י3k@lguJxl}T+^̨<'R(70+JV<5]jFןWdޏ%;z6I\9%_]+21tolEټ7)+^LKVlZJYq/ ^=#֘ޏ^ 2gT~.Y"N :Lb {{jggzk :^BWgD$c<@Um+;D^ RO8"9٭ΰʡGZ?ZWxJ~WϦ OV32z?l;%pZˊ'DVqDx12df+̈eS0+ՃWc )ZWF9,ޛrkABcjl,(D=VEF 7S̈́pxꡑO5mJS3׬,U`N{H.oJ6k- WY 6iGDq7Ccv6qtsp~LO5mJSm`OH%zmRM.TS6Kҭ4c& &QQE]=4dY?Gu/VZ(_"I@ͬxiJ$8lR-(-cQX8/fJ~ˠfYyvr(Kgǎ׎&aХ(ow鸣1u~~Ҳ{]Ԭ] gg!^<11z}^wmua qLNd3J ?+;#)s9{_]W .ᴋfE7TӠp +I^HH[\|2yM(ҏ1)+1bV7!t'9%6ȋ0?ySMiӆj" rX 'f+@5L%HdU$*y]~]@`Q>YN~PԛE)'yIHY#y(N$Uׯ.f f!/'UoraWbLWzO6EXE6Y@_ wչՇ~;tMЌ) DTL}xH;*P${x[DfLQj͞Ω<'{xj쩭NԶCK_;nĽ4d1W ئ%ڟqADF@;Ԉܛ/5+OMl:Jq?;B'l+>Qag|[oׯӋt]OvwlR7j#,Zۺ4/ӫ6mǣ~jf%FA_F~lDWcY4T3T8R+F1E^J91  㘒)'KBq\6rk/fC+8ݎY  9f$f@TV$f0 콟Q -dA!I\im>p48g$@S:O5uJ"qCEU*JLppG/NIC5h$ev{xB+'6IʚL2* ,CTMl cJ #s `͒RVԬ1mv$A2jkO#⩦u5 cj3-!`}?}kOnxl0b-f0͈Ȋ7?-ˑOlK1L0*ebc}ԘJO&?+:c4Nÿ8>Yp+2cokl8f_q9+Q8Avj&qX@ƒzcփ+2-iA[j@:AB2+NqL]p <` m{̙^*̅W2[VzX8/#wNyc|4_jXdl_c3FWymr?[[OzF0[&~vjr}=Cr>y712 `?mޗM"& ɘ+oe3V 4UJ+YұNb13""@6)m^eW̉#J?&(_33=W>,m$3a^^y$wug~EӬp+KׯyJ9,dE-DڭSM1ӥBuG`JuZb Z)SZX"ʊ|lCS5ͣIfH2yzԯ$tB)qF@>=ֲl77iW9 0S ZdŔ8F !f$H7BAl_J=Dͩ~(yDQ$+J ,( epS{c|vD"4*$=Ii2k-M#zߊ"K݊nOc)Ӹ@C#C4)Y], .sq@Z¬(+2Mu 䘆6h)eE"d=4+嘨! HҌ VdXJ(9"}+2@cR?|{֩ P"H:G џu, юC\b pLtokX}R%aV̋DD˂dQV5hx}H1t>MXaP#8а0-4_Dw ۴@*(3$)jJmܦz'67K QV515uE)M(PbQ޸USՀ:m6]]J2񚺗  !iQfCtҀY񅟎{?Y`N1&Ftt+OQ0B+g"Vxf =='~43t ?j0<;5 /[Ϗc'~><:'>%i<ϣO]l~E@;MPqALHާ.fb4T̡GgE>jŻwT Vqyy_k%T+A@'J>z=V<5]*|`tV~?dޏ)i8ϯl. BLw-?" /woRVxZ~ǒTRo4Ʌ ~3)B١\x%dŋ01L,82<<[;7wyKD?ҽ |{z[!Dz;ʋ?~}T$e#N\"ọo4ʷԬ174y^L~dnˊ' 1+}QvNR&{:+3T(+%43. zZL^vUKjM#IST "꽖kFA=?̡qSͤp'9m_cbwqaԉY6^JkQׯSMA`50mzD3AHD71`/ @g) 1#1hB..ˊ7ЏdQ}bû/v.+O5mY3!fHFL+xԻDZiʾ(nV,4IڨҜ d4BO5'㘦 *(lhPҖbFobE)15*Ly<' #}e̅~Iɳrߊ6);[b]ǒoE&sJLI@Ky6Ýk@V\=q7/&wnȊ"J穦~ Y)6(U^SVd\% ]x{9B YgE"S Ǵ dA& ["vQWE*dW@V@a@ICS}|W]#zJL4!i'2qLO;14H3iLuV-`ӶqLN Km򛡀 |#a8%A@i&[z[2m[)1cYQ&}jz a!Ϛ5^ʶ[%V"8դ$E*[A]Qٸm☊-_Ӳ /YI. @Vhވ6:M@.ʈ0αNR[G=HY{vLB1K\CoV8!Y`t$!T+8..N4Ou,1={z v\i Ո)Ky&S.ϰyT]< } 9ĸ{- yǝx >Nlß YS8!|gp)^#0N-6wz2lwnekImrVB`!N$6W[0%+i n%?1ۂCR[98˖4)["1z Jdz P ]Q ud G8E85>{"OR 탁RLɌXt G2inz;9:#h.G8U-HS ǒ*R(Oh"N&끭!Գ43iZS32TO E! lF0U)ޱ t]f 6M0,2?>BlQ,W/2XӛL/އhHkA:uuCLk i 8!˝lr GبY0:l%cܬao0уSq` Cahd:759X6T جemāPrHz-v3dHlz1΁UlF2Z#ρiةUP[Jw|ho4jӁ5iMb?&,÷\m@OŪj)fcH Aj_Me,"Gh9蝃Y4캚2!i"4maD7a|}f CxJ Mpb$0Fg۠,2e.G8Z L,^耬1.\H] ?BNW_>u';3sڝD¼<ꑔv]NJCvJLS@3Bi7*ҀiIcat3`5%^nLНϕrkWy :sh|řWJj,q?\:de+;FWhMi vIf5k"84}<^|g"eO݋;L. ZEMϐ3kn5ne&JxӴ•82N2@ q@W'j=|9f"z݋T;1 o&8$V6u7dwg|˘xTHuI64ٳncP1@0D褌+`nv^yZݔ p X/0vh(=7#2HX8()Ǡi܌45mniEڒfT/2e;O%j@T|vq̐6}/,NzHZAK[7p?@r} \w4*e7l`[mǴt/`>'gxK Δ,0*}r UT[ [ 򘕧1U3Lq Ua*Y+C25mh]/å$ZtC/dvt 9`¢13{.%fI9*<c 8 q` 8ߟt3c|% ;FqvPDSZL ׯ@f Zn^ʹS,v5Q}hEڔKw3 FQ,T vddK)(gu`{l:DOҧxx'BXs ';8ϴ Ft;NoNV P+ pTH:FEwW<3D1S7 1`cI}2 Aj}8u] da%PqNlE*Il =Zu׹I Mi7yCY;B];|iy qZ~Oڇ،w%9:oI&`T8yTs3lYYYANB(!۱}ٽnmB@:nGoCwz pk`Is`=fd['YaDaHii$:~=L&P.C2g3rAGPtt#?j/g.ԇͻI5Cvwjf NsqOY h|OWػȪ2I[|f_fhtc!h;$TkV'R㓝Yo~m 4l $na#2AiH %Msr+Ѣऑg#x`j Ķ-b}q 11%!FN qCbVz(&HNJzZrub!z6@Nl~1dۥZNNwa1dM Z^Ȫ(V%U^*VEB')^[ٻ~uJw_뗨7(RX4 b5*~{cT/XDQW5Us🟠WvoEPU\_oOkauJ7nvf'aCԆtuJorF mKlz^\*Wɰ.Vt%'(\y6zWVd]*bw! RW+uQC "TJ5.2$P5*GFrC?d& S{e썛<W0fTSiQacf0II)ߣɘA?ғ皕RMa #ΫHrFU#L z_NޖKJkˠR;:#QRսڞPPԨWńBQuR^yTfjëjjf!x]]++mTO5&?j3UN1+{Kq 䫦4-]ջJ="Vho4uh[^9%Ԉ몣7S3CԘKjn$7'.Vt+@VMjium|UereeJw/WGx% &y]M*S?7>Ԙ*S }ae,+ *SjB?'{\&@uTO-o>&}X~eJ)Ms/L閑W߽{wR޲+So\5WLWP5ב*Ԇ7RKwyMizSlJ_8ߘ+Mir:3•A Eo)1nnvSp)G*K dAnLW﷉>-gBefK+l0VBb 2޽?@WRf1އ= 5ݕ Ren9WLvl-5] b*>%۾1Zٸ]L6wjG'S=On襦[{LLt gUk򪄇^&&5W%H 'LE_#ޯLjėWWtǒ+1C|Qj'^LV'Ԇ_*S"m嬚!O/.Ln[yW*S뒧-ojqD:^ׯ?@\bqtv N8;Gk%2ϊ3t\(efό|#gPMj=q/Md(6Jx/ի[^DBIF*ݻF3Xi3q7OG @tvbZ@x^ x<8}̥ynKsRM/3G'wW'_#$D^=zK>$# KW耸{Mw v,M_{_.ȉy.rO2W/d:7o?yW7TK̻c 8rüOK< sin|iNqҹq.G_5+蓿[#P/,s3TBĕK5Bq.kq l{MG Ρx-_mKy.@p8cqX'8X< 5ʡ_I1s7>3kɴ@bSd'8Z+Iqy* .O ĥI6}]; Xl/a .g nɫuj?N.Qh~YlVkTd(h]_n/kxūW#ƍeX#mJ6< νxNv{pw׎Ok_;퇽QLwsC]~C_^q^n{L@!Zz|T(sd kĖ ы"RUss5B;q=qqng^@\+^qjyw%OS<' q8O 3m]B.sdz͟rWK6cy,tg.3HCX:2H6?St| Ӊ:r,Cm6c9@IهJ:c!UPMæ4uwi<đJwƒ{M,OQ6RZytT{:푩iL%9$$qt TB2푩ՊTGiL%y, L%\WRKitpd*tGKՎ W'_#<#SzjBIpdj9_ȓ`#S饻enfpdj둩ݽzGnKyNG:3u y<ҹX#S ^9X#$82T9X"!yNG:ϣKqd*tjX{\NR mJh7_X:H 6W: Y$7j:X6Cӑsgdܸ^/ }Sz-9X2 6LWlq5Vbځ+q+AQi84@)SigMA}HqٜLM Gzqirm%2ϊ:.3<o&<Ët{ʔn1Y$!gn@\a,փ#gxvGDޑe 8Cq ıx 1Wh` 8Wid &q c q 1Wid &q UT5c 1@q ]o 쑜>]6P] sGSZu 1@\ @{c 88 1@\U8* 1Wl` 8J 1@\U8* 1WQd Kc 8*-Kw74?M悫-$}u'3@q%q!8C\ <+40@Zid &q c q 1Wid &q UT5c 1@qjvkux>I٧nՆ%ws 1@\y@{c 88 1* 1WQd bq 1WB` 8* 1WQd &q U^@q 1W nZ߼|}>MWLe5g?n > . >e 8JCq ıx 1Wh` 8* 1WQd bq 1WB` 8* 1WQd &q UuS8c @ܾnUJRZ)5c d =1@{z@q#S+ 1WQd bq 1WB` 8* 1WQd &q U^@q 1W .r6~{>b)tO=m\V\M!|@XVc N1/c .V=8@\x 1WMd =UT5c @qc @q UZ5c @qUM4q 1@\E#6 cډx\w<;joxbKpbGW;0%ǩ^P͌znAjZ+kZrSʚ91ݍZٜV̈Vucq_xl+rl&bt\+vq(`!;nҕqfKn,J@ 9heJԡ.^Lcɫtjfk#*jQ7CmxffpúZRMu jR&y]rOuTmFyZ\JT5~6U+d[huT‘<\V9q+jSc>177ɓWTKޕ>D K>31A2ύR9Hf\05 &{7%CQ:f2Nڜǎ(@踚Ò{S1ǁ8c 8p8p8p%RYO "0@"MDmg{3*fWıC\Y&@ 1 _l1̳QY.)cxt T85􊶼*ey,.̻vw0ϐ T; Qsez29e͚ i4ub ^K 17om!&kQK0F⺻o0I:(q87?=Ds8pPny4 p "ϡ/ˠ̗ytb o̻y?uȮ~yZ1*V5]ܱi-,VS8#W 8ZP:"^7lOG<|}>R о ;:煣}bEߎ1{tGpn6x\(}3~ )]O_a4]Ly}}Cdң]h=3}Ш`JC$'Mj87#%  nUS֕h=AJ7ߏ%;U}_;C+:j%RShH$Ao9ʾhMquq#^~ lgd-ctPDj|f.sz9РT#Hg>hːoό1ںvˠqk[Ræ6//mm"1dm zo6͏rKH"iq=l*h@ "$vA Jy<8*\(COݱ-t׏ŽE$34J A&J/%ZIS$ao Tݷ4gg[|)$Db ǪrU(F$mm=單)xH.F[gm[UrI 6&:+tVII$mm+:od& UsO Hڈ"i a%{s;yV^!B|1;/tJ+AE먾43C4ePHڬ"iKjo븿\ IL{QHEn%MDaL F7UMHζX5*/B}x?5d"XGEҸ[c}TjZI"A||rUsb0g$_L+/t{ H`7V$H;ؔn.oZ ז;2Ί]$&(mo_MiHN"aP2W~+ihe76oS#>JW4nE+>ւ4Ҫue[PKj"Gp[V)ٙ`Ύ)tE\)Œ=pf0,8W)Fg:'ѻd@=j41Y}=86g@ V|O@8شvYюǂg;χ];H4x5UEhX3cC C~o~J&X9 9 Nc; g4Ewvx!:Vqa30yۊIXo{:Ы ` X9u[VcM:8E/hǶ#qou㿻γn\ ?f&'90p^'{+:do#ܹR\$ޢs_os=<}zw :_"c zVv{"s/Y$E6MJn%D"ǽ_(mrHA7?"_TF5Vr:h7bc#%G"U#H#޿ ГN#:vJP~ ErWlQ eU$&jI¾5/Rd_[{]oL헰HD- ߹^sr%HHDI"ApH#"KeTêHUo_/ˈM+Uvk[.$Eb0g f+"Ov[:z+1Diym/zΡ\}/U$2{%Hn̐"1qZj q/הZ:H&'YSr8g-Y5D/7K3Er-Y$œt7Oz; y:F9Q$멳ׂ)F~rZ]$~HRw>q3k=TBKw|=^篝o$#Q!=ew!G ;K!FenQ8qndp{tlϩZH_ð.|\_aqLڥF/u]M6qF_b{^KT-B ׌֦PfSXtg2]>Vيទf'Uyt6^yu 8}ĵ߀2GSqZf :OrMөpnŒݖ&+(A"1R4wd[@܆v=꥙S /t-s.wœa1ElpbR-`EŲ\ %O4^u>-zG Eb7.eN=hJ^K"1d+Axd۷PKwg>PI"D(AvD J4!I#>Hz؍KaZRbj%4XKѺ$q)fK"!<݀:Db5.,|4"!`e!+% t\k+FW%Ezhu1ePcBVs)`eZ!|WX,,|)K'A|=dD^OHtA_Uj,d]4͛J5Ӊz"Yx tAaZڝ?AҚM$ 0k{T*n6Jf /=DGJI"AtKْFRхD5[GHM:tn{!z3P(i@ʧrsGol x2j:>t>P&'KPHHoɩFt &[y*_&'vŽZuke "b,n~F$Ts4Xd Dؼ8muOl];>=N E5_1rK- OܤO\:H K=lE"AK/DB2.;e4xRIV\"~CɫweGm%MN|m$7kkϧzbݯ- =݁oGQ )<,Yz:b*oӇ xG'(0e;585cx: nhEyA!nh5ڬu ah<-JTG^CXq m! ,}ڑN;ANON4oѢ̇֡Vb %:Ilb-oM Eah(m/E`ᯓ".Q>}AI$/Qq!2QlM$x.DkE$[d\R!($L %`T$f{V4@ZIz$KE7 DF%jF$ }\ɸ+VG0ՔvMCJ)$oE2ļ]Q/D㒁WlH(>x]P@Q͂AUk"ј`ug 7'E"VdAK.އСY=3&USzDʒ 9U/~ FH_E;Ce.HB$P$ _jEY%._щ쪉 9T_O( E &8hDb$g_7Z6H&zY;ZhN$z'|Y4DJV gC/|zK"IvɎRԥ`3D!=Iо% Xwvoz&$<>DM6ʻȫw?80Czd*5hmߐ ġg"u#$7CXj2ׅ7 @˰S>{ِZ spdd>m zIfn:4=p$F4~n+>|V>` 5qA٧f7pH % ,dr{jg=U얘7Sێ:VIuf n+B$gvwz 7KemhZo1B@9Dr&#9 ۆ K%mhh%Dhd\*E g ER=H5 H7F3XE+Xxy*PH+ ^r~F|{܉dt0|HčƯp֤jsm,9\4ڌ1Q 5`l\q3絔ԢTܦ`ܐә.+;YٚU4+;7$+o1_;bv:}oY1;[4} ĵߐAc81ӹ%2 S ChF*yj ^o_8O=| u uQ'w; y9ŋM_ VX[u$$gE˕4 W']tXPLߚt$+ƥP("n̵V$rИ`s! d%'JQj_N8!BLٸ9RZZ[hEyV6.QT|2$ѯDQzɸ=]V"WM$&wJ5"u"X5e܇<D5"Q`b$hEL"I.@m%vKv@ASp$*Cu'֯hD&&%,"Q2mIB2Ǚy(~C\55_Nj>65ڌqGTPY-T:lg9UY4^p2orV-NKn\9N*gu)q^2{zèN3,y!^g-> ӎtݒt'*9RNMg. Nz+NĐ9[)A JmI~ %MʹBA\/BM6.|v.rӁx_,5D_s!<B'z:D/%vhe#meA]6H4^}IoIօ1BEJP~? 0˦-`L"DB\(D>ʹV"I4RٸTHtr=H,|2pQt"׃l_O(vH@e㒑n-B-l%/_QDrd` B_OⅷE#@m)i@{+ADn5]:GܳZ KX(I$T^Oo-`_r֒H K&߹^ D; P|L'j~zcL*^fْH Ӻ>x] ^ODI %:{ HA>ԼP_K"1}/P5N5"o&_O(pI,|sZ ^5]q!O+]S']wt=^&.do =|Y e_Oy Dմз9]cǯ nZzH?iUqq6ٓ-d*Oo,y79>M<]1 h^:7Ng<ˡZ.pgj*'/uk>ʛW nGN-ն@ܭѣ:r*aǢ1WC5 C5PC;C5H'Syz[ttx ,s;kk^ K= !ޖ4}7:h̭W8:jr*)#*0q< )^o4d\{ O>O!s@Ŝoj j[ bs7t:0cTE?LlCnI"EB(lQ$${ ]p%M"'\VEyrKil%}\Vi"Izr/_"Q$th,'%M$GЩD/: h5 &X q< Dv{_`9b;NЋ:'PLx]xbh%T}ay5h5N"CL"HtȪREWJ,]"Ihm HJl$`clR$ @&(UPtE/ݩ:(/t C$Xu'H44$/%^O(P5)M$785[ w*{='?(ZV$ GGy^$[*BXWyhE !`ᡔS/HjvA>Ӈ6iay~zd!aŦ*V(u=[7]+@TDaz,*>qx:J*@ |;^#yj:dwVՎX nwx}V{yo- 7}qfb P !0OoƧR1UQ(٧Êo蝾4ziG:!D7}A>?py@Ϟz)V\F3lA|b՜ a0" J-BPlPJs! }mT{Ⲧp""yhC9f"QֹZ DzPKK| }I SPz+ъD+ƥafi%@6b\VN_AJ&@$`!.WrWEͩb\Ž;ZH%=("A^ODGNH`\x;Nz+ dGޗ WL"I3Rj^c{ -D{U&EHl79װt7JB1Ư'ȣ.'ʓ2MHR PMHb^O(H? _: ^0)" _jH;:/`I]$K$ _jE0Y$_C}@׋ZE|Ca8(XZ Ng4׏ji=ĵGjkޯqUcJ1qI~?rSrS4;l# =U䦨7hl;br\Z;MO`@V#);=ӛ264<ǔvP?ht.5g Nzug~̳2I7\pm=iNΫR3+`ebSOzUe.TwH3.uȻTqU$^X3D0`z5ٺH@])zOxɭHzR_ 8`Mz$CW׊jERŶ>\$BTM="b\z37E\o3I$:R^[Q$* `o%J^Rm(($Hz@1.s|]i"AXxLu'XyUɁއ1NQ/_!"Y]B^O(x Ÿ}=!(DD$}ZXn[ g $:_UWHD"טb P4JD9FW]BtJ0HQU9e>R|G= n_ = ^7Uw} ˦y#r3)$D;4'R#&8 9Zj³=H6x:B }IMŰyKq=|S(`f v| y __ާfG3k@[7D\qO耸&78=5(q#[f@89y-Sev8skwwfE(P#H Yŕs!Vq!(z.DۚQs!TĶxGƥb]EB%9H .&oՋ,i[TLyG dڗq)>,b"QA4ZυȣA6"!lsf%Hn*B@qH{ Phլ .xPT3+`%/e2 *uJB$d^O(~@ߒH&REŤp-rK\$`}:`7D59yf:W"փH0:A$ЂHVzMD$@U^O(S5UHC.4NIC/e);Du ^>(Z "u0bH"1oΛYeתHtn{PFi (뤊D<^Ob㵱3obaBe] =]X*ޮOGRi*cN;Eg^J{!CQ1~G!JDQ̂!rJHf &YUIBnxo^?o3UNus<'9gTuUW} hܥ]vguŬ&!7Zq^Fw;񴐑'E9wV^{rx˵kԧw^K72uϴWu$ĩ'fs\c6Z^v{ U%WV_uNEvjfgMc~//f#[MH%˫6e3ݗXَY*K~Ryla{{Mc.uw3ESv/-sCt#ߺY*3?DYBk}3S b뾼QrFl2gA i݇4V$p%o_u(CH6lpB%$؟86ޥr(^BRaVa9$N``&e%^YB?h;Μ|V=B.@H<-Ӈoェ8p%@H ٕ5tţCvLNF ,$~"0mSSM?H*dWde{u*& g1$}kD?rJ!1p&xUG椴T1)3GIh&8g{Hrr~ΡNh{-ZF})E$#ݙ$KH{lE̳x/ۻl#N UhTLU\ʏCT($US I);5 *6T$HHT\bcАӠWJX5_jfWQ,~#~{a5He q!!.$ą-l!!.l#-$ąqrB!TPE(*t{~hHTIhp(Pb!$!!$B!TP!$nF4GljZt7BB\H q!!.$ą-$ą-l!!.lDS.'<ЪxfŅHSARi*T"ɆP5r("K I1,cUVPR}kx ꇆDЪYݳR,`T@R U"XA*x9OVMfEc'3u++!!.$ą67Bzm OoUgv8a3P3wVv]u԰ JtOR=0˂($һȉ!ohTG`gM'H<辙@$1S`~xz]&?# @.rSFF(Z g,)}dAئ""gTP/x'|A":qwtV܉zYYCbV靐Iy6Ln]tԴ f{7wm.Xјt mp~i75yhQF| ?x2XgNҙیΙ?:.;9^`bpd>+bMA9@b *p &]d^j:m'OCB\H qa qa qa [H [v3Jms ^'(P{>_N+i vrMS-B R`)让B`9U>ڭ:U`8  UivR)UVE 7P<$ "B[*$'6QTp:DNy7SV͂ E4jl_Pt^z*YH.*H sL&rNN>Rȡ3ԪнڶI,|h%UW Tή-DM!!.$ą99=_.?`YHoxYKo#ktHר >L;QnwӞa:׸igan[=t3'QO^V?jR2?%UG)}ٞ[fUMХT5i WCmя}Gơʠl4fB/ yAr7́>DIqh%Qc3J6d~GlHC N53U{MzlIG J]^W89"ʡtp!JYNי-yԛO`㣚Q}ҭE)M۫fTr[X`xd5`>ϸ"k2J4پym!OŃ_ $jc 9U3"S1jMpQn2@O2#K)Ĩ&,f&^iˋArdyK1YZ.Qh9lڠ\enΚ[FߣQa4fz0?T3J5zHAN5aɤ:Zݨi r09H.JیQ J@+%u?uTͅj;&G`2[LU3*$j2#2zpzgyi[HS0f6yGͨjRMwklR:-9,4JJLinC:jJ]TkI(qEd ws(fƎf`:ʪOLdؑ+-lfa!3W(#ʖF+?k`zdt9נyْGDݿ77Uګܱ_q[?nv|mor_ή>xYcf# o>dvJp~>k~{_*?tg%p 夲^ =(Dv;#H{$w1)tϾ t6 T嶙4i׻e=1Kp^OnWs1xjQ Nd%Yw-@ ѣ~G(3}Y:ƯvQ5t~>~u[`/յ-.[IrVΖ hkcWmѣs nLmx|KGmdK.7p_p3Swrۖ2Ěw*ZբdCӇ S@2 FtWc|*."\G5 |su JF*]b_m; "p HjGiѬ}{|+-zg.-Bry#3@șL[Yp0{=JsQ*wñLڪ=RD;3I4o"SM$B/8gr.g%uK$@wrDߍ~B5$UMJ>OFF2J]]~5lSM[HO&c%wl\ffR軙ej6O:m6>G|yvf39| ׄȡ 3F9zE[P]9 Poր\} ;$W&9]d }ו} mxRHf-HǪ}9pVIM5=MK -ۇDpЪX^j^EүE\nKՔB/1p\1YIEĵ%/TSdT5b|ɘǗ$` ɁcePM #.%%j%FK@rhɨfl PqU| IK!"gQFolEĵQM[n$Wj2V@G\u/AEdH5aHۼr"j-%9Մ9>N&*ݻ8+@C@y4Pky1XƗ)Ŀ eTnO/;VӣXCrpf{?8.T+Jzїpk ^u0n4f/z?]þ5$P4abt3}մ*DKax!9*ҌWW5es BH:؉RCT5I顙/_-T?IiCi.qVbqd2>X/9a?6XHJk%Wh_'2¼jrW+q]s%j 9Cz#dR.|I%$Ibc%uYlĕl1E# >΂r1 M\޻׵2W^'f?$+&o$? Ћ<%"8؛Czk@63"6s;! `"8V!co|\B%#wGJ}jۛ],+?mm7ʾrL7#6U/D-qKԛ7IeuYt6cϏE8Jw$/ݤDvٛHw^ hCVeiB[^6 ?Jkv^~-{v9ӆha)cmi}?{A}~7Mu}~ Q⇦&D$ĕ\:e3N;w4*r@S ]7?M8 ܙqAʮqBBB\nލ}]Vv#-vv*G̲BNYNz[p7B2ڀiv[^upw!C =֗r'Wk)]Br'4`) V2|ڟ]tvBAG hER`%LąrR_59$H$.q%6|]g.D`a^iE}Ji>w=_j5Xg"5*DD5c0 }`mJؗ!i<欄@\%X#[@ޢrc+#.6% jR59+A@ą| ("⵴0&į3W ]žDX@B8=*UbJ$--0'IMr`%%M8Mѕ Jk%NťpD `/,%\C )rVB,TEdEK$U8VtjJ#.D`ŐDR9pV(K0JE\/ $k7,96 /l%ˆ X8j!Akt\+sKKQMg0ΝK30w.HrQJAX-sf=ԇ5:;IDΡ 9_j&a~8q-8E\/xD2 #"ˁؗ篚rH@cRzFq Sr _E{g8qyI$ A B%hy/4U݂D ]E,`ȗkd ARF+1F\k\ĥɗHz?$\.Bq%&>)5Sk."ȡ+j73t$*wlU%8_R-TM9$hfR5 -OvhK|+G1B?!|0Ad;X $qȚ$Q6Z1G\/Q5mF+"WK|8?QC2/ÌJnhyKYh%Lq |n;`y&q!Ȣ Z5iɀ*J/4QS 0Z]Oh%8!Q9r -OX8_mMYI &qhDr3?bCefZ|u=Dg ٻ5߃oW㮔ivDZǜG7q/oq_vzs@Ƨ_i!!!?]YwUBܲu[FJrچ;ImOvz+v̝b@F[)!c~DmNohg9[nR]J;=nt|^pSxk֐'pQHפ7{&@ЫGF/ڤ"J 7i{AIozA r[M|gBЇ84%X9O`Uh7Arn!FJ?0%XK֤ s\򈫅% 'KrHj3|`θTfI\tKp./& ^i0jJ#6+K8䐈M񂔆.G_´Rլ]k&ڗ2jF3aƋ#,Kis@d%KN) aex9F\Зl2$ՌGzsVr\%#QAXb9Gih/Y }Wїp%,~Z LCO2אԗ{Esi{br/9<%\sPb֮,]4PuEDs AN*`_۹ȼ{RR9B86<7XE=OKC20jn xn xn(!`]USE3?L`RWX6Q&U3)DJPT]Q <3pO۽V 0^btT]Q >>3jzcq:*nGMisXu%X/&-UJSt s>횲$gl9xDp ч`:WV2NG\(m.% iG9pTWYg NKL%X qttDequ\K6)$/Ε Vb` VKr9rL7JnR~!B,qI}IN bHk%k%W,` _b,${0B+ `/Y%Dzt/`ZfkFqQ 32is7ct%DrqyHLE\ %ǓCLSj Kq}m $>b.1V2,{f)B6\sKI#._:$@#.=Ȃ_G+D,,>ZẂf.KzҰtOF\9fKH΅FFlĵ`qyG!D+AA/CRL 99&g4m>s*?#+j|N`WYI:DZ!y(|P_|4w5i*ŷѷK\#!3n@hb|]nH+{[ݺgfD?Of{qvщwKLV*O=ܹ_ӷl[?uO̿+[Gi~v@*;/]νV*~kNBӛ=] /!}S<=wSηӨRwo|7w~ I^u6suprڻUpPͼ}s&TDzoBwۅ ݪj=c];18nG%X@ A@E`^ʯ7O W'H^rK(!R#k҉ud ,Obzo>X=1;{y9HE tJS`#zZ;A26999HE HE }ZFHl Tg%1 `Yʜw$$^:Y;K̰bIkE5QPB \(! sS͵v86=L XI`/Y QfqlIZqV(CB_rU@ª&`9_2V'%kDo~A:j0V=hyKb=+R&%\$ *Y Hw nJJ{TY9 X qP94DFkḤfz' M9+ ,| 1ƛ S5M,OXG\/a eFVRGD\ȗ94sц"ݨj%3҈ 5HvXP#ꅼs3vz_+ߝmWZ{Ñ!!_{04@ 7BB\ ʧeeʞn-cew#ٟqۋr.[wɖ?;LNZ)\{=gnR$kn[w,ctoߵ!hv[cG]% esx^ Q>54R/4YN;wMz!ܵ1AUr;@fC7̆Hog^Ф7,%]!ht9 h:h7O`-J}p' E,k*JD*ޢ (YԹBf3U,d6(zi`D*{@q=qA*FNziu `Ai%AiIEOaJ  C?;eD/y.DEI>K=чKz%A^¢+JSN59+{[?pe*AZ*pSNnkԭAict  NIʠc9l$D8mW(X_"/ T^JbtB$8_2$Đ堧q\/ⴹ#P %jj֏KPÑA>Y_+*s_dpY q }IۨYS^ZV".XE#֗BB8Ke$%TT F j]|e VB"6QJ2#} ׿5$kHV|՗H!a߾E&n\F\K+γ^ZE%XPZd|Р\59HH94NIU&b|<۞S l5/O.1fe<W Kbt\f9<(H#K4ZYY/ӌhrHsN)f".bW_RpH` X>bKRa@/kY `|t_7DMLRXap(߹f{4|X^|y%Ť#~h_& ȁMV2#.~L="TVDe3*+|I A0zLMVD0K?ȫv?k-j巻4oXK89`hV _Z TV{<;铲's9kB܎SU~U;W*:&'?vۖGʝSYbN[sUm흂K営[No8B'Æڂv}\tot76IINOީ 9^[ow2ǪAwrv1[0ԴqʛnRybAvI%ls x]''}ݾ{;sūO}!M"יG+3G& {m9rߵ qw3&~cⲝKKG⍍Tw{NqZn1rޗff&qM_>>n.O׀eh'ev|{$]ewq*2чW&TgkF+QZjnثokPUÙgϼ0G(+Yrn K%DHH<ϢZeEPM NV5#M5V2YEe2XKW#./<BQ?2Zɨ59:{Xfux;0EэVr|&q }I'<@AYf%og׸Q5J"π>:(+ه彇,jB/4o2vDWvݬMn,TLJȢ7Zoe"ne-{Q"zu&ĭgO.}y|ڼ nkņiOBBNjCk [؊u/$[zNgI3%Ya4Oy㫴S %eI'BRO$UMI,&wPMUH>) TQe'Sғ8*{ T3_hޢT'32 *Ш(Ftrl;Ub5+MYO)ĉuT r;TR6gBX"f ]DTl@y_JB(cL=W抙y{)z>Dfd8TfqnX90/T]yL(~lPM*eD:1?!-SiG-gaF1i%Տ_o蹒i&Y^}Sg*jPLzVrRof sw~"GF_oGjT>1(AS8tY/Td4f5v(^,QD`)onDC.*͖ 69-ײB" q)R*IɆ^ApnTIQġģs%WG$(.y*P )ܚrÚUaZD\yNTD&k͂b92c+DVSqҦ׭)XJGGDm%%6]Ƚ:E\>H7bkR^ơ\!1 bdu@2rTV^QRƼhbn>lv3tEN&8g AϏR;HJkxVS/"Ow 6~7vqar,fS6B #%:kw 슞%k*ձ>z=5M?x9HRwګjJ=HTMn<:뒄*"TCE]o>nC2,_C5K=x NIeu?rOخSF ~韟'_%vB}O?^ӽ| m><3E)itr@3޾`[JzI 3S7M#.HؑYr ЉI绐2]Q"0>gNtpt{tCkJɹ5g;Ry T̪)= %vÉ:t)I՜wPMJ Ux*#$99t•XWJ]_56'yX+Q 9RMɡ:Zv=7jX,j'Ssk)  ;sX.8LȪKKsq%6IH Api0hȝiN3Sɍ!52Aa:IzwA30./ !5FMJc;|?:=lS cMu!f4vZځtgڡ]جO#2|POEOT|*Al>܇y7nͭȭWQ=Jpiдl~ӓ1dMغٿ;\8kYvr{eK\{{ Up ]J>2󗑿vC*(e]jv/V!˶f(]~/P*#KnmY*g/~o~78FywOù=^Z\G8q(>^eUpԙ/ce*86~ehGpp.\moC[onǓ-SoGn}['/^8v6Wpt3[?hZQjlɡc5Ne!\(%poCB7!$CfP"b|fCl} 8.jqf_[|_5n6c| ֢TM)$Vp G϶.p6|$8W5!-!7(`Sl3l穨xTsio H53]GIYԻ&JE~ȟT>ܣVCլ06GXwOl9pԖjJ!҈\LE YMjÝ, $fEbWTTWu(T )J:'OR5? B𑊢WM9$Wb(9?V>̎}FJn∫w"Mc>-šߥKDUqbx6AZg>l|UZ{UкPŖ}Lb%{LJ_j], K}s< `up<%2Sbw+ߕRmfFO[25{y}egUn_vDӖnf~^#l-Ja^^wJ買H*oWRAWeR F*ϟIfTeLF7n6~~}Mk >C.||-ԎWK_3|{y7< _Z< /F~D='Ck{C[L5xY T;1$љK%bHPKP5;׈JQ5`9lTJw$!H5JP,!ro(A$!I?JP5Pg݀V拿wAj"9$K<9$51$sIsXWJw8OȪIC2:~$?@`9\(ޭ!ՌOr@iTM$RPRD hf f8g@ӭ;1$C2J՝УB9|i7K184yGpф~UE:\y]v#R轏d啩^ڍV_]vkwCU^e[~ʘfO^Ϸdr.P:N7kN[ARy)nI첗_d'!ĭ?.hnA#-e/kvHyU~]zq`}[(X;~)Bܷ(p!cPk) eɇ~/C b3#4Ǟ#g1JQVb 9kK%JP5iHsU "]) &hfI@"orHJU<&IG%$$Cz'$+`T /lI Us~^k/![(Q$!71$!Xը47O-,KO54#` tO B\[ Ԏ)$kIܬAXC]IB"obH39Wk&0-¸"sGM1$"6|zY祥Rs4E)Ycg)ę3ᄍ4QUqZ+ݴc8j@} !Αpwȍ~ՖJwen"C*d-ϯ _xg9ep;e'\uʪ&tOݤ%>c~ٖn ^7p72 %@5қ?T8aKo@C!oԉ29! M-(y BIBIF2-Er8_ 9$Hͥ@Ð\ !iVjҐ3UѪf_*R`_!Djqz/.AZIvZ)$R`!dRWhHz~haJrHRwJP5Kgd ArTsrVKbkF9$x rH2RJ 9Քj~dլaR͒lbH h1W6rh(A!Z@&3LTɡԷ87,qm@=C?"_k^7[%N;9zB4/4n]<(+rTBK)Yi r~^`[ȿ+F6ero{G?܋m-^?Qw-q_ҝeSڽn;UBJO^ܯg~wcy Q _HdpDSG<ÿ'<3u_vK^J$LK, r?d_kud]"7}TV ݪ*|\^udA=A}cG5;/C2!VMݘ"AtHfu9̏CNK\$؟7f,RÊ3$PnOHrZ.Okf\ Us)f髪)ۣ 񶖸l_cvè xrL&d!ty|U8l`ǥ/ _;ΡSx[-${grb,[a%Xc q6LIl$6 89 :.s9juR܄[3(^i*Q5S\Q,$ԑikB( ^^YqŤmBa S yEGAmMy `憮jbS AM$I^@wVB8S837qzsT]Tp5[\=OՔdmIH-F|4+I5)Hf$1-3HOsjH28$@S(n{dˀ3gíަĶ3]kѢNogvY7%H1BRZ{m .i_Ӑv|#-!YiWori;NT /[^v (ݤt'nR&bC:!g8B>|_UoJLvCƛk\U'QJq;z iޕ!-CzŽCL# v.ɖ{~(۠τr0؊( 6b="YQAْPR#$xf QҼpػW<`s't ITյE= cCr[(à Ubl@Ǯfz·̕¸REFcjk,jCt3K<) jEJa\9@CSIfxػahGME:qZp.?9_ јj֯O0B"]j?$j&kXBI隔jJ"L|G+jP4$ޕdRk0 ]=YThQFg2=3;{~ÄAUE˦vzCMK7CEB}GB+0 4n]Ro]Pߓwee_~[.%Iw*,R6YN+ܑrrۭݲ_~t{nvnNoT,G7ipg9)M/`!ͯBKwz@{C_ R=$4Ab}*2U(`U1&9|(+Sܿ!1f:FiiK7Bbo "J`F57; ֡ _?K.NvXj"!)O$F[LgP連h1##xMܓ,`w{ј@5MHTsn:- uy`ET|GFHC[0?$%54#$FjT5x KkN#!!i>4{kmv%i%eಗn75۹v՗ C3%;Q<.!P[~v[ٚB|^!uޣ܀o= Q^ 58!mWz֙h5w҄8m"d^v3%tOA<;;L)r7謧υg.a!ب{Z`6 i-tٚP?A@Ɉ@}&J 9xSbڅ $.yOy#%TNR $5FU7VoAW\Z9)4;8gOB'`V൘Eg$?c1Hobߎ"rQ4O_H8lT re|V5pfI3߇. U5$CC-_v%SFHcw:Q>Vi *A;l{(zر\{n!y59;t$&9DGEzt*5!؅U0ȕ|gή (݁9&JNy"lw BĚ#~S4O( [ۚWB(_.bQ uY$4x;R񥁓"\J $6y$U8q`w>ܛ,!b5W4<1tp0ٔRMex5JjE/os $ML{!TSBwH^>_ TFD`֪l̺F-zha*HZM},RMU΋]O埭 %B_ I~$ga?!1%*}]^{Hk̚y+<r;X5HN$5ƛwSwޚ꧋RE j^HHX׉X%k׵1*WA\ R9t3$6Mya%pK@,4(يeG,JR$3; é@祪vEj7U}-/BUj $l2P8H'NbEte#̐d3V fA*D; GI$!C:jl{AbXv8ͭ,wmwVW/ǷD1 qKxG9 [#/mJSQ=Ff ud4z6̟K!Wu;q4;X뫝i`#ʫg5q8bۮ]n 8K}~o)x]K/^BR]ΧlڬrV=lc/9f% ck8tZ`F,;QB`bӊ׽_Ыz` ǚy Bii]$)V+Sbre3 Q\/̩ҕLdÙyP"(`$5crE=)aΪ0JIA6FaCkw桉hRP'3=YF&FsI!I ](9NY;${LCPW;օ5G;KƸ#b Il#Sb! #ᯙ&cc lsVÓŎ "~ we~SaޖNk !o(;+ 0"0UК s~˕Z2{I`ͱ&k ba;JOu8HOka@{\ tCɲ@ll%`@RI¬ёƣY8Ҙ`seS5 (%Y W$IH&O?0R"\Нצl9uCU:-B[i_qW je~|YEJo,*qFws~NN:HK%XZ ૫8 gw4quJo~ \ Zh?#*EóIvu?NzWi{KR7Fr/ne=f;nU樰jb)\ݝfgGWӲ5UKNi|$ʳ|Q1$Jxmd@$Ivl(z %ߡߩ# IMٹ'5,%\OːDmB 880I`4r{%! ØɣmwjԳp'ħkjMù5KaC#jaa_%ŝૢV?ΐDmnUBzP0Uw$huqd^Imߊ5k|k2>KdH3uuf)SH⿫$&Wka/J4n A_Pb& n&H¿vжP:{{*I#v(?3R IBZs{OO!4朵u"灴PK<$QCn.ƍ΀Y@Zt@ZW *t>|nfsD9ʚTyM 槿]3hz̭Kmi$6RQ9 F;kу ^&M'  㵣8zƿ8~lnX?@\w=.^`jW=v:-s @ڙy}R;VY.9{;םxs `a! @GVʋ#j"q/NS|5݋ʽ?u~7o_}|cfʬ] 1|/Y9[D.{,_ݿ7n~7ڣy:NǷ>ʃ%Ջ/_q[~yRc^yr?u%T`XX,.o l7G&}X(zXPlB91 ;ؼo--\Z.93rI0Vg3sXiR!>Ro3YsreȅQ(BF8IDޡBx(BQ+ZS%IR KBCQZ>x<(MtA5+Js~̆4Tl,+H ~1$-G+~ș<&MV ý2IgLM`.>d42EbK7cDLb :gUY:ΏPh,zW:rd&\i<#6I MS7 rCXd?K ŕ J_ 5-K&kN+\R.Z ;>rY=YCngB $$wuVa_NbU%ɨ9{[ ^gRgQO$*}\,)Ӡkiuo͓3DuVxxTeM?IW `[.tR/Zm" Uy.0XtT$W,m& 4~}KS{Šw7WіLƌZ{_DD_'rƷ ܯκjP:K תqB[s R rVPyR?Ԟ*j1;4ݝ[ʧ#BSu~];nZgH 'ur!_#Ev<#D'XZ}݌#,*}>gNBg3<( X9C˽-.I#55I߲ 9ikmpCfffOO) ADMwmO WzJkC*<foO2PnP,hzqST bU19$Xs) CV!IpBD# ç8^I#IF9OG%$1s+Js49q0`޽As`%UڪJS4YI q,Ifh >bԉIi5$$Qf}'@&UHw[͚3G* %x CfFF?I<^[-帤*!;(a@ϲIݔxjHnp$I.79Q-M$rP蘯#&kM*}n2HTx deg,E sKdHz3K=ӥ6f_GBңJ!$Tø8Vva՚-ޱ`2\G;glHI=Ce&IC#^^fapJ*!uկVR$+[zk ߤP>mLH\IVL/1)b{I:,8o"63*;$k;]tKx( <#$֜Xm.w7(C:y`vVVXZul)`-RM@ܳś~)uJ9S;!wy['>{ l>[~өwy4.2i0kNF %< @Ցh_ZF`h_xvgT pT;-iiP%w>c 0Z#(#yUJ@#Ti2t:J,zOv,1lLE]_Y sB% N09j2RcoDv9K NXS0WQmq~oΞEMxlʳ *'H%D5R4& k Fk S!η``jU9Uw/T c$X{S ` f \)3_̄2@<C?h]+bbMTˆFv ; I]G&U3+6D}"%yгUCt#vUiB~cIх#?} (TlxaM_+ˑEEL#n59 `NWif K.k ˆvRh,nF)BIVi$;̑6J(!#rZImEd@֫O?t!&C행Z*5U &B*g5NɿCBMEww`/lg8jpY 8)#Di&f4ֈ1ybRÖq>En$ Cv*l9Ȗ:22!K p9AǕ5EgTY%u8HabrL`TPi1h\v`Hd~Hs#= "x 5- =V\yz9t"Y]Hib9ݮN嶢 "ScI)Ƈi pmÛz@3ɫ-0 荔}OٛkFkkFk-WúMxNĕ" .NH^Ǎ(bb)\ X/WЦ.-W*H%# !PBKZ!> l$(x v҈J=9|S5EL#c"ؒ2V. lEPTr"r<9 T8k^6r+7y<ّ,=_/6r!Ek`%ng[ȖPmZ5CF2YWAIPB&XɚɠG*mie#Ȋ>/.e,MC"ɚ yTLv#^Йi E!rk$zǕ,C,dV1EXyO)h ‹?µu D /GM XMgMNtx1!:,fTǍ4>M(\B#gtU@1:,a0Y3hq:R< ȅt%NYǟ"i FkFBIbH8KfےB\h5D`qBI%ce6Wddҫ(<-~*gXb=0Pr ;Όpe"7Oՙ?n;udl+6XrAҒW8Z_*B5Z ߮# E>J~ KL;X1+t^I0To$LLB߬6]C飫;[L tqP= AB&Y="\ I,\UEXӘWUMjDN 2ϧ$mxw,<& "qK"+hȿNj_KU1VQr*Uvp-Dq}cC2eCfbaphJ1v<Kղ#ti,ho M(G*=S֬y?`jXb( !aNfX7J5jưtTԚJdh[;`*1dG5q @\5q5Z5qЂ>j9Lq b Ϩ)`E#\ bR< G)Cyh]. g)'?0_%ٛޑB&3ĨD&](bT=Î̹ ?8U8[.FI JDs + ( 4&1 GKU-Y?%b'2nMJ?’@yBb%;oJPTWE :^YI oh :d)IׂlM+i?lĕYa-A.| '@U8)=1HG&IQ2yO.%\| Q0.>.Ng^-+P#z)vκ,!ވAqa(afNokB fjn1er ]'VRVoPXN! (8'wNDeBeԓwQPE =Oqx:jm.C9.b%(*RO5\R:S(Bw}0Qi4RHxh7IYdgQ6DM//B`ޙ(AB4VR Jך: ]d6p"7t<|G jm!61G>L;$AP QHu c65yx qkX;|xQ^֑W&j:6iѽL"lR0[M"q܁ !KHN"%7BN5z@e*󒟺OQ Wt*ŘӤ`u %0|F*+¬I5LM |""|(T'z]Хoh5q5Zh5ZG(u9I8(1;Q o7Zf̿Z -).ƄQB-@'8 >a>`{+)Al4BsEᙢ(Ug"$ 9?z*5P""b=PUd1NWue<[d8\jCyAM@hBof7\TI5T6"͌HukA$9(V).B3akrr}gTJ.%V$GnU?Ӏ1' i#/!U))ДYZ}Z"+ ԯZ~+w/VuYXI#T8!KXdȱ޼!a~ CYij:BE6-ZNj xQV/)x:Jg쌙\ԊY95WO[5q5Zh5Z2 YI'"qp8֔HvEM(E$ krʓ'rBAspA,:oد-#N&^ꑨ݃5|F1 rJEHqJu%824ȸ9I6+Ɉ2SdR`PkrRCA*'PLMSX9n~ʅxP"Mc$#&,`c0[@; F0;fJġqb @ZO`+MX* /'}K"AtͷrN5FY-tCTE857m!x{Obqޱr2zC0nq>{U0eIqmZaXI! H9N[ v (,Fz6eOׅ6 Z%\Å nG-JGf擺ʤ֌dz|&m:& YVЗU'l FkFKBsIz KрuC{,1HBB|8 R+~x5  EWX S8K' jL@A>Kb5d-4I0LMBA<(51JĆ0 7pZB,'=ȯ$sk5"PAb:iTh uo*8` u;*["P]="Ob Fp/:X Z*}Ad(ȥzX I)v` If[ #HPV9.na'Vb;`yh0W~^(1l߀@IX吵CIDӗlGKaNE? Ņ!%'05`f)YMsjTG,B^{]]#*EqRp7r|0ie~۽}.,n=i`7~=xo]u?;'x2EvٳשOGWXGOO 4ok^g]EJ]g$*~KʵE%[z{_GW\|vz}n>]|>L|v?ݺvwxW}Z|v:[w*>{y?K^][YW.|1לln?_, ⳣ(߹">kڼk+wo]է?#lkzoc_/ͳ{蔋̈V7Fl9_oYaFB6ʗmSFߚN[[K9>9yMb4Sy|fVZG%?LMm}Ty1{sy֫q":Ϸ}xXYxd͉꛴43U^EH"5ME޴))G_^ebܗO`TIⒹ,oSɲ0ox7.φ*je5)#Y-YtuKUJz CAL⟇2㗈Uʯ9c͒tI_ɚU~(yGXshޅ)ŕIIyG+ڒ*9GOeX2GyЖPE`^TY[*Kee[,RVU'L^/k4">W$%-$cm0{hgYR1ke&9eͭϚ֬9WuW8zQoGfX?X;n=WdkRwUlI޵YBJJ%eۇ;XP}j=gzg JCPV%ùI6۳6ek+%ꎒY;ws֔Tb`n\94t3ñ%y\- *mkh{Jo*ǝ/k͟VYv3k"Pb|#_5ki!DiЇ<$qL^bty^s`'%IXqV?ْeCv֙Iw 8K^))O@A䏂-ڔ%ӚZBWJ'WLz\e>XHōr;w7;WCCRW .V% =*CD۫#lIΐQl>ɍ)^z\%*/lVmp! _N9iU5莈0ڒ K[w]%IYD+wv_L#_HIlKB%g+vk}G'u&%y"זYa]ǵ IG>-)>'LXZxmIQ]1HIW~5 S|͍^Fo1KJ:1q< 'fUx#iIIqCe(tGFkVfoI*kzAG [W&ȼ<$ gq51w42Ai&&NkWgXuJPW\]x,tʗdV7y+8vohz\/dKy_iR˿aEJCK1a.6[#ȎvGk֯XY}b2UfۗYrs/.>p[>~[atmo{_aNUok-U]|׮gl|^ˬߟz?Ưe}2n?>԰v}W|6zڕݼKvҝx弦ʏ~|r6ڕ@ _hGfhNU>;o1n޾[to[nnb{ҕ[W7GRjS~Z{S}9ggT>VP~Iioma=|wƒEvayFD>pEWgqXg%!^K|z\;EwJܶ]׹jo26Xyl,_lpZg3bU_8nʟI*C^kUeFҥtWYSgKS DWE]*cIxBOWQ~5&9kVg+~]"SY/+}e!%SgF9 . 3ZQu.k^\3P=K{k ؒ3ҖV[#I;'<>ph[)@dGerm%IņUͥd#%Sg{q[ ,vIۧg -wX}^KJ q@-mIF:>nc ;>Ttь!x-t⪾d$mά w^Q$Bǒq;P3dB?IL j՝ckn="fK32^gnc&~șš)9y\]`dMwX]dc],6g" 5u)hCUk[)0I+k,_biKJf5kx\-2 $Y]eqJ_3ǵ\["rgCǥA_$|aS Z*aCJ +oؒ^}VVw5kȎr<BuNEP"q5G#]!I{DZce`u.uYl*Df*6_b!%@ؒeg I~]}/6%مZ?dի_yyUJ>U;ʖ˄W d᫫e_exu5ǎ q 1l JsWoQfY]]Dt,sE8fH A:_%'k00k=EokZ_"m\|ݾ{½KvV{-үӿ==R"c'XޏOɏ7>|ہܻ4w7~b9+vURoKwbcؾOvxkԋ*/\ޕtۋI9qfevc닽iYyw7+iVŎL}t>~NTn{$_nGk}WGkAW.} @o]b?UL=,ܬdzY4;ӥ?)Phwm[_RWϜ+w%wXÏ^>ޮ~/^͝WOݳOW\yotGƜu\;ϋLаPXq5\i(k՟XnpF3K Y1Hׯv }\X7:rP8Ҟ}X{>it ]"Ѯ]/}fb,R$r\pՐI)=l-^ekJ,x Kz7L91.?Ě:IKubM$9a Wѻ3-Y2lIk$#]bْuÖZ% <]]/%ɚB+\UFD&hy̰%0[R0]qNS-nRJ\]2mx\ǥ-_քI"at"AkaKF-if֜PW"C[r<5u0􍐒ruIƖlI kN,x1WqYd[Xsv.%WƖfP0KIN%$I"ameMڧ_jK>3Xs㨝$rXRnx\8J|ʚפ>KU;Q3Ip++^XUd^Qy/7 9u Ƒ_|}]Ow|+{hֿ+m]Hz܍sq I0&~r>mV$^`]|6+?L?CĽTj ҽk@Ë$=:F -]E_掋ϾVĽE;S^{]8}Mc{?K߫?e>Bm;SF`97+n@P伹.Hi2ue If``X4Q tZ $m;e}pt:&~WGDI Kilz@pLZ ' <00ƚ+I) $IJ7lF )ص,NjRe )屴잡TTHH;Z l I2n&\ѥDBJl-)HgIIlP[baɁLlI޻)T[b%Q_XQM h 'I7: -ٝI ){q+/I(T=% K ӆ-AS+ kDjErՐ )m}˖)X$c jKd`85]8˹p-YlIBaY%IKA\8% aI"L/;ݓQrwxn$߉ìk%8gkZ 'vNusWJw  :I7)eRҪ`%XU33ia1$b%%"Ÿcj@PM ,G}eMWJK-lɞ4I£|Mcz.%RW ["r^{iFSJ:95%`@')5])/L%yFwsDZ lG=lު`šId35#fI%Lʹ%$IXPK,N A]Hð%ΫH5̚\<6uI%fI"3(keْܒC= X'cMD@J%LͼYHJ&ꕻ UOڒ}&%]eڒ$4HeZp=.=%GLuwMm؞0BJqI5axhGmI؞=4[Rě؞z\,gؒ\5% ]5DXP݅eK"XsN/"k6pKJ$}kc.eK"Xs7:RJfIvM@J<} *fel95 8:L%,׾ۚRR'ȁ4Fp2)ӯU4smv^^8HɇwGW0G/pG @ܗNo_9c; @KCZsTOi&:E_nmڬrh r嗴wJ}'_"Ľ[J+$YbSm?'6ZxF?_hP }x+Xrq'꠷wgO.އpu_q z`z7z`91zW82ۓX VX@PldEE\M&̋ /uX  s!D8Cȍas kgMx`͢s!X\`X\dDM|(k$ U5bAyd!Vp & ,)X/6p 'I?z%%\6"&B ):f ʚl"D-[bFw.t`IH%VI7KJM< $YmEteMGnKzRYRr,jMK$ )aa%` ' ,)IXN$YBAH K H)ܖu/͡s+B{\DH[r%&uD80)1̚<eK"OJɔhm I2ε ȚLJpǰ%h _îy8bx\+D 5(rp ' <H)=%9tN məzP/fD8%םyRP–mpRD k@¬>–鴌jX7| )I!Up`Ip46іXmbeGeKš#yD,5kx=t ]O$QM`96oX' J݆ {\-̟[H__T4)I4kWW]V۩;=IUL̵r^_ q;O>K"i: $4ƕ @Y!?>|΅*]@d}xe_q]?z.|sڬ}VCm6q.$ʍ^ qlV.̂8ny EAUFxx 5>瞧shs;^AoYE' 1-;z Ϙ(~t˝ާW;1D, g x(uas~e6[]Y Vlc2: FfZn}tCj\x.UOM|`5$QE 0۪/~A;׵kq(6jRjzq.6QY lu26ٮ^s,ۮ=I]NҷH{EqY@\zgZ6-|,wn&fn*BgQ}@oE.f'rq@oϘ[v}3:f*,G,Ao,wݵp{w>\yJŪU_G ps!KN,&mb{ł]b W[WaagȷL.6.*B9]}<]駩<~Bke<#[sF{aռR~ JkkzIR[&`9}*I2!5-)1r|xdkhҵ t!\p=ɷsDIT0db OeK [b4IFR2yDvIDvaK#5sVЩ쓒JHM$,5WdX0Xs|YWjKLb:KRM-FG79\wǥkiWȚ8I$4X [·'lyTO֜)փoO<.Ip[r%EDɷ'pM$$–mՆuGFIr`"MkrkqmI)\'tMhKZL=akZm#-y4Yn%4*%UzyNk‰^ZL0G`| ϫI][jϴ_|]ٱc ]Ib>?UF=vaT/_wg/J8+셤ǽXBof`"Y6mVwL}mvX-=|V~r+=?_xq .{= {uS1ܯOP4sҞF73z9xuIv[z8xÇy4 &ꖷAqSlٻ[as$c)XⲕEVGM,vlB_ @hX&>Vx&Ƞ+|]=_9UK`^ש#$<"R< 8n@\& B<IjZ 8^׉,xgq#¦N<Ӓ8WQE9׭Β)XT\HcVb6R%$$Il)%Q Xؖ ID$TJ8`#mIᕴYa]!qWndq]vՑ2ۃ)*>]G w\6{]֬ユU.q-.Ϗ۷P?+AovyM7_7{;iՒYᔌh,[Z/Xkwi%*^D-6? YG{ʖ<"ZE?`-#g`9pƻ,>y\q[',miu,o4s!DVYM! 4)5XfAAD4dߜ' YR"ZXV?|jmn{ba`t>˖Y& MkTW [bh&D0H ;*T*pZ)X$sIɥש#L%ua͡)tq;vZ[ukI<`l asfKg!I}1$%–CjQޔ0H).%ujY"YmvvZn4(m%Uzޙt,X n'i瀸 i?w @ܗ)Y b +\/ofeeҬ<gŅ }Ǯlg}}'^-̅IEC52~y-@cu92KWЇu[]Q.{Xb{uG׍yr)mI{ꭏƕSz`ze9zc`𞌝7]`Za^""a" ȶ K2_eՅTYUoOW+qm ߓ| MIM]s=!BJ[qnuJ&~11{R4^홛}z/D_ϒq p7Ttu ?E]WKOu_zrmq'|i@\D]n0׶"cn{q//jʃiCmˤr{/HJ W{P*?J),_1ET^xAoEc(1zNO@~a4rnu}ze:X#^tO3`NIg_`XN`&>u7ж78YIݞ$RE ,E mw n&ϘfSoK3Y[WHvhR݂-NǴǹb*=IޞZf=~H:/cǕ;vcʎoqrǹiwH7j怣Dvfcm/fߦ=gjvr ά}؁  sߍp@ GK+} 㷹îm݀vn\H:&1Ƭ {#{g]Lq &@ofTxMNZ~~U\܇u`1+'Ý; M0JBsS'2 ك/5~W!W憻D؜dؼUOI+v Uu߼n+[놻d wAH5Vfyp MBHB^]8# !g_5 wɰ)ؕDIڼR w:E݊)X$[h5-)1X:A8<08Ø~֥Ք"]" KdM$#Np<rS'2kْI)X$x֕#E@в%I)X$4',KtՅ5 N!5#s"nD`-9ےfĠ5K%RM-`0Ib+%2;mx\MfM$sf,œqݞ KubMYw{q,lK"R&IҴ=A`]M)Rܖ 5U@)lI]Xsۺ҅J)|f'o kd y\Ӊ'IALZSz\tڒz&nO0=5R &IT-$rǤDt'dڰ%LJ#^$<+o7$VYvpME-)q!C=kK0Aqؖ-YPIzRby\] Nȵs*0IR*VH%aHtݞ%ڏmKv{_oCVQ,Wzl{VQͭ,7,I_1r]i0Jo_pr! @?k?q?ywӇ 8ڽIUvTdl_y\LeE@{85/8Ck'BLeά<: {y nF?Uāa/h7DG^ϫ{ +I8 M~}l9u8pX|;) ・3 Z|V_:{1hJmD]oRD ~^{qRQm_*m6õs'#w2ߺ/FW=ID]W[F^koC̬ܽX'q^͗lRӃrmݐcϽ{[g"[矖.U^;[W~b۷6ۧZ lܹ_q@ܽn(AoK}bLm.,lﳿJ{"7=}Wsg1O_/'cÝrg10=:zS+>8޳ѡY螭=[_3:/5F =˻JUq7lj=P}&9r:r-VgHsMh3-Z-OzϘ%@7[/y-lawrzLuVZlZG>azgؑή,_yh2Qپw= 7$CsR2W-Rc=˚fUʬ FEHGÑ{q_JJ:'q\ZTYSHɐÚ#Xhosǧ̸T[6kbGRĩ+/'[ѱ<0Vfit)q)qڜÚr|-AXsu$Z$rc)&)! pڒ֠-XS"KJ4k*&e@mK6?$Ț=pSѯk)Inӣ kKވY5+Js߄,Tlc߈ՖfKvGK5Skf̬+%RD Ȗ‰%ϐ)&?'!%ǵcK0$i/{/Y^%q:WK֬ڒ#{* IuKPLXtU|טL:d %$?7.)CT$q%BMkBJ(nӠT#K-kAօ$iKmIhx(sC}=~Ie{\YƖ`! hM=«<Zz\*3`Kt$ Jfzk%ܖ(Cӫ?#D'Zج 4}y-@ܳn+1YO_Jo;?zcJLjJo;oߪg1Xz yNpu* 軳~.U@wDk/=~Zۂ @?}w<.ݿ~￴GçN5V_^V1qGvag*q;O|8>%=g'N?Y7t*7+$?Tey8X4 8h~9MiQ(~6Zu[SRHUF7LT?_ +bJs뀪yXv+W+eRƬ~1‘J52W?DWYqXsk$qpy X9xYs!53$OK:5g2ٲGGg#kǰ$ #`H2Rm6kz^2H+Ƶq653IgP]$(oJCw5=$ !>'^ZmGDDc‘j)ZV J$e8f-l%Ҷ*nڣ\륛qwKcNBug* sRa!ϷK @O;ˮFk/\7hWH׿3ow6Ng5`}JOtPThG5;('7(֤/'f0 ք5VP䇔~֤3]Y*TAůX3U(ÚTbMnt IT{Y3xըSrM^(sК]zB5 ahYX8yְueͺ/0:h`sTK$Xs2sG#knG/|th^}ё2oxΤ9+T&#ˌŒjMlS$]>~zjd3_. qdjhkF{ZOm ,x@p]McmDȰȌPR&p" WKS͚GÚ(&}ѰmcM V1=SGք=zbWDWCX3b9ZӎwnknAG6\^iʼn?5S^T?Gq֏5鳱&5&} Z/@V'a Z?˚RxK^kR&ܬ%kG?{*qGbHA &N' NHHH!Br1A!JE4FėGGq分qp.&`O}_ꜮsTuߨ3]=:/U%85BB/5Å jKteVGx"Qv# Y v'{ CM5oBy?Ú6ЭΫO&x;;5q@ 0W S$"k3(*h+jk@t%,)ոl2Hr,!; s|h4ѢfVC6KrM[qMOنH(>Y,JxTPS±@-{53 @vF, IPKd~ sTm΁BׄA->@ Yf=k")utm@6@ApCQ3gj!DEFqK&xA * yA\SfDKa"ec$;.yvؕ11IsO0ٟZ`8E&bjf=@ a5`v'3+7?M>jhti35q@"3WIR ;BPӛ6% op!26PUd9̠ơߖZEf =n{l-F<Ȝ~]Ҡ Ẓv,Y?jΚ@(j}xjn7F^u\3Ñ{jÖ1E VfOViG㴞G nj1_}~VZv%P3;H.!tx]5-נLW.NPTouSd5G( ƓdmXeJ6|b]j ZAנʜ$|'I%u'HwȒ`k T&^t͘ ƌ/xgx2vQl5?Y+fq0ԘEF *io*W jnRIw{b&Ki%5KuItԡ5D+0bm`޲"& .E⮆l,ww*>86Cq]y,SWVW:dts٩q'f{ꔎ?Z=G,?# iwJWYeZv;>1XyIa5{m\&K>]IKuySM$9_V*NkduCuxٟt#o q :4XKUPӼ\Qkn߃wF q'М]MaѸ #/WDYכP$Rڵk$c~+xt_!{umj׎5&}tE6лa]j{4+ߣKp,K\ZH-?g Ro.?jų7|ٳ' Vk߼F'G]>79*vO~%U"3_kj\9֓_{V"B>wGs/nYnOx8?^ /̬?I3޲yt}|̔xx79d4QJ/KȒ,{.K4k¼VLItl]{DR)6YR Pt5GNe~,@H J%+YBK^h^,1`3I #{e$>$PvqdȒ6A`>ҝ@M40DZѵCͽ/qd e] 5Ic%5G\W\)zOkNRICٜ$Xh4NAehk9@ 5z@_wJj될qLk=bQs^<]n|IV~O9,ߥM;@B^X7{gj!/[_W:m[cc N$Aߐ+{oJǞ?}nǜ7_+gS=v yMجy6+*g5߸P YyRwd|E#ϸz}H˿'u\/]W=yr|COK|}NzadL[ֹi_%yg?q-ߒ o%,gHY?N>} y <;2I͢%~Q#y. ҺaХEaל\ʡn^" f]◠uc1tp-nK5Smn;b/>B;wH d-" .rxMnLVM奆M@p!yX}|,5TD%B$8^d҃~uREǢ @ ,wx+r]cD%d̺$KxY"d7 V_WN$"[5H$* Z d6=H<(UJ$Ԭ75ĉyFGyK%D y/T"̺J(H QLf]Ҹ:ʧ}Z :0+DVj\{Rf3i9=D%#G$kJTh\f}h HD@Ԝ&pzY4<D%Ce AM!=1jJT2K4.>Nhf|,kABԨ)j\DIGA"QTʣ|]иd2 y-HPs_Wq d :a<\hKT4.#ynqzHFG¬+4._Y JBb2q\dtjAB=WFͮqu%CJ5}݂Ƶ1 @2O:tJӽDk$! hA2sAqd9Nh=$d;tKPb:NT .ʀ K􅞻8(J0j*ǷoGoQ!c/ϝ''}=8$㖐#SJE⯐<8oDϏL?6퟿F}۶뒕v\m.=뽰I9ebIa\ {A9+ϖf SDB QcOu}s/sH*c)z?d}&}K^Io$Y'Vxαj$eL&Fy,w1,g&dǯi|~I5>$[4Ytц`;+Z;2d96K‡`g=ygtwy-FL|]Юb])!&>K$tVun'?SlVO%zHT⁚&l!>;?5YJSnD,E>Khp55%*b,9$3pMJh d|,J D*4 I)E!"|V0ԔM$KH5բI%T2CtyA55Ekw,MCz*S`%*4.% J,M)5 1!Ku>jIV6!o>lf|^;OyXW&_8JTy kBٖ1\K}EGfCLJ`"?8\u1,QB.MB|Z miøL$pmNiRD1c|ث\+Q ICMg;yJ ! >B%|ST|B$dz4dGHjZԆ`{p+س6D%|S"KP eVEܲYJ:y M$Y=H GERJ,$&盆5WPP.Ц)Z&ieI(H覔.JƅR,!LM J%PӤyK+KAzB7 *\̦$YN YwLF/$я gYI ١\}g>eA[8! i.VHSFvn "Bއ<}Uo)Kq|ww sQPmIYNg(9Nodg~mk$|9lV?,lVpb%c-k G.Ԯ~TYZ$Y9ɹ-\*ظ5g-P0wܲgU5%*)AoY6? noةjc\*I`$֮b*Q*1BNBQWILdn8jxQ&' c* 8ꪗ%fptbz1jppFTrDq!Z ֊,y ym@s&ҸHDYm J%)y7,~bCT}6 $P,O@ܲN8F%PC$Yǭqe $pNNh\$^uM@2d[f$V2&\J%vw2ָ:$֤y b6JI%@@,9$ [ !~ OX$AIrXo%6KAFMJ@vDة$.[hP3 6*aVи$mШ[Ե/pQWv͉K@kN$M`[%Sу!͟;飊GIb;mZ&>}1ߏ%B?GbiWp~HG;rCoK۶x뮄oԻ d堝v\~ێ~E)cY7fsU!!9tl)J2! J>>}No7^_\Oazg9$>dmW\K|,7OsC[HI%#?=֮> jk(͍pd'^O5p<)FNs!!B- .MLp$^k8YׄY$&{u>/,dQp+d9\ëÅ]ۣ]s!;q ]q [f2I3r7;O6ks!pM@M5XAV2D=J }C] , ܽ? 51]t,*W_ΐlr Z]юNt%5I45@2HL{{ JԔ]q k**/HT" NwFd `!p^]v Dڊƺxjװk,H@-  x[r4%Tkz*тD>L&i\8ט$h̳(汰0+8:ָp q PLtE*Iqp# ɻ<8D%8װP\- N+AKbjԔDy,Ds *r'$`/$ \/K}PuLœvbY2%HZGxHT"DRJNXE>Nr;hݦgj@} fm.?a#Cp{0U_E&iOGkL;:# E>|xV`aj/H?w%4C~>r `HH,K1{Irvil``#A#Io$.J; :B &A•}z4G͑P$;`*XLdJ~>D" /Hgbbw *䁀],S` H@XZюS vRrdZ {Ȩn|L!Pɞ~@wZ{L 鸭RJN @➇~GF*$"ȑ]Y = H*{>焎/!yƥ% #'P~A$Rq%<G2P\ppcႉtC[`硏g J1 .whج=,!,~F⟾g!O&ĭvX;im'G[wQrGsߘ=QNo;y{kfyfvgA@s=1 q6h7sL'>k\9&.=bKyE6;Ψ~HzCUGr:^h4[]R7(oVr8nI[SI=Ʉ8Gh+j!fӎ:1!mnt`Q8">E9pC+ȱF֦_g /1D@֊8@$rU ;Ԛ0=IuH~ A33ldE>DJ$'UywB]!!XD4!rH6I%48W$},Tb߾n6 I(..1c' .*WmCC8h`|ԡj~}e* ,A!hg@~b \ $Բ H@DS,hgzuzƝ L+$PJ{$Yr⮌\Мނ*ָH.Kl;a yW\:FJP "YR"d5 B%,G4p+`$+y$<(C<$0AZdI%lTe2'F~Y.TYBUE`EY£؈U{=`**4EVQfR N%$SdIKF', aPsDUiFBbU5}FO;~L{|+(!.8 L͘zq+jBܛ8sgĶ/)vrla[z'Ķ_u}ج<'gY1w;Z7krO}^?듪?uT'<]p_3r9r#tS,a};T?3&*y :`(u?<b]1y!H۰ML, Jt-nsb`M RQSJhѸJ,9EM$NiJr Y0\~j*@Mc{8^J0XEQьovgf>Oa[#lOa^Uglkwm`BNm&7?ZrDT|ۭcL\razcFzFD7hқ,w\슱}kb7n!(\1ݨ7:qfؕ`I0Gto%s "' u bD $)|dx)&ԣ3h ($APz'#Z8qݾʺWaW5mߐ $!)FVç9:wѱv 3υ0=a8Tg$(pD1.$v%!XQ4wBQaW5î$=HM%R1)%aWEdL`ADͲ~tWy*%S~e N3t5G -DиyK%oPCZNC*a7:/4]i]iVҸ̰+ 껲 >f534ҸߠEM`0CzP eBz ,ntHEv!PYF^ 8G)KLry7Jh}H3?ڲ1sg;6-/^䷟BP=0 @J Y'x$=|#k?> Gs#gV̝~ @x1+75"y|><]VlBhI;k#9=l7s<NSn\ afe郱 ;^{|n,mr7cWDffhMdh=nq(kd9f&?~PD1' qmHT(!X3Y`E| s͌" m!sy r7i GCfۺsZ0'ZꘇWHWh<]C\WySk cO,GgCd!XACf.ѥe΍Р5SJiɒi,C+(lH^PRI-Ih\.YBQS \Q "`P oHL*DC,!Xsy%x|"H{VtSڣҸhyB26hUf<:DD%$k.I!XsIJXOݵ^FO: K$YR!B IjHqU34q5{/Hy^Qp,GoHX?{nq d_B0?H81w9ː;jY $ Smc5eæ<4e,og}.ãUI=XA%sŠ$I-Ɂxkq0z%M`PSt[Otӆ$DdQ/KX¼xJ_q5+eIx䚵=4YA2a],-hԘa\u lo.9S .n{nc4x}9cg;}Hl S~|$̈́)cT~=Y{}rI/<{z<{3tnrͽxn~T"+KsbH@aͿOE+ v;E2U u\*~W{aroxQWzJs'QPJ<[^ectfhwh j~h6r6+CShdd],H1 mի9JNVM:+!KNa*a6" qjfIqU߼"KлPS5{H`_o.h'מZI=5Cahq\zL80D+KtփH 5Ԯ Jhq=ȒeB%H,u7`bTd׸b%`e,vT .w0yWb=!kի#y|ZmOco^CK>y{7~^9i/^B/©w MpzsûTޮyLZܫq*3O_gΞ mǎ7cS[ss[>+_4ݴ$p~yp>^IUak IWc\ٸ6x/^$-ŭʕH#%^| :qK'~, qzk{.wYM-ֱ+gQq+{¬YƊV뗇 &uKEa"LZQdҍV6A6Y$6/c{KPM\iX$\Pӂ|WVu\s CPh7'NؾAb@9+\l jy>$(b͉ <@0CfYB>ΡfR0cA/q XfT a}ˆ^Ӆ5u'w{l`#Kش `Rr̊EWxV*3t$@45`i4C-pTWJmj(3dq9 J EFi\ ԬlAQMi`myGRy&p #b^QhcYi\z5!#K05QQfb"eqI8-zScJ D@ |&4HR̊[8PS̞|NiH*q bM%^[Ab}B^qJ77Drҟ5urF wμcf{8P%XmW?|/U fy}W7>NH"'LK^f`eʇr_#ٞqERI{ Y@!h޳Y} #H3 ^(j-k(x[g @WcfQ4otC@0kXv Nx)stVad#iQS2G嚐Ps5Yʡ:*dltMHVQhGhYqB44@C߀`U>*T3 zzφkl0j6^ʹ &:(Cs^frՖ50rF:24 5~!k: A 91zn} @M?D?/xZ} ^Z R+ >= |ԭX> (8xBw_hf*?bvRx2P4]d(jBj^M}c4\A*MM-6PL ٜõ> b,RJ Ppp54ܐX[pё)j65,8ӗ*jT_~b8'A=yVtrt i\ $w'e+4.*5mI)1nKi5ea`$8ww=P\Ӓ(iU`5#!me(<^#Zu 5Cp`3⚒9+HHa"1|Ƹ@_6P4.;eAjCM`&VAT7 5-j6+C@hI+R%FZȪZH2 ANig˳.LNiAjj^U&hޮvBA%i<4.gL*6DeuMP֨ /G&{m5/0>78*:ZWY sDMs a׏EӟɛskP3͙s0CXy꫍PW2>_"d]&3Jm=Զ\nuتWiڢy"d:tgd:%`/0er‡_svq_ ƸF,f9zY> qyB\)o;׾kٸv#-ŵqm6Z۸kW qtۨFOڙk\nĵq~\;ĵ'qb\{]Iako\T+o[͸v1׾kg帶ƵqׂEsqzڳv/-ŵqʥ2=kqy\khAF:т8!R\yW$qgZ+ĵ H*YuqJ\+ŵQC$ H*B%Qi+{+ k%}^ŵ*Hp\b te8Ug\tK$K kw+`)4qzܲ֕*r$d7HN§QZ㸶kqET9WJ[q?k#qv\;זqw\;FnŵW^ĕG\[kZ '+wqc\ƵcMŵqm2kw޸6Wvǵ]qm4D\kqm.ŕ=qm!kqm_\kjT$j y'tH* s $l YI9$$H60\ &=H! c@ҡI(`d% $TΗJ HP*Q!_*Y͂Jf@\W$&H: lƵ0Y"Qɰ$ t0+IS:YR!Hm5H$*iR݉ABI!Kz$KH,@QIփJMdYRI+ &$ $AʸRR %*:. 9,Z@NﳾT2(MvL@d525[%[ cAQɤqTFL@H%,PT,q#jYRIAJ(H:D%Feɻ dɻ`*٘e$MWSH6nI 娤ȒwH:@1+:YqSkexvޅSIn7*頲D ; %cAґ-H܌KU^jHL!SIH iR+A% W, ;%H:x/P ո %RI$M$ =H=K$OK%VL#=Io"p^CaKC* Q+}! p@0 VGQҕs^FP94c+@wػ5 OЏdf@_HCюX23>S[;ĝJu.T;' v|gx`WXۢyRvjk;VkxWf/hVbxuQ[ |5φxTL]J!]MjX-+[ܱ$9Hrg$_6HVs @RASɗ[|S _BWV| )|9 ɩ$9HrkӸVT%8g>?;—c|Vjǿg#_c׾z#SԼ%?2qdj^򒗼%/yK^򒗼%/yK^򒗼%/yK^/GGG%OKS`C~3<Ǘ{ qy Vh`k?6/_(j%LWvyɡ']^rEqrѕj\9W>K8\җ[m~رяNv2ࡡl,E7+ Z0Ԁ=4cL F["9B]qa Z4pTJuرaݎ}-6{M aNFX2C!!ZZ (oKooܝkGr D N0if%Lh;6 ;5c2]*Ȱhd@* rS}<e m7%WT"!3L=U UȞJR[ 1S: :B*b4D#A'PpLk C|҃$"pBEƕM@KwUBя~ھ)::N qyB\ qy"!qy\Gjʼnh=!QdP4zoq \BE6>oFS4+Аhټ|!'@-hu݂-S aoe+uPpAH8-eGh @hXϜeC";n6|Ѥwr I#l61(ϐrL|;mPmVh4f7c ٰcKChDCoNf 6KyB\;]XC\Oo{)OʒW%OT7|sL<u!B(j o+6wlr6 v%C$^n; P?ڮjX@F7jD n{\ڳ{){H@KyB\rw^z8vds5'5**O\SJCH/F >hs@(p ;4(!Av]|# \ ;@"*ewuEc@ΩOK$_(~ŒŚTQ@,( H79CTA}9X 5@J{F }%gew 9ݾ]Jp~TNw$‹j{j䞇nT.I9Bt c)5Ռ ʡPwB%a' qy}muvfymۖ'`~yX#?nǎg{?AWg_LD=\ѽee#rرZF?v\._zz^9S_Pt(7=U]Z:n* ULsl[m2qr_rgy95D vZ\:v[cx (`ݧN Nl ֦/ZVE ; ˧VDoڌpffc~SG0?ʅB߆_fjs(Dm[3r:ǥ q7>m;Cܷ;~pٖkG-K7wܿ2!.z-hor/U߁/ϩ~4 37v{/u\9`;wޘyɝo\-7`㜧+-KA^<'C ])־"ݕ+ sRdC[۷}qEˍKoojyyFW6G[O^뽰Cܷ=`i/-.hZd#!8V((MN_,ō_-ʝ,)-L9:YWg ӅB+ԙ eȪܹ͖Ѵ?xd7:p?KRF+oUPzA-ԮEl7'z5McAq/jiYt9te@ǃ쀄^PYeZȣP3WqGCS1i Z`6:Y_I?&Y+.(,%B}Eeݕ;WoX2!a…py5(۹f$4LO)}%} 6[̓$i\lE_aZ& a3cCVJ ejTyEI^_t. $QBP cb(dͩ޲%K`RoD%V, AD'^BI_vvnY@5tiH b Ͻ &+ICCGQWE|n׸zK I4 c%AW GA-JJel92N+4@䷈-UIh@M ]+H,9ji\6]Qfk1 "QtŊ|.ɸ_ 6ʄNWBZi(md`]qU"C8԰e[&1I-uZJWmвso$:nI֎&1@xU>(:YPy3PcEo6ڂ$QF9n55yӲ}hh_\[!Ȁ>F83~su[+w~YQ<]W[|LK%$y* G?yBWm?q}^!ȉ2j¦Lyіp=@r-RVL2˛ljALÖ7n WZj$惂ɹxnL$T,aKM0pVnE)Ӵ ~0n 7n_nkn-~fp4y,RK4pKW tV2R-fai=F7ev'#uԫHf'==|?Ǭ\׌Z>ʹUs +Τ\'{ʼnq!|1^i=Q.ǘھ+5zw{yӦ;NCcu#y,w;6qڸnZTͿ*^BsKʑ$ſ\V^_(]hs+`BMW%Bܽ4;'loO RK;@{|O MVe*⾸L\ӧ-/%\!NZ|8.|lK?vL^[n ,aiïY&ۼwzmzaSk7ZTZ.0\޿dR~Kretƞ3ĮRnyQcYQ G%$d V(UaxP \0h[V<YrMe3 `n|t%NJ\@U!efp Ӹ`[!'\TT.9ge03g L3R t-0kpx b`H=U:W_)7P57My0F#o o:u <-@M{]Gw! ~LӰz(-僐%jڟ]7K+Y2[$'(!XqxOij`%|tIG0 Ό,)ھR9I_4I v  +oZՅT ,|BUM!~K'p cC\1Wy\DT): `JNs"[1mHV!`gpi,dMemsNԱWNܑ 54 [FgM;TSvdRdiP4>Iv`Dtח5[wIGMƚ(VJPn 2$PK8_`ѯbBQD]IMYk NFBQX.)ߴ0설nx+=--&)nI+(M BY+P[n}ܭ8+ FvZS%Lk6VoEٓ#[@yZD`F E#H?خ*,RK!pk_zʷjܚsەq@҃ri리zR0pg!+WVK2_vk1WŮO =5ȭZR1=j'_OwTb qwtcҲeξLE|t<姈KߖsLkD7pgװtI qtx>T q[.}_._]/?:Yo]oqF_wtioRiV}gk/_ds~K fgB?9/?]O?- F :sN_ k^ykk_t+~K˭ ɱL%IB8zgO7W[/׳wkf֥+-\tyވ EI/+m W <:oYnD(m,\Tcaߜc1>xviWnkFٷ~[:}GؒVz>D5ڰf<[ ʚ Y5\ܴgzӾf&AjY9FMƒ3K:܉Mo(5!V5tgJvH-/z)fe9~Ah4[M-qf45訡xV RC@o(ʤ"Ft7Έ AiXEhvIÍKb6h.B=$iVZb0um؃d|B?}˰QJK@0Tw [MMK~@}Rv%ܱ [jVtMP{K|+iyR@vg ҭEoE2 }VmXAl$:v";HAeo`c`yow_=_ 2P(PS 9tt DZJ-@ۧ˻+ p_[ RP۬I*xʎ feи+a2ʤ*n  Pwd8fob.k9|T} #n:tMa1ob,$tEhB­NА Xvb*2†~'VZj<0l1Zrћ7eSvL @'S"7m ܆-7 gȟDa_Ig0Yh[>+m:Si)̜aC]bnJ lؓtӦm!RGuC5yS&4T氥)p Oʅn ē}xe1LjOݔ[,%-3~&ߍ /LHS%^s=W^Τoе| 9sEE.qs3fBD3XXKbTcaɗ;%;QxP Vvm!T/#D ` @ 3dK~1ع2 h3` :NH'&akOMXq#~%09 6IY%GnU5(z~ض܉ۅZV#b~ȯ 0p'1N ud_&wln7/z@itHÀm޶Xp\шA3uJc0i'*Gsy˔q}ɁxpWbydCE,脞[u8Z]at@#˰[~C-^>kLHED'$wtJu}qu.M쐐Y4Ge!1Uc$<=pF/=A+L#vhHHP!A&?>i?$f5lKnN"Hڜl6JU`-0v1.xʷji 5pi)QJq`{z.th Y@ ΅/ i#SnO1)Bx@U҉:j[1 1F!Jeѱ^t1=PM%]kւG䄦ն/2he&=gCr [d OyJ2s9}@e ތ=dO9Wp XfP $c!9- . q!@i\ i5p4,%oY>DMO33nd炎n'M\)˚ш`- i:.Zc%,F{)fjحkAM/ HQmz~Ս<01 y&nvj D~J*.V@p Q!{dD&{'j 2mf BYH5'bDŽ8&ı$gv XXw r['Et`ek? 487`(qQrzRnwm18Di(B'GwcD kHr`KG![[`lHCϜE@GpVV'Ozހ(j >PLQFCS98o&u&69H/t5&&,LzqLS_)̛7dk6gp'OyeS&C3MryR_ '{<,gŊsqB(_Ёa_ w]\BV &6L#,4бd2) dž~'v`Kj ֵ:;-Yӹ1^6M챟4BCx+%96:`u d#*i[4ascxvvhh2v8pѶv9P<\Z3#ІLbY͸]mc b@8l>4V6ɓݞ$1us Έt0Ϥ@COk[!Sjƭ?0Oe,^׹@ӅbLcBX„8&ı%9<`uwK#BVF 9mF {SC;ĸ4hCQ0=bB9SK(N>ݱ|#,)[DoG1(uqф{8;5IB 5k"0)/EanP 3ι.4@MPPR{R^8UqTˣ- vעOo2e>OU ,0@q3689qDN(ąR& 4+$8U`!E yuhh]09i5,,Κ{3?ef ҿci;8O Oܲw#ozku[N8p2em( &Mwuhsdkǟm!Œ?[mNDZbgJW {AiJ,`MyxU=C(:aj!$ۨ<উ(v8:gK8[꨽?$M 8U寬y#^t j?(6CSVdyє@4ϖU0z\X b%Ԫ[8Zi+2Zet{ȳeS ceൢNDa* ,frI^}gE=SFsu؋Δ2#kCXAi4xE[e-7;I'e㖖M󻿛bt㒁fY[TWFu-Kf4{ Iw?YqWv*Sgd% U긭'.x^d)5١UFZ%q[E݊.4_r٪Z#iHumDl"y?'gMVoVn$߿(Ee⦩ JK^Tתi"#uִ Q WPGӼӟ5ޔvHZi'0xy_O^a#[؈2.(5U$ #Y|eljx^+:"~A8/C6=t][Q4%l$w5M+DCh+%}W[]=PhuhzK[biLK#dYG%O|ȆFUMK=SKP2^ E]a)HX?"r"ZNWZ!W 1[A =f?,lH09?,DJ `TVvxd^C!Y}7U^BA!yMWx5& * FIMD=mnVT#ww>.DHKg,!u-:JK>tpf^dgLyfRύvȳšG(FɦYbhURkm@T&7&PύvP'i݉;nj?PG^4o wcH?o^U7vn<Ӥ/yȍvS?jwKQ!7ܘwrT%Mȅ hck7:>;LȳWos=O@6?m΍v'Kl-R~nȾ'(=sݝȦIϗ̏i&/I~|+o@_sw QF/U~%G84R+G?]Ǎ#ԏqnfR1Om*וWdH$8ʄ8&MBoDŽ8&1! qLcB\ncLcB\2 qgBgdBrjLcB\„8&1!.aBkdBrjLcB\„8&1! qLcaB\W qLcBDŽ8&68&ĥ*\! qL@DŽ8&4DŽt qLcB\„8&1!.צɄ8&4DŽt qLcB„0WcBDŽ8 {`BrjL q95M&1!.Ʉ8&4DŽt qLcB\„8&1!.צɄ8&4DŽt qK5DŽ8&e7 s8&1! qLk qLm qLKUB0! qL˵i2! q DŽ qLcB\M qL˩i2! q DŽ8&e8 s8&1! qLk qLm qLKUB0! qL˵i2! q DŽ qLcB\M qL˩i2! q DŽ8&e8 s8&1! qLk qLm qLKUB0! qL˵i2! q DŽ qLcB\M qL˩i2! q DŽ8&e8 s8&1! qLk qLm qLKUB0! qL˵i2! q DŽ qLcB\M qL˩i2! q DŽ8&e8 s8&1! qLk qLm qLKUB0! qL˵i2! q DŽ qLcB\M qL˩i2! q DŽ8&e8 s8&1! qLk qLm qLKUB0! qL˵i2! q DŽ qLcB\M qL˩i2! q DŽ88t9qUDŽ8&1! q\! q=0! q WcB2&1! q6M&1!.]aB0! qL˵i2! q95M&1!.]aB\BG֮hn΄'aDŽ8&1! qM\! q=0! q WcB2&1! q6M&1!.]aB0! qL˵i2! q95M&1!.]aB\B„8&1! qL qLm qLKUB0! qL˵i2! q DŽ qLcB\M qL˩i2! q DŽ83qmW\ssWOh{{v˛^Qk_eMC qKGEk$[JsOt87% C#WbJo(SRtnt`F #o8; !h7J}V"9 q(vBn qȤt?L[ovK{ ?^C8L5cN;!}~2&%'M6xzpB^a}mL`V q_k Q{;5iW^BTt'Q~wz'bJCWlfk45q PkL#cqc$_dbڕWl5by82x:+ qP(ވR6Y qv΄8=FqX ;!F#Hs_9r7B\8G+'ĕ>_ %^8L 4:E^PqQ"> !t[2rOkqG.Cqi oy0!.Zpxc+/A_ KW*ūÖqqkzF cFBܡC{)xiіb0)X~]jscS!x='Ǿ\kD&wo0қ.3}ϭVqGm*}7{=k4GLUi}%]W8 dBy(h_=|g6_5,ߨ8 v!N4ߐ{S& zDqxoPmESG!đÉgަ/F8҇q[[f~̩?y[!!A_Xɴ<ŮDl#ϑ;ǧ%* q66|26Iw'oGZ8vn8JmܢGG)˹籐tGBzgNv[pNҜUB}s!Ĺ.K5 !YqX>%j⒞Wbb.4,y,>! !.z.KqE=c!/y.8g<<'emn[S+đsaLXPs!Ĺ<'eE;cK^sXvS瞻2vaLck!EJ1B]l]S'e:kk[e7QWυ#⯌|DR䯐MӅC;wE;] qcHj[ڹ\sWƿ5EBs]CsW|B\Lu4MGe(%^q!ĹjK+9f%đƌthIQi'3!QsLq=< vEw뚻~_J_j88o=T[ؘBёϽӞ{cOi8Ӄ|>zc`IGv.;|ggW\}Cg3|a0to ~pə;yJ>zMi!+ݯ :3D+}q2!NO튢K9݂{gKs@`'NoM 7eBЙGZ~ cQl%!3B#Y!6рGp!nh'Zn3gN9u6xr 9g?>t<7g~\畒s:BM;GčI0u|ImI흿gmg8z7HKX8v]%Ľ啿! !.F_7k\stWfLsW|B\wi|:2:N ;W\q9+IpwWʄhxOXedGO=V# !=P!8et7 8/t~58JOK81/_P!NȯG.j>!KlR2$R>BppRi̢.]~\Z8Ҙ1 Ko=--u#!N qTKLKS^q}!n]]~]*!+yڲVB\S!ǎV! ?R}]j#SMWW\}#2 4Ro=/<֟?Á'wEѥ7Õ~W0jd2x4 S qa7zE I+h8!bnGZBmg~|b!_mj=shjp'Jߴ~V%?=|dE;Gđ|gj?MʚǪgUL@@_/by+vx# xW-?R-ڕ"4] q.MuvJ+v@fk;LҦ,!#! n2\&6k>đJI'XG^8z%L4'>feBE;GB]OLˣ{(Y}+!i 9hN Y<'BrZ!6i:✝7B\ ⬝rwE;c)U;'BM䱐<'Bvԏ(yK@4="u qXG9z,=7R<)Tqy,^Og_꧝Rlϫ27MGel )P%s"9]+&iW25mPa4> u9\sWFQi:CG.ʉ#DB\Ʀ`)%eVvN8w\cYk:stWfRܕtx<7fz|:29%^q"Ĺ<wqR:A$%k:Uk82qBH#!bN8tBm8@D_IwJtW!ąit~zP!F3WΏO3py{:ᣑ#S-c<` +DCvBe qB'ӾoC$ĩ=$.! fR>m殎 !W!ĭSZ;2upȑFZh[Sk &hq7ה_GB{F*W:kye:/ǿ{On_=2чz쾱zG0WvfDiT᪾oXL~^+N؍X:F3_#/ʗ..&T;vʑ"F yoL S~qT[Q{823zd*:qI|}T‡L=͛?q#S3:3B\T3[f$ٴkCxdjߎ}CgX!Pڙ{߻l TL%_7 U+@y/l_2㑩t6k,B^]LwCn>IrVJ_Bes:2q9wX$_䱌C5M#S]sX>"籔LMv^qc{7͏rd*A;#SsXhy,%Tg?ڭB R>25[tXhYXGs:<7z2ӑڹy,(y@-ycMٞ{ʑ `ؗC A7td]l|djffcdrs:2Q=Ge04n,MTGݕ=!'9RnQ>2`NGjqb90?VML3!<cA:?bG it~j~=tQcTX:ߏL jW8q1=Q__de3⌗{ʯKG?#S ctdС{tLvtG#S bAiDz qiޑ77?Z%y2!$ q4*懄Z^z]9=)Բt&&!hc7L_H7ryźz]'Q qq{S&%U[9qB:*Yoq) 8pv3HgMGB\=ůGR'đύvT*8*d,RMӑWOyg O#fkO#iw3Ѯ$#ԁHH knjFC3LݑWOa]g?4)HG-T|B\+wP q$܈0Mʉh7#v>!!-?YonQvBak@;L'MYrd]~?7ydMOs݉#T|B\Ca)JnnX'(=sOIϗ̏i~| IꉨkV*ĕd|%*q!d_'_rn\h!.v~}B2p!đƌP;Z"ĥ`B\zo+@ ׏7ݾ?l !.9z^W'a$>.!.FKFƏUBUDB\lGKVNBM488\JqOkR%Y%842!2kR%YMJJqiDBU q4D.odHҸ8T qV!$UB\+wӷF.odHR%M%JJq.yy#đa qLƅ hck7ͼHR%MYq qɚfq$&iV qV!.Y_3o8T q4ΗGF'ęJJ qȡz]>JDB\|E/⒑u3 !*zB\lGK+w;7BmWhn޲+^dB„8&1! q$%!u } qy=0! q95M&BDŽ\&SdB&1! qDŽ\&SdB&1! qLcB0! qLcBDŽ8&=8&ĥ*\! qL@DŽ8&4DŽt qLcB\„8&1!.צɄ8&4DŽt qK5DŽ8&1! qLcBW 0! q\!.צɄ8&4DŽ&0! qL@DŽ8&4DŽ&H1„B„ '2! qLcB\6xL[ qL˩i2!+4DŽ&SdB&1! qDŽ\&SdB=Y<2~8^Qߎ=㻋'NXG7BWcBX2&q8&1! qLs5B\bLcB\NM q\!.Ʉ8&4DŽ&0! qL@DŽ8&4DŽ&v-c=-=rbHOK-=-#c'{;BWcBX$DŽ8&1!+1!.&1!.U qLcB\„8&1!.צɄ8&ĥ+LcB2&1! q6M&1!.Ʉ8&ĥ]H@k8>2R-=0R<=B?BWcBX$DŽ8&1!+1!.&1!.U qLcB\„8&1!.צɄ8&ĥ+LcB2&1! q6M&1!.Ʉ8&ĥœ#GwG#SG qLcB 2BDŽ8&5q8&68&ĥ*\! qL@DŽ8&4DŽt qLcB\„8&1!.צɄ8&4DŽto! W!+1! q,Lp qLcBp8&1!.aBkdB&1! qDŽ\&SdBҕLkXBWcBX$DŽ8&1!+1!.&1!.U qLcB\„8&1!.צɄ8&ĥ+LcB2&1! q6M&1!.Ʉ8&ĥ+Lk\BWcBX$DŽ8&1!+1!.&1!.U qLcB\„8&1!.צɄ8&ĥ+LcB2&1! q6M&1!.Ʉ8&ĥ+Lk\BWcBX$DŽ8&1!+1!.&1!.U qLcB\„8&1!.צɄ8&ĥ+LcB2&1! q6M&1!.Ʉ8&ĥ+Lk\BWcBX$DŽ8&1!+1!.&1!.U qLcB\„8&1!.צɄ8&ĥ+LcB2&1! q6M&1!.Ʉ8&ĥ+Lk\BWcBX!č!@˚:ɑz]y~?;"b7r(sCKMˎ&qtHƾLV.ݷ4}!!/rb"~Ds \Hnc_=ɻw '#6;?C6?Susnz_Ny92M9Nu>w Qvd"oB6MWiוY^Ǎ#ԏqn=]a}kߦĜϓЁ!1 9&;@:I@ҟҟϏe xzxOݮs$ѝkQ ڿ+7I7aWvqlt3ɐ?|}!΍}Fɦ9Ny iP,?I~̑i"c#/?u MF4 p֤h%Y=2v?ʏv6MD~;JV<'? O.oBh Y˨ӈWˑiLh%(M F4M{&?NhF4g&=Jt1nȟSd4{NRebw;YCCfOI|}8!9RYҗ-n߅iԯ;4DŽ-?,zb򄒗'@j\{w:K沼’#?SCs63߸Y}{[1! q,,LcaaBXXN@+ K SDw/LcB XXDŽt1O'`B+ hwm6n؝2#ocU;A<:^>ÞBMcxK:50! qLcaB XX ֽ+{w9B.1_P^L>M`Ϥ uXF[&mBsWQA LvQ8ky^U]LY4 ;"WN{ 9g6bL8 f5erQKmdB„8&ı0!%! baIACVcaaai1֎faBR|ݹUO^R~jY=jlW\շpՖ?ݻߛ[x}2>ׯC _ץ`&>x'|Yw=4ܷهbO߻>nba| s򇿙1!ɧ-_76'T[,[GJr?˕}z>cyޟ/~?|ťf^[>8kw'ޭ,\|9 r݌@H?AYp,@ A8:bͨjwM lf>$%HԘTr]a2JnQEJ?F|G!I*R-py0"2yy92M;$˦yiwn|ּn<fV*,Br.K5~!%frOߞi^欌'pio7Ϛ 9 c,s?+dɣdm8'51woD,da0JLJdY(Łb%$H?P6w+o3iFdoKެ YLi^m } ;$4Ӽ7 tØ53*n3.BByg;n^P{-Y+m YDT4+T,kwy^]gIy4Kh0kfCFFJT"~L3f~Ls14-B8xIyg0xt]ƞiΨG0k>-vg bkz\T$r;V t&*(} C5z2Ng-4+ֱ8:qTLW*~oGqB9VK3٬'[{2xa'/*֧VA>“ q<2)}5ޟq;-6ח/?sy2;Wp-^w w[n+hwx ގ"V [ѹ1z*>w޳|K~뽅AE}(-h7xt+{"hteY~sIak $\ 2CMdR^臅V=LA |m8r):HpHʰ3fvD!ј& 6J/t›sc($:,{}jD AԬqH3 F= $Q ]᪾3UWOITWvUap~ahf($yY1Y <:$ ;W-ՊQHpZY̷ v $h+pvVV :Hp0HpϯLIU$y zW]$|i^]ꇌ~$z$zB?*[1fܕtx.'2 CΚ($vQHtT}lg;Ɖ@k@v$S!<)v[GDa-zj?V; 4Hp\D32O-$DH.nԓF ,y<6"W2XH xF5 HlʭZCёշ\\U$we$򧈐| mFxU*YyV H[?;E*[!_eɮ߃鞬ڿD}Larז\*4yy^BծsWvWio_ˇ]v2!AUJѱQԻ= 㿲1Ln L.SBܓVS׶DŽ OTޕ-!N'nx;6\삚ӅxxnmVAv mvI 7K =ߘ Ҍubz P!A~r{*$>_u}ox*$II1lCJ* (`;2$ƭMq "!nڅ$U\z)П Z],NwvRsβ RȽs+כͼe`}Әר-å_ q$zb3EXiެ=LXi9id_!%CvR[=VTiJ_I{h0gڹim왶m2q}ɧl|a2nמVDXۛ)!n맦ZvKp\"cGƄ8<'Z_BK 1rb2Az |V|#c  C6g 1QSd[ HiFQ9^יv9ƃJLSdi >J &h~P $mx+ $fu)X-$B|߃نk)X=:}ӌ Ke}!QqG(@|BNuQ@HMډ28e(@|_%yEI %k!^ n Yq! 4u7.5ClƔ1 $ؐQ§1"B">`dHEmx|P89*x2_ 4DsS8%H6EH|.S?@6_3$\%n]I;j 3-*|]!^g4!;jr(8)FzM2/}"pJ2*f3N4AʻFscôxO=ݘ@"$V&$J&5.!-_yA/$ĉ8ts*2֦)eW¹EŦ/fMɞJ+^z$,{k%lI۾:o>$DfX4ӮXwl ETmqnH)3oϔN's[f~,pғ />݊?Sa,:w_ qFH#%") 10n$`\@H^M J3(6*$m()XPbDVrfL}FH$FtM4쐀>Eنkq':25To\٩G%*$z'_QDKgLs4%d}kfIO@5|_Ə' {F.\0ڨ)؀ OtI 6HKfxBYH(rI.ekF!DHSf4'D!љ14F(A A /Sf]$pfPf_u֤67bR)lOʂ VH zHWV,Ŗ (!:^yQ"AN5MaP$:V\f/QH()hݡK}<%dOD|3~T%^C!hloR %@5`K:Bb5M8 ɥJ!l<(Qi!in@6!Yd0MioPg h($אH $uWβ 4&L-$vQHt)=)7MU;DKLY8$Bq{<I N~&3`m{$b$X,ĝ~p3(D:BI -QH4+^Ayb$lzkH6(4 ȓ 1=0;N$GT\Rӟ5Z[!שm4WSfD޵`6-{c-'˩YE,,}JTt11Ce1 qVrs1 ('):OcB\V;5f'qj -"+rPҶeG3"wFH,]KK=^w*!Nz+ɈgSO^"QN^6jg/[x1+ OoʻfN"G/b!bZR}aPئ0Bd{.t+s!|PK7/ʀqHC"ͥRf%Wɐ\mh)] 2N IH4OJ.UF6=DRr)')-]B\\$#$Ƨ`R;$Q𴐀}ٚ&a+$Rr28%Ƭi!QMSJ.UGv&  Y-y̅W!Rr23HQ!\x <)$T5 $ QBH#;MWIA `3 FvV#I$+.=1޾\R+NYD)"/@+3+T:H)X Ij§$HI:g\iL1JE%ZHT_ShNo)qլI%HBp0A@";\>=@ MxEf{z$rh<;@ĴCҦ٩*$HʿBT x%hV]D]dzf-$ ;OdD`dg(GjgX'ItM;d\*.Tx+jģ9jpZ<Bt[6n^k ܥ;`T۩,AIx++`(1*!N?w7wo3x6Φ>eqrJe>bU1QevQ\8a/ ^B-.>zW86M^ȷ)V6z%] ݺ:Rع;C"+`Êl\9=*RE%k:@*ާ#6H4N!rXޒ$I)X- IllυRQ";BHy,f(C"2% %$3P&H4DڏkX3$䒱B)XcllOHC?HS?OS_…DzLr9Ȭ$`E[3Uuc $CzJdkӛ Dr,m-H fH'`3}!~H>R[܇]RO4i1$B)k)>}D 4HV^6ƀx ,y$@\x@4OAy[!ʁOKA $=$kS8ӥdM^S>>3YS;J"P !Rȓ *$#cxBB(=lp$ݷ'd!Aąc ͆H]@q_! *2e5M~ʩ\M…~H^v-hǫ|uV;t2 |ɂ(,H퇗K|H>vBprMU$K#e%UȹU{x.a?)ݘDCQDBdBĖiDB\6LX$'q+SKʿI#{jvڵ]Y.yJ= q۷QgZg;SK?ӮXwl eeZU1P  3$UoMܖSK*t=NyNŧ[OU>E@C?I#=(a^%BHO*ʅ9+ 欸%y $mTnR~BBlnZIG9@XDTiF4JBv\hQ5M$Q Lg[&F  TK5epH0 ("`%kڦAK`Dn-'+jABH/˸\WH}),v`$iB|!`('A: sMDeHQU]C(oDcrM">u )Ri*Cyc$ F]S/$:,1Bo$4p>@, i!giY$C\MhC!i6xBj!I;4R-`%(vZH"୐ -9QH>D彯rN|т4`#$*j!^CffId8a|?\a;$q7YQlӫ}X¸UTud_c}t2*;PbijO-t?aBS|޷mrb_y .׾Y-⚗3!nbJ qIqSY/&v巺o߫ȵaʧ8k7yo)d2ӓ, k+GUOXiSgZy9>I->tԾnXu}-"cז!!NyXy%q@5KWB&OlSeRB\ݳ@Qp.-]TϤ~@;!SJrIS>$0 1._BfS{g$|_X^=qH!!`#BhSn%e: e! }9T9a h8@ %T&^g CRUNCt(m pTrʅOI4XA8ڒ\:[v )P !͚g <8V|4dLͺ1\FI[AWWI.ɧ!a $֬ 4nI+$] 'k@R0B\;]#$=(Q$K Vy<;2>Et8$|O~TTpHӖ^q jSL@ !K-?E%($QW$]f=k EdqsoMumFHLn4H\;ݹEąDs,^pH2$uj\אlk܂Cb ܪX↢'j6egЖKU$ I  u06anȢdSF,}JTt11ばc(c-q1!nBfj-2/]rPѶeG32wHf"$]K+=^"ZSd䳩'[ '/5'O^,3cc'La .> FȅM^tH 8]j]hk( |kKsz 5􃲕yQ9@AuC\Cz1LJj+א*ާ\AIC 6UL8hAJ;kKm>3$u2!Ct\Ҟ k$|>ߗi@zY%mdK4rf T$c"T$@R0@Bt^ B c؆,r%tH޳@K.c;.c|H.<3$4'2kKqDI.אżP O(˴fۅ* fJ 6BRp$:q :{: IEpH6>ןnx'eއ$Bkۙ7N'g$)&; ; 58}5hdo21*ʁ~pUU̟;W޻7V>zC gSB2q"aQ,G, zJ.*vӟˌ>k[%q^ʎܤ &:&G妈xrpͦ υK V8 fB^W`Guvm] iB]R}!k=$vݎ_*h!'HT٦p{M1'D\R?jj$h\%%xvfH $H,*ΚI/쐀 U?5fu <,mDOH4͐bAE:ܠNgj #'4>4Ry5!^_,A}-$#.|ff`iZ!%\qHf _yigȐLS"^C!UmhbUVP-ҖF!MHS8 IഐDZ*At(A+$ @=,$R5-D -$(ѣ#QD^LE֟PT m4UH"\x5d1X!)!AnW Ӗސ^h̐`*qPH @%L+Cȹ w1=$ $Z.O-(B5wX+WojMv`c!!n 0^oWrkGY9zFڵeWL7ngVO~!!N9^m][iKu uJ}Ә7ek2!n݁:$]Kaz0MaXGƣ=Cƚ|RK_Mʪ:O#TTڙvjZkm2{SX^֖s{nn[ccoof8St-է_>Б!!z+j;^?YP-]r91+OR;zpuIfI!~/hr+j;P3:I=M?5BMu_-@fSC].!$u(zAB6ͨaA =$6{P"i4ɐ"L,:H >$Ո~ <LT.| h?zu$}k!`#1:%\FIt-WC uZQƒżW*Z7n%`0E-^5Eb dHzu$Uo=bD8ɐD Se dtHzy BCq!@ѻʅze B-$1 vEř+H &$%EAWW0H@T?Ѱj*$9xU u[@3;!bG7MIx i"\MDxR935HTJ'^CW'Z!)ɂ u-tHgCb?GxO-!>\WLJ6LjGYl j&z՞}j;3K܄}ܦTB-sݺ+%J eu6M{>Kݸ6 *·:vEaUذ*֮VOppV*^>kNGy`2pYX[֝J-o;칅شƽM{ՆΝ*vw8JYVm}jZu*;/ckOtxzkS[.Uמފ6V*5]?(~i3J/ogW~8NQ % ~ٿU3"s/;[쫔N>V}=;>%{GtU|}>5p ZՌZ?$_D-xѡ`gnPk% >/IĆK]>[,=/:H $T^sW!Y|Msv$6%tti`iDpEj9YS1($g#ddY\5w+8$x($Qn{\Z0gzMD&ʫ ]Jq2DoF]MS^tDfz4\3tD $謹RI.]pnV d졐ZU^-ì)l),H!Bx\DK.Y&N;B'!jF4(wDoﻮknh/jtʭt@<yz ^XseÙ֬d8Ǚ Qh?\hY& r+,Z<ّe%vNTJDz -X($iUeӌLrR d!C!Q"3!:eFA $ ̐gVMsآ7_9%% S e AL:դu`Y Q|M҂qFxE uA!\RmV蠫7p3g $ʲ[+_ v4@,y$4Ւ e@lppH0(e6 |u9=Cr>$;_=wB65|n V#WY̚* QW=lAi=$!m\E+F,H<2/8{:?UWU[}Yi$ik: SΙهhVO#Yŭï%t-vdW6gP~Hok[_7d7ݢƶRǛظ;;z reB\sN`BD%ĭXȄ3[z ٵzŝw%qg5mXR-=7M=c-{~mwepH6[;ZUK54*BnS^>M=6dqUC-nƵJi:\ȯ(>*v(v+,S?|OkO/ŚSՋyak~ O4 Ani~تL׿Y+<3AMەge-Zrz9Wf$Pn9f+p6]?,V$!ve;.qH~iW^mEdc K(pYf␬refCR%YLS!)X~xY6ŁzH]{J?ܜi&IͯUYfҐW3gfCR +#)-wLSA2$Wf␔yjzIC27jeeICR_\3YZΚdeIC27j.ˬiR 2.46=i!V<ܖŬ"M?miH`P]?4DUޱb0+dŕ^?(b >/3L,67?AZ!A"5J:Rsy wmXΧ;~,FuRVqZ/"Y@'z4]/k,kd Y"7d{{:pz(daB+7%~H_53Z+Oszvqs=2%2tey!SC Q;ȦraL3GJi3b6+l9ujf alD6F(inȦ lܡiy ̚6ͼ&y{7M3+u`Y̜J&%B\uȘrx8 X8!G[R LHHM3o l?ꦙyry<+j-8E6͉JG;mgry; ?־&ʻWЮ|{x 0~Du݅ǵ]. Ϯ=9xQ;q[/F_<,z"8sޮGFN6SWSƹc:MhȞb&G?<}ύdtp%b_xp:3]D_on_{t}ST_?`o+XrЕ>Qy#:u:ט܍f@JƮG=.sT/doҁ7U]t{+*>TX)>^4* I4?9ExĊ;{\5cE"lSEcg69 U QW R֏X3?8Ź*1lptf<:d3T`i,E~Ϛ18HpYJl++ElȚQ)Y<`MH=mGt6RRr4TRjZ(MƢ5o=oyZg̬ٖ/-%G$9|F9xrq/l椤`<.[+q+MRe[6Qdbѵ-㊑WK&>hKtg DӀnbYs8S M5oAKI{TsLmꅇxG_ʟm=.ʖY$JH5c1Mi>[ee<.bXӠMRKf4FOfͶCH,8xC/LR4-$ibhX2ku ElіxIIfkȲ5!PY"As'I5G[\T$3k֚&)1Ȟcq' 1ukKL6A %kQ֜1J ǚQrpaDJIG5G;Afcb\^%!`:HI;"I,e**I{?QcK i.BaZsfpq4U9{, ŵ!%-EO4}D=.J`1?)ZSgqx@ՒC$8-vj^++1/%I(g Ÿ<]%+E5]y6WGQ۹v-R=KiU?4IIw5Z3.@$xj:R:jDM߯-:(/Y4ْel7_s4%%KÓ Fj1PXsG_7Qz\>J Inx&)9z],Y@CcsÃ89Ulή>h=&8w GoW?rÿ;7қ|y}=ys}}p?A_&rW\܃M;칛;йww%rgtbu?KLěepiw0! >C }vo\uLW>Y麅twttҟ]e965~ɕoۅ_18e}@ɏH'LO]2$AO=<̓ пɞ;Ys{2wwK {חѹOۗwgs#.Y.sӕw|r !. w=Aܹm;w~zp>8>GCW'|i>j3]\2=<4ZE7"Zqޮtv+s__=]ipB+$k$1l֚ݑ 8m⤤^G&hIrȚђ1*'%ʹYl ʕttX+%iLLtFa֚ NJ8k%8-њ&)&_[˳f(qtkKLuyd͑9p<5k8[BIJ5,d4'%@A RDfZCÜx<5( R2)% iVtDQJi"tqd%$kd`͡pYo!1瑒Ȗ`QSc35Y %>G{gKiRkv{#}F %kգy\ME-)XÚd3~ ^JIfJs E[r>-f~VJ&ͬb [fL  0W̖p5;:`!NJ:7;qW<7G}7ˁ^]=7|ihx:L4b_hC^لwÐ'Z5W߾M+{n6.?']CѻFJGD]Kg%׹E]9dJĚ40yLsR!bdM^kz$,+Í<0RҋwcR@`Ӓ$#U\ kƟlKw$RRRZs%\f8џ9`<MJ&XC4M-Ztl8)<.[%#A-'%}ZR29U2kb)97-e;6\\=`s52ԕx<"6#dB6:-K&'%ҍ?'%-ZؖC ⤤wKfM,x}ԕ|WcɶkrY5qْht#R[yNB3fKXsdIՈl' %B ΤϷLC.O2'<_6'D-S#0ȨFTc/B`Hm*iOUߧ>}z*=ڇ/i覗itS+DEiE頷?ha!> B+4`o1uH;z:HŸ]y}&Ptb3X!j 9:XGI v1gH.gwiV׉ne_{6һ"'7:M_C% qN_nk*ݠo;: =5 zÎkp;ztX,'%(;)eMNJP)Ē`9)Y'qRsz|і0MnKPjT;gaK)X-+.NJp#R-GJ,&ԍ8'%X$x3za&(y\ݥ57dá+9)R[ #%K%[<6eir[a,%d~=!#%\Cdg&dBqm:mIrI5q&Pqe)l b[‘=x\zr#%34)XVJ(#]"DG)#%IBk=3zFO<&5=RBgc(Aґ%XW6@.Lb#RϗH*HּzI]ϼ˵exЙ5@\/}q@ݷuu?HqPnzRc+@)GKg?C_$ ?SE<}HnjǙP^q-|' [;AݡxOo&اOC7T"nOjtZ9#@ _mJN˴^G*W {JW~++rׄ^|JYKɠ5$%'%h <.,%)-a$5R~Хm4+%rI Z|c+ӌ-9+jKI Z|cŧ /Rx\57DΚXJJ ӶdCQӋO㚒G) )A׋-:@f_5wⰔ$]&%ؖۏ8EҌpˁH %# ID[ ǥGb,l8(hKfq5&'%- !֜!ݽMFJz9k L}*%f"ά x$Dp< жl8v\+C N՝i~yCK~V9,ECցk{qo,G4PS5@\+7[Wӏuϯ8K `e,BwL[$dl­`9waݷdu;C[ο֙cwAo"orh"׽à7&7y8=Ao'>&֠J^E^H+ӈ'Vɻ_) 8c[U>㬜$/!G)x2B~]q@o,'tGKHW=sPy$ f]A`9 ŋKhġD}!@|^ġe4BL H)XA`.M5#|%aslb% ъ$!ih RUW*5QiH"&S4+gMy ^dB,'%$RDR %,hI HN8X)#C >)jvJmZ|U5QV$\"H d%Ҍ (͚D,'%(D+IbmR "n)X-IƅZKXz=AER2.%\ct %\ےqR^OіpD@&jw%,'%5HR"4k,;%,+%bh [ܖSMb[2E-.zڒ$0o\ONJ,<.`K;`1 ~/h^59)A'.%z=AlKh2x\ \׸SW[2&z=aҥ:څC'0Dl @J x\Cnkr[2!wi 0W #%,GR’V\Xx\}Gҧ)8)qdMNJX)'[µsrDž`@Жk2g_ H¶ptHJ%}t%%ڟHopRlx`$j%lV \:ACA}:+tG$ uc,t!x_'qas9asw\@Py=I[u+E.fRNW6?:TAFONP)y!z,!BI01'' |Qtt2BѾ rxxB*T_i|7ɀ ˁ=dљD06G/!)!/jRa k$OJr0M.a͚xXFF)5/ h) 0J9Mq%i! H9Ix (MRx7s$ )F)+8lIɖF'' dhI [9[?ƠHDR VF[" <"& QJ$D$&-%`ͫ҅B;qmbK''5Rb9X!Р-^k"y\auזFDZ/$%(%#^[Bd ? kTFR2x\}R[Ab CK$ ^h_ǗR2GK ^?c[>TKp h"ʽ$-LB_} qRTl`w-I~c!]4-¡ܓ5@\~No֩~z*5ܢ+AdE|1Ue :RjƭE'M8AVM82/*CZ IH!.6EW n_;M. [Xaqez(Z_t=O_+ J}Xc5IA#`x׍|␿@o,7+uG`eerЗtA! $ܟ/VVC;@ aa$`@ +ZV +I0W\EK|BTt` H$[ruHybX %>[}NJ!2.  f)1%Ĭ IZQkR0) /y[(1$HFuAH)|aMBJ&[niAʚraY010d AW `PJ XN,!%&d$1\ !PJ"t -i[$% 1,|LMuJіpMN /k Ay+,7x\0 !ҌFߍX䤄䶤KJ<(%R[5NJ  JT \2dCkr\0u Rl&'%ɵے )1tzBj9)40kf)Ď57lb%HZʚhKG5 9)+S m|\8:,H)7s r[b'%|=o/H7BJYiْ-!%NGXkY"A)CĬyx]MIM"kTqm mIbBJ,2b[4(%i7&&ה-&GI)krR%k: 1QNԐ̐QNn8)q)w4@JrBrm1$& \ 05((5)J*1 u9Ĵz/d[Jg\5z莽:C'sϐ`>kH?B4Q˽?cEwSeCG4X+)( ] W-UOϧO}ӭO=?ե{>`K"njV_tmG+>Lm7Whc&`;8wvI F{}[\;;1yW{NHC@$]y 3Fn3U],7Z_|xۗɮ-9Iÿ~soM_?]⠟4KI7V,wb A_"FA׵G::xB 'Θp0`UǕ(^b2Om[VU!3D;I]<,a!X[+'%(J3r[++%kƝ+p!,NI 4 f8)a+nxPװ1ׄޝIJImcږ 9&Bv3tnrLp| w?%Rehce4 Bs櫦}Oo-Ut]@83T="$m+74aB-0.L{! =BW dfk&uMm?7BZwn $τ8o[)&E^˹HЕt|iu!(KcUaq"!w@ A;G9=Xgwr;Fʀ~yxw MFm6GZFǰ&?Eh R[^["bMFJƽ lsACAB ֟59)D % -J)lIvyX5BxNRR)1i OL J Dc&'%mHJX)WіRR%Q:y\I:k%qRB(S$%E%I Jgi: =v7[RH$O:yR"6k,Kd)"'%bWLmI"H Úy<)-DD;9IkD+%%=zK4!FJh̓AJ 0 q-X(̶Z<+(RJrƄ8)`#eϚ,gS_vEg'9z\;X]̚$%4knr^O@@@א-aKLG0`<0mKXT p@43^)q`1l)RD4:NJW %EcFYwѝDX ےǺ1( RRɆe%ţRR[.P? RR4&uG`9_[DCŚ@IJ̶$KRYS,%Q#4 '걣L ,d%5Ը8KVVvh˽3&8g>,+ev{5n!<}fRrDyifCZ w/=čwKu?-ӡYQQrOz/Ӏ3,}~Tm} < 9$[BT{ A#Zq7f:qׄ'~$ 7\1-e'Dz_ nU9m[ ɵk҇wih 5 s)W 5'$/9Kxw #|'{-¿@ 8(l( iA'ỳĢE$:E [Rr'"t ,wP@Zm,tZ_ kD# F' [$[G90 %NK`MRsI+ Z,as8)ql%#%C4oBh&%PSjÚ|3~/B/3N1&Q Vy֯R iJѢ xs~yl^7j$`TG]r8ǘ0M1IRʖ$qm;2zѳEas5}!p@P_=[`A{(%xIE)XNJق5u)iy +z$`31ǘΏY#R|D'4J!$J*)Q$ksIE)XFJY$iQm]!uE᰹/,'I*FJ$xIbR$B͍Rb% CX`&N[ⰹ-''Κý\<`<.!%yऄ󸤶KΚ xO,( ] w7=}UWK OwyS+`M//"*M/: GW~r֢{ڟ9<+4`o1wp;}:̅$$M?l{xg``WR^{wvY4_1B. z-/G^oMku.܅8SrwD,w10Mw^{ӯEQwY0Y3-. G%1VY¢L3as=7H#od ^Nǥ:p@P'\`$:Guhà7Z O.)|pD:+6̓IJLa)kRQpQD3XJtဠN0,%z6-XNJP_ʚhTzcD''ln)%ʏ5X4I`8l>hKI 3z/?`) 然-XNJ$KNH0D,%B^hp %D)Xk0)sW\J8[²aMeCsl=.]jLd̙Mqu =M-f"TT6HrK'X–pM#X1d]|ĚyX#SǵdK\D᛬A fqebtr%LDD55m2N.)m,HitK& [X,gKI Zkܲ%uN0 D'k)p$iWy-<.= kKzfe]qwUa) H-᚞3RtfM배%NikĤ NJtk X{%aM`vU5MRb"t)m :ĶD_x\ cD9FhwS綤IF qlI{%I2)eMa_Jkֽ/kb) D9Ƞnŧ|Ll~IJLύ rkD;\v/= RbM߶˳r+8>y1OWoNpq`1MOJR ĝy ']Ŋ"Ҳ5|,M˵刴kST?GvG='.>@;;GCr#̞&ɞf@ӧ%ɗЃ=u%҃`Q>Y͞{1;ceU}acW+umh={'qVWW^ۗO&Iq]h }d/ו'{{֓8AO&IfkOrlsae-/ d =k2sގe>6r=3fY^?2@o[sR=M' 2>f> ]kXLW Ij1]@L24<}{=rM~.[#/f}awZ}e/ΗmK~ɟ2@of&s, nsy ~얩[kYe80jΞkΜaO*翖lxBə|W,NŅl7EBsqk,J fe)/$]Y: f~}Ů`BO&?E14CYIR#OY6.֔(fZ~Ãx.f"FY^k %0ޙ1΃YJX5{"Ț)IvOΚkF47G| |rK J{I)yUyƆ5aRJ csmkL-CkPJ.f ? R2C Ul;[L6Y3j%%2 ɦ` 7σGJY+.)s hV]J'I$ J;tf-]C6$]%,LRӭW &o ^ $<=k$Yj^4=\`uII!k&VS^[b-HjR͖d[2'*pZ2y ْŒVX %6ǣg͔&%ik5Fb[5)(%$kvt握^6Y=բ-ȇwnleHb5sGjtqi%͖tI$I=Ւ=.-fj(d͖3^b+mt 9[ғFD657BܑdYS!d=5-fd5 "I)DyHO6C)ѱ7̬ޑ-mB}hJ&tMTb,ǥےiƖD嬹*éLR$k͖Z"k9Ց?xquu<.-eL5 R,Rl<L6q2([%}ȖȈ7z6AJ " IRbqakUz]b LlIMfz[~GJ^d@[ӂ|#5tc%=(q5gl5)%kbW8kջ5#E\o]c2x\~6gޙƭ1=Zdo2&Z{D%i|I8َS7pr#%y[0>*OKkܹT>RaCQ{r?Uܣ-Y؃KwWy؝D+CD|yH!὞S{w麨Ç}yw gɡ}par?oW=qW&'ˉ {$V6q}i&Wԩ'䍣G?P ?_=HqMwۏ=m"~hmfob~oַstgqWngʕk@A4dG4[?"jK\>M>Q[r6̺8{g3 l{mʝ^y+}q=[ɖזFRBj M6ҔQJ3V`Rg?y5HB΃QJTP9g: Dq^D_б2l`MΖpiAA-%W6c%2[ŃYTP9Ǔd$mkqV?fOGqqӬbÚI}ov%hqD<Ь9z+](hgYq WvyՇ$ŃQάq׈-P1fyoLq.Xs`MA׼9~8]0si$%$Iy!Hߖp폙3:m gMGϖH՜58 JIpPz&(mɚΌGkR5:x\%іnM ?XrK[GJ:sKT#%yI7/*^=5*ܹTr1Rbv!qdټ/tg!xOVF1mOvy= BW>7,]id>hxܹ xїE[w?{nmk~+̵5@WڮmikO~=E@gZgNe|,QJVFJz$F$J>*2PDXJ!@ڰ+Y atfóӥ5+LҴHGڕ'ˢ5Va* KdM:. @ǡՕYmVMiTp;)ZIsMkcD2AWҬ:Y)4TA+̚$ʚx]Z<[Th`^e(0)5'9rY5!(]Urg) j j5G@5pe(BQ}c;$c) z&nXP #𾀬 eڴ+.V/X)~xbK~$e*Sko֮9g% k3(x!Oڡ@.fd)idR !G3*>PZƄ|=ZєPE3#gzrI%6]\$r;̶D9icMWIf C&-=bq -W)^Y–8dVJ{*jIe4 ^ ((-kMK3Wqǥ܇*Rʍz!H"àtG ZӲҝ/hKfKZ(ͥ$}V!%Q`,3͚>Q,fE^\{!'Q%]Z(Eǥʦ5kEhM7Gsv-&;(+ \ϱ4ydr'I| JpXI͋Apl?1I$1fBXl${_5J͚ISKR!֙TBʢ5mݜR1;5qMn~RB3y j j5@\@_$'6`rš< Ԩcmzٛ8+聓GOQe TnŸ_։ceMeFe}e`<@jMvU")QerJ@kQJ9tذ8#ì,S J̨O{;uF"*ki[rKhEoUP] tf_;TYi@*A*H PJH2~*;񔌧i`VEW%'*CsR<&T`MN<(Jʨ[#u,2)v?$Nkf jsAʕؽ\\Qdž3UEUJ; 'څ|nPq5@\9RQ ڶS28vC/++v-9hYrWjLԯX[}̀=` |JCLrwN׏.q[Íxr%~}<>GC=|} O]F7)K_pj* d΁Oڢ@_̄}5-_\p};8e>~NH-7Wq&; fU)ak4&\$ITa̚ ӇˠN,XPFqteAVXHf@ WD,eG85G;`p/\aq)]Țsi .^"Ԛ #޼L%`hmSx!݆L޹py6 #|B%]`-/\V !К>`3Py> ˴ͻSH#Krr믴9kvLoh:¶B I>5M ;cКۡ=D1kx8 t ,lX~5);5>nxt21LkYfş5arp\ӍװҾp%ƺJdž=XRjPT/t.#.v- "&d>9) Zwb͊-bTOq$ m r,,kX'pdشv wHSkrNG{C*~))Lt\ gZ iy_|wz >k튪w%?Vǭocpk턵v˕+?_˦oGo)h~p7x<^d[3eZA;%r{p<9??A{p?o{~{7aZ;)iᷳS蠮j"I< #U y>Gڠ= tVw32y֪5[>lGUhTMZ/ְ_;嬙klGUycUA{Gkvd&kOmk'utD=T{P'8ZI")5m_kA/_~<'/ W=~Zh=w==7O?.W MEOO ~Rm*lwE_{z~ ?~-S]/WS䖩v<--љ"89_UKnc̚M֪̚ҒŚ%99_լ9yFW7]Uċ|>->?Vk"AiZ;mk9 ?uV&ŮfͳU͚U5)qcfINk`ZJ|FQ?'itk(=v :`>.ddMUHJ*mpC-Sk[ԩ[u}n{s]7+U~3;=m~|s Wmd_{ޭd5Gx?_\ĝ֎^W*<\-VUY dR}u& W:`kbb94!9=mYsY3>FW] J3VW+ zf5niJ'5wzfu{cT'jU],SU;?ZͬtM=k&2ENQ #u-zNAGXհfMJB5@\mHAjܶ?<_{f fvzrnUK:ƲvU{q.U gjSpU50V]CpZ’+[[Օ j:_լY]C_lFWW_]謁m$eGufGu-& ֚CUU54z YF*UA @IҌUPlpְ>of T< s̚UV^5BU_K͐ZP]ѢLufC׹J3֟e-lQ%iZMJjn!v ;uJG7gwKv@ܟWCv=j^7C|3ien e~-Ï~ ĝ9oq7=L^ux.pṣm. =SAv1=$mgB!ax/'ZPBe(̜itiJCB,{2S)CA3d桙xS1;˚ oR6VP#M8z> 4 1de5']!XqDa%g|WŁ_4 ]P⎭-UǞ9CO |LqU~־w,lY_ĝ|ro\![ܟ~9!#+-\C &-2700Հs*;6T 74S?g)G&S^G{5sZ@REOY0%MzKWb<NIh=mEOaڦJw7!kfK3VLwpeOeOvȚ+Q3Qތ&k\rVEwrcZ3/=R;fR1%7 ـ _=k2w9nH*;m,p9<(C eJ/T\5gフ}V?k3sb :|M5C$kKYx%JYғx8r jwb mrU0gVN S<"ZȬ7(yj sEy)<n@%mTv.dƦ:ɯfdbBRIɠ{x+b ;}Sn{uǻ_ fBܩmwO?\Ue[P3~]_[>ޭ2@\+=~ıo F vQ\K~//p7r=gN֋\oRnO򦶸o1pؔJe11՛/T IcǾ;ŨtaMU!N yLDR23A Z.$#XfWFTrl6y&"z9af͓v7Tl;Ӊ}V o-n}i.V`k(S[N W6tLn+l*(wU]7@sU@L*0ef)̸\MҎ&B'S6FOcVR*+@l*rќA0mG';/hRS%qV 3|L^lVhɷ:k.MX't+MЂf͎@PCR%+q|Y=urX(P.I. 'aRrƾYu%bຆUrvh͹c[ _1*j.u_P$6U9YX0sʙy^ @{h(Qh# .l6-vUEiGwRTkȠRqު*ĝ qٮ雤~WvϪ @U`U!ؒxpR$E"U+i߀ j5zEBǿi*9n=X/ MYjC#тxѮ=wv'vnB6ȳ+i"9xv;+NrQkDGO!Peo9KegM+A>jT4S'MS}HaAE_2P;V.M>٫즩6=0̚]-k_9N o7J馩k:qc.XOOϹfT]97M%XT(^?CkVZ|f?Й-c-]Y}:<0g0<xerkX3k %L=O-n;gB?{*qx|>%5#ǺijQuX'-::iik=$0GGw8z1滩%2(}:bC7HIIJ*0]5@\mԋUj\><ڮNUyڇ75@\U*+%*eۯ7oW 9}tg@i/yTE *)fJ3%\^ 澫I[- U*PCJbBьF=IwUD-g8/ARFq Tg6 Cc:GS]f)H[^?:![vp|V>)FWlg54q4'moJ®}6y +ʗ=ElYs|c4-m٠W혝D-9 N[B4\Ų ȐΚeaM*q cF_gͲlJW1O!!1e0UIȒśxMO840TʓV*WyZք=ֵRCWIydTV5Ӻ^iN'r+J}XU?-3]ʑ[ fͱy*zc\>9@/ʱ_k!VrsCSʐ %]PJ"d y^(~XiC}TC_g,B~f,}֩9rHSSmng"JD0>4zqN=q`@%ysqIj ThaNUζ+o; '}~!7 "˻2l lTmܾovzsH eD+HR1.'U&xn6w eea$_#zNJM|^rEdIuvop/,|fF1kdd) ǯXTt i1*ja,G$3`VYSS!L+z@gxtb ^ak+_$NY₞ŴrT>5gM) jԵQ h9o S9 E~~X3h3U9~f;>3g0GXG'?eShU _f8+ =2,M_]*kdl % t*zQ}"L¦aۦ*e܏T?kaa}rg)RN CY]t '#ǚvt'C?)7AqheCb~Wy"\sV5l[@mh_lSXhWkg|D!=SFlt1B좞 >Sq5@h5B3^JqUni;NqwkެFZGUm3nh#} 2(o# 5XbQM~IlZՠ[ٱHy9 eQ dFt"_ڹwƨpWf /w~ Ch%W+4.W1J㨖i Jz 8yr!dWc;PfpN͢* \SʳmBj iPs 9}@+JnmO!RVAq%]צs&z@¯5(?Oꬔ%=i͊:ś]L׈8(B^\"8,Yg!'cSlGF2^*DMOvjd`MX{gU$6+O!k[ݗ/P|h)cPgFI7Zo=TEYV.jhpO_VKk}~ptTcqF&!Qqvi5H9M?h$s{JM9uTbEn5IJTd | ZI NI/t@ܛ Jۺk5>Vo:@s$snhE8(rDwJ,/T z%(:@j3{zl$qZh>$+|GWșEb1PȞ2~s**A(*U H?EE-J۶GTEOƚp_gTzhGkPx0~~kT5ԦM^T6U3IU٪xfV4UQ4̃j~IUlx)ucli §Vm$4zoM3јxV/cg.1e*.>̚ՕO?Q_ڋEdY[gN枒Z3ȅoH}2ⅆΔ[^Xn.JeJDshsr.5`U"ml(ghRpc~~P,C8ESZw/f8ieD)`lO&:Ay&q=&~eJ7,>}Nkh\=G&iAieVKT)O*F"{`{g> ]/ϋ NI{3jxRvm1} WǷcz{GUVLHܪ:@܋wr^s_?I NT ?)AHXZwxzQ%-  Kr-^ɍ$@hHD* 1['CpYQXPxa뜏jb! u ñHW5Z2N :ܛjC[rzF| xceFMdJ87s жZ;n%a7@ZJ] dL˚Amf%t- >D NW$ 鐨tNwOXƢܾ`[0ˀj<]WA:^!ꢀW㓹+m`cUXo6kcv1 ~叡YAnr>eѡܱnru<*4 (.3 ژIdMHJϫU4ΜDdJU~ D m0=W%\dDvdz V~Jqew۩,㎩b Lw$BPe#kK玡6y"jW'7sUVܘ ͳ=EOp\k"{yx{j 6ŹJXS*fR2;W]QYM .`q NKU;j픡.q?{oEq%pPg%<0fPpܸ!{e͢WI3e"!EP6o&.whB"5JQc;,:n5]]UTw82ySԩSUߧwـt5G#/1 ;^) ֺ-kËTZ䝩aP@ }=CU8xnQE0LA(L}D?t5Q ;ZT} y0Q֒B@ UF#!QqI/0/3h2iByzi&F3+g,:_yH*rڳIXwwJ4b_;W&fyy=Q2 t<%`Ln$St`h&RȚ&SZVX _lZ#IZ|b[OMIXH Ĺ?1%:8i%84~>u,ޟF>Ğ $W(šk$ wڎI܄laGw8 Xdu/2qRe+|#X 꺘 G٣X;qj.8|pXJ;4ZTSpxf n&c '8qb A]7_gU| g0&iV@}w֬g1/Ϡ9a@՛X?UY|gkp4?q `F]{=(P[8`g^m_Q9Tz+d@.P6UA!AsYΨ8?RoCbz3yw`k`mx`7G>Ye22>Uu2'sdTU-swdn]'2LwdT;|VnHTUr[je2wݜJ̥a[ygdicU@q?A A;\ 2w@>=&s\Ei%t{Kd27Gn A/U[e2M[J`OdlNVf UҜl3RUY'sJ\\.lMC;EAYjGd47F%wU TP% d"}R^eU2w\*3OwWeF{U~-sߓ{d2N2k=.s_MUCdaٖJ*_3Te}M^j*daTUvx\(s[^OUe _ܗd#rO܅2w͒+ex됙duQܹ2e{t;E>\o2w̝S ϓ+MVܧ\./{e2yݩ2 ??!sϔNQr̜)o9[>!s?oܩ2J)*J'T2߹JlX%S*P|JZ7d~Z%5*A%R+T%JTw?O% $sd4+P,TzF%uJ^TI?:qJ +[:]%b%g$URרD\ k.%cIz*)K>Y*Q%TR7~s* {ɟˇz^2Kbuyx׫NV qUB%P%gPW2˫KLȎt½RCKtWJT%cIN%l/T"{s˪$K*URzB%v*9JJTUO/[&{I]KҨYJcIKU;)*zI]?{ Y*{I]y>M/z_ʇ'UR/Kb^q)>[$N{%^RvxOfU 9F%uX2ߪ hJꕫn+;ԍT%PI=7UD\^R7V)$Ǖp/$PI=zJ%uXRP%u^*kLMӎ3yɟTRGzIq}I5zIR%uJ^*%TR7^qs*T *zQ%u7*;V iVIzZ%L/+U:;ԩRIם%iu$uVɗ *9zIvAU$lpv NRvgѻP{=sT=sTLmNbdF~!Nfܗ09) B6L2ʭZ\"\Pթ*;"lT:#uS:w**9*UEUX%Lp=x"i<]\`*'3rǙrW_P*H%wLBEz849T1ܥêxRUy8i ,<>ʝ)sv̽'e)re&'R, JчlaTV d*9U**9UrUrU*aJX%V U*a$*aJX%SI0 68v*=UcVz2 4Hy22Azqzǫ_N{e*'N8qĉ'N8qĉ'N8qĉ'NW>8@1@wN~oxPgg7WLis0yrpx]qtsqȋ xR֎m0 tK<5ym#lrZW\_γbHV 8)DqɓIZ7N+y͑WtI2;gF_U&LsD*G[ ~EvRo#ЯonEH+Be4G_Q}$NxӍ@"#b$!]€8{̘޿׍ϗkSzk aug2]gp^vޔ{/ͺ]J.3jSG*P+۴x *rI+3PĪb섒z|U>e9i*T6_qK4ti+4ÊS"bRJLj.:;s/踄t¥J RBwqjʯW*qlih\UKY.\n='$+nE*?V,^ܨ~hh,ÁQЯ #p!]j@6BRyMdzw?ŋb j REdA%i| cJWJwم[p93/H3en+WIaNJwaddʲ+&\.u-9U,,v%Ab @|x\~7? w;q@-{qOO;f58 dֻGbd'${{^ qW;CTcn J} ^%] ϦJy@vHԙb׾Z -ZCwM8U9Ryi"Z%`KjTuZP32 3/+.oiqX2_9B%PP%6w1};fWjU@%]_y]W~\/ ^ȦiԹ.:&*Zqed +9ʇwr)5_Mܫ.UfүQP !$פUfE/ ӌ ::6ئh'r\t·:J8.O]qBF/~FrVI)E*P%?$kg\',hV*}үvv؋l%M3+@qUB~sd_q*r&pUrD:RBRPǒ¦ ys/WܨV U6(^r 'ҹq ̆+UҀ"TKBނ^[X [W*~}K֭@qͽt$ުPqwB)U&\J@IˡU~s-ÕPGb^ NzI)WIMs/ bA[EJL **΅> *W4&n:!.Z/$T27]J'τ7(q ugp{T >!A *yR GK5nTK.ܖߧh<*ˮZIz)W* 5@7PG@\cN=bYwc?LNWqb ;#yW'Z]w״i?\-~iwOcƌxBK|-3>oj@׾ڧOF_j=ˀVr*Nu4c $ywݺcb~lSUq q@'N2 .&Ct)`{4O6VNb :l5-{j1PQs-fjv(m(@VjvPVKHPÿEhBzu`s hdB;@/@Ѥ1K@OƷ9Aԑ׮PH_#3 $D`**  Kqͦ7UScnK ?jo4Mz ]؍t[%v &CȦix`bKh3i"C T4ԎO?A_mUnN)F^TJ(Љ{=aoF2:}9ڛ# rsiS@ %0Ϧ f%C~!jkEQ˪V+5˅k`ATa .J)hmVln3̃*3Ei 0DQE44n3NбwI @0we6ʩDA@&6ϲCA0{RGT[ý z'jRmPT@0v Hk8%ai hk [zdz)/LPjH V `*!x 3bs ۛg 8Nĉc'w'ā=Q.W}CpyZ>v*P (n-W+;*\sߛqrj߷F n_{nE/)eύi6XWVj4[/%s9rq#e# 6g Z`]dԬ+-TDqz/)HUj=C@@?e&^򞥦NRykxy,il஛l0*q%#(1HI( hJE=M; 8:|lP|h@q q*u kFN[~HuߙvrvL8too~D/B|矯۱<%:l.nswWL~pɝt~.K\is.8$.]gܼbHt:f=-?cM iQ܎Y͘~DE-UQqts鎎Kxoz9(y'jOD"ݽR&qɻ/J7fLᇅt7n*(vww4=鸉sy$J6-[pSӊҲ =7U}3Sיִo:&&'vΪ]Y_1a]1L ])nw! J[\Ѫ"j rzٌuQӚ) ִ0_IF%BOo<DcaU9-*ih薗x0-+"$#UE*0ͨ*IXo5lFzM*. ܮcB̈́Nxq !,4*},L3_74\U%E!‚ʦ5|M޻N4*aTEDJ¿f6SٸY=H_UEQR׸* 2je[MLQ2ƆݿLJ53_ȕLQl6@m㒔Pb@:e-I+QJL@0M*qI*GI7sD%M]ۂ*Iwo:HPĂWD!_ U1vH vQݴՌqI*w"AB k@uZo,HJ:FCt46-bɒ  jibЩ$$n#[%l뼨+a~%. D1΋ W@Qe532EU21z?3.tfQP#ϋ|⒔bȗ4jN V9\U32E/I ,年@SRٴ=ۤdxU32M;%## 9.Hw+u@UQ#F+eVzFvdʒ֍K^ʒ aBKMRBV%uUE&$&d j=A* J̓LAL'gRZ˰9Y\Ҵp(J\P&@iVcu`sҍuDm 0A9'L2%jN6LUI3'=)bm:ȔԮ.&K"aB\P&$փj{d$ Sx[{;uΆ *%IaBb0 vuq'vnC7C"64 w6wKym7!*TwGLFd1T;:wؠ[ čeH@܆)7?r]Dy&3ZzOyu' _%BHwΔ=y|}ќhl`ItB܂us- {r g'w%gu#y:n}P|pfK5 M:TA^Ϯk^q+A?nHTEwBw/}uL=_V$D1cD?(lq ?MZnAi7NyV=pq~ K65 GVl!.(KM8f]R; 3S?3v{[O}j+M8O5 Jʺ2z@Q"vV4U%QH5 A!CۂexM^P?yh\U@qOQ2߶ٲ~;7mZFNHR%*4UQ>=(l[%|-߃ " /27ͨdNن*ާm٨$eeU},L3]P7(dXH{ zP^%IgZ%︒ӯv])*U -&iڸY >[uU ]R %&@rX2⥼-`uUdɸbq CJzsNBmA~^IJUt1B$)HKjլJbdRWR%ibUK"(В{JI*d>Hȫ"5M m ofDI̯+w*+ npk?2mX0 KRbQR$bSϋ#z[!^\M `A  daXͤw[zTW2V%.X\8FжCxhU %jMAS$bqA;b3C$$Vi*%4(xn.FO4.9#SU1,".̐- PK4F~uYo 'gdqtU*I`Pr!HG @_hs0_/U4 ֍J„u,UTNmg!y|)!n|)AQ`0;ða3t*H-۸nB]w3 2Pڱ]lЊ-bF|zuI@ܓ>԰x;/)AL8tD2I(v_K7Fu ʚw:M\eI}͟9Y8.a'̝{gyO◼^L ͟}@|-fK5 L:TA^W̚ ׏WoQDU4@׷,}n_SJD)hU_!IX)'^u+^8VfwN{aoI\nl=rȒMM#~mJ@I>}j0q^x㖑WYOyk_Tɵ478CJִ).*`*)jTjHɨ*8/Mkx(IeXMDI"WPTE4VfM=I5j/j[+LL!Y*%UUɪD (jd-IwAfqɜKz3tqӮb%~bBz q|U ?~J7/tOs}ԛϢx)dW.-'ĉ6p`.s?iI3ivaw yRE7oXc}#SV\`CM8!N\*HP9cu =O$ GO!Ī<5J1.Jvtn̘_cUiZ*|oi_j%?ִjUfY Y \TPܷ7JmΗ.\ &G%?v)fJbv5QPR*ėyQ*E*\g J&9iZUB 5tsUIe J&VxW.X]Sj*B2EU*%3Uѩd%%öMíHo#( cpƒZۏ^95:{qIBg>!зEb_ߏKj,) i MxYoʪ@F˳UQCA JAfu`_*)g짉W%U4͵ slU 6S8#K`*X\dh&~_Q$;#nȒ*Q[%a_6l֑+ l%Sd㒘_ufo gdPKLUEs3Gn\32-YpU`1Y˲ =:$gdf0ʏ 'Z7{DsTɜͩˆ 5TA%K 7UT%7'SR&( TV&Ϣ9oVn!_P"Tvԕ5S@\*PSU%p$q$׻5yℸd`(i^J@Sτ [P-K,.z8%LPy 9M9)Ө$&R+3 g)gJT2Z,~  @U27T3&VY!mxoPJb0i0szPYh_GtTAts qឬ&u]wc @d1T#Q/ ~~~ڕYM/ Kgg2=&n'A]ix괨*&QR"_HUDSi5j㨐m2&{_QUETUI<%umҟAdF%*QAaV%dmU#m;ˉ˄ij .q]XRf3r`S>_H6-mƪUt%sZVtm+kZŗ7)OܜRQ0YЕ5mܬ ˲4IUMVs qL+Jycpl0DAEU/mK5A||vs .[UDduG %N#*0M,^K"UYyL\g\Rߴ5cqۂ*Iw}D\ DIcj0]2TaccI WWlSOJfd=AB`Դ[bu:o.i9یox%3A<%r1fґ:c2#($2zd~/S9ۜ:-J1/Y$EM`$TE=#ˌE]TI`qIdI1BZ"qtU@YmʒU.CJcg|UH6.m sQP#hK(h`fFM\NY-H^]躴CU32@ 檢R %F%Aeʒꮫ#K„LI$3.i@WUE5)S 2 ,}_Ja|؜,0- ҩ4_h&evNI™U2\IŤ D„iR%[uhn^:lN:XL%m%e)AMI_ Jaė-(] .X_A0A]ss)j:ot/$AD 7hè< ݽu :p)lwD6wKy6nvw׉t;B, ؒE~i]o@O (]WA'^ t j5v/yȳl (CML ;u,mP|qxc*(r@:(xݯ; bVnma>*s]b"FW l*n\Og WH1/ch౉JI3P}J([%m 7qG֟mee&8D+w.T&V2v0HR಺PɅPRyCj->U4fJTU+r+֎˽o8 Mߜ-c[/cS|gð0M=︽ʎ c܌8Nĉa ĩg"8&:XIMi_Ռ ,L Ɯ,@^?flhtH5%Udy-y'J"K)Պ#m sd~R KBتG%4ĦUx79-@2ۃ,ji e dGOt䉁"aKىٍwJp&I{ 3c.GO H\ (Gp0e- N_@z@ 'oo (6^о7q,^sbZP4DQwf($J;hFJ,hpdY(9:l' 6mE^ޕ(WU@dD'G*uCsL*^@լy1\X#τ ^LG٭Ao@uU :.R_"bJ*Bt4f=q 1ǩT?0]ʼn8N;ŗ` 2B&@ T33tM1(28B ոF]ܵ X!1&"Ug5L/Yʩ&!qv_/@]m!Ѕ*FWb\F*HKI*6ޡkP vOp HXb8GG'c8-ԨNj+72yH%<򑴻se `ӣjKE+:E+dV0\hq3*QcvJ@EjndןA?J2 @)*R@@SrUB \ij'zjENj@#gJ 58y%ŭW5Pmv_v;/8Yk8ʌxKl;_P8✧<@q81ljq moM!?qn-ZmP7H$J F] Y%`^l!-YhwӶ!ƞN>}Ձ__=t})Ռ,r^:-1ZD՛݁Pk5C\@: WV8MF̉[RXNp$2.nײ%4+0Afft Hvi [KHib 7YJQ~R\Ibu@@ Z1br+:݋ 0 yufpT`=L,$@U828u@DV Љ!+/ ?P]!հS -< P bP*Xu{ԬI Yp)=Y <-k) +-@ibN8vF{*%<(rCA:)֤9i5dǸCn8c 8N qb '8:!tPI&K 5'3, No貙ΦJr{&@ ,phd0,:_r`BD/j++邔Mjk_+RߦjQV#|@4 r4Fզ@* 5C>LEf$!+=dP+V(kq(N4Bfd$JX* H=A0pЗ6(˪|C>GN,nZ~iVwK`f;݁Vټ?F3͹_UlXlvsJ GX|1(nl.2NgY~ٳNb+E Yҭy,B@- h 6j6f=: bdc0amƫ8v\V<-o ]#}n3@q@'N q*uUkw-רVsj˳Д (T{X= շ9F 7oFܞޠ5uGx8@^+%fiH\q"t[!j h?( ЂvtB9ЗF54)'Vn8IP {6B0ג_/A5_" o6VZ`cvF!kZhvG>h*عFio7a&n)Zi)@-ta `]NqՋCJ `_HD53>2#8Wa_@U`F_KR或 `1Q - V0e ǚ)ԅTb2P1VƓ@L25bJR)`)P -A2NKi3ʎP!(Hk][4ҨLSG4禔rz~eib稺k[4LEcOect!o:5֤/]fiQ& | *[~T gB{|,LimQMi 2?U`SDADQgYp/i2~%6qf鎮iHt+Chwh}/6MԒ-K~ s BM/:il(*t"4QLshfw4W$fzK.~a69",T(=%(%2(?~YiڭW}O559?<)Ӝ[MN3/gP7e+X^(dűC0e0uq]?u6v8R)ѻmÕp}_Ɍ{ y+P)-f XJci膦c˧C'_FjH7.q.Y%m{kZrFpwT- u$bM W؛3%/KJm҉D>Bn3iII閎JԿQJZ/U}7%e-TGܳTک]u-U9=sT.ܹi~wZRȦPkJUɦ9i~j:Ud|q}RlԎk禙+Sjy4MnLT鎌unt=dRu5El~iް*ݡP{4{cU72͟l*#-޽i.KJwkn˨(foJ7iU&ivߐoOyJsHRi{禹{ U_xp!tKeRXR8*B U|ۻiؘMs4goHJ7SM󷻩rs]ji?ɦys9v'UG6'vAyT/)O{";ݛ+I^}*;7ͷMMs-Uל3d\iR;̤tR[SB+/JS]t.MJE5_n}ߝǚ+/~QM`i>O^^=)}!IV*_ `vw ѷ8i~Χ2aHϽJ iwq 1@q 17쁸2@q 1@q 1@q 1@q 1@q 1@q 172@q 1@'c 87'P8c 8c 8c 8c 8c 8c 88c 881@q 1`a 8c 8c 8c 8c 8c 8(q q 1@q 1@q 1@qTc 8c 8c 8c 8c 81'1@@q 1@'c 8޹@ܞ?c 8c 8c 8c 8c 8c 88c 881@q 17=f&8c 8c 8c 8c 8c 8c 88c 881@q 1ǯLe 8c 8c 8c 8c 8c 8 1'1@q 1@q 1@q !q 1@q 1@q 1@q 1@q 1@q 1@@q|Bq 1@qc 8@+@>551@q 1@q 1@q 1@q 1@q 1@q 1'1@q 1@'c 8@c 8c 8c 8c 8c 8@Y 1@q|Bq 1@qc 8c 8c 8c 8c 8c 8c 8>!8q q 1@q 1@q 1@q 1@q 1@q 1@q 1@q 1@q 1@@q|Bq 1@q+_{mRxv``d&{ _lo>- `{.!@\Nyt~NfR'8a;T@Zyj H]i@VMg%^ a{@Z8w@ .ו|.`o@mX/7Zj sr%->]?ST-ɭ=\_&x^ ׾x= N\:98tbk?(/ %rw-.t*t8pw+bgY N%Ae8xW.1f/8p ŀ-z Nmmt7.qJFZخ^IW;kkj2@Rw.Uoʛqb@S_\,ŋ{^qj]x=f'=z➘>,-W4-n(ʛW_y[qj2@-#y{ۯG;0EZS@v4#>4j]oށ qp,Y N?䵑2KˏQb҅ Sf JG, N/H,6@/Z8ܯh#ND, ./^XqtaҥֱqLӏX6aE,@4 `!y) N`bB8V%dU=CF1K@1 ضog+ۆƶ;{v`m ,ܶsm-k{6پ%m?{73Kv?8B5@G>?寧+|2 6 v-:B-@t!_0u폕'~gu:akGR^kFy] 8B5@xGHZVq4q_ZyM--[Z|9[n۶u^>Yqw~:`W83wooVSkǯ~?fx0'>uo<qx^ w}.Moq=kF<ނ/{ٟ"ݼ}ޜ}'=;&8z(c!ҍ邏f8x'\C?߃|+w1GwFhG;)D;@I3MC;6p=};4) đL n a]'iӊwt{7=6?bWwOSKo8t?;8~m+h8-i'!=~Qi2GM':5-\} x׷'O3W?q$鎌E=vg)ygp=zҋ8tOM:qχ(ᦹiϨ@Z}ot-gÀ%!m3)X>)ù,ڇQĒ`` đL#4F-C V1VXL,#(]WBt^"8s(@;I'đ뢙"Q؇K'#<g. XCC}d~2E Ĺ3M?bKND,:2HE,Q0`@If~"O3HE,y xAB",񮧘f(8%!d~G1ME,,#)oe~+$#70ӌ"<gV< ra~.Xa0^QqMh+GoI[~CWfd5E"•;t]4Fy_[Jä+diI:/\ٹ&ݢDBRH $]biOHH+$g0ӌ•<gnRB5@3M/\ٴ X`AM3W@d"8+] i~}i ,9)WGnsxd~0\qFƇ+dyAeW$H'XP g\p*WB BOKYp_bXpW@prQ:7 صxm޶#o+NNCf8⌊b;Hv&G"gO+PR|%An(bU/b  #X 9),NđT24Ww5Oz 8 ˁ @\WضL<1gޡC#m~n>KKty'_"qW?Fd ʐ8B5@WȞ7 N {Z⍈ R׫Ȩ ~u>Z*8[B N ę@g< [K'8R@\JG~V^l 8eGҲY[9iC-[zZNzi-צ8>ѻ7޵75-sͭ[AmZjӓq645nhj؆9${:7O|jڗ~MW[VϤsx>v|f6}Mt=t?yN,8x8t t%8tY N- Żw|ꄸ?G_}__8T: ]0yBN߽h:!N/+Whg>ZE!̢E0~!;܏XpӼwaSK/{[ę0`8tk+# ~ҾͣEęG2͏ Фܷh)X 8cbɀEq$HG, Iy3J,#IE$8D2zsNCW@Y:D@IyJ;M3JM$Õk( ,i 3+sj7BQH ]bm2\@IAt~B4͐4/pEęWȱ^M 5xEę'94M/^ZHS*/X`40\@I$i|eA(\Qq&׼`+kv ,i 3-J9X^ >GXp_bXpW@y;!đL3;8 ?v u ܕmm'@q; gTL)ßv~W2$ ΞV*!%wEUJPM%g/.2>P `7@iFG g-#RĥQĩY&(.3wLqmo홉w^gcۼ7ۆ^?|iYWg{Gqj*:N˿ |>O' nsώ}үȨ` 4qT@:+S> ,'3K>&K'82@\BG~` 4qT2@chYđCMrʵ^:I/m{e*+S9!9nma ĵ z V ݺ|= tN4K  Q,uLӏW6@t={BqȂ{|BatBy;! QL3|e[]m]c|Nq{]vvKn'⌊N[,i2@\k/*Η'8}5@]]d|14!'Yr@Y:GQ KCL.3wl2uh`cȚC)]i# E?^Ҷ{IW<7u=/hp.j{+Y:aY5TW֟ߝ >WA+S=ڽ"O QA?P+SZ_j)]T:5gT޳}. 9Y(/<#N[W~ejBG*?_WSTKWL%hYTuW@ܡ-}e*W1w%%W>vOxu&7wlqw_}?aNnϛϛ^*iƞtz N%O:'ġ9::!,3 {]+/T ^J06\}+S|1͓t;}nu7G3{{_*^< ʛßX5<C\!]I^ MS)'^4-n2,ݞ5Lⅇ8xgLoT╩鎌E[20(UϣC8t))|"VT)݁ko8p, hNE^x4" .XWL#)ˢ=$鼏yX,: N'SA2 4ep!A:h^E, N'ݡ ` ^J4G'#iFⰞE:?bCLL>XW2M?b%H'"{)c颈EI+2` ^Jn`/I4ee7 bq:  y$# jQbiOKTxd~IJ3(bqyУvV< ra~.Xa0^qՇ ^ ^J(W{їՇ0^0M_JC:nt~Nм25. W4@NI 4F2 q ja c$_4xW&'ę;++S +$g0ӌ w+ 94pe&4xEi LӋWLӋW.Xςp%xe*A$i3pE ׼`+kv ,i.J+"tRxEx=Z^qZp(77mصwc[ weiҽ̵vB⌊vg6)LJH25k/*м2",\ ,8,Ub(_JPᕩ U=^劑bi;&8mꏰI qUͧ׺Z#%8B5@9AR@P? GYK%g+؍4'U.@IyF;P[]j*8[^v q$-+8uh8$ 􀫏H!i Δ@!ҡH Q%%GH#)*HE@@B O=K5vt&%4 q$4@! dmTGmiN(fb$Mzi*8iR;޿P'8馫Edӌ84)8< $ݑM<%k&}dӌPL;Ӽa%UShxDՍzcU<GJ#GvQ[(i8R@ٯ\H@1@) $XvkȦyJ4@) έiJOGn`sӼQty nC;9t /sn B 6䑤;|LSq$wn{ȦGcMhyD"؝T=Zi@ݛ/RqMTvqn 47B6W6͵T_snϐM3950Hn&禩HIqnMT{EitunWJ{ S[ӽi8~ɿ;_^K6͂%S/J 0ʒMyt*V*!UbTq"@CđT uqX@tS8M84uJU &mt!]ġi:EVIcCí$Pm,H K!'s=+ ġ*U;#vt qxrYCSU:qhڻٹiC O>4KqL"i6CӑM@GJ}dn@[5CH?Srq u/@Msq2܀8w{ UqhX4ġ-toHiacM@GJ\4Kq؝T4ġ =M447B6͆qh͹i7 -Jܙk ɋ дEqX(EqEK_yl Iv@V*! 5Aґ髶 Ф' +Cb NN:0q 1@q 1@q 1@q 1@q 1@q 1@q 1@q 1@\Cc 8c q:=SnvCY NU 1@q 1@q 1@q 1@q 1@q 1@q 1@q ; c 8;6קi;q 1@q#?X!@\nd\0'K2@q 1@q 1@q 1@q 1@q 17"c Ok78c c[p=.1u 1@q č nK+>ji9x視-oZ<ڕW-˧mۺw֦Ί?r s@$q 1@q 1@q 1@q 1@q 1@q|Bq 1'1zj;86ضGu nl+c c߾-8!n֩=+VR]gԞ_;SӿnS4%8hgLY0'}!q 1@qLrI+n9P˖/;e֮ [ִ|6-l[67g{ QJ2@q 1@q 1@q 1@q 1@q 1@q 1'16տ?8!no[[O_X/kl߀Wb8c :N>قjnTw׭A$7;q 1@q 5>iI&\;C^7'~_Z^#M^q 1@q 1@q 1@q 1@q 1@q 1@|8 I n[]mc7.۶[q>ǯLe 8 nԩS!M:gԦu3̆oSLzɽzQtL^@q 1@\^rgl9WD@\Tս2}7wlWF_>xe*$q 1@q 1@q 1@q 1@q 1@q 1'1'1gxejFmC}{7{C]=2u#28;V8UkJqӠi:)>! 8c 8䕩yH k? .Y n{[8c c#7f!88c 8K7Ǹ-ھm{.x>5 ĥuԞokr- rxiq 1@q 1@q 1@q 1@q 1@q 1@q 1@q 1@@q 1@\CRO\|V) ?mƺU3?,UUH̓`)HBT$Iby"ш-m'fIEg{1#5=]2 "Y0aZ޲׳=?YLĞNl|9ߣn{|ι{s!ٮ>:I7+fљ|BB\H q#!?CB\H q!!.$ąBB\H q!!.$ąBB\H q!!.$ąBB\H 3ąBB\H H6/(C\R$ġ:!.BEPeD kuI]W?~ۏFMڰއ~}͏?~bH>}zJK~!?.E1v~3?X;?"Xc _\_|czҟ_"K{K?? B޻{+{"ݟ{@5DڻW(mi~Tڻwc7}}W;G}Nڻ{?wwESI{ǿj{1x{_7Hռ2%VͿ'?bLvK}^1wUgV ޷~Jڻ/S_Jjy?iRu)xJ{7@_}W/V?}WyZ g>#[bz|W' @ڻ/w7 w}W/SڻMw_G~ɟ]5nI/UWU6[ ޷W?Д޻_?s?&7 `H wI{|Kݧkߗ~ww'"ݧUlT7"ݷWsiN~-S:L{)Vͯ;U)-?+V͏g}7|5 }9xILݤ8^fV?!˝xɍҠiJ/\6![_կPB_07g1tԍo|G!wgzt=y,!lr~lqښP?_R]_y=yþG>-.\Z>C_R?L&.>D\pJQ{kG~OkT~o/ ?o^?pׅ9q~Mڻi|BǠs_˯J{x~cнO{K(,~KڻWj_iFf5W)o%qJ(7CpJ(J(J(J(J(J(J(e(a4L ?\ q!!.P^7B\(J(J(J(J(J(J(J(B qPB qPB %PB %PByDCJ(J(コZu +V*/1\P>@s%!wr]\?YwO KҚ)6*WhB\͚+r%<&^)oJovdVԦT~x XqS+$H{HK *_|NJ&q7oHdCMë(+ɉ1I٬Yɬ!ɫf[zB Bm8UHkJr'V}hB2v/2GVoJ+铦V[#oYj6JN5!ClJ)UGcqgSy jL.f+fU3I{edEl֗ĦWϪY#V~KЅMZBxKv)MoMX=.BzM]4_ݚuR F5ωAh/\/)$2FaDݓ6oH5K])WD'+fM_+ܥݎisìę'X4ֵ@;|wHgM˼鷕mBH>^/tA {.x\  ͤjDbǍTS4wɪN"2COw: {6w) < D q\N>q+ 9,F'9r[ćnݹ}ㅅaDBkImNnoRH&ZqKK̷I!--)dh`KL;Zْkt[2y/[ei9z4̎Cj2;,\aIhɣVWfe%5gP]I(6%[~sq8]r<9dL' vr:q4_kMݎӔjʭ$QJH@¸ۊj>.r=x$El)cHr $'8 ޤԷsrJj%F)@ /@hz@K:L+ZfKAMNiNRo%cD}S2&\ͷhE?Å^~89Xh!1BHsi➬&A6h4M5|mo$\4V tCʖEϴ\WwBB+ OYo23b8Bܭſ6Kd1أ՝F/ʖ>},{Z"dyy3_ΰ m<붺bC/Ť {Nw0ծϻ%#"Εo8Bܓ">;D&{l=٤Mծj{bb=f&4BU=84!.Yt+5F˜0.#سDFmc* HiKwO5rn)&(a}j65B@<.+;a ABv_ [8@}uwַA2V8#׬P3$ Ar8C{(jCH"IS;7=OS]$J`%GXKtCIHU-BڇKnK<2%{g!Cc7ݦ4*S)YJbY+ׅ{K_ fջO+k@{5Pc?~ZN ⾗֘Kk!)/.~"W+b|STTW``w]`%oB{H=;, =F݅`ǻ!=bz&$΍&W!bx_>( $[܅`8QM% !!Q2}u@BL5g!)H}/c%2}xJi-y; J,tI儫8;vU3AR;*# RJ`۝Y FH!CRJi#\|LwR2[$B!JM)n5$wOul%6W<1b $*SJlĈJ# BlR5S%“!y2*څ>d%F}7 \uYQX =wss՜TӃ8i )dJRX 86XIqM1=$mfYv a'1$US~H >̚_+ ;2x.X5ASJ]zXf7FېAM8Q#x+t [8IwFHy@s]+n!)_+wմ@f|ZBxϢ_u<_ԝ(J$=\]R phii7n)8#GgB9&;u"ĥ/ihJNNS{'FH{5qo{yoi q1uTpOrwNA,¹N ɣOIQwˉc}Y|b<kCޒ4/ҷpou:k2?Eʱ j8 w)3ˍJYD[ݻ*I\ׁ׸aD`cL8@T諑FţBlD2@JiT|4*/dH#xT! } -u ؇`}G2qH( w$=1$bj%$$8WNgVd(‹EClI!!QHxofHV"z8vIWH]`B" f'HLT #HC¦HT a'0Cr$!Aɳ|." };Ҧ!Xxs@²|l!AgFE(H!e<Z>/<=i% {<N>^6>.z8YHvdHr,_b 4rH9$=@g9@= 3Rj%$ xp,_z8>XH pV3$א\x{Yd~|]uw=ǘ3s/}߱X~qks'$H(r( Qn3X㇯ !n{K2ӲŨi!֊̭y>|euػOnқVY?X ҹş>elb&ezSz;38\<=rgw 2?)#^S'{M'Ƹ#|{$[HrǓ̽kcu$ hHRĘҩj̐1$GHF޷a]5s mD;H?T],㭤̓KdV$ $KEn%N^HH,1:Z*w0q LUywLn^5Y-$}8}b+a >,~&$G)5[sn%n⪯r;E6˸WuebuRmk$f}9դcur+)RM ADD2,_2{ĬrsJLk !aC՞i!jX9> J"Q2/M6@+1bAmc+OQ[%vklx<XJ\,QA IcLv7JVHm[.X+!!ZUg IkÕj!1V!Hn6/<-+K# ܩ珷1$f+Κ&Hm^ GRM$榘!1jm2gJ=[ 3$&d׎='[ +Kc$oρHNXZ"1D{"]C%^[D;bh༪g4Yz\w 3HCЇn}Kųjh@ޚ!!kop\.&ĭ,l|n`aU7ba_qD 9C6EBkZZA UzZW\t/)Vd[y;r.x9: ~Y,X '0<^^cم~xò<Kl{b{[l,{/x w $*p)x@Mb\S䐀D2X  ApI ʁ| a)Fw.Fjenr 3*RsBJ G%(BA#e4C &v a)[?!$JB1!$T9zkVIDky$,Z{kɘ +CSJ6^Dd(xKvfH]9$n!RJ6~PpD'^om=J<@k[)$hsbHn%rHxJƭ8+COڨqV2qPJՔK@;ܵ6=P.R1$5>CbC)eT;гW @h 䐤omŭ1$%$bIRj &!7°qr$b O)N6 &H+HYat.UܿZ(`J+ޑ*[;_T)\2#3g8)ʊ i_CefǕ0,dvIitR[jJݪ q!b**枿qBd.7y&ݺz)8UɻHqW%5*Ou rHVȾ!z!Ê[ NkÇAqXu0K2V%ͰDN}-r00k!.y.K1䲥:.x9H) 3͐D8H#S!Q>[ s۱@29LyCBxC.Sk%V}pIF8.|^h^d .Abd*ӴH2^(qmc }Tv_U*UVL6/Թ2oZ=lw05 uDWBBܫB\}PQѳGgrӄog7*EPp vW]CrPC"29$'o넸c7]M|Sx^Y֫5_aߒ=<#L$@ȫpy^I-@w)wI/nʋ 7rY[-`N=7/AQv%* rga!X>PҦ x=W^]yNlĘ4e؆`b}pu*~\ }YH 'xu$D|$/.!WM$1$$0\_sU3 J5,$vܽH&$F>jDID Ewe$ +Q6L!aXȫ d JpH% ш6 XHn%&ȒCO{Wċ(1$e Qb+QB+dyߢ$C {mxDž u*@dx\ J@lC,QMĈ'+Q?!)X8?a%7Hʑ E帣D|[+t`%'$˗s8D!jA5 E!vSHR`*G铕8.6 E.e$ +QRHr(x,m K jz!Xȅ(!1 J@r΅ /wi(+ЫŸQ/ #*WWzo>@S7m4G ijo qMz盁Sr=K2emar¹N ɣuxe3'$H'`r޻*M]W^ OaS  %rk[A| 2v<f=ତ+C{k?V'.$LxAm8Kz7f7zXx=ua]W׾6*J^e4b3⸋d@/5넸9W tp*.#] q҄8HH8UO_O%>vIx15;6xrHA#}t5!n_F(w7Db ْHUI-MrVx]0ya}DTn7S.sb?Z ӹş2 ?]Lb.31[ӧ:!N1%&= 2ga,sb `fq@-E2LASWu&ze?08d9 ɠ{v3\PjH$K#un l$KcՔCR뿕h|D^&R@LU>B"!Xn JReI_H 38$ Jl [ EDIeCg$JrW;HC9sH|9$Qip)?$%em%$ҍ*u HJi6Kf-$Q7JHKE$+`D s{B6*DJ\@R5Dy $͎ ~!HL|XIH;UN59HĐ gM(%!.[@"QMQ_rȝcc+@@R9q;姕0nOcx_,EAj6b*(x%^+پK:yZa2Sܠ":!3($K1Wա5CB$!neav1?[-了?Ne'9$fO 8k8 40+. aUyfeC!ՇTiI1 Y-Z 1gFY~&Lan-8(F%&:FїP;_gMG)\BU-@oΖIDQп-$H\mm$قOSޠ$֦DC#$>K)$6ǪUSn%fH b NE2,J$$7. %1UY yR It>ZI~ȌH} Tw[Th%Jb%ʉX*s ;,70+1;H8R ٖX &^}@%R,R*. 1"F pR $l<$B+3;H",:nּJ2WG. ɽvE5ZQ9QO;H"T.)1$Xyek .1k[4/iHl}Hdu.$RJE5X⃕`}pI#^(jrVJI5IHM9:$6sf%"ՔAa)2ʢZ wD eT9HxK)eU[QVӐCE-YHhHRj\QJ.$QJSv{X$8`FHo(̑B^(}%;3Xp ͔ReEOI3qLZd_BE4qe*l<:' ͭn[ ir.qJݪ qLޢqnS6*yAU;s3nwF BC,E:Q'%{+/cy^_F|t9aUCc:|G*:j27S;)&}),SӼ:oMc&[, NW7wè,gxOn%H2 m7Q>t Nڀ}$bw}TIm@Ab\dD (1$q Q[#fBbRM3$8WlԝjqBTӒ7/!#$_+ |8#fAbh ҡ(xH\&m+Q"H"$yAY0x_+4C{]9$9|U>Xtg$ĥjfH"BB` LVWxǐĵ2' $;X4J]!c%=Ʀ(ۦX`9H %x!F&4P} PW&Xl@ B"_!J}&H *%DLVWVJ ^C J(,S9 J'b}_Dlʮ!FXDS,$*(+Bb?72Y]XuQXHL> א/(gqRDöVr,$V! (&s#f)$[ɹ8u;%d#Ҥ!.RgU3 1^BKzT}\$q EA35>@ mwɨ辪}}U૬l _Hse$l#''ߴzV7y~jZqSѯtIN?qg7*EPp vS];IrPC"29$\COh)]C$"ľ)} z &  {}%F G2h/vJ,bI8vVN#p1$֛W$v% IeE#$ 1O@!ABe悛W%Vqo%ZDaT#3З:Hx_Ht9Ġ $!$Hǡ0Pn% JX )JxeʳRDl Q4$Y\xd&=sh%@BI>DNY+Ic%&3qT<υ`g *Vb ͒{7,H9j XuӼ4T^S͠ H4)p];xw+YHJ@qc%pD{Hlǡ@pv]d͕j:F:$U Re@!qUR+a݌CmU;P`\xB89/I5tB&^}mCG2ŚxVb3w^;PJ(H{ Hl}Z|VM?ıjbHhJ)@`%:v>@^55lZQz?V~](yǂen;]7J/>A7ouc'i81M-_x!!uպ0QZk2opƸޝD%W{}_L7p8FH r2Pݢ 1H#HyfCp1Z1D Nڵ Κ>yqIb:XV ({̱D]w}Ҋ2ݪ"#w^\IO^yaSl"ѫ/ybK^kՔjF̷gUu;1kؾS3 I5Q7N5ZI5Xu$)>I|'(\h%'Rz\y&3xM}6I4ţ)$Rz,wJLMa!)xHbEGSͬ+'E:xddX%ܩ& 0ӻ)wCH4Ji}NjZIEFBw I#xHD9PM}H(b}e 1g+zHOCRqYhō>X@= 4 S-]h6E4SڣTd n)mJYA 1jX//!bĤ$x 1z1$B5Ǭ)e∙a?uA={7*{.epva)O==#w=wp^bTT^$UV/dU}iu/nҁ!nMgV𭟉3;^0Bboa8u܇ 2/EBk~Fqp&kƩn& V//w\¤Yjɏ^+B喸yp {CGgL{뻒Fxu: }qnVOkU?O L_;Սco~b*֔;,u_54eJ_SMҔ)Շ ޽KJtއ-)Sm.'}Ivv߃C5e'nՔؖ0Cw3*yHTrw9 Ӕ $=Cr$Z8QArd IdČ]w-WvHѽ^!()&b8tY䏃a[~^5͐6A I`UmU͒B!$ŒMFꮝβ;$]-8gU!7վrj5$Z剽q訢TSmHbIϞ!^!7#%5-4l$y}H ue~Sr$_`1;X `!)\0HU^ϪfJ!9gI>Rr<cj;$qOC?kn$$Ͷ+a^[C+HC; I jY !Q2U5զmQq !ݎ#a`xŝB 3/8vJg YD%l|\U a˨e Id;o \>. N}|wPﰺ!'X!UgI L{34VW5x^z!!}yp=e ,9)L85^'L %2=C[C E&tʆ(w IeWHgSMR~voCl($yKC,H UN! UՌl,I*lBTXmj:rVTrVM?Mԣjn$e"+#Tem\Κ+9k*6sQXH'Hds@~oa7@5$tJwK*u2Û1^B eB eSJH+wB29Lz2z9_c b uZRJ7Te*b8^ +QlaVWd50)'pU7h{*TMSjn\nR Up{US:2oMBW姚imOFvT_+IO4Nm> rᅩQJ5 q!!.$ąB qB|\{є*J_UP8I>oeC|Ts!o֨6US~ ]଩Ql+ R5j1k }ɟ\IV2R`[qA`ݩf/_j$Y3@.>kl.W!0k>ipB1Y8畍8zW͍ TsCj*M$.8GEBB\H qPBB\(P YK1 Y6bHLE?7 \κiSQPIS_ $ݶ4p~i HbҡW:QM%YFJo X\C+Q@RYӾr~rOV|`z5 )ۼW|v:@F_-UM!QNx?ؖ/NQ==<$ąJH %$ąJH %+S_߰l.?v -HmBu9U U߃ΎX{4UrT6)jn7]6T ]lWc U`B#iz%,q*#-Sb(_ ' 6wuUyl6U$|U8壚jXII-TͩJ')L*Sg!!.$ąPLZa&wOGGjrky<ݼlnG裏nMe?O鸵 ?deA,|#tSn2=y*x~R.?l"&?rZdYS#Kb{WUŻWkj=wU)or;icɫ/K zgl ٻs1_WWuHkfQ^KLٱ=;֟PE/\W;R5i {y="Ҝ4w>*xg7']t5? M&^Awá\{-1!_64\%Ν%iXCBkEK"^ǎ,uDDcqly^~p,>>~rdDC{]~q, g?Kw[Eޔf> ~INb.T3+yYt%v'#wj%(j]J$pStCo#ʂy3ſ'ѠE>J،YqLpԕ nP+gQB>*2J&7QR,_!Q ^ϫC/ajc#Nɾ9Qp%9UKޓz ➼K+#YzS=$>U'hMH6U1ۋ?ijJLڻ7+S+QP|;TTN-&˹!IrwVr |tX]J>:{D[I/%tlGĶAxbHpgKIԝjʭh/!$]R:,]);$MېC0.Ol%qOnY5ŐXTsg !I#Ji{wr+9g͎sRH<""^?"/s=7N ɠK峢ɭt֔øhjwQ tTRAsIYVlh<5ѐCلw X!UFi=gHjo/!$xRa+!$;ѠԺSʪrm+eVQ/U͚z!P+qz'Ľj&C2( T3FYI#ن!Bҋ}VQAwBr\ԻqXu"JiNwjZK_!*Z 5;D|-;DVB;D'nCA2;$ܩ&'Gݻ](;en%8H5GKv+]bJO  Ϋ(ȋTrP%q48 P-r/i"zi '8z(۸!nu𼍢D5ջWM>yA:[j'sKkWqT=I2dUߦID&z&<*r: tBNľz}wmf=mI=ǁdJ# Nzd9r1LM:} BNIo,A'B|NFz[~(Oη |ߨk!JᷤT'a!U{G/@'xD -ݡJV#+¦Y )/\p9zyQ6v%*d0p:׳_\Qql4  A1$x*gs)$_ !鏻 m.:qD݅`7JtIvW!XJU3H0TCbQBoJ:$M%߄!XܔtL)` C?;$dp SJ!X9$CBG2 Cr+'54'&~Vr+mwHb!2Knt4k*CSVBH0Zj%rH0TVB`4IMO !RjʭV ̅'Cd%rHޔz$=%O1j qYqS"m LIߛnCHokHҤkߛz5R/q1${\ޛF<[n|EgH0wx\&m% _RH0zlwr+x !AE< fn]@/A'3%}\dL) 8Ce !v=ZSDwz˫|p<Io4;o4Q4,Fqwq8;PCVtDPӹoD4e.o彙뫧F㊊R8B.()](t!Q Z4jr}pSP)]8;Cܵ 馸͡YK=~ ʡHoNtz)Bsr J PeM-QvKRfהLvk qKu_*H.#鋑M)r,{B~]|սEMRL>xqHL;ĦӞ}$ġI Q#BnT7}xET&*~丫,Y:`ǽCB'IO!E1BH䐌IwJQJ & Zd_YBҼ۝[ ,m%]!$? !H〲ICr+3ȎBcDRl J$.+ `)!ARiV ]}[ !tjʭuqD݅`V22tP "^KCr+G ^1$dp SӥfMx.Hn%]=~1$x- J|SJ)UHH!X9$2PUBHT[ ޺77C[ΒB(d[ Vm%E%ܩ& z7Kx.,NIǬWJ Nei%XKWB!K%k+vo&uWY.l '`ٍҤKt,Ho,w, Kd8].H4;nu, ȱbo|2ZQ|{**R,!ӡh0} r: tK54޴lsPKv÷FiOvS|+}i;H gIoh\)hVd9@z;vipqėurh̚H8%Y|[|GLo[Q¯C| uH_tҹi@RM"> wVi5'3Y OK+ׄ_K7T}J_ .!'Gt< "W5J+(:@iVޔ~BaKf)$}{;$(=索`d yK HCAXI ߮8d/m%ȻuHF 'T56HU3H_T[I#r'd J8V"!Xd%LjI P=q JƞRB,4D)`V2^y$-$L̨>h$x $R* b+CMY3HiS+PHBr+ðg H!AkiVIsT5QJ!XV9~wked%rHKxd 5et 6 A~!r+ޢRB(]fV?}4f&胮!i[5F&MC{\& ?^JHMz.(+A_ Ak`jʭwՔC(ςv/ ׏D'@'a*|w}i)Mc=z6{4 y=,J YYk8,~SFClp7C#·R|gKBB+ qnm=_2m̬OݘP(XfC=m!rg x$d1KH`u7OAIH{Dx擻4V8,`yl 3"&ő,7HhAM@\:jYa[KG2%%y{,!bxRPH"vHv\ym-Gvf_Չ!D!d=+)D{lBbD)ͻm{$郕 OsHl+ϻm{'*Hq?$GB6T~2 :- 6T~2J qȻm{W:+Cbx$zG͚ `^!1Ddh;DLCAbYܶwr+ANiX%WK JĐ Wq~2:pV^ɂxm .K7HRJm*hycL(-$mdI*v"VKֽBb%mJ`% $#8JjvZ9$)>W\5yHH_^!1-tC[ Jv_ IcH %!:]D$‹UJ5Bլ{Pqۛqq)$Cb( )ymﰕKG#\<%mZ:dGH"dh; 4$QTΒ%\R;;HH_=M#!דgACmiWH!?+ɀCI*O[ R4'+$1r(k̽K8Hڙ} g%U+qi_d Իd澪SWWBzU+L }:7:8Y. j3 Ywq/ q<@z{,MG>w!|fH{%䓷Tp^E7.U×8*8 8cK0YTOueXr@}{d+t`67 } Ces$칆U6J'[Y^њKrt-`^ qp78+v~@NF_b6}IoLSrtJ" ȰwIg _Eװv tNQdnZE tƭ ̙ t"Tػ])1KC ?$\R`}: %H| --cxR.ާ [ t<)\2>n юKz {Ʌ`5@rcIx<9RA-d;t`\ H F7"q;L& \f[HT5Ab|$O!AzkA_r+zXRJŧ`n )X$b+A'U-$x[լCrƃXOhf [ JTE/W0$u 1 J HĐ  ìwr+F֒!7Ab, ׻(p\9$V7RkpJy8k;AC8H %GIVcIHf}nUsnIAb, YZ=Z8H8lA#$*%6?KBJ)eU3 JĐ ԌJ:ŪyTMqH {'%f!$ƒx⣈U3F[I?GY+$TQA0w ɘ|AJiqaJڛŪY} '0׏-9G<I?nKSM{+)8IV"X™G@+A@~J:!1$g"JX599bڽ%B7jp-] z~@__?PV.q( 30`۸`04J`9̅ALrF Fh=P6.Ťt[;rYBB+![[W'WTTqjљէꭊ[Hw 6OZ~GHyBW@u4 m}!j#P-{ m^K-G8g)0 azrrߡr YKB {M 6! L0|b5<iZ~(E|Ο Pٞ'|KzȤ;{(g qһOѡF miN=6]3K7I7F&"DM`#X?#0=:ߧ*f% OB`^[ArDc .)ݎZDH%%, s'H$ d|t10}&\59+R*'.j*(Yϔ< @Ϲjֈ#(H̏lVҽmuduHU-$F.4\N T͑q!EeUYл}UK 1W(tOMl4$8Ji6'Jj^`rHb$LpIbH`i$!4\tY>f3j'Hqkjn%KBJ8 RUjzD:φ`%qp9HvgH kjʭ$i;OKB.jnPH+ٝzJjst$ xWJ< )b>&B). _]bS)٫*>^e>W}VA78MzwBnizF$,޽|!%@Q#71wmNYBBܫH;I~N,@BRABJA$G- giN J0Jjӹq3^pIP;yn[HB!$,rQY  хK#7&{Rs֕z2|y^rIoOz-y~a$s/c27`[|']޴z8J_ܛd(v3!CdB:bi? q`͞ga.P@U~.AǓ)\OJ0gqV`2H.? ǖ DUU~@F2Yy. klTU2!ct,*u#HVt!3 x/?d_B&[JUjC|!nsiSI!Ɏ9j:d@: פ+k (\5[!<+t+@)/O T5)0kN($q`V2{P ^t=wWVH!Y+.^y@YHjVt96W&I@F>ja%@)Jjd e CrHvRME@)uYZU ~nߐ(|M^VCl%;K ok#~X`}sTW/<#T$ L5DY|\Rc a+k[>Cb%^LKJH23rGV@7z_T5DaHT5!$IJ1;28\S$Vu};JL`9J (U⬡wAbd]%$!uJ ]h dٽsH{X ³6}fK__ |3xw_ս9,ńI!{] d'A$^+d9Dzd+aC)I qHo]:YnjL`ۺi[ .$ĽNL&2LrĶݠIo,Iob)cabIU:ed'=j7"& 5@w.r⦀XBMU (n q`n Io`nc&Z02Xù:?E|2cԹ>[r!>~HW4trZ#H])ck>j=[rnM^(XK?f{r&(KTn86*ryGᾇ7'tfG%ەAzu*aCʮDRc\ I Hդ9d=˛[aC2s>>*d%jhO1jk@d/g%oiT䳦Y. Ug贲WMyE N7X*Rie׽Zu!h;`%;Rl P[CUHX+4׮]VR=Cb@.@zE ICXPJuOl%Y$)- Zl^yrHt`Vx Ji1d$֌BbpggQ[!REv:ͭiHRʱ)j skI; ;\RDۥ0=29%N_Q6K4OO& Oʆ`MX *;.mEeCTS@ߴC̕C2hD2S&H(i+Hƭ%]p.8~JeЇlb_EoH$RmXI+"H*u`%CnT)$8|(`@Ĺ^!!X9 JPN0'HQi$JiD Q9CEݩ&k%mU2 /cTh#:$1_T$8 PVme$^y 3>K J䐘{Ofu];XK#S`9'q*RzYM>KմZԗU5̬ 84&>([ Ɉr}c&O(QrHRVH6vNTD\Z^ !*@)hNn%Y v 5e`J1l[VMNifj!T $8.|8k(<7ʼU,$BB)Ǐ{dn̬N8qg%Ybє(B%TȋJ rH*gIMCK{G@b5˽ N5H/zfg4 NY)OczD;_5$VH|1JFNi/Pnb*4,I\.a!1vVBjq8|\ \:CKFN]ySx_zP UV-@OULsI#1JZHowh'KSYBHHo鈖>@П^sB $8P{HpQSݘ]멓,d9Lzӛpk! s}&ˁ@Yw)v7r@[Y3ˡV8[&\q{G'PLzg8fc%k.|pJn}}E'%_}ojZ*Kq;skX;m /S&I3]d= KL3EwO3 Z^q$]0Zڢ^߲B6'yI9睋tϥ>h/eIC5e+\J*Iq( T.d,4QȐRF R9*'PMJ&I%OHdUl+X0  j&#ϚԽjdd%MOp+'%Er&O$"?`!ɮ+əH\o͊+/Q n%j.:CÍsѬ|\s%fI7HNddHiTh%5 g-'H$oQr _+M׌,C5%)27BiV5o DBuA'[7n43]RF$eHb1Z $d,pՔT5THR/3CgƇd8;U?2) bS5'<*3L&C$"QRQ10aGn+&%5ː)O0ydռqAvD׏0Us&-S"t(%ӣZfj$D2zP$pR$#z0>Jyfd,v+QDDm R#Ұ{O%䂋+\TޤD!@k"/XsNHe2*+9:sرzܳ;]LeZr#a@\*m!v3A;&ΜswCk ½z zk$vAh]X. zcS5z1Bmˀ6J0JkXf@oՈD+,MeAo2X7frk'` ϗ'7ikhy"ٛ s=*2{X=y¯ʆ5j˺@TݧG ,Eơ3i4G}ӻUSԃ LkvOܴϚq ?#>4`d)8Sٖs35)X`>0M=IF]s%hY$EBy.?$0&)fV pRVwq7/`6]>"ɞx J+) H `Vuz9@E )M?ri.f"t ,J.Lt*P"ё)8ƩrWN"A.jG3U)XUWuYL$6͈]2MƮI6F&dL.+ B$e. xꭤ*)8q9A5H&SiRKSq\RIKR d8g8k6C$2𚡚b%Hҍː ߇`+YUrBԉ疂UjȐRjV}{.W"t2ޜ+^E|tVRd5Q$ մ$]^%"I\{#><sX(녳ӸZVʬ/"yɏ'0Tn%Qg.BUsA!c\ R[1%Pռ"|qV>\Ӂ/JϖF$'|ۑTP55V#c\$2zVU,l9'g%|37Zb*sZV򅌕ȐRskg̰Bf_# -\$bA5UU<؉J~DiB- zf@o7ݧكVݧbV:!.uBx6;lۮ?fpwסzz?=;z{k5zf[z׭|7ecݾhqF g?]vuLzԕT#?[ՈPw%z }Wҥ%#k+ C$C(׭YC!Qz_uA,75Cvݒ-B>ڕ ]靾?t# nmǚ]ɂ- kk@Pz"`'w吱9s[.v"Ϛ%zkz)iuL0H7aƀr7q_:5ԇVO]jǼzMl#qW-An#>7ТO1>8p(l|׸OzC @Ԥ+oZ}F@ܢgo-$ {'2 }}21A {;Nu%HRF]YWE]ib\|o-1a3?i#`c YfLraćn\v9vcxgE?]nAB"d$>Js<#ZlHѻ/.;W͔\n H$9sKx Y?5Q$jE|uC" .\btqw_0c&daЕR{fTJ<+df"+^ϚP>|d34q/.\a\5q[t4jR.&n%gκ>sv-YˈDÇ<>܈ V2$"Ѧ.\aNCx[tGj," O}Yp<}OŦQ hc>LU0qe>?pŸ%_0ϚEr2-2&zI%ޛSͦY K$O?-l|[j~&hRMHdDԈ zO;I$ϓZkŸmfY(J{8ZIm5~QDl s>Ç7pVD I ᴇp4XIN57- ZU!zRM@$t3uDHr6lxKVo;3Y84k &ޏ&JηJDG:PMJ5vo~&RAʊD'.'wvHsM9 JJ$Z>̐j$'<4fdnF$rƔL TMmH|8/AD3MA"іgǁK2}$Ӗ3{:"-6$^־-{r" Ю,M̀.:Wk%oF ?[Ѕ@>9J`7q{?r-ΉhswQJs~*ky0G Ohcqv )훩SOrٜs=J}y' @$}ڔ8p(̶o1Pi,`s{w8W"@ ^𤻶 (ɾW|zr}ʞ&~,LkŽ^ovecԕzNl{s r]ٙteG^׍Gɍ~+l ʇ5;Bl6S+^X}m qWvܛm|wK- |xYו}uHe e`wK^{ADrK^H8EJmveMoKf -swÇ@Sj[Zoܽ/ah7c& WߌlI۷<>3z>lcJ/>b{3c@ķ'qc+&8-8pcsay6Ws兡./Xe"͊6z;yq^sn~yoqaѭa\6 H]?G"oG6-J0ȇ>{WY7vI1XrNs:|xHg|=E Jq+$TDͱGaz}*8+9+IK$vʹUڈ_Iع <լJR"9H p~Yj^gTJD] 蠉+ɹϤ|wI!iäKt L4XadxgfY{E!%:Lf[ _$X‡K8)Yf+I5n6A$">̬`$%FլMNqf,/|''|`<~.[Ib%7&?5c_"$'u}>!HZt0+y[s{Dg#C~.ct ܘl G]s1TSg%V-gM\$g"JX"&$i|61fqZH5u"Qf SfI(]F?6jqC.9DKM;@Ĺʉ s;q ?kѽC pl'U*H vL}ޓjdG%v%DBޒIaT X$6Q.UtִCY591+p^ Ts(Ϛ_vJL1['jӎ‘bf o@*7Tוa6 MHu,ct? ެ9Uw|bK^!դfi+&d TCfD*T\W&@V5 5 +h*5.yED2"eYs5R+(2@&UAi* Qf=,C$T.̄ U7@ x٤0> HGc\w \q8W W Ε qV`4uT&YHM55qlPPMs'-(e}\]:ROMPPմ Jd%dxBPZӡlGjxīWիW!]q8s8W ss LYe4IS$ M}QpvV=,I@r|dȺiUL.qk8@9@\ WW/WW_ vW'W*`=WWS 6Wro+R]YkH+@v;\ ~\ȹW?\4Z\\~\W ůѮl5te{+[So wٻ=zTŇ kS]ٙj|{ʮғ (+S@˭l殧{ jkpfp1 .=՛1rkGS]y)z)\=nUW]٘j|kp`p=Օ)>\`JS|葻7Еc"Dɣ* /z2z.z"z6 vip\. $z,z2:\=\}?6 '狀r0cîĕsܗ{{>]I'Ň'R|HwP6%>~F]_=uEP"E"IW+I$eTw^ҕ;t]E]ٚ~qX$+=<wE\ۈ[?\].ʬv܀jQp5)j*}Qp@@xnpuMp59:;|:|wqp٠+r[puN@@qUpqp1) |(:=:CJkn n  ጠӃR|2|v&c"$V$HڳڡUDD$H"iψU-Si#ִDҞIJQ+i75%*Ջd\J| DҚVP$4qJL"[Xo%y"iB$Kbj[I{Vrۀ$e%H @X$>+J5m%ɲfI+Jl"iEzyDժ8qkI0Ib%OWe% V2+N$ h%ϭ%MI{Z$՚Z"ډ"I[W6iִ՚əE/FddJ2NpkJ$ +9wDb-VZ6_$H˻w^KZQ+EbZK2Np+{Ej D8v\ڵJ"iEERtD$bkрJ+(V_ ִI$?qIF:$Z"L-i-M\EdHCEbYK=)`%HF"iM\*HZNp+3&*Db iDD İEnIJZ}ə̉}D*[I.s!(I;3 ~H/i&֌ǥNp+HT09&Y=Qiǣl6NOel9&(M[|J:$8C{U$8b5 NCJ:!d'T|X*a*k9wr&v& {mȽDĭ\U;/ qgW!˩UȈ"DՂqpu+GS]y)Օ RoOnIu%Lp~{v'DXL\l6@߅ W!Ur7 V B*\KU8^\*vpj}3 ְܕ1W*7ݕ îb-ܕR]yJՕR\ЕrWf+)>8"XO:'Wsxp:'OW\ȳUk.+XqڔnJa3+SR 庒\ejՌ}M   VoERԪsǨPTJU!';ԃI 'uuɘ5kwN]AuGZNV3+M*Nǯvpջe Bբ6?'HRױQwl]Vz/^E_՞lZfsȸ\qzB-ԣ܉jz+M?224GhWnYkHj@j"!&S$bA+Q%2QB]JjMIHİHå<WImpPER"Xl6  EUJ4+)+*2+%\" EB#`1<#eqWPm#h0$fo2DN\Ո4R t+'vT!XTn"b H*m"H$E@w^%6q U <4cy'#]*ՊLj+<u ߳["1/+OMqT='U%g0wڂElԅ379[@|{Wȑw/OrQBLvIǦbSè'j_UR %YQW\ \II^'h/?սk# Ջ@!6$9@+|Jql}[ǘ[vĽ {A˕ǯe˶p{wB6a@F}Q=~ '{-q^U=;uĥȘ\q+S φa5ҍS U UVmJTm_!5"Кp=*(b"2g*^ĠluGM"~(VB6h"XS;, $EC$`^ɆNM ?AU!!436E$OH `E&dETm SX+ FTՠ] Y|5 YbMŤ^KO>Μ?ggέݿ8|s~I&.[=#[2vC&B@>Tgs|=/Jȡ8(F0U<*x0X}_OʊrW\\I% s;fGH^ " +_ʧӺ nMo}v^_4{/W>~cpg?!NT ڄqC-O>Ȩaï&V`2͆fO[r$S3,Se%ŧ @(jc4T b2.VX@+Q|ˈDF SjV7utuqZ גz/I3{;fU'ĹP.6˕!)'T DR @"_V,s@sQ-g"LdGCcɀ !N8,Qy$Ej {}0^yadrԩt/0#TȞ,, z0ݒLM!FfSӚbEB #;)KU(2c*lr%-δiJq[Α0X(ҦhS|`F#E+1eM`ӛAQa&.:h͊;#KDI=%Tbͯ(:C|;1в8X}^$E9y8ք'gN4aZAYpKT7A;=u! v{N# yV(xdjpJ:m\KR:EE=m"N*]3/3'tCDl]ލk|ڤ)e:P3ox h,VJU;S%^q`!E#8xQ&S 躁ip@AQ>ȵXz@ϛ_EeF &WWj]T2#*N:v.:s,w~RUJzGDdT1jE83j(ᝈ`XPj5[jƔCtU_NLD*\HanڏO3 @;iiz6UQp A .LU>+Q۾cA(k"<G''A&PWqS:MZS*;vkke!M.liʎX7D#?wKqw"ڵb@]:1<Ϧ㮮ѣ:л.k%rtK}߯+>v>Ab(?5xH*xDҀ+1a<>AG\A\ DWWq<qqE)B"LrŧL_=˛xkx'~2c/ɮeox01k ~Y(/z/ X]V_064 o;;~bUd{lqgY~I(mS=-HGО $o-J=eO0ZdKgR8JQڜa) ŀ  #-NPB>BJZd=yq\ܾϗ܀(7t ģxL y8CiQBsjV'T"SXOMSZ܄ ADRi7T>'L(B &dmILqĸk}|,AC %dP=unBJPZwveF'C +Y' %" @Yd))4!J3nĜi&PMߜmMN 2~ Mfä&S ’I&v3Ho TqEx\*\W+6f̣no 9 g:^R Q$I]#Gwkz}|Ďe;@ܧlp䚝 qֶwmt=zGlW(wN]]U>uk ɕtS<$JDɀ*vx~ڽk#AG\A\ DWW~6UpE)PC+Wa<5[G׿qEPW)hN3f1k֎i: W#h_o1 /8HɸeeXp Hc ȫv1ɜF<9jVkcFuǟX6lZnHRfs!\4,~,载+`:ېJqCJlٻa N1g؞{->M` )Ϙ[[pykSkhSp틖r퓧.I5")vŀͱ={H@as!ej@Z]95yQ2QDHGEY}4ЎJ(v FҗX1GJBɗ!eŔ 4_=V8~Qb̌@JT8JS٦0*v4  tPpS THx$‘RpydJzL},J5.PZ IU7=LMzQz)ٙfFZGf@ eJHsϴ@{"N4Xq$ W$Q!#(,4iÄ́ x$; jH@$KExqM}d55MHCw(KTL; Ƶ6R{˩(,FzQBJQf5N˔P`vEfnJ"mF)$m,!'5:Քl-$YHB|˴`jd,F=bD/=F|lA )坞tgJ>5!Dn4Q32tSHQʁB[|m7@BBJ r8M$PZ.-nBR!#QjsB4c )B$S܄2&X"(5nBW*g5 %%4`&TNeVJw-XA+Mc sx4n)ev7!Eȴv6GPX))MSBDZݔ]m%(MJ\ؔU2YS-P-&Özٖ,ͥ8![NT5F& Q?Ȩ r%ʁ+~8ʬUg4rːrWyDT @TOE-R07-e{ S_>>r {vBܯN\vs[{tzoR[?~g+.J_I7A(bg (AG\A\U++[v > {h !x<*= c`A@j^ fzoNXlˉ 1◝ckvqzLy]l}snm(L]aɀ\. o{R*0s9:QR! Q "l`bkj<KI ^^7nf<(U8ZZL#:ʝ*b e^@z5YlFeIM%n&Sre)$L,!M^x G|›0'cpY94=N񴏠Jc̀K}2^rj[edzf2#;Qj G l-I]qd95E5&Tp}/FpYJ@yBoqX IV'U7fBiYSGv&R$P96^KfQ& j,(F[܀2%TڎGL uF`d L4dFDZB>5Md9 łS͏'UlS0HP4PDYȓChC rHj7|,A=rU(AMvH j7Aq˔@&JU(E+IB JͤPMP@S6HILwȹLjw4Vf2fB [uQ>T;i]7nue1pTԄPᗎ%a@i7AMaMP*Zl<DzŒ@5 uzSF5󁱩z)R ji7jT)U/)ͺQڢ('Sn^,WO4'jMȨ`+q5&Wz+A(ʫ|g0g!富rez( }.Qy- #uB܎0<G q}wv_Iw1uk![*R(PP` dz WqmQ0{9'xdԎC\iB"zoVƄV$I9hϸן'׎4G2zUɍMٶ`hB~sϣ&ި1 %e _y2.`Y>Qn8 ][HP̱27zP!e,(vtuEcУ=TphW1m/ F-) c?HR>#77j<$oE?w(1e#a41HXϛQ,13ĕأy<N@$SR#d)2gciv3^ " >B2#(RWiV,z¡/Q9QGnT,Ax1ThNcdIM'iIA*˓ꓭܾ͂Nq˴ٿ%); SvҸGõom[ =zٍnLkj-E8P#(KHi$i~@ɏiJ s>xBJ@@ vFayPQ@FRNҺ'ęt.E! qeHXHӿK 4R %&(֌j띣L 6&!/7%dV< 2 =&hsII70 qafo7%{i %Dv7!Ei IˡI)Jy<% (qk(!LtJP-YSDjB@ʵ=oHٞMm5ͩm{Qlk?Qj.Q[a#wJ뉦䡔G#7Cx\1\*;cW p~1qQzD͹'fߌܸq:@ܧPq[:;_q?^Rӻve=,J 1W]LvU<$qr<لҀ+1afSj"͂u*$({PZ$/-(NBLYo0D)(8=bGB@: i\#N#gh\xxԷ1v=)- ZnI9$ RN!1e<  [krcER^j&f|R3S#蕱{L`aQ~!^szPMllԄ"`^ڌ^A4 $v@@ aP| 5۵Wd%1ǕЀSĸ'kjb񑊲2Z%Q!Cp Q%jDEMC@VvN5ĂgbN @s LEx[EIf "ɛXCU<+ Zc"fĚk* p.:S̢Z y%r%$wG ] ""b_\j|k FgbȓP;߂LT&pfL.DaOq$Ţ*<|@YY^p2QGzh:SEvqfJº1dTe'Q uYqVԁ1(cT+ֻٯh\* '!*(W 8+ ېiF 5A9ِdD m)Bf<5*[M9Qh+Jװ7279t++ TfL~o)x(`Q KIZ"ApF›EK\X#myU,kMG@ces59d/mQ'T3Bb%rd:,m?#j D2.@~Z9 Lx8O&m&qҞ8yBe9Pfyp^S qS}.%yL(k>om-Ïk JpF[d}^Q99|kӈsy8~78Ii"݊A-WTZ֗L341 Ɗ:9Gt>PuM7R)9߼9*@I 1"kj#JV + #Nx {T?Jx'/~TXk,<UdpG >1W5 Z,:@+//q++ I"\UŞL, ҤxҎعO0_LfC/%~9 Fs×^NOQbhU,ubwbi>3v7 ~aS<Գ6JV-!q&kdJr?U)]/e0eIjSdA%n  YBc&0^Qcb{23/ve A|cG6[Ϯϥ@?RPC`+wf1A23JgOQ4P1P8Y|2THT\Z1՘ kBE_\2 -/b:Tc s /reh:U"urj; r_վ$W!׻vk+_}%Uo`٘hS:jSגRfHW+'n&V2f\pߧon~ k) ׻4e%p-gop-0eoǘ/E+//.(W.S]_v^c}\?RKTueC熢^cOCs\y.cY>5dn8ّ R }t5^ŧIS JeWtyYFQ/)~%nse$UYyNSQ(FSݔl蔣jkZw ˙l!GD9=27u~ZjCϢZ56)y˚9So43eYU"ְjo Ff# Pl\GmO%?yMEܪ f&ׇgTSjheٮq 2)gC# APfΚ%O}їCL&jּ]5t ćj玆I Ӯ1Pԧ%XF5JeeY8Pz_up2Mnt4۩43:mFZ4W 7,˚n.i79}zy9-VT+6?+)5^jxfF٧&?Ȫe4k^4L!mϚ]>biNn5S#?V<.5rާr8{Mo[(3osnj}c =SgC9*ЕfyS>ԧvT߇͆ueQuRJYSK̟YS߃. ԩd@CUiS976Q_/ =7W&մuW ub>ijq B/Ye~CBf@ye?*@D{e7a20Ee1IL3lژJb  Ϋ &8Sx7;!T~]BpR\>_T$q'RiqT.o#ڵVjZ_,Z;j/C#׻B\ w_~ʵ[j'-G?uz vr;ʷj?镫ĝrChp+^0d䢔܊ֻa\*\m h֯ <ꔕLykzjRV{~,_,m(aJ rsgOI*`kIVKr}گn)׻ uo~k՗6+?Uu_eKrDTߡn*{ 5wҷj"W%Zosrhq+CJ| \urntZK)+MuW:zjRV+?,WKJx q rs߁#(Օ AdWѣ<} rYy sr=̼Vͷa\qw<ڳV5;m5{7} {ڴn qNs8s8s8s8s8s8s8s8s8s8s8s8s8s8s8+ s'9@9@  na|x%޿GLHs8wB9@9@9@9@9@9@9@9@9@9@9@9@9@9@9@\e8wB9@9@z{w6mw5aڴl9r-޿3>`9@;!                  n޴i?۽˓Fr|B~'/qjf@\g3 n[nĩqN:@b8Kf^gb{^-{}|r_.k'o))J+^7ۏ6{v'i:omz<߸VW?瞮Wq@\J5~K Sk\[4mLPD¾K8=UﷳfYUS? .e@ҷK)a<+|i@`3slT4Ǐ #RJjGٌ0WҀ8_-%(PQBXޑ7J9=ǿ0@(~!}=03\Ϝu&js769=k A<}. 9.BV2V! jFy#*%K |m/ -A%Ȣz< N?[Ն62SҀ8p A]Zj#r?:zQ8qfk |h 'g/d51ӻ(R,mV@x\Jg3c R⊩f jg-+bGPx 'LF2kxqCD5Q!{O 5y88+x PPFi*y n[3mPL| z'3 ݳJV1b}(qԽ< dk Vޟw}<qweWGw~W[F᪋n8so/j)q)qPs @US@}5 <'/g)qjj@gٛS n[5)qP5 C:g,k9^3hyfgWݱ~ϫ_}s{Y{A8!o{77ΩS_s[ٶ5X߶Fm{G78Rw,#F|⬔#| Sx r?LdN STxq՟ qm돌$ @;e1ĩUSɀ8ˀVz?| ?xц+Zol?ރN0f#ڎ)Wك Wxgdo8> cz|Sf;myjna+bs&#_Pxg)c/A cyGV (4u@ޯ6#Vf@3u&j~3LR@eQRB|u_1vgY"h% @\u~ҷGG\mo 31g3c qx<>5c+x#( ?kMtޥ抷||kGw{K?@(|.b [ Uy]吃 kǵqPkUb<ޯWV Q6!q֠*T3&27b|B#*lQ!{O w8-7C?3֠UGaD%(ů03 >2 Vi*A( N8AW5H ռ(0 Ɉ=!qE+[3%(dfU۠w_΀}䓿򵓿r^&7ˮ;G'zɟSTM+TOSPLhq?'z8_?f@gKqz@9 V↸(qPs @US9+qvtާy?-Y8{27N׾:^>픱o{7{YnΧq>w}_gf>&.r[}JӞ#W?ٻg刣ԑP(W6lW[q:n`=3,qZdz1 ƣmC@rs#Y8+_Z V, Z"Q6غ BWtKOW@Mf|6? [ vgfZUQG(}\~xՆF @+*Qw^yA<#`@swie7+Yy@(|.b [ YfSO(/OfHy\!JoV75(^ ш@y\-5 Ռ`4;#k9ȷ#BamW lt6(X~5y88Qh2 i[}(Fm\4]iŭ'_[tE?亓/L7H5 XsP= iNcw3 TP&rE׿!\ǟ<}7Ԁ8k5 \iqz7ĭDCS qH5 9-y?--&@^@ܷN߾Sxi %ss^I \8W>;?+m^:!L(Mc@ 0 N7f* EHG+%@q2 NO؈!"X?c}Ŭ7G1p ppB@}=)' ׏=| a! ޲ O32NjC@e<,m|+1fOrSKq4o cyGVh]q6BWvS3(aϣDG##(6|u0 M:=b*SM(z"Y< 7bx L],]q| ҳ֑)J_9ę9>^nkyV{r hU8 @֠U$fO^:kO,n{x^  z@n=i*5(&*TSGZ B n #nbd Z%G/K goAzȬٿ]*g5gA_cA"`#q[[3%H,!~9#<kd6ȣ<+`@$65(!yQ 9a@(+]0m7eF GUn>ܾ SwgEkЫ`\'c301X?<0Y.-A"qv_qy^~ 9 zŅq&nơWyZ\lJ㊘0S>qZA>XwS< 5!T8 4rZ7~0FΛ6[K SM{ y?Lo}ֿ w8O{ %PMBALS2e@5 - ?S?YqjZ@9gY n[@sWr2|ej&p+S'3Sm!MT[yߢM??Ozx"﹓q?+SߎW[q{>=wX߿:սi!ȏ@L5R蕩FÍwĩ0E'<=xere#~e Gv8+;{'Wgw;BbԪy(IyԮBFV zX8{{k@/ V~wlTB 'o 씾]كm}_=f^1;xg2Njxo#!rQҷKG³q*n$>1wdpPjCnk{*`WH|fC7Uݷ+SWWPU⑯B_Y87,B{XWVQG(}\~xն%3O$X,)a~`3֠H k2r_0mB y\>TL—AՔ/(:W"n╩@5+SPQ2|e@sWrZ xe*񓆉8@ {9yӦ8kڴ}?{UQ?nUPapiI[fn&HȲ ` ˓v" }G𠁸#tH*M`gr"MU:#tyJQ>*ZguuNm֮ns^^^R@w!nW\˿pzI.s*h.:!NԜ1>T$&ą7!.8 qM !. qM -J[A5 E%B2!.IwқqU@B! ($D\"ĉjF"]q2E!NTsoE _Vqw?VgKD0o翗կ2]JK7bg]FX5kwϤ5jWCaC,V_ǯ?/j(%B\跱]!֪)֎$%%)!nV(!q E{B\h!qII%!\Vʚ% דҊ8wҚo5 q1w"bW*!ŧ{i_mb_+8j.BYFX5W!.>۟Ik2~\+81p-&YF? ŧk!N \?_5 ŧ/#X\\#81p"~{!:qoX*-BoX*-A -?M2mZ!r(V!.4~GT_/5]*A -,BR7⚫ŠNqqЊ ą-Ouh ąNB\h!q1T܄B A -!.Ti!.bﭑQh ?@uWKL?{uu?<SBVG qJSB8%)!N qJSB8%)!N qJSB8%)!N qJSBH8%)!N qZ88%)!N qJSB8%)!N qJSB8%)!N qJSB8%)!N qJ[88%)!N qZ88%)!N qJSB8%)!N qJSB8%)!N qJSB8%)!N qJ[88%)!N qZ88%)!N qJSB8%)!N qJSB8%)!N qJSB8%)!N qJ[88%)!N qZ88%)!N qJSB8%)!N qJSB8%)!N qJSB8%)!N qJ[88%)!N qZ88%)!N qJSB8%)!N qJSB8%)!N qJSB8%)!N qJ[88%)!N qZ88%)!N qJSB8%)!N qJSB8%)!N qJSB8%)!N qJ[88%)!N qZ88%)!N qJSB8%)!N qJSB8%)!N qJSB8%)!N qJ[88%)!N qZ88%)!N qJSB8%)!N qJSB8%)!N qJSB8%)!N qJ[88%)!N qZ88%)!N qJSB8%)!N qJSB8%)!N qJSB8%)!N qJ[88%)!N qZ88%)!N qJSB8%)!N qJSB8%)!N qJSB8%)!N qJ[88%)!N qZ88%)!N qJSB8%)!N qJSB8%)!N qJSB8%)!N qJ[88%)!N qZ88%)!N qJSB8%)!N qJSB8%)!N qJSB8%)!N qJ[88%)!N qZ88%)!N qJSB.~8%)!N qk%)!N qJSB8%)!N qJSB88%)!N qZb+ߏ_ݏٗ_ۗ/@s??忶/K'xJ%o_br _ Mz_^t$n.;Ay0h.'wG3Jv Q~e ~P'׃j+Aߍ J^z vTX~$,&G0_Io+qMqXgZ濬jF0W_31jgWm-az6Ts@濨j0Xn\<$1T5WR5Z?Ưj*v'jTͿ{@kilm&\CTͿ)O~OMUSnqӚP?({\nq'?GߪjVTkrmY[fIk?ns`l8/OʿUU'qa%J{ʉ/hYkZޱpP-ZjVzZZhѢE-ZhѢE-ZhѢE-ZmiՈ L qJӢE qZ(!NhѢE-ZhѢE-ZhѢE-Z8-JSB]E qZ(!NhѢ%bbEKuS-ZhѢ3-ZhѢEZ/JӢ8%)!NUZEⴼ{8?LU2Z'RO㮘೮ڋRU |QA.ze);op m`m&"MiwR7#mR ?.zZzRa`Yz5I $1~E qJSB-JӢE qZ4C-ZT^Nz8M]E-ZhѲ~ N^"R)kѢEթjE qJSB(!N%iy' qUlKN&zƫ$N#)'gDlˏi?wVҬbrZP5qL/2gbIXͺCqCG̣DMrz&bƁrkL*Cqk5̵ckhLAkC7j&`z|&2WnⅎuudrUcBRL%r>f7g FVJS qZw4w/‹3i;T{.ݙW,sD~Xs93/?e/Ow/nwn?{Ԁ)a/:GH%wY>W9j_Vw+rTT&;._]1{s>vS}.8;cfB˶{x7R»]pK]-EVb(w5=J.C;ֿoܣSL06T:;epxS{ ߚ5_F{α׽(5w71/\w3_zi~U|zTs|,a'l=ra]Rj|a,wxW-G@o>5*mTPsR{[n/,Llͩb㙱\'{J>bo-?^j| \ʎ qy()PA;3 :K4aߙϕg5_8^@gU؟-k!GT3 [ؚC3c{:۪-̣eܳ[ٚV#V`!<.2Yt*tP:(UWͩX.l)y(pסR´jfx*i"p6jkI;$UPpuD.F9feiG gl͏IbʴWjI;vU; ݱP4旁+ <t˨hq+b& e21ׇ \y!p%P2\-E wdq)pU5/pkq\{8po0ײ="kiZPlqQeપ8kpT4⧵bJq p-jG~-Es>ŕZ\6pW{(kGlnq-N  \p%21GbKT?^q.zNU{7=pI"Y\}T,v U[6p I+ x{< WuQs\ŵ[Z \ JK厊VkOx\2L- T|j4ǺۦW1RK]bBBi9JuyWp'nzCѴ,ceߌROw8=/EѴboRs=&{^״U&"iH%?ƑxH8ʿ\Gc4!%ĭr,7~l^rJ2}ʒ.NRdX$wW5wx. foT^ݢn yår焟TzS7ы7,G!M8B=Vt]!~P.)Mj˕QgfOs9=)* ~~|]>!sɑ6 kQxKe2ynf ǑoX(N׹j'"fY7e]VڸK^|^#SZ>!od{f/mZ. :m}e_{GSe aK9$S&8Sd-xsxNUfQ%>r^N%Ts&Uh{w gf8[;Xmմ$8e3 Sa(ۤdeg<@k \лQ gKdtζp5XWӒ`kOU\Q.2|`A' \#-RLIG\ pQŖ`DLkYF-V)pW[5eEt:A- v -Ֆņe"6prӴmi!pͤ- D.4,SRjj\9+|B6ˆ- ?5.(2-;~x+a<2P:0paALvmRmqٖ&+(ΠE%iWª) \ uɗe~谸:RCմoI\$mqWejmRц3ee(UiS,3+\մ, N=bİl'd'P􃠬Q!I8vX%42~=F(!n}ź[ N(⮱`Ҵ_yezjdbWw<6w^L\f/?O4mO*M$ ?r&_.G=;ez/HwnJw~rz{Q/ sԋ7xAg7^V!gB֬Bvc//Q.y+5klaQ.޴B@pphaJ>X9`=MAْh܎/;H7lkRSոA!C9 km{@Rkm|OvTIhWYNc:EfA*$8:$rȩv쮢ZޟNBNV;lj Mtim>.)z.Jlרumjt.D5Q3\#Ylmf{2<4҄TE+tArcPQAEjc&t"5%+\xfor[\ǜIUC3G4%fX@ v[\ imYJpgvȅiN53?ڃE p/}rv*[\?fR<( (aYZffI-.>#*\d-`r`^;3 dnaX\sXA`W! pQ}`kFUUD^eB&efW /5)vp\0z\poo[\aPJWuT \6Kr \U p/e.5%XHV[HNYZ\&[o, *2R]..r !N8 0 e)-f.\r>d>i1gڂ P5cs.6/6[o,K *)@UY- \ԛLkH<,*P, -x \܁@oM<^>Cc|i!f…I .^²E% q4}a\^GYy8o{eM)vS\=>Mdz{rU. !͞:͛p"oJ[_<55{1ԘNbHN߱ 5fxpJpzI٧'N/[=a/@ͳqs }Jfzc>}qkL^U[g̏93_*[$!x}꓈gӉ^?q)"dȻxӜt̛=¤s$LMzq) b !VaTMVCLU| *^ʴe}Ef #ATp˙KFA8Bt4FYf-ƹ 4jb[`OOR@#׎HXXJj A'銐͑!vWP9\)}@E%e-SJ,`L9;,eƒKOБ͹p.J~u[jΩN.+*>=( .ˈG:|r1gRJ5F\Qq% jfE&8s}3+ڴ\!hqxY\+\DuӸ:p1Rʀ`,2p &C@)k ]\B͊˄ sR\=յ5p,\#Cb+{Z\,-MTB9K> 4QJ)ʤݽ+-.nDVxeW#\#˭!e[\;,.Rz\Ǵ&U璝iB,w/v(rk{%X* [B\Ώ ,wӇ`b, mU.OeBo.KVVB|\6׃|SB ԙ+Bܧ٧'Qo>'%wP)sW[fܲ ?'a2=zugnɽBw#>iuIC^8uMosdGf ?}3rb-76O؍ mϬeSnx`7^$P yp/`"bΖ6fj\%}8\s:Ա~TgK)`%8pucx`ƀffіKcCu"43C,>h].ߒQ >(і'„[1# 9-n7CT\ HsA#;|30%D)5IWw@ O8JJ#*U!pMko98!@OW8KB٧NI.:.qR9p;"A.k𲸠:(fREMLebH.|X.| _|drq з1̬@~OҍHKE#.`T|5}-Rc jtj7 \nrh? -.R:R,}^R{B[\,!i(x6voh H5$+ƀMjqq\x x,U1 %Z\ ZH~)f۴ Le%&R^UWq#/RJUsLP*Q5)xȌs.+p m?qQbOym;MB_=VXl '^GYN&_3w2r(. !Ľ:- @ qwgp᛺/,/L>+΍I:WߣdαܸO_i].L'p8J9bgR m}M8spy2=ʰDu M65.y{sC,wAmi]mŜqE1.霵c :J"gUksoO51.WuA@F0dBeAi .Y̢@0(J}EJqC\R;= C C2i&dd.ʐeS"A}z V$*X  `M\KR=լ$ n:/B ˉ\5U=tqh'ZHțq8\n'*1#|#@wFM\Cс+x$ 18 ."JA2)5h u\&,d1 A"n:*</=sG}DŽX\esK% u L YBئDqak\D)K \;0*\B{\nwb0Q2pUiN+ qpuѨ Y>W@5n*@ƟSPf\ՌF-+P\UCM'p&< [BVmqa-.N+( 6J7גg>U +RJjqAZzj%psIځ-v?4ҡJHu?S5Wjn3 ;p=-.jveSeXlgKv~/?KVǥe{E}h3-Cz;홬n~{duwiBY:۔@}duW(s^G)fW}Roz .۵93k9xZ^!뒷W9[rXj^įK7Y8'NOqD/0=\"!0\viJ¬іUFFG> mjӇn=Sv$zqFŹS`T9e7r2pUE55tD/KcVjBb}BIBs!p9J+L5%](I8\&aW:"pE8ap#8xH&8T#S$U))2nF6I,+`eJ/3a:\v̂,)vX\3morb,P \ 8jʁ2r(,r X\$v,r@sGř,rЦ < ,.(E\IeL$ӖvYN@:$W5pYJ҄191Rx`f}OⲔ›0YJI)iP@r  g9p9܁b YNLr"04p%&.Jˀaq)ބZ\bઞ+N\Kb.Y\3dS\@F((cJ%MW\D݌Ci $!p{$cL (oE\Z626u+Yo6sԃ~$GYqE%a|r0m|uG~YBdbfLf9ƒ8NWtfLk>59Y!I!ٝ~ȇƑ{잝 忼ˇqcW9d!e9ʮ[>>0$;Ƒ츉!]jgq .k0g DĜǙ ɎKD̒/hOK뒧˄Di39><#]2QpieqDŻH8@_ukBVqFطFG߾y&RT(&y">X;GkouEntI Zk-HX;è8BU>Z3., .qҞPJP5.@!O(nډ4h9:ʍ +VQV?8\|pI޸Rj\%'p%@UR5c@2Nk;Y8J)"Bg1WFÅwsn☻Lj20J51p|}aE= _@\ɠfle(]O0|- T!L e$EhZ\MfMDNJ' \gn;2W]ͅ.[\}r*`ʨ \Y;vpsXE.Jo>$i-R +Ņh9#ƨIX5-@(ppb۠CrvTd!3\S$ "tC[i>"2RL 3 ] \ $p!e렒 FP41( EsT..򚉼jeKSK $!;? a1L M׽Ώp:ʊ1"mx|+Cc3=sҪWfO{M8!?M q.!fqǛ=EИMſc~7yӌ;ޔ;;޴#MMO[*Lo1\9L78K)9!9u\g>:ޔs)V9J9b#eRޚeq <0'1s+{鰟yʒb/hOK*>-J'=QT?Lk4Tnq%Ppz0fAg"+B,. \ՙpD.#.RqR+ge( lg撝E$BcpZxL|=xSKLBCqGYq)8s9(+f٠<OdT&pÊF{ׁo G=|SBܻK<9TMNYSLoM_jY?7sO*Iwr^p7})+ !E?Fw{ɓVN]S$Q?M)Rr721@U87mz!1/Xxq㉟Y˚&"fn`7^$PО y/p"bږD6fGηK^|qIpʧGa^c0rKqi:&My +^-8^=C{3hP5|!(7 [^F֞j-PcfgtSćfe^`pq4NW @M|/p=:h̕X7>d0pQD$'p9 \i.ri&.Y.A ;50!7Rj5JIIRxZbt x [\%f1pa.|p%γ'NXW:,qRQ.>*sU:N&\fNr{Q23˄f9/<~:ElKv$Nr`pY!\-.\rpf \eOOkh掝TXU⽛jښx& IVŅGo-&<|2(yՄ[gD岸3E"3%v t8 N2@"NsPв0p&t~hҋqEJ[f9nĹjeC%˱xSwX"^;1@CcڶS8&U(=,>k2'^GY17e)墦8G)gS2rVhJy Ū0kG$l" 7z 35a316k%% l6ZdO{|%B̍TR|죥wcx፼ )AB9 iI z\Gp>H.чsAǬaT(*dkTA .IFڸI^壥߼E!) .[]qh_q!a! ;_Bpy&= HDWP \|8 m>#\XeSz[S0p E.c8FQS:)M!q- V*τ\Eln(ZFMpŵ='qp VMlqA67\JP-gά  *&{ r o0Z(!N .pg+ 4puQoT`qeMtKB5H5R\UCx NSEWPI1p5A<ίG wh$a.:Kix#ŕZ\,&՜Hy!}-.3ZpdpuTy:/<֭`Z#, xu\q"*>qun%nRq\*zE1;JʏR%F`s EÈίg"M &ER8&[|BbGY1eE3xNLoL99ap'2Ý< {ҪOmׁoLڹ(!%]:βW^hZ/>\\y1R/,Cm+[+G~RiNZ?}O*KNZt"ı6>;Kezi q'nJҭ7gi*hݢ+R+MS HSApfާC4Cke)\wnQG$ȸ'xzm\f(pv`-}^Łݸ**q# WͅQqRL_/~9Ʌ$Tae̬pJT \`q5H5 8@EQJUʨ!,.R jʁ=6Z %UMyu\y[&4& EffE+ʁ MN.C MR[\IsXcࢢRrM$!hqQp%kaBU2.O:7p9S \xS'LBƦa|"ͅ[\Ʊ›h+dZSURфW\qrj. M$>`q R!,2J(2:崸\m[DdSqtֆ%#SpGY}s/6H)cSQVt:m.ۆ:LWKB qw/yFga|7%ĽnGnbIN_۝i}Yw'{\6~ Xv>,wLog:)GqKEw7nxn'G K_vquY箳}20ϣì5[* K.irJea,8_cuw ω$7iX&/?VJ*eόwb_ZlxX 'lغ0Y(6TsW>稙?-7S c=b*e~ G ;dͮoYxfxsq)B\\TqRMP?U tMKTk yfla fzR]kA;ڧѫO;;h~l|[|Cyǐ/`Gl> S˕΂jfQ[v,]]PAT!L5W]NH9q7vs}H fz J%qWU37pc'= 8Fjlz[:k:9/ nr485@5Gi-\. T(Ԭcrk ”FBf~_4-ft}.R#+f \5Gj-[532D'xO8pͻkoՖwtPZ]-WJdq<,5*f6~wA 6vˍS@eakQ;JA{\`q-uPW~[)k85*.K,E X\bಸ  \ɣf\0׼Zbq%w \)1pU/cx4%v`̥veˆB㻤\TIi|-7T̻ݯad 7R).ŕd#A%`ھx⫡j- ѻ(\8-y:$q;39B. =JFSijh#x*:<5?/U.vԇeY|74s 8w>F<}Ar pk pY]Μ;Si`f'eDTz"!cVi!!4- Ñ7|s(q%| W7EPPI=2dD}@!}bŽ֋YՌ,U6l<8aLpZxLIOx&juU3~M(y'_U3^aF1eh!ElR|pIC:SG&QFP8p%cq ÄX^>a'\QH 8"b+N\,8gt7Ҷ3\l`o.&aJ1iHHF6҈w1v [ ŏ.,CFGiaI%1g0S7{\T8|Emb46mTEn%&1Ƹqchr}K#،04"2*9jJ&@ &Ȑ$Dž) GfӢ8%)!N qZEⴼ{8:v<dgSj܄U.nL{^ט䙀LҪ$o$+tm'AeE5MbM@5M2j& \q㡟4pS1t.43'M %k& j`xu3LMoVNF8HWl "Z\>YfU _Oc=jOZkiJlMS %&!9lWRpotwܘڨfũLcÅZ5n 4q~5MW2=](!N q<=ssC`Gxmx*x7)9['23Q|0Q[h$OT~Q7pQAOnE8Ў0֎PմK5@-⯔TQHinjvp;HKb7>!bVbpfc`NA3KU3l.ieIU.7v%фkfixZcFF5g]9Sh~*PM;1Ie>4USLs .a3R'\ X䀫1f =´;*f8dCMv,v ǝ7FB󹑳;B̜EI3 \RM1py[\vLB,>%QRj+_KbTR͠3SU?RQTQsF7bG$K\o!B AԜ.^ vuN FΧ\`n fR3D.kfi-H%[NVk.{JlZk\ŵLD5!fUR iM \ T) h3KebG1#A^\"Q]"%!%nqIU E0>H]qVsl ypnzzAסrcZo2"+bM8lTKPBܻUƳn} |ȖKw6ejNy guM} |zYl_׭4{~~+7:yjߋb1sOo7|;go .| >n(N@I%߶>jyWEY~.0z@>w{[Cu\ $]ɏ@w*~(B7*hn|l]vg||T S2uagogApY7쮡r}>Ϻa}+\v[o]]o0V`0wX搱| >/@a;_>a\X 16=Wҏ̿c貛ߩngxl%-ha,7.;,£@]R* 4>8< ( [TqKP$Xq,5w}@v~yN-a3syzC :_l6lY-?BEJeǝg 0+nq,tqt(XhA5SR=~[´6ٲjH$£XjxṔvT#R5?@5{3=bG&%|f6,@Q|Z1$Aͨf{~ީ VXq/xq$x*K $KG'l!#%v$$pSPw㩣"ʍeFQŭeR]%{P6p-6>j\ ^Rp1]%=kOP͏P.jk|nktW@5O"W(uȚK.FIE3JgP]VUЇ}Tp%bZ|=Gw`2kiq-Nk|j6x%(Yj \WŚ;,.{.!%N9w|9ࢦ,=pq]Œi\ YRw6VŵQ";;>j\X5xfDvoꥀgPYlq:cQ5#a&K%3Q"oje"WZ\:.)fD)pmB0O1fK5Uhc |( TRXs.K{k>\j, Z\\pD\6zǭ|Wt|XrͰ+oL.^Xe$0|zQs KQGH*P57gs=/;8vw6A.*0~l|^8+u@\s8Fvr.aCe0+7-<}?\s6CL gsj~~|:QDg%wIvLLx}|zQ|=k+s.) g05\-O f }ZW6{c#SD.|-;r?~QvW9psE͡d,U.[(D=S*mO*e~?]7cYE##kYHK2g.z~R}ʖטyx^5ŷG7?]6w |}گ[DG>Ŵ'oȮ$Sv0cX#ٰētND6pݕ9f|{ueaTڸKzZӃeY3؝Q;Vݶ.Le Zo9Eď2JTCu=[2+m$QT'}tݡoP8%GCTM,[1c1oGb޵M%@@2jQgHa1m6޹8}%%(GKh_rvR5LT 9HbbG%(Q4(:.^P5}\\pgXfQpTFX5?蓾 \c1e9t 7Jj \S[ySsn(Fhͷ2W}U\=Abn1\bFI{u?GY\rjKQ"Pl}Zc#fi nZ ra}L0D\Q;.^̌pq]C}oq}Hwff[ۚ6 op(/bpq]ҌF^447Y!eN1]r%=mjb1o,~+Ky.1pmuI[} H&[E :@frچvYpD \7B,f{4eqZ\rqHyDtY~eXg˚Co'KeVpM솗pME}/x8K4y~M>ʱIas(x貧K<KL]w凁o̅r?]}\^޸6{qg!n֜_]qޗ]79\cX%K$ ʵ~W:N]ׄ#c%bܣ݆g jWxQ!Ջ놾ׂ3P\2_j zKwdםF"B܆=آ'2Є,B{n-*L>&bHolr&u pd\:6)ˈ}*liR%=mtcp^fA>ZfmFŷ K㛄_l!nskc,G:.A!ipap 7N:Nf[bմc8NHhP}%F1 .;Oyb('r6ap)H\x.vKQ9~1[b.KLKERKd]Œ:8E)QcdToz/Q"0A \+RR”R;u 7JՏf\\]r6ZfaT<̢- -\2-%b:ũD8f$%8dd] \C| Q-vb`.iu 7Jb4 %l~f5GU4p!k5HZ\\uI{; \JDwl[\4p>#(额+ p\]1Y\=璩^w zMm1tZ=Tx(\.IxkTjqM(ҹ$0J&<+ޜj\a,. \$GsؖFMO7.kCf C`HM?\2X o _7(aSJzM${]/ҫ (Su{qg>f.NhJ[>Xwk\AXBBܣ9$caf;|kX0Z=b&lTt 9zWg2=WPfz+[}9{CBܨid$u.{aoC#mD_ﳲLo8k!!n#{dr7o␙8yF1!>vYR[Ͼ &=>GOn;v- v4o}*쒞ր'/C( A6C!kȓGp㴫{>Z{3t)ຄKQ.iȓGo}ؓv wl&* GQT5Q¾9Ǟ<:ԛbO.1];uR>Z̢G<:Ah'/#4Jm5T|Y9pM=7pmiu 7J/\zp~s 7JPH5vE;%#o:bCQ.Ɂd;ݳ\pD\9;S9p!.ZxuN:VWՁZ ̣tR*<fzCOKy&J7.D>Nm_xez{ Ox )`B~Ebukܽ9 kDzpb?ڣHh|.BkRj]~ 1&gףO٫lcQЩopc!܍^&.a\.iKM@z9"s??#"7#"cW0*$8KKrDxR\. !n4DLp yP=+gVn<ieu "ˁ,:=%C*Rf)Qb]5/RJQqC-:͹.FIF7J8(NOY .ua_Gu 1J8r4UsӶu H) L eu %0p'-.q1%t4-Kܓ%h%uѶ\dK2WO+ Gb8K.*.t`.⢁ QJMB8J`%#K΍Wk"fV6y%(">H3`w]}O#rDR\.)ehtte:/.LJ,.|W \Wd. %emjvk Riq-.o{Fh \SC%z 5 VJ!wągTFs 7JʀՕҋ.Ǵ}. %$paNt '}Kjq9'+м^[rpT]xptlINB>ۡY?I*@[~Q183`4O* ?xv¹~RytO 1^Wc(!n-3'uuW_wΌ]yo|&!"=4NK/=_7+|t5wts)1w*HѠ9HeZ?w׏MKHTouN='iqQh6 q')ғV7~ aFtxc4j/*bA* cN_mo.K將^M<6. )~ՁvghAq;vc'? aQenk\b˥ FZfӁ{gqЁUX5S/q#(LK CtM(\ j$hzG\VDDQ<^e>)Aho+A~P . Ts+A~sv3 kl"Ɲ]|stM\ͤ> EJ>Zgpo+O.qQ -t8T'p0W .9U 裕%if i-KOk*ՅQ<\ru`+@pn'UM(xr*uG.g`t4 \Ɠ)8,W#\h.qVk8CFvؽ%p9;EA̬s %A\u2`ǾdNr6%Is.\nE+=JWƓ+u.I3$pJ%Y*3KM?`k 愁+gss,}tSU[\qmq}H%;` fckB}_E 8C[\G"K Y!YY \IW1mKmW auy}E[\@>6z,`#+Krɝ){C 7hu:H sI-x2:Yݡ.qgse ܞ'5M[\u l \LoPZ;e.?(lfʯm`u~裍`KȋG>J22`QfzpXo7@n5wNʿ{T#vدH]"#]K5]&{oBnb]HnOqst[4nw_5wb5𘯞{IEI굤;PNkUDC2-➽/f]qњ^>'gřvWsOH/Ѐ q@'7O6B5a7>'" [12.'GI2OZ:҄p LŇb!a  vK(Yh]]ߜ%&Knue,pz@;%(A!I 'iF`4b.A$%\bZyHH)e:H\h.%Qҧv7Ag֔S9B.E8JDU %. r+%.H)1KQ"@5mv-.\2CI #jf: W)L2\2-%b nґ0p`'W3g ipAb%(}j7KZ]1pMlt \p׮ pQ\%pœڝpm Y\[,AbKūwvan9ʐ.4." NiQEY3 xbqKzaiy>]3pI5hqe&~b~d&a,Q5\4J& U9K37rWtpt:U 0 tjط7J::+}LP9\z q!@kqeU'M1^zٱϽ2rLoT-qBH]2ˏpu.V#Mc'!MM:sG| @n{^Z4$1}+ۙ^W\-ܪb3z> YM3!U.qYՍG&}NZ}ˉK^++fzxpZLoa[@,F{l[Oņ$Fl=y.srUTrM!_&c=Baf̍¼oMv"!bx) xQ6C"̘9Q&ߢý<\2/њ_} V:%iڗ7{s_^)kGg!ƹ.oD(Meqysn%!}yPpn@_K`8#B*8xs%x팓 `\Q iʁkCw{\21זipD\.I` NZOo.F B xGkC!&_%-tP Spu6TpȁPN%KQ"s΀NW \Kqi_Q):r:5u. OY\.⺄%bJV" 'p '$n.F $qQq pt4:Di.gq͋-.ϹhɀH>)v[\;ⰸNHl088 ri fR=iq=\Q2/. %\Si(\7J"6K8ܜ+2d]\CJ{Y}\ǥ9{%(I8JK2lX\qu 1\<7;*qfgY@n\s(@bK熂K ć}̣<߈ƛK:k!$\2@\KtpsCABRp =߲7(1!7Td\B .q]< z+ѢQ2A` tnyNiqAtKQUhET`%L0 \Gy@ԕ@4Ҵ͙Q{7&lp\i 0xxj vIjB`g8ZաE Ω@5m%yY6ph~2`4GX5.061i~!pQ(8Xz#F3bX8X,+exZ Js-]kCk* u1m+VS{mm k)8rG{(+ΛnH8L8 ,wN!Eتl|8 qw-y굹/Lo'@ܨLony%_/y*[?sP'+zn[_FR>S&͵+OI廅/|^;ȦتsIm6ͽVLno8'6*27Bl+ MOZg"}*f}~'AN 7uM:r7כtq^]H+O\ {Q8GZ ܶ)[CP{Pg[+y@ ML$CJ~2->T|TiHG%G@CDp^KQ )H^::B.Y)Q24;F@;J>t->#Kܑux{jzWPJ'Tuslu 8-G$e<撐QԌ{:-,62*]<"0kq .q]%p`\xGp1\ rdS\Žubt—K`q;m;\s dG5. dѹ?r0 K`o>ja8 [\pJ)< N;K#?^w-&`feYkS%+Fcʢp̬ubK.OzN6|8p\RTnqpkj%Q^ӦfzW_8jSAޏ \eq5H{.qpz+mf[n\Oрjr.<~5:p WWX\#Np1du \A9"5YJ#rCvb7lk֣ܰ>UsnuCӎ O7*K.j^$=Ybr;S97y؊|{O*=vėr=_w?D!!W~4^q׽yBM%bj䜪G& t?yzwĥbg;{O*_f ʛlG O=;[&%g$d8ƫ-R]Loݤ:-C͞ўə,A+?tͣ{&ׇKVK8'&ā-!z,&gzdr+&aMfC /i0Q/(¼o ۛR9F?6.꒥ t A#w{RDyXv^ a{@g%@@@R7cM@m¡qK2}lT%vr ⁨Y-ja͹.W(vЈњj6w; 1JgE Bjvr @@Ȼ%Don:>X@hp$Coۜnˡ[8UӤ.xg[pivQT\\]B2a+YnZCԟmK8}'~5K +KQR!p+)h,B,FŅKvhwIQCM[5Zf .8^\¹wc8x1ʁ{ۍEn.{;^x270!EMۉ!|B&@ #P AK@HBTx@S"BBm^r#V/ۿޙ<~{ug9'߁'3s:k?kZ{>.ނ )wo7J؁+]`w2 ٣E1J?r..CڞKt ܁NT/X`2U쌫(9TwrWDwTo2fPf4l*v]fI Jt/qI{*IoyT_0nI';ON@ncJJd9Io~M.M#N#7fHp],}S,8+u2_w6hiuoƲ{#ަ~[ۏ~gN{lB\/_@ߑ UN!i5C[o;*7Ynp,?nR&ywl=igySNH_>m:ҟn.لS̬m̗w_B\| J!Ӝ{<`or5ZϺcW!ffٛK;J ssy9wų'ٛKFH)3ތ+=`uI~XmV2pQK׼ތkx.Wꝺ٩1˚ .)b qڝbK2hmtɆWV qy'[WscXgWih128ȸh5jДk^}c?sAj.9dR=*fz,j5suN*T6ԁd;86834)ׯWڻX ޖM5S%%^4zB׾<d}_x&`})eKn43nM|Xe۾f>QbOJ||QpݾsB޺MELo}[w ԝtr{nƣff/mL],=1U:j @_rCgu.n 6m,wǷ[J=|ܷVD twz,'ǃ >qX Hj98G)sX)Ȩzu>\s>Ez8ՔVsx`5o4rN?!n7?.l5ef>QÎ3d|CN=[ϞkJG<1[wѡ. Xx˳8Ż1٧5- K.XQSO=u{ 4Q1Ƴj4ЭO)+f6pѡtտS/wLU.ߺuU:Քs =ܼrdҶJu1+jʡ V #G:}p);8{E绕']g3:+>8iHלC-:(hj[2#qU7:kκ3Ywܼ횣ֲyjlkhf;.9%գwCNT_~p5uyq.R1Iwtp7ۣ$ONn=&!q랦N j pm.z.ٺH(:N \ʍjk)hZKݦ%p%٦FOځlr/\s@: 敩i] # \$\u"*fQW[ėqY]rx pqG.Co4h2.l;Ju]\2Jn}Ӫ/peO峵J}vu$;p &H\uߗ%[]D຤u'pY$Zګ<ٷtD۝]s(9f<ױNPs͓O\Gשe\),?R.\蛝d%+)%i0YH8r9583UN]ZŰ \y=qp5 \Il}hkoU% +2tEZy!+rPΑ֖_쒭s-Gx$pgU*CBkYsyeMi |AپvC LAn>;SZ[}YKu}NĂ!7HrV4+)qS'[ _OoomT޿Mlp^\|)Q3[/Qwc:uOXx*-!%OZڣnk>6Jq_x'mu&׶}ˍ- ׾[6}LOmm"P[f NzwS([nXeQ qn5XG:C`2.vR*]7ѺH* A̥K05Voݼ.Z]- 3rVyl7Y|/%{[ ~S=zC@tUG5-GG\$bSxCܿcP Oy1(WFg.V{pFMo,w}ժ޿s3FCh G]FCݎ䶹M; nÅ_#> eͭV{ϴe~U^КKV/mْSg6~okdEEsg y?^9e9{?uYv*ϸ|ӬP7g|h=w@˭9J zN)%s/sJL0ՔeG{)\9:x3Z֒8w-^i~6/?zOG'y.rMm/Jٶk]tSTE'MXpw)kN;4FPƝUd5LkýtȌV1zc9e(Zm=yy;5cKk濏%Q2a.8tV5wZa. G삙֫.VgZojEW>ҭz^_2J4-j[]R=z.%ժ=Oિv摇53eA[WC&yDj>yXKnKFIu!t5xWs/!2.+V2.j.ɹf2JՃE͇|< \o#p9ps2p7j+p\TGdfDR+p^2J/|<\*^LkȖw2#sIu]Vd󔽁+ GʥFu(7Z˸WRotI5M~T[grAIU|ٌ˙q|T\xdWR8 i>gv~\g^Ug\sKUIK:"pAP73#68$ܖj8˘k8삭Ǫ>'>0m$u|\ȹ$\kb.!I]d}(W2t/ےXK \ds̋cg{q}r1oj8⭮3y+V5 ԭ8%eL]ւ[xy,]q6xCpӧ6*P||$|7οm>X?to=vj q.(4 `3 Bt8Oķz3p4̟)2װ`ofxK9"n4QOɨ_T55TcDdrb:Mה[҈Ôpm ac4XN皦d458®IǗɉB]F6S'"ӽDie?g wM+䡦_ŀ5]jee\GMu3;TkXaٞ pI_|>>9)Uy!nƥKB9Ɂ iQMU0 Ңfv.M,0zEiǍ斍(׌絿|kIQ?!Jpgnsh! āE D+3P+.FNA=i|ea3 /7jyVZ"Ƨ̈8~&CJjX1,4qqWkyM$V$z*{̄aI \vl&F3`㸦*pm2)5,Bߢi_yI510ᚓLŸanfsj20(i Gq5 r+8ۘY|> 5yfocN- g\硓RĚM M{=F:X܈=iT"bHSRq#2pTHy;aC?e   qq9(^F5aǀ~,E[E8}/S=DrQҷM[nVޖk`3TS̸(JY6:su*eaTxUIf-6jB桲1\E绦(S_._2ωA2.*) 9X.hdQH#DY ̰J|Y>β0>U\>^sqvkU(.qBBq ā] {!v6*'f\˘ĪMB}.MjD`ѸP2`kH`=gPv}0NS/|xϷ8akxǜ׃`ar&91®I|.PP\s3UGya=g`yp1> d;^m}$ \Obv2lo77)+]qb0Â.?ë́Hyzr/y']ByV|qMơq1/rC#T@+Gy;n=& ;f${2jJ baIkZjg!Dz34ʻF}YR \ZEbk 늚WfF_u+ZwbWf 115[ɸ{V0s9G֜О֨rEpUGbB( [\rZ k1[m@NN"|ch^`$|d<` xg2|9rsX2J qz(ʵtt_.'Snf\i--8QӡyvcUD3IuVFL;/%RZΑr4/+N4J#W!Mkri73u4gGMvۜ:=Wa/=<ג+?bqcno WPBY 2ήd&txr*6z˲u' -^q4*?s/嬯ѩcZe-MTzJ /s-PS&.seHHV7pHoç`G ( ݻI t t w[wb\wb=iiq_yP6L`![onPMȗ =CCV+olQ}NNʿȡ-QX5KC'\n鍶M2om[?ckr<= r @ܟwo0<|/u+04ZCYH1)눨J9UC0>C 1hCSoӚk O4d(2L4>44o'HNg5tb. )]ڜڨ> |V0;g|/B榅a!׆Ӛ;tLɽ虪S1rMO"d G`~(鉚UDb'u 5o9UwZΰi%CC7/ RJ;YId<[@*$!o*0Ĉ F;'jZ(PlN,VǶ3rm] y  OyQ̸d|y ^Rvaʛ A8^ yf$vȸ!ITb:cbvCŋ!eZdՐ':?X>7[ݟC`V5Hz<9䑔yΚA'kN#D j#6=!@aen]^5=29s $f˱!x~3q`8'B! @? z7uԭ[`39@#v= Mz ۶]<ڗv]DAh:Ufv^k/-} ' /n Q0@bv3;'t@8( āBB=@!!@@t8 . . . . . . .t8@@= Vq ā  jB>g . . . . . . .q8@@!#6)hl߁BqqBBLq ā  p4. . . . . . .Lq ā  گLŷ@ ٸ!888Lnp\p\p\p\p\p\pe2/)BqqBB!FBq BMijt^ $g`jT B\DHT ā qMK2Ox61p.ƂUwe*-5p\JdtL2 -P$=ۢqsWi.m"%\&%.gBBq  qq@8@g hr ҹ4JM`tqr[XrI@} Bq  qq@'ę?s/Sj31إLs,wN8t :>9U0`!19:e΃ tzb筧A!8@\cF/g0ّn[i/=:#|t'5rՕ_;g/(ԝ`Xf]j" Ć)RW'6G]JMNn(.B9s!!+e[lG5%'RMD[)u-՚\"Jrah='r2NBR@ԭ+W }mpٍoq}u Lɭ;T*51U[&綊wx~SA=CxB\MSnb!SPA4gަ9?Rv9ũwU*KZigdCM[[FegmTŚV1FV3[VlV_HaX].v5XWxhҮ“ɥ”m4X*L+7?C ٖ\.խX[Dn&Yb-lhmקWV%ҴkYn0ia*L*%vW2b,)ifW8Rf!XG(F^]-aLHg!n5# -ab0KnOPKN+51;Z43W}^kY?`-Ͱ-9q)mCDmbpF^#mh2Fa lvtvkxZb Ke؞JnnE6ݲ2;n<eF[: F["t^:us#DvVv vrJV'O#vvknh$ܲ;/2dn2|o|FΎR[o%#(<`S HE%ִ kMeo1kb%.+~笉eS[^|+;MZnVJ ʠԍʠA]Ϥrm*Vj˙*eЄVINS[vgS[,ڦJR- JAA\"nΜSVɭR;Ua*51UʭRs[E{hmS.Aeܯ#iXP75b1]zm˞[e|R;م/]1>_UK/;K. $ *78.%t떝t:͒;dWneZ X*,?+ N;;gX(,p[})f Uj"UbY>YY%a [1b5k\y9Zv1ŸDCn$by&(\p[vQXŒU [Nn ӿ2cȄɭRX%.e#P2a6vc,^MVI(|Ɏ,NKfDҌhdxKى*51Ujr)Rk;c59Ujb EV Z sRF|v+[n#ϙ\ӑܲ6;/;nyb -O;1KNW+# J&Y6gs[*ƒJ"&J"J.|%[HmC1"Y&Om})"mb _s[X. A*6eQɭNm9ePFNdDL$r _綉3ʠDNnEjsrHsf8J79Um"6b[g綉fYDL11R[f綉*-SLaWj6SXE&b|ԎP'yj&DNfDNdDL+I""Md%s*[%ˋm|tmWjxDLɶT9іj"&R.r$6!.1S{"'$4()!!Om܇ O%*z @0ڔ6vat/3YfѶ2c;3|w)ޠ6LG]m ۘJ Mw^)60AY絓@yd&-<1x`B.`ĝF%yOlꆐ2 }6j^TN/e/X#_IPf b+ִ/Ru{褚fVmXI0굪b6pIn'+2c{VۆECȑT_*^̡=zv3Azj*ж3OJ:ޠ#60mzMN\|()!ڶ}i2'1mt|[6 =(߷㣭'K;(/ht w3pi*m؀Ƿ/,>4+kce+ O9Ʈq |V*[EUi1S*3ڶU Еsa0%}T_@!88!!P⌳H"j&[pՖT|)UGXxo(R"s)x*a.87$x[F*1rC{9rL`bI!fڈm:ρsܚOĐ6IQ` āBB(pq@gDl Jܖp=~'AF|Oʳ}&ޖq(y10S5$:<\b؆[T`}w),+ST*{z2ŵ@tko$(ONJ.p3`ˌw\߳b\#65WA#<h {3,,OKEgjوb5w7H 68_2, Lic1,*>LRAesXv2рU}Fl(I@!R9z܅SeePQI2 3U4+q-Ѷbte;3 ?h jz2iխ5LRҢQ4:)S®6li^i8BQc|x "q@!8@,aN?^ԉQ_S=*g*׉i]sNRgz]ᚑldh]󵸮p O*սSN9|]:>y-\XR1/k85}7MO7A&N`Wy|%iLjyt~o^XQr$ey}ښ_GߟM|cΆEZfn з~%k򕺚9ݮ95=FOF隩7sjhg\'f̏3ҟ Y12@5Cdu[.\ĸ)e[џMf9͟&lu^[#u?ƛ0'\%cyd'j/"PRO]s /\tԸ"P\0g6b6TaZo脇RcIqMqu1M3w (_[wsMxv^Ciaʼ5 a5&ƹ|_Iwj5sFuN8Ta\wКܟd3M#ggSvs^']sy{KT*M',{ m'SoCj{oNfb7H95Yc|ߟ4aDU&LL2ʅsxFH>ן٠?+c̨i;+ L^p '$uI0M%vNHHK' 3+ ݵP*jo.Aݙ1pW?1E?Y5e52hL^2gsڋD c麤?SfFQ67^F2]g~Ȓ~9F W SA[&fy啕iѯ ӣ/ˠtFƈ?Q$Y^#V2nA-{7j~BK,Ei>ZU.[~Bv {r}_h1W2;Fy*iVL~WZ#LNy?1x׋'-N;'~+Om9|>I;.4gjܽweZ\ו8U^[{E'^S=lEQrtr^V'wV9R:5J|ߋQ¸}J_L3fjNP5*;C1>k=S7K^թY\rN]r.Q{U9ܨS7O*]S;ܥSK~SKKR%?NJ_yr]Tuq]Z LWv+;҅Q}FU =ikK^TvޢD9^LuəKP]<Wvt]\o%Eim]޸u3K$nKtk[.9ɯKN%KW#%uWfD(-uI ״r.Q ʹDH9h\; JT%jLwBB$ĝ6Ӊl\uktrr :uj5 ZܥUݳ*yOf.<3E:\%#hqT\[*\sN]_SZe9agԩj?N]r.S~uR%O57UݳwihuʹTy]ᚗuJ%J_%Jucʺ՟RY&,ZSwWU>N.Q]DU{{:%!'Q!8.@!uI;u qE% !8.@! NW:⨑Bqq!Z"!j&qTB8J^{! āי Bq\BVku qO@8PSWd]B@8z.!!qE! āW{Gq%fG Q@! %O !ՁGA āי qG%x"Bq\Bv⊬K@#Bq\B\% Q\G! M^G7-FWB\c\'BHu Q# <\{! 8!tM@8ⰺ_Kn<8lNBNBq\Bv⊬K@#Bq\B\% Q\G! M2!ni女][ާomܾgty#qe_M\:C::1(G! ~zgVyndB\H]^P]b qAux!}B &ĕB\Y'K +W0!.`B\Yri\\t͂ q!}B &ąL+WOAu &ąRW0!Ӓb qAuŞTWS0U]O=/FKnDuG]rBLm]R(!.nwܺd,n]S%%B[L %z.e]kP.KꒅQա.).)iu NBA1B=2oWu܌{]7Awx/=_:p_^/wy',:k~7SF'Gb4!.,GJ =KSGrSiͤ q qfޣ6&1f# qa1AZ&1Y]w>]w'kܫw]܍wK!.*ޮU Y8:r)"ı+;uBo<ݨSr.VvMW:VKԩ%J\K'yi\\tMm]m]T7K'_*kl_6ȍ:VKBo<&LKyʺD-Z7U񴷻拑{.Q&Q7;S[| j];n]2.)W<.yN(K@]BuɚKA]B%tI]0:%%EvG]NYܠKuɚ{4!.,;^IJ{ﻕڹDY,&ˀW!n'8n'~_=I.}v*\} qkƇkvf̽bSyHB\X&ąhB\iVxihB\Xx*4!4u4!{Tbf$ҬBr$!.,F#hqTЄ8:+iO|?ڏo1qauvTvUnX&qՑOau6!_9ܩz)Fs_kڄل8:\rNv.Q:\bo<1}pKbkjh1e]bo<1RYO%UGn<ل8:m]z4u4!g?m]LS%o荧5_\u2뎺䄸ߙںQq뒱uOQXЄ|噸usEim]r0!nMO%נ.aJK钺daTuK Kt_펺d.A9(5qiB\XNwr&ĕfqw+s.Yw]M!B#X]o@&!n?n1k|7\} ^@s}5QY_s}ejH%Yu dI\_s}5QIt}ejI\_l=*1m3]_sWU\_s|ejH8U\_TK^z=iɻ^}K x,uoWZ%_ q..[uIu+SKg%)uK885=]\E+.KꒅQա.).-J;uʺ\K{w}ejHNwr%YewܽEi\K{We@+׷v`}Hӌ !. 3@+r@B8j$W`B\k QAK6@#Bq%!,@!3W#!KD8!8%% YG8!^K@68usɹ !TB5@۫B TBHu Q# <\{! 8!tM@8ⰺ_Kn<8lNBNBq\Bv⊬K@#Bq\B\% Q\G! āW{Gq%fG Q@! @L!8.@!$N u!-B\%M!n@8z.! q@+RqH!n"Ľr7L<&.g=vڒ:㵪NN)tNnx* 1`{\5`_QZe+Xk؍*ܩ]w)Ս-nҚ>~7sNz.QS7k+KKtuMmQ]Ik? q]S[5_"Zul}EҸa}_5k7[ӮuIOYuIO'K(uIu6{o\|u &֠.A]u ޫKn%KnjWr'{={N%:u^NB\s'sط_~}[OMIKU"X%-Wݾ+ _='}{?Dөk{͌mVy#^XpqS5~!'^62|u6QPA؆ԉRPkR[eCJK\3j3]]N^救;+Ug tʆTzs#a*'l,f'D.z<xeݺd"hu͸au*%KʮKauIĕ.Jk3lO%gJ>%8*"KߞB)V;ɳ3}]KpOP̩^I@/ۦ'q}uIuTbOLcȵ~̩f>1VQ"U)@LЁI-_6yY @BqW. .@!@@8BqqBB%ęxna\}1%ܝ +"El[g]S^)AD {g7t̰ppIP%S۷V7枽)+7 q]M3INTkM&3czޔ^ͣt11"uaփ=qsK\=Ek4B\OInqʿͦԞever?gs-K-sw q~B p悎vM}ۺ; ߚ]du^1|M뼁Wtn6¾fϰ3)T5,2lNr;q< 3lA3E XIgtur&^a y{QQY:dKʮKz5Mn #!v&=e${&ddY.,Luj@_N ā[ei]!n06RٜIz^M qB\V;Xd|%xu a ܆݌bo1:#*SIvB=rs%F(ڳ9Ψٻ3lL W\3Dfszu,7)= 3=o͸lILQy-:{u \3].4Fo-L{/UXYT1ԁB\oR~~ CR8gC͔= qB\gɼ&zMzҪa}β'Fl͂n1:{]_ b{5ZXǵ}wE,깦t#O8tB_ pPUFw\cf{e?)זwʨ)L:5WW+Ѹ+\‹w./|ݥ_5gynymkyEzq]sN.7(nw.u\|q]&#/Jrq'w(sȮ ZS.P^}{ww1qw?N\|;2ZZ"mScZ& <Dnk~k6yti<ĭ&im!n?a:I{9{Ͷm%B Mtb=U5:k+ևTb{No=_6Pӹس޷8ZO:J'ѩV\UTt*a1+{wTw;;\a}tB9|%k~Wu ~W~?ݧ~[uwKQ]_Uy ]隿񨮩KMuwK~5GuQ~7kj[Tݝ.O*k?~Tww|Ըq]_ww^xߋocϨ7~5oFuH7}Ou1e\֏_u種7u,jHo5Jn\KQES[8W~+#jT|vD(ߔqK~/᫪^ +78[y|IThK{Qq)).Y[ |,![w)K6Zq;u٦OOjzMg'p]}iB-yQ u !s āBq āB\Bq āBq āBq āBq āBq āBq āBq ā= EXe} jWe<M+3́@!8@!8Bq āBq āBq āBq āBq āBq āBq āpL_?/"5qйF-&۽ qi:봺 8@!8@!'M*!L@!8@!8@!8@!8@!8@!8@!qO^Eߡeguugs}i} 8@!8@!'8@!8@!8@!8@!8@!8@!8@!.C0\6l,Bk_߇sj<| Mb?$ĵpu[/:8Bq āBq 8@!8@!8@!8@!8@!8@!XsA!.!mk}sYZ\+mjegÄ[ٲl_eq8!8@!8@ q āBq āBq āBq āBq āBq āBq8!8@q;[O7_&mbeᶱaB\"`>pBq āBq ā&w&q āBq āBq āBq āBq āBq āBq āydY9JZuSbs/T}$,׷~|[o@!8@!8@!8@!8@!8@!8@!8@!8@!q{Tg)qJ9gmiמ4!C۔Q|[g  ѥ|ͫ@ q āBq ā׻8Bq āBq āBq āBq āBq āBq 털sA!!ęO~q sEr\RK2q[lwɇȪ#"ĝ8Bq āBqK q āBq āBq āBq āBq āBq āBN!8⺅׷NP)g>2gĭo G<|) !άqJ8n`A qI3 8@!8@ BNp&q āBq āBq āBq āBq āBq āBq ā׉}RT $!omlZsӔgCG {i櫖5+7fF42P2q N7mwӅիF@ q āBq ā8@!8@!8@!8@!8@!8@!8@!kq}ڶ>!n6Oi>sy 2ֳ~B#V4ok4{OI|830M\8oGn4Io}BBO·4!-%yqUB\S2^K?|>_7섌o}gIˈ冄{)#gw_]LB\V^}Uu7~G+ O~zL/('&ߛ.z>ka.!.]!enOggC˺滮? BywcA%q'^'w1kһqsqfk~o_a_eg/!.w_yt׏}w83wc̸20!WDžk']&!.;'|iG ~ݿ#e!QX:57?>*$MLVwL>F򳹝 q<ϼ;+aqf&+Vƒ'Ĺ.p\s섥PB\݂T,qg%,$wܝ&X8ݥ3yt&aq=LGg2ˣ[ W;+aǕL2L״3k2!yلPBwV{Lg,\״!.-hBW Aƕf!+ay4M#+cf*caj#/86wg1]X8Ke,3s+])ϭ| ĹT!Mܝvl@wg/[pw|E=+_Oث5NW8ݥXi+.ByTMkZK:/pq~ˮJƲ ,yBoa|VX&\3tM;]5 ,\״!.{T^&:W$WXg+ =Iӄ82dX8o\i+,B\mR+I! q9!Žt/`GLuM;_E$_a&&ߖ/l&k֦{y%(^pO+,BY;֢t|%Os]zyEI; B\nGy qa7NI!)FS| q9RqVqJ2M9ʚ^Be.XlBN!7M_zDKMnn07nW]6Dg7pT_ߞnwoYjR_j&#vzuZB睰cn\MrvmwyWQ".k^"Hds׈-Dpyk $ΚjleÅuU5)A ҅ Z12P ( k{g<Ϲ?.̙9g|<6u0s)7逸ݍr)u^s|ң@S'׭?zlx\9>6|E%u]SN;SWM]1{,Y@bTqvSqrJ ^:d nɌ'2ݲќ?9vA[nΜ @Xh2Or@ػ8mDob'e딁8Cj@܎ 6 f-5А?>adw;gU3?p{қO_<ٙӼyW641P3֣ⴭbⴽ؜8u],ͫ q֝} usChY[W#u_=yE:[|h>jr|;Sٚ5ⴭ[tJGަΊ7@uo't@y4 n;{8uή:s`.4Gn3Lz9u=fs_Z!:6{a;gyNy3gu@u=PG4եk8Iy<sD5Q9<8I@#<ň9X͓U P-˓8s(D;C|"q/b.qIKsl]!bq6M9`8s`4別95@uy]雎X|LSXD ؼPO G,)F,@9 o$ 8󤈥ĹͼB"q ,It@iBi  X'k H9f8bq.#zV9\Qq'qy(^)q.s!^v>\!lM(H@1b.i* Ζ\qgӔss+Rs+*:1\8sPK㕷@u 1ls5P%9w5a9^ M30M1jMr"quE yx4xELJ[f8r$-qƉ''X@++"g<9^Q+ix%ĹNLf8&3'o\M3@1>BId|I~-N. w!\9X|pEَ Iiy vp '8{9CfZ!`(r\="`(6s%Ek15"`->fx<#_ n Μ .MWtM^Sq*j .=ⓠ~-Zmy{z _nw8\ ꘦<חf{? ]rmKzo[2ϏPS<%V8{9-w.c70@ܱ9O#9N=S2U.7@ܺ8EC9-L}u: .כb^ɖTl nV1Ug88{9%g/FA2nf/؝8ck"s] h˙8Cd8}@{1q54^kG,Z P]( q#u7 }(v~$5ev?M[@VB@\vsl!?{¬;?8Ճӿ7+=UȞ꩙qY8mo>( WS7 ęg`!Nۺuk6~bt{8kz:u7/RxCyQW.9O%fhwTm8[re937o:қf]" gsLsw@4k8]>Le@y qU #6n =q١;粴N'θ ^u"g<v4C{qdoZxF̭;,\4tK5q#.3nMssyB ęy(`i4xL <8o.F,37OW9+j )VQq I@u+ޭyE͓a_b3M1v8SBKsif(`8s`4CKsh@e ,Lt6M9b3B"q%gxgZ5Cˀi),"gN#QOj]sB"qօF$3.H@yE8xN9f8b)q#zy(\Qq'qy(^)qs!^v>\!l&m$ Θ}qN5gnf(gʵurRZYipEW!hx-++"gj]8B5M3`),•)+BR)m@!{@0qYDPNes%Ek15"`-iWioJݙ nV9UoꀸR6Sq{9C9qk۾/:}W =4?(MwO:`[urd{ҷ+|ۗ5sр3Wmk<5k1SRթoq8w!qRj SN(4dKpAB|99?8~[k,rz%[R՛* .TSqbz2\dn:d4 qrf N_,@y7 N<mЋI!_b0ͥPGӭ;* Z%X_8xu u8؅orr7͌798]FRݗq/woKh#إuOZְMsf 8xi-kގ)ݓIĝgloMRMWޡЪ{4~  ݺm֩8s2ȅ/ [ gy{uOOtΟuxOFX557ęܹ8i +zs q\wC9Xqq*`EYc5gl](`!<3'.4bňE,g@SX$ h*F0.HO'ӮXd V͌kr!>@\q!a!Μ}Q7a` Cj <9^w5PpcBZ'$XSGb"=jj*'fa %!  ĵ<#Y a%{oڰaSq7LmtO%T݋rF k}L?/|2u|Kur%s_ѯY|Ls1{ڥ-)zq3T2uWr%Ux25\TLyW՝<*G~{ &iHeϊ7Lي.g.Խy{ͧwe>6woK/n;5& NٺTGjƵ~'S7A;B\dyW\S#%S/Ѷnɓr ʥu7;.Z'=jlմ`ջᆔ'"H\LOD4tfjݬw)ԙ8sLRZ#j=jqWp2/_>cx2غt)/ ę'{/ۓ3i֤;Tq5]q>i/ L57ob4էꗻmT[qP'S[wT~:-Pҳ8;gӔ1EoXOڽԻNXD 2xRܸ5X#ⓩ.+,a N׺PBty'S G,t͛a<,c''S G,'S#gӬgnL;\ qNq8S> ,Lt4pcB2 ӯ+Ry26Oˊ x8@9 oO ͓#T#0kݎP$Biօ#"Y@yr"<[9f8b)>jo;CT3?ZܟT\WFiB!`ij]!^wAW/:g'q~2غpR|2:1jMr"qɡeG +o 8}b>JiSgjޔIiieG +gjo`q5P└ Qp5pcB2 &+9_.GҷpK+'S w!\9X:9^8˱!)m:Oⓩ.+W4O&T[9-EfZ!`(f O2S=*J*b>O:3|zP;LC1ՓBL (:Sdjϓ4P6&4=j%@\/zV7 T]|1*G7%{C\-_F  @}BWL ˽zAS<%V8{9gg@ܻN逸W8G+G@ LL -XF*FAh8T9ZuHrOҪ/{ŭOՑD m%9p~U.2.4 q"q͓8wKmeu"3x-ZKZ@\Ҧ)qB\@ifu쨦)q>8gY~hgYMSܥֺD8ӤK)q>8g[J)q eZ @O)Ҷ5RxLsxmEfSiN=/9MjA}p7)=@3M%g@j ^N%8tЊ)87DbL[gjoUg%hfqN逸LX+X"qGfK]g.ykyBEw6O~UqGZjk.J|ew_yO/73;7Rc-l9Roogl@m4,D{V@\+d+9,X@Y@\23i@_ Knb;g@KEH|@\)*xM3v ,KqGE$& 4ϲfL@Cq&u_㠎/1fKxqLIuyQ8O sN$DLs]Vwt)^tC8q bEⒹ!@8q @8q @8q @8qqI@\gW[mz{?Gyzpx廵$W3̗+K'sU$!@8q @8q @8q @8q{@8qą!y%7V߭R\GWEo( @8q!@8q @8q @8q 7 .& [&ZZ_(t-8 @8q q% @8q @8q @8q z=!Vf Gj8q @n q @8q @8q @8q|}l&yZڀڗ;BR 7 @8qpC8q @8q @8q @08qS;}Qފo5w@\'Zq!@8q @8q @8q @8qpC8q\텗wnMO=z|TEu/<ܾ* @8q q- @8q @8q @8q @\qq!@8q qpC8q @8q @8q @8q !@8qW@n @8q @8q @8q@ @pC8q @8q @8q 7 @@n @8q q*Y @8q @8q @8q @8q @8q4 78q @8q @8q @8qA__wЊ:^bR^Kn{Pd]##vNU͵ iq넝wuv[T콥ꎝT#Ɏ4[9K$iL&Z:hZҧI;]Ok՗.[7L%βfzּ; 4׳޻1ZZhiEiGuiko`5ͳiZYו7YMֺ_<߉,qzXMs\E i=jDIt7IZ׿֙uZMl4Zغ D$4K%M0yM}u9U4]^Ӝ"6MTefֺ+$Nk} u=YOly^l"IZ먦9kڬ0fCE̦1oyWQf$E WLfPYJ╀WCVwμ!Pӟz<[1;Y1R!VMmm3_W:yx'<~,0 ^Y. 'پ ۧfLeiF!9ͺKqAIUog٢$pIJ xAS/[,+K J.^ ŁP'QwWhӪJE{x'^!qRx%l5}W7Pn8ůeOpu6 uen~-E4w 1Cq7YL9:+䑧CıWG^j /-Q.>vuq@@88@@VJuy@@.rm<%0 (}pCHL/M,> 8f^<љ/%/x0l4K%󪯇Q`S6yٟ  8 82;X+9JJXK,"%6bPiwDOpYg^\zmLm5 BTp\)wj5IO))\2Xd+erI_A=ƋTK$cyAԟ (G5#K8U3ZGMM$xEUұf_5 dLUeHiZ1Śl sJl2fEK ~N6y9   qI]7E;6I:6Z_Hwh ^N134p&s${IHPYHz> C(껁G8.ۉ{P$qq ˧7~9I_ribx#{:^y^I{KU-U+FTKӏ>)3+h w\ϵpo|/''QϑZwwnlYVrw{NPӡE u^C齅Qɚf{v0{}LvI>香ݿdӽo_:z?ǶjM^53SlLfҪ Sh' cϚ&>4) N(9ֵ}Yh%|R.0a}•/6x_N%"^i`ĸ ^λ܄%+?ru/^7h'GrOi{즆W̄m+|IL/isB KͰ/IK_|{/qil˹Q?` Kwytmb)o.?|~e}./if',aY%>oVؼh56>Yli7(Hh^1ǒ<*&ϑIY5> "mo1i2Ĥt!W3ifr:!NX;9-^{oRzf";֣~WGy8TUqn$q) 7' Z7VwVnRD56[w8 iI45+VqRЪ[VVu3ʭX@*qޕD5 jOM+he| Md| H|Fi.hy搴D~؛4-RZCsNަ^D$虯ܺ'iO4(E4]F4g5n yMs{J$@6mrf'3D5iވ4=d1iv3˼\a2͙kmD$ boD5xW͉f@3i@43;9oSEbb1#v枡줙&5\Z5Mj"a(sa4!4ẢmU3BE_H4k{XM3Hp&ƺYM9]9)4]9MBr\7ҲGDlM$AAijD¼}KB>$#V|q!Y{exyXX./?^x޶H:nj^:U&m$VWx륕۸Tl&ZumJo+kun[" ^g5D5eUu oumrh+3hkdrd_RG/i! MM/y*M ijmmf=Ma/U͆YM&՗ܺ4[w#iKv$똏MKjN51$-^G$-קhjR%D5%XMfWin%K7IL$P%D_{Ne 8JS N!b@T"q\} ! + @8q<@8qq N%Tg NY8qvT)>@j&qK8Y!Y @\Bq  @8q^qH @8qqe Q)8ĩ)@:q N9ĩR$!N&8: .d!rHp@B @ @8D8qH$+8q Ny Nu N!gq9 N" q*5)@\8qI' dj9$ K8Y N! k_ @8q|  # r\9 8'8;8SV @])88U7ĩq1V%, q, .d!8 @8 @8q^qǑHW9.q@@@B) .s@*ETjS qN+d!8q ' )@8qyK+O cq! CMq*<:{q 8J8 .pCx2%,q#ĕ) G @T Ny Nu N!*-yYzHsӊS*TS @\nK, @8 @8q^q! CMq*<:{d8Q^Hw27W#U .9N+ N%!NY8L qK, @8 @8q^q! CMq*<:{@\,BsӊS*TS @\LK, @8 @8q^q! CMq*<:{qmGj'- jqsӊS nSV N5pC8qu!@/A@8q<@8qyes @ @85ĩ@@@B)[~_{凵׿ֱoߧW .9N+ NSU N5pC\]ݡT*1 g ݝid8WéPoƙ,T9NSRCIqJ5ק32Ā855Eoԝy5}I5%ĩ{s,ݹ7Y8Ju,J*YAWԞ8rԜn -O5$ĩM9 e ӯrZP|@^M ;vf U3j ^9ꐼPl@)(z!W ;؀8Cg ^4CN(> ЛE/e 98 {!' .N(> ΰݐ 3͐ 3 Ӈrj|P|@^M9qY5C>(j >j7B;JęvEfz胜\o>(> ЛE4h}Pl@q6BcrAqH 9 YAN9Z'8}NFA.@\Vp..^Kqe 8b2qC>(> Nr\*J>(> Δ., ' .כ 3t rq:SvA )$\M33(z$8zTAN@\4eg0Ma N%b@:^ n8PwU[{W˖OnXmy7_cwЊ!륕SB bj ^N %+9Xuj ^N}𔘚j .79,UM5X8{M f2^QqU!^K*h``O R} {Ӈu#;@~y αm͝=ͻwe5c= Nf] aռfM -gPN9w5kPFNu_nj @]j5ZN5q1fMׄӪJs%jqZ5_i4 ΢h1oL۳v5d477ΠÃu V .Fj9T:lbTsDM3.!/D4<粲^H, i@Øy/$q65E'Dݗ4t5͐"fsRBvBD5@r5͐Jܗ85/$qӔP2,x!3섈c^\ܐ ;!y EK ;[eDq8]A"g]\q1fn]l )8lNoAgRMsᄗi.H 0M9,%ɛD Ψf샔On54l>1c$ 5gW3Ĺ)\l}W7M9.cNfDAiW=r28KJ]9. gKp.h156R65E4͜y$j>H 1Ĭf@Q"fsR 9.S rQSLƹ[9LFSjN>H ūL>hy EKqD%ij={o#:hH}I/ٻKT_B]y'꾄Kxɰ -wedruKˑq%^L 1$W]/bSqrj .^QĪSqrꃧTqU!AgIŨjzE mMX50K: qrd_"1#u7 }(~$5۞kj XMm]hϮGv fKtVͫj8;B5fOѪ9oN®Y֨rx4׏mq1_d|I4q:5?LeęHgO5@7e>w9ї@7>u#d¦yYbq4R {@\jnNw6O8BD,q+D8 8Rqc~G͂8K}y uf M9h)z!wӔQ54͐Jܗ85/$qޔP2,z! 3rB}I8a'DT9h)zzǰ=䂚9 >H̻  .F̭j ά䃔@]"f@Q>h '<6BrAj ήfi>}+%YAgR3䃔On54j}5әō똇\YwBj|W7MI@)'AiW=r28sJ9.̑3) jklG͞NUSAj .>̩YAgR3Cj >HLj}4 @[R 8!5墦s6M 2f\\Aj .^5rM9h)nd1MygmWc/={WqU0K;$1@ݗ}I;eZ2_%2^bɚ^ʛ0/k1 ĐXurjZLYizE3ǓNYiRS%U85% g-6a5@}-c TuȾDb&}xxpݕ̟oWb&2jcMYOWcrCNMG ξj^uUizh+a ΠfN`f]a qv5@7 ꝴI:v8qX:}G:35l쾤٣3Z N掆L3 TեⴽyHˉ$ z u&;ifzr`BLsϰLgZ]nk)q]wCqV5% @QM i8P$ E'Dݗ4t5͐"YiN;34CNh+q_"q5/$qޔP2,z! 3rB188i4% nݣ>Y1\n)q]wCywf3) 5g}, lG7ͅ^[mrA Ϊfi>}+%YAgR3ODBq5CWH$H@3zƚ5C.HYՔ8N4EVgɤnkjGNFsC9st%[H Ք]"bklCM!W}/֜v?y$q&5>H1Ī$ қȦ)qL8 rQSL9[LFSj}UM55)qv6KԜ{FquN>&^޳w Tu8O } ї񞽓aq#YW[cRqVo[w 6VL]Ri=j+{25^Pu'SmtO%Ԅx QgIŨjLMWtOmMXݓWtO&Tݲrd_2y;UӐʞhoHչiK'Sjv783Of NTT/qV'1qL5`ғLw cj6]sjJO՜0q*5է;<ioN76q*-G\LMnНiv Md}eP:j);>⾤dWԑN LAw5/4 ө9%;!꾤Ý ^4꣛fTC}CICNfTr5EJܗLu͢iՔP۲MSB8;d'Dݗtz)0r8䄈jLk)zzǰ=䂚9ⓩ]A8BAO4g,Y@N h 'ܵ7B;>'SmjvQo39%%ɛfq$ DDBFMsyhDquyh)l\tO,>jWS A_I44 ddHx#'#02rӲ q:5duWSL;L4sj >hO<l}4OڵAa N?$5ԙB24d40E4rTsXA'STSAMMj.}4OڵAT_<޳6bv1їLMT_B]y'꾄Kxɰ իg0X;>;x\f6A+VN 1؋8{95XVON}CϒQTqDj a-c5XuȾ$T_"0]521HSKD GMs@4-}1:i@P} QMK4E GMsD_"q 98jDӤSMY|t##K @DTqc~MM꾤f'i8ӤYxLs+q_R8St?MͶe96:E O͆Ej8l" @"#g5]hj*8lN0i.5M5gW4KXMk!MSs4 @Ϙ8 G&9EK$JTZir\c.>ȐݭL*S99%q1`+똏 j .15x  s̩d1MygmWc/={WqU0K;$1@ݗ}I;oog-^x [rטE1$S]/b03KWz[Yqf<%K|UM=LumrhVYf2^qTl!ٗ ę$qU*M؁8Ic}ı$ _g5؁dԌ]45cOEXc?qf?FS3v ,,c~e@\"ky 4jvVDx@\";gٗtԌ3i6s\uͥ1؁8K6'MS3v βC5M=gӬjN.a5؁8K"ibs=gm%^%y2r\qfݭ選q}7D K&;M<+T_<޳6bv1ї끸D{xI%ŝ}HK }-+--|tLѲ@ubT"8UuT3@!Y @\Bq  @8q> @\q/ @8 @8qjS 8'8;8SV "@JX8e NU8L qEHd!8q> @8q|@ܭ8qqǡ&8SpSSq_@@8J8 .pC!%,q#ĕ) G @T Ny Nu N!!NY8qvT)R@BXnt@\9$ K8Y N! k_ @\yq!@8q|@8qq N%Tg NY8qvT)R@BXntOC@ @8q|@2pC8q8qP@J)8ĩ)@:q N9ĩR$!N&8: .d!n+d!8q ' )@8qyK+O 7 @ @85ĩ@@@B ĩ88U7ĩq1V%,WBqNS8qq q# CMq*<:{@8Sqq nS N!b7K:Y'S!Y @\Bq  @8q^q q# CMq*<:{q 8J8 .pC!%,q#ĕ) G @T Ny Nu N!gq9 N" q*5)@\8qI' ĕC@ @8q%ĕ' G @T Ny Nu N!}qqTs@*ETjS qNrHp@B @W@n @8 @8qjS 8'8;88eu@rSHpCJMq cu!@\BWBqNS8qڗW@n @8 @8qjS 8'8;8SV @])88U7ĩq1V%,WBqNS8qq q# CMq*<:{q 2p7ĩqTvqMuTqξBqq @8q @8q8Wg)@rqj n8Hq uo[[Wh͍r Xymi[ASm!7homfMڪ8Ql˕[Ki.UGMcNuw>i]In/mBiJ/Et=MM/i!u&q.Ĭ1t45Me MM/JSsk436{Iˑ!ZgFS8+YM&՗9؛V:s4CDT/!~{iF^J4ͮ4i%$#mYM4ld5 fKS3"y9-[=\CTs[#fM[KwkcI^&JoӜU5ͽHkYs\]Qr\Us1p1Ǖ)@q 10ƜbM$ tVF7JM$_q1'xs\ۈjM$4&5Kr<<Nbv%qiڗp. F>Ӥ2V=6D1Mٻʎ9#?/_^~I{VXghv>Ǫ3IŞ`bu/d'|?x)㝴r;LSB+eZ?TCF6wižſWr_ V5MkXզi~iVHT///7/T///ib__ _rc_՝&xrģYT!ٳwBeyk8qHYʏ~% A @ @ @n A @8o@AA @ @ @J' AA >SAO|>(ʭ) yt\7;-^#1 '|bP*88qq88n@ @*[@<MT)8RM_|)G_ w@ T@ &MI@*_ƉdwKSA $O>CO Esx!@ @ @ ,8TT;PT1 V9938 VRAM timings

    V9938 VRAM timings

    Measurements done by: Joost Yervante Damad, Alex Wulms, Wouter Vermaelen
    Analysis done by: Wouter Vermaelen
    Text written by: Wouter Vermaelen
    with help from the rest of the openMSX team.

    Introduction

    This text describes in detail how, when and why the V9938 reads from and writes to VRAM in bitmap screen modes (screen 5, 6, 7 and 8). VRAM is accessed for bitmap and sprite rendering but also for VDP command execution or by CPU VRAM read/write requests.

    motivation

    Modern MSX emulators like blueMSX and openMSX are already fairly accurate. And for most practical applications like games or even demos they are already good enough. Though there are cases where you can still clearly see the difference between a real and an emulated MSX machine.

    For example the following pictures show the speed of the LINE command for different slopes of the line. The first two pictures are generated on different MSX emulators, the last picture is from a real MSX. Without going into all the details: lines are drawn from the center of the image to each point at the border. While the LINE commands are executing, the command color register is rapidly changed (at a fixed rate). So faster varying colors indicate a slower executing command.

    From left to right these pictures show:

    • (left) The output of MSX emulators that use Alex Wulms' original command engine emulation core. All(?) modern MSX emulators use this core, including blueMSX, OCM and (older versions of) openMSX. The output are squares, this indicates that the speed of a LINE command doesn't depend on the slope of the line.
    • (center) The output of openMSX version 0.9.1. Here the command engine was tweaked to take the slope of the line into account, so the test now generates clean octagonals.
    • (right) The output of a real MSX. The overall shape is also an octagonal. But there are also a lot of irregularities. These irregularities can be reproduced when running the test multiple times. So it must be a real effect, and not some kind of measurement noise.

    This test is derived from NYRIKKI's test program described in this (long) MRC forum thread. This particular test is not that important. But because it generates a nice graphical output it allows to show the problem without going into too much technical details (yet).

    In most MSX applications these LINE speed differences, or small command speed differences in general, likely won't cause any problems. (Except of course in programs like this that specifically test for it.) But it would still be nice to improve the emulators.

    measurements

    To be able to improve openMSX further we need to have a good understanding of what it is exactly that causes these irregularities. It would be very hard to figure out this stuff only by using MSX test programs. It might be easier to look at the deeper hardware level. More specifically at the communication between the VDP (V9938) and the VRAM chips. This should allow us to see when exactly the VDP reads or writes which VRAM addresses.

    So at the 2013 MSX fair in Nijmegen we (some member of the openMSX team and I) connected a logic analyzer to the VDP-VRAM bus in a Philips NMS8250 machine. The following picture gives an impression of our measurement setup.

    Next we ran some MSX software that puts the VDP in a certain display mode. It enables/disables screen and/or sprite rendering. And it optionally executes VDP commands and/or accesses VRAM via the CPU. And while this test was running we could capture (small chunks of) the communication between the VDP and the VRAM. This gives us output (waveforms) like in the following image.

    It's not so easy to go from this waveform data to meaningful results about how the VDP operates. This text also won't talk about this analysis process. If you're interested in the analysis or in the raw measurement data, you can find some more details in the openmsx-devel mailinglist archive. The rest of this text will only discuss the final results of the analysis.

    Because one of the primary goals was to improve the command engine emulation in openMSX, the measurements mostly focused on the bitmap screen modes (a V9938 doesn't allow commands in non-bitmap modes). So the following sections will only occasionally mention text or character modes. Because we used a V9938 we also couldn't test the YJK modes (screen 11 and 12). But it's highly likely that, from a VRAM access point of view, these modes behave the same as screen 8 (or as we'll see later, the same as all the bitmap screen modes).

    VRAM accesses

    Before presenting the actual results of (the analysis of) the measurements, this section first explains the general workings of the VDP-VRAM communication. This is mostly a description of the functional interface of DRAM chips, but then specifically applied to the VDP case. Feel free to skim (or even skip) this section.

    Like most RAM chips in MSX machines, the VDP uses DRAM chips for the video RAM. There exist many variations in DRAM chips. You can find a whole lot of information on wikipedia. Most of the info in this section can also be found in the 'V9938 Technical Data Book'. Often that book goes into a lot more detail than this text. Here I highlight (and simplify) the aspects that are relevant to understand the later sections in this text.

    Connection between VDP and VRAM

    Between the VDP and the VRAM chips there is an 8-bit data bus. This means that a single read or write access will transfer 1 byte of data.

    There is also an 8-bit address bus. Obviously 8 bits are not enough to address the full 128kB or even 192kB VRAM address space. Instead the address is transferred in two steps. First the row-address is transferred followed by the column address. (Usually) the row address corresponds to bits 15-8 of the full address, while the column address corresponds to bits 7-0.

    Though this still only allows to address up-to 64kB. To get to 128kB, there are 2 separate column-address-select signals (named CAS0 and CAS1). These two signals allow to select one of the two available 64kB banks. So combined this gives 128kB. (Usually) you can interpret CAS0/CAS1 as bit 16 of the address.

    In case of a MSX machine with 192kB VRAM there is still a third signal: CASX. To simplify the rest of this text, this possibility is ignored. It anyway doesn't fundamentally change anything.

    Next to the data and address bus there are still some control signals. I've already mentioned the CAS signals (used to select the column address). There's a similar RAS (row address select) signal. And finally there's a R/W (read-write) signal that indicates whether the access is a read or a write.

    Timing of the VDP-VRAM signals

    When the VDP wants to read or write a byte from/to VRAM it has to wiggle the signals that connect the VDP to the VRAM in a certain way. This section describes the timing of those wiggles.

    The timing description in this section is different from the description in the 'VDP Technical Data Book'. The Data Book has the real timings, including all the subtle details for how to build an actual working system. This text has all the timings rounded to integer multiples of VDP clock cycles. IMHO these simplified timings make the VDP-VRAM connection easier to understand from a functional point of view.

    A single write

    To write a single byte to VRAM, follow this schema:

    • Put the row address on the address bus and activate the RAS signal. Most signals are active-low, so activating means make the signal low.
    • After one cycle (remember these are functional timings, especially in this step the real timing rules are more complex):
      • Activate (one of) the CAS signals.
      • Put the column address on the address bus.
      • Set the R/W signal. A low signal means write.
      • Put the to-be-written data on the data bus.
    • After two cycles the CAS signal can be deactivated. At this point the value of the R/W signal doesn't matter anymore (it may have any value). But measurements show that the VDP restores the R/W signal to a high value at this point.
    • Again one cycle later, the RAS signal can be deactivated.
    • The RAS signal has to remain de-active for at least two cycles.

    So a full write cycle takes 6 VDP clock cycles.

    A single read

    Reads are very similar to writes, they follow this schema:

    • Put the row address on the address bus and activate the RAS signal.
    • After one cycle:
      • Activate (one of) the CAS signals.
      • Put the column address on the address bus.
      • Set the R/W signal: a high value indicates a read. The VDP keeps this signal high between VRAM transactions. So in measurements you don't actually see this signal changing for reads.
    • After two cycles the read data is available on the data bus. The CAS signal can be deactivated now.
    • After one cycle the RAS signal can be deactivated.
    • Wait at least two cycles before starting the next VRAM transaction.

    So this is very similar to a write: address selection is identical. Obviously the R/W signal and the direction (and timing) of the information on the data bus is different. And just like a write, a full read cycle also takes 6 VDP cycles.

    Page mode reads (burst read)

    Often the VDP needs to read data from successive VRAM addresses. If those addresses all have the same row address, then there's a faster way to perform this compared to doing multiple reads like in the schema above.

    • Put the (common) row address on the address bus and activate the RAS signal.
    • After one cycle:
      • Put the first column address on the address bus.
      • Activate (one of) the CAS signals.
      • Set the R/W signal (though the VDP already has this signal in the correct state).
    • After two cycles read the data from the data bus, and deactivate CAS.
    • Two cycles later, put the 2nd column address on the address bus and re-activate (one of) the CAS signals.
    • Again two cycles later read the data and deactivate CAS.
    • It's possible to repeat this process for a 3rd, 4th, … byte.
    • After one cycle deactivate the RAS signal.
    • Wait at least two cycles before starting the next VRAM transaction.

    The above diagram shows a burst-length of only two bytes. It's also possible to have longer lengths. The VDP uses lengths up-to 4 bytes (or 8, see next section).

    In this example reading two bytes takes 10 VDP cycles. Doing two single reads would take 2×6=12 cycles. When doing longer bursts, the savings become bigger. Doing a burst of N reads takes 2+4×N cycles compared to 6×N cycles for a sequence of single reads.

    In principle it's also possible to do burst-writes. Though the VDP doesn't use them (it never needs to write more than 1 byte in a sequence).

    Multi-bank page mode reads

    Burst reads are already faster than single-reads. But to be able to render screen 7 and 8 images, burst reads are still not fast enough. In these two screen modes, to be able to read the required data from VRAM fast enough, the VDP reads from two banks in parallel.

    There are 2 banks of 64kB. These two banks share the RAS control signal, but they each have their own CAS signal. The address and data signals are also shared. This allows to read from both banks almost in parallel:

    • In burst mode it was possible to read one byte every 4 VDP cycles. For this the CAS signal had two be alternatingly two cycles high and two cycles low. The address and data buses are only used during 1 of these 4 cycles.
    • Multi-bank mode uses both the CAS0 and the CAS1 signals. CAS0 is high when CAS1 is low and vice-versa. When looking at a single bank (which only sees one of the two CAS signals) this looks like a normal burst read. The only difference is that the RAS signal is at the start or at the end 2 cycles longer active than strictly needed. But that's perfectly fine.

    So this schema gives (almost) double the VRAM-bandwidth. The only requirement is that you alternatingly read from bank0 and bank1. At first sight this requirement seems so strict that it is almost never possible to make use of this banked reading mode: to render screen 7 or 8 you indeed need to read many successive VRAM locations, not locations that alternatingly come from the 1st and 2nd 64kB bank.

    To make it possible to use banked reading mode, the VDP interleaves the two banks. This introduces the concept of logical and physical addresses:

    • Logical addresses are the addresses that a programmer of the VDP normally uses. For example the bitmap data for screen 8 (possibly) starts at address 0x00000 and goes till address 0x0D400.
    • Physical addresses are the addresses that actually appear on the signals between the VDP and the VRAM. So the combination of the row and column address and the CAS0 or CAS1 bank-selection.

    In most screen modes the logical and the physical addresses are the same. But in screen 7 and 8 there's a transformation between the two:

    physical = (logical >> 1) | (logical << 16)

    So the 17-bit logical address is rotated one bit to the right to get the physical address. The effect of this transformation is that all even logical addresses end up in physical bank0 while all odd logical addresses end up in physical bank1. So now when you read from successive logical addresses you read from alternating physical banks and thus it is possible to use banked read mode.

    Usually a VDP programmer doesn't need to be aware of this interleaving. But because interleaving is only enabled in screen 7 and 8, this effect can become visible when switching between screen modes. An alternative design decision could have been to always interleave the addresses. I guess the V9938 designers didn't make this choice to allow for single chip configurations in case only 64kB VRAM is connected.

    The diagram above shows a read of 2×2 bytes, in reality the VDP only uses this schema to read 2×4 bytes. In principle it's also possible to write to two banks in parallel, but the VDP never needs this ability.

    Refresh

    DRAM chips need to be refreshed regularly. The VDP is responsible for doing this (there are DRAM chips that handle refresh internally, but the VDP doesn't use such chips). Many DRAM chips allow a refresh by only activating and deactivating the RAS signal, so without actually performing a read or write in between. When extrapolating from the above timing diagrams, this would only cost 4 cycles. Though the VDP doesn't actually use this RAS-without-CAS refresh mode. Instead it performs a regular read access which takes 6 cycles.

    Each time a read (or write) is performed on a certain row of a DRAM chip, that whole row is refreshed. So to refresh the whole RAM, the VDP has to periodically read (any column address of) each of the 256 possible rows.

    Distribution of VRAM accesses

    The previous section described the details of isolated (single or burst) VRAM accesses. This section will look at such accesses as indivisible units and examine how these units are grouped together and spread in time to perform all the VRAM related stuff the VDP has to do.

    The VDP can perform VRAM reads/writes for the following reasons:

    • Refresh
    • Bitmap rendering
    • Sprite rendering
    • CPU read/write
    • Command read/write

    Note that next to bitmap modes, the VDP also has character and text modes. I didn't investigate those modes yet, so this text mostly ignores them.

    The rest of this text explains when in time (at which specific VDP cycles) accesses of each type are executed.

    We'll first focus on refresh and bitmap/sprite rendering. Later we'll add CPU and command engine. The reason for this split is that the first group has a fairly simple pattern: refreshes always occur at fixed moments in time. Enabling bitmap rendering only adds additional VRAM reads but has no influence on the timing of the refreshes. Similarly enabling sprite rendering adds even more reads without influencing the bitmap or refresh reads. CPU and command accesses on the other hand cannot simply be added to this schema without influencing each other. So those are postponed till a later section.

    Horizontal line timing

    The VDP renders a full frame line-by-line. For each line the VDP (possibly) has to read some bitmap and sprite data from VRAM. It's logical to assume (and the measurements confirm this) that the data fetches within one line occur at the same relative positions as the corresponding data fetches of another line. So if we can figure out the details for one line, we can extrapolate this to a whole frame. Similarly we can assume that different frames will have similar relative timings. So really all we need to know is the timing of one line.

    TODO: odd and even frames in interlace mode probably do have timing differences. Still need to investigate this.

    Let's thus first look at what we already know about an horizontal display line. The 'V9938 Technical Data Book' contains the following timing info about (non-text mode) display lines.

    Description Cycles Length
    Synchronize signal[0 - 100) 100
    Left erase time [100 - 202) 102
    Left border [202 - 258) 56
    Display cycle [258 - 1282)1024
    Right border [1282 - 1341) 59
    Right erase time [1341 - 1368) 27
    Total [0 - 1368)1368

    So one display line is divided in 6 periods. The total length of one line is 1368 cycles. The previous section showed how long individual VRAM accesses take. The next sections will figure out how all the required accesses fit in this per-line budget of 1368 cycles.

    A note about the timing notation: in this text all the timing numbers are VDP cycles relative within one line. For example in the table above the display period starts at cycle 258. The display period of the next line will start at cycle 258+1368=1626, the next at cycle 2994 and so on. To make the values smaller, all cycle numbers will be folded to the interval [0, 1368). The staring point (cycle=0) has no special meaning. We could have taken any other point and called that the starting point. (For the current choice, the external VDP HSYNC pin gets activated at cycle=0, so it was a convenient point to synchronize the measurements on).

    TODO horizontal set-adjust: The numbers in the above table are valid for horizontal set-adjust=0. Similarly all our measurements were done with set-adjust=0. Using different set-adjust values will make the left/right border bigger/smaller. I still need to figure out which timing values of the next sections are changed by this. E.g. are all the VRAM accesses in a line shifted as a whole, or are just the bitmap data fetches shifted and remain (some) other accesses fixed?

    TODO bits S1,S0 in VDP register R#9: The above table is valid for S1,S0=0,0. In other cases the length of a display line is only 1365 cycles instead of 1368. The rest of this text assumes a line length of 1368 cycles. I still need to figure out where exactly in the line this difference of 3 cycles is located.

    Sneak preview

    The following image graphically summarizes the results of the rest of this section. This is a very wide image, it is much larger than what can be shown inline in this text (click to see the full image). It's highly recommended to open this image in an external image viewer that allows to easily zoom in and out and scroll the image.

    Here's an overview of the most important items in this image:

    • Horizontally there are 6 regions in the image (each has a slightly different background color). These regions correspond to the 'synchronize', 'left/right erase', 'left/right border' and 'display' regions in the table from the previous section.
    • Horizontally you also see a timeline going from 0 to 1368 cycles. This corresponds to one full display line.
    • Vertically there are 3 big groups: 'screen off', 'no sprites' and 'sprites', see next section for why these groups are important.
    • Within one vertical group there is one color-coded band and a set of RAS/CAS signals. Usually there's one RAS and 2 CAS signals, but the 'sprites off' group has 2 pairs of CAS signals. For the 'sprites off' and 'sprites on' groups there are subtle differences in the CAS0/1 signals between screen modes 5/6 and 7/8. But to save space these differences are only shown once.
    • The colors in the color-coded band have the following meaning:
      • red: refresh read
      • green: bitmap data read (dark-green is dummy bitmap read)
      • yellow: sprite data read (brown is dummy sprite read)
      • blue: potential CPU or command engine read or write
      • dark-grey: dummy read
      • light-gray: idle (no read or write)
    • The CAS signals are drawn in either a full or a stippled line. Full means the signal is definitely high/low at this point. Stippled means, it can be high or low depending on whether there was a CPU request or VDP command executing at that point. Note that the RAS signal always toggles, even if there is no CPU or command access required.

    The next sections will go into a lot more detail. It's probably a good idea to have this (zoomed in) image open while reading those later sections.

    3 operating modes

    When looking from a VDP-VRAM interaction point of view, the VDP can operate in 3 modes:

    • Screen disabled (sprite status doesn't matter). This is the same as vertical border.
    • Screen enabled, sprites disabled.
    • Screen enabled, sprites enabled.

    Note that the (bitmap) screen mode (screen 5, 6, 7, or 8) largely doesn't matter for the VRAM access pattern.

    TODO sprite fetching happens 1 line earlier than displaying those sprites (see below for details). This means that the last line of the vertical border before the display area likely uses a 'mixed mode' where it doesn't yet fetch bitmap data but it does already fetch sprite data. I didn't specifically measure this condition, so I can't really tell anything about this mixed mode. (One possibility is that it's just like a normal display line, but the fetched bitmap data is ignored.) Similarly the last line of the display area doesn't strictly need to fetch new sprite data.

    We'll now look at these 3 modes in more detail.

    Screen disabled

    refresh

    Screen rendering can be disabled via bit 6 in VDP register R#1. There's also no screen rendering when the VDP is showing a vertical border line. From a VRAM-access point of view both cases are identical.

    In this mode the VDP doesn't need to fetch any data from VRAM for rendering. It only needs to refresh the VRAM. As already mentioned earlier, the VDP uses a regular read to refresh the RAM, so this takes 6 cycles.

    The VDP executes 8 refresh actions per display line. They start at the following moments in time (the red blocks in the big timing diagram):

    284412540668 79692410521180
    refresh-addresses

    I didn't investigate this refresh-address-stuff in detail because it doesn't matter for emulation accuracy.

    The logical addresses used for refresh reads seems to be of the form:

    N×0x10101 | 0x3F

    Where N increases on each refresh action. So each refresh the row address increases by one and every other refresh either the CAS0 or the CAS1 signal gets used (the columns address doesn't matter for refresh). Note that this formula is for the logical address, in screen 7/8 this still gets transformed to a physical address. So in screen 7/8 a refresh action always uses the CAS1 signal. That means that in screen 7/8 the DRAM chip(s) of bank0 actually do get refreshed using the RAS-without-CAS refresh mode.

    The refresh timings are the same for all non-text screen modes. But in text modes there are only 7 refreshes per line and they are also located at different relative positions than in the table above. I didn't investigate this further.

    dummy reads

    Next to the refresh reads, in 'screen disabled' mode, the VDP still performs 4 reads of address 0x1FFFF. At the following moments (marked with dark-grey blocks on the timeline):

    1236124412521260

    I can't image any use for these reads, so let's call them dummy reads. In all our measurements these dummy reads always re-occur in these same positions, so it's not a fluke in (only one of) the measurements.

    The refresh actions remain exactly the same in the other two modes. But these dummy reads are different in the mode 'sprites off' or disappear completely in the mode 'sprites on'. (This confirms that nothing 'useful' is done by these dummy reads).

    Anyway for emulation we can mostly ignore these dummy reads. It only matters that at these moments in time there cannot be CPU or command VRAM reads or writes.

    screen enabled, sprites disabled

    refresh and dummy reads

    Refresh works exactly the same as in the previous mode. The dummy reads are a bit different. Now there are only 3 dummy reads at slightly different moments (also shown in dark-grey):

    124212501258

    The first of these 3 reads is always from address 0x1FFFF. The second and third dummy read have a pattern in their address. For example:

    1st2nd3rd
    0x1FFFF0x03B800x03B82
    0x1FFFF0x03C000x03C02
    0x1FFFF0x03C800x03C82
    0x1FFFF0x03D000x03D02

    This table shows the addresses of the 3 dummy reads for 4 successive display lines (this is data from an actual measurement, unfortunately our equipment could only buffer up to 4 lines). The lower 7 bits of the address of the 2nd read always seem to be zero. The address of the 3rd read is the same as for the 2nd read except that bit 1 is set to 1. When going from one line to the next, the address increases by 0x80. Our measurements captured 10 independent sets of 4 successive lines. Each time bits 16-15 were zero (bits 14-7 do take different values). This could be a coincidence, or it could be that these bits really aren't included in the counter. Note that again these are logical addresses (so still transformed for screen 7/8). I didn't investigate these dummy reads in more detail because they mostly don't matter for emulation.

    bitmap reads

    The major change compared to the previous mode is that now the VDP needs to fetch extra data for the bitmap rendering. These fetches happen in 32 blocks of 4 bytes (screen 5/6) or 8 bytes (screen 7/8). The fetches within one block happen in burst mode. This means that one block takes 18 cycles (screen 5/6) or 20 cycles (screen 7/8). Though later we'll see that the two spare cycles for screen 5/6 are not used for anything else, so for simplicity we can say that in all bitmap modes a bitmap-fetch-block takes 20 cycles. This is even clearer if you look at the RAS signal: this signal follows the exact same pattern in all (bitmap) screen modes, so in screen 5/6 it remains active for two cycles longer than strictly necessary.

    Actually before these 32 blocks there's one extra dummy block. This block has the same timing as the other blocks, but it always reads address 0x1FFFF. From an emulator point of view, these dummy reads don't matter, it only matters that at those moments no other VRAM accesses can occur.

    The start of these 1+32 blocks are located at these moments in time (these are the green blocks in the big timing diagram):

    (195) 227 259 291 323 355 387 419 451
    483 515 547 579 611 643 675 707
    739 771 803 835 867 899 931 963
    995102710591091 1123115511871219

    The following is only speculation: I wonder why there is such a dummy preamble block. Theoretically this could have been used (or reserved) to implement V9958-like horizontal scrolling without having to mask 8 border pixels. Unfortunately horizontal scrolling on a V9958 doesn't work like that :(

    screen enabled, sprites enabled

    refresh, dummy reads, bitmap reads

    Refresh and bitmap reads are exactly the same as in the previous mode. But the 3 or 4 dummy reads from the previous 2 modes are not present in this mode.

    sprite reads

    I've only investigated bitmap modes, that means the stuff below applies only to sprite mode 2.

    For sprite rendering you need to:

    • Figure out which sprites are visible: There are 32 positions in the sprite attribute table, and of those maximum 8 sprites can be visible (per line).
    • For the visible sprites, fetch the required data so that it can actually be drawn. This data is: the x- and y-coordinates, the sprite pattern number, the pattern data and the color data.

    Figuring out which sprites are visible is done by reading the y-coordinates of each of the 32 possible sprites. These reads happen interleaved between the 32 block-reads of the bitmap data, so read one byte between each bitmap-block. Because of this interleaving it's not possible to use burst mode, so each read takes 6 cycles. There's also 1 dummy read of address 0x1FFFF at the end. The reads happen at these moments in time (yellow blocks between the green blocks in the diagram):

    182 214 246 278 310 342 374 406
    438 470 502 534 566 598 630 662
    694 726 758 790 822 854 886 918
    950 98210141046 1078111011421174(1206)

    In the worst case, the 8 last sprites of the attribute table are visible. In that case all 32 reads are really required. Though even if the limit of 8 visible sprites is reached earlier, the VDP continues fetching all 32+1 bytes. Also if one y-coordinate is equal to 216 (meaning that all later sprites are invisible), still all 32+1 fetches are executed.

    Once the VDP has figured out which sprites are visible it needs to fetch the data to actually draw the sprites. This VRAM access pattern is relatively complex:

    • In the worst case there are 8 visible sprites. This requires reading 8×6 bytes. Some of these reads can be done in burst mode, others are single byte reads.
    • Even if there are less than 8 sprites to display, all read accesses do still occurs. It seems to be that the useless reads are duplicates of sprite 0. (Or is it the first visible sprite? I didn't look in detail because it's not important for our purpose. It only matters that the VRAM bus remains occupied).
    • The data fetches happens in 4 chunks of each 2 sprites. Each chunk reads:
      • Y-coordinate, x-coordinate and pattern-number of 1st sprite. Burst of 3 reads, takes 13(!)cycles.
      • Y-coordinate, x-coordinate and pattern-number of 2nd sprite. Burst of 3 reads, takes 13(!)cycles.
      • Pause of 6 or 10(!) cycles
      • 2 pattern bytes of 1st sprite. Burst of 2 reads, takes 10 cycles.
      • Color attribute of 1st sprite. Single read, takes 6 cycles.
      • 2 pattern bytes of 2nd sprite. Burst of 2 reads, takes 10 cycles.
      • Color attribute of 2nd sprite. Single read, takes 6 cycles.
    • Note that the burst of 3 reads only takes 13 instead of the expected 14 cycles. If you look at the RAS/CAS signals you see that this uses an illegal(?) RAM access pattern: RAS is released together with CAS (even slightly before if you look at the raw measured data). But obviously this seems to work fine … makes me wonder why the VDP doesn't always use this faster access pattern.
    • Even for 8x8 sprites, the VDP always fetches 2 bytes of pattern-data per sprite line (and the 2nd byte is ignored).
    • Note that the y-coordinate is fetched again. It was already fetched to figure out which sprites are visible.
    • The positions in time of these reads (single or burst) are like this (yellow blocks (mostly) in the border period in the big timing diagram):
      123812511270128012861296
      130213151338134813541364
      2 15 34 44 50 60
      66 79 98 108 114 124
      Note that some of these fetches occur in the previous and some in the current display line. Though the start of the display line was chosen arbitrary (we could have picked the staring point so that these numbers don't wrap). It only matters that all sprite data is fetched before the display rendering starts.
    • Also note that the timing is slightly irregular: in the 1st, 3rd and 4th group there's a pause of 6 cycles, there fits exactly one other access in this gap. But in the 2nd group there's a pause of 10 cycles. There also only fits one other access in this gap, and the timing is 2+6+2, so 2 'wasted' cycles before and after that other access. I suspect that these 2+2 cycles are related to the R#9 S1,S0 bits. TODO measure this.

    It's worth repeating that whenever sprites are enabled, the VDP always performs the same fetch-pattern. So even if no sprites are actually visible, or if sprites are partially disabled (with y=216), and even with 8x8 vs 16x16 sprites, magnified or not. This confirms the fact that the VDP command engine is slowed down by the exact same amount in all these situation. Also all (bitmap) screen modes behave exactly the same with respect to sprite data fetches.

    CPU and command reads/writes

    position of access slots

    The previous sections explained when the VDP reads from VRAM for refresh and bitmap/sprite rendering (and even some dummy reads). Depending on the mode (screen/sprites enabled/disabled), this takes more or less of the available VRAM-bandwidth. The portion of the VRAM bandwidth that is not used for rendering can be used for CPU or command engine VRAM reads or writes.

    All CPU and command engine accesses are single (non-burst) accesses, so they take 6 cycles each. However it is not the case that whenever the VRAM bus is idle for 6 cycles, it can be used for CPU or command engine accesses.

    Instead there are fixed moments in time where there could possibly start a cpu or command access, let's call these moments access slots. Each slot can be used for either CPU or command accesses (there are no slots that are uniquely reserved for either CPU or for commands). The position and the amount of access slots only depends on the VDP mode (screen off, sprites off, sprites on), not for example on the amount of actually visible sprites or on the (bitmap) screen mode.

    The 3 tables below show the amount and the positions of the possible access slots for the 3 different modes (in the timing diagram these are the blue blocks):

    screen off, 154 possible slots
    0 8 16 24 32 40 48 56 64 72
    80 88 96 104 112 120 164 172 180 188
    196 204 212 220 228 236 244 252 260 268
    276 292 300 308 316 324 332 340 348 356
    364 372 380 388 396 404 420 428 436 444
    452 460 468 476 484 492 500 508 516 524
    532 548 556 564 572 580 588 596 604 612
    620 628 636 644 652 660 676 684 692 700
    708 716 724 732 740 748 756 764 772 780
    788 804 812 820 828 836 844 852 860 868
    876 884 892 900 908 916 932 940 948 956
    964 972 980 988 996 10041012102010281036
    10441060106810761084 10921100110811161124
    11321140114811561164 11721188119612041212
    12201228126812761284 12921300130813161324
    1334134413521360

    sprites off, 88 possible slots
    6 14 22 30 38 46 54 62 70 78
    86 94 102 110 118 162 170 182 188 214
    220 246 252 278 310 316 342 348 374 380
    406 438 444 470 476 502 508 534 566 572
    598 604 630 636 662 694 700 726 732 758
    764 790 822 828 854 860 886 892 918 950
    956 982 98810141020 10461078108411101116
    11421148117412061212 12661274128212901298
    13061314132213321342 135013581366

    sprites on, 31 possible slots
    28 92 162 170 188 220 252 316 348 380
    444 476 508 572 604 636 700 732 764 828
    860 892 956 9881020 10841116114812121264
    1330

    Note that even in the mode 'screen off', when the VRAM bus is otherwise mostly idle, the access slots are still at least 8 cycles apart. A single access takes only 6 cycles, so 2 cycles are wasted.

    Very roughly speaking in mode 'screen off' there are about twice as many access slots as in the mode 'sprites off' and about 5 times as many as in the mode 'sprites on'. This does however not mean that in these modes the command engine will execute respectively 2× and 5× as fast. Instead in the mode 'sprites on' the speed of command execution is mostly limited by the amount of available access slots, while in the mode 'screen off', the bottleneck is mostly the speed of the command engine itself.

    Also note that the access slots are not evenly spread in time. For example:

    • In mode 'screen off', the slots are often only 8 cycles apart (measured from the start of the 1st to the start of the 2nd slot). Though starting at cycle=120 there's a gap of 44 cycles.
    • In mode 'sprites off', during the horizontal border, the access slots are roughly 8 cycles apart like in the previous mode, but during the display period, the spacing is more like 26 or 32 cycles. The largest gap is now 54 cycles starting at cycle=1212.
    • In mode 'sprites on', the pattern is again completely different. Here the slots are roughly 32 or 64 cycles apart. (The border even has slightly larger gaps than the display area. So contrary to some speculations, the commands do not execute faster in the horizontal border in this mode). The largest gap is now 70 cycles, starting at cycle=92. There's even one location where the smallest gap is also only 8 cycles. (Though if you look at the measurements you'll see that the slot right after this smallest gap (at cycle=170) is rarely actually used, even though the command engine is starved for VRAM bandwidth).

    These large gaps between the access slots are important. For example if the CPU is sending data to the VDP at a too fast rate, and this happens right at a moment where there are no access slots available, then some of the data send by the CPU is lost. We'll see later in this text that this can even happen when the time between the incoming CPU requests is (slightly) larger than the size of the largest gap.

    allocation of access slots

    The access slots can be used for either CPU or VDP command reads or writes. This section explains how the slots are allocated to these two subsystems.

    The basic principle is very simple: the CPU or the command engine take the first available access slot. And when the CPU and command engine both require an access slot at the same time, then the CPU gets priority. Though if you look at the details it is a bit more complicated:

    • When the CPU sends a read or write VRAM request to the VDP, this request is put in a buffer until it can be handled.
    • When the CPU sends a new request when there's still a previous request pending then the old request is lost. More on this below. TODO most logical is that the old (not the new) request is lost, but actually check this. Though the Z80 might be too slow to be able to test this.
    • Similarly when the VDP command engine needs to perform a VRAM read or write, this request is also put in a buffer. This is a different buffer than the one for CPU requests.
    • In contrast to the CPU, the command engine is stalled when the command engine buffer holds a request. So command engine requests can never get lost.
    • 16 cycles in advance of an access slot the VDP checks whether there is either a pending CPU or command request. If there's a pending CPU request, that request will be executed (16 cycles later). If there's no cpu request but there is a command request then that one will be executed (16 cycles later). So the CPU takes priority over the command engine. And very important, if there's no request pending yet, then 16 cycles later nothing will be executed, not even if a request does arrive within 16 cycles.
    cpu access slows down command execution

    A surprising result (at least to me) of these measurements is that the speed of VDP command execution is reduced while simultaneously doing CPU VRAM accesses. Looking back this makes sense because the same VRAM access slots are shared between CPU and command engine and the CPU gets priority.

    This effect is clearly noticeable in the mode 'sprites on' but much less in the other two modes. This is easily explained by looking at the amount of available access slots in these modes.

    The most extreme situation occurs in this test. Execute a HMMV VDP command (this is the fastest command, see below) while simultaneously executing a long series of OUT (#98),A instructions (the fastest way to send CPU write requests). In our measurements, in the mode 'sprites on' the command execution speed was approximately cut in half! But in the other two modes, the execution speed was barely influenced. (Actually our test program wasn't accurate enough to measure any significant speed difference, but theoretically also in the latter two modes the execution speed should be reduced by a small amount).

    too fast CPU access

    The fastest way for the Z80 to send read or write VRAM request to the VDP is by using a sequence of IN A,(#98) or OUT (#98),A instructions (of course such a sequence always writes the same value or ignores all but the last read value). This takes 12 Z80 clock cycles per request. (Instructions like OUT (C),r or OUTI are all slower). The VDP is clocked at 6× the Z80 speed. So when the Z80 sends multiple requests to the VDP, the minimal distance between these requests, translated to VDP cycles, is at least 72 VDP cycles. Earlier we saw that the maximal gap between access slots was 70 VDP cycles, so at first sight there's no problem. However consider this scenario:

    • Suppose we're in 'sprites on' mode. At time=236, we're 16 cycles before an access slot. Suppose there's no pending CPU nor command request at this time. So nothing will get executed at time=252.
    • A bit later at time=240 there arrives a CPU write request. This request gets buffered.
    • At time=252 there is an access slot, but nothing will get executed in this slot (because this slot wasn't allocated at time=236).
    • At time=300 we're again 16 cycles before an access slot. Now there is a pending CPU request, so we'll execute that at time=316.
    • At time=312 we receive a new CPU write request. This is 312-240=72 VDP cycles (or 12 Z80 cycles, the duration of a OUT (#98),A instruction) after the previous request. But the buffer still contains the previous unhandled request. The new request overwrites the old request!
    • At time=316 there's an access slot and we've allocated this slot to the CPU (at time=300). So the pending CPU request gets executed. Though this writes the data from the new request, the data from the old request is never written!

    Note that this scenario used a gap of only 64 VDP cycles between access slots, while there were 72 cycles between the CPU requests. (And the largest gap between access slots is 70 cycles).

    Command engine timing

    The command engine needs access to VRAM. In the previous section we saw when the VDP will grand access to this subsystem: when there's an access slot available and when that slot is not already allocated to CPU access. In this section we'll see when exactly the command engine will generate VRAM access requests. Obviously the type (read or write) and the rate of these requests depends on the type of the VDP command that is executing.

    Some commands (like HMMV) only need to write to VRAM. Other commands (like LMMM) need 2 reads and 1 write per pixel. Many commands execute on a block (a rectangle) of pixels. Such a block is executed line per line (all pixels within one horizontal line are processed before moving to the next line). Moving from one line to the next takes some amount of time (but YMMM is an exception, see below). This means that e.g. a HMMM command on a 20x4 rectangle executes faster than on a 4x20 rectangle (same amount of pixels in both cases, but a different rectangle shape).

    The following table summarizes the timing for all measured commands:

    CommandPer pixelPer line
    HMMV48 W 56
    YMMM40 R 24 W 0
    HMMM64 R 24 W 64
    LMMV72 R 24 W 64
    LMMM64 R 32 R 24 W64
    LINE88 R 24 W 32

    TODO timing for PSET, POINT, SRCH

    I'll explain the notation in this table with an example. Take the LMMM command:

    • Per pixel the LMMM command needs to:
      • Read a byte from the source.
      • Read a byte from the destination
      • Calculate the result: extract the pixel value from source and destination, combine the two (possibly with a logical operation), insert the result in the destination byte. And write the result back to the destination.
    • So per pixel, the LMMM command will generate 3 VRAM accesses: 2 read followed by one write. Between these accesses there will be some amount of time.
    • For LMMM the table lists '64 R 32 R 24 W'. Let's start at the 1st 'R' character, this represents the 1st read. Next there's the value 32 and a 2nd 'R', this means that the 2nd read comes at least 32 cycles after the 1st read. Then there's '24 W', meaning there are at least 24 cycles between the 2nd read and the write. And the initial value '64' means that there are at least 64 cycles between the write and the 1st read for the next pixel.
    • When moving from one horizontal line to the next in a block command, there is some extra delay. For the LMMM command this takes 64 extra cycles. So 64+64=128 cycles from the last write of a line till the first read of the next line.
    • Note that all these values are the optimal timing values. The actual delay can be longer because there is no access slot available or the slot is already allocated for CPU access.

    All the commands in the table above are block commands except for 'LINE'. For the LINE command the meaning of the columns 'Per pixel' and 'Per line' may not be immediately clear:

    • The VDP uses the Bresenham algorithm the calculate which pixels are part of the line.
    • This algorithm takes at each iteration one step in the major direction. The timings for such an iteration are written in the 'Per pixel' column for the LINE command.
    • Depending on the slope of the line, in some iterations the Bresenham algorithm also takes a step in the minor direction. For the VDP such a minor step takes some extra time (32 cycles). This is written in the 'Per line' column of the LINE command. (If you look back at the very beginning of this text, these major and minor steps explain the general octagonal shapes in the images. The uneven distribution of the access slots explain the irregularities.)

    Note that for the YMMM command there's no extra overhead when going from one horizontal line to the next. This might be related to the fact that a line of a YMMV command always starts at the left or right border of the screen.

    TODO What we didn't measure (also couldn't measure with our test setup) was the delay between the start of the command (when the CPU sends the command byte to the VDP) and the moment the command actually starts executing (e.g. when the first read or write command access is send to VRAM). It's logical to assume that the 'per line' overhead also occurs at the start of the command. But it's possible there is also some additional 'per command' overhead.

    speculation on the slowness of the command engine

    When looking at the above table, we see that the command engine is very slow. For example in a HMMM command there are 24 cycles between reading a byte and writing that byte to the new location. Or in a LINE command it takes 32 cycles to take a step in the minor direction. I believe there are two main reasons for this slowness:

    • I believe that internally the VDP command engine subsystem runs at 1/8 of the master VDP clock frequency. This matches the observation that all values in the above table are multiples of 8. It also explains why the access slots are always at least 8 cycles apart (while a VRAM access only requires 6 cycles).
    • The command engine gets stalled whenever there's a pending command engine VRAM request. A VRAM request (CPU or command) only gets handled after it's been pending for at least 16 cycles. So combined this means the command engine gets stalled for 16 cycles on every VRAM request it makes. (Note that especially this point is just speculation).

    Taking these two points into account, the above table can be rewritten as:

    CommandPer pixelPer line
    HMMV(4×8+16) W 7×8
    YMMM(3×8+16) R (1×8+16) W 0×8
    HMMM(6×8+16) R (1×8+16) W 8×8
    LMMV(7×8+16) R (1×8+16) W 8×8
    LMMM(6×8+16) R (2×8+16) R (1×8+16) W8×8
    LINE(9×8+16) R (1×8+16) W 4×8

    When you look at the data in this way, the numbers already look more reasonable.

    Next steps

    All the information above should already be enough to significantly improve the accuracy of MSX emulators. The following months I plan to work on improving openMSX.

    • First I'd like to improve the CPU-VRAM access stuff, so that e.g. too fast CPU accesses actually result in dropped requests.
    • Next step is the timing of the VDP commands. This depends on the previous step because e.g. CPU access slows down command execution.
    • Still a later step could be to more accurately in time fetch the data required for display rendering (bitmap, sprites). This is lower priority because:
      • These effects are limited to the visual output. Errors can't influence the 'state' of the MSX machine. So it's impossible to write a MSX program that checks (= makes a decision based on) the rendering accuracy. (OTOH it is possible to check for dropped CPU requests or the speed of the commands).
      • I don't know of any existing MSX software where this will make a noticeable difference. Maybe an idea for a new test is to vary the y-coordinates of the sprite(s) within one display line. Thus causing the sprite engine to use two different values in the two phases of sprite rendering.
      • Hmm … or maybe there is an existing program: the verti demo. On current emulators the vertical bars are all equally wide. But on a real MSX there are wider and smaller bars, but all are multiples of 8 pixels.

    I'm afraid this will all still take quite a bit of work.

    Anyway, I hope the information in this document is useful. For (other) MSX emulator developers or for MSX developers in general.


    2013/03/30, Wouter Vermaelen

    openMSX-RELEASE_0_12_0/doc/internal/vdp-vram-timing/vdp-timing.png000066400000000000000000017167301257557151200246440ustar00rootroot00000000000000PNG  IHDRlabKGDIDATxnAQ2ڳoڰ,)7EVJ˓s~/Ono^>G}{߿ڽ@WWWWWWWWWWWWWWWWWWWWWݨ o_b7b9oMj\ޗ]_~PWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWw+j{^\S\1D=~!aa k5AZǘS;O|Х K@&.M]4(*;ցco?K2]1v`:P֡rWtiХ K@&.M]4(*;9aisপ"uCOХ K@&.M]4tixATn*s~0OU K Fs~1xjadiХ K@&.M]4ti8\ư*#MExPF੊2M1v`:P֡rWtiХ K@&.M]w|UU8 E { 4"\2̀?|qFչGD)RB BMBw##)$4>'Z{]k @Jل~?Uaaa >PYR6!@B8OUBBRRR8pCg;lVM lB)eH)@JلT),<>?!cE3(ѣ_:}TDhVXӋaWظqBCp1GkNJڷKW/J/gȸBp^cƼٿ_P{1O>o B4hOLLLJJJ̞={XX0YSed}IIIɛٳg wYXӐMg( I 31i; TFZ̙7g$%hyg2դEFyL:u0$WBR p^-Z4ʛ7gԩ𰰰pqz5j"gμ?'O,B~,J~+ -III_BKrq4it-s.[ٴ]b\t9pV,t󮻚 ! t-\ttA3dل -{ ,VۻwOf",.%..>t3W\b,>>.tS /ĉWB\-7~8p\];w… (VTɒ7_-c9uʘug`\/T`:u{ *f͚/ccw \EoBŋ  #Hޛn Άݳ#^{mpm۞w٪U۶o?wĉ dޛ[Xњ5/YL&yk׮߿@p3\e *QXR%ϫq)^Qg\0/tm oWb`D|e̺u]@B ֫W'Xc+z ۵kukvw (P/ C }͛馼e˖]N\.)߷jg۷o Nqĉܹ.|Kʕ^&&&Yq={=r^{۶oHVXu`\s5\\k]^v\l[<|*^{y%&&~k !>ɓ.w܅ *VXR%o$':pO>ۼis3)Meʕ]vp+?cǎ= o`IIIA[Xj5̙g,_&}?vdMqqO> lkɕ+ץ:Vlm)N8;w…o)S\UgϞuVVXuˬ\ ի׺|l׮֭ٱc{p3_@e{U}pI޴cblذo>|$x.P mo!Ҟ(8nݺ-!Pw*V,_f+pre̺uٳ7l0… իW'oޛ~ӷs5km߾#..>FW`r?Yg;vlӦ7~w -Vhrybr\˝;w0%J+U/+E0|2XA䃰3[JZm=r? ,]:uj_[wSC,.-tƬYǍ{പ+S授-[j3ݱdK|A/?eWb˖ͧx/9uJT=66ysܹAξ>dI8pN:koߨ֭/&b;v|mwM[aCRRRu|s&MڴlپmیRJ x׶U0YšCߝ>`\9jרQbTڨQc'N\zuq`<Խ{;دFEGO1#ysС=vMfrv}WEWn}uĈLٶ}{J/٢EG-UUvqaO)Xu~{aK/O׾]}V,_lɨq=prvS&L"ǏϞ;w>(*CQCΝ;I|9rH=?qaul޼WGmHZ,٢_GK*Z0cƬ6h<^ȭO>L{;v׿#DJ۷d)̽KYC}w)kTJbLy@ѥ;_`L&yƬѣǽ2jݺeV3.S/%uTTԩ!C{L|C[$y۷߿oŊ?5[y)S&\ !O:]Pguj}.(~EV/\Xq96m۷ժ/ F6qիWUoٲu;l#WT"6ܹK""8zݺ5C8}qqiUF+zsK/̛7'111e'۶CZmyYp {K/4oμT~}{X9:*z?tЮ=ӧg͘5n>zhZʔ+qGD-j+WjV*Q)v{]27hu 0d칩>=]d0j\)8~xЇs|4ukHZܹ?;ߟ#GvG%+oXzXl-_lI|'?^ܱsk3mú IIIֹ9M5i٦em,5u۶Zx-"[<Jx?H.OUJ%bcܹK""8zݺ5C8}qqiUF+'oFGG͘15ys]`~{°aC/__ޮ]}/_lɸq~[rv&L2&?p`9gġ_ZM >̓ҥc: ︣߅ ܹO8qή뮻M{VZ?jk']zUsfeݻ?t睍 5w_zy$&&d۶HغuNRPx-"}_~İp(Qi%KqGk֬{š;sZծ]rɛQQSH>|hϞ]3~5aC[dyʏ۷o׿ߊ˟-5j\n'Wh.rʔ 6`' ]Vm*<?3رK: ׿}u歯:l۶= %KlѯߣJ«ǏϞ=w}=}tg̝;wp.]9r\@(#Ϙ1kq?ѣiU +WLp֭[j6lZ* 7!!gCzxU|^ʿ8v[o٠A'j%%%++8ĬL뫰A7lyW۶Ͷn| Lezǎ"oHy'Nn>/xmpK* 皘5<[ȑ# .W+S-y^Xf->:p-_lI̖-[מ]/S@^R}~,S7 &e},2KK=z,_s۷mOZka*w@B|7F'N з^cǎ;B:bϔ-[h;-[8:0nu=K8Ɨ_\vaÆ8?\K.vQQw/]j &4ecǎ֛ z~VH_0_JŊ%11+z{Ѝ,-[նm[7(+;wh׮9R]EA'Ǐ#8%YfݻG͚}WV`D W %w@F{`{-wѸS/Kc:!U;pӗ.k+T=tzhvT#eݻG4hmӪ ^B`jٲe[n*TmĬIg99rdA2eM<5}[juݺMm=Mo5j\otԩSFO:|Hjq+WcSX7 @ʶ{}šA?qV{T̛W)"bQNx}ǬY?l#G$RJV>#}f|~լ_̙Vf /|#ƌ`ĉ$6h|W_]-o 3ҙj}wtɋ?[K-5n\\| ;?i}' ).\\#Fd&i9 \>|d*dɲQe2elz*YԭtgzufdDDÌtLڷtXڻ7c+zLRxoyDឡSޞrf-)ZrDcޜy"F 51k/a/⅋o|c7#O87h=8rpT/SDl2oޜkΜ9 ;bﺫԩ{`u!SOo1#2p7o૯vg wS':?Գ_{fͩ[iF9:5}F=hPz7~gݻyv |\,{课e{-$_#X 5]]tcR˖.]jeRpΜ93rV͛+Z4y387Tδi%u`?b̘s%˖-{֏Y.}?iRݳ'սs.^حe\.r^2;11{'JJJz{ _휬J0m:v|wz~/o ^{m] )?fgU6|m]X`ŋ_'Oʽ׮۴iWXzuŧս!<\2enM{]: o+YpB^YgLJ36|FҲP%_}jIkmz27ٌ\Bw#G"28KL2ͷVp3-֮]߮]n,[tU˗/{-I ΔUVGFޛ귢o!w sB4m{_y̚[p%͜9M_}7[`Ks}jMݱ#6n;uj20OʽkWmZ$~>wpW;}EhUrWx eʔ2y\Z}ccƌ80[lٳgO~Lʾ}Ihgήrq&111SAωjr/yצlhGQ'O;ֱcMuo޼+YJ:V˱2צͯl"eܹ.]Tҹs-)? ̙Ӄ}7)wy|BAYg]iӺ;vf Kr_xߴWZyo\\|]7^\2en<Ӧ߻wĈ1Z2GfgWmT? *XdsBwkm]-A'J.\(՛3-֮]߮]tpCrM7Xlի/_[ *v߽{3f KO8ѡC{maɒlp9rj5.BdM;~xhIzѣ]w*X0<=yƍ+cbޟ={֜9qi~_?3%G#dKoڥ_rK-2pIC ;kکSV"jA; ŋC[3m3EFWs{iӦY&M.o}IpƷ}wݙЁ\s5@FFHXXX@>^$Up> ~vF ٸa u"" (#{woڼyS>†+'yΤ3W^OHHvԩ.={,]z[ɒ1rѢsy:|ETԖmےKy̓'OZXѱK'N-RO^͛4VJ0޳_N1c;wJcǃ=z̟93ry`A7-Сܹsӧ}U,_>0f͚` y啳FŜ**EnQVUzkrӧ?ߴnÆ&}&Y6.KWt"iYH>7oRZHif 4xΐe1~*I˩S'n.]u˖SA;L35pg_ygE&O8C=2>|ԩ! ۵S'`9rd߹sM'NAT:"VUaM~8ذ04iΌջwLrz?ܣiӻ JIްqʘߟ=k֜Tuԙr&@~}:ubŐy̚' Fsq ɟwԦM?߳wA ?gZ5] W̊KtxNHziҤy*Ւ_Θ1mAvLcGlΜ9?ecĩU޶mKfФn;zĨW>J/?&W\{?ʖFbbs_{_!>_=SO *SX~=nժ?pqIƿK grN:ճgKcJ-S9rQÆ##ED)P`9v޹yɓ'Gs_ {:wVf˗ƈ7x=v۷7,Kv-_l -9 wֹfɅ˗,9o_y}}Kz;~xhIz<㮦w,T0ɓ7lY3sf͉ԉ} >>:w}yeʕ9[rs#:By|RJQkEԪZEnM.M??b؈ 66y7jҨu:fl~_?3%zwdW:/[\?z%&&{:6M5ɛ/or7_ɲO_[޵;+ bXK.'N-,RH>4oRZcg c1mAwܕ\sG9f`3f|x GFS@s޽sM'Oȳ?~[J-<>S ׬4i+ 9vX9zk{SG6l\sMN]##֬q‚/\8?dǡ}>xE)5sEɻFOK/O0sջwNlgK,k>V}4??{T>~=z<ܪUxIhz>hݺExsw6[hiGwue&n;v9#h"}j޼IjU?2vڴ ޹sWr>cRJjԨZȭOM~XnCh&Mygֻw`4hP{4mzWBCOj*mMY~W#_yDbۋ/>S%s̙ꫯ:v:k2Z*4<}ysР=w 5lXSm_В&}{$s.U՗^jܰy'm{n9 pgS- -{ gN?q%7*:zÇٵ%YB ֏VڮݻKeO=6r vqaϽBhI'֪شysNmؐrW)&du8v„^]nz7eKNrp ׿='MZyl&-yO:OҲt[lK\py_R|1WKW,2%JTھ=~RR 3c'^e3~UO@]Jf_|oߌe'=d+J.L;p=y'+Q"w:gk9eʄ Xw`W:O0g@ƅ'$o5OSttT.:tx׮=өQi]Y{",c;vdhO>ӿ[ժmے9{ºu<@cƼuePJ%bc,Xرo)S.V3顇?ga\he 9m윯4GS*lб;-[8!Mi T/jÆޛzk kIEuqϞoSl7a”8/>,W߃~ޛ<Zrk[Ǿ=vک_Ѣ.vO]"'L։gL9tЮ=Zsu1Ͽ|};'N=W'URJcSWV%uҥRm5i=;]_upqȰ!5ԳOXLtf%ٲeǞx,gΜ6 c};rlha0`-g^c<7IOBD"q :D3dVRVZmE-mBh]-hBҐCED-ILh$=>srU.os͚R:|.:~?oXx"UQˋnsZNrO϶k ɘ1{uqW3fLQ_bbIn][vj_~7nJFƅ5z{w`AA}6iVgキL,y>8p׮'LSXTtǧgnM%^Vv:^|s-߈~_O75kYao1COݕUoV^hRua]Ϝ*=|oyެvl=ڱիVfj>:_ɩY忺t/-ujFW_v?6UTnz8p;y_cĨ z6_RyW18v7ߔ:tʔ3OOX4ws=bX)qHN8 idMJu{vw¢wޙ&?~U]P,|d~Ν|A?Âߖ5 ;wm<{&9tOQ4ey~?:x> : m#ث㊋3kf<+WPg.],~ELEםX.rb_T*5rwU}ʸqDFƈy~OzKGs(u[fxªgu O:uLI\,XP4ݳ燪N71)}Y` ESŞգ&}kܤFþpıo{SJCkk)S˽bvmֳmUFm6KKKuǏ9_ջWFv'_~fffoFdLxS'NhmbIdD8vPui3}]XXP(dgκ3h~o 4Yxܛ"fw̢YYYUn]鳧_ByO& 4DU>K{몚xzvnV_c6e=fEWssuӧ bRyϨ>vǁ ͛U^~׺{UM:t^6b)mU|;be[yxFvuu3P۶m.++Sۚ5sͭuz讶l NEE?>nu-t9ro% G/^<_U_zE,9ɓCt_aZ}_~AO{C,YrV]v_|܁eggmݺI|@8bjwXYYUdmDԱ;o%r%bɦ[MBryLܣ[7)m5c0s ؉@_֮LZv;rh&-7mee2 nf?kf @޹InY8,O ܣyq?C sf#Fh>[Vrt魷ޑfɒkixFߧO?)5U+P4K6VUyÆİY3PU={W,p!#>I͝ӳZ[ܭ[jwaa :n릭bحG!4 mQ-t&:g@O)5Y,Y ıgOuXrdgjRTXg䵙}.lC m666z&n6LvwޒV5%,\l yyy֭ĒɓCu!m``Q~ ˖}Xm5ggC%Υ&=O~Rj.YBT,XU f͜C)Wqw7]…#;ZLuT'MJðY>V;;>t~Z`n{iV$$drݤ?pܸQ/sttCŒsF_kF/:u|睷Dkwbx -76|򡭭m^y)4}rkkjN4^ ϞMcנŜ%`~af%m͎{t)?>,L:B!̱KvSk k?V85k}\媨KΛgaa!cGtuqQ.]7 YTʹ~],  |ƴi6^۫W{LZ.;UHZ^re$gOɶi%d /_)(2I,EZ.դrr4r3j?Oﯙ?O˨ @K[(+1rXWux҅b3g!BtWŒاT*>X2u4/aΜ?j}vϞ]gdnn>}l :cfffRڪΕ ᔵ,0u<,x}yϞnڊaljk;O:I,zG6ګ+vVG} K6FU{x:tk6|pNѣG{4Su .~7eK-zJaؑ..҅KyT#Msz_CﯿQIB_Y3CkvxRz󀀾Z=/3ġZ[[5NƏ$=]SSֲԝ9uFFFIo>~x7p?74h68p{w+yywLbUگ_yeoܸ^IbɤI!үҬС5;|R<`zԷoϷ2\IIgİys~Ho>t ±611HNvڊLMM֨5=26jRjvAdȐ%^K KKKbv  stR:cԷJN- /LYDg+k-ES 0@ U~WT3+o 7]ػOjpgԳKK3 NHwE\!E? aiiJ݂^@cfo"Խ5}jyYU+1\Niϻϟw]<@NY3~OYefF8ytZaZڹ\}%ѣXϧ+WWY0`fdI(l=_(9Tmk^&q8e- :{{33.TtqT8Joޫ!78Gak 03q.ݺى%gN]+--}X-I3fc'K6F&Lzj:TX0Obhkk40H0`ҝ<~6nu+1L;&g=ѽ{O)1ҥ;[XXu^x.bT3M knn.~lllĒ>]wQW(w5aFFZII!Nfr1訐ŵ}{/O<^k[aZ9#y@TVTT(w ={v7hNnݺ}ե_mл r^m=5tP8DJ ϝKRZZݽ[PU󠠾.yii&ڦzHigggkk+%}KKK]ӢzAQ h22js$4INYS.r{5ܢe $1!1/ 8(xYYzVgβ[XXt$\ȸ` s}ӺM^7$pw+E+EggNaQaQ CU%K)hj_W?+++_pUN]n]6<?-r{ٿ6hѢbT3MD^^bIZڹ22j~~]İ8驵?;;G }߹6hٲEeݻ4JMMCoot,w\_n PT:ٹhUa6:!!/ ((x͚YڦE+7iX 7wq4~d !>hZ .lղe`}z۩U |}S_;6<4rM_ʼn%NB!>~9ׯED,%O䩜W[P ;t;5o//}׻￿w>d+|d9ꘝ;UÂBSyPsF..r{A?Y&&%imDEEE>_sǎZ'- YZSڵgժ&&?$GST$'$~{naV-{k __d#?\sWsϛI`:A;w|hR\XXP]5hzV> h 4w:ʽJ/Syg{ía<`Bkܳڵog|GӧL*OLHT,|naV-{էWNj|m[mҦ]ovgמgRbֽ%***j||S64u2pk-;w|Ӿ|a<`.En5h"jT!Ø;v|[˗º9-vYꟉIZ쳏LUyZ ݧO|};j Q/2j[Ó]{fYӫ'^v/S"7oũ~Tvqv2iҋKoΟ7nZZtlu2zٿzY䗯|?kPzS8K\Kutp{Mգ-[>_Z,Jl~m1T=$k-,,4 I3L:ڵ_?_Nxu@ݺeIgsek|kr^?rĀW_x굿Lr8sO'7ÃCB&b5d?:hsß^ [bHM7{{Z6yh:88ʽJ&ҿF֤ F{8k05"1wC H4Չ׮^_VQQQooތS<{7-V[rD=/ .M/[P8zq-,,,| A/*Ҹk988fVVVlbM **FTn ~RZǬ ۷İo.o,@ aaQ-e9:e˺uv=\#gYY}ՉW^_Z{366N!!^|Ѯ֒ վ];m2jW׮ѱOqT2yrƍaҙ3镫mצN5R1T( iWA V;;;5~<|ŒvmݱFb%%%zUOut!EZ{YY?HZ>+#+//bɒj߾L򝼭[ 5:ZW&4\s놕? Ap^1.Iz'MhB V޽{ƹ/{ پO?Kanü R6u*/dлǏY2O2/~Rr`ҥ~O>|WQM><3۷o I KMz;kʆ? ᔗ?Z.I!c=I XTcB 1yL3lپep`KKKݕ2fҸ|xQ^&vKco\GA/]}NK/yJg괩5W'%Q$:433 f6{'^W㗢qsqttbݻ2VE:bXQQQi/Z~At1G`ﲔbe5җ^{ 7E-]xϞOKaaÇyJgڴr/7|xgo S_w?HX ++hecgk++-[<tQ-fBR&5/3Ē cƬ\Gb;;;ͤegYLteV23S39aʕ۴1TI&yՏRYOǎo*矫$84t脄72R(4ϋqz0([[;1\lY(ZkS QX!V@ ,&NySM䲴0yXY|c G'?󹲲?t衣&lԸUr֎ G˕_fef%c&YrGjj]ψw|Õ]ſU ֳQ#-+*,2ܹ2rղe+_<'=;GG*p^˨[VUIӦM+*j"EKXLz[[;1\l9P8jR+PTteT+-^<ߘr嗙YbɄ cV\ަ7i5T?JeO?;z4!!?+++{j ::!`ƍL}=Y¾I 죏-q޽ ~g)νlYv{wuXXTkux$fN0Zgg1{3{6ԂB=TnT'MÇGO#nXz`Pܮ0ڵgZN͚L=\rW Ē]_ãf,5X]'ȯ43᡻v}-哲*@7ya}QRт]IN9{Qrr̟_?| A99i|ڵlDB+*h=MO-,,,?0& jNk@qT8aQajp3kbd>죣IGo{` Y湔s[&|#hҴn^^^ bIhx׻h!i# j]]<{vQ%Kv 3;hݻ!&^㯖^˾3Afw̼,+W.mRTQQVaaai7Q(b5;Ɲ]ꅓS31|9Sn/_a՜5޵fg_3і$<죤7ػp:xVrj]{ƙLQƍrK))ziP 7oϯ93fƨ(w|X8;9ҨF\z3{T#/^Cƅ Z%NN f>yX?),$yafVֳtbx6-MngSSpT;~էJo~m^wI,DWӷo HUd o-٤IC\ҥRLyy (lrkϟM`PGdMtt&/CWWu]t4… o7]'[s_pIn_7nxAW.Or/ ժy|)]eeȽzFFZ~FOs:wtէқ߾uƗ1gFmR+աȱ# 5~.ݠH.YY<t/iigzeҥ r\x^ ]]*qvvD 2J nn :k>++4==2yxaff1ӷoQV}*[5&M:h.?~襗jUX~s^^I6صg9Kg-W^;!7K9Ƈ0I鐞~NVV_Wͫ(֗7v+5s0 c_ Υ!=5gѫOq,zG,,--=r舔Rݾno%{v,O'aFIl[RRr>|/=~xG:,Ç7nH[U>[С#&`T/(gv9K>ߪXr.-VvvvS%TMMO?k6>}İl/'OAkY[[jDEGsW[7lq#wo1|gBzP[l\;wZݺ}[ =۶61)I.0R1&-mllİe2jnif =d  \|kfϥerByyύ WށbF8κO= >^CZ{V֫FXyqY;xpu5Ga{xA /]pu33n` @z n\!y/99F2˗zXv.MJäĤ|¢k[Űg[mUS(++즄NK6F|*Y,mk>}P5rzx_ށy@ uoJosgtyyF ks'8~<Ç=zHkUwYZ^wQ/.V~(xkggW>ztqvK\|СB1"8XkTO&R/)Y~qUaQJo!2&B@@OͤƘo"%%X-@n0lI&yf&9/\/iSňZ$5~d͚^ gggd/pM؋%2b?^׮bؾWfNu]룰үrfnBQ$ V߿ =SZ 0'n7򎍓|]3KK~%yR޹[b5u޵GF~EEb(K"7Ds0f[Lii閈-ba`@Db%m2;=z:8%k\˓B48xXv헥Rږ_~1ϮI{1|^Q\}ڵ] ۷j̩r.][[[%үrfnBQ$ V߿:ֳg֑FT(6U,|Z/Yf1޾b@@Og4),,CP6l4,-- 'j!4r/ EzsUh0ىfXo/0/??zXfљL,CG<[okk;~hsoޔ/(//KB&M2utS7u~Zn-,,BLKThB3t03+kKi׏>k rrƽ=efБG.i(ׯ`e*-f {<٨Q5d[p̺f̟gfX!-׏n`hS%G6}\#v՟KykXjGKoM/D룰&hvvvcLK""z𡔶o޼N4On=aXUy߼~qM$6())R3HOKO0ZĆ/]޷w_-?P+ f4772Y,9G1;SB%$Mynb+4裿09::k$W.իWbɔ)Oikk;zxdo̕x/\,4)@ӷ3fXÇxү+vȡ<uobbG7nԴe]n)bɡCGM0qkr$6t޽ =Fުj!4r İU˖ y[XT5?zqNٳĒoQPX f'ajzMĒ]LnG/宯}s̱Kz3tð!C|;uK[uZFPCʪm3jfJI˩j\I<099Ҽf?E2#C3ܪ/f-װaC|}5xk#>܈}zkΜZYnjT ))5ӧO񍔚=z---CBª.;QEP_byohQdme?y?ӫoZد6nK?s5kN5&!E5N}ࡇr%-={P͠A'W]fݺ|e5jrxXum}jjgC>㏟O9zfbŊedee]w㍃nuwFw_?hCqEpi%n}33`e L||[n9~۷?ATL|r֑M7=9wol|/yJ?էW% aA׊Gr _OsP#Ơ[E֤mI֩[=پ}رg5j/1cJ(Y3l/.m׹ݻtѵ 7|p…5| ]9lX #kVZռmۧǏ߷ov_l;YÇoIK;q.r֢yG8p`̸qUky^~yԩ{WW[ UXqСEpi5mܸF51k֭ KS*FGG{E9QÓ+W5os\l;j֬÷lI+k.~嗉?|afCS32ٚ6m\ߎx羯5.䲗^sr޼7z17"<{eI>fVX/YU͇=*>f\ժ{6aR˓&M5=VSO_RËdV/aF6lx؟ݮZm?,DzeKch55-mKA01]2_~yʼnO<~gOf%܎pӽ{^m#nGDV{_s1OQؑ#ymQإKu.5uCde.ݻvQq=oa}\㾳qptƎ=`@C)ɱ88i#o}eg={}ۍsyo^ve-sguYȚg|hWvpȫċ}>03sO]e95YrUm~z|n^,Y6t5kֿ[\|6m\FȚvēO>}׬Yw%ҔC\h޼7 ?|2{.Ys5[:DZE܉r(VZaZhмI T(_>6&Xb֯_lٔiR?%O_??֭Ϯ[(GLүL梋2ɮ\ꫛ6nܽK.Fj+Tf;vY.Z|7'ӂ9s8bo3G+}{YN5j˔I۶m￘<}#QҸG=ۏyG:,&]_DII:tHۺ5rΝoߣkZ>..X陾kWr˖/[&O;rk+r޽wv b;>ʼzB;nm!5Y3O8o6ZU9㌠r矿r+_}աԯ`rzRV";v]۷OlذAqeʔLO\|ŲeSR_Z#,]T^Wodlْ6xmٸM f"v0ymp ۦM*U@nVIIsjr+W7a -ZNh޼I ~I\n%˦LሾG x{O?]|x|ɓvڴ> q@!މ'%MС֭Н;wtS}׮=7h0.|e233vJZX|e秤/*U+{=߲kҶvlf1n# G‚iڴyv(/a„gmۮjjYYY7oz㍹&<{jvipàu2-NNM77=#5o} NݷwvmVzFޛ61 Lx5;x#A7<6ت~ eX 4oҠa+V,3#s˖,6eGr/o閯*q&?I˟TZ/ܼqZ֢ yjL%kP;n2pHvop۪M3w[?__y夗9PAd9~7{1@uϮ[h tғ^tQdɮL]zu7mܥ{ .Zj*V;v[.XHW7|s'^AD'uhakʝ;v]''|\0ڕ+hb˖C 2rk|:}zrd޽{'RD^gDV+VLTW^g]g㄄6g/(atӯy;uY_uUK\W_}iYfL#:{_*U:#獟sι#F;Y9z__y޽v5˔ oۖ+^ #xqp86mCo0`m۶ZZVV͛xc ϼ޻vi70F~_o.4CV[eWܹ㦛v푘ؾAqqK.kWzrŊ˗/??%5u˽5#+;yrri^ϊUVҥzo{&f˖om$,X0;_DfV:mܱͮcg7ѵ}Ć /WL錌]AW,[<%eP(t 8$&0'зMVUۺ[+'%W_]` AMwrw2,!E ͛7 PBؘ`qXn%˦LሾGZE܉r(Ⲳ.Yyrtߧf||ʌuېzXhM#f͚8[322}Μ9闛<?x#+7lX'aʹo2ɓ/_+W7oޜʥKzw߃p[4ﭜ7g^d^CnWYYYK. r/rI.?޽{4%x%***&!gڧ_'5ʅ zž0x5/t왳;o!/Ȇ&H+uO.'^Y|)3uꖺ!5~Mc<<p\+VnmSO=żҴ-[^*DEj^w}߬Y36mxЭ>;PqΜY{]w }6瞻G}˔ O<| u8_yDV.](x yW||3Ru딚!>X cBJJz~ܴ)7mK[\juQʺᄏg̘qBo͚))3:uaCjd}0?=n!0?NhEXĝ%J"hIH(VXzO;mv\lȚW]u駟(Akؠ{o=rذp{%n}Tot|=O_^nGGGŋ6nˍJ+})&48..W𔘛=v]͛6uP8*UO2eByOZv8isw=WY$'O3Ψ$2G+_>nѢ޽suٲ77[rĈ۴{&Ow/YK/z;3gNկ7k$&իux=˃0V=3RW/+M }v}vzs9~.^~M 9J:B 瓆bJ{ ;v~I?rK;^̙~͚5)w*zj;^x=w})S&TV;8RQQ:g-^<[K yx*Uߧj켾wqҤ{xA|ERz*7{lٛ͛lĈ۴I&^-[$pI!_DN3? fedPع .^h}l!%JhؠAmٳi?fwAȚUKS1l*_~]l֤Ip8}꫅Kdo&O{ߝ1q_}5ݞ{nPT4kֹcǮ?YzrwG\7^ѽr@ v PоӁ^9sK/͙7ogzWޥu}Ԋ/wپ=XN*+,}׮|9C|iqfw68֮.ĜXOԭ۬9sOӯOxav >P8%fڵ볋^.] ̟<9yΜy~I˺[:.С}llc>cQMs۶ϷlI 8nŶnݲ[Y,e&eʕF;kM@Ysm$"?V@^tABBN Ƃ #_ۿ'ܰAvmgӦTMzժOD^*U ,ީ۬YקV) 7_!NP8#ß'sk]??%9yys֬;~k=V\pQbbbs'sz][zcoKKےy0㖕u >66eɉgd.v~E?ں5-}חPFMW߾ח-[w}3_ !HOyԫX~stާuKEP4i1Qڲes`]Yd}TTTf-z_/S4|QJʿԪ:uiQXK89(mټɱO&ON^7nѬG22*?蓔djuiUg{vN:Ta$;w\Ƃ /]ؾ6%JhаAvm{^ӳqƹ9Iݐ]pnwj^'JߕU.\0W\u S09׮.].j媱Ν5wG5;vw@FMeW]9%D,Y9؝1yW_~5X {=AҥYuYqǼ"O[lp׮[V }11P8##Kҥb&yիoOvNz+W)wiΜY1cӧ_'N;ؽ*}ݻg„g ?|[Zږ̬̌q-[Η0ǭ[Ӟ|r}:K(jԨWe80s/4a޼9;iҥ{>*i˖A(']Yd}TTTf-z_/S&|O>JIWvZ_N?O vDf͚O׮{y.;~XN7tP5++0wh'f͚-FVZl.֫WJ4N͚5'ScC'ߐ^qE3Ψ}ٳg'D^e /޶-[2^de87qq[,#29s}{[vV]t:)ՒmUG5kMљgܹ}4i]kXb0섄9lǎo`WhG)QDÆ ڵk{5=6m\z:UۋO~ҬLҹ[ۗ]lѢyҥNBo_SNk֮M۶mϞ=Aeʔ-[j*z:.݁*Ԭ3==fܹ -Z#qӦ۶>gS85B k5k֦_2mҲjZORddM[n۹ dkS Bq$6|f>gZ g`B`1d,\87OE |rg]k׮ٶ-PLpٲeT_OeO?ٵ+=NLLl:u9Z ֭[6šV\Nz|vgw.^x\\ڵ,Y8!á k?[7.Wn킾wvڽ1ucbŊ-W6&&5sj!58w -QشqӶwY*S|>Ŗ[h|7׮߰~C_tpp|5BPџxϩknK۶gϞ22e˖RJ0(ρGYp|8pf ;k]آEݻw}'vgee֩Slr`6[nm}*Ur*u߽k?۽{w׮]dɒklۖ7/>xs*Uk<@! }*Tsgzv…sZLvڽfڴm^2K|ժUjՊ/"/7nںu[pX+V\ڵϊ)WqHMݸ{wFffĜyfS*ڜ:G- ztj);s8B^Lg/Zݺ;M{'j 3],YdMŋ/ @>}F׮O!x[p(N1:'uCjvq¹-ZK/[n'{k6qCጌ,q8f̘޳gbɒ%7mJ/^9 YYB}OѵkȫM }M9EQV@ )9ygzzv1.6.pzbD|5y{s VrRrbl\eW^f@c"/.,?ҨQE۷OtѢu,SdI qfdM+Nqcƌ7y/s ЁFyTdM~}J,i@1nܘ7ߜYsG3fܼyoFup(!xz?8}M7\aҤ[Zʕ]&L6myɟ0[ϟWrOÏn`@ټySRҋeI^>Ț*U^vٕ" iӦ/^t#kVr啗$GŷY窫jNj p w͛z-ii9t͚_ᄏx" $=}5_Qmْc5޽ʟ/@ZaUM( vM{lTZږZY_}Udw_xqkׯ|ԨǶlIˡ姟/}WY,S(++K;CkVFP>䳵ko۷!5u…ӧG3:?Zf|X`lyfMh֬QÆ+T(W/رsGƂK-;pݻwPהS4Om^xOFwY3>^neK;wS`wG._zi O/<¨?ʡ3fNeΝ/} WYneK/p%u6n߱c_۷\ٲq{n ?g+geɷ|pwyw͚u6n߾coŖ/׸'^p+˟PBpF?p8sPsj!58w -NFiԡCPlfڶ}]&͚8|p(%?Xdᄈnݚ ;vl⋽+[\ll\\\sm/8p(qJ>\p;[fͺ Ro߱wUVreb˗k/ʕϰHr) r C~b:#C(çڇW2| 32{C_2) r 8ZѢ%p(!?;vl"ACQT2v@@ `0E>u'VRk|T-VR{A9Gc`0E"LHS$) BVs砜r0E>u{:9(\;~lLHS$) `0E"\hU:[x>|ջExVs砜r0E"LHS$) `U%ZUKU"70E^jo~-_nE"LHS$) `0E>u'VRk|T-VR{A9Gc`0E"LHS$c.뽁/S"!B&&1D7P5pGL3$8M4av&84y Ԍ!f ?@=ܻߛu5NGoos\}~Z=U46רԯT6>=Wtv:;ߩԯT6>=詿@OzS-k_ Z}PF}zxڿ9?@u~5l(Mw@OzS-k_ Z=}P zo} k G 9^xᅷo oGx|p5HG4|c^xa oQu{|@~ݿS+_>olx??}#q7< =>cIZo?uدwjuׯ{O7Z}?믿~w܌3vޭ'?} zϝ7/Oю;[zi :KM{g,D,V0q3{;:}OՍrN:iѕf_O]7u[g!C1u-_Ï6lƵS7ue}s=UM=?3=Zq͜5޷h[*>X|(>XeXmj:>l `Q]*OSE?_vvt,+_Ϸ̘Qy82yn `}$_OѴlي &|ƻV>x6cƵw7rY 8iĈsCs=_vY>+[WzڠA\rɴ~&nݢuu]Y#/Ճ3O-=_9z<Ti|Ç|,y0OS?|σGuU+}>oi1dȈ-{}`J:1bܹ2W_PrgtҀѣG̛wXݯvܵs66hvyŊe'Nhjxp[~yΎn=2hڴKyusŃ ?=t1~bo뺲^_pgtȐ1u-%Fou֕y֬[-O*+_}>9sfCsnuuׇ>aθU>Wy=iݝ;n2iL{U}}n:鐏!ϟǞ[yUQ+[~r%뺲^g1dDe˻sX`hXוXy ٕJ֩[6o~Wyk +_e+&N4޿[}D]*b>c탡u嫸}.񹝴|ϩU}*_s|UTWY3gzmU|U|>굟Uub8u*+_s|U<}NWq\t:_U+_Vf}09Ws`u<}[U>k+_ssu嫈}.q_'+_s9p|UTW|{u嫸}N=^|ϩCWqz=|ϱ|._s}0|ϩKԕ"9|{s *VSϷ֕9u*O<}N=%WqQu<}N̷Yu嫸}N=^|UNмD}Ĝ|ۃWyq z9>TW u嫈}.Z#_sU>W s}>oU>go+_Es|ۃWyk |ϱкU>o/QWpX}N=ZW>9u*+_su嫸QrU>o+_s|e=o+_suB?c>X}N=ZWX`|ϱкU>o/QWuL_?U>o+_s|`=sкU>o/QWΙX}N=ZWX`#_s}0|ϩKԕ"9|{}Vu<}NS*OS竢U>o/QW{%o/7G'_sкU>WoкU>o/QWQlsYXue}5ϡ{bh]y7֬{V>ǚuXW֊uL_?U>ǚuUW{(Wqk=b]*bcͺǪ+_sYXu<}KϱUmBk=b]*bcͺǪ+_sYXu<}NS*OS竢U>ǚuXW{EuX}5|ϩY<k=V]*ncͺG+_s?s3f1d7ͷʋÆw3:;w[~U{݅ }G pDk-[6W~B+[]Oz駟X$ o>?tX7躲\ƪ+_suE|\W˖0aM͇?ϛ=2dȈS[7}ر㼽 ̙*m|Cۯ?a3f\H碱x-g5~F=y{>wtlmz :K=}s~?}1 =rt]Yr}z?}1ǜxsέ~~x>[X<}NՕ,>O=cӟn7Eu :dΜ//Xo}OnY7~|׎ik[=u1ue}}euܾ}㏯2ŞrNg~ b!C9ݶ]tuO>ϙ3^}M<|s󰫮nX}~Θ2su6l_K/nxp/WnKw 뺲1>ZW| ;M\|UTW|Tկ~}sfꫛ6mz 6}zwmܱ{V^t>g?{4}.z=u9kOlzi|cWLr}pww~Z7ue}/EO]7ue}>O=cZo<֍u]Y}V>`{/o\*]Ϲ׏|ϱкU>:s|ۂ޿[}0|E۱кU>~~+_s|UtWy:_Օ+_GI>7-O*+_sQ?S(_s9p|UTWWy:_Օ9u*+_գ͸`s|EqWywYh<}^Wu嫸}Rוs|ϩGWqz=|ϩ˝cWz=|ϩU}*_s|U}N:ZWQu<}N̷Yu嫸}N=ZW*'t^">XnNCu<}>Wyk +_sкU>c|U"9|{h]*OS+y7|{ϗ9|{h]*nSϷOX}N=ZWX`<}֕9|{h]*nSϷÑb9|{h]*OS竢T|ICWqz9|ϩCWy:_oϳCWqz=|UNa}|ϩCWyk 9>ZWu嫸}.Z#_EsкU>WoкU>o+_srU>o+_s}09>ZWu嫸}N=^nb9|{h]*OS竢T|ICWqz|ϩCWy:_oϳCWqz=|UXZqk=V]Y+OCкfoYXue}5E:_U~䫈}2|ϩY<k=V]*ncͺǪ+_sYXu嫸}5|ϱUsib9VMhcͺǪ+_sYXu嫸}5|ϩU}*_s|UTW(Wqk=nNb9֬{U>WfoYXu嫸}5|UϡU9X*VSϷ֕9V =ߖ9V +_sкU>c|U"9|{h]*OS+y7|{h]*nSϷ֕:t9s,Xг?wm^={iӦ~ذaӧO2_smԽoܸ{Vn[譟3g^{'6=îja껋b5-]Om}챧o탷7yG䓛}v 7Ss^:ܾʥ~^Wַ\~9 ]xS7\}0VVsv>|ϡG뿯*n~2gΗxޟ2嗷&qk/^Dկtvscx77o:ҹh wرힶ󝷧s^}Mxg k~sк--3n|픃rו-mKKu]}|ʔ)2i\sMz衋.襗^WΝ4 /\1cnkk}}tɯ|Ҷj՜on_}qƍS[[W\xዛ7775e^]c}E<#X|˯r>?~9wՕo}WyΎREu嫈^xS}(>XnWLV͙ss{;gMM#/^x5ݟ=zES_zi۠A}6ΟhM Nm]s/nnnuOQ7ue}^5j̭tWX߉'z=u{]Yߢ>{ϟPiӜS%xL'_~SO_yg]σy7`ԩmwݸqCkԟtyMM͖tXoRIV? _ȚG>ot OX9wdʋWv_=УS/m;S3jMtuUe}'OU}~+/S?<߸ac֟\Mu)/Kѯ>/♧򺫮{8>XKו`s swl߱UUr|U}|j[uz5֕Ro.>X{]*EkL]WʳU}*_E_^:_ՕyY'5:&WEu嫸[K?S*u΁嫞UM~|w}:_Օ"9|U|UZ:кU>8>TWʳ#ޏ|b}{9>TWJSוo|EUso*WoU|n=|w}3̷֕b9|{P]*C|UNhc탡u嫸}> |{h]*VKԕR9|{h]*W soкU>o+_]%W)Du<:_oϳCWa=|UN=%W|@кU>W)ַgc탡uD}N4ZWIKԕ"9|{h]*WoП/_%WCWq7|{U>go+_Y`_Tc5֕ ^c]*V샵וR9XWʳU\k+_so|w}}{uoo|g}S+y7XW^c]z>Kԕ"M7ZWʳ*s}0|ωCW7|{U>o+_Y|{M=ZWJͷ֕ouX}ٱ3|{h]*FKU %e,QWoH4^|b}wru}N1^|g}S7|{U>'o/QWIՕR)Kԕo|e=&o/QW=tD]*Tqgݣԕ"Օo=ub=s=V>Gu}}#κG+_EsYWy7u2g}κ_WJ̺_Wqgݣԕby߾}qg+_YX*ʭo|U^B_Ro|U|Qf+_Y{8^]*nκ_WqgݣԕRQf+_Yʬ{;^]*b{uzַ羖zD]*b7_^WʳqU2ߞz}{7_^WJDutD]*bSϷ֕o|e=o+_%WCWE>c9mmmsioo_f#J|Ȧ _y5?uE۶tR-*W]ϹsoرcUu娂ue}CXrו]`U>y~VUw߶Us^ pww56l:uʟs΅/yB̙OZidkEzы.K\3,>fpϣF֛>ĉ_\;'ĺoh^wݏ*O矿xՑ:hoo߹sرcqnذAg{7Gw©N<ɿ%O.[|ݝw/[6md'_={%} ~Nzuut[bEg4秞ZtO˱cƼ'۵'nܘnϮo=4ueͰ}Zߕ+/;?mYWzOj}J]*{/q~CWy7>ϗe*={n޲%>XTWJcΗƎ=8qû?׮}ou}aҥ{ExgllʟWVyλ^6mZ=uVokOcoN8ԉ'?=x?g<&r=G8߻Q"gn?XT}샏=7|cܸѧz'3Mt]]bg4SOV}}XK]*}r岏|;`W=WqXlǏeʕݻw}ӦWk?coN='9㥝/9x~5j}.z=uݮ|rWʝ;ݽl:|,?uĩoSv>K_n/o-K뒨nϮos{nټ%>xĺ+۳V|ưaOI%_գ>k+_]*"וhs +_EsgҺUMS*|]lLJya-:_+_sԕCρ䫞uXzWk(_%]Z棒֕RoaҺU>_WyUUM̷Y DAu}N=ZW Au<w=Wq/*bE)WIh=|ϩk+_Y|{04_-_ESϷ^W^Ztu}N:^|>J7ZWʳX`U>W}|ωۃWzUM̷Z A'U7|{u嫸}3TWJcͷ^WʳGo/QWtu<:_5oϵIKWtu嫸}N=^|(Ǧo+_Y |{M:^Suy_zO,QW֊XOsdt}'~>ZI7{Y+zͺ֕9{U(ݬ{h]*WfoY^Ub=|IgKԕ"9{h]*ͺk'|Tr|]r|g}3f}6>h=<^K]=ɫA.ZWy7>h=l^K]*^K]*yQr+_EsD`u"_s}UM]Au܃WCWTWʳX{ݸוqs +_EsQ뺊^yt\W˖hn>q؟ԊC~_/^y{Sݯ>_p;_{\t\膲碵qq岕+^Yݻ;{ٴ:E{֭?7xsq'pĉ>{]Yߢ>P(=z '0q'|uu.D;:;;+{J߿Mgz|޻j6mO\Ҳw߾ 1wn%4y'[%K,GjKÇOg5vԛhm͝['m嗯~_TnkOܳ=cS=漮f{O\W `簬O_[~[ϳZWʟWSUW=|k֕o}ן/_9;ʰ]?UD|O}j;.@8>XTWJ^>_uy橻tF>e{j)++ѽ'6<׭[K2uu:ϯڽ'wժ=i̘O\zi޽D}J<=_5y0fx{<3rZۻwwbwtLuPoݺu޼y z,Z4۶sv={{>ⳟbs*OZԷsQGl:ׯ_dɝx_ϯje˗ڴO|bLK˥M]7ue}/|~ueo/Fk{uXW?5_ڭ?]ߒ'_ZW|T~k֬[[n97*nݺ;;__m붹獯u?޻嫖ozn'|Җ}{9XWi\z]jdȓ?yK_C_\s`៍w~UKU_#/E뒨n/ɧowl}i9pS샵Mq]Y^q|[_Ono9>X{]*Vq|E]`P]*z>?֕o|UtW׷>WU>嫸;gk~Gwh-^Wq\K?S*uD9s^/*icG{:Wַut~|ϡWU>z!_{W7u2ߞg}3̷QJͷՕ9|{h]*b3̷Օo}0(W9>%_E\zrQU>'o+_EsUM̷ZUM=TW uD96|{P]*fWo/W7|{P]z%Kԕ"zG*i샡_UM1^ZCWtD]*nη֕o|e=o/D9|{h]*zη+_EsкUMe~9>X|q}RWJtTW^{]*W soo:?oo|y=|(ǦWy7C2ߞg}3|{#_E\<^{]ҟuu嫈}.q,y+_%s}0h~@J=t>DI=|ϩ=|ϩ=|g}S+y7Wso+_Eso+_Eso+_YX`#_X`*VG샡ut}N1^|CWy7uj0ߞk}ηoкU>o/QWJcS̷֕o|e=&o/q)_E\us_ϞXϱޟZIeO |ҭoYVM7ZW֊礳%Wqt=|g}S+y7{Y/*Q~N1ZW礳%Wz=|g}Sf5s׿~A>(_k+_Ek+_Y YpͰu#wRWʟWSfK]ԕo}Ь{پ˽%_Ek+_=/]u嫈}.b N䫤}r-_[DTWJI=|ϩ=|ϩ=|g}S+y7w7*Q~N]Au͟?֭;7*KSSSSqݺuwyg_Em:w[?gܮ#G6|ׯ[䭺?G|ST}>S>/.]zGʟy>xlc|UC*q\tpů{m7u'uw^{kw6mzriJ]7ue}^7ue}kWyT~闯__{k.ç8O(zr}积^5k֯u׏n><\s~ΰhm󜷧7EF֭sўu_>[U?ܦ1c>q-E}.z=uWZ3f̥^wކhll> y_vvvV>;8c/~ѣp֯O]wLyvz]|}~-[w5v[x5?dZ^OTnG57?ae}'ٓbOY7u7Xt湮쳩VtOs5rde-Yg쵮|9&Wym>;+3+_Y`ϗWѭ^_?nԷ>8eʤOcǮ=W\z!Euk-\>w_ѣ0`wt_.uוmnn6awe޷o_}ƺѯ>G\X`uĉsgFue- .<׺U磊/3̨QZZZ,YR&9r׮]{imm=|B]myk|ӿ8=`/hXn_q~ە&M9};vqEw/S.=g={-pƺѯ>G_%Qוwy9's'M>`u_Wַ>s5O8߻c]mu#_#QWq׷qW\Ek+_X??Sוo|UtWq׷ωUQ]*[y&:35.YXT̓z+_E~>:R?嫢Us,g]*WE?_}NW^ϟ3!_Ry}0|w}K|ϱ#}܎|f}kJ]WJ+_s|>8}굮|g}S+홞Ϸ֕Du嫸}N=ZW~%o+_Y߈`P>J s}0|w}KDuD}N7TWJIۃWy7u2ߞg}3̷Օ߿ۃWϷՕtAu<:_oϳۃWAurJKԹ֕o#탥G*ic탡_UM4ZWJCWqz=|>J:ZWʳLϿC|(?'o+_sкU(|{h]*Fk|UZ{|w}Dk+_%so|b}S{u<:_oϳyU7^W|k+_%IUM̷Y<^{]*k+_S}0|w}k,y+_%s}0t~@JCW)7|{h]*nSϷ֕GICWy7u2ߞ7|{h]*Q~N4ZWuQкU]?Uia}Օoћ#AuD}N7TWJIۃWy7u2ߞg}3̷Օ߿ۃWϷՕtAu<:_oϳۃWAu>ۯZq7Y+i}h烬h}S}{`]Y+&.V>'.U(w֕o|e=or+_%ϩ=|ɿ=|>J]uz4Mr|9&ͺgk+_YY<}{u|{u嫸ECWq׷0C*i5oW7|{h]*o+_sкU(|{h]*W3=&o+_%ωCWuZp׬Y3dȐÓ&M9r]zyݻw¶߿fN2GرcW^|yݲe;WwIΝ8+u챍{:Wխ~"*W9y؄ q-9'uէ{ѣ0`wt_.uݸו-zg}q+{.>X{]*yGu5lXO&7oYqP:\y'~35%K*O]籍vw.Z{CW^Y!gȑڵcϞ+;eE{=gg8p/hoXn}(u~}g}ou(߅}GssI'OYg3q;p਑#~ʮ][w' 4p];ve+W^9*Teˎkv7O1}ݚN:''?u\W7Xt濮>z,+k%ٹ+üM`e"0*@MO$xИ'&i~h&RB6DCb0Ҡ 1ZH -BK(i*2Vqp>0Cg7uL7}uo7g_W탍2/~ ,~LF*sv{lTWn5l{饿>ꪫ:;kYWOGǕW\1}7xqٲU[lݵ덮e޽磏νeڴ}~ͽ?@廮8Wۻw_Omm/?񫽮.Vcyʔ9sf N~L~_G Mp>7F}PaB9m^Ov9:g˖-۲eˮ]N>Umݺ7v͜yuc+~Su޸gnenW״{ߪ/Ȯ:7hs׭|Rر}.\3ujm==8[wb׹mʬYsj`>8ו|:9nu%9F㯫z._|hֶxWVmݲ]o<}6m֜Y-c>^q+w_Ess=6pl} {Nm:mr|/~?Zp= |4p[u~~UŪW{]ɷ$Luc+\3󚗶toUW_yUg_7ouη_EiuWwrh˱6}6]WU&UTou_5z9._ͷz_et\R]U|sW^_ou_5 y\:g]U5>o^>ZW΍VK{u樿ʑr_殫7]W|{뜻2^&uWLuWoO η_7>x\iK#|uWԺ|sϷ_eS䛻2^&tfoO7|{j]Uu=L8__]qs>8κun$|g]U| ̷*i}uWe_o/oq_eͷ*6b㬫tog]U|sW[l}uWoq_USԺun}*:G탩oO}η_{=*>ߞZWU&2oOtfoO7|{j]Uu=L`,iun}0*GYۓr߿IuWe_o/oo| ̷'_eͷ'_7weLۓIuWuZk^=1A#|uZYԺz|sϺ_egS䛻2^&ܳuWLuWuO κ_7>hj |U*8rwLA_>{4wdo4^X}uWeͽu/>κpe.q_ŞoO]FbD{coro=*;ߞZWoԺo|{j]U|sW{=*i=B,[l˖-v:޽{{zz/^Fϯe[ny]3gzcۻpaԩ',~?=>喹]]~^W}dWmkfK/m;|o骫j֬9W^1}Sߎ߽1w&'p_éOu55_綶)iR>ؼnjbw滮&r`oϋ뺊]Fy^Ww裁so6{/b]'!؎+b碿q.ڼsU[lݵ.yy`޽zzOmkxqO#EO֝2mΜYƜ廮&fֹ!uO0gΜ3/{VL(?5k{?{:ue C=:}՝3f̿E]~y̙6m]~G?׿j?~?Qojj#o=U]c%K|}T[Zu5n5O?w޻oO/w~[|pڵ9ѣW= }]ɷι&ijiYFuZ^|Hݵ{/FuW>YWя>39 {l2gGYFuWw8vv x'V4iR}uW! >k֬}#~xwvO:ӳd;_6Ĥ}SW_coxVuN=wݨJMmW 6 uLi9uu]ɷ:?C=;ĕ;;g̟oON}9׹я~F?`>7&Ep]57'߷ws-bOڽ{c-w&uWFg_xȑ#CCCvZ|yQaZWW׆  Lr%}^g7>C?+Wqss?v#9սUunxֺ욹iæuۦq~u\яvrǃq}_oϯvJWIu+~i|4%_J'ݾڭW>SF]WmQ5Owݷ:샩uW9jL Ϸv>ZWouW_5OW6YU|G_xHݻv?RjTW_5Z9mtjRWU U_dWs?8j 7CԺ|ηG 6(GUlsYYbpn*6 uֺ|g_ȷ@eLu=*<ߖuWoO ηW*o>X?_UPaCԺ|η_u=*6 䛻2^&uWoO ﯲη_ےsB]U|sW{=*:ߞZWUYD>TWo uW S?b:ߞZWoԺ|sϷ_{B]U| WW_ےoOͷߞTW~S &},i_u=*<߬uWoPWU 2oO7|{j]Uxu=*<ߖ 䛻2^&uWoԺj*샩uWfoPWU ߨ}*6߬uWoO7|{j]Ul+_ȷ@eLu=*<ߖuWoO ηW*o>z诪I]}0*[>Ru%};u ͷEu]Ůs_WkO~29BlڴygLok|Uˆͫ7uƍC>[gg_wЁuϺƎׯE|9is <߾,Yy=q }}O$ǨJyѢGk{m=q_'_>Q?`:lRW5g?σyx;ߙv?ɘ :gfppgkNk:` n8:7tunl٪[o{w=/]W|rkGnjɓ$Xy7o޴h“N~Im1?x]V\rn+}SGM _G]ToΛ7۶+\W=&Mz=ׯs+SF]W=΍rS*d}խK_rW笫 |u֥K\}oo%K\{si˧im7t7>^;om?.[lxhwk_u69wݨJgK\=Jg_O ~<8;\O샩u+7ߐ}psׯ[_{Ύ V<:bݼiMdzmQUj]Ul޷ԺQו|OsϬuW_5OW6YUխ.=>rλ{iԺ|Ϲ&9bmipjTWU,W^_ou}~ը*6s?{*Hu~os}0X!`Mr_[a>*w]U`{$0k]Us|FYFuW_o/uB]Ul-9S:^{|UlYSꯊjR9jL7|{j]UlS~06uW_o/o uWUԺ|ηWQoKԺb>2^&߬b:ߞZWUYҼD>TUxYSꯊ~_{B]U`SߟηW8_{=XYޟηWx}Ul-9S:^>_u=X`ʒ9pL7|{R]Ulۓ~06IuW_o/oԺ*|{R]UxS!WdoO*o+e=ߞZWoʒί=ߞTWU,ߨ}0u~@oԺ| ̷'_?oO_[`=XYޟsϷ_ےy=*<|sϷ'_7jL~Wդs>ZWo| ̷'`lۓꯊ囵2^&uWU|sϷbm<ߞTWU,W{=*6IuWIRgfC: 7j=^X{bR]e u ̷Yt}0^+6F9\?7j=XYuͷ%h=*<ߨY|fCꯊ_KQU69j=*6Y7p=XY+e ufCuWͺ_7Y2FͺG_5RWU-yԨ*|η_7p 7|{|sϷ_{7|{j]U|W-KuB]Ul-9SkӦM .<%\vg}ȬYNٳ:Toɒ%^{m~;wn7y~|7oZT/< n8:7tun}+W.Ѻ70WNm?u1Rbo맞zbpٳk㟜ꯖyw=u<|_IuG?羽gu\{ŊǛ_?ו|.]~>8om z]ɷ:gWCVdoWCVd+^^U:7Yϯui慧?_r=o[;;;<>3:>oTX皭[_]wdWwݽcWZ644<~.z]wl<߹hHg?N̞}!{yɒEν碩u-zms~\.ounݶ/?O}.8Ф'NLaohҥ~=qbRii$>ϭ't?-||k]J_~˖- qqqݻwoРAܹs>`u-Zn]Z`#T^yyy'O111smժUcƏYz뭣Gv*(T^-*((M6mժՏ`ȢO>@Ņ\kǎE5 ׾}"9BضmۓO>ٵk׺uVR%66ZjM4ѣ?dɒr(//o̘1W]uUFRRR;u4pٳg=>---fk_nذaСmڴB8ofԨQ^zi 3333uԊ:>͛ ֪U5joJoE~ wk޼yuܹ>e˖ϛ7/򩮸+3gΌ93G9SKtRw?Hqqgܸzy5KmLWU_E_W_}W_}}}W_}I}]U}}W_}W_}E_E_WJ᳭[rїOrox)+W}}W_}W_E_E_}W_}] ꫯ苾苾ꫯ//otk~ͯꋾ苾諯ꫯ/Fꫯ苾苾ꫯ//otk~ͯꋾ苾諯ꫯ/Fꫯ苾苾ꫯ//otk~ͯꋾ苾諯ꫯ/Fꫯ苾{شiӣ>:苾+E_E_}W_}q_p_/KV_oƝ;wv~K.}׆ RPP5p[~q~s1bİaÂ!&OܒWx{w۶m_ܵkתU&NxgO4VZ{<=߾kJ^~姞zꮻ:{[mɋ_7k֬{l~;EUw#}}}W_}E_E_E_)~]vW????8kΜ9/=jժ[n̙wwlڴiС%geffN81X|O?thܸ!C~G#Gfdd^Nw?@^nzͩՠ=O>_Fw^tv-_y[ycIz=WWFg{o=rްkk/>;;c~}m_5+eȨ}oH/ꫯꫯꫯ苾ꫯꫯy苾ꫯꫯ/ꫯ5?N{-wڵUFƠ>xco ί_Uoo5iٲPLL?n8㌊S~̂O ok#?tMN9Ǜ65Q#{3҄0苾ꫯꫯ/ꫯꫯak~ͯꫯ苾ꫯꫯ/Fꫯꋾ諯ꫯꫯ/otk~ͯꫯ苾ꫯꫯ/Fꫯꋾ諯ꫯꫯ/otk~ͯꫯ苾ꫯꫯ/Fꫯꋾ諯ꫯꫯ/otk~ͯꫯ苾rss_ 6iӦ#Gٳ~GպuK/֮]ꫯ͛W\\ܩS:T/ZoO8q苾ꫯꋾ諯p_E_*p/yqŗ_~ʕ 4hМ9s"~x%[7o۷iӂuKС_VZZڵ^,_O?,222"5``|Lw}2U_;fѢ_x’3ozR/ꫯꫯꋾ諯ꫯ/5ꫯ/ꫯꫯ+諯_󫯾/ꫯꫯ苾ꫯ5ꫯ/ꫯꫯ+諯_󫯾/ꫯꫯ苾ꫯ5ꫯ/ꫯꫯ+諯_󫯾/ꫯꫯ苾ꫯ5ꫯ/2iҤ+9e={LMM>ŦMN4)&&&X/\p?ү]vԨQ#<|[SLԩS{Jy߳g͸u3ߍ$XDխWw`qn a9i];wּU9ox5}OeBbճ/u{`|Ħm=њ%_|fE z(6>>v7ަ諯ꫯꫯ/ꫯꫯ Iϓ/ꫯꫯꫯ苾ꫯspK暜W5J*s9vV8*,.޲国{S[ed{g˖-ӃomM$0苾ꫯꫯ/ꫯꫯa_}W_}}W_}W_}UG_W_}ͯW_}}W_}W_}W_uE_}W`_}W_}}W_}W_}UG_W_}ͯW_}}W_}W_}W_uE_}W`_}W_}}W_}W_}UG_W_}ͯW_}}W_}W_}W_uE_}W`_}W_}}s۷o߸qcnpjjjǎ+rV۶mk֬׬Y;/66Aiii~;IHHhڴivvvp}˖-[ʡ/+Wꫯ/}W}yyy"...99';6777XP(tI&o?~@n\s͏q}/XO8CGҺtrז}e˖ڵ7o^?~ӦM+ 3fLpEF\ ѣ5k/^}zKk޼AGf"'' F֝:u*Ȓ.XJR~y၁oE=nҒߓW.+,7yq =+x>v~{:aؾv:{JMx#kwow?9ꫯꫯ苾ꫯꫯ/'=Oꋾ諯ꫯꫯ:ꫯDjպ5lxo|uϞ,_~g-ݱcuUᚉ+l~}W_}W_}W_W_}W_}Wc5ꫯ/ꫯꫯꋾ諯_󫯾/ꫯꫯ苾ꫯ5ꫯ/ꫯꫯꋾ諯_󫯾/ꫯꫯ苾ꫯ5ꫯ/ꫯꫯꋾ諯_󫯾/ꫯꫯ苾ꫯ5ꫯ/jժ5w`]XXG|v=NAAA׏VZ͜9sƍ͛7B_E_WW_}W_E_}}}Ry}?~۶mY;}=餓֭{UW [l){fnn={"F']{prK䰜#rUvY۷ݻ7nРA9G6l0غuJ瞧^>z {!7\ظkY]>MȑxYgǗԀg.7k=tUg(諯ꫯꫯ/ꫯꫯ IϓJ/ꫯꫯꫯ苾ꫯs%IJ:W}4{C>kOAYG>/ꫯꫯꫯ諯ꫯꫯNJ_k~W_}E_W_}W_}}W_}7:5W_}W_E_}W_}W_}}W_}_k~W_}E_W_}W_}}W_}7:5W_}W_E_}W_}W_}}W_}_k~W_}E_W_}W_}}W_}7:5W_}W_E_}W_}W_}}W_}_k~W_}E_=>s͚5cƌyO>/矟0aB ƏnѢ֭[O;D!E_|J_}W_}E_W_WW/WLqqqdώ=zڵ'xz%/nܸ^z_]VҶo,֬YӰaò~۷o_i ~,nQFUmذᤓN7mڔq#,7ndɒߚ2eW\,9GOl޼9XX"33?)K;/:w!m/^v}nxl\'_G֭[W_}W_}W_}W_}W_}W_}W_}=Nx<ꫯꫯꫯꫯꫯꫯGoqhyIk~W_}W_}W_}W_}W_}W_}{5ꫯꫯꫯꫯꫯꫯ{l=_}W_}W_}W_}W_}W_}W_}=6}mͯꫯꫯꫯꫯꫯꫯ6W_}W_}W_}W_}W_}W_}W_}M_G_󫯾ꫯꫯꫯꫯꫯꫯǦߣW_}W_}W_}W_}W_}W_}Wcfꫯꫯ[Y>Cߤ?=z>>???X/_^z-%%%..n޽zڵr\fMdQfM+ǡ7"苾苾苾///yR_A_E_E_E_}}}///苾苾苾}}W_E_E_E_}]}0苾///苾 _}}}W_E_E_苾苾諯////_E_E_}}}W_//ꋾ苾苾//}uE_rycccʘWC_E_<_苾苾 /Zo7klԨQ5jxG/_gyڵ1cF>}^x?\gΜ,|.]ӏ-[~GzΜ9|ٳgG[v6E}}}W_E_E_E_}.苾苾苾///_E_E_E_}}}}//苾苾苾/`~}W_E_E_E_}}0///苾苾 }}}W_A_E_E_E_0苾苾/// _E_}}}W_E_/"苾K4h//E_E_E_} }їA_~yde˖ү_|őK/7w饗Fƍ9Ƨm]t,&NXPP|}p ?ٳ{rڸqcd^n)%%%Xl۶-Xw7W^=Xܹw}#x|PbŊ'x{"M{Wc&jժw}ec~^3;J EVPࢭԦl_eԃE-EEY B~8"`"nxD5~>0wpϹs{CBwo544U_gϞgϞ}7}ttŋ5,nѢE.]ߺukǎ񰑑'O\qLȑ#SN}鿣o޼<{`6UJҿMMM6lXlY}}}XyiuҥKO8lׯY&IlϳCǏm}xeOOO=ںuU666~vϴ3g =|0[^… ۷o_xW߿_.òYfS]?yr?>>~Νx͛]:w\[[ѣG'&&ߍ5/رc֭+ cccъxիWXۧϨ ܽ{СCW^MӴT*ݼyI>}TYV=Z[[Kҽ{?~ŋr$7nܸ\.g9x`WW˗|Xpassڵklٲs4Ϩ+W l]ׯ_OMM-Y$Kk׮)쫫>%IQQbZWUQi,i^*~uVggg?[w9@  @,H Ă @,H Ă @,H Ă @,H Ă @,H Ă @,H Ă @,H Ă @,H Ă @,H Ă @, 0s9Ii\{4U梩) `:r|MI)}=|>#sl;9J'J)]U'.>Ă |a~B9?wL2$vE$tj!D*u!!qBBBZXj$.PpL&h͙6HQj;s^}x}>0{=әy0*2FEF"#R9@Q˥RluJ#W4HE9ץO(JǏ\0*2FEF"#Q`TdR\6+!-j0}~~`orT*.}BIT=~ Q`Td0*2FEF"#@\6EJ0T. ?T. Yd0*2 }C[`CCAEF"#Q`Td0*2* S[/ptollLLLJ;9;[ȣm\8sgꫯꫯꫯꫯꫯqqb"WꫯazҾꫯŋ;wN޷۽2;;ꫯꫯwlQ_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}WqsD}Wfg'W_}W_}u ʼn+WW-`fS8d;y~H:\ꫯꫯꫯꫯwr o)OW_/諯ꫯK:!tꫯꫯ'N^ {$ꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯ;;yR'辝$ꫯꫯ\W턽'Ig[ྃA0[pukaaayyl;f=ž}Μ)\^YI8S뫯ꫯꫯꫯꫯ[*}./4홙6 Ν+_}' ꫯRi}~Strݬ}?z} gΜ+W_}W_}շ}p?.\Y^i7v[wpW_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}[.ӏ`}.,z}&ܾナ3gﯾꫯꫯ+Vue./4홀f/,;W諯XdlKKKox_zoڵkJ%³g}'xb׮];vؾ}=s=zv$8N?Z/V{hqW_}W_}W_}W_}W_}wl}o+X_} ^OW_}Wj4o~?~X_}W_}W_} orW_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}WF<<`tssꫯꫯ\Wሳ}?.⾯[`e]v~/ݻ~gϞo }W_@x=i_W_}W_}ǫu5Gꫯꫯ7[V7 ꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯo#pt(W_}W_}u D-Ͱq}}WfFq_|G}t/IOo8q{~믿>zK.#G={v333۷oT*?c>_|V`^Vn7Kz67tNWժJK:}W_}W_}W_}W_}W_}[zZt$ zmNMUk5}W_x=i_W_}W_}ǫVW*^~$YUS5}W_}W_}7ZZu{Pd=mVӅW_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}շVT^I훬onթ难ꫯꫯ\Wkj%I95UqW_}-}~;:uƍ{yϼkWwOO>|&'' 0c i woQ8hrL}W_}W_}W_}W_}W_}[o+ԾW_}}A_}W_}Fc~@ϟ|y}W_}W_}WF[W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}w\}c|y}W_}W_}u]B3PW_}*2n!wy'oܸqԩb񩧞ڶmۅ Ο?= {Pqg|.eGˏ }W_}W_}W_}W_}W_}qַjW_}}A_}W_}Fc~f}CW_}W_}W_}C~W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}W_}շ ov|ꫯꫯr]}RTRJq6CWMP'`_E^Ք~cǎ={+۶m;|ӧG|sΥ7gff~7xW?vǫ?_~M~WŘa^Ov$ YOTZ.ZRzIꫯꫯꫯꫯꫯc[Un$]Ozͩj ' ꫯxjJu;ϟ$뛛jujzꫯꫯ[*J넷&fo:U.ꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯꫯ?O$gqcV7]A JWw!!ADD!!?<$O'qw{ t%6(vu.vu^W%uWۯI4سn> fSgz3S|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|+t%uϫ|rY'I0H˗/_|u$-u߾?Uu4}/_|C}W՛oo^xv]w߱t;WO}S'?Ƀ>ԩS_|gя~EL&EQ?]oZ<-EYndٕa/_|˗/_|˗/_|-mFLW$)O˗/_|,>_Elȣф/_|˗/_|e|o;(/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|wMÖEtȣф/_|˗/_ʹ:Vz&;_ķ#OBJ7Es˗/\p^u]Kow}{{{':o~_W׿w}?g}駿/;vlG?:y7/|-Ͻ-oy7h?#77_|G /o^[x܎?[3`;,_|˗/_|˗/_|3;Eo3r8˗$IO˗/_ע'x2ۑf|˗/_| f,ۑw˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|wg|;lYDی܎ϗ/_|˗s\EX,E|~;rϗ/_Aq tw_G}'tǿ\t?{ǎۼSO·?88nϲ?5˗/_|˗/_|˗/_;߃|W$)O˗/_|,>x|˗/_|Yۑ_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|ݕoo=<η/_|˗/_ʹYxۑ}| K/| 'Nx/_3gά^ry-\t>:na΍7EI,9*~Aؓ4u]|˗/_|˗/_|˗|ibUu4˗/_IHs?ɗ/_|t%u#zfe$ ˗/_|˗/߿^ҫ_=;M˗/_||imE˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗|v䲈ȷv4˗/_|\9W17i"}vǾϗ/_q 3-ܲ^zW7o?z/_W7TU/˲Gy?y]/³>_6ɓøhy7/|-[3f;2_|˗/_|˗/_|3Eo3l48˗$IO˗/_עx2a#f|˗/_| f ,ca#w˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|wg|;rYD Η/_|˗s\E[ެEL~;lǾϗ/_q ZsC=nC+o#s}{3Ϝ9s{}׻u뭷p ӧO7~w@/|xv,[3f;2_|˗/_|˗/_|3߼=ɷ/_|%I$_|˗n˲=Η/_|˗/_۬1Y$/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|7˷#D Η/_|˗s\E[ެ1y>_|W%u?nz7yZm~c=v̙;/_>~+o~g]ox6Ν{'?o|!]p$b_ަn7#4z'I:!3LӤ[u50/_|˗/_|˗/_|^*@a$Ţ8|gU]/$M˗$IO˗/_Ο^/|^[UN`0/_|˗/_V#W/yo52'8/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/ߨ|W^/|^E[͖:IA/_|˗/_ʹ_u0MbQWqYU~?Iz_||B|RP dy[E ey47ˮ Η/_|˗/_|˗/_|wq6cN&|+I$|˗/_;.˶ecF|˗/_|} f-1GQ|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|+l,m&|˗/_|:WΕ&;_ıcNb7:.py7/|Q,?F<;x˗/_|˗K64s˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_" f8˗/_|׹r7~ǾߎǴ˗o`u\)yb)r;8OlͰ|˗/_|˗/_|˗|"E͘}/_$Ir?i_˗/_|^ڟ-(onj˗/_|*|ecmnj/˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/ߝeo3f;2_|˗/_ΕsyyqyL>_|V%~0v,ȷ/_|˗/_|˗/_|ݙoیَ̗/_$I~/_|wey(olG˗/_|˗/m8v,/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|wmlG˗/_|׹r"/o߃8v<}/_|H7$Y,UU=鬪岟$`o&ޢ/_|˗/_|˗/_|ݙ0MbQWU辳~|+I$|˗/_;.M^R׋ߎ6|˗/_|o|wێ6v˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/]f"L`}/_|˗/_ʹ?7~ߎǺ˗o0u\)y v)r;8OlE˗/_|˗/_|˗/_|w|G(mF|$II|˗/kQeh3_|˗/_|_ohŻ|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|3qeo3Z;&_|˗/_Εs+"\﷣|L@o :v,Է|˗/_|˗/_|˗| \fvL|$II|˗/#*˶<;Z;&_|˗/_|oe|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|+,^Ã`}1˗/_|u+])opv<}/_|H7$Y,UU7ݬ岟$`o&ޢ/_|˗/_|˗/_|ݙ0MbQWU~|+I$|˗/_GX{yWl?|˗/_|o|i/Ջz[ͪeL /_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|w{yo5[.$)_|˗/_Εsl8LXUlVOX}| K d2ټ-"ъ8EeW._|˗/_|˗/_|3I[ 5˗$IO˗/_|yY},|j4˗/_|˗eo;(/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|wM,m&|˗/_|:WΕnE~;$}/_|èH1ySL}ʥ˗/_|˗/_|˗/_|w曷X ѷ*W$I'˗/_|,>?<;x˗/_|˗vq/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|7˷bZ 5|˗/_|:WΕ.o"}*{˗/0R/|ySvqܟؚK/_|˗/_|˗/_|̷rQ 7+I$|˗/_ע{2ۡ"f|˗/_| f=,Cۡ"w˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|wg|{)"@fv@|˗/_|+JRެE~;Tϗ/_a^]):}t?p|~;˗/_|˗/_|˗vUWӧ~{|$II|˗o{{f>3ϟۣ˗/_|7 nwouAͫAÈ/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/_|˗/|{j};O{o/_|˗s\Oz#j~:}szjE;[/^^2jԪEK-/|W|W|W|)/&t-jUE/_$II_3H$eYͨQ%ի[TXXŗoȋF#ԨQի-WE/_:ctըR1 _vqi?}_|__d~___Mì5+++|W|W|W|J2++|W|W|W||W_+++W|W|W|ŗ$++W|W|W|ŗW|5+++|W|W|W|J2++|%IJ|V4aUR]j޼EEhZ++r\^E#m"odԪUՋ+rW/6OIOW0/Ο?ʕv:x`۶m{5tq 2>ݺuۻwopW_}<~'|^۽{ѣG;uꔕ5hР.lĈXfzg9ңGkf:thY5LO<>p`mm[]SpS_|˗/_+|˗/SF[Qk;旯J$/_|~y0⡃m.$55/__|&h$:x`\4__Yg͸w=0θ7SyOC}O=h̯˗/_|W|ŗ/_|˗$˗/_|W|ŗ/_|˗of5|˗/__|˗/_+|JW|ŗ/_|˗/_|5/_|+˗/_|W|ŗ/__|˗/_|_˗/_|Ó˗/_|W|˗/_|+_˗/__|%I_oҊFx",m.,57|W|W^E)$Z8.4W|өH$=%/#@)i\pAII#<~{﷿mcc5kf͚uE1b֭ .ܻwshjj*--o>^c? gϞ~f7n߿M}vM۰! s#G|y+˗/_|W|ŗ/_|C۾]Sӡu{‘/J$/_|akС օacf̸w_W|ŗ/_|Movm{g,/_Yg 40]fm6f$_˗/_|˗/_|˗/_I/_|˗/_|˗/_4_k~˗/_+|˗/_|W|̯˗/_|˗/__|73(?Ex|W|˗/_|˗o}w۱c}B;w0_+I$+|{s玾}s:tX_/?(8p0+|˗o |wӷC!oy׼97IW|˗/_|+˗/_|W˗/_|+˗/_|W|7 _|˗/_|˗/__|%k~+˗/_|W|ŗ/_| O_˗/_|_˗/_|+˗d~/_|W|˗/_|˗/__k~˗/_+|˗/_|W|̯˗/_$Iί7M}w7CR?\˗W:ر3'oǎzP`|WiS$H J)΁ݻ7-X⋿oW z:zUƍo'{v9S 4ٿsu;렘__+++|W|W|%_+++|W|W|W|6ͯ/____̯/_____Ux___+___ko_'ͯ/____̯/_I$9DD|s23c˖ھ}:-p~__9t /袑6e[b|*E")4}iʕnGy$,[lȑ_?M6'|rofUUUyyynn-[/ɇ-Y+ C*VZZxo}[֭;F$//oӦM-gLGsNʷpoYvxQ7150|˗/_|W|˗/_7^i kXfE+$Ir>)˗/__^$dx($;eMQ>}i˗/4F#0TX|׼vڢE}W|ܵ׾=F]XoKP̯/_|˗/__|˗/_/_|˗/__|˗/_+i˗/_|W|˗/_|+_˗/__|˗/_+|˗ox_|˗/_|˗/__|%k~+˗/_|W|ŗ/_| O_˗/_|_˗/_|+˗d~/_|W|$Ir~%) 'f5EE+˗/_r\. nfk֬>+T$mHϗdwJJJFnӦ 9z?ܙ4iRp{M7n~饗N<[~wʆ9r)--=oدۼymZg][W݌,(:sM __|˗/_+|{fֽf}[PV6_I$9_˗/3շv7S <7ujܹO+˗/_|ҷn}<`jԹAg5^n 8h +˗/_|W|ŗ/_|˗$˗/_|W|ŗ/_|˗o:g5|˗__|˗/_+|JW|ŗ/_|˗/_|5/_|+˗/_|W|ŗ/__|˗/_|_˗/_|Ó˗/_"|W|˗/_|+_˗/__|%I_oծ_f}<7ujܹO+˗/_r\)iUmmݛeeSrW|N}^PZ|O>ܹ{OfϞݦM'xsx76oG]{|d=zt~W޲eKm6`ȑ#=Xp/7 *++ۊjE740vl ƀoW580|˗/_|W|˗/_ַ.R^{6|W$I'W|˫*/,Z4coHf46^}ކ/_|Mߪ0Ԣ/)no߫G_ ǟٟw3wL}_˗/_|+˗/_|W˗/_|+˗/_|W|7m_|˗/_|˗/__|%k~+˗/_|W|ŗ/_| O_˗/_|_˗/_|+˗d~/_|W|˗/_|˗/__k~˗/_+|˗/_|W|̯˗/_$Iί79UUVWwKǎ!ztnnoCW|ŗ/_| ]Uep[QQ2^.ᆔ^|?zսW|өʪ)[;֯_ΠA?> nGݱc̟?b~3fwC-,, t={<+رnݺpΝ-ި ??3ؕ:v3ƌ12|W|˗/_|7̾h| 3S;ft_$II_|*E"{??c:v3wW|ŗ/_|o4 oA~̎R} On_y=q|o̯/_|__|˗/_/_|˗/__|˗/_+k5|W|W|˗/_|+_˗/__|˗/_+|˗ox|5/_|/_|˗/__|%k~+˗/_|W|ŗ/_| ˗/__˗/_|+˗d~/_|W|$Ir~%h$:3;tbf`W|ŗ/_|R*(1cƌ4+H$%|;vܹ{޽ÇWX1bĈ|wޗ]vV&Lܙ3gɟr뭷n۶mҥsW\ѩS۷o׷o%KثفW[[jVZu̜=__|˗/_+| o"V>{/V|Wf4|W$I'W|ŗ/߯T8|y[Rwj{`7>]~A~pvTm>|W|ŗ/_|˗/_|˗/_I/_|˗/_|˗/__Yk~/_|__|˗/ +|JW|ŗ/_|˗/_|5/_|/_|˗__|%k~+˗/_|W|ŗ/_| O_˗/_|W|ŗ/_|˂/_5|_W$x}x[2U+xٳg5+|+ǕفWx]\4k믿|vܛn.O~w3gܳgOIIInNVZ1bč78}믿>##7Ǐ߮]a;w|';7pC=N"u]WPP *++ۊ|pҔ)ьÇd>6m\:lXA~9˗/_|/_|VVUUT={&MF3FL /_$II_|JUVVWj|)S&edDBa./0n|W|˗oVUU)dD3wإW|ު}k/Vɽtĥ)ـOu_we0+|˗/_|W|˗/_|J2|˗/_|W|˗/_|y_˗/_|W|ŗ/_|˗/_5|_˗/_|+˗/_|'ͯ˗/_+˗/_|W|ŗ/__|˗/_|_˗/_|Ó˗/__˗/_|+˗d~/_|W|$Ir~%I"\?eʤ#S;W|ŗ/_| ]Uep[QQb^?8iҔh4cdJ/tXsoUYY="=_~kGٷw֬Y+RWWܹ|̜9s./:w^UUUp{M7׿~_%xvvl߾]vM!Cꕪ xW|ŗ/_|˗/_|IC/M? $IO/_gtC +7US+˗/_|޷pH^muW|ub޷vTm>2+|˗/_|W|˗/_|J2|˗/_|W|˗/_|y_˗/_|W|ŗ/_|++|JW|ŗ/_|˗/_|5/_|/_|W|W|̯˗/_|˗/__|7///^?nx駿1v n>|8xX]w3<|ȑ#G>ᄏ?'N 444t޽%L~$&pnӦ+0u֦ŗ/__$H4HP>_2W|%I|R|W|Dqj>մi0__UZF:iy&xؾ#{^f,~_}J6GG{kfwa~W|W|ŗ/_+++|%___+++|W|<e~W|W|W|%_|__d̯/____|$˗/____++_+++˗/__d~++$Iί7 E#x_2+6OI #iʕ}H$|#F|#?6mwߵk> 믿G>|gϞc=6iҤ/݆#GD7ƍ{駏t x7OxѣϟiӦ_tsO7VԔ23m kr˗/_|__|˗*n)--Œ[ЫWΝv2|W$I'W|ŗ/߯Z$xvSMMuIIiff,OP߿[2h|W|˗oZFx~vSmMuMIiIfү9k7NO>{v9ݓNC;w\_˗/_|+˗/_|W˗/_|+˗/_|W|WgYk~/_|__|˗+_˗/__|˗/_+|˗o_|++|˗/__W2/_+|˗/_|W|˗/d5|W|W|˗/_+˗d~/_|W|$Ir~%h$'B[433l߆{eeu޲eA+˗/_r\. nxKzQ6VWהĒ~__ЫWΝvy?_H{J"=_***JfO>C/^\TTԽg<9s,ZsضmsNsݼyٳo>^n]=>s>|3o޼{}k6e(TVV'?4k֐}bW$Çw.*,r4ŗ/_|++|˗/SUeUUEE9C ܯ_*|eb~$IO/__ʪmgzh!yyݭ 4+|7|*sPOЬC7[vaQ+:_K$?+/3tqc@__|˗/_+|˗/_|%_|˗/_+|˗/_|W|u˗/__˗/_|/_5|_˗/_|+˗/_|)ͯ˗/_+˗/_|__|%k~+˗/_|W|ŗ/_| O_˗/_|W|ŗ/_|++|JW|ŗ/_I$W{*/f=4x𐼼~)VXXtC __|*tUmEEyKzQO~YC nݲ x?_US*v(egg?쳥nQQѸqƌ߾}ս =Xcc!;ߝ3gNp[RRҮ]cǎu 0.;ҷo߶m_s˖-?ž|_~뮻u˖-曏9ңGSSiqq,3sVV[o5)|W|˗/_|˗[Zev ߯ǦNW|%I|R|W|m ffƲ:'czT|W|˗/߰g2RqzV׿|WSƚ7uU:xvࠁ̯/_|˗/__|˗/_/_|˗/__˗/_|x_˗/_|W|ŗ/_|++|J__|˗/_+|˗ox_|++|˗/__W2旯˗/_|_˗/_|Ó˗/__˗/_|/_5|W|ŗ/_I$W⛦ť)}["+˗/_r\)%bSqǦ+J"D^P۰aÍ7o| w>S]]=dȐ#O?q>ڿԩSgΜjժC=z= .ܳge]iӦcjȑ#.],__~-g#6p+*ewjRZ^|_U DDMoylW|%I|R|W|Dqj~_EEy[V6kl{7'}˦u_Il%?$?Q_\?+A___˗/_IW|W|ŗ/__e̯/____++/_|W|W|ŗ/_++_+++W|W|W˗W|W|W|Ó2+|W|W|W|J2|__I$W{Fx}+ʏM5+++U/袑6;$NISe{?_H{JOIW0dɒ\r׮]x<{ذa7xcAAL<;sݛ_mҤIٱcǝ;wi&?O_y7|sԩӀJKKǏ'>`̙C-L=zt6n~ܰ_Xoۼ[ _|˗˗/_|OUh|wu6NtX۶m_I$9__|jPwŊ7I#۲4+|7|H<|?uw銍6ޙo޶9|W'cy}I~~_˗/_|+˗/_|W˗/_|++|˗/___k~˗˗/_|W|ŗ/__|˗/_|_˗/_|Ó˗/__˗/_|/_5|_˗/_|+˗/_|'ͯ˗/_+˗/_|__|%k~+˗$I+=E#x<FK7n\wӓ#۲4+|+U/袑oaҥ+֭8=X۶m~E";3M_~ھr)kjݻo z$6n~睈Nk|˗/_+˗/_|D8fݻo ={&7Þ/_|%I|/_|{Fw|AQPPnn־}{7lؚ3)Y8˗/_|o:K|9ܬ}{mغ!'׿)8|=cӥϞ{6{N=i; {ܥs:W|ŗ/_|˗/_|˗/_I/_|˗˗/_|W|q_˗/_|W|ŗ/_|++|J__|˗/_+|˗ox_|++|˗/__W2旯˗/_|_˗/_|Ó˗/__˗/_|/_5|W|ŗ/_I$W{Fm<on־}{7lؚ3+nܸ;1+|+Uhjy/*++w}[nKW[qsoWIWlDIWUV%_˗/_|_˗/3wbI-;ɾ;+˗/_|ȷuүy]_}n/.~q횵s'I n/87+|˗/_|W|˗/_|J2˗/_|__|˗>˗/__˗/_|/_5|W|ŗ/_|k_˗/_|Ó˗/__˗/_|/_5|W|ŗ/_|k_˗/_|Ó˗/__˗/_|/_5|W|ŗ/_I$W⛦'m}v}+|+ǕR~_Rry}⫴,55p~55L8___+FD"nGV喉C_+I$++_He5GY=q-]4++⫴F#ǮǾ?Rf-/_4{;ޤ=i=Cw{= 0++˗/____̯/____+8e~W|__d~W|W|W|Ó2+|W|W|W|J2|W|W|W|+++___+++|W|W|%_++$IίDx;2+|˗/_|W|˗/_|J2|˗/_|W|ŗ/_|+⫏_|++|˗/__W2旯˗/_|+|˗ox_|++|˗/__W2旯˗/_|+|˗ox_|++|˗/__W2旯+IJ|}xܹOOm_˗/_9{w֬ٳ{p⧞>_UZI$k̙'ҦMky55}o۾=oXfmԦꚚҒy˗/_|W|ŗ/_|FD"۶2KҒy_I$9__|bȱ0.پ}[߾9-'TSS]RR<+˗/_|7> Am߶oNXf}k7Tהdzsڱ}G^$h|v{m̯/_|˗/__|˗/_/_|˗/__˗/_|5/_|/_|W|W|̯++|˗/_|W|˗/d5|W|W|˗/_+˗d~/__˗/_|+˗/_|'ͯ˗/_+˗/_|__|%k~/_$IίDx<F̌Ko꒒y__|*ed4mX$^oT[]]SZZ~_M*i#Dk̙UUUw}BjۧM뗗W6aB֭_+++|W|,= !3++3|W$I'W|W|C]$Ŀx(hڴMPƷEƗoF#ǮsPn/oߖ:|ꌭO>{v9ݓt 4ٿsu;|+˗/___˗J2W|W|W|ŗꄬ5+++|W|W|W|J2++|W|W|W||W_+++W|W|W|ŗ$++W|W|W|ŗW|5+++|W|W|W|J2++|%IJ|OSH4!v{^^ W|W|]FFS|]>_2-4|*iʿVQQ__ /z6lB/}Hiqj*9[dw ?O˗/_|__|˗YeUUp[QQS^D%K'On-0|W$I'W|ŗ/?cWsK 0iK.Ɂo~~+˗/_|ͷQXxP.YzGo7_K$<+/3tqc|+˗/_|W|ŗ/_|˗$+|˗/__˗/_|ꄬ5/_|/_|W|W|̯++|˗/__˗/_|Ó˗/__˗/_|/_5|W|ŗ/_|++|˗ox_|++|˗/__W2旯+IJ|OSUUmyyEH}K 0yKqc/_|quFt9UZZ|a,Y:z9|7+oػ({hلK$\rL& "0(^f:T#ء0V{ўX/rDAKBBBk+hMIZ#ٰ>tvMg}^*~w566~;3fL pΘ1g?G6{yӧ/33s֬Y? Co/lr˖-Ǿ*((DZpaqaጵk7ZZ*3.x}W_}WD:ΏA= gߖ斪ʪ}/rRpռM/}vW\TeӖO5w7苾ꫯꫯ/ꫯꫯ _W_}W_}}W_}W_}E_#\k~ͯꫯ苾諯ꫯ/5/ꫯꋾ苾ꫯ?\k~ͯꫯ苾諯ꫯ/5/ꫯꋾ苾ꫯ?\k~ͯꫯ苾諯ꫯ/5/8B m$ӾO]pڵzoKsUUezz6\/ꫯ}+$'׵vS g(#-U}/&O},[LSӟtݺuӧO߾}wn!79bĈ/}K66lؐ~_7xl >s7|3<}; w{ { n_~_ v ɩoٵg}; խ޺x`˪LJJ: ߸qcYY١C=zݻw?C`51z|eK|Ԕ\5E=>e5d`zꫯ//ꫯꫯ+e˖hvjETQC_}Ǔ苾諯ݳ|yK,ϧ?zQك1Y/ꫯꛀ}W\q~*wU{wT>}$%_[{N [|諯ꫯ/ꫯꫯE_}W_}}}W_}W_E_>ꫯꋾ苾ꫯ///_//ꫯꫯ苾諯ꫯꫯꋾ苾ꫯ///_//ꫯꫯ苾諯ꫯꫯꋾ苾ꫯ///_///+􍑕WK,Ӿ)}_}Q=x//WB8Z|eKKK<߿~v!>}X(^|eGQoSwWYWmsΉ'~ݽ{qNYrrr{{W򕧟~Ïk&LHMM {ͭްaìY͝;'=ꋾ苾苾//}o(FY[[7m>j~E_O//vO(9$ⲚΜ9mxj苾苾K ;;{PAںfN͟7E_N\o^̈jԌ5~lxӲ_E_E_W_}}}}//苾苾苾/\E_}}}}W_E_E_ꋾ苾苾///op/}W_E_E_E_}}}///苾苾苾_W_}}}}E_E_̯苾苾7¡ppD9sԩ<}}qNhR|uӦϟsE0M %#[z;smxvܹƍox7~Ү/OOOs׾ /-?;/'''[oyvm?.Aʔ)k׮Zl{ѻɋFNHzMikyvX?55MM$}W_}W_E_W_}W_}I5_쳱[QMc/'}W_}5\3gcMM\_E_W_}ӾkJ؟VUTir/rjr.KvUvqeZs苾ꫯ//ꫯꫯE_}W_}}}W_}W_E_Nꫯꋾ苾ꫯ///_//ꫯꫯ苾諯ꫯꫯꋾ苾ꫯ///_//ꫯꫯ苾諯ꫯꫯꋾ苾ꫯ///_///+Mоkf֭ƼoUEMFk///WE$F#}խYS:5|jFob ~?T-7tӯ5kl޼(??/3ḟy׃/}K ?oo|#%\|ydm[[iʕ+{CUTTL2ۣO޳tݺG(,͞w\_E_W_}W_}}W_}{}=Ǝ=cFΛw/'}W_}={׭+=zlaጘhy;wW_E_W_}߾{֕=vtagϛ)˙MUUstG.܉/ꫯꋾ苾ꫯ/`~W_}W_}E_E_}W_}}їq5ꫯ/ꫯꋾ苾 ꋾ苾ꫯ//ꫯop5ꫯ/ꫯꋾ苾 ꋾ苾ꫯ//ꫯop5ꫯ/ꫯꋾ苾 ꋾ苾 }uJG[X8#}g۹s/}ESZn3b|?oE_۹[.W_}uҥ7n'N|ڴi]۴ ?Hsssp[cѣ$KJJ n?X#G۷oʔ)}Ҽٳ3 cw}w%Xp}}W_}W_}E_E_} }H/(葾c}p<苾7͞=/==#?^r //ꫯoy3{/,wbnprWwԵa~}W_}W_ //ꫯꫯE_}W_}}}W_}W_E_>ͯW_}}}W_}W_E_W_0W_E_W_}W_}}W_}W_}ͯW_}}}W_}W_E_W_0W_E_W_}W_}}W_}W_}ͯW_}}}W_}W_E_W_0W_E_W_W蛠}gKO/艾/}E7ovFFzAq/Bh*G?W~7_UU5fLKKKo$!99Çk׮O~~Է<Nc.š;\}L[[\=aiCy{}}}W_E_dP8F|NKK 2/'}}'?qYg yK//}P=$m6)16Xc .aò҆A>}J¾$Oπ /_G~kzz߹1X[[Ö,[6.vohh:|Pc㠁~鋾苾ꫯ//od[tСFj/x}}շZlIA人C^}}W_}e byPppF)>raó# `#E_E_}W_}}}W_}W_ꫯꋾ苾ꫯ//'_k~W_}E_E_}W_}}}̯}}W_}W_E_W_}W_k~W_}E_E_}W_}}}̯}}W_}W_E_W_}W_k~W_}E_E_}W_}}}̯}}&neK &Ç 8Ȃ닾苾qbɒe'|C닾$s-4uԍ7p`}}W_}ʊ9_}їȹ,gӛvUȌ^v6苾諯ꫯ/ꫯ _W_}W_}}W_}W_}E_\k~ͯkE_E_}W_}}}̯}}W_}W_E_W_}W_k~W_//ꫯꫯ苾諯`~ͯ苾諯ꫯ/ꫯ_󫯾Zm}}W_}W_}E_E_}k~E_E_}p| ڷzWeeyEEYzz/1bd}}苾諯x_?v./,+Ȉȑ#;}Ilh4jN-2eʔ9s$''w=׿+_ʛo_[V׃W^y5kF~ԩ]644#{kw_~y׃A~n?~|w<Cn[_ ૂŋ{o>mmaYE|3aBc\[ZRSRB,=O_}W_}苾諯ꫯP8?1}:IJosKj}p<苾{B#,;aBN4_}}W_}Mp+߃<ߖo苾tӊ+wuKcw-޹λe//ꫯꫯ苾諯ꫯ}W_}W_E_W_}W_}}9 _󫯾Zm}}W_}W_}E_E_}k~E_E_}W_}}}W_}7~_}j닾苾ꫯ///_//ꫯꫯ苾諯ꫯꫯV[_E_W_}W_}}W_}_}}W_}_H$}+:Nȉaߖ攔T///WNrr[~ue9|x}}}(<Gr lذ!?lذ /}ZZZGv1iҤny]{?>x 7Uoٲe];6V]QGEח)///苾վp hRE۷__o~E_O//v[7C=|{I|v}}}I̾pǥ⑄{PEK֗|C_mxrU^4ݲi'WϚ;˂_E_E_W_}}}}//苾苾苾/rꋾ苾苾///`~E_E_E_}}}7~///ꫯ苾苾}}}W_E_E_E_/ꫯ苾苾諯//W_E_E_W{ƅC6&z%%E_E_INnFQQח8mڴH$?鳟Y}Sˀ#^RrWܹsohѢ[Ɩƥ^rʢQF|233`oC7F>wP83~uTf}}W_}W_E_W>ҷ}P8w3}u(}E_O/Ym ]qնdf/o\mk_0wAguu-E_?r#ݼqsWXo˦-vE_E_}W_}}}W_}W_ꫯꋾ苾ꫯ//'_k~W_//ꫯꫯ苾諯`~ͯ苾諯ꫯ/ꫯ_󫯾Zm}}W_}W_}E_E_}k~E_E_}W_}}}W_}7~_}j닾苾ꫯ///_///+Mоm _]֖QV[_E_W_mms.¡u--닾$P4 p8͈F"}cSЬc_.OKW_}W_E_E_}W_}F#R~|ƦC;v.Qןv.?ꋾ8D_E_}=MkDƬu"-|//ꫯ7x{PMMY]O_=]|C_tξC5苾諯ꫯ/ꫯ _W_}W_}}W_}W_}E_[}c}}W_}W_E_W_}W_}ꫯ//ꫯꫯ苾to/ꫯꋾ苾ꫯ/`~W_}W_}E_E_}W_}}ї_E_W_}W_}}W_}W_}/ꫯꫯ_h}DoScVЎO5.7J/}՛N:'yW566 }޿6/?a_9$:#KF(yPoz@8{P|p(D@; h4b)Bpϔ0;d xId xId xId 4aT(s~p"¡EY%%Y8 H"@/C$|H4bDd xId xId xId xh4jp(D:8|v765 Z_E_}W_}W_}}W_}{Gh:g~ccСꋾ8D_W_}"+}ٽԘ5T}}W_}l< wMMYE_0W_}W_}E_W_}W_}}W_}}'OF_諯ꫯ/ꫯꫯW_}W_}}W_}W_}W_E_k~W_}W_E_}W_}W_}}̯W_}}W_}W_}E_W_}W_k~W_}W_E_}W_}W_}}̯W_}}W_}W_}E_W_}W_k~W_}W_ꫯ:r|/7Xpojj*苾ꫯx_cݡxi}}7#F'iħ-///ꋾ苾K7_/'}}T-:苾苾苾/r/}}E_E_E_}}YuuLjkjb_E_E_E_}}}}//苾苾苾/&8b~}///ꋾ苾E_}}}}E_E_E_/A_E_E_E_}}0///苾苾?\E_:苾8?W_}W__K/޶hA_E_E_H4mm;}O4MJ sNJ/)˳B}///ꋾ苾gV(Lǟ5FoQח_}Ǔ苾苾F(9$/o/)YNߣ苾苾,u߃*.*.^^$o_}//苾苾苾/rV}튯mzz9gɬ+]wkf^1i/ꫯ苾苾苾//W_E_E_E_}}}}///苾苾苾}}W_E_E_E_}KpE_E_E_}}}W_//ꋾ苾苾諯}5苾苾#}W_}u|/ H4^o/)YN_}E_E_8Ӓ )**޾||}[~$+EQ9[6mOliiIIIʚ:uW_=y}ջロ~СO>9wܓbÆ =؎;~>`СÆ 8q^~iiiǶ|w?z+3gnܸo~L7BxxeA oX}W_}W_}E_W_}W_}lPやh4}[RSoYg/x}W_}jiiNIIeA r̯苾ꫯ&Bpc%#qZ[Rw7E_0W_}W_}E_W_}W_}}W_}KWû뎥+';y1諯ꫯꫯ/ꫯꫯ _}W_}W_}}W_}W_}E_M@5W_}W_}E_W_}W_}}W_}_}}W_}W_}W_E_}W_}5W_}W_}E_W_}W_}}W_}_}}W_}W_}W_E_}W_}5W_}W_}E_髯ꫯ+Wƿp(F"xҜ/}+:N1/ꫯ} innI苾S%a_y{PTT^;|{wȑʔ)_~}>k׭[wСV:.ڮٳg^:z;SSScǎ7W^yo<3g~{+))_FԶ={,]"p-E_E_}W_}W_}}W_}ԶtE^^ƍꋾ8D_W_}eRSSOfŊy[lE_E_}W_}= }KWnq/5ꫯ/ꫯꫯ苾WN nwU'4}W_}W_}W_E_}W_}W_}󫯾ꫯ/ꫯꫯ苾 ꫯꫯ苾ꫯꫯ//_󫯾/ꫯꫯꋾ諯ꫯꫯꫯ苾ꫯꫯ//_󫯾/ꫯꫯꋾ諯ꫯꫯꫯ#}W_}u|J_7u`{{۞=qw¼-[6//Wmmqt銼E_ƹK/͚5+s9 .׿;p7xgy衇_~9++/_jUp;a„꧟~\pxw̙hѢǧ=;Oɹ˂s=?74d7xР<)$닾諯ꫯ/o|o7x8;$}E_O/ꫯ2 e4xƳdrr&}ss'/ꫯ 2 ˗/_|_sݷwĈAoiHoN1_˗/_ݻoРz\z>_Ub΂{Ko̘1D"/ĉc˿L0[ojmm:tӧx[n{ᇫ?a _wySO=$.䒏wŏdWNM'>"֭׷5W|W|W|ŗBp1ʋD27mZׯ+$Ir?)+{a uh[M^^VffdݺM}z++P(H_\i]_ߔ_|%___+++|W|/?x=;r&ii5dԀKڷ _+++W|W|W+++W|W|W|7m___+___l_'ͯ/____̯/_____Up˗/_W¡p17/+33nݦ}JW|W|y-#DʼH$sӦuS1|uA|VIWb'|2 [,c?XSSS[[{嗿uuuyyywaV7ΦMYRRy把իWĉO>yE]gTVZ:˗/_|_⫿wW;=dߗ~6oޜ%K'W|ŗ/_|R®]o'{4gμҩ/8 JXO~wxsW^hQ^}ً/8##qΝ'OٷoO~wyzgO'O~/{I-o)ѣqvX^9˗/_|+˗/_|=uuǚj˖h;0`9܃:uj\s͙<}!C~Ο?c?eѢEov?oqg9\II͛+**V^uLGz3EL0Ԥ^|_Ug1wEL<_I$__˝PV'8E&Ov6*M|%>Q_1/Z>_MPTEo|{߻K~GI&6?x`eeeNN~V=p5vmwߴi222n,[lƌ}=3mmmӧOOM'?<裳 gd%}ᇽzdla9˗/_|/_||kjv>Ys^]2v+$Ir?)˗/_k}sgedʓ[RruaXW|ŗ/_|o]]m(`_ÏΝ57#Qo%+5|˗/__|˗/_+|\l_lϻ<gw&}}+˗/_|W|ŗ/_|˗$˗/_|W|ŗ/_|˗og5|W|W|˗/_|+_˗/__|˗/_+|˗op_|++|˗/_|W|̯˗/_|˗/__|78k~/_|__| WW[ ϝ;+##\VVLߒ 7+|+U袓r~YI]_}uXߴ'|%ۿ=󝆆Yzv]},^:seoyg_ǚXTTVZ7thѣ@_˗/_|+˗/_ĢҼJ${AWT4lмd=?N/_||߉EeC+5|˗/__|˗/_+|fcu~SSCSCy2+|˗/_|W|˗/_|J2|˗/_|W|˗/_|y_˗/_|W|ŗ/_|++|JW|ŗ/_|˗/_|5/_|/_|W|W|̯˗/_|˗/__|78k~/_|__|J|uξEKKˆKo0+|+וĉEeey[3z>_Ub΂V4D"'O+W~8}^x_N:w/~ǔl޼bթqD`= ,(3sfϞ=MMŗ/__o(?bQ6,9`W|%I~R|W|3 uV'-XpOAș3_++Jp8Թ>uPgAȂ+̯/_|W|W|W|o*}oAnAf$%)O`pd#Gۛfa~W|W|ŗ/_+++|%___+++|W|<e~W|W|W|%_|__d̯/____|$˗/____++_++#|+A. ?]pOAș3_++r].=,9`so~VIWżC6gΜz* \sY>?իW;7Myԩ!C7yg͚~p8r-/'~LII͛+**V^'v3nsHffWKkQde߽ۼtS|˗/_+˗/_|=/b(ۼuk}UUe$pߖ֡CG +$Ir?)+|~BA߭ZYYI[[[F5`/_|Mp8Թ> A5\23֖QCGe +_˗/_|_˗/_|+˗oiwّ;(7Ѿ{ZG 5a~W|˗/_|˗/_|d~˗/_|˗/_|_5/_|/_|W|W|̯˗/_|˗/__|7Hk~/_|__|˗+_˗/__|˗/_+|˗op_|++#|p(Fcm~~keeUff$Ѿ-F {~W|ŗ/_| x'Rn֭UU[ZZ5`@~t555ƎW\YZZ Nyĉo~ww\x/|E?ݻE_sηzwyg{۶mgyfҥ|ߝ8q}L[[[~~SSɳg?pa# N:]:qbWn/_|W|W|˗/_ںcMM5Θc Ə ߜ҉EE旯J$|&hOl >6~|QAfgLXzE/_|MߺQj [8h|A2ֿ9K'^ +_˗/_|_˗/_|+˗o_UF\Cזo_˗/_|+˗/_|W˗/_|+˗/_|W|W__|++|˗/__W2/_+|˗/_|W|˗/ e5|W|W|˗/_+˗d~/_|W|˗/_|˗/__k~˗˗/_Wj5=cƏ/*(윉KȠ_˗/_ELW4c[XT4~d299٥W|JھಳӟVUU>|[n {]v^駟>zhQw/^ӧϧ7ts=um۶]~孭_GQFѻw{?я?%|_رco|wƟ痢gLHf怬ȼ6)|W|˗/_|˗[Ud ߯FW|%I~R|W|M**23#YYHw=__|7(U̬d#_mW|%k~˗/_+|˗/_|W|M Y[+48f W|ŗ/_|˗/_|˗/_I/_|˗˗/_|W|u&ͯ˗/_+˗/_|__|%k~/_|˗/__|78k~/_|__|˗+_W|W|˗/_|˗/__k~˗˗/_Wšde How=__|JI"WgW|bķ}no+zAmݺ(#/-iԩ޼y?oQ\\|Oe˖]q͟SFԔ:'NyU_;gN&%W|W|W|ŗR7cӛ|.߹s/_$II_T(>4j[Ι3wlg7u|v̝3|W_˗/__MQ&W^Wa~W|W|ŗ/_+++|%___+++|W|e~W|__d~+++|__ e̯/____++/_|W|W|ŗ/_++_++#|p(FcX[S;g___^F k?wlt<+YZjٲe7o޿4 Æ +))Ǝ{cfϞO_|Ň8۬Κ5??o߾^zW^y^{[[[O8qE]r%_~yUUՌ3/W(((hnn_MMMsOfkׯ߶c}'IG |vɶ˗/_|W|ŗ/_|{^ ]x|׮߶m}%72v_+I$+˗g*Կx(~;͟_}ѝ;۲s __|>ps}]~Ƕw_̯˗/_|˗/_|_7E۾ 3ђC}9>7+|˗/_|W|˗/_|J2|˗/_|W|ŗ/_|+3k~/_|__|˗+_˗/__|˗/_+|˗op_|++|˗/__W2/_+|˗/_|W|˗/d5|W|WG|˗+ lP8Ƃ~Ͽ/Ѿ#tζW|ŗ/_| x'RZvm;Kz?|mm;sW|JھP2/BEѳ|LV^ޡ_o>dР~B]~ێ͟[˗/_|__|˗y)bQ;t׿>dHb}|+I$|˗/ P(x(^^^VGǡ=hАD&etƗ/_|+,EuPyYy:zĮW2旯˗/_|W|ŗ/_|Mj#W|ŗ/_| .?b{]YYyuۇ$vvmv⛖B)|=]>;f̈ ]GFW|ŗ/_|˗/_|^wt~)ᾕ߈s$IO/_ьw}{R}8+˗/_|ȷk3.^_+_˗/_|_˗/_|++)^5+454%.8zh˗/_|_˗/_|+/_|W|W|˗/_+5/_|/_|W|W|̯++|˗/_矯/_|5/_|/_|W|W|̯++|˗/_矯/_|5/_|/_|u%:gwtR}+|+ו]/%|_YJ}⫴,p)&??ƢѠ='|rK}]wQ|UR/|W|W|W|*|Cp,uē[u旯J$9 Qj~ߓO>Q_;bg7E}GOPy>/_|W|W|W|J2++|W|W|W|_+++W|W|W+++˗/___ N___+++___˗'e~W|/_|u%- G1}-wqUW;|W|W| .?bQW'ܲ(~_MBp4}+@'qEy+W>dI?__|˗/_+|{Av^pQ[B}W|%C;|W$I'W|ŗ/Ꭾ?NYȃ\bɒv˗/_8ś+}⫴,Ŝ)&??Ƣѳ|L[{#"-- {b67oSƗ/_|++|˗/R(ŢX>hЈH$#6oZ_UU?.+I$+˗g/ GAV6bĠHKK77[_*~\#W|ŗ/_|o8G lIU+5|˗/__|˗/_+|t{$wpd#GۛfD`~W|˗/_|˗/_|d~˗/_|/_|W|We5|W|W|˗/_+˗d~/__˗/_|+˗/_|'ͯ˗/_+˗/_|__|%k~/_|˗/__|78k~/_|__|J|[8FcAmo1bPff#qo׈_˗/_ ⏱X4^W[[A#"̎}yʈsoZ >+7_gsfٳz+++W|W|?p8x޳`Ȃ9s/_$II_ߠ _<o{ FΜ9oJƗo CѠ~Ԃ{,i˗$++W|W|W|ŗZܱgGuO!\:`׾]N_˗W|W|%_+++W|W|W|5+++|W|W|W|J2++|W|W|W|:|W_+++W|W|W|ŗ$++W|W|W|ŗW|5+++#|p(3gᛚ˗uϹ ⏱X4^=,9`~ƗX(KϗŚnkaUWףG_I$__|ϭP(t[+,6˗I?ؐ?p_˗/_iA:W2i˗'d{ƆƜܜ|o__|˗/_|˗/__MQ&pW^Wtͯ/_|˗/__|˗/_̯˗/_|W|ŗ/_|+⫏e5|W|W|˗/_+˗d~/__˗/_|/_|5/_|/_|W|W|̯++|˗/__˗/_|˗/__Y˗/_7Ch4PWOv}y$Ɔᆋ/_|uu,¡c,M|+_?m5 Y;v177gso >E [ǚX޽/A:]ߩ7OKwHސ+5|˗/__˗/_|oz<=qp?{8;__|˗/_+|˗/_|%_˗/_|/_|W|W_k~˗˗/_|W|ŗ/__+˗/_|__|78k~/_|__|˗+_W|W|˗/_+˗/_|'ͯ˗/_+>˗/_|_o :# o>So !yg,+|+Q]]m:^Z7O~^ސL4sJMzP,sHi7P1)O(-.~^|_UJb4߶I*JK{_I$__=B]P4j[**&>s___o8ܹ8m*K_d~W|+++W|W|ӦK8cώAz=p]v9W|W|W|+++W__+++W|W2˗/____++/____+++__˗/___I旯/___d̯W|WG|˗+ ZP8UTL*..} ++r]]kp(ES[[Iy?q c4}+@Yzqҩ޺v>P}ccKkk9__˗/_|/ 7e+ҩSo]}Z;:/_$II_ ԥK_֩ׯ56ַ>a/_|7%:ݿmolmi= +˗/_|W|ŗ/_|++TcSCSw!F휛__˗/_|/_|W_|˗˗/_|W|ik~/_|__|˗+_W|W|˗/_+˗/_|'ͯ˗/_+˗/_|__|%k~/_|W|W|˗/d5|W|WG|˗+ ZX4K_֩ׯv֖Ç;__W+_\:uk__4S ,m۾}ÇO./]7e7s+˗/_|__|/xmۗ.]QP0|nnΝ|$IO/_VX:|xAyn=Д)׽N|W|ŗ/_|m+ /림iW|%_|˗˗/_|W|7*WfƆo]n=P18W|W|˗/_+˗/_|d~ŗ/_|++|˗/__}Z_˗/_|W|ŗ/_|++|J__|˗˗/__k~˗˗/_|W|ŗ/__+˗/_|__|78k~/_|__|J|uξ۷Xt;7ɗ+וҶmۗ.]QP0|r7nN}S loL)+-:u겯} ++|˗/__7|SV_I$__|S)S---K׾vW|W|7M)Mkֿ|W˗/_|/_|W|W|Ӭ3|SCSw!W|W|˗/_+˗/_|d~ŗ/_|++|˗/__%ͯ˗/_+˗/_|__|%k~/_|W|W|˗/d5|W|W|˗/_+˗d~/__˗/_|/_|5/_|/_|u%:g)䖖%k9|W|ŗ/_nJnnNYBY_wX,,Hi7P1)eE237[ׯo_@_+++|W|p8:IʋD27mZׯ+$Ir?)+{΅BhԶ/eefF֭Էo?g7E}T7+/3nӺ+̯/_|W|W|W|oڴ}oAnA̷ttG=ry`@/_|W|W|W|J2++|W|W|W|WW|+++W|W|W+++W|W|W|78++|___+++___+++_|/_|u%, G1yyYu6+++Յ ⏱XԩaWW]SSTV}ػo#Gut__˗/_|/_)[]STT֝{>|СjW|%I~R|W|Mjjʊۺ=rpG!/_|ڷ;׿;rHǡ2_d~˗/_+˗/_|_ߴ*;';k@V䷵v_i=zKWͯ˗/_|W|ŗ/_|+/_|W|W|˗/_+:K_˗/_|W|ŗ/_|++|J__|˗˗/__k~˗˗/_|W|ŗ/__+˗/_|__|78k~/_|__|J|Tw=rpGǡ}9|W|ŗ/_kںswÇ:z*H:{M۶mmhR_M8޽-{8|W|ŗ/_|++|oӶ[l6ǎmiW|%I~R|W|MٶmkjhZ_%''1p`޽iq/_|ڷi[ֆ-9ݶ=pﱽ{$˗/_|__|˗_c ׬\Д;*Gl__˗/_|/_|W_|˗˗/_|W|u5/_6__˗/_|/_5|W|ŗ/_|++|˗op_|:|W|ŗ/_|++|J__|˗˗/__k~l__|J|uξۚoߟ޻؞=-6__W+m[6lRmǎm+JBXYnCc,,Ӆ_~w9vx޽<+%8|++|˗/S(Ţ4?wKoaw;ާ+$Ir?)+|Bh4j}/|?ֻwW|W|p(쯃Z^ޝǎ_d~˗/_+˗/_|_ߴ<ȃ}?xtj?p++|˗/__˗/_|J2˗/_|__|˗Β++|˗/__W2旯˗/_|W|ŗ/_| N_˗/_g˗/_|W|ŗ/__+˗/_|__|78k~/_|m+>˗/_|_o h,о[:}//Fzc~/_r]]` pوŢt s>+BpXzIgh W^}6__˗/_|/_)[4~„n{lȗJ$囲_t{߻9'gvIlL!5y ģM k[`Cmt& c:"ߟoiϟM[_E_W_}=w7}<|r/W_}W_}E_E_}W_}}}?!a}W_}W_}E_E_}W_}諯ꫯ/ꫯꋾ+kk/ꫯꋾ苾 ꋾ苾ꫯ//ꫯpꫯi닾苾ꫯ///_//ꫯꫯ苾諯ꫯꫯ//W_}W˻~=l߇G}}+_?_>}hVh<)5?ix2.˫E_E_}W_}}}]Gquix2.˫+}E_|?苾i׫xGId\ W//ꫯuz<oR[[?NrW_쯾ꫯꋾ苾ꫯ//vϗ/o8?<}}W_}W_E_W_}W_}ꫯ//ꫯꫯ苾ꫯ//ꫯꫯ苾諯`苾諯ꫯ/ꫯ_苾諯ꫯ//ꫯꋾ苾ꫯ{7׿/ur=n }ꫯ//ꫯꫯ苾_E_W_}W_}}W_}W_}/ꫯꫯ苾諯ꫯ/򮾇_苾ꫯ//ꫯꫯE_}W_}}}W_}W_E_pk}W_}W_}E_E_}W_}諯ꫯ/ꫯꋾ{//ꫯ>r}//W_{{l8|)֏.苾諯x]~ټ/k󰹌my 苾l*}桔?tje .pZ‰!C# W98Egg+CJgyjjjjB#%s*B2לPJg~ Z4ʹ5'٧jjjjB#%s} NT)xs1ޔK6R!kN(IO?z@-Ԣ@-Ԣ@-Ԣ@-Ԣ NTxR0ޔB2ˢ@-|@D}~g9CMdC,Ԣ@-Ԣ@-Ԣ@-Ԣ@-tPil! R RHpr.ds` 9tGE#Z4E#Z4E#Z4H@y DXGq2bbu -b+Ȕ BUU]KL%m8_Z"V@! ڭ H%soY S<{s@}'S}9>ӲeI'%?hև99$ ĬT>a·| ϵiwnn C9|"ibR޽9Js9餐srd-&]U,ďA}'>p 9!V9"cId^۶-޿J4Qmk_+k)]iiisss=>iҤO?Oر7loׯ_V:u4~Ohv6s"9AUxrz?ئM2N<޻`9s׋zO ; ر(Qo޻> nSOmݫWѤI?O~t;_Jԫ /Ӷmm2lJA 7k'{ë*eE //ת{Nߒ!zdUBG 7o>묳Zn]TT4~g1!* ׮].7ސ!Ӽpg=zM:ܹMCWlowvZӮ7Es^yw>sмt S*h^œ>C98C9 3YU9ͳ dV4IWB9O @ի4szI5>m*^œ>C9LIu_^œ>C9'>U*^Ay3ē>*.W!g'g} Uԫ5nIg+gK ?WnA} Uիxrg7gK ?WIYȶzd^s ܜlWAR9'+I/gKϐTgd[n'4zy䜓V @+K'>C<93TC9 \UK'g}psvo+N'T]L93Wi|V 1Tpsg^5g7g*TTuoxoX k\_<;5qC9<ָ^Wo귗?777??7|Ygպu뢢:nEEŰaڵkweɿt;w㰜'Lo߳N=u^E&}nN;ݕW^o$?ָ%EEvp7qc NWUU:Q\3/ת{N$qSߺI{ޱ㉉H qƼ^j $&Ȋmu&9]z]9s:΃aussS朝UU>ݿC'M~Oh}ѰMr~Mr뿮c7*1$&?kժUNƏ|՞>-ZضfkB(cǎ6m  /رk˖-8/\7o~WߖxSG?ZdI 7x<߱C8G|qٲï1bu,_>h@^_;=zwq/\8;7Sر*<<_ kl޲ qG{ϞWC6qEn͉KfxMPQωAF}]滨Vnm;W^}^5xAN}'^}k[w9F{f}~ɟ5k6lٲ?QQlًÇqu/eA22[Y*[~+ny!IO<֭[kMYnrKAW_}>èݻw_w-((:t '|}-,_x~Mm<`쨱{vY~ル_hrN3/Fkϯmzmk+oS/y klq8h^%e˖7|g;u=vGSyًSƈGۼn+*^s<O @f՟OznAg}xrg^ ;N97g^իxrg7u>A) ԫxrg7>m*^Ay3ē>m*.Wg'g} Uԫ[sdRU<93szFȶzt\'g}psv?ԫzTK!lWA\*ܜS}^d[ \znΩ_Iz9 \f6g}< } f6g}psv?ԫK%~.rg^y^j<9YȔy|g+gKԌn'4zyY!ܜOދ޳S9hmOz@իyV ={zd[xrN>CX9 m*U<93{ͥ^ԫxrg=gExrNń>zoիpsN 7g} Usvjz~{rNJc\roƵ 7簞ָNu kT+7yaƎ;mڴ>[n-5uG{׿CMԝߎyGr~'^}y yg]ncǎڳg|kxX^R6Xjʔ뮫K翮Ya˖?i9/[bF~ݺ˗2pxMr~w/l޼#Ҩ_Ƽ^f7MsMr~tS7M/2M5͹\Wyyǎ=>tР/;6} ܙ3XnzySۣyԨwY~YP?h}4ƽWUm;?K< l޼Fhr~e%%ï~^yeE7MsXuQ_W/bII_yW^yeРA̙3;vlun U^^~~UV}с_Nh>^}^Y'9s9S>:]S9xwu,]jIk֬8jkesbиqyG0r9kx r~pݺ+?7'o^'{;vwK{~0sXA9'Wb{+KK|r?~˵^{ @&<_kӟeռ`Ayו{_|s'3wypʔY=v 73&p#~ƪK22[Y*O8m$1gN]n.,mڴX3q:FOr慃~]s5:On}Ͽs%[>5kox[ھ6-$yyeOb}Ѧ[+i[[nkQ~gON9{S~C<93YU/HJOк%>W!ĜOh.*^œ>C9縟 LUq*dfQի\ d[ W7g} UNHիpsNJdfQmRcy{>C<93GK'$>C9 \Uf4g}psv?+Kl !sNAxrZ9ēm*dzSʹ^C<99V<;5V|{VR=^œ>C9 \Ugf4g}svoY+N'T],psg2URznΩ~?d[jNMc^o`Ω^Wi2P-ָWֳS~0ܜީ΃ajawyy̙s_wuK.=5kL8CG_AO& rׯ5\?vnؾ}MÇ?.㡌{_)m[[n믝1ޚϷ?gv^\s]M_5SNĭoa#.]7_W>UAAT ~yO>9ϏmT+7yΩMr~tS7M/2M5͹\WkЪՉ u^7l'?d_?ྵ~]wRhr^fĉu1G닦1?Bz؟ə3~)_GN='O1z #E/ָ^Wo򜓜_Qu5yѣG95y^֭7o?nkTZZ߶mW_}駟N?/'*K^ڷo?`DIr)m۶}>}y'1x"5{6h͹[}zw^.]\~k]qZKΏkx;**~sΩ]}Ԯ_:^%W\17IʪW^~yҧѣmA/~uusϜXxJ|mlp\&Qσ|Z_Esl༲.]rEW:48Cǵ ]PQσ k}qz 7F=&*"A۶Ul~>juGι˗-)KJ?7߭;+y aO>׫yw05kb3r]'5swG͚eN`t9Ƕn7Wr`9cUc^/Ut93{ 63Y⥗s^IԫQ{=ԫsxEŶ~9ukĺ׉.ܜ+^޷եK/vW γUgΞ}ouo̺/_~yVZZڵk׾}{g9̟?핕sν+ _S\6KQRY+J߼ʪҹ6- U97>Ct93YXc9zg} իxrg=Ƭ/PY!Ĝ.^hsJ0ܜlWAuIn_vYȪzt *=A$\}sdgVY!Oh_.g}'dsJV1?ϥO @ի}zEi?/dajsU97JXO R3>Cښ< } uI.g}sv?+K:4~.sgprN9s}xrg2e>C9 \f*g}psv? ٜBdlV.O6zda慣>;^!/xvj9b-5M)gf6g}й мxvjrb>%[*ܜ#~>vj2Fs˽e_{~c~vQǵn,ƌnqۗ/_~yd۷o;wW^Yp~_ZާO]nW\QUUs/.㡌Voܸb[bsΩ\s]) 7yp}ryti֮]ø]Wlj.#k_<] ~̃{=>6~܈Qϣ7 :΃7Mcu }ӾhzuUZt~~m ^}yw;jV}/M%%W̟߯;&E]{cmH`>:sN_~y' p&E?nו<6~>nUr֫W0`͚5sYs8 :tժU{m%޽{߾} .\`;s9 80AǛ2zUUUߧrʁ_&jA˖-|u:_s{Ԝg]PV}|;ۓƏ?؄SƵao<ؘשU96Ι7뮫9X_EМ*tAyF=68UN+,{m5+q1m`Ν:Mޞ4Z+ZڹS W\5{=Ckݵsg,({s%{1'f:QgzAD]Fsl9spuQ;4 UG}WN+izm[{gwN}MOJm?65d萿ˢ?.`wvGTyO]lق?K| a :s:K4g}psO-|jŷN:a>#U9783ē>A ΧA_ 7g4g} UsI/ @6?WzG]731K{_^E>C97e]JsѤsJᅨ1ܜlWAuIn_vYȪzt *=A$\}s~} ;UzO ~2@WU93Du? UzDy.}xrg^sի(rNy>m*sU97J[O R3>C~> 1uI4g}sv?Y+K!4~.sgprN9s}xrg215MyV!OhR3>C9 ЌKl !~2@6KcovEk=xrs^yvj<9yzd~S#9(O}svoxi:}踷 <;593˽efdzS#iz @6hoիpsI)7} ձT=e4Я#uoxoli̸W8ژqۧ=5y귇slSnڴiҤI~01nΝ\rɿƞ;r?&΅Z{f'?ʸ{n߻VUUm{fݛ8M[(r޳gw<²׿s甔 Quu2d_jѢ?&wVS_/?¬Yu:MޞM?t\|)+W݄h~[C.|j'MVghMλk >`A;?眳,ٛn_{]9IQgF}]޽_`wys8p޽{s?lFf͚UTT4ha{^N:_<0w>M6Fݻwnnw߽m۶D :ޔ۶m={*QN>c5 7z䑇~Әǭ;w#G+u뻧MVQr* ϜYV^t:v־>]ծ'x;dРwmZ]Q1쪫t{?55o~{疖&خnsx̉Ǎ:~wQσAV.0Y\\rL̃ k}$ySHsm\?eˈk9X_E㈜c*\AyF=k}ee׿KkđCǵNP=>|۶ϹUGιӧ,++_tqǎZ\SK/T\}׮Æ]/]7;y'5{m}ӶmX291S1zaYy!s"p̮W#9uҥz-#F\c?#r~Xu׋zi [~ƿ_x%^Z{Aȸ3-^CIm <2?}Hb|6m{ʺ=k#GիwֹӦ]Qmժ2UY*yaK?W\Q2}=Mc]O^VVtҎrK/={s==x׮]Æ җt 1 g/^?-Ӗ\3ϺdP[wmjU~R7O;<7nȬG~/\V3 /PNV N:) sk~f?;իr>g5W^s[O{ZŶU>䜃֙AbWGW=(b=Wo>#U9ͳ 1dH|Tիps>*}Hsg^5~!rN8i_ /VbY!Ԝng޾@4g}psN{]zI)Tc93ٶ/ KUD# daj~AUz3\}ps~} +UzS ~2@ٗWdW ի&js3Ĕ>e*h^Es ,W ~.K y_t? f2g}< } KQzi Q~2@WǾ,ş d93YVC׳{|M893Dýe,٩Y!t-4/]L893ߪWRn @v֫c٩{<iΡ_Wϗ{S7⹷L#ǵ y_׳S9`Dg56r\polӧ-]cǎƽKwU]]=lذ/}K{ ~{~gN///[xiu9tI[a^>t_WMu^Woy wl^WocF(93UAA[>† gz}3ʗ.]Q=kナڵbذK?nॗ{,.){~l U|`>j޽{涾i۶UL/qýߠxѸQ_WQ{ͽm۶rʣ 6̚5롇sL\Dٳg-Mꩧ^/_yN8ᬳ:V^K;v/uvW8zniiY\ L)+/_w+*:zO |ݨd\#U93sW\4 7g} UP]R"Zg'g} U Rn 3s} ՁzO ~2@sWzu sOz`YjF<>C<93VU91 ,W~.K 7ޯ$dWғ,Wzu Q|(EԈrN%n @+Km`Y! JR/RY}gEԘsg7gK:dV"k=xrs^yvjF9G<;O!WiAy3~= pYzn~?d:꽧̃Quu2ԍ-q9g6~\^G >5y귇s< &-[[nܹsŊcƌi߾}nn޺e˖k}?:S&ˊr۵kʕ+FӮ]֭snju-֭ :ָ ]=·ڶvNRXxZqqΝ;u:WW]W_<˹ 76j^Wdz{^^ziΝ;ݺ}̃_]Z:7qý4^wuu<1y0]Woc]^jFX9/رw/uv_z} Sʗ-}7hr޹s׊+njվ}ַ:f˖k6x=ڏ|꩝V3Rg8K=۴ٳE}Fw]7<&эuզM={~~Ea|;v;// .=wΝEO~G__/gΜoٳ'QYn+OLM7c_>͓&M*,,۷oMq 7$glڴi'O:thv1lȐ֬Yđ{W_7~Æs7cFaAA>}4{}%7Ey;&_a ^cǪxuͼM7o3f 2)0nו8[QѣsTVU%~6g֠5{*+W\yĉW+IrtN 7x4E=&Bo߾lI-_>qD=a *I`иW1xwK\H`1̃ǵ WPQσIΣUVZ-;wJ6)S[;'>7iN:'hj?C1s o? 9sիrgn\ܹ+Q:cڝw7n̢EK:v;t>m 2l͚j*\]^ls=c9o͛ z)z#G?aCX͘1Ob2U̳U9۷t-++,_lԉy6|Arcƌyg,YwXsݼy={-[6qġCu裏VVVfi3#Ǽ0fܢgvo;'xsuSԭhΣsj~$MnO}tssȰ!kZSھs?whU+Wmy{ˮ;M3m޴Ϙ>Є$OUzC \[e˖O85nL|>U9'gYȈ$iPW9C @ի4s%^Ag} 9 իrg1$F} #W1z OFȲzI#4'ܜl/%*ܜs 1daj~AUڂ- sBdfQ\zO !~2@3/WbY!sOh&z)<>C<93VU9BYȶz,*uNW^9'4zyY! iURc 1dPPxrnpg7gKԌ:dfQ RgƓsF!WOIzz1d~!dzS9(O}Яg^z\'g}psvoY+Nl 2@3/xvj9qbYTJU9>mxvjJ_uҸ- 2k}:6g2`7'}΃ 41cy%K}:nnݺ=裕gnӦgh3풎?-uQQ9sGfaj-[޵ks״iwLrgۯfn޼Ϙ1yȐi =#o۰a}<8cRAAa>}7_?7ߨܳr劉o޼ ^ f[];?[3>W3qӸ9t4+7yA1y0qS4rnF`J47Bfμ7ܳrŊ2 V?onj3,m~{d9'֭GTf~M3Ro߾sK7oޒ8l=fμoӦ۷?yv|M#{k&(,,z=qӸ$9$3qj{ 훤 6쭷Z]g=wkzĿ sac=z=]vY͹; e5oިc^_:%NhuEE"j/>[=N?}Ú5Qk옔9ݻzLL jr+//۫to|caѢEƍ޽{mw۷0Ca <^O>`?9O~jS]u>jҸIgE{En/ڄlS/o/ڄ_?'ǿ뾻\g2rO兵4hѢ?~$Q |Ǵٟ7g؃u:SWԾ^œ豣eg8 } 9ן׫χԫxrg}|'WWUv),FoO?@UNCg*Y}xrg2"|TիpsnO @իĜ>TFkdἐg*͹u>A) ԫxrg=>d[J󺤑sn @zn9 Yzt *=A ~2@WI>^œ>Cͥ^5x\'g}sq?ԫUS\ dUJUB90ܜ,/}.K 7ޯa9縟 Lf6g}< } f6g}ps3VW^/K =g} Ɠsg}xrg2(.y^j 9ͳ @ͿٜdRmxvjJ_u2sºLXZ_{~zvjX{~s{12~{7~9sFuN:)77z=PHqqѣdž _,ZhܸqݻwOۻw/۷o;[sN'n[QQgr;k_-4iYgܫW_zQvkܗ_O~rkWTVskxq4G.좚yܾ .>*qSD5yGf'/sI!Y|"Q6-ZYzXǠ>8sBV|2m_% Esw8t'']{u޿_&pO>ٻurAAe{G.}x 1;㜏M?)8*8v 8UUU?oz@h׶,՘'sPǟKEUUbS-ERP_K@}-EihѶjBF۶-QmVϞDQֻʓ(Rwi/& (Y@J"R"E}9:B8 T)8!Oc<d!B @",DYWJ\/7M)|tz^EWկ~_WWկ~_>IO>_yn<˹M/^Dܸov>XίUt`/}](vn_ `/h ks _bk__k/_څ66_ `/v./`qu_]h ks _bk__k/_څ66; Ó}ߟ^WZWy__6Wկ~_~V\yӛt* 0El[!<d!l6/88Q*ZEֽjXubպECBQ 51z=><'7{};Ǔ|Ywvԩf͚͛7袋͛G5kֵ^۾}qqq?cqƇz_~ 4HHH^>p/ ..{E]wumڴ ܺuna%=^w_z=ӥK:uDFFOx:4iR薵k׆nYtimС>O>I&SRRZhqye>1}ْ29۱"_+_W|/EW| '}?)_Sٜ͙"ccJӛ?]ٚt MX;}Ombjso-Y]; ϵ~nXⰧ?Mk-t=H#O|Sںd| lߐQG=P,ٜѿݿ_~P' zq39]1v?GMk- ^Ȟ翏=RG lv=iǿE/9˻>z'j>D'>#'腃܊ܒܰלzzz_ϭC5gsF^0gsFV l0 nk-,937k-,93zz_ϭ5gsF^0gsFC^0gsF^%p3<לzzz_oEoIgd+937k+([k׮A)S|/E|Wr[ǎ{goܸe?_uUWvv^:jԨ`47~'<Dx!Clٲe}gѢE?rz*x999ŷ,Έ# 'ꫯdddRXXXy9os̥K=_y啺u|@,7;!9!H"_|+_"_|/EOWx?c#-ŽWv<;T)*,YC:gG֭!R9U5e+ΙqNLr3Fx'x(W i 锱2cC3Lpp(R%0&2r}/ת5н~5[Ֆw睈?Xп 4?#j%do̞p1 z0^0g|]w mGGzs~AOS[l[Lп ]V?ߘDDF9F/3P>܋(ݏg}`@=htC_⼬OoG5B/<憵пlz߿B/Q@ZX腃P7ZX_sܿB/3z߿B/\տBQ@ZXs6 (?% lz/Tל߿ֿB/Q@7WsֿܿB/\տֿBQ@Ws6 Z `F__SIgd+9رK.袋Jq.GGGm6ɺu6mT#}ԨQ 7l}͚5sΝ;wqѣq^`Ӌ&Nؽ{iӦծ]lA/+#?sGY6izC#7k~倣Y;wdE\zmox;w ~9s8{I8>믎/=|9GSTQa\yʅ5o~5;7FW|+_W/E|+_Wh~6g_7(EyO۲dKB&߾e+?Z%'&9qMZ >_>ۃ?f.9ߓ?S]^;l iȺG=Ce^Vޢ>H8 i >"*`Șͫ>p݄]nY%g~p'^{M 6l n֬Z\WW6EmcSbKE6^;c֥[vꉫ(琓9מM=45""bü Fտzm_}쐱N6ar:>o3>97sMfB̈́vv[a[&6p?^z͋6ITmָƭdUTlTP[Z=?>s 6?6 pGK/ gW$wqu]\9s]pʕ+/{lA/_臘fWI]?ྗNZu[%>R3m3~K7 {V];=SG|/³wBFM^sI-o>r#FW|+_W/E|+_Wh~6g_7(̇g4Jcm92nX+qmT/^zm/lyTRvQV~-]%/~sq(>|kfmFp F-8bq2WeF'߯do̞]va{=ܺҎuZX1I1?]X;}m;{Ħ+6Q嘨?g7w-5~3vNHKȬ)wO׽^n[mʽS'X;@^fqimӂg^}-oTg]MK};<ۓ&zPYkVAz꠭ZO˼'Noj@}}Qe{0zMY:$/+o'\7w~͔$M4SC2Vfq-No^{*kmVrjwN:l*93A%K& \J7ߌ}I11׌s|DDD{޲ɓgw^շ,ݺOΛI'V |?ѿ?'F9п _Mlͩۥ.k93胋>p=`-_υu|Xk;#͠*ѿweͪөNp"\_B9WGίڸjc`- â5?yt|~b`qٜѿؿE{?z^f%[k)qBe/:|λ_B9y^  l߰ ؿB9+ZXs6go8ZXs6go ؿB9ݿB/9տſk+937 z /^F9ֿֿB/9ֿܿB/9+WJ_o__ٜѿWֿB/9ֿֿB/9ݿֿB/T~Y k eW^] 7ܐ/]f͚5xjժ#GlӦW_f͚UV߻Ov> 6ԪU+s۶mcbb!_W|+_"_WW|oBrYYY=7W|c֭_y啈]7gΜ y+V<;Q:uz^vǍ7Cĉ?}jժ"#:gy#w}_}swlر㮻*gC4lذbf~WG 099y:t0~ucƌ2eJ·ۺU+gO=CkԮ|j`#g| 6 <`J9oVdnҲ}R8 8,>1Sز7WغK͚/_K̛}K`˥#_+_W|+_"_W|+_IOW:x?$sM֥[[:2&2!-͋?UGډU"TkZaܸ#nM7MiqZ:|-moW=[ɪwl%S#""Rl[m򝓛 hm]5x=oir\]EEOI&YZ7{]FF3Y?g}_cwdO&*.5'@^{)Hi䍪9Áo}{h6.ş:la՚U vAxeI9=_־fwo3ՄU{=5S7踤I= ޶lۊWPiGZr;]isUk1fJ`-p}п|۶w-{_޲err~ܽ## f_=7Z\\:u{eǎjv}kzh~϶-nF9п-ly~>qٜѿݿ38)ͥ^0gsF[թ:xnvOoT_oߜ9}NjWʓ8ל[==%d^0gsF{a~:#غ}Xqٜѿڿ Ygʟff}5@/T^se-,937 z[k-,937k-,B/2пl l0 l 9-f-,B%/2пl0 lk+937 z /R^F9ٿֿB/9ֿܿB/9߿ֿB/Tv+]Z kbֿB/9ֿֿB/9ֿܿB/\Ew3ל~M6?::Zj={<{uڵVZڵ:ccbb4i?gߺuٳg{͟?cǎ/y|+_W|/ ++Wґo}s9ޱcnݺ}iZn]'| h޼޺}cccޮݮ_`=@08rVVV )))?u6kn|O_yG}ZjvtMs=iӦ]>SN[nk GգG26zZԴڛוS?sd~WRXvF|BbZΡ276zjr5m&"_W|+_WW|+_O~Rٜ}ݠ$;7MI }T7Sk] U"g秵MpZ ȈqzQqQUcC&N,+ݞcÎ}lxL>\^=n'P|%DTxbc7g$yV(0gR7؎Nl_`dLPPj[Kfmۂir\r{Vk (%3=sgqqV?zPeW0=5/8fؔ]'lo'z[ z S?i[| %..=33Gܫu͚Ĝ9~:uٳwˮA/;ɱ,xa񃃗)n937 T/a?C `-ݺḋg;\ٜѿnܸqݻwkՠA`cǎO=ԏ{oKzb'pBڻ߹s)SϹY?<6;Rv;C&M*ppUa-v].(߰fu:l'% qƖ;O.:.8~VFF^nní6HGW|+_W/E|+_Wt~6g_7(I|yyso|df_ siJm/CEE;7 wQS[sdLdlJlbZbp{S U~6:1:?;[v۱  DT`k ;SkqE`gAZQ^0g8p *ǬY7z{=㒕\/**~~/}=e(_Ϥar^V^qn[-*.*(M~P+S vSIuk'noY##k)q93A5NI wފ/+Z׬-4mz]]; _Xx'g5לȨ?ٜѿ׿ҳ7fl3rx{lG/9vU_(nB/RoLrLՋ Yٜѿ};ɹq_`-ޔvڎWuOI9#3Fٜѿz[qS_? Y6$aJp-R%^\EVlknX k ^0gsFVTZ `_ka*ѿ5gsFV l0 l <zr[ZX_s6goZ `-|^0gsFA/\EV_s6goŞZ `-z[ֿB/TF+]Z k__ٜѿa__ٜѿrk+Be/JY2{FֿB9߿.%%eΝndff*...???cǎ2ƍ'$$|׳gر,|q"_W|+_|J|[۷/q|j6mV|cQQќ9sB۽z*?;k֬,:e]ֿ#G^{%}***K.?]k }Yhcҥ#K6nܸnV*pp۸i~M/7UK*{u#mؼmwfW͛V,Zо1t iѬuwF>๹W|+_W:E|+_W|' J\ie- Y=?{JjUWQe疝ӋW=.sM UTP%1{/\/9ٖտjxLÈ:՚Լ9Ul_}w"#L ==OiW'} ,~cqTlԾkksCokq 6sJQUk|xf]S0k_ݾm -(E/.^Iu|@ܿݼhsa~FE5连I̼vL}rDDFTo^ic/lrcqk9bƾ1SKM{`ڶv1-l|["q93\78 N w /X@/TjͫWߴ鐱c`Cvk_hq|SP]+11&?e;qXN _\8Iu:]թx`sG?vkT;x{>B~M}nO XWuꕯ&19w{ntS[RFq絗OmZ)ͽK^0gANyCfk nLmzԟzK j&4d疝wzsױƿj|k瞎1ټps5OВ{{pqW{Aآ_{>s'My ]^M=4N 7`ϓ_> W?(>5x=쑳-gTQA?{?Yٜѿݿe% NʼJ腃K'tgk'&Ҽ[qϿuE7GEFLM}Sߟҿ9[sf?6;xݫeUo^wK~'n93#&x9[r'zG(Yٜѿڿ11]?eϟ`-*Uz^s&:U;pppstٚ1/g=Q|F ל[NrUTmtl#)ٜѿ߁~zO7}0MO߫A/9˼'Oߩ >m-k_tneYsZX_s6goZXs6goZXs6goZ ppq[)ZX_s6goZ `zb-,A_oe_kaٜѿ_kaٜѿԿ l ѿ5gsFV l߰ l 8z/пlo_ٜѿa__ٜѿ__&^O{FֿB9߿ֿnG?~LLL-Cwqo#Zlr2:|!_W|+_"_WW|+QTTJKK۴iS|Mwx[o 6?<W^ݨ_vm:uJz#G^xFV.\hNNΣ>:|+V .LMMS/jɒ%͛7/7xСC!C<޽taܹĉ{Zht`cGyMOOoذM.///x?q>T]?5W8d.{ <2yPka~ W|+_W|+_W|+_W IOWx?l/6e0+?qg7v[^Efz{qQ9 A6닮W{(!/={zP"zHW:u пל/Bz/_W_YW/\-oW:u пל/Bxz/_W_YW/\-oW:u пל/Bxz/_W_YW/\-oW:u пל/Bxz/_ozz/6h O/=ClѢE|+ʞW|+_W|+*ߊ}}}}%_W-|BF#ogeegg-[6w{쬳 c 6RX~1 A"_||/E"_'kx?c~ 5B}֧29`@3zs@/3zп ^0g!3z/ ^0g ^9`@3zs@/3zп ^ ^99@bPnfC/y /E"_B29J߾}CF>_|ԩSڿLviחϋ/X{7llѣG-iO>93l۶lDQ.]Zx>_~yhu}9gggرCk.i˗:t۳nUV#NKKywƖ-[=RiӦ{,4m+ m~{o^^^gہN4)СC.]ڸq[ne~vƌW_}uhO. 111v[Ѕ7wY`AxwLj۵kxWJڧjժ=XhݻԩS-xiO8l^@<3&O\TTtw<#sLWZ駟v CiӦ|VZ;vlРABBƍ,X_W^%5\s7OO>)))˗/?~|{ǚ7o^#>3 lkڵѣGOr){m}+W  >޽{f͂~y|KtY(>>? /mڴ7s+G 7l0v}ڵK/Tvri%%%sO%$$ 6좋.*K/lOխ[wvmm۶WX|;EM4)2\T{??ԧ9SN9e֭g}>kG?Qbڣ-n4wiq/P_}W_}W_}W_}W_}W_}'778W_}(/gyIi[ïFvExgW<3U}]x< ^3약g+yW<3{3<Wg^3< gy`xg^3< ^+y< ^+y{3<W<3+yg+{3<W<3{ex< ^+y{3<W<3{exggy``WꫯꫯSn6ꫯꫯr_a5q<*`~qW_}1qf)~Ws=O o^~;w^r%w}3`$_ٳs,.-΀(*]T~ꫯꫯꫯꫯꫯ;qw}U諯Oz^g3<ؤ,--FA< gy0^Ek./}<g+yW<3{3<Wgy`xg^3+yg^3< ^+yg+{3g+{3`xggy{3<W<3{exggy`<g+{3`xggy`< ^3J_}W_}w}G*}/P_}W_}W_ r?y__}gl4O?=[ouÿO7yÿ%Izh}~㎣ځ/>묳nZT.{׿.&I:T Պwu0 *~&ꫯꫯꫯꫯꫯNo= J&IYv4TAꫯOz^g3<،$Iw0HZk)aXT4z < gy0&tTZ a%Wu< gy`xg^3< ^+yg+{3`xgʹ3`xggy`< ^3약g/{3<Wg^3< gy`xg^3< ^+y!< ^+y{3<W<3篽g+y}v_髯ꫯNo jaJ%H~諯ꫯr_a5z~?M<&i:Vꫯ8`s饗??o?ry~pÿee_~?+>kOV3ڜ/6h|ꫯꫯꫯꫯꫯ;ͬo} o6W_}J'=/ gl::|s?(g8< `*{6}]x< ^3약g+yW<3{3<Wg^3sg^3< ^+yg+{3_gy`<g+y< ^3약g+yW뫯bR ˡCx뮻n߾}K.?e]=+W_}; Vm۶p  s|{yvfZh[vie}W_}W_}W_}W_}W_}תo],ks ꫯ36KKïQP>s.[^uU,,,\{^?яn߾}8o~s}ˠte~D~md3[*W_}W_}W_}W_}W_}]۾ͬo|}Kn6W_}J'g3<Dt:|\53<p+yUl~EN3+yg+{3<W<3{exggy`<g+<g+yW<3{3<Wg< ^+y{3<Wy{3<W<3{exggy`<;+yW< gy`xg_{3<W<3{W_}W_}rNkwYQs|ꫯꫯWecyyeeef<*`~͢?뫯N2olrtM?xSO=/N~Į]z 8׸(eQ4X}W_}W_}W_}W_}W_}X8.9W_}J'g3<Dۣ?(hqQ?gyW<3۫YcEv3+yg+{3<W<3{exggy`<g+<g+yW<3{3<Wg< ^+y{3<Wy{3<W<3{exggy`<;+yW< gy`xg_{3<W<3{W_}W_}rvkԷo/V_}W_}W_ VK 2K6'elݺ]zݻϧzK_ҿ_ӟ?gwGO |?>t߮NglꫯꫯꫯꫯꫯqU%7W_~p?yY?(P<3篽g^e?`P|x< ^3약g+yW<3{3<Wg^3sg^3< ^+yg+{3_gy`<g+<g+yW<3{3<Wg< ^+y{3<W<3篽g+y}v_髯ꫯNo5F\ꫯꫯ}`|σ%EW_}7p~>ڷo߿_~AW\q '0O>I4(*]`~ꫯꫯꫯꫯꫯ;qw|}K/__}~R_}~v?3Mk?(Py^3oWgz?h|x< ^3약g+yW<3{3<Wg^3sg^3< ^+yg+{3_gy`<g+<g+yW<3{3<Wg< ^+y{3<W<3篽g+y}v_髯ꫯN} /V_}W_}W_ =y__}dl|38ȑ#__;W^9>OFq^zg۷/[yGy>;>Aiu:#ef/Y_}W_}W_}W_}W_}W_}'ַmovͦ/@~gN5:?%g8< `=*{i6}]Lx< ^3gy`xg^3< ^+y{3<Wy{3<W<3{exggy`<;+yW< gy` < gy`xg^3< ^+yvW<3{ex< ^9x^3< ^>W_}W_}ܷoo_ꫯꫯ+V̞[ez/Y}}W_LY )N;{eaaa/>ˇ_=$IrGQt7WJ}W_s5ßm۶+c=׸(Q4d}W_}W_}W_}W_}W_}X82.6W_}J'g3<ۣ?(hQ?gyW<3ثYc}Ev3+yg8< ^3약g+yW< gy` < gy`xg^3< ^+yvW<3{ex< ^9x< ^3약g+yW<`xg^3+yr.3篽g+y}v_髯ꫯNo5ۈK7Ǘꫯꫯ}`LσxW_}ŤB6o{>O~Oo{dž/ /<3^|Ņo}['to0{mFlh8ز/Y_}W_}W_}W_}W_}W_}'7VL}-o뫯}~v?3ML3?(\<3篽g^e/?`\Lx< ^3gy`xg^3< ^+y{3<Wy{3<W<3{exggy`<;+yW< gy` < gy`xg^3< ^+yvW<3{ex< ^9x^3< ^>W_}W_}ܷ]x#.E2KW_}W_}}jyUbr<뫯bRRSO=ueee޽ ÿu֏o]vwy端}vW\qy~wrg_r%w~T;KKŏRle旬ꫯꫯꫯꫯꫯg}7W_}J'g3<,--FѺAg8< `=*{ig^3< kxggy`< ^3약gW<3{\gW<3{3<Wgy`xg^̳g+{3`xgʹ3`xggy`< ^3약g/{3<Wg^3sg8< ^3J_}W_}w}Gť]f~ꫯꫯWXM=.y0ظꫯ`pM7dOx;ޱ< wum6CN=Է.,, f'xVW_}wcKsg?kN>؞27FnA5Z 2aT*4Mz2\ꫯꫯꫯꫯꫯcVװo= J&I9v4TA_}/gyƴYYY&I jz~n+ M^3<p+y; AP j.^93+yg8< ^3약g+yWW_}W_}ܷ]r#.Qb W_}W_}}jyU22=뫯b"R`,.->Jte_ꫯꫯꫯꫯꫯNo],G2KW_}(9W_< `8ESy^3ve4Tu< gy`xg_{3<W<3{exggy`<[gy` < gy`xg_{3<W<3{ex ^3약gW<3{\gW<3{3<Wgy`xg^̳g+{3`xgʹ3gy`xg }ꫯ;KK.6p}W_}W_}W+&Ξ<_f\}}W_LY LnA5Zy__},&hߣ[v}W_}W_}W_}W_}W_}X8Xܾ٥嗩 O/gyYZZ~)Abxg_{3<^eȢiK}_3`xgʹ3gy`xg^3< ^+yvW<3{\gW<3{\g_{3<W<3{ex ^3약g/{3<Wy{3<W<3篽g+yW<`xg^̳`xgʹ3gy`xg_gꫯ.-.3K7\ꫯꫯ+Vgσ}/-./Y LnA5ZaT*4Mz=}W_}W_}W_}W_}W_}Xz~?M"&i:V0W_}'g3<$Iw0HZaR Ҵ%<3篽gLx ՠ6ua=g^3sg8< ^3약g+yW<`xgʹ3`xgʹ3gy`xg^̳g+{3_gy` < gy` < kxggy`<;+yW<`xgʹ3gy`xg_gꫯ& joXT4zꫯꫯ+VSAPӤσnj5cd 6,Ecꫯꫯꫯꫯꫯ;ͬo}j6W_}I}36:Nku~~1Qxg_{3<^e/7({_):y{3<WyW<3{3<Wgy`xg^̳g+<g+<p+yg+{3_gy`<;+yr.3+yr.3篽g+yW<`xg^̳g+<p+yg8aWꫯꫯSi7K7jU諯ꫯr_a5yU~W_}ű0KlkhYBEcꫯꫯꫯꫯꫯ;qE]T뫯 glto4xKg8< `{kluQ]u< gy` < kxggy`< ^3약g/{3<Wy{3<WyW<3{3<Wg< ^+yvW<3{\gW<3{\g_{3<W<3{ex ^3약g/{3<WyW<3{3<p>W_}W_}ܷmĥcꫯꫯWXM=<_T\}}s7U=Q9ZZ@dN w^Hý%sC4e,-drcja,a C S7]0bim#/aBO{)gTN'K1}soO'/)S&Ŗگگگگگگگگگگo7+qP?~_Oگl yų#ٜп99y2˓:0:s6gʜٜJ/9׽2gs6g2gs6grٜpٜp+s6g Wl Wz2gs6g l΀uٜpٜp+s6g Wl W9+s6gs+` ^93^s6g@Wl Wl W9+s6gs+ʜY99^0gs{el΀{el΀}____ xEzQدگگگگ˼l lh߷__BR)@0/X,|#GFv'9گگگگگگگگگگX+տl_Yʫ9{%W/#svkξqW`W^yE/uWJ^yտל,W/9Wʫ^0g>+/zA/xWu`}W^9_y@Xʫ5g>+/{e})kdԜ`?9ZQQbbkkx^3X}w=SbtyxbAe/{mwehaeb/bb/fI<9s.)·GHtO1E-oEEIwwW>JO/G-/A9~n3svWyoIEw~JtoݕW0%^=~Wx/ud+/ל,kʋ^0g>+ ^+Eu9ʫ gyE/腨gyu7пW^}W^5gJ^yE/gyտsvWY~ٽW^^:2gY^yѿ:}W^^0g+п_/ʋ5g>+ {%`}W^^:2gyu7пgyE/gyտsֿ+/z>+E{%׽_>Awu9/b/WDMoۼ߷_엜Q?3S*OdOիm͘5kss~_~~~~~_~T*/1kX [k/~ٜ93oްacKK[yLadҧWbd2u|̍7YxHg>跈Zި5kFs[?|ul_9?d7oܰmdl˼O?>`7Ly#׿3f5on~=~_ȜW^9:}W^9W+ gyտz+yտWu`>+ z,ב,տgyE/uWkʫ}W^ٜ+yտW/uWs6gJ^y^ ^G>+/ל}>+ z,sֿ_^@+_s}W^9W+ gyտzA/xWu`>+ z,}W^9_Y^y+_SO_@X9aRF6nxw^}&Oo}_<ǯ:y#b/_K_7ne]v%=ļyV^8qL?/|СUUU;5Bso_VXq{Z}گگگ/گگگگگ~W8v{ڴ-/`I<9sNlmmˇ}uu5Ƕ|ț}55ur~VX}~Z><_e/snʫ^07~-?fF̀W\Z}>?/bKsH/ud+/ל}>+ ל+yWsֿ__Yʋ^0gsvW`}W^^}W^wC_sֿYʫ5gJ^yտz>+տl_Yʫ9{%W/#svkξqW`W^yE/uWW^yE_ٽW^y^0g>+ zȜпל,s6gY^y^ gyE/5_Y x^ٯz}wŪcb-/67`bkkx^V8v{Z~Ŗ߷_oعsۿ͘1?m^{ozkk#|{I?d2;v:::"EOۧ/.۷ۯگگگbدگگگگow彽Ƿn͏.Z\_?_~$ 9e|x0هQ7qXԥ?C>$y9_>۷ÃYxO?[s6gJ^y + z,}W^9_Y^y+_SO_^>۷~/>k/bkkx^1І xh~~_l3ȽM6}M&Ї/_6l؛ok׮7˖-WTTMMMW_}֭[7lp?fMp]wUUUޱc5k֜STcccGGСCr-C ~ww8p ӿK.ꪫx5~Q& /,}?(!`¨ E3 EY^yEsgyտzA{%_`}W^W^9_Y^y l_Yʋ^ Q{n_k}W^9_Y^y^п^+W/gyտלٽW^9_Y^y^0gsvWud9_Y+sֿ_^@+_s}W^9W+ gyտzA/xWu`>+ z,}W^9_Y^y+_SO_?a¨ ~u[3^~~~b`Ԩ Ç̓u߷_cylC;;wULSUVVznyC,O&Ռ_v;:G7謪SZZ{7jƎQTTS +4 sgyտzs8x,~ѹ+տ{%W/`}W^9^+ gyEuWY+sֿ_Y^yտuWsƽW^0g>+/zgyտsֿYʫ9W^y zgyu79^+ uWsƽW^@ʋ5ggyտzsֿYʫ^:2gyk}W^J^y^,W/{%Wb}W^~엁œw숢⧞4lX)kb/W ]+/ל}>+ zʋ^0g>+ ʋ5g>+ ל+yWsֿ_Y^y Yʋ^0gsvW`}W^Q>+տל x^ٯz}wgז-ۇ + xm˗/ݳ___ڹ} ﷶ-]%JF@.%˗?1c/3~w]lYo=Ɛ!C^}g> A;3̜9Аy+j+[s_jgo?km____~_____k/5OΝ;J__OblΡs]ݩ8-؇}uu5꺋2~8ؼPSS'/ wڶ`ƓO?U+ٜ+yտW/ f@M+oFwE\W^G,Yʫ5gJ^yտz,W/{%W_sֿٜgyտzA/u:rпלʋ^0g>+ ל+yW,W9Wʫ^0g>+ l_}W^9:}W^9_y`}W^ _yk}W^9W+ gyտzA/xWu`>+ z,}W^9_y׽_>߁]۶~\3UVڈbدگ/Wyk~f͓s_b~Cȥ?_ӟ̞=<~ƍ.}kZZZ}.ISS?.}8ҟ??O'NHr5_m"w񦛦Nۿ֮__Y[{ϢEkkkk/kkkkk~׮瞠[}McMSگbzgs2WWkk{#/Zޚ`f@y[~mmmE0Lj^+տzYfނ~0rg+oF׮]Eƛ^G,Yʫ5gJ^yտz,W/{%W_sֿٜgyտzA/u:rпלʋ^0g>+ ל+yW,W9Wʫ^0g>+ l_}W^9:}W^9_y`}W^ _yk}W^9W+ gyտzA/xWu`>+ z,}W^9_y׽_>~)Sڈbدگ/Wڵ++k ~u57M~~_l4ȥm۶e?;_裏?666><#wqOG^z%ؼ}0*jyv>=8ۜٽW^9x+/ל}>+ zʋ^0g>+ ʋ5g>+ ל+yWsֿ_Y^y Yʋ^0gsvW`}W^{>ʫ5g+WkާD}ygouEof6_____<<8ꢢÃ~?{vl%JT*e wIr>|ɓ<_w}w̘1]]]o}_dɒ:G9{ \uUu>ϧ?ٲeɓ$ɏ~O~}+?KL%iZ4uʔO _~__~/P,~$RI;F0gJ^y/zP<~'A/;z?LT*-+ݝjZ4eב9+_sֿ_ٽW^y gyտz+yտ_sֿ0gY^y/z,ב,לʋ^0g>+E{%п_9Wʫ gyE/`_ב9ʋ9_9_y`}W^ŽW^yѿgyտzsvWYʋ^ ^G,ל,,`W^yE/gyտW{%׽_>tX<1L~ljZ2eo~~_ |O?RE;xВ%MSN0_%2'LRь_`ɓƍ .(3gNd޼y]]]x%%%я~4a„7zno|xa_~$ 9V?um$yϹxjuumc{{kPyI7j|g}fϞWz7?-Y9{%WsO+Y3ۃ{?')o7}=}_t%aב9+_sֿ_ٽW^y^0g>+ ^+տל,s6gY^y^ gyE/腨gyu75g+Yʫ5gJ^yտz>+տl_Yʋ^0gsvWud9_Y+sֿ_p?{%_k}W^ٜ+yտzA/ugyu75g>+/z,W/_svW^}W^5gJ^y/+_S_NL%ϵ7xgy%n~[aׯگگگ/WWo3Ͼ|}ŋoܸ;w{?$ɛo+xz?O>яFj_**ϛ7Sw <#744X~~~~~~~~~CpCÊw\fͲ_~$ 9՝Z[ۂzߺFށP]]To]SS'/GjhaA=qohe^+տzef~0[(oGnhXE+Ư?!:2gyk}W^9W+ gyտz+yտWu`>+ z,ב,տW^9_Y^y^п^+W/gyտלٽW^9_Y^y l_}W^9:}W^9_y`}W^.qW^Yʫ9{%W/sֿב,տgyE/uWkʋ^@ʫ^+{e} #54ܰr[1n޼ ~_~~+r顇ᆆ7by߷_ {ݻw?~O~|co[˖-_uUٟWW^yeU3iR?k6mlEگگگ/گگگگ/knرfR?a/~ٜC?uâ.5:0]{miUT ds6gJ^y +/zA/Du>+9_y`}W^9W+ _Y^y5gsvW`}W^9{%W/#svkξqW`W^yE/uWꅋ_+yW_sֿ_`^+ z,u>+9_Y^y gyտzA{%п_9Wʫ^ٯz{miUT l[bدگ/WX}]7mlpkj&xo/߰R)S gdqq_|36lk_/~sC+ ^+Eu9ʫ gyE/腨gyu7пW^9_Y^y/ל+yuW_ٽW^^0g>+/zsvW}W^/qWY+sֿ_/ʋ5g>+ {%`}W^^:2gyu7пgyE/gyտsֿ+/z>+տל+yпگ)/gd*Oʉs̵p~~_ |C?RE'V~?~CX<)Əlr2wܥKb~?_r?9vؕW^yĉ/gwy'_W~_dp̙6l_~3~~ccڵk+++;::r2_L'DtĈ{x][ZfΘQ\T~~~~~~~~E,ycWss̙3s}z/ '_|6ϹlC;;wUߺ31fLUYY=osCǗ>|hǎQ9ΛHtVU)--۽@~Ө博]:ZZg̘YTT1tϞl_9o=e:sGy&:UcJJw)ocWKsˌ3r}ľDUEՈ{"}Q#sW^yѿgyտzA{%_`}W^W^9_Y^y l_Yʋ^ _svW^9_Y^y^0gsvW_uW_s6gJ^y^ gyE/9ʫ^:2gykξqWkʫ9_Y^y^J^yտgyE/9Wʫ^0g>+/zA/Du>+9_y`}W^9W+ _Y^y5gsvW׽_>x,Lκ]--3f,**~*F(ݳ5 ~~~~+ϫA.KL2s✿߷/QQQUZ:~~ X<)hN[hW\~r-/|dzuʕ'NG>2{藺 _Bg>_ꫯ Oځ`[M~~~~_~~~~~.Xxw>\҇~pB__OblΑsmmuckk{oZ[;IށS]}O>o74V^… Mo}饗=Bs6gJ^yտzqf~0NAy Qwz /ze>߉EY^yEuWs6gJ^yտz,W/{%W_sֿٜgyտzA/W^y z"}W^wCzkʋ^0g>ع ;T@Y%9ABGs S)X(=(Li{wv|1*Qk{Wh`e@TJH40wd&aZq_v?~ f t2?7տzٽW^}W^9{%W/sֿٜgyտzA/xʋ5gqWkʫ9_Y^y^J^yտgyE/9Wʫ^0g>+/zA/}W^wC_sֿ+/z,W/_svW^п_k^+{e9wO߸q}'Gϟ^[___3o_={xy~_~sX*,,lmm9ri~#G+ϯxGRTYtiqر{~o1yMMM_Wx7ݷo߫:cƌo=X,v7o}+_ ~/~w}[z ;4k֬ج;6{ƏsӤIE}دگگگbدگگگگґ?榛&u~/͚uG[_~׳9Üa55tE+͞C5ͼheet1cOtS>Et,vA_2gsvW^ `mg<f^2Ay͡y׿LiRhXG,Yʫ9{%_`}W^W^9_Y^y l_Y+}W^wCz5gJ^yE/uWs6gJ^y _Y^yѿl_Yʋ^0gsvW}d+/ל>}W^9W+ gyտz+yտWu`^+ z,#Y^y Y+sֿ_ٽW^y^@ʫ9{%пگ엓wInӧm~_~~_Wo1743/ 6k9SPPf~~IIɊ+b؉D"˗/k~iӦ|pر۷ /УGZ} Rnm ckkkbدگگگ?붒_~׳9oWMM]/yp|[YY| ?oE3KH:./ՋKٜ+yտW/ڿDZx̼hqR޼@sz!}ՋG,Yʫ5gJ^yտz,W/{%W_sֿٜgyտzA/W^y z ,kʋ^0g>+ l_пٜ+yտzA/u`>+ zȜW^9},W/_svW^YʫWʫ5g>+/zٽW^9_Y^y z!GWW^y gyտzA{%_uW_s6gJ^y/+_)/'oz߅mwXbدگ/]wvW'=ߗ~^btHk tիWXW_ݳgO2F;bĈSVUUe>g̙?x_XXx_w֭[kw[~[otȑ/}K^xe]6~iӦ㣿Ȼ.[lΝǎׯ׿s+H1Lh?pyǎwnUhnQZk/~?gs9-eecމĮ_:s[[{<dʽH4<iiXֻwl׮Dy+*bm 0s]ng 8оcGsII9{%WsH;xcw+/zA/|uпٽW^y gyտzٽW^}W^ٜ+yտzA/u`>+ zȜW^9},W/_svW^YʫWʫ5g>+/zٽW^9_Y^y z!GWW^y gyտzA{%_uW_s6gJ^y/+_)o>FdT]fٳ熽ߊ؁;v4ZS߿kkkkuu>E#E$ufͺsCޏ*47(ͻakAH4j$oC;ru۷ϝ=ۨOwmW^jگbدگگگگbدfEc+OS_p>߿k Oگ׳9w}ػw_c^_}޽S,N o{555ܯΝCo7I$oNyy0{S"sy_̹ce)wPطwe!>656 7oП7]؟ζp/)Ayg>yʋ5g>+ l_YʫWʫ5g>+/z}W^9_y_Y^y ל+y`}W^ٜ+yտW/gyE9Wʫ^0g>+/z}W^9+_sY^y^п^+W/uWpW_k}W^9{%W/sֿB޿gyu75g+Yʫ5gJ^yտz>+տl_@WkSDdT'ؿm;B5۷Ϟ=ך____ו3Ya}߿?;fͺs=gگG[%oGZ PP0v蹳Z:m9s>,_________:rN?g߱[s~I|=s^͹jh𱦦.ͼ\UymЪc]]M/y!CcMv9:{ayٽW^y 朳~ y0rC:yP^ys(o̟sV=vt^-G,Yʫ9{%_`}W^W^9_Y^y l_Y+}W^wCz5gJ^yE/uWs6gJ^y^ _Y^yѿl_Yʋ^0gsvW}d+/ל,W/_svW^YʫWʫ5g>+/zٽW^9_Y^y z!GW{%Yʫ5gJ^yտz>+տl_@WkS_NiΙ3{Ξ=~~~uEgi9;cGo/]lzGΘEpŋۯگگگbدگگگگґŋG:uƫگb$ 9\]}/ E3/W]])o Z| E3/WYY-/k.Z$}Gf̘믚9W+ |oy6UvP޼.\xQq}GgLz>}_ȜW^9_Y^y^0gsvW^YʫWʫ5g>+/z}W^9_yn_Ͽ^+sֿ_`^+ z>+/לٽW^9_Y^y l_Y^yEuWkʫ9_Y^y^J^yտgyE/9Wʫ^0g>+/zA/}W^wC_svW^9_Y^y^п^+W/gyտלٽW^ _~s I-... u>2c_~~~uEg.^$Gytz޷_.V`;r'|ᅧy&j %KدگگگbدگگگگґM>В?w O?Lo/~?gsΟ9W0_4r՝[c]]M/yKKjmMCa /|晧6gsvW^CY]σ딿8[^ys)oo5] ~_/<3}Q#sW^yѿgyտzٽW^y^0g>+~ ^+տל,s6gY^y^ W^^?>+={%Yʫ9{%W/,_s6gJ^y^ gyE/9ʫ^>2gyk}W^9W+ gyտz+yտWu`^+ z,#Y^y ٽW^y gyտzA{%_uW_s6gJ^y/+5)/'okbIz(H߷6o2~u'_ly2xl`s*񆒒by?HSe{v WT%!7xyР-ǎ9{%_`]V49|tTKe`ޱ޻B|U7CW\ʛwT>(6ݰeΛzʋ5g>+ lʫ9_Y^y^J^yտgyE/9ʫ^0g+?>+={%Yʫ9{%W,_s6gJ^y^ gyE/9ʫ^>2gyk~>+ ל+y`}W^W^9_Y^y l_Yʋ^ GWW^y gyտzA{%_uW_s6gJ^y/+_)o~Fd2uƛ *;kW"6eckM}گگگگՙH~xyXw"nؼycc~~#=Wo7|0KCJK1ҁ{ウ:~~~~~~~~헎oYwθp;?o/~?gsΫ9ٻw_SSsyyY޷oQIgaO}677MMmEE}K䥣߿e3f9paB_2gsvW^9OQ}{575<^ԷSlyͥy׿e310k!/}d+/ל,W/9W+ gyՏz+yտWu`>+ zʋ^ ,kʋ^0g>+ l_пٜ+yտzA/u`>+ zȜW^9_Y^y^п^+sֿ__Yʋ^0gsvW`}W^^>rпל+y`}W^9W+ _Y^y5gsvW׽_r͘q ~_~~_Wt;qi]2bt#>d5׌'"?{͜Zٯb/kkkk/~~WqKگb$ 9\U54XSSe^j:*XWWe^hȐ*yɆ̙ ֬Y=n5O>ԜٽW^y^0`]Xσ҉σʛCy~/fq׌{2/}d+/ל,W/9W+^ gyE/{%W_sֿٜgyտzA/W^y zgyu7_svW^9_Y^y^0gsvWuk^+ z,s6gY^y^ G,Yʫ5gJ^yE/uWpW_k}W^9{%W/sֿ,տ^+sֿ_`^+W/gyտלٽW^ _~r>3~׬7'\jbkksg|/ի\s͸7H$̙s%t޽G]wݿۿ}ӎ;O=:u֭|ܸqO<ĉO(,,D"{]~}_k׮=?5j5k֞={>i~扗0a„ |r;C6lʆ MMF7ߜ=o'____~_____#[B76lz k/~?gsλ9WWjjjy UWWC連޶_Z^7k͛ڟߴiÆ 4559W+ #~ y0B<(97zy7woڰi+G,Yʫ9{%`}W^Wʫ5g>+/z}W^9_y}W^wCz5gJ^yE/uWs6gJ^y^ _Y^yѿl_Yʋ^0gsvW}d+/ל,W/_svW^9_Y^y^J^yտgyE/9Wʫ^0g>+/zA/xn_kʋ^0g>+ lʫ}W^ٜ+yտu)/'~ɥ!wӆ ^ijj4y~___|]ѹjkߜ={Ұ7l+=/Ko+0O%H\~?O//ǎ[n??~>p={<Õ۶m?~Ϗ1^J6o+/לٽW^9_Y^y l_Y^yEuWkʋ^0g>+ ^+տל,s6gJ^y^ gyE/#Y^y ٽW^y gyտzٽW^y^@ʫ9{%п9mn\+ i[jw3~~~uEjll~%υ?o=k׮y~_~s^$J[`?zg}W^ܷo߯~{ħ}~˟~#Gf~_pa,,,k3|گb/+gh$J%{]dï󼟋~"Rg$o67xe~_3O?Çᢋ.:sh?я2Կq6ΰw~$f9z̘W]cT_~_~_~_EјW9򪧞_~׳9weY}Ʀ~[{Ny?C۷wƲmjj<_QQߝ;ww~-/Y7sUW|챧LÜ+y`]\/Ij@޸,σMM .[S7^hzU#z,//}d+/ל,W/`ʋ^0g>+/z+yտ_sֿ0gY^y/zʋ^ >+={%Yʫ^+E/gyEb_9_Y^y ,}d+/ל,W/_svW^9_Y^y^J^y/ל,ٽW^^0g>+/zA/xnٽW^y gyտzsvW^}W^1gJ^y/+s oh1LuƛnjF>S__~uE>E#T*qW_=fȫ򼟋~"R_+S)+;}w}dbŊ_?hРOgΜY|y/--_ַW^ywgϞC ~??{Y`AUUU^w~W7:uj_-\qf N^*R[)jJaZ|)S֬[_~_~~~~_ͯ.[>q5k-v5%k/~?gs9WU_e2/QU5D0 Z| e^bȐ*yɞ˗M2qݺ5~-MMZ[lʋ^0`]3/1ӟ7]/,[>euhڦ]M}Q#sW^yѿgyտzٽW^y^0g>+/z+yտWu`>+ z+y}W^wCz5gJ^yE/uWs6gJ^y^ _Y^yѿl_Yʋ^0gsvW}d+/ל,W/9W+/z,W/{%W_sֿٜ+yտzA/W^y z}W^wC_svW^9_Y^y^0gsvW^п!לٽW^ _~r._6eud}[v&~__LRd_eٲ'NY-[jwjJxگoFP(A0 |=}*v[ݗ-[V\\|w"?bٍ͚3O0`~(xȑ0ŋ߿e_9 vl70o۶|7گگگb/kkkkkt~-_r ;vl~_~_朧s>f2/Q]])o?]\ʼDeed϶m+W.߸q}_h„nq?6gsvW^9Tk+/z+yտWu`>+ z+y}W^wCz5gJ^yE/uWs6gJ^y^ _Y^yѿl_Yʋ^0gsvW}d+/ל,W/9W+/z,W/{%W_sֿٜ+yտzA/W^y z}W^wC_svW^9_Y^y^0gsvW^п!לٽW^ _~rn_rƍ볾 7lݺlbد/ -_r}oaŽ[y~_~YF\|ů??duus=t~5iOy+((>a%%%xw޹+}y䆿\|9 JKGikkbدگگگґaBYYQ_~׳9WMM]_(՝[c]]M_(,0Ҳ#G _|9{%97VKt_-7z e#Cy}Q#sW^yѿgyտzٽW^y^0g>+/z+yտWu`>+ z+y}W^wCz5gJ^yE/uWs6gJ^y^ _Y^yѿl_Yʋ^0gsvW}d+/ל,W/9W+/z,W/{%W_sֿٜ+yտzA/W^y z}W^wC_svW^9_Y^y^0gsvW^п!לٽW^ _~rNlQaK ~__] 7L(++%7DR)|ӟ6ogmhh޽{mm%mĉ/_O+=ztϞ=EEEo|o~?8xn闿?~3̡)&O stc{o|^gH~~_~~FT~޸^'_l8xl`0DbWV_(hkkJJ"O&?մ,;kW"y+*bm No%K|e^+sA[ +σxCq>+o.%[ЧUƗzʋ5g>+ {%Yʋ^J^y/ל,}W^^0g+9n_Ͽ^+sֿ_9Wʫ _Y^yѿ{%`}W^>+E/xʋ5g>+ ל+y`}W^W^5g>+/zsvWYʋ^ G_svW^9_Y^y^+yuW_ٽW^ _~h$|L&S]h{饍˂rkb/PGr&K>c7~._hM^`M=FhѢ۷;SO=||>ۯx,I$|I䆖ݻ['jaoaF5kkk/گگگگ/69mڶ¿5_~׳9KKKChjjޫ466[TkX-))ӧ){VTԷD^jFg{wK%H?oʋ^0Ғ>E}Zۚ<^Էlyͥ -5lT<ݲ;JO}d+/ל,W/9W+^ gyE/{%W_sֿٜgyտzA/{%9n_Ͽ^+sֿ_`^+ z>+/לٽW^9_Y^y l_Y^yEuWs6gJ^yE/uWpW_k}W^9{%W/sֿ+/zA/xn_kʋ^0g>+ lʫ}W^7D9Wʫ^/S_N߅5,}޽5y ~__]}-6lTs6[Zvߟy~_~D| \uUٓo|UVر37+WܲeKG}J>9;+WBn().>E7Լͷ/bkkkbo>~7~Klye_~׳9kj_j:*n[/>dHd[}}]M-[K|ٜ+y`9ؿl>f~!]yP^ys(oB]}-hqIqG,Yʫ9{%`}W^Wʫ5g>+/z}W^9W+/zA/,5gsvW^9_Y^y^0gsvWuk^+ zʋ^0gsvW}d+/ל,W/9W+/z,P/{%W_sֿٜ+yտzA/W^y z}W^wC_svW^9_Y^y^0gsvW^пٜ+yտu)/'o}]M-[~KZZ>˦m/گ늮~7_RR|`˞7O2{+ٜ+y`}W^ٜ+yտzA/gyE9Wʫ^0g+ٜgyտzA/xʋ5g>+ lʋ^0g>+6 ^+տל,s6gJ^y^ W^^>rпל+y`}W^ٜ+yW/,_s6gJ^y/+s I{u63>=tg/3m~_~_Wt ܻiӺ|?xPaa<4W ~_~_sN}^55u{/^5|[YY|Kd~ KV]~+^>}RiiM69W+/zsӏiu|]/ΖW\ʛw0+}铦*ݔ>2gyk}W^ٜ+yW/sֿW^9_Y^y l_ٽW^y zgyu79{%Yʫ9{%W/,_s6gJ^y^ W^9,W/#sW^yѿgyտzٽW^y gyՆz+yտWu`^+ zʋ^ GW{%Yʫ9{%uk^+{exN~9~/vóJK{mڴ~__|]u v,?O4W 7Oeʿ˿]~}ssΝ;{yWN4oҗtofڵO|̙3WE"H1J}GL~Çs=:[V^ѽtԏ~_____~qGN|˪U{D;Cg/z6缞se嗃/Rf,~!yC//b4 %=r-W^8+/z+yտWu`>+ z+y}W^wC9W+/z,W/9Wʫ^@ʋ5gsvW`W^yE/9ʫ^>2gyk}W^ٜ+y`}W^mW^9_Y^y l_Y+}>+9W+/z,W/9W+^ _Y^yѿl_@W엓[nzH%͇*/gbد/:9:y-sz$ށ?nn:t}~F?N} ?Q!G޴'9XǙq2}?N^sE}^:k/گگگ/b+Ș7]z /L7;<\k/~?gs6;ٿ3| ghmM\tхY;c ~-o9,Kد9W+/z AiZeyĺR޼,Eᙗ_#sW^yѿgyտzٽW^y^0g>+/z+yտWu`>+ z+y}W^wC9W+/z,^0gsvWuk^+ zʋ^0gsvW}d+/ל,W/9W+/z,pW_k}W^9{%W/sֿ+/zA/,տ^+sֿ_`^+^@ʋ5gsvW׽_<`y%m2:&.H#hD,vt~~~ו3.Ta9 ;K\yدگf~q*? w~%FG$ tȧrX4IAIh223. >RIsDN *0#*0#*0#*0 "Ѩ!*}~ш!! >T2ipJ|d2ػ*m=0pˑD X$l$$dMu>JWt$^(ΰbqt:+V;VY&X3;eEEL[(Oh,{I.7kIysI1'OG^*>S<Йul]Dg"8[ٺEpH"o[m~;ߏOx4zꛗ6'WWկ~__7MC}WO_p= ԋOWFRCMyy},gk @{Bі'SCy^_9`,g` ]w<c]3vAr v쯜 `삜 rv u/W+gk ]k ]]3v!  W. g9 gBx+g s()o78s4j__WHD˹;rNMM:~#~:‚Ns|-k>~| -]W_~ѯ~/EKǧNկ~/DzǛw ]3i (_9`3vA؅p뛷qOIcC7n ]3v9`"g삜 N Y9` ]쯜  gBx+g9` ]3vr. g삜 _쯜s()6xN~/EMsE$E"̿H"p@+J-l]3/E_Wߎ+~!L▍kW|Tկ~3r6~?w[ [*jk7>Ri g'>uvػPqKgV>SuQr. g삜 |W~}?y'7rS=Ϳfyes_9`3vA r.tB gr.E9`3v!< 3vA ] r. gBx+gpupN/Dx~o3+K__tܶ`E-7֮\L~:ү~WI$.2Reڵ˗/ٷo߱cz著7vI&g?W^y۵kW(//_fMz~3Z]]]]vM=vgH$ FEWկ~_WWH D<,=ֳgh}__|?~,g_7y!H'ǎѣgHv`/H]3vA=ht9(Ͻ Gu-( g9 gBX- pcw~z __9_ g삜 `_9.9`lB'` `쯜]3v3vAr vA؅p/Wοv9`_9`,g` ]` ss(s~~H4x'=zߺN;@mկ~_Wկu.22|xӦM-kǏ>l}|k_O]vw~_7}{a}g~$ 5`AqYٚEVz|[_Wկ~/կ~_Wկ~۲^=o~.(..[FE~YξnLϞo޺uK̂sʊׯ_#g9_ gxz<||k8^]0wAYqz` r3] r.Q#7mÃi} +gk ]]+g9 g삜/]脻_쯜3 r.r.Yv.._9_ g_+g삜 `삜 _쯜p s~ہ[sʊׯ___Whozln>%sq/%d" ^}o1_r%vۭ:r^z;v~K,կ~USSwO>|˖-VڷoW\qM>=;7tӽ;lذX,l۶_|guYPPPXX1?K.ԔY&=hnv5P֠AW_xw^/E_W*oxe {7/~RzFv"ӯn! {W{- 9`9E09(Ͻ Y2c/B/3v9` ] bn> }E;` ]@9`+gr.i+g삜 9` ]@؅p/W9`3v/r. _쯜9rH4x' /ګWoAWE"-dd45(|z;#^`U:mD" #'x" eeeȏ>{gyϟ?|7|s…s4hΝ;_zo1xW_;v8KJJjjj׬YM4ILozM7+-6tĉnݺaWWկ~__bDoHH$=Molڴyܸ/~WO_p= B*rxBkσJK E99M4z6x@G݅Mol޴t\`` r3] r.+vڟF]3vAr v쯜 `삜 rv3rv+gk ]k ]]3v!< +._9_ g_+g삜 `삜 _쯜]8_v~wԿ֛)wų3gV-[T#EWկ~q]몾~Ǜ>?슪K~ YiHW^yĉ;'N3~ڵkw~3;;k_ZNuu9jԨ G{Op%cƈm-_2s_Wկ~/կ~_W/m<~ 0I g9b+W./,̟;>. cƔY.vم+ uѫ yc. r.Yv.n#G n޴9>]3vAr v쯜 `삜 rvrv+gk ]k ]]3v!< +._9_ g_+g삜 `삜 _쯜]8_v~W./,̟;{u7O3D#EWկ~q]˗/| 'Ly_藐dz뭷Z5jԟӟz #wu._ß` = ,>UW]5v=zD"D<9q„‚;CEWկ~_WWoaAfK)wuI g9RO޽ar3 rvOfީ~]e. r.Yv.Go_{=>]3vAr v쯜 `삜 rvr._9_ g_+g삜 `삜 _9`쯜]3v3vAr vA؅_쯜9g[PاOf}RoK/Eկ~늳Em"o_ 3OT'N0yW9E"іMItl#wNJ?ã>o߾s8п'Onذa?xԩ666˿OYx??߿?د_'Κ5+''|͚5ȉ|3o߿d̘\GE_~~/Hhho%%cnWO_p=Y΄W Ej߿z1%ee7HC9P_.r^\=dLE r.+`{OhxaaWdػUs r. g_ ]3v9w_9`삜 r.W ]3v!< 3v. g` ]@9`_9s()7̢h6OSRVv__~q]qgh$xHﷶoŋKJ༟_B(:a"蜿.:?yUW]uܥ˔)Sw)sOccu|no߾ٳ!CV^i %tƍ=c6;;wWWկ~__7==zlÆy'ul|G\E~Yξn±cG_ziΝlX#-^ `킜h]8z /Lk6>#. r.Yv.o7oڜGp__9_ g삜 `_9.9`l]p/W+gk ]k ]]3v!< 3v9`_9`,g` ]` Ρ8WcG_ziΝ郎ߍ5<ŏBW/+'x{G۰wRwޯC<>_lid޼y./__~}s;|?~UW=ׯ?K.?;={mݖQ[[~'Oٳ9?ݒ%K|g'N||Gݻw~i3fD32ƍKЯ~/կ~_W/EEpav޼9GMfJ =~$׳} ?:9DA{чg̘7,PRr]AA `킜ChG!sP{~tƴьq)}]互` r3] r.;ԼR3h_lum~]3vAr v쯜 `삜 rvr._9_ g_+g삜 `삜 _쯜 W. g9 gBx+gp9E o̙w~}xƌiqRouj~_W/+U׭ۉe~ifDe)=_w]I~/[ kd˿w6m_~yǎ;8TWW_{u/-\0x{ww[d??|F#vR\T4tP^~_W/Eկ~_W--.7tР;rD"~$׳]!QTT\Z:./oP@)g9_ gwJǕuіY( g9 gB0r?yT=O0οrvA9.r3] r.؅N ` `킜 ` r3] r.r._9_ g_+g삜 `삜 _쯜9[T\Z:./oPm)XEWկ~q]dEƕJy#~ _*H$@X,vĉZj՟o~o߾gj'O >,xz~s~LIIIMMMyy5kҳf_5eJ׮]]GE_~~/Hh4xHuoСUUE~Yr&"ӯnv7k}CL r.9?pp߬S. W ]3v#x3cv 8{̹Us r. g_ ]3v9w_9`삜 r.W ]3v!< 3v. g` ]@9`_9s()7̢h6OY2Jկ~/EhiYCW9)ЮJGI'|2_~ȏ>{;G+?'vmgɓ':+W֭ۏ~=zt;e]}>p]w]vmٲe| K,ywoc|g'N\ߟyѢF.~s^sM_Wկ~uuv~ɓ?آCSq/yW9WӦՅE?[[[[TTO?=iҤ}ɓ'92s|ƍcǎ=u>>''g\sͧ0??|?eĈ7oN/̛|}QUs啮__~/EE^H4M$j~;*'WO_p=Y΄Z$ruxm5о͛BUՌ+̑ rh4r|A\..Z5J3v9` ] ~_}nsIԷTܲ~7L9`3v/r. _p/W g삜 3v9` ]` ] r._9`3vA؅p/W s h$awޜV͸MWE⺢-dd4ojNyƌt_U鴿HpTYzkjjǣUW]URRrw~ӟz.zN6wݻ/~_~7lhhhnn֭[߾}^YY9y8Wϯ?߯?bĈ͛7Of:0#۷qi/NZaÖmf2?~~ѯ~_Wկ~/Em"aw݆-[͞~c>ظ_?__|?~,g_7H!h]ذaݶm[vwaؑ#oo'g9_ pF#GsP.۰m˶؀#loܞuQ3vAr vA؅=όenڝO|?οrvA9.r3] r._9`쯜]3v3vAr vA؅p/W+gk ]k ]]3v!< 38_V4 aݶm[v;r994Վ_Wկ~_\W,#u6lٲmv؀Ç46nߞ_7_U鴩F׵^xB_p7lزmW4`Sӻ[MWWկ~__-DD"~<[Ln_OW]rD/A4(֭L./3vF#P9(Ͻ Y6m}wknr_M_PS r.Y.W >ퟴO^ðòޱwK:9`,g`Yv. g `' 3vArvAW9.9`_9`,gk ]k ]]3v!< 38_V4wPVS[~7۶mQ>_Wկ~q]:. &v<[&n݆-[vogկ~iHeS3Ү*l]tWDަN,Х]Wկ~___Wկ~_ڲߩ-^~+#_|?~,g9@L<5؅]/M. 99]hy]k_m` r3] r.t0kWݼisI ގ(:οr. g9Wr vA9.؅Os/W99`_9`,g` ]` ]]3v3vAr vA؅p/W s~ہ_쯜  g` ]@9`_9`3vA r.rPSohE#x<~A]kS}cu__69E#D"N^cӑ~KhE"іMIt~WpWZlYӡChoAȋ/ѯ~_WEWկ~_WeM-Z~_tS_O_p=u65EV=lSY.$BӡYH뢫_lC^_9`,g` ]hFݼis2?ik__9_ g삜 `_9.9`l]r.Y. g9 gBx+g삜 `킜 ` r3] r.rPS-'U/[4/Eկ~Tij:hE=?K59~[$HH:3? &}uuLq={r rnڽ;iTEWկ~_W߶Fxmܓ;$ljJboUVVSLO_p=٥NE"xm5t{4۽;y׿]WW[QQ|^99Y4A|]h3$wHf,sw_"_+g삜 `삜 {=W\RοrvA9.r3] r. ` ]]3v3vAr vA؅p/W99`_9`,g` ]` Ρ8WDą8dHnffl[v]]mEEeyuԾ_Wկ~_\W{FD~=Cb̦$~"_"h˦$:gN;,if:E7k)Sv* _~/կ~~/X"hᕋYCWU7M/~RzFp"A5ST9`HퟄO^ðòޱw쯜 ]3vA r.p` ]3v9`"g삜  gr.E9`3v!< 38_)w}CLoz>_~/+:. &7k*4_OD"іMIt*[-x]k ]]3vzWfo~e없|) _~gύnI]W9`,g`Yv. g9p` ]]3v3vAr vA؅p/c~B~dOw <~~/կ~_W2 0;z==WTߕT9_ \rsarɓǻ{FGT \n*m޻MrvA)A߃x]|ъ|.ZX)r^` r3] r.T?O}?F?'~"οr. g9Wr vA9.؅Oq/W99`_9`,g` ]H` ]]3v3vAr vA؅p/W s~NaFQ%^=yxwwJ[X妲{Tׯ~_W/+U 5bt'{zT伿R57gۜ+Bկ~7A)qmoCCC*5ůCOo4!VW|g <~~/կ~_Wr ?hLiJ_ \rs~C(uuۊ?bevuǎ99_3߃x]X\R( g9 gBuRxx_>7MX r.Yv.]3vAr vS 3vAr vAW9.9`_9`,g` ]k ]]3v!= 38_P~붭?bmiݱAAzWկ~_r]Us熋_{mk+uommipޯW0<|nmSj3 cW+?_(*G G;~yAm=/E__~o qwa!_ \r3!Xm5|~#w?4 ]sʅ}Ǒ߃?z}. W ]3vj?2͉>Z~^۽p.3vA ] r. ggr/W ]3vr. g삜 _쯜  g` ]@9`_9s()7m ,~8E=rp*h ~_~/+x 8Ʒ?rhgw"ꗔ >'6+\nv~~iyYtqdԩׯWկ~~/կ~_Woŋ#^MҒׯ~/O_p= Ɩ=S_rKr3vA闪r.Yv. g9 gBz+g삜 `삜 ` r3] r.rPSߴ;rԩׯ&onr~~vyyI;}Wկ~_r]9.^?u5dnvv~y_藔[ I}ܾfti_[[Ow~_W_Wկ~+sR~g\ڷ'~z?@Ex?~,g?7ݙKFuw$D}oP g삜]{iR۾?sQr.Yv.P_Tgumk ]3vAr v쯜 `삜 r3] _9`,g` ]k ]]3v!= 3vAr vAW9.9`_9s()o{wҥ}=w/EWuEJo_[O~I_^C6g]կ~__~_WկK9mnvuUߗ_ \rs z{ή K/,g9` B_o9YE_(_9`,g` ]r/~N֟bοr. g9Wr vA9.؅_쯜 r3] r.r.Yv. g9 g_+g삜 `삜 _쯜9귊f;;*K/ \_)ܜye}_o-8+?_(*G;[[n^V}_~//E跼0H;w666ܼyW'/,gFQHDkΆknn^/ 9`~aD~ kֆƆk7m(_9`3vA؅_׼lOÿڴUs r. g_ ]3v9`>r. g삜 3v9` ]H` ]@9`+gr.CpNA)a)ugCCk7o|W_\W@ůqmogƆ7;oE/)ڦĵg\{_W/EWկ~_ \$]%j_ \rs :޽r.Mޅ|[`%K8_9`,g` ]jv|5?7_ M\ g삜 `_9.9`,g`>r.Yv. g9 gBz+g삜 `삜 ` r3] r.rPSwh#Oս{,/-߾^EWH$ܿTO_KψذieqqvnN7=3skjjbr9կ~___Wկ~/ewz֭մ8;__DzU`ffzjD6ۜS45Z\\ r`vazfd6Ev5-,\쯜 r3] r.Tگt~_^k3vA9.r3] r.Yv.|1` ]]3v3vAr vA؅p/W9.9`_9`,g` ]H` Ρ8WUԭɉl6WMMWf_~ѯ~ukjbb9]M++Eq,W~Q|PVWO_o;Я~/Eկ~__[vAq~'mOߕ¶m^Ex?~,g?7>AFj9]xvPXV g삜KQ wasW\R(_9`,g` ]~o/zb~֯~Ӆ ` r.Yv.]3vAr v +g삜 `삜 ` r3] r.r.Yv. g9 gBz+gp9EM0(JNNJ{Vws^կ~__\W;Ѕ-qMۓ<ﯬ9oW- pmSL2b6 >]͎:|С޵W_Wկ~~/~;zώW'/@8xС݅ӧ19 g6m:Jsq3vAr vA؅b|W9`,g`Yv. g9 3vAr vAW9.9`_9`,g` ]k ]]3v!= 38_Vq;z=~Odc/EWuEztt|=~/)FdD%X0xx!/<|ز{+N⓽~~/կ~_WZcNAXד^xa!_(,"m_~_\WǣG;a?^X [~ټ~m`q-8v=A5h~Br8V)?ggR/EWկ~__~7Giwil}\|/I g9A5Ym5Ԣ={x E_ŧhl|Nr.ȹ*<S{PO K{>]{q)}. W9.9`V_9` ]]+g9 g삜 `S_+g삜 r3] `,g` ]]xڜ_k ]3vAr v쯜 `삜 r3] Osr `999E|EOҞ=oaPŧhl|N5կ~_r]Ug}T'ZZZ~~̓dy9M_Wx=6ׂvUyHM#P]'߃ AXE(ѳ>Z3R@TFP*#(ʈJeD5(( C9VAr+JqTjϸTFP(lUQq$Gf+JeD2"R@TFP*#(ʈjPPro""!@cX|(P0P|sA)J~WʈJeD2"R@TFP*#(| A T`UBKA(eD2"!an.켁POe+!+I 0`ū%=+.bOƢ|H0GSlћvNMN aQiG ᔼ@A"gG[Mv3N&H7 NR_8*)ɲep,#ce+XYF28V( ƟP63Osh跇'~wDI?r/گگگگ/گ~c9©}w__Obl9{> d0L{#IޞEG\I8Ji_sh_9Ƞy-AyMڿ_ }2/_Yʫ9{%W/sֿ}W^9_Y^y gyտzA{%п_k^+տz,W/9Wʫ^@ʋ5gϿ_Y+sֿ_g~J^yտל,W/9Wʫ^0g>+/z+yտWu`}W^9_y@rW_k}W^9_Y^y^п^+,W9Wʫ^ٯzN~FcGޞa/kkI?w)|w}~&މ{3O'Y^XOe}μVCKWk/b/k/2 ;8s/ '_z6紝%]3LŸbʴ%%#:CΎ$LKwDIxLߺ+锗B[3_9_Y^y^+yտsֿ}W^5g>+/z>+E{%п_9Wʫ gyտzsvWп>+E/W^y _Y^y/J^yEuWٽW^^0g>+/z+yտ_sֿп_9_y@rWY+,kʋ^@ʫ/^+{e9$[oib/LX4 zb~4ǿ'l\qE}CóO>9v1ۯbدگگگگbد l0vcM >'W~_|=׳9kk\zMccsaᨔ2.}BG?}ۯv͚͍F5`ϿʛNy3khox]-%?SpW_k}W^ٜ+yտzA/u@sW_k}W^9_Y^y^п^+W/gyտלٽW^9_Y^y^0gsvWukΞgyտzA/u`}W^2п+/ל=ʫ5gJ^yտz,W/{%W_sֿٜgyտzA/u@qW_k}W^9_Y^y^п^+W/gyտלٽW^ _~s K8>:h'9;'XeY~~~~~С}\Q_>> Wy޷_7'NH5kr-&LD"rJNNy7o޼^{ÇsssCqV}qW;3`iuYC-++8kzՆ+X@Wהj_________Xv@ה)c5/ '_z6sUUy𶩩y0|2OBR^^mnn yFEE|>˖:P]=e0|254`>+ zSп<4*yP^y(oe]ǯ6SpW_k}W^ٜ+yտzA/u@sW_k}W^9_Y^y^п^+W/gyտלٽW^9_Y^y^0gsvWukΞgyտzA/W^y gyտz!sz+,W/_svW^YʫWʫ5g>+/z}W^9_Y^y dY^yտgyE/uWkʫ}W^ٜ+yտu~=`wVtu2(ZCëk/kkIbŲ ^k|b ,# %n555?M߿iɒ%_.·~SUVݻ7~]]>D___mme]_ꭷz:ѱaÆꪫ~~_;O#߿uѢEeee[le-SK^eYg2-oEEU𶥥i0M|OI^>v`'s N~ߘ9Wʫ^0瓦_-y0i _-7wYn.zVO}^+տל,W/9Wʫ^0g>+/z>+տל,sֿ_ٽW^y^@ʫ9{%Wsֿ_`^+ z>+/ל=ʫ^0g+YʫBW^yE_Y^y^п^+W/uWpW_k}W^9,W/sֿ4_Yʋ^0g>+ ל+yW,W9Wʫ^ٯzN~9~v`~_8ujK/^~~~b vV_yb !F@[ooַUQQ1lذw}?s%%%uuu &lܸqջw?} iӦzeeeH}kkoۧ~?M>}˖-C}衇!C fڵkW 駟*/wΫf]Eٱ~~~~~~~~~^m;w>+ ל+yW,W9Wʫ^0g>+ l_п,W/sֿ+/z,W/dN/W^9{uWkʫ9_Y^y^J^yտgyE/9ʫ^0g>+/zL>+տל,sֿ_ٽW^y^@ʫ9{%пگw㋲ѓWoyb/kkm]EEUht&MO_%%%~a}}5\[o?wUTTg͚#|g|#F>3fy駧Oѷ_W9hѢ+}6՘BcDrrֽbaa/bدگb/{ X,:;&YlMǟkIgygOw{{kQQa ?] :4)fܸ=텅E)^VVmۮ@7ӌyuÆe9gyտzs pѧwPunmo-L`G{GYqY^~޶~Wtʛq;&'uQ}W^1g>+/z,W/3ʫ}W^J^y^,W/3_}W^9_Y^y^ʋ^0g>+ ^+sֿ_`θWʫgyE/,Wb}W^9_Y^y^0gsֿ+/z>+տ{%W/`}W^J^y^>+/לqWYʋ^0g>+ W^9_Y^y/^aS?-[W2}{eeiӂw~gྚy7o_[nް!{0_________s[7oސ5ojj~|~ד/x=sι<%sn2_ۖM|'Vuk S񛛛4gsvW_`'YƟ[R<&&yP^y(oo 7o onj~Ƿ+yտWuWs6gJ^y^ gyE/,W_sֿYʫ5gJ^yտz>+տl_Yʫ9{%W/,_s>+ zʋ^0g>+ _yk}W^9W+ gyտz+yտWu`>+ z,2,W_sֿ+/z,W/_svW^п_k^+{e9ڹaaòSd7m~_~~+sS#y~_~RLhީ:uꉿio{o9x|gȐ!ooO|bGI ӦM?l̴=3]bWo:Sڶm{c&_________ymx#}ٳ._nI9m\Uu/kjjN*_Ué͛** ¶mm6Og;wٜ+yտW/IֿT?&>$<+om۶)տ.̝=wOW^9_Y^y^0gsvW`}W^ʫ5g>+/z,W/_svW^п_k^+տz,W/9Wʫ^@ʋ5gϿ_Y+sֿ_IW^yѿgyտzA{%_`}W^W^9_Y^y l_Yʋ^ Ӹʫ5g+Yʫ5gJ^yտz>+տl_@WkS_Nmm6>ܹWXn#bkkx]U[۶7R̞=w}~dMKYF@2w3cƌYNkwe}{VUU#x?a{{g_uСs=oܴ)PNɓ&y>[ZYy…kkkk/kkkkk~W>[ZZy睩oӧ_>y/ '_z6紝sUjjjI/˴UۖM|OF^>g]YYYpᝩ4=˧O4ٜٽW^9dkI`H_-7w峕 S疟{'+yտWuWs6gJ^y^ gyE/,W_sֿYʫ5gJ^yտz>+տl_Yʫ9{%W/,_s>+ zʋ^0g>+ _yk}W^9W+ gyտz+yտWu`>+ z,2,W_sֿ+/z,W/_svW^п_k^+{e9}veee…wx_~I&ۈbدگ/^W$ʕϖVޙsO|}~dMKCdڴiS❪?ӧ><-frH$rwG;v__UWW+..>G޹sgQF~vJEBm,9c/~WY^dSiدbدگگگگbدo<'w__Obli<窪mc4@ⓑ#ޘڼO o9Si{p9{%Ws>{=σO< σʛFy3+ˇ Nk^+տל,W/9Wʫ^0g>+/zgyտWu`}W^9W+ _Y^y5gsvW_`}W^ٜ+yտzA/gyE_Y^y^ W^9_Y^y^Ȭ^@+_sֿ_ٽW^y^0g>+ ^+տל,s6gY^y^ gyE/qgY^yտW^9_Y^y^п^+W/gyտלٽW^ _~s [^9|xNv7`___cám,<'gT?Ϙ1}`kBpSb9P&'%__wvޝ'݅^v^?__͛eҥ?яo5jԌ3ϟ_PP7ZfΆ ;O8hK_ROHbz=K&Otԩ^~~_~~/(NFcN޳tiɓ._~$ ^sgOw{{kQQaJ>]9$@Q4fܸ=텅E)^VVmۮ$|LKٳnIN4ٽW^^0A(DA׽0Eσeeyyے+锗d=uK&M4կ?^+EuWٽW^^0g>+/zgyտ_sֿп_9W+/z>+տ{%`}W^0gJ^y/z>+/Ͽ_9_y`}W^ŽW^yѿgyտzsvWYʋ^J^y/ל,,`W^yE/gyտ_sֿ+/z,kʋ^@ʫ/^+{e9dP8x~쮫[:iS/4kb/^W|gp(xE/{.mSSK>ć(@(/ ޶4*oCOX)/_ܻko3U@}ol_`KzbҞ7f\k_ޞ___},)Wʫ5g>+ l_Yʋ^Y^yտgyE/uWkʋ^@ʫ9{%Wsֿ_`^+ z>+/ל}W^9_y`}W^2pW^9_Y^y^п^+W/uWpW_k}W^9,W/sֿ4_Y+sֿ_ٽW^y^@ʫ9{%пگw^~yolxྥKۅbدگ/^W+bсyݵk_~3uw= _cJ6kdIuuuۛn) }gΜiӦc!C\|5knz]wzh+L|p8x;9x`/}K%%s̹b4/݁%K.6mkkkk/kkkkk~䢋-[-;wWدbzgsN9WUÚS $>tUU*xܔ]QQ%/_ܒ%?6˗();giӮ0gsvW_`'eƟS<I{W4ʛq%.+ z,2,W_sֿ+/z,W/_svW^п_k^+{e9]i.Z|Y[2vΜӦ]abkkx]L?ɒ.,ucǖ̝; 2|%NSSG1^ym۶ SO]]׾8_-Zw{=cO|{AAA;tuuo}#G̨}ULu;@?)SƔدگگگbدگگگگҟ> VL ~$ ^s]MM-*YqX孨 ޶4*oC'> y?zʔ JJƤ`9{%Ws>Y7 ֒ćN_-7)L)Iݯk^+տל,W/9Wʫ^0g>+/zgyտWu`}W^9W+/z>+տl_Yʫ9{%W/,_s6gY^y^ W^9_Y^y^^W^yѿgyտzA{%_`}W^W^9_Y^y l_Yʋ^ Ӹʫ5g+Yʫ5gJ^yտz>+տl_@WkS_LdL_E/گگuEUWSƤybb}~|MWX,f $M4D"ofx<:tݻ>|8no}'ȑ#:t讻Zh'c=6s4YF_g~?tM7 2ī7دb/bدb/m,o?ٳ__Obli>箮HNOώ|}wus:4fޜȎ=);ztd}mm]#G$eZ^lJKϹל+yտswP;vDrvyptd}ۺF&W^y)/旞Sz_ƽW^5g>+ {%`}W^ʫѿgyE/gyտ_svW^}W^1gJ^y/z,W/`_}W^/>+E/W^y gyտ_+yk}W^0gJ^y/z,pWYʋ^@ʫ W^}W^5g+Yʫѿ^+,Wb_@WkSLhlwmtlMϟk/u6th_ZS:~_eжJ&b1'fϞ#B^z /{>|>z7pȑp8_s5/X[[z &ӧO_reii-[g}􎎎۷{-mm 55$h_______B !nikhhD%%eyywk/~ٜnoo-**Lno(..۵kOq%9oGG{YYq^^mA3-oikPSSI~wm߾ۜٽW^9Np;qt&y,/?o[7f\niklhIv(+)SpW_k}W^ٜ+yտzA/u_Yʋ^0g>+ ל+yuW_s6gJ^y gyտzٽW^}W^9,W/sֿ+/z,W/dd/^+_sֿ_ٽW^y^0g>+ ^+տל,s6gY^y^ gyE/igyտWW^y gyտzA{%_uW_s6gJ^y/+_)o h4vmillɉ${;JrsomS~~~~+С}#oX[[I%%eyy=_OmjIp³>;]uU~}{O=T._СCz3~ 磏>Wzw?xo7nҲjժn!6ug,~`ʺukkkk/kkkkk~/[^y%=ŋ\~ד/x=sF̹ĉ_,-7[J?glʫ9lI`NLQތ nuu3<~^+WuWs6gJ^y^ gyE/,W_sֿYʫ5gJ^yE/gyտלٽW^9_Y^y^0gsvWuk>+ zʋ^0g>+ W+/ל,W/9W+ gyտz+yտWu`>+ zʋ^ Ӹʫ5g+Yʫ5gJ^yտz>+տl_@WkS_ou^I~8 l~_~~+o7x+x?3/~p}~dMcYF@+/zgyտWu`}W^9W+/z>+տl_Yʫ9{%W/,_s6gY^y^ W^9_Y^y^^W^yѿgyտzٽW^y^0g>+ ^+տל,s6gY^y^ W^}W^9_y`}W^9W+ _Y^y5gsvW׽_r^Z{`l bkkx]|^{yx?ܹsz޷_A46H]w]ss/> Ko\uU'1/-^//Oܧ~+WF"% 7-[\7pC#9r$W^}gdԚX,v#Gz`?kkkk/گگگ/gfw{__Obl?ԒU2-oEEU𶥥)y4 K)sw G9ٽW^9kI`&/ΖWtʛqdS _ >H{%W_sֿ_`^+ z,pW_kW^yE/uWkʋ^@ʫ9{%Wsֿٜ+yտzA/gyE9ʫ^0g+YʫBFʋ5g>+ ל+yWsֿ__Yʋ^0gsvW`W^yE/igyտWW^y gyտzA{%_uW_s6gJ^y/+_)/o|=wG,~___Ho}{?b P5R_rwFرc'O|uUVV&wG}O߻wСCO|?φk׮N;-1{W^y7;SG1a„믿389tŋxm۶9rwܑn;\ܺuTv;Kǵ_~_____~__x` k7nl㎤72z[G~_|=׳9vF"9==;9۷-)BG?=ٹ0''cGOٿ_[[W2s]֍ݑ^{[; F9Wʫ^0)}>:kgiaiN$gGҟGGF߷-SLֶnl_ {`[ ~ _Yʫ9{%W/sֿ}W^9_Y^y gyտzA{%п_k^+տz,W/9Wʫ^@ʋ5gsvW`W^yE/uWꅌ+yk}W^9W+ gyտz+yտWu`>+ z,2,W_sֿ+/z,W/_svW^п_k^+{e9XP8xNߵ/n7dwt[vkkkkוlо{]ڍ~$2z[GyȻak{ldTCk 2g~(tܵk71oq 1cysqQQR ~~~~~~~~C(Ţqcyo*~>گ/_ٜq{t&탶wڵM/" / ݽ0y?::ʊmەfZL3fLnO͛.**Nf//0>+ z]8?{Owk{ka;;ʊ%W^y)oo=\_M_PN?ʫ5g+Yʫ5gJ^yտz>+տl_Yʋ^0gsvWukΞgyտzA/{%YʫŽW^y5g>+ l_Yʋ^Y^yտgyE/uWkʋ^@ʫ9{%Wsֿ_`^+ z>+/ל}W^9_y`}W^2pW^9_Y^y/^axN_¡p4;~ݼw ͻÎگگگ/^W^W']8Ţٻ7'ynzfkqP8)*XYYY5_cm tʐ!kkkk/kkkkk~g{J[sq/ '_z6̙seekZ.';+׬YTX?|ĉҿ~f C^`9{%_`'}VY%qvKypbJ7f\]tH]4^ ~?J^yտל,W/9Wʫ^0g>+/z+yտWu`}W^9W+/z>+տlʫ9_Y^y^0gsvWuk>+ zʋ^0g>+  W+/ל,W/9W+ gyՏz+yտWu`>+ zʋ^ Ӹʫ5gJ^yE/uWkʫ}W^ٜ+yտu=`w\EE{.9 5lʫ9lN`UyP^y(owѽ#?f+yW_sֿ_`^+ z,pW_k}W^9_Y^y^ ^+,W9W+ gyտzٽW^}W^9,W/sֿ+/z,W/dv/^+_sֿ_`^+W/uW?pW_k}W^9,W/sֿ+/zL>+տל+y`}W^9W+ _Y^y5gsvW׽_r{]4rdARCf]W_~~~"U~{- =uZy~_~[G]` \CK?d~իWX/گگگگ/b%bήHNOO&~/ '_z6 sW$9o~P8=ٹ0''cG8ёuYfZL9~|Qֶ6lʫ9fp(zjgҜHΎ$>޿o[WȤ?+o:͸_4>mXS[SpW^Yʫ9{%W/sֿ}W^9_Y^y gyտzA{%п_k^+տz,W/9Wʫ^@ʋ5gsvW`W^yE/uW +yk}W^ٜ+yWsֿ__Yʋ^0gsvW`W^yE/igyտWW^y gyտzA{%_uW_s6gJ^y/+_)o hD?(''cGO۶6v4?~~~~:y¡m,h|$ӓ-[kkk"گDžBx2sCc|{۽{1c|[wmWkkksssM)! Gu֬sJK?!Cدگگbدگگگگ?-u뭳9'÷__Obl5Q r,**LGloطsQfoc[UJ$$  SpqB@,Bޔڡ7ԛiUg8Lն-)P 1!L@ DZݖ+d7G&~y‘{o ˵%Eoy(i--Wlmm9rh?~K-kϧ=x9{%_`п%ŅEoz3)M`kK#Ezo͖Wlʛs[ZE#뢉Z)W+տgyտzٽW^9_Y^y ^+տל,sֿ_ٽW^y _Y^y5gsvW^Yʫ9{%W/,_s6gY^y^ W^9_Y^y^^W^yѿgyտzٽW^y^0g>+~ ^+տל,s6gY^y^ W^}W^9W+/z,W/_svW^п_k^+{e9-)]讑#?&lbkkx_уJKKk#o/g|{oӦM'>رcد_UVwy祗^2laƛO_zQǣ=ŋjk/گگگ/bGַҸ oiʕk/~>9ܜǎ|oL%_hǘ1'Ecc}z^.BGTxG/Vza ӧߴzJs6gJ^yտzσzL|W,ʛsC7l~~?J^yտל,W/9Wʫ^0g>+/z+yտWu`}W^9W+/z>+տlʫ9_Y^y^0gsvWuk^+ zʋ^0g>+ zJ^yEuWs6gJ^yտz,pW_k}W^9,W/sֿ+/z>+տל+y`}W^ٜ+yW,W9Wʫ^/S_κ߇][ ӧߴzJ___}+6l+=/K7joo#Fիwރ[|}￿zo/4hWZu CСCmOyS~|3>to<<˽+3g 80aBUUGF^" >o+\'7F6^SuVH={.[rj_________s {.]޾f--k/~>9ܜOa y ɛcƔ'~4է*++Tسaٲi55۷ob^+W/soy!]σ*Ay͢9 {-]f{[~ ʫ9_Y^y^0gsvW`}W^Wʫ5g>+/z,W/svW^}W^ٜ+yWsֿ_`^+ z>+/לٽW^9_y`}W^ʋ5g>+ lʫ9_Y^y ^+տל,s6gY^y^ W^}W^9W+/z,W/9W+ _Y^y5gsvW׽_<`u{-[z4f[[[[L~_~~+zVCÞKL5[noo/߬g}ܸq?pKKï˖-wOǏ| ,ؼys^//\CڵkL|Y> X,v-y۶m #GS_qA;w /xodew߽o7hDYbگگگگbدگگگگҝm<Ȋkӵewo\}k/~>9ܜW}}cz^.B=Z޲ccc}z^.BnXȳϮM}ͷ~9{%_`YҿGt=&_lyͦ9 -x6].z7WSpW^Yʫ9{%W/sֿW^9_Y^y gyտzA/{%п_k^+W/uWs6gJ^y^ _Y^yѿl_Y+sֿ_pW^9_Y^y^0gsvW^Yʋ^J^yտgyE/9ʫ^0g+=_ٽW^y gyտzٽW^y^@ʫ9{%п9XȳϮM~^V]0y~___Y--GY6]˖ݽys%Bx|O?t߾}oO}mݶvA=olkk{Ǘ/_/F}^yӟ'_ꩧ~3&+iժUFzWe宸}{|ܒ%KqH.;G[N>S?+ lʋ^@ʫ9^+ u`θWʫ_Y^yѿ,W/`W^yE/uWpW^9_Y^y/^aS+E/^+_sֿ_9W+/z,pWYʋ^gyտsֿ+/z>+E{%Yʫ^+,Wb_@W7ׄCc,ϠF#S8iRc=aAY~~_gp(Dz#N$Ʃ𼟍~pį܌pAJKOO?k}Wk#ŋ[zII~ez'[nЯ}ƌ|SOK >|'>7xc٤(iMU՜7nbkkbدگگگگ^BLYS5gSߺv?~'_~6ر'~K;ZދwN8|ÿۿз455%gظOv~)Æ зO8q΅ G?:t諯z/޲eK4{#k̚9ݻFNM{V[m___~~~~~oުu۶~f~'_~6syɿ8!/|2yi̘9SBɗ(++ٻiݺ;fΜ{+/z}W^9W+/z>+տlʫ9_Y^y^0gsvWuk^+ zʋ^0g>+ zJ^yEuWs6gJ^yտz,pW_k}W^9{%W/sֿ+/z\>+տל+y`}W^ٜ+yW,n5gsvW׽_<`u{֭ڱc[;sݯ?~__LԴjݶ?Ϛ5wbd~s' >|Νw?˄zo15Ǐ>^y[=|O?~B/|gϞ'|.{g O+Vhjjy?#;{#745sfiIIeEگگ/bkkkkKwwҒʴw/ Oblι;U_ߘJDyOa|9{%97֘K_-ٔ7za̒Ғp{%Yʫ9{%W/sֿW^9_Y^y l_ٽW^y _Y^y5gsvW^Yʫ9{%W/,_s6gJ^y^ W^9_Y^y^ W+/ל,W/9W+ gyE/{%W_sֿٜ+yտzA/W^y 0Y^yտ^+sֿ_`^+W/gyuC9{%п992>~__f,--Lyx$wߔ)S6o]q)XpЂ~vl *دb/bدb/۽Bp<¡vԷ/x?s.9m+-L9/TP0H4x/ bjڢ#GWܞҼ9ҹo_to%E- E6ꪾaʋ^0bj,ٯ_s >yd_t}WlKzph.ڴcU~J^yEuWٽW^^0g>+/z+yտ_sֿп_9W+/z>+տ{%Yʫ^+E/gyEb_9_y`}W^^W^yѿgyտzsvW^9_Y^y ^+Eu9ʫ W^9}W^5gJ^yE/uWٽW^y _Y^yŜ+yտu)o ±X<;_Mv\uU_k/}E7>ЅCx<q ۱cS_~  yO޽+++kӦM{wx4jԨΝ;?wՍ_y~O|Awmttvno{VK{/PYF~~~______s.02I~ttt>w'_~6眝sIIqQQa0H^ȀE=Z¢ΎH5urHgQр%/rB4oùٽW^y 朝[R\XTI`kK#Ezo͖Wlʛp T聶[p%J^yEuWs6gJ^y^ gyE/{%W_sֿٜgyտzA/{%п_k^+sֿ_`^+ z>+/לٽW^9_y`}W^ʋ5g>+ lʫ9_Y^y ^+տל,s6gJ^y^ W^9}W^9W+/z,W/9W+ _Y^yk^+{exN~9~[Y9!M}>pUW5p~_~+2Ľ.02o/g;I&VWW;W|\n]]]]w}E1bDOx< /@G^\(ү~W*8ۯb/kkkk/~jkwJ~=k/~>9ǎ|oLK$cG˛~cƌMhOՓc%՚kv%,nk;~lʋ^0,`c* yW,ʛsT_[__X+/zٽW^9_y@nsW_kʋ^0g>+ lʫ}W^9{%п9mjە,nk;~m/گ}EhllߕGy~_2l9".ҥK_8qԷ|'OƎnOӗ</|?̙x<{o{ЯH cǎ-YdΝད;ߧuFE=5[̘:~~~~____~OM͖3Rߣ\w/ Obl9=qX}}C^"ɛ~cƔSL\^R{߲f{cǎ?julʋ^0,`C*yY<+o͹^-5[E='({%Yʫ9{%W/sֿW^9_Y^y l_ٽW^y _Y^y5gsvW^9_Y^y^0gsvWuk^+ zʋ^0gsvW{%Yʫ9{%_`}W^Wʫ5g>+/zٽW^9_y@nsW_kʋ^0g>+ lʫ}W^9{%п9-[jN;ڧOQיb/k響2#Gs}~ɰ<# v7o^߾}?O<98|p -:e?k 4~⒒˗ϫ+/z+yտWu`>+ z+yuW_s6gJ^yE/uWs6gJ^y^ _Y^yѿl_Y+s6gY^y^ W+/ל,W/9W+ gyE/{%W_sֿٜ+yտzA/W^y 6Y^yտ^+sֿ_`^+zA/gyE9Wʫ^/S_κqƏ.[Rҷfi/bkx_9&L71s.۷d}~ɰOя~v۶mHd}s~ԧ>u>Oo޼yժU?_}ÇM>k_J޽_C]]ݻ/!C̟?Μ9eee+/REEESS?qz0Ⱦdɒ/}K x"??p<K~rcH4zk 2;5o 8}//bkkkbo.D׼y ' wHر\c/x?sNϹoJ%/$o]?ip( /$/)uby6Tb'Iry49~ؠAט9W+/ziN<(Y7zDy 7$~]4䢻h$zA~?J^yEuWs6gJ^y^ gyE/{%W_sֿٜgyտzA/{%п_k^+sֿ_`^+ z>+/לٽW^9_y`>+ zJ^yEuWs6gJ^yտz,pW_k}W^9{%W/sֿ+/z>+տל+y`}W^ٜ+yW/,_s6gJ^y/+s Yuby6TE,~r/P~F?6h5m/گ}E8qk޼Չx9=xN#cǯo/=?xķsd܉ Y# 1>y{G#2Kw}nbدگگگb/ۍ㘹mߤ7Γ/q~/ Obl9odΜ/7pO!C4o.(:Ckysͩ'_~ٽW^y |KAuw I(Ș_yͦ9 oK/ʋ5g>+ lʫ9_Y^y ^+տל,s6gY^y^ ^+,W9W+/z,^0gsvWuk^+ zʋ^0gsvW{%Yʫ9{%Yʋ^J^yտgyE/9Wʫ^0g+}W^9W+/z,W/9W+{пٜ+yտu)o`coG!O+p?^jkkk/WWyxbq0Ur6yΔgp<#3)LyFg3A@8lP(6.^P(Al<8S@b1sl9pB J%gyΔgp<#3)LyFg qSL ?BSy,vpܟwt2Ry?՟ۯbدگگگگbدo7s(ܳwo/x?ѝ~ 2?& Mi5;?V>dH}Wg~2ɷz8ٜ/Q_`{yh_yͦ= _ '٧>9{?_Y^y+k׽W^y5g>+ {%W/>+տ{%W/9_Y^y^0g>+ sn}W^Yʫ9_J^y ל,W/x?c^_s6g>+^ {%W/9_Y^y^~ֿWל=_yW/sֿ_`׽W^y~_Y^y5g+yտzYʫ9^+ ʫ9_Y^y^0gW^Yʫ+9_Y^y=/WS_.`9C=yGG!~_~~+&x%{%'>}$ rP򢝧]]wue+_~__~I~Oty/ OblD3ῤ5?=Ҽ?St"4)%H&W^R]wi|)rW̹hf+{+y`}W^J^y^p}W^9^+ u`}W^ggW^y\f}W^J^yE/uWgW^y+9_Y^y ,W/`}W^u׽ҿ{%Yʫ9^+ ^_sƽW^0g>+/zqW~gyEuWٽW^y gyտzW^Jb}W^Nz_xN~u⮻4~_\sDם%-Bp.wdx+ ʫz?ۯ,W3_Yʫ9^+ ʫ9_Y^y^0ggy z,W/~~W^ל,W/3_`gyտzW^J9_Y^y^0g>+ ,z!{Y^_s^+zAuWsƽW^gguW_sƽW^ٜ,zA/uW9>+տgyտx_xNb{¡p,~gO}y/׬YgS~~~~~. >O{LxӦ~>9$; /|߼ >OoܸqK.ݵk9{0nGD|sg?ۧO^z 9ݩ|L𱾾!ce9>XVV3 ɛ}y{ʪUFߺᆊLU[3+{,W/M<6d`?,ϿʛMy{~*VuѺ]uY}W^JsuW^Yʫ9^+ ʫ9>+ l}W^YʫcguW_sֿ_`׽W^y5g>+ 3+5gsֿ_`θWʫ9uW/ggyu9{տ`}W^YW+^ȝ^~ֿWל=W+^пgyտzqW~gyտלqWs6g>+^ gyտz}εʫ9_Y^y/WS<دd~u ߺ]>A3r4o$H`VKw JVmUė.tDq;ZvꬌjCgXbG-,E"VB`Vy'ϟ u99C)p~N 6V%|+_ʗ+ @+V쭷 ߛ/_/ |,Pm۶ۿ9s}ꫯMMMK.=87|3Uv==޽{g͚5{{7~߿{d_ .}e̘1cɒ%<i1 o:}<ÊҨ[n|+_W%|+_W|+ߡwQ{eۧN/ɗ|>I%r7Lu2^˨7V--Mǚ=>Dx:jԻݺupx1~Syi>~Ƌ>7{l&7Nj7J¨7>}gl3^9>_] |/^kl^xBl3^W_ŋ._>_ |&} y~Ƌ_>‹>gx]x__}/^`l3^.g/^W?kI_kl3^v.8ϤWW?k~Ƌ>_}/^?<_|vWx W?kI_k~v嫟⵿Ϥ⵿\W!{Ԩwݻua7O>g|I$_W%rQڻ-ͷO:i}|i=dן}٩TC袋oԌ5wikk{ꩧxذa?3'p–-[|]v|]y啙79s\s5UUUDw]kk>裏~5я~Q]uodԽsgŤI +_W|I$_W|+_eYQ1i肾ao]6/W$_""""$ɗyJK374^|IK3u{?|F7,=iREA_L.||>⵿v7K}02syŋ7JBI FED}QgxsT}/^`l3^.+xڅ|gx‹]|/^kL /^k,_|&} |xg⵿vA?y~Ƌ_>_ | /^vgx]$_x3~Ƌ>‹>gx]pI?ū/~Ƌ.>_ | /^v!vy/^}ek/~Ƌ>‹˺***2{޼y˖-Wy s̾?C?7l0cƌ~:X1J9ah„Da5k F|I$_/ɗ+_ʗK%0zhB"Q~FW%""""|gsޫdbOOoGGkEEy/#9n\UIIΝjX,JVLvTU+..پ}gNy'N,im(/1_ӄ E5k֏UEL /^kLS<K 5dboOokGkydGj\UqIohP40QfQuIx+g/^K|&} |& ‹]γ|3^L /^kLW?kϤ⵿vA?;x_x_gx W?k~&ŋx_3+x]3_] 3^~Ƌ.~Ƌ>ŋ]γ|3^^._/^K|&} y~ƋI?kɹ""rO!R.Sg;0fQ _ʗK%sEct:5lQфDp5Q|KLb즤nmݱXlŊSNϧz{_x?<N:+"3?O?e˖ַWr1|z=;vbhչm_,5J|+_ʗK|+_W/ e۶n}1755/[W$_""""$ɗybVgm ;ߖ싙'--MɓksDյoxҶm/5vx|>⵿v7[Bf_ohйŭ/]›\/<_| /^vgx]3+x]γ|3^^.W?k~ƋoY/^kl3^.+xg⵿vy&ū/l3^.^.W?kL/^}ekk~Ƌ>_}/^?<_| /^vgx]3+x]γ|3^^.W?k~ƋoY/^kl3^+""7'n|ŭFosӃ.۱uȗK|+_r(uvnۺłMM˖=|I4FT*z3?9s?Ȯ~d~pWd97xg?yG߳g%z/X| V ;찶_yUW|+_/W|+_W4m啰},_._|$_"zcUW7ljje _ 3/^}eg⵿vϤ⵿v|/^`gx+g_/^`l3^.+xڅgxWx W?kI_k~v嫟⵿Ϥ⵿v|/^`l3^.|;x_g⵿\Ww{۫r?`+KD$_|+_sE}_}_$_fFR#X@A7?oy_r%Y\\<{?+2őU}sN)Z|nY|+_ʗK|+_W/ e+ᆰ>Ω|IDDDDI/g1Ī~p_ 7&--Mǚ\?S >'XY[[y72N⵿v7[Bf_@p/( +9EO>ΙEgWS_ŋ._>_ |&} y~Ƌ_>‹>gx]x__}/^`l3^.8$_x3~Ƌ>‹>gx]pI?ū//^x]x_gkggWS_ŋ._>_ |&} y~Ƌ_>‹>gx]x_I%r37|/>/X]ybBވv‚ᾌϏp?9x_dž]Ka_@uxF7ި]8 EE3^9}/^`l3^.+xڅ_gx3+xg⵿vϤ⵿vA?;x_gx_g>_] |/^ksggxx_gk/~Ƌ|x_gWx_g>_] 3gxŋ] |/^kl^xBl3^^xx_gWx_ggx3gx/9WDD))oouхC7W|+_ʗ+ѣþy˗K/H*N@i?я2?صkWqqk׮=3?7n\gg˿,\nO3_hQYY~Æ 3fx#eөԁٮ)SΚ>鍞+_/ɗK%|K%|Bt:%']= S:ǯ|IDDDDI/g>JJ&vtVT::UܹeP*gYM2QU5dNXQ^^Jx)0eJgq3^.P}?wǩa&Lmh-HV*.)/(.ilhR?ezu#~x+K|x%g⵿v|Wx%_u嫟WϤ⵿vl3^dL /^k,_/_>_ g/^ |/^k3/^׽W?kI_kgx.8ϤWl3^dl3^./^x)wy/^}eL /^v.gx]3+x]γ|3^dL /^kW?KvW?k~v/^r/x_=\{|(gS0ͷgWccÔ)ӧ%~W$_/ɗ+ tX1NEm׮)gGQ/ CŲz?cO?qee~rWwvv^:c9&]]]w}Iz)X6nw\wYn;vW|+_|I|+_W=HN ǿ]w{+_/|Ky??Omii~Ƌ>G-YO=9/^Q m~gu;{_tㆍwΆ/_ |&} y~Ƌ_>‹>gx]x_</^gx]Wx W?kLŋW__>gx]3+x]3~Ƌ.3^ŋ.>_ | /^v_u嫟⵿Ϥ⵿v|/^`L /^k,_|/^kl/^xx_Ix⵿|/^=\|cJS׭[cqÝwIW|+_ʗ+~guw߰a~p=%07 R?سg666f/X,Gꫯnܸӿkȑgy_w۶m7xT*S(б/ΙÊ\K~eΜ-_._W|K%W|+_ʗ2/Wg{ W+_/|Kx⵿v7Cf&/^Q m~tW,}ǎzs,_|&}/^`l3^.^.gY/^kL /^kg vW?k~η,_x_g⵿vW_ŋ._>_ 3/^}eg vϤ⵿v|/^`gx+g_/^`l3^.V+xڅC|y~Ƌ_>‹>gx]3+x]γ|3^gx]x W?k~&ŋgxsEDBϟϙZ㏽s|U%|+_ʗ+ R?ү|e»{ |I4F iߏ?{۷g~Y}ill<wqwqG;x?L9ԓN:mڴ W|+_|I|+_WwI6m„򭙜O%'ID3.ZzO]7 ){xN:ii?!`K)- |!o>}0ԁ}}x.L=iiƇhv,_|&}/^`l3^.^.gY/^kL /^kg vW?k~η,_x_g⵿vW_ŋ._>_ 3/^}eg vϤ⵿v|/^`gx+g_/^`l3^.^x!_/>‹_/>_ g/^]p~ƋW__>‹] |/^kL /^k,_/_>‹x]x_gŋl3^WO:WDD"_Tz/l|+_|I$_r/ct:=k]t#*ʗbxvSyϟlٲX,3Ϝq_ug~[o{xE}Wƍ~w_q5Oo_S__aÆ3f_ |&} y~Ƌ_>‹>gx]3gx]y/^kl3^.+xg⵿vy&ū/l3^'+""o>+R͛fΜUX:d{U;v)W|+_W\9W.<ө\᯽ֶiYf&ﷷ'Ǐ*..徟3W'ųOWG8Xn棏>:!w_w/9?/_{} _8,{_y7x7^Ã>?!ZO_<{kNJiђ%߾֯|+_W%|+_W|KC%U=F_<{ZD%'ID3FU[[yljj vr4e'OTW{j KK,o_\O}_{%|. |!o>}0%vaђ}0=Gu]ˏEggx3+x>gx]3+x]γ|3^^.W?k~Ƌ|/^W?kI_kl3^.8$_x3~Ƌ.>~Ƌ>ŋ.gx‹]|/^kL /^k,_|&} |xgx_</^gx]ೕWx W?kLŋW__>gxIO:WDD)$_oK]uշׯ.|]w-|I$_W%犂עEK /q=w-qߗ/ɗYP*++/yGٳgڴi^z~{vww[o;vޛNKcccq̙Gu . iӦ-[d~L&;X~}ۻaÆy]uU_H$o}y2?]~&URG' :C;͚1 /,.*|+_W%|+_W|KC\raqqXR/ɗ|>I%rNuu}zIj򄷹)XSSVW{j K3f̺KD3^.`s({ŋ7J¬^raQ&Ndg!/u嫟WϤkg⵿vϤ⵿vA?;x_gWx_g>_] |/^k!+_x__>_ |&} |x_<|W|/^`L/^kg/^`gW /^vgx]3+x]γ|3^^.W?kI?k~,_x_g⵿vVR_ŋ._>_ 3/^}eg%=\|iΘuᅗo諳KA$_|+_sEk֬\raqN,Xp}|iadSO=uƍsmnn_Y}'32,M6+\p3<̂z{#Fg+W&'yg>O<%b[w/+-uhs+_W|I$_|+_W!3M|;L%'ID3FT~MM!' Ҕy InF7,e~Ƌ>ŋ.gx‹]|/^kL /^k,_|&} |xgx_</^gx]Wx W?kLŋW__>gxIO:WDD)$_oـ>wwKK!_/W|ɹnH̳˗K)+9\SO\rÆ vJRxc;wnmm௹+޽{Gy?+O:z;w~?Ws=+$ɽ{~ _8cN8Yf]zGq'ʶ'OniiG~,yLRfH;[۶)sɕ>knim~ W%|+_W|K%|JxI%r7rD=??o,OjƎM u*+ }7ڻv- ^.d|~뭁m:3^.9>{P]ݕ啅ccںڂxF7]XuK[:γ|3^^xx_gWx_ggx3+x]3~Ƌ.>_ _/^x/~Ƌ>‹>gx]pIx+g>_] |& |x<_|vWx W?kI_k~v嫟⵿Ϥ⵿v|/^`L/^ksޟgŋ_>_ | /^vgx]pIx+g>_/I犈=ϊT@]ںewl⭷m,+#~W|+_s\x,D*Gj[^}?;0Vg1 Wb`j,oɉ]{]vKk **0aw_ߛ[tP+_/W|+_W$_|+Bcj8[4a7:n\||KDDDD>|yv*)ZQQؓvt$Ǎ*))޹s{xAP2QU5dNXQ^/k„[YQ1.]^xK'5,Ē=ɪqU%%vhBon}PA~&ŋgx]3+x>gx]pIx+g>_] |&} |x<_|vWx W?kI_ŋ.gY/^kL /^kg vϤ⵿vA?;ŋ|/^kl^xx_<|W|/^`L /^kg vy&|ŋ] |/^kL /^vggx3+x/9WDD&wx*gSP׷{7+*խ[.^L|+_W%ʹ:u{sMؽ7 z-[Zwϱ+_ӫp3aĈgyBs]~7.Ztȑ|+_W$_|+_W|i(o\ta;L_5/ɗ|>I%r7Pmmu汩%'|yҔy<6x}/oxҥ^hэ#G.y̼|3^.W`KMoxCۅ/]t㢑/z3껄;x+gWx |x_gWx_ggx3+x]3/^x]x__ŋgx]3+x]3~Ƌ.|xgx_g>_x<_|&}/^`l3^.^.gY/^kL /^kg vϤ⵿vA?y/^xx_gWx W?kLŋW__>gxIO:WDD)$_o^hэ#Gtg\z˗K|+_r(,]~7޸3gy|I4F>{oy6ndE[n[JK+_W|I$_W|+_P[ֲ`sm _|$_"z#窫655OWWW'Mǚy K[o,'{͛q>9x_gB_eOW/( ?;n- }oy}QY/^}eL /^v|/^kL /^k,_|&} |ŋ] |/^k!+_x__>_ |&} |x<|W|/^`L/^kg/^`gWϤk/~Ƌ>‹_/I犈=Kze{߽ݸqK%W/9W~[ֲ`{ܹ6˗K,hk ZW۳V +_W|I$_W|+_P۷g҆Wd%'ID3\.]Z| Ҕy wI_={_\>+g|3^.W`KӅ ohз'; /'W,_'u嫟WϤkg⵿vϤ⵿vA?;x_gWx_g>_xg⵿vA?ŋux_gWx_g>_] 3/^}eg vϤ⵿v|x vy/^}eL /^vgx]3+x]γ|3^^.W?kI?k~,_x_g⵿vϤk/~Ƌ|xtSH|5d4'WX}|im4Q{|mͳfL*ꪘ4)s_{{`O*_|I|+_W%|ICXG@/^kUGa++ &u~ŋ7JBgפIZMgΚY7:x+gWx |x_gWx_ggx3+x]3~Ƌ.>_ /^g⵿vϤkg⵿vy&ū/l3^v.3^./^v.8W?ū/^xx_gWx_ggx3+x]3~Ƌ.>~Ƌ</^gx]3+xg⵿vy&ū/l3^'+""o~*gS40\mm޼iYSn?~+_W|ɹrfy᝝]¾ֶiYf&s,W䭥#*"&w\?>1c7>`ȑ|+_2_$_|+_W|ih-s5?>|'%'ID34fLYqqQOOo2YQQ3vt$JJn&%e󖕍)**LWoxQ>o5 /Nʳ~s⵿v7SVT\ۙ,>ؑ(.)%v|̼kUhv}QY/^}eL /^v|/^kL /^k,_|&} |ŋ] |/^k/^W?kI_ŋ.W?kLŋW__>gx]3gx]3/^x]p~ƋW__>‹]|/^kL /^k,_|&} |xgx_gŋ_>_ |&}/^`l3^.8$_x3~ƋsEDB;|޼k*+4l̗/ɗ+_W\Q*/s5?iqKo5D =zgCF?k2U|I$_|+_W%|IO}j駟(_|$_"z# VgZy''--Mɓk⭮2F7,}Ova>j3$ |!U>}0Dû%v'w_Z}g?:x+gWx |xgWx_ggx3+x]3/^x]x_-|kl3^.^xl3^v.8$_x3~Ƌ.>~Ƌ>ŋ.Y/^}eL /^vgx]3+x]γ|3^^.W?kI?k~v/^kl3^.^xl3^.8$_x3~ƋsEDB{O]gC/ɗ+_W\QO6SO>Kao5D = #dE0j~克7>|+_W/ɗ+_W|+_|_^pq`_~֭{C%'ID3F@655tOTWW'Mǚx K/ܼx /<>9Jx_gB_éOT/( //^8/_] |& y/^xx_gWx |x_<|W|/^ғ{ ɗ͋/|ƀ}_Lvp^$_|+_sE᪹ 7u֭{}_$_fF^#X@D{'^t+QGg҆GV|+_W%|+_W|KCoGҥ <T{'Ε|IDDDDI/g%|`--MǚxzeoXhhXr#_] |&} y~Ƌ_>‹>ŋ.>_ 2/^W?kI_ŋ.W?kLŋW__>gx]3gx]3/^x]p嫟WϤk/~Ƌ>‹gxIO:WDD)$_o KW|$|[\t\˗K|+_r(\utt.]HPŋ{'˗K,+N@wX1Jb8hWOOCcc)gM |I$_/W/ɗK=30}}׮)g%(~WDDDDD>/L|{uuuW&}}<]"14GRjƎM uUYY^Xho zzv566LR?}YZϤ⵿vϴOG85\TwWweyea=؁ҐxF7]8~t:$_x3_] >‹>xr嫟WϤkW?kI_ŋ.gY/^K|&} |& gWx_gŋ~Ƌ>‹]|/^kg/^x/gx^.~Ƌ.L/^}eL/^vl3^.^x~v嫟WϤ⵿vd3^vL /^k,_%>~ƋsEDB!U<a:O?cyKNϊŲL>ήSg̘v)?_Qѓ|K%|I$_|I$_*BcJ]:cڴS~s%'ID3JJ&vtVT::Uܹ=2dj\qq;C8"Or3N=i˖|3^.9z>~0{PK&vHV*.)/(F:f:i,Eggx3+xx]3+x]γ|3^dL /^k/^dl3^.g/^x{~Ƌ>‹] >_/g%gx_@|x%_ |&} y~Ƌ/I_kgx.3^.gYx%g⵿vϤkW?kLŋW__xtSHx,yL(߮3N=i˖\@|I$_/9W4wx,Nu3M;Q|K\t:?G8DyLSdo.{k_z\?|K%|+_W/ɗKͯ|~k_x绹='_|$_"z#PVgZrDOQ[;9Ox[Z2'׆[]]oXz챇/kk׮57oN&3' |!U>}0þ%v.kshd{rO/gx]pIx+g>_] |& |泾‹] γ|3^^xl3^.^.gY/^kL /^kg vϤ⵿vA?;ŋ|/^kL /^v|/^kg/^W?Kzҹ""rO!~}/ڵsod}Ϟ>˗K%W\сJT.^>`%_W JfT}\Ξ6V-[>jsW|K%|+_W|KC=rﹳm{;+_/|Kgx]pIx+g>_] |& |泾‹] γ|3^^xl3^.^.gY/^kL /^kg vϤ⵿vA?;ŋ|/^kL /^v|/^kg/^W?Kzҹ""rO!~ݺeժ֯.>v۝تr?۶t|I4d}\q_ֹgsڴi|+_|I$_W|pP{1HW$_""""$ɗyZuԒ'|``--Mǚpy_=ް4{cƔOvZ/hK?ko}p>Ep/( S>fZ ~)u嫟WϤkg vϤ⵿vA?;x_gWx_g>_xg⵿vA?|kl3^.^xl3^v.8$_x3~Ƌ.>~Ƌ>Y_ŋ.Y/^}eL /^v|/^kL /^k,_|&} |xgx_gŋ_>_ |&}/^g>_ 3/^}eg%=\|i>w̘iN "/p|I$_ʗ+&:cN |I44Q{~OM(,\fMQL+_/ɗK%|K%|Px ]Y^Y(lwlb@@[W[i_x](Is5}QY/^}eL /^vl3^dL /^k,_/_>‹ŋ/>_ /^^g⵿vϤkW?Kvy&ū/gx.3^./^x.8x+gWx vW?kI_k~v嫟%gWx_@|/^ |& y/^xgx]3+xx_<|Wl3^'+""o)gS0wBQaab͚F(/ɗK%犆TG72,EEGQ/#ODԽs}}Zк[N:ή.W|I$_|+_We|i(間N:3vܳ>_5/ɗ|>I%r7֘1eE=KGG,6 o210_\\RZZ.oY٘đa-8Yg1Qo<'bSA/BC& c3t[z)]&jy)g6%ZYVvo|sa_`{5;_w_>~(oۗWQ[f/,tvepϳ^_sƽW^y9uW/sƽW^gϳʫ9^+ lW^y z,W/دʫuWsƽW^y9uW/3+5gsֿ`θʫ9{%y_Y^y+kθW+^0gsֿ_`θWʫy_Y^y5g+yտzYʫ9>+ ~W^Yʫ9^+zYʫW^J9_Y^yq'=WS_~?Xk6 eN}/n/گbHammV~u՞o/ôߊ73=m5O/ggkkk/گگگ/ 8?=R~g/ ?Oby6gwo߾I}Om4鏯_o޼%;zyr~ިOξ%gZ9rWsֆ%ܫ}Q`_yB~Z?+3ӭn<ۯ,5g+yW/9_Y^y^0g+yտz}<ۯ,W3_`^+zA/uWW^y9uWsƽW^y9uW/3+5gsֿ`θʫ9{%y_Y^y+kθW+^0gsֿ_`θWʫy_Y^y5g+yտzY+zA/3_>{W^yտל,W/3ʫٜ,zA/x_yu9uws==ogg_ L=886m~_~ã8?=)}ffm{߷_!oEFzcc FjGG67~~~_____AwV;zv{ck/<ٜݍ,/D'_^^Hޯ_KKÐ͛_,{{GGw}^;66h+ 3+_s6g>+ {%s6g>+^ gW^y+kgy zqWs6gJ^y zlWלqW^`gyտzqWl_kθWʫ9W^y^0ggyտz}<ۯʫ9_Y^y^0g+yW/9_Y^y^+/ { tT|W~{ݱEӶ_~~\1(}0ɇ/ܗWQ[Z/}ѝىZ/y_Y^y+kθW+^0gsֿ`θWʫy_Y^y5g+yտzٽW^y^0g>+ 3+_s6g>+ {%s6g>+^ gW^y+kgy zqWs6gJ^y zlWלqW^`gyտzqWl_kθWʫ9W^y^0ggyտz}<ۯʫ9_Y^y^0g+yW/9_Y^y^+/ { ^~wfg'jӶ_~~\1<~7?7׽lΣ}W^Ypϟ>G_ya}8~fܼh^}Qϳ^_sƽW^y9uW/sƽW^gϳʫ9^+ lʫ9_Y^y^p_yW9_Y^y^0g+yW/9_Y^y^+^ ,W/9W+^ gu׽ҿ{%s6g>+ {%W/ϞguW_sƽW^ٜʫ9>+ ~W^Yʫ9^+zYʫW^J9_Y^yq'=WS_~ۛÏwD`?7׽m/گbxno~==]o/e7>' DM>?}#E۝Is.E:?ˠ>ۯb/kkkk/VozTbyK<b$ gsv7Joq ~!oWχ$oӞwT>/a\ʫ94c<Tݙ/}м+(-}E/aΞgu׽ҿ{%s6g>+^ {%W/ϞguW_sƽW^ٜ+yW/sֿ_>c+5gsֿ_`θW+^0gsֿy~׽ҿl}W^Y^y^0gsvW^<>+{J^y l}W^J/3_>{}W^J^y^0gsֿ+^ ,W/l+տgyտzqW^`gyu7W^J9_Y^yq'=WSJIgN{~~n$Ns{)گگ/s^貥$rTrA>x}$kk$*cq`( b?Q'^ 1Ib :' =Fy@^hFyD!p'Cৢ 2B#oHJlj!SqPe@^hFy@^hFy@%86x  }~;Q?! 0^Xy>yPW+@]DuJPW+@]NB_jONΑAO? 4Z8_9'"t$/hm -pSs5DZ%yڶl+dJPW+Ќ 0 '''//ǧO~gz꩝:u6l֭[ovi;w2eʱ룏>۷o˖-wt3&Lرg1zhSׯ_3,{?lDhɒt׳籏mUlhނ{7swoY7e9OUNaQnvzKc3gwEHa%Yc Kڠf ?yrnoWVUɊ_~i{mWQqhtr,(nѱjlr뭇f,hבFs=çRM\Iɒty ;vvG?tEŞQ۽}FgCK[l{΁$[ozyZ>)'smWa<}ɽpذQ[nK7l~>/{Zc6|RInWŅux뭷w9U9ϙ3O;u:5Qöm<ct߅l=KK>ŗ_.(خUO>9oΜ=j3g~uذo߮hs玿馱c9veACsӺ/UOǾ{oyy…gmuǤIx}s~_Rm^GMz5dH-;񏟜7oNQQ|_߾}7Mv{ǎml6֨6xqҥO^|΁䶫 kpuW`JGvѧZP%g̘;~Mcǎ9nݺ~s|0{׫{*A9ZAyMs_-/)Yxmccx}G^3# ѿ뮻V5Y_yͥ|r_,FOxΜy=z|Ng k׮$y74~̘#ڵ ΫyzΧ򅜜6 > ,\x$p W6m6N6cǎa~?P'{On]ۻoկW=]oڸiwnj^m\ |}륋K\z_+% =I7^x䏟3oN30sWl~;ׯ}vj~AG mY+ͫhr;cMoS~и?Adj^Wg=SpJNzgj^U9g g2i793DȶyyS9I-zg=m*6YnAF=AIWg7YdۼJs$_c9lWAsɼ 7YMz UW Z$83Me^U49͹Md^=n^E!ܜ]Oh*Al^&sYdۼ :59nz UW땤Mb^_js3&YlWٜ z2@SW-/5S\0!gm^_j9?YdD/5g !z2@SW-/59d2/59By}'ddEީ9Dȶy½Sʹ.ZM: {Fs< a m*ͫhr3kMe^pԌg=gזļrhrN7g=yoͫpsNȶyީQuީީcҷ!59/+;1Gɹ뮻Vyo֡CǚGYgG3ni1cϮdkjv{)/߅ K?H0i7xܹ\kvR?r{i}?3g^E5p k>h/=/)'ɾz/qʿl9' &]Ѽ 9_ziZP1޼ŋK.}cz7Mv{x0fy9 !ՌsǏilm혾IlW3Ə?vڟ[8mڴ;՘,…Gr V|+'m۾K,=_{СàAb'ַ5O{={o/袏>h.NYtis7p} |q챠꫗{jQI4v,Y׳gW_{mOzfܸ3dDc`m"6!^p5|߾5k_ Qsnǟ|:t:誫6nd,dk80~zŸV:u6)\5pב/g=ؽbϞ]3__3oY3_t/cZYYhQᶱu7΁Z.lJ%yy=۶ז,i 4Rm>޽/СAWmܸ)ps|p®/؜p _̫嬿 Q=]t[,Kk(sbjܸo{|*>^Q\pMغ7.ܜcu^䓏/w׮jЦMdv^{5q\Xs o]CEԵvڋ.,nݺ>˹xҥ{쩬\h @ k_[O.]\ڵxsuտ\_UYU*hrʳfqEȹn|ж*vWĒ_ %%KzoګtO<Ӽ&皞u|E/ڡU \:3QΫWYwis;}v== 'U9{3DȠzA߼ 73/g=bL9d'ng|ټ&g=C97dݨg"yy !ܘu@*Ez]gm^%*}e'\Mz U̫[B9yU6Yzή'4yo^/g=C]Oy6!3Dȶytk^#@s3Y8}yb΍|֓'drgHYcgg2>8̫g=gדʼrt 1g=_K 33DȈ/Ȝ=Bd2/5S9d&t\pg1gדY fWr>9Dyީ>:s|SӚsPzsvmd8^xzrm wjfs3εeŽSӪ1.9,59Mϧ,W}}:4}S?_pTBܮi}^t$立˥￿dUW ?ҷ{O>]vA6m ܮm۶ڮcɒ=۾ګ?钚l؞{Gff,y;4}!KK=,y1'վpo)s뫆p+%yy=۶זso}k:\qKrϞ++-*u|in7^oU>޽/СAWm<ڵk/(F~wc8pO>Y|ey .`Ȑ!? ꪪ۵kw9ުUtYwݵOb#oV\9zfɄ ǂMGu)*jWPP~{Qc9#F߭Xqnn݆ۿɇm?Uǭ۴~ OugYVZΖ-vMAKŋo_UP g}weU{w7~xIRCyiZs3m?[3{mKQv,HaaCu4ʬYwmSlWi2r_V:ߙx~t@s^̲eS 28vN!ܜ#;\pM7秄sC/y֜W!:묂~.=ʪ}}.JdۧLi<2[ 7c>s:mڴnOG|g.۲ϿxyUqּ =#SW\}mY.fھ}Gr7|իWO6:ujmx%)//_`耨 z{Sɋ.珙E+X_^hrt+oz0r.8~=ʪc_W`1rċ_\LL*gJnyg_<g#ufϫ=2Қ!ܜG~cS˟Zzmn{gMg|ټ 7zzhr3T4hWzgp^5V; y5kۧO|+;'O?=??+_ʃ>hѢ>M6ǍקO{gݱ~֭ᇛOݶmÿ;38#̛WZVzN;A.^EEW_}9C& N8|W Zԣ]+*F]seW^yh:'/+xߢE5]t4^{;'=޽|}ps|p~{1%>?/nΟ_̫欿 ./]zW^;u37oޜҕ+Ww)ӶmX9x6}Nʺ==9׬ǎ׻w֭sfϾb25UDžAKz]}u94u'3gNiiի;u:ەW^y%ի{impEQQGue@DDž9JV^>/|{u=l^ bh直f5W^vsP۶n{t o{._:KW\zŠx6ǿ>XA Z~z^~ހ J zyQ·{qc{:{fWX 9h8@sϿ̫g;z]]|{Ķ؃z kNͫps:"YdH4hW iYdjy$S9Iy{3Y{pBl^E!Ԝ?wݨgyy֜  #*ETɾ.Ɛs3v^0̫4?YdἪzAUj3zps|yU6"Yjή'4ϛUZs3dWU#5\zr3Y6sͫt%g=eW!z%']Oy~Yϐggd^}\2Қ!9 $Fq^%rz FΩ3DȠ|qVϐ]OhR3!Ԝ]Oh*R3!uY+s[+M9xC49,Wq!tiYd{5< oϮ-45gHז{f6g=C\[hZ;5Rv1g259Tnz ;{b_hyQ̽S=_pTBܮyޜҕ+WwHCѣh׮\3+/CGq{i:g{**v__.@]Ŕ.ιK 2HlWQs?`WE5myX]uu @<96.^W_]\ۯbhQQwUWWue]y8{+]rŽz_}sJ ٮwƍۧO3{c:=q铓s=޽<>(**ڿuuQ.C9Ι[KW^ݩS&[bW^-[4Dcfmץ˙;ox#k j+"MNN=zk&ǡ{?fذcZnݖ?ڶiS׳: wf,6#Mwz’3;oPf~%f>}[W>a¸䴾 ;wڴ6v{\|sM:l͚t^h.:h~M^6'tu?sdOݻ|&uI"6&纏w׹󙯾zs:6|Nsϓa9)*[[Y2UZ9'|"5u5kt99۷oݺu&LСCNNmݶsM60ufYioN'~:aj{+7a\[紞pۄ];wm֋sP/}i}_5ދƾ.ы6b{VOM6 zuί־>!^2BԼ&69C g5uf'@*y i] @Ϋg*ܜzhr3t< U9'[ztg^%>dj9'$Cٞz k yMzpsNn3*9͹W y_4 7g=m*h.WiZ?YdἪzAUj-Azpsg^ͫhr3Me^%Us3iz2@ϫzgy.=C49lWAU:r>VC@X9,WGߗe^sjWz2@+K`zԤv? ==*\2ҝ!9d&1/5M9'`Iȶy~s g2(O|r{3{{v=I+K8g=C9 Tf6g=CדVfW:r9DȶyީYpYTRc΃k֮6k#dEV u.=GӫRSy7|36[֕O6:gԈomܸ'뙲̳~k%FCm?p%Xt {ܹ۷k'1薱cOlZs;wnA~~}mW84c>2>X~;wۿ?GwΞ}̙JHH#Y@ ok70o۷س39}vw/|책mlmsm,}|ԨoqÆzxܳ_΁nW&L~V*=ڮ9o֕O4kO1@([n?ٲ%9O[P߯_ߠ`yT>*ߠ/R9b^E*Q^w۷?6:s]wtz &ObӿժNr=;Fq[m(+,̳>w_c9=~ 7;sg1]b =coOߺ3_sN/۷LͫY*Ĝ?䓒E;ws36;}!{tG,ױM-;+}{9ObӿժNr?˹Snaa 99mz:f~'?y|-5s}vՐ*f;޿߳wΜyבkٮϹbĈٳg~ĉ80por֭[?ߘ_7????D39s/>hС?я"|8?#=>{ƍ6QNYx̢ΝsZ}]l3fTUWw-(Zq;Xc|=;/ӻ_C{bj9Gyy# }睇3gY>%~lW Z̜:`e兗^Z2G>(+nks/p\U49=⧞ޭۿ x/]?al={m^GMzRpqj;䴮ogΜz`CGG+V|=zķ>WEs㺈x琟#1Ґ??s{uFU9_s F6{4?Ư'N[ ]reMc9x>}c/~{n/WNj9׻WBv/]w0v.Zd~c_~G>ƍ[㋜'LΧnSQQ}:t0yy?dj9'ԙ/s:C }19?1| -_ҺJp5],[V6cU]v3?8~֭5qUUU]v׾6cƌOXbɓ{~ۡC0QO>O>k?խ{?_铧E{hmDAy~yy_]?<=y/ԙS+V^}p!z}D˖-]hl. 1.^xbhO̫hr?q|h28~ ]sq9 yu\|yMz9ʖ͸cFuUuAׂ1dj^6B98g&g= A߼ 7zg=C49lWcnv\HqRO>3Yx\hg* \QdPRU49BϹlW)_rNwYdB\2͹E}! @ΫW Z{g=gדļJg* ]Oh*ͫhr3s דȼ6#sYdռJpk^s a9,<_z_ynI^)Ôsnz2@Wٜ I~z f6g=C9lWA׫7B?/Ksȶy~\og&g=AAsR#98g73vug7gדʼr g=gדAעwrN9DȪyީsusp^pHrSnή-dۼJq* !2@SWAyMz9-4y½S#9kzz #*ߚW 1g=mʽS/=vw:Awr_phrnީaҷ';s[Ω>x~pM7n+O|y=jrXѾ}9~bo `8Mu_5f̸kqcliB Po)B9UX7mjO?sNNK\ѿ@AAވf׹puˉr+V|==@>WE㤚sLYٲkׂ}xF?lW'?pxޯ߅+Y]5]Wwy\y8bŊɓ'#>~ۡC5k#k={nݚs~Nv0 I-۶ODMN˖5=U?SOo-qE pj۲mUU)jӦZuT9iVV"NO?4'|G땟FUiydN^NR~*h>#UUU5OkV"Zj%?{voFTӅA*, ' C&(UNU*յy=뼃ov绾  *BRB|T(SOd!xAurRkϛ e* ,DYd!B +V)@)EpWj u+J ](;]&g\ `5.3+<(a?C{\solQwvԿwy^R_[rnn п _Qt_9@_ /п.kwl6{z8o.r830 "(K﹖dKެ^jYKWJ[4sRQTr_R}TDE\qy̜93gx͙Ѳ Ҵ4m]6s̞={֨QEyyy= ?^z}___MϋcccǏ߸qc{hh믿~9s/z||ߦMjժiZ0l޼;v0\h̙3KĊKo͛}Y߾}kժ2tuYYho8y:u:9UO`(mc-Z,hf&&fx2n Çon˲z7JQPP0s?Lj2y|MXFZuWJRDזAiL[ηkl?y&le4 8?-~tro˸iP"fc妙?Ker"kˠrӌ_ _&Tx?i;vKժ{z}ii9y(_i6 `6'n=mĽ/p'n=KLsק 71;.ss4 9 ٹrʧ}7&!->m׷=|[ns=v}uNp p[9M̲>u  i%!z5{|ժ2UŋN}o䫑Cq x )5Կ +=wLޱN?|UU7,E/$__}~i3533( ok/r_ZIqdwg󛷝rߤ̈5>{'N\gS}'R4;rPt _yժQ$AWVg&eX3wg^xdu(.sܠ f?ԿPOAVVA/,*E W"K/,*EH _za +=Uj{aq׀^XPOK/,_R_zaAUK/,t_zaA=K/,DvXPUAJ-E毹D^XPU |U/___AUW+T[K+*U+5S+T_/AJ$Uj_ "EH _/_s3$eҤĉw>`:oeu{Bߌ*^^% +7ݨUxVZDזAiL[,' PfeY]}Aݍ2d4cɘ4ǯ-'Vn+K+W9rdJJJS׿,ׂ Ǝmx/bΜ9-?~I&--x^/'dɒ7xڵk/LOO?sҥK;w?^~ڵ,16lΝ-,k O;MЦys {||+e2nr4~|wdXvve4l.5.Tbw3Z/sr-[.5[!99ϧl2n4Fܿat:mV/A y *7eibg/P)lLwߍ޴}Ϲ\⋿}^CSx+ 4I=;:NK___qZ^6s>< Two:ܾtW}(MOϙ?ghω)O?^3mZg^=_Z]HG9JZI:_W3?#ÆZWNzN=}B}RN}z^әr_N Tz5%ZV䫑mLg9(:i%Aұgg *: HG:))UI+ wq᫻?$?'?r1cHO9yrݗ_?򏌴ԀZzzܮgp^|oFpF/tj hV,9zxʐ6Ϟ͛>;?6>znhg/k^PJ!VvҼɯ:rЧZ#ܕCĬY?|Ra&>> fӦ펍޸8fr"k2(m3~e4W sL-F 3\U=YܜߚuXX0Uɵun~Oc͍k/85:6?[;R?39562+˄ >3%Tvw(тGO\na>Գ=q"+9#)S\Uxug/q!;%[箫p(> iRs7+-=!}ǯ;Ŀ5=Fyz+5FZm@}-s3rc^9xE,Usk1{uwZ}34G3xVe1r|d@S)y<}`rLrvjJ*y%O}6ܮ0#8q_37ĺƵ?\AAM>u|yYy&][\=rկo%c~lX/fFvye c/?k/kwLٖW4F5׽ݲܻ{ݷo=}Kʸe언mq"+:e#r?jR[)B,SaⳂUVy|d#"ЍYpUʁNt{I_ pa/Bǎ.?d8[)[UP9B*Dŋ5hw%3s~Ϟ-U}ڳgo=Ciӏ'Nh5 NaD&֬U?ٮ/5o>}{Fϥvᇽ,yѦ|^UN9ѥM_v*:/_or[>UX+ Lp1ǖTPz*/_毽^Gg9#J e;lr;=r[oZY,xҘbk94e]:l8mt)}Ϟo(8!g"Kq_=r[mʁ,)/ȹke e"A1ŃIܓގ⿶ۮ^ܡVzaqdL{n _e¢TEU@ _;E"A* zaq|UF/,__zaA=^X/_/UF/,vXPO*A4E毹^XP[* WFk{W/_/UF8 WK+*E WK+-Ԕ"AJ'2__2__AUK+___A=W/_/5=#_ W/_篹 nן)ܫW Ǐl&ԩQ =o(855[\f͙޽-^|4>'EñĦ7nW_}UK.=쳑Jn֭kruqkN]oa=XxwB޽[nm˖- Ô)SƏnСCǯ:S _/1b't+)a 1yZU[80p5 ֝A ~ភ_޼}S֯ ƂmB1wV5"᫯p]ǂ5 Vrkc4Zz]<}$pMY3rO=M;uM{lz-柗>g_1eNǟgy>h>7fH-I~Ml&AA7׮]С+{hذjxxscrulZ?v?衲LN9~ ƯfJeBŜҜ֭?ܱcXѲe5q ro&֯ Ƃ_%K{v^ fu 5=:=w̟%5fr"dPڦ5ь_ ֿL3>/`TYV|bbƕ+-Z~lժZ^>&&c78n,ߣG7zefprjz!uϴ=nn/wr^v+轢jêΔi1-K82{n'G՞=Gz=N٬Vnnǒ}8BV<?`!rZj<}rLru9 ;D L m W٭$V>S4!n:ݸ[oܸ'zFq׮֯u2%eΝ J>s/#GӰj׷m{yO=X{#Wy_E+&fۄm7Hg*(=Ut|c;qj #zrPtkV+ͻw;V_Čj-B؏TPn_,_z_}׀!__cKy{֤%szz7BC}_D#Xan,!ݰ}P1z} jpR"_}r98h "^*UZTh&"ba|}dd_lV❝ΝK]=}}]\\>gSS's /4]{?8أ_zcuFlc+~{?uw'ؿȾ}.  uoŋ&D%q"R37B ..>;TwwU]%kеLރ7ݫG^S`}}Na#dJa\>0n[\AAA̫svh4bJԝSw]GܸnQ٨õtET/0>Z=km%L &~wʡ+yycG Ckdecd_#[nᱮǍb@>'_s]E/O_Uݿ?iԿ |# VJ5[;ii9/g̘W\H DpDp鿨*s'uy92G^X/_{ jEq9coQ9@ ! s\D/,6 Paʽ WaK/,! ԓ u{r ^XPO*/_e/H_ _zaAm^X/_eԿFÂWyn^a /_/UL|/_ WJ/_Y/ 5/AJ'_ zW/_/HJ/_//_}WP|* WK+(5KψW/_/U*xP5z}iYYyuXkB.A611Ɇ{%JQw{JIF[::?\CW23JLv[?Խ{Ma_ _:"tsEo?X#>؊}u o_R{bƂL ^^TP]Æ5ljk!tY6-,4GN֭ȿOqΞMݸ…Ww{iS_*DG_3n|c{zPYׂ1~%5~^]WUWgt77킿n]/3׮e^]@9swW{ԭ[oz7jU;udJdfb9!!ݰ\$iMoT$x`ԨO>f.?tfMŅO rY[OL Xw2++//O/cBĠa!$:~|̙ݬ ֯^z,88h4JB=|jz_O1lhݣFpU .^~Ɔ\]2ғ/{xy{ޞ ֮ëc UNϚ_L --k^B`YzmZ]F=TE=~-;Wj/*w~v̘fi;I]Y3~s{4 6 n3|x{z/MֿoԿ̟ok!t*zl9?KjDAk!RPg}^_}no5{f^_q[:w+6&]w6j./+Ϸ{f.wޮh5ξb%ɼ).wv.}7_dܛ'u˩\<&jRf:W],W ѸzrG{?ڻc&) W%p#_WbС/9˹W s& _Y¢TrEUR j_e"A*&Կ u{r^XPU*/_eԿ‚_Y"A* U2za Q * /_/-=__QNK/,'+-=__AK+o/{8 WK.C K+___A=_ W/_Կ_Y* eWԿ $+_A%_ rENK+']~xP]_~MP:U""5_ ohw]%ʤIQKD^Q=JN zɘǯɉmƯ9 W eE5jHHH\`/xw3888Grʭ[ŏo3dmE~:yݺuCZc=6n8c{Rgffe$(=##,ñO썕Zr=d_VVx5ϜaA^…5nGpu)Z.:|xho׹sPժkמBSÅ;v$lxX׋]70/:tCDDMK+˺`,X(^<W ֯~WeYwW=(VV$oo&Ɲ]_ZۅnnٷO\7xMFl-s, WyuQ!Hk6l#Xz+ԩscuͭ^ћ6]-qq7=Tɉ4ǯzEoڀ+ s~s~ww]Hq+6gno꥕e]0*?-T-׿w]݂ _ϖ'BgyYRg&'K-_f˄ >3F>E pw=tj_' 3*Bv⿜͜oѢG G;YիY"C E4cFWWWk5jT]w@֭qݻTuz&tSC7DD4ܴ~eŌ ^^҇˗3""lޣG8Vԩ;sca޴ wwߧ߸?jyff(Ny䞑{j_Q!s|}1952Ư/[?緦19Y~R&PoY>χPwSwfB鐵Y`.ߩ$X/0~oTJ_|NxD`zʰ0f̘^z-Y$>>}}6m 0ymVܵkםfy[l1,..e?=WϫWڣG&}vG^pl>pII'6q|Oĉ멩{&>.{}˖..~~:rh#ǎ%Wvv8Rd_ş>irQ{ݻ/j$"3/:4Djq zFbwv5oq]͛9?hQ#6=^uXX /1mE0hP/]ǂfa]:urʄIOŊU-_wD~^Of;:;7n۱Iؾjvonv=|lL],ߡe5 mvɂ;Y{aYC0?Q_&B%oop_ļ/P\ogx#QMܹ+W2L9hP//'7PݫԮrvY՟ppy^V]џܰJ3Sެ \k^uZ+:=ݻnefDP FZ/Ȃ#7 n,\(uж~ 82.gx7opj){Gҽ :iCa&EWpNgXQZtvss+śƏo|x`wi˖EFFXIcc`{BlE%SLioܚ f֬n8oz]T|LxwVzz`:ۯG|3B7:GGqsӉc_ݽM^1v m=cg;,7W;:9_xu!BC~cNpqիVJ-ca7mdԨÆm ?39~&ܦL[97Ս],u֬nuX ,YrV-vʱ`\Կ(dئw̟6fr"k2(m3~-d4Wj }g̿o4uj-|eO7Pv^muxїZ75xΩ;==r3r ׿ޑGm8Wkf%>4PzB{ 6huoЯ)|G&M@s{-QmmNZw}>5\%nwyߍo7;{;٩K[.E>9\޸=wAPvK}Yx;9+r䯹U[+e}Lݵ_wuWޓ ZYyyoEE^n6_?h/߲oE;W=d^5*:/_YֿKї#NvV,毝[I 嘿:WN9[TUttrWy_E'A'Z97s܃=ը=SS+ r*F.Z \?PGg*(7Wk^ow' __ _蒣;ws⁉}˖EFF88hYeŌsoFY9YſF@P^J"1;vL g^jܙ8>  iQ|yڴ6--~}o;vK: u:!)db Jی_ eBŜs~ ̶fΟk&)oO@c‡4 Vn6-zdڠ^0i LƤ9~/krb`knӌ_ _&?y?č>c ooW&''ӧO׫Wfx㍏?X,9r…W?w\ڵ-<͛>|X,DEE=ů 9sXػwo֭Kt&77W%>?ɛi/Kk-x\_mqu閯-۶ыx ^ 9?0 ԿԿ̟0~r [XijxjBB< 5^P/   _K@@R_2//  A _ @@R@ / K __@ڲ墫Cf~ _Eϗe[e\`,z 0~0cݠ^$Ρ&9`/Bן:uߟ3g;xXf UDY䔝-O<twu*gnnj`,0p|`,J (osW5˻s}y)A/%/ K A\@@//%/_ K K_@///% _ 699ٿJKtPz9?Xdaaa}{'~\z9sL?۷ɫcXnٲe?k>־}m۶7ѣ6mbcVRR] c&N!M_/9? Pr` 0~ g(x+P4a۬nr| ../K_@Կ%/ ___K\2//%/K__PK///%_K_@%//K _KY5m6WP:"8Dn p@`cH0~9Zkn\b6}^^ɫ6nxUܡCr...s۷aaѢE?_VVVff%$$ܹsq322&L`XRJ?__ѣG{{{!CYd /Z3g,}}4,tI&}zxaBBX%&&F,N:"CXXa4wOOϹs׬YӵkݻwxNڵ8/99"~~~-ZQ˵kbbb=jM``)SG}4nܸ7xcݺu8w֭[srr 7;wnz*9 4hΜ9b Сغ~uհ^pavj߾}:uēʺrʑ#GĿ&7Q};L9mᶷ "[v(uA B !J01 |p!Ƹ%DAEI>FEA ek-wxC8jpL;~<}?9=S ~g}ٳ?aUUϙnҤ<tr蝟uuuV5jҥ%%%?7є)S fΜgϞW_}G=zX`pq-}TZZ:v9s*++]vE;OO8dȐAeoJ>t-=tYf6mھ}{]]][[[aaG9mڴI&As͘1cы/^jUMMMGGǀ3g_c…'N|W߿:'Om7\r jkkdIIIEEEuu N8ft۶C--Dx He>_|Ձh|қn0??'_bNJ;L0Fm55׾s;V}PkaaN]}ܸ7=oĺu5˗LQ5לi۳ﮏNaVss{MǏ,>2sD"Gaa~tÆ 7oޙןa͛aIgN:}|h}>zyAA<']Fsn7`֎^3ǖ-'(--hjj=uv[֭Lre~r޽?Pu4Gx⓲̣Y#{̟GKʞ}v܎G{㪪ҒdԳzUVVNaֳ~<Ԙ_sx`$B] rb̘'N@T{orE=z$zʿ ֮.NQϮ] d,qJJAmc죉=1zy B9^ػ_;dz&L'Z놶0k>T^^^7Wl޼t ]T^DEt)*ˌ$?wu{Zpsyy믿i]…g_{t}˗飼kn핕s)zD?޶g}{d0 gvMۭ[0gκH2{`s2rAB@U[۔)::uuͽ{wVTF㹹a*C]]]:V3_xabt ƎΟ顇/8Zt}~3ʼn{D>d24:P@zƎ[om˩ zΒ%[ZZڏi}㍯nj޽Gl?~DYTf&+?z77GׁۡC-}a4ywٞfvu.^ímCCۆ ;V#!A}g+0 nfx/{MNNPUUzx[[ҥ[0 fuF&OsʝO?j6Ϗxgy=W^;4z[*^*S"['[o(z9MN(?___/D"hܿ U( Eq .ą"PB@\( Eq .ą"PB@\( Eq .ą"PB@\( Eq .ą" NK t*t@\NDG~ t( ~tTÿ(kccZ]QaQ!pV~@\(?]\uP D۝3;[raH{fN|.VCH&5p!Q@1Ak 0(֒C">dʞі9b3P2g̼^baY Z `VAX UD0&''&&&u~*c_p`0VږuQPk$ Z `VAX N?T__#6n߅fsjwq+qt7]ptfͩ>G6mڨ8W:y;wγk6߲?q7鬳JguW7#Ǐlt^鬳΀Yg{etYg^鬳΀W:3`tYg^+uW:3`tYg{:Jgu약Yg{:^3Yg{et^鬳΀Yg: +uWJgu: +{Jgu약Yg{:Jgu약Yg{:^. 8Wj[L7nr+Jgp` oNG6}qt>m6 8xjvli=6 F .s\sθn[bey-㹊t;jjKqTgqtw8`gYgɡh4 ~:G6tYg{}꼼b[WqcK?kNg: +uWJgu: +{:Jgu약Yg{:JguW:3`tYg^+uW:3`:[^鬳΀W: `tYg^鬳Jgu: +{:Jgu약Yg{:^3Yg{:^鬳΀Yg{epwAg:7-[yѣ zs3 0waPA_ߡ399111iP7~Ig+]Fc\<?n۶_nqeIq33߻9W]u3Ε:\ n :[ssv8tp6oYg+h;XyYg`-.v~^㪫=>Ď>~:3YgUyUۮzoضt^鬳΀Yg{etYg^鬳΀W:3`tYg^+uW:3`<t^鬳΀Yg{etYg^鬳΀W: +uWJg: +u^鬳΀Yg{etYg^鬳΀W: `tYg^+uW:3`tYg^鬳΀Yg{epwAg+j|Ux`:v lv+Jgp`x mWmv:tB8W#ybbǕCoޜo#'3_~ӦMG^xɓ'b/m(}!x\m(}!x\v.A4v+] 2J$iJAgfAPl6x\s<Aٌ? w5t? O:u~vj+++mO-{ s=w#?l6l:KEQ_( ⢳JNBz8J]]ptƹrtƹYg7w]ЙZz(콜:\ pF;__:γ:#iqJ^# 鬳΀篽Yg*qNg: +uWJgu: +{:Jgu약Yg{:sAg: +uWJgu: +{ΞYg{et^鬳΀Yg: +uWJgu: +{6W:3`:+uW:3ktYg^鬳΀W 3Εsy\9z/s\  ]}__FF-}~߯}qt>Qry=?i{n4sΧ? `_ͭg}v{YZZVWlk^wh]Hg 8WΕ8W:F. :sUKUnr:s30Xp|}}<;: z=PDtYg^鬳@{~u%y:+uW:3`: +uWJgu: +{Jgu:+uW:3`: +uWJg={:^3Yg{:+uW:3`: +uWJgm`tYg^+uW:3`tYg^鬳΀Yg{epwAg+js։P9}\9W:{sחQK߀G}ڸJ3/o#N8O|k۷ήmk}3wnlZ;7nx뭷^z饗_~WxUa^rz=3Εs3Ε:F I_j>=WQUݗYg+h;XyYgrhb:?}yKt_Ngu<:''' pJ?@ y]%:uW:3`tYg^+uW:3`: +uWJg: +uW:3`tYg^+uW:3`:{Jgu약Yg{:JguW:3`tYg^+uW:3`:^鬳΀W: `tYg^鬳΀篽Yg{:^. 8W?WYt^DwN_rutwF. 羯/#c>}~%s$qt>Qry=f?[~^n_ǎ;x`vɫzͯ{u]Ak>wO|b˖--/KKKIJrך)b$q$IU .s\sθn3>WKqJ\)AL⸡8W:;wγV+ t^\)$i6: x+utV Jt?+ϔA1i&Ng: +uWJgu: +{:Jgu약Yg{:sAg: +uWJgu: +{ΞYg{et^鬳΀\Yg{:Jgu약Yg{:^鬳/`tYg^+uW:3`tYg^鬳΀Yg{epwAg+jsj%AP.syX hNs\  ]}__FR$R)(L9f}qt>3A48;oɓ^zW\up 7177/馛?|m}jɣG~߼zWVټ\5 ;/Z;.Dz:;.. :\9W:\3n.VYY^vrj鹪fuaEuJg`|<3YZY9?zFV{/:Jg+xӽJ?:0>~3Yg{:^鬳΀Yg{etYg^鬳΀W: `tYg^y.3Yg{:^鬳΀Yg{etW:3`:+uW : `tYg^鬳΀W:3`tYg^+u: +{Jgu: x+uW:3`.s5jyeNjFjE\ ]}__F@-}]~5}_ϑtƹ;W7H8qȑ#+>W\͵x 7LLLg?[(|wku]y啷~Ç;O_^~}%vh4Fk:EQ6/fqI&ww!=Q.. :\9W:\3n.LU-=WQV* {/8W:;____gYg!yJ%è:3ktYg J?:dy]~^8~3Yg{:^鬳΀Yg{etYg^鬳΀W: `tYg^y.3Yg{:^鬳΀Yg{etW:3`:+uW : `tYg^鬳΀W:3`tYg^+u: +{Jgu: x+uW:3`.s\v^u:W:QEq+] oGJ3/o#oMmذ]z׮]~_O?/}u/n|Sܿ}=wqO8ZoJgakLBz,G]]ptƹrtƹYg7w]Й\Zzu+Yg+/γ3 u_Tgu<: dWGgXw_>~3Yg{:^鬳΀Yg{etYg^鬳΀W: `tYg^y.3Yg{:^鬳΀Yg{etW:3`:+uW : `tYg^鬳΀W:3`tYg^+u: +{Jgu: x+uW:3`.s\v:( u_ʹ޻0(Z;~j~_g+ϸsh׏{{{뭷{_}ۿ饗䅮׷SORe1|ayхG݅Xv.s\sθn3>W鹊:W u_Tgqtw88γ:Cdaakt_:3ktYg J?: B y:+uW:3`: +uWJgu: +{Jgu:+uW:3`: +uWJg={:^3Yg{幠Jgu: +{:Jgu약Yg_^鬳΀W: `tYg^鬳΀篽Yg{:^. 8Wjο~e9}\9W:{sח0z}{3Εg9J_4߂Gr-_|S /{Z /^2 guVg}Ȥ[ZZJT*l^q\fčKB'V)3Εs3Ε:F \-I*r9s5SbqCgqtw88γ:"Z$JF+ϔA1i&qNg: +uW:3`tYg^+uW:3`:+uW : `tYg^鬳΀W:3`tYg^+u: +{Jgu:+uW:3`: +uWJg={:^3Yg{幠΀篽Yg{:^. 8WjxJ4=]ΨsyX hιs\  ]}__R$R)(g~ff38W:'g h6qo $&''/q{'ޗ_~y'/x#_|5\%>죏>fr=hZLZQ5 ;/];.:;.. :\9W:\3n.LU-=WlU5콴:\ p|}}}qguByL^# Yg_{:YUlׅuqNg: +uW:3`tYg^+uW:3`:+uW : `tYg^鬳΀W:3`tYg^+u: +{Jgu:+uW:3`: +uWJg={:^3Yg{幠΀篽Yg{:^. 8Wjzyi{i+Jgp`x x2jj8Js5}> n Co WVV3sss?^}]tћU;wlOkm{}W>=znzEuY%\}|;=WQ*}K3Ε`yYg`(,,t~^# 3yu_Zgu<: dWfau_na>Yg{:Jgu<: +uWJgu: +{Jgu:+uW:3`: +uWJg={:^3Yg{幠Jgu: +{:Jgu약Yg_^鬳΀W: `tYg^y.3ktYg^鬳΀W 3Εsya/FvN_8WΕ.ޅAeͧolw_n~s9J_:߂HN~+7 x;ۿۿ8q\p\w&Ir9cTڽ{㏿⋯josg?;QJo+W}zݙr9(I76 qZ (OOgxqtJgp#|p<Ǫ8IZRP.g{fAPl68n3Ε`yYg231s~Kqj%AP.gL$IшuYg^[yrroWKq+i`:3bPLIc<>Yg{:Jgu<: +uWJgu: +{VW:3`<t^鬳΀Yg_{:Jgu약Yg_^鬳΀W: `tYg^y.3Yg{:^鬳΀Yg{etW:3`:+uW : x+uW:3`.s5DjrbV+ t9b1Hf;#x+] ;DWVVTR$R)(g~ff3s$qFs~Lf3s-0jZj5a^U w!=Yww]ʹJgq#wAg~j鹪\U?:\ p|}}}qgur^~^# uW:3߽J?.uay]}<>Yg{:Jgu<: +uWJgu: +{VW:3`<t^鬳΀Yg_{:Jgu약Yg_^鬳΀W: `tYg^y.3Yg{:^鬳΀Yg{etW:3`:+uW : x+uW:3`.s\v:gV{8WΕ.ޅAeHҷA߯k~_g+ ߂K;|݉(@.BzJ]]ptƹrtƹYg7w]ЙZzA(3Ε`yYg ;?Q 50謳΀篽YgUqYeׅuy:+uW:3ktYg^鬳΀W:3`tYg^+u*Yg{幠Jgu: x+uW:3`:{Jgu약Yg{:sAg: +uWJgu: +{ΞYg{et^鬳΀\Yg^鬳΀Yg{epwAg+jbӹ sp+] ːoAߏx3ΕU(_AY_zW\܅(v.s\sθn3>W\ ֭tJg`|<3s~@uW:3߽J?.uEy:+uW:3ktYg^鬳΀W:3`tYg^+u*Yg{幠Jgu: x+uW:3`:{Jgu약Yg{:sAg: +uWJgu: +{ΞYg{et^鬳΀\Yg^鬳΀Yg{epwAg+jsWD蜾hʹ޻0( Zv;8W:W ߂Kj~~AY_v|]HbX 8WΕ8W:F. :s5h*}?:\ p|}}}qgurnaa8h@gu<: wҏA|^}хNg: +uW:3`tYg^+uW:3`:[^鬳΀\Yg{:Jgu<: +uWJg={:^3Yg{幠s1ruY0I)93KzHv4B{ACA@ /M! & K%?t'P39JyfRv=z]G~|ϞS,g9Jr3,g@_Yr_@_Yr,g@_ r3`+9Y΀ +}.. rƹrVs>0RO 7~+J. I=_6u7]}9\]\?@JKEQX,JKPhGQjy$|0tAPqp9\9Wrƹq#wAΤ\-Q)R)sU.Aݎ°%g9\Hp<_y4 åN' L_T* A[Pr3`+9Yj)D$}]\*} ,g9^W_Yr,g@_+9Y΀ +}%g9ۿ +{ArWr}%g9,g@_Yr_@_Yru ,g9^,g@_Yr_}%g9Wr},gWr},g}%g9W ؿJr3,g9J_. q՜åN' L)KB!v:Εs%gp`ޅ/REb1(%_.nGas%ws9viܿ!dRV%jc4 ߅wA8WΕq,g4.șF|jɝZecY8Wr'yv,g טOkT*!g9,g`T}Q6ufֿ,g@_ r3`+9Y΀ +}%g9Wr},gWr}e/YJr3,g9 +9Y΀Wr +9Y΀Wrn,g@_ r3 +9Y΀,g9Jr3lJr3 ,g9^W_Yr,g@_+ww]3Εss1yXΕr l޻z/P#~Ѯ%_Yߗ3Ε Z1Ҹ'Ch ^'x,.z$|CXVwA8WΕq,g4.șF|ɝzecY8Wr'yv,g _ZMkT*!g9,g`T}QVMJ}bֿ,g@_ r3`+9Y΀ +}%g9Wr},gWr}e/YJr3,g9 +9Y΀Wr +9Y΀Wrn,g@_ r3 +9Y΀,g9Jr3lJr3 ,g9^W_Yr,g@_+ww]3ΕsbcsXΕr l޻z/P#~Ѯ'_Yߗ3Ε z1Ҹ'Ch5':lz$|C8< g+J8Wr3n.șTF|cǐq $O8s%gO8~|=_gorxfs5k~Ja,g9 \"OJ}]3YJr39,g@_Yr +9Y΀Wr +9Y΀,g}%g9W ؿJr3,g9J_Y/,g9J_Y/,g9^,g@_Yr_}%g9Wr},gWr},g +9Y΀,gWr}%g9.ʹZ͹<_K8Jm\9Wrw6]p=_t^+v-Zq2ss&uwU,ߠkz^'Bbw!>~j]]ps\JrF Ij窞W>s%gO8~|=_gorxרVJ}Yr_}%g9}EVMJ}bv,g@_ r3`+9Y΀ +}%g9Wr},gWr}e/YJr39,g@_Yr_@_Yr_@_Yr g9+9Y΀ ؿJr3,g9J_Y/,g9J_YWr}e/Y΀,g9Jr3`+ww]3ΕsbcspΕڇr l޻z/obד~߯JWs=0ۿ"dkdUh6= Eww]3Εs%g+9F. r&Xa,g+9~<{34 00rW_Yr~__%_`=~_'g9+9Y΀,gWr}%g9WJr3,g9J_Y/,g9^,g@_ r3`+9Y΀ +}%g9ۿ +}%g9ۿ +{ArWr}%g9,g@_Yr_@_Yr5 ,g9^W_Yr,gW  g+j5fc0fp~+J. +v=hd}_8WrIp!{z'T OB|G]]ps\JrF IZU=sᇑq O8WorkT* }~9Y΀,g9W*I_7'} ,g9^W_Yr,g@_+9Y΀ +}%g9ۿ +{ArWr}e/Y΀,g9Jr3lJr3lJr39@_Yr,gWr}%g9WJr}%g9WJr0 +{Ar_}%g9WrW_. q՜O, Ƭ9s\ؼw|=_B]O}/g+9_qänN1KKKQR')JAЎ\H.a43.. rƹrs%g9ywAΤ\-Q)R)sU.Aݎ°%g9\p<_ٛ( :(33 }R\(QnB9Y΀,g9WN LוʥBPQ+YJr39,g@_Yr +9Y΀Wr +9Y΀,g}%g9W ؿJr3,g9J_Y/,g9J_Y/,g9^,g@_ r3`+9Y΀ +}%g9ۿ +}%g9ۿ +{Ar_}%g9WrW_. q՜åN' L)KB!v:Εs%gp`w|0:bPJ}\.Aݎ,JW#rv; ӵ'FȘFgVKÇU*lz.$pWwp9\9Wrƹrq#p9sՈU-VYHr3Ε? |q`M9Jm#Yr_}%g9}96*u,~_'g9+9Y΀,gWr}%g9WJr3,g9J_Y/,g9^,g@_ r3`+9Y΀ +}%g9ۿ +}%g9ۿ +{ArWr}e/Y΀,g9Jr3lJr3lJr39,g@_Yr_}.. rƹrVsn69Rsp+9.e4Z:k~#rƹUʹt 2W|i0J`= W g+J8WΕs%gp`w|Fr]O~=~od}_8WrJ9㏔;6B抯8_uпfs!!tp9\9Wrƹs%gO8~|=_gorXl/?#Yr_}%g9}9hf:9@_Yr g9 +9Y΀Wr}%g9WJr}%g9W ,g9^W_Yr,g@_+9,g@_+9,g@_ r3 +{Ar_}%g9Wr},gWr},gWr}e/Y΀,g9Jr3`+ww]3Εss1ZOE~$+J. r庞hd}_8WrJ9㏔z.$lR\Wa^_xq>ajfON͖ wwa<q,g+9F.rhxjv6*lqs%g ? |qg9HSry6j7߼Cr3`+9Y*˳ oyc\9Wrw6]p=_ϗfS~ߗ3Us޲!#ܱ^x84|]^\?ϗ_u!W9D,>=59..d.JrƹF.r驩Ԝs%gO8~|=_gorxӧ''Ra핹i9Y΀,g9WOOJ{.YJr39,g@_Yr +9Y΀Wr +9Y΀,g}%g9W ؿJr3,g9J_Y/,g9J_Y/,g9^,g@_ r3`+9Y΀ +}%g9ۿ +}%g9ۿ +{Ar_}%g9WrW_. q՜O/ONN%ܴ\9Wrw6]p=_ϗX^>=tFss|^oCr^ݘriF\nAݮ(J^)rn_|>nO.+2N8ބ` z"&DM֛7!xr`|0Bل` vT{~n'V0&DM֛7!XoBބ` z^'ج8,˧OOMNìss5l7qnwd)r&\u,g׼ zԙgu\iOO.J+9ټΕt~rd1 _e{ӧ''R2o277m^3~6y|ԼWs~4y34_Wr52|?M~6y_ټ5ڿγ~6y_}γyͫy5/kgW?+uy͋+g9gWo+l^uyͫk:5~=_gl^g_ټgl^ڿ~6y|,gl^zk^_y6y͋k:ټy6y|:ټ=_uk^<_y5/嬟k^<_γ~6y|_W?׼Y_ٿl^|qyͫW_׼ټcr tynNsnMg3˓Si9ϣyuwa^w!|._{FZ^>=tF,g:Wrz ?i0=7:{6 $lƼu 2tҥAwPrv+9ټ敕yrq&fhq7dΕl^Jo=K߿O/x/czWٞwnnVm [6|ͥKy5~W{t׼Yw+g9gW}_l^g}ڿγyk^<__l^g}<׼W_Y?׼Y_a:5~=_y5|,g9׼zC_ygl^bgW?+uk^{!9ۼΕ_=r6.}}stak:ry+9ǵ"ͫ g#G.,.Ζˉ|;x!]p6̛wO?v3;Ys%g9u zgr!>{x晑={9fgbΕl^J@ھg}/w$~}Aez#l̛7ݻ# l?FwܱСgF[ۼw߽zŒȯ<6oRhmaǨVM={~a?L_l^g}ڿ }p1ÏQy͛ynzr~6y_}Lǟ.[8?5~Wݻw{~E?Nh4|?~>|xyyy~/ٳď?__0 _z~{׾7+ӟjWv+o|[n9uꔧ6SyLł?#׼aD\uKNr>̃sg}s|{vo*kX;#6|7֭_tO<ȃv|ggl^g}eڿW߭otG|d]gyͫk:ټ_l^g}<׼W_Y?׼_ټk:ټ=_9Y5W_Y?׼xk^<_y6y͋k^zuk^ڿ~ӟV_?Ϟx_?xzɓ'9?X,}o뮻w-LNN/yW~'?ys=Nڲec=v^{=c ?y.8MkxM7l۶rL. nft,g׹_ Άyz3(`7y;Wry+9is7x 7l[YI {zLod7|y+b=qb! ?#7Ν{nܶ3gVZy5~W{F_N}p1*#}4y34_Wr52|Ͻvovö3)J,~6y|_Y?׼xk^<_g׼M7ݸm gά<wݩ x~z,g:WrN >Ou_?я>Ork׮o}[KKK_җr= {?\;{x>{oqu }c__y啇~ʡC~Ow}}{;駟kΜ9or ( [d?RFN1J335l7)6} 0>б^-_wp|:Wry.׼ߑɥ0:bP*KAPh0y;Wry+9i439}zz|Cǎ0;[\<_^7RPjKNřy|Ӭ?SS.O<У/p\uyͫk^\.BԎZIKa'`f5oYټUFoۧ.zPgW?+uy͋kڿټy6yͫ<_γ~6y_}eڿγyͫ_uykzrk^<_γ~6y|_W?׼Y_yl^Y?׼Y_y<׼=_y5_y6yYټ7=_gW?+uy͋kڿټy6y͋~6y_ټ5ڿγ~6y_}%g9ټcݻw'׿u뭷~#>?zo>Xz!xVo:gx_yUo疇7?<*[7\{|ƋW>+5/^+J+/^W^/?+_5Y>kb::+7Y>Kk/^,gx?gx峼/^,5k3^Y^:|Ƌkgx峼_3^+JKkg|Wx5gܐWkg/kgx峼_3^/^Lqŋ/3^W:  Uܖ^t9njCW9 B{·>Xz-!xVo:gx_yUoo6E?u֌Y7U/^,gx|ƋW>+%~Ƌ_/?gx_ye|6gܐWkg/kgx峼_KK,kgxk|ƋWxgy/^_J_~x|ƋW>+%~Ƌ/3^Y^/?ŋ_3^:3^rC^3^+J_~x?gx3Y_/^x_,d3Wskv{[2[T3u/_77: ݿiߴ鑈[4k֌nZ#(SCQ}_/_QP/~?9s;gI'D"1uŋ!2|pvttw_|1xrӦMXӲ=ӳmm (xV^ݔNϽh_F;C>uvY_RguƋWlѓaq8F+uVg|DDD43\Ԕ;7 ;C=XG^&7_U6owY" k7o8Zze:tEs}:>P~xgye >Xz!ûxVo:gx_yUEQ{@c;}W>+/^_xgyxWkg+_,51WYJ,%5_|Wk3^xI3^Y^/^W_~x5/^,WY>Kk3^Y^/^_xgyx%|ƋW>+gxk|Ƌ_uVguƋWn+|Ƌ53^Y^/^_g|8kŋVxq\unn1-s:Ǎsx+馋.}@c;Cue{h6=2﫳:+u<6miN~;n*}f?W\Y(?fܹ>|#9CxmV'|21dȐ౯w T*xܰqc/J//^g7Ft*Uѣ};Ol :/_:,^7+5Y>K,kgx,5/^l/|ƋW>xgy/?gxI_W>+:gxI_|Wx%5Y>k+%~ƋW>+|Ƌ_gx峼_3^/^!/^_xgye/^W3^_yk_g19Y/|50u޸aIe*Ef7^Rgoou>=z9#F zB!=2﫳:AU!CFgJ_odRguWLDDD>t,_}k֬%ɍW͛L6&\T\GO$xIƌyGG/5on:|ƋWLGOSOϿhڊ~W>+/^_xgyxWkg+_,51WYJ,%5_|Wk3^xI3^Y^/^W_~x5/^,WY>Kk3^Y^/^_xgyx%|ƋW>+gxk|Ƌ_uVguƋWn+|Ƌ53^Y^/^_g|8kŋVxq,uu D>YuN6&\7^Rgoou"޻ǼGw?]F;TCC2C;:r|.}_/_QлEK.}g;9N >Xxd̙k֬#8}{_ɮ+r~{lwt*on:|ƋW>{Dt^xgy/?ŋ53^Y^/3^xY>k+gx峼_3^ܐWkg/kgx峼_KK,kgxk|Ƌ_gx峼_uVg/xgy/?gxI_W>+?ŋ/^,_k/^WY/^!/^_xgy/?gxI3^,‹/3^W:  ׹- s*:Ǎsx?<{=kx_qť-ԋ޾r\.]CK+}WguW@ Jl}u(LUWW>y_xN2d_OڴiUCUeR౵5_-]%ݢG|ٲz3s欣?gx峼2sqln,]%}/ _uVg+*uUGeѽ;nq3gk+gx峼_3^ܐWkg/kgx峼_KK,kgxk|Ƌ_gx峼_uVg/xgy/?gxI_W>+?ŋ/^,_k/^WY/^!/^_xgy/?gxI3^,‹/3^W:  ׹5- s*:Ǎsxu_q̜9?+SCKg*}WguWϢs/^|Ao. _ /dɒd ]]]oOkbLd϶E6`. x0aĉ7.1؞AuVg痯YU+x@z2[dxo_4J/_3 L4i>}F/Ln*7ܿloEKq㍛7Mpĉƍ@T/o ?gx峼2߁sqkn,]:x/J_uVg+*u=dIEoqmg|Wx5g+/^,/^_gx峼_3^/^!x%5Y>+k3^x_%~xg/^,g/kgx峼_uVg/+5/^/?gx峼_gx峼_/?gx_5_~ƋW>+5Y>K,J,%5_|Wx%%~xg^}^g sֹ-ed*El7^Rgoou"޻'2qq>]\5e{h6=tLxJޚ?ϟ'o“N:ioÇ?S[~_~9xZ2>|g^}3/yweK,9Ӄr\ccc4+ڶ{H^@رB=Y_xٳ?4ӆZΠ3:3^owFCFz“5cBWW{2TSS 72_:+u&"""z}fM3fT}_܈+wȐ{{lX]]ukw$c doܤ1g55}fTxgyo94bĐumkjl>8fl!_hjeŋxc7WY>k+*{6Vgx峼_Kk/^,/^J,_~ƋW>k|ƋWYJ,%%W>+/^_g|Wkgxk|ƋW>+2/^,WY>KK_~ƋW>+5Y>KK|ƋW>+%~Ƌ/3^Y^x5g|_uVguƋWn+|Ƌ_|Wk|ƋY>+ /^x_,d3W;CF}oQ5B>YuX]]ukw4~q+uv~^g!{쳚>xi3*{;Ç?_uumkllh߯ړ﫳:AVU~֡05bĈSO=5;/R?я޿B矿馛vUUKZL???}MōX>5$ڞ|>;r|PH$ux cK/r߹ TFgTguv~J/^g/^󅺺Dh"74$ڞ|. |ꌗԙ}ڗ.]}|%ɍXW͛L6&\\GO$BM[|iP(뾳`/^, Dm'>D]"u/J_uVg+*x}}(m]_|Wk3^xI_W>+?ŋW^3^毼2_~ƋW>+5Y>Kx y_~x|ƋW>+5/^/^,_~Ƌ/?gx_5_~ƋW>+Ugux|Wk|Ƌ53^Y^/3^xI,5/^,5WYJ,%5_|Wk|ƋY>+)+x?u~y`_9 _ dsB>OVx |3:Ǎsxǖ/_)m,X0nDmOO>ё uudxJBE]{>|??Ksٲe/{}sF}ϼJ_{ 7pßt:SO=5x|GVZ|0m4+ҩTmk ҥux?3].K9l ?\Tguv~J/^‹k'EO=TY:`0SO={ϰ簅 D2pySq֖ t4^w?gnݽ_zذ=,Xx|ƋW>+-`[`!xVo:gx_yU+%~Ƌ_/?gx_5_~ƋW>+5Y>Kx y_~x|ƋW>+5/^/^,_~Ƌ/?gx_5_~ƋW>+Ugux|Wk|Ƌ53^Y^/3^xI,5/^,5WYJ,%5_|Wk3^xI3^,‹/3^W: munNǢΩt?r7^Rgoou"޻ϟ}晧[wo{=,Xx38PJl{hxxJBV28|𞞞'r)?Om۶s=wرc꾾җ,^8x2eȑ#޷=LJzG >r/J.u_~gO~gϩ6mZwg>s!/+~ԩS_}%͚5KʭL8`Z[#0ŋfZZuxQI|6K]S3k :;|Zk5Ɠ٢'3LqWxJhg8'D" {̚53 Kd/q *7bm--.}G}'&¿tMŷg|ƋW>+-`k`-!xVo:gx_yUQIߚkL~xgye/^W_~xKxgy_~x/?+,%Ugux y_~Ƌ//^,_k/|ƋW>xgy/?gxI_JY3^_|ƋW>+5/^/?gx峼gx+|Ƌ_gx峼_3^/^!x%5_|Wx%%~xg^}^gx׹5-X9GCW9 B{'OMDw=x38Pl{h颙xxJ*~ׯ?[[[WZ0><-ࡇzG?7'O=zŋ/\pwUQdrʕ5552dȊ+;{SN6mZ^}naÆ]L|϶E0`-x;H3?\y'_g0gP+uVgx /^;0=-odRguWLDDD>×gpA>×g$D2pyK/֖./ƍ7npT|ˑ\z޼ \>/^,_w\\ڢK ŋxc7WY>kʫJ[|7z} xgy/?ŋ53^Y^/3^xY>kk3^Y^/^_uVguƋWn+|Ƌ53^Y^x%%~xgye3^x_5Y>kk3^Y^:|Ƌkgx峼_3^+JKkg|Wx5gܐWkg/kgx峼_KK,g^x:jY/|5unn-XԹYBCW9 B{wqfw{Ɨ 2=4Zh&:3^R*%Hoܸn>}?ѣG:4x<d;c̘13/^<s̛|]w >XlK/tAuuu]{'x?#G.7;vԩS-Z?Immm.t:=|v-xm͛t-J6n ҥ^^o$zo.\uY+ >08AuVg痯YU+xxrCѓp`rKǁ72_:+u&""Ax;(3\rAerCnqM^ƍ-]wޱp7ßK\s啗dgx峼_7?ױQ샥B _Ugux;\KYrelxgy/?ŋg|WK/^y_~xk|Wk|ƋWYJ,%5_|Wk3^xI3^Y^/^W_~xk|W,%/^,g/kgx峼_gxY>+k3^x_5Y>k::+7Y>Kk/^,gx?gx3Y_y/^gίZu 2/_ d7nRXԹYBCW9 B{w,\%\y1ٻel8 Q졥f﫳:+u RP'͛7 _~9x|ᇿoO~m3nϺӧ/[Gyg_ylݺugya7}Νf{?k71z @#F ֐L&jk{\ggB]"NjY Gժi3f̹­\ ~X~ ɜAgP+uVgxx;/co[x#dg!Hsp`DJ/_3 f̙s֭gX~tAer#nջ-' |3su D>s7޸IC3fL9֐/?Ah~xgye@o6%|O3}0ב+ D}/^/^W^U;mƅs.̅˯`~2gx峼_Kk|ƋW>+%~Ƌ_/?gx_5_~ƋW>+5Y>Kx y_~x|ƋW>+5/^/^,_~Ƌ/?gx_5_~ƋW>+Ugux|Wk|Ƌ53^Y^/3^xI,5/^,5WYJ,%5_|Wk3^xI3^,‹/3^W: vP}oQ\GO$q~ |3Ԝq+uv~^g!{ƴ /m {~ BC Q64$ڞ|.}#xxj9(POq~!At*o]C'O9|ƋW>+-`[`r͑xVo:gx_Sf7{hN28+J_~Ƌ//^,/^J,55/^,g/::+7Y>Kk/^,gx?gx峼/^,55/^,WY>Kk3^Y^x%5_|WK/^_/?gx峼2_~Ƌ/?gxGUguVgx冼_/?gxI_W>+/^_g|8+KU+΂}A嫁s[v{1s*:ǍsxSN=Cw {<쳿*]C˥﫳:+u\UJ@DJTq:`˴,ơw'ü\s_o:Π:Wx^_wg=-z2̤_@x#:3^Rg"""l1.N&ýp5'<׫Fkoߘweׇ5W_3|ƋW>+5/^/?gx峼_gx+|Ƌ_gx峼_3^ܐWkg/kgx峼_KK,kgxk|Ƌ_gx峼_uVg/xgy/?ŋ53^Y^/3^xI,5/^,5WYJ,%5_|Wk3^xI3^,‹/3^W: έunĨΩL?x7^Rgoou"޻}/Ow_s'_;Lq͆.Ӿꌗ9rU)툾 ;ڴi@)mk u/W4^oTjѢO]umn) :/_:ūVxߝdh8oqWxJhgEP3mٲKF+kť-}tH]x+7vWY3^VS\C}Uݴ즞8+J_~Ƌ//^,/^J,55/^,g/::+7Y>Kk/^,gx?gx峼/^,55/^,WY>Kk3^Y^x%5_|WK/^_/?gx峼2_~Ƌ/?gxGUguVgx冼_/?gxI_W>+/^_g|8+KU+΂}A嫁s[v[21s:ǍsxE;ԽU7ݴ,;Lq͆.Ӿꌗ9r SzK}+_Ypn|+ѻ<ֵm[cSSMuu֭]f|ޞNjY J]]cƏV?aQS׍Π3:3^^_kȈ}}~{M|՞L~566TwwxWxJhg3f|?+5/^/?gx峼_gx+|Ƌ_gx峼_3^ܐWkg/kgx峼_KK,kgxk|Ƌ_gx峼_uVg/xgy/?gxI_W>+?ŋ/^,_k/^WY/^!/^_xgy/?ŋY>+)β+x?u~ `_9xj5bȈ޾[5B>:ojjjٺ;+5/^/?gx峼_gx+|Ƌ_gx峼_3^ܐWkg/kgx峼_KK,kgxk|Ƌ_gx峼_uVg/xgy/?ŋ53^Y^/3^xI,5/^,5WYJ,%5_|Wk3^xI3^,‹/3^W: ιB!H'cTdCmm"šq+uv~^g!⽻q/]pPƟxwD' k󅺺D2V}:3^RUD4ؔNl[[8+](܌x+:㌡Cs׬9#/]j:Π:Wx^_wg=-z2L_FxY_Rg"""g+:+ 9#/^Kd/q *7Jmmp.WPss/WW^gqVhw͚ۏ+-`[X`BxV_uVg/o}!5Gv+J_~Ƌ//^,T/^J,55/^,g/::+7/^+J_~Ƌ//|ƋW>+k/^Q_JY3^_/?+/^_xgyx%|ƋW>+gx+5Y>Kx y_~x|ƋW>+5/^/^LqW^ŋVxW;[:7cWT?u:77: Wg޽#o+UguxV9g r{W>+/^_3^Y^<3^xY>kk3^Y^/^_uVguƋWn+3^xI_W>+/^_g|W_/?ŋ_3^&/?+:gxI_~ƋW>+5/^+JKkg|WxWk|ƋWYJ,%5_|Wk3^xI3^,‹/3^W: vέunĮΩL?~7^uVgoou"޻:gK.i~'PyglX{hBxJU-uwwx;::w8W_]vӞ{ɓ'zwu{+/^_3^Y^<3^xY>kk3^Y^/^_uVguƋWn+3^xI_W>+/^_g|W_/?ŋ_3^&/?+:gxI_~ƋW>+5/^+JKkg|WxWk|ƋWYJ,%5_|Wk3^xI3^,‹/3^W: vmmĮE~7^uVgoou"޻;;-j[Bڻϙz]'p;PfCKoWguW*W_&O`ѥO>3i_3fٲe'N,}r۶m]wWճ:K.9r^z)̙3%KR3/x#noow 0 )yƍ\tEux Ԅܹ|p85r&Π3:3^j 1+'~Ƌ_/?gx_5_~ƋW>+5Y>Kx y_~Ƌ/kgx峼_KK,kgxk|Ƌd_gx峼_uVg/xgy/?ŋ53^Y^/3^xI,5/^J_~x:3^rC^3^+J_~Ƌ//|ƋW>SeWx%~^g s[jĐoV_4]%0uƫ9 B{=rԤI+3X&e{膰҅2Y:G*%x[ڲeKxg=s_[nu]㎉'>LΛ7uc… G|gĈ6mZ|yUVRq{xÛ7om;~qk}Kꪫzǎ|a6- /ƒ>o.Z1h`ՐL&jk{\ggՑ D"Y_xW~Ʌqx7;)ga:Π:Wx^_wg=ّ uud23ؐL$j{z\,xY_Rg"""gK\rɅG}ήq?3]er#Ue& |3WkrB>O%}*K?չ8~xgyea!Y;˿:r|!Qpċx:gxI+dK\xɅoWg?#gx峼_Kk|ƋW>+'~Ƌ_/?gx_5_~ƋW>+5Y>Kx y_~Ƌ/kgx峼_KK,kgxk|Ƌd_gx峼_uVg/xgy/?ŋ53^Y^/3^xI,5/^J_~x:3^rC^3^+J_~Ƌ//|ƋW>SeWx%~^g sN9Q(dlM=89n/8#,Dw'.?"sǝq) I D'+ё uud}uVg|ѪJ ޖ/{ʕ+c9fo 8o#X ]v3L=vٯ3gs=STo7s׿t:=z=㠃|'5o޼W^y7h`.:3V .nnƋY Y7XO\vm/uk.c:Π:Wx^_wGw=-z2L_LyUgu::}W|'YS Znuwp׻Lc[[ܼK47ox& G+V|≟^vM/ں!/^,`[%$_Ugux[+Nko\O\xgy/?ŋg|WD/^y_~xk|Wk|ƋWYJ/^_xgy/?ŋY>+xWk|Ƌ5/^,WY>Kk3^Y^x%5Y>+?ŋ/^xWk|ƋWYJ,%5_|Wk3^xI3^,‹/3^W: Wo޾޿[:7cZTsxY_q8GYx^q'~z5e߻[7r[{z,=4[}tt\}uVg|J ޖf̘{|ohѢ=׿ux7Ї><裥orf3L"wP.,x~aޠU&U0e0KdZZux`֣7Xu}cNݲq\b:Π:Wx^_wg=-z2̤_LxY_Rg"""gxte+D^&7W͛Jeao5kKhi%}kGWZnݽԩnܸKg|Wk>Z}tAx+W^:|ƋBG7Zj]zԍ[6^3^Y^x%5Y>+?ŋW^3^f/?+,%UguVgx冼_/?ŋ53^Y^x%%~xgye3^x,%5_~ƋW>+Ugux|Wk3^xI_~xgyx%|ƋW>k3^x,%UguVgx冼_/?gxI_W>+/^_g|8+KU+΂}';skv{[21s*_89n/8#,Dwo~tժ[{7n+,2=4[=tL\}uVg|j->E]twE+?яs#Gܑo8|}g-~旼 oor^{H& ]]]{A iٞٶ%JËY VN0ir__o}_gTguv~J/^‹k'EOFyK:WLDDD Nmll4) Kd/rܿtokKk7o8:؆Ɖ'0w?~xgyox~..hmKŋx:gxI+d=vjCcP7gx峼_Kk|ƋW>+"~Ƌ_/?gxD5_~ƋW>+5Y>Kx y_~Ƌ/kgx峼_KK,kgx+5Y>Kk|W,%/^,gx,JKkg|_gx+5Y>Kx y_~x|ƋW>+5/^/^LqW^ŋVxOw^˵dbZ"xqsxY_q8GYxzlCCĉػO*SCCKuWguW*Uss /|xxϝ;wY>UUU}}}A<`RqƍP,{ZZ?!\zݝw5uAuVg痯YU+xNyrCѓ`^LxY_Rg"""9%趱zݺ;GKd/1 *77n(J(]/Zŷ`ojjuFg|W K" ^y/^ T]S};G_|Wk3^xI_~xgyExWkgxgy/?gxIY/^!x%5_|Wk3^xI3^Y^/^W_~x/?+:gxI_~ƋW>+5/^+JKkg|_gx+5Y>Kx y_~x|ƋW>+5/^/^LqW^ŋVxOw^_.i"ġqUguv~^g!⽻xw75VWyQF+3X&e{Kd﫳:+uPUJ4lذI&]|ſL?n|g߹/eRwwwgggAcc#K!Lʤ\._(%zx;uy&M*%=dЎuwtY_RguƋWɎ\>_K$Qd"Qӓb: ̛0aRgg93l{2Qw=z/Ln *7lM=]%(D]}}/oh޼ &MUƜ|mA+mA>+!Y;˹:r|!Q|ċx:gxI+g`ޤ ۞ ZQq}W>+/^_3^Y^3^xY>k&k3^Y^/^_uVguƋWn+3^xI_W>+/^_g|W_/?ŋW^/^_gx峼_uVg/xgy/?ŋg|WK/^_/?gx/?ŋW^/^_uVguƋWn+|Ƌ53^Y^x%%~xg /^x_,x/_:: |"QW_i |3:ǍWsxyL4??ݿ5j;SCC2ʹvtB]]"}_/_sR`UG>zO}Sիlq Ri lP5kV.] N¼YWgCwYW[Zz?ݺv+]Al]ΕuURzͿddeә, 5Xw)^l]Εuug^j|nݺk/ }#9l&?_ߊ76Bb;Y^juiڵ[nYVzg6=M֜y~@Aի__lgA߻gZ5=x`n]s^γzի^ugW֯yVzի__?W_+gW֯ugkuVz gW_nn5h>6,zSYVzgS%YccVwǎ}gdVo6^V u֟ի^Y}Vq%>c<ط#30zۑ ]._lg׹G;?:U__䣏>+ٳYYYz*,,KGy3?^x}d_(--]bhgDd^^.[!CN?t[Et^nz! 7 BdEg= n2$O /~]!@_|jeRdO#10߾`5+0@k@_`+@@p̿_ _/ _/ b^3e^{H8YRzذacƌqƷ~O>yw*++|.hԨQ;vү}'X?aرW^y۷~aMM+?k뮻>ɧQZZ:k֬ŋD6@J޽q̞3x_6t+((H>?m]w4gz;Sd 804ߍyڵkw>zL߿gʟLaa C`}C Dv5hP;wO+Lo~PwA}L/W/yɏ{5Oi4?%0@Ϳ @_B`5 /_0  8 _/W/_  B1w5hP;wOܝWxdj_S$HXv؝o߾̥Kx<>k֬믿 &>|x͚5&Mھ}; [o'yo~KI9deeO?SNGtŊʏD @0/3#㥧Nj`%"h"vRW~/tz/' nN~zedd>K=z[ Q4sP{x{x/_pր{nzsS>S4jެ훛}V[m _@/ @@h̿  _p/@̿  _0/Du|zH@$?&qKIDM ;;{g߾}HdѢE^G}?͟?C >|ԩSGyd֬YoO۰aCUX$++کS'Q[]}iiT__YU5rĈo`S0U[*WVV9: }p B6[9bmS0*F1_ :OLf_>S4O _/_/_ @̿_ _/k@_`+@@p`5y@(@+*G9`m qJx:ujɓ;w}g}';]__o7e6nLx|3g.\Ȏ` [Z_gx|3.: }0֭[6n|-OcgΜhB;tw֍~wKgN_ :¼o4S4@~ak W`5+3׎ /_0  8 _;  Wp/ry@X[6n|-sgΜhB;p,m^㏓&N5|ʕ]t}W&3 J]woZyh_,[6ٳS4?¸q%EEv/dK,8;R:C&+): }YlIAٳH8q&̿K ,;܄qE@_Q'?VUVi4?'_/_/_ @h̿v+@ _0 _/ _' B1w/[RP0p;R'$7 ڋH)WгgFzz>'=Qhĉ[3gzm oa^^Mh~O̿_ /_/_ W/_ /_ ̿ /_N` h$jy={fL4xI$V6O>>ݻwo޽:jժQF5Ç[[[n—̝;{ٳg1''gĉ~9?tŊʏD3 Rg򒢢GR 7̝[^RRt^gC/Vt{ԕ-**=rDG;sP|[|nyQIh Ȁݳwӛz青'PfMn쳲j; ` _B`/_0/ _`/_ ̿h$',{O]yܢѣ/a频D"n)8I N^>tP/=iiSLI>(//o%3f̨?_/J[ofРA˗/X\s޶-UOb͚{ws ̿K"\;u3T~;3_@7h7ܞ ZxU۶;UO`͚w =QJV@Ͽ>m{wMŚo/t,yyɏUUz? _/_/_ /_ ̿ /_ _/ _`^ 'N}sϭڶ)Tw߽s^4K@[h~P___[^^8eʔH$_馛6nܸf͚_չs}{sYr޽{vix?ammM`z/++[pa@Xl_ x.+[ u3͜9cx3_@7|첅  bg̘YV6^ /j}X،3ʼ c+K~\_>UOG ̿_ /_/_ _/@_/_ ̿ /_ ̿y@((+l)cg̘YV6^Ζty5?E/.諿wH~rĈ~X@$vxȑ#K $7 Bk#FbR%@_:[<|Խ4z+@@G_x>UUV 4_/_/_ @ȹ̿ /_0  8 _/@_@.yPËGK4xI$V6333?+⩧:/{챯nݺ%?СCOK>~NIIIEEEii+:$?&qgo4ic=SRRRQQQZZbŊ~3im-k׭;fLfFF55^m h^ 6o޲vcdf ;kb^uu^goОDC)eu֎36##tMX^۶Hܟy˺ƌɍ_ :g ػg77ӻ͚ܾgeoݽF`5+0@?\D> bljp)@RB) 8h5 QY%^5_82HkF,) 쇆5fW79~N?G`gO߅q_/ /_@+@ Wpqa63~SyJKSݝW]uiYٸ?#z> oot>[7lxx#/xɧ\`xGݾuW7T#/_ i۶lkmn*ѢiS\_/_ +` @+@@|8 /_ _/ćѻ}d6zM ݻG^OgaP`jCCCOOٳoE֎=ɓ|֭[ޙ3g'֭^͛wE}Ӈׯ_w޶ɓ'wvv'?2eJ*&M 7y$J?rĉ?|w>[FOP0 qee_zl*}@ihOJǍ.Ʀz> oot q_:{}_ Ҳojl>/ SkmӟKGFSOu_ / A W / /_@_ _@_HDo(-Mzwj}= ˙o߾%K|s窫;sW~vm?쳲Ǐ?CO=SN}ﯨشiӏŚoOL:LAfQr49 s9$ʕ SQapߠA:V W{(/0 g! ;)/_v^oYp/y?۱mۍB // q/ / @@|8 \0ٜ9@q+{=WF ]D\(8GAdJB ~oiӦfa?k,Y2mڴsw?ѣGGuvw׿~pС~o;.ɓ'744~#GGioo?eʔrb:ibԩS;w߿|ٲB}?*+l8r&Abqg[ ϐ{@e}ppߠ~0sgm˖-/p:~J+BY۸se;>5x]*<@_0tkjJS/otW W @_/ /_@_ W /rA}, l6g04{gm˖-/t:~J"A. ds CW6qL}&TWlSN5ٶe\_ 4⢬lѣ>7aBaw_> ooPLCК8,9oG z;lwd}@o̾Uw_ K~_U]UE;v^5q. @@%@+@ Wp_/ /_ // OaFl(`hzIJL}UWO(hٸ۲e]5TIg> ~{.ވ~ZRR27@ҥ +>ü7w} Qy ;wD_/ [jmZU]UE)Ӧ? @@ /_ _@_  >@./}={B}g D}}f_gtҥu\c远Ak.5 gݳ{gp}7(&Aпϟ:Vg}iҥ^sMi= a#W36iZzk< W=ǖz|U]ؚ_ǯ~y%/ @ _/ / / @@|8 \}A_x 0zfsFEܻ}iҥ^sMi$ͨQ}PJb? /_ _ Sk63 ޝ44ё)\niy Ѻ.AҌg U$3[Uuו55uWDo + 0]_}}"UwuM͕߁ǿ +)߁/Wᬢl\G>쪪*;{d\yE @__/_ _@_  > /_ _ ݕUwuM͕Yz)@of@Qh1}zIIIa{ϧ>/g'իWP?cFh}i\_}}"fM>ʕZ ӧ(X66wG[[;<Ě~bo<+@0ounm W W @_ @+@ Wp_/ /_ // COy"z]rQgw7wi@7C`R%y@޲+ֽba۵{[v4y_lيu ϰk[o:x> oo{eŊe/0޽k׮:;<W0o{+xPwڽ] /_j--Ynpӧ< @@p:@+@ Wp_/ /_ //<ﵬXw޵k[Mqvk_ٴ0˭x[M(Fv=W^)>Êwxm /_:vӛ6R~xk%& {kk7+~mkz `:}jZ\_/_ ++ /_ _@_  >@.`$ww\M^)P~xk%&pv\.g AY 9vݺkgͺa\@%V0^s9G֮]wn> g٬c5;rukgͺvLoQsY{Ⱥf];k @C;]SUS*t`z / @_/  /_@@|8 /_@  _@_b( 5 Y>rxݺf];w A~;]D\(8G>a.} 9$7ttϮ[s˷ HuϞ]~}}C@K믫sMY!>Y+]]}< G#?* ujU._/ k9 / @ _ _/ć 0zfsFEٻ]~Nk.5 Q0O%tğH薗Jƍ /nܹ3 5ttvfzz(^/qōghjd3/7x;PKKSggGOO@ ߖΎ@dV6{%Lb+_ /|@+@ Wp_/ /_ //@"zƗ/^sgc{wKSggGOO̓)rYsEJb?smmܼo;7 -?5s_x۸qsM7ܐ}Um޼kν! ͟w_$ۼq5 /_kmӟ4 EKDSO5s@/_ @+@@|8 /_ _/ć {_/fۻ/xz7a܂*+̞>+.`9s p}pp?Aees ]v )wʪy{ @@bL>5zmmnBK . @@ /_ _@_  >@.TVV͞=+ \\ lTi;۷=4VAF\(HT;l3> A }lֱMXVZھѣǘ߳A?}P|&J} @@tkjdtuz/_@  @/_@/_ _ @܄Af9JKS۷3zHQ J~%.>rst[Сcǎf2cF._\z9]]g>s?M774W3gf:ݕ%>v'9- Aw93Pc=2G3  !**+ƕEՙ<{w%+_ / o/ /_@_ W /rAHJ^rΜt:}XO&st1pJkmkܼ)KT~M(vm{6ٓ}'|> oohkkmnԴ'KWtwx7M/ 꿭m{D IR;6zmmn|ʴ) /_W W9 /_ _/ć @+@@|8 \}ѻZ65c.>o6r)@EdOSөSj'O>ĉ'/ LPk.5 fϞ}|38yE\_}}"٬c5@^45wɓ'." ܅ag}PENߓ'N^/_0z-_<-5_b/[ @@p W / /_@_ _@_b% 5 qn߻'w oo׸3fkq-cve HPyEAFo_ ]]'?0> ooӾEnxT ʐNw} ߾Oݸh~vOw?9I @@^o>x۱mǗ /_W W8 /_ _/ć @+@@|8 \}ѻ?['_~?䓓L\\ qZf"K hQfo 2/gO%Ǝp6X /==K/8zgV3ShTjQ=w}P3=<r:о-/_HjGG?+_ /S? @@o8 /_W W?/\ //l63ݓҋ;a XNnÐ؄s lr ' Co|_Lg}1 $C߼E*a}P|0ٜ9_ 5(LI>a?p#ӕt%F+18]JBE* Vad%F+18 lHre9C[esYCt%F+18]JرA EA?PkXر*=S$) `p}tO*Z"Uॺޭϝrʵ `0E"LHS$)QWUkA9Gc`p}tU޻usPQv"LHS$) `0E:t'vR{|towRUkA9Gc`0E"LHS$) Jx>|UDn `-["70E"LHS$) `p}tO*Z"Uॺޭϝrʵ `0E"L;F((;x=WA`0E"\hU:Zk Bool" be the function that tells whether an address is inside a table: inside.addr = (Exist i : i in Indices : i & mask = addr) In English: there is an index i, for which the VRAM address is addr. A convenient formula for the inside function is this one: inside.addr = addr & (~mask | (-1 << index_width)) = mask & (-1 << index_width) Note that "addr" only occurs once and all other values are constant for each display mode, so the check takes two operations (one compare and one AND). This is probably the cheapest you can get. Index Width ----------- Which address bits are stored in the table base address registers is documented in the VDP data book, but which address bits are produced by the index is not documented there. This section fills that gap. - Name Table - Name table base address is [A16..A10] (R#2). Display mode: Index width in bits: Text 1 12 (*1) Text 2 12 Multicolor 10 (*2) Graphic 1 10 (*2) Graphic 2 10 (*2) Graphic 3 10 (*2) Graphic 4 15 Graphic 5 15 Graphic 6 15 + plane (*3) Graphic 7 15 + plane (*3) (*1) Text 1 index is calculated as 00C00h + position. On MSX1 it was 10 bits wide. For some reason, unifying address calculation with Text 2 must have been simpler than keeping it 10 bits wide, even though unification requires an add operation. (*2) Maybe this is 12 bits and index = 00C00h + position just like Text 1. That's equivalent to 10 bits though, because there are 1024 characters per screen. (*3) See "Planar Addressing" section for details. - Pattern Table - Pattern table base address is [A16..A11] (R#4). Display mode: Index width in bits: Text 1 11 Text 2 11 Multicolor 11 Graphic 1 11 Graphic 2 13 Graphic 3 13 Graphic 4 - Graphic 5 - Graphic 6 - Graphic 7 - - Color Table - Color table base address is [A16..A6] (R#3 and R#10). Display mode: Index width in bits: Text 1 - Text 2 9 Multicolor - Graphic 1 6 (*1) Graphic 2 13 Graphic 3 13 Graphic 4 - Graphic 5 - Graphic 6 - Graphic 7 - (*1) Bit5 is fixed to zero. - Sprite Attribute Table - Sprite mode: Index width in bits: mode 1 7 mode 2 10 (*1) (*1) Bit9 selects sprite color table (0) or sprite attribute table (1). Bit8 and Bit7 are fixed to zero for the attribute table. - Sprite Pattern Table - Sprite mode: Index width in bits: mode 1 11 mode 2 11 Index Calculation ----------------- The following variables occur in every display mode: org_line: display line, 0 <= org_line < 256 line: org_line after scroll adjustment (R#23 is applied), 0 <= line < 256 name: value in VRAM at address (name_index & name_mask), 0 <= name < 256 The "/" operation is integer division ("div"). The "%" operation is modulo/remainder ("mod"). - Text 1 - Aka SCREEN0, WIDTH40. 0 <= x < 40 y = org_line / 8 name_index = 00C00h + y * 40 + x pattern_index = name * 8 + line % 8 Adding 00C00h was probably done to make the index 12 bits wide, just like in Text 2 mode. You can see this addition in action if you enable 212 lines display and then play with different values of R#2. Pay attention to the characters displayed in the lower few lines of the screen. - Text 2 - Aka SCREEN0, WIDTH80. 0 <= x < 80 y = org_line / 8 name_index = y * 80 + x pattern_index = name * 8 + line % 8 - Multicolor - Aka SCREEN3. 0 <= x < 32 y = line / 8 name_index = y * 32 + x pattern_index = name * 8 + line % 4 - Graphic 1 - Aka SCREEN1. 0 <= x < 32 y = line / 8 name_index = y * 32 + x pattern_index = name * 8 + line % 8 color_index = name / 8 - Graphic 2 and 3 - These modes are equivalent, except for the sprite mode. Aka SCREEN2 and SCREEN4. 0 <= x < 32 y = line / 8 name_index = y * 32 + x pattern_index = ((line / 64) * 256 + name) * 8 + line % 8 color_index = ((line / 64) * 256 + name) * 8 + line % 8 - Graphic 4 - 0 <= x < 256 name_index = line * 128 + x / 2 - Graphic 5 - 0 <= x < 512 name_index = line * 128 + x / 4 - Graphic 6 - 0 <= x < 512 name_index = line * 128 + x / 4 plane = (x / 2) % 2 - Graphic 7 - 0 <= x < 256 name_index = line * 128 + x / 2 plane = x % 2 - Sprite Mode 1 - 0 <= sprite_nr < 32 attrib_index = sprite_nr * 4 0 <= pattern_nr < 256 pattern_index = pattern_nr * 8 When sprite size is 16x16, Bit1 and Bit0 of the index are ignored. Four patterns define the look of a single sprite, like this: 0 2 1 3 - Sprite Mode 2 - 0 <= sprite_nr < 32 attrib_index = 512 + sprite_nr * 4 color_index = sprite_nr * 16 0 <= pattern_nr < 256 pattern_index = pattern_nr * 8 When sprite size is 16x16, Bit1 and Bit0 of the index are ignored. Four patterns define the look of a single sprite, like this: 0 2 1 3 Planar Addressing ----------------- The V9938 has 3 pins that select a group of VRAM ICs each: CAS0, CAS1 and CASX. CASX is used to select the expansion RAM, which can be accessed through the CPU interface and through commands, but cannot be displayed directly. Each VRAM group can be 0K, 16K or 64K. A group can consist of 8 chips of 1-bit wide RAM or 2 chips of 4-bit wide RAM, but for the purpose of addressing we can just pretend it is 1 chip with 8-bit wide RAM. In real MSX machines, the following group combinations are used: CAS0: CAS1: CASX: Description: 16K 0K 0K MSX1 with V9938 and 16K VRAM (*1) 64K 0K 0K MSX2 with 64K VRAM (*2) 64K 64K 0K MSX2 with 128K VRAM 64K 64K 64K MSX2 with 128K VRAM and 64K expansion (*3) *1: Examples are the Spectravideo SVI-738 X'PRESS and the Yamaha CX5MII/128. The only V9938-specific display mode that they can fully display is 80-columns text mode (TEXT2). *2: Known 64K VRAM machines: Canon V-25, Hitachi MB-H3, Talent TPP-311 and 312, Toshiba HX-23. *3: No known machines shipped with expansion VRAM. It was a popular amateur upgrade for users of FastCopy, to reduce the number of required disk swaps. In most display modes addressing is linear, meaning the lower 64K of address space uses the VRAM connected to CAS0 and the upper 64K of address space uses the VRAM connected to CAS1. In SCREEN7/8 (Graphic 6/7) the addressing is planar, meaning that even VRAM addresses are mapped to CAS0 and odd addresses are mapped to CAS1. This means that SCREEN 7/8 are only available on 128K VRAM machines. The most likely reason for using planar addressing is getting a higher bandwidth: even though the data lines are shared between all VRAM groups, the row select has to be done only once. VR=0 / VR=1 mode ---------------- The VDP chip has 10 pins that are connected to the VRAM subsystem to control the read/write address. There are 8 address pins, these select the CAS/RAS address. And there is a CAS0 and CAS1 pin, these select one of two ram chip (groups). In total this allows for a 17-bit address space (=128kB). (I'm ignoring the extended VRAM here). When the VDP does a memory access, it's puts the 17-bit logical address on these pins. The way how this is done depends on the VR bit (bit 3 of register 8), see table below. In VR=1 mode, it first puts the bits A15-A8 on the address bus, followed by the bits A7-A0. The whole time either pin CAS0 or CAS1 is selected, depending on address bit A16. In VR=0 mode, .. well see table ;) VDP | VR=0 | VR=1 | address|----------------------|----------------------| pin | RAS(high) | CAS(low) | RAS(high) | CAS(low) | -------+-----------+----------|-----------+----------| AD0 | A6 #2| 1 #1| A8 | A0 | AD1 | A7 | A0 | A9 | A1 | AD2 | A8 | A1 | A10 | A2 | AD3 | A9 | A2 | A11 | A3 | AD4 | A10 | A3 | A12 | A4 | AD5 | A11 | A4 | A13 | A5 | AD6 | A12 | A5 | A14 | A6 | AD7 | A13 | A6 #2| A15 | A7 | -------+-----------+----------|-----------+----------| CAS0/1 | A14 | A16 | #1 this pin is always '1' in the CAS phase #2 A6 is output both in CAS and RAS phase, this allows to use both 8/6 and 7/7 RAS/CAS configurations (see VDP data sheet for details) One important side-effect is that in VR=0 mode, the VDP can only address 32kB memory. This is what makes the demo 'syntax infinity' work even though R14 is programmed 'wrongly' (it writes to address 0x1????, but expects the data to end up at address 0x0????). Another side effect is that when switching between VR=0 and VR=1 modes, the address space gets shuffled around, much like bit 7 in VDP register 1 for TMS99x8 chips. openMSX-RELEASE_0_12_0/doc/keyboard.readme000066400000000000000000000050601257557151200201640ustar00rootroot00000000000000Keyboard related settings in hardwareconfig.xml In section ...: Setting name: key_ghosting Default value: true Description: If true, the emulator will press 'ghost' keys when user presses multiple keys at once on crossing rows/columns of the keyboard matrix. It must be set to true for all MSX models that suffer from this hardware problem Setting name: key_ghosting_sgc_protected Default value: true Description: If true, the key ghosting routine in the emulator will protect Shift, Graph and Code keys from key-ghosting. According to schematics and tests, several European machines have such a protection but some Japanese machines don't have it. In the protected machines, those three keys are connected to the keyboard matrix via a Diode, which prevents that pressing those two or more of those three keys together would ghost to other rows/columns via the 'character key' that you press in combination with them. Setting name: keyboard_type Default value: int Description: Indicates which type of keyboard the MSX has. It is used to load a file that maps unicode characters to the appropriate key-combinations on the MSX. The file-name will be: unicodemaps/unicode. Setting name: has_keypad Default value: true Description: Indicates if the emulated MSX model has a numeric keypad. If true, key-presses on the host numeric keypad will be mapped to corresponding key-presses on the MSX's numeric keypad. If false, key-presses on the host numeric keypad will be ignored. User can overrule this with a setting in the settings.xml file. Setting name: code_kana_locks Default value: false Description: When true, the the CODE/KANA key locks on the emulated machine. Is in practice the behaviour of all Japanese MSX models and of the VG8010. If false, CODE/KANA key does not lock (e.g. it is only active as long as the key is pressed) In section ...: Setting name: keyboardlayout Default value: Description: Indicates if the "keyboard mode" flag (bit 6 on PSG register 14) must be set. It will be set to "1" if keyboardlayout is JIS. Otherwise it will be 0. In practice, this bit is only read by the Japanese ROM-BIOS to determine the keyboard layout; Japanese-JIS or Japanese-ANSI See http://map.tni.nl/articles/keymatrix.php for further details about Japanese keyboard layouts. Note that this setting is ignored by the keyboard driver of openMSX. The keyboard driver only looks at the keyboard_type parameter in the PPI section to determine which unicode to keycombination mapping to apply. Keyboard related settings are described in doc/commands.html openMSX-RELEASE_0_12_0/doc/manual/000077500000000000000000000000001257557151200164615ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/doc/manual/commands.html000066400000000000000000005713331257557151200211640ustar00rootroot00000000000000 openMSX Console Command Reference

    openMSX Console Command Reference

    1. Introduction
    2. Commands
      1. after
      2. bind / unbind / bind_default / unbind_default / activate_input_layer / deactivate_input_layer
      3. cart / cart<x>
      4. cassetteplayer
      5. cd<x>
      6. cycle / cycle_back
      7. debug
      8. disk<x> / virtual_drive
      9. diskmanipulator
      10. escape_grab
      11. exit
      12. ext / ext<x>
      13. filepool
      14. findcheat
      15. hd<x>
      16. help
      17. incr
      18. iomap
      19. keymatrixdown / keymatrixup
      20. laserdiscplayer
      21. list_extensions
      22. load_icons
      23. load_settings
      24. machine
      25. create_machine / load_machine / activate_machine / list_machines / delete_machine
      26. machine_info
      27. message
      28. monitor_type
      29. mute_channels / unmute_channels / solo
      30. nowind<x>
      31. openmsx_info
      32. openmsx_update
      33. osd
      34. palette
      35. plug / unplug
      36. psg_profile
      37. record
      38. record_channels
      39. remove_extension
      40. reset
      41. reverse
      42. save_settings
      43. savestate / loadstate / list_savestates / delete_savestate
      44. screenshot
      45. set
      46. slotmap
      47. slotselect
      48. soundlog
      49. store_machine / restore_machine
      50. test_machine
      51. toggle
      52. trainer
      53. type
      54. unset
      55. user_setting
      56. vdpregs
      57. other
    3. Settings
      1. accuracy
      2. audio-inputfilename
      3. autoruncassettes
      4. autorunlaserdisc
      5. auto_enable_reverse
      6. auto_save_replay
      7. blur
      8. bootsector
      9. brightness
      10. cmdtiming
      11. color_matrix
      12. console
      13. consolebackground
      14. consolecolumns
      15. consolefont
      16. consolefontsize
      17. console_history_size
      18. consoleplacement
      19. consolerows
      20. console_remove_doubles
      21. contrast
      22. cputrace
      23. debugoutput
      24. default_machine
      25. deflicker
      26. deinterlace
      27. DirAsDSKmode
      28. disablesprites
      29. display_deform
      30. di_halt_callback
      31. enable_session_management
      32. frequency
      33. firmwareswitch
      34. fullscreen
      35. fullspeedwhenloading
      36. gamma
      37. glow
      38. grabinput
      39. horizontal_stretch
      40. inputdelay
      41. interleave_black_frame
      42. invalid_psg_directions_callback
      43. joystick<n>_config
      44. joystick<n>_deadzone
      45. kbd_auto_toggle_code_kana_lock
      46. kbd_code_kana_host_key
      47. kbd_deadkey1_host_key
      48. kbd_deadkey2_host_key
      49. kbd_deadkey3_host_key
      50. kbd_mapping_mode
      51. kbd_numkeypad_always_enabled
      52. kbd_numkeypad_enter_key
      53. kbd_trace_key_presses
      54. keyjoystick<n>.<button>
      55. led_<name>
      56. limitsprites
      57. master_volume
      58. maxframeskip
      59. midi-in-readfilename
      60. midi-out-logfilename
      61. minframeskip
      62. mode
      63. mute
      64. noise
      65. pause
      66. pause_on_lost_focus
      67. pointer_hide_delay
      68. power
      69. printerlogfilename
      70. print-resolution
      71. r800_freq / r800_freq_locked
      72. renderer
      73. renshaturbo
      74. resampler
      75. rs232-inputfilename
      76. rs232-outputfilename
      77. rtcmode
      78. samples
      79. save_settings_on_exit
      80. scale_algorithm
      81. scale_factor
      82. scanline
      83. sound_driver
      84. speed
      85. <soundchip>_balance
      86. <soundchip>_ch<channel>_record
      87. <soundchip>_ch<channel>_mute
      88. <soundchip>_detune_frequency
      89. <soundchip>_detune_percent
      90. <soundchip>_vibrato_frequency
      91. <soundchip>_vibrato_percent
      92. <soundchip>_volume
      93. throttle
      94. too_fast_vram_access
      95. too_fast_vram_access_callback
      96. touchpad_transform_matrix
      97. turborpause
      98. umr_callback
      99. vdpcmdinprogress_callback
      100. vdpcmdtrace
      101. videosource
      102. v9990cmdtrace
      103. z80_freq / z80_freq_locked
      104. other

    Introduction

    This manual describes all commands and settings which are available in openMSX. You can use them to control openMSX fully from the Console (a built in command line interface, use F10 to call it), via Tcl scripts and via remote connections (explained in Controlling openMSX from External Applications). If you want to unleash the full potential of openMSX or just want a reference of all available possibilities, this manual should serve you well.

    Commands

    after

    Execute a command after a certain event occurs, for example a given amount of time has passed or the emulator has been idle for a given amount of time. Every postponed command executes just once; if you want a command to run periodically, you have to issue it again every time it runs. The after command returns the id of the postponed command. It is possible to query a list of postponed commands and also to cancel postponed commands.

    usage:
    after time <seconds> <command> Execute a command after some time. Timescale is in MSX seconds.
    after realtime <seconds> <command> Execute a command after some time. Timescale is in host seconds.
    after idle <seconds> <command> Execute a command after some time being idle
    after frame <command> Execute a command when a video frame is finished (VDP scanning reaches vsync)
    after break <command> Execute a command after a breakpoint is reached
    after boot <command> Execute a command after a (re)boot
    after machine_switch <command> Execute a command after switch to new a machine
    after <input-event> <command> Execute a command after the given input event occurs. The events are e.g. mouse, joystick, focus and resize events, the same ones as for the bind command.
    after info List all postponed commands
    after cancel <id> Cancel the postponed command with given id
    examples:
    after time 2.6 "set renderer SDLGL-PP"
    after idle 100 exit
    after info
    after cancel after#2
    after "mouse button1 down" foo

    bind / unbind / bind_default / unbind_default / activate_input_layer / deactivate_input_layer

    Associate events (such as key presses) with commands. Whenever the specified event occurs (e.g. you press the specified key), the corresponding command will be executed. Any Tcl command or combination of commands separated with ; (normal Tcl syntax) can be used. To customize your bindings you should use the (un)bind commands. A script that wants to provide a default binding for its functionality needs to use bind_default, this allows users with different preferences to overrule the default bindings. Using the -repeat option makes sure that if the event is repeated (e.g. keyboard events when keeping a key pressed), the command is repeated as well.

    Tcl scripts that need a whole set of bindings and only conditionally activate those bindings can use the 'input layer' system. It's possible to associate a binding with a specific layer and later specific layer(s) can be activated or deactivated. Such a layer can also be activated in a blocking mode. Blocking mode means that even if the layer didn't have a binding for a certain event, that event is still not passed to the emulated MSX. This can be useful to implement certain OSD widgets (like a virtual OSD keyboard).

    Events can be:

    <key>[,release] Short for keyb <key>[,release]
    keyb <key>[,release] <key> is pressed [or released]
    mouse button<n> <up/down> Mouse button <n> went up or down
    mouse motion <x> <y> Mouse motion of <x> and <y>
    joy<n> button<m> <up/down> Button <m> of joystick <n> went up/down
    joy<n> axis<m> <value> Axis <m> of joystick <n> got value <value>
    focus <boolean> The openMSX window got (1) or lost (0) focus
    OSDcontrol <button> PRESS|RELEASE The virtual OSDcontrol <button> got pressed or released.
    usage:
    bind Show all bindings
    bind -layer <layername> Show all in a specific layer
    bind <event> [-layer <layername>] Show binding for the given event, optionally you can specify a layer
    bind <event> [-layer <layername>] [-repeat] <command> Make a new binding. Optionally make this binding in a specific layer. Also optionally it's possible to retrigger this binding periodically (e.g. when a key is kept pressed).
    bind -layers Show the names of all layers that currently have bindings
    unbind [-layer <layername>] <event> Undo binding for this event (optionally in a specific layer).
    unbind -layer <layername> Undo all bindings in the specified layer
    activate_input_layer Show a list of the currently active layers.
    activate_input_layer [-blocking] <layername> Activate the specified input layer, optionally this layer can be activate in blocking mode.
    deactivate_input_layer <layername> Deactivate the specified input layer.
    examples:
    bind PAGEUP "set speed 100"
    bind PAGEDOWN "set speed 50"

    Only run with full throttle while F9 is pressed (like BrMSX):
    unbind F9
    bind F9 "set throttle off"
    bind F9,release "set throttle on"

    Pause when window loses focus (like fMSX):
    bind "focus 0" "set pause on"
    bind "focus 1" "set pause off"

    Middle-click to toggle input grabbing:
    bind "mouse button2 down" "toggle grabinput"

    Map button 8 of joystick 1 to F2-key:
    bind "joy1 button8 down" "keymatrixdown 6 0x40"
    bind "joy1 button8 up" "keymatrixup 6 0x40"

    Use PageUp/Down to increase/decrease emulation speed.
    bind PAGEUP -repeat "incr speed 1"
    bind PAGEDOWN -repeat "incr speed -1"

    Use Joystick hat left/right to increase/decrease volume.
    bind "joy1 hat0 left" -repeat "incr speed -5"
    bind "joy1 hat0 right" -repeat "incr speed 5"

    Toggle fullscreen with ALT and ENTER.
    bind ALT+RETURN toggle fullscreen

    React to joystick or cursor up movement in a Tcl script:
    bind_default "OSDcontrol UP PRESS" -repeat {osd_menu::menu_action UP }
    React to joystick button 1 or spacebar press in a Tcl script:
    bind_default "OSDcontrol A PRESS" -repeat {osd_menu::menu_action A }

    cart / cart<x>

    Insert a ROM cartridge in a running MSX. The cart command inserts the cartridge in the first available slot. The carta, cartb etc. commands insert it in the specified slot. The cartridges can be removed again with the eject subcommand.

    ROM cartridges are a special class of extensions. For extensions that are not ROM cartridges, see the commands ext, list_extensions and remove_extension.

    usage:
    cart KMARE.ROM Insert ROM cartridge in first free slot
    cart insert KMARE.ROM Insert ROM cartridge in first free slot
    carta USAS.ROM -ips USAS.IPS Insert ROM cartridge in slot A, with IPS patch applied to the ROM contents
    cartb NEMESIS.ROM -romtype Konami Insert ROM cartridge in slot B, and explicitly specify the mapper type (is normally auto detected)
    carta eject Eject the currently inserted cartridge from slot A

    cassetteplayer

    Controls the openMSX cassette player. The various subcommands can be used to insert, remove, create and rewind tape images.

    usage:
    cassetteplayer insert <tape image> Insert tape image (WAV or CAS format) in the cassette player
    cassetteplayer eject Remove tape from virtual cassette player
    cassetteplayer rewind Rewind the current tape
    cassetteplayer motorcontrol on|off Selects whether motor control signal (remote) is obeyed (default: on)
    cassetteplayer new [<tape image>] Create new tape image and go to record mode
    cassetteplayer play Go to play mode (when in record mode) and rewind the tape

    cd<x>

    Change the CDROM image. The commands cda, cdb etc. are assigned to all available CDROM drives in the MSX. They will not correspond to drive names as used in MSX-DOS.

    usage:
    cda <iso image> Use ISO image for CDROM drive "cda"
    cda insert <iso image> Use ISO image for CDROM drive "cda"
    cda eject Eject CDROM from CDROM drive "cda"
    cda Show current ISO image for CDROM drive "cda"

    cycle / cycle_back

    Iterates through the values of an enumerated setting.

    cycle_back does the same as cycle, but it goes in the opposite direction.

    usage:
    cycle <setting> Changes the specified setting to the next value in the cycle
    cycle_back <setting> Changes the specified setting to the previous value in the cycle
    examples:
    cycle scale_algorithm
    cycle videosource

    debug

    This command provides access to the debugger functionality of openMSX. It's meant to be used by an external debugger (see also Controlling openMSX from External Applications). The general format of the debug command is:

    debug <subcommand> [<extra arguments>]

    where 'extra arguments' are specific for each subcommand. Below is a list of all subcommands:

    debug list Return a list of all debuggables.
    A debuggable is (part of) the state of
    a MSX device that can be accessed via these debug commands.
    Examples are:
    • the VDP registers
    • the currently visible memory for the Z80
    • the contents of the RAM
    debug desc <name> Return a description of this debuggable
    debug size <name> Return the size of this debuggable
    debug read <name> <addr> Read a byte from a debuggable
    debug write <name> <addr> <val> Write a byte to a debuggable
    debug read_block <name> <addr> <size> Read a whole block at once
    debug write_block <name> <addr> <values> Write a whole block at once
    debug probe <subcommand> See below.
    debug break Break CPU at current position
    debug breaked Query CPU break status
    debug cont Continue execution after break
    debug step Execute one instruction
    debug list_bp List the active breakpoints
    debug set_bp <addr> [<cond>] [<cmd>] Insert a new breakpoint at the given address. Optionally you can specify a condition and a command. When the CPU is about to execute the instruction at the given address, the condition will be evaluated, if this evaluates to true then the command is executed. The condition can be any Tcl expression and the command can be any Tcl command. The default condition is 'true' and the default command is "debug break".
    debug remove_bp <id> Remove a certain breakpoint
    debug list_watchpoints List all defined watchpoints
    debug set_watchpoint <type> <region> [<cond>] [<cmd>] Insert a new watchpoint. When the CPU is about to read or write to/from the specified memory or I/O region, the condition is evaluated. If the condition evaluated to true, the command is executed. The condition and the command are similar to the ones in the set_bp subcommand. A watchpoint can either be set on a single memory address or I/O port (specify a single value), or on a whole memory or I/O port range (specify a begin/end pair). For example: debug set_watchpoint write_mem {0x8000 0x8FFF}
    debug remove_watchpoint <id> Remove a certain watchpoint
    debug list_conditions List the active conditions
    debug set_condition <cond> [<cmd>] Set a new debugger condition. Conditions are like breakpoints, but not tied to a specific address. Simulation is much slower when conditions are used (though generally while debugging this is not a problem).
    debug remove_condition <id> Remove a certain condition
    debug disasm [<addr>] Disassemble instructions at PC or given address

    The probe subcommand again has subcommands:

    debug probe list Returns a list of all probes.
    debug probe desc <probe> Returns a description of this probe.
    debug probe read <probe> Returns the current value of a probe. But note that not all probes have a value
    debug probe set_bp <probe> [<cond>] [<cmd>] Set a breakpoint of a probe. Like in the 'set_bp' subcommand you can optionally specify a condition and a command.
    debug probe remove_bp <id> Remove the given breakpoint.
    debug probe list_bp List the active breakpoints set on probes.

    At first sight 'probes' and 'debuggables' are very similar. Though there are some important differences and that's why probes and debuggables use different subcommands:

    A debuggable is an array of bytes. A probe is a single value and can have any type.
    A debuggable is readable and (usually) writable. A probe is always read-only.
    It's not possible to set breakpoints on a debuggable. You can set breakpoints on a probe (and sometimes this is the only purpose of a probe).

    Many examples of usage of the debug command can be found in the scripts that come with openMSX (in the share/scripts directory). We also list a few here.

    examples:
    • break (only!) after 0 is written to 0x8000):
      debug set_watchpoint write_mem 0x8000 {[debug read "memory" 0x8000] == 0x00}
    • break on address 0xF37D, but only when Z80 register C has the value 0x2F:
      debug set_bp 0xF37D {[reg C] == 0x2F}
    • break when CPU reads from any addresses between 0xFBE5 and 0xFBEF:
      debug set_watchpoint read_mem {0xFBE5 0xFBEF}
    • break after a write was done to I/O port 0x99, but only when Z80 register A has a value of 0x81:
      debug set_watchpoint write_io 0x99 {[reg A] == 0x81}
    • break as soon as there is a pending Z80 IRQ (even when in DI mode):
      debug probe set_bp z80.pendingIRQ
    • break when register HL has the value 1234:
      debug set_condition {[reg hl] == 1234}
    Note: Some of the commands are pretty low level. In the share/scripts directory you'll find some Tcl scripts that offer convenience wrappers around these commands. For example: showmem, disasm, cpuregs, save_debuggable, etc.

    disk<x> / virtual_drive

    Insert a disk image in a drive. Optionally apply an IPS patch to the disk image. The commands diska, diskb etc. are assigned to all available "physical" disk drives in the MSX. They might not correspond to drive names as used in MSX-DOS.

    In addition to the physical disk<x> drives, there is the virtual_drive. This fake drive does not correspond to any MSX hardware. It can be used as a source or target for diskmanipulator operations just like physical drives.

    usage:
    diska <disk image> Insert disk image in drive "diska"
    diskb insert <disk image> Insert disk image in drive "diskb"
    diska <disk image> <ips> Insert disk image and apply IPS patch
    diska eject Remove disk from drive "diska"
    diska ramdsk Insert scratch disk in drive "diska"

    diskmanipulator

    A collection of commands to manipulate (the files on) a disk image.

    It can be used in so many different ways, that we wrote a separate manual for it: Using Diskmanipulator.

    escape_grab

    Only has effect in windowed mode and when the grabinput setting is active. Temporarily release the input grab. After the openMSX window has lost and regained the focus, the grab is again effective.

    usage:
    escape_grab Temporarily release the input grab

    exit

    Terminate the openMSX application.

    usage:
    exit Exits the emulator

    ext / ext<x>

    Insert an MSX extension in a running MSX machine. The ext command inserts the extension in the first available slot. The exta, extb etc. commands insert it in the specified slot. The extension can be removed again with the remove_extension command. See also the commands cart, list_extensions and remove_extension.

    Note that some extensions (i.e. those without any memory) will not physically occupy any slot when inserted, even when they were inserted in a specific slot.

    usage:
    ext fmpac Insert an FMPAC in a running MSX machine in the first free slot
    extb scc Insert the empty SCC cart in slot B of the running MSX machine

    filepool

    With this command you can manage your file pool settings. File pools are directories on your host system (PC/Mac/Dingoo/etc.). They are used by openMSX to search files in, which are referred to from machine or extension definition files, save states or replays which you are trying to load. First, the file will be searched at the path that was also used when the save state or replay was created. But if it isn't found there (which is usually the case if you load such a state or replay you got from someone else), it will use the file pools to search instead. In other words, if you are trying to load such replays, it's probably a good idea to put the media files referred to (ROMs, disks, tapes) in the (proper) filepool.

    File pools have the following properties:

    path
    The path to the directory which is the actual file pool
    position
    There exists a list of file pools, which are searched in order of their position.
    type(s)
    A file pool can serve specific types. Currently, the valid types are system_rom (for system ROMs, you are probably using this one already if you installed your system ROMs in the recommended place, share/systemroms), rom (for other ROM files), disk (for disk images) and tape for cassette/tape images.

    Apart from the default sytem ROM file pool as mentioned above, the other default file pool is share/software, where openMSX will search for other (than system ROM) software.

    usage:
    filepool list Shows the currently defined file pool entries (see below for example output)
    filepool add -path <path> -types <typelist> [-position <pos>] Add a new entry with the given properties as explained above. For the types, use a format like "rom tape disk". Optionally, you can also specify where in the list of existing file pools the new file pool should be added. By default, this is at the end.
    filepool remove <position> Remove the file pool at the given position
    filepool reset Reset the file pool settings to the default values

    An example of the default file pools for a Windows 7 system with user Quibus:

    1: C:/Users/Quibus/Documents/openMSX/share/systemroms  [system_rom]
    2: C:/Users/Quibus/Documents/openMSX/share/software  [rom disk tape]
    3: C:/Program Files/openMSX/share/systemroms  [system_rom]
    4: C:/Program Files/openMSX/share/software  [rom disk tape]
    

    The first one is the system ROMs dir in the user's home directory. The second is the software file pool for other software in the user's home directory. The last two are similar, but then on system level. On a UNIX like system, you get something very similar.

    findcheat

    This is a tool to find new cheats, for example for a certain game it can help you find the memory location where the number of remaining lives is stored. These cheats can later be added to the trainer command.

    It works more or less like this:

    1. Initialize the findcheat tool, this takes an initial snapshot of the MSX memory.
    2. Perform some action in the game that changes the variable that you're interested in. For example if you want to find the memory location where the number of lives is stored, you have to loose (or gain) a life in the game.
    3. Now use the findcheat tool to compare the current MSX memory state with the previous memory snapshot. findcheat offers a lot of possibilities here, for example you can search for memory locations that became bigger or smaller or locations whose value changed or didn't change.
    4. findcheat will show a list of memory locations that still match the search criteria.
    5. If there still are still too many matches, repeat from step 2.

    Vampier made a video tutorial on how to use findcheat, you can find it here.

    hd<x>

    Change the hard disk image. The commands hda, hdb etc. are assigned to all available hard disk drives in the MSX. They will not correspond to drive names as used in MSX-DOS.

    usage:
    hda <disk image> Use hard disk image for hard disk "hda"
    hda insert <disk image> Use hard disk image for hard disk "hda"
    hda Show current hard disk image for hard disk "hda"
    Note: Because of disk caching, changing the hard disk when the MSX is running can lead to corruption of the hard disk contents. Therefore openMSX blocks the hd<x> commands unless the MSX is powered off. See power setting.

    help

    Shows help info for console commands.

    usage:
    help Shows a list of all possible commands
    help <command> Shows help info for a specific command
    help <command> <subcommand> Some commands have more detailed help on subcommands

    incr

    Increment an integer setting.

    usage:
    incr <setting> Increment the specified setting by one
    incr <setting> <num> Increment the specified setting by the given amount
    examples:
    incr speed
    incr renshaturbo 10
    incr scanline -5

    iomap

    Shows what I/O ports are connected to which devices. The related command slotmap shows a similar overview, but for memory-mapped devices.

    usage:
    iomap Shows the I/O map of the current MSX machine

    keymatrixdown / keymatrixup

    Press or release keys in the MSX keyboard matrix. Can be used to make an external program or Tcl script press MSX keys. For some more information about the keymatrix, you could read the article on the MAP.

    usage:
    keymatrixdown <row> <mask> Press the indicated MSX keys
    keymatrixup <row> <mask> Release the indicated MSX keys
    examples:
    keymatrixdown 6 0x01
    keymatrixup 6 0x01

    laserdiscplayer

    Controls the Laserdisc player; a Laserdisc can be inserted or ejected. When a real Laserdisc player is connected to an MSX, no other controls are available either.

    Note that this command is only available when the Pioneer PX-7 or Pioneer PX-V60 is being emulated

    usage:
    laserdiscplayer insert <filename> Inserts the specified file into the virtual laserdisc player.
    laserdiscplayer eject Ejects the laserdisc from the virtual laserdisc player; this emulates pressing the eject button on a real Laserdisc Player.

    list_extensions

    Returns a list of inserted cartridges and extensions. These can be removed with the remove_extension command or additional items can be added with the cart and ext commands.

    usage:
    list_extensions Lists all currently inserted cartridges and extensions

    load_icons

    Load a different icon set (used for the On Screen Display (OSD) LEDs).

    Icon sets are stored in the share/skins directory.

    usage:
    load_icons <name> Load the given icon set, but don't change the position on the screen.
    load_icons <name> <position> Load the given icon set and place them at the requested position. Position can be bottom, top, left or right.

    load_settings

    Load settings from a given settings XML file. The settings file does not have to be complete: settings that are not mentioned in the given file are left untouched. See also save_settings.

    usage:
    load_settings <filename> Load settings from the given file

    machine

    Switch to a new MSX machine.

    usage:
    machine Returns the handle for the currently active machine
    machine <machine name> Switch to the specified machine, also returns the handle for that machine
    Note: The machine handle is mostly used by external applications controlling openMSX (see also Controlling openMSX from External Applications). For interactive use you can omit the machine handle to have the commands operate on the current machine.

    create_machine / load_machine / activate_machine / list_machines / delete_machine

    openMSX has the possibility to have multiple MSX machines concurrently in memory. This is more or less like multiple tabs in a web browser: you only work with one at-a-time, but you can have multiple open at the same time and easily switch between them. These commands are low level commands to manage this.

    Some commands are specific per machine, for example if you insert a disk image into the disk drive of the emulated MSX machine and if you have multiple MSX machines, you need to specify in which MSX machine you want to insert the disk. To solve this, we introduced the concept of the 'active' MSX machine (this is also the machine that is visible and audible). All unqualified machine-specific command will act on the active machine. If you want to execute the command in a specific machine, you can qualify the command with a machineID prefix.

    diska <diskimage> execute diska command in the active machine
    <machine-ID>::diska <diskimage> execute diska command in the specified machine

    create_machine:

    This command returns a new machine-id. This machine-id can be used in the following commands. In the web browser analogy this command would open a new empty tab.

    load_machine:

    This command loads a machine configuration (= MSX model) into the given machine-ID. In the web browser analogy, this command would load a page in a previously created empty tab. And unlike a web browser, where you can reload a different page in the same tab, you can only load a machine configuration once in the same machine-ID.

    activate_machine:

    This command activates the given machine-ID. At any time there can only be one active machine-ID. This is analogue to switching tabs in a web browser.

    list_machines:

    Returns a list of all currently existing machine-IDs.

    delete_machine:

    Deletes the given machine-ID. This is analogue to closing a tab in a web browser.

    examples:

    set oldID [machineID] get the current machineID
    set newID [create_machine] create a new machineID
    $newID::load_machine Philips_NMS_8250 load an MSX2 configuration in that new machineID
    activate_machine $newID switch to the new machine
    activate_machine $oldID switch back to old machine
    delete_machine $newID delete new machine
    If you don't care about multiple active machines, the machine command is much more convenient to switch to a different MSX configuration.

    machine_info

    Shows information about a certain topic. This command is similar to the openmsx_info command. The topics of machine_info are all machine specific, while the topics of openmsx_info are generic.

    usage:
    machine_info Shows a list of all possible topics
    machine_info <topic> Shows info on the given topic

    message

    Show a message, with optional level (info, warning, error). By default this message will be shown in a colored box at the top of the screen for a (short) duration and then fade away.

    usage:
    message <text> [<level>]
    examples:
    message "Hello world!"
    message "Something bad happened" error

    monitor_type

    Select a monitor color profile.

    usage:
    monitor_type Shows the currently selected color profile
    monitor_type -list Lists all available color profiles
    monitor_type <profile> Selects a new color profile
    Note: This command is a convenience wrapper around the color_matrix setting.

    mute_channels / unmute_channels / solo

    Mute or unmute specific individual channels of sound devices. The syntax is very similar to the record_channels command.

    usage:
    mute_channels <device> [<channels>]] [<device> [<channels>]] Mute the specified channels of the specified device(s). If a device is given but no specific channels are specified, all channels of that device are muted. If no arguments are given at all, this command return a list of all currently muted channels.
    unmute_channels <device> [<channels>]] [<device> [<channels>]] Unmute the specified channels of the specified device(s). If a device is given but no specific channels are specified, all channels of that device are unmuted. If no arguments are given at all, this command unmutes all channels of all devices.
    solo <device> [<channels>]] [<device> [<channels>]] Mute all channels of all devices except for the specified channels.
    examples:
    mute_channels
    mute_channels PSG
    mute_channels SCC 2,4
    unmute_channels
    unmute_channels PSG 1 SCC 1,3-4
    solo PSG 3

    nowind<x>

    Similar to the disk<x> commands there is a nowind<x> command for each nowind interface. This command is modeled after the 'usbhost' command of the real nowind interface. Though only a subset of the options is supported. Here's a short overview of the command line options:

    long shortexplanation
    --image -i specify disk image
    --hdimage -m specify harddisk image
    --romdisk -j enable romdisk
    --ctrl -c no phantom disks
    --no-ctrl -C enable phantom disks
    --allow -a allow other diskroms to initialize
    --no-allow-A don't allow other diskroms to initialize

    If you don't pass any arguments to this command, you'll get an overview of the current nowind status.

    This command will create a certain amount of drives on the nowind interface and (optionally) insert diskimages in those drives. For each of these drives there will also be a nowind<x><1..8> command created. Those commands are similar to e.g. the diska command. They can be used to access the more advanced diskimage insertion options.

    In some cases it is needed to reboot the MSX before the changes take effect. In those cases you'll get a message that warns about this.

    examples:
    nowinda -a image.dsk -j Image.dsk is inserted into drive A: and the romdisk will be drive B:. Other diskroms will be able to install drives as well. For example when the MSX has an internal diskdrive, drive C: en D: will be available as well.
    nowinda disk1.dsk disk2.dsk The two images will be inserted in A: and B: respectively.
    nowinda -m hdimage.dsk Inserts a harddisk image. All available partitions will be mounted as drives.
    nowinda -m hdimage.dsk:1 Inserts the first partition only.
    nowinda -m hdimage.dsk:2-4 Inserts the 2nd, 3th and 4th partition as drive A: B: and C:.

    openmsx_info

    Shows information about a certain topic. For machine-specific topics, use the related command machine_info.

    usage:
    openmsx_info Shows a list of all possible topics
    openmsx_info <topic> Shows info on the given topic

    openmsx_update

    Enable or disable update notifications of a certain type. This command is intended for external programs controlling openMSX. More about this in Controlling openMSX from External Applications.

    usage:
    openmsx_update enable <type> enable notifications for this type
    openmsx_update disable <type> disable notifications for this type
    examples:
    openmsx_update enable led
    openmsx_update disable setting

    osd

    openMSX has the possibility to show OSD (on screen display) elements. For example, the LEDs and the fps-indicator are implemented via OSD elements. This command allows to create new OSD elements, configure existing elements or delete elements.

    This command is only useful if you plan to adjust or enhance the openMSX OSD, or create your own OSD widgets.

    Execute "help osd" to get a detailed description of this command, which we will not repeat here.

    palette

    Shows the current VDP palette settings. Related command: vdpregs.

    usage:
    palette Show the currently active color palette

    plug / unplug

    Plugs or unplugs a plug into a connector, for example plug a virtual joystick into a virtual joystick port.

    usage:
    plug Shows all currently connected plugs
    plug <connector> Shows currently connected plug for the specified connector
    plug <connector> <plug> Plugs the specified plug into the specified connector
    unplug <connector> Unplugs the plug connected to the specified connector
    examples:
    plug cassetteport cassetteplayer
    plug joyporta mouse
    plug printerport logger
    unplug joyportb

    psg_profile

    Select a PSG sound profile.

    usage:
    psg_profile Shows the currently selected sound profile
    psg_profile -list Lists all available sound profiles
    psg_profile <profile> Selects a new sound profile
    Note: This command is a convenience wrapper around the PSG_vibrato_frequency, PSG_vibrato_percent, PSG_detune_frequency and PSG_detune_percent settings.

    record

    Controls video recording: write openMSX audio/video to an AVI file.

    usage:
    record start Record to file "openmsxNNNN.avi"
    record start <filename> Record to indicated file
    record start -prefix foo Record to file "fooNNNN.avi"
    record stop Stop recording
    record toggle Toggle recording

    The start subcommand also accepts an optional -audioonly, -videoonly, -doublesize and a -triplesize flag. Videos are recorded in a 320×240 size by default, at 640×480 when the -doublesize flag is used and 960×720 when using the -triplesize flag. If only audio is recorded, the created file will be a WAV file instead of an AVI file.

    If any stereo sound devices are present or any sound device has an off-center balance, the recording will be made in stereo, otherwise it will be mono. If a recording is made in mono and then a stereo sound device is added, you'll receive a warning that stereo sound has been detected and that the two channels will be mixed down to mono. You can prevent this from happening by using the -stereo option to force a stereo recording even if no stereo devices are present at the time you enter the command. You can also force a mono recording with -mono to save space.

    The soundlog command is a shorthand for record -audioonly.

    Use record_chunks if you want some extra options. You can control the maximum length (in seconds) to record and also set up multiple recordings of a certain length. This is very useful if you want to record for e.g. YouTube. The default length is 14:59 (to make sure YouTube will accept it). Using this command implies -doublesize.

    record_channels

    A high level command to record individual channels of sound chips to separate files. In the following variants of the command you can specify devices and channels. Multiple devices can be specified and multiple channels as well. If you want to specify channels of a device, put them right after the device. You can also specify all for the device, which means that all sound devices in the currently running MSX will be recorded. When starting recording, an option -prefix can be given to specify a filename prefix.

    usage:
    record_channels [start] <device> [<channels>] [<device> [<channels>]] [-prefix <prefix>] Start recording the specified channel(s) of the specified device(s). If no channels are given, all channels of the device are recorded.
    record_channels stop [<device> [<channels>]] [<device> [<channels>]] Stop recording the specified channel(s) of the specified device(s). If no channels are given, recording for all channels is stopped for the given device(s). If no devices are given, all channel recording is stopped.
    record_channels all -prefix justtesting Record all channels of all sound devices and create the file names with prefix 'justtesting' (e.g. to quickly delete all these files again).
    record_channels list Lists which channels of which sound chips are currently being recorded.
    examples:
    record_channels start PSG
    record_channels PSG
    record_channels SCC 1,4-5
    record_channels SCC PSG 1
    record_channels "MSX Music" 7-9 SCC 3,5 PSG 2
    record_channels stop
    record_channels stop PSG
    record_channels stop SCC 3,5
    record_channels list

    remove_extension

    Remove a cartridge or extension from a running MSX machine. See also the commands cart, ext, list_extensions.

    usage:
    remove_extension fmpac Removes the FMPAC extension from the running MSX

    reset

    Emulates the pressing of the reset button on the MSX. This sends a reset pulse to all devices, but does not erase memory contents.

    usage:
    reset Resets the current machine

    reverse

    Controls the reverse feature. When this feature is enabled (the default), openMSX will collect data while emulating, which enables you to go back (and forward) in MSX time. In other words: you cannot use the commands to go back and forward in time, if you disable the feature.

    usage:
    reverse start Start collecting data (enable the reverse feature).
    reverse stop Stop collecting data (disable reverse feature) and remove all collected data.
    reverse status Gives information about the reverse feature and the data it collected. Mostly useful for scripts.
    reverse goback <n> Go back <n> seconds in time. Of course, you cannot go back to a time before the time the reverse start command was given.
    reverse viewonlymode <on|off> Control the view only mode of the reverse feature. In view only mode, the replay will never get interrupted by any user actions that normally would interrupt the replay. Use this to safely view a replay without accidentally ruining it by touching a key.
    reverse goto <time> Go to the indicated absolute moment in MSX time (given in seconds). If the time is before the time openMSX started collecting data (with the reverse start command) openMSX will jump to the time when collecting started.
    reverse truncatereplay Stop replaying and wipe all replay data that is in the future (so after now). This is useful if you are hindered by the future events somehow, for instance when you are playing a game and jumped too early and therefore reversed. Be careful with this, as there is no way to recover this future. If you are at time 0, it means your whole replay will be gone after executing this command!
    reverse savereplay [<filename>] Save the collected data (an initial savestate and all collected input events) to a file.
    reverse loadreplay [-goto <begin|end|savetime|<n>>] [-viewonly] <filename> Load the replay from the given file and start it. Loads the initial snapshot and starts replaying the recorded events. Enables the reverse feature automatically. With the -goto option, you can specify where to jump to in the replay after loading (begin is default), where savetime is the time at which the replay was saved and n is an absolute time in seconds in the replay. The -viewonly option is a shortcut to put the reverse feature in viewonly mode directly after loading the replay. Without this option, it will always go to normal mode.

    There are some extra helper commands to make the feature easier to use.

    usage:
    go_back_one_second / go_forward_one_second Go back or forward one second in time (if possible). These are used for the default PageUp and PageDown key bindings.
    reverse_prev [<min> [<max>]] Go back in time to the previous (internal) snapshot. The further back in the past the less dense the amount of snapshots are. So, executing this command multiple times, will take successively bigger steps in the past. You can optionally specify a minimal and maximal step size. You will at least go back the minimal amount of time (even if there's a snapshot closer to the current time) and at most the maximal amount of time (even if there's no snapshot within the maximum specified time from the current time).
    reverse_next [<min> [<max>]] As reverse_prev but then it goes to the closest snapshot in the future (if possible).

    Because the reverse feature is very useful, it is automatically enabled via auto_enable_reverse setting.

    save_settings

    Write the current openMSX settings to a settings XML file. See also load_settings.

    If you disabled save_settings_on_exit, you can use this command to save your preferences.

    usage:
    save_settings Save settings to the default settings file
    save_settings <filename> Save settings to the given file

    savestate / loadstate / list_savestates / delete_savestate

    These command can be used to manage savestates. These are much easier to use than the lowlevel store_machine and restore_machine commands.

    savestate [<name>]

    This creates a snapshot of the currently emulated MSX machine. Optionally you can specify a name for the savestate, if you omit this name, the default name quicksave will be taken.

    loadstate [<name>]

    This restores a previously created savestate. Like above you can specify a name which defaults to quicksave if omitted.

    list_savestates

    This returns the names of all previously created savestates.

    delete_savestate <name>

    Delete a previously created savestate.

    screenshot

    Take a screenshot of the openMSX screen. By default this takes a screenshot of the 'scaled' MSX screen (see scale_algorithm setting) without OSD elements (e.g. console and icons). If you want to include the OSD elements pass the -with-osd option. If you want a screenshot of the 'unscaled' raw MSX screen, pass the -raw option. The screenshots are PNG files and (by default) are saved in the screenshots subdirectory of the openMSX data directory in your home directory. There's also an option -no-sprites to take a screenshot with sprite rendering disabled.

    usage:
    screenshot [-with-osd] [-raw [-doublesize]] [-no-sprites] [-prefix <prefix>] [<filename>]
    examples:
    screenshot Write screenshot to file "openmsxNNNN.png"
    screenshot <filename> Write screenshot to indicated file
    screenshot -prefix foo Write screenshot to file "fooNNNN.png"
    screenshot -raw Create screenshot of the raw MSX screen only (so no icons or console and no scaling)
    screenshot -raw -doublesize Create screenshot of the raw MSX screen only, with resolution 640×480
    screenshot -with-osd Create screenshot of the scaled screen, including OSD elements
    screenshot -no-sprites Create screenshot with sprite rendering disabled

    set

    Change or query the value of various settings. See also: unset.

    usage:
    set <setting> Query the current value of the specified setting
    set <setting> <value> Change the specified setting to the given value

    The settings that can be adjusted with this command are listed and explained at the end of this document.

    examples:
    set accuracy pixel
    set blur 25
    set scanline 20
    set deinterlace on

    slotmap

    Shows what devices are inserted into which slots. The related command iomap shows a similar overview, but for I/O mapped devices.

    usage:
    slotmap Shows the slot map of the current MSX machine

    slotselect

    Shows the currently selected slots. To see what devices are located in the slots, use the slotmap command.

    usage:
    slotselect Shows the currently selected slot for each page

    soundlog

    Controls sound logging: writing the openMSX sound to a WAV file.

    This command is a shorthand for record -audioonly.

    usage:
    soundlog start Log sound to file "openmsxNNNN.wav"
    soundlog start <filename> Log sound to indicated file
    soundlog start -prefix foo Log sound to file "fooNNNN.wav"
    soundlog stop Stop logging sound
    soundlog toggle Toggle sound logging state

    store_machine / restore_machine

    These are low-level commands, used to implement savestates.

    store_machine:

    Saves the state of the specified machine to a file.

    store_machine Save state of current machine to file "openmsxNNNN.xml.gz"
    store_machine <machineID> Save state of indicated machine to file "openmsxNNNN.xml.gz"
    store_machine <machineID> <filename> Save state of indicated machine to specified file

    restore_machine:

    Load a previously saved machine in a new machine-ID, next to the already available machines. See the section on activate_machine.

    restore_machine Load state from last saved state in default directory
    restore_machine <filename> Load state from indicated file
    Note: These commands are pretty low level. The savestate and loadstate scripts are built on top of this and are much more convenient to use.

    test_machine / test_all_machines / test_all_extensions

    Test whether the given MSX machine configuration works. For example whether you have all required system ROMs for this machine. See also load_machine.

    usage:
    test_machine <machine-config> Test whether the given machine configuration is OK.

    Use the convenience commands test_all_machines and test_all_extensions to get a full overview on which system ROMs you are still missing.

    toggle

    Toggles any boolean (on/off) setting: if it was on, it will be turned off and vice versa. See also: cycle.

    usage:
    toggle <setting> Toggles the specified setting
    examples:
    toggle mute
    toggle throttle

    trainer

    Control game trainers. You can enable or disable individual cheats of each trainer. Make use of the TAB key to see what is available. When switching trainers, the currently active trainer will be deactivated.

    usage:
    trainer See which trainer is currently active
    trainer <game> See which cheats are currently active in the trainer
    trainer <game> all Activate all cheats in the trainer of <game>
    trainer <game> \[<cheat> ..\] Toggle cheats of <game> on/off
    trainer deactivate Deactivate all trainers
    examples:
    trainer Frogger all
    trainer "Circus Charlie" 1 2
    trainer Pippols lives "jump shoes"

    type

    Type a string in the emulated MSX. This command automatically presses and releases keys in the simulated MSX keyboard matrix. This command is useful for demoing and for automating tasks in MSX-BASIC.

    The command has a -release option, with which you can specify that keys are always released before new ones are pressed. Some game input routines need this, but it also makes typing twice as slow.

    With the -freq option, you can tweak how fast typing goes and how long the keys will be pressed (and released if the -release option is used). Keys will be typed at the given frequency and will remain pressed/released for 1/freq seconds.

    usage:
    type "Hello world!" Yet another manifestation of the most famous program

    There are also a few scripts extending this command:

    type_from_file With this command you can automatically type text which is stored in the given (text) file. Mostly useful if you want to type in some BASIC program fragment that you found somewhere and pasted in a text file.
    type_password_from_file A special version of type_from_file, made to type in passwords of games, which you have stored in a file. The text file should have a special format: one password per line, lines starting with # are ignored. After the filename, you can give the index of the password to type (which is the index of the first non-comment and non-blank line in the file).

    unset

    Undefines a Tcl variable. When used on openMSX settings, they are reverted to their default value. See also: set.

    usage:
    unset <variable> Undefines the given variable
    unset <setting> Reverts the given setting to its default value

    user_setting

    This command is only meant to be used in Tcl scripts. It allows to create Tcl variables that act very much like built-in openMSX settings. They have a description (can be queried with "help set <setting-name>") and their value is stored saved/restored when openMSX is quit/restarted.

    Execute "help user_setting" to get a detailed description of this command.

    vdpregs

    Shows the current register settings of the Video Display Processor (VDP). Related command: palette.

    usage:
    vdpregs Shows the current VDP control register contents

    other

    Most commands described above are generally useful. openMSX also has a bunch of other more specialized commands. Some of these are intended for programmers who code MSX programs using openMSX as a tool. Other of these commands are more like toys or examples that show the openMSX scripting capabilities.

    We've only listed a very brief overview of these command. As always execute "help <command-name>" to get a more detailed description of the command.

    about Search command and setting help-texts for given keyword
    cpuregs Gives overview of the CPU registers
    data_file Helps locate openMSX data files
    disasm Print disassembled instructions at given memory location
    getcolor Query V99x8 palette settings
    get_active_cpu Returns the active cpu, z80 or r800
    get_color_count Gives an overview of the used colors in the current screen
    get_screen Capture the content of a MSX text screen in a Tcl string
    get_screen_mode Decodes the current screen mode from the VDP registers and returns it as a Tcl string. get_screen_mode_number returns it as a number which would also be used for the basic command SCREEN.
    get_selected_slot Returns the selected slot for the given memory page
    guess_title Use heuristics to guess the title of the current game (cartridge, disk or tape). For specific media use guess_cassette_title, guess_disk_title or guess_rom_title.
    listing Reimplementation of the BASIC LIST command in Tcl
    load_debuggable Write the content of a file to a openMSX debuggable
    main_menu_open / main_menu_close / main_menu_toggle Control the visibility of the default OSD menu
    multi_screenshot Take screenshots of multiple successive frames
    pc_in_slot Check whether CPU is executing from the specified slot (useful as breakpoint condition)
    peek Read a byte from given memory location
    peek16 Read a 16 bit word from given memory location
    poke Write a byte to given memory location. Use dpoke to only write if the to be written value is different than the current value.
    poke16 Write a 16 bit word to given memory location
    psg_log Log or replay PSG register values in binary format
    ram_watch Add or remove RAM watch addresses to/from the list on the right side of the screen, useful for debugging or TASing
    reg Read or write CPU registers
    reg_log Log or replay register values for the specified debuggable in ASCII format
    rom_info Gives information about the given ROM device, coming from the software database. If no argument is given, the first found (external) ROM device is assumed. This command replaces the info that was previously (before openMSX 0.8.1) automatically printed on stdout.
    run_to Execute instructions until PC reaches specified address
    save_debuggable Save the (partial) content of a debuggable to a file
    save_to_file Helper function to save data (e.g. the output of another command) to a file.
    setcolor Change V99x8 palette settings
    set_help_text Associate help text with a Tcl proc
    set_tabcompletion_proc Associate tab completion with a Tcl proc
    showdebuggable Print the content of a debuggable in a table
    showmem Print the content of memory in a table
    show_osd Print an overview of the defined OSD elements
    skip_instruction Skip the current CPU instruction
    sprite_viewer Show a widget with which you can view the sprite patterns
    stack Print the top of the CPU stack
    step_in Execute one CPU instruction, go into subroutines
    step_over Execute one CPU instruction, but don't go into subroutines
    step_out Step out of the current subroutine
    step_back Step one instruction back in time
    text_echo Echo all printed MSX text on stderr
    toggle_cursors Show (or hide) a widget which shows which keys are pressed
    toggle_fps Show (or hide) an fps indicator
    toggle_frame_counter Show (or hide) a widget which shows the current frame number since start-up
    toggle_freq Switch between PAL/NTSC
    toggle_info_panel Show (or hide) a panel with various types of info on the currently running MSX (emulation); similar to the info in the panel you get when using the DIGIblue theme in blueMSX
    toggle_mog_overlay Enable (or disable) graphical extra information and game hints when playing The Maze of Galious
    toggle_mog_editor Enable (or disable) wall drawing and Popolon-placement with the mouse when playing The Maze of Galious; needs to have the MoG overlay enabled, see toggle_mog_overlay
    toggle_music_keyboard Enable (or disable) keyboard view of all existing music channels. EXPERIMENTAL! Be careful, it's very slow when many channels are present in the system
    toggle_nemesis_1_shield Enable (or disable) an OSD drawn shield in Nemesis (Gradius) 1, all enemy objects will repel from it
    toggle_osd_keyboard Show (or hide) an on-screen keyboard (mostly useful for devices without physical keyboard); only supports an international layout for now
    toggle_psg2scc Enable (or disable) playing PSG sound on SCC
    toggle_reversebar Show (or hide) the reverse bar, which helps to control and view the data of the reverse feature, which is automatically enabled when the bar is enabled
    toggle_scc_editor Show a graphical view of the SCC chip(s) of the system, showing waveforms and volume per channel and also enables you to edit the waveforms per channel
    toggle_scc_viewer Show a graphical view of the SCC chip(s) of the system, showing waveforms and volume per channel
    toggle_show_palette Show (or hide) a graphical view on the palette registers
    toggle_vdp_access_test Enable (or disable) reporting in the console when VDP I/O is done which could possibly cause data corruption on the slowest VDP (TMS9xxx), which is not emulated
    toggle_vdp_busy Enable (or disable) display on the OSD how busy the VDP is
    toggle_vdp_reg_viewer Enable (or disable) a widget that shows an overview of all VDP registers and highlights changes in the values
    toggle_vu_meters Show (or hide) a graphical view of the volumes of the sound channels of several sound chips
    umrcallback Example proc to use with the umr_callback setting
    vdpcmdinprogresscallback Example proc to use with the vdpcmdinprogress_callback setting
    v9990reg Read or write a V9990 register
    v9990regs Print an overview of all V9990 registers
    vdpreg Read or write a V99x8 register
    vdrive Easily switch disks in multi-disk games
    vpeek/vpoke Read/write bytes from/to video RAM

    The source code of all these scripts is located in share/scripts directory. Feel free to inspect these scripts and modify them to suit your needs.

    Settings

    Settings control many aspects of openMSX. Below, the available settings are listed and described. You can change setting values with the set command.

    accuracy

    Sets the render accuracy. openMSX supports three levels of render accuracy:

    screen accurate:
    Changes in VDP state become effective only once per video frame. Works well for most MSX1 software, but will break a lot of MSX2 software (anything that does so-called raster effects).
    line accurate:
    Changes in VDP state become effective only once per display line. Works well for almost all software.
    pixel accurate:
    Changes in VDP state become effective immediately. In this mode even the 'Unknown Reality scope part' is rendered correctly.

    In some cases switching to a lower accuracy level can speed up emulation, but in many cases the speed difference is negligible.

    The default is pixel accuracy, since this is the most realistic. If the software you are running shows a jittery screen split and you would prefer a stable screen split, switching to line accuracy can help.

    usage:
    set accuracy Shows the current setting
    set accuracy screen Selects screen accurate rendering
    set accuracy line Selects line accurate rendering
    set accuracy pixel Selects pixel accurate rendering

    audio-inputfilename

    Sets the audio file from which the wave input is read for the sampler.

    By default, it is read from "audio-input.wav" when available.

    usage:
    set audio-inputfilename Shows the current setting
    set audio-inputfilename mysample.wav Read from "mysample.wav"
    Note: The file is fully read into memory, so under Linux/UNIX do not attempt to read from a device node such as /dev/dsp.

    autoruncassettes

    Switches the "auto-run cassettes" feature on or off. When it's enabled, openMSX will try to type the proper loading instruction when a cassette is inserted.

    usage:
    set autoruncassettes Shows the current setting
    set autoruncassettes on Try to run cassettes automatically
    set autoruncassettes off Do nothing when cassettes are inserted
    Note: Autorun is only supported for cassette images in the CAS format.

    autorunlaserdisc

    Switches the "auto-run laserdisc" feature on or off. When it's enabled, openMSX will try to type the proper loading instruction when a laserdisc is inserted.

    usage:
    set autorunlaserdisc Shows the current setting
    set autorunlaserdisc on Try to load Laserdiscs automatically
    set autorunlaserdisc off Do nothing when Laserdiscs are inserted

    auto_enable_reverse

    Using the reverse feature has a small memory and performance cost. Therefore it has to be enabled before it can be used. This setting controls whether the reverse feature should automatically be activated when openMSX starts. On desktop computers this generally won't be a problem. But for small handheld devices it can be.

    usage:
    set auto_enable_reverse Shows the current setting
    set auto_enable_reverse off Don't automatically enable the reverse feature
    set auto_enable_reverse on Enable the reverse feature when openMSX starts
    set auto_enable_reverse gui Enable the reverse feature and the reverse bar when openMSX starts

    auto_save_replay

    Enable this setting to make automatic backups of your current replay. The replay is saved to the filename specified in the auto_save_replay_filename setting (default: "auto_save") at an interval as specified by the auto_save_replay_interval setting (default: 30 seconds). The interval is in real clock time, not in MSX time.

    blur

    Sets the amount of horizontal blur effect. A value of 0 turns off blur, while 100 selects maximum blur.

    usage:
    set blur Shows the current setting
    set blur <value> Change the value
    Note: Only some scale algorithms apply horizontal blur; the default algorithm "simple" does.

    bootsector

    Sets the boot sector type for DirAsDSK. Default: DOS2. Only relevant on turboR, because it boots differently depending on the type of boot sector on the disk in drive A.

    usage:
    set bootsector Shows the current setting
    set bootsector DOS1 Use a DOS1 boot sector

    brightness

    Controls the brightness of the video output. Can be between -100 and 100. Lower values are darker, higher values are brighter. The default is 0, which is neutral. This setting shifts the brightness of all colors, including black and white, while the gamma setting changes the relative brightness of colors but does not change black and white.

    The section about the noise setting describes a typical way of using brightness.

    usage:
    set brightness Shows the current setting
    set brightness 5 Make the video output a bit brighter than default

    cmdtiming

    Controls VDP command execution timing.

    This is useful for debugging and for speeding up games where the command engine performance is a bottleneck.

    usage:
    set cmdtiming Shows the current setting
    set cmdtiming broken Make VDP commands finish instantly
    set cmdtiming real Make VDP commands take a realistic amount of time
    Note: When set to broken the emulated MSX acts different from a real MSX. This might cause some software to fail.

    color_matrix

    This setting represents a 3×3 matrix that is used to transform MSX RGB colors to host RGB colors. This setting can be used to generate all kind of color schemes, see scripts/monitor.tcl for examples.

    To get the following color transformation:

            | a b c |   | Rm |   | Rh |
            | d e f | × | Gm | = | Gh |
            | g h i |   | Bm |   | Bh |
      

    Use this command:

            set color_matrix { { a b c } { d e f } { g h i } }
      
    usage:
    set color_matrix Shows the current value
    set color_matrix { { 1 0 0 } { 0 1 0 } { 0 0 1 } } This is the default (no color transformation)
    set color_matrix { { .33 .33 .33 } { .33 .33 .33 } { .33 .33 .33 } } Transform to grey scale
    Note: It is often more convenient to use the monitor_type command.

    console

    Turns the openMSX on-screen console on or off.

    usage:
    set console Shows the current setting
    set console on Turns the console on
    set console off Turns the console off

    consolebackground

    Change the console background image. Only images in the PNG format are supported. Background images may also have an alpha channel (amount of transparency per pixel).

    usage:
    set consolebackground Shows the current setting
    set consolebackground <image> Sets a new background image

    consolecolumns

    Change the width of the console measured by the number of columns.

    usage:
    set consolecolumns Shows the current setting
    set consolecolumns <value> Set the specified width

    consolefont

    Change the console font. This should be the filename of a True Type Font. The default value is skins/VeraMono.ttf.gz.

    Font files of other types than True Type Fonts (TTF) are not supported.

    usage:
    set consolefont Shows the current setting
    set consolefont <myfont.ttf> Sets a new font

    consolefontsize

    Set the size for the console font. The default value is 12.

    usage:
    set consolefontsize Shows the current setting
    set consolefontsize 16 Set a bigger than default font size

    console_history_size

    Determines how many commands are saved into the console history.

    usage:
    set console_history_size Shows the current setting
    set console_history_size 5000 Keep a maximum of 5000 commands in the console history

    consoleplacement

    Changes the position of the console on the emulator screen.

    usage:
    set consoleplacement Shows the current setting
    set consoleplacement <place> Moves the console to the specified location; <place> can be topleft, top, topright, left, center, right, bottomleft, bottom or bottomright.

    consolerows

    Change the height of the console measured by the number of rows.

    usage:
    set consolerows Shows the current setting
    set consolerows <value> Set the specified width

    console_remove_doubles

    Determines whether the console history remembers two identical subsequent commands.

    usage:
    set console_remove_doubles Shows the current setting
    set console_remove_doubles on Remove (do not remember) the last command if it's the same as the previous (default)
    set console_remove_doubles off Allow double subsequent command entries in the console history

    contrast

    Controls the contrast of the video output. Can be between -100 and 100. Lower values are less contrast, higher values are more contrast. The default is 0, which is neutral.

    The section about the noise setting describes a typical way of using contrast.

    usage:
    set contrast Shows the current setting
    set contrast -5 Reduce the video contrast a bit

    cputrace

    Enable/disable CPU instruction tracing. When enabled, the state of the CPU (Z80/R800) is printed on stdout after every instruction. This creates a lot of output and slows down emulation considerably, but it can be very useful for debugging.

    usage:
    set cputrace Shows the current setting
    set cputrace on Enables CPU tracing
    set cputrace off Disables CPU tracing

    debugoutput

    Selects the file to where the output from the debug device goes.

    The User's Manual describes the debug device in more detail.

    usage:
    set debugoutput Shows the current output file name
    set debugoutput stdout Writes debug output to openMSX's standard output stream
    set debugoutput stderr Writes debug output to openMSX's standard error stream
    set debugoutput <output file> Writes debug output to the specified file
    Note: This setting only exists if the debugdevice extension is present in the current MSX machine.

    default_machine

    Selects the default MSX model. openMSX uses this machine when it is started without the -machine option. This is a typical setting that should be saved, see also save_settings.

    usage:
    set default_machine Shows current setting
    set default_machine Panasonic_FS-A1GT Use the turboR GT the next time openMSX is started

    DirAsDSKmode

    Determine the behavior of the DirAsDSK when inserting a directory to be used as diskimage.

    The possible values are read_only and full. The default mode is full.

    read_only The MSX can not write to the virtual disk.
    Changes on the host-OS are still reflected on the virtual disk, however.
    full All changes are performed both ways, no restrictions apply.
    usage:
    set DirAsDSKmode Shows the current setting
    set DirAsDSKmode read_only Disk image will be read only
    Note: this setting is only used when the directory is inserted, it is not possible to change the behaviour of the current virtual disk by altering the setting. The new setting will become effective after the current virtual disk has been ejected.

    deflicker

    Turns deflicker on/off. deflicker is a filter which tries to detect pixels that alternate each frame between two different color values and replaces those alternations with the average color. It gives a very nice result for software (mostly demos) that use this technique to get the optical illusion of more colors than are actually supported by the hardware. It also works well in games with flickering sprites on a static background (like Maze of Galious). This setting is disabled by default because there aren't that many situations were it really improves video quality and it does have a performance cost.

    usage:
    set deflicker Shows the current setting
    set deflicker on Turns deflicker on
    set deflicker off Turns deflicker off

    deinterlace

    Turns deinterlacing on/off. Deinterlace is a filter which combines the even and odd field of interlaced video into a single frame which has double vertical resolution. It results in a sharp and stable image, but can show artifacts on fast animations.

    usage:
    set deinterlace Shows the current setting
    set deinterlace on Turns deinterlacing on
    set deinterlace off Turns deinterlacing off
    Note: Deinterlacing is only supported for scale_factors bigger or equal to 2.

    disablesprites

    Can be used to disable sprite rendering. Only the rendering itself is disabled, all other MSX behaviour (like sprite collision detection) stays the same.

    usage:
    set disablesprites Shows the current setting
    set disablesprites on Disable sprite rendering
    set disablesprites off Enable sprite rendering (the default)

    display_deform

    Select display deformation effect. This effect is only supported in the SDLGL-PP renderer.

    usage:
    set display_deform Shows the current setting
    set display_deform normal Turns off display deform
    set display_deform 3d Deforms the image in 3D, to look like a CRT (like JEmu2)
    Note: In the past there was also a 'horizontal_stretch' mode. This is now replaced by the horizontal_stretch setting.

    di_halt_callback

    Selects the Tcl procedure to be called when the running MSX software has executed a HALT instruction while the interrupts are disabled (DI).

    The default openmsx startup scripts initialize this setting with a proc that prints a warning message.

    usage:
    set di_halt_callback Shows the current setting
    set di_halt_callback my_callback_proc Sets a new callback proc

    enable_session_management

    Controls session management. When enabled, openMSX will store the state of all machines when you exit openMSX and restore that state again when starting it up next time. Note that the reverse history is not saved.

    Sessions can also be saved manually with the command save_session, and explicitly loaded with load_session. A list of saved sessions can be retrieved with list_sessions.

    frequency

    Sets the sound mixer frequency. Sound hardware and sound APIs typically support a limited set of frequencies, such as 11025 Hz, 22050 Hz, 44100 Hz and 48000 Hz.

    usage:
    set frequency Shows the current setting
    set frequency 44100 Use 44.1 kHz mixing frequency (CD quality)

    firmwareswitch

    Some machines (e.g. turboR) have a switch on the front (or on the back) that controls if the machine should boot 'normally' or start the built-in software, also called firmware. This setting controls the position of that switch.

    usage:
    set firmwareswitch Shows the current setting
    set firmwareswitch on Boot into the internal software
    set firmwareswitch off Boot into MSX-BASIC or on-disk software

    fullscreen

    Switch to/from fullscreen mode.

    usage:
    set fullscreen Shows the current setting
    set fullscreen on Switch to fullscreen mode
    set fullscreen off Switch to windowed mode

    fullspeedwhenloading

    When enabled, openMSX will try to detect when the MSX is loading from diskette, cassette or laserdisc. During loading openMSX will run at full speed (throttle off). This can be convenient if you're not interested in the realistic but slow loading times on MSX. Default is off, because it is not according to the behaviour of a real MSX.

    Unlike the fast loading features in for example fMSX, fullspeedwhenloading does not intercept BIOS calls. Instead, it speeds up the emulation of the entire MSX, including all hardware devices.

    usage:
    set fullspeedwhenloading Shows the current setting
    set fullspeedwhenloading on Load as fast as possible
    set fullspeedwhenloading off Load at the same speed as a real MSX

    gamma

    Sets the amount of gamma correction. A value of 1.0 will turn off gamma correction. Lower values will result in a darker image, higher values in a brighter image.

    If you want to get a realistic picture, set the openMSX gamma correction to PC gamma / MSX gamma. TVs use a standardised gamma of 2.5, let's take that as the value of MSX gamma. You can measure the gamma of your PC screen with a simple test such as the Gamma Measurement Image in Robert W. Berger's "An Explanation of Monitor Gamma". If your PC gamma would be for example 2.0, the most realistic value for gamma correction would be 2.0 / 2.5 = 0.8.

    Alternatively, you can just try a few values and see what you like.

    usage:
    set gamma Shows the current value
    set gamma <num> Sets a new gamma correction amount

    glow

    Sets the amount of afterglow effect: 0 is off and 100 is a very heavy afterglow.

    usage:
    set glow Shows the current setting
    set glow <value> Change the amount of afterglow
    Note: Not all renderers support this.

    grabinput

    Controls whether openMSX grabs all input events or not. When this setting is turned on, all input events are directly passed to openMSX. The mouse pointer can't leave the openMSX window and the window manager won't be able to react to keyboard shortcuts.

    You can turn this setting on when you want to use mouse-controlled MSX software while openMSX is in windowed mode. It is best turned off in all other cases. See also escape_grab.

    usage:
    set grabinput Shows the current setting
    set grabinput on Starts grabbing all input events
    set grabinput off Stops grabbing all input events

    horizontal_stretch

    Sets the amount of horizontal stretch, thus also the aspect ratio of the screen. More specifically, a setting of n means stretch the center n MSX pixels to the full width of the host output window (at scale_factor 1).

    usage:
    set horizontal_stretch Shows the current setting
    set horizontal_stretch <value> Change the amount of horizontal stretch
    examples of typical useful values:
    set horizontal_stretch 320 (no horizontal stretch)
    set horizontal_stretch 272 (approach real aspect ratio of MSX screen)
    set horizontal_stretch 280 (default: show all generated border pixels, so that all border demo effects are still visible)
    set horizontal_stretch 256 (borders are not visible at all; doesn't work well in combination with set-adjust)
    Note: when using the SDL renderer, this setting may cause a lot more CPU usage (e.g. on a Dingoo) when not using the value 320.

    inputdelay

    Input events for the MSX machine are delayed by this amount. Increase this value when the MSX machine misses keyboard presses when you type very fast. Decrease this value to reduce the latency between pressing a key on the host machine and seeing it being typed in the MSX machine.

    usage:
    set inputdelay Shows the current value
    set inputdelay <time> Sets the input delay to the specified number of seconds
    Note: The default value of 0.0 seconds (no extra delay) should almost always be OK. It only makes sense to increase this setting if you have a slow host machine and you're typing text very fast and the emulated MSX machine misses (some of) the keys you typed.

    interleave_black_frame

    Insert a black frame in between each normal MSX frame. Useful on (100Hz+) lightboost enabled monitors to reduce motion blur and double frame artifacts.

    Make sure you configure your monitor to use a refresh rate of 100Hz (for a PAL MSX machine) or to 120Hz (for a NTSC machine). The brightness will decrease, so adjust the gamma, brightness and contrast settings to compensate.

    usage:
    set interleave_black_frame Shows the current value
    set interleave_black_frame true Enable this feature.

    invalid_psg_directions_callback

    Selects the Tcl procedure to be called when the running MSX software has selected invalid PSG port directions (port A should always be set as input).

    The default openmsx startup scripts initialize this setting with a proc that prints a warning message just once. Though if you're a developer you may want to change this to always print the warning or automatically break emulation when this happens so you can debug the problem.

    usage:
    set invalid_psg_directions_callback Shows the current setting
    set invalid_psg_directions_callback my_callback_proc Sets a new callback proc

    joystick<n>_config

    This setting configures how the buttons/axis of the host joystick(s) are mapped to the corresponding inputs of the emulated MSX joystick(s). For many joysticks the initial value of this setting provides an acceptable default mapping. But depending on your joystick type and taste you may want to tweak it.

    The value of this setting is a Tcl dictionary. This means it's a list of key/value pairs where each even element is a key and each odd element is the corresponding value. The keys in this dictionary represent the 6 possible MSX joystick inputs. Possible key values are LEFT, RIGHT, UP, DOWN, A and B. The corresponding dictionary-values are lists of host joystick inputs. Possible elements for these lists are button<n>, +axis<n>, -axis<n> and {L,R,U,D}_hat<n>.

    Let's explain this with an example. The following is the default value for this setting:
     LEFT -axis0 RIGHT +axis0 UP -axis1 DOWN +axis1 A {button0 button2} B {button1 button3}
    Axis 0 is usually the primary X-axis of the host joystick. When that axis is moved towards negative values the LEFT input switch on the emulated joystick is activated. When it is moved towards positive values the RIGHT MSX input switch is activated. Similarly host axis1 is mapped to the UP and DOWN MSX inputs. The (default) configuration for the buttons is slightly more complicated. Here both host button 0 and 2 will activate MSX button A, and both button 1 and 3 will activate MSX button B. (This example shows a host joystick with 4 buttons, but in general (by default) all even host buttons will active MSX button A and all odd host buttons will active MSX button B).

    There are no restrictions on the possible mappings. For example it is allowed to map host axis/buttons to MSX buttons/axis or vice-versa. This allows to for example map a host joypad (which acts like 4 buttons) to the MSX directional inputs. (Technically speaking the MSX axis inputs LEFT, RIGHT, UP and DOWN are just 4 input switches, just like the buttons A and B are just 2 input switches). It's also allowed to map the same host action to multiple MSX inputs. This allows to for example make one specific host button press both MSX buttons simultaneously (e.g. to have a 'crouch button' in Metal Gear).

    It is possible to set this setting directly using the set command, but often using the Tcl dict command is more convenient. See below for some examples.

    usage:
    set joystick1_config Shows the current configuration of the first joystick
    dict set joystick1_config A button5 (Re)map MSX button A to (only) host button 5. Leave the mapping of the other MSX inputs unchanged.
    dict set joystick1_config A {button0 button2}
    dict set joystick1_config B {button1 button2}
    Map button 0 to A, 1 to B and 2 to A+B. So pressing host button 2 will press both MSX buttons.
    dict set joystick1_config LEFT {-axis0 -axis2 button10}
    dict set joystick1_config RIGHT {+axis0 +axis2 button11}
    dict set joystick1_config UP {-axis1 -axis3 button12}
    dict set joystick1_config DOWN {+axis1 +axis3 button13}
    Map 2 pairs of axis and 1 keypad (4 buttons) to the MSX direction inputs.
    dict lappend joystick1_config LEFT L_hat0
    dict lappend joystick1_config RIGHT R_hat0
    dict lappend joystick1_config UP U_hat0
    dict lappend joystick1_config DOWN D_hat0
    Additionally map hat0 to the 4 MSX directions.

    joystick<n>_deadzone

    This setting configures how big the dead center zone of an (analogue) joystick is. This is expressed as a percentage: 0 means no dead zone, 100 means everything falls inside the dead zone.

    usage:
    set joystick1_deadzone Shows the current size of the dead zone of the first joystick
    set joystick1_deadzone 25 Set the size of the dead zone to ¼ of the total range

    kbd_auto_toggle_code_kana_lock

    Switches the "Automatically toggle the CODE/KANA lock" feature on or off. When it's on, openMSX will automatically toggle the CODE/KANA lock when a user enters a character for which the CODE/KANA lock state must be changed.

    usage:
    set kbd_auto_toggle_code_kana_lock Shows the current setting
    set kbd_auto_toggle_code_kana_lock on Automatically toggle the CODE/KANA lock when required
    set kbd_auto_toggle_code_kana_lock off Only toggle CODE/KANA lock status when user presses the CODE/KANA lock key
    Note: This only works on MSX models for which the CODE/KANA key locks (e.g. Japanese MSX models and the Philips VG8010). On other models, this setting is ignored.

    kbd_code_kana_host_key

    Host key that maps to the MSX CODE/KANA key. By default right-ALT (RALT) key.

    It is especially useful for people with AZERTY host keyboard, on which the RALT key has a special function; on azerty keyboards it is called the ALT-GR key and not the right-ALT key and it's used to enter some special characters (some keys are tagged with 3 characters; normal, key+SHIFT, key+ALT-GR).

    It is also useful for people with a Japanese (jp106) keyboard; they can map the HENKAN_MODE key (which is similar to the KANA Lock on Japanese MSX models) to the CODE/KANA key.

    usage:
    set kbd_code_kana_host_key Shows the current setting
    set kbd_code_kana_host_key MENU Binds the MENU key on the host keyboard to the MSX CODE/KANA key
    set kbd_code_kana_host_key HENKAN_MODE Binds the HENKAN_MODE key on the host keyboard to the MSX CODE/KANA key

    kbd_deadkey1_host_key

    Host key that maps to the (1st) dead key. By default right-CTRL (RCTRL) key.

    Some MSX models have one dead key that can be used to enter accented charachters. For example the MSX models sold in the Netherlands have a dead key that has following four accents printed on it: ` ´ ^ ¨. On the other hand, the Brazilian Gradiente Expert XP-800 has following four accents on its dead key: ` ´ ^ ~.

    There are also some MSX models with multiple dead keys like for example the Brazilian Gradiente Expert Plus, which has two dead keys and the different Sharp Hotbit models that have three dead keys. On such machines, this setting is for the first dead key which can be used to enter following two accents: ´ `.

    In order to enter an accented character on the MSX, you first have to press and release the dead key, optionally together with SHIFT, CODE or CODE+SHIFT and then the correct character. The combination with CODE or CODE+SHIFT is only relevant for the MSX models with a single dead key that can be used to enter four different accents.

    Following table shows for example how to enter respectively ù, ú, û or ü on the MSX models sold in the Netherlands:

    Key pressesCharachter
    DEAD_KEY1 followed by uù
    DEAD_KEY1+SHIFT followed by uú
    DEAD_KEY1+CODE followed by uû
    DEAD_KEY1+SHIFT+CODE followed by uü

    In order to use the dead key in openMSX, you must map an appropriate host key to the DEAD_KEY1 of the MSX and another one to the CODE key of the MSX with respectively this kbd_deadkey1_host_key setting and the above documented kbd_code_kana_host_key setting.

    Note that especially the last key combination (DEAD_KEY1+SHIFT+CODE) can be impossible to enter on some host systems; depending on the host operating system, keyboard type and keyboard driver it may be impossible for the host system to send a combination of three keys at once to an application like openMSX. Unfortunately openMSX or its developers can't do anything about that.

    usage:
    set kbd_deadkey1_host_key Shows the current setting
    set kbd_deadkey1_host_key PAGEUP Binds the PAGEUP key on the host keyboard to the 1st dead key

    Note that to use for example PAGEUP as 1st dead key you will have to unbind it from the go_back_one_step command in the console; by default openMSX has bound the PAGEUP key to the go_back_one_step command and such a binding takes precedence over keyboard mappings, so if you want to use PAGEUP as the 1st dead key you will have to enter following additional command in the console: unbind PAGEUP.

    kbd_deadkey2_host_key

    Host key that maps to the 2nd dead key. By default Page Up (PAGEUP) key.

    This is only applicable to MSX models that have at least two dead keys, like the Brazilian Hotbit models or the Brazilian Gradiente Expert Plus or other Gradiente models with Gradiente basic version 1.1.

    On the Hotbit models, the second dead key can be used to enter accent ¨ while on the Gradiente 1.1 models, the second dead key can be used to enter following accents: ~ ^.

    It can be used in the same manner as the first dead key, explained in previous section.

    usage:
    set kbd_deadkey2_host_key Shows the current setting
    set kbd_deadkey2_host_key PAGEDOWN Binds the PAGEDOWN key on the host keyboard to the 2nd dead key

    Note that to use for example PAGEUP or PAGEDOWN as a dead key you will have to unbind them from the default functions in openMSX using the console; by default openMSX has bound the PAGEUP key to the go_back_one_step command and the PAGEDOWN key to the go_forward_one_step command. Such a binding takes precedence over keyboard mappings, so if you want to use PAGEUP or PAGEDOWN as the second dead key you will have to enter following additional commands in the console: unbind PAGEUP or unbind PAGEDOWN.

    kbd_deadkey3_host_key

    Host key that maps to the 3rd dead key. By default Page Down (PAGEDOWN) key.

    This is only applicable to MSX models with at least three dead keys, like the Sharp Hotbit models.

    On the Hotbit models, the third dead key can be used to enter following two accents: ~ ^.

    usage:
    set kbd_deadkey3_host_key Shows the current setting
    set kbd_deadkey3_host_key RCTRL Binds the Right CTRL (RCTRL) key on the host keyboard to the 3rd dead key

    Note that to use for example PAGEDOWN (default setting!) as the 3rd dead key you will have to unbind it from the go_forward_one_step command in openMSX using the console; by default openMSX has bound the PAGEUP key to the go_back_one_step command. It is a very useful setting for many openMSX users when playing games but unfortunately it conflicts with the default set-up for the third dead key. Such a command binding takes precedence over keyboard mappings, so if you want to use PAGEDOWN as the third dead key you will have to enter following additional command in the console: unbind PAGEDOWN.

    kbd_mapping_mode

    The keyboard driver can work in two mapping modes; KEY mapping and CHARACTER mapping.

    KEY mapping:
    A key pressed on the host keyboard maps to one specific key on the MSX keyboard. This mode is convenient when the host keyboard and the MSX keyboard have the same layout or when working with a program that directly reads the keyboard matrix and forces the user to enter a certain key combination that can not be auto-generated in the CHARACTER mapping mode.
    CHARACTER mapping:
    A character entered by the user is mapped to the correct key combination on the MSX to enter that character. For example, when the user enters an ! character and openMSX is emulating an 'international' MSX model, the keyboard driver will press SHIFT and 1 on the MSX keyboard. This will be done regardless of the key or keys that the user pressed on the host keyboard to enter the ! character.
    This is especially useful when the user has an AZERTY host keyboard and is working on a QWERTY style MSX or when he has a US-QWERTY keyboard and is working on a Japanese MSX.
    Note that special keys (like CAPSLOCK) are mapped directly, just like in the KEY mapping mode.
    usage:
    set kbd_mapping_mode Shows the current mode
    set kbd_mapping_mode CHARACTER Set the CHARACTER mapping mode
    set kbd_mapping_mode KEY Set the KEY mapping mode

    kbd_numkeypad_always_enabled

    Some real MSX computers do not have a numeric keypad. openMSX will ignore key presses on the host numeric keypad when emulating such an MSX model. With this parameter, you can indicate that even on such MSX models, presses on the host numeric keypad must be mapped to the MSX numeric keypad. So, you can override accurate behaviour with it, which is the reason that by default, this setting is set to 'off'.

    usage:
    set kbd_numkeypad_always_enabled Shows the current setting
    set kbd_numkeypad_always_enabled on Enables numeric keypad, even if the emulated MSX does not have one

    kbd_numkeypad_enter_key

    There is a subtle difference between numeric keypad of MSX computers and of most host computers; the MSX computers have a '.' and a ',' on the numeric keypad. On the other hand, the host computers have a '.' and an 'ENTER' key on the keypad.

    In some respect it is logical that the 'ENTER' key on the host numeric keypad is mapped to the 'normal' MSX 'ENTER' key. On the other hand, that would make it impossible to enter the ',' on the MSX numeric keypad. Therefore, the user can choose whether the host numeric keypad ENTER key should be mapped to the MSX numeric keypad ',' (which is the default) or to the main 'ENTER' key.

    usage:
    set kbd_numkeypad_enter_key Shows the current value
    set kbd_numkeypad_enter_key ENTER Maps the keypad enter key to the main 'ENTER' key, instead of the comma key on the MSX keypad

    kbd_trace_key_presses

    Log SDL key code, SDL modifiers and Unicode value for each key that gets pressed on the host keyboard on stderr. Also show Unicode value and corresponding MSX key-presses for characters that get 'pasted' into the MSX by the console type command. This setting is especially useful when defining unicode keymap files, so that you can find out the unicode values belonging to certain keys/characters.

    usage:
    set kbd_trace_key_presses Shows the current setting
    set kbd_trace_key_presses on Turn logging of key presses on

    keyjoystick<n>.<button>

    Configure the keys of the keyjoysticks. It's likely this will change in the future.

    Valid values for <n>: 1, 2.

    Valid values for <button>: up, down, left, right, triga, trigb.

    usage:
    set keyjoystick1.up W
    set keyjoystick1.down S
    set keyjoystick1.left A
    set keyjoystick1.right D
    set keyjoystick1.triga SPACE

    led_<name>

    These are read-only settings. Their value reflects the current status of the corresponding LED on the emulated MSX machine. The currently supported LED names are: power, caps, kana, pause, turbo and FDD.

    As for any setting you can use the native trace Tcl command to trigger a callback when the value of these settings changes. (In fact this possibility was the main motivation to make these read-only settings instead of topics of the machine_info command.)

    limitsprites

    Controls whether the VDP has a limit on the number of sprites it can display per line. The default is on, because the real VDP has such a limit. You can turn off the limit to reduce sprite flashing in games such as Aleste. Note that some games (Penguin Adventure, among others) make use of this limitation, so they will display incorrectly if the limit is turned off.

    The 5th/9th sprite status flag of the VDP is not influenced by the limitsprites setting: the flag always takes the limit into account.

    usage:
    set limitsprites Shows the current value
    set limitsprites on Limits number of sprites per line
    set limitsprites off Turns off number of sprites per line limit

    master_volume

    Controls the overall openMSX volume. The volume of individual sound devices can be controlled with the <soundchip>_volume settings.

    usage:
    set master_volume Shows current setting
    set master_volume 50 Sets master volume to 50%

    maxframeskip

    Sets the maximum amount of frames to skip: show a frame and then skip at most <number> frames. So 0 means show everything (no frame skipping), 1 means show at least every second frame etc.

    Frame skipping is done on demand, as a way to keep the flow of time for the emulated MSX in sync with the flow of real time. You can set limits on the amount of frame skipping with the minframeskip and maxframeskip setting.

    In a situation where the number of consecutive frames specified by maxframeskip has been skipped, openMSX will display the next frame, even if that means emulation will start lagging behind real time.

    When throttle is off, the number of skipped frames will be equal to maxframeskip.

    usage:
    set maxframeskip Shows the current setting
    set maxframeskip <number> Sets the maximum number of consecutive frame skips

    midi-in-readfilename

    Sets the file from which the MIDI input is read. By default, it is set to /dev/midi when available.

    usage:
    set midi-in-readfilename Shows the current setting
    set midi-in-readfilename mymidilog.dat Read MIDI events from "mymidilog.dat"

    midi-out-logfilename

    Sets the file to which the MIDI output is logged. By default, it logs to /dev/midi when available.

    usage:
    set midi-out-logfilename Shows the current setting
    set midi-out-logfilename mymidilog.dat Log MIDI events to "mymidilog.dat"

    minframeskip

    Sets the minimum amount of frames to skip: show a frame and then skip at least <number> frames. So 0 means no forced frame skipping, 1 means skip at least every second frame etc.

    Frame skipping is done on demand, as a way to keep the flow of time for the emulated MSX in sync with the flow of real time. You can set limits on the amount of frame skipping with the minframeskip and maxframeskip setting.

    The minframeskip setting can be useful if you want to ease the burden on your PC processor, for example for longer battery life on a laptop. It can also be useful if your PC is consistently too slow to run without frame skipping: in such cases video might be smoother with a low but constant frame rate than with a fluctuating frame rate.

    usage:
    set minframeskip Shows the current setting
    set minframeskip <number> Sets the number of frame skips

    mode

    Sets the active mode. A mode is a set of settings (mostly key bindings, but also OSD widgets that are activated) that are most suitable for a certain task. Currently only mode 'normal' and 'tas' exist.

    usage:
    set mode Shows the current setting
    set mode tas Change mode to TAS mode
    set mode normal Set mode back to normal, which is the default (all purpose) mode

    TAS mode

    So far, the only special mode is the TAS mode, which is made for doing Tool Assisted Speedruns, with TAS widgets and easier ways to save replays. It is still experimental, but already very useful for doing a TAS. This mode enables the following widgets:

    • frame counter (can also be toggled with toggle_frame_counter), which shows the VDP (not V9990) frame number on screen
    • cursors (can also be toggled with toggle_cursors), shows which keys (important for games) are pressed

    It also (re)enables the reverse bar widget.

    The mode configures the following key bindings, overriding any existing key bindings (note: Mac key bindings are not proper yet...):

    keys (PC) keys (Mac) function
    F6 (F6) Load replay from current slot
    F7 (F7) Open slot select menu
    F8 (F8) Save replay to current slot
    End (End) Advance one frame (advance_frame)
    ScrollLock(ScrollLock)Reverse one frame (reverse_frame)

    Note that this mode may change in future releases!

    mute

    Mute/unmute all sound output.

    usage:
    set mute Shows the current setting
    set mute on Mute sound
    set mute off Unmute sound

    noise

    Controls the amount of Gaussian noise that is added to the video output. A small amount of noise can give a more authentic look to the video output on TFTs. Values can be between 0 and 100, where 0 is no noise and 100 is lots of noise.

    This setting is best combined with brightness and contrast: noise creates small random fluctuations in the brightness of pixels. When noise is applied to pure black, it is not possible to make it any darker, so half of the time the noise is ineffective. The same happens with pure white. By setting the brightness slightly above 0 and contrast slightly below 0, you will get a better looking noise effect.

    usage:
    set noise Shows the current setting
    set noise 7 Add a moderate amount of noise

    pause

    Pauses the emulation.

    usage:
    set pause Shows the current setting
    set pause on Pauses emulation
    set pause off Unpauses emulation
    Note: Some video settings cannot be applied to an already rendered frame and will therefore not take effect until openMSX is unpaused.

    pause_on_lost_focus

    When this setting is enabled, the emulation will be paused when the openMSX window loses focus.

    usage:
    set pause_on_lost_focus Shows the current setting
    set pause_on_lost_focus on Emulation will be paused when the openMSX window loses focus
    set pause_on_lost_focus off Emulation will continue when the openMSX window loses focus (default)

    pointer_hide_delay

    The amount of seconds before the mouse pointer will be automatically hidden after it got shown due to mouse activity. A negative amount means that it will never be hidden, an amount of 0 means that it will be always hidden. By default the pointer is hidden 1 second after the last mouse activity.

    usage:
    set pointer_hide_delay Shows the current setting
    set pointer_hide_delay -1 Never hide the mouse pointer
    set pointer_hide_delay 0 Always hide the mouse pointer
    set pointer_hide_delay 3.4 Hide the mouse pointer after 3.4 seconds of inactivity

    power

    Turn the power of the emulated MSX machine on or off.

    usage:
    set power Shows the current setting
    set power on Turns the MSX machine on (the default)
    set power off Turns the MSX machine off

    printerlogfilename

    Sets the file to which the printer logger writes.

    usage:
    set printerlogfilename Shows the current setting
    set printerlogfilename myprinterlog.txt Log to "myprinterlog.txt"

    print-resolution

    Sets the resolution (in dpi) for the emulated dot-matrix printer.

    The emulated printer 'prints' pages as PNG files. This settings determines the resolution of those images.

    usage:
    set print-resolution Shows the current setting
    set print-resolution 600 Sets resolution to 600 dpi

    r800_freq / r800_freq_locked

    These two settings control the R800 clock frequency. See z80_freq / z80_freq_locked for details.

    renderer

    Switch to a different video renderer. See the User's Manual for a description of the available renderers.

    usage:
    set renderer Shows the current setting
    set renderer <name> Switches to the given renderer

    renshaturbo

    Sets the speed of the built-in auto fire on some Japanese MSX models, for example the turboR machines. A value of 0 turns off auto fire, while 100 selects the most rapid auto fire.

    usage:
    set renshaturbo Shows the current renshaturbo value
    set renshaturbo <num> Sets speed to value <num>
    Note: This setting is only available if the current MSX machine has hardware Rensha Turbo support.

    resampler

    Sets the method to resample the sound of sound chips from their native frequency to the desired output frequency.

    usage:
    set resampler Shows the currently active resampler
    set resampler fast Sets a fast, but low quality resampler. This uses simple linear interpolation, which can result in very audible aliasing effects in some cases. Although this is a relatively low quality algorithm, it's the algorithm that was used in pre-0.6.3 openMSX versions (and most other MSX emulators).
    set resampler blip Sets the Blip_Buffer based resampler, which has the best quality per CPU usage ratio (this is the default value).
    set resampler hq Sets the highest quality resampler, but it also takes the most CPU time. It's based on the libsamplerate algorithm.

    rs232-inputfilename

    Sets the file from which the RS232-tester reads data. Note that the rs232-tester has to be plugged in the msx-rs232 connector for this to become useful. When plugging the tester, this setting needs to point to a valid file.

    usage:
    set rs232-inputfilename Shows the current setting
    set rs232-inputfilename myrs232input.txt Reads from "myrs232input.txt"

    rs232-outputfilename

    Sets the file to which the RS232-tester writes the data. Note that the rs232-tester has to be plugged in the msx-rs232 connector for this to become useful. When plugging the tester, this setting needs to point to a valid file.

    usage:
    set rs232-outputfilename Shows the current setting
    set rs232-outputfilename myrs232output.txt Write to "myrs232output.txt"

    rtcmode

    Sets the Real Time Clock mode. Can be either RealTime or EmuTime.

    In RealTime mode the MSX clock is always synchronized with the host clock, even when for example emulation is paused for a while or when emulation is run at 200% of real speed.

    In EmuTime mode the time is only synchronized with the host clock when openMSX starts. From then on the clock ticks at the same pace as the emulated machine. So when emulation is paused, the clock is paused as well. If emulation is run at 200% speed, the clock also ticks twice as fast.

    In EmuTime mode it's not possible for a MSX program to detect whether it's running on a real or on an emulated machine. That's why this is the default mode. On the other hand the RealTime mode might be better if for example you care that timestamps of files written by the emulated MSX machine are in sync with the host machine time.

    usage:
    set rtcmode Shows the current mode
    set rtcmode EmuTime Set EmuTime mode (the default)
    set rtcmode RealTime Set RealTime mode

    samples

    Sets the size of the sound mixer buffer. Higher values help against buffer underruns (hickups), but increase the latency of the sound output.

    usage:
    set samples Shows the current setting
    set samples 1024 Use a mixing buffer of 1024 samples

    save_settings_on_exit

    Automatically save the current settings when openMSX exits: execute a save_settings command on exit.

    usage:
    set save_settings_on_exit Show current setting
    set save_settings_on_exit on Enable auto save
    set save_settings_on_exit off Disable auto save

    scale_algorithm

    Selects the algorithm used to transform MSX pixels to host pixels. The User's Manual contains more information about scalers.

    usage:
    set scale_algorithm Shows the current setting
    set scale_algorithm simple Selects the default scale algorithm
    set scale_algorithm hq Selects the HQ2x/3x/4x scale algorithm
    Note: Not all renderers support all scale algorithms.

    scale_factor

    Selects the scale factor. Scale factor <n> means the typical MSX pixel (MSX resolution 256×212) is mapped on <n> by <n> host pixels. For the moment the possible values are 1 to 4. In the future we may support a wider range or even non-integer values. The User's Manual contains more information about scalers.

    usage:
    set scale_factor Shows the current setting
    set scale_factor <n> Sets a new scale factor
    Note: Not all renderers support all scale factors.

    scanline

    Sets the amount of scanline effect.

    usage:
    set scanline Shows the current setting
    set scanline <value> Changes the value
    Note: Some scalers will not render scanlines at all.

    sound_driver

    Select the sound output driver. The list of available sound drivers is platform specific.

    usage:
    set sound_driver sdl Selects the SDL sound driver (all platforms)
    set sound_driver directx Selects the DirectX sound driver (Windows only)

    speed

    Sets the emulation speed relative to the speed of a real MSX. Speed 100 means as fast as a real MSX, lower values are slower than real MSX, higher values are faster than real MSX.

    usage:
    set speed Shows current emulation speed
    set speed <num> Sets new emulation speed

    <soundchip>_balance

    Sets the balance (distribution over the left and right channel) for individual sound chips. It replaces the previously available <soundchip>_mode setting. The range is between -100 (totally left) and 100 (totally right).

    usage:
    set <soundchip>_balance Shows the current setting
    set <soundchip>_balance 0 Plays the output of this chip on both the left and right channel
    set <soundchip>_balance -100 Plays the output of this chip on only the left channel
    set <soundchip>_balance 75 Plays the output of this chip mostly on the right channel, but also a bit on the left channel
    examples:
    set PSG_balance
    set PSG_balance -100
    set FMPAC_balance 0

    <soundchip>_ch<channel>_record

    Sets the filename to which the sound of an individual channel of individual sound chips should be recorded. When this setting is not set, no recording takes place and recording starts as soon as the setting is set. Normally, you would probably prefer to use the record_channels command to set up channel recording instead of this low level setting.

    usage:
    set <soundchip>_ch<channel>_record Shows the current setting
    set <soundchip>_ch<channel>_record filename Starts recording the sound of the specified chip and channel to the file with name <filename>
    examples:
    set SCC_ch1_record
    set PSG_ch3_record /tmp/PSG_ch3.wav

    <soundchip>_ch<channel>_mute

    Use to mute a specific channel of an individual sound chip. Normally, you would probably prefer to use the mute_channels command to set up channel muting instead of this low level setting.

    usage:
    set <soundchip>_ch<channel>_mute Shows the current setting
    set <soundchip>_ch<channel>_mute on Mutes the sound of the specified channel of the specified chip
    set <soundchip>_ch<channel>_mute off Unmutes the sound of the specified channel of the specified chip
    examples:
    set SCC_ch1_mute
    set PSG_ch3_mute on
    set SCC_ch5_mute off

    <soundchip>_detune_frequency

    Sets the frequency of the detune (a random variation in a sound's frequency) effect. It makes a sound fatter and more natural, as if played by a human being.

    usage:
    set <soundchip>_detune_frequency Shows the current setting
    set <soundchip>_detune_frequency <num> Sets new detune frequency in Hz; 1 is minimum, 100 is maximum
    examples:
    set PSG_detune_frequency
    set PSG_detune_frequency 5 (default)
    Note: For now, this setting is only available for the PSG soundchip.
    Note: It is often more convenient to use the psg_profile command.

    <soundchip>_detune_percent

    Sets the strength of the detune effect. By default it is 0, which means the effect is switched off.

    usage:
    set <soundchip>_detune_percent Shows the current setting
    set <soundchip>_detune_percent <num> Sets new detune strength; 0 is minimum, 10 is maximum
    examples:
    set PSG_detune_percent
    set PSG_detune_percent 0 (switched off, default)
    set PSG_detune_percent 0.5 (recommended)
    Note: For now, this setting is only available for the PSG soundchip.
    Note: It is often more convenient to use the psg_profile command.

    <soundchip>_vibrato_frequency

    Sets the frequency of the vibrato (a periodic variation in a sound's frequency) effect.

    usage:
    set <soundchip>_vibrato_frequency Shows the current setting
    set <soundchip>_vibrato_frequency <num> Sets new vibrato frequency in Hz; 1 is minimum, 10 is maximum
    examples:
    set PSG_vibrato_frequency
    set PSG_vibrato_frequency 5 (default)
    Note: For now, this setting is only available for the PSG soundchip.
    Note: It is often more convenient to use the psg_profile command.

    <soundchip>_vibrato_percent

    Sets the strength of the vibrato effect. By default it is 0, which means the effect is switched off.

    usage:
    set <soundchip>_vibrato_percent Shows the current setting
    set <soundchip>_vibrato_percent <num> Sets new vibrato strength; 0 is minimum, 10 is maximum
    examples:
    set PSG_vibrato_percent
    set PSG_vibrato_percent 0 (switched off, default)
    set PSG_vibrato_percent 1 (recommended)
    Note: For now, this setting is only available for the PSG soundchip.
    Note: It is often more convenient to use the psg_profile command.

    <soundchip>_volume

    Sets the volume for individual sound chips. The overall volume is controlled by the master_volume setting.

    usage:
    set <soundchip>_volume Shows the current setting
    set <soundchip>_volume <num> Sets new volume; 0 is off, 100 is maximum
    examples:
    set PSG_volume
    set PSG_volume 60
    set "FMPAC_volume" 50

    throttle

    Sets throttle mode. In throttle mode the emulator tries to run at the specified speed relative to a real MSX (see speed command). When throttling is turned off the emulator runs as fast as possible.

    usage:
    set throttle Shows the current setting
    set throttle on Turn throttle mode on (normal operation)
    set throttle off Turn throttle mode off (fast forward)

    too_fast_vram_access

    How should software that accesses the VDP-VRAM too fast be emulated? Most existing MSX software should not access VRAM too fast, and in that case this setting has no effect. But you may want to change it when you e.g. emulate an overclocked Z80 (see z80_freq).

    usage:
    set too_fast_vram_access Shows the current setting
    set too_fast_vram_access real Accessing the VRAM too fast results in dropped VRAM accesses, just like on a real machine.
    set too_fast_vram_access ignore All VRAM accesses are executed, so timing of VRAM access is ignored.

    too_fast_vram_access_callback

    Selects the Tcl procedure to be called when a too-fast-VRAM-access (read or write) has been detected. This is useful for debugging MSX programs that show certain kinds of VRAM corruption, especially on MSX1.

    By default this setting is empty, which means that nothing is done when a too-fast-VRAM-access is detected. We ship a few example procedures called warn_too_fast_vram_access and debug_too_fast_vram_access which respectively print a warning or break CPU emulation when this condition occurs. You can find the source code for these procedures in scripts/callbackprocs.tcl. Feel free to write your own procedure that does exactly what you need.

    usage:
    set VDP.too_fast_vram_access_callback Shows the currently installed callback
    set VDP.too_fast_vram_access_callback warn_too_fast_vram_access Print warning when too fast VRAM access is detected
    set VDP.too_fast_vram_access_callback debug_too_fast_vram_access Print warning and also break emulation right after the Z80 instruction that triggered this callback
    set VDP.too_fast_vram_access_callback my_custom_callback_handler Install a custom callback handler
    set VDP.too_fast_vram_access_callback "" Remove any installed callback handler

    touchpad_transform_matrix

    Specify a 2×3 transformation matrix that maps host mouse coordinates to MSX touchpad coordinates. To get the following coordinate transformation:

            | a b c |   | host-X |   | touchpad-X |
            | d e f | × | host-Y | = | touchpad-Y |
                        |    1   |
      

    Use this command:

            set touchpad_transform_matrix {{a b c} {d e f}}
      
    usage:
    set touchpad_transform_matrix Shows the current value
    set touchpad_transform_matrix {{256 0 0} {0 256 0}} This is the default, map the full host window to 256×256 touchpad input
    set touchpad_transform_matrix {{320 0 -64} {0 240 -14}} Attempt to map touch coordinates to corresponding MSX pixel coordinates.

    turborpause

    Controls the pause key on a MSX turboR machine.

    usage:
    set turborpause Shows the current setting
    set turborpause on Activate the pause key
    set turborpause off Deactivate the pause key
    Note: If you use this setting often, it may be useful to bind it to a key on your PC keyboard. See the bind and toggle commands.

    umr_callback

    Selects the Tcl procedure to be called when an Uninitialized Memory Read has been detected. This is useful for debugging MSX programs: uninitialized memory is not guaranteed to have any particular value, so reading it is most likely a bug.

    By default this setting is empty, which means that nothing is done when an Uninitialized Memory Read is detected. We ship a useful procedure called umrcallback which logs all UMRs. You can activate it with set umr_callback umrcallback. You can find the source code for this procedure in scripts/callbackprocs.tcl.

    usage:
    set umr_callback Shows the current UMR callback setting
    set umr_callback umrcallback Sets callback proc to umrcallback

    vdpcmdinprogress_callback

    Selects the Tcl procedure to be called when a write to a VDP command engine register is detected while there is still a VDP command in progress. Often this is an indication of a bug in the running MSX program. Note that writes to VDP register R#44 with a command in progress are normal behaviour, so the callback is not triggered for such writes.

    By default this setting is empty, which means that nothing is done when a suspicious VDP command engine write is detected. We ship an example proc called vdpcmdinprogresscallback which simply logs all occurrences. You can activate it with set vdpcmdinprogress_callback vdpcmdinprogresscallback. You can find the source code for this proc in scripts/callbackprocs.tcl. Feel free to write your own proc that does exactly what you need. For example it might be a good idea to execute debug break in your callback, so that you can easily examine what code triggered this write.

    usage:
    set vdpcmdinprogress_callback Shows the current value. Default is "" (meaning no action)
    set vdpcmdinprogress_callback vdpcmdinprogresscallback Sets callback to vdpcmdinprogresscallback

    vdpcmdtrace

    Enable/disable VDP command tracing. When enabled, every VDP command is logged on stdout. This is useful when debugging MSX programs that use the VDP command engine.

    usage:
    set vdpcmdtrace Shows the current setting
    set vdpcmdtrace on Enables VDP command tracing
    set vdpcmdtrace off Disables VDP command tracing

    videosource

    Switch between video sources: MSX (V99x8, default when no Video 9000 is available), GFX9000 (V9990), Video9000 (V9990 superimposed on top of V99x8, default if available) and Laserdisc (for Palcom machines). There can be even more video sources, e.g. due to cartridges with a built in VDP like the Neos MA-20(V).

    usage:
    set videosource Shows the current setting
    set videosource MSX Switch to normal MSX screen
    set videosource GFX9000 Switch to GFX9000 screen
    Note: This setting is only available if multiple videosources are present.

    v9990cmdtrace

    Enable/disable V9990 command tracing. This is the V9990 equivalent of vdpcmdtrace.

    usage:
    set v9990cmdtrace Shows the current setting
    set v9990cmdtrace on Enables V9990 command tracing
    set v9990cmdtrace off Disables V9990 command tracing
    Note: This setting is only available if the gfx9000 or video9000 extension is present.

    z80_freq / z80_freq_locked

    These two settings control the Z80 clock frequency. When z80_freq_locked is true the emulated Z80 runs at the normal 3.5 MHz (or optionally 5.3 MHz on some machines). When z80_freq_locked is false the value of z80_freq is taken as the Z80 clock frequency.

    examples:
    Overclock Z80 to 14 MHz:
    set z80_freq 14318180
    set z80_freq_locked false

    F8 switches between 3.5 MHz and 7 MHz:
    set Z80_freq 7159090
    bind F8 "toggle z80_freq_locked"

    other settings

    Like with the commands, there are also some specialized settings, for which we only list a very brief overview. As always execute "help setting <setting-name>" to get a more detailed description of the setting.

    fast_cas_load_hack_enabled Enable a hack that lets you quickly load CAS files, without having openMSX convert them to WAV

    The source code of all these scripts is located in share/scripts directory. Feel free to inspect these scripts and modify them to suit your needs.

    openMSX-RELEASE_0_12_0/doc/manual/compile.html000066400000000000000000001410761257557151200210100ustar00rootroot00000000000000 openMSX Compilation Guide

    openMSX Compilation Guide

    Contents

    1. 1. Introduction
      1. 1.1 New Versions of this Document
      2. 1.2 Purpose
      3. 1.3 Contributors
      4. 1.4 Revision History
    2. 2. Preparation
      1. 2.1 Build Tools
      2. 2.2 Libraries
    3. 3. Getting the Source Code
      1. 3.1 Released Version
      2. 3.2 Git Clone
    4. 4. Binary for Local System
      1. 4.1 Compilation
      2. 4.2 Installation
    5. 5. Stand-alone Binary
      1. 5.1 Compilation
      2. 5.2 Installation
    6. 6. Android build
      1. 6.1 Integrate SDL Android build with openMSX build
      2. 6.2 Compilation and installation
    7. 7. Next Steps
    8. 8. Contact Info

    1. Introduction

    1.1 New Versions of this Document

    The latest version of the openMSX manual can be found on the openMSX home page:

    http://openmsx.org/manual/

    You can also use this URL to get up-to-date versions of the hyperlinks if you printed out this manual.

    1.2 Purpose

    This guide is about openMSX, the open source MSX emulator that tries to achieve near-perfect emulation by using a novel emulation model. You can find more information about openMSX on the openMSX home page.

    If you just want to use openMSX, there is likely a pre-packaged version you can use. This document describes how you can compile openMSX from source, which is useful if:

    • There is not yet a pre-packaged version for your favourite platform.
    • You want to closely follow the latest developments.
    • You want to make changes to openMSX.

    If you need help compiling openMSX, please contact us. If you needed any modifications to make openMSX compile, please send those modifications to us, so we can make openMSX ever more portable.

    1.3 Contributors

    The following people contributed to this document in one way or another:

    • Jorrith Schaap
    • Manuel Bilderbeek
    • Maarten ter Huurne
    • other openMSX developers

    Thanks to all of them!

    1.4 Revision History

    For the revision history, please refer to the commit log.

    2. Preparation

    Before you can start compiling openMSX, you have to make sure your system has all the necessary build tools installed, as well as the libraries openMSX depends upon. The following sections tell you how you can prepare your system.

    Every section starts with generic instructions, that apply to all platforms and compilers. At the end of each section, platform specific notes cover differences or additional steps for certain platforms or compilers.

    2.1 Build Tools

    For compilation, you need Python, a C++ compiler, and some compiler-specific programs. If you have compiled packages from source before, you probably have some of these installed already.

    Python
    A compact and dynamic programming language. Version 2.6 or later is required. However, Python 3.0 contains incompatible changes in the language, so make sure you install a version from the 2.x series. Eventually Python will replace Make completely in our build system, but at the moment both are required. Note: on Windows 7 you may have to add the path to Python (e.g. C:\Python26) to your PATH manually.
    There are three compilers that are supported to build openMSX: GCC, clang and Visual C++. The GCC compiler builds openMSX on all supported platforms, clang is the default on Mac OS X, while Visual C++ is the primary compiler for Windows.

    GCC

    For compilation with GCC, you need GNU Make and g++:

    make
    GNU implementation of the Make tool. Make interprets rules that define how a project should be built. Version 3.79 or higher should suffice, but 3.80 or higher is recommended and even necessary if you are building a standalone binary with the method described below.
    g++
    The GNU C++ compiler. Version 4.7 or later is required to build openMSX.
    Microsoft Windows (MinGW)

    You need to install MinGW (with MSYS).

    You can download the latest version of mingw-get-inst from the download page of the MinGW project (it's the default download if you're on Windows). Double click on this installer after downloading. Click Next, Next, select "Download latest repository catalogues" and click Next again. Accept the license agreement and click Next. Use the default installation directory (C:\MinGW), or at least a clean and empty (e.g. non-existing) directory without spaces and not a network folder (i.e. shared folder on another computer) and click Next. Click Next again. In the "Select Components" step, make sure to check "C++ Compiler" and "MSYS Basic System" and click Next. Then click Install. This will then update the package repositories first, which takes a while. After this, it will download all the MinGW packages and install them.

    Dingux

    For compilation for Dingux (Linux for Dingoo), you need to install the OpenDingux toolchain. This toolchain is for x86 Linux; if you have an x86 machine but no Linux, you can install Linux in a virtual machine. Extract the toolchain to /opt; anywhere else won't work. It can be downloaded from the OpenDingux Releases page.

    clang

    Mac OS X

    Install Xcode from Apple, which you can find in the Mac App Store. Start Xcode and from the menu select Xcode > Preferences... > Downloads and install the "Command Line Tools" component. The openMSX build is not done inside the Xcode IDE, but it uses the SDK and command line tools that are provided by Xcode.

    Visual C++

    To build with Visual C++ you need to have the latest version of it installed. Currently, this is 2013 update 3.

    There are three different ways to obtain the Visual C++ compiler:

    Visual Studio
    This is the professional IDE and development environment that comes with Visual Studio, as a standalone purchase or via an MSDN subscription.
    Visual Studio Express for Windows Desktop
    This is a free (as in beer) IDE and development environment. It builds 32 and 64-bit binaries out of the box. Make sure you download the latest version.
    If you require the resulting binaries to run on Windows XP, the DirectX SDK is required. To generate an MSI installation package, WiX package must be installed.
    DirectX SDK
    This is the SDK that contains DirectX headers, libs, samples, documentation, etc. We recommend using the June 2010 version. This is a large download, about 580MB. To build openMSX, only the headers and libs are truly required. Download only when you want the resulting binaries to run on Windows XP as well.
    WiX
    This is a tool that generates MSI installer packages. We recommend using version 3.0 or later. If you see a message that the "Votive" component won't install, that's not a problem, just continue.

    Android

    Warning: the build of the Android version is only supported on a Linux or Unix host platform. If you want to build the Android version on a Windows host, then you must install a virtual machine with a Linux distribution on it. This is because the Android build depends on the "commandergenius" SDL Android port, which can only be build on Linux and Unix platforms.

    The Android build depends on the following toolkits and libraries that must first be installed:

    The Android SDK
    Download the Android SDK from the Android SDK site. It is sufficient to download the SDK Tools only package; For openMSX you do not need the so-called ADT bundle. The current SDK Tools only package is Android SDK R23.0.2 Linux. After downloading it, unpack it to your favourite location and add following Android SDK directories to your PATH environment variable: ${ANDROID_SDK}/tools:${ANDROID_SDK}/platform-tools:${ANDROID_SDK}/build-tools/${ANDROID_SDK_VERSION}. It is advisable to do this in your ~/.profile script so that the setting remains available on each login. Note that ${ANDROID_SDK} stands for the directory in which you installed the Android SDK, for example /opt/android/android-sdk-linux and ${ANDROID_SDK_VERSION} stands for the version of the SDK, for example 20.0.0. See the README in the ${ANDROID-SDK} directory for further instructions on how to download extra tools and libraries.
    The Android NDK
    Download the Android NDK from the Android NDK site, unpack it to your favourite location and add the following Android NDK directory to your PATH environment variable: ${ANDROID_NDK_HOME}. It is advisable to do this in your ~/.profile script so that the setting remains available on each login. Note that ${ANDROID_NDK_HOME} stands for the directory in which you installed the Android NDK, for example /opt/android/android-ndk-r9c.
    The "commandergenius" SDL Android port
    Download the SDL Android port from the git repository to your favourite location. You can download it into the current directory with the following command: git clone https://github.com/pelya/commandergenius.git. Once that is done, you must set-up an environment variable called SDL_ANDROID_PORT_PATH with the location of the SDL android port. For example /opt/android/commandergenius
    Java Development Kit
    Make sure you install the JDK from your distro. Example packages: openjdk-6-jdk (Debian Wheezy), java-1.7.0-openjdk (SuSE 12.2).
    Ant
    Make sure you install Ant build tool from your distro. Example packages: ant (Debian Squeeze/6.0), ant-1.8.2-11.1.1.noarch (SuSE 12.2).
    Ant-optional
    Make sure you install Ant-optional extensions from your distro. Example packages: ant-optional (Debian Squeeze/6.0). Note that this seems to be not applicable for SuSE 12.2; apparently everything is bundled in the ant package.
    zip
    Make sure you install the zip command line tool from your distro. Example packages: zip (Debian Squeeze/6.0), zip (SuSE 12.2).
    libz 32-bit
    Make sure that if you have a 64-bit distro, you (also) install the 32-bit version of libz (it is required by the Android NDK). Example packages: lib32z1 (Debian Squeeze/6.0), zlib-32bit (SuSE 12.2).
    libstdc++6 32-bit
    Make sure that if you have a 64-bit distro, you (also) install the 32-bit version of libstdc++6 (it is required by the Android NDK). Example packages: lib32stdc++6 (Debian Squeeze/6.0), libstdc++47-32bit (SuSE 12.2).

    After you installed the Android SDK, you actually only installed the "Android SDK Manager". You'll have to use this manager to install the proper Android SDK packages. Start up this tool by running android in a terminal (you added it to your PATH in the previous step). This should open a new window. You should first get some messages in the status bar that it is fetching the repositories. After that, you should get a list of all possible SDK API versions. If you get errors like HttpHostConnect Connection to http://dl-ssl.google.com refused, you are hitting this bug: IPv4/6 Related Network Problems. You'll have to solve this to proceed.

    Once you have the list of packages, you must select the following options for installation:

    • Tools -> Android SDK Platform-tools
      • Android 4.4 (API 19) -> SDK platform
      • Android 2.3.3 (API 10) -> SDK Platform

    It may be possible that by default another version of the Android API is pre-selected. You may unselect it in order to save download time and diskspace. (You also don't need the Android Support Package, so if it gets selected, you can also unselect that one.) Next step is to click the "Install N packages" button (N seems to vary a bit... it may be like 12) to install the packages (accept the license terms). After downloading and installing, you can close the Android SDK Manager.

    2.2 Libraries

    openMSX depends on the following libraries:

    SDL
    Simple DirectMedia Layer, a cross-platform library that provides low-level access to video, audio and input devices.
    SDL_ttf
    Library that provides support for TrueType Fonts in SDL.
    libpng
    Library for handling PNG images.
    zlib
    Library for file compression.
    Tcl
    The Tool Command Language, an embeddable scripting language. openMSX requires Tcl 8.5; older versions will not work.
    OpenGL (optional)
    Library for accelerated graphics. It is likely that OpenGL is already installed on your system. If not, you should probably get the OpenGL implementation from the manufacturer of your graphics card. On Linux, using Mesa with a DRI driver can be an alternative.
    GLEW (optional)
    The OpenGL Extension Wrangler, a library that greatly simplifies the use of OpenGL extensions.
    Ogg (optional)
    The Laserdisc emulation expects files in the Ogg container format for the video and audio of Laserdiscs.
    Vorbis (optional)
    The Laserdisc emulation expects audio encoded with Vorbis in the ogg container format for the audio of Laserdiscs. Vorbis was chosen since it is not patent-encumbered.
    Theora (optional)
    The Laserdisc emulation expects video encoded with the Theora codec in the ogg container format for the video of Laserdiscs. Again, Theora was chosen since it is not patent-encumbered.

    GCC / clang

    You can install the required libraries systemwide, or you can use the "3rd party libraries" support in the openMSX build system and build a stand-alone binary. Systemwide is recommended if you run a modular operating system (such as Linux, BSD or Mac OS X with MacPorts, Homebrew or Fink) and you intend to only use the openMSX binary on your computer. The 3rd party library system is recommended if you build for other operating systems (Windows, plain Mac OS X or embedded systems) or if you want to build a binary that can be used on other computers besides your own. If you choose the latter, please skip to the Stand-alone Binary chapter.

    Android

    For Android, most of the required libraries are bundled with the SDL Android port that you downloaded in the "Build Tools" section. The only missing library is the Tcl library. However, you don't have to do anything special to get it. The Tcl library is automatically downloaded and compiled by the Android specific parts of the openMSX build system. If you want to build openMSX for Android, you can immediately jump to the Getting the source code chapter.

    Debian and Ubuntu Linux

    You can easily install all required packages using the "build dependencies" feature of the APT package manager:

    sudo apt-get build-dep openmsx

    Do note that if the Debian/Ubuntu package that is in the version of the distribution you are currently running is older than the openMSX version you are trying to compile (which is typically so), you might miss some dependencies that were added in the newer version. The build system will complain and you will have to install the remaining (missing) packages manually. Read on to get some tips about that.

    Dingux

    We only support the Stand-alone Binary method, which means the openMSX build system will download and compile all libraries automatically.

    Other Linux

    Most Linux distributions have libraries split over two packages: a runtime package and a development package. The runtime package for the "Foo" library is typically called libfoo, the development package is typically named libfoo-dev or libfoo-devel. Applications that use a library only require the runtime package to be installed, but compilation requires both the runtime package and the development package to be installed.

    Mac OS X

    The easiest thing to do is to use the stand-alone binary method, which will get the libraries automatically.

    If you want to do things manually, you can get the libraries from MacPorts, Homebrew or Fink. These are tools to give you access to a large collection of software packages (or ports). You can use them to install those packages (or ports). The manual approach is not regularly tested by the openMSX developers; unless you enjoy tinkering with builds we suggest you use the stand-alone binary approach instead.

    Microsoft Windows (MinGW)

    We strongly recommend using the Stand-alone Binary method, which means the openMSX build system will download and compile all libraries automatically.

    Visual C++

    When building with Visual C++, the optional libraries mentioned above are arranged as follows:

    • Headers and libraries for OpenGL are included with Visual C++ by default.
    • The GLEW, Ogg, Theora and Vorbis libraries are required.

    Also, when building with Visual C++, the "3rd party libraries" support in the openMSX build system must be used. The resulting binary statically links in all dependencies, including the C runtime. For more details, please see the Stand-alone Binary chapter.

    3. Getting the Source Code

    openMSX is developed using the tools GitHub freely offers to open source projects. The code is stored in Git, an open source version management system. Once in a while an openMSX release is made.

    There are several options for getting the source code:

    Released Version
    These are tested versions, which should give you little problems compiling and running. However, as openMSX development if often quite fast, they may not have all the latest features. Also there could be bugs that have been fixed since the last release.
    Git Clone
    Through Git you can get the same development version the openMSX developers are using. This is the bleeding edge: the latest stuff, which may be great or may be horribly broken. Usually the latest openMSX code compiles and runs fine, but we're only human, so once in a while it breaks. Also there may be changes that are not documented yet.

    Releases are intended for general users, Git Clone is intended for (would be) developers, heavy testers and people who want to follow new developments closely. It might be a good idea to play with a release first. If you like what you see and want to get in deeper, you can switch to a Git Clone later. A Git Clone is very comfortable, because then you can do efficient incremental updates, saving network bandwidth and compile time.

    If you downloaded a version that is either a lot older or a lot newer than this guide, it is a good idea to read the guide included in your downloaded version instead of the version you're reading right now. You can find the Compilation Guide in the directory doc/manual.

    3.1 Released Version

    You can download a released version of openMSX from the releases page on GitHub. The latest version is probably the best one. This guide assumes that you are using the latest release.

    After downloading, type the following in a UNIX or MinGW Shell (in your start menu), or use another decompression tool:

    tar xzvf openmsx-VERSION.tar.gz

    in which VERSION is the openMSX version you downloaded, or use the file name you saved the tar.gz file with. The directory that is created by uncompressing the tar.gz file is called the top of the source tree.

    3.2 Git Clone

    Getting a Git clone means you use Git to retrieve the latest version of the source code of openMSX. This means you will need to install a Git client. This package is usually named git. There are graphical front-ends for Git, but this guide will tell you how to use Git from the command line. More information about Git can be found on the Git Documentation site.

    Windows users might want to look at msysGit for a command line tool, TortoiseGit for Windows Explorer integration, or Git Extensions, which also includes Visual Studio integration.

    With the following line (which is also displayed when you browse an openMSX git repository) you can retrieve the latest sources (also works on Windows when using msysGit):

    git clone https://github.com/openMSX/openMSX.git openMSX

    In this line you specified where you want to retrieve the files from (host name of the Git server), what project you want to retrieve (openMSX in this case), what module you want to get (openMSX.git in this case, which is the module that contains the sources of the main openMSX program) and what directory it should be cloned to (we chose openMSX in this example).

    When compiling openMSX on Windows with GCC, it's often convenient to use C:\MinGW\msys\1.0\home\<username>\openMSX as the target directory, as this is easy to reach from your MinGW Shell - it's your MinGW home directory.

    If you're a developer, it makes sense to use this git commandline:

    git clone git@github.com:openMSX/openMSX.git openMSX

    For this to work smoothly, without having to type your password all the time, it's probably a good idea to read the GitHub docs about SSH keys.

    The Git command creates a directory called openMSX for you in the current directory (or in the directory you specified in TortoiseGit). This directory is what we call in this manual the top of the source tree. In addition to the openMSX code, you will see a hidden Git administration directory called .git. Do not mess with it (nor move contents of this directory around), otherwise Git will get confused.

    If you want to update your source tree later, go to the top of the source tree and type:

    git pull

    or right click on the openMSX directory in Windows Explorer and select "TortoiseGit -> Pull...".

    4. Binary for Local System

    This section explains how to build openMSX for a local system. If you want to build openMSX for Android, you can skip this section and use the Android build method. If you want to create a binary that runs everywhere, we recommend to skip this chapter and use the stand-alone binary method. We also recommend to do that for Mac OS X and especially for Microsoft Windows systems.

    4.1 Compilation

    Now that all the necessary tools and libraries are installed, you are almost ready to start the actual compilation of openMSX.

    GCC

    The first thing you may want to know is that you can build openMSX in different flavours. The default flavour depends on the CPU, but is always one that is optimized for performance. For example for x86 processors the default flavour is "i686": i.e., with optimisations for Pentium II and higher, without any debugging stuff. Note: On an older VIA Epia (with the Samuel 2 CPU at least), a binary built with "i686" flavour will not run. Please use the "opt" flavour for that platform.

    If you are testing new openMSX developments or making changes in openMSX, you can benefit from asserts and debug symbols. To get those, you should select the "devel" flavour, like this:

    export OPENMSX_FLAVOUR=devel

    Although the default flavours will probably be OK for most cases, you may want to write a specific flavour for your particular wishes. The flavour files are all named build/flavour-*.mk.

    You can select the C++ compiler to be used like this:

    export CXX=g++-4.7

    This can be useful if the default compiler on your system is an old GCC version which is either not capable of compiling openMSX, or generates less efficient code.

    Now we can let a script check if you have indeed all necessary libraries and headers installed. Go to the top of your openMSX source tree and run the following script:

    ./configure

    This script will report what versions of libraries you have installed. It also reports which components can be built with those libraries. If the script reports that it can't build the openMSX core component, you should install the missing ones before you can continue. Otherwise, you can decide to install the libraries needed for the optional components, or to continue without building some components (e.g. the OpenGL based renderers).

    If installing the correct libraries doesn't help, contact the openMSX developers. If you file a bug report, please attach the probe.log file that is written by the configure script in the directory derived/<cpu>-<os>-<flavour>/config/.

    You can customise the build process by editing the file build/custom.mk. The most likely thing you might want to customise is the installation directory (INSTALL_BASE). If you are installing openMSX on a system on which you do not have superuser (root) privileges, you can set the installation directory to a subdirectory of your home directory.

    After successfully running configure, it's time to compile everything. To start compilation, type:

    make

    Depending on how fast your system is, this may take several minutes to half an hour.

    If you get errors during compilation, there may be something wrong that was not detected by configure. Verify that you installed all required libraries, both the run time and development packages. If that doesn't help, or we forgot to list a library openMSX depends on, contact the openMSX developers. Make sure you provide us with the error message you got.

    clang

    Linux and BSD

    Add CXX=clang++ as an argument to the make command to select clang as the compiler instead of GCC.

    Mac OS X

    Since openMSX 0.10.0, the minimum required Mac OS X release to build openMSX out of the box is 10.7. This implies that only x86_64 CPUs (64-bit Intel) are supported.

    Building for older Mac OS X releases should be possible in theory, but it requires a lot of effort to set up the compiler and SDK (C++11 support is required), adjust the openMSX build (build/platform-darwin.mk) etc. It's easier to try an older openMSX release instead.

    Visual C++

    While it is possible to configure the supplied Visual C++ project files to dynamically link against import libraries, this is not currently supported.

    4.2 Installation

    GCC / clang

    Linux and BSD

    To install openMSX, run the following command:

    make install

    This installs openMSX, by default in /opt/openMSX. Note that only root has rights to write to system-wide directories such as /opt, so you may have to do su before make install, or use sudo.

    Mac OS X

    On Mac OS X, the build creates an application folder. You can "install" this by copying it to a different location. The application folder will depend on systemwide installed libraries, so it will not work on Macs without those libraries.

    You can run openMSX from the application folder with the following command:

    open derived/<cpu>-darwin-<flavour>/bindist/openMSX.app

    If you want to see the messages openMSX prints to stdout and stderr, start openMSX like this:

    derived/<cpu>-darwin-<flavour>/bindist/openMSX.app/Contents/MacOS/openmsx

    5. Stand-alone Binary

    This chapter describes how to build a binary of openMSX that does not depend on any libraries except those that are available on the platform by default. This procedure is highly recommended on Microsoft Windows. It is also recommended on Mac OS X.

    The stand-alone binary is made by linking statically against all libraries which are not available as part of the basic platform. The build system will automatically download the sources for these libraries and build them in the minimal configuration needed by openMSX.

    5.1 Compilation

    GCC / clang

    If you want to change something about this process, for example switch to a newer library release, edit the build/3rdparty.mk Makefile.

    Just like for normal compilation, you can set environment variables such as OPENMSX_FLAVOUR, CXX, OPENMSX_TARGET_CPU and OPENMSX_TARGET_OS.

    To build a stand-alone binary, run the following command at the top of the source tree:

    make staticbindist

    The final output files will be produced in derived/<cpu>-<os>-<flavour>-3rd/bindist.

    Mac OS X

    The final output is a DMG file (Mac disk image) containing the openMSX application folder and documentation. This file is internet-enabled, which means it will be automatically mounted after it is downloaded.

    Microsoft Windows (MinGW)

    Here's the very short version if you want quick results on Microsoft Windows with GCC:

    • Open a MinGW Shell
    • Go to the top of the source tree (e.g.: cd openMSX if you chose the MinGW home directory as target directory)
    • Type: make staticbindist

    A complete package will be created in derived\x86-mingw32-i686-3rd\bindist\install.

    If you additionally want to build an (by current developers unused) openMSX installer, you should refer to the package-windows module in our Git repository. It contains a README file with a short manual.

    Note: on a 64-bit Windows system, MinGW will pretend to have built 64-bit binaries (target directory is derived\x86_64-mingw32-opt-3rd\bindist\install), but they're actually just 32-bit.

    Dingux

    After having the toolchain installed, you can simply compile openMSX with this command:

    make staticbindist OPENMSX_TARGET_CPU=mipsel OPENMSX_TARGET_OS=dingux OPENMSX_FLAVOUR=opt

    When this is done, you have a ready to use ZIP file for distribution in the folder derived/mipsel-dingux-opt-3rd/.

    Visual C++

    When building with Visual C++, the result is a static executable with minimal dynamic library dependencies. Two platforms are supported:

    • Win32 - a 32-bit binary that runs on both 32 and 64-bit versions of Windows
    • x64 - a native 64-bit binary that runs only on 64-bit versions of Windows

    Three different configurations are supported:

    • Debug: assertions enabled, debug prints enabled, no optimization, very slow
    • Developer: assertions enabled, debug prints disabled, no optimization
    • Release: assertions disabled, debug prints disabled, full optimization

    When building with Visual C++, you can either use the IDE or build from the command line using msbuild. To do the latter, you need to open a Visual Studio command prompt. A shortcut to a Visual Studio command prompt can usually be found in your start menu. For Visual Studio (Express) 2013, it will be in "Microsoft Visual Studio 2013\Visual Studio Tools" as the "Visual Studio Command Prompt".

    In order to build openMSX, the libraries it depends on need to be downloaded and unpacked:

    • Open a command prompt and go to the top of the openMSX source tree
    • Type python build\thirdparty_download.py windows

    The following steps can then be used to build openMSX:

    • Open a Visual Studio command prompt
    • Go to the top of the openMSX source tree
    • To build 3rd party libraries, type msbuild -p:Configuration=Release;Platform=Win32 build\3rdparty\3rdparty.sln
    • To build openMSX, type msbuild -p:Configuration=Release;Platform=Win32 build\msvc\openmsx.sln

    The openMSX executable will be generated in derived\Win32-VC-Release\install.

    To build for other platforms or configurations, simply replace "Release" and "Win32" in the command lines above with the desired options.

    To build using the Visual C++ IDE, simply open the aforementioned solution files and from the Build menu select "Build Solution". This is exactly equivalent to building from the command line using msbuild.

    5.2 Installation

    Visual C++

    The following step creates .zip and .msi installation packages for openMSX on Windows using WiX:

    • Build openMSX and wxCatapult for a given platform (Win32 or x64) and configuration (usually Release builds)
    • Go to the top of the openMSX source tree
    • Run build\package-windows\package.cmd platform configuration catapult_source_path:
      • platform is one of { Win32, x64 }
      • configuration is one of { Release, Developer, Debug }
      • catapult_source_path is the path to the wxCatapult directory

    An example command line would be (for 64 bit):

    build\package-windows\package.cmd x64 Release ..\wxCatapult

    The resulting package files can be found in derived\x64-VC-Release\package-windows.

    Dingux

    From the ZIP created in the previous step (or downloaded from the openMSX download site), copy the directory named local to the root of the Dingux miniSD card. If you have run Dingux from the miniSD card before, there will already be a directory named local. You can safely merge both directories.

    To get it in your own menu, you'll have to add the openMSX entry to it yourself. Use the start up script openmsx-start.sh for this, which is installed in local/bin. Even without the menu, you will have to use this script to start openMSX, it won't work otherwise.

    6. Android build

    This chapter describes how to build an Android APK package of openMSX for Android and install it on an Android device.

    Integrate SDL Android build with openMSX build

    Once you have downloaded the openMSX source (see Getting the Source Code chapter) and the toolkits and libraries (see Preparation chapter), you must integrate the openMSX build system with the SDL Android port build system.

    Basically, the build will be driven by the build system of the SDL Android port and you must set that one up in order to be able to build openMSX. In order to do that, you must execute the following commands, whereby OPENMSX_SRC_HOME stands for the directory into which the openMSX source has been loaded:

    	cd $OPENMSX_SRC_HOME/build/android
    	./setup_anddev.sh
    

    Compilation and installation

    Build of the Android version

    In order to build the Android version, you must enter the following commands:

    	cd $OPENMSX_SRC_HOME/build/android/openmsx
    	./launch_anddev_build.sh
    

    If this gives an error like "Unable to find a javac compiler; com.sun.tools.javac.Main is not on the classpath. Perhaps JAVA_HOME does not point to the JDK.", you may have to set the environment variable JAVA_HOME manually. E.g. if javac is actually installed in /usr/lib/jvm/java-6-openjdk-amd64/bin, make sure to export JAVA_HOME=/usr/lib/jvm/java-6-openjdk-amd64 before running this command.

    (Re-)installation of the Android version

    In order to install the Android version, you must connect the Android device with a USB cable to the computer, enable the "Developer options" in the settings menu and within the "Developers options" enable the "USB debugging" option. Once that is done, you can install the APK that was build in the previous step with the following commands:

    	cd $SDL_ANDROID_PORT_PATH
    	adb install -r project/bin/MainActivity-debug.apk
    

    Alternatively, you can put this file on a place where you can access it with your device (e.g. on a webserver or sending an e-mail). Then you can download and install it like any APK file.

    Build and (re-)installation of the Android version in one step

    In order to build and directly install the Android version, you must enter the following commands:

    	cd $OPENMSX_SRC_HOME/build/android/openmsx
    	./launch_anddev_build.sh -i
    

    Note that this only works when the "Developer options" -> "USB debugging" setting has been enabled on the Android device.

    If all went well, you should have openMSX installed now or have a stand alone working binary. You can test it by executing openMSX from the command line:

    openmsx

    or, by tapping (on Android) or double clicking (on other systems) the openMSX executable or icon that resulted from the previous step.

    You should get a screen similar to this:

    C-BIOS 0.25        cbios.sf.net
    
    No cartridge found.
    
    This version of C-BIOS can
    only start cartridges.
    Please restart your MSX
    (emulator) with a cartridge
    inserted.
    

    C-BIOS MSX2+ is the default system BIOS used by openMSX. It was written from scratch by BouKiCHi and he was kind enough to let us distribute it together with openMSX. It is not perfect yet, but it runs many ROM games well. Nowadays C-BIOS is a separate (SourceForge.net) project, with its own web page.

    If you have a ROM image ready, you can try to run it with C-BIOS:

    openmsx ~/msx/games/my-favourite-game.rom

    or, you can just drop it on the openMSX executable.

    The next step would be to read the openMSX Setup Guide. That document describes how you can configure openMSX to emulate actual MSX machines, such as the Panasonic FS-A1GT (turboR). It also describes how you can have openMSX start up with your personal settings, how you can configure openMSX and your system for optimal performance and several other configuration related topics. And finally there is of course the openMSX User's Manual, which describes all the things you can do with openMSX once it is fully running.

    If you got stuck somewhere in the compilation and installation process, please contact us. The next chapter will tell you how.

    Mac OS X

    You can run the openMSX application folder from Finder or from the command line:

    open derived/<cpu>-darwin-<flavour>-3rd/bindist/openMSX.app

    We do not have a finished GUI yet that works on Mac OS X, so you can either use openMSX from the command line for now, or use third party software like NekoLauncher openMSX.

    Microsoft Windows

    You can use the Catapult launcher to run openMSX.

    8. Contact Info

    Since openMSX is still under heavy development, feedback and bug reports are very welcome!

    If you encounter problems, you have several options:

    1. Go to our IRC channel: #openMSX on irc.freenode.net and ask your question there. Also reachable via webchat! If you don't get a reply immediately, please stick around for a while, or use one of the other contact options. The majority of the developers lives in time zone GMT+1. You may get no response if you contact them in the middle of the night...
    2. Post a message on the openMSX forum on MRC.
    3. Contact us and other users via one of the mailing lists. If you're a regular user and want to discuss openMSX and possible problems, join our openmsx-user mailing list. If you want to address the openMSX developers directly, post a message to the openmsx-devel mailing list. More info on the openMSX mailing lists, including an archive of old messages, can be found at SourceForge.
    4. Create a new issue in the openMSX issue tracker on GitHub. You need a (free) log-in on GitHub to get access.

    In all cases, please provide as much information as possible when you describe your bug or request.

    GCC / clang

    For experienced users: if you get a crash or a hang, try to provide a gdb backtrace. This will only work if you did not strip the openMSX binary of its debug symbols.

    Another useful thing to do is to install the debug versions of libstdc++ and libc6, and then run openmsx with an LD_LIBRARY_PATH=/usr/lib/debug exported in the environment. This will give a more detailed stacktrace, especially in optimized code.

    Visual C++

    For experienced users: if you get a crash or a hang, try to provide a user dump. This will work for any openMSX binary, including pre-built binaries obtained from openmsx.org.

    As of Windows Vista SP1 and later operating systems, you can find user dump files for crashed processes in the "%LocalAppData%\CrashDumps" directory. The default Windows crash dump behavior can be further customized as per MSDN.

    To generate a user dump on demand on any Windows OS, please read KB286350.

    openMSX-RELEASE_0_12_0/doc/manual/diskmanipulator.html000066400000000000000000000334741257557151200225700ustar00rootroot00000000000000 Using Diskmanipulator

    Using Diskmanipulator

    The diskmanipulator command is probably the most exuberant console command available in openMSX. It implements the basic stuff needed to handle files and subdirectories on MSX media. It also helps one creating new disk image files, both simple 360 kB and 720 kB disks as well as hard disk images containing up to 30 partitions of 32 MB each (only for Sunrise IDE!). Creating disk images and manipulating the files on them can be done without the need of a running emulated MSX (set power off).

    First we will see the generic syntax of the command, then the individual commands are explained in more depth. At the end of this document you will find some examples of typical use.

    Note: For now, this command does not work with SCSI/SD disks and drives! So, you cannot create valid formatted SCSI hard disk images, SD card images or manipulate files on hard disks formatted for use with a SCSI interface or SD cards for an SD interface.

    General Syntax

    The general command syntax is always of the form:

    diskmanipulator <command> <disk name> <command arguments>

    <command> specifies the action to be performed. The next section lists the commands available and explains them.

    <disk name> specifies the disk to operate on. Typical values are: diska, diskb, hda or the special virtual_drive device. disk<x> and hd<x> are the drives available to the running emulated MSX machine. This allows interaction with the currently used disk images.
    In case the disk contains a Sunrise IDE (or Beer IDE 1.9RC1) compatible partition table (FAT12 only!) you can add a partition number (from 1 till 31) to the disk name to specify on which partition the command will act. For example hda2 is the second partition on the master IDE disk, hdb3 is the third partition on the slave IDE disk.

    <command arguments> depend upon the command involved, see the detailed descriptions of the commands below.

    The diskmanipulator and all its commands (including most parameters) can be tab completed in the console.

    Commands

    These are the commands understood by the diskmanipulator:

    1. chdir
    2. create
    3. dir
    4. export
    5. format
    6. import
    7. mkdir
    8. savedsk

    chdir

    syntax:

    diskmanipulator chdir <disk name> <MSX directory>

    explanation:

    This command selects the directory on the MSX disk image that will be used for the export and import commands.

    Note: The directory structure on the MSX disk image cannot be tab completed.

    create

    syntax:

    diskmanipulator create <dskfilename> <size|option> <size|option> ...

    explanation:

    You can create new disk images using this command.

    This new disk will be formatted using an MSX-DOS2 boot sector, unless you specify the option -dos1.

    You can specify multiple sizes in which case a Sunrise IDE compatible partitioned image will be created (FAT12 only!), each partition will be formatted as required. If a size of 360 kB or 720 kB is given, a normal floppy disk image is created single or double sided, respectively. Any larger value will result in a Sunrise IDE hard disk image.

    You can specify the disk/partition sizes by using the following postfixes:

    • S or s -> size in sectors
    • B or b -> size in bytes
    • K or k -> size in kilobytes (default)
    • M or m -> size in megabytes

    dir

    syntax:

    diskmanipulator dir <disk name>

    explanation:

    This will show the directory content of the current working directory. The output is formatted similarly to the MSX Disk BASIC 2.x command files,l.

    export

    syntax:

    diskmanipulator export <disk name> <host directory>

    explanation:

    This will export the files and subdirectories from the disk inserted in <disk name> to the <host directory> on your host OS. The subdirectory that will be exported from the MSX disk image is selected by the chdir command.

    format

    syntax:

    diskmanipulator format <disk name>

    explanation:

    The currently selected partition from <disk name> will be cleanly formatted with a MSX-DOS2 boot sector, unless the option -dos1 is specified. FAT and directory sectors will be correctly initialised. Any data on the disk image / partition is lost!

    import

    syntax:

    diskmanipulator import <disk name> <host directory|host file> ...

    explanation:

    This will import the single <host file> into the disk inserted in <disk name>. In case of a <host directory> it will import the files and subdirectories in <host directory> into the inserted disk. Multiple files and directories can be specified at the same time. The place were the files will be added in the MSX directory structure is selected by the chdir command.

    If you want to use wildcards when importing files, you will have to use the Tcl glob command. This command will perform the wildcard expansion and return a Tcl list. Enclose the glob command in between '[' and ']':

    diskmanipulator import hda1 [glob *.txt] [glob *.asc]

    This command will copy all files matching *.txt and *.asc in the current directory on the host OS to the first partition of the master IDE drive on the emulated MSX.

    The glob command can also take extra options. For instance, if you only want to expand regular files and not the names of directories you can do this:

    diskmanipulator import hda1 [glob -type f info*]

    Consult your local Tcl guru or documentation for more info about the glob command and Tcl lists.

    mkdir

    syntax:

    diskmanipulator mkdir <disk name> <MSX directory>

    explanation:

    This command will create the specified directory on the MSX disk image. All the needed parent directories will be created if they do not yet exist.

    savedsk

    syntax:

    diskmanipulator savedsk <disk name> <dskfilename>

    explanation:

    This simply reads all the sectors of the <disk name> and saves them again in the file specified by <dskfilename>. This command is mostly equivalent to copying a disk image file on your host OS, but it has the additional possibilities:

    • saving a ramdsk (see diska ramdsk) into a real disk image file
    • saving your current DirAsDisk image into a real disk image file
    • saving your disk image which has undergone IPS patches as a patched disk image
    • copying the currently active image file in case your host OS would give sharing violations while the file is being used by openMSX (Windows)
    • saving a disk image if you removed the directory entry by accident, but openMSX still has an open file handle for the file (UNIX-like systems)

    Examples

    In these examples we will run the diskmanipulator while the emulated MSX is powered off. It is possible to run these commands while the machine is turned on of course, but be warned that this might have some strange, unexpected behaviour depending on the emulated MSX model and the running software on this MSX.

    For instance, the turboR models contain a physical switch inside their diskdrives to detect disk changes. If no disk change is detected their internal MSX-DOS2 kernel will cache certain sectors, so that files imported using the diskmanipulator import command will not show up if you perform a files or dir. Even worse, if you would write from the emulated MSX to the disk you will overwrite the result of the import. The same would happen if you were running a disk cache program in your emulated MSX machine.

    creating a new disk with content

    Here we create a regular 720 kB (double sided, double density) disk. Then we place the files and subdirectories from the directory /tmp/todisk/ on this new disk:

    set power off
    diskmanipulator create /tmp/new-disk.dsk 720
    virtual_drive /tmp/new-disk.dsk
    diskmanipulator import virtual_drive /tmp/todisk/

    creating a new harddisk image with content

    Here we create a new HD image with 3 partitions the first partition is 32 MB, then 16 MB and finally a small one of 720 kB. Then we place the files and subdirs of the directory /tmp/topart1/ on the first partition and /tmp/topart3/ on the third partition:

    set power off
    ext ide
    diskmanipulator create /tmp/new-hd.dsk 32M 16M 720
    hda /tmp/new-hd.dsk
    diskmanipulator import hda1 /tmp/topart1
    diskmanipulator import hda3 /tmp/topart3

    importing data in a new subdirectory

    On the diskimage /tmp/disk.dsk we will create a new subdirectory called newsub and then we fill this subdirectory with the .txt files from /home/david/sources:

    set power off
    diska /tmp/disk.dsk
    diskmanipulator mkdir diska newsub
    diskmanipulator chdir diska newsub
    diskmanipulator import diska [glob -type f /home/david/sources/*.txt]

    extracting files from an MSX harddisk image to the host OS

    We will extract files from the currently used harddisk image on partition1 in the MSX subdir \demos\calculus to /tmp/:

    set power off
    ext ide
    diskmanipulator chdir hda1 /demos/calculus
    diskmanipulator export hda1 /tmp
    openMSX-RELEASE_0_12_0/doc/manual/faq.html000066400000000000000000001033411257557151200201200ustar00rootroot00000000000000 openMSX FAQ

    openMSX FAQ

    Contents

    Installing openMSX

    What is the easiest way to install openMSX?

    openMSX used to be difficult to install, but it isn't difficult at all, nowadays! At least: it shouldn't be.

    The easiest way to install openMSX is to use a ready-made package. Whether one is available depends on your operating system:

    Windows
    The easiest way to install openMSX is by running the installer. Download the installer from our website (check out the Download box), unpack the .zip file, and double-click on the resulting .msi file. After this, you should have a working openMSX and openMSX Catapult.

    You can also download the Zip distribution which contains only the emulator files (no installer) and unpack it to the directory of your choice.
    Mac OS X
    The easiest way is to get the .dmg file from our website (check the Download box). There is no Catapult for OS X yet, but NekoLauncher openMSX is a very good alternative. If this site gives you problems, check the mirror download site. And of course you can also use the built-in OSD menu, open it with Cmd+O.
    Linux
    Debian GNU/Linux
    Install the openmsx, openmsx-data, cbios and openmsx-catapult packages. The simplest way is to run aptitude install openmsx-catapult, APT will install the other needed packages as well.
    Ubuntu
    The Ubuntu packages are similar to the Debian ones. You can find them in the "universe" repository, so make sure you have that enabled in your /etc/apt/sources.list.
    Fedora
    Fedora has packaged openMSX in the main archive since Fedora 7. You can just run yum install openmsx to install openMSX. Catapult does not seem to be packaged yet at the moment.
    Gentoo
    You can find an ebuild for openMSX in the "games-emulation" category. Catapult does not have an ebuild yet.
    openSUSE
    openSUSE 10.3 (and 11.x) seems to contain an openMSX package, but it seems to be somewhat outdated.
    Slackware
    You can find binary packages for Pentium 2 and higher on the Linux Packages site, but they seem to be somewhat outdated.
    Mandriva
    There seem to appear at least source RPM's on the RPMFind.net search for openMSX sometimes, but we don't know how to use them exactly or if they are also included in the official distribution.
    ArchLinux
    There are packages for openMSX and Catapult in the ArchLinux User-community Respository (AUR).
    other
    It is possible that openMSX has been packaged for other Linux systems as well, but we are not able to keep track of them all. Search in your package management system for openMSX and there's a fair chance that openMSX will pop up. If it doesn't, see a few lines below at "other".
    FreeBSD
    There is a port for openMSX in FreeBSD. It is called simply "openmsx". There is also a port for Catapult.
    NetBSD
    There is a port for openMSX in NetBSD, but it seems to be somewhat outdated. There is no port for Catapult yet.
    other
    You'll have to install openMSX from source. This is not as complicated as it sounds, because we have automated most of the process and documented the rest. Just read the Compilation Guide.

    Note that in any case, there are no system ROMs installed, so only the C-BIOS machines work out-of-the-box. See also the next couple of questions.

    If you still think openMSX is difficult to install, please tell us why!

    Should I download the Zip or the Installer for Windows?

    We strongly recommend to use the installer. It contains a straightforward Windows Installer package. With the installer, your openMSX experience is only about 3 clicks away. The Zip file is meant for experienced users and those familiar with openMSX and emulators in general.

    Why doesn't openMSX come with system ROMs?

    The MSX system ROMs are copyrighted. In other words: it's illegal to include them in our software package without a license. The Setup Guide contains a section about system ROMs.

    Where do I install the ROMs?

    You can put all the ROMs in a file pool, which is a central directory in which openMSX looks for files. The default location is share/systemroms. If you want to tweak this, add locations or get a list of all file pools, take a look at the filepool command.

    See also the Setup Guide.

    Using openMSX

    Is openMSX easy to use?

    Ever since openMSX comes with the optional GUI dubbed "openMSX Catapult", it is quite easy to use! The Windows installer installs it by default. For other systems, the same counts as for openMSX itself. Check out the Catapult manual for more information. For basic usage, you just select a machine to run and click on "Start"!

    An alternative to Catapult, is the built in OSD menu. You can call it by pressing the MENU key (or Cmd+O on Mac). This menu is also used on handhelds, like the Dingoo.

    Catapult and the OSD menu don't give you access to all features of openMSX, though. You can do a lot more by using the openMSX built-in console. You can read a lot more about this in the User's Manual. It's not a GUI, but we did our best to make this console as easy to use as possible.

    If you think openMSX is not easy to use, please tell us why! Contact info is in the manuals.

    Oh, you might wonder: why don't you make a normal GUI like other emulators? One in which the emulation is inside the GUI? The reason is that we are currently not able to make that: the GUI toolkit we use does not support it and we want to keep the GUI optional, so we cannot make it part of the main application.

    All I get is "No cartridge found." What's wrong?

    You are probably talking about this:

    No cartridge found.

    openMSX doesn't come with any system ROMs, see Why doesn't openMSX come with system ROMs?. To have something to be able to run some software, openMSX comes with a free replacement of an MSX BIOS ROM called C-BIOS, written by BouKiChi, Reikan and nowadays the C-BIOS Association. This is also what you can see in the screen: C-BIOS 0.25.

    So, the message is not from openMSX, but from C-BIOS, an MSX program which tries to start a cartridge that is inserted in the MSX that is being emulated by openMSX. And it seems you didn't insert any cartridge for it... So, either run openMSX with a ROM image or install real MSX system ROMs for a certain machine and run that one.

    Note that the current version of C-BIOS can only run cartridges and does not support disk or tape usage. More information can be found in the Setup Guide.

    I get "Fatal error: No drive named 'diska'." What's wrong?

    You are probably using Catapult with C-BIOS and you're trying to run a program on a disk image.

    Unfortunately, the C-BIOS machines that come with openMSX do not support disk or tape usage, see previous question. Please see the Setup Guide.

    The tricky thing is that the old (current) version of Catapult always shows the disk, tape and cartridge controls, independent whether the machine you're emulating actually supports them. This will be greatly improved in the new (upcoming, but not finished) Catapult.

    If you want to use disks or tapes with your emulated MSX, you will have to install the system ROMs of a real MSX (one with a disk drive!) to emulate that one, until C-BIOS supports this as well. More about this in the Setup Guide.

    How do I save my game?

    In General

    You've been playing a game and want to continue another time, so you want to save your progress. You can either save your game as you would on a real MSX, or you can use the built in save state mechanism, which is explained in the manual.

    If you consider that cheating, or are interested in how you could save your game on a real MSX, read on... How it works, depends on the game:

    Game Disk
    Some disk-based games save on one of the game disks. Make sure the disk is not write protected; in openMSX this means the disk image must be an ordinary DSK image (not compressed as ZIP or XSA) and not read-only.
    Separate Disk
    Some games save on a separate disk. You can create an empty disk for this purpose by opening the openMSX console with F10 (Cmd+L on Mac) and executing the command diskmanipulator create filename.dsk 720. Since many games save in fixed sector locations instead of files, it is best to reserve a separate save game disk for each game.
    PAC SRAM
    The PAC and FMPAC contain a bit of battery-powered memory that can be used for saving games. Make sure you insert the pac or fmpac extension when you start openMSX.
    The SRAM is divided into 8 blocks and different games typically use different blocks. However, there are many games using the SRAM, so it is unavoidable that some games use the same block. If you play more than one game and want to be safe, copy the .pac file from "persistent/fmpac/untitled1" in your openMSX user directory (see this overview where it is for your platform) to a safe location.
    Game Master 2
    The Game Master 2 can be used to save many Konami games. Insert both the game cartridge and the Game Master 2 cartridge.
    Embedded SRAM
    Some cartridges games, for example the strategic games from Koei, have SRAM inside the cartridge. You don't have to do anything special to use this.
    Tape
    Saving to tape is a bit clumsy, but for some games it's the only way.
    Note that the current version of C-BIOS does not have tape support, so if you want to save to tape, make sure you use a machine based on the original MSX system ROMs.
    Password
    Some games show a password that you can enter to resume the game at the same point. You can write down the password of course, or type it into a text file, but if you're really lazy, just make a screenshot! (press PrtScr)

    In Metal Gear

    Many people wonder how to save in Metal Gear. Here we will explain how that is done on a real MSX, which is also the way to work in openMSX.

    Metal Gear has two ways of saving games:

    1. The usual way. This is the save option from the game itself:
      1. Press F1 and then F5 and you can save on tape.
        For this to work, you need to use an MSX machine with a cassetteport. (So, you cannot use C-BIOS machines, because they do not have a cassetteport (yet)!)
      2. Insert a tape with the console: F10 (to open the console) and then type cassetteplayer new filename.wav, where filename.wav is the (optional) name of the tape file that will be created to save the game to.
      3. Type the file name in the Metal Gear game (can be any name, but you need to remember it! Choose something easy.) and press enter.
        The game will now save to that filename.wav cassette image file, under the name you just entered.
      4. To verify: in the console type: cassetteplayer rewind and then press "Y" in the game to verify.
        You can also skip this step.

      Note that saving in this way is only useful after reaching the elevator. You will continue in the last elevator you were in.

    2. The special way. This is done with the Game Master 2 cartridge. You will have to start openMSX with both the Metal Gear cartridge and the Game Master 2 cartridge. The Game Master 2 cartridge should be in a lower slot than Metal Gear (mention it before Metal Gear on the command line, for example).
      1. After booting, select "GAME" to start Metal Gear.
      2. At any time during the game, press the MSX STOP key (F8 in openMSX).
        The CAPS LED will light, which means you are now in the Game Master mode.
      3. Press CTRL to open the save and load menu.
        For this to work, you need an MSX with a diskdrive (again: not C-BIOS yet!).
      4. Insert a (blank) disk in the openMSX diskdrive.
        This can be a disk image you created with some external tool, but you can also create one from within openMSX itself: in the console, type diskmanipulator create filename.dsk 720. Then insert the disk image in the drive: diska filename.dsk.
      5. Choose "DISK SAVE" in the game from the menu.
      6. Choose "GAME DATA" in the DISK-SAVE menu.
      7. Input a filename for your save game. This will be the name of the file that that will end up on your disk image called filename.dsk.
        The game will now be saved to disk.
      8. After saving, choose "END" and press the STOP key again (F8).
        The game will resume.

    For loading back your game:

    1. The usual way:
      1. Press F1 and then F4 in the game and you can load from tape.
      2. Insert the tape image into the cassetteplayer by typing in the console: cassetteplayer insert filename.wav.
      3. Type the filename you choose for the save game in step 1c above. The game will now load the save game and start. If you want to retry, don't forget to rewind the cassette by typing cassetteplayer rewind in the console.
    2. The special way:
      1. Start Metal Gear as described in steps 2a to 2c above in the saving instructions.
      2. In the save and load menu, choose "DISK LOAD".
      3. Insert the disk image which you inserted in step 2d above, to save your game on: diska filename.dsk.
      4. Choose "GAME DATA".
      5. The game will list the files on the disk that are save game files. Select the one you want to load and it will be loaded.
      6. Choose "END" and press the STOP key (F8) to resume the game.

    What is the openMSX harddisk support?

    This is explained in the User's Manual.

    It seems the MSX hangs when running GFX9000 software; what's wrong?

    The real GFX9000 has an external video connector to which you can connect a second monitor. Because of limits of the SDL library we used to create openMSX, we cannot have more than one window for openMSX, so we cannot emulate a second monitor. To see the GFX9000 in action, you need to switch the videosource setting, which equals to a so-called SCART-switch in the real world: set videosource GFX9000. If you started openMSX without GFX9000 extension, this videosource is not available. To get your normal MSX screen back, you should type set videosource MSX. If you want to toggle with a hot key between them, it might be useful to bind a key for it. E.g.: bind F6 cycle videosource. cycle is a Tcl command that cycles through the options of the setting in the parameter.

    Instead of using the GFX9000 extension, we recommend you use the Video9000 extension (also present in several Boosted MSX machine configurations). This makes the switching go automatically in case the software is Video 9000 aware (e.g. all TNI GFX9000 products). When using this extension, the default value for videosource is Video9000.

    Note that GFX9000 emulation quality is not as good yet as the classical MSX video chips.

    See also the corresponding section in the User's Manual.

    Great, those OSD LEDs! How do I get rid of them?

    Open the console with F10 (Cmd+L on Mac) and type:

    load_icons none

    If you have save_settings_on_exit set to on, this change will be permanent, until you load another icon set. This gives you the default:

    load_icons set1 bottom

    Where is the full list of commands and settings?

    The full list of commands and settings that are available in the console can be found in the Console Command Reference. Check it out, it's really useful!

    How can I speed up openMSX? How hard is it to emulate an old MSX on a modern PC?

    Actually, openMSX is quite fast, depending on what you ask it to do. If you have minimal hardware, you should use minimal settings as well, to get decent speed. This was proven by Karloch who ran openMSX 0.6.0 on a 206MHz HP Jornada 720, see this thread on MRC. On PC's, it really helps if your graphics card is not slow, or actually, the pipe between the CPU and the graphics card. Tips on how to performance tune openMSX can be found in the Setup Guide. The developers do their best to keep the performance of openMSX as good as possible, whilst still achieving the highest level of accuracy that we can.

    In general people wonder why you need 200MHz+ machines to emulate a computer based on a 3.5MHz CPU. Well, you don't only have to emulate the CPU, you also have to emulate the VDP (21MHz), the monitor (writing at least 50 times per second at least 256×192 pixels to a window), the sound chips, etc. This is relatively simple hardware, but having that run in software with cycle accuracy is quite heavy. In general, it is a lot of work for a general purpose CPU to emulate functionality that was originally made in dedicated hardware. Rebuilding an MSX in general purpose hardware (using VHDL on FPGA) is already a lot less demanding. An example of this is the One Chip MSX.

    How do I create perfect play videos, like Vampier's YouTube videos?

    The trick is to use the reverse feature to correct any mistakes you make during game play. You just play, play, play, correcting all mistakes you make by going back in time a bit (using PageUp) when you (e.g.) die and doing it better.

    As soon as you get to the end of the game, we recommend to save the whole replay (using the reverse savereplay), so that you can always load it and play it again (using reverse loadreplay).

    Next step is to pause openMSX, load back the replay (if you saved it) or simply go back to the complete start (click at the start of the reverse bar, or use the command reverse goto 0). Now you're ready to record your heroic movie! (Note: if you want to record a video from a later point in time, just let it play until it reaches the right position, or click on another part of the reverse bar, or just search a bit with PageUp/PageDown.)

    So finally, start recording and unpause openMSX. If you want it quickly, run in full throttle (F9 by default) to speed things up. The resulting movie will be the same. If you want to split up the recorded video in chunks that are accepted by YouTube, you probably want to look at the record_chunks command.

    Easy! Now fill up YouTube with MSX material!

    How do I use openMSX for TAS (Tool-Assisted Speedruns)?

    If just creating perfect play videos isn't enough for you, you are probably going for Tool-Assisted Speedruns, of which you can find all information on tasvideos.org. The rest of this item gives you some hints to help you with this.

    First of all, the basics are explained in the previous section, of course. But you can get extra tools if you enable the TAS mode, of which we won't repeat the explanation here. If you need also a live RAM watch, check out the ram_watch command. It enables you to add live views of values on multiple addresses with several view options, like titles, formats and data types.

    For MSX TASing, we recommend to not use the (default) C-BIOS based machines, but real machines, because C-BIOS is still in development and this means your replay (in most other TASing emulators called "movie") won't be easy to get working again once a new version of C-BIOS has been released. Also, MSXturboR machines are not recommended, because it has a relatively large amount of timing inaccuracies. So which machines do we recommend? Here's a table with some very rough directions:

    game type machine
    Japanese/Korean (MSX/MSX2/MSX2+) Panasonic FS-A1WSX
    European (MSX/MSX2) Philips NMS 8250 + FMPAC

    This is mostly motivated by the fact that Japanese machines run at 60Hz interrupt frequency and the Japanese games are made for that. Besides, you will get proper Japanese characters with a Japanese machine, which are often used in such games. The mentioned machine can run practically all Japanese MSX/MSX2/MSX2+ games and has FM on board. For European games, the 50Hz (PAL) Philips NMS 8250 is recommended, just because it's very common and runs most European MSX and MSX2 software. The FMPAC extension is for better sound. There are no native European MSX2+ machines.

    When doing MSX TASing, be aware that (unlike consoles), MSX machines have full keyboard attached, and touching them means input for the MSX. And that means you will interrupt the replay if it's playing. You can avoid this (when merely viewing a replay) by using the -viewonly option when using the reverse loadreplay command. The keyboard problem also means that it's a bit tricky to assign keyboard shortcuts (with the bind command) to (e.g. TAS) functions, without also blocking MSX keys. So, be careful which keyboard shortcuts you configure and which keys you press.

    Now it's time to make a TAS and submit your entry on tasvideos.org!

    A final note: if you have problems replaying other people's TAS (or other openMSX replay), because openMSX can't find the required media (ROMs, disks, tapes, etc.), make sure you put the required media in the proper filepool; by default share/software.

    Other

    What codec do you use to record videos and where do I get it (because I only get blackness in my videos)?

    The ZMBV (Zip Motion Blocks Video) codec is used, which has been developed in the DosBox project. It enables openMSX to encode video in real time (on most systems), has a very good compression ratio and gives excellent video quality, because it is a lossless codec. Razor sharp movies! Also, because the encoder is built in, it doesn't make openMSX depend on an external codec to be installed for recording video.

    A developer of DosBox built a Win32 binary of the codec for our users. It is included with openMSX 0.6.2 (and up) for Win32. Note that the installer does not install the codec by default, so you will need to enable this option when installing. You can also download the codec separately from our web site. After unzipping the file, use the .INF file to install the codec (zmbv.dll) by right-clicking on it and selecting Install. Note that other binaries of this codec that you can find on the internet may not work, as not all of them have support for more than 8 bits per pixel recordings. Also note that this is a 32-bit codec, so it will not run inside 64-bit Windows Media Player or Windows Media Center.

    You only need this codec installer to be able to replay the videos on a Windows system. (Although a workaround could be to upload it to Google video, where it is re-encoded into a lossy MPEG-4 format.) Programs like Virtual Dub can use the codec and re-encode it in another way. On other operating systems, you can use any video player which supports this codec; e.g. mplayer. Re-encoding can be done with e.g. mencoder.

    Why do I hear serious sound glitches and/or stuttering, especially when using Windows?

    Not sure, but for some reason, especially on Windows, we can't find default settings that work for everyone. If you indeed have glitches like sound delay or stuttering, you can try the following.

    • Chances are that your CPU load is very high, which may cause stutters. Check the Performance Tuning section of the Setup Guide to see what you can do to get it lower. Also, peaks in CPU usage (e.g. caused by certain P2P programs) can cause stuttering, in some cases.
    • Try to tune the value of the samples setting. If you have stutters, double it and see if that helps. If you have delays, halve it and see if that helps.
    • Try to change the value of the sound_driver setting (only on Windows for now). After changing it try to tune the value of the samples setting again.

    Where can I find more technical info about openMSX?

    Next to the openMSX manuals (which includes documentation on how you can control openMSX from an external application, so that you can make your own GUI, launcher or debugger), also check out the doc directory of your openMSX distribution.

    My question is not listed here.

    You can look in the following places for answers:

    openMSX manuals
    We have a nice set of manuals describing most of the functionality in openMSX.
    Catapult manuals
    The manuals of Catapult, the graphical interface to openMSX.
    The Ultimate MSX FAQ
    Look here if you have a question that is not specific to openMSX, but about the MSX system itself.
    openMSX Forum on MRC
    You can post your questions here.
    openMSX IRC channel
    The openMSX developers and testers hang out on this channel (chat room). If the link doesn't work, try the webchat or get an IRC client, go to server irc.freenode.net (freenode network) and join channel #openmsx. It is possible you will not get a reaction immediately, so please ask your question, stay logged in and check from time to time if someone is active.
    openMSX-RELEASE_0_12_0/doc/manual/index.html000066400000000000000000000057341257557151200204670ustar00rootroot00000000000000 openMSX Manuals

    openMSX Manuals

    The openMSX documentation is split into a number of separate documents, each with their own purpose and intended audience.

    Compilation Guide
    This guide describes how you can get the openMSX sources and compile them. If you downloaded a binary release, you can skip this.
    Setup Guide
    This guide describes how you can configure openMSX to emulate actual MSX machines. It also describes how you can have openMSX start up with your personal settings, how you can configure openMSX and your system for optimal performance and several other configuration related topics.
    User's Manual
    This manual describes all the things you can do with openMSX once it is fully running.
    FAQ
    Answers some frequently asked questions. Of course you shouldn't have any, after reading the manuals...
    Console Command Reference
    An overview of all commands and settings that can be used from the openMSX built in console. Check this if you want to know exactly how to control openMSX. Because the current openMSX Catapult GUI is running behind in functionality, it is also useful to read this for some common settings not supported in Catapult yet.
    Using Diskmanipulator
    The diskmanipulator command is so powerful that we made a separate manual for it. Use it to create (hard)disk images, import files to them, export files from them, etc.
    Controlling openMSX from External Applications
    This is a guide for application developers who want to control openMSX from their own programs. Very useful if you're planning to make a launcher, GUI, debugger or another kind of external program that needs to control openMSX.

    There is additional documentation for (would-be) developers in the doc directory in the openMSX code tree.

    To the openMSX Home Page.

    openMSX-RELEASE_0_12_0/doc/manual/manual-minty.css000066400000000000000000000006761257557151200216170ustar00rootroot00000000000000@import "manual.css"; p.version { border-top: 2px groove #60C090; } h1 { border-bottom: 2px groove #60C090; } h2 { background-color: #90FFC0; padding-left: 4pt; } h3 { background-color: #C8FFE0; padding-left: 4pt; } h4 { background-color: #E8FFF0; padding-left: 4pt; } div.note { background-color: #E8E8E8; } a.internal, a.external:visited { color: #184830; } a.external:link { color: #30A860; } a.external:active { color: #40F080; } openMSX-RELEASE_0_12_0/doc/manual/manual-purple.css000066400000000000000000000004761257557151200217640ustar00rootroot00000000000000@import "manual.css"; p.version { border-top: 2px groove #8070C0; } h1 { border-bottom: 2px groove #8070C0; } h2 { background-color: #C8C0FF; padding-left: 4pt; } h3 { background-color: #E8E4FF; padding-left: 4pt; } h4 { background-color: #F4F0FF; padding-left: 4pt; } div.note { background-color: #E8E8E8; } openMSX-RELEASE_0_12_0/doc/manual/manual.css000066400000000000000000000031261257557151200204520ustar00rootroot00000000000000body { color: black; background-color: white; } ul, ol { padding-left: 16pt; } ul.toccat { list-style: none; padding-left: 0pt; } ul.toccat ul { list-style: disc; padding-left: 0pt; } ol.toc, ol.inlinetoccat { list-style: none; padding-left: 0pt; } ol.inlinetoccat li { font-weight: bold; text-transform: uppercase; margin-top: 0.5em; } ol.inlinetoc li { font-weight: normal; text-transform: none; padding-right: 8pt; display: inline; } p, dl, ul, ol, h5 { margin-left: 16pt; } ol.alpha { list-style-type: lower-alpha; } p.image { text-align: center; } p.version { padding-top: 1em; margin-left: 0pt; font-size: 50%; text-align: right; } dt.toc { font-size: 150%; font-weight: bold; } dd { margin-left: 16pt; margin-bottom: 0.5em; } div.commandline, pre, code { font-family: monospace; letter-spacing: 0.1em; } code { margin-left: 0.1em; margin-right: 0.1em; } div.commandline, pre { margin-left: 32pt; margin-top: 0.5em; margin-bottom: 0.5em; } div.note { font-style: italic; padding: 0.5em 4pt 0.5em 16pt; margin-top: 0.5em; } div.subsectiontitle { font-style: italic; margin-left: 16pt; } div.examples { margin-left: 32pt; } p.todo { color: red; } a.internal, a.external:visited { color: #000080; } a.external:link { color: #0000FF; } a.external:active { color: #8080FF; } table.border_enabled { border-collapse:collapse; border: 1px solid black; } table.border_enabled tr th { border: 1px solid black; } table.border_enabled tr td { border: 1px solid black; } table { margin-left: 32pt; } th { text-align: left; } td { vertical-align: top; padding-right: 32pt; } openMSX-RELEASE_0_12_0/doc/manual/nocartfound.png000066400000000000000000000217711257557151200215210ustar00rootroot00000000000000PNG  IHDRKsRGBbKGD pHYsaa?itIME91" IDATxݱI.o!M@O {%D\j $= #lD=4vUwǞyhd2'O `Ϗ(?\x?ܲnH9qv?F!?KMHv;9 Û: /}_UJOG_ſUeX璣,;Cxsx9I<{u;9-izsهTKzFnV5ꣴvK^[ןF{AK˼y\nNRn9%7 #*h*h;0ٻl 05`@$` $`@ @ @/g*"l/g>TQߡOIݱ*5Rs {(^)..e{q8>'~JƤX {{9 }?^˗x6>?q>(N[xHtbϺn:>Z ijg|v|3޿xx/v<8p8ȼyL~}OdO'Y(]1ޟ15% ,JorV8?x&d*g]pޚeΞs x3gg;gNm"^>H.篁~VM5omEx [Ӝ|~ٗ/66ԛJw)+'~>زYs}iOEpM;+&xMnO4Xg^*͙!w^z.ױZc'#K;Af}kӭ cUҽ_9mr_iSÑZsMm?+Ҝ}%o߮ڻw7HFsϲKp鳆92j?gU4-9^Zyww|qy)#Kg٥ cy[UpR9rׅYUve~˾K-#KW 8ݬױ^hz>5ke_^N] 0  H 0H 0H 0  H *3VGÇ*[ro춉~̴sF&5Zˎq> qzzE|_%bw[.Ӝ6ۏڣvbȤ[w1$`˗x6>9/_Ǐ1ׯuе|~>xtM9;njt눈vjQxL;?w·ᨂϟ}ƫWo\A?xPU:-ijg|V79=wF&U|}m^뱭Gy3m=#­5HMr5Ԝ-sxzZe}lxߵOx%y҄Z_:zggquUۋ|pC9ajjY/G]=^yRPϟnw*˿ r.]C-=CFoLFdIfk&wJS]8?N+ p*{,V;˳BӛlIHda2ެ]yǫJ 4J͟_/^$LppZtDkƇ|f̄d&n`5@ 83;CnYkn*W}^3>UޒB3H_-^;zyY]ϾUOiU5 D[Ҟ&5\O]\:O|<)}|@׎^:}~?Wu)U5ڭWI)nZ3>5';ǧTKi?/؛?(Yͧk}yshkyuo̟+4;~?{f n0H 0H 0  H  H 0H 0  0  H 0H 0H 0 |V矱ZED\]Ňquux#Fh,yc<^g|/~$}3J?<8Vϋ/_^ӛw $;s{xF_]?*"vEÇq~N$asFrUU*Ϻw~Rkӏ̼ g;wkx_%Z]jŋ?|U:u0/Nby0;mV^'GW:U&rVBJm=>uzְ.=|[k*)toɎϟebZ:R-O?bg E޿^ۤ3]^~BϫW# x".._~6yìVL]}fjy[G6~x,dϟ#"./˒D, UTeƩWO`W&w6i/K诩OoA &Í&҄.ZOݙxr7ND+j?g'ǻ+>{Ǐ1 )_￿dnMkx? $Zk߬mKz5ϒ+ͩ\Xz2*۝KkO0 cz=ruaNZRo^e+- z1\TR2yyvn߭nwkM=5gJ[&7' ssCpMĜUPKw㓟J{Z?g%`'\;q]}u`o\9FYq0ܗ`kswf?kឰv~^š(`kր6> 0  H 0H 0H }17*-\[ϸ]y-> 7Q~zq>ۏּbg:9gϮmjxל歁JԺzjYgՓ%G! r83snMn98fzn x|~? zOvݚTկy UI=9>;θϟm%ͳ6{y[:OywF6Ol7sZ*gHH[9xwb pDy34z9~jGgWlxRom իL3<'~gG_z|g|ٟnTgHH|;=Gn=? qo3zn^ ֵqs>uM@x^jooj3~=O$;s[浣9xjMNE5ʼn^gTFiQ:%jඎoDF/۟X%` 4?Lf^;:#O_mӫԚ5ω^'Qڟ*,ϼn~z zc X>+J~~')Fݩo{;F>K֢?KZ>y&l~5o#e >8`kpԬLv5\q0+Lmk? Xkv5\q0ٹoպ NɿkvUjN]%%Rw^RZulXH5)Ac*T]z5ݿV]OY3[Xtj7t|mUuVw͒ۅ|S/vYրsoU-X3^Xߴ{=Z;wSsԓ4׭wp??i{=Z;ֿ-˿q>>T| [H9gYRk`>|S߷tM ]Vk>VsV].4u{,9ʡ-i7Lj{G{s? kB齝F}yC=]#"ΊM=}M>;vr${w=o͕eN%Wc߂縷+gZv:q7eubA=ںsziüoߣ^rNO0Ժs]}V-G[w|[=ykD͇y߈s5ݓ,"L[W: x^}PKp9*-Ngstcć92[Ŗ}IY* ߝ`O GWd?WZt+~oz9>83#sća ܙsȾZKΈ 7K맶zUnƬzuz9#?j]7\Uշn]yy}_oXtM|gĥSK+Fa=OV=ݜYbftFfxNT몘Kgi9c8p#5֯W_TO+xy=ȫ4^loW_/TTt>j?u|?^}H{Mk`"nFԩ*rz;3ͫ[7[;9%/_:j]VKCS1ϛa^%׭o˫,v"&b%U(,3?g>;:VycnW x xI}~J% 'b%U(FḋvG1/9z;a6*iR7NW^eɒW+Jm:j#?>յLϒq[>_:R7?P ~mi*:q;]8Ŗ[5_:ԝk:ϛKߚ ab*ߕNھV%齞;˜O=yayU;c>zujHwz0I0w{ye܃SM]}sݟSM؝2-y\_mT}S@&nKڨ̈%p_zo Sn}Vz_ ?\7;'W9 wM&w]ҟ9O{ gʹLjG Iݱv~o3?pYբ~gweh5:>?ZZou=y~%mIDAT[ԯ8`kZLb/UyivP?nQ@Sw&zgg# K[;'U,7|fF[]WAO S;KtgnQ?Op' :7/_;x :o/m/wUր6> 0  H 0H 0H 0  H 0H 0H 0  H  H 0H 0  0  H 0H 0H 0  H 0H 0H 0  H  H 0H 0  0  H 0H 0H0H 0H 0  H  H 0H 0  0  H 0H 0H 0  H 0H 0H 0  H  H 0H 0  0  H 0H 0H 0  H 0H 0H 0  H , 0  H 0H 0H 0  H 0H 0H 0  H  H 0H 0  0  H 0DхIENDB`openMSX-RELEASE_0_12_0/doc/manual/openmsx-control.html000066400000000000000000000253121257557151200225210ustar00rootroot00000000000000 Controlling openMSX from External Applications

    Controlling openMSX from External Applications

    Introduction

    The architecture of openMSX is that the main emulation code is all in a separate program, the main openMSX executable. Debugger GUI's, launcher GUI's, etc., can be external programs that control openMSX. This document explains you how you can control openMSX from your own application.

    Note: This document was not written for normal end users, but only for developers who are interested in writing their own application that controls openMSX.
    Disclaimer: it is possible that some update events are still missing and it is also possible that the structure of the replies and commands change. We will do our best to be backwards compatible, though.

    Connecting

    There are multiple ways to connect to openMSX. The first (and oldest) way is using a pipe. On Windows you can use a named pipe, on other systems you use stdio. To enable this, start openMSX like this:

    openmsx -control stdio

    or for Windows:

    openmsx -control pipe

    The second method is using a socket. Connecting on non-Windows systems goes with a UNIX domain socket. openMSX puts the socket in /tmp/openmsx-<username>/socket.<pid>. The /tmp/ dir can be overridden by environment variables TMPDIR, TMP or TEMP (in that order).

    On Windows (which does not support UNIX domain sockets), the socket is a normal TCP socket. The port number is random between 9938 and 9958. This is done to enforce applications to deal with multiple running openMSX processes. The port number will be put in the following text file:

    %USERPROFILE%\Documents and Settings\<username>\Local Settings\Temp\openmsx-default\socket.<pid>

    or, when %USERPROFILE% does not exist:

    %TMPDIR%\openmsx-default or
    %TMP%\openmsx-default or
    %TEMP%\openmsx-default or as a last resort:
    C:\WINDOWS\TEMP

    After connecting, openMSX expects XML input on the channel and it will also give you output. This is explained in the next section.

    Communication

    After connecting, openMSX expects XML input on the channel (pipe or socket) and it will also give you output in XML format. The first output it gives is this:

    <openmsx-output>
    

    On non Windows systems you can easily try it out by just starting openMSX via the stdio method, like explained above. You give XML commands via the keyboard in the terminal and openMSX will print its responses on the terminal as well.

    This first output is the opening tag (<openmsx-output>). All messages that are normally printed on stdout in the terminal from which you start openMSX are in a <log> tag. The level can be "info" or "warning" and the message is in the text node itself.

    When you want to start communicating back, you always have to start with the opening tag first:

    <openmsx-control>

    When starting openMSX with the -control option, it will not show a window: it starts with the 'none' renderer. So, a nice example (if you're still experimenting on the command line) would be to type this:

    <command>set renderer SDL</command>

    With the <command> tag you can give any openMSX console command to openMSX. The commands are documented in the Console Commands Reference.

    Every <command> will result in a reply from openMSX. In the above case it will be:

    <reply result="ok">SDL</reply>
    

    The order is guaranteed, i.e. the replies will follow in the same order as in which you gave the commands to openMSX. In this reply example, you see that the command succeeded (result=ok) and it also gives you the actual result text that would also be printed on the console. In this case, the value of the renderer setting. When a command fails, you get something like this:

    <command>biep</command>
    <reply result="nok">invalid command name "biep"
    </reply>
    

    "biep" is not a valid command, and openMSX tells you this via a "nok" reply with the error message in the text node.

    The next important thing is events. When you use this interface to control openMSX, you want to know when things change. For this, you can enable events for certain event classes.

    An example:

    <command>openmsx_update enable led</command>

    This command will enable updates about LED events. So, when a LED changes, you get messages like this:

    <update type="led" machine="machine1" name="power">on</update>
    

    So, when you get <update> tags, openMSX tells you that something changed. In this case, it is a LED update, for the machine with ID "machine1". The name of the LED is "power" and the value is in the text node: on.

    Here is a list of the currently available event types and when they are sent:

    hardware hardware changes occurred, like a change of machine
    led LED status changed
    media media (disk images, cartridges, etc.) changed
    plug a pluggable got plugged
    unplug a pluggable got unplugged
    setting the value of a setting changed
    setting-info the properties of a setting changed (e.g. number of options changed)
    status status changed, currently this is pause and debug break status
    extension extensions changed (add/remove)
    sounddevice sounddevices changed (add/remove)
    connector connectors changed (add/remove)

    Update Examples

    Someone changed machines from Boosted MSX2 to Toshiba HX-10 at run time:

    <update type="hardware" name="machine2">add</update>
    <update type="hardware" machine="machine2" name="carta">add</update>
    <update type="hardware" machine="machine2" name="cartb">add</update>
    <update type="hardware" machine="machine2" name="cassetteplayer">add</update>
    <update type="hardware" machine="machine1" name="diskb">remove</update>
    <update type="hardware" machine="machine1" name="diska">remove</update>
    <update type="hardware" machine="machine1" name="carta">remove</update>
    <update type="hardware" machine="machine1" name="cartb">remove</update>
    <update type="hardware" machine="machine1" name="cartc">remove</update>
    <update type="hardware" machine="machine1" name="cassetteplayer">remove</update>
    <update type="hardware" name="machine1">remove</update>
    <update type="hardware" name="machine2">select</update>
    

    CAPS LED went to OFF:

    <update type="led" machine="machine2" name="caps">off</update>
    

    A tape was inserted in the cassetteplayer:

    <update type="media" machine="machine2" name="cassetteplayer">/home/manuel/msx-soft/tapes/Zoids.zip</update>
    

    The cassetteplayer got into play mode:

    <update type="status" machine="machine2" name="cassetteplayer">play</update>
    

    Someone plugged in a joystick:

    <update type="plug" machine="machine2" name="joyporta">joystick1</update>
    

    And unplugs it again:

    <update type="unplug" machine="machine2" name="joyporta"></update>
    

    Someone set the maxframeskip setting to 12:

    <update type="setting" name="maxframeskip">12</update>
    

    openMSX got paused:

    <update type="status" name="paused">true</update>
    

    openMSX got into a debug break state:

    <update type="status" machine="machine1" name="cpu">suspended</update>
    

    openMSX got out of debug break state:

    <update type="status" machine="machine1" name="cpu">running</update>
    

    Someone inserted a Philips NMS-1205 Music Module:

    <update type="sounddevice" machine="machine2" name="Philips NMS 1205 Music Module MSX-Audio 8-bit DAC">add</update>
    <update type="connector" machine="machine2" name="audiokeyboardport">add</update>
    <update type="sounddevice" machine="machine2" name="Philips NMS 1205 Music Module MSX-Audio DAC">add</update>
    <update type="sounddevice" machine="machine2" name="Philips NMS 1205 Music Module MSX-Audio">add</update>
    <update type="extension" machine="machine2" name="Philips_NMS_1205">add</update>
    

    And with this, you should have all info that you need to make any external application that can control openMSX.

    More real world examples can be found here:

    • in the Contrib directory of openMSX (openmsx-control*)
    • in the code of the old openMSX Catapult (C++ via pipe)
    • in the code of the new openMSX Catapult (Python, still via pipe)
    • in the code of the openMSX GUI Debugger (C++ via socket)
    openMSX-RELEASE_0_12_0/doc/manual/setup.html000066400000000000000000001015371257557151200205160ustar00rootroot00000000000000 openMSX Setup Guide

    openMSX Setup Guide

    Contents

    1. 1. Introduction
      1. 1.1 New Versions of this Document
      2. 1.2 Purpose
      3. 1.3 Contributors
      4. 1.4 Revision History
    2. 2. Machines and Extensions
    3. 3. System ROMs
      1. 3.1 C-BIOS
      2. 3.2 Dumping ROMs
        1. 3.2.1 Tools
        2. 3.2.2 Legal Issues
      3. 3.3 Downloading ROMs
      4. 3.4 Installing ROMs
        1. 3.4.1 ROM Locations
        2. 3.4.2 Checksums
        3. 3.4.3 How to handle split ROMs
    4. 4. Palcom Laserdiscs
    5. 5. User Preferences
    6. 6. Performance Tuning
      1. 6.1 OpenGL
      2. 6.2 Various Tuning Tips
    7. 7. Android Tips
    8. 8. Writing Hardware Descriptions
    9. 9. Contact Info

    1. Introduction

    1.1 New Versions of this Document

    The latest version of the openMSX manual can be found on the openMSX home page:

    http://openmsx.org/manual/

    You can also use this URL to get up-to-date versions of the hyper links if you printed out this manual.

    1.2 Purpose

    This guide is about openMSX, the open source MSX emulator that tries to achieve near-perfect emulation by using a novel emulation model. You can find more information about openMSX on the openMSX home page. You can also download the emulator itself from there.

    openMSX is not completed yet, which means that most things work but not all features are implemented yet. Many emulation features are implemented, but in terms of user interface it is rather bare bones. However, because the emulation is already pretty good, it would be nice if non-insiders would be able to play with it, too. For those people, we have written this guide.

    This guide describes the setup of openMSX. After installation, openMSX is ready to run using C-BIOS and the default settings. In this guide you can read how to configure openMSX to emulate actual MSX machines (such as Panasonic FS-A1GT). It also describes how you can have openMSX start up with your personal settings, how you can configure openMSX and your system for optimal performance and several other configuration related topics.

    Disclaimer: We do not claim this guide is complete or even correct. What you do with the information in it is entirely at your own risk. We just hope it helps you enjoy openMSX more.

    1.3 Contributors

    The following people contributed to this document in one way or another:

    • Jorrith Schaap
    • Manuel Bilderbeek
    • Maarten ter Huurne
    • other openMSX developers

    Thanks to all of them!

    1.4 Revision History

    For the revision history, please refer to the commit log.

    2. Machines and Extensions

    We use the word machine to refer to a specific MSX model. For example, the Sony HB-75P is a machine. openMSX does not have a fixed machine hardcoded into it. Instead, many different MSX machines can be emulated. The details of a machine are described in an XML file. This file describes how much memory a machine has, what video processor it has, in which slots its system ROMs are located, whether the machine has a built-in disk drive etc. openMSX reads the machine description XML and will then emulate exactly that MSX machine, which can be anything from an MSX1 with 16 kB of RAM to the MSXturboR GT.

    The openMSX distribution contains XML files describing many existing MSX models. You can find them in the share/machines directory. If you want to run one of those machines, you also need the system ROMs for that machine. See the next chapter for more information on system ROMs. You can also create your own machine descriptions, to expand existing MSX models or to create your own fantasy MSX. There are currently some of such fantasy MSX machines, based on real MSX machines, shipped with openMSX. Examples are a machine called "Boosted_MSX2_EN" (a European MSX2 with loads of hardware built in) and one called "Boosted_MSX2+_JP" (a Japanese MSX2+ with loads of hardware built in). You can find some more information about them in their accompanying txt file in share/machines/. More about creating fantasy MSX machines in a later chapter.

    An extension is a piece of MSX hardware that can be inserted into a cartridge slot to extend the capabilities of an MSX. Examples of extensions are the Panasonic FMPAC, the Sunrise IDE interface and an external 4MB memory mapper. Extensions, like machines, are described in XML files. You can find a lot of predefined extensions in the share/extensions directory. Some extensions need ROM images in order to run, similar to system ROMs.

    In general, the XML files that describe the hardware configuration are called "hardware configuration XML files".

    3. System ROMs

    An MSX machine consists of a lot of hardware, but also contains some software. Such software includes the BIOS, MSX-BASIC, software controlling disk drives and built-in applications (firmware). openMSX emulates the MSX hardware, but it needs MSX system software to emulate a full MSX system. Because the internal software is located in ROM chips, it is referred to as system ROMs.

    The software in the system ROMs, like most software, is copyrighted. Depending on your local laws, there are certain things you are allowed to do with copyrighted software and certain things you are not allowed to do. In this manual, a couple of options are listed for providing system ROMs to your openMSX installation. It is up to you, the user, to select an option that is legal in your country.

    3.1 C-BIOS

    C-BIOS stands for "Compatible BIOS". It tries to be compatible with the MSX BIOS found in real MSX machines, but it was written from scratch, so all copyrights belong to its authors. BouKiChi, the original author of C-BIOS, was kind enough to allow C-BIOS to be distributed together with openMSX. Since then, Reikan took over maintenance of C-BIOS and the license was changed to give users and developers even more freedom in using C-BIOS. Even later, C-BIOS was moved to a SourceForge.net project, with several new maintainers. Every now and then, an updated version of C-BIOS is released. You can wait for it to be included in the next openMSX release, or download it directly from the C-BIOS web site.

    C-BIOS can be used to run most MSX1, MSX2 and MSX2+ cartridge-based games. It does not include MSX-BASIC and does not support disk drives yet, so programs depending on that will not run. openMSX contains several machine configurations using C-BIOS. The machine C-BIOS_MSX1 is an MSX1 with 64 kB RAM. The machine C-BIOS_MSX2 is an MSX2 with 512 kB RAM and 128 kB VRAM. The machine C-BIOS_MSX2+ is an MSX2+ with 512 kB RAM, 128 kB VRAM and MSX-MUSIC. The latter is the default machine for openMSX after installation, so if you change nothing to the openMSX configuration, then C-BIOS_MSX2+ is the machine that will be booted. The mentioned machines have an international keyboard layout and character set and run at 50Hz (PAL) interrupt frequency. From C-BIOS 0.25, there are also localized versions available: Japanese and Brazilian types. You can recognize them easily.

    It is always legal for you to run the C-BIOS ROMs in openMSX. You are allowed to use C-BIOS and its source code in various other ways as well, read the C-BIOS license for details. It is located in the file README.cbios in the Contrib directory.

    3.2 Dumping ROMs

    If you own a real MSX machine, you can dump the contents of its system ROMs to create ROM images you can run in openMSX. This way, you can emulate the MSX machines you are familiar with.

    3.2.1 Tools

    The easiest way to dump system ROMs is to run a special dumping tool on your real MSX, which copies the contents of the system ROMs to disk. Sean Young has made such tools, you can find the tools and documentation on BiFi's web site. These tools can also be used to dump cartridge ROMs, which may be useful later, if you want to use certain extensions or play games.

    3.2.2 Legal Issues

    Using ROMs dumped from machines you own is generally considered a proper thing to do in the MSX community. When the MSX machine was bought in a shop years ago, you or the person that originally bought it paid money for the MSX machine. A small part of that money paid for the software in the system ROMs. However, we are no legal experts, so it is up to you to check whether it is legal in your country to use dumped ROMs of machines you own.

    3.3 Downloading ROMs

    Some WWW and FTP sites offer MSX system ROMs as a download. Some MSX emulators include system ROMs in their distribution. Downloaded system ROMs can be used in the same way as system ROMs you dumped yourself, see the previous section.

    It may be illegal in your country to download system ROMs. Please inform yourself of the legal aspects before considering this option. Whatever you decide, is your own responsibility.

    3.4 Installing ROMs

    3.4.1 ROM Locations

    The easiest way is to use a so-called file pool; a special directory where openMSX will look for files (system ROMs, other ROMs, disks, tapes, etc.). The default file pool for system ROMs is the systemroms sub directory. The best way is to make a systemroms sub directory in your own user directory, which is platform dependent

    PlatformUser directory
    Old versions of Windows (e.g. Windows XP)My Documents\openMSX\share
    Modern versions of Windows (e.g. Windows 7)c:\Users\<user name>\Documents\openMSX\share whereby <user name> stands for your Windows login name
    Unix and Linux~/.openMSX/share
    Android<external storage>/openMSX/share, whereby <external storage> stands for the default external storage location of your Android device. On the Samsung Galaxy Nexus it is for example /sdcard

    That way, you do not need special privilages. Furtermore, the installer won't touch them for sure on Windows nor on Android.

    A template for the systemroms sub directory is present in the installation directory of openMSX, which is also platform dependent:

    PlatformTypical openMSX file pool installation directory
    Windows (any version)C:\Program Files\openMSX\share
    Unix and Linux/opt/openMSX/share or /usr/share/openmsx
    Android<internal app install dir>/org.openmsx.android.openmsx/files/openmsx_system, whereby <internal app install dir> stands for the default internal app installation directory of your Android device. On the Samsung Galaxy Nexus it is for example /sdcard/Android/data

    So, you can just put all your system ROMs in that directory. More info about file pools is in the documentation of the filepool command. If you can't get this working, please read one of the next sections about Checksums.

    For advanced users, it is also possible to let openMSX load a specific set of ROM images for a machine, independent of any file pool or the checksums of the ROM images. For that you copy the ROM file with the name and path as mentioned in the hardware configuration XML file that describes the machine, relative to the path of that machine description file. For example, if you dumped the ROMs of a Philips NMS 8250 machine, copy them to share/machines/roms, because in the machine description file (in share/machines/Philips_NMS_8250.xml) the name of the ROMs is like this: roms/nms8250_msx2sub.rom. We recommend to not use this feature, but use the file pools as mentioned above.

    3.4.2 Checksums

    All necessary system ROM files used in machines and extensions are primarily identified with a checksum: a sha1sum. This enables openMSX to find the right ROM file from one of the file pools of type system_rom, without depending on the file name. So the actual content is guaranteed to be what was intended. If the ROM is explicitly specified in the configuration file (which is also supported) and the sha1sum doesn't match, a warning will be printed.

    You can also manually check whether you have the correct ROM images. The value in the <sha1> tag(s) in the hardware configuration XML files contain checksums of ROM images that are known to work. You can compare the checksums of your ROM images to the ones in the hardware configuration XML files with the sha1sum tool. It is installed by default on most UNIX systems, on Windows you would have to download it separately. If the checksums match, it is almost certain you have correct system ROMs. If the checksums do not match, it could mean something went wrong dumping the ROMs, or it could mean you have a slightly older/newer model which contains different system ROMs.

    A typical case in which you can have problems with checksums (or ROMs not getting found in a file pool) is disk ROMs. The ROM dump can be correct, and still have a different checksum. This is because part of the ROM is not actually ROM, but mapped on the registers of the floppy controller. When you are sure it is correct, don't put it in a file pool, but put it in the proper directory, which is explained above. Alternatively, you could add the checksum in the XML file that describes the machine you made the ROM dump for (multiple checksums can be present, they will be checked in the same order as they are in the file).

    3.4.3 How to handle split ROMs

    The machine configurations bundled with openMSX often refer to ROM files that span multiple 16 kB pages. For example, in the NMS 8250 configuration, the BIOS and MSX-BASIC are expected in a single 32 kB ROM image. If you created two 16 kB images when dumping or got those from downloading, you can concatenate them using tools included with your OS. In Linux and other Unix(-like) systems you can do it like this:

    cat bios.rom basic.rom > nms8250_basic-bios2.rom

    In Windows, open a command prompt and issue this command:

    copy /b bios.rom + basic.rom nms8250_basic-bios2.rom

    4. Palcom Laserdiscs

    The Pioneer PX-7 and Pioneer PX-V60 are both emulated including an emulated Laserdisc Player, making it possible to run Palcom Laserdisc software.

    The laserdisc must be captured before it can be used with an emulator. The file must adhere to the following rules:

    • Use the Ogg container format
    • Use the Vorbis codec for audio
    • Use the Theora codec for video
    • Captured at 640×480, YUV420
    • A bitrate of at least 200kpbs for audio, otherwise the computer code encoded on the right audio channel will degrade too much for it to be readable
    • Theora frame numbers must correspond to laserdisc frame numbers
    • Some laserdiscs have chapters and/or stop frames. This is encoded in the VBI signal, and must be converted to plain text. This must be added to the theora meta data

    The metadata for chapters and stop frames has the form "chapter: <chapter-no>,<first-frame>-<last-frame>" and stop frames are "stop: <frame-no>". For example:

    chapter: 0,1-360
    chapter: 1,361-4500
    chapter: 2,4501-9450
    chapter: 3,9451-18660
    chapter: 4,18661-28950
    chapter: 5,28951-38340
    chapter: 6,38341-39432
    stop: 4796
    stop: 9089
    stop: 9178
    stop: 9751
    stop: 14818
    stop: 14908
    stop: 18270
    stop: 18360
    stop: 18968
    stop: 24815
    stop: 24903
    stop: 28553
    stop: 28641
    stop: 29258
    stop: 34561
    stop: 34649
    stop: 38095
    stop: 38181
    stop: 38341
    stop: 39127
    

    Note that the emulated Pioneer PX-7 and Pioneer PX-V60 are virtually identical, except that the Pioneer PX-7 has pseudo-stereo for its PSG.

    5. User Preferences

    Almost all user preferences can be done via the openMSX console, at openMSX run time. This is more thoroughly explained in the User's Manual. In short: you can save your settings with the save_settings command, which will save the settings in your personal settings file. The settings file will be loaded every time openMSX starts.

    One of the preferences is the default machine: the machine openMSX uses if you did not specify one on the command line. The name of this setting is default_machine. Switching machines at run time is done via the machine command (more about this in the User's Manual).

    By using the bind command you can create custom key bindings. These bindings will also be saved as settings in your settings file if you issue a save_settings command.

    The other settings are discussed in the User's Manual and there is an overview in the Console Command Reference.

    If you're a power user and want to specify commands which are executed at the start of each openMSX start up, put those commands in a text file, one command per line (i.e. a script) and put it in the share/scripts directory. You can also explicitly specify a Tcl file to be loaded and executed on the openMSX commandline. For this, use the -script command line option, which has the filename of the Tcl script as argument.

    6. Performance Tuning

    This chapter contains some tips for tuning the performance of openMSX on your system.

    6.1 OpenGL

    The SDLGL-PP renderer needs hardware acceleration to run at a decent speed, with support for OpenGL 2.0. Practically all modern PC hardware has this, but if your hardware doesn't, use the SDL renderer instead.

    Getting OpenGL running hardware accelerated used to be a little cumbersome in some situations. However, nowadays there is a big chance that your system already has hardware accelerated OpenGL supported in the default installation of your Xorg or Windows environment. Just make sure you install the development header files for the OpenGL library if you want to compile openMSX with support for it.

    You can verify hardware acceleration on your Linux system by typing glxinfo on the command line. If you have everything working, this command should output a line like this: direct rendering: Yes.

    In any case: if you have a decent video card and you have hardware acceleration working, you can get a lot better performance of openMSX by using the SDLGL-PP renderer.

    6.2 Various Tuning Tips

    CPU and graphics performance varies a lot, depending on the openMSX settings and the MSX hardware and software you're emulating. Some things run fine on a 200MHz machine, others are slow on a 2GHz machine.

    If openMSX runs slow, you can try the following measures:

    • Disable horizontal stretching (mostly useful when using the SDL renderer), which is enabled by default: set horizontal_stretch 320.
    • Disable the reverse feature (especially if the platform you're running on has a low amount of RAM), which is enabled by default on most platforms: set auto_enable_reverse off
    • Make sure there are no CPU or I/O heavy background processes is running. Downloads, P2P software, distributed calculation efforts, search indexers etc may grab the CPU from time to time, leaving less time for openMSX to do its job. Even if they only do so only once in a while, it may be enough to cause emulation to stutter.
    • Increase the number of frames that may be skipped (set maxframeskip 10, for example).
    • Use a scale_factor of 1 with the SDL renderer (75% less pixels to fill compared to the default setting of 2). For most games (and any MSX1 software) it works perfectly, especially when using full screen. The drawback: no special effects at all, not even scanlines. Turning off special effects can also mean a speed up, of course.
    • Use the fast resampler instead of the hq or blip one.
    • Emulate MSX software that uses less sound channels, for example MSX-MUSIC (maximum 9 channels) instead of MoonSound (maximum 18+24 channels). Or run simpler software altogether (e.g. MSX1 software instead of turboR software).

    7. Android Tips

    This section has some tips specific to the Android version

    • If the sound is choppy, you should increase the SDL audio buffer size as follows
      • Start openMSX
      • Tap the button "Change device configuration" on the SDL Splash screen that is shown for 3 seconds during the startup
      • Tap the button "Size of audio buffer" in the device configuration screen
      • Change the buffer size from "Very small" to "Small"
      • Tap "OK" to close the device configuration screen
    • By default, the Android build has 4 virtual buttons on the right bottom side, mapped to functions as in following table:
      ConsoleopenMSX virtual keyboard
      Joystick button 2Joystick button 1
    • It is possible to change these mappings using the "Change device configuration" button on the SDL Splash screen
    • By default, the Android build has a button in the left upper corner of the screen to open the Android virtual keyboard
    • If you use the Android keyboard a lot, then it is advisable to install the "Hackers keyboard" app from Google Play and to use that one instead of the standard Android keyboard. It is much more convenient for openMSX and other old-school applications.

    8. Writing Hardware Descriptions

    There are two ways to use extra devices in your emulated MSX: you can use a shipped extension (which is analogous to inserting a cartridge with the device into the MSX) or you can modify the hardware configuration file (which is analogous to open the MSX and build in the device). As in the real world, extensions are easier to use, but modifying the machine gives you more possibilities. Normal usage of machines and extensions is covered in the User's Manual; this chapter tells you how you can create or modify these hardware descriptions, which is a topic for advanced users and definitely something very few people will (want to) do. By editing the hardware configuration XML files, you can for example increase the amount of RAM, add a built-in MSX-MUSIC, add a disk drive, create extra cartridge slots etc.

    You can modify an MSX machine (e.g. to add devices) by editing its hardware configration XML file. So, let's make a copy of share/machines/Philips_NMS_8250.xml and put it in share/machines/mymsx.xml. It's the config we are going to play with; our custom MSX. Note: it is convenient to use the user directory (see above) to store your home-made machines, instead of the openMSX installation directory.

    The easiest thing to do is to copy and modify fragments from other existing configurations that can be found in share/machines or share/extensions. For example, to add an FMPAC to the 8250, just copy it from the share/extensions/fmpac.xml to some place in your mymsx.xml file (between the <devices> and </devices> tags!):

        <primary slot="2">
          <secondary slot="1">
            <FMPAC id="PanaSoft SW-M004 FMPAC">
              <io base="0x7C" num="2" type="O"/>
              <mem base="0x4000" size="0x4000"/>
              <sound>
                <volume>13000</volume>
                <balance>-75</balance>
              </sound>
              <rom>
                <sha1>9d789166e3caf28e4742fe933d962e99618c633d</sha1>
                <filename>roms/fmpac.rom</filename>
              </rom>
              <sramname>fmpac.pac</sramname>
            </FMPAC>
          </secondary>
        </primary>
    

    Don't forget to add the fmpac.rom file to one of your system_rom file pools.

    Because we changed the FMPAC from extension to built-in device, we have to specify in which slot the FMPAC is residing inside the modified 8250. So, we should replace the slot="any" stuff, with a specified slot as you can see in the above fragment. The number in the slot attribute of the <primary> tag indicates the primary slot of the emulated MSX you're editing. In this case the second cartridge slot of the NMS-8250 is used. <secondary> means sub slot. If we leave it out, the slot is not expanded and the primary slot is used. If we use it like in the above example, it means that slot 1 (of the <primary> tag) will be an expanded slot. If a <primary> tag has the attribute external="true", this means that the slot is visible on the outside of the machine and can thus be used for external cartridges like extensions and ROM software. As explained above, the parameter filename can be adjusted to the name of your (64 kB!) FMPAC ROM file (note: if the file is not 64 kB (65536 bytes) in size, it won't work). "balance" defines to what channel the FMPAC's sound will be routed by default: in this case most of the sound goes to the left channel and a little bit goes to the right channel. "sramname" specifies the file name for file in which the SRAM contents will be saved to or loaded from. The saved files are compatible with the files that are saved by the (real) FMPAC commander's save option.

    After saving your config and running openMSX again, you should be able to get the FMPAC commander with CALL FMPAC in the emulated MSX!

    In a similar fashion, you can also add an MSX-Audio device (<MSX-AUDIO>, note that some programs also need the MusicModuleMIDI device to detect the Music Module, an empty SCC cartridge (<SCC>), etc. Just browse the existing existing extensions, the Boosted_MSX2_EN configuration file or the extensions and see what you can find.

    Devices that contain ROM or RAM will have to be put inside a slot of the MSX, using the <primary> and <secondary> tags as is demonstrated with the above mentioned FMPAC example. Other devices don't need this. Remember that you can not put two devices that have a ROM in the same (sub)slot! Just use a new free subslot if you need to add such a device and all your primary slots are full. Devices that do not need a slot, like the MSX-Audio device, you can add as many as you like.

    Another thing you may want to change: the amount of RAM of the MSX: change the "size" parameter in the <MemoryMapper> device config.

    In principle all of the above mentioned things are also valid for extensions. The main difference is the fact that you should use "any" for the slot specification as was already mentioned above. Just compare the fragment above with the original FMPAC extension we based it on.

    If you understand the basics of XML, you should be able to compose your MSX now! You can use the ready-made configurations in share/machines as examples.

    9. Contact Info

    Because openMSX is still in heavy development, feedback and bug reports are very welcome!

    If you encounter problems, you have several options:

    1. Go to our IRC channel: #openMSX on irc.freenode.net and ask your question there. Also reachable via webchat! If you don't get a reply immediately, please stick around for a while, or use one of the other contact options. The majority of the developers lives in time zone GMT+1. You may get no response if you contact them in the middle of the night...
    2. Post a message on the openMSX forum on MRC.
    3. Contact us and other users via one of the mailing lists. If you're a regular user and want to discuss openMSX and possible problems, join our openmsx-user mailing list. If you want to address the openMSX developers directly, post a message to the openmsx-devel mailing list. More info on the openMSX mailing lists, including an archive of old messages, can be found at SourceForge.
    4. Create a new issue in the openMSX issue tracker on GitHub. You need a (free) log-in on GitHub to get access.

    For experienced users: if you get a crash, try to provide a gdb backtrace. This will only work if you did not strip the openMSX binary of its debug symbols.

    In any case, try to give as much information as possible when you describe your bug or request.

    openMSX-RELEASE_0_12_0/doc/manual/user.html000066400000000000000000002660131257557151200203350ustar00rootroot00000000000000 openMSX User's Manual

    openMSX User's Manual

    Contents

    1. 1. Introduction
      1. 1.1 New Versions of This Document
      2. 1.2 Purpose
      3. 1.3 Contributors
      4. 1.4 Revision History
    2. 2. Starting the Emulator
      1. 2.1 Machines
      2. 2.2 Extensions
      3. 2.3 Other Command Line Options
    3. 3. The Console and Settings
      1. 3.1 Console Introduction
      2. 3.2 Some Simple Console Commands
      3. 3.3 Settings
      4. 3.4 Plug
    4. 4. Running MSX Software and Using Media
      1. 4.1 Running ROM Software
      2. 4.2 Running Disk Software
        1. 4.2.1 Using Disk Images
        2. 4.2.2 Using Directories as Disks
        3. 4.2.3 Using Real Disks
        4. 4.2.4 Managing Disk Images
      3. 4.3 Running Tape Software
        1. 4.3.1 Using WAV files
        2. 4.3.2 Using CAS files
      4. 4.4 Emulating MSX Harddisks, SD interfaces and CD-ROM
        1. 4.4.1 Sunrise IDE
        2. 4.4.2 Beer IDE
        3. 4.4.3 SCSI devices
        4. 4.4.4 Mega Flash ROM SCC+ SD
      5. 4.5 Running Laserdisc Software
    5. 5. Input Devices
      1. 5.1 Keyboard
        1. 5.1.1 Key Mapping
        2. 5.1.2 Keyboard Layouts
      2. 5.2 Joystick
      3. 5.3 Mouse
      4. 5.4 Arkanoid Pad
      5. 5.5 Trackball
      6. 5.6 Touchpad
    6. 6. Video
      1. 6.1 Renderers
      2. 6.2 Accuracy
      3. 6.3 Scalers
      4. 6.4 Gamma Correction
      5. 6.5 Special Effects
      6. 6.6 GFX9000/Video 9000
      7. 6.7 Video Recording
    7. 7. Audio
      1. 7.1 Audio Settings
      2. 7.2 MIDI
      3. 7.3 Recording Audio to File
    8. 8. Useful Extras
      1. 8.1 Saving/Loading the State of the Machine
      2. 8.2 Reverse
      3. 8.3 Game Trainer
      4. 8.4 Debug Device
        1. 8.4.1 Enabling the Debug Device
        2. 8.4.2 Output Ports
        3. 8.4.3 Single Byte Mode
        4. 8.4.4 Multi Byte Mode
    9. 9. Contact Info

    1. Introduction

    1.1 New Versions of This Document

    The latest version of the openMSX manual can be found on the openMSX home page:

    http://openmsx.org/manual/

    You can also use this URL to get up-to-date versions of the hyper links if you printed out this manual.

    1.2 Purpose

    This manual is about openMSX, the open source MSX emulator that tries to achieve near-perfect emulation by using a novel emulation model. You can find more information about openMSX on the openMSX home page. You can also download the emulator itself from there.

    openMSX is not completed yet, which means that most things work but not all features are implemented yet. Many emulation features are implemented, but in terms of user interface it is rather bare bones, unless you use the optional Graphical User Interface dubbed openMSX Catapult, which has separate manuals for now. However, because the emulation is already pretty good, it would be nice if non-insiders would be able to play with it, too. For those people, we have written this guide.

    This manual tells you how you can use openMSX, once it has been installed and properly set up. You should be able to use most of the features of openMSX if you have read it. If you are using openMSX with Catapult, you don't have to pay attention to the exact command and setting names. However it is still useful to read this document to find out how openMSX works and learn its terminology.

    Disclaimer: We do not claim this guide is complete or even correct. What you do with the information in it is entirely at your own risk. We just hope it helps you enjoy openMSX more.

    1.3 Contributors

    The following people contributed to this document in one way or another:

    • Jorrith Schaap
    • Manuel Bilderbeek
    • Maarten ter Huurne
    • other openMSX developers

    Thanks to all of them!

    1.4 Revision History

    For the revision history, please refer to the commit log.

    2. Starting the Emulator

    In this chapter we will tell you how to select MSX machines and how to use extension cartridges.

    2.1 Machines

    If you start openMSX without any command line parameters, you will get the default machine, which is stored in the default_machine setting, see the Setup Guide. If you did not change the default machine, you will get the C-BIOS MSX2+ machine.

    To select a different MSX machine, you can use the -machine command line argument:

    openmsx -machine Panasonic_FS-A1GT

    But, you can also use the machine command to switch at run time in the console, which is explained in the next chapter.

    The C-BIOS machines come with ROMs installed; for other machines you will have to install system ROMs yourself, see the Setup Guide for details.

    2.2 Extensions

    Extensions are simply MSX cartridges (extensions to the MSX system) that you can plug into the emulated MSX. openMSX ships with a lot of predefined extensions. Note that many of them require firmware ROMs (called system ROMs); see the Setup Guide for details.

    We will use the FMPAC as an example. openMSX ships with a definition (XML file) for the FMPAC extension, but you will have to add the fmpac.rom firmware ROM yourself. When you have done so, you can insert an FMPAC into the emulated MSX machine with the following command line:

    openmsx -ext fmpac

    Similar to machines, you can also use the ext command in the console to do it at run time. You can also use something like -extb to explicitly specify cartridge slot B.

    If you look in the share/extensions directory (or when using the console, type the TAB key with the ext command, see next chapter), you will see all the extensions known to openMSX. For example -ext mbstereo gives you the MoonBlaster stereo effect: FMPAC on the left speaker and MSX-AUDIO on the right speaker.

    2.3 Other Command Line Options

    Often used other command line options will be discussed later in this manual. For a complete list of them, type the following command:

    openmsx -h

    3. The Console and Settings

    3.1 Console Introduction

    openMSX has a built-in command interface called the console, which allows you to control almost all aspects of openMSX while it is running. You can access the console by pressing F10 (with default key mapping; Cmd+L on Mac) when the focus is on the emulator window. This will give you a command line in the openMSX window. Note that due to a known problem in SDL on Windows, the console won't come up if you went to fullscreen by using ALT-ENTER. Use F12 to go to fullscreen to work around this problem. (See also Settings and Keymapping.)

    Typing help gives a list of commands. Using PageUp you can see all of them. If you type help [command] you will get help for the specified command. This manual describes a few important commands; a full list can be found in the Console Command Reference. The console can be used to change disk images, plug in joysticks or mice, change settings at run time and to change key bindings, among others. It actually gives you full control of openMSX: if it can't be done via the console, it's probably impossible!

    One very practical feature of the console command line is that you can use "completion" features. Just try typing half a command and then press the TAB key; openMSX will then try to finish the word you were typing or show the possibilities in case of ambiguities. You can use it also for file names, connectors, pluggables and settings and even for machine and extension names.

    3.2 Some Simple Console Commands

    You can reset your MSX with the Console command reset and exit openMSX with the command exit. As is explained in the previous chapter, you can change machines with the machine command and you can insert extensions with the ext command. Remove extensions with the remove_extension command or list them with the list_extensions command. Other commands will be discussed later on in this manual.

    3.3 Settings

    An interesting console command is set. You can use it to change the various settings. E.g., you can use it to set the current renderer. If you issue set with only the setting (like set renderer), you will get the current value of that setting. Settings that have only two possible values can also be toggled with the toggle command (an example is the default key binding of F12 to toggle fullscreen, see also below). A complete list of settings can also be found in the Console Command Reference. Note that using the "tab completion" feature can help you a lot in getting an idea of what settings are possible, as it will only complete possible options. Just try that.

    If the MSX goes too fast or too slow, adjust the emulation speed with the speed setting, which has the speed percentage as parameter. So, typing set speed 120, will let the emulated MSX run at 120% of normal MSX speed. This is useful for debugging purposes (slow down) or when you want to skip certain parts of a demo for example (speed up).

    If you got the MSX sped up to maximum (set throttle), but openMSX is still not going fast enough for you, you can increase the maxframeskip setting: set maxframeskip 10 will mean that openMSX may skip 10 screens to be displayed, just to get to the requested speed. Note that you can also force openMSX to skip frames, with the minframeskip setting. This sets the amount of frames that will be skipped always. Of course frame skipping makes emulation a lot less accurate.

    Some MSX machines like the Panasonic FS-A1GT have built in software (called firmware), that can be switched on and off via a switch on the machine itself. In openMSX the internal software is switched off by default, but you can switch it on with the following setting: set firmwareswitch on.

    If you're not really interested in how long a real MSX would take for loading from diskette, cassette or laserdisc, you could enable the full speed when loading feature: set fullspeedwhenloading on. It runs openMSX at maximum speed whenever it thinks that the MSX is loading. The drawbacks: it might detect a bit too late that the MSX isn't loading anymore, so sometimes the first notes of music played right after loading might be too fast. Also, when loading openMSX will use all CPU power it can get to get the maximum speed; the feature has no influence on the state of the MSX, of course.

    You can save all your current settings with the save_settings command. If you specify a file name after this command, the settings will not be saved to the default settings file (share/settings.xml), but to the specified file. At start up, alternative settings files can be loaded by using the -setting command line option. You can also use the load_settings command to load settings at run time. Settings that are not mentioned in the saved settings file that you are loading will be untouched. If you want openMSX to automatically save your settings when it exits, issue the following setting: set save_settings_on_exit true.

    3.4 Plug

    The console command plug can be used to plug the so called pluggables (devices) into connectors on the MSX. Examples of connectors are the joystick ports, the printer port, the MIDI in and out connector, the cassette port, etc. Examples of pluggables are joysticks and mice, but also printers and MIDI equipment. The command plug without any parameters will show a list of connectors and what pluggables are plugged into them. Using plug [connector] will only show what is plugged into [connector]. You will not be surprised that the command plug [connector] [pluggable] will plug the [pluggable] into the [connector].

    Note that using the "tab completion" feature can help you a lot in getting an idea of what plug commands are possible, as it will only complete possible connectors and their possible pluggables. Also just try this.

    4. Running MSX Software and Using Media

    With this information, you can run most of the existing MSX software.

    4.1 Running ROM software

    Suppose you want to run the ROM file galious.rom. Then you simply type:

    openmsx galious.rom

    and the emulated MSX will run the game. (Of course, in this case, the file galious.rom should be in the current directory.

    You can also explicitly indicate that the thing is a ROM image like this:

    openmsx -cart galious.gam

    This lets openMSX know that the file galious.gam is a ROM cartridge and that openMSX should insert it in the first available free cartridge slot. You can also use -carta to explicitly specify cartridge slot A.

    Or, maybe openMSX didn't have the ROM in the ROM database and failed auto detection of the mapper type. You can specify the mapper to Konami (formerly known as KONAMI4) like this:

    openmsx galious.rom -romtype Konami

    Note that in practice you won't need this, because most ROM images are in the database or auto detected if they are not. The -romtype option should follow the ROM it applies to immediately on the command line.

    If wanted, openMSX can apply IPS patches to ROM software before running it. IPS patches are files that describe a modification of the ROM you are applying it to, e.g. a translation or a cheat. This way you do not need to alter any files. To apply an IPS patch you have to provide the IPS filename like this:

    openmsx -cart galious.rom -ips galiouspatch.ips

    As with the -romtype option, the -ips option on the command line must follow the ROM file it applies to directly. You can also use multiple -ips options if you want to apply multiple patches.

    If you already have openMSX running and want to insert cartridges at run time (maybe even when the MSX is powered on), you can use the carta command in the console as well, which is just as powerful.

    4.2 Running Disk Software

    4.2.1 Using Disk Images

    To run a disk image, you can type:

    openmsx relax.dsk

    for example. Or, if you use a disk image with a filename extension that is unknown to openMSX:

    openmsx -diska relax.di

    You can also change disks at run time of course. Just type

    diska <diskimage>

    in the console to put the specified disk image in drive A. To eject the disk from drive A, use:

    diska eject

    Note that inserting another disk image automatically ejects the previous one.

    Disk images in XSA format are also supported, use them as regular disk images, but do note that they are read only. The same counts for (g)zipped disk images. Note that in zipped disk images the first file that is packed into the zip file will be used as disk image.

    If wanted, openMSX can also apply IPS patches to disk software before running it. This way you do not need to alter any files. To apply an IPS patch you have to provide the IPS filename like this:

    openmsx SDSNAT1C.DSK -ips sdsnat1-eng.ips

    The -ips option must follow directly the disk image on the command line it applies to. You can also use multiple -ips options if you want to apply multiple patches.

    You can also apply the patches when changing disks at run time. Just type something like

    diska SDSNAT1C.DSK.gz sdsnat1-eng.ips sd-cheat.ips

    in the console to put the specified gzipped disk image SDSNAT1C.DSK.gz in drive A, with both IPS patches applied.

    4.2.2 Using Directories as Disks

    The DirAsDsk feature permits you to use a directory on your host computer's file system as a disk image for your emulated MSX. Note that this has nothing to do with harddisk emulation. It simply creates a virtual disk structure in memory from the files that are in the directory that you specified as if it were a disk image. So:

    openmsx -diska .

    will try to put all files of the current directory on a disk image in memory and start openMSX with it. The actual data is still read from/written to the files in your directory so that if you change the content of the files, these changes are immediately visible to the emulated MSX. This way you can for instance edit source files with your favourite text editor but compile them immediately in the emulated MSX.

    Using the default value of the setting DirAsDSKmode (full), all changes to the directory on the host system and on the MSX system are performed, so that they are immediately visible to the other side. If this is not the desired behaviour, please check the documentation of that setting.

    Be careful when writing to files from your emulated MSX. In the default 'full' mode, you can change/overwrite/delete/corrupt files on your host system, if you made them accessible for the emulated MSX! Still, this is the behaviour what most people want/expect and it's very useful if you know what you are doing.

    Note that MSX disks only have a limited capacity, typically 720kB. If the host directory contains more data, then some host files will be ignored: they will not appear in the virtual disk image.

    4.2.3 Using Real Disks

    To use a real disk, just specify /dev/fd0 as a disk image. This is of course a Linux (Unix, actually) specific feature, but for now it is usable. It may be a bit slow though, with the FDC emulation enabled. It should be just as slow as a real disk drive, however! Don't forget that you shouldn't have it mounted to be able to use it this way. We recommend to use only write-protected disks! It is possible that you damage the contents of your disk if you don't. Windows users can try real disks by using the DirAsDsk feature. Because we have not tried this before, we advise you to be careful and always use it with write protected disks. Only regular disks with normal files will work with it; specify A: as disk image to use it.

    4.2.4 Managing Disk Images

    openMSX has a special command with functionality to perform file imports and exports, with support for Sunrise harddisk images (FAT12 only) with partitions and normal disk images. Please see the separate manual for this, called Using diskmanipulator.

    4.3 Running Tape Software

    4.3.1 Using WAV files

    openMSX supports WAV files for tape emulation! Just use an MSX with a cassette port (at least any MSX1 or MSX2 machine will do) and it should be available.

    Then type in the console:

    cassetteplayer insert <file>.wav

    And then in MSX Basic, type:

    run"cas:"

    (or another command to load the program on 'tape'.)

    Note that in Linux, one should not use the special file /dev/pcm for tape input. openMSX will try to read the file until the end, which doesn't exist.

    Other cassetteplayer related commands/settings you need to know of are:

    • cassetteplayer rewind, to rewind the tape
    • cassetteplayer eject, to eject the tape
    • cassetteplayer new <filename>, to create a new WAV cassette image to record to; also sets the cassette player in record mode
    • cassetteplayer play, to set the cassette player in play mode (when you've just recorded to the cassette)
    • cassetteplayer record, to set the cassette player in record mode, to append to existing cassette images (NOT IMPLEMENTED YET)
    • set cassetteplayer_volume, to set the volume of the cassette player sound (yeah, the screeching tape sounds!)

    As you can see in this list, appending to existing cassette images (or (partially) overwriting them) is not supported (yet). If you want to save again, just insert a blank tape by using the cassetteplayer new command again.

    4.3.2 Using CAS files

    You can also use the so-called CAS files. Use them exactly as you would use WAV files, described in the previous section.

    We don't support using CAS files anymore by patching a BIOS, because it is not really something we want: we prefer a more authentic emulation without hacks like this. So, nowadays, the CAS files are automatically converted to WAV files, internally. Note that the loading time is drastically longer this way (but: doing a set fullspeedwhenloading on will help a lot). On the other hand, you will be able to hear the cassette sounds now also with the CAS files... What is using cassettes with an MSX without those characteristic sounds?

    To make it even more comfortable to run software from CAS images, try the following setting, that will attempt to type the loading instruction for you after the MSX has started up:

    Note that saving to CAS files (new or existing ones) is not possible; one can only save to new cassette images in WAV format.

    4.4 Emulating MSX Harddisks and CD-ROM

    openMSX supports mostly the emulation of the Sunrise IDE interface, but there is also some experimental support for two types of SCSI interfaces: the Gouda/Novaxis SCSI interface and the MEGA-SCSI. Nowadays, openMSX also emulates the SD interface MegaFlashROM SCC+ SD and the Beer IDE interface.

    The extensions that enable this have a built in harddisk configuration, in the form of a 100MB sized disk image. This is the default size: if the harddisk image is not present, the file is created with this size. The image will end up in your openMSX user directory/persistent/NAME/untitled1/IMAGENAME.dsk, where NAME is the name of the extension used and IMAGENAME is a name that is configured in the extension's XML file.

    When using these extensions for the first time, one has to treat them as if they are real interfaces with a blank harddisk connected. How to initialise them depends on the type, it is advisable to read the manuals. The sections below give some hints. The diskmanipulator may be helpful, but only supports hard disk images with a partition table format compatible with the Sunrise IDE at the moment.

    For clarity: because the emulation is done on a big disk image, there can be no data corruption of your PC's harddisk. This does mean that you need free disk space for this image, which can be quite big (default 100MB). So, in other words, you can't really use your normal PC harddisk as an MSX harddisk for these extensions. (Maybe on UNIX systems it works if you choose a device like /dev/hdb as harddisk image file, but we have not tested it and do note that it can cause loss of data of that partition or disk!)

    If you still want to use files from your real PC harddisk on the emulated MSX, you have to use the DirAsDsk feature. See the DirAsDsk section for more details.

    Please read the following sections for details about the specific extensions.

    4.4.1 Sunrise IDE

    The extension for this is called 'ide'. By default it has a harddisk connected to the master port and a CD-ROM player connected to the slave port.

    If you don't want to use the default harddisk image as is described above, you can specify the harddisk image to be used on the command line:

    openmsx -ext ide -hda symbos.dsk

    This means that you're using the ide extension with symbos.dsk as harddisk image. You can also change the harddisk image at run time in the console (only when the MSX is powered off via the power setting). This works the same as the diska command:

    hda <diskimage>

    The 'ide' extension needs the BIOS that can be flashed into the Sunrise IDE interface. It can be downloaded from the Sunrise for MSX web site.

    The initialisation of a Sunrise IDE harddisk is described in the text files that come with the FDISK program for IDE, downloadable from the Sunrise for MSX web site. There are also some threads on the MSX Resource Center forum that may give you valuable hints.

    You can side step these procedures by using the diskmanipulator to create the initial hd image, and you can immediately put some files and subdirectories on it. For instance to create a hard disk with 3 partitions of 32 megabyte on it, and have each partition filled with files and subdirectories you can do the following:

    Start openMSX with the ide extension, then type in the console:

    set power off
    diskmanipulator create /tmp/new-hd.dsk 32M 32M 32M
    hda /tmp/new-hd.dsk
    diskmanipulator import hda1 /home/david/msxdostools/
    diskmanipulator import hda2 /home/david/msxdemos/
    diskmanipulator import hda3 /home/david/msxdrawings/
    set power on

    Note that the diskmanipulator can only handle hard disk images that are compatible with the Sunrise IDE interface!

    As announced above, there is (limited) support for CD-ROM with the 'ide' extension. You can insert an ISO image in that virtual CD-ROM player with the -cda command line option and change it at run time with the cda console command, all similar to the aforementioned hda and diska commands and options.

    4.4.2 Beer IDE

    The Beer IDE interface, as brought to us by SOLID, is emulated by openMSX, too. This interfaces only offers a single device (no master and slave) and can only handle up to 5 partitions of 32MB. But the up side is that it doesn't need MSX-DOS2, and thus it can run on any MSX (with 64kB RAM to run MSX-DOS). Emulation of this interface is quite recent, so consider it experimental.

    Usage is identical to using the Sunrise hard disk interface: you can use the hda command and the matching command line parameter -hda to control which image will be used.

    By default, the image is 170MB, so that 5 partitions of 32MB fit easily. Firmware version 1.9RC1 is selected by default, because we could not get the 1.8 firmware to work: the MSXFDISK program didn't create partitions which actually worked with the 1.8 firmware. If you want to experiment with it, you can change the firmware to use by editing the extension file in share/extensions/Beer_IDE.xml.

    With the 1.9RC1 firmware, we were able to work with an existing harddisk image, partitioned with the Windows tools that come with that firmware. Using this firmware, you can also partly work with Sunrise IDE compatible hard disk images, so you can use diskmanipulator to create a hard disk image and to import and export to them. Be careful though, this has not been extensively tested. Always back-up your hard disk image before doing this, in case something goes wrong. Important note: the Sunrise partition format numbers partitions the other way around as what Beer IDE 1.9RC1 firmware expects. This also means that you can't boot from Sunrise IDE images (and thus also not from images created by diskmanipulator).

    Unfortunately, the Beer IDE is hardly documented and the software is hard to find. So, it's for experts only!

    4.4.3 SCSI devices

    First of all: the SCSI emulation is experimental! There is a lot bigger chance that you may lose data on your emulated harddisk images with SCSI than with Sunrise IDE! When we tried it, everything seemed fine, but you are warned.

    The SCSI extensions (currently Gouda_SCSI, ESE_MEGA-SCSI and ESE_WAVE-SCSI) have the default 100 MB harddisk image connected on target ID 1 and an (even more experimental) LS-120 device (basically a harddisk like media that can be changed/ejected when the power is on) on target ID 2.

    Specifying or changing hard disk images works the same as with IDE, see above.

    To change the disk image of the LS-120 device, use the lsa (LS drive A) command, exactly the same as the hda command. Of course you do not need to have the power turned off to do this, as this is the whole point of the LS-120 device. You can also just eject it, with the eject subcommand. At the time of writing there seems to be a bug when doing this: the device isn't listed in the device list if there is no media inserted. It is not possible to specify an LS-120 device on the command line.

    Initialisation for the ESE SCSI devices needs tools like MGINST, which can be found on Takamichi's web site. They include small manuals in English. This manual is not the place to explain the procedure, but the idea is as follows. First, install the MSX-DOS 2 kernel in the SRAM of the device, using the MGINST program (you might want to use KSAVER first to save the kernel of your DOS 2 cartridge). After this, the MSX will boot from the SRAM disk. Use the SFORM-1 (for MSX-DOS) or the SFORM-2 (for MSX-DOS 2) to format the drive (use a physical format, for now). Use ESET to assign drive letters to partitions.

    For the Gouda (Novaxis) SCSI interface, you need the Novaxis ROM, see also Hans Otten's Page or The Ultimate MSX FAQ. Those sites also contain manuals for the Novaxis ROM. Initialisation is done with the NFDISK utility, which can be found on Marcel Delorme's site.

    4.4.4 Mega Flash ROM SCC+ SD

    Currently there is only one SD interface emulated: the Mega Flash ROM SCC+ SD. All features of this cartridge are emulated in the sense that all currently working software with it runs on openMSX too. It is not emulated accurately enough to rely on it for development.

    Usage is again identical to using the Sunrise hard disk interface: you can use the hda/hdb commands and the matching command line parameters -hda and -hdb to control which image will be used for the first and second SD card.

    Currently, by default, the first SD card is 8MB and the 2nd SD card is 100MB size. You can change these defaults by editing the extension file in share/extensions/MegaFlashROM_SCC+_SD.xml. For formatting and managing the SD cards, please refer to its manual and tools on the Flash part of the MSX Cartridge Shop.

    4.5 Running Laserdisc software

    In order to run Laserdisc software, you need to have this optional feature compiled into your openMSX binary. Laserdisc is only supported by the Pioneer PX-7 or the Pioneer PX-V60 machines, which have special hardware to control the laserdisc player.

    In the console, type:

    laserdiscplayer insert <file>.ogv

    to insert a Laserdisc (image) into the Laserdisc player. By default, the Laserdisc will be loaded automatically. If the autorunlaserdisc setting is off, then you will have to enter the following into the MSX by hand:

    After booting the MSX, choose option 1 when asked if you want to run P-BASIC (Palcom-BASIC). In MSX-BASIC, type:

    call ld

    to load and run the Laserdisc program.

    The program is encoded on the right audio channel which will not be audible. With set fullspeedwhenloading on, openMSX runs at maximum speed whenever the Laserdisc is seeking or loading a program.

    5. Input Devices

    5.1 Keyboard

    5.1.1 Key Mapping

    This subsection lists the default key mapping of openMSX. The mapping of the special MSX keys is hardcoded, but the mapping of the keys for emulator functions is fully customisable using the bind command in the console. Your customised key bindings are saved together with the settings.

    The special MSX keys are mapped as follows, the first column for PCs (running Windows, Linux or BSD), the second column for Apple Macintosh computers:

    MSX key key (PC) key (Mac)
    CTRL key L-CTRL L-CTRL
    dead (accents) keyR-CTRL R-CTRL
    GRAPH key L-ALT L-ALT
    CODE/KANA keyR-ALT R-ALT
    iee ('no') key L-Windows
    hai ('yes') key R-Windows
    SELECT key F7 F7
    STOP key F8 F8
    INS key Insert Cmd+I

    Several emulator functions are available under keys as well (which can be changed with the bind command):

    keys (PC) keys (Mac) function
    Pause Cmd+P (Pause) Pause emulation
    ALT+F4 Cmd+Q (Quit) Quit openMSX
    CTRL+Pause (Break) Quit openMSX (not in Windows)
    PrtScr Cmd+D (Dump) Save current screen to a file (screen shot)
    PageUp PageUp Go 1 second back in time, using the reverse feature
    PageDown PageDown Go 1 second forward in time, using the reverse feature
    F9 Cmd+T (Throttle) Toggle full throttle (maximum speed)
    F10 Cmd+L (consoLe) Toggle console display
    F11 Cmd+U (mUte) Toggle audio mute
    F12 or ALT+Enter Cmd+F (Full) Toggle full screen mode
    ALT+F7 Cmd+R (Restore) Quick loadstate (from 'quicksave' slot)
    ALT+F8 Cmd+S (Save) Quick savestate (to 'quicksave' slot)
    MENU Cmd+O (Open menu) Show the On-Screen-Display menu

    Note for Mac users: if you want to bind your custom keys on the console, use META as a modifier for the Command (Apple logo) key.

    For handheld devices like the Dingoo, most of these functions can only be used via the On-Screen-Display menu and the On-Screen-Keyboard.

    For the Dingoo handheld device, this is the key mapping:

    Dingoo key MSX key OSD action
    D-pad up Cursor up / joystick up Cursor one item up
    D-pad down Cursor down / joystick down Cursor one item down
    D-pad left Cursor left / joystick left Value - / previous page
    D-pad right Cursor right / joystick right Value + / next page
    A button CTRL / joystick trigger A Confirm
    B button Graph / joystick trigger B Cancel
    X button Space
    Y button Shift
    L shoulder button TAB
    R shoulder button Backspace
    START button Toggle OSD keyboard
    SELECT button Toggle OSD menu

    5.1.2 Keyboard Layouts

    This section is about how keyboard layouts from host computers are mapped to keyboard layouts of MSX computers. This is mostly interesting if those differ (a lot). For example, you have a US-English keyboard on your PC and you are emulating a Japanese MSX computer. Or, you have a Japanese Mac and you are emulating a Spanish MSX computer.

    As of openMSX 0.7.0, there are facilities to make this as smooth as possible, so that you can use your own keyboard on any kind of MSX with as little surprises as possible. The trick is the new character-based mapping mode, which tries to convert any character you enter with your host computer's keyboard to an MSX key press. For this feature, all MSX hardware configuration files now have information about their keyboard layout. Anyway, this mapping mode is enabled by default, so you don't have to do anything to make this work!

    However, there are always some nasty details. For those details we refer to the documentation of other keyboard settings, where they are explained in full detail: mapping mode (as mentioned before), kbd_numkeypad_always_enabled (use numerical keypad even when your MSX doesn't have one), kbd_code_kana_host_key (specify an alternative host key for CODE/KANA) and kbd_numkeypad_enter_key (specifies mapping of the ENTER key of the keypad).

    5.2 Joystick

    If you have a joystick connected to your PC, use the following command to connect it to the emulated MSX:

    plug joyporta joystick1

    To connect a fake joystick (emulated with the keyboard arrow keys), you can use this plug command:

    plug joyporta keyjoystick1

    will connect a fake joystick to joystick port A. Button A of the joystick is mapped to the space bar and Button B to M, when using the default configuration. There are two keyjoysticks, 1 and 2. If you like, you can change the bindings in the console and save the settings as usual. Examples: set keyjoystick2.triga LCTRL or set keyjoystick1.up KP8.

    Most modern joysticks have more buttons than the 2 buttons that are allowed by the MSX standard. Therefore a lot of games use extra keys on the keyboard for extra functionality. For instance, all most all Konami games use F1 to pause the game. You can assign this extra functionality to your joystick by using the bind command. As an example here is how to map button 4 of the first joystick to the F1-key, button 5 to F2,...

    bind "joy1 button4 down" "keymatrixdown 6 0x20"
    bind "joy1 button4 up" "keymatrixup 6 0x20"
    bind "joy1 button5 down" "keymatrixdown 6 0x40"
    bind "joy1 button5 up" "keymatrixup 6 0x40"
    bind "joy1 button6 down" "keymatrixdown 6 0x80"
    bind "joy1 button6 up" "keymatrixup 6 0x80"
    bind "joy1 button7 down" "keymatrixdown 7 0x01"
    bind "joy1 button7 up" "keymatrixup 7 0x01"
    bind "joy1 button8 down" "keymatrixdown 7 0x02"
    bind "joy1 button8 up" "keymatrixup 7 0x02"

    For a more detailed explanation of this command see the Console Command Reference.

    5.3 Mouse

    To connect a mouse, you can also use the plug command:

    plug joyporta mouse

    will connect a mouse to joystick port A. If you want the joystick emulation feature that some mice (like the Philips SBC-3810 and the Sony MOS-1) have, keep the left mouse key pressed when plugging it in, just as on a real MSX.

    If you are using openMSX in windowed mode, it might be tricky to use the mouse. For that you may want to use the following setting: set grabinput on. This makes sure all input goes to openMSX. Your cursor cannot leave the openMSX window with this setting. Just turn it back to off, if you want to disable this again. If you only want to escape the window briefly, use this command: escape_grab. It permits you to leave the window, but the next time you enter it, the cursor is grabbed again. It might be a good idea to bind this command to a key, using the bind command, which is mentioned above.

    5.4 Arkanoid Pad

    The Arkanoid games by Taito both have support for a special Arkanoid game pad, with a classical turning knob to control the position of the bat. This device is emulated as well and can be controlled by the mouse. Plug it as follows:

    plug joyporta arkanoidpad

    5.5 Trackball

    Some MSX trackballs like the HAL CAT and the Sony HB-G7B seem to be the same and are also emulated by openMSX, again using the mouse to control it. The trackball is mostly supported in port B only:

    plug joyportb trackball

    Quite some HAL programs have support for it, e.g. Hole in One, Eddy II, Music Studio G7, Space Trouble and Super Billiards. The test program provided in the Sony HB-G7B service manual also works fine, of course.

    5.6 Touchpad

    Some MSX touch pads like the Philips NMS 1150 Graphic Tablet are also emulated by openMSX, again using the mouse to control it (where mouse button 1 corresponds to touch or no touch and mouse button 2 to the button on the pen of the touch pad). Also the touch pad is mostly supported in port B only:

    plug joyportb touchpad

    This device is mostly supported by the Philips drawing programs Designer, Designer Plus and Video Graphics (all in port B) and by Pioneer MSX Video Art (port A).

    Note that the whole openMSX window will function as the surface of the touch pad. This will not align with the actual pixels of the screen in that window, see the touchpad_transform_matrix setting for more details.

    6. Video

    6.1 Renderers

    A renderer is a part of the emulator that generates the graphical part of the emulation: the MSX 'screen'. At the moment, there are two working renderers:

    SDL
    This is the default renderer. This renderer is not using any hardware acceleration and has a steady CPU time consumption. The CPU load can be relatively high though, as all graphics are calculated on the CPU.
    SDLGL-PP
    This renderer uses the OpenGL graphics library for all post processing (hence the PP), which includes scalers and other effects. Because part of the rendering is done by the graphics hardware, the CPU load can vary a lot. The SDLGL-PP renderer is only useful if you have a hardware accelerated OpenGL library; a software GL implementation will be very slow. See the Setup Guide for OpenGL performance tips. If your card supports it, we recommend to use this renderer. Note that this renderer requires both your video card and video driver to support OpenGL 2.0. Sometimes you need to upgrade your driver to make it work. If your videocard or driver don't support OpenGL 2.0, openMSX will switch back to the SDL renderer if you try to select SDLGL-PP.

    You can set the renderer with the renderer setting. You can set full screen mode with the fullscreen setting. Again, to make these settings permanent, use the save_settings command to save them.

    Note that openMSX can be compiled without the SDLGL-PP renderer; if that is true for the build you're using, you will not be able to switch to the SDLGL-PP renderer.

    6.2 Accuracy

    The accuracy setting controls how often the renderer is synchronised with the MSX video processor (VDP). There are three options:

    screen
    Synchronise once per screen (frame). Good enough for most MSX1 software, but will break most raster effects.
    line
    Synchronise at the start of a line. This is good enough for most software. This setting hides imperfections in raster effects, which could be considered a useful feature.
    pixel
    Synchronise at the exact pixel where a change occurs. This is the most realistic setting and therefore set as the default. To see demos like Unknown Reality (scope part) and Verti correctly, you should use this setting. Also, you will see any imperfections in raster effects just like they occur on a real MSX.

    6.3 Scalers

    Most MSX screen modes are only 256×212 pixels big. This is quite small for PC screen resolutions of today. That's why you have the possibility to scale up the image. Normally, there are three possible scaling factors: 1, 2 and 3. If you select 1, all MSX pixels are mapped to a 320×240 pixels PC window, for 2 to a 640×480 pixels window and for 3 to a (surprise!) 960×720 window. The setting which determines this is called scale_factor. In general, the higher the factor, the better the output image is; the downside: it takes a lot more CPU processing power.

    Use scale_factor 1 only if you have a slow computer to run openMSX on, because it is very limited in possibilities and in the case of MSX screen modes with more pixels than 256×212, you even lose pixels! in that case, the pixels are interpolated. However, when using it full screen, the low resolution is not a problem, especially because most MSX software uses a 256×212 mode.

    There is also a number of scaling algorithms (setting scale_algorithm) that can be set. The scaling algorithm determines how exactly the mapping is done between the MSX input screen and the PC output screen. Especially for scaling factors bigger than 1, this allows for extra possibilities in the algorithms, like deinterlacing and adding scanlines, blur, anti-aliasing (rounding of blocky patters like stair cases) or even a Trinitron-like TV effect. When the factor is set to 1, you always get the simple algorithm, see below.

    An exception to all of this is the SDLGL-PP renderer. With this renderer (when using a suitable video card and driver), scaling is done on the graphics card hardware and will not take extra CPU power. This renderer also gives you the possibility to use a scale_factor of 4. The down side is that not all scalers have been implemented for this renderer. See also below.

    openMSX contains the following scaling algorithms:

    simple
    This algorithm simply expands each MSX pixel to a square of (scale_factor)×(scale_factor) PC pixels. This is the default scaler and it is fast. The image looks blocky, especially diagonal edges, but it does support scanlines and blur for scale factors of 2 and higher. In combination with a scale factor of 1, you get what was previously the SDLLo renderer, which is the fastest scaling method available.
    ScaleNx
    This scaler algorithm smoothes edges by using only original colors, so it will not give any blur. It is fast and its image is less blocky than that of the simple scaler. However, all corners are rounded, which does not look good on all graphics. This scaler has not been properly implemented for scaling factors of 4.
    SaI
    This scaler algorithm smoothes edges by interpolating neighbouring pixels. It is heaver on the CPU than the simple and ScaleNx algorithms. It does a good job on most graphics, except for high-contrast edges; for example white fonts on a black background get some nasty gray lines around them. Also corners are rounded, similar to ScaleNx. This scaler is not available in the SDLGL-PP renderer.
    hq
    This scaler algorithm looks somewhat similar to SaI, but its output is sharper. This complex algorithm is very heavy on the CPU; use this algorithm only on fast PCs. It does a good job on most graphics; it avoids excessive blurring and it keeps corners sharp. On some graphics, it does not identify edges correctly, making those edges blocky instead of smooth. Especially with high scaling factors, it can give a very smooth looking image.
    hqlite
    This is a variant of hq: the resulting image is close to hq, but it is calculated a lot faster. It has a good quality per CPU usage ratio.
    RGBTriplet
    This algorithm only works when a scaling factor of 3 is used. Also, it only works well for MSX screen modes of 256×212, which includes most games. The idea of the algorithm is that each input pixel is mapped on a triplet of pixels which represent the R(ed), G(reen) and B(lue) components of the input pixel. This arrangement of RGB components is also used in the Aperture Grille CRT's, also known as Trinitron and the modern TFT screens. You can control the effect with the blur setting. This algorithm also includes scan lines.
    TV
    This algorithm is trying to emulate the fact that on a CRT brighter pixels look bigger than darker pixels. This scaler is only available in the SDLGL-PP renderer.

    A small (somewhat outdated) demonstration of some of the algorithms can be found on the openMSX web site.

    6.4 Gamma Correction

    PC monitors can have different gamma values than MSX monitors. To compensate for this, openMSX has a gamma correction feature. It is controlled by the gamma setting. A value of 1.0 disables gamma correction; a lower value makes the image darker; a higher value makes it brighter.

    If you want to know what gamma correction really means, read this page about monitor gamma. The gamma correction value you can set in openMSX should be the gamma of your PC screen divided by the gamma of the MSX screen. I measured the gamma of my PC screen (TFT) at 2.0 and the gamma of my MSX monitor at 2.5. That puts the gamma correction at 2.0 / 2.5 = 0.8. So if I enter that value, the openMSX image will have comparable brightness to the MSX image. However, 0.8 is not the value I'm actually using: I prefer a brighter image than my MSX monitor, so I chose to use a gamma correction of 1.1.

    6.5 Special Effects

    openMSX contains a couple of special effects settings that can be applied to the video output:

    deinterlace
    Interlacing is a technique to double the vertical resolution by splitting the image into two frames: the first frame the even lines are displayed, the second frame the odd lines are displayed. The after glow on a TV and some processes in the human brain combine both frames into a single image. However, this process is not perfect and you can notice flickering, especially on horizontal lines. The deinterlace feature combines the even and the odd frames into a single output frame, thus eliminating the flicker. The deinterlace setting controls this feature: it can be on (enabled) or off (disabled); it is enabled by default. This feature needs a scaling factor of at least 2.
    deflicker
    This filter detects pixels that alternate each frame between two different colors and replaces those alternations with the average color. Such 'flickering' pixels can occur in software that rapidly changes between colors to create the illusion of a wider color palette. It can also occur because of 'sprite flickering'. This setting is disabled by default because there aren't that many situations where it really improves video quality but it does have a performance cost.
    scanline
    On TV's and MSX monitors, you can see a small black space in between the display lines, especially when using NTSC. The scanlines feature simulates this by drawing some lines a bit darker. This feature is disabled when a scaling algorithm other than simple, tv or RGBTriplet is used and needs a scaling factor of at least 2.
    blur
    TV's and MSX monitors are less sharp than PC monitors: neighbouring pixels tend to blur into each other. The blur feature simulates this by interpolating neighbouring pixels. The blur settings control this: 0 means no blur (completely sharp), 50 means some blur (like a monitor), 100 means maximum blur (like a TV). All other values between 0 and 100 are also possible of course. This feature is disabled when a scaling algorithm other than simple or RGBTriplet is used and needs a scaling factor of at least 2.
    after glow (glow)
    The after glow feature blends each frame with the frame before it. This results in moving objects leaving a trail (motion blur). The glow setting controls the amount of after glow: 0 means no after glow, 100 means maximum after glow. This feature works only in the SDLGL-PP renderer.
    noise
    This setting controls the amount of pixel noise on the screen. The noise setting controls the amount: 0 means no noise, 100 means maximum noise. The value is actually the deviation of the color of the original pixel and non-integer values are also possible.
    display deformation (display_deform)
    This feature makes it possible to change the shape of the MSX screen. Here are the possibilities:
    • normal: no deformation (default)
    • 3d: emulates a 3D view on an arcade cabinet's screen
    This feature works only in the SDLGL-PP renderer. In openMSX versions prior to 0.7.0, there was also a horizontal_stretch option here, but that has been replaced by a horizontal_stretch setting.

    6.6 GFX9000/Video 9000

    openMSX has GFX9000 emulation. As there isn't that much software for it available, it is not as complete, functional and optimized as the video emulation of the classical MSX chips. Despite of all this, most existing GFX9000 software runs pretty well, so we found it worth sharing with you anyway.

    The real GFX9000 has an external video connector to which you can connect a second monitor. Because of limits of the SDL library we used to create openMSX, we cannot have more than one window for openMSX, so we cannot emulate a second monitor. To see the GFX9000 in action, you need to switch the videosource setting, which equals to a so-called SCART-switch in the real world: set videosource GFX9000. This setting is only available when there are actually multiple videosources available.

    Alternatively, instead of the GFX9000 extension, you could use the Video9000 extension (also built in in several Boosted MSX machine configurations). The Video 9000 hardware has the possibility to superimpose the GFX9000 video on top of the V99xx video (and this is practically the only feature of the Video 9000 that is currently implemented). Software that is Video 9000 aware, will tell the Video 9000 to show the GFX9000 if something interesting is to be seen on the GFX9000 video output. So, for such software, you do not have to switch videosources if you simply use the Video9000 videosource. When a Video 9000 is present in the currently running MSX configuration, the Video9000 videosource will be selected by default, to make use of this superimpose feature. For programs not aware of Video 9000, you will still have to switch videosources manually, just like on a real system.

    To get your normal MSX screen back, you should type set videosource MSX. If you want to toggle with a hot key between them, it might be useful to bind a key for it. E.g.: bind F6 cycle videosource.
    cycle is a Tcl command that cycles through the options of the setting in the parameter.

    6.7 Video Recording

    The video recorder enables you to record the audio and video rendered by openMSX to an AVI file. The output video is in 320×240 resolution by default, at 640×480 when using the -doublesize flag and at 960×720 when using the -triplesize flag. The video is compressed with the ZMBV codec, a fast lossless compression algorithm that works very well on 2D computer generated images. The FAQ contains more information about this codec. The audio is not compressed.

    The recorded AVI file will not suffer from any hiccups, even if the emulation ran too slow when you recorded it. The current video source (see previous section) is recorded and the sound is recorded with the current frequency setting. If you change the frequency setting during recording, or, more importantly, if the software changes from PAL (50 Hz) to NTSC (60 Hz) during recording, the video will get out of sync with the audio. Most of the special effects will not be recorded.

    Use the command record start to record to a default file name, or you can use an additional parameter to specify a file. The command record stop stops recording and record toggle toggles it. You can use the -audioonly or -videoonly option to record only sound or video.

    If any stereo sound devices are present or any sound device has an off-center balance, the recording will be made in stereo, otherwise it will be mono. If a recording is made in mono and then a stereo sound device is added, you'll receive a warning that stereo sound has been detected and that the two channels will be mixed down to mono. You can prevent this from happening by using the -stereo option to force a stereo recording even if no stereo devices are present at the time you enter the command. You can also force a mono recording with -mono to save space.

    If you want to put a recorded video on your web site, it is better to transcode the audio to MP3 or Vorbis format, as this makes the file a lot smaller. YouTube supports the ZMBV codec, so if you want to upload your recording you do not need to transcode the video. If you want to share your video with people who do not have (or want to install) the ZMBV codec, you should still transcode it, of course. This can be done with programs such as Virtual Dub (Windows) or MPlayer's MEncoder (Linux/UNIX). For YouTube you may want to use the command record_chunks instead: it will enable you to chop up your video in several parts and enables -doublesize automatically.

    Recording as explained above will happen at real time. This can be annoying if you want to make a demonstration video, because you all mistakes will be recorded as well. To work around this, you can also use the reverse feature during the scene you want to record. After the scene, reverse to the beginning, start the recording as explained above and let the scene replay relaxedly. You can even speed it up using the throttle setting. This method of recording is also useful when real time recording has a big impact on the performance of openMSX on your hardware. See also the chapter about this feature.

    7. Audio

    7.1 Audio Settings

    There is a master_volume setting, which controls the overall output volume of openMSX (it applies to all sound devices). Volume 0 means no sound, volume 100 is maximum.

    There is also a mute setting, to disable all sound from openMSX at once. It can be on (muted) or off (sound is audible). By default, mute is bound to the F11 key.

    Each sound device in the MSX you are emulating also has its own volume setting. Volume 0 means no sound, volume 100 is maximum. For example: set "MSX Music_volume" 50.

    For each sound device, you can control the distribution of the sound output of this chip over the left and right channel, with the balance setting. This is very similar to the balance knob on (older?) hifi equipment. Example: set PSG_balance -100, which sets the PSG entirely to the left channel. Any sound device can also be individually (un)muted using the mute_channels command.

    If you'd like to apply some special effects to the sound, you should take a look at the vibrato and detune (both percent and frequency) settings, which can be only applied to the PSG, for now.

    For Windows users there is the choice to use DirectSound or SDL as an audio driver. By default, DirectSound is used, because it gives a better quality in most cases. Change it with the sound_driver setting, if you like.

    7.2 MIDI

    openMSX supports the MSX-MIDI interface of the MSXturboR GT and the mu-Pack, the MIDI interface of the Philips Music Module (NMS 1205), the MIDI interface of the Yamaha SFG-01 and SFG-05 module (also present in the Yamaha CX5M series of machines) and the FAC MIDI Interface.

    To use MIDI, start openMSX with a machine that has a MIDI interface built in, or add one of the mentioned MIDI interface extensions. For example, use the machine Panasonic_FS-A1GT and plug in a MIDI device on the console:

    plug msx-midi-out midi-out-logger

    This logs all MIDI commands to a file. Because there is no timing information logged, this is not very useful yet.

    It's more interesting to connect the MIDI out of the MSX to an actual PC MIDI device, such as a MIDI out port or the internal synthesizer of your sound card. On Linux, you can use the midi-out-logger and set a MIDI device node, for example /dev/midi, as its output file. This is done by default. To play with this setting, use set midi-out-logfilename. On Windows and Mac OS X, real MIDI devices are separate pluggables. On OS X, you can also select Virtual OUT to provide a port to external software.

    Vice versa, the MIDI in port can also receive data from the system by plugging a device into msx-midi-in (for the Panasonic FS-A1GT; use the appropriate connector name for other devices). Analogous to the above mentioned outputs you can connect a midi-in-reader which reads from a file or /dev/midi on Linux. On Windows and Mac OS X available MIDI devices show up as separate pluggables. On OS X a Virtual IN port is available as well.

    On Linux, it might be useful to route the MIDI events to a software synthesizer as well. On MSX Resource Center there is a forum thread which describes how to do this.

    7.3 Recording Audio to File

    openMSX records the sound at the exact speed at which it should be produced, no matter the speed at which the emulation was running while recording. Note that recording sound to the uncompressed WAV format will take a lot of disk space: at 44.1 kHz it will take about 176 kB per second.

    You can start the recording of sound by issuing the command soundlog start. It will automatically choose a file name and save it in the soundlogs directory in your personal openMSX folder. You can also add an extra parameter to specify the filename for the new WAV file. To stop recording, use soundlog stop. You can toggle the recording status using soundlog toggle, which is useful if you bind this command to a hot key.

    There is also an advanced feature for recording audio to file: you can record individual channels of sound chips to individual files on disk. The sound is in the native frequency of the sound chip this time, which means that for chips like PSG or SCC (which run at very high frequencies), the files will be huge. (You are warned!) This feature is easiest to control with the record_channels command. Note that in contrast to the soundlog command, the output file of this command ends up in the current directory and not in a special directory. We hope you can use this command to study the fantastic compositions of MSX software and make great remakes of them.

    8. Useful Extras

    8.1 Saving/Loading the State of the Machine

    A feature of emulators which is particularly useful is saving the state of the emulated machine to a file, in order to load it again later and continue exactly where you left off when saving. Not only useful for games, but also for debugging or testing. This feature is now also available in openMSX! And the best thing is, that it is designed in such way that it is able to cope with older save states in future releases. So, you don't have to be afraid to upgrade to a new version of openMSX: your save states will remain usable!

    The easiest way to use it is by using the keyboard shortcuts for quickly saving and loading a state, see the key mapping section. These shortcuts basically use the savestate and loadstate commands, with the parameter quicksave, i.e. they use a savestate file with the name 'quicksave'. You can also use the commands directly yourself, with the argument as the name of the slot you save the state to (use TAB or the list_savestates command to see your previously saved states). Without having to browse the file system of your computer, you can also conveniently delete existing save games with the delete_savestate command.

    Note that when saving the state of the machine, a screenshot will also be saved with it, so that those could be used for save state browsing. (Currently only used on the On-Screen-Display and in a prototype version of a new Catapult.)

    8.2 Reverse

    Inspired by the meisei MSX emulator, openMSX now also has a reverse feature. This enables you to go back in MSX time, so you can correct mistakes in your game play or you can watch what you did (and also record a video of it).

    You can go back in time a second (upto the moment in MSX time you started the reverse feature, but it is auto started by default) using the key binding for this: PageUp. Once you went back, openMSX will replay whatever you did when you were at this time for the first time, until it got at the point where you went back. From then on, everything will continue as normal. If you touched any control of your MSX during replay, you have indicated to take over from the replay. If you do that, the rest of the replay is erased (openMSX forgets that that future ever happened). This is the typical way to correct mistakes using this feature.

    While replaying, you can also jump forward in time ("Back to the Future") using PageDown. Also, you can go back a specific amount of seconds or to an absolute moment in (MSX) time, all using the reverse command. (This can also be useful when you're developing/debugging MSX software.)

    If all of this sounds a bit confusing, you can use the reverse bar (hover near the top of the openMSX window to make it appear), which will show you a visualisation of all of this on screen. The bar represents the time while the feature was enabled and shows the current moment in time (the red indicator). You can click on it to jump back and forward in time. The vertical lines indicate times when snapshots where made. The bar will fade out after a while, but hovering your cursor over it makes it reappear. If you want to get rid of the bar, issue the toggle_reversebar command. (This will not turn off the reverse feature itself.)

    If you want to disable the reverse feature, you can use the reverse stop command. And if you don't want it to restart again anymore, use the auto_enable_reverse setting.

    If you want to save a very compact recording of what you did, or want to have the possibility to start off in the middle of a recording, you can save your complete replay to a file with the reverse savereplay command. They can also be loaded of course, with reverse loadreplay.

    8.3 Game Trainer

    openMSX includes a game trainer system. Although it has to be used from the console, it is very easy to use. As always, you could type: help trainer, for some basic help.

    Suppose you want to cheat on Metal Gear. Then it would be useful to type: trainer Metal[TAB], which will expand to: trainer Metal\ Gear. When you then press enter, you see which cheats are available in the Metal Gear trainer. You can active them by typing e.g.: trainer Metal\ Gear 1 2 3 4. This will activate (toggle) the first 4 cheats (as the list will tell you which is printed after the command: the crosses mean an active cheat). You can also use the descriptions instead of the numbers: trainer Metal\ Gear "enemy 1 gone" "enemy 2 gone". Or, if you want to activate all cheats, as was default in the old cheat system, you could simply type: trainer Metal\ Gear all.

    If this sounds a bit difficult for you, just try it out. It's really much easier when you actually work with it. As always in the console, using TAB to complete your commands and their options proves to be very useful!

    8.4 Debug Device

    This chapter describes how an MSX programmer can use the openMSX built-in debug device. This is an artificial MSX device that is connected to an MSX I/O port. It can be used to send debug messages to the host operating system.

    Note that openMSX also contains built-in debugging functions, which can be accessed with the debug console command. With that debugger you can read and write all registers and memory of almost all devices that are supported in openMSX. It also supports break points, watch points and stepping.

    8.4.1 Enabling the Debug Device

    To enable the debug device, insert the debugdevice extension. To do this when starting openMSX, simply add -ext debugdevice to the openMSX command line. If openMSX is already running, you can use the ext console command.

    You can use the debugoutput setting to specify the file name to write the debug output to.

    8.4.2 Output Ports

    Controlling the device is done from within an MSX program. For this purpose, the output ports 0x2E and 0x2F are used. The first port is the Mode Set Register. Bytes sent to this port have the following meaning.

    bit(s)meaning
    7 unused
    6 line feed mode (0 = line feed at mode change, 1 no line feed)
    5-4 output mode (0 = OFF, 1 = single byte, 2 = multi byte)
    3-0 mode-specific parameters (see below)

    When using mode 1, single byte mode, the lower 4 bits each enable a particular output format:

    bit(s)meaning
    3 ASCII mode on/off
    2 decimal mode on/off
    1 binary mode on/off
    0 hexadecimal mode on/off

    So, every parameter bit turns an output format on or off and more than one output format can be specified at the same time.

    The parameters for mode 2 (multi byte mode) are as follows:

    bit(s)meaning
    3-2 unused
    1-0 mode (0 = hex, 1 = binary, 2 = decimal, 3 = ASCII mode)

    8.4.3 Single Byte Mode

    In mode 1, any write to port 0x2F will result in output. This way, the programmer can see if a specific address is reached by adding a single OUT to the code. The output depends on the parameters set with the mode register. Each bit represents a specific format, and by turning the bits on and off, the programmer can decide which formats should be used.

    Here is an example:

    LD  A,65
    OUT ($2f),A
    

    This will give the following output:

    41h 01000001b 065 'A' emutime: 36407199578
    

    (when all bits are on, mode register = 0x1F)
    or

    41h 065 'A' emutime: 36407199578
    

    (when the binary bit is off, mode register = 0x1D)
    or

    41h emutime: 36407199578
    

    (when only the hexbit is on, mode register = 0x11)
    and so on.

    The EmuTime part is a special number that keeps track of the openMSX emulation. The larger this number is, the later the event took place. This is a great way to get an idea of the timing of things.

    If the character to print is a special character, like carriage return, linefeed, beep or tab, the character between the ' ' will be a dot (.) and the normal character is 'displayed' at the very end of the line, so it won't mess up the layout of the whole line.

    8.4.4 Multi Byte Mode

    Unlike mode 1, the data in this mode is always shown in one mode only. It's either in hex mode, binary mode, decimal mode or ASCII mode, but never a combination. Also the EmuTime bit is left out.

    Here is an example:

    LD  A,xx
    OUT ($2e),A
    LD  A,$41
    OUT ($2f),A
    OUT ($2f),A
    OUT ($2f),A
    

    If we substitute $20 for xx, we get:

    41h 41h 41h

    and if we substitute $22 for xx, we get:

    065 065 065

    The extra zero is added to keep alignment. Finally, if we want ASCII output, all we need to do is change xx for $23:

    AAA

    In this special case, the space in between the data is left out. Any special character like carriage return, linefeed, beep or tab will be printed as you would expect.

    9. Contact Info

    Because openMSX is still in heavy development, feedback and bug reports are very welcome!

    If you encounter problems, you have several options:

    1. Go to our IRC channel: #openMSX on irc.freenode.net and ask your question there. Also reachable via webchat! If you don't get a reply immediately, please stick around for a while, or use one of the other contact options. The majority of the developers lives in time zone GMT+1. You may get no response if you contact them in the middle of the night...
    2. Post a message on the openMSX forum on MRC.
    3. Contact us and other users via one of the mailing lists. If you're a regular user and want to discuss openMSX and possible problems, join our openmsx-user mailing list. If you want to address the openMSX developers directly, post a message to the openmsx-devel mailing list. More info on the openMSX mailing lists, including an archive of old messages, can be found at SourceForge.
    4. Create a new issue in the openMSX issue tracker on GitHub. You need a (free) log-in on GitHub to get access.

    In any case, try to give as much information as possible when you describe your bug or request.

    openMSX-RELEASE_0_12_0/doc/msxinfo-article.html000066400000000000000000000250771257557151200212110ustar00rootroot00000000000000

    openMSX

    MSX gebruikers en emulators, vroeger was het een behoorlijke haat-liefde verhouding. Tegenwoordig is het gebruik van een emulator een al meer door de vingers geziene praktijk en is het soms zelfs een behoorlijk stuk eenvoudiger. Een handige portable MSX werd nog steeds niet gebouwd, maar een laptop met een MSX-emulator kan al goede diensten bewijzen.

    In het begin waren de emulators vrij simpel en voornamelijk bedoeld om de technisch simpelere MSX1 spellen te kunnen spelen. Later kwamen er MSX2 beelden bij en de complexere geluidschips. En hoe beter de emulators werden, hoe meer men verwachte dat alle programma's vlekkeloos op deze emulators zouden draaien. Voornamelijk de prachtige demo's die voor de MSX2 zijn geschreven, bezorgen de emulators de nodige moeilijkheden.

    MSX emulators zijn dus zeker niet nieuw meer. Er zijn er al meerdere op het toneel verschenen, elk met zijn specifieke voor- en nadelen. Er zijn echter wel een hoop zaken die al deze emulators sterk gemeen hebben. De meeste van deze emulators zijn persoonlijke projecten van een enkele programmeur, die een sterke controle over hun emulator willen behouden. De binding tussen de emulator en de GUI is meestal vrij sterk wat de porteerbaarheid niet echt bevordert. Tevens valt op dat een groot aantal van de emulators een afgeleide zijn van de zogenaamde oer-MSX emulator fMSX.

    Als de sources dan al worden aangeboden, dan is het meestal al vrij moeilijk om wijzigingen terug in de oorspronkelijke emulator te krijgen. Ook praktisch is er een klein onevenwicht. Voor de MS platforms is er ruime keuze aan emulators. Op bijna alle andere platforms is er enkel een afgeleide van fMSX beschikbaar en indien je een beetje geluk hebt, is ook de MSX emulatie van MESS beschikbaar.

    Sinds kort is er een nieuwe emulator op het toneel verschenen. Er is echter iets speciaals aan deze emulator. Dit is de eerste emulator die onder de GPL wordt ontwikkeld en die als doel heeft de interne werking van de MSX zo dicht mogelijk te benaderen zonder zich te richten op een bepaalde klasse van programma's.

    Technische specificaties

    Voor de techneuten onder ons, laten we even stil staan bij de interne verschillen die er zijn tussen de meeste emulatoren en de nieuwe openMSX. De verschillen zijn onder te verdelen in enkele grote groepen:

    • openMSX probeert om elke chip als aparte eenheid te emuleren, met stricte grenzen tussen beide
    • De CPU is niet langer heer en meester van de emulatie
    • Er is een duidelijke grens tussen wat de emulatie core betreft en de interface naar het guest-OS

    openMSX is object georienteerd. Dat betekent dat in plaats van n (heel) groot functioneel programma gekozen is voor een structuur waarbij elke chip een apart programma is, die dan samen op een virtueel moederbord geprikt worden.

    Waar in andere MSX emulators het moederbord niet als aparte eenheid bestaat en onrechtstreeks verweven zit tussen de andere code in, is in dit geval het moederbord zeer expliciet aanwezig. Dit is namelijk het object waaraan alle andere chips zich moeten registreren om deel uit te maken van de uiteindelijke ge-emuleerde MSX. En net zoals in het het echt de koperbaantjes op het moederbord voor de communicatie tussen alle onderdelen zorgen, zo zorgt ook in openMSX het moederbord voor alle communicatie tussen de Z80 en alle andere chips.

    Sommigen onder ons hebben ooit misschien al wel eens met simpele elektronica projectjes gespeeld op hun MSX. Een veel gebruikt iets was om dan output via de printerport te laten lopen, en de input langs de joystickpoort terug in te lezen. Om dit soort zaken te na te bootsen in andere emulators zouden er op verschillende plaatsen in de code aanpassingen moeten gebeuren. Daarom is het concept van plugable devices niet beperkt gebleven tot het moederbord en z'n chips maar is verder doorgetrokken tot alle connectoren. Op deze manier manier kan een stuk code zich laten 'inpluggen' in de joystickpoort en in de paralelle poort. Tevens kan een stuk code zich beschikbaar stellen als connector door de juiste interface te implementeren. Op deze manier zijn de joystick splitters zoals gebruikt door sommige spellen op een een eenvoudige manier te maken. Ze registreren zich bij openMSX als joystickplugable, en aan de andere kant laten zij zich gebruiken om ge-emuleerde joysticks en muizen te laten inpluggen.

    In openMSX heeft de CPU de overheersende taak verloren. In de andere emulators is het de CPU die in alles het initiatief neemt. De CPU bepaalt hoeveel T-states (=kloktikken) er voorbij gaan voor de VDP een interrupt kan generen, de CPU beslist hoeveel T-states hij uitvoert alvorens de VDP emulatie te verplichten om een lijn (of een heel scherm) te tekenen. Aangezien de CPU bij deze emulators in alles het voortouw neemt, "weet" de ge-emuleerde Z80 ook zaken waar hij eigenlijk niets van zou mogen weten. Bv. het verschil tussen PAL en NTSC wordt vertaald in het aantal T-states dat de CPU uitvoert alvorens er getekend en ge-interrupt wordt. Het uitlezen van het toetsenbord, muis en joystick van het guest OS wordt ook bepaald door de CPU van de ge-emuleerde MSX, terwijl dit eigenlijk los zou moeten staan van de emulatie kern. Tevens maakt dit soort ontwerp het moeilijk om op onvoorspelbare externe interrupts te reageren. Kortom, hoewel dit ontwerp zijn voordelen heeft naar eenvoudige implementatie en snelheid, wijkt dit toch een hoop af van de manier waarop de CPU in de echte wereld de zaken aanstuurt.

    Er is ook een radicaal andere aanpak ten opzichte van de oudere emulators hoe tijd wordt afgehandeld. In de vorige paragraaf was er al sprake van de manier waarop de NTSC/PAL problematiek werd aangepakt door na een aantal T-states telkens 1 lijn te tekenen. Elke ervaren programmeur zal je vertellen dat het praktischer is in een computerprogramma tellers te laten aftellen naar nul. In de fMSX based emulators ga je dus de cpu steeds zien aftellen tot nul en dan terug beginnen met zijn startwaarde. Tevens gaan die emulators er makelijkheidshalve van uit dat een scherm steeds 255 lijnen hoog is (192/212 echte scherm en de rest border). Hierdoor wordt het bijzonder moeilijk om dingen te plannen in de emulator die slechts na enkele seconden gaan optreden. Dit vraagt immers extra tellers om bij te houden hoevaak de CPU herbegonnen is met tellen (50 a 60 keer per seconde!). Tevens werd van alle andere chips verwacht dat zij ook met dezelfde kloksnelheid als de CPU zouden rekenen om het geheel toch nog overzichtelijk te houden en niet met extra omzettingsfactoren rekening te moeten houden. Om dit soort zaken beter te kunnen opvangen is er binnen openMSX het emuTime concept onstaan. De achterliggende idee is dat de resolutie van de openMSX klok groot genoeg moest zijn om de snelste chip in zijn eigen eenheid te laten tellen, dat tijdsperiode van meerdere minuten opgeven zo simpel mogelijk moest zijn en dat de chips zich geen zorgen moeten maken om mogelijke conversiefactoren. Hierdoor kan de VDP op zijn eigen kloksnelheid werken waardoor het mogelijk wordt om een pixelperfect werkingsmodel op te zetten, aangezien er geen afrondingen naar een lagere kloksnelheid meer moeten gebeuren.

    Er is tevens een scheiding gemaakt tussen de echte emulatie core en de GUI. Dit maakt het mogelijk om de core te ontwikkelen en er afhankelijk van het platform een andere GUI op te plakken. Deze strikte scheiding veroorzaakt ook impliciet een betere porteerbaarheid aangezien de core niet afhangt van het OS en enkel de GUI/OS-interface klassen moeten worden aangepast.

    Voor- en nadelen

    Natuurlijk is er aan deze werkwijze ook een nadeel. Je hebt een vrij krachtige computer nodig. De OO aanpak en de verschillende lagen van abstractie en interfaces veroorzaken immers een behoorlijke overhead. De grens van 1 giga Hz is ondertussen al een tijdje overschreden en voor deze emulator is dat een goede zaak. Het belangrijkste is echter dat de interne structuur een zeer hoge nauwkeurigheid van emulatie toelaat.

    Er zijn bovendien nog andere voordelen, die zeker opwegen tegen de (relatieve) nadelen.

    Als eerste voor de programmeurs betekent de OO aanpak dat het aanmaken van een nieuwe uitbreiding voor openMSX betrekkelijk eenvoudig is. Er moet niet langer in allerhande functies dingen worden aangepast, maar een nieuwe chip dient als apart object geschreven te worden en kan zich dan via de standard interne API aanmelden als onderdeel van een MSX. Tevens kan een nieuwe programmeur zich nu beperken tot de kennis van de (verplichte) parent classes, zonder dat hij gedwongen wordt om in de andere code op zoek te gaan naar mogelijke conflicten of noodzakelijke wijzigingen. Door de GUI vs core moet een programmeur zich ook niet echt zorgen maken over de portabiliteit van platform naar platform. Nieuwe programmeurs die willen contribueren aan openMSX kunnen dus sneller (en meer gemotiveerd) aan de slag.

    Voor de gebruikers betekent het gebruik van de GPL licensie dat alle wijzigingen die gebeuren aan de code uiteindelijk ook terug in openMSX terecht kunnen komen. Dit zorgt ervoor dat alle goede ideen door alle programmeurs verder kunnen worden uitgewerkt, terwijl slechte ideen/implementaties door bekwamere programmeurs kunnen worden verbeterd of rechtgezet.

    Toestand en toekomst

    Op het moment van schrijven is de emulator al in een zeer bruikbaar stadium. De render is momenteel line-based en kan al zaken zoals interlacing( met flicker of 100Hz mode) ,overscan en de meeste illegale VDP register settings zonder problemen weergeven (ook fullscreen), er is de mogelijkheid om de Music Module en de FMPAC in stereo weer te geven. Tapes, disk en cartridges worden automatisch herkend. Meerdere XML configuratie files kunnen te samen gebruikt worden, en er is een quake achtige console aanwezig om disks te wisselen, de machine te resetten etc.

    Een eerste publieke voorstelling van openMSX is gepland om door te gaan op beurs int Tilburg. De ontwikkelaars zullen ook aanwezig zijn, zodat het steeds mogelijk is om een praatje te doen, vragen te stellen en suggesties te doen.

    Wij, de oorspronkelijke ontwikkelaars, hopen met openMSX een goede stap gezet te hebben in de richting van de perfecte emulator, die in gevoel en mogelijkheden nauwelijks te onderscheiden is van een echte MSX computer.

    Voor meer informatie en hoe je kan helpen bij de verder ontwikkelingen kan je steeds terecht op http://openmsx.sf.net/

    openMSX-RELEASE_0_12_0/doc/node.mk000066400000000000000000000004421257557151200164620ustar00rootroot00000000000000# Not all of the docs are useful for end users, either because they are about # the implementation, or because they are too rough/unfinished. # Below is the list of docs that should be included in installation. INSTALL_DOCS:= \ authors.txt GPL.txt \ release-notes.txt release-history.txt openMSX-RELEASE_0_12_0/doc/openmsx.sgml000066400000000000000000000153721257557151200175710ustar00rootroot00000000000000 manpage.1'. You may view the manual page with: `docbook-to-man manpage.sgml | nroff -man | less'. A typical entry in a Makefile or Makefile.am is: manpage.1: manpage.sgml docbook-to-man $< > $@ The docbook-to-man binary is found in the docbook-to-man package. Please remember that if you create the nroff version in one of the debian/rules file targets (such as build), you will need to include docbook-to-man in your Build-Depends control field. --> Joost"> Damad"> October 10, 2004"> 1"> andete@debian.org"> OPENMSX"> Debian"> GNU"> GPL"> ]>
    &dhemail;
    &dhfirstname; &dhsurname; 2003 &dhusername; &dhdate;
    &dhucpackage; &dhsection; &dhpackage; perfectly emulate the MSX standard and more &dhpackage; DESCRIPTION This manual page documents briefly the &dhpackage; command. This manual page is mainly meant to point to the available documentation in the HTML format; see below. &dhpackage; is the MSX emulator that aims for perfection. Supported file types of MSX media: cas: tape image in fMSX CAS format di1, di2, dsk, xsa: disk image rom: ROM image of a cartridge wav: Raw tape image, as recorded from real tape ogv: Video recording, as recorded from real Laserdisc Player Zlib compressed files can also be used. OPTIONS The program follows the usual a command line syntax, with long options starting with two dashes (`-'). Some commands also start with one dash. A short summary of options is included below. Show summary of options; at least a completer summary than this one. Show version of program. Insert the ROM file (cartridge) specified in argument Put WAV or CAS tape image specified in argument in virtual cassette player Enable external control of openMSX process Insert the disk image specified in argument Insert the extension specified in argument Use machine specified in argument Put ogv video file specified in argument in virtual Laserdisc player Load an alternative settings file SEE ALSO openmsx-catapult (1). The program is documented fully by openMSX User's Manual and the openMSX Setup Guide, available in HTML at the location /usr/share/doc/openmsx/manual on most systems. AUTHOR This manual page was originally written by &dhusername; &dhemail; for the &debian; system, but is now maintained by the openMSX team. Permission is granted to copy, distribute and/or modify this document under the terms of the &gnu; General Public License, Version 2 any later version published by the Free Software Foundation. On Debian systems, the complete text of the GNU General Public License can be found in /usr/share/common-licenses/GPL.
    openMSX-RELEASE_0_12_0/doc/release-history.txt000066400000000000000000002351531257557151200210750ustar00rootroot00000000000000Release History =============== This is an overview of the changes that were incorporated into past releases. openMSX 0.12.0 (2015-09-12) --------------------------- This was going to be (mostly) a bug fix release. But at the end we also got inspired by Grauw to add a lot of MIDI devices. And, reviewing what we changed the last 10 months, we saw that we also got loads of help from several people to add many new machine configurations and added some fun stuff like Sensor Kid and (experimental) Beer IDE emulation. On the emulator features category we give you stuff like triplesize video recording, some TAS enhancements and a callback for too fast VRAM access. So, enjoy this 'various features and bug fixes' release! Here's the list with all highlights and details: MSX device support: - fixed (S)RAM writing on turboR - fixed border color in screen 11/12 - fixed some details of SD card emulation (found with FUZIX) - fixed clipping bug in sprite-mode-1 drawing - fixed crash in openMSX when using the AVT DPF-550 extension - fixed support of 8kB RS-232C ROMs (which is the only correct size!) - fixed emulation of joystick mode of the mouse - fixed broken MegaFlashROM SCC+ (introduced with openMSX 0.11.0) - improved trackball movement emulation so that JoyTest can also detect it like on real hardware - fixed touchpad joystick pins (fixing detection in JoyTest) - tweaked volumes of SFG-01/05 against the PSG - added Panasonic FS-A1WSX/WX variant ot the MSX-MUSIC mapper - added proper YM2148 emulation (MIDI in/out for Yamaha SFG modules) - added proper MC6850 emulation (MIDI in/out for Philips Music Module) - added emulation of FAC MIDI Interface - added emulation of Sensor Kid, ported from yayaMSX2SK, which is based on Mr. Takeda's Common Source Code Project - added emulation of BeerIDE (experimental) - added many new machines: Canon V-25 (thanks to Rudi Westerhof), Canon V-8, Fenner/Samsung SPC-800, Hitachi MB-H1, Toshiba HX-10D (thanks to Ricardo Jurcyk Pinheiro), Canon V-10, Canon V-20 (JP), Spectravideo SVI-728 (ES), Mitsubishi ML-TS2 (partly, it's still work-in-progress), Sony HB-101 (JP), Sony HB-201 (JP), Sanyo MPC-6, Mitsubishi ML-F120 and ML-F110 and Hitachi MB-H3 (thanks to Werner Kai) and Yamaha AX350IIF (thanks to Rudolf Gutlich) New or improved emulator features: - added "too_fast_vram_access_callback": you can now run a Tcl script when the running MSX software accesses VRAM too fast, e.g. break to debug - added display of 'movie length' in TAS mode. This is the length of your 'movie' if you upload it to tasvideos.org - added -triplesize to video recorder: allows creation of video files in 960x720 pixels, for which YouTube renders videos at 60 fps. - some fixes in keyboard mappings - fixed slow console when emulation speed is very low - add a snapshot 'near the end' to the OMR when saving it, allowing quicker continuation of the replay after loading it - similarly, add snapshots when fast-forwarding to the target time, so they can be used to jump back more quickly - show reverse bar in green when replaying in viewonly mode - added a converter from/to OMR to/from text, which allows you to more easily edit the OMR outside of openMSX - improved implementation of 'auto-save' feature of replays: the setting will now persist over openMSX sessions - added an OSD overlay for Metal Gear Build system, packaging, documentation: - replace mingw32 build support with MinGW-w64 build support on Windows - updated our website and other URL's to point to http://openmsx.org/ or GitHub openMSX 0.11.0 (2014-11-08) --------------------------- This release brings you the following important new features: VDP access timing now also for MSX1 modes (corruption with too fast VRAM access will be visible now), implementation of most defailed differences between all used VDP chips and emulation of MegaFlashROM SCC+ SD. On top of that, there are some important (long standing) bug fixes, like the proper implementation of envelopes for MSX-AUDIO, fixing for example "Copy is Crime" by Impact. And last but not least, a lot of work was done under the bonnet, giving you even better performance and preparations for the future. Here's the list with all highlights: MSX device support: - VDP: - implemented access timing also for character and text modes (both for MSX1 VDP's and V99x8): too fast VDP access will now cause corruption on openMSX too - added support for specific MSX1 VDP's (mostly thanks to hap for all the research): - the exact VDP type can now be specified in the config file - differences between Toshiba T6950/T7937A, TMS99xx and TMS91xx MSX1 VDP's are now emulated (except sprite cloning) - updated many machine configs with the proper MSX1 VDP type (where known, additions/corrections are welcome!) - implemented that V99x8 does not support undocumented mixed modes - fixed bug in 5/9th sprite detection status register - added I/O port mirroring to machines of which we are certain it exists - MSX-AUDIO (Y8950) and related: - fixed envelope for MSX-AUDIO (Y8950), the infamous legato-envelope bug is now finally fixed! - fixed handling of key-off for percussive tones - fixed detection of Philips Music Module in Tetris II Special Edition - added emulation of MegaFlashROM SCC+ SD. Note: the implementation is such that existing software works on it, but do not fully rely on it when developing for this cartridge! - improved Sony HBI-55 emulation - fixed some details in several machine configurations - fixed KANA/CODE key not working in CHARACTER input mode - added the following machines: Daewoo CPC-51 Zemmix V, Daewoo CPC-300E, Toshiba HX-51I, Yamaha YIS-503IIR, Toshiba FS-TM1, Sanyo MPC-10 (Wavy 10), Yamaha CX5MII, Fujitsu FM-X (thanks to NYYRIKKI, Werner Kai, Maxim Vlasov and Alex Krasivsky) - added the Casio KB-7 and KB-10 docking stations (note: these extensions only work with the Casio PV-7 and MX-10 respectively; do not be worried when they appear to be not working according to Catapult!) - added the MSX Acid Test machines, contributed by FRS - added the Gradiente CT-80E 80 column card extension, thanks to Maurício Braga New or improved emulator features: - added de-flicker video filter via new 'deflicker' setting. Use this to remove flickering in cases like the dvik demos where 105 colors are simulated on MSX1 by alternating different native colors. It's disabled by default. - performance improvements: - speed up execution of Tcl scripts - many various other optimizations - fixed auto_enable_reverse (set/unset auto enabling reverse at startup) - rewrote most OpenGL 2 code to be more compatible with OpenGL ES - improved guessing of mapper type for 64kB ROMs - fixed a bug with watch_points affecting the execution flow - fixed corruption in XSA disk images - added NYYRIKKI's fast CAS loading/saving script, enable with setting the 'fast_cas_load_hack_enabled' setting to on - joysticks/controllers: - added setting to configure dead zone for joysticks - added support for joystick hats (can be used just like buttons) - improved support in OSD menu (take deadzone into account) - added dir-as-disk support for the OSD menu - removed experimental libao sound driver - added support for R-ALT on Korean keyboard (thanks to Miso Kim) - TAS: added possibility to query the length of the current replay (defined as the time stamp of the last input) Build system, packaging, documentation: - even more use of C++11 features, when using gcc use 4.7 or higher - removed build dependency on libxml2 - removed many/all unused features/libs from the build system - added support to cross compile with MinGW-w64 for (at least) 32 bit Windows using Linux - cleaned up the stuff in the doc directory - you now get what we really wanted you to get :) - changed packaging method to include stuff by default instead of exclude stuff by default - fixed native compilation on mipsel - upgraded C-BIOS to release 0.27 openMSX 0.10.1 (2014-05-01) --------------------------- This release fixes mostly bugs we introduced with 0.10.0 or were already present for a bit longer... sorry for those, but most should be fixed now :) There are also some small but nice additions... Here's the list with all highlights: MSX device support: - fixed bug in YM2413 (FMPAC), that was audible in BPS Tetris - added Sharp/Epcom HB-4000 80 column cartridge extension New or improved emulator features: - extended save_msx_screen script to take VDP(24) into acount - fixed crash on Visual Studio build when using wav files (e.g. in the cassetteplayer) - fixed crash on 32-bit Visual Studio build when recording videos without the -doublesize option - fixed crash on 32-bit Visual Studio build when using scale_factor 3 and SDL renderer - fixed waves shown upside-down in the SCC Viewer OSD widget - fixed crash when loading a savestate that has JoyTap plugged in - performance improvements: - several improvements that result in shorter start up time - fixed reverse-performance when using hard disks - fixed handling of corrupt hardware configuration XML file - fixed crash when MSX with TC8566AF FDC (e.g. turboR) crashed - fixed possible hang up when reversing with harddisks - fixed Tcl error when changing horizontal stretch in OSD menu - fixed displaying of error messages on OSD - Mac OS X MIDI support: - fixed MIDI output on Mac OS X (was not working for some applications) - added support for running status - added support for system realtime messages - added support for MIDI in - fixed issues with on-screen-keyboard on Android 4.4 - added default keybindings for controllers targeted at gaming - added mapping of B-control button to press CTRL or SHIFT in OSD keyboard - added fine grained control in which slots carts and extensions will end up - added support for single sided disk drives for dir-as-disk - added support for hard disk images to the OSD menu - added warning if ROM images with larger size than supported on real Konami and Konami SCC mappers are used Build system, packaging, documentation: - updated Windows build to Visual Studio 2013 - made compilation faster by removing more than 250 unnecessary includes - even more use of C++11 features - upgraded Tcl to 8.5.15 - added build support for 64-bit ARM - added 256x256 pixel openMSX logo image and use it e.g. for Mac OS X icon - fixed staticbindist build with more recent clang releases and for Mac OS 10.9 SDK - do not link against libraries in /usr/lib anymore on Mac OS X, only link against frameworks. This enables to compile against the default SDK. openMSX 0.10.0 (2014-01-05) --------------------------- This release adds many larger and smaller features, but most outstanding are: cycle accurate VDP command engine timing, an Android port, subdirectory support for dir-as-disk and Neos MA-20 support. These features meant a lot of work was done in practically all parts of the code (of which you can read the results in the complete list below), but at the same time we also freshened up much of the code using many of the new C++11 language features. All in all, it was time to release this stuff officially... Here's the list with all highlights: MSX device support: - accuracy improvements: - improved TMS99X8/TMS9929 color palette - improved V9990/GFX9000: - VRAM reads go via 1-byte buffer - VRAM read/write pointers only get updated when R#2 and R#5 get written - fixed initial content of VRAM of GFX9000 - V9990 P1/P2 sprites that are disabled still count for the 16 sprite limit - fixed roll behaviour of layer B in P1 mode - turboR TC8566AF has other register memory mapping than other machines - use correct Kanji font ROM for Victor HC-95A and removed S1985 - added cycle accurate VDP command engine timing (can be disabled with the new too_fast_vram_access setting) - fixes in YM2413 emulation (FMPAC) - added mapper support: - MSXtra cartridge (a debugging tool released by PTC which has its own RAM) - MultiROM Collection, designed by Manuel Pazos in 2006 - ASCII8 mapper with 2kB SRAM - ASCII16 mapper with 8kB SRAM - Super Swangi (a.k.a. Super Altered Beast) - added support for MSX touch pad - added mu-PACK extension, a MIDI module to upgrade a turboR ST to GT - added support for large hard disk images and LaserDisc images (>4GB) - added initial RAM patterns for Sony HB-F1XDJ and Sanyo PHC-23J - added support for Neos MA-20 Version Up Adapter (MSX1 to MSX2 converter), insert both Neos_MA-20R and Neos_MA-20V extensions for this - fixed Toshiba MSX-AUDIO with no sample RAM - fixes in joystick, mouse and track ball emulation - minor fixes in Y8950 (MSX-AUDIO) and YMF262 (MoonSound) - added OPL3Cartridge2_mono and OPL3Cartridge2_stereo extensions - added Yamaha SFG-01 extension and replaced the SFG-05 with the SFG-01 in the CX5M machine - added an experimental Boosted_MSXturboR with IDE machine config - added Casio PV-16, Casio P-7, Casio MX-10, Goldstar FC-80U and Sony HB-F1XV machines and an experimental Zemmix Super V (Daewoo CPC-61) machine config New or improved emulator features: - added subdirectory support for dir-as-disk - added configurable host to MSX joystick mapping, e.g. useful to bind the crawl command (button A+B) in Metal Gear 2 to a single PC joystick button - added support for bash completion (useful for using openMSX on the bash command line) - performance improvements: - improved start-up performance (on Dingoo openMSX starts over 20% faster) - reduced CPU overhead when enabling reverse - further optimized CPU emulation - sped up CAS loading (use 3744 baud instead of 2760 baud) - reduce input latency (poll host events more often and remove delays) - speed up reverse in combination with harddisk by using incremental hashing - reverse/TAS: - added reverse subcommand to truncate the current replay - show two decimals in the reverse bar time - added setting to prevent the reversebar from fading out and made that the behaviour in TAS mode - added bookmarks feature to reverse bar - added possibility to customize TAS key bindings in an external script - added setting to auto save replays at intervals - added command to reset the lag counter - added commands to go to frame boundaries and advance/reverse to frame boundaries - show values of TAS widgets also in the middle of a frame - several improvements on the OSD menu: - several improvements on the file browser - better error handling - only show media menu options that are relevant for the current machine (e.g. do not show disk drives when the machine has none) - added support for LaserDisc - added a quit menu with confirmation - added limitsprites setting - added basic mouse/touch support, including: - right click to close current menu - support for scrollwheel of mouse to scroll in lists - only show video options if they actually make sense - added option to make current machine default (started when launching openMSX) - added resampler setting (for performance tuning) - added detailed sound chip control (balance, volume, per channel mute) - added possibility to browse to other drives on Windows - added possibility to insert a new tape in the cassette player and made it easy to browse to your newly created tapes - added short cuts to browse to your file pools (if they contain stuff) - added confirmation dialog before overwriting existing save states - added feedback when executing actions like inserting disk, saving state - remove SSE and MMX assembly routines, only keep rewritten SSE2 routines - added interleaving with black frame for monitors with LightBoost support - added OSD widget that displays how busy the VDP is - enable grab on fullscreen (workaround for Mac issue) - added Cmd+I hotkey to press Insert key on Mac Build system, packaging, documentation: - created Android port (available via Google Play) - migrated to git - updated Windows build to Visual Studio 2012 update 3 - prepare for Visual Studio 2013 (next release will be built with it) - now a compiler which supports C++11 is required to compile openMSX, like g++ 4.6, clang 3.0 or Microsoft Visual Studio 2012 - integrated Dingoo packaging in the build system - better support for 64 bit systems (sizes, memory, etc.) - added document about details on VDP command engine timing - changed machine configurations from specific directories to just an XML file - removed the GP2X port - Mac support is now limited to Mac OS X 10.7 (so 64-bit only) and higher openMSX 0.9.1 (2012-09-30) -------------------------- This release mostly fixes a bug in dir-as-disk that was introduced in openMSX 0.9.0, when adding support for the DMK format and low level disk emulation. We not only fixed that bug, we rewrote dir-as-disk almost completely, making it a lot more robust. Here's the list with all highlights: MSX device support: - Accuracy improvements: - improved some details on the laserdisc emulation - improved timing of the VDP LINE command (thanks to NYYRIKKI for the ideas) - added Toshiba HX-21, Toshiba HX-22 and Toshiba HX-22I. The latter two have a switchable RS-232C interface (use the new toshiba_rs232c_switch setting) New or improved emulator features: - fixed crash with fast resampler and 8192 samples - dir-As-Disk: - dir-as-disk works properly again and is now a lot more robust - improve error reporting when something goes wrong - console support/scripts: - added script to save current screen to file in MSX loadable format, thanks to NYYRIKKI - performance improvements: - improved console rendering speed (uses less CPU) - improved Tcl integration - speed up low level disk emulation - optimized rendering of superimposed modes (Video9000, laserdisc) - several other performance improvements Build system, packaging, documentation: - Added build support for DragonFly BSD, thanks to John Marino openMSX 0.9.0 (2012-08-12) -------------------------- This release improves a lot on accuracy of floppy support, especially by the introduction of support for the DMK format. This means it should be possible to run all MSX disk software without patching it. Furthermore, our efforts to improve performance has some results: start up time has almost halved on a Dingoo A320! Here's the list with all highlights: MSX device support: - Bug fixes: - overscan: 512x512 demo by NYYRIKKI and Don't Cock It Up by Matra now work - cursor order in B-modes of GFX9000 - several small issues in existing MSX machine configurations - broken border rendering on ARM CPU's - 2nd drive detection on National machines - detail in MSX-AUDIO that prevented proper detection in MSX-AUDIO BIOS 1.3 - Accuracy improvements: - much improved accuracy for Floppy Drive Controllers (mostly WD2793 and alike) - added support for delayed motor off for disk drives, as in real machines implemented by the CXD1032 chip - disk drive rotation is now correct - added support for persistency of S1985 back-up RAM - added support for specifying the initial content of RAM and VRAM. Fixing this for the Philips MSX2's shows why Cas Cremers never noticed a bug in Akin, causing white pixels on the screen - Added initial support for Video9000: - new extension, which gives a new Video9000 videosource - the Video9000 can display the GFX9000 output superimposed over the normal VDP. Software which writes to I/O port 0x6F when a Video9000 is connected will make sure the right video signal is displayed. This is now emulated. So, use the video9000 extension instead of the gfx9000 extension to benefit from Video 9000 aware MSX software! - added video recording for Video9000 videosource - Added support for the FDC connection style of the Victor HC-9x - Added support for several floppy drive extensions: Sanyo MFD-001, Mitsubishi ML-30DC/ML-30FD, Talent DPF-550, AVT DPF-550, Philips NMS 1200 - Added Spanish Mitsubishi ML-G1, Spanish Mitsubishi ML-G3, Japanese Sony HB-10 and Talent TPC-310 machines New or improved emulator features: - MSX developer features: - more mappers now have a romblocks debuggable - added several new procs for break point conditions: address_in_slot, watch_in_slot - you can now have breakpoints and watchpoints anywhere in a MegaROM - previously hardcoded warnings for invalid PSG directions and di;halt detection are now a Tcl callback, which means you can e.g. let openMSX go into a break point when they occur, or disable them altogether - save_debuggable can now also save part of a debuggable - Console support/scripts: - added type command that can type from file and a special version with offset to type passwords) - added syntax highlighting for Tcl in the console - show errors in the console in red - implemented tab-completion for nested Tcl commands - OSD menu improvements: - list of machines and extensions are now sorted alphabetically - file lists are now filtered on extension case insensitively - Performance improvements: - OSD - start-up time - several scripts (reverse bar a.o.) - Miscellaneous: - guess_title script is now a lot better and is used to generate file names if no file name was given (e.g. to screenshots) - reverse bar has now even clearer colors to reflect recording (red) and replaying state - don't print an error when an initial CMOS/SRAM file isn't found - enable auto-run for cassettes by default - show an error message when using harddisk images larger than 2GB, because that is currently not properly supported - show progress on calculating SHA1 sum of large harddisk images - additional files for ROMs (like samples for Playball) can now also be in the same directory as the ROM file Build system, packaging, documentation: - Upgraded 3rd party libraries - Removed support for PPC on Mac and added build support for clang, which is also our new default compiler for Mac OS X - Added several utilities for DMK support: dumping tool, analyzing tool, creation tool and conversion tools - First step in phasing out the roms/ directories: removed them (including SHA1SUMS file which was redundant with the hardwareconfig.xml files) and new configs do not use the path with roms/ anymore; use the systemroms pool instead openMSX 0.8.2 (2012-01-25) -------------------------- This is mostly a bug fix release with some small improvements. Here's the list with all highlights: MSX device support: - Bug fixes: - (regression) sprite colors in screen 7/8 (visible in Ikari for instance) - VDP emulation bug, which was visible in Psycho World's 3x3 power up matrix - Y8950 (MSX-AUDIO) when using NOP Real Motion - MoonSound FM 4op mode - crash in YM2151 (in Yamaha CX5M) - sample playing in Nettou Yakyuu - several small issues in existing MSX machine configurations - Accuracy improvements: - MSX-MUSIC (YM2413 (Okazaki)): implemented SETTLE (or DUMP) phase - MoonSound (YMF278): fixed x-tal frequency and some other details - GFX9000: added basic support for set adjust registers - Added support for several laserdisc features to be able to run the Lascommate Junior High School Mathematics LD's, including a dummy Lascom Kanji cartridge implementation - Added emulation of the following mapper types: - Baby Dinosaur Dooly - Manbow 2 2nd release - Best of Hamaraja Night - Added emulation of memory based I/O for MSX RS-232C, making emulation of Sony HBI-232 possible - Added Sony HB-F9S and Sony HB-G900P machine - Added support for JoyMega: Mega Drive joy pad with 6 buttons New or improved emulator features: - Video output: - Faster laserdisc rendering - Added horizontal stretch for SDL renderer (useful for Dingoo for instance). As horizontal stretching is enabled by default, the CPU usage of openMSX when using the SDL renderer will now be higher than before. Set horizontal_stretch to 320 to disable it and gain some performance (but have a less accurate aspect ratio of the screen). - Sound generation: - Sync sound to EmuTime (greatly improves sample playback in e.g. Real Motion and TRAX Player - Added new sound driver based on libao - Fixed sound quality of blip resampler - MSX developer features: - Added vdpcmdinprogress_callback setting, which can be used to trigger a script when a write to the VDP command engine registers is detected while there is still a VDP command in progress. - Fixed memory usage issue when using step_back (e.g. via debugger) - Greatly improved performance for step_back - Fixed bug with watchpoints that triggered too late sometimes - Command line options/support: - Added recognition of .tcl extension as extra start up scripts - Fixed recognition of OMR/OMS files which were re-gzipped - Added command line option -replay to load and view a replay and also support dropping replays (OMR files) on openMSX to view them - Added command line option -savestate to load a savestate and also support dropping savestates (OMS files) on openMSX to view them - OSD menu improvements (mostly useful for hand held users): - added connector submenu - added support for running tapes - added support for horizontal stretch setting - Joystick support: - Allow joystick axis motion events to be bound, as the manual promises - Made real joystick support a bit more flexible: read all axes - Channel recorder: - Fixed record_channels command to record to the soundlogs directory - Added option to record all channels of all sound chips with record_channels - Platform specific improvements: - Fix Caps lock behaviour on Mac OS X - Add support for MIDI output to a virtual endpoint for Mac OS X - Comfortable machine configurations: - Added new fantasy machine: Boosted MSX2+ JP, based on Panasonic FS-A1WSX - Added basic compiler to Boosted MSX2 EN - Miscellaneous: - Updated all scripts to make full use of Tcl 8.5 (and clean them up in the process) to improve performance - Support in the hardware config XML format for using a single ROM image (i.e. a physical (EP)ROM dump) which is used by several devices - Make PgUp/PgDn reverse step depend on the speed setting - Reverse bar now indicates recording (red) and replaying state - The 'about' console command can now also be used to find settings - Fixed 64k ROM mapper detection Build system, packaging, documentation: - Upgraded Tcl dependency to Tcl 8.5 - Upgraded 3rd party libs - Optional additional dependency on libao - Added support for the Clang compiler - Make our ARM inline asm routines compile in Thumb2 mode - Moved Dingux port to OpenDingux: openMSX now only compiles for OpenDingux - Added lto flavour to enable Link-Time Optimization openMSX 0.8.1 (2011-03-12) -------------------------- This release builds on the previous one, in the sense that we worked out the features a little more. We added a lot of scripts to help you with Tool Assisted Speedruns (TAS), one of the reasons openMSX got officially approved as a TAS capable emulator on TASvideos.org. The reverse feature also got some updates while doing this. For the rest, this release does a lot of smaller fixes and additions, see the list below. Here's the list with all highlights: New or improved emulator features: - Quite complete support for TAS (see http://www.tasvideos.org/) - TAS mode with many TAS widgets activated automatically - extended tools: keyboard view per frame, frame reverse/advance, robust frame counter, save slot mechanism, RAM watch, etc. - Updates on reverse (and related features): - enabled by default now (except on Dingoo) - changed (default) extensions of replays and savestates to OMR and OMS respectively - added a view only mode to the reverse feature (watch replays without interrupting them) - replays now usually contain multiple snapshots, so you can quickly jump around in a just loaded replay (e.g. to the end!) - huge performance improvements when loading replays/savestates - added filepools to put your software files in; when loading a replay with a file which is not available, openMSX will scan the filepools: default location is share/software - added hover with time indicator on reverse bar - fixed bug when using DirAsDisk in combination with reverse - added step_back function: while debugging step back one instruction - Added info topic to query VDP timing information - Fixed saving of replays, screenshots, etc., when the directory wasn't available yet - Fixed bug in per-soundchip-channel audio recording - Fixed auto stereo recording for mbstereo-like configurations (was done in mono) - OSD: - Show warning/error messages that were previously only shown on standard-output in an OSD text box - Added gradients for rectangles to make the OSD look more polished - Added border property to OSD rectangle - Added script to find cases of too fast VDP access (of which the consequences are not emulated) - Added setting to disable sprites and option to make screenshot without sprites (especially useful for map makers!) - Added script to record movies in multiple files, especially useful for YouTube (record_chunks) - Replaced automatic printing of ROM info (from the software database) with a command to get the info on demand (rom_info) - Automatically plug in real joysticks into the MSX - Several performance improvements to several toys scripts MSX device support: - Fixed accuracy of mouse emulation - Fixed bug in sprite collision detection (sprites can't collide in the border) - Better support for keyboard of Brazilian MSX machines - Don't emulate Yes/No keys on machines that don't have them - Accuracy improvements: - (minor) fixes in MSX-AUDIO, YM2413 (Okazaki) and R800 emulation - Added emulation of the Mega Flash ROM SCC+ and ASCII Japanese MSX-DOS2 - Added emulation of seek delay, head-load and rotational delay on TC8566AF based disk drives - Added Sony HB-F5 and CIEL Expert Turbo machine Build system, packaging, documentation: - Updates of libraries used in the static builds - Windows binary built with Visual Studio 2010 - Basic (experimental) support for cross compilation of Windows 32 bit binaries from Linux (using MinGW) - Updated C-BIOS to 0.25, with support for various localized machines openMSX 0.8.0 (2010-06-02) -------------------------- This release brings you two major new features: reverse and Laserdisc. Reverse is similar to what you can do in meisei: you can 'rewind' time with your finger tip, to correct game playing mistakes or inspect what caused a crash. Laserdisc is the Palcom system introduced by Pioneer in 1984: now you can play all the Palcom MSX Laserdisc games in openMSX! For the rest we have mostly fixed smaller issues and added smaller features (see below). Here's the list with all highlights: New or improved emulator features: - Added reverse: go back in time to correct mistakes or replay your actions (use PgUp and PgDn to control, enable visualization of this feature with this console command: set auto_enable_reverse gui). For more information on usage, please see the (links in the) FAQ of the manual. - Debugging: - added information on last address/value written which triggered watch point - added several new debuggables: keymatrix, joystick ports, romblock - Windows unicode fixes - Added possibility to link to Generation MSX from within scripts - Various internal code cleanups - Better SDLGL-PP TV scaler, sensitive to scanline setting - Many speed and code size optimizations, developed in the context for the Dingoo port - Renamed "update" command to "openmsx_update" to avoid conflicts with the native Tcl command - Various tweaks to OSD menu: new colors (thanks Wolf), save states show date, toys menu, ... - Added new icon skin especially made for handhelds - Added OSD virtual keyboard (only international QWERTY layout for now) for devices without keyboard - Added OSD mouse support, you can now easily create drag and drop of MSX objects, for instance - New scripts that demonstrate the OSD framework: - experimental OSD script for tabbed MSXing (controllable from OSD menu) - music keyboard (visualize notes of sound chips) - SCC editor (edit SCC wave forms) - Experimental (still primitive) support for TAS (http://tasvideos.org/) MSX device support: - Added emulation of Palcom Laserdisc games on the Pioneer PX-7/PX-V60 (note: not visible in screenshots and video recording yet) - Added support for balance per channel, including support for the "stereo" effect of the FM Stereo PAK and the stereo PSG of the Pioneer PX-7 - Added new mapper: Arc - Accuracy improvements: - minor fixes in V99x8, V9990, YM2413 and YMF278 emulation - Added emulation of the trackball (thanks n_n) Build system, packaging, documentation: - Added platform/CPU support for: PA-RISC, Dingoo, SuperH, Nokia N900/Maemo, MacOSX 10.6 / 64-bit, GNU/kFreeBSD, Atmel AVR32 - For Laserdisc support (which is optional), added dependency on libogg, libvorbis and libtheora - Removed dependency on SDL_image openMSX 0.7.2 (2009-06-30) -------------------------- This is a quick fix for the 0.7.1 release. Due to a small bug in a Tcl script, openMSX wasn't able to load savestates with paths that contain spaces, which is the most common case on standard Windows installations. 0.7.2 fixes that. openMSX 0.7.1 (2009-06-28) -------------------------- This release comes mostly with good news for Windows users: we have a brand new installer with binaries which have been created in a brand new way, using Microsoft Visual C++ 2008. This means smaller binaries and also support for 64-bit Windows operating systems. Also, several long standing problems have been fixed in the Windows release of openMSX. E.g., it now includes full support for unicode! For the rest we have mostly fixed smaller issues (see below) and added some cool demonstrations of what one can do with the combination of (Tcl) scripting, the debug command and the OSD. New or improved emulator features: - Removed buggy SDLGL renderer (instead use SDLGL-PP) - Debugging: - implemented step_out - implemented skip_instruction - implemented general debug conditions (not bound to addresses anymore, but note: this is very CPU intensive!) - Speed optimizations in the CPU emulation - Console now uses TrueType fonts, and can display localized text now (as long as the characters are in the used font), in other words: it supports unicode - Optimizations in ZMBV encoding cause openMSX video recordings to become smaller and the encoding to be faster - As is default in fMSX, it is now possible to automatically pause openMSX when the mouse pointer leaves the openMSX window (not enabled by default) - Added icons in the OSD for 'pause', 'full throttle', 'debug break' and 'mute' status - Added several improvements on the OSD, regarding features, bug fixes and speed - Fixed some long standing bugs on Windows: - ALT-SPACE no longer pops up a context window - Unicode is now fully supported (e.g, no more problems with localized versions of Windows). Note: this means openMSX requires Windows 2000 or higher as of this release. - socket communication (e.g. with the openMSX debugger) is secure now. This does mean that older builds of the openMSX debugger will not work anymore with this openMSX release, you need an updated build! - Added the possibility to include OSD elements in screenshots, which are now no longer included by default (e.g. typing screenshot in the console no longer shows the console in the screenshot) - Added some scripts that demonstrate the OSD framework: - vu_meters: shows graphical VU meters for each channel of each sound chip in the currently emulated MSX - scc_viewer: shows current waveform and volume of the SCC channels - mog_overlay: help and extra information when playing The Maze of Galious - info_panel: a general information panel, similar to the DIGIblue v2 theme of blueMSX MSX device support: - Several new machines were added, e.g. Sony HB-F700P, Panasonic CF-2700 (German), Talent DPC-200 (Argentinian), Yamaha CX5M, Sanyo PHC-28L, Sanyo PHC-28S, Yamaha YIS-503F, Sanyo MPC-25FD (thanks jltursan) - Added Sharp HB-3600 dual disk drive - Accuracy improvements: - Added difference between AY8910 and YM2149 PSG's when reading registers - Z80: added 'ld a,i' quirk - V99x8: - implemented sprite collision coordinate status registers - fixed detail of LINE command (fixing one problem in Syntax Infinity) - implemented VR bit (fixing another problem in Syntax Infinity) - Added a memory mirror device to properly emulate the Sony HB-10P - Fixed sound quality regression in YM2413 emulation - Fixed very long standing bug in vblank interrupt timing, resolving many problems like the Zanac title screen, Adonis music speed, Galaga slowdowns, Penguin Adventure start up, ... - Added emulation of Nowind (mostly useful for Nowind firmware developers) - Added emulation of the Arkanoid pad, using the mouse Build system, packaging, documentation: - Build support for Microsoft Visual C++ on Windows. As a result, we have smaller binaries for Win32 and we now also have support for 64-bit Windows. - New installer based on WiX on Windows - Added a super-opt flavour which does the most aggressive optimizations. This includes using "computed gotos" in the CPU code (which speeds it up by roughly 10%), but may need 1GB of RAM at compile time. - Added support for Hurd operating systems (tested on Debian GNU/Hurd) - Converted (most of) the build system to Python, so that it is a lot better maintainable and the Visual C++ build can also make use of it. Python (> 2.4 but < 3.0) is now required to compile openMSX. openMSX 0.7.0 (2009-01-07) -------------------------- This release contains several exciting new features, clearing some long standing feature requests! The most important one of all is that we have implemented save states! It took us a while to think of a way to implement it without having some disadvantages you could have with some other emulators. The biggest one is backwards compatibility. Our system is designed in such way that it is able to cope with older save states in future releases. So, you don't have to be afraid to upgrade to a new version of openMSX: your save states will remain usable! Another big feature is the heavily improved keyboard support. In previous releases it was always a bit of an annoyance to work with emulated MSX machines which have a different keyboard layout than your host computer. E.g. using an MSX turboR, which has a Japanese keyboard, on a PC with a US-English keyboard was very annoying, as the characters that appear on the screen do not match the ones you typed on your PC keyboard. This problem has now been solved and you can use any combination of host computer keyboard and emulated MSX machine, without getting the wrong characters on the MSX screen. Full transparent dir-as-disk support has also been a long standing feature request. Well, it's here! Dir-as-disk now behaves as you might expect: any change on either the host or the MSX side is reflected on the other side immediately. This could help a lot for cross-platform development. Before we give you the list with details, we want to thank hap, the author of meisei, for helping us out with some of the features below; you might recognize them from his recent meisei releases. New or improved emulator features: - Save states (keyboard short cuts: ALT-F7 or Cmd+R to quick-load and ALT-F8 or Cmd-S to quick-save your state): - Exchangeable between different host machines, OSes, platforms - Designed to be forwards-compatible, i.e. they will work with future openMSX releases - As a side effect it is now possible to have multiple emulated MSX machines in memory (comparable with tabbed browsing) - Fully automatic host-MSX keyboard mapping (enabled by default), support for MSX keyboards of type: de, es, fr, gb, int, jp_ansi, jp_jis, kr, proto_fr (e.g. French Philips VG 8010), proto_int (Philips VG 8010), ru - Fully transparent dir as disk (by default) - Debugging: - It's now possible to break on changes of the IRQ lines: the global Z80 IRQ input but also specific per-device IRQ outputs ("debug probe" command) - Made more devices 'debuggable' - Simplified usage of the 'debug device' - Recording movies in higher resolution: 640x480 using the "-doublesize" option - Major speed optimizations: - Z80 and R800 emulation - V99x8: command-engine, bitmap and sprite rendering - SCC, YM2413, AY8910 and sound mixing in general - rewrote some critical routines in x86 or ARM assembly As a result most MSX1 and MSX2 software now runs realtime on a ARM9 200MHz CPU, which you can find e.g. in a GP2X handheld console. - Screenshots can now be made of the MSX screen only (using the "-msxonly" option) - Added an On-Screen-Display (OSD) framework, which will be useful to control openMSX in full screen mode and especially on hand held machines like the GP2X. There is already a proof-of-concept OSD available, which can be accessed by pressing the MENU (or Cmd+O) key. Note that this is still an experimental feature! Feedback is most welcome, of course. - Amount of horizontal stretch can be tweaked by the user (horizontal_stretch setting) MSX device support: - Support for new mapper types: Nettou Yakyuu (with sample ROM, for Moero!! Nettou Yakyuu '88), MatraInk (for Ink), Manbow2 (for Manbow 2), MegaFlashRomScc (for new MegaFlashROM SCC extension) - Several new machines were added, e.g. Philips VG 8000, Philips VG 8010 (also French version), Philips VG 8020/19, Sony HB-10P, Sony HB-20P, Sony HB-55P - Added Brazilian OPL3 Cartridge - Major accuracy improvements: - turboR: extra wait cycle when accessing VDP - Z80: - more accurate IRQ timing - fixed some undocumented flag behaviour (not caught by zexall) - implemented differences between turboR-Z80 and normal Z80 (SCF and CCF) - R800: - more accurate timing (correct page break behaviour) - more accurate flags (rexall passes now) - SCC: - fixes in rotation mode - corrected power-on state - V99x8: - implemented VRAM remapping (register R#1, bit7) - implemented blink in bitmap mode - V9990: - much improved command timing - bug fixes in command handling - fixed vertical scroll details (in combination with screen splits) - MSX-AUDIO: various fixes for the different MSX-AUDIO variants - PSG: emulate joystick input pin 6/7 mask quirk - more accurate 'HarryFox' and 'CrossBlaim' mapper types Build system, packaging, documentation: - Mostly updated support for building a statically linked executable on Windows. openMSX 0.6.3 (2007-12-09) -------------------------- This release contains several new features and improvements related to sound. All sound chips are emulated at their native frequency and resampled using advanced resampling techniques to the desired output frequency (typically 44.1 kHz). The advanced resamplers improve the sound quality a lot, because they are free of aliasing distortions. There are three resamplers you can choose from: "fast", which produces approximately the same results as previous openMSX releases, "blip", which produces better sound and is still quite fast and "hq", which produces the best sound but takes a lot of CPU power. Since the quality difference between "blip" and "hq" is very small and the speed difference quite large, "blip" is the recommended resampler and the new default. Many thanks to Blargg, the author of Blip_Buffer, not only for the code but also for explaining the principles behind it. More sound related improvements include stereo balance settings for each sound chip and the ability to record individual channels of sound chips. For PSG we even introduced some sound effects: vibrato and detune can be enabled to get a fatter sound. Try this in Penguin Adventure! Thanks to the blueMSX Team, we were able to quickly bring some new features: emulation of the VLM5030, the sample chip used in the unreleased Konami game Keyboard Master and emulation of the synthesis part of the Yamaha SFG-05 sound module (keyboard and MIDI support is still missing). Also on the non-sound department, the code of the blueMSX Team enabled us to add some new features: Gouda SCSI, MEGA-SCSI, ESE RAM, ESE SCC and WAVE SCSI. Note that the SCSI emulation is still experimental, so make sure you don't use it to store data of which you don't have recent backups. Last but not least: a lock-up bug was removed from the Windows version, which could occur on dual core and hyperthreading CPU's. New or improved emulator features: - SDLGL-PP renderer (OpenGL 2.0) can now do RGBTriplet scaling in hardware. - Several resampling algorithms now available: hq, blip and fast; the first two eliminate aliasing. - Channels of sound chips can be individually recorded and muted. - Sound chips now have a stereo balance setting instead of a mode (left, right, mono). Because of this, the _mode settings have been replaced by _balance settings. - PSG sound effects: vibrato and detune. You can use the new "psg_profile" command to select known good combinations, or experiment wiht the PSG_vibrato_percent, PSG_vibrato_frequency, PSG_detune_percent and PSG_detune_frequency settings directly. Thanks to Wolf for the idea and his feedback on experiments. - Several small optimizations, mostly in SDLGL-PP renderer and CPU emulation. MSX device support: - Partial support for Yamaha SFG-05 (only the YM2151, no keyboard and MIDI). - Support for the VLM5030 in Konami's Keyboard Master. - Improved accuracy in SCC emulation. - Support for ESE devices: MEGA SCSI, ESE RAM, ESE SCC, WAVE-SCSI. - Support for Gouda/Novaxis SCSI. - Support for password cartridge. - Support for the Super Lode Runner mapper. - Real support for the Halnote mapper. Thanks to the blueMSX team. Build system, packaging, documentation: - Added support for building a statically linked executable on Windows. - Added support for building a backwards compatible executable on Mac OS X 10.5 (Leopard). Thanks to BouKiCHi. - Added support for DESTDIR, which should help packagers. openMSX 0.6.2 (2007-04-15) -------------------------- This release contains several new features and many bug fixes and optimizations. The main new feature is the addition of the video recorder. It enables you to record videos, including sound, of what you are doing with your emulated MSX. Thanks to the DosBox Team for their great ZMBV lossless video codec. The most noticeable speed optimizations are in the rendering: the SDLGL-PP renderer can be up to 50% faster and the SDL renderer is about 6% faster. If you were having problems with speed, give this release a try. The trainers that are shipped with openMSX are now a lot more user friendly. It is possible to enable or disable individual cheats of a trained game. Bug fixes in the PSG and SCC, as well as a new way of sample rate conversion, result in the PSG and SCC sounding very much like the real MSX now. New or improved emulator features: - Video recording and improved sound recording (stutterless). - SDLGL-PP renderer (OpenGL 2.0) can do hq and hqlite scaling in hardware. - More usable trainers. - Extended hot keys: you can now bind any host event to a TCL command, which means you can e.g. let openMSX push the MSX F1 key if you press a certain button on your PC game pad. - Event recording and replaying (experimental for now: only available at command line, recorded session starts when openMSX starts). - Debugger now supports watchpoint regions. MSX device support: - V9990 enhancements: deinterlace, cursor Y position in overscan mode, huge speed optimizations for P modes. - SCC sound quality improvements (no more aliasing). - Support for the Playball mapper (and samples). - Fixed mirroring of some FDC registers and diskROMs. - Dot matrix graphical printer emulation added (thanks to the blueMSX Team). - Cassetteplayer recognizes end of tape and stops. - Small bug fixes in various devices: MSX-Audio, V9990, VDP, SCC, PSG, TC8566AF. - Various speed optimizations: VDP command engine, Z80, video rendering, V9990, debugger response time in break mode. - Added a few Arabic MSX machines, but note that they have not been verified to be correct. Build system, packaging, documentation: - Added support for building an application folder on Mac OS X. - Added support for building a universal binary for Mac OS X. - HTML-ized the Console Command Reference and the diskmanipulator documentation. There is now also linking to these new manuals, which improved the usefulness of them a lot. - Added a text document about how developers can control openMSX from their own application: doc/openmsx-control-xml.txt. openMSX 0.6.1 (2006-07-30) -------------------------- This release includes major internal changes, although many are not visible from the outside. It is now possible to switch from one MSX machine to another while openMSX is running. This will reset the MSX of course. Also, it is possible to insert or remove cartridges (extensions or game ROMs) while openMSX is running. This does not reset the MSX, but the MSX system was not designed to support insertion or removal of cartridges while the machine is on. Although doing so will not damage openMSX (unlike a real MSX), it is possible the MSX will hang if hardware is removed that was being used, or that newly inserted hardware will not function properly until you reset the MSX. A new advanced video renderer was added: SDLGL-PP. This renderer requires OpenGL 2.0 and uses pixel shaders to postprocess the image. For new video cards, this is a very efficient way of scaling the image. For old video cards, please stick to the SDL or SDLGL renderer. Not all scalers are implemented yet: "simple" and "ScaleNx" work, as well as the new "TV" scaler, which is exclusive to SDLGL-PP. New or improved emulator features: - Run-time switching of machines: change: set machine query: set machine - Run-time switching of extensions: insert: ext remove: remove_extension query: list_extensions - Run-time switching of ROMs: insert/change: carta remove: carta -eject query: carta - New command "hda" to change the IDE harddisk ("hdb" for second drive etc). This is allowed only when the power is off, for the safety of your data. - SDLGL-PP renderer (OpenGL-2.0), with the following exclusive features: * scale factor 4 * TV scaler: brighter pixels are drawn bigger * 3D monitor effect: (arcade look) set display_deform 3D * horizontal stretch: (MSX aspect ratio) set display_deform horizontal_stretch - Monochrome monitor effect: monitor_type - Video noise effect: set noise Low amounts of noise (for example 2.5) can make the video look better, high amounts are just a gimmick. - Brightness and contrast control: "set brightness " and "set contrast ", where 0 is neutral. A small decrease in contrast will make the noise look better. - Preload disk image: avoids repeated spin-ups when loading from PC CD-ROM. - TCL procedures can now have a help text and TAB completion. We also added help and completion to the TCL scripts that ship with openMSX. - New console command "about" which searches for commands (built-in or TCL scripts) that seem to be about the given keyword. Useful if you forgot the exact name of a command. - Watchpoints for I/O port and memory access: debug set_watchpoint
    [] [] debug remove_watchpoint debug list_watchpoints Where type is one of "read_io", "write_io", "read_mem" or "write_mem". Address is in range 0..255 for I/O ports and 0..65535 for memory. Condition and command are the similar to the "set_bp" command. - Detection of DI/HALT (hanging MSX). - Detection of Undefined Memory Reads (UMRs). When memory is read that was never written to, a TCL procedure is called. You can make your own, or use the included procedure named "umrcallback": set umr_callback umrcallback This is "valgrind" for MSX ;) MSX device support: - IDE CD-ROM support: the "ide" extension now contains a CD-ROM drive in addition to a harddisk. You can insert ISO images with the "cda" command, or "cdb" for the second CD-ROM etc. There is no support yet for audio tracks. Note that you have to run IDECDEX in the emulated MSX before you can use CD-ROMs; you can download it from Sunrise (http://www.msx.ch/sunformsx/). - Support for V9938 with only 16 kB VRAM, as used in the SVI-738. - Major V9990 improvements. - Minor sound differences between two different PSG types (AY8910 and YM2149). - Added Ninja-tap, a joystick port expander. - Added DDX-3.0, a port-based external floppy interface. - Small bugfixes in various devices: VDP, RTC, FMPAC, MegaRAM, IDE. Build system, packaging, documentation: - OpenGL support now depends on GLEW (http://glew.sourceforge.net/). GLEW is a library which allows us to conveniently use of GL extensions. - Build fixes for Intel Macs. openMSX 0.6.0 (2006-01-21) -------------------------- After a long time, a fresh release which brings a lot of internal improvements and some long standing new features as well. The biggest change for the user is the fact that we removed the SDLLo renderer and that we split the scalers into a separate "scale_factor" (controls the zoom) and "scale_algorithm" (controls the look) setting. A factor of 1 replaces the SDLLo render and as a bonus we also added a factor of 3! The latter also makes more exotic scalers possible, like the new RGBTriplet one, which tries to emulate a low res Trinitron (Aperture Grille) monitor. Also, the existing scaler algorithms have been implemented for scale_factor 3. Note that currently the GL renderer only supports scale_factor 2; if you want the new scale factors, use the SDL renderer. Mac OS X Tiger users will be happy to know that this release will compile cleanly on their systems, because openMSX can be compiled with GCC 4.x now. Also some problems specific to big endian CPU's (like the Power PC in most Macs) were fixed. In the sound department we made big quality improvement for DAC devices, like the turboR PCM and the Konami DAC which can be found in the Hai no Majutsushi and Synthesizer products. Note that in this release several incompatible changes to openMSX commands were made. First of all, the cassetteplayer subcommands have changed, because we added the possibility to save to cassette now as well. To learn the new subcommands, use "help cassetteplayer". Also the keyjoystick settings are incompatibly changed, which means your old bindings will be lost. Finally the already mentioned renderer and scaler settings have changed, so you will have to re-select your favourite scaler. New or improved emulator features: - Printer logger and SRAM are flushed at regular intervals now, so if openMSX or your PC crashes, it is less likely you will lose data. - Settings that have their default value are not saved anymore, which will make future upgrades easier (new defaults are automatically picked up). - Added support for a second keyjoystick. - More extensive debugging, including conditional break points. - Apart from communication via stdio, communication via sockets is now enabled. This means you can connect to openMSX at run time! This will be used by a debugger that is in development. - V9990: speed improvements, enabled scalers, scanline and blur. - Reorganisation of renderers and scalers (including new ones), see above. - Screenshot command has a -prefix option, useful in combination with the new "guess_title" script: give meaningful names to your screenshot. - New "fullspeedwhenloading" setting. When enabled, openMSX automatically switches to maximum speed when the MSX is loading from disk or tape. - New "autoruncassettes" setting. When enabled and you specify a CAS image on the command line or in Catapult, openMSX will automatically type the required loading instruction in MSX-BASIC. - Finally implemented a work around for the CAPS LOCK problem. MSX device support: - Internal CAS to WAV conversion produces now 5520 baud WAVs, for faster loading. - Implemented saving to cassette (see above). - Implementation of SCREEN6 border and background stripes. - Implemented support for 192 kB VRAM (now default in the Boosted_MSX2_EN machine). - Implemented support for ADVRAM: use extension "advram-p" for machines in which slot 0 is not expanded (most MSXes) and use extension "advram-s" for machines in which slot 0 is expanded (such as turbo Rs). - Sound quality improvement of DAC devices (see above). - Implemented TurboR hardware pause and hardware PCM mute. - Implemented "magic key" joystickport dongle. Build system, packaging, documentation: - Compile fixes for GCC 4.x. - Support for FreeBSD 6. openMSX 0.5.2 (2005-06-09) -------------------------- This release is mainly a bug fix release with only a few new features, but a lot of quality improvements, mainly related to audio. We reworked the YM2413 engine by Okazaki, bringing the openMSX fork up to date with the latest release of MSXplug. We also fixed an bug in the SCC emulation, which screwed up some sound effects. Other fixes include MSX-AUDIO sample RAM access and the timer accuracy of OPL4 and MSX-AUDIO. Finally, we also added a DirectSound driver for Windows. A new sound related feature is the sound logger. It writes the sound played by openMSX to an uncompressed WAV file. The current version logs exactly what is sent to the sound driver, including any inaccuracies caused by realtime synchronization. We are planning a logger that is not timing sensitive to appear in a future openMSX release. For usage instructions, see section 7.3 of the openMSX User's Manual. A last new (but still a bit experimental) feature is the disk manipulator. This is a new built in toolkit to transfer files from the host OS to the MSX disks (disk images and hard disk images with partitions are all supported) and vice versa. You can also use it to create new (hard) disk images and format them. A complete description is in doc/using-diskmanipulator.txt. New or improved emulator features: - New scaler: HQ2xLite. This is a scaler that is almost as good as HQ2x, but uses a lot less CPU. - Added DirectSound driver for Windows, which solves the problems some people had with SDL on Windows. It also runs at a lower latency than the SDL driver. Many thanks to Daniel Vik for helping with DirectSound. To switch between the DirectSound and SDL sound drivers, use the new "sound_driver" setting. - Improved debugger, with some new (TCL based) commands like disasm, step_in, step_over and run_to. - Extended IPS patch support, it should now support all IPS files. - Updated ROM database. - Added trainers.tcl: hundreds of game trainers. Use trainer_[TAB] in the console to switch on the trainer for the game you want. - Added a cheat finder script: create your own game trainers. For an explanation how to use it, see this openMSX forum post: http://forum.openmsx.org/viewtopic.php?t=34 - Use revamped console background and font as default. If you're upgrading from an older openMSX version and want to use this new background and font, you can either remove your settings.xml file or type the following lines in the openMSX console: set consolebackground skins/ConsoleBackgroundGrey.png set consolefont skins/ConsoleFontRaveLShaded.png - New icon, made by Eric Boon. MSX device support: - Updated Okazaki YM2413 core (now the default). - Added pixel accurate rendering to V9990 emulation. Note that the timing may still be quite incorrect. - Improved timing on turbo R machines. For example, speed difference between R800-ROM and R800-DRAM mode is now emulated. - Various IDE fixes from Adriano Camargo Rodrigues da Cunha. Build system, packaging, documentation: - Compile fixes for GCC 4.x. - Added experimental build support for Sparc, DEC Alpha, ARM, HP PA-RISC, IA-64, Motorola 680x0, MIPS and IBM S/390. Except for Sparc these are all untested. - openMSX is now relocatable on Mac OS X, which means you can move the installation directory around. - Our Debian packages are now uploaded to the Debian archive, soon after release. - Updated C-BIOS to 0.21. openMSX 0.5.1 (2005-03-05) -------------------------- This release includes a redesign of the ROM database XML file. This new format is supported by both blueMSX and openMSX; other emulators are welcome to adopt it as well. The old "romdb.xml" file is still supported, but will disappear in the future. The new "softwaredb.xml" file is generated from a central SQL database, using information from the old blueMSX and openMSX ROM databases. The new database design is prepared to support disks in addition to ROMs. Another major new feature is emulation of the Sunrise GFX9000. We have been working on this for quite some time already, but in this release you can see the first usable results. Do note that the emulation is still far from complete, fast or bug free. But we thought you might appreciate it already in its current shape. To use it, start openMSX with the "gfx9000" extension selected. You can switch between the MSX VDP and the GFX9000 picture with the "videosource" setting. Note that right now, only the SDLHi and SDLLo renderers are able to render the GFX9000 picture. Also new are the OSD (on-screen display) LEDs. Catapult users have had LEDs for some time, but now if you use openMSX from the console or full screen you can also see the status of the LEDs. OSD LEDs are configurable via TCL scripts. New or improved emulator features: - A new mixer implementation improves the sound quality of PCM and PSG samples a lot. - The "samples" and "frequency" settings can now be changed at run time. - Various speed ups, especially for low end machines due to better frame skip. - You can now use IPS patches to modify disk and ROM images as openMSX loads them into memory, without changing the images files. - Extensions inside ZIP files are now used to guess the right file type. - Many improvements for Mac OS X, including bug fixes and more Mac-like key bindings. When upgrading from openMSX 0.5.0, you can remove your ~/.openMSX/share/settings.xml to get new the key bindings. - New "escape_grab" command: escapes from "grabinput" once. - Debugging: enabled "cputrace" setting for normal builds as well. MSX device support: - Fixed turboR DRAM support. Programs that use this, like TRCAS, work now. - Fixed booting of SVI-738 CP/M disks. - Timing improvements of the WD2793 FDC. Fixes a few games that rely on it. - The 13 bit MSX-AUDIO DAC actually works now (used in modplayer). - Added MegaRAM Disk, thanks to Adriano da Cunha. - Improved Sony HBI-55, thanks to Daniel Vik. - Fixes in Tetris II Special Edition dongle. - Added separate machine configuration for Philips VG 8020/20. - Added MSX-AUDIO 2 extension. This is an Y8910 on alternative I/O ports. - Added FM Stereo PAK extension. Build system, packaging, documentation: - We now provide Debian packages, shortly after the source release. - Added build support for OpenBSD and NetBSD. - Updated C-BIOS to 0.20. openMSX 0.5.0 (2004-10-18) -------------------------- This release includes a complete redesign of the hardware configuration XML files. Also many devices were renamed. I/O ports are now specified in the hardware configuration instead of hardcoded. Configurations in the old format are no longer supported. If you copied the configurations that came with openMSX 0.4.0 to a different location (for example, ~/.openMSX/share), replace them by the configurations shipped with 0.5.0. The Python script share/scripts/convert_hardwareconfig.py can be used to convert your custom-made configurations. It may not convert every detail correctly, but it will save you a lot of time compared to manually converting configurations. The new ROM pools feature makes installing system ROMs easier. A ROM pool is a directory where openMSX looks for system ROMs. The new hardware configuration XML files contain the SHA1 sums of the required system ROMs, so openMSX can find the right ROMs in the ROM pool automatically. The default ROM pool is the directory share/systemroms. Settings, including key bindings and user directories, can now be saved. By default, settings are automatically saved when openMSX exits. If you don't want that, do "set save_settings_on_exit false" and save manually with the new "save_settings" console command. It is also possible to save settings to separate files, so you can keep multiple, independent configurations. Old configuration options were converted to settings: - machine: the default machine (needs openMSX restart) - frequency: the sound mixer frequency (needs openMSX restart) - samples: the size of the sound mixing buffer (needs openMSX restart) - user_directories: directories that are searched for data files The new setting "save_settings_on_exit" controls auto-saving of settings. Finally, we renamed "frontswitch" to "firmwareswitch". Changes in the video system: - Use of MMX to speed up video scaling considerably (mainly simple scaler). - Big speed up of scanline effect. - Enabled blur effect in the SDLHi renderer (simple scaler). - Scale2x and hq2x scalers now also work in hi-res modes (screen 6 and 7). - Made pixel accuracy the default. - Removed 8bpp support (which never worked before). - Fixed crash when making screenshot in 16bpp. New or improved emulator features: - Added channel mode 'off' to mute individual sound devices. - Added meta data of machines in config files. Some of this information is shown in the window title bar. - New "iomap" command lists the I/O ports and the devices connected to them. - Added a debugger interface to all (S)RAM. - Improvements in the 'type' command, thanks to Albert Beevendorp and Arnold Metselaar (welcome to the club, Arnold!). - Release key presses when the console becomes active. - Improved geometry detection of disk images; MSX1Mania disks work now. - Fixed crash in Win32 when dir-as-disk is used with files with weird dates. MSX device support: - MoonSound fixes. - Added slotexpander extension: use it to change one primary slot into four secondary slots. - 8 kB BASIC ROMs now work correctly and are auto detected as well. - Added simple DC filter for cassette images. .wav files created by MicroWAVer should work now. - Fixes for TC8566AF FDC (used in turbo R), thanks to Daniel Vik: improved timing (fixes Gazzel intro) and added format command. - Force PSG portA to be input port. This fixes joystick and mouse reading in games that write illegal values to the PSG (Match Maniac and others). - Emulation of the Tetris II Special Edition dongle (plugs into joystick port). - Fixed high-frequency reads of low-frequency clocks (such as RTC). Thanks to Daniel Vik for spotting this problem. - Many bugs were fixed, including long-standing bugs such as a VDP bug causing glitches in Andorogynus and CPU bugs causing glitches in Pennant Race and Fony Demo Disk 1. Build system, packaging, documentation: - Separated OS and CPU in the build system. - Build support for x86-64 (Athlon64). - Fixed bug: new TCL was not detected by running "make probe" again. - Added openMSX FAQ to documentation. openMSX 0.4.0 (2004-05-28) -------------------------- - Finalised the control protocol, making a fully-featured Catapult possible. - Implemented TCL as central scripting language (including console). Consequences: * AutoCommands in settings.xml have been replaced by init.tcl file * "restoredefault" command replaced by TCL's "unset" * "alias" is deprecated, use TCL's "proc" * "decr" is deprecated, use TCL's native "incr" with negative argument * "quit" is replaced by TCL's "exit" * old "info" is replaced by "openmsx_info"; "info" is a TCL command * Some cool TCL scripts added, e.g. multi-screenshot (make 'movies'), save_debuggable, vramdump (replacing old console command) - New build system which replaces the GNU auto* tools. It is more efficient, has cleaner output and is easier to maintain. You can still use the traditional "configure ; make ; make install" steps. For details, read doc/manual/compile.html. - Added support for compiling on FreeBSD 4 and 5. Thanks to ag0ny, Jorito and Reikan. Also updated support for Mac OS X, thanks to Jalu. - New frameskip/sync algorithm: tries to skip as little frames as possible to keep the right speed; maximum and minimum number of skipped frames can be set with the minframeskip and maxframeskip settings. The new algorithm can deal much better with the situation where another process or the OS claims the CPU for a while. As a result, animation and music play more fluent and openMSX feels faster. - Better CPU timing (Z80 and R800), and also for R800 specifically: * implemented CAS/RAS optimization * implemented refresh delay * IO operations take 3 cycles - CPU frequency is not fixed anymore: * frequency can be unlocked and modified from the console * "6MHz mode" of Panasonic MSX2+ machines is now supported - Fixes in TurboR FDC: FDD LED, disk change signal, drive detection, empty drive behaviour ("Disk offline"). Thanks to Tetsuo Honda. - Finalized internal mapper for Panasonic FS-A1FM and added support for its "frontswitch" for the firmware. - RS232 interface in Sony_HB-G900P has 2kb RAM. - Sony HBI-55 datacartridge now fully implemented. - Added proper support for Koei and Wizardry mappers (with SRAM). Thanks to dvik (blueMSX author) for the info. - Added about 14 new machines. - Volumes are all in a 0-100 range now. - Added master_volume setting. - Several optimisations in rendering. - Fixed sprites in overscan. - Added basic frames-per-second indicator. - Scaled up icon to 32x32; fixes icon on Win32. - Using SHIFT and PageUp/PageDown you can scroll whole pages in the console - New "type" command: use to enter text into the MSX keyboard buffer (not finalized yet) - Added a debugger interface to more devices: SCC, MSX AUDIO, MSX AUDIO sampleRAM, MoonSound, MSX MUSIC, memory mapper, PSG. Also extended CPU debuggable: IM can now be read at position 26, IFF1 and IFF2 can be read as bit 0 and 1 on position 27 - New debug interface commands: "read_block", "write_block", "after break" and "after frame". openMSX 0.3.4 (2004-01-16) -------------------------- This new release brings you the following improvements: - Several fixes in VDP emulation; more games and demos work correctly. - Added scalers for SDLHi. A scaler is an image filter which enlarges the MSX screen to PC resolution. The following scalers are implemented: * simple (original) * 2xSaI * Scale2x * hq2x Use "set scaler " to select a scaler. - Scanlines now work in SDLHi (previously, they only worked in SDLGL). - Per-pixel alpha blending for console on SDLHi/SDLLo. This makes anti-aliased console fonts look better. - Rendering fixes in Win32: * Fixed slowdowns in fullscreen mode. * Fixed console flickering. - Switched to Jarek Burczynski's YM2413 core (MSX-MUSIC). - Added high-pass IIR filter to mixer to avoid audio clipping. - Added I/O device multiplexing: you can now safely insert an FMPAC in an MSX turbo R, for example. - Added screenshot feature. Default key bindings changed to put this under PrtScr. F12 toggles full screen now, while Quit is not mapped anymore, by default. - Added Ren-Sha Turbo (autofire) emulation: "set renshaturbo ". - Added emulation of the MSX turbo R pause key. - Added LED status of disk drives. - Fixed harddisk image creation: automatically create or enlarge hd-image when the specified file doesn't exist or is too small. - New console commands: * after: execute command after a certain time * alias/unalias: put long commands under a named alias * incr and decr: increase or decrease integer settings (useful when bound to a key, see "bind" command) - Added a 'boosted' MSX2 configuration: a fantasy machine with lots of internal hardware. More of these will be added in future releases. The current configuration is still experimental; feedback is welcome. - Added alternative build system ("alternative.mk"), which is more flexible and easier to maintain than the old system which uses GNU's auto* tools ("configure" and friends). The alternative system will replace the auto* system in the future, so please test it on your machine and report any problems. You can use it with "make -f alternative.mk" to compile and "make -f alternative.mk install" to install. - Added support for Intel's ICC 8.0 compiler (only in alternative.mk). - Added experimental support for Mac OS X (only in alternative.mk). Thanks to Jan Lukens (Jalu) for testing/debugging this. - Dropped support for GCC 2.95 (please upgrade to GCC 3.x). - Added the ability to control openMSX from an external process (launcher, debugger) using an XML-based control protocol. This is an experimental feature; the control protocol will change incompatibly in the next release. For an example client implementation, see Contrib/openmsx-control.cc. We're working on a new openMSX Catapult which uses this protocol. - New features for external control clients: * renderer "none", which displays nothing. * "power" setting: enable or disable power to the MSX machine. * "restoredefault" command: restores a setting to the value specified in settings.xml. * "info" command: gets info such as lists of pluggables, renderers etc. * "keymatrixup"/"keymatrixdown" commands: manipulate the keyboard matrix. - Added a debugger interface, accessible through the "debug" command. The following devices currently support this interface: CPU, I/O ports, memory, VDP and VRAM. Also it is possible to set breakpoints. - Small improvements in debug device. The debug device is very useful for people developing MSX software in openMSX; read the manual for details. - Added "vdpcmdtrace" setting: enable or disable VDP command tracing. openMSX 0.3.3 (2003-09-26) -------------------------- This new release brings you the following improvements: - Windows specific code was merged into the main branch. We have an "official" Win32 port that is part of this release. - New HTML manuals replace the HOWTO. These manuals will make it easier for you to find the information you need. Also, the HTML layout looks better. Due to time pressure, the manuals are a bit rough at places, we'll improve them in the future. - New MSX devices: * MSX-MIDI interface (turbo R). * MSX-RS-232 support (8255 UART). * PCM input (sampling) for turbo R via a .wav file. - Added a debug device: write data to special I/O ports and its get logged to stdout or a file in a format you specify. Very useful if you develop MSX assembly programs in openMSX. - Several improvements and fixes in the VDPCmdEngine; VDP emulation is even more accurate now. - All read-only input files (ROM images, disk images, background pictures, XML) can now be gzipped or zipped and will be transparently decompressed by openMSX. Note that some games require write access to their disk in order to run (for example, Seed of Dragon). - New dir-as-disk feature: a directory can be specified instead of a disk image and the contents (up to 720K) will show up as files on the MSX. There is no support for subdirectories (as used by DOS2) yet. This feature is still experimental, so please report problems if you encounter them. - Several cassette emulation updates and new features: * Automatic .cas to .wav conversion in CassettePlayer. (no patched ROM needed anymore to run .cas files) * Cassette sounds are now audible. * Tapes can be rewinded. * Tapes can be forced to play. (like unplugging the 'remote control' plug on a real machine) - A different keymap (e.g. for Japanese PC keyboards or to use Russian MSX keyboards on a US English PC keyboard) can be used now, via settings.xml. - Keys for the key joystick are now configurable in settings.xml. - Added joystick emulation for mouse (a feature that the Philips SBC-3810 and Sony MOS-1 and similar mice have). - Various console updates: * Tab completion behaves much more like the UNIX Bash shell now. * Clearer error messages. * More than one command is possible, use ';' to separate them. Especially useful for binding several commands to a single keypress. - User-configurable channel settings for mono sound devices. You can set them to left, right or mono mode at run time. - Many things are now user configurable via the console: * .wav input file for PCM sampler * log file for the printer logger * output file for the MIDI out logger * input file for the MIDI in reader * input and output file for the RS232-tester - ROM database now uses SHA1 sums in stead of MD5 sums. - The MSX turbo R machine description uses a single 4MB ROM image, plus the Kanji ROM. This is closer to how the real machine works. - Several machine and extension configurations added. - Fixed compilation on GCC 3.4-pre development version. openMSX 0.3.2 (2003-06-09) -------------------------- - New feature: MoonSound support. Original implementation taken from MAME, thanks to Jarek Burczynski (FM code), R. Belmont and O. Galibert (wave code) for allowing us to use their code in openMSX. Improvements and bug fixes were done by Arjan Bakker and our team. - New MSX devices: * MegaRAM * PAC (Panasonic SW-M001) * Support for 8 kB ROMs - New feature: grab input (try "set grabinput on"). When enabled, the native mouse cursor cannot escape from the openMSX window anymore; makes using the mouse a lot easier in windowed mode. - New timer using the Linux Real Time Clock (RTC), which is more accurate than the SDL timer. SDL timer is still available as a fallback. Instructions to configure your system for using the RTC are in the HOWTO. - New effect in the SDLGL renderer: afterglow (try "set glow N"). - User-configurable gamma correction (try "set gamma X"). Also the default value is less bright than the previous hard-coded value. - Screen accuracy works now (try "set accuracy screen"). It is fast, but very inaccurate. - Volume of sound devices can be adjusted at run time. - Various console updates: * Commands can be on multiple lines. * Search in command history. * Command history is saved. * Support in SDLHi/Lo for console font PNG images in indexed mode. Previously, only images in RGB mode were supported. * Console remains usable when openMSX is paused. - Several machine configurations added (thanks to Albert Beevendorp). - Several extension configurations added. - Cache coherency problem in SDLGL renderer fixed. This bug caused garbled graphics in for example Ark-a-Noah and Dr Archie. - Fixed compilation on GCC 2.95. openMSX 0.3.1 (2003-05-18) -------------------------- - Fixed compilation on GCC 3.3. - Fixed SDLGL renderer on XFree86 4.3. - New feature: configurable file-extentions. - Improved layout of help text (try "openmsx -h"). - Many improvements to the console, including: * Extended the console editing keys (left, right, del, bs, home, end etc). * Added key repeat. * Reset the scrollback when any key other than page up/down is used. * Added resizing and moving to the console. * SDL console characters are no longer blended (improved readability). * SDL backgroundimage can be scaled and can have any pixel format. * Fixed color of font on GLConsole overlayed on Text1 mode. - Emulation and event handling are now done in a single thread. The previous multi-threaded solution was not very portable among platforms or even different versions of the same platform. As a bonus, the single thread model is simpler and more efficient. - Improved "make install". - Cleanups of the directory structure. openMSX-RELEASE_0_12_0/doc/release-notes.txt000066400000000000000000000115261257557151200205200ustar00rootroot00000000000000Release Notes for openMSX 0.12.0 (2015-09-12) ============================================= This was going to be (mostly) a bug fix release. But at the end we also got inspired by Grauw to add a lot of MIDI devices. And, reviewing what we changed the last 10 months, we saw that we also got loads of help from several people to add many new machine configurations and added some fun stuff like Sensor Kid and (experimental) Beer IDE emulation. On the emulator features category we give you stuff like triplesize video recording, some TAS enhancements and a callback for too fast VRAM access. So, enjoy this 'various features and bug fixes' release! Here's the list with all highlights and details: MSX device support: - fixed (S)RAM writing on turboR - fixed border color in screen 11/12 - fixed some details of SD card emulation (found with FUZIX) - fixed clipping bug in sprite-mode-1 drawing - fixed crash in openMSX when using the AVT DPF-550 extension - fixed support of 8kB RS-232C ROMs (which is the only correct size!) - fixed emulation of joystick mode of the mouse - fixed broken MegaFlashROM SCC+ (introduced with openMSX 0.11.0) - improved trackball movement emulation so that JoyTest can also detect it like on real hardware - fixed touchpad joystick pins (fixing detection in JoyTest) - tweaked volumes of SFG-01/05 against the PSG - added Panasonic FS-A1WSX/WX variant ot the MSX-MUSIC mapper - added proper YM2148 emulation (MIDI in/out for Yamaha SFG modules) - added proper MC6850 emulation (MIDI in/out for Philips Music Module) - added emulation of FAC MIDI Interface - added emulation of Sensor Kid, ported from yayaMSX2SK, which is based on Mr. Takeda's Common Source Code Project - added emulation of BeerIDE (experimental) - added many new machines: Canon V-25 (thanks to Rudi Westerhof), Canon V-8, Fenner/Samsung SPC-800, Hitachi MB-H1, Toshiba HX-10D (thanks to Ricardo Jurcyk Pinheiro), Canon V-10, Canon V-20 (JP), Spectravideo SVI-728 (ES), Mitsubishi ML-TS2 (partly, it's still work-in-progress), Sony HB-101 (JP), Sony HB-201 (JP), Sanyo MPC-6, Mitsubishi ML-F120 and ML-F110 and Hitachi MB-H3 (thanks to Werner Kai) and Yamaha AX350IIF (thanks to Rudolf Gutlich) New or improved emulator features: - added "too_fast_vram_access_callback": you can now run a Tcl script when the running MSX software accesses VRAM too fast, e.g. break to debug - added display of 'movie length' in TAS mode. This is the length of your 'movie' if you upload it to tasvideos.org - added -triplesize to video recorder: allows creation of video files in 960x720 pixels, for which YouTube renders videos at 60 fps. - some fixes in keyboard mappings - fixed slow console when emulation speed is very low - add a snapshot 'near the end' to the OMR when saving it, allowing quicker continuation of the replay after loading it - similarly, add snapshots when fast-forwarding to the target time, so they can be used to jump back more quickly - show reverse bar in green when replaying in viewonly mode - added a converter from/to OMR to/from text, which allows you to more easily edit the OMR outside of openMSX - improved implementation of 'auto-save' feature of replays: the setting will now persist over openMSX sessions - added an OSD overlay for Metal Gear Build system, packaging, documentation: - replace mingw32 build support with MinGW-w64 build support on Windows - updated our website and other URL's to point to http://openmsx.org/ or GitHub And of course the usual various bug fixes and performance improvements. In "doc/manual/index.html" you can find a set of HTML manuals for openMSX. Make sure you read this if you haven't used openMSX before, but also to learn more about the new and changed features. CPU and graphics performance varies a lot, depending on the openMSX settings and the MSX hardware and software you're emulating. Some things run fine on a 200 MHz machine, others are slow on a 2 GHz machine. For performance tuning tips, see the Setup Guide. openMSX is confirmed to run on the following operating systems: Linux, Windows, Mac OS X, FreeBSD, OpenBSD and NetBSD; on x86, x86-64, PPC, ARM, MIPS and Sparc CPU's. Running on other operating systems (wherever SDL runs) or CPU's should be possible, but may require some modifications to the build system. If you are compiling on a new platform, please share your experiences (see below for contact info), so we can make openMSX more portable. openMSX Home Page: http://openmsx.org/ Project page on GitHub: https://github.com/openMSX Contact options: - Talk to us on #openmsx on irc.freenode.net. - Use the forum on http://www.msx.org/forum/semi-msx-talk/openmsx - File a ticket on https://github.com/openMSX/openMSX/issues Thanks to all contributors for their feedback, support, bug reports, testing, coding and other help! Have fun with your emulated MSX! the openMSX developers openMSX-RELEASE_0_12_0/doc/release-process.txt000066400000000000000000000105111257557151200210370ustar00rootroot00000000000000openMSX Release Process ======================= This is a kind of recipe for doing releases. Having it written down decreases the chance of forgetting a small but crucial step during the hectic work of getting a release out. Preparing for a release ----------------------- - Tell all developers to commit only fixes. - Sync with most recent C-BIOS release. - Check that the "staticbindist" build still works, in particular on win32 and Mac OS X. - Verify the documentation is up-to-date. Currently most documentation is in "doc/manual". - Write release notes in "doc/release-notes.txt". - Add the change list to "doc/release-history.txt". - Edit the version number in build/version.py: packageVersionNumber = '1.2.3' And set releaseFlag to True. Note: An alternative approach would be to create a branch dedicated to the release and continue accepting any kind of commit on "master". To put the development focus on bug fixing, we haven't done this, but it is an approach that might be useful at some point. Creating a release candidate ---------------------------- - Update the release date in "doc/release-notes.txt" and "doc/release-history.txt". - Don't forget to commit and push all these last changes before tagging! - Tag the git archive: git tag -a RELEASE_1_2_3 -m "Tagging release 1.2.3." git push --tags - Create the distribution tar.gz file based on the new tag: build/gitdist.py RELEASE_1_2_3 - Save the created file somewhere you can find it again: mv derived/dist/openmsx-1.2.3.tar.gz / This is the release candidate. - NOTE: for a Windows build, you also need a RC of Catapult! Sanity check on release candidate --------------------------------- This is a small check to be performed by the release coordinator. - Test build: * Compile and check for warnings: unset OPENMSX_FLAVOUR (and any other OPENMSX_ flags you have set) make * Test installation: su make install - Start openmsx with your usual config. - Verify the version number in the title bar. If the sanity check is passed, distribute tar.gz to fellow developers and testers. Full test of release candidate ------------------------------ - Check behaviour with empty ~/.openMSX dir. - Check that the default config (C-BIOS_MSX2+) works. - Check random other configs. - Do extensive testing with things that are known to work and likely to give problems (like Unknown Reality, Metal Limit). - Do some valgrind runs to make sure there are no hidden problems. TODO: More standard tests? Repeat until release worthy --------------------------- Create and test release candidates until a satisfactory release candidate is made, which will become the release. If there are any last-minute patches, don't forget to move the release tag, such that the released code is equal to the tagged code. Make the release public ----------------------- Put the release on GitHub (refer to https://help.github.com/articles/creating-releases/): - Make sure you're logged in - Go to https://github.com/openMSX/openMSX/releases. Click "Draft a new release" - Select the tag version you made above: RELEASE_1_2_3 - Type 'openMSX' and the release number of the new release at "Release title": openMSX 1.2.3 - At the "Describe this release" box, describe the release, e.g. take the first few paragraphs of the release notes (before the list of changes starts). - At "Attach binaries" select the files to upload, including our own tarballs. In short: all the files we used to upload for an SF.net release. - Note: GitHub also generates a tarball and a zip file. Just ignore it for now. Do not use these in any links on our website or somewhere else. They contain a lot of unnecessary stuff. Announce: - Post change list to msx.org. - Post news item on web site. - Post news item on our Facebook page. - Post news and/or change list to openmsx-devel and openmsx-user mailinglists (at least one packager uses it to get a trigger) Finally: - Update web site to point to new downloads. - On the openMSX home page, update the lists "features of current release" and "features in development". - Set releaseFlag to False in build/version.py - Update libraries in the 3rd party build system to latest versions, so that next release will profit from this and you won't forget it. Just before the release is too late (unless it's some minor security fix)! openMSX-RELEASE_0_12_0/doc/schema1.png000066400000000000000000000200031257557151200172260ustar00rootroot00000000000000PNG  IHDRXW^AD pHYs  ~bKGD̿IDATxOeGv׿CeKa˃lwVba@žݨؠkFy<*Ĉ6f2YD9! +Ķ$fP8$i,}޻s}nݪsO}ԩ A&pq\,rq\,rq\\,rqq\,Ry^Kd+Y ;ђZ)""dA$i(X5;ԝwQeC,Z1N(n/+`NEąZE*ІwkV>XqpY)I;'$-%,IPz҂ˀ!{_;B LB怠%T""Ak%$9"XTbr,1*&=|Rg(Y#Rʚ8np,r,r#,r,;rj`yG:X[r,Hr9X`9Xn5r#,˭`9X֮{O|GO>lç` ۯ;^U+8X~/=xp?{\]e}OYnݸ}ܹy5WW.[o}` w~?sd{_XOK^tK/~kV`}K7pw|ލoǚz;/o Oyp;ކ`9Xzp'\>cmKX[Ɔu5G{ jg{_ٵ=gs`%}8@X,p, _]mLSn*|6hϤ]>-8Xn{Et,S_~ YNVt>[Ic N7$$iI$Ha}ǻEDH(Hi)]bXEtKIB"Ia 2V$"dLI *4 %ko[02eG Ǫ##RrceZ\QF5# ~mZɌy,(mY I=0tC/ +HVtLRҕ1HF~X]o!8LkPO7=ݠR[h#eeD0Xǵq"KqjD/ô M՚lya 1snSS K 6,5NP\UU,ղ#X5[;YX*{a֗)7`az7p XZ`]b5uZ8^+`q!tXk?,䩩P sO7> CE$uAF1hCe5 tժ O+7n޺ywnܾ=C}Ŗ7n5+~~#;|p[.yŧoԎ/oXwɷ?Y*nr,r,r,r,r,r,r,r,r,r,r,r,r,r,r,#XO?y:yܹk`|>xO=w7L_{=zW|s=_ɩLɿ'_y7o=ܭ{uo~t"?ݯLȿO zg?K]z 7~i?_xco`}?{xQӀ7nSǿ߹[GϿt+}ON|goWɉ+G|SyO t>~_.bǘP/?~xXO?|_؟~^pssϽxz:bʹq‹5NGM/v*PWgmu|9zvM~fPn8CbʹNn($I;}ۚwtv3Q(7Nn, (0W3[ۛ[8ZIHn|n8"X}'< pOV1Wcn0*tka񞢒x`Zڽb`x`\ ふG3H7hIBSJ %[D ɔ$zZ߫%QIZj(-","# e7& $U }PqtCIb )YERq!F ( dZo,R :HQݥ/W5 ڌ % "$mQYj{ %L妀UA#QkςFĘϗnUj$ J6cI& #P#dH)kFS7Ln("$, =t,$Y+I!,( @RIfD0 1X!rtYgN7TMM+`)0e>:T8o!8L{*gn`'FM`!F)_9]i&VȢ:utC%&HidumZIZfn(Vj՞Z,,U՝)w ,fyl1 XVAAb㦖nK)  /*'A5n(ˀ52*dd "" ` KDvM($㨊Pj d+ 5 @ #2}YO)"hf!fV(ݵG A[NV*K{tCYX*iMLYl ( `f2ZJHu@&mﴈ1̲kIH. ,:43eiYztM,^XTDrak-7,ºZmB[_ќnݝž.Ct%P;O2dN? 5.c%6s{/ݠz1`olKXw᚟6p:K 6ԙ >m utŭOn`C9pqӦ.PgM7\*O7\ΚnUin`Cy n^{_ɯiW{?:ig]'M7˯Wxxw<@7N~7{ x֥|o; ` =Zv)n7^۝}'d6ytÇǸ[Sɿ,?}<wۿ~Y<ع'?L߰ `oX%c-ةēuJN<v),r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,r,`}Hh+[s,r,ryB_zr,rw`9X`[s;l/ޗ/I._wU㖼^֟,C?ݥ5k"R44]4锅‹ 'Xe{q  {_&=u"díu6Muw)D3cL,`I )޾2f6#I~A"崱umK!=WWdu۾VI,\%IFDK vK ɽH,9X?ef`ǎK0t ` $cRJ \*V6ׂ5Yܸ}unKUK7KE V+EJ RpM%[ZZE\WrtԫbumߑLKa6BiHAadq: iد~ $,+`.TZ)9HcbR M %FkuMj$]k.\rlM`qX}s5[; XD"Y1ݲbC'vTAr DdֿL^EDB0+,ȐǰGFCJ-,2<$R eV @4 л$UՈvk\+!n;ZՎUF,C0*XvqRK!DU49&"s@7\N1Z Zv-),u`tA K2Fri)VPuXЍ`X8_*ɫ `D#,VjE1 KV(1dz?a.78V{b$.rd  #""'._:Ehth_jiSKda;/hreɝ,X=c$CԀ2qI2Եz[kj=9kaTWM96}P.-6j:Q|R߆;Ԧ[li,hd Jh!oVzc2*]e!k)\0Mnj* 7 NgHY8O>QW  LjrE6.Mj,$rY& \Edd@e (uP 怺?&!i],$!z4kUZSS+ [>vljbۜM(ꨪBcu>\,rq\\,rqq\,rq\,rq\\,rqq\,rq\,rq\\,rq" ( tIMEsRIENDB`openMSX-RELEASE_0_12_0/doc/schema2.png000066400000000000000000000342021257557151200172350ustar00rootroot00000000000000PNG  IHDRXW^AD pHYs  ~bKGD̿8IDATxݏו ħ@/0p!艍@x iWۘbRf+=Q0k.?lOfLǃvZXR{nNizZb4OϪ[ .-s={y:44ӐX2R+%16L)gKw’`B;`/ꆴ,K%`IK(P%-KZB ȏ%`I$X,) JdPeЬmc޳fmDŽ[Dz| US|vn 5pòj-[x;X{C6M*5Ӭ9k HkU"$%nu 5WmA{X:y k[AߚX!T;0cқ@2; +4vvҔs'XPl0vk<_flЁ8vr d,=3`Q_k,֐ehLej,esYЃ~h!6-7YCq8S@u^qeV&#`,/זG|egGcaЍʈ ]̴x?:ôzWBuWĭ׮NVG'k#䴶O6wnmܫc#I^\XwX7hVA֚Z$z #ѯv!4]4 Xckj,ؠc }m!δ.H;gybڱ3rq g`*pru |hEtJЩ*BQǟ NJ6iFl~'vVX#Rj4=6[aa`PQ}3`iӦ+4oidžrEqh V͓Qnӱ#ZFE#ca/a,e a^{ iVK:eY4φXj3uYU),Fڌ)4"* ]Q?,;{XHŽps, "c\hc2A^̀5|;㲅`!_)} rU<ɅzX^"v˛nj$.˂t߁Dm Zl.wMii셚/X`"0 X&7X,!,X]g͓ut7e6x:vͥX8\@]c' Xb`Y@C:c2 71ÝStqXLlP,W,XzX(&4ĝ,X@M /C6\㮝{U>]_խnijDX>Vitݱ&XCF!ӽsBY4kfHm~w/XN@ĴDHC,Xm*F[,n[c)U ؀c8NVaIf&(4j.),9W#'ZW^ W8֑`@ظ+OO5?\ $ё`ɏa\!lϻi?X$Xɉ+Tpp Vڂ"Q`M#ªk&"N`B$\w),i %XJVHRp#K\-x#0_ۖD^sZM ,Ȕ$G ?x [Cn_y-'LqZM ,q3"[oރgӺoj`DA#l܀R/5||wu&o8PWa^<'T,LN^gӺoZ`E',O_|:?YXe$Gs)W3 t5e'yÃ|2 DBهH(v~L= LIl`S:iq @/_g}v/u@|?U0Fa>K/ o8-u/h'hPvr6q8GWM +ϟp*T ==!щ:G\ЩݚȦ:? _~|6X=f\fTI~{ibꅣI<%VEy?%T ў!`% Ap/¼Ώ\c{,[_! "lak;@ | X٥.땋PF͜%` R'^/lBxoX_}cj$Y*a)9SAR7%ɊPYT/QR+hq]CNO<f=hBdѬT8p(sʚ6ߊs^ x_?ȕqK3]x;rK۹'p4ߗLp_$Wq8Y58f,n"sxE2<ƚ% Un:3&_s$0qE٠)a 7. D/ CXQ2m\· Y4!RU:tl!\e,.d WK U; (a3O]•8c'=h3+ N? #}X5J(k>TRQ PJel]epUEI ,:]b@0: vʐ@""8*"z]Y3ޠWD)%#YD7uA0AQXA|'d ׬)%#C,"٫X`юʀKFJ`1,! *]DJ,z ,솥KKqKS֣Sy]nZ4>)Ӏ),;2]zX]?Lw$X >FXS$. Xn\d&;XځT>XޚX  `u ^ lu}2,*BҴ2-_V@qX QatZL`-_+eR[R*gШUT#@ :49Xf10V2@\ FpzLhT*<5[wZ{͔0϶ 4A␗)ʨUg w舡Xm_KuwJ[[fSn 9 Kg0SB@ K'+CNV_}[7$XR:_,6?ޖ`Ihuik "VfjmKXd]IA~p~{ ;} \6[tBRj ` VEᄸV%rEˑ6PŒWXS\2]X.؁ S8&є,,EP4/p@1wr`ou-ަdՄ @z=  v&u5 -nIC-CFv:`Q(ҝhEJ!|vo" UTeВ D+(ĚM~J"A!q_3CrI#Fn0%'(RE'"_${.O}-]|rEpO\ v*St'D8,_r)KQ)hz,Q'd A»K@.о2S||1* z&Ӓ X82果- !\ > jJ+i"IPd!1[: `Iu4lafz(K PX±Yi ibfQef2 :L,i ee%L} d\,i %X6L^QYr"x:>ގ5ݽv݇p "8qr%rhaܦpx'rdS; r`0]wp [(eM£wxZqZB6!alɭF,v[I8z`e4]0ϟwr#`16me} B`kUqkTk9}$XhY^/knP # DR.< Vdw\-`(l$X|lYbI$# DZ{Y[~sQcN%X|M$ A8>u$/`}^F ݲ6Y/%KAj3 V&wa?V(~YKnV&~^z~k`=TZZ8KXq}=wD泪)XBzT͋~dݻvo$Gaz5 {]!~`f*WSY^c~?xRBh*@qh ֮;ṣ~?[`LYS*1pDʳG߾Y+g;8^Xu3wTLe(15<>T:Uhxud`s{.k7 f2cuC#kQLr,@־U#gOݑ{PTK{f"w,>0C`, = g,\N8X?sj%Mkb.ׁlJRV"om XUƮ-\lyr[ "+EAo1?iUY=yλ8,-N ޹Ki5 z'XPՂ8X ,nh01ޠҰڷ|;:s qõ2XSΠwU/(vҦaTp3U=WT"& M3N:wQn .SX@$ h{(XU#`A%,ZJ "Akٴ8=w2LM/>-tNy'(bR1bZfrz4}Kl&1rl+PAQet# HdkFjxH%Z!3sѴ%W7Dq!Y,_{]u'+ ;A𮛭zBmxXq_^ XCG?X ]mrDZ`R:\gA>UKEGzKK.A:_m@w>~nGUG,HjmџjJv0ju1 X*w:l <.R@\NY=xiRU\[#6-JxP7g#`z-{ r鹯HKSd DZexO3Qت,~+Tp >O(2N!Y]ERYO>x9D)<,ʯX8-^穰 $ݍ}'[QQX*r1i Kߏrݮl!ʞeW퍙a/[=^akùR7g􅒥 $XYod_( s d Vd}WBcaHPW6>rm1,X:͵N ߪСN/b |JJۂ?v5lrK _WQr2RZA5A'DpfFUWuUki Ĕk\OoX2X4%gD9nެKy3]"rB A>?OYyù~lWX=~<-uc{qGd57>o+OR/4gp97+zU@֧1p%t-Cxu淢獑WX u_6́Wpt?fj/VX)SLT?fjX4iEx &(5e %%"ƦBYXy!)uyapl%`DgAXOdFe XQOቭ3E K]`1W4+g@4+~K1_uGe]~~kJ9>MX#vDh>ȍ|Ӵ#kkNJ9mP5{#XSK*`zX`J{'"=`-.J'+&dB+1XA[+H'[~!T}dfS*QMh9}Xuޭz{hv6﵈jBRM}+n5r <fd}.(Zڭ*}eiorz ]&1B)*jzk +2* b( R1 -n*7ރeyE0]xAw]_anhǀE[`yHN9+:rt2UptW4c@Wt*ल(*Eix5PR.72j@tAD[kPK6XwK w7̅v $M0ZiT\`)N l*-eCUYoo d)/xn.B*>]rhX`-cVU8b Xp8iA^7,2X (sBen5xAM_[se}7Ya5K[gm,lP=ֵP$X1]4 I,e],&+ Ac~տdO. ;Uqex~á&+fB Q UXSI# R:"Fui,O;Ԇ&+f:>`Ơkil4,5k@"f8pU. tmB YȚ\`ha IъA|+vr޻ƕ`} h6X, Y7"q'E{k] c V)) `BnXCoQ^v6dm`lA`: Qi7Ya5+x$xy(xCR .X׫AE93cǂRDE G7覀 =-CJEwx5xA)_zdXB )XZȰFfSa@>z +FngA~L3{oa7Y@ 2S@“B?d K%XS@hD͎y #`NaJ`ѡpȐdK:nɏl%k4I%E%XW@`B,dI'+?`B,Xrs\ %X3SY_](WdȺ\(clJ!{4'ԜX`$ 䧙7JtIME6ڱYIENDB`openMSX-RELEASE_0_12_0/doc/screenshot.png000066400000000000000000000233671257557151200201020ustar00rootroot00000000000000PNG  IHDRe̮PLTEä777_*# 77_1* # ***_ _777777iii_ 777_ 77/Dnnn77_17$ֻݾԭ벲*¼tRNS@f%IDATxmo6R‚|>-ФiҤ۲{n?jfO^NL휔IYV<0KM_eSHK!dI9!E!!lOTsB\Ķ DJ&U' Obqr/\r"HILK5Di[8=}$eC׿>k}(m~?|KA<5Ih /V$#D #.xm=n!s} MPKnD::Bݘu!F&Cu+B,X:_ 4XkAup2ofҾ}suAeNpIgPQ|P,SD8 ᛻ )ݯ[B,s&fP $=pwe2 Q2BD qN 8p3r/Pwh-bS[\u;B,p b@  s6@W{s6-[rC>aF\J86/E 9n'B ⥮Sn߿G|B6V{ 'O`F"SF IB!"9B|bȳAq͉M,2%d l\F41 !2 !20""C C !2 !20""C C !2 !20""C C !2 !20KX&dlt q9V]w;!DB$DB$DB$DB\2ĦYRjJ:ˇXUF <5[]MSm4˃8rB$DB\*D۞*mkˎKXU8<:HT)-qKP\ !"7\J cMHqZ." {DժjB b4OJkO:R)*G{/Yľ3HŊ> zb pCP1HPѢCޥ$2gbv=O3FB$DB>lP4Ĵμ mLʁJq> q `q"(RE,fD0BX9U5  c #4y0Vd 6>D_IKZ=E^EHmfhC8 q.< g~8FHK oh_Ā_o09faDlcQ1GHÏ2 qUӥTY /X~!vg@ HHhD?5g)FD@ !D@؟(53SԚ wah#CCç8I1hK ۶v|_|E!"!flC;rZ"Dm [8HcG؏  qW-ScF`GnԻ8<c31⵭eg  ~ E08#x>_g&UGG 쿞F1.Hx(%@|SAg1";mDm$}u/r{s<2ŀs.LjQqٖ_"3k=&"rD^ tL_ b@صkD J<]Q O5B&m u.!F`- qߑbd/(- >^m` x o󶿱wv}, bKɗ>g[ DR{k}vb(8zNJ: ߜ| +" yF[r61ADAlqZS Py{.Cp!lB2 6iq)|Wg DFu %BI|!ɷ@)\A o70- ꚽ.l1gVyh '%]3I 60ְ vƗ &lںIokV-:5tx<*5q[DQ{A暑|cۏ4 b0RtAٴuz{BBOVc@ Cc bok8m "7?@\k1eX b;"wgB {ߏDAؿz u$,~@, m "(Ԯˬ(8"" 2ۙcc >['(m!:% CQQzB,'8!9NQDLٴuۓ,}}~ADAz1ȧ~{.X -`8=`盧i!fbyD^{߼)Ⱥv(8nbd"ɦ˷^?ip+޺ADA5BҠ{xu-y,(l9+$/O1Y7DAįJbn$Yy~ADAW 3h_8K t_Q  kHQ ?kF\n?X~*D'DA̖ " 0ơ&A6Hu(dEG(g@gtQngY몭C><^:i3\61XwcV?kxЍk>;;|×nG,(mo5b6xq-ۧ8<:~: -D㚂kG QD?x|@5y9';w A"tHPQ[][˖OK*dֹ8FP܇诅T9 U0AADAl MR7-Iֹ)+{DADA<$y9߿az|c` "~ADAyc,ziݲiν1l[1ed5{O:!`&@\||$vADAqMM:ܕ ql{k`D:/B,5ܒDAs rnYDXxˣb(wH 9H:?TW~<ݔDA~Qxaj8{]s! E@so1)ayDAđ@k)뚽 E@'S! X?$@A |r0$D/ s ޹(AT<ղCe)\8U/9D &xQ~#P8DV7 jvFgex%6s`.Q DOj>xj nĖQ?2XN:5i56J^ Hu}/ D8-aSZ~ `U&3ܳ <Ԍ h  ?q}TƵuĬ !G6=#UH|bbX A4|wC1C=u:Pk]] c=0\FA@SA4bLߌjݔ fu1 eS`y寣PG- =ADպqm=` 5؆JO G1 "]HQ&}%gAE)h A< Qo.Ǭ^8>X\W A4Al-N0jBY;Sa A~ÓI\a"~u\{rѳ6}O\Kh yx/ rİZ7rzWW@` D8dxD f[r|>g3P rɓz V`c( =A55'kjjΩ\vS Čٟh< D8{҃XoUkjJ.y?]_[诸5 S++ \h %L=ʏ͙V׋ 1XA/ 61 "04 bX|TZė!ܖIkj95D  b r\1ucвnFr l 瀈 zV 3pe m?]q<5_l cP8[9VkNkxx}MXsj6 miz>Z[ǵwZ+ Rs(fh_:X=1\%d;SOЩ 1vNkݢkuGj7z D\s&wT+ۏևDI]qt DI_{6.'Jjxtgjp)5DhMcZ9ʍ]_75d A<D,! (im\`VNiu{ 7SA4/bJ+uZ׃ڂ^+6֪5:s A| S:+T $:R3@^?>5DxCi唶NƩQux *DS:A_~N+%rt w|FҪ j\m1U8[ "?nO3e&*Qfh > F 0ArZ[Ali_eA4qJ~[͕VNi6%[?}ϯۓLH b<>N =@=וHa1Mi制NLhԬrzN_ ;[â@ȴrJ[qU&Ol=]SZ;u~ݾFǷDr:Ф}b6Niu{)7x DxX(rB[qZ9SItշ Dx,!\+w6NiFۓLz ߳b dR֘6"J\wL7[W3W9]J @e2[.q]-EǷD F$E?X-q-΍ A|15WKw#eTKsUn):f ԃ3?ڹzclSA4 b]ئQ-΍ LImCl ADqTKsc5vٷ皚 A@ '@~#7r]%;W\)ܧ2~IPez#EvI?oa 1A-KD5MSdz2I-KƋ n\"5DY dgY:]v7^ 9` bH9ȁ{s~wnDh7 bY dGY:Uv7^*Kin?@ A4[ >,.;}Y?Q}- b`HztY޲sחc8iNm3 n41բet:e馉 } Z AD s{[ӿ*[W߃8tq#Di@.{ڱ_U6n }n\r į",q/ !o3 n"Sòfqծ1θŖfh?Ly~ v b,7@T6NՃf'!|-h D KCHq.ATOŒ/DAƗSvh#^hi6@9=ƩU*)R ?V Eh =AWkHUGl@DXd A^nvoatBkb;x Eؒ9a^.Վ&||X~?;δ"{/1 [3Ϙr A4cAL ass2UG Y-kATv'!Q(b=|D0|d ADziPRN aL'AD+AL PTu;A˪%q4D7!Z5$+| s(s^_DѿjgkϣaFJse$ -@L [ ԄnMOҩ?[XbzHsrfh x3  ִď o~0%x[ A4[I\~ b"R[Mg0?A̿J)ʹ4DhM#H8~υ(_|hmeg42XK A4+Avشi C۰MO{Pː|*WKsX A4mWo 7ϔD"^,Ŀ5Lo+s;4C7%l :@Pm&'u[akG#";ˣ( bf N ^b4 K~tmV >Q?`l1Ҝ'`)0?0 !֫쌸|[l A_ ֯cm?_sYNҫSEv^0 wk)<4xS%;ַTa B1Lbztx]rʎS717P Bh H* GB@@W_q]k쌸|$x/l5q\D'=@1hW%MT({'5EvHs@H3Yf  @9 #@27/UNŋw"^3u@KGr1q(sNMrNf_t-JeD]eg->,HיA42TOkHʺc`AS읈e 9Gg @Ҍ JQet;A5U;/މx]qv, ~ei%|IuSÄAYWezin^& 5=R6=AOLET/nUvFN)ǻ}A4;uY7|";9${=4ɊA4Gל'e<=ҽ5 :GÞ_egc`ci6 nY靦y5U l'8_eg3 x> ߛ=1雯((W:!D%@̲ny ޲uAq>߉RG.Ӕ}ނ5x ĭA\L1%@-[ PJi._f?`qio ]1PXy0RͰ*[[E3@t GlZ7a@?mϼn{D3_L ė z|!ȴnm)oHkh8+u@j0lZCI: GE[M0-7(K3֍؝/"C@/͉lZ:(]Qli" 9ք55u?Q ė8yr(\ ؘ2apkwVMKwdMi8!w gQAD*a;m__2Y7oQ Ȅ#h BzˍMi "GRPas﬑c+Q ~x@uc|ں~HϨj+@Q D.tWU(w8爫wKsi@q__.Q3_n;9[ 笆q ?$X &N{sY=-ݡ}̢@"RPT y4%!ŽfhM = b*i4Wﬓf .>(ĿSbϴ+廓O O1M]|; 9M,@4ľ4;f4 D(-q<_2O⫃@/T"qNH-$ľ$P WXr-wtj-N Dx^6d' +uR(?D75h,s2,@ ]^F"tut | fC@<~P>,# W[@>>4@m!ҺA'kPǾ4{lƂ| _qFt DѦ z6F^FR?$#y 4XiDZq4s + ȃFN@ ?zG Dx\{Ay~I(f f4@& \(zAy~IX_Y7AfQ C_ D[AyvIqb_7Q "Z}4W Ǭ /Ag(3UҳDxgx ivq .Skk堇@{=pi8Cz:iMfIl#bu#¼A&eqDҌ8LRSfK@n$ϛdY|iQ =&iNCPzYLT č ڤEߏLf;qSL@ s= >2IY_;w0 EaBQ"!1EQjZܓqĪ zfv]"+}^Ozs}^[?_GGD5ԚADm骽NłSHH'3"#M7b(bȠC`0M +j"`Q,DLzq%D qLexXVV.t}_Ufr՜fc Japanese MSX-DOS2 ASCII 1988 MSX-DOS2 cartridge with 256kB memory mapper and kanji BASIC. OS ROM + memory expansion 42f4e3360eb134857b0efa82c1abedf342680124 ascii_msxdos22.rom MSXDOS2 256 42f4e3360eb134857b0efa82c1abedf342680124 ascii_msxdos22.rom openMSX-RELEASE_0_12_0/share/extensions/AVT_DPF-550.xml000066400000000000000000000015271257557151200221210ustar00rootroot00000000000000 Floppy Disk Drive AVT DPF-550 External 5.25" disk drive. external disk drive AVT 633ad62599361bbb8ba6803cdcfaa14c54521c43 avt_dpf-550_disk.rom 1 openMSX-RELEASE_0_12_0/share/extensions/Beer_IDE.xml000066400000000000000000000020741257557151200220230ustar00rootroot00000000000000 Beer IDE SOLiD 1995 ATA-IDE interface with hard disk. external hard disk hddrom.rom 5a14ac0a1cce3ceebdcd66c42b2d5c975d45b755 11bd12a95142185d18659ccf74748fc1bb20c60d e92f47bd8320f94b8f7ae1943b31316002075ffb IDEHD hd.dsk 170 openMSX-RELEASE_0_12_0/share/extensions/Boosted_audio.xml000066400000000000000000000042301257557151200232410ustar00rootroot00000000000000 MSX-Audio Philips 2009 Philips NMS-1205 Music Module, upgraded with the MSXPro expansion that includes 256KB of SampleRAM and the MSX-Audio BIOS that makes it a fully compatible MSX-Audio device. sound expansion Philips 12000 256 Normal0000 3b02e5f45603df2c180f9c07d59b2ccc5a87bd0a dde213b6d9324b94e78ed8df753042469bd31ef0 05e9f40bb7bdc46b557d445c717cb84ad75252f6 7f115ff923a1cc0d1944cf280168add946fde854 msxaudio12pre4a.rom 0x1000 0x1000 openMSX-RELEASE_0_12_0/share/extensions/Casio_KB-10.xml000066400000000000000000000011171257557151200223120ustar00rootroot00000000000000 Docking station Casio KB-10 1984 Docking station for the Casio MX-10 and MX-101 with 2 slots and a printer port docking station openMSX-RELEASE_0_12_0/share/extensions/Casio_KB-7.xml000066400000000000000000000013161257557151200222410ustar00rootroot00000000000000 Docking station Casio KB-7 1984 Docking station for the Casio PV-7 with 2 slots, additional 8KB RAM and a printer port docking station openMSX-RELEASE_0_12_0/share/extensions/Checkmark_FM_Stereo_PAK.xml000066400000000000000000000017551257557151200247600ustar00rootroot00000000000000 FM Stereo PAK Checkmark 1990 FMPAC clone (Yamaha OPLL), with home-made program. sound expansion 13000 -100 100 19bf90b8d2e695ffad61fff6f47f7fcda2f24a1d fmpak.rom openMSX-RELEASE_0_12_0/share/extensions/DDX_3.0.xml000066400000000000000000000020711257557151200214610ustar00rootroot00000000000000 DDX 3.0 Digital Design Double Sided Double Density port-based external floppy disk drive. external disk drive Microsol 1 ddx_3.0.rom a363015286341ffabcdc38ef0d890897ed51e322 124126cee840e43c7668c11c2bec2f34b3d00c68 openMSX-RELEASE_0_12_0/share/extensions/ESE_MEGA-SCSI.xml000066400000000000000000000015511257557151200224300ustar00rootroot00000000000000 MEGA SCSI ESE Artists' Factory 1995 SCSI interface with hard disk. external hard disk 1024 MEGA-SCSI_1024.sram SCSIHD SCSIhd1.dsk 100 SCSILS120 openMSX-RELEASE_0_12_0/share/extensions/ESE_Ramdisk.xml000066400000000000000000000012131257557151200225450ustar00rootroot00000000000000 ESE Ramdisk ESE Artists' Factory 1995? MSX SRAM disk. SRAM cartridge 256 ESE-RAM_256.sram openMSX-RELEASE_0_12_0/share/extensions/ESE_SCC.xml000066400000000000000000000013451257557151200215710ustar00rootroot00000000000000 ESE SCC ESE Artists' Factory 1995? MSX SRAM disk with built in SCC SRAM cartridge with SCC 256 ESE_SCC_256.sram 13000 openMSX-RELEASE_0_12_0/share/extensions/ESE_WAVE-SCSI.xml000066400000000000000000000017351257557151200224650ustar00rootroot00000000000000 ESE WAVE-SCSI ESE Artists' Factory 1995? MSX SRAM disk with built in SCC and SCSI controller SRAM cartridge with SCC and SCSI 1024 WAVE-SCSI_1024.sram 13000 SCSIHD SCSIhd1.dsk 100 SCSILS120 openMSX-RELEASE_0_12_0/share/extensions/FAC_MIDI_Interface.xml000066400000000000000000000007601257557151200236400ustar00rootroot00000000000000 FAC MIDI Interface FAC 1991 MIDI cartridge openMSX-RELEASE_0_12_0/share/extensions/Gouda_SCSI.xml000066400000000000000000000017441257557151200223500ustar00rootroot00000000000000 Gouda Novaxis SCSI MSX Club Gouda 1995 SCSI interface with hard disk. external hard disk novaxis.rom 352ec8c99fe1397a88ecb3aee6dbb4d121c818e9 SCSIHD SCSIhd1.dsk 100 SCSILS120 openMSX-RELEASE_0_12_0/share/extensions/Gradiente_CT-80E.xml000066400000000000000000000016471257557151200233140ustar00rootroot00000000000000 Cartão 80 Colunas Gradiente CT-80E 1987 Gradiente 80 columns expansion cartridge graphics expansion V9938 16 false e89eb87f295a2c93688265c4f6f25a9ccb4e3697 ct-80e.rom openMSX-RELEASE_0_12_0/share/extensions/Konami_EC-702.xml000066400000000000000000000011701257557151200225540ustar00rootroot00000000000000 Kanji ROM Cartridge Konami EC-702 1985 Kanji ROM cartridge. kanji font expansion ec-702_kanjifont.rom fc71561a64f73da0e0043d256f67fd18d7fc3a7f openMSX-RELEASE_0_12_0/share/extensions/Konami_SD-Snatcher_Sound_Cartridge.xml000066400000000000000000000013121257557151200271640ustar00rootroot00000000000000 SD-Snatcher Sound Cartridge Konami RA-011 1990 SD-Snatcher sound cartridge (a.k.a.: SCC+). sound expansion SD-Snatcher 13000 openMSX-RELEASE_0_12_0/share/extensions/Konami_Snatcher_Sound_Cartridge.xml000066400000000000000000000012761257557151200266710ustar00rootroot00000000000000 Snatcher Sound Cartrdige Konami RA-004 1988 Snatcher sound cartridge (a.k.a.: SCC+). sound expansion Snatcher 13000 openMSX-RELEASE_0_12_0/share/extensions/Lascom_Kanji-dummy.rom.gz000066400000000000000000000002661257557151200245650ustar00rootroot00000000000000Mlascom_kanji.rom {=z0c^openMSX-RELEASE_0_12_0/share/extensions/Lascom_Kanji.xml000066400000000000000000000007061257557151200230170ustar00rootroot00000000000000 Lascom Kanji ROM Cartridge kanji font expansion lascom Lascom_Kanji-dummy.rom.gz openMSX-RELEASE_0_12_0/share/extensions/MegaFlashROM_SCC+.xml000066400000000000000000000015041257557151200234320ustar00rootroot00000000000000 MegaFlashROM SCC+ Manuel Pazos 2010 MSX Flash cartridge of 1MB with SCC-I and PSG. Flash cartridge 9000 megaflashromsccplus.sram MegaFlashRomSccPlus openMSX-RELEASE_0_12_0/share/extensions/MegaFlashROM_SCC+_SD.xml000066400000000000000000000020601257557151200240160ustar00rootroot00000000000000 MegaFlashROM SCC+ SD Manuel Pazos 2013 MSX Flash cartridge of 8MB with SCC-I and PSG and 2 SD card slots. Flash cartridge 9000 SDcard1.sdc 8 SDcard2.sdc 100 megaflashromsccplussd.sram MegaFlashRomSccPlusSD true openMSX-RELEASE_0_12_0/share/extensions/MegaFlashROM_SCC.xml000066400000000000000000000015051257557151200233600ustar00rootroot00000000000000 MegaFlashROM SCC Manuel Pazos 2003 MSX Flash cartridge with SCC. Flash cartridge 9000 512 megaflashromscc.sram MegaFlashRomScc openMSX-RELEASE_0_12_0/share/extensions/MegaRAM_Disk.xml000066400000000000000000000013551257557151200226510ustar00rootroot00000000000000 MegaRAM DISK Digital Design MegaRAM with built in RAM disk. RAM disk expansion 512 f0abc38f345c05e2112079239d09f25fb08a7c56 mr512.rom openMSX-RELEASE_0_12_0/share/extensions/Microsol_CDX-2.xml000066400000000000000000000015411257557151200231070ustar00rootroot00000000000000 Microsol Disk Drive Microsol CDX-2 1986 Double Sided Double Density port-based external floppy disk drive. external disk drive Microsol 1 cdx-2.rom ffc1bafca38d20ed6e9a27c90c6ab089a8482d89 openMSX-RELEASE_0_12_0/share/extensions/Mitsubishi_ML-30DC_ML-30FD.xml000066400000000000000000000015641257557151200246510ustar00rootroot00000000000000 Micro Floppy Disk Controller/Drives Mitsubishi ML-30DC/ML-30FD 1985 External Double Sided Double Density 3.5" disk drives with controller cartridge. external disk drive Philips 658bc7cd4b19bd5face14f09e4e2904157a5c298 ml-30dc.rom 2 openMSX-RELEASE_0_12_0/share/extensions/Neos_MA-20R.xml000066400000000000000000000015451257557151200223110ustar00rootroot00000000000000 Version Up Adapter MSX BASIC Vev. 2.0 Neos MA-20 (R) 1985 MSX1 to MSX2 expansion kit, clock chip part. ma-20r.cmos 2a3d953a423fa513f0d26a8bf0bd50664688868c NEOSC.ROM openMSX-RELEASE_0_12_0/share/extensions/Neos_MA-20V.xml000066400000000000000000000015411257557151200223110ustar00rootroot00000000000000 Version Up Adapter V Neos MA-20 (V) 1985 MSX1 to MSX2 expansion kit, video part. V9938 128 20100697cc10c3a1e49d371bf647fed9c47c4d04 NEOSV.ROM openMSX-RELEASE_0_12_0/share/extensions/OPL3Cartridge.xml000066400000000000000000000011051257557151200230210ustar00rootroot00000000000000 OPL3 Cartridge Luciano Sturaro 2004 Yamaha OPL3 based sound expansion, version 1. This is the older version without MSX-Audio BIOS. sound expansion 12000 openMSX-RELEASE_0_12_0/share/extensions/OPL3Cartridge2_mono.xml000066400000000000000000000030201257557151200241310ustar00rootroot00000000000000 OPL3 Cartridge MSXpro 2011 Yamaha OPL3 sound expansion, version 2. Using the mono version of the MSX-Audio BIOS. sound expansion 12000 Normal0000 b344b5b7988f459354a2db49d7d339ceeee7d864 msxaudio13.OPL3_mono.rom 0x1000 0x1000 openMSX-RELEASE_0_12_0/share/extensions/OPL3Cartridge2_stereo.xml000066400000000000000000000030261257557151200244700ustar00rootroot00000000000000 OPL3 Cartridge MSXpro 2011 Yamaha OPL3 sound expansion, version 2. Using the stereo version of the MSX-Audio BIOS. sound expansion 12000 Normal0000 9c924cc79b17cfe468fc7c969868be86ccbe9d46 msxaudio13.OPL3_stereo.rom 0x1000 0x1000 openMSX-RELEASE_0_12_0/share/extensions/Panasonic_FS-CA1.xml000066400000000000000000000025551257557151200233360ustar00rootroot00000000000000 MSX-AUDIO Unit Panasonic FS-CA1 1986 Panasonic's Yamaha Y8950 MSX-AUDIO cartridge. sound expansion Panasonic 12000 32 36d47cf70618fdb460f97a8ceb75013ec4529063 a45692849acf29ddb653707a62747985439f6d4f 930eae7057af1652abae794072b296a59decd61b fs-ca1.rom openMSX-RELEASE_0_12_0/share/extensions/Panasonic_FS-FD1A.xml000066400000000000000000000015651257557151200234450ustar00rootroot00000000000000 3.5 inch Floppy Disk Drive Panasonic FS-FD1A ? External Double Sided Double Density 3.5" disk drive in one cartridge. external disk drive a2f70f911ccaa6831008bd473e92a3f0eb1e8b0f fs-fd1a.rom 1 openMSX-RELEASE_0_12_0/share/extensions/Password_Cartridge.xml000066400000000000000000000006171257557151200242540ustar00rootroot00000000000000 Password Cartridge Password Cartridge implementation 0x1234 openMSX-RELEASE_0_12_0/share/extensions/Philips_NMS_1200.xml000066400000000000000000000017401257557151200232530ustar00rootroot00000000000000 2DD Micro Floppydisk Drive Philips NMS 1200 External Double Sided Double Density disk interface. external disk interface false Philips nms1200_6.rom f0e0447a0872a2f09d7b28d9599fdd183acb5cb2 0b63e9cf99bc2a3d7f21cabf1cb668a6ca71ad2d 1 openMSX-RELEASE_0_12_0/share/extensions/Philips_NMS_1205.xml000066400000000000000000000022261257557151200232600ustar00rootroot00000000000000 Music-Module Philips NMS-1205 1986 Philips' Yamaha Y8950 MSX-AUDIO cartridge with sound sampler and MIDI. sound expansion Philips 12000 32 c7463e1fd0433c5d41b70670d6c10fd781b66426 nms1205.rom openMSX-RELEASE_0_12_0/share/extensions/Philips_VY_0010.xml000066400000000000000000000021541257557151200231520ustar00rootroot00000000000000 3.5" Disk Drive Philips VY-0010 1985 External Single Sided Double Density 3.5" disk drive. external disk drive Philips adb90803ba6fd25df35d10ac0d90da23f1b9c164 4a55eb07e1ab8b847e230854e2e26e48983982a6 8924e3e11eb1c8c1edcb7efa63c26d2bdc142473 vy0010.rom 1 openMSX-RELEASE_0_12_0/share/extensions/README000066400000000000000000000002711257557151200206200ustar00rootroot00000000000000This directory contains the definitions of extension-hardware, which you can insert in your MSX machines. Use the ext command in the console or the -ext command line option to do this. openMSX-RELEASE_0_12_0/share/extensions/Sanyo_MFD-001.xml000066400000000000000000000014351257557151200225420ustar00rootroot00000000000000 MSX Floppy Disk Unit Sanyo MFD-001 External 5.25" disk drives. external disk drive Sanyo bb72819da1160bc375fa3623f2d0a5424d08c89b mfd-001_disk.rom 2 openMSX-RELEASE_0_12_0/share/extensions/SensorKid.xml000066400000000000000000000014171257557151200223660ustar00rootroot00000000000000 Sensor Kid Cartridge Sony HBJ-H081C 1988 Sony Sensor Kid cartridge sensor cartridge 794aca3c2136933f1fe6405e56e0ce7fdbb10385 sensor-kid.rom openMSX-RELEASE_0_12_0/share/extensions/Sharp_HB-3600.xml000066400000000000000000000014501257557151200224760ustar00rootroot00000000000000 Dual Disk Drive Controller Sharp HB-3600 1985 External 5.25" disk drives. external disk drive Philips 608e8e9ab91c67f7015a67ea12f52ea3b0d609cd hb3600_disk.rom 2 openMSX-RELEASE_0_12_0/share/extensions/Sharp_HB-4000.xml000066400000000000000000000010461257557151200224720ustar00rootroot00000000000000 Cartão 80 Colunas Sharp HB-4000 1987 Sharp 80 columns expansion cartridge graphics expansion V9938 16 openMSX-RELEASE_0_12_0/share/extensions/Sony_HBD-50.xml000066400000000000000000000022131257557151200223070ustar00rootroot00000000000000 Micro Floppydisk Drive Sony HBD-50 1985 External Single Sided Double Density 3.5" disk drive. external disk drive Philips 4000 ab57b4c8e5d47c2a39031c2057b97d2d10546582 2144036d6573d666143e890e5413956bfe8f66c5 hbd-50.rom 1 openMSX-RELEASE_0_12_0/share/extensions/Sony_HBD-F1.xml000066400000000000000000000015651257557151200223420ustar00rootroot00000000000000 2DD Micro Floppydisk Drive Sony HBD-F1 1985 External Double Sided Double Density 3.5" disk drive. external disk drive Philips 4000 e067ce2b444ec7a3dd45d9e2df4b34c1ba732872 hbd-f1.rom 1 openMSX-RELEASE_0_12_0/share/extensions/Sony_HBI-232.xml000066400000000000000000000015051257557151200224010ustar00rootroot00000000000000 RS-232C Interface Cartridge Sony HBI-232 1985 Sony MSX RS-232C (version 2) cartridge. RS-232C cartridge true true 373aa82d0426830880a7344ef98f7309d93814c7 hbi-232.rom openMSX-RELEASE_0_12_0/share/extensions/Sony_HBI-55.xml000066400000000000000000000007321257557151200223250ustar00rootroot00000000000000 Data Cartridge 4KBytes Sony HBI-55 1984 SRAM data cartridge. SRAM cartridge HBI-55.sram openMSX-RELEASE_0_12_0/share/extensions/Sony_HBK-30.xml000066400000000000000000000016301257557151200223160ustar00rootroot00000000000000 Micro Floppydisk Drive Sony HBK-30 1985 External Double Sided Double Density 3.5" disk drive HBD-30W connected via HBK-30 interface. external disk drive Philips 4000 ba83a80a0041e3a8fb5c075a9b4a29ee92255d62 hbk-30.rom 1 openMSX-RELEASE_0_12_0/share/extensions/Talent_DPF-550.xml000066400000000000000000000015011257557151200227060ustar00rootroot00000000000000 Floppy Disk Drive Talent DPF-550 External 5.25" disk drive. external disk drive National d9c82e091b5fa1b5ee9b7f012f0084611762eb93 dpf-550_disk.rom 1 openMSX-RELEASE_0_12_0/share/extensions/Toshiba_HX-MU900.xml000066400000000000000000000016521257557151200232260ustar00rootroot00000000000000 MSX Music System Toshiba HX-MU900 1986 Toshiba's Yamaha Y8950 MSX-AUDIO cartridge. sound expansion Toshiba 12000 0 0d246b0e3edc63803fcce861ea07eadf29dc488c hx-mu900.rom openMSX-RELEASE_0_12_0/share/extensions/Yamaha_SFG-01.xml000066400000000000000000000015231257557151200226000ustar00rootroot00000000000000 FM Sound Synthesizer Unit Yamaha SFG-01 1983 Yamaha YM-2151 based sound cartridge with MIDI sound expansion 30000 49a1750c10e407293af6bce27a02e99307ceba12 sfg-01.rom openMSX-RELEASE_0_12_0/share/extensions/Yamaha_SFG-05.xml000066400000000000000000000014531257557151200226060ustar00rootroot00000000000000 FM Sound Synthesizer Unit mark II Yamaha SFG-05 1985 Yamaha YM-2151 based sound cartridge with MIDI sound expansion 30000 6680d7118d85418813f1db9449bf3e20942b16da sfg-05.rom openMSX-RELEASE_0_12_0/share/extensions/advram.xml000066400000000000000000000013541257557151200217370ustar00rootroot00000000000000 ADVRAM Ademir Carchano A hardware modification that makes direct access to VRAM possible. This version can be enabled by software. hardware mod true openMSX-RELEASE_0_12_0/share/extensions/audio.xml000066400000000000000000000014201257557151200215600ustar00rootroot00000000000000 MSX-AUDIO 1986 Generic Yamaha Y8950 MSX-AUDIO, including MIDI ports to enable proper detection. sound expansion Philips 12000 256 openMSX-RELEASE_0_12_0/share/extensions/audio2.xml000066400000000000000000000015221257557151200216450ustar00rootroot00000000000000 MSX-AUDIO 2 1986 Generic Yamaha Y8950 MSX-AUDIO, on alternative I/O ports. sound expansion Philips 12000 256 openMSX-RELEASE_0_12_0/share/extensions/debugdevice.xml000066400000000000000000000010061257557151200227250ustar00rootroot00000000000000 Debug Device openMSX Team 2003 Artificial device to create debug output, see the openMSX manual. debug tool stdout openMSX-RELEASE_0_12_0/share/extensions/fmpac.xml000066400000000000000000000015501257557151200215510ustar00rootroot00000000000000 FMPAC Panasoft SW-M004 1989 Yamaha YM-2413 based sound expansion, with SRAM. sound expansion 13000 9d789166e3caf28e4742fe933d962e99618c633d fmpac.rom fmpac.pac openMSX-RELEASE_0_12_0/share/extensions/gfx9000.xml000066400000000000000000000006701257557151200215620ustar00rootroot00000000000000 GFX9000 Sunrise 1994 Yamaha V9990 based graphics expansion. graphics expansion openMSX-RELEASE_0_12_0/share/extensions/ide.xml000066400000000000000000000022021257557151200212170ustar00rootroot00000000000000 Sunrise ATA-IDE Sunrise 1995 ATA-IDE interface with hard disk. external hard disk ide250.dat 93e41c7d479bc90c1d1f6d081af20fd9924b8ada 1c79c8fcfd661394f91be94b218955c84cabd742 51a1467d73d26c91aab7923e3be66490daaffdee IDEHD hd.dsk 100 IDECDROM openMSX-RELEASE_0_12_0/share/extensions/mbstereo.xml000066400000000000000000000025451257557151200223100ustar00rootroot00000000000000 Moonblaster Stereo openMSX Team Combination of FMPAC on the left channel and generic MSX-AUDIO on the right channel. sound expansion 13000 -100 9d789166e3caf28e4742fe933d962e99618c633d fmpac.rom fmpac.pac Philips 12000 100 256 openMSX-RELEASE_0_12_0/share/extensions/megaram.xml000066400000000000000000000011051257557151200220700ustar00rootroot00000000000000 MegaRAM Digital Design 512 kB MegaRAM. RAM expansion 512 openMSX-RELEASE_0_12_0/share/extensions/moonsound.xml000066400000000000000000000013051257557151200225020ustar00rootroot00000000000000 MoonSound Sunrise 1995 Yamaha OPL4 based sound expansion with wavetable. sound expansion 32760893ce06dbe3930627755ba065cc3d8ec6ca yrw801.rom 12000 640 openMSX-RELEASE_0_12_0/share/extensions/msxdos2.xml000066400000000000000000000014351257557151200220640ustar00rootroot00000000000000 MSX-DOS 2 ASCII 1990 Generic MSX-DOS 2 cartridge, as was spread in Europe. OS ROM c36c9e0f96738a340381e23b4f97245388801a46 51a1467d73d26c91aab7923e3be66490daaffdee msxdos22.rom MSXDOS2 openMSX-RELEASE_0_12_0/share/extensions/mu-PACK.xml000066400000000000000000000023701257557151200216210ustar00rootroot00000000000000 μ.PACK Bit²/ASCII BM-117 1991 Upgrades your Panasonic FS-A1ST to a Panasonic FS-A1GT by adding 256kB memory mapper and MSX-MIDI including BASIC extension MIDI cartridge 256 mu-pack.rom e88ca790ab58e7709cd99cbbb23ff7d33631b71f openMSX-RELEASE_0_12_0/share/extensions/nowind.xml000066400000000000000000000021511257557151200217570ustar00rootroot00000000000000 Nowind USB Interface Sunrise 2008 An interface that emulates a (hard) disk drive on an MSX, backed by (hard) disk images on another computer connected via USB. For now only Nowind 4.0 is supported. nowindDos2.rom 1d4c09166755c6569ff0ed3878bbf1b22d7e3864 8afd1ec5843ecfdcbff11497fa8c55b870c0c6f5 7f49675940b9a4ffff4e8abc0b410c7b1fe22b87 2c359c979fbfe5c7c3cce90b4593dba4b7cbcd92 nowind.flash openMSX-RELEASE_0_12_0/share/extensions/pac.xml000066400000000000000000000011031257557151200212200ustar00rootroot00000000000000 PAC Panasoft SW-M001 1989 SRAM game save cartridge. SRAM cartridge pac.pac openMSX-RELEASE_0_12_0/share/extensions/ram1mb.xml000066400000000000000000000010461257557151200216420ustar00rootroot00000000000000 Memory Mapper (1 MB) 1 MB memory mapper cartridge. RAM expansion 1024 openMSX-RELEASE_0_12_0/share/extensions/ram2mb.xml000066400000000000000000000010461257557151200216430ustar00rootroot00000000000000 Memory Mapper (2 MB) 2 MB memory mapper cartridge. RAM expansion 2048 openMSX-RELEASE_0_12_0/share/extensions/ram4mb.xml000066400000000000000000000010461257557151200216450ustar00rootroot00000000000000 Memory Mapper (4 MB) 4 MB memory mapper cartridge. RAM expansion 4096 openMSX-RELEASE_0_12_0/share/extensions/ram512k.xml000066400000000000000000000010521257557151200216420ustar00rootroot00000000000000 Memory Mapper (512 kB) 512 kB memory mapper cartridge. RAM expansion 512 openMSX-RELEASE_0_12_0/share/extensions/rs232.xml000066400000000000000000000014631257557151200213410ustar00rootroot00000000000000 Generic MSX RS-232C Generic MSX RS-232C (version 1) cartridge. RS-232C cartridge 47c78981b5ffbb9e8d9c2145f9c2e642ff0e8193 4e9384c9d137f0ab65ffc5a78f04cd8c9df6c8b7 rs232.rom openMSX-RELEASE_0_12_0/share/extensions/scc+.xml000066400000000000000000000012461257557151200213100ustar00rootroot00000000000000 SCC+ Konami 1988 Generic expanded Konami SCC+ cartridge. sound expansion expanded 13000 openMSX-RELEASE_0_12_0/share/extensions/scc.xml000066400000000000000000000012041257557151200212270ustar00rootroot00000000000000 SCC Konami Empty Konami SCC cartridge. sound expansion 11500 SCC openMSX-RELEASE_0_12_0/share/extensions/slotexpander.xml000066400000000000000000000011211257557151200231650ustar00rootroot00000000000000 Slot Expander Generic 4x slot expander. Creates 4 secondary slots in the primary slot it has been placed in. slot expander openMSX-RELEASE_0_12_0/share/extensions/video9000.xml000066400000000000000000000011371257557151200221030ustar00rootroot00000000000000 Video9000 Sunrise 1997 Yamaha V9990 based digitizer/superimposer expansion. graphics expansion openMSX-RELEASE_0_12_0/share/icons/000077500000000000000000000000001257557151200166545ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/icons/openMSX-logo-128.png000066400000000000000000000202301257557151200221560ustar00rootroot00000000000000PNG  IHDR>a _IDATx xMWBо(*j,bb.b!ƄH""hQ͊Tkgb!Hsw4{onB[ku9!\rɥ쪉 xwq"?aO3ٳDc'II4*4ޓƐ7q1£#LuGpP\r6}=' |.]8e~OZB?'{,}<>!;kF.+AAse(P@"&hϏ]c(CdIؾ"2<<"-%ڴ-y3xx{k$`x<ҳ|vdx"CyyEB9ב qeDCm/\zpc{$ibbXg~qq.25kA0LA0ȝ\zkn+JJJȑș3?T784/'OmX:7oϏtCڵ iJNNԩS+gw/ DtgKOBk =EDh'ʕYشi#ۖw>jժȌz|;}5V  8;v]!Co{+~z~MپfݽO $d`'d,~C DU q깸SZٜ̘; vnhGKY';=R]yփ +Y/Vl?k[ wz뷇F!|QlcdbN'-;$[vK8=ž/g9 % ˖ 0~j0Pv{s7 pRV4-DZ+S+gtS ؕEԬA2sjy$=HM-[OO~QSvk 4!j:{ |q(bl՝$ CMIF DSP$IFUЫ2>TWX[ڵ[EDt¾N Do$ӳpioX]Q^7ޒ𱗄hJ C !<&x*E87 (k1e<@hng߆QEׯuG4n ]Hf8p_|(Dź֗1]2"˘)b>Bn*غo*.?D>:|wpI/#QcO}{dߕ/< FVѪ%L##)Vű'/j6JoMX DȥJt~Е!iMjT/#z]gu\#=+FbApr<W'NFseKGcp\5ҁߏ9e߼yC?1t;"+W#,Z4f>RjAQlg-u'ۥJ\ 4sD9Oh 7aANsfͯ ?!ZR zHHG߾}*c'ӗ|fI|[5)/y;$]>u RCb"œ;U SUdR(JāU{KLoaf9r>37Ag@ə?LTkֲ+\&}k1@gTP^3X.Ux6DMa >$s[oy޷nn, rR zI 󑩳xo!Iv`4y?2[[J߄_@D*9:)S%<\?|_K$G,S 8uHvs*8bN{O<=7Ij * ]|Q- ˕Ѱƌ3g4bėxqs*zx|2[-oǢ8*' q4~yb.88¤IBk K_f.r(Q|}:+W.cU{5fʍ{.Z ہ#3`هS}&㽪Pp a,~v)Zu!<|X .%=zan|Cǻ,Z_;"6:PAOW"˰9MG_"F3tჺ9/V&^wY4yҖڨN _xJi?Q-”)¦ 6d#kQ e[6p b{+|n.8|%]ȊiGٮ4~ Tls@L)F- ǫzk׀mӱ\enVzr }A&**J ܺ VpAIr}\ta},s 4tRF5/qQa,=+OE7|my9󅷩 gφM0ADhA;6I0)gNZ{~qI'lR3,F+ ] _ަ৯e|ѣ~װ`X*KW^5EX/$G#|n_1kffVJ긕`=bM$c8ddA O`%Le mzS^a󸅟oQ}; wYW#|* =@sz( ˽9^1WL|ݛ'NDZ\O6*{Q[o$gPC'n^͝x÷W4E(›Tl@z:z(Uׯ;_ ]|D4ɉTuc)%!ʶcm̷du ~ưI&3gLe.U?ٷ~3t\/oa2 &~}#U%Ÿ)66V*(Z(ϟo>Q^r6K 8`/{ ؠл GQXrulڧjHGBܤ rBU+uP/l߷(h( ^A)E fMо}0]D}\I DRt"u^ ͠Egf~GFʃ / .d`*,| p@9 AD (MΤ^V(XP\ޖ"E?:kց S};믿FXXXj)Ve?Ճ EN}Vxp ]N3+^OgsybbC_d :υYw &Sh9N8I˸[ōRg{u+r&ԙ=yeAm0/I;~Jb6Hb˭?-oheJYB]H$i,^ޜG<| pLÓ@/{=<H4Y갭6Gcw٫nNdcbXs=v)_d8 7}b[3j}Yr!KDb#ngR#P2G]P*xϴ⭫L q_ y)s `ܠĒIENDB`openMSX-RELEASE_0_12_0/share/icons/openMSX-logo-16.png000066400000000000000000000007561257557151200221050ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME ,3tEXtCommentCreated with The GIMPd%nRIDAT81KPǏ] MP EpR$:dt'A0EbED")D//{M"i 8%;lV*1Z v*7Z. %I\"ݏ'\ӻSjg| %@tV:5nkfXEJ0Jp;{OMӢREyF@JWie >)_Xꁂ%l}h| M|(fRKup[!#.H*i9SUI4!1ˆv0r+Ua>rĞhvyR9Y9$IENDB`openMSX-RELEASE_0_12_0/share/icons/openMSX-logo-256.png000066400000000000000000000535371257557151200222000ustar00rootroot00000000000000PNG  IHDR\rfW&IDATx PT MUlMf0Z4IkrL*J|!E4s5V[$5!56MB(5G|y*\XΙþt|w$Dgٳ0 0 0 0 M$&z&D;}$a;k()›B!Ϧz,%jUJZ{I$}_b)I?B뙅$7.'ЇP5ulP$QcDʮMИF_bI7#8f0LfddM蕚q&Z6Ɠ Mt|Hjq$7[|"PZ+: 1N\CΥcj$7%쇞ԇ߽OTcep#`.igS,өd 5DZ6ݤ#I+In ]}1S6o70V|$򩆐F09Rm||;_>d\c{&0nL`5S&\x;j]!טƭK{k0#X;W8_n9r_cT`y ɍa,wQ={ "3&C/^~c~|&Z+&﹌.oVR|&5R][ ,,YY_WhmmE^^96o>_x;W\F.?9W7_1^khoO=nD/) Vovmm6r?Lr`zr׈6\^3G|8L_>> *UnˏHҒ`wQmUvζx,] $Wk/\*Ħ ,;.IoLT]Mhhz{F3 aZ>\ISq웢`c!_T?vl*<;|I&B_?M1~1%[W ]MC11B.zlIZ(1̽Kr%B8o?qťENNl\,!${v?U o^E<bS_ B+} , & 0y?yvGDd^B<觾"Zh 0.(Wp#n ivY 6R̽͢2WO&D{n;zbᣏ " /I!doavSRt}ǎC^^=Ӭ pH+;F|xE{!,MAs+;vĉш~sq X,-._^XX>}'ƍ=V&!B=x#V_kjSL-#!!йM~~2d{=x %Tu}}HQI> `cO:.N<)&.b%0i$أ ?\>>x=p3P 2n:XGh q~uG `3 j-f\{MM=%Sn&(V;e#JLx9 ɉ69bܹSjkpxkdP !Lw&a0}6x|0;;ÆJ8kBC - ^MjK:+&LM 6Ć8+1EǍG+}S\/<~k?On:};c~<^ڢNˀ3 QS$ K jPV?-[6'Ų_L~_9(NqaTyΘipY|G XBZD Jb@"~8au=t5c4m5i\22UnLw&?5kT~3epNߏO7D_@#>kbPmSoK/fݺlz ҟ_|M(ts{SЄ_bMI掟3{lDEЄGkm(/˿2M5]荟?*᫯BWQYYK z(yI}׹0^H NB%"}X8'۰A,[[K?`0`XdfS }Ǔ_5^p`;COE]"4GZ% a۶ϰI @Z*M,^9ן`- >V|8޹s'z7(7+غu;r3s~o  Ass3<0lXM>SVnvgW~o< ݟ#J|p4X> AʚM8u?D]}xl@={֣ <R5:-W]ypv⿞E})r?47@ -(/ _3\#:T@?nP/cPlnLw'?Z+nitק[o[_LK"?4N1I*+i@wF#wʥK9s p7jUgo y͒_7U;vfʄ _%gK 4ZeB^HLLԄ0w`Ew_t 0Y6j @144ͅsF<9Fµ|1fv~-T-{1cWߟ]^-ȑ듺/EFX0^ @ eG#t`ԕHP&<6ݟ@Tf(--44X{]k`4k'ɯ^je[0^@PK~Q5j09 } QQQAEEloIK~͍ԝY|wޖPS,77WS^~7oKAAqL~u;Z낯VaEfr`;ȁWNz2?~~_Bhk>})ػӀ~G!5jpX4RyJ@lՀ?텭[j_̀3aS:;r6j4Y6$0^@_J> %yMtPG֓Md&Nc?'_R!B ձhmm薕 mAy0W Ɣ%? L7QU_8|Sl}wM}~$AA7dgu6un 7 XaW7a Mہ{-ry9o wx9aU n5%r J~8wx ,<:\>*xͺҡh%,hh%_-"P Wje *V a?:srz ~~;I o8J.K/񓢙y2EUhƝ?vDqems6|dk a8~Vα_{5⣟WKG6֭g+D5" qeEGEģ.>~={}/O81QOGO7'TPj/p(7W5 {.>9v`hdce^g8 #C@Я6wuƳXط]"x-!DEvؘ4rqAPw߄BLt<d}ER\mt&rqu3ΨT*L@G 3 x?qcIg@BkWTe>su#J|O>p3 Z4<8SYQ6vd"R,RDe %u;/ y[Ե?-.ujE#ٱ͛N|ks]Jx~x/:yFnqfQ JKB`ғ4!^gt̡k=WtӫH*,ۓkヲi +"T;L(/yTv09 SX^0::yVz$1L~&T;"1?ns`}zgWl&Wub1-8?D J Ae=%f:6Ԅ sam_7;Be0(&"AR&S°!">S090 ^%BneE.=X2tc?;2QȰc7T TPj*ӛ 6߃K "AXK7@ ǎf}j7%hPgt=f"/:7.EdG+gA 5,*D}i6;9f|g]e%s#J6Ȗa+wp.Is7U|9hF ^]NlC6S;&2%*ϔ̕c>Moǵ-M`à !T&j@NVƫm#d}Хo'ߚyQqdǍr;w 5 Gĸu|iY~-̟j"K#zCR0|a|fwy)0S,7evԁX K4#%{ z?v2n^ S lw6$g``_[29X\ږ?|ZL}s}DύT iw)5ji#y"KZ-J~(I^I4*@)XU(” kmq`fgC`JXE@w3taJs3b Ο3,{ςIq@UJyL/)M ML|hq3ZmS_'IAQ* n#_} e_LΨd-{0'ېQq$45 P 6lBczjќ7.9{l85eXso+7ߎ5FBR0>`|I $$g @kiv3PNJvi`xs1~cPͿ=7ueK. ƍ0i&_ǨzJ"oB9cyA'Ew`# ΃ n~" \"(g axgь4)$}G%*@)(t#2h7$FcFvsJƍlbz'M ug ۀ?X^7 kMx@UJAX77ȳ0SP(4N#s1D|Z~>MCܾ-̿hyob.9g~ o `qxU(Be}s3{]nG>Pw7u$nE-|D5~pDucO }9?*Vw7Mdb07Lo5?U@N PoT1i¶pNS̹ )j`L0u0y}/)pN~`3H|j>W-Ko|:iNO0o"j_R;QK~\X[lػQCqŘh&x2\ ltMҸwߺL7ErANW^r%_kٳT*Iq$Pz4֌U7t7w4oXQbM$ƫ`CX vy]cDoNq`4`8[cgyoIdMK@ )C J]ՙnz9a|gۍmd´.u7$( |'kk\8Fc6!%}yiܭ[G~yF~ N\lRdGPìh&aכA4-X% ޮqL> .\rOAV ]JB18nX֡C'xذej @ JE$Qc7>k۔%Wz=Y '#R_6P߄ ;sxx#^~=֮‘)'f9 $3 Uk.9/GcYjT\\=ztoZe3S4_\z<\:9Hj[dgY=x IIhf|d|<*v޷ -B[_vs8M}vv>}yj;@:@rl~()"RˁܸRRË|}ܽG=3Ѕ%ţcnHڵqG6zmO tƁTp'THIJL"z3f<_k,;s|D\ oWW_冪7|G̷U^r) jZR-~L^YA([z~m ݽP(#+ "4-5-6֘YL E xD_A@_P82%{%SAΝ5\j/K|%c%C suؤoH܁n`>(7.ri`" Ffs{o7,sx6>L]KJ B`e~24GMCY)֭ <Ǯ$h0LB]JJA+/Ka*W8CïcLIK%Ƃl~X)8n:nveߚכOSD"RKA>)e}@S6i H~~<+nU s$zL fgHT `֐Rd~a|&Ȍ f lo%9rGM47^̥a/S2)[dq"BR *Et4JKmeI$lZъRSM3ز PIfȏSd0D]]JAuDW7`'&>Jns6sbT1HeBUBc9azG/8߲o| d۝ 2mGEliYm %48S0["w)ջ ^9@nq0.,\7V . 2=oZ58?*i`)ܴ[!/x9X,Dzy~@%J m a(E @{&"VRi Qo[l9e Y|*++qb[v33kZ0e0tJDH^D`~6緛?tRio?652%av|fs͟$F2bYtR8Ugj7g: ԧ)Gqq+DD DGAْ~_;]ͽH;U$sN˪U\>#ݩT?tR\#*|簼;+ZϡU {qy '.s'L@D5*EWV_v_"<СCJJJait#F=[b;?wbOS]{~ ~g0^[0JvK!#Km8>ΡI۵;.*[{Ke7hwłnp 7n<{Sx~NܹhuĉEfK~IZڟ'1#EHm"PIv&Q&˭>Sjf;wDMAl\] :߲tn߾ ݻ9㝜: {%#?/uF d7@?ZX\C4V@eCArKas&*Q5[\)(r jּ[?Xlpw}dB3k~`o)~8d>B[DfiE9EY7C 'P 7^[@pi\֚5kf;vјbz)_ P'3~)~po*ņw_n?P#;\ǡ1[2[)rEځ)b]'aV.]_핒2^fm{e/#7Na`tY\ [@l_%_FʎYn~-hњ5/TPj+@R[0݂n V 0C۩? mq'hF#̧N%JUos~gu W&N;w+VE`zcZ ^qFrUl˪~`/%(ц%٠q&T0c >-:L]{ongEܭ_F~``s j7h]ګ8Z~c-_uYj1-'NGyb+y"ۡC'1KlmL@.Q]6@"iRE?-PD Rly]rit0A 7s=iLW_P_0_v$hs~+~- dԲˈ(Fp^!/?UY73quvbbkϝ/S#B]`6]ds)~A [Qq z`RxdW3/CLQ]>֎ߕs/E@ _vkw%xp{o [!+ KW'j ,?>vm7^ޡ3y?HB}̢oϛ" JDQ?'Z`Dđ!t-?)~w,LGdLpo@~(03)*a;h , WU "mHl%7H$ů;E_ѩRhUh w]XD"W߲?Ey\^73+@5(wyV%,0Q!<.)COCpEI$!~M>y$ln?$Vel!#؈#pQ׉8;Om/@n#M`.Q(8R: oFہz'HG7D]M{B4i!ş_y f fRf/qT] DA2tUǎS#% +eeP 5Xy?$y}-fcgiN0p uOa!gl p1o)vBF8.sh8 2`V lZH i۳ ,V}I,XiՂk֔W7oWڹm\^=a!PB(KU{ ợ?ֱi;ñ@l!ğ_t T՜Zg)yni}gALt45jׯq683sonpv[~u%cR&ADc ~F5t'ժI3OC`\*J&G+(yE_,8+p89;/EY<׬y.9?~Qzs-4ep ,ŋC".;߉/zM&~JU>fO /o"\]l\؛a[/]ww`ڟ+qob?\"3?SVС#znh?w au M#͋T&~`&"3oݾ7n~֊d#c}*4:{17|3WdQů968pp|$B=ۤK%>;qaݷD9/Gv݊㉣#bjPKg֭{cb}͕~Ǵwz(c.N ر9@o|U'DC!~mMԈ`Vq[勃B9Ѵ1~cOE9?ϙ8aI YN MNSeFOX?SsL-hǀW/{;#ŏ>$wӗ3yÚ7&D*?>9+D遐3E {0Fd9A6*4^|2K/%ME,̳ '_zlݍ?@肩O{رz}DlϞ} SYي|Bzߝ?xgfbnքxd%Qp-עc@D>w޶Of۽{79t:|P 0w:(P|)~_dY?MxsVܶh#'>)2 t} @ѭ{"~(W :K/-y%޹Q_?o(rS'|i:c܀~H[bXdBs}7UZ<7 7A9&{`i!iBs߾l~ Vg!˘" j#@G]߁ c*8Hs-NKKéG OgNBGV:$,_Ni?O Sv!|Eʞ"6ܜ j 1FZrjj*gddsN*TK@$p/! wyd;ORRVW\|M/xx/2kj`;~Ώs쿮B&8˩]ӗ#>=MM˪[/F$QM GVF{FV~͔_Q$hwB7(GuHdObUG _=㕿-W_}C>{PwWiE~s^=lVoLgOQ@VЪ/}րxRT_h sJ/ 䒈|}G$9 ~K.t >S~k[pC<_D{9[lF!~eݲ 8o]qToNnMAۉHn͉*'wb(Uz[_e/=%{p@#xIjpNZ-t #S_S@\q_uY [~Ϝ8Lu>U'86v`kK\+|KK)'/S|!tHBɢW|z[4#&"YCwJBmDmB@n;BpH-/U#KkuS@Lb ƹntO{A;ƻW^d M+իWsh\}g8pHN(nuEZ>W*v>4U{@D{#聸㣎A{Gg1$ OlÆ G2@I&!zs tP b ͷƌs~eaOk v={|\34?מL@Y gZ*>g-@x"bV֋" !*"Z`Q@QE"XU G jy^@PDDH֜9k枓??$C֞s"{Ϟ=3ok%Krrro4h3兠9z-|aA-|EB,S~M/U_@?ڿ'7fN zY0g8ѣ;Hk6ͣdA:[w.=M]^G0}ܤJz ~p?#z6-~ %> ,Xj˳9ݻYkZrR~usp$} v1AkY5"G+qpШ-|$U82j%d !hCjB_>;Q8^I=V}56i^h i?gU/T= ޺L9Ĕ)Sآ?8ahՏ䂘M`L)BoE/) pPU7ET_M `'}aޥ#;H23'sO~ˁ1X^~FH BjUN`9/ꚾ{Cs}h,TiYEwZzfh'ǂT"?(]7Y GVm߾xآB#X, v=6,$*Ar_"P**GOPz/x5A <ƕAA؍/ 9s&ggg3Nȑd`„ ,߰EO54ٳ?`Ȃ|ۇ , v<꾢:҇ECz+/iJgY^WˍBX{q٢t;mvqN7"t܋.}Jƞ|34_/hOaH4ǾXpJSn:bXBoKA|Y*v]EUm9oK__[s^7>s~X'eC a tJСC9 dWKb,Y9)J/TW ,EO=E%zA5ycl+V\èQ8--BGX$1,+~Fe"_\'D71JKĮ,(4f;3Vs}ʛ|k8?̞=[ $1wX5"SYDG`PP/nۅ5+ LAw">Eל3Uwp~1cO8x9!!wrw_`vWp&EvwfaϜn1f[5Q?K[Cni~H&.'DTpUWĢEp7_;)|PN Θ2eVQR`c ݋Pn Gpk=C?÷rNB0D/cP^-8z :"f͚aQVp+:W_}U%PN ޳tf_V&ظS7mkD}[v_s`iV9|u7Tu80|@٬gNcƌa Tছn(TdŊg3E4]T|okGm s|X}UU6E" ~Ytk#hӦ Y'=zu~&''ls2|@EWQG{%hv5hF{֧ |`yt*K*/mȲ:3xOh7zP{E+kdBs_ukD$#ʙ{ Ľ{4v`ofR`(VOgT˜CA_(T_E}o VG!Jf .ܣGnҤ#(诿ڵ9".xH:Z~a_[n`PXfO_(RAX~}Zg̿gqɯ}✬`?ǻLT!p X,?nN҅.k>uQϘ6#>(|E-xeBZaÆqatRnժhB-j*(9qc:+W!SD ˓E|X׎* S~-"_PR*PUg%Unݺq ʗ//IJxرsN#~Ǻ_XCf/{KqGyk@oǬ.)+uU}SFh/ vfWv*߿J` =LEԸr"xs\} ;l8ښKyC U7#|^pˋ}]iBD$P8/&lݺ\ _r.D$yae=Yu#]Hs~3ߙ@w)+z9 Pv}w$[}իWsÆ g fbŋvR&>r6 KqEEǡeM{VZښ;@g^V~3H/}/1VvnEojƜgGr۷g"b.bE_D a `޸B +0y`~ܕrI` t_L mimۤvأs45& @DBn)_N 8ua߱ ] ߤPQW*33K^fMMH6U~ƕ"Ō\ؔXLEF0vwiO'UJH()G uaXZOa ^kޢl n9~xyO^[3Wi>_*\aÆ Vg 4V}`l3Q J9(絼8Oe-z#,:7yF>}*90>?s o!is". NAz,J m̜LyVV'.C*VuO ^8ͧg ][:=+jB8-[zѣ_d^ւNdQh b ?WEw9CɢQO9O.O_VW龪+4C:K᧿%u^q}틣Oď|W2q+u\87ҥǿ`ܹsQDĝT%| aRG1MD_{Or?k׮LD|e+9/V{U*җdqNQCcy?F?W(/m '7X&]Xv{c<ݷh=PO?ve}g\rG"_HCmzn8%x2ϟ>Niĉ_J}@^NDF k\d~U m97EEsoݺn\}0o~{B/8Ğ={r322p`ě][|9` Y9k.8p oD1#zXLx 6"P^R+t_qug$ޜBp˽޼ѩ ѥK6챥)ܹ-8e{d-%?pv(o=yΜyڲ/CM|~Vy)m6sc97ǻOD\"2kP`ۋ¢XNrVZ5a%.J 2E>1V䯿ΒN+(@Ecp*~W{91]O\,[ wLmwP^NY^ÿi_Y_]C`|XBJ&T*e9N%V"/î8-260[5$y|a.7x5K>7 #]0daaApwSix½;*Ո>[Ddaa!19:07/lUjѧ7_Gc;&( s'[P "Z Y4B@E,Z\h BU ō**P *7څpe;Wbq+so$Lٜ{\B$J`ۭױv[ {'|?B4˛|V]T LB)#P.iؼ x UO%YRrs":x NOFoIK+Qh'_\-+k@EEJJJ֊VT*Hy D[#ȉB[t-DYG777Hnd&O2Nv?8VRF=uu5r_f_5mjπ9 ex_Dj0ΎތW5#Z02bRUZ U"ҒL=@,gA,m밙?apB<e2bB9=w-3^l gIǶ Ŧr#Csii[Њ,LD+-QCi?zZ!|Psp#'c]7{@Of >D^Y# bLi%r]j07_G K+Yʈf QFǧH~C~9VϬc0 FӦWY{{;|}c0iR(MCf-XYOc#^/Q8\ճ#G.( rfp/ƓY,g0h`[UgG42֯eܘO#πsxEΑ 14tg\gf1cϼiix$ѯ!ȷfbclﻇ~ߗZ?fjL.m͇dHR"IÐ!>ru-:66 Acc,Y{+Â/ "L4iq&NBLm/kķbuEL!qA#P__?""')ȲT%bnQ[s4P^^n\t1K |憈r?Fee%@H/p AUgB^^\Q-Rj<%;]+W2Du_JK^Qf1" .Ϟ=õk\ywU8vd{dhd22MMXy%/eJD^ /G1kT$@]G<2Rub#O|Ά"ẀH%eNT6>21 jƍ#,,%cg"6j3B7"kIJ cckZhOF gÉr-PӦڜ.QEkflQϖ .}xj|pRrӹBn4EۉDW=hQ~aB*'OajjDJR=SŨ!B 2_3P~ʹn$[j zFԾ_~D䠙 C/qr^&>AHHN8ϟ5khtML?&OynjYTl?|ݺB303sΓ_TnBti+?J >Z6r6N]޺'A/3WEq({ pm3֔,i99SHEof쵖bt,`0WLǣTA28qY>ٿvdy fֆm5J~~̿1Wu{O7oL92(fj[$:^idjI)zuqiA(-s Ѣcδg:vJ=Qm}L}SW[4S˽+Z-: aŊoŊ}6*[ *NPpTWO{Xfee.{\ֵF:a5eJR?e0,-ҥd^2NJ"=p1!*"~VϹ?􍤧g-!=zر0z3 8~$ݻ{M[ O6PY#ǭ85ywx&NŝDQrB)+xmld{a<8+3Ly8݀D@U(7 &jr%Lq&Pbծ_"55U/s|6^=`]AԘi"ЯJnqUOd"\Y-;NS:y $IQ!96ۑڪ?Wh& Um{ވ EFƣv0l6ڹN*1gN3Tw/0RQ|LqC*:ZU2_m aN]Y) ­[Dߺںu{NjuͨS2 کx QGDD+_}qݾ4Nmu ^g ͧJszq mIckcۼ>UP8XU~g^5 P rUTN7q#k5聸ܶzy7={xx G$߭g y/>j^F;oM| ߲Esi]*==z6gYg𣋌Setj)YS"IR3-X'ׅ]u9 dujvql\K9dLULWfPwl/1'{GcjOUXyu"ypA FK6͐S}^Cg{ɔ+jț$IENDB`openMSX-RELEASE_0_12_0/share/icons/openMSX-logo-64.png000066400000000000000000000057451257557151200221130ustar00rootroot00000000000000PNG  IHDR@@iq IDATx Teǟ}hcx\юVNb6l"* *ʈդZ&2 .,(K%CL%9sg^7ꕐ!D #8se]Hԙ^9/8X3,sdDO ,XnݾO26AQL7F.s@{pl9ZHbrX)5057b׮HN.ă> "5ڵHIj{jEBLT/ D.IWq&!:l%z p/bc i=ml۷3ڷ;k.OOsp*5PqVo(:|a ,X E;!2yzTDDB`ώ~i"{0j;&`玕؞莨%Pܤ#g$w~>e )(/$%m!/"w y_+J_fdh$ݺ6KUq/}m8z8g +IUB rk^n,fvmVq*WĂ7A! rRcg@"}S];8Ws7,7'actث q|_K[Ɍ%!h1˦F?,>?Jy B^FK͛oKq4М᧢k=p(;B *v=BQѮ1H~T^^ѰtqjI/fѬL$&GZjT)8ѷ9#99-_%ѐhw1m_ `)xNʟ!]k}Ypq !'( a WJ@l2S9{_=QRޚ2aDJPL5y{/GQJDdZ^:zmԆWh=9Ӟ7A5F-+-}Qџ^-_ї9ϡndbrd$#F^t_[c~{NdB4$w3Q;*vEyuh^?>?7SJ5`?%I=OpQvLB\O*XX3MHLډ!3sBCcԝ<7.;"]tGCЯ+,YIϞbUoߠ .sTKG)5Iu.ʜiעW/x Wi썢*qaN''ߟ6QB=m_=ĚtpSVNȗx(r#2QV-nʞ'V^##9۪":L3YQ_TVVExuj ?* ;^Z-.Uk6SGx̙3|wS/qqI zFa CTr.dpk[~i.**+rrr`*#uUS]8]).?v}A^ˤQ!QVVSCԢR.1Reiϧd*S#7)x-Y;ю*q=')n ֯_S8DKd*^E7&F5ST+ٳgၺ&*bOS|3"+.BEyXS-9ۘK)x~~~K_*Tj[:D&aħ(تpXx躔\J} 6 GVXa%Sq9u1V(U'\So±GސspQpr7R$$lv Yv*Eau\v6# `?Ϯgfƣg[nqߴ(k_ 1MG_HthhʪCCW!))7 sZ>0Wҕ,]y sh y "'H=8c%4t+msWOQ=N@dy+G_O}bDƼM Ȝ%$|OطT*,PXz=GXiKT M49N#njbą N޻TŔ:o_Vi3=x|KwVA7<#^)ZE:B>qm`A^:PuO]e#rM7~RD xIENDB`openMSX-RELEASE_0_12_0/share/init.tcl000066400000000000000000000143561257557151200172210ustar00rootroot00000000000000# evaluate in internal openmsx namespace namespace eval openmsx { variable init_tcl_executed false variable tabcompletion_proc_sensitive variable tabcompletion_proc_insensitive variable help_text variable help_proc variable lazy [dict create] # Only execute this script once. Below we source other Tcl script, # so this makes sure we don't get in an infinte loop. if {$init_tcl_executed} return set init_tcl_executed true # Helpers to handle on-demand (lazy) loading of Tcl scripts # Register 'script' to be loaded on-demand when one of the proc names in # 'procs' is about to be executed. See also 'lazy.tcl'. proc register_lazy {script procs} { variable lazy dict set lazy $script $procs } # Lookup the script associated with the given proc name. If found that script # is executed (and the script+proc-names are removed from the list of # yet-to-be-executed lazy scripts). proc lazy_handler {name} { variable lazy set name [namespace tail $name] dict for {script procs} $lazy { if {[lsearch -exact $procs $name] == -1} continue dict unset lazy $script namespace eval :: [list source [data_file scripts/$script]] return true } return false } # Execute all not yet executed lazy-scripts. ATM this is (only) required for # the 'about' command which has to search through the help text of all the # scripts. proc lazy_execute_all {} { variable lazy # cannot simply iterate because the 'source' command below might # trigger a load of a script later in the collection while {[dict size $lazy] != 0} { set script [lindex [dict keys $lazy] 0] dict unset lazy $script namespace eval :: [list source [data_file scripts/$script]] } } # Return a list of all command names. This includes: # builtin Tcl commands, # procs defined in Tcl scripts, # procs from not yet loaded lazy-scripts (see register_lazy). # This helper proc is used for tab-completion in the openMSX console. proc all_command_names {} { variable lazy set result [info commands] foreach procs [dict values $lazy] { lappend result {*}$procs } # only one level deep, good enough for machineN::* foreach ns [namespace children ::] { lappend result {*}[info commands ${ns}::*] } return $result } # Is the given name a name of a proc, possibly a name defined in a not-yet # loaded script. This helper proc is used for syntax-highlighting in the # openMSX console. proc is_command_name {name} { if {[info commands ::$name] ne ""} {return 1} expr {[lsearch -exact [all_command_names] [namespace tail $name]] != -1} } # Override the builtin Tcl proc 'unknown'. This is called when the Tcl # interpreter is about to execute an undefined command. proc ::unknown {args} { #puts stderr "unknown: $args" set name [lindex $args 0] if {[openmsx::lazy_handler $name]} { return [uplevel 1 $args] } return -code error "invalid command name \"$name\"" } # internal proc to make help function available to Tcl procs proc help {args} { variable help_text variable help_proc set command [lindex $args 0] lazy_handler $command if {[info exists help_proc($command)]} { return [namespace eval :: $help_proc($command) $args] } elseif {[info exists help_text($command)]} { return $help_text($command) } elseif {[info commands $command] ne ""} { error "No help for command: $command" } else { error "Unknown command: $command" } } proc set_help_text {command help} { variable help_text set help_text($command) $help } set_help_text set_help_text \ {Associate a help-text with a Tcl proc. This is normally only used in Tcl scripts.} proc set_help_proc {command procname} { variable help_proc set help_proc($command) $procname } set_help_text set_help_proc \ {Associate a help-proc with a Tcl proc. This is normally only used in Tcl scripts.} # internal proc to make tabcompletion available to Tcl procs proc tabcompletion {args} { variable tabcompletion_proc_sensitive variable tabcompletion_proc_insensitive set command [lindex $args 0] lazy_handler $command set result "" if {[info exists tabcompletion_proc_sensitive($command)]} { set result [namespace eval :: $tabcompletion_proc_sensitive($command) $args] lappend result true } elseif {[info exists tabcompletion_proc_insensitive($command)]} { set result [namespace eval :: $tabcompletion_proc_insensitive($command) $args] lappend result false } return $result } proc set_tabcompletion_proc {command proc {case_sensitive true}} { variable tabcompletion_proc_sensitive variable tabcompletion_proc_insensitive if {$case_sensitive} { set tabcompletion_proc_sensitive($command) $proc } else { set tabcompletion_proc_insensitive($command) $proc } } set_help_text set_tabcompletion_proc \ {Provide a way to do tab-completion for a certain Tcl proc. For details look at the numerous examples in the share/scripts directory. This is normally only used in Tcl scripts.} set_help_text data_file \ "Resolve data file. First try user directory, if the file doesn't exist there try the system directory." proc data_file { file } { global env set user_file $env(OPENMSX_USER_DATA)/$file if {[file exists $user_file]} { return $user_file } return $env(OPENMSX_SYSTEM_DATA)/$file } namespace export register_lazy namespace export set_help_text namespace export set_help_proc namespace export set_tabcompletion_proc namespace export data_file } ;# namespace openmsx namespace import openmsx::* namespace eval openmsx { # Source all .tcl files in user and system scripts directory. Prefer # the version in the user directory in case a script exists in both set user_scripts [glob -dir $env(OPENMSX_USER_DATA)/scripts -tails -nocomplain *.tcl] set system_scripts [glob -dir $env(OPENMSX_SYSTEM_DATA)/scripts -tails -nocomplain *.tcl] set profile_list [list] foreach script [lsort -unique [concat $user_scripts $system_scripts]] { # Skip scripts that start with a '_' character. (By convention) those # are loaded on-demand (see 'lazy.tcl'). if {[string index $script 0] eq "_"} continue set script [data_file scripts/$script] set t1 [openmsx_info realtime] if {[catch {namespace eval :: [list source $script]}]} { puts stderr "Error while executing $script\n$errorInfo" } set t2 [openmsx_info realtime] lappend profile_list [list [expr {int(1000000 * ($t2 - $t1))}] $script] } if 0 { foreach e [lsort -integer -index 0 $profile_list] { puts stderr $e } } } ;# namespace openmsx openMSX-RELEASE_0_12_0/share/machines/000077500000000000000000000000001257557151200173305ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/machines/Acid1Test-slots.xml000066400000000000000000000163541257557151200230060ustar00rootroot00000000000000 FRS Acid1Test-slots 2014 Acid1Test v0.7, aka "Usual hardware in unusual slot layout". This machine was carefully dedigned to stress-test slot accesses routines. Incorrect assumptions about memory/resources layout will fail to run. Software must also correctly handle extended slots to run properly. For more info, see http://frs.badcoffee.info/MSXAcidTests.html MSX2+ largest 16000 jp_jis true false true false V9958 128 21000 Acid1Test-slots.cmos true phc-70fd2_basic-bios2p.rom e90f80a61d94c617850c415e12ad70ac41e66bb7 phc-35j_msx2psub.rom fe0254cbfc11405b79e7c86c7769bd6322b04995 2 Sanyo_PHC-70FD2_disk.rom 9efa744be8355675e7bfdd3976bbbfaf85d62e1d 2+FM cks-B414h.ROM c5bfa85f7315fb11928b27a721a908c93b7d3fbf ExpertTurbo_msx2psub.rom cc1744c6c513d6409a142b4fb42fbe70a95d9b7f 2+FM cks-B414h.ROM c5bfa85f7315fb11928b27a721a908c93b7d3fbf ExpertTurbo_fmbasic.rom befebc916bfdb5e8057040f0ae82b5517a7750db 9000 -75 Toshiba 12000 75 256 Normal0000 3b02e5f45603df2c180f9c07d59b2ccc5a87bd0a msxaudio13.NMS-1205.rom 0x1000 0x1000 openMSX-RELEASE_0_12_0/share/machines/Acid2Test-hardware.xml000066400000000000000000000171521257557151200234350ustar00rootroot00000000000000 FRS Acid2Test-hardware 2013 Acid2Test v0.5, "unusual hardware in usual slot layout" This is certainly the hardest of all tests. It's aimed to detect illegal direct hardware access. The MSX Technical Handbook, pg-336 clearly states that programs should not handle the hardware directly and the only direct I/O allowed is to the VDP, by using the ports indicated on addresses 0006h and 0007h of the BIOS. Pg-42 of this same book, on "Notes on I/O Address Assignments", mentions the reason: "Although I/O addresses are defined above, the software must not access those devices directly using the above ports. All I/O accesses must be done using BIOS calls, in order to make the software independent of hardware differences. MSX manufacturers may change some of the hardware from the standard MSX system and maintain software compatibility by rewriting BIOS. The hardware differences would thus be transparent to the software." Remarks: Because of technical restrictions on openMSX, the MemoryMapper I/O ports and the expanded-slot register cannot have their addresses changed. This means that this version of the AcidTest2 will not yet be able to test if those two devices are being correctly handled. For more info, see http://frs.badcoffee.info/MSXAcidTests.html MSX2+ largest 16000 jp_jis true false true false V9958 128 JIS 21000 Acid2Test-hardware.cmos true phc-70fd2_basic-bios2p.rom e90f80a61d94c617850c415e12ad70ac41e66bb7 acid-tests/Acid2Test_basic-bios2p.vdp.ips acid-tests/Acid2Test_basic-bios2p.psg.ips acid-tests/Acid2Test_basic-bios2p.ppi.ips acid-tests/Acid2Test_basic-bios2p.lpt.ips Toshiba 12000 75 256 Normal0000 Generic_msxaudio.rom 7f115ff923a1cc0d1944cf280168add946fde854 acid-tests/Acid2Test_msxaudio.ppi.ips acid-tests/Acid2Test_msxaudio.psg.ips acid-tests/Acid2Test_msxaudio.opl1.ips 0x1000 Generic_msxdos22.rom c36c9e0f96738a340381e23b4f97245388801a46 acid-tests/Acid2Test_msxdos22.ppi.ips acid-tests/Acid2Test_msxdos22.rtc.ips MSXDOS2 1024 2+FM cks-B414h.ROM c5bfa85f7315fb11928b27a721a908c93b7d3fbf acid-tests/Acid2Test_msx2psub-fmbasic.vdp.ips acid-tests/Acid2Test_msx2psub-fmbasic.psg.ips acid-tests/Acid2Test_msx2psub-fmbasic.opll.ips acid-tests/Acid2Test_msx2psub-fmbasic.ppi.ips acid-tests/Acid2Test_msx2psub-fmbasic.rtc.ips ExpertTurbo_msx2psub-fmbasic.rom ca79a8cc7714a5d48d2e567a2d4d7ba3fe587825 acid-tests/Acid2Test_msx2psub-fmbasic.vdp.ips acid-tests/Acid2Test_msx2psub-fmbasic.psg.ips acid-tests/Acid2Test_msx2psub-fmbasic.opll.ips acid-tests/Acid2Test_msx2psub-fmbasic.ppi.ips acid-tests/Acid2Test_msx2psub-fmbasic.rtc.ips 12000 -75 2 phc-70fd2_disk.rom 9efa744be8355675e7bfdd3976bbbfaf85d62e1d acid-tests/Acid2Test_disk.ppi.ips acid-tests/Acid2Test_disk.rtc.ips openMSX-RELEASE_0_12_0/share/machines/Acid3Test-mapper.xml000066400000000000000000000135361257557151200231270ustar00rootroot00000000000000 FRS Acid3Test-mapper 2014 Acid3Test v0.1, aka "unusual memory-mapper configuration" This machine was carefully dedigned to stress-test memory-mapper handling routines. Any of the following bad practices will fail to run: - Reading the Memory Mapper registers - Assuming that the primary mapper is the biggest one For more info, see http://frs.badcoffee.info/MSXAcidTests.html MSXturboR 47 221 5 c212b11fda13f83dafed688c54d098e7e47ab225 4b84465c2faa802bfd5f772118c4d31f993f29d0 fs-a1st_firmware.rom fs-a1st_kanjifont.rom 5aff2d9b6efc723bc395b0f96f0adfa83cc54a49 16000 jp_jis true true false true false V9958 128 JIS 21000 Acid3Test-mapper.cmos false 21000 DRAM 40 43 62 63 9000 36 37 4096 256 DRAM 56 57 DRAM 58 61 7FF2 48 55 1 PANASONIC 16 false Acid3Test-mapper.sram 0 255 openMSX-RELEASE_0_12_0/share/machines/Al_Alamiah_AX170.xml000066400000000000000000000042751257557151200226520ustar00rootroot00000000000000 Al-Alamiah AX170 1986 Arabic MSX1 machine. MSX 16000 int false true false false T7937APAL AY8910 21000 ax170_basic-bios1.rom 5e094fca95ab8e91873ee372a3f1239b9a48a48d ax170_arabic.rom 0287b2ec897b9196788cd9f10c99e1487d7adbbb openMSX-RELEASE_0_12_0/share/machines/Boosted_MSX2+_JP.txt000066400000000000000000000021041257557151200227420ustar00rootroot00000000000000This directory contains a so-called "Boosted" MSX2+ configuration. It has the following features: - based on Panasonic FS-A1WSX (most complete MSX2+), which includes: - Kanji ROM - MSX-MUSIC - firmware - three external slots, slot B and C are in a sub slot (2-0 and 2-1) - 2048 kB memory mapper (in slot 3-0) - 2 disk drives - V9958 VDP with 192 kB VRAM - SCC+ in slot 2-3 - MSX Audio - MoonSound with 640 kB sample RAM - GFX9000 with Video9000 - 512 kB MegaRAM in slot 2-2 - BASIC Compiler (MSX-BASIC Kun) This configuration needs the following ROMs: - fs-a1wsx_kanjifont.rom, fs-a1wsx_basic-bios2p.rom, fs-a1wsx_fmbasic.rom, fs-a1wsx_msx2psub.rom, fs-a1wsx_kanjibasic.rom, fs-a1wsx_disk.rom, fs-a1wsx_firmware.rom: same as needed for the Panasonic FS-A1WSX machine - phc-70fd2_basickun.rom: same as needed for the basic compiler in the Sanyo PHC-70FD2 - yrw801.rom: same as needed for the MoonSound extension You can make this configuration the default by changing the 'default_machine' setting from the console. See the Setup Guide and User's Manual sections of the manual. openMSX-RELEASE_0_12_0/share/machines/Boosted_MSX2+_JP.xml000066400000000000000000000146631257557151200227400ustar00rootroot00000000000000 openMSX Team Boosted MSX2+ JP 2011 A super-charged Panasonic FS-A1WSX MSX2+ 47 221 largest fs-a1wsx_kanjifont.rom 5aff2d9b6efc723bc395b0f96f0adfa83cc54a49 16000 false jp_jis true true false true false V9958 192 YM2149 JIS 21000 Boosted_MSX2+_JP.cmos true Boosted_MSX2+_JP_matsushita.sram Philips 12000 75 256 32760893ce06dbe3930627755ba065cc3d8ec6ca yrw801.rom 12000 640 fs-a1wsx_basic-bios2p.rom f4433752d3bf876bfefb363c749d4d2e08a218b6 fs-a1wsx_fmbasic.rom aad42ba4289b33d8eed225d42cea930b7fc5c228 9000 phc-70fd2_basickun.rom 22b3191d865010264001b9d896186a9818478a6b 512 expanded 13000 2048 fs-a1wsx_msx2psub.rom fe0254cbfc11405b79e7c86c7769bd6322b04995 fs-a1wsx_kanjibasic.rom dcc3a67732aa01c4f2ee8d1ad886444a4dbafe06 7FF8 2 fs-a1wsx_disk.rom 7ed7c55e0359737ac5e68d38cb6903f9e5d7c2b6 PANASONIC 16 true Boosted_MSX2+_JP.sram fs-a1wsx_firmware.rom 3330d9b6b76e3c4ccb7cf252496ed15d08b95d3f openMSX-RELEASE_0_12_0/share/machines/Boosted_MSX2_EN.txt000066400000000000000000000020101257557151200226540ustar00rootroot00000000000000This directory contains a so-called "Boosted" MSX2 configuration. It has the following features: - based on Philips NMS-8250 (most standard MSX2 in the Netherlands at least) - three external slots, slot B and C are in a sub slot (2-0 and 2-1) - 2048 kB memory mapper (in slot 3-2) - 2 disk drives - V9958 VDP with 192 kB VRAM - SCC+ in slot 2-3 - FMPAC (slot 3-1) - MSX Audio - MoonSound with 640 kB sample RAM - GFX9000 with Video9000 - 512 kB MegaRAM in slot 2-2 - BASIC Compiler (MSX-BASIC Kun) This configuration needs the following system ROMs: - nms8250_disk.rom, nms8250_basic-bios2.rom, nms8250_msx2sub.rom: same as needed for the Philips NMS 8250 config - fmpac.rom: same as needed for the FMPAC extension - phc-70fd2_basickun.rom: same as needed for the basic compiler in the Sanyo PHC-70FD2 - yrw801.rom: same as needed for the Moonsound extension You can make this configuration the default by changing the 'default_machine' setting from the console. See the Setup Guide and User's Manual sections of the manual. openMSX-RELEASE_0_12_0/share/machines/Boosted_MSX2_EN.xml000066400000000000000000000122401257557151200226430ustar00rootroot00000000000000 openMSX Team Boosted MSX2 EN 2004 A super-charged Philips NMS 8250 MSX2 largest 6103b39f1e38d1aa2d84b1c3219c44f1abb5436e nms8250_basic-bios2.rom 512 expanded 13000 5c1f9c7fb655e43d38e5dd1fcc6b942b2ff68b02 nms8250_msx2sub.rom 13000 -75 9d789166e3caf28e4742fe933d962e99618c633d fmpac.rom fmpac.pac phc-70fd2_basickun.rom 22b3191d865010264001b9d896186a9818478a6b 2048 eNpj+M8wouEI9z4DAJBx/wE= Philips c3efedda7ab947a06d9345f7b8261076fa7ceeef nms8250_disk.rom 2 16000 false int true true false false V9958 192 YM2149 21000 Boosted_MSX2_EN.cmos Philips 12000 75 256 32760893ce06dbe3930627755ba065cc3d8ec6ca yrw801.rom 12000 640 openMSX-RELEASE_0_12_0/share/machines/Boosted_MSXturboR_with_IDE.txt000066400000000000000000000020501257557151200251260ustar00rootroot00000000000000This directory contains a so-called "Boosted" MSXturboR configuration. It has the following features: - based on Panasonic FS-A1GT (most complete MSXturboR), which includes: - R800 - Kanji ROM - MSX-MUSIC - PCM - MSX-MIDI - firmware - three external slots, slot B and C are in a sub slot (2-0 and 2-1) - 2048 kB memory mapper (in slot 3-3) - 2 disk drives - V9958 VDP with 192 kB VRAM - SCC+ in slot 2-0 - MSX Audio - MoonSound with 640 kB sample RAM - GFX9000 with Video9000 - 512 kB MegaRAM in slot 2-2 - BASIC Compiler (MSX-BASIC Kun) - Built in IDE interface with 100MB harddisk and CD-ROM This configuration needs the following ROMs: - fs-a1gt_kanjifont.rom, fs-a1gt_firmware.rom: same as needed for the Panasonic FS-A1GT machine - phc-70fd2_basickun.rom: same as needed for the basic compiler in the Sanyo PHC-70FD2 - yrw801.rom: same as needed for the MoonSound extension You can make this configuration the default by changing the 'default_machine' setting from the console. See the Setup Guide and User's Manual sections of the manual. openMSX-RELEASE_0_12_0/share/machines/Boosted_MSXturboR_with_IDE.xml000066400000000000000000000167341257557151200251250ustar00rootroot00000000000000 openMSX Team Boosted MSXturboR 2013 A super-charged Panasonic FS-A1GT with IDE interface. MSXturboR 47 221 largest e779c338eb91a7dea3ff75f3fde76b8af22c4a3a 5fa3aa79aeba2c0441f349e78e9a16d9d64422ea fs-a1gt_firmware.rom DRAM 40 43 ide250.dat 93e41c7d479bc90c1d1f6d081af20fd9924b8ada IDEHD hd.dsk 100 IDECDROM 62 63 9000 -75 phc-70fd2_basickun.rom 22b3191d865010264001b9d896186a9818478a6b 36 37 512 expanded 13000 2048 DRAM 56 57 DRAM 58 61 7FF2 48 55 2 PANASONIC 0 511 32 false Boosted_MSXturboR.sram Philips 12000 75 256 32760893ce06dbe3930627755ba065cc3d8ec6ca yrw801.rom 12000 640 5aff2d9b6efc723bc395b0f96f0adfa83cc54a49 fs-a1gt_kanjifont.rom 16000 false jp_jis true true false true false V9958 192 21000 JIS Boosted_MSXturboR.cmos false 21000 openMSX-RELEASE_0_12_0/share/machines/CIEL_Expert-Turbo.xml000066400000000000000000000102101257557151200232000ustar00rootroot00000000000000 CIEL Expert Turbo 2000 MSX2+ largest 16000 int false true false false V9958 128 AY8910 21000 100 ExpertTurbo.cmos true 2+FM cks-B414h.ROM c5bfa85f7315fb11928b27a721a908c93b7d3fbf ExpertTurbo_basic-bios2p.rom 5029cf47031b22bd5d1f68ebfd3be6d6da56dfe9 2+FM cks-B414h.ROM c5bfa85f7315fb11928b27a721a908c93b7d3fbf ExpertTurbo_msx2psub.rom cc1744c6c513d6409a142b4fb42fbe70a95d9b7f 2+FM cks-B414h.ROM c5bfa85f7315fb11928b27a721a908c93b7d3fbf ExpertTurbo_fmbasic.rom befebc916bfdb5e8057040f0ae82b5517a7750db 9000 -100 100 1024 openMSX-RELEASE_0_12_0/share/machines/Canon_V-10.xml000066400000000000000000000043421257557151200216160ustar00rootroot00000000000000 Canon V-10 MSX 16000 jp_jis false false true false TMS9918A YM2149 jis 21000 v-10_basic-bios1.rom 302afb5d8be26c758309ca3df611ae69cced2821 openMSX-RELEASE_0_12_0/share/machines/Canon_V-20.xml000066400000000000000000000041411257557151200216140ustar00rootroot00000000000000 Canon V-20 (EU) 1984? MSX 8963fc041975f31dc2ab1019cfdd4967999de53e v-20eu_basic-bios1.rom 16000 gb false true false false TMS9929A FF00 JIS YM2149 21000 openMSX-RELEASE_0_12_0/share/machines/Canon_V-20_JP.xml000066400000000000000000000047111257557151200222100ustar00rootroot00000000000000 Canon V-20 (JP) 1984? MSX 302afb5d8be26c758309ca3df611ae69cced2821 v-20jp_basic-bios1.rom 16000 jp_jis false true true false TMS9918A FF00 YM2149 jis 21000 openMSX-RELEASE_0_12_0/share/machines/Canon_V-25.xml000066400000000000000000000050771257557151200216320ustar00rootroot00000000000000 Canon V-25 1984? One of the few MSX2's with only 64kB VRAM. MSX2 16000 jp_jis false false true false V9938 64 YM2149 21000 v-25.cmos v-25_basic-bios2.rom 0081ea0d25bc5cd8d70b60ad8cfdc7307812c0fd v-25_msx2sub.rom b8e30d604d319d511cbfbc61e5d8c38fbb9c5a33 64 eJxjYBhY8H+AAQAioH+B openMSX-RELEASE_0_12_0/share/machines/Canon_V-8.xml000066400000000000000000000036571257557151200215550ustar00rootroot00000000000000 Canon V-8 Very compact and rather minimal MSX1 machine MSX 16000 jp_jis false false true false TMS9118 YM2149 jis 21000 v-8_basic-bios1.rom 97f9a0b45ee4b34d87ca3f163df32e1f48b0f09c openMSX-RELEASE_0_12_0/share/machines/Casio_MX-10.xml000066400000000000000000000033241257557151200217340ustar00rootroot00000000000000 Casio MX-10 1986 Cheap gaming MSX, with joystick buttons and bad keyboard and no printer port. MSX 16000 jp_ansi false false true false TMS9118 AY8910 50on 21000 mx-10_basic-bios1.rom 302afb5d8be26c758309ca3df611ae69cced2821 openMSX-RELEASE_0_12_0/share/machines/Casio_PV-16.xml000066400000000000000000000032651257557151200217470ustar00rootroot00000000000000 Casio PV-16 1984 Cheap gaming MSX, with joystick buttons and bad keyboard and no printer port. MSX 16000 jp_ansi false false true false TMS9118 AY8910 50on 21000 pv-16_basic-bios1.rom 302afb5d8be26c758309ca3df611ae69cced2821 openMSX-RELEASE_0_12_0/share/machines/Casio_PV-7.xml000066400000000000000000000033241257557151200216630ustar00rootroot00000000000000 Casio PV-7 1984 Very cheap gaming MSX, with only 8kB RAM, with joystick buttons and bad keyboard and no printer port. MSX 16000 jp_ansi false false true false TMS9118 AY8910 50on 21000 pv-7_basic-bios1.rom 302afb5d8be26c758309ca3df611ae69cced2821 openMSX-RELEASE_0_12_0/share/machines/Daewoo_CPC-300.xml000066400000000000000000000052241257557151200222600ustar00rootroot00000000000000 Daewoo CPC-300 1986 MSX2 largest cpc-300_s1985.sram 16000 kr true false true false V9938 128 YM2149 21000 cpc-300.cmos cpc-300_basic-bios2.rom affa3c5cd8db79a1450ad8a7f405a425b251653d cpc-300_hangul.rom 47a9d9a24e4fc6f9467c6e7d61a02d45f5a753ef 128 cpc-300_msx2sub.rom fb51c505adfbc174df94289fa894ef969f5357bc openMSX-RELEASE_0_12_0/share/machines/Daewoo_CPC-300E.xml000066400000000000000000000051251257557151200223650ustar00rootroot00000000000000 Daewoo CPC-300E 1986 The educational version of the CPC-300 MSX2 largest cpc-300e_s1985.sram 16000 kr true false true false V9938 128 YM2149 21000 cpc-300e_basic-bios2.rom affa3c5cd8db79a1450ad8a7f405a425b251653d cpc-300e_hangul.rom 47a9d9a24e4fc6f9467c6e7d61a02d45f5a753ef 64 cpc-300e_msx2sub.rom 09f7d788698a23aa7eec140237e907d4c37cbfe0 openMSX-RELEASE_0_12_0/share/machines/Daewoo_CPC-400S.xml000066400000000000000000000063561257557151200224130ustar00rootroot00000000000000 Daewoo CPC-400S 1986 MSX2 largest cpc-400s_hangulfont.rom 30fff22e3e3d464993707488442721a5e56a9707 cpc-400s_s1985.sram 16000 kr true false true false V9938 128 YM2149 21000 cpc-400s.cmos cpc-400s_basic-bios2.rom affa3c5cd8db79a1450ad8a7f405a425b251653d cpc-400s_hangul.rom 6a50295ea35e720ba6f4ba5616c3441128b384ed 128 cpc-400s_msx2sub.rom b6d3649a6647fa9f6bd61efc317485a20901128f National 1 cpc-400s_disk.rom 914f6ccb25d78621186001f2f5e2aaa2d628cd0c openMSX-RELEASE_0_12_0/share/machines/Daewoo_CPC-51_Zemmix_V.xml000066400000000000000000000034041257557151200240170ustar00rootroot00000000000000 Daewoo CPC-51 Zemmix V 1986 MSX game console MSX 16000 kr false false true false TMS9118 21000 cpc-51_basic-bios.rom a08a940aa87313509e00bc5ac7494d53d8e03492 openMSX-RELEASE_0_12_0/share/machines/Daewoo_CPC-61_Zemmix_Super_V.xml000066400000000000000000000056011257557151200251770ustar00rootroot00000000000000 Daewoo CPC-61 Zemmix Super V 1990 MSX2 game console MSX2 largest 16000 kr true false true false V9938 128 YM2149 21000 cpc-61.cmos CPC64122.ROM dd3c39c8cfa06ec69f54c95c3b2291e3da7bd4f2 CPC64122.ROM dd3c39c8cfa06ec69f54c95c3b2291e3da7bd4f2 64 CPC64122.ROM dd3c39c8cfa06ec69f54c95c3b2291e3da7bd4f2 openMSX-RELEASE_0_12_0/share/machines/Daewoo_DPC-100.xml000066400000000000000000000033101257557151200222510ustar00rootroot00000000000000 Daewoo DPC-100 1984 MSX 16000 kr false false true false TMS99X8A 21000 dpc-100_basic-bios1.rom 171b587bd5a947a13f3114120b6e7baca3b57d78 dpc-100_hangul.rom 4421fa2504cbce18f7c84b5ea97f04e017007f07 openMSX-RELEASE_0_12_0/share/machines/Daewoo_DPC-180.xml000066400000000000000000000033101257557151200222610ustar00rootroot00000000000000 Daewoo DPC-180 1984 MSX 16000 kr false false true false TMS99X8A 21000 dpc-180_basic-bios1.rom 171b587bd5a947a13f3114120b6e7baca3b57d78 dpc-180_hangul.rom 4421fa2504cbce18f7c84b5ea97f04e017007f07 openMSX-RELEASE_0_12_0/share/machines/Daewoo_DPC-200.xml000066400000000000000000000033111257557151200222530ustar00rootroot00000000000000 Daewoo DPC-200 1984 MSX 16000 kr false false true false TMS9918A 21000 dpc-200_basic-bios1.rom 171b587bd5a947a13f3114120b6e7baca3b57d78 dpc-200_hangul.rom 4421fa2504cbce18f7c84b5ea97f04e017007f07 openMSX-RELEASE_0_12_0/share/machines/Fenner_SPC-800.xml000066400000000000000000000037561257557151200223140ustar00rootroot00000000000000 Fenner SPC-800 Samsung MSX1 for the European/Western market. MSX 8963fc041975f31dc2ab1019cfdd4967999de53e spc-800_basic-bios1.rom 16000 gb false true false false TMS9129 AY8910 21000 openMSX-RELEASE_0_12_0/share/machines/Fujitsu_FM-X.xml000066400000000000000000000056001257557151200222730ustar00rootroot00000000000000 Fujitsu FM-X 1983 Fujitsu's one and only MSX computer. Can be connected to Fujitsu's FM-7 computer, which gives several special features, but all of that is not emulated. MSX 16000 jp_jis false false true false TMS9928A AY8910 jis 21000 fm-x_basic-bios1.rom 302afb5d8be26c758309ca3df611ae69cced2821 openMSX-RELEASE_0_12_0/share/machines/Goldstar_FC-200.xml000066400000000000000000000027401257557151200225030ustar00rootroot00000000000000 Goldstar FC-200 1984? MSX 16000 int false true false false TMS9129 21000 fc-200_basic-bios1.rom 829c00c3114f25b3dae5157c0a238b52a3ac37db openMSX-RELEASE_0_12_0/share/machines/Goldstar_FC-80U.xml000066400000000000000000000035001257557151200225510ustar00rootroot00000000000000 Goldstar FC-80U Basically a Daewoo IQ-1000 (DPC-200) with Hangul BIOS ROM 2.0 MSX 16000 kr false false true false TMS99X8A 21000 fc-80u_basic-bios1.rom 171b587bd5a947a13f3114120b6e7baca3b57d78 fc-80u_hangul.rom 58dbe73ae80c2c409e766c3ace730ecd7bec89d0 openMSX-RELEASE_0_12_0/share/machines/Gradiente_Expert_DD_Plus.xml000066400000000000000000000042431257557151200246600ustar00rootroot00000000000000 Gradiente Expert DD Plus 1989 Same as Gradiente Expert Plus, but also includes 3.5" disk drive MSX 16000 br_gradiente_1_1 true true false false T7937ANTSC 21000 expert_ddplus_basic-bios1.rom d6720845928ee848cfa88a86accb067397685f02 National 1 expert_ddplus_disk.rom f1525de4e0b60a6687156c2a96f8a8b2044b6c56 openMSX-RELEASE_0_12_0/share/machines/Gradiente_Expert_GPC-1.xml000066400000000000000000000033731257557151200241400ustar00rootroot00000000000000 Gradiente Expert GPC-1 1987 The follow up of the Brazillian Gradiente XP-800 (1.0), also known as Expert 1.1 MSX 16000 br_gradiente_1_1 true true false false TMS9128 21000 expert_1.1_basic-bios1.rom d6720845928ee848cfa88a86accb067397685f02 openMSX-RELEASE_0_12_0/share/machines/Gradiente_Expert_Plus.xml000066400000000000000000000037711257557151200243160ustar00rootroot00000000000000 Gradiente Expert Plus 1989 MSX 16000 br_gradiente_1_1 true true false false T7937ANTSC 21000 expert_plus_basic-bios1.rom d6720845928ee848cfa88a86accb067397685f02 expert_plus_demo.rom d4cea8c815f3eeabe0c6a1c845f902ec4318bf6b openMSX-RELEASE_0_12_0/share/machines/Gradiente_Expert_XP-800.xml000066400000000000000000000033671257557151200242300ustar00rootroot00000000000000 Gradiente Expert XP-800 1985 Brazillian MSX1 based on the National CF-3000, also known as the Expert 1.0 MSX 16000 br_gradiente_1_0 true true false false TMS9128 21000 expert_1.0_basic-bios1.rom ef3e010eb57e4476700a3bbff9d2119ab3acdf62 openMSX-RELEASE_0_12_0/share/machines/Hitachi_MB-H1.xml000066400000000000000000000042131257557151200222470ustar00rootroot00000000000000 Hitachi MB-H1 The Humanicatio... MSX 16000 jp_ansi false false true false TMS9918A AY8910 50on 21000 mb-h1_basic-bios1.rom 302afb5d8be26c758309ca3df611ae69cced2821 mb-h1_firmware.rom 3e005832138ffde8b1c36025754f81c2112b236d openMSX-RELEASE_0_12_0/share/machines/Hitachi_MB-H3.xml000066400000000000000000000056411257557151200222570ustar00rootroot00000000000000 Hitachi MB-H3 One of the few MSX2's with only 64kB VRAM. MSX2 16000 jp_ansi false false true false V9938 64 YM2149 50on 21000 mb-h3.cmos mb-h3_basic-bios2.rom 0081ea0d25bc5cd8d70b60ad8cfdc7307812c0fd mb-h3_msx2sub.rom b8e30d604d319d511cbfbc61e5d8c38fbb9c5a33 mb-h3_firmware.rom 74ee82cc09ffcf78f6e9a3f0d993f8f80d81444c openMSX-RELEASE_0_12_0/share/machines/JVC_HC-7GB.xml000066400000000000000000000027331257557151200214300ustar00rootroot00000000000000 JVC HC-7GB 1984? MSX 16000 gb false true false false TMS9929A 21000 hc-7gb_basic-bios1.rom 8963fc041975f31dc2ab1019cfdd4967999de53e openMSX-RELEASE_0_12_0/share/machines/Mitsubishi_ML-F110.xml000066400000000000000000000043151257557151200231720ustar00rootroot00000000000000 Mitsubishi ML-F110 A Japanese MSX1 with built in software. MSX 16000 jp_jis false false true false TMS9918A AY8910 jis 21000 ml-f110_basic-bios1.rom 302afb5d8be26c758309ca3df611ae69cced2821 openMSX-RELEASE_0_12_0/share/machines/Mitsubishi_ML-F120.xml000066400000000000000000000044501257557151200231730ustar00rootroot00000000000000 Mitsubishi ML-F120 A Japanese MSX1 with built in software. MSX 16000 jp_jis false false true false TMS9918A AY8910 jis 21000 ml-f120_basic-bios1.rom 302afb5d8be26c758309ca3df611ae69cced2821 ml-f120_firmware.rom 21a9f60cb6370d0617ce54c42bb7d8e40a4ab560 openMSX-RELEASE_0_12_0/share/machines/Mitsubishi_ML-F80.xml000066400000000000000000000027411257557151200231210ustar00rootroot00000000000000 Mitsubishi ML-F80 1984 MSX 16000 gb false true false false TMS9929A 21000 ml-f80_basic-bios1.rom 8963fc041975f31dc2ab1019cfdd4967999de53e openMSX-RELEASE_0_12_0/share/machines/Mitsubishi_ML-FX1.xml000066400000000000000000000031551257557151200231620ustar00rootroot00000000000000 Mitsubishi ML-FX1 1983 MSX 16000 gb true true false false TMS9929A 21000 ml-fx1_basic-bios1.rom 0cbe0df4af45e8f531e9c761403ac9e71808f20c openMSX-RELEASE_0_12_0/share/machines/Mitsubishi_ML-G1_ES.xml000066400000000000000000000053651257557151200234270ustar00rootroot00000000000000 Mitsubishi ML-G1 (ES) 1986 MSX2 largest 16000 es false true false false V9938 128 YM2149 21000 ml-g1_es.cmos ml-g1_es_basic-bios2.rom e4fdf518a8b9c8ab4290c21b83be2c347965fc24 ml-g1_es_msx2sub.rom 1e9a955943aeea9b1807ddf1250ba6436d8dd276 ml-g1_paint.rom 5cf0abca6dbcf940bc33c433ecb4e4ada02fbfe6 openMSX-RELEASE_0_12_0/share/machines/Mitsubishi_ML-G3_ES.xml000066400000000000000000000056451257557151200234320ustar00rootroot00000000000000 Mitsubishi ML-G3 (ES) 1986 MSX2 largest 16000 es true true false false V9938 128 YM2149 21000 ml-g3_es.cmos ml-g3_es_basic-bios2.rom e4fdf518a8b9c8ab4290c21b83be2c347965fc24 ml-g3_es_msx2sub.rom 1e9a955943aeea9b1807ddf1250ba6436d8dd276 Philips false 1 30ba1144c872a0bb1c91768e75a2c28ab1f4e3c6 ml-g3_es_disk.rom 128 b1ac74c2550d553579c1176f5dfde814218ec311 ml-g3_es_rs232.rom openMSX-RELEASE_0_12_0/share/machines/National_CF-1200.xml000066400000000000000000000035561257557151200225600ustar00rootroot00000000000000 National CF-1200 1984 MSX 16000 jp_ansi false false true false TMS9918A FF00 AY8910 50on 21000 cf-1200_basic-bios1.rom c7a2c5baee6a9f0e1c6ee7d76944c0ab1886796c openMSX-RELEASE_0_12_0/share/machines/National_CF-2000.xml000066400000000000000000000030031257557151200225420ustar00rootroot00000000000000 National CF-2000 1983 MSX 16000 jp_ansi false false true false TMS99X8A 50on 21000 cf-2000_basic-bios1.rom 302afb5d8be26c758309ca3df611ae69cced2821 openMSX-RELEASE_0_12_0/share/machines/National_CF-2700.xml000066400000000000000000000030031257557151200225510ustar00rootroot00000000000000 National CF-2700 1984 MSX 16000 jp_ansi false false true false TMS99X8A 50on 21000 cf-2700_basic-bios1.rom c7a2c5baee6a9f0e1c6ee7d76944c0ab1886796c openMSX-RELEASE_0_12_0/share/machines/National_CF-3000.xml000066400000000000000000000030211257557151200225430ustar00rootroot00000000000000 National CF-3000 1984 MSX 16000 jp_ansi true false true false TMS99X8A 50on 21000 cf-3000_basic-bios1.rom c7a2c5baee6a9f0e1c6ee7d76944c0ab1886796c openMSX-RELEASE_0_12_0/share/machines/National_CF-3300.xml000066400000000000000000000040301257557151200225470ustar00rootroot00000000000000 National CF-3300 1985 MSX 16000 jp_ansi true false true false TMS99X8A 50on 21000 cf-3300_basic-bios1.rom c7a2c5baee6a9f0e1c6ee7d76944c0ab1886796c National 1 cf-3300_disk.rom f1525de4e0b60a6687156c2a96f8a8b2044b6c56 openMSX-RELEASE_0_12_0/share/machines/National_FS-1300.xml000066400000000000000000000030221257557151200225650ustar00rootroot00000000000000 National FS-1300 1985 MSX 16000 jp_ansi false false true false TMS99X8A 50on 21000 fs-1300_basic-bios1.rom c7a2c5baee6a9f0e1c6ee7d76944c0ab1886796c openMSX-RELEASE_0_12_0/share/machines/National_FS-4000.xml000066400000000000000000000046611257557151200225770ustar00rootroot00000000000000 National FS-4000 1985 MSX fs-4000_kanjifont.rom 9ed3ab6d893632b9246e91b412cd5db519e7586b 16000 jp_ansi true false true false TMS9128 50on 21000 fs-4000_basic-bios1.rom df48902f5f12af8867ae1a87f255145f0e5e0774 fs-4000_msxword.rom 931d6318774bd495a32ec3dabf8d0edfc9913324 fs-4000_kanjibasic.rom 77bd67d5d10d459d343e79eafcd8e17eb0f209dd openMSX-RELEASE_0_12_0/share/machines/National_FS-4500.xml000066400000000000000000000114701257557151200226000ustar00rootroot00000000000000 National FS-4500 1986 MSX2 fs-4500_matsushita.sram fs-4500_kanjifont.rom 9ed3ab6d893632b9246e91b412cd5db519e7586b fs-4500_s1985.sram 16000 jp_jis true false true false V9938 128 YM2149 JIS 21000 fs-4500.cmos fs-4500_basic-bios2.rom 0081ea0d25bc5cd8d70b60ad8cfdc7307812c0fd fs-4500_msx2sub.rom b8e30d604d319d511cbfbc61e5d8c38fbb9c5a33 fs-4500_msxbunse.rom e89ea1e8e583392e2dd9debb8a4b6a162f58ba91 fs-4500_msxbudic.rom 1ebb06062428fcdc66808a03761818db2bba3c73 fs-4500_wordfont.rom 3ce8e35790eb4689b21e14c7ecdd4b63943ee158 fs-4500_msxjusho.rom 6442c1c5cece64c6dae90cc6ae3675f070d93e06 fs-4500_msxword1.rom 3f047469b62d93904005a0ea29092e892724ce0b fs-4500_kanjibasic1.rom df07e89fa0b1c7874f9cdf184c136f964fea4ff4 fs-4500_msxword2.rom 4c8ea05c09b40c41888fa18db065575a317fda16 fs-4500_kanjibasic2.rom c63db26660da96af56f8a7d3ea18544b9ae5a37c openMSX-RELEASE_0_12_0/share/machines/National_FS-4600.xml000066400000000000000000000107071257557151200226030ustar00rootroot00000000000000 National FS-4600 1986 MSX2 largest fs-4600_kanjifont.rom 5e872d5853698731a0ed22fb72dbcdfd59cd19c3 fs-4600_kanjifont12.rom a7a23dc01314e88381eee88b4878b39931ab4818 fs-4600_s1985.sram 16000 jp_jis true false true false V9938 128 YM2149 JIS 21000 fs-4600.cmos fs-4600_basic-bios2.rom 0081ea0d25bc5cd8d70b60ad8cfdc7307812c0fd fs-4600_msx2sub.rom 0fbd45ef3dd7bb82d4c31f1947884f411f1ca344 fs-4600_wordfont1.rom 31292b9ca9fe7d1d8833530f44c0a5671bfefe4e fs-4600_kanjibasic.rom 3a9a942ed888dd641cddf8deada1879c454df3c6 fs-4600_wordfont2.rom 02155fc25c9bd23e1654fe81c74486351e1ecc28 NATIONAL fs-4600.sram fs-4600_msxword.rom 005794c10a4237de3907ba4a44d436078d3c06c2 128 National 1 fs-4600_disk.rom 073feb8bb645d935e099afaf61e6f04f52adee42 openMSX-RELEASE_0_12_0/share/machines/National_FS-4700.xml000066400000000000000000000121601257557151200225770ustar00rootroot00000000000000 National FS-4700 1986 MSX2 fs-4700_matsushita.sram fs-4700_kanjifont.rom 9ed3ab6d893632b9246e91b412cd5db519e7586b fs-4700_s1985.sram 16000 jp_jis true false true false V9938 128 YM2149 JIS 21000 fs-4700.cmos fs-4700_basic-bios2.rom 0081ea0d25bc5cd8d70b60ad8cfdc7307812c0fd fs-4700_msx2sub.rom b8e30d604d319d511cbfbc61e5d8c38fbb9c5a33 fs-4700_msxbunse.rom e89ea1e8e583392e2dd9debb8a4b6a162f58ba91 fs-4700_msxbudic.rom 1ebb06062428fcdc66808a03761818db2bba3c73 fs-4700_wordfont.rom 3ce8e35790eb4689b21e14c7ecdd4b63943ee158 fs-4700_msxjusho.rom 6442c1c5cece64c6dae90cc6ae3675f070d93e06 fs-4700_msxword1.rom f5af1d2a8bcf247f78847e1a9d995e581df87e8e fs-4700_kanjibasic1.rom df07e89fa0b1c7874f9cdf184c136f964fea4ff4 fs-4700_msxword2.rom 4c8ea05c09b40c41888fa18db065575a317fda16 fs-4700_kanjibasic2.rom c63db26660da96af56f8a7d3ea18544b9ae5a37c National 1 fs-4700_disk.rom 78cd7f847e77fd8cd51a647efb2725ba93f4c471 openMSX-RELEASE_0_12_0/share/machines/National_FS-5000.xml000066400000000000000000000072441257557151200226000ustar00rootroot00000000000000 National FS-5000 1986 MSX2 largest fs-5000_kanjifont.rom 5e872d5853698731a0ed22fb72dbcdfd59cd19c3 fs-5000_s1985.sram 16000 jp_jis true false true false V9938 128 YM2149 JIS 21000 fs-5000.cmos fs-5000_basic-bios2.rom 59967765d6e9328909dee4dac1cbe4cf9d47d315 fs-5000_msx2sub.rom 0fbd45ef3dd7bb82d4c31f1947884f411f1ca344 fs-5000_kanjibasic.rom 3a9a942ed888dd641cddf8deada1879c454df3c6 fs-5000_setuprtc.rom 98bbfa3ab07b7a5cad55d7ddf7cbd9440caa2a86 128 National 2 fs-5000_disk.rom 073feb8bb645d935e099afaf61e6f04f52adee42 openMSX-RELEASE_0_12_0/share/machines/National_FS-5500F1.xml000066400000000000000000000071151257557151200227710ustar00rootroot00000000000000 National FS-5500F1 1985 MSX2 fs-5500_kanjifont.rom 9ed3ab6d893632b9246e91b412cd5db519e7586b fs-5000f1_s1985.sram 16000 jp_ansi true false true false V9938 128 YM2149 50on 21000 fs-5500f1.cmos fs-5500_basic-bios2.rom 44e0dd215b2a9f0770dd76fb49187c05b083eed9 fs-5500_msx2sub.rom 4be8371f3b03e70ddaca495958345f3c4f8e2d36 fs-5500_kanjibasic.rom 3a9a942ed888dd641cddf8deada1879c454df3c6 fs-5500_superimp.rom b677a861b67e8763a11d5dcf52416b42493ade57 National 1 fs-5500_disk.rom 78cd7f847e77fd8cd51a647efb2725ba93f4c471 openMSX-RELEASE_0_12_0/share/machines/National_FS-5500F2.xml000066400000000000000000000071151257557151200227720ustar00rootroot00000000000000 National FS-5500F2 1985 MSX2 fs-5500_kanjifont.rom 9ed3ab6d893632b9246e91b412cd5db519e7586b fs-5000f2_s1985.sram 16000 jp_ansi true false true false V9938 128 YM2149 50on 21000 fs-5500f2.cmos fs-5500_basic-bios2.rom 44e0dd215b2a9f0770dd76fb49187c05b083eed9 fs-5500_msx2sub.rom 4be8371f3b03e70ddaca495958345f3c4f8e2d36 fs-5500_kanjibasic.rom 3a9a942ed888dd641cddf8deada1879c454df3c6 fs-5500_superimp.rom b677a861b67e8763a11d5dcf52416b42493ade57 National 2 fs-5500_disk.rom 78cd7f847e77fd8cd51a647efb2725ba93f4c471 openMSX-RELEASE_0_12_0/share/machines/Panasonic_CF-2700_DE.xml000066400000000000000000000037631257557151200233040ustar00rootroot00000000000000 Panasonic CF-2700 ? One of the few European MSX machines by Panasonic. German version; one of the most common MSX machines in Germany. MSX 16000 de false false false false TMS9129 FF00 AY8910 21000 cf-2700_basic-bios1_german.rom 69bf27b610e11437dad1f7a1c37a63179a293d12 openMSX-RELEASE_0_12_0/share/machines/Panasonic_FS-A1.xml000066400000000000000000000053401257557151200226160ustar00rootroot00000000000000 Panasonic FS-A1 1986 MSX2 fs-a1_s1985.sram 16000 jp_ansi false false true false V9938 128 50on 21000 fs-a1.cmos fs-a1_basic-bios2.rom 0081ea0d25bc5cd8d70b60ad8cfdc7307812c0fd fs-a1_msx2sub.rom 0fbd45ef3dd7bb82d4c31f1947884f411f1ca344 fs-a1_deskpac1.rom 63098f27beac9eca6b39d837d2a552395df33fe1 fs-a1_deskpac2.rom 7f5b76605e3d898cc4b5aacf1d7682b82fe84353 openMSX-RELEASE_0_12_0/share/machines/Panasonic_FS-A1F.xml000066400000000000000000000075651257557151200227370ustar00rootroot00000000000000 Panasonic FS-A1F 1987 MSX2 fs-a1f_kanjifont.rom 5e872d5853698731a0ed22fb72dbcdfd59cd19c3 fs-a1f_s1985.sram 16000 jp_jis true false true false V9938 128 JIS 21000 fs-a1f.cmos DA1024D0365R.rom 9a62d7a5ccda974261f7c0600476d85e10deb99b 64 DA1024D0365R.rom 9a62d7a5ccda974261f7c0600476d85e10deb99b DA1024D0365R.rom 9a62d7a5ccda974261f7c0600476d85e10deb99b 7FF8 1 DA1024D0365R.rom 9a62d7a5ccda974261f7c0600476d85e10deb99b DA1024D0365R.rom 9a62d7a5ccda974261f7c0600476d85e10deb99b openMSX-RELEASE_0_12_0/share/machines/Panasonic_FS-A1FM.xml000066400000000000000000000074601257557151200230460ustar00rootroot00000000000000 Panasonic FS-A1FM 1987 MSX2 fs-a1fm_kanjifont.rom 5e872d5853698731a0ed22fb72dbcdfd59cd19c3 fs-a1fm_kanjifont12.rom a7a23dc01314e88381eee88b4878b39931ab4818 16000 jp_jis true false true false V9938 128 JIS 21000 fs-a1fm.cmos fs-a1fm_basic-bios2.rom 0081ea0d25bc5cd8d70b60ad8cfdc7307812c0fd 64 fs-a1fm_msx2sub.rom d552319a19814494e3016de4b8f010e8f7b97e02 FSA1FM1 fs-a1fm_modem.sram fs-a1fm_firmware.rom f89e3d8f3b6855c29d71d3149cc762e0f6918ad5 716952eab41077f18e6835d11caad6761b17d400 7FF8 1 fs-a1fm_disk.rom 141e61cc8e0e51382e508fbd77a34b778a4f8444 FSA1FM2 fs-a1fm.sram fs-a1fm_firmware.rom f89e3d8f3b6855c29d71d3149cc762e0f6918ad5 716952eab41077f18e6835d11caad6761b17d400 openMSX-RELEASE_0_12_0/share/machines/Panasonic_FS-A1FX.xml000066400000000000000000000121611257557151200230530ustar00rootroot00000000000000 Panasonic FS-A1FX 1988 MSX2+ 47 221 largest fs-a1fx_IC16.rom 623cbca109b6410df08ee7062150a6bda4b5d5d4 fs-a1fx_kanjifont.rom e0e99cd91e88ce2676445663f832c835d74d6fd4 16000 jp_jis true false true false V9958 128 JIS 21000 fs-a1fx.cmos true fs-a1fx.sram fs-a1fx_IC16.rom 623cbca109b6410df08ee7062150a6bda4b5d5d4 fs-a1fx_basic-bios2p.rom e90f80a61d94c617850c415e12ad70ac41e66bb7 64 fs-a1fx_IC16.rom 623cbca109b6410df08ee7062150a6bda4b5d5d4 fs-a1fx_msx2psub.rom fe0254cbfc11405b79e7c86c7769bd6322b04995 fs-a1fx_IC16.rom 623cbca109b6410df08ee7062150a6bda4b5d5d4 fs-a1fx_kanjibasic.rom 1ef3956f7f918873fb9b031339bba45d1e5e5878 7FF8 1 fs-a1fx_IC16.rom 623cbca109b6410df08ee7062150a6bda4b5d5d4 fs-a1fx_disk.rom bb59c849898d46a23fdbd0cc04ab35088e74a18d fs-a1fx_IC16.rom 623cbca109b6410df08ee7062150a6bda4b5d5d4 fs-a1fx_cockpit.rom 9d67fab55b85f4ac4f5924323a70020eb8589057 openMSX-RELEASE_0_12_0/share/machines/Panasonic_FS-A1GT.xml000066400000000000000000000121271257557151200230520ustar00rootroot00000000000000 Panasonic FS-A1GT 1992 The last officially released MSX machine. MSXturboR 47 221 5 e779c338eb91a7dea3ff75f3fde76b8af22c4a3a 5fa3aa79aeba2c0441f349e78e9a16d9d64422ea fs-a1gt_firmware.rom DRAM 40 43 62 63 9000 36 37 512 DRAM 56 57 DRAM 58 61 7FF2 48 55 1 PANASONIC 0 511 32 false fs-a1gt.sram 5aff2d9b6efc723bc395b0f96f0adfa83cc54a49 fs-a1gt_kanjifont.rom 16000 jp_jis true true false true false V9958 128 21000 JIS fs-a1gt.cmos false 21000 openMSX-RELEASE_0_12_0/share/machines/Panasonic_FS-A1MK2.xml000066400000000000000000000057041257557151200231340ustar00rootroot00000000000000 Panasonic FS-A1MK2 1987 MSX2 16000 jp_jis true false true false V9938 128 JIS 21000 fs-a1mk2.cmos fs-a1mk2_basic-bios2.rom 0081ea0d25bc5cd8d70b60ad8cfdc7307812c0fd 64 fs-a1mk2_msx2sub.rom 0fbd45ef3dd7bb82d4c31f1947884f411f1ca344 fs-a1mk2_cockpit1.rom 2752cd89754c05abdf7c23cba132d38e3ef0f27d fs-a1mk2_cockpit2.rom e194d290ebfa4595ce0349ea2fc15442508485b0 fs-a1mk2_cockpit3.rom a3f4e2e4934074925d775afe30ac72f150ede543 openMSX-RELEASE_0_12_0/share/machines/Panasonic_FS-A1ST.xml000066400000000000000000000121651257557151200230700ustar00rootroot00000000000000 Panasonic FS-A1ST 1990 MSXturboR 47 221 5 c212b11fda13f83dafed688c54d098e7e47ab225 4b84465c2faa802bfd5f772118c4d31f993f29d0 fs-a1st_firmware.rom fs-a1st_kanjifont.rom 5aff2d9b6efc723bc395b0f96f0adfa83cc54a49 16000 jp_jis true true false true false V9958 128 JIS 21000 fs-a1st.cmos false 21000 DRAM 40 43 62 63 9000 36 37 256 DRAM 56 57 DRAM 58 61 7FF2 48 55 1 PANASONIC 16 false fs-a1st.sram 0 255 openMSX-RELEASE_0_12_0/share/machines/Panasonic_FS-A1WSX.xml000066400000000000000000000114621257557151200232220ustar00rootroot00000000000000 Panasonic FS-A1WSX 1989 MSX2+ 47 221 largest fs-a1wsx_kanjifont.rom 5aff2d9b6efc723bc395b0f96f0adfa83cc54a49 16000 jp_jis true true false true false V9958 128 JIS 21000 fs-a1wsx.cmos true fs-a1wsx_matsushita.sram fs-a1wsx_basic-bios2p.rom f4433752d3bf876bfefb363c749d4d2e08a218b6 fs-a1wsx_fmbasic.rom aad42ba4289b33d8eed225d42cea930b7fc5c228 6354ccc5c100b1c558c9395fa8c00784d2e9b0a3 9000 64 fs-a1wsx_msx2psub.rom fe0254cbfc11405b79e7c86c7769bd6322b04995 fs-a1wsx_kanjibasic.rom dcc3a67732aa01c4f2ee8d1ad886444a4dbafe06 7FF8 1 fs-a1wsx_disk.rom 7ed7c55e0359737ac5e68d38cb6903f9e5d7c2b6 PANASONIC 16 true fs-a1wsx.sram fs-a1wsx_firmware.rom 3330d9b6b76e3c4ccb7cf252496ed15d08b95d3f openMSX-RELEASE_0_12_0/share/machines/Panasonic_FS-A1WX.xml000066400000000000000000000110271257557151200230740ustar00rootroot00000000000000 Panasonic FS-A1WX 1988 MSX2+ 47 221 largest e90f80a61d94c617850c415e12ad70ac41e66bb7 fs-a1wx_basic-bios2p.rom aad42ba4289b33d8eed225d42cea930b7fc5c228 6354ccc5c100b1c558c9395fa8c00784d2e9b0a3 fs-a1wx_fmbasic.rom 9000 64 fe0254cbfc11405b79e7c86c7769bd6322b04995 fs-a1wx_msx2psub.rom 1ef3956f7f918873fb9b031339bba45d1e5e5878 fs-a1wx_kanjibasic.rom 7FF8 bb59c849898d46a23fdbd0cc04ab35088e74a18d fs-a1wx_disk.rom 1 d37ab4bd2bfddd8c97476cbe7347ae581a6f2972 fs-a1wx_firmware.rom PANASONIC 16 true fs-a1wx.sram 5aff2d9b6efc723bc395b0f96f0adfa83cc54a49 fs-a1wx_kanjifont.rom 16000 jp_jis true true false true false V9958 128 21000 JIS fs-a1wx.cmos true fs-a1wx_matsushita.sram openMSX-RELEASE_0_12_0/share/machines/Philips_NMS_801.xml000066400000000000000000000026601257557151200225730ustar00rootroot00000000000000 Philips NMS 801 1989 "MSX Compatible", special Italian MSX, without cartridge slots or a printer port MSX 16000 int false true false false TMS9929A AY8910 21000 nms801_basic-bios1.rom 21329398c0f350e330b353f45f21aa7ba338fc8d openMSX-RELEASE_0_12_0/share/machines/Philips_NMS_8220.xml000066400000000000000000000056401257557151200226570ustar00rootroot00000000000000 Philips NMS 8220 1986 MSX2 largest 16000 int false true false false V9938 128 YM2149 21000 nms_8220.cmos nms8220_basic-bios2.rom 6103b39f1e38d1aa2d84b1c3219c44f1abb5436e nms8220_msx2sub.rom f5eb0a396097572589f2a6efeed045044e9425e4 64 eNpj+M8wouEI9z4DAJBx/wE= nms8220_designer.rom cb754aed85b3e97a7d3c5894310df7ca18f89f41 5df95d033ae70b107697b69470126ce1b7ae9eb5 openMSX-RELEASE_0_12_0/share/machines/Philips_NMS_8245.xml000066400000000000000000000115451257557151200226670ustar00rootroot00000000000000 Philips NMS 8245 1986 MSX2 largest 16000 int false true false false V9938 128 YM2149 21000 nms_8245.cmos NMS8245SystemROM1.08.bin cc57c1dcd7249ea9f8e2547244592e7d97308ed0 NMS8245SystemROM1.06.bin b746192dc333eaf2a725a44777303808a3649d63 nms8245_basic-bios2.rom 6103b39f1e38d1aa2d84b1c3219c44f1abb5436e NMS8245SystemROM1.08.bin cc57c1dcd7249ea9f8e2547244592e7d97308ed0 NMS8245SystemROM1.06.bin b746192dc333eaf2a725a44777303808a3649d63 nms8245_msx2sub.rom 5c1f9c7fb655e43d38e5dd1fcc6b942b2ff68b02 e7905d16d2ccd57a013c122dc432106cd59ef52c 128 eNpj+M8wouEI9z4DAJBx/wE= Philips 1 NMS8245SystemROM1.08.bin cc57c1dcd7249ea9f8e2547244592e7d97308ed0 NMS8245SystemROM1.06.bin b746192dc333eaf2a725a44777303808a3649d63 nms8245_disk.rom b484c8fa4549b79cff976001ac4beb19abf7cfb5 c3f3ca454d66a0bc791f1d4b60e6857b44eb8755 openMSX-RELEASE_0_12_0/share/machines/Philips_NMS_8250.xml000066400000000000000000000063631257557151200226650ustar00rootroot00000000000000 Philips NMS 8250 1986 A popular MSX2 machine in the Netherlands. MSX2 largest 6103b39f1e38d1aa2d84b1c3219c44f1abb5436e nms8250_basic-bios2.rom 5c1f9c7fb655e43d38e5dd1fcc6b942b2ff68b02 nms8250_msx2sub.rom 128 eNpj+M8wouEI9z4DAJBx/wE= Philips c3efedda7ab947a06d9345f7b8261076fa7ceeef 8625c6b633d9cca2875e4dc33404fb98653379d7 nms8250_disk.rom 1 16000 int true true false false V9938 128 YM2149 21000 nms8250.cmos openMSX-RELEASE_0_12_0/share/machines/Philips_NMS_8255.xml000066400000000000000000000061361257557151200226700ustar00rootroot00000000000000 Philips NMS 8255 1986 MSX2 largest 16000 int true true false false V9938 128 YM2149 21000 nms_8255.cmos nms8250_basic-bios2.rom 6103b39f1e38d1aa2d84b1c3219c44f1abb5436e nms8250_msx2sub.rom 5c1f9c7fb655e43d38e5dd1fcc6b942b2ff68b02 128 eNpj+M8wouEI9z4DAJBx/wE= Philips 2 nms8250_disk.rom c3efedda7ab947a06d9345f7b8261076fa7ceeef openMSX-RELEASE_0_12_0/share/machines/Philips_VG_8000.xml000066400000000000000000000027561257557151200225370ustar00rootroot00000000000000 Philips VG 8000/00 1984 MSX 16000 proto_int false true true true TMS9129 AY8910 21000 vg8000_basic-bios1.rom 42252cf87deeb58181a7bfec7c874190a1351779 openMSX-RELEASE_0_12_0/share/machines/Philips_VG_8010.xml000066400000000000000000000031061257557151200225260ustar00rootroot00000000000000 Philips VG 8010/00 1984 MSX 16000 proto_int false true true true TMS9129 AY8910 21000 vg8010_basic-bios1.rom 42252cf87deeb58181a7bfec7c874190a1351779 openMSX-RELEASE_0_12_0/share/machines/Philips_VG_8010F.xml000066400000000000000000000027601257557151200226410ustar00rootroot00000000000000 Philips VG 8010/19 1984 French version of the VG 8010 MSX 16000 proto_fr false true true true TMS9929A 21000 vg8010-19_basic-bios1.rom 898630ad1497dc9a329580c682ee55c4bcb9c30c openMSX-RELEASE_0_12_0/share/machines/Philips_VG_8020-20.xml000066400000000000000000000036261257557151200227550ustar00rootroot00000000000000 Philips VG 8020/20 ? MSX 16000 int false true false false TMS9129 YM2149 21000 vg8020-20_basic-bios1.rom e998f0c441f4f1800ef44e42cd1659150206cf79 openMSX-RELEASE_0_12_0/share/machines/Philips_VG_8020.xml000066400000000000000000000030501257557151200225250ustar00rootroot00000000000000 Philips VG 8020/00 ? MSX 16000 int false true false false TMS9929A YM2149 21000 vg8020_basic-bios1.rom 829c00c3114f25b3dae5157c0a238b52a3ac37db openMSX-RELEASE_0_12_0/share/machines/Philips_VG_8020F.xml000066400000000000000000000034221257557151200226360ustar00rootroot00000000000000 Philips VG 8020/19 ? French version of the VG 8020(/20) MSX 16000 fr false true false false TMS9929A YM2149 21000 vg8020-19_basic-bios1.rom ae4a6632d4456ef44603e72f5acd5bbcd6c0d124 openMSX-RELEASE_0_12_0/share/machines/Philips_VG_8230.xml000066400000000000000000000061021257557151200225310ustar00rootroot00000000000000 Philips VG 8230 1986? MSX2 largest 16000 gb false true false false V9938 128 YM2149 21000 vg_8230.cmos vg8230_basic-bios2.rom 0de3c802057560560a03d7965fcc4cff69f8575c vg8230_msx2sub.rom 3288894e1be6af705871499b23c85732dbc40993 64 eNpj+M8wouEI9z4DAJBx/wE= Philips 1 vg8230_disk.rom b43a4be5c754f68dc3ee64eee29d64f30fac2aff 0f5798850d11b316a4254b222ca08cc4ad6d4da2 openMSX-RELEASE_0_12_0/share/machines/Philips_VG_8235.xml000066400000000000000000000076271257557151200225530ustar00rootroot00000000000000 Philips VG 8235 1986 MSX2 largest false 16000 int true false false V9938 128 YM2149 21000 vg_8235.cmos vg8235_basic-bios2.rom 5e1a4bd6826b29302a1eb88c340477e7cbd0b50a vg8235_msx2sub.rom 3288894e1be6af705871499b23c85732dbc40993 e7905d16d2ccd57a013c122dc432106cd59ef52c 5c1f9c7fb655e43d38e5dd1fcc6b942b2ff68b02 c289dad246364e2dd716c457ca5eecf98e76c9ab 128 eNpj+M8wouEI9z4DAJBx/wE= Philips 1 vg8235_disk.rom f283ad736a8f3b88ca3bd940e28adf27c12cbda1 8954e59aa79310c7b719ecf0cde1e82fb731dcd1 383de340910981bc1e8a2acaec8663b020804ba1 849f93867ff7846b27f84d0be418569faf058ac2 openMSX-RELEASE_0_12_0/share/machines/Pioneer_PX-7.xml000066400000000000000000000041511257557151200222270ustar00rootroot00000000000000 Pioneer PX-7 1984? MSX 16000 jp_ansi false true true false TMS9928A 21000 0 -100 100 px-7_basic-bios1.rom 302afb5d8be26c758309ca3df611ae69cced2821 px-7_pbasic.rom 665d805f96616e1037f1823050657b7849899283 openMSX-RELEASE_0_12_0/share/machines/Pioneer_PX-7UK.xml000066400000000000000000000044051257557151200224710ustar00rootroot00000000000000 Pioneer PX-7(UK) 1984? MSX 16000 gb false true false false TMS9129 21000 0 -100 100 px-7uk_basic-bios1.rom 8963fc041975f31dc2ab1019cfdd4967999de53e false px-7uk_pbasic.rom 4f0102cdc27216fd9bcdb9663db728d2ccd8ca6d openMSX-RELEASE_0_12_0/share/machines/Pioneer_PX-V60.xml000066400000000000000000000037561257557151200224460ustar00rootroot00000000000000 Pioneer PX-V60 1985? MSX 16000 jp_ansi false true true false TMS9128 21000 px-v60_basic-bios1.rom 302afb5d8be26c758309ca3df611ae69cced2821 px-v60_pbasic.rom 4f0102cdc27216fd9bcdb9663db728d2ccd8ca6d openMSX-RELEASE_0_12_0/share/machines/README000066400000000000000000000002301257557151200202030ustar00rootroot00000000000000This directory contains definitions of MSX machines. You can activate them with the machine command in the console or the -machine command line option. openMSX-RELEASE_0_12_0/share/machines/Sanyo_MPC-10.xml000066400000000000000000000033511257557151200220620ustar00rootroot00000000000000 Sanyo MPC-10/Wavy10 ? A simple Japanese MSX1 machine MSX 16000 jp_ansi false false true false TMS9918A AY8910 50on 21000 mpc-10_basic-bios1.rom 302afb5d8be26c758309ca3df611ae69cced2821 openMSX-RELEASE_0_12_0/share/machines/Sanyo_MPC-100.xml000066400000000000000000000027361257557151200221500ustar00rootroot00000000000000 Sanyo MPC-100 1984? MSX 16000 gb false true false false TMS9929A 21000 mpc100_basic-bios1.rom 8963fc041975f31dc2ab1019cfdd4967999de53e openMSX-RELEASE_0_12_0/share/machines/Sanyo_MPC-25FD.xml000066400000000000000000000053501257557151200223030ustar00rootroot00000000000000 Sanyo MPC-25FD An MSX2 with diskdrive and only one cartridge port. MSX2 16000 jp_jis true false true false V9938 128 YM2149 JIS 21000 mpc-25fd.cmos mpc-25fd_basic-bios2.rom 0081ea0d25bc5cd8d70b60ad8cfdc7307812c0fd mpc-25fd_msx2sub.rom b8e30d604d319d511cbfbc61e5d8c38fbb9c5a33 Philips 1 mpc-25fd_disk.rom 58ac78bba29a06645ca8d6a94ef2ac68b743ad32 openMSX-RELEASE_0_12_0/share/machines/Sanyo_MPC-6.xml000066400000000000000000000042101257557151200220020ustar00rootroot00000000000000 Sanyo MPC-6/Wavy6 ? A simple 64kB Japanese MSX1 machine MSX 16000 jp_ansi false false true false TMS9918A AY8910 50on 21000 mpc-6_basic-bios1.rom 302afb5d8be26c758309ca3df611ae69cced2821 openMSX-RELEASE_0_12_0/share/machines/Sanyo_PHC-23JB.xml000066400000000000000000000046561257557151200223060ustar00rootroot00000000000000 Sanyo PHC-23J(B) A blue MSX2... MSX2 phc-23jb_s1985.sram 16000 jp_jis true false true false V9938 128 YM2149 JIS 21000 phc-23jb.cmos phc-23jb_basic-bios2.rom 4ce41fcc1a603411ec4e99556409c442078f0ecf phc-23jb_msx2sub.rom fd9fa78bac25aa3c0792425b21d14e364cf7eea4 0000FFFFFFFF0000 openMSX-RELEASE_0_12_0/share/machines/Sanyo_PHC-28L.xml000066400000000000000000000032011257557151200221740ustar00rootroot00000000000000 Sanyo PHC-28L ? French MSX machine (upgrade of the PHC-28S). MSX 16000 fr false true false false TMS9929A YM2149 21000 phc-28l_basic-bios1.rom d3af963e2529662eae63f04a2530454685a1989f openMSX-RELEASE_0_12_0/share/machines/Sanyo_PHC-28S.xml000066400000000000000000000035001257557151200222050ustar00rootroot00000000000000 Sanyo PHC-28S 1984? French MSX with only 16kB of RAM: Ordinateur Personnel MSX 16000 gb false true false false AY8910 TMS9929A 21000 phc-28s_basic-bios1.rom b1cce60ef61c058f5e42ef7ac635018d1a431168 openMSX-RELEASE_0_12_0/share/machines/Sanyo_PHC-35J.xml000066400000000000000000000057441257557151200222060ustar00rootroot00000000000000 Sanyo PHC-35J 1989 MSX2+ 47 221 largest phc-35j_kanjifont.rom 84a645becec0a25d3ab7a909cde1b242699a8662 16000 jp_jis true false true false V9958 128 JIS 21000 phc-35j.cmos true phc-35j_basic-bios2p.rom f4433752d3bf876bfefb363c749d4d2e08a218b6 64 phc-35j_msx2psub.rom fe0254cbfc11405b79e7c86c7769bd6322b04995 phc-35j_kanjibasic.rom dcc3a67732aa01c4f2ee8d1ad886444a4dbafe06 openMSX-RELEASE_0_12_0/share/machines/Sanyo_PHC-70FD.xml000066400000000000000000000076701257557151200223050ustar00rootroot00000000000000 Sanyo PHC-70FD 1988 MSX2+ 47 221 largest phc-70fd_kanjifont.rom 84a645becec0a25d3ab7a909cde1b242699a8662 16000 jp_jis true false true false V9958 128 JIS 21000 phc-70fd.cmos true phc-70fd_basic-bios2p.rom e90f80a61d94c617850c415e12ad70ac41e66bb7 64 phc-70fd_msx2psub.rom fe0254cbfc11405b79e7c86c7769bd6322b04995 phc-70fd_kanjibasic.rom 1ef3956f7f918873fb9b031339bba45d1e5e5878 7FF8 1 phc-70fd_disk.rom 9efa744be8355675e7bfdd3976bbbfaf85d62e1d phc-70fd_fmbasic.rom aad42ba4289b33d8eed225d42cea930b7fc5c228 9000 phc-70fd_basickun.rom 22b3191d865010264001b9d896186a9818478a6b openMSX-RELEASE_0_12_0/share/machines/Sanyo_PHC-70FD2.xml000066400000000000000000000076701257557151200223670ustar00rootroot00000000000000 Sanyo PHC-70FD2 1988 MSX2+ largest phc-70fd2_kanjifont.rom bcdb4dae303dfe5234f372d70a5e0271d3202c36 16000 jp_jis true false true false V9958 128 JIS 21000 phc-70fd2.cmos true phc-70fd2_basic-bios2p.rom e90f80a61d94c617850c415e12ad70ac41e66bb7 64 phc-70fd2_msx2psub.rom fe0254cbfc11405b79e7c86c7769bd6322b04995 phc-70fd2_kanjibasic.rom 1ef3956f7f918873fb9b031339bba45d1e5e5878 7FF8 2 phc-70fd2_disk.rom 9efa744be8355675e7bfdd3976bbbfaf85d62e1d phc-70fd2_fmbasic.rom aad42ba4289b33d8eed225d42cea930b7fc5c228 9000 phc-70fd2_basickun.rom 22b3191d865010264001b9d896186a9818478a6b openMSX-RELEASE_0_12_0/share/machines/Sharp_HB-8000_1.1.xml000066400000000000000000000030411257557151200224420ustar00rootroot00000000000000 Sharp HB-8000 v1.1 1985 Original white/gray model of the Hotbit. MSX 16000 br_hotbit false true false false TMS9128 21000 hotbit_1.1_basic-bios1.rom 663f8c512d04d213fa616b0db5eefe3774012a4b openMSX-RELEASE_0_12_0/share/machines/Sharp_HB-8000_1.2.xml000066400000000000000000000031151257557151200224450ustar00rootroot00000000000000 Sharp HB-8000 v1.2 1987 Black model of the Hotbit, with an updated BIOS MSX 16000 br_hotbit false true false false TMS9128 21000 hotbit_1.2_basic-bios1.rom 9425815446d468058705bae545ffa13646744a87 openMSX-RELEASE_0_12_0/share/machines/Sony_HB-10.xml000066400000000000000000000031371257557151200215750ustar00rootroot00000000000000 Sony HB-10 MSX 16000 jp_ansi false false true false TMS99X8A AY8910 50on 21000 hb-10_basic-bios1.rom 302afb5d8be26c758309ca3df611ae69cced2821 openMSX-RELEASE_0_12_0/share/machines/Sony_HB-101.xml000066400000000000000000000070301257557151200216520ustar00rootroot00000000000000 Sony HB-101 1984 MSX false 16000 jp_ansi false true false TMS9118 YM2149 50on 21000 hb-101_basic-bios1.rom 302afb5d8be26c758309ca3df611ae69cced2821 hb-101_firmware.rom 64adb7fcf9b86f59d8658badb02f58e61bb15712 openMSX-RELEASE_0_12_0/share/machines/Sony_HB-101P.xml000066400000000000000000000041001257557151200217650ustar00rootroot00000000000000 Sony HB-101P 1985? MSX true 16000 int false false false TMS9129 YM2149 50on 21000 hb-101p_basic-bios1.rom 5e7c8eab238712d1e18b0219c0f4d4dae180420d hb-101p_firmware.rom 8ffc24677fd9d2606a79718764261cdf02434f0a openMSX-RELEASE_0_12_0/share/machines/Sony_HB-10P.xml000066400000000000000000000040331257557151200217110ustar00rootroot00000000000000 Sony HB-10P MSX 16000 gb false true false false T6950PAL FF00 YM2149 21000 hb-10p_basic-bios1.rom 5e7c8eab238712d1e18b0219c0f4d4dae180420d 3 openMSX-RELEASE_0_12_0/share/machines/Sony_HB-201.xml000066400000000000000000000044011257557151200216520ustar00rootroot00000000000000 Sony HB-201 1985 MSX 16000 jp_ansi false false true false TMS9118 YM2149 50on 21000 hb-201_basic-bios1.rom 302afb5d8be26c758309ca3df611ae69cced2821 hb-201_firmware.rom 0f4f09f1a6ef7535b243afabfb44a3a0eb0498d9 openMSX-RELEASE_0_12_0/share/machines/Sony_HB-201P.xml000066400000000000000000000033431257557151200217760ustar00rootroot00000000000000 Sony HB-201P 1985 MSX 16000 gb false true false false TMS9129 YM2149 21000 hb-201p_basic-bios1.rom 5e7c8eab238712d1e18b0219c0f4d4dae180420d hb-201p_firmware.rom e84d3ec7a595ee36b50e979683c84105c1871857 openMSX-RELEASE_0_12_0/share/machines/Sony_HB-20P.xml000066400000000000000000000030201257557151200217050ustar00rootroot00000000000000 Sony HB-20P Spanish MSX MSX 16000 es false true false false T6950PAL YM2149 21000 hb-20p_basic-bios1.rom 63050d2d21214a721cc55f152c22b7be8061ac33 openMSX-RELEASE_0_12_0/share/machines/Sony_HB-501P.xml000066400000000000000000000027351257557151200220050ustar00rootroot00000000000000 Sony HB-501P 1984 MSX 16000 gb false true false false TMS9929A 21000 hb-501p_basic-bios1.rom 5e7c8eab238712d1e18b0219c0f4d4dae180420d openMSX-RELEASE_0_12_0/share/machines/Sony_HB-55P.xml000066400000000000000000000035651257557151200217330ustar00rootroot00000000000000 Sony HB-55P 1983 MSX 16000 gb false true false false TMS9929A AY8910 21000 hb-55p_basic-bios1.rom 8963fc041975f31dc2ab1019cfdd4967999de53e hb-55p_firmware.rom b262aedc71b445303f84efe5e865cbb71fd7d952 openMSX-RELEASE_0_12_0/share/machines/Sony_HB-75P.xml000066400000000000000000000040051257557151200217230ustar00rootroot00000000000000 Sony HB-75P 1983 MSX 16000 gb false true false false TMS9929A YM2149 21000 hb-75p_basic-bios1.rom 8963fc041975f31dc2ab1019cfdd4967999de53e hb-75p_firmware.rom b262aedc71b445303f84efe5e865cbb71fd7d952 openMSX-RELEASE_0_12_0/share/machines/Sony_HB-F1.xml000066400000000000000000000056621257557151200216300ustar00rootroot00000000000000 Sony HB-F1 1986 MSX2 largest 16000 jp_ansi false false true false V9938 128 50on 21000 hb-f1.cmos hb-f1_basic-bios2.rom 0081ea0d25bc5cd8d70b60ad8cfdc7307812c0fd hb-f1_msx2sub.rom b8e30d604d319d511cbfbc61e5d8c38fbb9c5a33 hb-f1_firmware1.rom 9db72bb78792595a12499c821048504dc96ef848 hb-f1_firmware2.rom aa78fc9bcd2343f84cf790310a768ee47f90c841 hb-f1_firmware3.rom 58accf41a90693874b86ce98d8d43c27beb8b6dc openMSX-RELEASE_0_12_0/share/machines/Sony_HB-F1II.xml000066400000000000000000000057661257557151200220570ustar00rootroot00000000000000 Sony HB-F1II 1987 MSX2 47 221 largest 16000 jp_jis false false true false V9938 128 JIS 21000 hb-f1ii.cmos hb-f1ii_basic-bios2.rom 0081ea0d25bc5cd8d70b60ad8cfdc7307812c0fd hb-f1ii_msx2sub.rom 0fbd45ef3dd7bb82d4c31f1947884f411f1ca344 hb-f1ii_firmware1.rom 30d914cda2180889a40a3328e0a0c1327f4eaa10 hb-f1ii_firmware2.rom ed2fea5c2a3c2e58d4f69f9d636e08574486a2b1 hb-f1ii_firmware3.rom 917d1c079e03c4a44de864f123d03c4e32c8daae openMSX-RELEASE_0_12_0/share/machines/Sony_HB-F1XD.xml000066400000000000000000000056061257557151200220620ustar00rootroot00000000000000 Sony HB-F1XD 1987 MSX2 47 221 largest 16000 jp_jis true false true false V9938 128 hb-f1xd_s1985.sram YM2149 JIS 21000 hb-f1xd.cmos hb-f1xd_basic-bios2.rom 4ce41fcc1a603411ec4e99556409c442078f0ecf hb-f1xd_msx2sub.rom 0fbd45ef3dd7bb82d4c31f1947884f411f1ca344 Sony 4000 1 hb-f1xd_disk.rom 12f2cc79b3d09723840bae774be48c0d721ec1c6 64 openMSX-RELEASE_0_12_0/share/machines/Sony_HB-F1XDJ.xml000066400000000000000000000140721257557151200221710ustar00rootroot00000000000000 Sony HB-F1XDJ 1988 MSX2+ 47 221 largest hb-f1xdj_kanjifont.rom 218d91eb6df2823c924d3774a9f455492a10aecb 16000 jp_jis true false true false V9958 128 hb-f1xdj_s1985.sram YM2149 JIS 21000 hb-f1xdj.cmos false Sony_HB-F1XDJ_main.rom f2a1d326d72d4c70ea214d7883838de8847a82b7 hb-f1xdj_basic-bios2p.rom e2fbd56e42da637609d23ae9df9efd1b4241b18a hb-f1xdj.rom ade0c5ba5574f8114d7079050317099b4519e88f hb-f1xdj_msx-je.sram Halnote 64 eNpj+M8wouEI9z4DAJBx/wE= Sony_HB-F1XDJ_main.rom f2a1d326d72d4c70ea214d7883838de8847a82b7 hb-f1xdj_msx2sub.rom fe0254cbfc11405b79e7c86c7769bd6322b04995 Sony_HB-F1XDJ_main.rom f2a1d326d72d4c70ea214d7883838de8847a82b7 hb-f1xdj_kanjibasic.rom 1ef3956f7f918873fb9b031339bba45d1e5e5878 Sony 4000 1 Sony_HB-F1XDJ_main.rom f2a1d326d72d4c70ea214d7883838de8847a82b7 hb-f1xdj_disk.rom 12f2cc79b3d09723840bae774be48c0d721ec1c6 c9500da2151881ea40b005f383b8602450129bf3 Sony_HB-F1XDJ_main.rom f2a1d326d72d4c70ea214d7883838de8847a82b7 hb-f1xdj_fmbasic.rom aad42ba4289b33d8eed225d42cea930b7fc5c228 9000 openMSX-RELEASE_0_12_0/share/machines/Sony_HB-F1XV.xml000066400000000000000000000114731257557151200221030ustar00rootroot00000000000000 Sony HB-F1XV 1989 The last Sony MSX. Same as HB-F1XDJ, but with upgraded BIOS, kanji driver and disk basic. MSX2+ 47 221 largest hb-f1xv_kanjifont.rom 218d91eb6df2823c924d3774a9f455492a10aecb 16000 jp_jis true false true false V9958 128 hb-f1xv_s1985.sram YM2149 JIS 21000 hb-f1xv.cmos false hb-f1xv_basic-bios2p.rom 174c9254f09d99361ff7607630248ff9d7d8d4d6 hb-f1xv.rom ade0c5ba5574f8114d7079050317099b4519e88f hb-f1xv_msx-je.sram Halnote 64 eNpj+M8wouEI9z4DAJBx/wE= hb-f1xv_msx2sub.rom fe0254cbfc11405b79e7c86c7769bd6322b04995 hb-f1xv_kanjibasic.rom dcc3a67732aa01c4f2ee8d1ad886444a4dbafe06 Sony 4000 1 hb-f1xv_disk.rom 5a4e7dbbfb759109c7d2a3b38bda9c60bf6ffef5 hb-f1xv_fmbasic.rom aad42ba4289b33d8eed225d42cea930b7fc5c228 9000 openMSX-RELEASE_0_12_0/share/machines/Sony_HB-F5.xml000066400000000000000000000046061257557151200216310ustar00rootroot00000000000000 Sony HB-F5 1985 MSX2 16000 jp_ansi true false true false V9938 128 50on YM2149 21000 hb-f5.cmos hb-f5_basic-bios2.rom 0081ea0d25bc5cd8d70b60ad8cfdc7307812c0fd hb-f5_msx2sub.rom b8e30d604d319d511cbfbc61e5d8c38fbb9c5a33 hb-f5_firmware.rom 06ba91d6732ee8a2ecd5dcc38b0ce42403d86708 openMSX-RELEASE_0_12_0/share/machines/Sony_HB-F500P.xml000066400000000000000000000053131257557151200221050ustar00rootroot00000000000000 Sony HB-F500P 1985 MSX2 16000 gb true true false false V9938 128 YM2149 21000 hb-f500p.cmos hb-f500p_basic-bios2.rom 0de3c802057560560a03d7965fcc4cff69f8575c hb-f500p_msx2sub.rom 3288894e1be6af705871499b23c85732dbc40993 Sony 4000 1 hb-f500p_disk.rom 1566532146fcfc28c707753777c66585dc12418a openMSX-RELEASE_0_12_0/share/machines/Sony_HB-F700D.xml000066400000000000000000000060501257557151200220720ustar00rootroot00000000000000 Sony HB-F700D 1985 German version of the Sony HB-F700 MSX2 largest 16000 de true true false false V9938 128 hb-f700d_s1985.sram YM2149 21000 hb-f700d.cmos hb-f700d_basic-bios2.rom cef16eb95502ba6ab2265fcafcedde470a101541 hb-f700d_msx2sub.rom 3288894e1be6af705871499b23c85732dbc40993 Sony 4000 1 hb-f700d_disk.rom 0e081572f84555dc13bdb0c7044a19d6c164d985 7dcd434d9a244e23afd212d3fea4c3bf3fe2a300 256 openMSX-RELEASE_0_12_0/share/machines/Sony_HB-F700P.xml000066400000000000000000000062611257557151200221120ustar00rootroot00000000000000 Sony HB-F700P 1985 European MSX2 with a lot of RAM (256kB). MSX2 largest 16000 int true true false false V9938 128 hb-f700p_s1985.sram YM2149 21000 hb-f700p.cmos hb-f700p_basic-bios2.rom 0de3c802057560560a03d7965fcc4cff69f8575c hb-f700p_msx2sub.rom 24624c5fa3a8069b1d865cdea8a029f15c1955ea 3288894e1be6af705871499b23c85732dbc40993 Sony 4000 1 hb-f700p_disk.rom 3376cf9dd2b1ac9b41bf6bf6598b33136e86f9d5 7dcd434d9a244e23afd212d3fea4c3bf3fe2a300 256 openMSX-RELEASE_0_12_0/share/machines/Sony_HB-F900.xml000066400000000000000000000064041257557151200217730ustar00rootroot00000000000000 Sony HB-F900 1986 MSX2 hb-f900_kanjifont.rom 6acaf2eeb57f65f7408235d5e07b7563229de799 16000 jp_ansi true false true false V9938 128 50on 21000 hb-f900.cmos hb-f900_basic-bios2.rom 0081ea0d25bc5cd8d70b60ad8cfdc7307812c0fd hb-f900_msx2sub.rom 0fbd45ef3dd7bb82d4c31f1947884f411f1ca344 256 Sony 4000 2 hb-f900_disk.rom fc760d1d7b16370abc7eea39955f230b95b37df6 hb-f900_video-utility.rom 558b7383544542cf7333700ff90c3efbf93ba2a3 openMSX-RELEASE_0_12_0/share/machines/Sony_HB-F9P.xml000066400000000000000000000055541257557151200217600ustar00rootroot00000000000000 Sony HB-F9P 1985 MSX2 largest 16000 gb true true false false V9938 128 hb-f9p_s1985.sram YM2149 21000 hb-f9p.cmos hb-f9p_basic-bios2.rom 0de3c802057560560a03d7965fcc4cff69f8575c hb-f9p_msx2sub.rom 7b4a96402847decfc110ff9eda713bdcd218bd83 hb-f9p_firmware2.rom 8cc1f7ceeef745bb34e80253971e137213671486 hb-f9p_firmware.rom 2d1880d1f5a6944fcb1b198b997a3d90ecd1903d 128 openMSX-RELEASE_0_12_0/share/machines/Sony_HB-F9P_Russian.xml000066400000000000000000000051071257557151200234560ustar00rootroot00000000000000 Sony HB-F9P Russian 1985 An extremely rare Russian version of the F9P MSX2 largest 16000 ru true true true false V9938 128 hb-f9p_russian_s1985.sram YM2149 21000 hb-f9p_russian.cmos hb-f9p_russian_basic-bios2.rom 7f440ec7295d889b097e1b66bf9bc5ce086f59aa hb-f9p_russian_msx2sub.rom a6d7b1fd4ee896ca7513d02c033fc9a8aa065235 128 openMSX-RELEASE_0_12_0/share/machines/Sony_HB-F9S.xml000066400000000000000000000066731257557151200217660ustar00rootroot00000000000000 Sony HB-F9S 1985 Spanish MSX2 MSX2 largest 16000 es true true false false V9938 128 hb-f9s_s1985.sram YM2149 21000 hb-f9s.cmos hb-f9s_IC11.rom 4811956f878c3e03da46317f787cdc4bebc86f47 hb-f9s_IC12.rom 1166a93d7185ba024bdf2bfa9a30e1c447fb6db1 hb-f9s_IC12.rom 1166a93d7185ba024bdf2bfa9a30e1c447fb6db1 hb-f9s_IC13.rom 7efac54dd8f580f3b7809ab35db4ae58f0eb84d1 hb-f9s_IC13.rom 7efac54dd8f580f3b7809ab35db4ae58f0eb84d1 128 openMSX-RELEASE_0_12_0/share/machines/Sony_HB-G900P.xml000066400000000000000000000063541257557151200221200ustar00rootroot00000000000000 Sony HB-G900P 1986 MSX2 16000 int true V9938 128 AY8910 21000 hb-g900p.cmos 0de3c802057560560a03d7965fcc4cff69f8575c hb-g900p_basic-bios2.rom 3288894e1be6af705871499b23c85732dbc40993 hb-g900p_msx2sub.rom Sony 4000 false 1 12f2cc79b3d09723840bae774be48c0d721ec1c6 hb-g900p_disk.rom true true 373aa82d0426830880a7344ef98f7309d93814c7 hb-g900p_rs232.rom 8779b004e7605a3c419825f0373a5d8fa84e1d5b hb-g900p_video-utility.rom false openMSX-RELEASE_0_12_0/share/machines/Spectravideo_SVI-728.xml000066400000000000000000000031711257557151200235430ustar00rootroot00000000000000 Spectravideo SVI-728 1983 MSX 16000 int true true false false TMS9129 AY8910 21000 svi-728_basic-bios1.rom ea6a82cf8c6e65eb30b98755c8577cde8d9186c0 openMSX-RELEASE_0_12_0/share/machines/Spectravideo_SVI-728_ES.xml000066400000000000000000000036471257557151200241420ustar00rootroot00000000000000 Spectravideo SVI-728 (ES) 1983 MSX 16000 es true true false false TMS9129 AY8910 21000 svi-728es_basic-bios1.rom 82415ee031721d1954bfa42e1c6dd79d71c692d6 openMSX-RELEASE_0_12_0/share/machines/Spectravideo_SVI-738.xml000066400000000000000000000050201257557151200235370ustar00rootroot00000000000000 Spectravideo SVI-738 X'PRESS 1986 A very complete MSX1 machine. MSX 16000 int false true false false V9938 16 AY8910 21000 svi-738_basic-bios1.rom c53b3f2c00f31683914f7452f3f4d94ae2929c0d false svi-738_rs232.rom 4e9384c9d137f0ab65ffc5a78f04cd8c9df6c8b7 National 1 svi-738_disk.rom 17b2810545e55d3fa5821d74d8b5ab5475165fba e3130d89a422db291142f366ab24cd0dbc921467 openMSX-RELEASE_0_12_0/share/machines/Talent_DPC-200.xml000066400000000000000000000031321257557151200222650ustar00rootroot00000000000000 Talent DPC-200 ? Daewoo's DPC-200 localized for Argentina. MSX 16000 es false true false false TMS9129 AY8910 21000 talent_dpc-200_basic-bios1.rom d5bf4814ea694481c8badbb8de8d56a08ee03cc0 openMSX-RELEASE_0_12_0/share/machines/Talent_TPC-310.xml000066400000000000000000000060031257557151200223070ustar00rootroot00000000000000 Talent TPC-310 1988 The most advanced Argentinian MSX, includes turbo BASIC and several built in programs. MSX2 largest tpc-310_s1985.sram 16000 es false true false false V9938 128 YM2149 21000 tpc-310.cmos tpc-310_basic-bios2.rom 7bba23669b7abfb6a142f9e1735b847d6e4e8267 128 tpc-310_msx2sub.rom 39dfc46260f99b670916b1e55f67a5d4136e6e54 tpc-310_turbo.rom 181bf58da7184e128cd419da3109b93344a543cf tpc-310_accessories.rom cdeb0ed8adecaaadb78d5a5364fd603238591685 openMSX-RELEASE_0_12_0/share/machines/Toshiba_FS-TM1.xml000066400000000000000000000070761257557151200224440ustar00rootroot00000000000000 Toshiba FS-TM1 1986 Italian MSX2 based on the Panasonic FS-A1(F) MSX2 5 fs-tm1_s1985.sram 16000 int false true false false V9938 128 50on 21000 fs-tm1.cmos fs-tm1_basic-bios2.rom 7a69e9b9595f3b0060155f4b419c915d4d9d8ca1 64 fs-tm1_msx2sub.rom a4bdbdb20bf9fd3c492a890fbf541bf092eaa8e1 fs-tm1_utility1.rom 30737040d90c136d34dd409fe579bc4cca11c469 fs-tm1_utility2.rom ff6e07d3976b0874164fae680ae028d598752049 openMSX-RELEASE_0_12_0/share/machines/Toshiba_HX-10.xml000066400000000000000000000033371257557151200222660ustar00rootroot00000000000000 Toshiba HX-10 1984? MSX 16000 gb false true false false TMS9929A AY8910 21000 JIS hx-10_basic-bios1.rom 4dad9de7c28b452351cc12910849b51bd9a37ab3 openMSX-RELEASE_0_12_0/share/machines/Toshiba_HX-10D.xml000066400000000000000000000037551257557151200223760ustar00rootroot00000000000000 Toshiba HX-10D MSX 16000 jp_jis false true true false TMS9918A AY8910 21000 JIS hx-10_basic-bios1.rom 302afb5d8be26c758309ca3df611ae69cced2821 openMSX-RELEASE_0_12_0/share/machines/Toshiba_HX-21.xml000066400000000000000000000044321257557151200222650ustar00rootroot00000000000000 Toshiba HX-21 1984 Japanese MSX1 with RAM disk feature and stereo PSG. MSX 16000 jp_jis false false true false TMS9928A AY8910 jis 21000 0 100 -100 HX21-IC2.BIN 01600d06d83072d4e757b29728555efde2c79705 4e2ec9c0294a18a3ab463f182f9333d2af68cdca HX21-IC3.BIN openMSX-RELEASE_0_12_0/share/machines/Toshiba_HX-22.xml000066400000000000000000000046131257557151200222670ustar00rootroot00000000000000 Toshiba HX-22 1984 A Toshiba HX-21 with RS-232C MSX 16000 jp_jis false false true false TMS9928A AY8910 jis 21000 0 100 -100 true HX22-IC2.BIN 302afb5d8be26c758309ca3df611ae69cced2821 4e2ec9c0294a18a3ab463f182f9333d2af68cdca HX22-IC3.BIN openMSX-RELEASE_0_12_0/share/machines/Toshiba_HX-22I.xml000066400000000000000000000043071257557151200224000ustar00rootroot00000000000000 Toshiba HX-22I 1985 MSX 16000 int false true false false TMS9929A AY8910 21000 true HX22IIC2.BIN 829c00c3114f25b3dae5157c0a238b52a3ac37db 3289336b2c12161fd926a7e5ce865770ae7038af HX22IIC3.BIN openMSX-RELEASE_0_12_0/share/machines/Toshiba_HX-51I.xml000066400000000000000000000033061257557151200224000ustar00rootroot00000000000000 Toshiba HX-51I 1985 MSX 16000 int false true false false T7937APAL AY8910 21000 hx51-basic-bios.rom 829c00c3114f25b3dae5157c0a238b52a3ac37db openMSX-RELEASE_0_12_0/share/machines/Victor_HC-95A.xml000066400000000000000000000077071257557151200222410ustar00rootroot00000000000000 Victor HC-95A 1988 A top notch MSX2, but note: superimpose and turbo mode are not yet implemented! MSX2 largest hc-95a_kanjifont.rom db03211b7db46899df41db2b1dfbec972109a967 16000 jp_ansi true false true false V9958 128 50on YM2149 21000 hc-95a.cmos hc-95a_normal_basic-bios2.rom 0081ea0d25bc5cd8d70b60ad8cfdc7307812c0fd hc-95a_normal_msx2sub.rom b8e30d604d319d511cbfbc61e5d8c38fbb9c5a33 hc-95a.rom caeffdd654394726c8c0824b21af7ff51c0b1031 3 256 Victor false hc-95a_normal_disk.rom a7a34671bddb48fa6c74182e2977f9129558ec32 2 0 1 openMSX-RELEASE_0_12_0/share/machines/WIP_Mitsubishi_ML-TS2.xml000066400000000000000000000066351257557151200237210ustar00rootroot00000000000000 Mitsubishi ML-TS2 1987 Mitsubishi Telecom Station (with built in telephone). This hardware description file is a Work-In-Progress! It's certainly not correct. MSX2 largest ml-ts2_kanjifont.rom f4dba5ba267b5dd1a35eb74a6ec4fd4d5ca9a1b4 16000 jp_ansi true false true false V9938 128 ml-ts2_s1985.sram AY8910 50on 21000 ml-ts2.cmos ml-ts2_basic-bios2.rom bb608fe748ee88d45564ad8e3582c2222730b3b5 ml-ts2_msx2sub.rom 0fbd45ef3dd7bb82d4c31f1947884f411f1ca344 ml-ts2_firmware.rom 73d1ce106501b9e8151b4214448537617d82667f MitsubishiMLTS2 64 openMSX-RELEASE_0_12_0/share/machines/Yamaha_AX350II.xml000066400000000000000000000074201257557151200223170ustar00rootroot00000000000000 Yamaha AX350II ? Arabic MSX2 sold by Al Alamiah. MSX2 largest 35195ab67c289a0b470883464df66bc6ea5b00d3 ax350ii_basic-bios2.rom f8cd4c05083decfc098cff077e055a4ae1e91a73 ax350ii_arabic.rom 3a74e73b94d066b0187feb743c5eceddf0c61c2b ax350ii_swp.rom ace202e87337fbc54fea21e22c0b3af0abe6f4ae ax350ii_painter.rom ebb76f9061e875365023523607db610f2eda1d26 ax350ii_msx2sub.rom National 358e69f427390041b5aa28018550a88f996bddb6 ax350ii_disk.rom 1 128 16000 int false true false false V9938 128 YM2149 21000 ax350ii.cmos openMSX-RELEASE_0_12_0/share/machines/Yamaha_AX350IIF.xml000066400000000000000000000100121257557151200224140ustar00rootroot00000000000000 Yamaha AX350IIF ? Arabic MSX2 sold by Al Alamiah, French edition. MSX2 largest ax350iif_s1985.sram b034764e6a8978db60b1d652917f5e24a66a7925 ax350iif_basic-bios2.rom 5077b9c86ce1dc0a22c71782dac7fb3ca2a467e0 ax350iif_arabic.rom 54ff13b58868018fcd43c916b8d7c7200ebdcabe ax350iif_swp.rom ace202e87337fbc54fea21e22c0b3af0abe6f4ae ax350iif_painter.rom 4cbceba8f37f08272b612b6fc212eeaf379da9c3 ax350iif_msx2sub.rom National bd0ad648d728c691fcee08eaaaa95e15e29c0d0d ax350iif_disk.rom 1 128 16000 fr false true false false V9938 128 YM2149 21000 ax350iif.cmos openMSX-RELEASE_0_12_0/share/machines/Yamaha_CX5M.xml000066400000000000000000000037161257557151200220550ustar00rootroot00000000000000 Yamaha CX5M 1984 The famous synthesizer MSX, one of the few models that got to the USA. This is the first version with built in SFG-01. MSX 16000 gb false true false false TMS9929A YM2149 21000 8963fc041975f31dc2ab1019cfdd4967999de53e cx5m_basic-bios1.rom 30000 49a1750c10e407293af6bce27a02e99307ceba12 sfg-01.rom openMSX-RELEASE_0_12_0/share/machines/Yamaha_CX5MII-128.xml000066400000000000000000000056361257557151200226120ustar00rootroot00000000000000 Yamaha CX5MII/128 1985 MSX largest 16000 gb false true false false V9938 16 YM2149 21000 ea4a723cf098be7d7b40f23a7ab831cf5e2190d7 521928db7bc81a8db4cac1e666851e2bf01c8d53 cx5mii-128_basic-bios1.rom c2340313bfda751181e8a5287d60f77bc6a2f3e6 df503d6f5d72a2a355db53c35950348fb7c767bf cx5mii-128_sub.rom 30747a56f45389be76362f7fc55d673f1bff8312 cx5mii-128_fmvoicingprogramii.rom 128 30000 6680d7118d85418813f1db9449bf3e20942b16da sfg-05.rom openMSX-RELEASE_0_12_0/share/machines/Yamaha_CX5MII.xml000066400000000000000000000054331257557151200222750ustar00rootroot00000000000000 Yamaha CX5MII 1985 MSX largest 16000 gb false true false false V9938 16 YM2149 21000 0dde59e8d98fa524961cd37b0e100dbfb42cf576 cx5mii_basic-bios1.rom 0ce800666c0d66bc2aa0b73a16f228289b9198be cx5mii_sub.rom 7b1798561ee1844a7d6432924fbee9b4fc591c19 cx5mii_fmvoicingprogramii.rom 64 30000 6680d7118d85418813f1db9449bf3e20942b16da sfg-05.rom openMSX-RELEASE_0_12_0/share/machines/Yamaha_YIS-503F.xml000066400000000000000000000027241257557151200224160ustar00rootroot00000000000000 Yamaha YIS-503F 1984? MSX 16000 gb false true false false TMS9929A YM2149 21000 yis-503f_basic-bios1.rom 8963fc041975f31dc2ab1019cfdd4967999de53e openMSX-RELEASE_0_12_0/share/machines/Yamaha_YIS-503IIIR.xml000066400000000000000000000062341257557151200227650ustar00rootroot00000000000000 Yamaha YIS-503IIIR 1986 Russian MSX2 for use in classroom network (as student). MSX2 largest 0f851ee7a1cf79819f61cc89e9948ee72a413802 yis-503iiir_basic-bios2.rom 03bf6d2ac86f5c9ab618e155442787c700f99fed yis-503iiir_msx2sub.rom f4f7a54cdf5a9dd6c59f7cb219c2c5eb0a00fa8a yis-503iiir_cpm.rom 128 16000 ru false true false false V9938 128 YM2149 21000 yis-503iiir.cmos openMSX-RELEASE_0_12_0/share/machines/Yamaha_YIS-503IIR.xml000066400000000000000000000033341257557151200226520ustar00rootroot00000000000000 Yamaha YIS-503IIR ? Russian MSX MSX 807a823d4cac527c9f3758ed412aa2584c7f6d37 yis-503iir_basic-bios.rom 16000 ru false true false false V9938 16 YM2149 21000 openMSX-RELEASE_0_12_0/share/machines/Yamaha_YIS-805-128R2.xml000066400000000000000000000071511257557151200230300ustar00rootroot00000000000000 Yamaha YIS-805/128R2 1986 Russian MSX2 for use in classroom network (as teacher). MSX2 largest yis805-128r2_s1985.sram 0f851ee7a1cf79819f61cc89e9948ee72a413802 yis805-128r2_basic-bios2.rom 7fd2a28c4fdaeb140f3c8c8fb90271b1472c97b9 yis805-128r2_painter.rom 03bf6d2ac86f5c9ab618e155442787c700f99fed yis805-128r2_msx2sub.rom National false 3a481c7b7e4f0406a55952bc5b9f8cf9d699376c yis805-128r2_disk.rom 2 128 16000 ru true true false false V9938 128 YM2149 21000 yis805-128r2.cmos openMSX-RELEASE_0_12_0/share/machines/Yashica_YC-64.xml000066400000000000000000000027161257557151200222630ustar00rootroot00000000000000 Yashica YC-64 1984? MSX 16000 gb false true false false TMS9929A YM2149 21000 yc-64_basic-bios1.rom 8963fc041975f31dc2ab1019cfdd4967999de53e openMSX-RELEASE_0_12_0/share/machines/acid-tests/000077500000000000000000000000001257557151200213705ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/machines/acid-tests/Acid2Test_basic-bios2p.lpt.ips000066400000000000000000000000401257557151200270140ustar00rootroot00000000000000PATCH=EOFopenMSX-RELEASE_0_12_0/share/machines/acid-tests/Acid2Test_basic-bios2p.ppi.ips000066400000000000000000000006711257557151200270170ustar00rootroot00000000000000PATCH(G?(((((.(J(Q ((o?(k(((("()(:(((G?(()+(>P*7(s((((?(((e**)*=*) **) * *)D+t++ **)+*((++X+`+>+Q(Q(Q(ywx({g({(O({({({({(( (^(sz((<((?((G?((EOFopenMSX-RELEASE_0_12_0/share/machines/acid-tests/Acid2Test_basic-bios2p.psg.ips000066400000000000000000000001671257557151200270200ustar00rootroot00000000000000PATCH {!> "  "/!! !!> "!| "!k "" ")"9"EOFopenMSX-RELEASE_0_12_0/share/machines/acid-tests/Acid2Test_basic-bios2p.vdp.ips000066400000000000000000000777401257557151200270330ustar00rootroot00000000000000PATCHÃ&Æ&%E 3É&< Òà ;=ÀñÛ>GOW_gow òúj  úÿ###d×: ) ? ÞIâicüÇÐVÐôåQXÒ_C Ô=z   c  ]ÿÕÛìé 8óijn6! gۨG?Ө:/O_2xӨ{2͘x?Өy2xӨy2SۨẀ{͌ U;0xTS5ۨWÅ͌nU}?Ө|2}Өۨo?Ө:/g2}Өx%x?Ө}2xӨG{x͛W**~#^#V#SۨÌ |ʶxO}! wWx?Ө{2xӨs0S@ۨӨxO}! wy|_>]_/OUWGzOWۨG?ӨzW>UW{/g:/o2xӨ!O!~!CD %~y~~8p####y O8O> y8r<<<>ӻ>ӫӨ>PӪOӨ!!6~ w~< 2!~/w/w , %T.$}|0 :/oۨgx( :/@8ۨP0!9|Ө}2yOۨ?Ө0 :/?2!~/w/w , %|0.$}|0 :/oۨgx(:/@0ۨ@0a{:!~6(7 !~6(̈́ ]I0 !j͞:&@3{c:j*k|*#|7*"۪Ӫ۩۪=Ӫ۩*":2>27>>> _ @2>Ӑ!?qw#u!. !<8>7 x:@:Gy :0 8!-xӉyӉx! @!Õ!!!!!!!!yxA<:8| g>oWG:x:Wď|?@:Wď|?|Ӊ>Ӊ}ӉӉo&))) ())*&o&))*(:>> *$:* ӈ# y :0:  y ͇= ͳӈ!Õ:0: ӈ y ͳ͇= &o)))* @:#!Õӈ͘ Ӊ>Ӊ}Ӊ|?@ӉɯӉ>Ӊ}Ӊ|?Ӊ:=͎:󇇇!* ͩ8ͳy(ӈ J_|?g +|@8ůO>@G{8 M|@G!{8:󇇇!:GwN :ͽ!Õ!ÕͶd8(ӑӐ=Ӑɯ2> 7ͻې?>>Y}|!w( @ 8@7 wͤN 0͋ < > :=2aO !~ y 8!* ͍ ͡ &; . !  ## # N#F*͊  e       jEK J l L M Y A B C D H x y >>>>2 6y! v =(=(%=w:(6 Gy <xwy4( =(wy4 <2=<2::ͩN *̓ 2o&)))*$T !:(~/w+ k Í ::ͮN *:OÍ :$Ͳ :g%>-͡ & 8,"-> < := .& !!Õ !%Õͩ  Í & > ӈ$:0 &,͸K * :ͳ!Õ2:͖ :O=͖ :O!̀͘ Oyӈ&%-\&T)))MD))::(" )8 )R */_*"&~> :!O۪Ӫ۩*d}|}.:8A&(%= :8 2<>2)͚y ͟2 !m*+| !*"*#":?O1<8!5 l60>0O!qO!p!|!v!y!s2 !5 6! 6N ۪O !yӪ۩w #:(: !@ÿ + >2 !Oqĉ #*:N 0:!!? b > O; 8 g0~3465:<=FAB!"#$%&'()UU -^\@[;:],./=~|`{+*}<>?_-;:,./=+*<>?  *+/0123456789-,.y! 00:0 }:ƒy#^#V#0G:x8 ! ~U:_! ~#foy G:/ @! :_~#foY~U:8yOY!~ ))))/U*#|(!~~w:<2:> E_! ~6!F(ˆ:<6 % !~/w/> (<ӫ:>0<28B!9Û::>2ӫ> = >(<ӫ::( !8 !M!8! U:~ @ɱܦݻ½ζ÷ɧڢӰįҭζ÷̱Ͱڹʷ׾Ķû¦̧Ͱޢڹʷ׾Ķûݯ! ~>UUOGABCDEFMNWI_][ZTXUSJV^KPRLYQ\H#}!j 7 j (̈́ !~ 6*N"y8 !Ӡ{ӡ>Ӡۢ!}Õ>ӠۢG>(ӡG +V+^s#rzx2>([WOzw+w W _x |@(W_> 2?:>ͫ8 =!)_~!9G>Lӡ ۪Ӫ۩=b] <G7G0 L>ӡӡ>Ӡۢ( • y8 !Õ 8==::"f/@O>濱ӡ 558(#8##8x0/<0y0/<0z2{2| 5U5]g^Q˂˒p gpp|>5Wp (ˢ˪*f}/W>Ӡۢӡɧ > >ӫ۪E!w# w#  color auto goto list run color 15,4,7 load"cont list.  run ۨӨͧͬ:**:: 2wg:o:o: o:|//u}ۺ !> ӻӻx<#+++#O~#fo sq6 y#<++###O~#fo ~q<>O!p ~p#p#p#w#s#rx<#Gyo&F#N#~GO* ͩ8 !Õ0b  (_ 8Xͫ:2*Kͥ0B@Q58Xʹ8 :( 8 827 2:( 82|0!:8+|0!:88+: ==;;xDM::03(9QyO!6 ~2,{GzO{O* "*ɀ@ `i"*{2,!Õ:,**2,"*ͩ8 !Õ QG Gx(͚2:( (? ?8G:0(:,**ͦ!Õ(:,>Ӊ>ӉӉ>Ӊ8>$Ӊ>Ӊ}Ӌ|ӋӋ:Ӌ>,Ӊ>Ӊ:ӋӋ>PӋ( !ÕQ0d}> ( !ÕQ0C8( !ÕQ0(}> ( !ÕQ0 "*2,( !Õ***8}< ?( !Õ**#}A( !Õ***0} 7( !Õ**}+ "*Q8 0E0=ͦ}O|}?G( :  ! ~=/G=GO _W:((x(x/x x/:V- * * (?:2!M( !Õx2f2i:G_ z50Q"B2D58 _(Q*B:DX"g:fCX!M( !Õ2i:Gq8_(#V[gC:iO!<2i x > ӫɷ>ӫ!+| : (GM? x d*}oP@9@@d*PMM*-Q> ӫ%Y> ӫ>ӫ>Ӡ!WQ4y080/<0+| !EU4  |W)|W2z0x2:Wdۢ0dۢ8Ax8.??y#-dz:Gۢ{/_ yyd ( ۢ%{/_ dۢ8#% (!Hlÿ:(_: I > c: ( 8 8:<2:( #80 8 8ͺ!sÿ>  ~B~B~BT(D4R8T(|8T|~BB~BB~@~H<(~(D<$L(\lP| |D|D| p~"D|(((NB$$B|(DTT0 PPPPPPPP xp(  @@@` @ @@@ @  @ p p @x`` @pȈp ` p`p0p0P0@p ppppx` @0``0`00`p php PHHpHH0HH0PHHHPppp p8`بȨppph𠐈ppp pPP ؈P Pp  @p@@@@@pP p p pp P@ pxxȈȰpphhpp( hhp ` p0`@@HP`PH` pШȈppȰhhȀx@@@@H0hP PP Php @ @   @8|8|l|888|8||8xxxx Lp@< x xd@pp0p ` $xhXd Ԕ ptx8 | |d@88D0p `$ |D@DJH0 x @ Hx|xp8H8DDDH@<D( @@<``8 @ 00 @`@ `  @ ` @HP@@p |(0 @ `  0P (((H xH @xP PPP  P@HP@@8H @xHx  @ p @@@`PH@@ @p P 0  @ HHHHx @@ P ` @P Px @HHP@@pp HHHHH PPPPX@@@HHP` H`` N@ xx  $8`|,DD&NHH|ҶL@LbNN8TDL&DDH0  R PLptp~H "`bHH|D $H\j$Ғ xp`Ą8D0x8DL8 ,$d&x x8@@bBD `R##\A: .>?> *- ,"2!7$ v #0!]? > #:= !( 8 ~$6>>͋ 2> a%$#&%%%$ Z$%P%l&:(&͋ ^-,̓ ѧ( 0 (O>y@($:0 &( (> > > ߯27, (-$2^&ͲI8: !~w-$,$͋ * ̓ K͍ :$z0 (7y :(> , 8 !5 4. -> -,&:  :>*͋ %z%$-( :g ":($̓ %͍ $$:< % ͍ > ,&̓ ͍ ͋ l&"͋  &,> 2-$͋ *-, (:g$%(̓ (͸ ͋ 4&$&(8$&(0͋ 4&(04&(8͸ *͸ _:W *ͩ *h&̓ 0?:A?[a?{??><-( (,:&*v|ÌUfF×U!G~w .!G~G/0/<Nwy#22O!Hͣ'!G:&:O2Y'<'~4g@'Ek'!~ # }. ͗' >(HGy( !Fwg@!~P+7'w+~4g@#6!N'+!O~P 4'+~w!>'w+!ůo+ɷ'/O#Gw#yGg#!>q.:G}.G!O˸~G6 g@x@ȱ+wE!#w#(͊(!=a'͊(N  ! 8(a'!.6 +~#w&!'w+ :GX@G!~}.O˸~G@6(xg@xAw(6+w!N +""x2!>**: Gկ'+:0Ga'04yO8X!͚'x 6(#0.! #8w#&x:_=2(}.y8w#w(!E.Ã(!c-;,:2!#-2,͍.!c-;,:,,0M,,͌&:@):%)u)M,!-\,͌&)!-P,͌&!-È,,͓)o,ͬ),:GŸ(g@:,A<*M,!-\,͟(<*M,!C-\,Ì&!K-G,l*,!S-,,o,!S-;,!-2,,͟(l*![-,,!0.È,q.ZGZG!~6A!+-G,*2,,͟(,͚&!-;,M,,:= Y,! -;,,͊/}(|m+O0,!-\,g@":0M,,͌&!-G,(8!-2,,!-͈,o,!k-͚,,,,͌&!>g,,,͚&!>P,͟(8(!+-;,:!Nwg@q.!W(.g,!>Wj,!,P,!,\,E.(!X.!W6\,!6@2&-!W>P,Ú&P,Ì&!P,'P,ß(P,\/!Gj,!G/!W.",,Y,*͍.!.",*͚,,'"8,*~#!g,\,=!;,P,͚&!N!>F+N+= !G!>q#p#= 8 B!!$T@dQ7#X@CBH$@PA@%A1b'v`@8PA#XP@Apyc&yI@&yI$1A2V@R5wU0@T0ACt#`1D' 1i@DDc5W@XB1#`uCrqDQx QbqC32&AbP6Qyh#p$As#A!x5VBGRV8sdft CB)AWP##i!V) A8(8Wq DGHBBfsUvpXYh2B`RI'UA4$AbSRi9?u0qIH42$pP@@)(WUH@g333331`A:/<ɯ2͡.+2m@!~w͡.ogÙ/m@q.*||**."`i"**MDN#F#^#V#^#V#N#F#G:cGw#w+!G.!G.:cyq.!w.q.y!y;/y#x#{#zz|x. }y.Gq.!w.q.O!y #\/w.*m@]0g@">2c2v!/!/m@S0R7#xGA'*|!2S0!""zʧf!!00>^#V#DM<8`i (>@2 y0񇇇ww#z(Í.m@/!""|2>>Þ/m@!0!~FA0)`)0 `= |G!2y|Gz//ͱ./N2|ʙ/2DM!>)8)0 8= |1x2(/ͱ./\2x//Í.|X@2!2DM!> 03370j= õ1|G2|/OoygÙ/*!2|/1g{o͜/.̀2B0Ú&͍.̀2B0'*"*"̀2B0ß("I`i"G!"K"M=+`h͙/~&ʸN-(+(+چ3.O3e(E l( L(q(Q(0~%b3#p3!q3d(D )w3GOJ0{ 0 0_ _43:( z@2L3̆.0 !2͢/<'g@ 0 w3: Wó2!2!/w3̲/:00“3ʓ3ʳ2z w3x Wó2!6z8<O Ozy0w!?xfwf͙/2!6 _7(6+͡.A46-͆.#60:W:c44ҡ46!F :_ ( x* { Aq(E(D( 0(,(. +60{(+6\{+pR7P:?80͎660.ͳ6+~0(..(6E#6+46-/</ 0:#p#w#6!#zf55͆6zf66{̕2=f6]4(p#6!#:~ (*(+<5-+\0 #0 ++w((6%5R7P:O8 %4!6%q.Ģ75_xf6z6ͳ6t6Ġ65_yē25{7{_x5f6z6f6yͣ6Of6GOͳ6 *=f6P5/R7Xq.Ģ7yē2OzWO(6{7 '!4/6/<͠6N#F#*/}o|g0"p#= ͠6w'd 9TO|g}oyO0:8} !!2:0!-:\,!6 /<_>ʜ7R7' #xGA'ʹ7R7:@2R7~ ~ +x̀2B0,o,,:GC8g:M8,98<"O0,9O0*Z8:GY,!g,!-\,|&8OoygÔ8O0Y,o,r*,'J+| !W8z |0X@!Ù/"|!2DM!xGyO0 9 Lx(cbk 9(!g,/B0xGyO0!;,x(4,!\,;,g,,:0M,/B0!g,Y,:c /B0: *}g@M,!-\,ß(͓1:cY,,0,\/7]0c$E'e[HlK^KHGGIcG!H]Hc$Jd.RbH@P#T$do?p@J)RyQ]H8d9d>dwdI]ISIhTGGG!GKjR|[wXwl]k^k/lH|M|k*l[yn]YsWWsy{Hz7{Z{UylxK~snn|| |%|*|/|4|fwahhh.0.*+)r*J+))*i@Ogfh hhT{HheeOe//:00@yLyZyiy9|9mf|k|p|%mmmW|\|a|r::::.;O;i;{;;;;;; <<+<]<^<<<<<= =$=%=UTϩNBTSTTRASSAVLOAEEINALLOSŴOPONԙLEAҒLOAěSAVŚSRLIINSNDB V(V)V*O HRIRCLżOLOҽLӟMELETŨATI͆EFSTҫEFINԬEFSNǭEFDB̮SKOEƗSKISK&RA׾LSšNāRASťRROҦRRX O+QO҂IELıILEӷRI!PO'OTωO TωOSUEԲEXNPUԅƋNSTNNMNKEYPILEPRINԝLISԞPOEԈOCATINůOAĵSEԸISԓFILEӻO O,EEFTO-OTOERGŶOKI.KS/KD0IDAEXԃAMEהOPEΰUԜΕCTFRINԑUԳOKŘOEESERESEOINAINԿD$A%LAETURΎEAćUΊESTORŌE͏ESUMŧSEԹIGHTNENUͪCREEPRITTOАWAФEAVźPCTEGQI TRTRINGPACEOUNTIC"TRI#HERO΢ROFƣABIMA SINSAARPTDPOKPEEIDTȠAIԖOyy||PF<2(z{:0/X0/&&'(7/N2W2\2g27!/r1g11M?8M/NEXT without FORSyntax errorRETURN without GOSUBOut of DATAIllegal function callOverflowOut of memoryUndefined line numberSubscript out of rangeRedimensioned arrayDivision by zeroIllegal directType mismatchOut of string spaceString too longString formula too complexCan't CONTINUEUndefined user functionDevice I/O errorVerify errorNo RESUMERESUME without errorUnprintable errorMissing operandLine buffer overflowFIELD overflowInternal errorBad file numberFile not foundFile already openInput past endBad file nameDirect statement in fileSequential I/O onlyFile not OPEN in Ok Break!9~#N#F#`iz( 9TDMxO/T,R @y @+(,RWͽx(*|<(: #d*"   ͱ͇y*|( :w!"*"|<("@A*b{K2*"*|<(""*|!( 5 FwY#s!u={<0208>/_]H# *~? !u=xf>|< 4>s{m{ !"!":(*4͕B>*8> 2J ͮ0 24Ats<=(iG0JU@E:(*  #z(~ #ͲB"82Hm׷:(7S*8 "82͕B8 HM 87TT(1!"* Pb"t##s#r# w# y ͡y*d"͚b*"d4A*vbk~####~( 0 8jF#s#r( _G( _GU@*vDM~#+##~#fo`i~#fo??2e2d ;~ !@}O|G!"C (:d~(=# ~>D:(J >2d2eU ~|((~# ~>D#B ~(#+?>ʣC~_ʣC&=ͩNͨdD%!>:AO ^#V#ͩNOD# $fC*C>D2eDBC+/CO( ᡊڍ>2eD(_ )D#ͩNBC #~( (:(<((80´DD>D!C~.( :ҢD0ڢD:e~B(.B>DiGE}D|DB~͙2E:c *|> }e. 0D!:c !~D#= B%=D E&BͪNH( O(>&B> > D͸NID>: yo@4+=2eͩNC~ 0 ( (> :e<(=C+~ ( ( (#>d2̀H[H"!9?  +V+^##* " ^b**m@dL0E͊/~Rͫ.]:0!9/~-> dL:0q.DM!9.-Ͳ/.A9~> eLͲ/.q.ŷ >OG*3>sͺ:ĉc"~:(%U@#~#9@#^#V":( >[4>]FC_ʧUցڀHXҭQO!.9 N#FH#~: (0l 8a :h(4#2h000 ~#"f&o"j>2i!F~##"f+fF*f<2ijG."f!F fF0?<=:h0 8*j ###^#V62:i2c *j"!j/ͧdU@AOG ͧdAGx3*>3+"#s FiG:h U@*f]H#*ܘBԕB0 > 2̓U`io@M"?(+"o@|(~>c!F*+G`i">:yHG+׷ȸ#"(<(֌ Wͤ^S:cdLG:cx(zQ:c  .*#^#V!8*0!8!y8 >gfg. %ωiGz( ͓BPYHS:{:_Ö@x81ύŹU@iGz( ͓BPYH\x+,<R~G(ω+K xFFjG,: 22d@<2~(iGz( G22<**"~ #####2[HRZGo@ (_G(,[(iGU@|ZG"2"4AdL~,fF(+͡.(G FF*j[H  >2Wm+(sJʱ`JJ,(O;JdL(;%45f6 *4RJ #*:(: :G:a=81s(s{f{f)JW*d J~ :(::G:a(sJ0/-R)+(*d J~ :J::a/0 .J\2go"d§Xυ#ʏm{Kͤ^X0ͱc8f>ÒH?Redo from start a:O@!:Kxf*Um!]ÛK#(K">6f;{fʹc#~+ZH6,*2,ͤ^~,(:@L>?ʹc#~+ZHJƒm "WG"( :W(:,+9f!LÓHKÙ2+(,MK+ªK:cJ ~!/LxfJ?Extra ignored [H #~#o@#^#VS K(+^bfM"*~"8__ :c{ʇg !;=xVvLzkQ8Pz(J!:cm@*L*L**KG"M*gL80WU@"͊/xOxdd!WO³L*ey2d:c (ʝM0+Wx("z(Dx(Rzm@0T!i= N#F*:0 /"".:0!Q=:dog~#fox /2c("Ͳ/!]=ͱ./.""/ͱ./e2j@ڙ2ͨdқN ڸFp|C| #(# Rmj]_)|ZG͙/OhGs)hʇl y@PbL)}gL*͆.ͤ^"/~a{_&iGͪNB(O(HU@#~ͪN:8A800 )g@o͙/#~ցOy0bL,X0*R͇N} 8 u0:0N9z N#fi-++<͚.ZgL͊/}/o|/g"vLx͊/z:2{1OF {o|P {o|< {o|2 {/o|/}//o|//ɷR62::aogÙ/O͇N^#V!2:cg!0 8:jO! O/Ts#r(͡Q͓Qs#r~([Hͤ^~)[H,͡Q:c"~#fo|a@~(P"*(>2ͤ^:cdL""zQ^b!9/:c*~)(,*,2N(82c!9/!9.:cG:NOdZG}!P O͎QPÞH**)>":O^bO/2Oé^~ #~#niʿwʱwU@R̈́!]_Ө~/Ow{?WxӨ{ӨywxӨ2> {0/<2dL͊/zdLRZG+{>2͉yB!"N#F#xAJ̺N#F#A"4~ (> ̈́R!^{R(s~gs#^2d#~ 8% aS" :d2d>": :d82d>:R:d8.0>~!R M E R :%~ÏR:d2d:d8~RS~<~ #~#  ͎!q:G@ #T]~(#:S~ y[ ~#_ʧfNSÓR+F~S 7 "7*j%4:hO ( H >&{:i8!(#~ #~#( :i8  .(D(E {(*fÓRyBT͕B0T]ZG!?{!7B* `i"""9T~O/T,RdL9T/͓q.Ͳ/E2v!/EeS`!/g@eS`N2 PX(&,( _GBK(,_G(,iGU@zZG͕B͕B`iZG ZG!ZG^#Vz(~#+ T^#Vz(#s#r :A2*v+#~##^#V׷(O:y(V͘   qGz ' qG͕B > 8<#s!ZUxf4 4+Undefined line qG###N#F>!UU*f+p+q+w~#U@fF:c07~( :( ((#x( (@ U@*~O.~+8x8ͷͩN#@@ kn*~O.~>8O>@ 80ѧ?-~O.~2SVdLg.AJSx(z(2;| :X WÔt"@Zͽ:J( *K*I*Myx2S(0 8ZZ2JZ]T(++|8SM"K2I:S/2J*QZ*B:D*O[QR(98Z(/(*B:DO:SGZ{X++|8#+| :S/Z Z:TO:U:SGO^b,SO"Q|y2U*B:D"B2D/y2T{X͜W,R"͙/Ͳ/@pq\2͊/"6252AMX]]>0=:5O2528S?"3+ &|(2>2A*,dLͲ/q.ZGZGc] <2Ag2C%`\2͊/"1S=*)ͽ{8#ʹYʹY\ZDM*=#|8 `i)+R "=`i\:AS9!";["E[SG [H\*6";[9R"9*E{X"E [>[;*6)";*9*?(0*3( 0 :8 $:50:50\\:8(\0 =[E ["E*G"G [J\*"*"@>!w0 {02ZGzZG{2:!= z0ʹYʹYzW0{ZGZG+,^2bNͧ͢dU@G8ͨd8 G8ͨd0&0^%$!#y_!V+z2c:=__~(ʺ_3ʺ_2:2(<*"o :c ʤ_&:#_: :(2*"*ͤ^W_`_(N(1:cO* Pb"`i"+6 s#s#r2go" !?"*bWUG[(-^#~# :c ~#^#V# :b^@DMʗ2}` o@:cw#_ZGq#p#O^b##"q#:by 0q#p#J1= BKubgb"+6 W*^) ++s#r#80GO~#^#V#`J1=DM :cDM)8)()` *eLX0;*:( 2&Fb߯_WFbW~#! b#(<@ ba+>(+~#.(@&( \(* #x8~\> #W(H~#.(#(, z@W~#>.`# (%~##(aT]^###xG#z+ x(~-( >W(LdLCxZGz&4xf+7( 2;(,~#_~#fox`Fb¿`(sgJ>Fb(dLX0*Axhh{f*aG> az>+gb~ +* >>o>g89SB*t++"o@*v9d22w#w#"*v+"nc!6#$,2og""*r"c*""l:| 2|*t++"##!z"xsJgo"2"N""2*~w( &~6~w(&~w (:<2~w :82!Lw#w#w#! w#2:*|<(!L~(####^#V++z(Xc1c^bG*v(iG͕B`iH+"¥w< 2l"!z"x!*}<( "*"s#s!?@A*|o@[S>2ͤ^!.*,ͤ^Gm@* ..ZG>2ͤ^2`i * `i"~,~A[?ʡbVG+*JDM*r(*,/T+U@|ZGZG:_ =z+}_|Wub* ub"t`i"J"r͡b:_k~*F}_|WĤ^"?[@~#~#ke=  2c/0N.N2..!/) N#F#^#Vi`r1:cg@r+s^#V#M/.("i`E͗&/.\/"*~,F*eg~#N#FgW^#N#F{zد< #(?y.7 "77%45fg%h~#͎fN#F*fog>͎f!w#s#r+"P#~ (( "fF#y*f>*x">2c."x~o@#5fg. 1s*t*/O #8"#o@f*r"!*!z[xfBg!L"*"*[(~### Cg_*^#Vz*("##^#V#"[cg~#.  "N #*(7gů#^#V#DM*`i`i|+F+N+n& PY+DM*Sbq#p`i+ùf*MX0~*o@'fgg*ͿgͿg!sLTf~#N#Fo,- X0*gPYN* G "͝*x+F+N+"xOgW~OhZG#^#V%fR*sTf(R,dL)(RhMhR> {'fG(*w#hO>~8x͎f#F#fh DM*foggTfh~hZGiih=O~GChO_#~#foF"x2r+͙2!"p)CbL>(RZG,dLX0,*dL)g2OgG=O>x~#F#fh GN#^#V  ( #ѯx<#(ͤ^X0#^#V*8*v0 f.,RZG~i_LgG!2y~ZG<8yH #^#fk GN#~#foyw# )(,R)*!9OgͶf[t*OdLg~(.#^#fk_of (5~ 8.(# W:f<(kn#z GjGj(> >  Ro:_}n&)*`~#fo:|<~~ 0NÀn~7+#fFRmjwn"dSJj~ (( APPρϳAS~#fFR}nX+_U@{mjnnz ]ڀn r>͏o :|lmj("d8bÀn>͏olwgo"d67jg( ~, R7j*d 2|2f~kćbͪj4Ajl+7(,Az 8j8+.Rqknvkn*d~ kŷ=k~,~#fFRk7$k:_:|$k:_>2{ZG͞j8̀kn>Ïobl0ͅknO>͏or*d!~ bl0͊Àn>͏os$(*d!"dR~, ͞jʰlƒn~)}ZG'f(͟ͽw# ͏"dTfqlڃnllɯw#*d ͔jj( > 8!͙"͔jj(> 8͔͞jjwn>ڏoͣÀn͔jj>8ͨJ@F$kqn#R,{ͪj~((  y}n~ců$kK , ZJUmͤ^X0{HW_qlڃn  " {,>" W_ql8G!^Oz"y(* (P O{,yanql8$ ( { (,> ( ( ( (anql0"( +ql8& (,( ql8 (Obl0ͭÀn>͏o6!]{ (8fܙ2ԙ2ɷw#Bn8695;2437:2|2o@j, o", o"}+( , o"zoknjկ2+(,R 2(, oBKzpkn:( $k!l*$kdL9TͲ~:8S~#( :(~#$oZ>ͷͼknz=OGvoͩNG# coѷɷYo0H UCASLPTCRTGRPqrqq~JV>_!o^#Vo&^#V͘p+(,-z>%q*"}*v>q>%qrp*}p*p~r0#}r|rrorg͸pr p p p"rw(#n֑(/#͌p͸p2܇b:2T*v]q "!?{*v7B#*qpo@+ !fdLh++F!fw# (A6 #r r !qrw#f  f #!p q!q qFound:Skip :[zxf!q~#(sr r!f~#rTr*}r .r- r/W r_gb{s~#  qqnqZGZGZGZGZGZG:ZGyÍqqnqZGZGZGZGZGZGrkn"dsyâqrn*r?rZGZGmrZG|r w2rkn( >%q͸p~(>͋r/ŕr( 6# /r2y͋rr~r#!;r͛r rrw#%GO ~7?27?r!wÚ.!q ~O6_ ~4###O s ~4###ɶqqnrZGZGZGZGZGZGyM~6337?w7{kn0$0 0 òsɯ2:> s> s2ͥòs:a> > J(:(22a͜( ͟%f_!h!?">2c > 1s> !^ql8w ( ( (#w!] :|2|{m:fA͚bFo@( _(ϕ>{RZG,R ˳Ó !.u"V>25!9"6dLg.{ sQHPs#r#q#T] s#rx0+(,x28ux8+U@28G!utxP~t2;#^#V#S<^#V#.$S*6+Sb`i>2XâV:; u:8P:;w#[ZG.$S Sb+p+q<Mt:(:58!@4͙!5~wLt͐:5<25:8(:8A>vB>vC>vD>vE>vF>vG>v͞uֆuӾu!vuuuuXW ] <  s k8>8P L.S>@w{8/<_(8.S~#fos#r++>@w{0_8{A0.S (wZG8x{ 8.8{ 0.8 ۳(A0!.S##~296++{0 (a0{X 0 OsvAy@OV(#(+(-( W xC(F  .S^!_u N!nu {^#V=( :ZG_W.SNV(/W>@8㯲 ߳(KB#^J1/ /!Tw;.͟(͊/T]V(. :Z>(g W8 r#s##^{w>w# {@( #^#Vr#s# z(r#s .Sqy#w+z :9 wF#^#u!uڎtâV@EÓyx5l[,( Ry =>>5!j$!mERϔ!>|+!pwR= ZG~wF!!Iw~wc+c1cU@  ((7 E Rϔ/TzZG""+Go&M s#r >! ~#ͫ8(~#_ͫ(>{ ( 0> (s (w((R= ZGo&)))) ,dLgF#^#Vx( ZGw# (p# q*62:!/TS>|:?8y)y>0=͚.(>R0 RZGÚ.R= 0<OR0G>{x8!U_2|Ӊ>Ӊʯz!_bwWB#":0 ZG}2b!Y_e8!m_ͫWWe8!u_͜WêXe8!i_͜WYe8!_BYd+dR2R=ZG!( $«w:ZG͠z_Lg#^#V͊O++=~8 kO\͟z͊O'f*YTf$>|{̈́ZG|ZGR ZG͇,,(+͜W{Mxy0 #M##JM++++,,(RZG###J怳M++++,R͊{0@ZG##M!a_=G!e_!~O!i_!9|Ө}2yw !6O!w+ۨOӨ:/o>@Ө:/g>Ө:/_>Ө:/WyӨ""Vv|?!m_!~#fo62!q !!_!u_O͊/@k(R8a){X|SNID}? :50+!&&!+05: ?DIZG1v/!6!"J]}"H!'>2`2|>,2]>:2*" !"L"t "r>2k~b*Hw#"v͇b;1!y_!_:ڶz!Q_!_ZGb*Hw#"v͇b)}A|{! ~ w!~xf**t}o|g 4!xf!~/w/w , |=g.$!O#!@~!AB *~y~~~p#?|8#y }O8O> y8!@~8#*~$*0"*#"v|2͚bF~Zy W#>@G&Ox! O~ϷRU@ZG"lk~ͧbF*J=r~*tDM*r}o|g DM* ub2_kb"`++"r}o|g"t++:_o,&) s#r#6 =~ "bMSX systemversion 3.0 MSX BASIC Copyright 1988 by Microsoft Bytes freeӨ^ӨszӨӨ͘ӨZGZGZGZGZGZGZGZGZGZG' 8 88Y2S\&-%-S\&-:^sۨ?ӨyWۨG?Ө:/OW2xӨ{0}EOFopenMSX-RELEASE_0_12_0/share/machines/acid-tests/Acid2Test_disk.ppi.ips000066400000000000000000000001071257557151200254660ustar00rootroot00000000000000PATCH(#($($d($(($(%](%h(}(%t(EOFopenMSX-RELEASE_0_12_0/share/machines/acid-tests/Acid2Test_disk.rtc.ips000066400000000000000000000001771257557151200254750ustar00rootroot00000000000000PATCH4> 5455O554> 5> 4>5> 45 y45 45 y45=4>5Q455u445EOFopenMSX-RELEASE_0_12_0/share/machines/acid-tests/Acid2Test_msx2psub-fmbasic.opll.ips000066400000000000000000000000471257557151200301020ustar00rootroot00000000000000PATCHAA{A\{mxEOFopenMSX-RELEASE_0_12_0/share/machines/acid-tests/Acid2Test_msx2psub-fmbasic.ppi.ips000066400000000000000000000002641257557151200277250ustar00rootroot00000000000000PATCH(((((!(;(B(l((((((( (G?(1((; (;(;)(;1(ywx(;E(>{+>+B(UY(EOFopenMSX-RELEASE_0_12_0/share/machines/acid-tests/Acid2Test_msx2psub-fmbasic.psg.ips000066400000000000000000000000761257557151200277270ustar00rootroot00000000000000PATCH {!5 !5' "> ">!d {!EOFopenMSX-RELEASE_0_12_0/share/machines/acid-tests/Acid2Test_msx2psub-fmbasic.rtc.ips000066400000000000000000000001471257557151200277250ustar00rootroot00000000000000PATCH4545?455M45b455554{45 5> 45EOFopenMSX-RELEASE_0_12_0/share/machines/acid-tests/Acid2Test_msx2psub-fmbasic.vdp.ips000066400000000000000000000722531257557151200277350ustar00rootroot00000000000000PATCH?,CDZ1X6ùÇ'b|}$(F!8*͝'M 4Q&3(L(())%)Û)üv>goáöQ]àR_)u*S u ê ( @ v mU\ g z ñøÈ+Ç>L<A  u }Ë* "7"{""^#q#S?ì#2.û.X.!0-1//`4kO x?Ө:/O_2xӨ{2͘x?Өy2xӨy2ÖÞ ۨẀ{(ۨWÅx?Ө}2xӨ~#^#V#:ۨvÌG: zx?Ө:/O2_xӨ{2x!Ìx?Өy2xӨy2ۨӨO}! wy|_>_/OUWGzOWۨG?ӨzW>UW{/g:/o2xӨCopyright 1988 by ASCII corporation!Z,22͎2g."!;":2!ˎ: G 2 !6>,2>&2>2>!پ #ˁ ˉ(ˑww(˙(ˡӁӁӁ>@Ӂہ? ˩ۻ(˱y !ͅ` (͗ͅ`iQ<< !<ͅ`W`_i'8 ͪv,>o'}og@ ͪ A!{ >J > Ӵ~# +ӵ  >JJ> J>͇ >J7!~ (!~ (>>>5ہOɯͅ`OyO:G ͈i_`22`2`2`O G0x2 2I!(! 222O J  @ ۨO! O####~!Nyy(yOa(#x82!@]͓'&]͓'!Oyy(yOa(x8&@:ͽ&@ͽ'8O  !@6<6U=~7 x6 x~ >Uw(>x2::::`:?GWy 1x!((!~wxG !~?wx/w͈xӉyӉPy!8!8 ( 0! r:O:O: O::!$Y D @ v } $ ; k Ӊ>Ӊ!!>Ӊ>Ӊ!y!t :͇:*& :_*( g>8>\ ##y\ # :0 {\ #g*(B: o&)))())*&o&))*(ɇ:>> *$t :* ͍'ӈ# y ͂ yxA(<= t yxA(<= &o)))* @:͍'#g& 0 a& > h+:O,go ,?,:(:!(!(,>G,+G* x:xG:x:*" :)0>  R !p#Á'- *: * - !~*!"m t ӈm ͂ |?gͥ |?@ ͥ |?|Ӊ>Ӊ}ӉӉ:O:! Z W :):8g>oWm( 5 = - !:󇇇* t y(ӈ  !:󇇇5 :Ç:2:2z!A !c O N#fiu (  f s 2Ͳ :2!"*""*)8!"$ s>2Ͳ :2*""*"$*"&*"(@ ͐>Ͳ * v t ӈ< & ͐s>Ͳ ! } "(*"&*"$*"">Ͳ *"&*"(*"$*"" t z ӈ< W > >vxͤ %>vxͤ $ >ͤ ;  >ͤ k ͍͐s.b"&c"(e""222!" " +}͈͈͈:Ç :)8 :):0*͊ *~ 6:*$͊ *~ *?W͘ 6  :O*󇇇͊ * *~ XQ*͘ :*J> :O*󇇇͊ * *~ XQ*͘ :G*(*(ͯ 6  :O*󇇇͊ * *~ XQ:*  :O *"~ Qͬ 6  !" !"   !" !" :O *"~ yW  :?2o<<2}o<<<2}))lg}2|2*()lg}2|2*&o<<<2}::2:2:2>^:!> :!>8!2̈́:)0͒8͠>O  *y͒(͠=t= t>O  *͔*z̈́>(> 0x~# W0  8͢KOͬkb#!5:)0,P-y,= ,Q-z,j-͔,= K͢gKlOͬkb+t:)0-P,y-= -Q,z-j,͔-= &æ'&: (G:(+@ >+@(x!8(808u0y!80y _x obMD) )))))08|g8%%i)) a !(*!]T=(#~##x   ****@" " " "!W   U:2*Kv%:( :82* | "!":82*+?,0_ ,{.8o>oEb(,*:g@O|0}O,:G,>Ӊ>Ӊy>Ӊ>Ӊ0ia0iaiax x ۶ !W S U@+?,0 ,!(,!O|0}O,>G,>Ӊ>ӉӋy0ͤiͪa** ,!w( @ 8@7 wmҷ|0!8+|0!8+==;;xMD|0!:(8+|0!: 8+xDMm8`i{2,#y>0>2,yO{O{G* "*:,**2,"*m[0Gd V**:,͙+2m:,**8+Gg Ox/O:\ m0++|6"*m09::,(0d<2,ms:,7=2,0>%} 80>%>0>%>"*2,ɧ>%} 7*8 }< 70#} 0}+ "****0} 7ɧ:(?2!Mm)**:,_:*8T\**ѷR8("B:,2D_:*8 :! %"*8#*BR:D_:*0 0 *B͞0 z00"B2D!#0!Mm͡**:,_:*8!#"*R#:,_:*0q08m* >+:󇇇O>:O:> >>$Ӊ>Ӊ}Ӌ|Ӌ:,Ӌ:Ӌ{ӋzӋ>ӋӋyӋӋӋ͡#>G:7:oH!͕7FMEMINI6MKILLr8MNAME8MFILES7͕8+F7DATEKANJI!<(ͱ(~#^#V87Ⱦ # =3OJ'''+( ,A >(*G<<(=Ӵ۵'0w# (w# ?6#s#r::::  / ///ͅ:O:J:A:J:J:J:(:(:(:!(y}({,)WͅzA{W2Ӊ>Ӊ(͹(!0!J'(*(~#^#Vk{ʗ7͕D'+PAGESCROLLVIDEO25ADJUSTTITLEqPASSWDPROMPTDATE>:>/ D'O$ 20WG(`0 OGG(`J'BW+>( ,A <<]( >>] zA(](,{Az] g(>͇ J͇ր'A > Ӵ۵ӵ#D'e_e`~#08 J',(+,=0ͅ ` OJͅ `,(=0 G+(,=J'G  J>>+D' Ó JJ=( ( ~#A  >!A>Jy.{AzAO y>({' J!/ >>!/ *5!7 kGU `ͅ `OGyO# N#F>> >  ɯ<>Ӡ{ӡ x !+ ^  ~5?'́^!? (i(x 8þ'xӴ۵`W`ioig>> ` ӵͨ۵ͨӵy0͇Ӵ~D'#H#~: (0i 8^ :h(4#2h000 ~#"f&o"j>2i!F~##"f+f>*f<2ijw"f!F 0?<=:h0 8*j ###^#V':i2c *j"!j:c0O`Oʋ [,( _+(,,( W+( ,2+"}2 J'*( :( (? ?8G>͂ ,,(̀_xG+)(,,( ̀O+)( ,̀xG)xYPumJ'((( ϔmJ'A :ͬ'7>{J',(y J' J'8]!FHˠ (>! (ˮY(1a >f!(!"!">2>2x* Y >O ͈O :_~ +,,(J':2m+,,(2,,(e+,,(2,J'O( G: GÈ=J'!( Gm8 >0>x[,(~J'_+( ,~8WS+,('(zJ'C;;{?_xDWS~, =,( 2~, /,(J'!ˎ(~, J'!ˆ(6J':{(!J')8QJ':> {2~ :={ 2> ):{2> 8OO _0/<2gJ'$(>͂#J' $>͂#(*(~8>#^#VG J'2`""^G> G2ab,,(,Ͳ's#xy0 w!~w#!#+(:,,(J'!O~怱w#!+(,{0@J'wDM:`(4*:aG!(:a(G!f[bs#r### J'>/͂#8J'=8 J'8 J'a(()z8% {J' 0! (!0!~'͹(! J'{+>͂#(@J'KB})!"y #xJ'!q#p0:̼"= @ =v ???>@͂#-8 2J'-00! :)8! !~#fo'0 _ *H#_!J#fjvxvx(,\ !g 'g !g '(J'){:c07͏#*V'(5"^((u`%SbCd01+ʷ% ( 2o͕%!!0,`%ڷ%SfCh!/1+ ( 2o͕%!."`%`%D'SfCh!/"`N%!2."`Ͳ'͸'+:(,~J'2evCb{2d(v(Ͳ(0#"j(0#"l2oKj]T2J'+((m`%SfCh0!-1%Kj[l/>_ͥ%(%( {??  {?8 8RJ'!X."`5͸'vCf{2h2i+:( ,,:$(~J'2iq(2pmJ'!Z%*`!bJ'(:c(">2'J'2`i+ o& *(7,J'2o:ɯ2*^7:TO:U:SG0O* >o>g89\'͠SO"Q|y2U0*B:D7"B2DRy2TvJ'͹(++|8#**R"*:S/%Ó&Ͳ'L(:_+(,{͌J'&%(&%>@%":ę':J( *K*I*My7x2S(0Ͷ8%ʬ&2J&]T(++|8SM0"K2I:S/2J*Q%*B:D7*O[QRʓ&'&%ʓ&(*B:DO:SG%!kn.!n(!nn"!U@!ZG!j@!m@ !ub!tn.(!z.!}(!"!!!!  !!X!A!h!X!Wv!Wp!(sF!#s@!9T:!j4!$k.!o@(!4"!X0!^!62!O !'f!/"!xf!dL!/T!R !R!gb͸'O(q(v0o:_aJ'+(,,({gJ'>_+(.,P',(%_T(( < ύDR+D'_{.)D'*BЯog*Rv(*"*3Ͳ'͸'L(+(%,,(B($,( Fq(u**"*"q(͛)q(_)()Gmx((( (7>>>>2((Ͳ((1((()(*͛)"͛)+"*MD͛)"MD***(v((Ͳ(08+>$Ӊ>ӉyӋAi:ӋYQia:ӋӋ:pӋ"":: *::: !>22>2͘>27722͘222*vi`{ ,((Ͳ(0#(0#(,?,:O,:ƀG,+ O*y(c+Ӊ>Ӊ>`Ӊ>Ӊ+>Ӊ>Ӊo> Ӊ>ӉӉ>Ӊg7+> Ӊ>Ӊ}Ӌ|Ӌ{Ӌ:ӋӉ>ӉɇOc.^,!^{P DHG ~(+#~ #HC ~(+~ +^R7,?,>@G,+>+_*{>G*x ,>,Ӊ>Ӊ:ӋӋ:PӋ>+8Ӊ>ӉӉ>Ӊ> >$+Ӊ>Ӊ}Ӌ|ӋӋ:Ӌ>(Ӊ>Ӊ{ӋzӋ}Ӌ|ӋӉ>Ӊ>Ӊ>Ӊ ;>2!^"^!, :2a!f͝'!"">2,!+} !!y} .!p>t͊-!-͑-))))͵-͑-!-͑-ӉӉӉ>Ӊ́^(v=````( -x>͠' >͠'!2>͊-!-͑-`=(!^u' (w#>.x!>͊-ix 8u'"*2,~#Password:Main RAM:Kbytes!^----}0w6!^><8RA 0w#og"^>!^O700zW{_# зKj[l2+> 2!b~y͙2:8(0=( 2w# yg.6͌.2w y( #~w6~w2w y ( #~ wB2+> 2!b~:8,(l=('2:_%3~ӋӉ>Ӊ y#{%38~Ӌ2:_%3~ӋӉ>Ӊ y{%38~Ӌ# y{%38~Ӌ2:%3~ӋӉ>Ӊ yp/:%38~Ӌ yŠ/:%38~Ӌ y¢/:%38~Ӌ y¸/#:%38~Ӌg/!>)07)0 07= 42#### ####42N#F#^#VB#J33̶3342!]J33"u[qs#r+B28,+> 2!b~!k0:820=(,32 3~ӋӉ>Ӊ y#1 38~Ӌ2 3~ӋӉ>Ӊ y 1z~#8+ 38Ӌ2 3~ӋӉ>Ӊ y 1z(=(=( 38~Ӌ#~~~ 38Ӌ####42!J3[qs#r͙2:8(M=( 22w# y 1 2>Ӊ>Ӊz0w#wӉ>Ӊ y Գ(z8LI 2>Ӊ>Ӊz( =(=(w#wӉ>Ӊ y ij(z +)23*q[u3"u*q[s*s͹3*q[qR#ù3~#fo'z 2'*bN#F#^#V#2*f:o 3R>#""PYR0!""|2CjSl/DM>_+> 2!b>yKj[l*fq#p#s#r#/DM>_>Ӊ>ӉӉ>Ӊ>Ӊ>Ӊx7z7YP3:!(!!:$:*}(+"*+} *"*"7:J'!wr{!f (3< b'CS!"| !90 [R8| [b!Sq"s)*sw&3B*sw'3}7'w!bOBۺ/>!~˾ۺW۸o&۹:_  {Ӹj}ӹ|Ӻۺ :3J4}2:3|8 B8i`.}2>( (! (@! (::ȯ555555555555555 8 424zD2чD2>_%5%5{Ӡӡ>&5{Ӡۢ,(<0}((=W_:ϳ 76z_>?5{_:߳56+,,( 5>5+,,(5 :56+,,(5_>5+,,(J'/_>5+,,(5_>5+,5 >!w0ħ ( ,( 5= +>(,W:z16;6@76;676GÈ>+@(>+w (:< ͯ87!p6o0$~#fo8}9<8998&898Z8K:+( ((J'): (o WX!a.͍'/_͓'͍'Gy/_͓'y - $$$$62 68#<J'w(! [::%|;R8f$O= o:DO o:!p! \:! h'8 bytes allocated $$$"!:ɯ2 8No RAM disk ̀;'! [:!~(.  8~ (>. 8:G:a( 0> 'yb''Z8'8 bytes free ~#~#fnCR:~*&(5n0~=ˆ;R:nf[&!0#}!n: $ ">2c͔8)t;2\::#On:o: (̀;'z0 8ͯ82'!8 # 0MEM0͔8t;AS͙8)͐:Aʈ;[:!f "̀;͐:{8'((I G HY;=2%\:  8z8! [::( CÈ;Y;!w#=2%8b'>'Y;*&tuo"&9:#:$w:R:2%\:~~:*&n"&\~ 6R:}n:( BÈ;:#y 2#No:2$wO>o:\: ^q4:4R:!'4]R:nf[&?~6~( O ~44~O:#n:w:~ 4q#N#F++707! (0w::(f a8{0! [:!~   f  #:%7 ~W8|:z@!: W8;OOۨG3_Өz W~/Ow{?WxӨ{ӨywxӨOzOۨG<_zW{Qqpwww"ds͐:b'@: wF2|''; ȓ(i`^#V!^w#w =;@;A;B;C !>(!>( #T>J=j>(!FT>A8[8 a8{0˾a8 WX(: zͲ=z9>ͣ=z2>@>C<=z 9_Ͳ=:(= N >?>J=>N{ Y(2>?>2>q!V#FOͲ= x2>2yá?>(>xH :zC(D(!T(&S ?>>?>yI(>JY<>2>?>>>?>zT xS yU >?>xY=ͬ=GW{Ͳ=oz0}p#0}0}#>>o& ~ͣ=ͣ=8دWͣ= 0zB!8 &0z<>>dzP >H<>>?>J >?>=K?=F >?>{U>L-Y <>{ W <>{I(EV >U9>=_>Q|=!~(8 0 *w#} !:"::>2ӫ> = >ӫ!F(ˆ:<(:82>2>ӠۢG>(ӡ*:]_?>{}\@>?[<{}ޡߤ %(#@!K (@IG"8+N+F+fVp#^q r#s?,@ABP@@APRLOPLL@ AQV1.3 1988 04 26@A{ABC0DGD;G 1C|{}_|{}wo|g~}o@A~w#p0B]T6>;F>PA  PA<>  PA<>0 PA<Ao0<_! xGۨ&B_!~怳"B_####~&B !CB@A!!{!7 !'!:2!@$!~怱O0y&@$!@# (yO# >yAPRLOPLLPAC2OPLL@A~#fo= =w:~C>PA 69 C696;^#V#z%C667Cut6 4; DC= ~9tCnfut66͵C nfut66666 6>lA_>PA ~;w PA66 66>lA_>PA o&)))L @A~#fo= ~9ʒDnf}ʋD^V{́Fsr ^V{ʩD{srDnf}D^V{Dsr ~ >lA_>PA~#$E`TEpEE FF1FTFzFsFkFD66~ 5<~:DE=w:DC=eDOGuty~oEcjÌE!)ҀEoŒE&.tu i&> EM!|_!(A>^#PA>lA _>PAOj|EEg?O>/lA_>/PADO>/lA_>/PAD>lA _>PAD>lA_>PAD~#;FD+^PA=IF^#V#^#PA< ]FD~#wD6 D6 D~#$EFW~#OG0>7lA_>7PA0>8lA_>8PA0>8lA_>8PA0>7lA_>7PA0>6YPAÁFO_>lA_>PAlA_y_>PAGut~# _zWɷC=_!~#foxB@A~#fo: ~GQL 0 aa V11 CC&&0 T14 rVqq SR$$40 P0R $$cc ))AA SS #C)  Ҵ   ҃ $$ p  B 11 BD  @   &&  ''@@ '' %%  S 0p Bb$$bq% dC&&!+  CS 40 P033 4 a! vTcp KK vTax 1q5 &&aq u  2 1 #@ t ZC q 02 @@tt02 @@tt x I@) B QB QB 04 #p MPyP[`xP!uvF6>O!͆T~0>$TP(!amyTP APRLOPLL!@LP O #2͛gpg2:cpg + 7O+ïgTU"!c~(!(sg6͡g6͡g!!eTPkpgeTkpgRO͟lpgXUۨzU_!~怳_####~zU am!' yw666666 :Ćm ͟l!': (hͧg,ͧg(ͧg)ͧg(^ ~,(> ~V>8/w#s#r#~)(ͧg,ͧg)mg>2Vpg!^~('#mV~#( N#FheVNy@?#heV# !' = ͧg(V?2^S_Caͧg,V?2cSdCf { 81ͧg)mg!^:c V8@( ͯg7ͯgg@pgV{(YPTB#:c*d }<7@7!:^*_ }(Oi( 2W 7}?!o&))))) nʹR:pg{`WU͛gT* |(>og<= :|:&@$͊/&@$ QW:|!0.9^#V!sRdg+T]:|0# ͯggU( =(89pg<42:(Gx=gW6#s#r#T] s#r25:225!9"6!{W%͵gg4h{ W_wgs#r#q#T] s#r:=8"+ͯg(Ux28ͯY͕Y:=0+ͯgmg28O:!Y8!p\ :(!_"VyGYWYxg~WY2;#^#V#S<^#V#.$g*6+'h`iͯYe:; ͕Y:8g:;w#[pg.$g 'h+p+q_:8f :8AgA[TZ=ZvZZϦZҵZԙZ̀ZXf>]<]\\@)\&\%\ ] <  s k8>8PA\.g>@w{8/<_(9.g~#fohs#r++>@w{0_8{A0 .gA\(wpg8x{ 8.8{ 0.دW8 ٳ(A0!.g##~296++~0A\(a0{X 0 O-[Ay@Of(#+-(@f xC(F  Z.g^!Z N!%Z {^#V=( :pg_W.gNf(Qf>@8A\(K#Ͱ^ r#s##^{w>w# {@( #^#Vr#s# z(r#s .gqy#w+z :9 wF#^#͞YYYeDMgoB)|Mj8RWY!)0 [0>{09A\f, /Nf'8{ 0!0fV(W(&']8 Qf{@0zpgf']8Qf{0f(']8QfùZ@fóZA]&_{__7]̀Zѝ]d]ϦZ>]<]a]Xf]]ԙZ@\͎]ӕ]fV(,W(H']8LQfA\{@0AO:8͟a0y03YYu^f']8!Qf>]OA\YYu^ ]m^pg͂^f(Qf>@8A\(Kï^+-=0:?0({m8"A\f, NfA\Y]Yu^pg8A\{0"OYYu^8A\{0 >_{pg{0A\8{ 0.&ËZ.g~< 0w.g~=(w8A\(A0!^.gâ^0A\{a8pgZ.g F>W!Z ~ W͂l͔^f( &@f(A.&g~(7G!<<<R(XYxYY]Y\Y XYx(YY]Y\YYYe. gNy.gN͂^f(Qf>@8pA\(K#~[[z[<[_n&u0u#bkf(. :Z>(g@f:8O! u^pg. g~ *<:;fN( R(A8H0 }({(= ͤf.g^f( @f']8Nf>@8A\yBK[ }A0. gw2;"<. g~(/6B_S_M_C_H_]@*`ԙZ7]{]XfBSMCHpg@ff']0$!_  Vf!@f zGzO (>!`.g@fNfâ^_YXm^fV\A f']8QfA\{0>OYYu^pg!: /22&͇`:& 25f`4û:?( ͱc :?*!čeKx(i`:G>G):=`)`2.g^#Vz(r+sz#:G:c͈c<b=5bW_(͈c_. ͖cs#r͈cO͈cw+qzwaa.͖cN?a½c͜araJj !F͈cȷ`(W͈c_. ͖c{ #za͈cO͈cw+q:͜a0 ?acva?acaXl :!?:=(?ac͜a0y2aKGjh :͟a0 (!!(G#!'( = Fwa. ͖c6#6:!G()*{ozg"=h_Ac{No>bgN#FnbqbbbbbcGadbfbc>2`wa͜a0c.͖c͈cw`.͖c͈cwb͈cO͈cG͵m͈cO͈cw+q͈cOͲa?ac͜a0 (aa͈c_i ͈c_>?bO_. ͖c͈cw_.͖c~//cÇb. ͖c͈cw_.͖c~/cÇb?acOi͈cW͈cO͈cw+q.͖cz( r. ͖c^/cz/W. ͖c^/c{?O?achl:g:gͱc7*}! :::uQ͈cW͜a0(WͿcQ͈cW:G:G͈c([WOzw͈c+w ͈cW ͈c_x̀d Ād|@(͈cW͈c_> ̀d/cxeyedVe:͚e=2wag4hAJSx(z(2;|Y"<f(*VA8H8O~pg(###~eO0 Of fͮhfQf7@f#~#fof(!;~(*5*<~#"< (7f7a{ !;4*<+"<f=f+(- f,(;:008! 8N0_8Df A[?%!#$7f^(͏f8͖f8 f>; ;(fpgÏgͤfͤf:;pg*<͔hæe_WPgx<#+++w####O~#fo sPgy#<++w###O~#fo ~7Xgp#p#p#w#s#rPgx<#Gyo&XgF#N#*GO 358 Pče!o@N!H!NB!j2?!~5:(G*}'6#6+*">2&* >>o>g89yg{}><&@[?:c07|}i`Biy@i668i~ OmG{G͵mxj!(iy(>=O  0! i2WBi^#V#sr!' ~ Om sr ~WF͵m#F͵m#F͵m#F͵m#####F͵m#~WmG͵m#F͵m#F͵m{wiy!W{_0s#i!:W#G76jz#G8@jz#G6jz#G7@jz#G6@jmGõmmXlXl>w˺srxj:l~OmG͵mnf^V^V)|j0 j jgM.<8g.ڳjRE|!,k͆Tx^#V#f.E)0 )0 )0 )0 )0 )0 )0 )0 l`<<0#T(+0< 8>gNE͵myOm0G͵myy  P t APYbkS*k!4!f?)vk^#V'2G~ OmG͵m~Om/G͵myW/_moG͵m}G͵mz<& 8ҊlҎl*͆T^{y? 8ng!l͆T NRS ~#  5#,            '#,)3R$.V )3R$.V{m {m  {m0 G͵m m G͵m!m N#F#͵m P&'(678ym8!!o>gpy|x}7?7ym!yo>g~?? )?09?Piano 1 10pPiano 2 00pViolin naaVFlute a l@1C&Clarinet @0TOboe 1 r @4VTrumpet n1Q&@qR$`PipeOrgn 47Pv000Xylophon fR$Organ (pc)pGuitar u A`Santool  S`Elecpian#J CPClavicod  `Harpsicd @Harpscd2  Vibraphn$pKoto  30҃Taiko D $Engine n@PUFO P`p@SynBell !0Chime  B `SynBass @@1SynthsizlB 3DPSynPercu %`SynRhyth @7@HarmDrum 9`Cowbell  & &`ClseHiht 'SnareDrmn@@'BassDrum 6@%Piano 3  0pElecpia2Santool2!S`Brass n0B&@pb$`Flute 2 b%d@qC&Clavicd2 ! P`Clavicd3  @`Koto 2 CPS`PipeOrg2 4&Pv000PohdsPLA sZ`3RohdsPRA s3`POrch L av#@!TpOrch R cuE`pKpSynViol a v@TSynOrgan a @x`SynBrass1@q&`Tube a u@q`Shamisen P `Magical  `2@Huwawa # 1@ WnderFltZ@t@CHardrockl V q`Machine 0@2@t0MachineV0@2@t0Comic  x@SE_Comicj v@SE_Laser0nI @`SE_Noise$ BSE_Star nQB@BPSE_Star2$nQB@BPEngine 2 0#&@4pPSilence v yy}zyy>!"^!^`!Tz\L{]L{8zcL{y!^!y+nf^R0!ut###nf^|8!ut###L{L{            !^U^#V#{:Ӊ>Ӊ zQYL{L{>Ӊ>Ӊ@(@ Ӊ>Ӊ<<\\<\8z#L{(|>U!MEu{{L{!x0>u{!0x>u{!t>u{!Uzv(͈{L{?L{-L{*L{L{'L{ !|!W|>-&L{N#ͻ{~#(70Oͷ{ MD{{!N#0 ͻ{>{{~ {ӉzӉ>a{8Ӊ>ӉӉ>Ӊ͝{y(ӈ {͝{yxA(<= |?@|Ӊ>Ӊ}ӉӉ($YL{X_,L{p.L{W{P> Ӵ۵ ӵ> Ӵ۵ O!G| N#F#^#VL{||!D|!wia!7|V#^#L{#(   'rVppnoooooo<<<{{{zxz//////ՀD0g+("D*m))"C&s%)$C "w#)%B! |)'B!")(A#"(*A#$)+@%$(-@%&).?'&)/?'( (1>)()2>** (4=++ )5=, , 7<- - &LP8<. . #QL:;/ / !TJ;;0 0 WF=:11!WD>:22#WA?933&U>A944F6;B855K28D866N05E777P.2G7qR-/H6r T+,J6s U+)K5t U)*K5u U%-J4v U#0H47 T 3G3 6R6E35P9D2 4NA1 2FB?11<&D>0 0@"H<0 / A J;/ . A N9/ - @ ('8. , >" )'6. + 9& )'5- *} ( '3-)| ) '2, ({('0,'z)'/+%x('-+%w)',*#u('**#t''))!q("'')!p'%'&(m(('$(j'+'#'f (.'!'c '1' &_ (4'&Z(7'LPEOFopenMSX-RELEASE_0_12_0/share/machines/acid-tests/Acid2Test_msxaudio.opl1.ips000066400000000000000000000002541257557151200264530ustar00rootroot00000000000000PATCHPQPRSR1R8RFRScPQmRSPQRS݆PyQ݆RyS PQyRSPRP R" PQ>RS" P|Qg>R|SEOFopenMSX-RELEASE_0_12_0/share/machines/acid-tests/Acid2Test_msxaudio.ppi.ips000066400000000000000000000003031257557151200263630ustar00rootroot00000000000000PATCH((((<(V(]((((((%((l(G?((,;**)**)@(@3(@:(M(M(M(M(M(M(N$(EOFopenMSX-RELEASE_0_12_0/share/machines/acid-tests/Acid2Test_msxaudio.psg.ips000066400000000000000000000000221257557151200263620ustar00rootroot00000000000000PATCH/ {!EOFopenMSX-RELEASE_0_12_0/share/machines/acid-tests/Acid2Test_msxdos22.ppi.ips000066400000000000000000000003351257557151200262200ustar00rootroot00000000000000PATCHQ( 6( V( ( ((($(-(E (E1(E:(FA(Fa((F((Gz(G(G(#(H(<(G(}(S(k(Ԣ*ԭ*)ֿ+++ **)(EOFopenMSX-RELEASE_0_12_0/share/machines/acid-tests/Acid2Test_msxdos22.rtc.ips000066400000000000000000000001771257557151200262240ustar00rootroot00000000000000PATCH 45G<5> 4>5> 4x5 y454>54z5> 455> 455O45^4>5m5> 4z54x5EOFopenMSX-RELEASE_0_12_0/share/nettou_yakyuu/000077500000000000000000000000001257557151200204665ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/nettou_yakyuu/README000066400000000000000000000010371257557151200213470ustar00rootroot00000000000000Moero!! Nettou Yakyuu '88 Sample Data ===================================== The Jaleco game cartridge Moero!! Nettou Yakyuu '88 has a built in ROM with samples that are replayed during the game. Due to copyright reasons, we cannot distribute these samples with openMSX. If you have the samples (in WAV format), you should put them in this directory, with the following file names: nettou_yakyuu_0.wav nettou_yakyuu_1.wav ... nettou_yakyuu_15.wav If you want to have real fun, provide your own samples instead of the original ones! openMSX-RELEASE_0_12_0/share/playball/000077500000000000000000000000001257557151200173415ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/playball/README000066400000000000000000000012761257557151200202270ustar00rootroot00000000000000Playball Sample Data ==================== The Sony game cartridge Playball has a built in ROM with samples that are replayed during the game. Also the game Hardball (by Sony) can use these samples, when it detects the Playball ROM in the second cartridge slot. Due to copyright reasons, we cannot distribute these samples with openMSX. If you have the samples (in WAV format), you should put them in this directory, with the following file names: playball_0.wav playball_1.wav ... playball_14.wav (Note that playball_0.wav and playball_5.wav are the same sound, you do need to provide them both though.) If you want to have real fun, provide your own samples instead of the original ones! openMSX-RELEASE_0_12_0/share/scripts/000077500000000000000000000000001257557151200172305ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/scripts/_about.tcl000066400000000000000000000031651257557151200212120ustar00rootroot00000000000000namespace eval about { set_help_text about \ "Shows a list of commands and/or settings that seem related to the given keyword. If there is only one such command or setting the helptext for that command or setting is also shown." proc about {keyword} { openmsx::lazy_execute_all set command_matches [get_matching_commands $keyword] set setting_matches [get_matching_settings $keyword] set result "" set lc [llength $command_matches] set ls [llength $setting_matches] if {$lc == 0 && $ls == 0} { error "No candidates found." } elseif {$lc == 1 && $ls == 0} { set match [lindex $command_matches 0] append result "Command $match:\n" append result "[help $match]\n" } elseif {$lc == 0 && $ls == 1} { set match [lindex $setting_matches 0] append result "Setting $match:\n" append result "[help set $match]\n" } else { append result "Multiple candidates found:\n" foreach match $command_matches { append result " command: $match\n" } foreach match $setting_matches { append result " setting: $match\n" } } return $result } proc get_matching_commands {keyword} { set matches [list] foreach command [info commands] { catch { if {[regexp -nocase -- $keyword [help $command]] || ($command eq $keyword)} { lappend matches $command } } } return $matches } proc get_matching_settings {keyword} { set matches [list] foreach setting [openmsx_info setting] { catch { if {[regexp -nocase -- $keyword [help set $setting]] || ($setting eq $keyword)} { lappend matches $setting } } } return $matches } namespace export about } ;# namespace about namespace import about::* openMSX-RELEASE_0_12_0/share/scripts/_backwards_compatibility.tcl000066400000000000000000000016071257557151200247710ustar00rootroot00000000000000# Provide 'quit' command for backwards compatibility. The preferred command is # now 'exit'. Tcl normally only has a 'exit' command. Also most shells have # exit but no quit. proc quit {} { # wouter: I prefer to not deprecate this, because I occasionally still # use this myself. exit } proc decr {var {num 1}} { puts stderr "This command ('decr $var $num') has been deprecated (and may be removed in a future release), please use 'incr $var -$num' instead!" uplevel incr $var [expr {-$num}] } proc restoredefault {var} { puts stderr "This command ('restoredefault $var') has been deprecated (and may be removed in a future release), please use 'unset $var' instead!" uplevel unset $var } proc alias {cmd body} { puts stderr "This command ('alias $cmd $body') has been deprecated (and may be removed in a future release), please define a proc instead: proc $cmd {} $body" proc $cmd {} $body } openMSX-RELEASE_0_12_0/share/scripts/_cashandler.tcl000066400000000000000000000211771257557151200222070ustar00rootroot00000000000000namespace eval cashandler { set help_text_cas \ {----------------------------------------------------------- CAS-file tools 1.0 for openMSX Made By: NYYRIKKI & wouter_ ------------------------------------------------------------ Usage: casload | Open CAS-file for load cassave | Open CAS-file for save caslist | Show loaded CAS-file content casrun [] [] | Automatically run program caspos | Select file to load from CAS caseject | Deactivate CAS-file support } set_help_text casload $help_text_cas set_help_text cassave $help_text_cas set_help_text caslist $help_text_cas set_help_text casrun $help_text_cas set_help_text caspos $help_text_cas set_help_text caseject $help_text_cas set_tabcompletion_proc casload utils::file_completion set_tabcompletion_proc cassave utils::file_completion set_tabcompletion_proc casrun utils::file_completion variable fidr "" ;# file id of opened read file, "" if not active variable fidw "" ;# file id of opened write file, "" if not active variable bptapion ;# tapion variable bptapin ;# tapin variable bptapoon ;# tapoon variable bptapout ;# tapout variable bptapoof ;# tapoof variable bphread ;# h.read variable casheader [binary format H* "1FA6DEBACC137D74"] variable casbios [dict create r [list 0x00E2 tapion 0x00E5 tapin 0x00E8 tapiof] \ w [list 0x00EB tapoon 0x00EE tapout 0x00F1 tapoof]] proc casload {filename} { casopen $filename "r" return "Cassette inserted, overriding normal openMSX cassette load routines." } proc cassave {filename} { casopen $filename "w" return "Cassette inserted, overriding normal openMSX cassette save routines." } proc caseject {} { casclose "r" casclose "w" return "Cassette ejected, normal openMSX cassette routines in use." } proc casopen {filename rw} { # Possibly close previous file. casclose $rw # Open file. variable fid${rw} set fid${rw} [open $filename $rw] fconfigure [set fid${rw}] -translation binary -encoding binary # Install BIOS handlers. variable casbios foreach {addr func} [dict get $casbios $rw] { variable bp${func} set bp${func} [debug set_bp [peek16 $addr "slotted memory"] {[pc_in_slot 0 0]} [namespace code $func]] } } proc casclose {rw} { # Was active? variable fid${rw} if {[set fid${rw}] eq ""} return # Uninstall BIOS handlers. variable casbios foreach {addr func} [dict get $casbios $rw] { variable bp${func} debug remove_bp [set bp${func}] } if {$rw eq "r"} { # In case of read (possibly) also remove bphread. variable bphread catch {debug remove_bp $bphread} ;# often not set, so catch error } else { # In case of write align end of file in order to make combine with other CAS-files easy. set align [expr {[tell $fidw] & 7}] if {$align} {puts -nonewline $fidw [string repeat \x0 [expr {8 - $align}]]} } # Close file and deactivate. close [set fid${rw}] set fid${rw} "" } proc tapion {} { # TAPION # Address : #00E1 # Function : Reads the header block after turning the cassette motor on # Output : C-flag set if failed # Registers: All reg F [expr {([seekheader] == -1) ? 0x01 : 0x40}] ;# set carry flag if header not found ret } proc tapin {} { # TAPIN # Address : #00E4 # Function : Read data from the tape # Output : A - read value # C-flag set if failed # Registers: All variable fidr if {[binary scan [read $fidr 1] cu val]} { reg A $val reg F 0x40 ;# ok, clear carry flag } else { reg F 1 ;# end-of-file reached, set carry flag } ret } proc tapiof {} { #TAPIOF #Address : #00E7 #Function : Stops reading from tape #Registers: None ret } proc tapoon {} { #TAPOON #Address : #00EA #Function : Turns on the cassette motor and writes the header #Input : A - #00 short header # not #00 long header #Output : C-flag set if failed #Registers: All if {[catch { variable fidw variable casheader set align [expr {[tell $fidw] & 7}] if {$align} {puts -nonewline $fidw [string repeat \x0 [expr {8 - $align}]]} puts -nonewline $fidw $casheader reg F 0x40 ;# ok, clear carry flag }]} { reg F 1 ;# write error, set carry flag } ret } proc tapout {} { #TAPOUT #Address : #00ED #Function : Writes data on the tape #Input : A - data to write #Output : C-flag set if failed #Registers: All if {[catch { variable fidw puts -nonewline $fidw [binary format c* [reg A]] reg F 0x40 ;# all went fine, clear carry flag }]} { reg F 1 ;# write error, set carry flag } ret } proc tapoof {} { #TAPOOF #Address : #00F0 #Function : Stops writing on the tape #Registers: None ret } proc ret {} { reg PC [peek16 [reg SP]] reg SP [expr {[reg SP] + 2}] } proc seekheader {} { # Skip till 8-bytes aligned. variable fidr set align [expr {[tell $fidr] & 7}] if {$align} {read $fidr [expr {8 - $align}]} # Search header. variable casheader while {![eof $fidr] && [read $fidr 8] ne $casheader} {} # Return position of header in cas file, or -1 if not found. expr {[eof $fidr] ? -1 : ([tell $fidr] - 8)} } proc readheader {} { # Read (first) type-id byte. variable fidr set byte [read $fidr 1] if {![binary scan $byte cu val]} {return -1} # This must be one of 0xEA 0xD0 0xD3. set type [lsearch -exact -integer [list 0xEA 0xD0 0xD3] $val] if {$type == -1} {return -1} # And it must repeat 9 more times. for {set i 0} {$i < 9} {incr i} { if {[read $fidr 1] ne $byte} {return -1} } return $type } proc checkactive {} { variable fidr if {$fidr eq ""} {error "No cas file loaded, use 'casload '."} } proc caslist {} { checkactive set result "Position: Type: Name: Offset:\n" append result "--------------------------------\n" variable fidr set oldpos [tell $fidr] seek $fidr 0 set i 0 while {![eof $fidr]} { set headerpos [seekheader] set type [readheader] if {$type == -1} continue append result [expr {($headerpos < $oldpos) ? "| " : " "}] append result [format %5d [incr i]] " : " append result [lindex "TXT BIN BAS" $type] " : " append result [read $fidr 6] " : " append result $headerpos "\n" } seek $fidr $oldpos return $result } proc caspos {{position 1}} { checkactive lassign [seekpos $position] headerpos type return "Cassette header put to offset: $headerpos" } # Seek to the start of the n-th header and return both the # file-offset and the type of this header. proc seekpos {position} { if {![string is integer $position] || ($position <= 0)} { error "Expected a strict positive integer, but got $position." } variable fidr seek $fidr 0 set i 0 while {$i != $position} { set headerpos [seekheader] set type [readheader] if {$type != -1} {incr i} if {[eof $fidr]} {error "No entry $position in this cas file."} } seek $fidr $headerpos list $headerpos $type } proc casrun {{filename 1} {position 1}} { variable fidr variable bphread if {[string is integer $filename] && ($fidr ne "")} { # First argument is actually a position instead of a filename, # only works when there already is a cas file loaded. set position $filename catch {debug remove_bp $bphread} ;# often not set, so catch error } else { # Interpret 1st argument as a filename and load it. casload $filename } lassign [seekpos $position] headerpos type catch {carta eject} catch {cartb eject} ;# there are machines without slot-B reset set ::power on keymatrixdown 6 1 ;# press SHIFT set bphread [debug set_bp 0xff07 {} [namespace code "typeload $type"]] return "" } proc typeload {type} { variable bphread debug remove_bp $bphread keymatrixup 6 1 ;# release SHIFT set cmd [lindex [list "RUN\"CAS:\r" "BLOAD\"CAS:\",R\r" "CLOAD\rRUN\r"] $type] set keybuf 0xfbf0 debug write_block memory $keybuf $cmd poke16 0xf3fa $keybuf poke16 0xf3f8 [expr {$keybuf + [string length $cmd]}] } ###################################################### proc tapedeck {args} { if {[string toupper [file extension [lindex $args end]]]==".CAS"} { switch [lindex $args 0] { eject {caseject} rewind {caspos 1} motorcontrol {} play {} record {} new {cassave [lindex $args 1]} insert {casload [lindex $args 1]} getpos {} getlength {} "" {} default {casload $args} } } else { caseject } return [uplevel 1 [list interp invokehidden {} -global cassetteplayer] $args] } ###################################################### namespace export casload cassave caslist casrun caspos caseject } ;# namespace cashandler namespace import cashandler::* openMSX-RELEASE_0_12_0/share/scripts/_cheat.tcl000066400000000000000000000100371257557151200211600ustar00rootroot00000000000000package provide cheatfinder 0.5 set_help_text findcheat \ {Cheat finder version 0.5 Welcome to the openMSX cheat finder. Please visit http://forum.vampier.net/viewtopic.php?t=32 and http://www.youtube.com/watch?v=F11ltfkCtKo for a quick tutorial Usage: findcheat [-start] [-max n] [expression] -start : restart search, discard previously found addresses -max n : show max n results expression : TODO Examples: findcheat 42 search for specific value findcheat bigger search for increased values findcheat new == (2 * old) search for doubled values findcheat new == (old - 1) search for values decreased by 1 findcheat repeat the results from the previous operation findcheat -start new < 10 restart and search for values less than 10 findcheat -max 40 smaller search for smaller values, show max 40 results findcheat -start addr>0xe000 && addr<0xefff search in defined memory locations } namespace eval cheat_finder { variable max_num_results 15 ;# maximum to display cheats variable mem # build translation dictionary for convenience expressions variable translate [dict create \ "" "true" \ \ "smaller" "new < old" \ "less" "new < old" \ "bigger" "new > old" \ "more" "new > old" \ "greater" "new > old" \ \ "le" "new <= old" \ "loe" "new <= old" \ "ge" "new >= old" \ "goe" "new >= old" \ "moe" "new >= old" \ \ "equal" "new == old" \ "eq" "new == old" \ "notequal" "new != old" \ "ne" "new != old" \ \ "<=" "new <= old" \ ">=" "new >= old" \ "<" "new < old" \ ">" "new < old" \ "==" "new == old" \ "!=" "new != old"] set_tabcompletion_proc findcheat [namespace code tab_cheat_type] proc tab_cheat_type {args} { variable translate set result [dict keys $translate] lappend result "-start" "-max" return $result } # Restart cheat finder. proc start {} { variable mem [dict create] set mymem [debug read_block memory 0 0x10000] binary scan $mymem c* values set addr 0 foreach val $values { dict append mem $addr $val incr addr } } # Helper function to do the actual search. # Returns a list of triplets (addr, old, new) proc search {expression} { variable mem set result [list] dict for {addr old} $mem { set new [debug read memory $addr] #note: NO braces around $expression if $expression { dict set mem $addr $new lappend result [list $addr $old $new] } else { dict unset mem $addr } } return $result } # main routine proc findcheat {args} { variable mem variable max_num_results variable translate # create mem dictionary if {![info exists mem]} start # parse options while (1) { switch -- [lindex $args 0] { "-max" { set max_num_results [lindex $args 1] set args [lrange $args 2 end] } "-start" { start set args [lrange $args 1 end] } "default" break } } # all remaining arguments form the expression set expression [join $args] if {[dict exists $translate $expression]} { # translate a convenience expression into a real expression set expression [dict get $translate $expression] } elseif {[string is integer $expression]} { # search for a specific value set expression "new == $expression" } # prefix 'old', 'new' and 'addr' with '$' set expression [string map {old $old new $new addr $addr} $expression] # search memory set result [search $expression] # display the result set num [llength $result] if {$num == 0} { return "No results left" } elseif {$num <= $max_num_results} { set output "" set sorted [lsort -integer -index 0 $result] foreach {addr old new} [join $sorted] { append output [format "0x%04X : %d -> %d\n" $addr $old $new] } return $output } else { return "$num results found -> Maximum result to display set to $max_num_results " } } namespace export findcheat } ;# namespace cheat_finder namespace import cheat_finder::* openMSX-RELEASE_0_12_0/share/scripts/_cpuregs.tcl000066400000000000000000000055501257557151200215500ustar00rootroot00000000000000namespace eval cpuregs { # reg command set_help_text reg \ {Convenience procs to read or write Z80 registers. Usage is similar to the builtin Tcl proc 'set' usage: reg read from a Z80 register reg write to a Z80 register must be one of A F B C D E H L A2 F2 B2 C2 D2 E3 H2 L2 IXl IXh IYl IYh PCh PCl SPh SPl I R IM IFF AF BC DE HL AF2 BC2 DE2 HL2 IX IY PC SP examples: reg E read register E reg HL read register HL reg C 7 write 7 to register C reg AF 0x1234 write 0x12 to register A and 0x34 to F } variable regB [dict create \ A 0 F 1 B 2 C 3 \ D 4 E 5 H 6 L 7 \ A2 8 F2 9 B2 10 C2 11 \ D2 12 E2 13 H2 14 L2 15 \ IXH 16 IXL 17 IYH 18 IYL 19 \ PCH 20 PCL 21 SPH 22 SPL 23 \ I 24 R 25 IM 26 IFF 27 ] variable regW [dict create \ AF 0 BC 2 DE 4 HL 6 \ AF2 8 BC2 10 DE2 12 HL2 14 \ IX 16 IY 18 PC 20 SP 22 ] set_tabcompletion_proc reg [namespace code tab_reg] false proc tab_reg {args} { variable regB variable regW concat [dict keys $regB] [dict keys $regW] } proc reg {name {val ""}} { variable regB variable regW set name [string toupper $name] if {[dict exists $regB $name]} { set i [dict get $regB $name] set single 1 } elseif {[dict exists $regW $name]} { set i [dict get $regW $name] set single 0 } else { error "Unknown Z80 register: $name" } set d "CPU regs" if {$val eq ""} { if {$single} { return [debug read $d $i] } else { return [expr {256 * [debug read $d $i] + [debug read $d [expr {$i + 1}]]}] } } else { if {$single} { debug write $d $i $val } else { debug write $d $i [expr {$val / 256}] debug write $d [expr {$i + 1}] [expr {$val & 255}] } } } # cpuregs command set_help_text cpuregs "Gives an overview of all Z80 registers." proc cw {reg} {format "%04X" [reg $reg]} proc cb {reg} {format "%02X" [reg $reg]} proc cpuregs {} { set result "" append result "AF =[cw AF ] BC =[cw BC ] DE =[cw DE ] HL =[cw HL ]\n" append result "AF'=[cw AF2] BC'=[cw BC2] DE'=[cw DE2] HL'=[cw HL2]\n" append result "IX =[cw IX ] IY =[cw IY ] PC =[cw PC ] SP =[cw SP ]\n" append result "I =[cb I ] R =[cb R] IM =[cb IM] IFF=[cb IFF]" return $result } # get_active_cpu set_help_text get_active_cpu "Returns the name of the active CPU ('z80' or 'r800')." proc get_active_cpu {} { set result "z80" catch { # On non-turbor machines this debuggable doesn't exist if {([debug read "S1990 regs" 6] & 0x20) == 0} { set result "r800" } } return $result } namespace export reg namespace export cpuregs namespace export get_active_cpu } ;# namespace cpuregs namespace import cpuregs::* openMSX-RELEASE_0_12_0/share/scripts/_cycle.tcl000066400000000000000000000033241257557151200211740ustar00rootroot00000000000000namespace eval cycle { set help_cycle \ {Cycle through the possible values of an enum setting. 'cycle_back' does the same as 'cycle', but it goes in the opposite direction. Usage: cycle [] cycle_back [] Example: cycle scale_algorithm cycle scale_algorithm "hq2x hq2xlite" } set_help_text cycle $help_cycle set_help_text cycle_back $help_cycle set_tabcompletion_proc cycle [namespace code tab_cycle] set_tabcompletion_proc cycle_back [namespace code tab_cycle] proc tab_cycle {args} { set result [list] foreach setting [openmsx_info setting] { set type [lindex [openmsx_info setting $setting] 0] if {($type eq "enumeration") || ($type eq "boolean")} { lappend result $setting } } return $result } proc cycle {setting {cycle_list {}} {step 1}} { set setting_info [openmsx_info setting $setting] set type [lindex $setting_info 0] if {$type eq "enumeration"} { if {[llength $cycle_list] == 0} { set cycle_list [lindex $setting_info 2] } } elseif {$type eq "boolean"} { set cycle_list [list "true" "false"] } else { error "Not an enumeration setting: $setting" } set cur [lsearch -exact -nocase $cycle_list [set ::$setting]] set new [expr {($cur + $step) % [llength $cycle_list]}] set ::$setting [lindex $cycle_list $new] } proc cycle_back {setting} { cycle $setting {} -1 } set_help_text toggle \ {Toggles a boolean setting Usage: toggle Example: toggle fullscreen } set_tabcompletion_proc toggle [namespace code tab_cycle] proc toggle {setting} { cycle $setting "on off" } namespace export cycle namespace export cycle_back namespace export toggle } ;# namespace cycle namespace import cycle::* openMSX-RELEASE_0_12_0/share/scripts/_cycle_machine.tcl000066400000000000000000000014001257557151200226510ustar00rootroot00000000000000namespace eval cycle_machine { set help_cycle_machine \ {Cycle through the currently running machines. 'cycle_back_machine' does the same as 'cycle_machine', but it goes in the opposite direction. } set_help_text cycle_machine $help_cycle_machine set_help_text cycle_back_machine $help_cycle_machine proc cycle_machine {{step 1}} { set cycle_list [utils::get_ordered_machine_list] if {[llength $cycle_list] > 0} { set cur [lsearch -exact $cycle_list [activate_machine]] set new [expr {($cur + $step) % [llength $cycle_list]}] activate_machine [lindex $cycle_list $new] } } proc cycle_back_machine {} { cycle_machine -1 } namespace export cycle_machine namespace export cycle_back_machine } ;# namespace cycle_machine namespace import cycle_machine::* openMSX-RELEASE_0_12_0/share/scripts/_disasm.tcl000066400000000000000000000270561257557151200213650ustar00rootroot00000000000000namespace eval disasm { # very common debug functions proc peek {addr {m memory}} { debug read $m $addr } proc peek8 {addr {m memory}} { peek $addr $m } proc peek_u8 {addr {m memory}} { peek $addr $m } proc peek_s8 {addr {m memory}} { set b [peek $addr $m] expr {($b < 128) ? $b : ($b - 256)} } proc peek16 {addr {m memory}} { expr {[peek $addr $m] + 256 * [peek [expr {$addr + 1}] $m]} } proc peek16_LE {addr {m memory}} { peek16 $addr $m } proc peek16_BE {addr {m memory}} { expr {256 * [peek $addr $m] + [peek [expr {$addr + 1}] $m]} } proc peek_u16 {addr {m memory}} { peek16 $addr $m } proc peek_u16LE {addr {m memory}} { peek16 $addr $m } proc peek_u16BE {addr {m memory}} { peek16_BE $addr $m } proc peek_s16 {addr {m memory}} { set w [peek16 $addr $m] expr {($w < 32768) ? $w : ($w - 65536)} } proc peek_s16LE {addr {m memory}} { peek_s16 $addr $m } proc peek_s16BE {addr {m memory}} { set w [peek16_BE $addr $m] expr {($w < 32768) ? $w : ($w - 65536)} } set help_text_peek \ {Read a byte or word from the given memory location. Optionally allows to specify a different 'debuggable' (default is 'memory'). usage: peek [] Read unsigned 8-bit value from address peek8 [] unsigned 8-bit peek_u8 [] unsigned 8-bit peek_s8 [] signed 8-bit peek16 [] unsigned 16-bit little endian peek16_LE [] unsigned 16-bit little endian peek16_BE [] unsigned 16-bit big endian peek_u16 [] unsigned 16-bit little endian peek_u16_LE [] unsigned 16-bit little endian peek_u16_BE [] unsigned 16-bit big endian peek_s16 [] signed 16-bit little endian peek_s16_LE [] signed 16-bit little endian peek_s16_BE [] signed 16-bit big endian } set_help_text peek $help_text_peek set_help_text peek8 $help_text_peek set_help_text peek_u8 $help_text_peek set_help_text peek_s8 $help_text_peek set_help_text peek16 $help_text_peek set_help_text peek16_LE $help_text_peek set_help_text peek16_BE $help_text_peek set_help_text peek_u16 $help_text_peek set_help_text peek_u16_LE $help_text_peek set_help_text peek_u16_BE $help_text_peek set_help_text peek_s16 $help_text_peek set_help_text peek_s16_LE $help_text_peek set_help_text peek_s16_BE $help_text_peek proc poke {addr val {m memory}} { debug write $m $addr $val } proc poke8 {addr val {m memory}} { poke $addr $val $m } proc poke16 {addr val {m memory}} { poke $addr [expr { $val & 255}] $m poke [expr {$addr + 1}] [expr {($val >> 8) & 255}] $m } proc poke16_LE {addr val {m memory}} { poke16 $addr $val $m } proc poke16_BE {addr val {m memory}} { poke $addr [expr {($val >> 8) & 255}] $m poke [expr {$addr + 1}] [expr { $val & 255}] $m } set help_text_poke \ {Write a byte or word to the given memory location. Optionally allows to specify a different 'debuggable' (default is 'memory'). usage: poke [] Write 8-bit value poke8 [] 8-bit poke16 [] 16-bit little endian poke16_LE [] 16-bit little endian poke16_BE [] 16-bit big endian } set_help_text poke $help_text_poke set_help_text poke8 $help_text_poke set_help_text poke16 $help_text_poke set_help_text poke16_LE $help_text_poke set_help_text poke16_BE $help_text_poke # because of reverse we can now save replays to a file, # poke-ing adds an entry into the replay file and therefore # the file size can grow significantly. Therefor dpoke (poke # if different or diffpoke) is introduced. proc dpoke {addr val {m memory}} { if {[peek $addr $m] != $val} {poke $addr $val $m} } # # disasm # set_help_text disasm \ {Disassemble z80 instructions Usage: disasm Disassemble 8 instr starting at the currect PC disasm Disassemble 8 instr starting at address disasm Disassemble instr starting at address } proc disasm {{address -1} {num 8}} { if {$address == -1} {set address [reg PC]} for {set i 0} {$i < int($num)} {incr i} { set l [debug disasm $address] append result [format "%04X %s\n" $address [join $l]] set address [expr {($address + [llength $l] - 1) & 0xFFFF}] } return $result } # # run_to # set_help_text run_to \ {Run to the specified address, if a breakpoint is reached earlier we stop at that breakpoint.} proc run_to {address} { set bp [debug set_bp $address] after break "debug remove_bp $bp" debug cont } # # step_in # set_help_text step_in \ {Step in. Execute the next instruction, also go into subroutines.} proc step_in {} { debug step } set_help_text step \ {Same as step_in.} proc step {} { debug step } # # step_out # set_help_text step_out \ {Step out of the current subroutine. In other words, execute till right after the next 'ret' instruction (more if there were also extra 'call' instructions). Note: simulation can be slow during execution of 'step_out', though for not extremely large subroutines this is not a problem.} variable step_out_bp1 variable step_out_bp2 proc step_out_is_ret {} { # ret 0xC9 # ret 0xC0,0xC8,0xD0,..,0xF8 # reti retn 0xED + 0x45,0x4D,0x55,..,0x7D set instr [peek16 [reg pc]] expr {(($instr & 0x00FF) == 0x00C9) || (($instr & 0x00C7) == 0x00C0) || (($instr & 0xC7FF) == 0x45ED)} } proc step_out_after_break {} { variable step_out_bp1 variable step_out_bp2 # also clean up when breaked, but not because of step_out catch {debug remove_condition $step_out_bp1} catch {debug remove_condition $step_out_bp2} } proc step_out_after_next {} { variable step_out_bp1 variable step_out_bp2 variable step_out_sp catch {debug remove_condition $step_out_bp2} if {[reg sp] > $step_out_sp} { catch {debug remove_condition $step_out_bp1} debug break } } proc step_out_after_ret {} { variable step_out_bp2 catch {debug remove_condition $step_out_bp2} set step_out_bp2 [debug set_condition 1 [namespace code step_out_after_next]] } proc step_out {} { variable step_out_bp1 variable step_out_bp2 variable step_out_sp catch {debug remove_condition $step_out_bp1} catch {debug remove_condition $step_out_bp2} set step_out_sp [reg sp] set step_out_bp1 [debug set_condition {[disasm::step_out_is_ret]} [namespace code step_out_after_ret]] after break [namespace code step_out_after_break] debug cont } # # step_over # set_help_text step_over \ {Step over. Execute the next instruction but don't step into subroutines. Only 'call' or 'rst' instructions are stepped over. Note: 'push xx / jp nn' sequences can in theory also be used as calls but these are not skipped by this command.} proc step_over {} { set address [reg PC] set l [debug disasm $address] set instr [lindex $l 0] if {[string match "call*" $instr] || [string match "rst*" $instr] || [string match "ldir*" $instr] || [string match "cpir*" $instr] || [string match "inir*" $instr] || [string match "otir*" $instr] || [string match "lddr*" $instr] || [string match "cpdr*" $instr] || [string match "indr*" $instr] || [string match "otdr*" $instr] || [string match "halt*" $instr]} { run_to [expr {$address + [llength $l] - 1}] } else { debug step } } # # step_back # set_help_text step_back \ {Step back. Go back in time till right before the last instruction was executed. Note that this operation is relatively slow (compared to the other step functions). Also the reverse feature must be enabled for this to work (normally it's enabled by default).} proc step_back {} { # In the past this proc was implemented totally different. It's worth # mentioning this old algorithm and explain why it wasn't good enough. # The old algorithm went like this: # - take small steps back till we're not at the start instruction # anymore (this works because 'reverse goto' only stops after # emulating a full instruction) # The problem was that on R800 it could take _many_ (more than 80) # steps till the destination was reached. # # The current algorithm goes like this: # - take a large step back # - take small steps forward till we're back at the start # - we now know where the previous instruction started, so go there # (= take a small step back again) # # So the old algorithm takes (potentially) many backwards steps. While # the new algorithm takes exactly 2 backwards steps and (potentially) # many forward steps. In the current openMSX implementation, (small) # forward steps are orders of magnitude faster than backwards steps (an # optimization I added specifically for this use case). So the worst # execution time should now be much better. # 'z80' or 'r800' set cpu [get_active_cpu] # Get duration of one CPU cycle. set cycle_period [expr {1.0 / [machine_info ${cpu}_freq]}] # (Overestimation) for the maximum instruction length. # On Z80 the slowest instruction is probably 'EX (SP),IX' (25 cycles). # On R800 it's probably some I/O instruction to the VDP, followed by # a memory refresh (up to 87(!) cycles). I added some extra cycles as # a safety margin in case I forgot some extra penalty cycles (e.g. # access to a device that inserts extra wait cycles). set max_instr_len [expr {(($cpu eq "z80") ? 35 : 100) * $cycle_period}] # Get time of the start instruction. set start [dict get [reverse status] "current"] # Go back till a moment that's certainly before the start instruction. reverse goback -novideo $max_instr_len set curr [dict get [reverse status] "current"] if {$curr >= $start} { error "Internal error: initial step-back was not big enough" } # Take small steps (forward) till we again reach the start instruction. while {1} { # Note that 'reverse goto' for a small forward step is # orders of magnitudes faster than a backwards 'reverse goto'. # The '-novideo' flag is required to not (temporarily # internally) step back a few video frames (so that immediately # after 'reverse goto' we have the correct video output). # Also note that this may take a bigger step forward than # requested: it will only stop after a complete instruction is # emulated. reverse goto -novideo [expr {$curr + $cycle_period}] set next [dict get [reverse status] "current"] if {$next > $start} { error "Internal error: overshot destination" } if {$next == $start} break set curr $next } # The previous step was the correct one, so go back there. # Note that (only here) we don't pass the '-novideo' flag reverse goto $curr } # # skip one instruction # set_help_text skip_instruction \ {Skip the current instruction. In other words increase the program counter with the length of the current instruction.} proc skip_instruction {} { set pc [reg pc] reg pc [expr {$pc + [llength [debug disasm $pc]] - 1}] } namespace export peek namespace export peek8 namespace export peek_u8 namespace export peek_s8 namespace export peek16 namespace export peek16_LE namespace export peek16_BE namespace export peek_u16 namespace export peek_u16_LE namespace export peek_u16_BE namespace export peek_s16 namespace export peek_s16_LE namespace export peek_s16_BE namespace export poke namespace export poke8 namespace export poke16 namespace export poke16_LE namespace export poke16_BE namespace export dpoke namespace export disasm namespace export run_to namespace export step_over namespace export step_back namespace export step_out namespace export step_in namespace export step namespace export skip_instruction } ;# namespace disasm namespace import disasm::* openMSX-RELEASE_0_12_0/share/scripts/_example_tools.tcl000066400000000000000000000163701257557151200227550ustar00rootroot00000000000000# Return the content of the MSX screen as a string (text-modes only) set_help_text get_screen \ {Returns the content of the MSX screen as a string (only works for text-modes). } proc get_screen {} { set mode [get_screen_mode_number] switch -- $mode { 0 { set addr 0 set width [expr {([debug read "VDP regs" 0] & 0x04) ? 80 : 40}] } 1 { set addr 6144 set width 32 } default { error "Screen mode $mode not supported" } } # scrape screen and build string set screen "" for {set y 0} {$y < 24} {incr y} { set line "" for {set x 0} {$x < $width} {incr x} { append line [format %c [vpeek $addr]] incr addr } append screen [string trim $line] "\n" } return [string trim $screen] } #*********************************************** #* Basic Reader (version 0.1) #* #* A special thanks to Vincent van Dam for #* giving me permission to translate his #* script into Tcl #*********************************************** set_help_text listing \ {Interpret the content of the memory as a BASIC program and return the equivalent output of the BASIC LIST command. (May not be terribly useful, but it does show the power of openMSX scripts ;-) } proc listing {} { set tokens1 [list \ "" "END" "FOR" "NEXT" "DATA" "INPUT" "DIM" "READ" "LET" \ "GOTO" "RUN" "IF" "RESTORE" "GOSUB" "RETURN" "REM" "STOP" \ "PRINT" "CLEAR" "LIST" "NEW" "ON" "WAIT" "DEF" "POKE" "CONT" \ "CSAVE" "CLOAD" "OUT" "LPRINT" "LLIST" "CLS" "WIDTH" "ELSE" \ "TRON" "TROFF" "SWAP" "ERASE" "ERROR" "RESUME" "DELETE" \ "AUTO" "RENUM" "DEFSTR" "DEFINT" "DEFSNG" "DEFDBL" "LINE" \ "OPEN" "FIELD" "GET" "PUT" "CLOSE" "LOAD" "MERGE" "FILES" \ "LSET" "RSET" "SAVE" "LFILES" "CIRCLE" "COLOR" "DRAW" "PAINT" \ "BEEP" "PLAY" "PSET" "PRESET" "SOUND" "SCREEN" "VPOKE" \ "SPRITE" "VDP" "BASE" "CALL" "TIME" "KEY" "MAX" "MOTOR" \ "BLOAD" "BSAVE" "DSKO$" "SET" "NAME" "KILL" "IPL" "COPY" "CMD" \ "LOCATE" "TO" "THEN" "TAB(" "STEP" "USR" "FN" "SPC(" "NOT" \ "ERL" "ERR" "STRING$" "USING" "INSTR" "" "VARPTR" "CSRLIN" \ "ATTR$" "DSKI$" "OFF" "INKEY$" "POINT" ">" "=" "<" "+" "-" "*" \ "/" "^" "AND" "OR" "XOR" "EQV" "IMP" "MOD" "\\" "" "" \ "{escape-code}"] set tokens2 [list \ "" "LEFT$" "RIGHT$" "MID$" "SGN" "INT" "ABS" "SQR" "RND" "SIN" \ "LOG" "EXP" "COS" "TAN" "ATN" "FRE" "INP" "POS" "LEN" "STR$" \ "VAL" "ASC" "CHR$" "PEEK" "VPEEK" "SPACE$" "OCT$" "HEX$" \ "LPOS" "BIN$" "CINT" "CSNG" "CDBL" "FIX" "STICK" "STRIG" "PDL" \ "PAD" "DSKF" "FPOS" "CVI" "CVS" "CVD" "EOF" "LOC" "LOF" "MKI$" \ "MKS$" "MKD$" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" \ "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" \ "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" \ "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""] # Loop over all lines set listing "" for {set addr [peek16 0xf676]} {[peek16 $addr] != 0} {} { append listing [format "0x%x > " $addr] incr addr 2 append listing "[peek16 $addr] " incr addr 2 # Loop over tokens in one line while {true} { set token [peek $addr]; incr addr if {0x80 < $token && $token < 0xff} { set t [lindex $tokens1 [expr {$token - 0x80}]] } elseif {$token == 0xff} { set t [lindex $tokens2 [expr {[peek $addr] - 0x80}]] incr addr } elseif {$token == 0x3a} { switch -- [peek $addr] { 0xa1 {set t "ELSE"; incr addr} 0x8f {set t "'" ; incr addr} default {set t ":" } } } elseif {$token == 0x0} { break } elseif {$token == 0x0B} { set t [format "&O%o" [peek16 $addr]] incr addr 2 } elseif {$token == 0x0C} { set t [format "&H%X" [peek16 $addr]] incr addr 2 } elseif {$token == 0x0D} { # line number (stored as address) set t [format "0x%x" [expr {[peek16 $addr] + 1}]] incr addr 2 } elseif {$token == 0x0E} { set t [format "%d" [peek16 $addr]] incr addr 2 } elseif {$token == 0x0F} { set t [format "%d" [peek $addr]] incr addr } elseif {$token == 0x1C} { set t [format "%d" [peek16 $addr]] incr addr 2 } elseif {$token == 0x1D} { set t "(TODO: Single)" incr addr } elseif {$token == 0x1F} { set t "(TODO: Double)" incr addr } elseif {0x11 <= $token && $token <= 0x1a} { set t [expr {$token - 0x11}] } else { set t [format "%c" $token] } append listing $t } append listing "\n" } return $listing } set_help_text get_color_count \ "Gives some statistics on the used colors of the currently visible screen. Does not take into account sprites, screen splits and other trickery. Options: -sort , where is either 'color' (default) or 'amount' -reverse, to reverse the sorting order -all, to also include colors that have a count of zero " proc get_color_count {args} { set result "" set sortindex 0 set sortorder "-increasing" set showall false # parse options while {1} { switch -- [lindex $args 0] { "" break "-sort" { set sortfield [lindex $args 1] if {$sortfield eq "color"} { set sortindex 0 } elseif {$sortfield eq "amount"} { set sortindex 1 } else { error "Unsupported sort field: $sortfield" } set args [lrange $args 2 end] } "-reverse" { set sortorder "-decreasing" set args [lrange $args 1 end] } "-all" { set showall true set args [lrange $args 1 end] } "default" { error "Invalid option: [lindex $args 0]" } } } set mode [get_screen_mode_number] switch -- $mode { 5 { set nofbytes_per_line 128 set nofpixels_per_byte 2 set page_size [expr {32 * 1024}] } 6 { set nofbytes_per_line 128 set nofpixels_per_byte 4 set page_size [expr {32 * 1024}] } 7 { set nofbytes_per_line 256 set nofpixels_per_byte 2 set page_size [expr {64 * 1024}] } 8 { set nofbytes_per_line 256 set nofpixels_per_byte 1 set page_size [expr {64 * 1024}] } default { error "Screen mode $mode not supported (yet)" } } set page [expr {([debug read "VDP regs" 2] & 96) >> 5}] set noflines [expr {([debug read "VDP regs" 9] & 128) ? 212 : 192}] set bpp [expr {8 / $nofpixels_per_byte}] set nrcolors [expr {1 << $bpp}] append result "Counting pixels of screen $mode, page $page with $noflines lines...\n" # get bytes into large list set offset [expr {$page_size * $page}] set nrbytes [expr {$noflines * $nofbytes_per_line}] binary scan [debug read_block VRAM $offset $nrbytes] c* myvram # analyze pixels set pixelstats [dict create] for {set p 0} {$p < $nrcolors} {incr p} { dict set pixelstats $p 0 } set mask [expr {$nrcolors - 1}] foreach byte $myvram { for {set pixel 0} {$pixel < $nofpixels_per_byte} {incr pixel} { set color [expr {($byte >> ($pixel * $bpp)) & $mask}] dict incr pixelstats $color } } # convert to list set pixelstatlist [list] dict for {key val} $pixelstats { if {$showall || ($val != 0)} { lappend pixelstatlist [list $key $val] } } set pixelstatlistsorted [lsort -integer $sortorder -index $sortindex $pixelstatlist] # print results set number 0 set colorwidth [expr {($mode == 8) ? 3 : 2}] set palette "" foreach pixelinfo $pixelstatlistsorted { incr number lassign $pixelinfo color amount if {$mode != 8} { set palette " ([getcolor $color])" } append result [format "%${colorwidth}d: color %${colorwidth}d$palette: amount: %6d\n" $number $color $amount] } return $result } openMSX-RELEASE_0_12_0/share/scripts/_filepool.tcl000066400000000000000000000056201257557151200217070ustar00rootroot00000000000000namespace eval filepool { set_help_text filepool \ {Manage filepool settings filepool list Shows the currently defined filepool entries. filepool add -path -types [-position ] Add a new entry. Each entry must have a directory and a list of filetypes. Possible filetypes are 'system_rom', 'rom', 'disk' and 'tape'. Optionally you can specify the position of this new entry in the list of existing entries (by default new entries are added at the end). filepool remove Remove the filepool entry at the given position. filepool reset Reset the filepool settings to the default values. } proc filepool_completion {args} { if {[llength $args] == 2} { return [list list add remove reset] } return [list -path -types -position system_rom rom disk tape] } set_tabcompletion_proc filepool [namespace code filepool_completion] proc filepool {args} { set cmd [lindex $args 0] set args [lrange $args 1 end] switch -- $cmd { "list" {filepool_list} "add" {filepool_add {*}$args} "remove" {filepool_remove $args} "reset" {filepool_reset} "default" { error "Invalid subcommand, expected one of 'list add remove reset', but got '$cmd'" } } } proc filepool_list {} { set result "" set i 1 foreach pool $::__filepool { append result "$i: [dict get $pool -path] \[[dict get $pool -types]\]\n" incr i } return $result } proc filepool_checktypes {types} { set valid [list "system_rom" "rom" "disk" "tape"] foreach type $types { if {$type ni $valid} { error "Invalid type, expected one of '$valid', but got '$type'" } } } proc filepool_add {args} { set pos [llength $::__filepool] set path "" set types "" foreach {name value} $args { if {$name eq "-position"} { set pos [expr {$value - 1}] } elseif {$name eq "-path"} { set path $value } elseif {$name eq "-types"} { filepool_checktypes $value set types $value } else { error "Unknown option: $name" } } if {($pos < 0) || ($pos > [llength $::__filepool])} { error "Value out of range: [expr {$pos + 1}]" } if {$path eq ""} { error "Missing -path" } if {$types eq ""} { error "Missing -types" } set newpool [dict create -path $path -types $types] if {$pos == [llength $::__filepool]} { lappend ::__filepool $newpool } else { set ::__filepool [lreplace $::__filepool $pos -1 $newpool] } return "" } proc filepool_remove {id} { if {($id < 1) || ($id > [llength $::__filepool])} { error "Value out of range: $id" } set idx [expr {$id - 1}] set ::__filepool [lreplace $::__filepool $idx $idx] return "" } proc filepool_reset {} { unset ::__filepool } proc get_paths_for_type {type} { set result [list] foreach pool $::__filepool { set types [dict get $pool -types] if {$type in $types} { lappend result [dict get $pool -path] } } return $result } namespace export filepool } ;# namespace filepool namespace import filepool::* openMSX-RELEASE_0_12_0/share/scripts/_guess_title.tcl000066400000000000000000000120041257557151200224170ustar00rootroot00000000000000namespace eval guess_title { set_help_text guess_title \ {Guess the title of the currently running software. Remember... it's only a guess! It will be wrong some times. (But it will be right in many cases.) } # Here are some cases worth to consider and test. # * FM-PAC as extension and a ROM game slot 2. You don't want to get the FM-PAC # returned when it's used as FM module, but you do when you did _FMPAC and use # the internal software of it. # * Rollerball runs in page 2 (when using the proper ROM type) # * Philips Music Module, start up with ESC. You don't want to return the ROM # as 'running software', but when you did not press ESC, you do. # * Koei games like Teitoku no Ketsudan. In combination with an FM-PAC in slot # 1. This games seems to run from RAM mostly. # * Tape converted to cart. Runs in RAM. # * SCC extension in combination with a disk game/demo. Runs with an empty ROM # which you don't want to return as title. # * Sony HB-75P with internal software (Personal Data Bank). Runs in page 2. # * MSX-DOS # * MSX-DOS 2 (all pages are RAM) # this one checks on the checkpage for external or internal software proc guess_rom_title_z80space {internal checkpage} { lassign [get_selected_slot $checkpage] ps ss if {$ss eq "X"} {set ss 0} set incorrectslottype [machine_info isexternalslot $ps $ss] if {$internal} { set incorrectslottype [expr {!$incorrectslottype}] } if {$incorrectslottype} { foreach device [machine_info slot $ps $ss $checkpage] { set type [lindex [machine_info device $device] 0] # try to ignore RAM devices if {$type ne "RAM" && $type ne "MemoryMapper" && $type ne "PanasonicRAM"} { return $device } } } return "" } proc guess_rom_title_nonextension {} { set system_rom_paths [filepool::get_paths_for_type system_rom] # Loop over all external slots which contain a ROM, return the first # which is not located in one of the systemrom filepools. for {set ps 0} {$ps < 4} {incr ps} { for {set ss 0} {$ss < 4} {incr ss} { if {![machine_info isexternalslot $ps $ss]} continue foreach device [machine_info slot $ps $ss 1] { set path [lindex [machine_info device $device] 3] if {$path eq ""} continue set ok 1 foreach syspath $system_rom_paths { if {[string first $syspath $path] == 0} { set ok 0; break } } if {$ok} {return $device} } } } return "" } proc guess_rom_title_naive {} { for {set ps 0} {$ps < 4} {incr ps} { for {set ss 0} {$ss < 4} {incr ss} { if {[machine_info isexternalslot $ps $ss]} { set device_list [machine_info slot $ps $ss 1] if {[llength $device_list] != 0} { return $device_list } } } } return "" } proc guess_disk_title {drive_name} { # check name of the diskimage (remove directory part and extension) set disk "" catch {set disk [lindex [$drive_name] 1]} return [file rootname [file tail $disk]] } proc guess_cassette_title {} { # check name of the cassette image (remove directory part and extension) set cassette "" catch {set cassette [lindex [cassetteplayer] 1]} return [file rootname [file tail $cassette]] } proc guess_title {{fallback ""}} { # first try to see what is actually mapped in Z80 space # that is often correct, if it gives a result... # but it doesn't give a result for ROMs that copy themselves to RAM # (e.g. Koei games, tape games converted to ROM, etc.). set result [guess_rom_title_z80space false 1] if {$result ne ""} {return $result} # then try disks # games typically run from drive A, almost never from another drive set title [guess_disk_title "diska"] if {$title ne ""} {return $title} # then try cassette set title [guess_cassette_title] if {$title ne ""} {return $title} # if that doesn't give a result, try non extension devices set result [guess_rom_title_nonextension] if {$result ne ""} {return $result} # if that doesn't give a result, just return the first thing we find in # an external slot # ... this doesn't add much to the nonextension version # set result [guess_rom_title_naive] # perhaps we should simply return internal software if nothing found yet # Do page 1 last, because BASIC is in there set result [guess_rom_title_z80space true 3] if {$result ne ""} {return $result} set result [guess_rom_title_z80space true 2] if {$result ne ""} {return $result} set result [guess_rom_title_z80space true 1] if {$result ne ""} {return $result} # guess failed, return fallback return $fallback } # use this internal proc if you only want to guess ROM titles proc guess_rom_title {} { set result [guess_rom_title_z80space false 1] if {$result ne ""} {return $result} # if that doesn't give a result, try non extension devices set result [guess_rom_title_nonextension] if {$result ne ""} {return $result} # if that doesn't give a result, just return the first thing we find in # an external slot # ... this doesn't add much to the nonextension version set result [guess_rom_title_naive] return $result } namespace export guess_title namespace export guess_rom_title } ;# namespace guess_title namespace import guess_title::* openMSX-RELEASE_0_12_0/share/scripts/_info_panel.tcl000066400000000000000000000123471257557151200222140ustar00rootroot00000000000000# Shows an info panel similar to the blueMSX DIGIblue theme # # TODO: # - make layout more flexible? # - make sure it doesn't interfere with LEDs? namespace eval info_panel { variable info_panel_active false variable textheight 15 variable panel_margin 2 variable sub_panel_height [expr {$textheight * 2 + $panel_margin}] variable panel_info proc info_panel_init {} { variable info_panel_active variable textheight variable panel_margin variable sub_panel_height variable panel_info set panel_info [dict create \ software [dict create \ title "Running software" width 400 row 0 \ method guess_title] \ mapper [dict create \ title "Mapper type" width 170 row 0 \ method {set val ""; catch {set val [dict get [openmsx_info romtype [lindex [machine_info device [guess_title]] 1]] description]}; set val}] \ fps [dict create \ title "FPS" width 38 row 1 \ method {format "%2.1f" [openmsx_info fps]}] \ screen [dict create \ title "Screen" width 50 row 1 \ method {get_screen_mode}] \ vram [dict create \ title "VRAM" width 42 row 1 \ method {format "%dkB" [expr {[debug size "physical VRAM"] / 1024}]}] \ ram [dict create \ title "RAM" width 51 row 1 \ method {set ramsize 0; foreach device [debug list] {set desc [debug desc $device]; if {$desc eq "memory mapper" || $desc eq "ram"} {incr ramsize [debug size $device]}}; format "%dkB" [expr {$ramsize / 1024}]}] \ mtime [dict create \ title "Time" width 60 row 1 \ method {utils::get_machine_time}] \ speed [dict create \ title "Speed" width 48 row 1 \ method {format "%d%%" [expr {round([get_actual_speed] * 100)}]}] \ machine [dict create \ title "Machine name and type" width 250 row 1 \ method { set name [utils::get_machine_display_name] if {[catch { set type [dict get [openmsx_info machines [machine_info config_name]] type] set result [format "%s (%s)" $name $type] }]} { set result $name } set result }] \ ] # calc width of software item set software_width 0 dict for {name info} $panel_info { if {[dict get $info row] == 1} { incr software_width [dict get $info width] incr software_width $panel_margin } elseif {$name ne "software"} { incr software_width -[dict get $info width] incr software_width -$panel_margin } } incr software_width -$panel_margin dict set panel_info software width $software_width # set base element osd create rectangle info_panel \ -x $panel_margin \ -y [expr {-($sub_panel_height * 2 + (2 * $panel_margin))}] \ -rely 1.0 \ -alpha 0 # create subpanels set curpos [dict create 0 0 1 0] dict for {name info} $panel_info { set row [dict get $info row] create_sub_panel $name \ [dict get $info title] \ [dict get $info width] \ [dict get $info row] \ [dict get $curpos $row] dict incr curpos $row [expr {[dict get $info width] + $panel_margin}] } } proc create_sub_panel {name title width row pos} { variable textheight variable panel_margin variable sub_panel_height osd create rectangle info_panel.$name \ -x $pos \ -y [expr {($sub_panel_height + $panel_margin) * $row}] \ -h $sub_panel_height \ -w $width \ -rgba 0x00005080 \ -clip true osd create text info_panel.$name.title \ -x 2 \ -y 0 \ -rgba 0xddddffff \ -text $title \ -size [expr {int($textheight * 0.8)}] osd create text info_panel.$name.value \ -x 2 \ -y $textheight \ -rgba 0xffffffff \ -text $name \ -size [expr {int($textheight * 0.9)}] } proc update_info_panel {} { variable info_panel_active variable panel_info if {!$info_panel_active} return dict for {name info} $panel_info { osd configure info_panel.$name.value -text [eval [dict get $info method]] } after realtime 1 [namespace code update_info_panel] } proc toggle_info_panel {} { variable info_panel_active if {$info_panel_active} { set info_panel_active false osd destroy info_panel } else { set info_panel_active true info_panel_init update_info_panel } return "" } ## Stuff to calculate the actual speed (could be made public later) # We keep two past data points (a data point consist out of a measured # emutime and realtime). These two data points are at least 1 second # apart in realtime. variable last_emutime1 -1.0 variable last_realtime1 -1.0 variable last_emutime2 0.0 variable last_realtime2 0.0 proc get_actual_speed {} { variable last_emutime1 variable last_emutime2 variable last_realtime1 variable last_realtime2 set curr_emutime [machine_info time] set curr_realtime [openmsx_info realtime] set diff_realtime [expr {$curr_realtime - $last_realtime2}] if {$diff_realtime > 1.0} { # Younger data point is old enough. Drop older data point and # make current measurement a new data point. set last_emutime $last_emutime2 set last_emutime1 $last_emutime2 set last_emutime2 $curr_emutime set last_realtime1 $last_realtime2 set last_realtime2 $curr_realtime } else { # Take older data point, don't change recorded data. set last_emutime $last_emutime1 set diff_realtime [expr {$curr_realtime - $last_realtime1}] } return [expr {($curr_emutime - $last_emutime) / $diff_realtime}] } namespace export toggle_info_panel } ;# namespace info_panel namespace import info_panel::* openMSX-RELEASE_0_12_0/share/scripts/_metal_gear_overlay.tcl000066400000000000000000000141141257557151200237350ustar00rootroot00000000000000# This script is based on: # openMSX metal gear overlay script 2014/12/07 # By Dunnius / Bifi / Vampier namespace eval metal_gear_overlay { variable num_enemies 11 variable max_hp variable punch_max_hp variable currentfield # the map feature is disabled here in code, because it isn't very useful yet variable map_feature_enabled false variable metal_gear_overlay_active false variable enemy_names [list \ "" "01" "Bridge Segment" "Ellen Screaming 'help me'" "Soldier - Pattern 1" \ "Soldier - Pattern 2" "Camera" "Landmine" "Gas cloud" "Enemy - Tank" \ "Soldier - (Alert)" "Soldier - Red (Alert)" "Tank Shell" \ "Soldier - Shooting" "Soldier - Guard" "Roller" "Trap Floor" "Metal Gear" \ "Enemy - Bulldozer" "Soldier - Truck" "Soldier - Flying" \ "Soldier - Fly to the Switch" "Soldier - Flying (Alert)" "Unknown3" \ "Soldier - Run to the Switch" "Guard Dog" "Mr. Arnold" "Basement Guard Dogs" \ "Soldier - Guard to building 3" "Event - Dogs will appear" "Soldier - Pattern 3" \ "Scorpion" "Big Boss" "Shoot Gunner" "Machine Gun Kid" "Laser fence" \ "Fire Trooper" "Fire ball" "Hind-D" "Event - Incoming tank shells" \ "Event - Guards will switch shifts" "Coward Duck" "Unknown8" \ "Shoot Gunner - Bullet" "Power Switch" "Event - Snake Gets Captured" \ "Event - Access to building 2" "Bullet 1" "Soldier - Stationary Guard" \ "POW" "Ellen" "Grey Fox" "Dr. Petrovich" "Laser Camera" "36" "Fake Dr. Petrovich" \ "Unknown9" "Soldier - Silencer Guards" "Unknown11" "Bullet 2" \ "Machine Gun Kid - Bullet" "Bullet 3" "Tank - Bullet" "Boomerang" "Zzz" \ "Unknown13" "Unknown14" "Unknown15" "44" "45" "46" "47" "48" "49" "4A" \ "4B" "4C" "4D" "4E" "4F" "50" "51" "52" "53" "54" "55" "56" "57" "58" \ "59" "5A" "5B" "5C" "5D" "5E" "5F" "60" "61" "62" "63" "64" "65" "66" ] set_help_text toggle_metal_gear_overlay \ "Shows OSD widgets that help you play Metal Gear. Only useful if you are actually running this game... If you are not, you may get a lot of weird stuff on your screen :) It mostly shows Snake and enemy properties on screen and events that will happen." proc init {} { variable num_enemies variable max_hp variable punch_max_hp variable currentfield variable map_feature_enabled # Create OSD elements osd_widgets::msx_init metal_gear for {set i 0} {$i < $num_enemies} {incr i} { osd_widgets::create_power_bar metal_gear.powerbar$i 16 2 0xff0080b0 0x00000080 0xffffffC0 osd_widgets::create_power_bar metal_gear.punchbar$i 16 2 0x0080ffb0 0x00000080 0xffffffC0 set max_hp($i) 0 set punch_max_hp($i) 0 } osd_widgets::create_power_bar metal_gear.powerbarsnake 24 2 0x00ff00b0 0x00000080 0xffffffC0 osd_widgets::create_power_bar metal_gear.boss 24 8 0x00ff00b0 0x00000080 0xffffffC0 set currentfield 0 if {$map_feature_enabled} { for {set y 0} {$y < 16} {incr y} { for {set x 0} {$x < 16} {incr x} { set field [expr {$x + ($y * 16)}] osd create rectangle metal_gear.field$field -x [expr {($x * 3) + 1}] -y [expr {($y * 3) + 1}] -relw 2 -relh 2 -rgba 0x0000ff80 } } } # Start the overlay refresh update_overlay } proc update_impl {} { variable max_hp variable punch_max_hp variable num_enemies variable enemy_names variable currentfield variable map_feature_enabled # check for field change set newfield [peek 0xC130] if {$currentfield != $newfield} { set previousfield $currentfield set currentfield $newfield # clear out old HP values if field has changed for {set i 0} {$i < $num_enemies} {incr i} { set punch_max_hp($i) 0 set max_hp($i) 0 } if {$map_feature_enabled} { osd configure metal_gear.field$previousfield -rgba 0x00ff0080 osd configure metal_gear.field$currentfield -rgba 0xffffff80 } } for {set i 0; set addr 0xD000} {$i < $num_enemies} {incr i; incr addr 0x80} { set enemy_type [peek $addr] set enemy_hp [peek [expr {$addr + 0x0D}]] if {$enemy_type > 0 && $enemy_hp > 0} { set pos_x [peek [expr {$addr + 5}]] set pos_y [peek [expr {$addr + 3}]] if {$enemy_hp > $max_hp($i)} { set max_hp($i) $enemy_hp } set power [expr {1.00 * $enemy_hp / $max_hp($i)}] set text "HP: $enemy_hp $i [lindex $enemy_names $enemy_type] - #$enemy_type" osd_widgets::update_power_bar metal_gear.powerbar$i $pos_x $pos_y $power $text # only show the punch bar when the enemy can be punched set enemy_punch_hp [expr {3 - [peek [expr {$addr + 0x7A}]]}] if {$enemy_punch_hp > 0} { if {$enemy_punch_hp > $punch_max_hp($i)} { set punch_max_hp($i) $enemy_punch_hp } set punch_power [expr {1.00 * $enemy_punch_hp / $punch_max_hp($i)}] osd_widgets::update_power_bar metal_gear.punchbar$i $pos_x [expr {$pos_y + 5}] $punch_power "" } else { osd_widgets::update_power_bar metal_gear.punchbar$i -50 -50 0 "" } } else { osd_widgets::update_power_bar metal_gear.powerbar$i -50 -50 0 "" osd_widgets::update_power_bar metal_gear.punchbar$i -50 -50 0 "" } } # get stats for snake set snake_hp [peek 0xC131] set power [expr {$snake_hp / 48.0}] set poisoned [peek 0xC139] set pos_x [peek 0xC184] set pos_y [peek 0xc182] set text "" if {$poisoned == 1} { set power 0 set text "POISONED " } append text "($pos_x,$pos_y) HP: $snake_hp" osd_widgets::update_power_bar metal_gear.powerbarsnake [expr {$pos_x - 13}] [expr {$pos_y + 20}] $power $text } proc update_overlay {} { variable metal_gear_overlay_active if {!$metal_gear_overlay_active} return # check if not in demo or weapon/item screen set c012 [peek 0xC012] if {$c012 == 0x00 || $c012 == 0x47 || $c012 == 0x50 || [peek 0xC151] != 0} { osd configure metal_gear -y 999 } else { osd configure metal_gear -y 0 update_impl } after frame [namespace code update_overlay] } proc toggle_metal_gear_overlay {} { variable metal_gear_overlay_active set metal_gear_overlay_active [expr {!$metal_gear_overlay_active}] if {$metal_gear_overlay_active} { init set text "Metal Gear overlay activated!" } else { osd destroy metal_gear set text "Metal Gear overlay deactivated." } osd::display_message $text info return $text } namespace export toggle_metal_gear_overlay };# namespace metal_gear_overlay namespace import metal_gear_overlay::* openMSX-RELEASE_0_12_0/share/scripts/_mog-overlay.tcl000066400000000000000000000244351257557151200223440ustar00rootroot00000000000000namespace eval mog_overlay { variable num_enemies 16 variable num_rocks 8 variable mog_overlay_active false variable mog_editor_active false variable item_cache variable tomb_cache variable ladder_cache variable wall_cache variable demon_cache variable ep variable mouse1_pressed # Demon summon spells variable spell [list \ "" "YOMAR" "ELOHIM" "HAHAKLA" "BARECHET" "HEOTYMEO" \ "LEPHA" "NAWABRA" "ASCHER" "XYWOLEH" "HAMALECH"] # Demon Max Power variable demon_power [list -1 -1 24 40 64 112 72 56 64 80 80 248] # Item names variable items [list \ "" "Arrow" "Ceramic Arrow" "Rolling Fire" "Fire" "Mine" \ "Magnifying Glass" "Holy Water" "Cape" "Magical Rod" "World Map" \ "Cross" "Great Key" "Necklace" "Crown" "Helm" "Oar" "Boots" "Doll" \ "Robe" "Bell" "Halo" "Candle" "Armor" "Carpet" "Helmet" "Lamp" "Vase" \ "Pendant" "Earrings" "Bracelet" "Ring" "Bible" "Harp" "Triangle" \ "Trumpet Shell" "Pitcher" "Sabre" "Dagger" "Feather" "Bronze Shield" \ "Bread and Water" "Salt" "Silver Shield" "Gold Shield" \ "Quiver of Arrows" "Coins" "Keys"] variable enemy_names [list \ "" "Gorilla" "Twinkle" "HorBlob" "Gate" "Fire Snake" "06" "Ring Worm" \ "08" "09" "Knight" "Water Strider" "Sparky" "Fish" "Bat" "Pacman" \ "Insect" "Hedgehog" "Rockman" "Cloud Demon" "Mudman" "Ill" \ "Bird Dragon" "Egg Bird" "Worm" "Butterfly" "Snake's Fire" "Fireball" \ "1C" "1D" "1E" "Goblin" "20" "Crawler" "Pea Shooter" "Trapwall" "Swine" \ "Bones" "Living Helmet" "Owl" "Ectoplasm" "29" "Poltergeist" "Wizard" \ "Shoe 1" "Frost Demon" "Bamboo Shoot" "Frog Plant" "Seahorse Demon" \ "31" "32" "Armor's Dart" "Bally" "35" "36" "Great Butterfly" "VerBlob" \ "39" "3A" "Moai Head Projectile" "Trickster Ghost" "Star" \ "Flocking Bird" "3F" "Cyclop's Ghost" "41" "Maner" "Gero" "44" "45" \ "46" "47" "48" "Huge Bat" "4A" "4B" "Fuzzball" "4D" "4E" "4F" "50" "51" \ "Middle Bat" "Mini Bat" "Bone" "Small Bone" "Small Fireball" "57" \ "Crab's Breath" "Seed" "5A" "5B" "Spiral Ball " "5D" "5E" "5F" "Maner's Arm" "61" \ "Gero's Tongue" "63" "Shoe 2" "Breath" "66" "Protectors"] set_help_text toggle_mog_overlay \ "Shows OSD widgets that help you play The Maze of Galious. Only useful if you are actually running this game... If you are not, you may get a lot of weird stuff on your screen :) The script shows the Great Demon summon spells, ladders which will disappear, walls which will grow, hitpoints of enemies, hidden items, etc.\nSee also toggle_mog_editor." set_help_text toggle_mog_editor \ "Gives you mouse bindings to fool around in The Maze of Galious. Only useful if you are actually running this game... If you are not, you may get a lot of weird stuff on your screen :) With this editor, you can draw walls with one mouse button and put Popolon on your position of choice with the other. Needs the MoG overlay to work, see toggle_mog_overlay." #Init Overlays proc init {} { variable num_enemies variable num_rocks variable label_color variable label_text_color variable item_cache variable tomb_cache variable ladder_cache variable wall_cache variable demon_cache variable max_ep osd_widgets::msx_init mog for {set i 0} {$i < $num_enemies} {incr i} { osd_widgets::create_power_bar mog.powerbar$i 14 2 0xff0000ff 0x00000080 0xffffffff } osd_widgets::create_power_bar mog.item 16 16 0x00000000 0xff770020 0xffffffff set item_cache 0 osd create rectangle mog.tomb -rely 999 -w 16 -h 16 -rgba 0x00000080 osd create text mog.tomb.text -x 1 -y 1 -size 5 -rgb 0xffffff set tomb_cache 0 osd create rectangle mog.ladder -rely 999 -relw 16 -rgba 0xffffff80 set ladder_cache 0 osd create rectangle mog.wall -rely 999 -relw 8 -relh 24 -rgba 0x00ffff80 set wall_cache 0 osd create rectangle mog.wall2 -rely 999 -relw 32 -relh 24 -rgba 0x00000080 osd create text mog.wall2.text -x 1 -y 1 -size 4 -rgb 0xffffff for {set i 0} {$i < $num_rocks} {incr i} { osd create rectangle mog.rock$i -rely 999 -w 24 -h 4 -rgba 0x0000ff80 osd create text mog.rock$i.text -x 0 -y 0 -size 4 -rgb 0xffffff } osd_widgets::create_power_bar mog.demon 48 4 0x2bdd2bff 0x00000080 0xffffffff set demon_cache 0 # Enemy Power for {set i 0} {$i < $num_enemies} {incr i} {set max_ep($i) 0} } # Update the overlays proc update_overlay {} { variable mog_overlay_active variable num_enemies variable num_rocks variable label_color variable label_text_color variable item_cache variable tomb_cache variable ladder_cache variable wall_cache variable demon_cache variable spell variable demon_power variable items variable enemy_names variable max_ep if {!$mog_overlay_active} return #see if not in demo if {[peek 0xe002] != 64} {osd configure mog -y 999} else {osd_widgets::msx_update mog} for {set i 0; set addr 0xe800} {$i < $num_enemies} {incr i; incr addr 0x20} { set enemy_type [peek $addr] set enemy_hp [peek [expr {$addr + 0x10}]] # If enemy's power is 0 set max power to 0 if {$enemy_hp == 0 && $max_ep($i) > 0} {set max_ep($i) 0} if {$enemy_type > 0 && $enemy_hp > 0 && $enemy_hp < 200} { #Set Max Power Recorded for Enemy if {$max_ep($i) < $enemy_hp} {set max_ep($i) $enemy_hp} set pos_x [expr { 8 + [peek [expr {$addr + 7}]]}] set pos_y [expr {-6 + [peek [expr {$addr + 5}]]}] set power [expr {1.00 * $enemy_hp / $max_ep($i)}] set text [format "%s (%d): %d/%d" [lindex $enemy_names $enemy_type] $i $enemy_hp $max_ep($i)] osd_widgets::update_power_bar mog.powerbar$i $pos_x $pos_y $power $text } else { osd_widgets::hide_power_bar mog.powerbar$i } } set new_item [peek 0xe740] if {$new_item != $item_cache} { set item_cache $new_item if {$item_cache} { set height [expr {($item_cache < 7) ? 8 : 16}] osd configure mog.item -relh $height osd configure mog.item.text -text [lindex $items $item_cache] osd configure mog.item -relx [peek 0xe743] -rely [peek 0xe742] } else { osd configure mog.item -rely 999 } } set new_tomb [peek 0xe678] if {$new_tomb != $tomb_cache} { set tomb_cache $new_tomb if {($tomb_cache > 0) && ($tomb_cache <= 10)} { osd configure mog.tomb.text -text "[lindex $spell $tomb_cache]" osd configure mog.tomb -relx [peek 0xe67a] -rely [peek 0xe679] } else { osd configure mog.tomb -rely 999 } } #todo: see what the disappear trigger is (up, down, either) set new_ladder [expr {[peek 0xec00] | [peek 0xec07]}] if {$new_ladder != $ladder_cache} { set ladder_cache $new_ladder if {$ladder_cache != 0} { osd configure mog.ladder -relx [peek 0xec02] -rely [peek 0xec01] \ -relh [expr {8 * ([peek 0xec03] & 31)}] } else { osd configure mog.ladder -rely 999 } } set new_wall [peek 0xeaa0] if {$new_wall != $wall_cache} { set wall_cache $new_wall if {$wall_cache != 0} { osd configure mog.wall -relx [peek 0xeaa3] -rely [peek 0xeaa2] } else { osd configure mog.wall -rely 999 } } if {[peek 0xea20] != 0} { set text [format "Wall (%02d)" [peek 0xea23]] osd configure mog.wall2 -relx [peek 0xea22] -rely [peek 0xea21] osd configure mog.wall2.text -text $text } else { osd configure mog.wall2 -rely 999 } for {set i 0; set addr 0xec30} {$i < $num_rocks} {incr i; incr addr 8} { if {[peek $addr] > 0} { set pos_x [expr { 8 + [peek [expr {$addr + 3}]]}] set pos_y [expr {-6 + [peek [expr {$addr + 2}]]}] set rock_hp [peek [expr {$addr + 6}]] set text [format "Rock %d (%02d)" $i $rock_hp] osd configure mog.rock$i -relx $pos_x -rely $pos_y osd configure mog.rock$i.text -text $text } else { osd configure mog.rock$i -rely 999 } } if {([peek 0xe0d2] != 0) && ([peek 0xe710] != 0)} { osd configure mog.demon -relx 112 -rely 26 # let's not print text, it destroys a replay # print_mog_text 1 1 "Now Fighting [lindex $spell [expr {[peek 0xe041] - 1}]]" set new_demon [peek 0xe710] if {$new_demon != $demon_cache} { set demon_cache $new_demon set demon_max [lindex $demon_power [peek 0xe041]] if {$demon_max > 0} { set power [expr {$demon_cache * ([peek 0xe076] + 1.0) / $demon_max}] osd configure mog.demon.bar -relw $power } } } else { osd configure mog.demon -rely 999 } after frame [namespace code update_overlay] } # better don't use this if you want to keep your replay OK proc print_mog_text {x y text} { set text [string tolower $text] for {set i 0} {$i < [string length $text]} {incr i} { scan [string range $text $i $i] "%c" ascii incr ascii -86 if {$ascii >= -38 && $ascii <= -29} {incr ascii 39} if {$ascii < 0} {set ascii 0} poke [expr {0xed80 + $x + $y * 32 + $i}] $ascii } } proc toggle_mog_overlay {} { variable mog_overlay_active variable mog_editor_active set mog_overlay_active [expr {!$mog_overlay_active}] if {$mog_overlay_active} { init update_overlay osd::display_message "MoG overlay activated" info return "MoG overlay activated!" } else { set retval "" if {$mog_editor_active} { set retval "[toggle_mog_editor]\n" } osd destroy mog osd::display_message "MoG overlay deactivated" info return "${retval}MoG overlay deactivated." } } # MoG editor # Separated from MoG overlay, because the overlay is useful for TASing and the # editor will ruin your replay proc draw_block {} { osd_widgets::msx_update "mog" lassign [osd info "mog" -mousecoord] x y poke [expr {0xed80 + int($x / 8) + int(($y - 32) / 8) * 32}] 240 if {$mog_overlay::mouse1_pressed} {after frame mog_overlay::draw_block} } proc put_popolon {} { osd_widgets::msx_update "mog" lassign [osd info "mog" -mousecoord] x y poke 0xe507 [utils::clip 0 255 [expr {int($x)}]] poke 0xe505 [utils::clip 0 212 [expr {int($y)}]] } proc toggle_mog_editor {} { variable mog_editor_active variable mog_overlay_active if {!$mog_editor_active && !$mog_overlay_active} { error "You need to have the MoG overlay active to use this editor!" } set mog_editor_active [expr {!$mog_editor_active}] if {$mog_editor_active} { bind -layer mog_editor "mouse button1 down" { set mog_overlay::mouse1_pressed true mog_overlay::draw_block } bind -layer mog_editor "mouse button1 up" { set mog_overlay::mouse1_pressed false } bind -layer mog_editor "mouse button3 down" { mog_overlay::put_popolon } activate_input_layer mog_editor return "MoG editor activated!" } else { deactivate_input_layer mog_editor return "MoG editor deactivated." } return "" } namespace export toggle_mog_overlay namespace export toggle_mog_editor };# namespace mog_overlay namespace import mog_overlay::* openMSX-RELEASE_0_12_0/share/scripts/_monitor.tcl000066400000000000000000000034031257557151200215620ustar00rootroot00000000000000namespace eval monitor { variable monitors set_help_text monitor_type \ {Select a monitor color profile. Usage: monitor_type Shows the current profile monitor_type -list Show all possible types monitor_type Select new type } set_tabcompletion_proc monitor_type [namespace code tab_monitor_type] proc tab_monitor_type {args} { variable monitors set result [dict keys $monitors] lappend result "-list" } dict set monitors normal {{ 1 0 0 } { 0 1 0 } { 0 0 1 }} dict set monitors red {{ 1 0 0 } { 0 0 0 } { 0 0 0 }} dict set monitors green {{ 0 0 0 } { 0 1 0 } { 0 0 0 }} dict set monitors blue {{ 0 0 0 } { 0 0 0 } { 0 0 1 }} dict set monitors monochrome_amber {{ .257 .504 .098 } { .193 .378 .073 } { 0 0 0 }} dict set monitors monochrome_amber_bright {{ .333 .333 .333 } { .249 .249 .249 } { 0 0 0 }} dict set monitors monochrome_green {{ .129 .252 .049 } { .257 .504 .098 } { .129 .252 .049 }} dict set monitors monochrome_green_bright {{ .167 .167 .167 } { .333 .333 .333 } { .167 .167 .167 }} dict set monitors monochrome_white {{ .257 .504 .098 } { .257 .504 .098 } { .257 .504 .098 }} dict set monitors monochrome_white_bright {{ .333 .333 .333 } { .333 .333 .333 } { .333 .333 .333 }} proc monitor_type {{monitor ""}} { variable monitors if {$monitor eq ""} { dict for {type matrix} $monitors { if {$matrix eq $::color_matrix} { return $type } } return "Custom monitor type: $::color_matrix" } elseif {$monitor eq "-list"} { return [dict keys $monitors] } else { if {[dict exists $monitors $monitor]} { set ::color_matrix [dict get $monitors $monitor] } else { error "No such monitor type: $monitor" } } } namespace export monitor_type } ;# namespace monitor namespace import monitor::* openMSX-RELEASE_0_12_0/share/scripts/_multi_screenshot.tcl000066400000000000000000000011021257557151200234540ustar00rootroot00000000000000namespace eval multi_screenshot { set_help_text multi_screenshot \ {Take multiple screenshots Usage: multi_screenshot [] } proc multi_screenshot {num {base ""}} { multi_screenshot_helper 1 $num $base return "" } proc multi_screenshot_helper {acc max {base ""}} { if {$acc <= $max} { if {$base eq ""} { screenshot } else { screenshot -prefix $base } after frame "[namespace code multi_screenshot_helper] [expr {$acc + 1}] $max $base" } } namespace export multi_screenshot } ;# namespace multi_screenshot namespace import multi_screenshot::* openMSX-RELEASE_0_12_0/share/scripts/_music_keyboard.tcl000066400000000000000000000142371257557151200231020ustar00rootroot00000000000000# TODO: # - optimize by precalcing the notes for all reg values # - make a better visualisation if many channels are available (grouping?) set_help_text toggle_music_keyboard \ {Puts a music keyboard on the On-Screen-Display for each channel and each supported sound chip. It is not very practical yet if you have many sound chips in your currently running MSX. The redder a key on the keyboard is, the louder the note is played. Use the command again to remove it. Note: it cannot handle run-time insertion/removal of sound devices. It can handle changing of machines, though. Not all chips are supported yet and some channels give not so useful note output (but still it is nice to see something happening). Note that displaying these keyboard may cause quite some CPU load!} namespace eval music_keyboard { # some useful constants variable note_strings [list "C" "C#" "D" "D#" "E" "F" "F#" "G" "G#" "A" "A#" "B"] # we define these outside the proc to gain some speed (they are precalculated) variable loga [expr {log(2 ** (1 / 12.0))}] variable r3 [expr {log(440.0) / $loga - 57}] variable keyb_dict variable note_key_color variable num_notes variable machine_switch_trigger_id 0 variable frame_trigger_id 0 proc freq_to_note {freq} { variable loga variable r3 expr {($freq < 16) ? -1 : (log($freq) / $loga - $r3)} } proc keyboard_init {} { variable num_notes variable note_strings variable note_key_color variable keyb_dict variable machine_switch_trigger_id foreach soundchip [machine_info sounddevice] { # skip devices which don't have freq expressions (not implemented yet) if {[soundchip_utils::get_frequency_expr $soundchip 0] eq "x"} continue set channel_count [soundchip_utils::get_num_channels $soundchip] for {set channel 0} {$channel < $channel_count} {incr channel} { dict set keyb_dict $soundchip $channel [dict create \ freq_expr [soundchip_utils::get_frequency_expr $soundchip $channel] \ vol_expr [soundchip_utils::get_volume_expr $soundchip $channel] \ prev_note 0] } } # and now create the visualisation (keyboards) set num_octaves 9 set num_notes [expr {$num_octaves * 12}] set key_width 3 set border 1 set yborder 1 set step_white [expr {$key_width + $border}] ;# total width of white keys set keyboard_height 8 set white_key_height [expr {$keyboard_height - $yborder}] osd create rectangle music_keyboard -scaled true set channel_count 0 dict for {soundchip chip_dict} $keyb_dict { dict for {channel chan_dict} $chip_dict { osd create rectangle music_keyboard.chip${soundchip}ch${channel} \ -y [expr {$channel_count * $keyboard_height}] \ -w [expr {($num_octaves * 7) * $step_white + $border}] \ -h $keyboard_height \ -rgba 0x101010A0 set nof_blacks 0 for {set note 0} {$note < $num_notes} {incr note} { set z -1 set xcor 0 if {[string range [lindex $note_strings [expr {$note % 12}]] end end] eq "#"} { # black key dict set note_key_color $note 0x000000 set h [expr {round($white_key_height * 0.7)}] set xcor [expr {($key_width + 1) / 2}] incr nof_blacks } else { # white key set h $white_key_height dict set note_key_color $note 0xFFFFFF set z -2 } osd create rectangle music_keyboard.chip${soundchip}ch${channel}.key${note} \ -x [expr {($note - $nof_blacks) * $step_white + $border + $xcor}] \ -y 0 -z $z \ -w $key_width -h $h \ -rgb [dict get $note_key_color $note] } set next_to_kbd_x [expr {($num_notes - $nof_blacks) * $step_white + $border}] osd create rectangle music_keyboard.ch${channel}chip${soundchip}infofield \ -x $next_to_kbd_x -y [expr {$channel_count * $keyboard_height}] \ -w [expr {320 - $next_to_kbd_x}] -h $keyboard_height \ -rgba 0x000000A0 osd create text music_keyboard.ch${channel}chip${soundchip}infofield.notetext \ -rgb 0xFFFFFF -size [expr {round($keyboard_height * 0.75)}] osd create text music_keyboard.ch${channel}chip${soundchip}infofield.chlabel \ -rgb 0x1F1FFF -size [expr {round($keyboard_height * 0.75)}] \ -x 10 -text "[expr {$channel + 1}] ($soundchip)" incr channel_count } } set machine_switch_trigger_id [after machine_switch [namespace code music_keyboard_reset]] } proc update_keyboard {} { variable keyb_dict variable num_notes variable note_strings variable note_key_color variable frame_trigger_id dict for {soundchip chip_dict} $keyb_dict { dict for {channel chan_dict} $chip_dict { set freq_expr [dict get $chan_dict freq_expr] set vol_expr [dict get $chan_dict vol_expr] set prev_note [dict get $chan_dict prev_note] set note [expr {round([freq_to_note [eval $freq_expr]])}] if {$note != $prev_note} { osd configure music_keyboard.chip${soundchip}ch${channel}.key${prev_note} \ -rgb [dict get $note_key_color $prev_note] } if {($note < $num_notes) && ($note > 0)} { set volume [eval $vol_expr] set deviation [expr {round(255 * $volume)}] set color [dict get $note_key_color $note] set color [expr {($color > 0x808080) ? ($color - (($deviation << 8) + $deviation)) : ($color + ($deviation << 16))}] osd configure music_keyboard.chip${soundchip}ch${channel}.key${note} \ -rgb $color if {$deviation > 0} { set note_text [lindex $note_strings [expr {$note % 12}]] } else { set note_text "" } dict set keyb_dict $soundchip $channel prev_note $note } else { set note_text "" } osd configure music_keyboard.ch${channel}chip${soundchip}infofield.notetext \ -text $note_text } } set frame_trigger_id [after frame [namespace code update_keyboard]] } proc music_keyboard_reset {} { if {![osd exists music_keyboard]} { error "Please fix a bug in this script!" } toggle_music_keyboard toggle_music_keyboard } proc toggle_music_keyboard {} { variable machine_switch_trigger_id variable frame_trigger_id variable keyb_dict if {[osd exists music_keyboard]} { after cancel $machine_switch_trigger_id after cancel $frame_trigger_id osd destroy music_keyboard unset keyb_dict } else { keyboard_init update_keyboard } return "" } namespace export toggle_music_keyboard } ;# namespace music_keyboard namespace import music_keyboard::* openMSX-RELEASE_0_12_0/share/scripts/_osd.tcl000066400000000000000000000036611257557151200206660ustar00rootroot00000000000000namespace eval osd { variable default_color "0x7090aae8 0xa0c0dde8 0x90b0cce8 0xc0e0ffe8" variable error_color "0xaa0000e8 0xdd0000e8 0xcc0000e8 0xff0000e8" variable warning_color "0xaa6600e8 0xdd9900e8 0xcc8800e8 0xffaa00e8" set_help_text show_osd \ {Give an overview of all currently defined OSD elements and their properties. This is mainly useful to debug a OSD related script.} proc show_osd {{widgets ""}} { set result "" if {$widgets eq ""} { # all widgets set widgets [osd info] } foreach widget $widgets { append result "$widget\n" foreach property [osd info $widget] { if {[catch {set value [osd info $widget $property]}]} { set value "--error--" } append result " $property $value\n" } } return $result } # this can display only one message at a time, the previous message # will get overwritten by a new one proc display_message {message {category info}} { variable default_color variable error_color variable warning_color switch -- $category { "info" {set bg_color $default_color} "progress" {set bg_color $default_color} "warning" {set bg_color $warning_color} "error" {set bg_color $error_color } "default" {error "Invalid category: $category"} } osd_widgets::text_box osd_display_message \ -text $message \ -textrgba 0xffffffff \ -textsize 6 \ -rgba $bg_color \ -x 3 -y 12 -z 5 -w 314 \ -bordersize 0.5 \ -borderrgba 0x000000ff \ -clip true \ -scaled true \ -suppressErrors true } proc is_cursor_in {widget} { set x 2; set y 2 catch {lassign [osd info $widget -mousecoord] x y} expr {0 <= $x && $x <= 1 && 0 <= $y && $y <= 1} } # only export stuff that is useful in other scripts or for the console user namespace export show_osd namespace export display_message namespace export is_cursor_in };# namespace osd # only import stuff to global that is useful outside of scripts (i.e. for the console user) namespace import osd::show_osd openMSX-RELEASE_0_12_0/share/scripts/_osd_keyboard.tcl000066400000000000000000000270641257557151200225510ustar00rootroot00000000000000namespace eval osd_keyboard { # KNOWN ISSUES/TODO: # * Shouldn't use the keymatrix command, but the 'type' command for all keys # that are not on the same position in the matrix for all machines # * lots more? :P variable is_dingoo [string match *-dingux* $::tcl_platform(osVersion)] #init vars variable mouse1_pressed false variable key_pressed -1 variable key_selected -1 variable keys_held variable row_starts #init colors variable key_color "0x999999c0 0xbbbbbbc0 0xddddddc0 0xffffffc0" variable key_pressed_color "0x994400c0 0xbb5500c0 0xdd6600c0 0xff8800c0" variable key_background_color 0x00000080 variable key_hold_color "0x009933f0 0x00bb44f0 0x00dd66f0 0x00ff88ff" variable key_select_color "0x999933f0 0xbbbb44f0 0xdddd66f0 0xffff88f0" variable key_edge_color 0xaaaaaaa0 variable key_edge_color_select 0xaaaa00a0 variable key_edge_color_hold 0x00aa44a0 variable key_edge_color_pressed 0xaa4444a0 # Keyboard layout constants. variable key_height 16 variable key_hspace 2 variable key_vspace 2 variable board_hborder 4 variable board_vborder 4 proc toggle_osd_keyboard {} { if {[osd exists kb]} { disable_osd_keyboard } else { enable_osd_keyboard } } proc disable_osd_keyboard {} { osd destroy kb deactivate_input_layer osd_keyboard #reset keyboard matrix for {set i 0} {$i <= 8} {incr i} { keymatrixup $i 255 } namespace eval ::osd_control {unset close} } proc enable_osd_keyboard {} { variable is_dingoo variable mouse1_pressed false variable keys_held [list] variable row_starts [list] variable key_color variable key_background_color variable key_edge_color # first remove other OSD controlled widgets (like the osd menu) if {[info exists ::osd_control::close]} { eval $::osd_control::close } # and tell how to close this widget namespace eval ::osd_control {set close ::osd_keyboard::disable_osd_keyboard} #bind stuff bind -layer osd_keyboard "mouse button1 down" {osd_keyboard::key_handler true} bind -layer osd_keyboard "mouse button1 up" {osd_keyboard::key_handler false} bind -layer osd_keyboard "mouse button3 down" {osd_keyboard::key_hold_toggle false} bind -layer osd_keyboard "OSDcontrol UP PRESS" -repeat {osd_keyboard::selection_row -1} bind -layer osd_keyboard "OSDcontrol DOWN PRESS" -repeat {osd_keyboard::selection_row +1} bind -layer osd_keyboard "OSDcontrol LEFT PRESS" -repeat {osd_keyboard::selection_col -1} bind -layer osd_keyboard "OSDcontrol RIGHT PRESS" -repeat {osd_keyboard::selection_col +1} if {$is_dingoo} { bind -layer osd_keyboard "keyb LCTRL,PRESS" {osd_keyboard::selection_press } bind -layer osd_keyboard "keyb LCTRL,RELEASE" {osd_keyboard::selection_release} bind -layer osd_keyboard "keyb LALT" {osd_keyboard::key_hold_toggle true } } else { bind -layer osd_keyboard "OSDcontrol A PRESS" {osd_keyboard::selection_press } bind -layer osd_keyboard "OSDcontrol A RELEASE" {osd_keyboard::selection_release} bind -layer osd_keyboard "OSDcontrol B PRESS" {osd_keyboard::key_hold_toggle true } } activate_input_layer osd_keyboard -blocking #Define Keyboard (how do we handle the shift/ctrl/graph command?) set key_basewidth 18 set rows { "F1*26|F2*26|F3*26|F4*26|F5*26|null*8|Select*26|Stop*26|null*8|Home*26|Ins*26|Del*26" \ "Esc|1|2|3|4|5|6|7|8|9|0|-|=|\\|BS" \ "Tab*28|Q|W|E|R|T|Y|U|I|O|P|\[|]|Return*28" \ "Ctrl*32|A|S|D|F|G|H|J|K|L|;|'|`|<--*24" \ "Shift*40|Z|X|C|V|B|N|M|,|.|/|Acc|Shift*36" \ "null*40|Cap|Grp|Space*158|Cod" } # Keyboard layout constants. variable key_height variable key_hspace variable key_vspace variable board_hborder variable board_vborder # Create widgets. set board_width \ [expr {15 * $key_basewidth + 14 * $key_hspace + 2 * $board_hborder}] set board_height \ [expr {6 * $key_height + 5 * $key_vspace + 2 * $board_vborder}] osd create rectangle kb \ -x [expr {(320 - $board_width) / 2}] \ -y 4 \ -w $board_width \ -h $board_height -scaled true -rgba $key_background_color set keycount 0 for {set y 0} {$y <= [llength $rows]} {incr y} { set y_base [expr {$board_vborder + $y * ($key_height + $key_vspace)}] lappend row_starts $keycount set x $board_hborder foreach {keys} [split [lindex $rows $y] "|"] { lassign [split $keys "*"] key_text key_width if {$key_width < 1} {set key_width $key_basewidth} if {$key_text ne "null"} { set key_y $y_base set key_h $key_height set bordersize 1 if {$key_text eq "Return"} { set bordersize 0 } elseif {$key_text eq "<--"} { set bordersize 0 incr key_y -$key_vspace incr key_h $key_vspace } osd create rectangle kb.$keycount \ -x $x -y $key_y \ -w $key_width -h $key_h \ -rgba $key_color \ -bordersize $bordersize \ -borderrgba $key_edge_color osd create text kb.$keycount.text \ -x 1.1 \ -y 0.1 \ -text $key_text \ -size 8 incr keycount } set x [expr {$x + $key_width + $key_hspace}] } } variable key_selected if {$key_selected == -1} { # Select the key in the middle of the keyboard. key_select [key_at_coord \ [expr {$board_width / 2}] \ [expr {$board_vborder + 3.5 * ($key_height + $key_vspace)}]] } else { update_key_color $key_selected } return "" } proc selection_row {delta} { variable row_starts variable key_selected # Note: Delta as bias makes sure that if a key is exactly in the middle # above/below two other keys, an up/down or down/up sequence will # end on the same key it started on. set x [expr {\ [osd info kb.$key_selected -x] + [osd info kb.$key_selected -w] / 2 \ + $delta}] set num_rows [expr {[llength $row_starts] - 1}] set row [row_for_key $key_selected] while {1} { # Determine new row. incr row $delta if {$row < 0} { set row [expr {$num_rows - 1}] } elseif {$row >= $num_rows} { set row 0 } # Get key at new coordinates. set first_key [lindex $row_starts $row] set y [expr {\ [osd info kb.$first_key -y] + [osd info kb.$first_key -h] / 2}] set new_selection [key_at_coord $x $y] if {$new_selection >= 0} { break } } key_select $new_selection } proc selection_col {delta} { variable row_starts variable key_selected # Figure out first and last key of current row. set row [row_for_key $key_selected] set row_start [lindex $row_starts $row] set row_end [lindex $row_starts [expr {$row + 1}]] # Move left or right. set new_selection [expr {$key_selected + $delta}] if {$new_selection < $row_start} { set new_selection [expr {$row_end - 1}] } elseif {$new_selection >= $row_end} { set new_selection $row_start } key_select $new_selection } proc selection_press {} { variable key_selected key_press $key_selected } proc selection_release {} { key_release } proc key_press {key_id} { variable key_pressed set key_pressed $key_id key_matrix $key_id down update_key_color $key_id } proc key_release {} { variable key_pressed variable keys_held if {$key_pressed == -1} { return } set key_id $key_pressed set key_pressed -1 set index [lsearch -exact $keys_held $key_id] if {$index != -1} { set keys_held [lreplace $keys_held $index $index] } key_matrix $key_id up update_key_color $key_id } proc key_select {key_id} { variable key_selected set old_selected $key_selected set key_selected $key_id update_key_color $key_selected update_key_color $old_selected } proc row_for_key {key_id} { variable row_starts for {set row 0} {$row < [llength $row_starts] - 1} {incr row} { set row_start [lindex $row_starts $row] set row_end [lindex $row_starts [expr {$row + 1}]] if {$row_start <= $key_id && $key_id < $row_end} { return $row } } return -1 } proc update_key_color {key_id} { variable key_selected variable key_pressed variable keys_held variable key_color variable key_select_color variable key_pressed_color variable key_hold_color variable key_edge_color variable key_edge_color_select variable key_edge_color_hold variable key_edge_color_pressed if {$key_id < 0} { return } elseif {$key_id == $key_pressed} { set color $key_pressed_color set edge_color $key_edge_color_pressed } elseif {$key_id == $key_selected} { set color $key_select_color set edge_color $key_edge_color_select } elseif {$key_id in $keys_held} { set color $key_hold_color set edge_color $key_edge_color_hold } else { set color $key_color set edge_color $key_edge_color } osd configure kb.$key_id -rgba $color -borderrgba $edge_color } proc key_at_coord {x y} { variable key_hspace variable key_height variable key_vspace variable board_vborder variable row_starts set row [expr {int(floor( \ ($y - $board_vborder + $key_vspace / 2) / ($key_height + $key_vspace) \ ))}] if {$row >= 0 && $row < [llength $row_starts] - 1} { set row_start [lindex $row_starts $row] set row_end [lindex $row_starts [expr {$row + 1}]] for {set key_id $row_start} {$key_id < $row_end} {incr key_id} { set relx [expr {$x - [osd info kb.$key_id -x] + $key_hspace / 2}] if {$relx >= 0 && $relx < [osd info kb.$key_id -w] + $key_hspace} { return $key_id } } } return -1 } proc key_at_mouse {} { lassign [osd info kb -mousecoord] x y key_at_coord [expr {$x * [osd info kb -w]}] \ [expr {$y * [osd info kb -h]}] } proc key_hold_toggle {at_selection} { variable keys_held variable key_selected if {$at_selection} { set key_id $key_selected } else { set key_id [key_at_mouse] } if {$key_id >= 0} { set index [lsearch -exact $keys_held $key_id] if {$index == -1} { key_matrix $key_id down lappend keys_held $key_id } else { key_matrix $key_id up set keys_held [lreplace $keys_held $index $index] } update_key_color $key_id } } proc key_handler {mouse_state} { if {$mouse_state} { set key_id [key_at_mouse] if {$key_id >= 0} { key_press $key_id key_select $key_id } } else { key_release } } proc key_matrix {keynum state} { set key [string trim "[osd info kb.$keynum.text -text]"] set km keymatrix$state #info from http://map.grauw.nl/articles/keymatrix.php (thanks Grauw) switch -- $key { "0" {$km 0 1} "1" {$km 0 2} "2" {$km 0 4} "3" {$km 0 8} "4" {$km 0 16} "5" {$km 0 32} "6" {$km 0 64} "7" {$km 0 128} "8" {$km 1 1} "9" {$km 1 2} "-" {$km 1 4} "=" {$km 1 8} "\\" {$km 1 16} "\[" {$km 1 32} "\]" {$km 1 64} ";" {$km 1 128} "'" {$km 2 1} "`" {$km 2 2} "," {$km 2 4} "." {$km 2 8} "/" {$km 2 16} "Acc" {$km 2 32} "A" {$km 2 64} "B" {$km 2 128} "C" {$km 3 1} "D" {$km 3 2} "E" {$km 3 4} "F" {$km 3 8} "G" {$km 3 16} "H" {$km 3 32} "I" {$km 3 64} "J" {$km 3 128} "K" {$km 4 1} "L" {$km 4 2} "M" {$km 4 4} "N" {$km 4 8} "O" {$km 4 16} "P" {$km 4 32} "Q" {$km 4 64} "R" {$km 4 128} "S" {$km 5 1} "T" {$km 5 2} "U" {$km 5 4} "V" {$km 5 8} "W" {$km 5 16} "X" {$km 5 32} "Y" {$km 5 64} "Z" {$km 5 128} "Shift" {$km 6 1} "Ctrl" {$km 6 2} "Grp" {$km 6 4} "Cap" {$km 6 8} "Cod" {$km 6 16} "F1" {$km 6 32} "F2" {$km 6 64} "F3" {$km 6 128} "F4" {$km 7 1} "F5" {$km 7 2} "Esc" {$km 7 4} "Tab" {$km 7 8} "Stop" {$km 7 16} "BS" {$km 7 32} "Select" {$km 7 64} "Return" {$km 7 128} "<--" {$km 7 128} "Space" {$km 8 1} "Home" {$km 8 2} "Ins" {$km 8 4} "Del" {$km 8 8} } #cursor keys etc (not implemented... should we?) #numeric keyboard? } namespace export toggle_osd_keyboard };# namespace osd_keyboard namespace import osd_keyboard::* openMSX-RELEASE_0_12_0/share/scripts/_osd_menu.tcl000066400000000000000000001470121257557151200217110ustar00rootroot00000000000000namespace eval osd_menu { set_help_text main_menu_open "Show the OSD menu." set_help_text main_menu_close "Remove the OSD menu." set_help_text main_menu_toggle "Toggle the OSD menu." # default colors defined here, for easy global tweaking variable default_bg_color "0x7090aae8 0xa0c0dde8 0x90b0cce8 0xc0e0ffe8" variable default_text_color 0x000000ff variable default_text_color 0x000000ff variable default_select_color "0x0044aa80 0x2266dd80 0x0055cc80 0x44aaff80" variable default_header_text_color 0xff9020ff variable is_dingoo [string match *-dingux* $::tcl_platform(osVersion)] variable scaling_available [expr {[lindex [lindex [openmsx_info setting scale_factor] 2] 1] > 1}] proc get_optional {dict_name key default} { upvar $dict_name d expr {[dict exists $d $key] ? [dict get $d $key] : $default} } proc set_optional {dict_name key value} { upvar $dict_name d if {![dict exists $d $key]} { dict set d $key $value } } variable menulevels 0 proc push_menu_info {} { variable menulevels incr menulevels 1 set levelname "menuinfo_$menulevels" variable $levelname set $levelname [uplevel {dict create \ name $name lst $lst menu_len $menu_len presentation $presentation \ menutexts $menutexts selectinfo $selectinfo selectidx $selectidx \ scrollidx $scrollidx on_close $on_close}] } proc peek_menu_info {} { variable menulevels uplevel upvar #0 osd_menu::menuinfo_$menulevels menuinfo } proc set_selectidx {value} { peek_menu_info dict set menuinfo selectidx $value } proc set_scrollidx {value} { peek_menu_info dict set menuinfo scrollidx $value } proc menu_create {menudef} { variable menulevels variable default_bg_color variable default_text_color variable default_select_color variable default_header_text_color set name "menu[expr {$menulevels + 1}]" set defactions [get_optional menudef "actions" ""] set bgcolor [get_optional menudef "bg-color" $default_bg_color] set deftextcolor [get_optional menudef "text-color" $default_text_color] set selectcolor [get_optional menudef "select-color" $default_select_color] set deffontsize [get_optional menudef "font-size" 12] set deffont [get_optional menudef "font" "skins/Vera.ttf.gz"] set bordersize [get_optional menudef "border-size" 0] set on_open [get_optional menudef "on-open" ""] set on_close [get_optional menudef "on-close" ""] osd create rectangle $name -scaled true -rgba $bgcolor -clip true \ -borderrgba 0x000000ff -bordersize 0.5 set y $bordersize set selectinfo [list] set menutexts [list] foreach itemdef [dict get $menudef items] { set selectable [get_optional itemdef "selectable" true] incr y [get_optional itemdef "pre-spacing" 0] set fontsize [get_optional itemdef "font-size" $deffontsize] set font [get_optional itemdef "font" $deffont] set textcolor [expr {$selectable ? [get_optional itemdef "text-color" $deftextcolor] : [get_optional itemdef "text-color" $default_header_text_color]}] set actions [get_optional itemdef "actions" ""] set on_select [get_optional itemdef "on-select" ""] set on_deselect [get_optional itemdef "on-deselect" ""] set textid "${name}.item${y}" set text [dict get $itemdef text] lappend menutexts $textid $text osd create text $textid -font $font -size $fontsize \ -rgba $textcolor -x $bordersize -y $y if {$selectable} { set allactions [concat $defactions $actions] lappend selectinfo [list $y $fontsize $allactions $on_select $on_deselect] } incr y $fontsize incr y [get_optional itemdef "post-spacing" 0] } set width [dict get $menudef width] set height [expr {$y + $bordersize}] set xpos [get_optional menudef "xpos" [expr {(320 - $width) / 2}]] set ypos [get_optional menudef "ypos" [expr {(240 - $height) / 2}]] osd configure $name -x $xpos -y $ypos -w $width -h $height set selw [expr {$width - 2 * $bordersize}] osd create rectangle "${name}.selection" -z -1 -rgba $selectcolor \ -x $bordersize -w $selw set lst [get_optional menudef "lst" ""] set menu_len [get_optional menudef "menu_len" 0] set presentation [get_optional menudef "presentation" ""] set selectidx 0 set scrollidx 0 push_menu_info uplevel #0 $on_open menu_on_select $selectinfo $selectidx menu_refresh_top } proc menu_refresh_top {} { peek_menu_info foreach {osdid text} [dict get $menuinfo menutexts] { set cmd [list subst $text] osd configure $osdid -text [uplevel #0 $cmd] } set selectinfo [dict get $menuinfo selectinfo] if {[llength $selectinfo] == 0} return set selectidx [dict get $menuinfo selectidx ] lassign [lindex $selectinfo $selectidx] sely selh osd configure "[dict get $menuinfo name].selection" -y $sely -h $selh } proc menu_close_top {} { variable menulevels peek_menu_info menu_on_deselect [dict get $menuinfo selectinfo] [dict get $menuinfo selectidx] uplevel #0 [dict get $menuinfo on_close] osd destroy [dict get $menuinfo name] unset menuinfo incr menulevels -1 if {$menulevels == 0} { menu_last_closed } } proc menu_close_all {} { variable menulevels while {$menulevels} { menu_close_top } } proc menu_setting {cmd_result} { menu_refresh_top } proc menu_updown {delta} { peek_menu_info set num [llength [dict get $menuinfo selectinfo]] if {$num == 0} return set old_idx [dict get $menuinfo selectidx] menu_reselect [expr {($old_idx + $delta) % $num}] } proc menu_reselect {new_idx} { peek_menu_info set selectinfo [dict get $menuinfo selectinfo] set old_idx [dict get $menuinfo selectidx] menu_on_deselect $selectinfo $old_idx set_selectidx $new_idx menu_on_select $selectinfo $new_idx menu_refresh_top } proc menu_on_select {selectinfo selectidx} { set on_select [lindex $selectinfo $selectidx 3] uplevel #0 $on_select } proc menu_on_deselect {selectinfo selectidx} { set on_deselect [lindex $selectinfo $selectidx 4] uplevel #0 $on_deselect } proc menu_action {button} { peek_menu_info set selectidx [dict get $menuinfo selectidx ] menu_action_idx $selectidx $button } proc menu_action_idx {idx button} { peek_menu_info set selectinfo [dict get $menuinfo selectinfo] set actions [lindex $selectinfo $idx 2] set_optional actions UP {osd_menu::menu_updown -1} set_optional actions DOWN {osd_menu::menu_updown 1} set_optional actions B {osd_menu::menu_close_top} set cmd [get_optional actions $button ""] uplevel #0 $cmd } proc get_mouse_coords {} { peek_menu_info set name [dict get $menuinfo name] set x 2; set y 2 catch {lassign [osd info $name -mousecoord] x y} list $x $y } proc menu_get_mouse_idx {xy} { lassign $xy x y if {$x < 0 || 1 < $x || $y < 0 || 1 < $y} {return -1} peek_menu_info set name [dict get $menuinfo name] set yy [expr {$y * [osd info $name -h]}] set sel 0 foreach i [dict get $menuinfo selectinfo] { lassign $i y h actions if {($y <= $yy) && ($yy < ($y + $h))} { return $sel } incr sel } return -1 } proc menu_mouse_down {} { variable mouse_coord variable mouse_idx set mouse_coord [get_mouse_coords] set mouse_idx [menu_get_mouse_idx $mouse_coord] if {$mouse_idx != -1} { menu_reselect $mouse_idx } } proc menu_mouse_up {} { variable mouse_coord variable mouse_idx set mouse_coord [get_mouse_coords] set mouse_idx [menu_get_mouse_idx $mouse_coord] if {$mouse_idx != -1} { menu_action_idx $mouse_idx A } unset mouse_coord unset mouse_idx } proc menu_mouse_motion {} { variable mouse_coord variable mouse_idx if {![info exists mouse_coord]} return set new_mouse_coord [get_mouse_coords] set new_idx [menu_get_mouse_idx $new_mouse_coord] if {$new_idx != -1 && $new_idx != $mouse_idx} { menu_reselect $new_idx set mouse_coord $new_mouse_coord set mouse_idx $new_idx return } if {$mouse_idx != -1} { lassign $mouse_coord old_x old_y lassign $new_mouse_coord new_x new_y set delta_x [expr {$new_x - $old_x}] set delta_y [expr {$new_y - $old_y}] if {$delta_y > 0.1} { menu_action_idx $mouse_idx DOWN set mouse_coord $new_mouse_coord return } elseif {$delta_y < -0.1} { menu_action_idx $mouse_idx UP set mouse_coord $new_mouse_coord return } elseif {$delta_x > 0.1} { menu_action_idx $mouse_idx RIGHT set mouse_coord $new_mouse_coord return } elseif {$delta_x < -0.1} { menu_action_idx $mouse_idx LEFT set mouse_coord $new_mouse_coord return } } } user_setting create string osd_rom_path "OSD Rom Load Menu Last Known Path" $env(HOME) user_setting create string osd_disk_path "OSD Disk Load Menu Last Known Path" $env(HOME) user_setting create string osd_tape_path "OSD Tape Load Menu Last Known Path" $env(HOME) user_setting create string osd_hdd_path "OSD HDD Load Menu Last Known Path" $env(HOME) user_setting create string osd_ld_path "OSD LD Load Menu Last Known Path" $env(HOME) if {![file exists $::osd_rom_path]} { # revert to default (should always exist) unset ::osd_rom_path } if {![file exists $::osd_disk_path]} { # revert to default (should always exist) unset ::osd_disk_path } if {![file exists $::osd_tape_path]} { # revert to default (should always exist) unset ::osd_tape_path } if {![file exists $::osd_hdd_path]} { # revert to default (should always exist) unset ::osd_hdd_path } if {![file exists $::osd_ld_path]} { # revert to default (should always exist) unset ::osd_ld_path } variable taperecordings_directory [file normalize $::env(OPENMSX_USER_DATA)/../taperecordings] proc main_menu_open {} { do_menu_open [create_main_menu] } proc do_menu_open {top_menu} { variable is_dingoo # close console, because the menu interferes with it set ::console off # also remove other OSD controlled widgets (like the osd keyboard) if {[info exists ::osd_control::close]} { eval $::osd_control::close } # end tell how to close this widget namespace eval ::osd_control {set close ::osd_menu::main_menu_close} menu_create $top_menu set ::pause true # TODO make these bindings easier to customize bind -layer osd_menu "OSDcontrol UP PRESS" -repeat {osd_menu::menu_action UP } bind -layer osd_menu "OSDcontrol DOWN PRESS" -repeat {osd_menu::menu_action DOWN } bind -layer osd_menu "OSDcontrol LEFT PRESS" -repeat {osd_menu::menu_action LEFT } bind -layer osd_menu "OSDcontrol RIGHT PRESS" -repeat {osd_menu::menu_action RIGHT} bind -layer osd_menu "mouse button1 down" {osd_menu::menu_mouse_down} bind -layer osd_menu "mouse button1 up" {osd_menu::menu_mouse_up} bind -layer osd_menu "mouse button3 up" {osd_menu::menu_close_top} bind -layer osd_menu "mouse motion" {osd_menu::menu_mouse_motion} bind -layer osd_menu "mouse button4 down" {osd_menu::menu_action UP } bind -layer osd_menu "mouse button5 down" {osd_menu::menu_action DOWN } if {$is_dingoo} { bind -layer osd_menu "keyb LCTRL" {osd_menu::menu_action A } bind -layer osd_menu "keyb LALT" {osd_menu::menu_action B } } else { bind -layer osd_menu "OSDcontrol A PRESS" {osd_menu::menu_action A } bind -layer osd_menu "OSDcontrol B PRESS" {osd_menu::menu_action B } # on Android, use BACK button to go back in menus bind -layer osd_menu "keyb WORLD_92" {osd_menu::menu_action B } } activate_input_layer osd_menu -blocking } proc main_menu_close {} { menu_close_all } proc main_menu_toggle {} { variable menulevels if {$menulevels} { # there is at least one menu open, close it menu_close_all } else { # none open yet, open main menu main_menu_open } } proc menu_last_closed {} { variable is_dingoo set ::pause false deactivate_input_layer osd_menu namespace eval ::osd_control {unset close} } proc prepare_menu_list {lst num menudef} { set execute [dict get $menudef execute] set header [dict get $menudef header] set item_extra [get_optional menudef item ""] set on_select [get_optional menudef on-select ""] set on_deselect [get_optional menudef on-deselect ""] set presentation [get_optional menudef presentation $lst] # 'assert': presentation should have same length as item list! if {[llength $presentation] != [llength $lst]} { error "Presentation should be of same length as item list!" } dict set menudef presentation $presentation lappend header "selectable" "false" set items [list $header] set lst_len [llength $lst] set menu_len [expr {$lst_len < $num ? $lst_len : $num}] for {set i 0} {$i < $menu_len} {incr i} { set actions [list "A" "osd_menu::list_menu_item_exec {$execute} $i"] if {$i == 0} { lappend actions "UP" "osd_menu::move_selection -1" } if {$i == ($menu_len - 1)} { lappend actions "DOWN" "osd_menu::move_selection 1" } lappend actions "LEFT" "osd_menu::move_selection -$menu_len" lappend actions "RIGHT" "osd_menu::move_selection $menu_len" set item [list "text" "\[osd_menu::list_menu_item_show $i\]" \ "actions" $actions] if {$on_select ne ""} { lappend item "on-select" "osd_menu::list_menu_item_select $i $on_select" } if {$on_deselect ne ""} { lappend item "on-deselect" "osd_menu::list_menu_item_select $i $on_deselect" } lappend items [concat $item $item_extra] } dict set menudef items $items dict set menudef lst $lst dict set menudef menu_len $menu_len return $menudef } proc list_menu_item_exec {execute pos} { peek_menu_info {*}$execute [lindex [dict get $menuinfo lst] [expr {$pos + [dict get $menuinfo scrollidx]}]] } proc list_menu_item_show {pos} { peek_menu_info return [lindex [dict get $menuinfo presentation] [expr {$pos + [dict get $menuinfo scrollidx]}]] } proc list_menu_item_select {pos select_proc} { peek_menu_info $select_proc [lindex [dict get $menuinfo lst] [expr {$pos + [dict get $menuinfo scrollidx]}]] } proc move_selection {delta} { peek_menu_info set lst_last [expr {[llength [dict get $menuinfo lst]] - 1}] set scrollidx [dict get $menuinfo scrollidx] set selectidx [dict get $menuinfo selectidx] set old_itemidx [expr {$scrollidx + $selectidx}] set new_itemidx [expr {$old_itemidx + $delta}] if {$new_itemidx < 0} { # Before first element if {$old_itemidx == 0} { # if first element was already selected, wrap to last set new_itemidx $lst_last } else { # otherwise, clamp to first element set new_itemidx 0 } } elseif {$new_itemidx > $lst_last} { # After last element if {$old_itemidx == $lst_last} { # if last element was already selected, wrap to first set new_itemidx 0 } else { # otherwise clam to last element set new_itemidx $lst_last } } select_menu_idx $new_itemidx } proc select_menu_idx {itemidx} { peek_menu_info set menu_len [dict get $menuinfo menu_len] set scrollidx [dict get $menuinfo scrollidx] set selectidx [dict get $menuinfo selectidx] set selectinfo [dict get $menuinfo selectinfo] menu_on_deselect $selectinfo $selectidx set selectidx [expr {$itemidx - $scrollidx}] if {$selectidx < 0} { incr scrollidx $selectidx set selectidx 0 } elseif {$selectidx >= $menu_len} { set selectidx [expr {$menu_len - 1}] set scrollidx [expr {$itemidx - $selectidx}] } set_selectidx $selectidx set_scrollidx $scrollidx menu_on_select $selectinfo $selectidx menu_refresh_top } proc select_menu_item {item} { peek_menu_info set index [lsearch -exact [dict get $menuinfo lst] $item] if {$index == -1} return select_menu_idx $index } # # definitions of menus # proc create_main_menu {} { set menu_def { font-size 10 border-size 2 width 160 } lappend items { text "[openmsx_info version]" font-size 12 post-spacing 6 selectable false } if {[catch carta]} {; # example: Philips NMS 801 lappend items { text "(No cartridge slot available...)" selectable false text-color 0x808080ff } } else { foreach slot [lrange [lsort [info command cart?]] 0 1] { set slot_str [string toupper [string index $slot end]] lappend items [list text "Load ROM... (slot $slot_str)" \ actions [list A "osd_menu::menu_create \[osd_menu::menu_create_ROM_list \$::osd_rom_path $slot\]"]] } } if {[catch diska]} { lappend items { text "(No disk drives available...)" selectable false text-color 0x808080ff } } else { foreach drive [lrange [lsort [info command disk?]] 0 1] { set drive_str [string toupper [string index $drive end]] lappend items [list text "Insert Disk... (drive $drive_str)" \ actions [list A "osd_menu::menu_create \[osd_menu::menu_create_disk_list \$::osd_disk_path $drive\]"]] } } if {[info command hda] ne ""} {; # only exists when hard disk extension available lappend items { text "Change hard disk image..." actions { A { osd_menu::menu_create [osd_menu::menu_create_hdd_list $::osd_hdd_path]} } } } if {[info command laserdiscplayer] ne ""} {; # only exists on some Pioneers lappend items { text "Load LaserDisc..." actions { A { osd_menu::menu_create [osd_menu::menu_create_ld_list $::osd_ld_path]} } } } if {[catch "machine_info connector cassetteport"]} {; # example: turboR lappend items { text "(No cassette port present...)" selectable false text-color 0x808080ff post-spacing 3 } } else { lappend items { text "Set Tape..." actions { A { osd_menu::menu_create [osd_menu::menu_create_tape_list $::osd_tape_path]} } post-spacing 3 } } lappend items { text "Save State..." actions { A { osd_menu::menu_create [osd_menu::menu_create_save_state] }}} lappend items { text "Load State..." actions { A { osd_menu::menu_create [osd_menu::menu_create_load_state] }} post-spacing 3 } lappend items { text "Hardware..." actions { A { osd_menu::menu_create $osd_menu::hardware_menu }} post-spacing 3 } lappend items { text "Misc Settings..." actions { A { osd_menu::menu_create $osd_menu::misc_setting_menu }}} lappend items { text "Sound Settings..." actions { A { osd_menu::menu_create $osd_menu::sound_setting_menu }}} lappend items { text "Video Settings..." actions { A { osd_menu::menu_create [osd_menu::create_video_setting_menu] }} post-spacing 3 } lappend items { text "Advanced..." actions { A { osd_menu::menu_create $osd_menu::advanced_menu }} post-spacing 10 } lappend items { text "Reset MSX" actions { A { reset; osd_menu::menu_close_all }}} lappend items { text "Exit openMSX" actions { A quitmenu::quit_menu }} dict set menu_def items $items return $menu_def } set misc_setting_menu { font-size 8 border-size 2 width 150 xpos 100 ypos 120 items {{ text "Misc Settings" font-size 10 post-spacing 6 selectable false } { text "Speed: $speed" actions { LEFT { osd_menu::menu_setting [incr speed -1] } RIGHT { osd_menu::menu_setting [incr speed 1] }}} { text "Minimal Frameskip: $minframeskip" actions { LEFT { osd_menu::menu_setting [incr minframeskip -1] } RIGHT { osd_menu::menu_setting [incr minframeskip 1] }}} { text "Maximal Frameskip: $maxframeskip" actions { LEFT { osd_menu::menu_setting [incr maxframeskip -1] } RIGHT { osd_menu::menu_setting [incr maxframeskip 1] }}}}} set resampler_desc [dict create fast "fast (but low quality)" blip "blip (good speed/quality)" hq "hq (best but slow on Android)"] set sound_setting_menu { font-size 8 border-size 2 width 180 xpos 100 ypos 120 items {{ text "Sound Settings" font-size 10 post-spacing 6 selectable false } { text "Volume: $master_volume" actions { LEFT { osd_menu::menu_setting [incr master_volume -5] } RIGHT { osd_menu::menu_setting [incr master_volume 5] }}} { text "Mute: $mute" actions { LEFT { osd_menu::menu_setting [cycle_back mute] } RIGHT { osd_menu::menu_setting [cycle mute] }}} { text "Individual Sound Device Settings..." actions { A { osd_menu::menu_create [osd_menu::menu_create_sound_device_list]}}} { text "Resampler: [osd_menu::get_resampler_presentation $resampler]" actions { LEFT { osd_menu::menu_setting [cycle_back resampler] } RIGHT { osd_menu::menu_setting [cycle resampler] }}}}} set horizontal_stretch_desc [dict create 320.0 "none (large borders)" 288.0 "a bit more than all border pixels" 284.0 "all border pixels" 280.0 "a bit less than all border pixels" 272.0 "realistic" 256.0 "no borders at all"] proc menu_create_sound_device_list {} { set menu_def { execute menu_sound_device_select_exec font-size 8 border-size 2 width 200 xpos 110 ypos 130 header { text "Select Sound Chip" font-size 10 post-spacing 6 }} set items [machine_info sounddevice] return [prepare_menu_list $items 5 $menu_def] } proc menu_sound_device_select_exec {item} { menu_create [create_sound_device_settings_menu $item] select_menu_item $item } proc create_sound_device_settings_menu {device} { set ypos 140 set menu_def [list \ font-size 8 \ border-size 2 \ width 210 \ xpos 120 \ ypos $ypos] lappend items [list text "$device Settings" \ font-size 10 \ post-spacing 6 \ selectable false] # volume and balance foreach aspect [list volume balance] { set var_name ::${device}_${aspect} set item [list] lappend item "text" set first [string range $aspect 0 0] set rest [string range $aspect 1 end] set first [string toupper $first] set capped_aspect "${first}${rest}" lappend item "$capped_aspect: \[[list set $var_name]]" lappend item "actions" set actions [list] lappend actions "LEFT" lappend actions "osd_menu::menu_setting \[[list incr $var_name -5]]" lappend actions "RIGHT" lappend actions "osd_menu::menu_setting \[[list incr $var_name 5]]" lappend item $actions lappend items $item } # channel mute set channel_count [soundchip_utils::get_num_channels $device] for {set channel 1} {$channel <= $channel_count} {incr channel} { set chmute_var_name ${device}_ch${channel}_mute set item [list] lappend item "text" set pretext "" if {$channel_count > 1} { set pretext "Channel $channel " } lappend item "${pretext}Mute: \[[list set $chmute_var_name]]" lappend item "actions" set actions [list] lappend actions "LEFT" lappend actions "osd_menu::menu_setting \[[list cycle_back $chmute_var_name]]" lappend actions "RIGHT" lappend actions "osd_menu::menu_setting \[[list cycle $chmute_var_name]]" lappend item $actions lappend items $item } # adjust menu position for longer lists # TODO: make this less magic if {$channel_count > 8} {;# more won't fit dict set menu_def ypos [expr {$ypos - round(($channel_count - 8) * ($ypos - 10)/16)}] } dict set menu_def items $items return $menu_def } proc create_video_setting_menu {} { variable scaling_available set menu_def { font-size 8 border-size 2 width 210 xpos 100 ypos 120 } lappend items { text "Video Settings" font-size 10 post-spacing 6 selectable false } if {$scaling_available} { lappend items { text "Scaler: $scale_algorithm" actions { LEFT { osd_menu::menu_setting [cycle_back scale_algorithm] } RIGHT { osd_menu::menu_setting [cycle scale_algorithm] }}} lappend items { text "Scale Factor: ${scale_factor}x" actions { LEFT { osd_menu::menu_setting [incr scale_factor -1] } RIGHT { osd_menu::menu_setting [incr scale_factor 1] }}} } lappend items { text "Horizontal Stretch: [osd_menu::get_horizontal_stretch_presentation $horizontal_stretch]" actions { A { osd_menu::menu_create [osd_menu::menu_create_stretch_list]; osd_menu::select_menu_item $horizontal_stretch }} post-spacing 6 } if {$scaling_available} { lappend items { text "Scanline: $scanline%" actions { LEFT { osd_menu::menu_setting [incr scanline -1] } RIGHT { osd_menu::menu_setting [incr scanline 1] }}} lappend items { text "Blur: $blur%" actions { LEFT { osd_menu::menu_setting [incr blur -1] } RIGHT { osd_menu::menu_setting [incr blur 1] }}} } if {$::renderer eq "SDLGL-PP"} { lappend items { text "Glow: $glow%" actions { LEFT { osd_menu::menu_setting [incr glow -1] } RIGHT { osd_menu::menu_setting [incr glow 1] }}} lappend items { text "Display Deform: $display_deform" actions { LEFT { osd_menu::menu_setting [cycle_back display_deform] } RIGHT { osd_menu::menu_setting [cycle display_deform] }}} } lappend items { text "Noise: $noise%" actions { LEFT { osd_menu::menu_setting [set noise [expr $noise - 1]] } RIGHT { osd_menu::menu_setting [set noise [expr $noise + 1]] }} post-spacing 6} lappend items { text "Enforce VDP Sprites-per-line Limit: $limitsprites" actions { LEFT { osd_menu::menu_setting [cycle_back limitsprites] } RIGHT { osd_menu::menu_setting [cycle limitsprites] }}} dict set menu_def items $items return $menu_def } set hardware_menu { font-size 8 border-size 2 width 175 xpos 100 ypos 120 items {{ text "Hardware" font-size 10 post-spacing 6 selectable false } { text "Change Machine..." actions { A { osd_menu::menu_create [osd_menu::menu_create_load_machine_list]; catch { osd_menu::select_menu_item [machine_info config_name]} }}} { text "Set Current Machine as Default" actions { A { set ::default_machine [machine_info config_name]; osd_menu::menu_close_top }}} { text "Extensions..." actions { A { osd_menu::menu_create $osd_menu::extensions_menu }}} { text "Connectors..." actions { A { osd_menu::menu_create [osd_menu::menu_create_connectors_list] }}} }} set extensions_menu { font-size 8 border-size 2 width 175 xpos 100 ypos 120 items {{ text "Extensions" font-size 10 post-spacing 6 selectable false } { text "Add..." actions { A { osd_menu::menu_create [osd_menu::menu_create_extensions_list] }}} { text "Remove..." actions { A { osd_menu::menu_create [osd_menu::menu_create_plugged_extensions_list] }}}}} set advanced_menu { font-size 8 border-size 2 width 175 xpos 100 ypos 120 items {{ text "Advanced" font-size 10 post-spacing 6 selectable false } { text "Manage Running Machines..." actions { A { osd_menu::menu_create $osd_menu::running_machines_menu }}} { text "Toys..." actions { A { osd_menu::menu_create [osd_menu::menu_create_toys_list] }}}}} set running_machines_menu { font-size 8 border-size 2 width 175 xpos 100 ypos 120 items {{ text "Manage Running Machines" font-size 10 post-spacing 6 selectable false } { text "Select Running Machine Tab: [utils::get_machine_display_name]" actions { A { osd_menu::menu_create [osd_menu::menu_create_running_machine_list] }}} { text "New Running Machine Tab" actions { A { osd_menu::menu_create [osd_menu::menu_create_load_machine_list "add"] }}} { text "Close Current Machine Tab" actions { A { set old_active_machine [activate_machine]; cycle_machine; delete_machine $old_active_machine }}}}} proc menu_create_running_machine_list {} { set menu_def { execute menu_machine_tab_select_exec font-size 8 border-size 2 width 200 xpos 110 ypos 130 header { text "Select Running Machine" font-size 10 post-spacing 6 }} set items [utils::get_ordered_machine_list] set presentation [list] foreach i $items { if {[activate_machine] eq $i} { set postfix_text "current" } else { set postfix_text [utils::get_machine_time $i] } lappend presentation [format "%s (%s)" [utils::get_machine_display_name ${i}] $postfix_text] } lappend menu_def presentation $presentation return [prepare_menu_list $items 5 $menu_def] } proc menu_machine_tab_select_exec {item} { menu_close_top activate_machine $item } proc get_resampler_presentation { value } { if {[dict exists $osd_menu::resampler_desc $value]} { return [dict get $osd_menu::resampler_desc $value] } else { return $value } } proc get_horizontal_stretch_presentation { value } { if {[dict exists $osd_menu::horizontal_stretch_desc $value]} { return [dict get $osd_menu::horizontal_stretch_desc $value] } else { return "custom: $::horizontal_stretch" } } proc menu_create_stretch_list {} { set menu_def [list \ execute menu_stretch_exec \ font-size 8 \ border-size 2 \ width 150 \ xpos 110 \ ypos 130 \ header { text "Select Horizontal Stretch:" font-size 10 post-spacing 6 }] set items [list] set presentation [list] set values [dict keys $osd_menu::horizontal_stretch_desc] if {$::horizontal_stretch ni $values} { lappend values $::horizontal_stretch } foreach value $values { lappend items $value lappend presentation [osd_menu::get_horizontal_stretch_presentation $value] } lappend menu_def presentation $presentation return [prepare_menu_list $items 6 $menu_def] } proc menu_stretch_exec {value} { set ::horizontal_stretch $value menu_close_top menu_refresh_top } # Returns list of machines/extensions, but try to filter out duplicates caused # by symlinks (e.g. turbor.xml -> Panasonic_FS-A1GT.xml). What this does not # catch is a symlink in the systemdir (so link also pointing to the systemdir) # and a similarly named file in the userdir. This situation does occur on my # development setup, but it shouldn't happen for regular users. proc get_filtered_configs {type} { set result [list] set configs [list] foreach t [openmsx_info $type] { # try both .xml and /hardwareconfig.xml set conf [data_file $type/$t.xml] if {![file exists $conf]} { set conf [data_file $type/$t/hardwareconfig.xml] } # follow symlink (on platforms that support links) catch { set conf [file join [file dirname $conf] [file readlink $conf]] } # only add if the (possibly resolved link) hasn't been seen before if {$conf ni $configs} { lappend configs $conf lappend result $t } } return $result } proc menu_create_load_machine_list {{mode "replace"}} { if {$mode eq "replace"} { set proc_to_exec osd_menu::menu_load_machine_exec_replace } elseif {$mode eq "add"} { set proc_to_exec osd_menu::menu_load_machine_exec_add } else { error "Undefined mode: $mode" } set menu_def [list \ execute $proc_to_exec \ font-size 8 \ border-size 2 \ width 200 \ xpos 110 \ ypos 130 \ header { text "Select Machine to Run" font-size 10 post-spacing 6 }] set items [get_filtered_configs machines] foreach i $items { set extra_info "" if {$i eq $::default_machine} { set extra_info " (default)" } lappend presentation "[utils::get_machine_display_name_by_config_name $i]$extra_info" } set items_sorted [list] set presentation_sorted [list] foreach i [lsort -dictionary -indices $presentation] { lappend presentation_sorted [lindex $presentation $i] lappend items_sorted [lindex $items $i] } lappend menu_def presentation $presentation_sorted return [prepare_menu_list $items_sorted 10 $menu_def] } proc menu_load_machine_exec_replace {item} { if {[catch {machine $item} errorText]} { osd::display_message $errorText error } else { menu_close_all } } proc menu_load_machine_exec_add {item} { set id [create_machine] set err [catch {${id}::load_machine $item} error_result] if {$err} { delete_machine $id osd::display_message "Error starting [utils::get_machine_display_name_by_config_name $item]: $error_result" error } else { menu_close_top activate_machine $id } } proc menu_create_extensions_list {} { set menu_def { execute menu_add_extension_exec font-size 8 border-size 2 width 200 xpos 110 ypos 130 header { text "Select Extension to Add" font-size 10 post-spacing 6 }} set items [get_filtered_configs extensions] set presentation [list] foreach i $items { lappend presentation [utils::get_extension_display_name_by_config_name $i] } set items_sorted [list] set presentation_sorted [list] foreach i [lsort -dictionary -indices $presentation] { lappend presentation_sorted [lindex $presentation $i] lappend items_sorted [lindex $items $i] } lappend menu_def presentation $presentation_sorted return [prepare_menu_list $items_sorted 10 $menu_def] } proc menu_add_extension_exec {item} { if {[catch {ext $item} errorText]} { osd::display_message $errorText error } else { menu_close_all } } proc menu_create_plugged_extensions_list {} { set menu_def { execute menu_remove_extension_exec font-size 8 border-size 2 width 200 xpos 110 ypos 130 header { text "Select Extension to Remove" font-size 10 post-spacing 6 }} set items [list_extensions] set possible_items [get_filtered_configs extensions] set useful_items [list] foreach item $items { if {$item in $possible_items} { lappend useful_items $item } } set presentation [list] foreach i $useful_items { lappend presentation [utils::get_extension_display_name_by_config_name ${i}] } lappend menu_def presentation $presentation return [prepare_menu_list $useful_items 10 $menu_def] } proc menu_remove_extension_exec {item} { menu_close_all remove_extension $item } proc get_pluggable_for_connector {connector} { set t [plug $connector] return [string range $t [string first ": " $t]+2 end] } proc menu_create_connectors_list {} { set menu_def { execute menu_connector_exec font-size 8 border-size 2 width 200 xpos 100 ypos 120 header { text "Connectors" font-size 10 post-spacing 6 }} set items [machine_info connector] set presentation [list] foreach item $items { set plugged [get_pluggable_for_connector $item] set plugged_presentation "" if {$plugged ne "--empty--"} { set plugged_presentation " ([machine_info pluggable $plugged])" } lappend presentation "[machine_info connector $item]: $plugged$plugged_presentation" } lappend menu_def presentation $presentation return [prepare_menu_list $items 5 $menu_def] } proc menu_connector_exec {item} { menu_create [create_menu_pluggable_list $item] select_menu_item [get_pluggable_for_connector $item] } proc create_menu_pluggable_list {connector} { set menu_def [list \ execute [list menu_plug_exec $connector] \ font-size 8 \ border-size 2 \ width 200 \ xpos 110 \ ypos 140 \ header [list text "What to Plug into [machine_info connector $connector]?" \ font-size 10 \ post-spacing 6 ]] set items [list] set class [machine_info connectionclass $connector] # find out which pluggables are already plugged # (currently a pluggable can be used only once per machine) set already_plugged [list] foreach other_connector [machine_info connector] { set other_plugged [get_pluggable_for_connector $other_connector] if {$other_plugged ne "--empty--" && $other_connector ne $connector} { lappend already_plugged $other_plugged } } # get a list of all pluggables that fit this connector # and which are not plugged yet in other connectors foreach pluggable [machine_info pluggable] { if {$pluggable ni $already_plugged && [machine_info connectionclass $pluggable] eq $class} { lappend items $pluggable } } set presentation [list] foreach item $items { lappend presentation "$item: [machine_info pluggable $item]" } set plugged [get_pluggable_for_connector $connector] if {$plugged ne "--empty--"} { set items [linsert $items 0 "--unplug--"] set presentation [linsert $presentation 0 "Nothing, unplug $plugged ([machine_info pluggable $plugged])"] } lappend menu_def presentation $presentation return [prepare_menu_list $items 5 $menu_def] } proc menu_plug_exec {connector pluggable} { set command "" if {$pluggable eq "--unplug--"} { set command "unplug $connector" } else { set command "plug $connector $pluggable" } #note: NO braces around $command if {[catch $command errorText]} { osd::display_message $errorText error } else { menu_close_top # refresh the connectors menu # The list must be recreated, so menu_refresh_top won't work menu_close_top menu_create [menu_create_connectors_list] } } proc menu_create_toys_list {} { set menu_def { execute menu_toys_exec font-size 8 border-size 2 width 200 xpos 100 ypos 120 header { text "Toys" font-size 10 post-spacing 6 }} set items [list] set presentation [list] # This also picks up 'lazy' command names foreach cmd [openmsx::all_command_names] { if {[string match toggle_* $cmd]} { lappend items $cmd lappend presentation [string map {_ " "} [string range $cmd 7 end]] } } lappend menu_def presentation $presentation return [prepare_menu_list $items 5 $menu_def] } proc menu_toys_exec {toy} { return [$toy] } proc ls {directory extensions} { set dirs [list] set specialdir [list] set items [list] if {[catch { set files [glob -nocomplain -tails -directory $directory -types {f r} *] set items [lsearch -regexp -all -inline -nocase $files .*\\.($extensions)] set dirs [glob -nocomplain -tails -directory $directory -types {d r x} *] set specialdir [glob -nocomplain -tails -directory $directory -types {hidden d} ".openMSX"] } errorText]} { osd::display_message "Unable to read dir $directory: $errorText" error } set dirs2 [list] foreach dir [concat $dirs $specialdir] { lappend dirs2 "$dir/" } set extra_entries [list] set volumes [file volumes] if {$directory ni $volumes} { lappend extra_entries ".." } else { if {[llength $volumes] > 1} { set extra_entries $volumes } } return [concat [lsort $extra_entries] [lsort $dirs2] [lsort $items]] } proc is_empty_dir {directory extensions} { set files [list] catch {set files [glob -nocomplain -tails -directory $directory -types {f r} *]} set items [lsearch -regexp -all -inline -nocase $files .*\\.($extensions)] if {[llength $items] != 0} {return false} set dirs [list] catch {set dirs [glob -nocomplain -tails -directory $directory -types {d r x} *]} if {[llength $dirs] != 0} {return false} set specialdir [list] catch {set specialdir [glob -nocomplain -tails -directory $directory -types {hidden d} ".openMSX"]} if {[llength $specialdir] != 0} {return false} return true } proc menu_create_ROM_list {path slot} { set menu_def [list execute [list menu_select_rom $slot] \ font-size 8 \ border-size 2 \ width 200 \ xpos 100 \ ypos 120 \ header { text "ROMs $::osd_rom_path" \ font-size 10 \ post-spacing 6 }] set extensions "rom|ri|mx1|mx2|zip|gz" set items [list] set presentation [list] if {[lindex [$slot] 2] ne "empty"} { lappend items "--eject--" lappend presentation "--eject-- [file tail [lindex [$slot] 1]]" } set i 1 foreach pool_path [filepool::get_paths_for_type rom] { if {$path ne $pool_path && [file exists $pool_path] && ![is_empty_dir $pool_path $extensions]} { lappend items $pool_path lappend presentation "\[ROM Pool $i\]" } incr i } set files [ls $path $extensions] set items [concat $items $files] set presentation [concat $presentation $files] lappend menu_def presentation $presentation return [prepare_menu_list $items 10 $menu_def] } proc menu_select_rom {slot item} { if {$item eq "--eject--"} { menu_close_all $slot eject reset } else { set fullname [file join $::osd_rom_path $item] if {[file isdirectory $fullname]} { menu_close_top set ::osd_rom_path [file normalize $fullname] menu_create [menu_create_ROM_list $::osd_rom_path $slot] } else { if {[catch {$slot $fullname} errorText]} { osd::display_message "Can't insert ROM: $errorText" error } else { menu_close_all set rominfo [getlist_rom_info] if {$rominfo eq ""} { osd::display_message "No ROM information available..." } else { osd::display_message "Now running ROM:\nTitle:\nYear:\nCompany:\nCountry:\nStatus:\nRemark:" append result " \n" \ "[dict get $rominfo title]\n" \ "[dict get $rominfo year]\n" \ "[dict get $rominfo company]\n" \ "[dict get $rominfo country]\n" \ "[dict get $rominfo status]\n" if {[dict get $rominfo remark] ne ""} { append result [dict get $rominfo remark] } else { append result "None" } set txt_size 6 set xpos 35 # TODO: prevent this from being duplicated from osd_widgets::text_box if {$::scale_factor == 1} { set txt_size 9 set xpos 53 } # TODO: this code knows the internal name of the widget of osd::display_message proc... it shouldn't need to. osd create text osd_display_message.rominfo_text -x $xpos -y 2 -size $txt_size -rgba 0xffffffff -text "$result" } reset } } } } proc menu_create_disk_list {path drive} { set menu_def [list execute [list menu_select_disk $drive] \ font-size 8 \ border-size 2 \ width 200 \ xpos 100 \ ypos 120 \ header { text "Disks $::osd_disk_path" \ font-size 10 \ post-spacing 6 }] set cur_image [lindex [$drive] 1] set extensions "dsk|zip|gz|xsa|dmk|di1|di2" set items [list] set presentation [list] if {[lindex [$drive] 2] ne "empty readonly"} { lappend items "--eject--" lappend presentation "--eject-- [file tail $cur_image]" } set i 1 foreach pool_path [filepool::get_paths_for_type disk] { if {$path ne $pool_path && [file exists $pool_path] && ![is_empty_dir $pool_path $extensions]} { lappend items $pool_path lappend presentation "\[Disk Pool $i\]" } incr i } if {$cur_image ne $path} { lappend items "." lappend presentation "--insert this dir as disk--" } set files [ls $path $extensions] set items [concat $items $files] set presentation [concat $presentation $files] lappend menu_def presentation $presentation return [prepare_menu_list $items 10 $menu_def] } proc menu_select_disk {drive item} { if {$item eq "--eject--"} { set cur_image [lindex [$drive] 1] menu_close_all $drive eject osd::display_message "Disk $cur_image ejected!" } else { set fullname [file normalize [file join $::osd_disk_path $item]] if {[file isdirectory $fullname] && $item ne "."} { menu_close_top set ::osd_disk_path [file normalize $fullname] menu_create [menu_create_disk_list $::osd_disk_path $drive] } else { if {[catch {$drive $fullname} errorText]} { osd::display_message "Can't insert disk: $errorText" error } else { menu_close_all if {$item eq "."} { set item $fullname } osd::display_message "Disk $item inserted!" } } } } proc menu_create_tape_list {path} { variable taperecordings_directory set menu_def { execute menu_select_tape font-size 8 border-size 2 width 200 xpos 100 ypos 120 header { text "Tapes $::osd_tape_path" font-size 10 post-spacing 6 }} set extensions "cas|wav|zip|gz" set items [list] set presentation [list] lappend items "--create--" lappend presentation "--create new and insert--" set inserted [lindex [cassetteplayer] 1] if {$inserted ne ""} { lappend items "--eject--" lappend presentation "--eject-- [file tail $inserted]" lappend items "--rewind-" lappend presentation "--rewind-- [file tail $inserted]" } if {$path ne $taperecordings_directory && [file exists $taperecordings_directory]} { lappend items $taperecordings_directory lappend presentation "\[My Tape Recordings\]" } set i 1 foreach pool_path [filepool::get_paths_for_type tape] { if {$path ne $pool_path && [file exists $pool_path] && ![is_empty_dir $pool_path $extensions]} { lappend items $pool_path lappend presentation "\[Tape Pool $i\]" } incr i } set files [ls $path $extensions] set items [concat $items $files] set presentation [concat $presentation $files] lappend menu_def presentation $presentation return [prepare_menu_list $items 10 $menu_def] } proc menu_select_tape {item} { variable taperecordings_directory if {$item eq "--create--"} { menu_close_all osd::display_message [cassetteplayer new [menu_free_tape_name]] } elseif {$item eq "--eject--"} { menu_close_all osd::display_message [cassetteplayer eject] } elseif {$item eq "--rewind--"} { menu_close_all osd::display_message [cassetteplayer rewind] } else { set fullname [file join $::osd_tape_path $item] if {[file isdirectory $fullname]} { menu_close_top set ::osd_tape_path [file normalize $fullname] menu_create [menu_create_tape_list $::osd_tape_path] } else { if {[catch {cassetteplayer $fullname} errorText]} { osd::display_message "Can't set tape: $errorText" error } else { osd::display_message "Inserted tape $item!" menu_close_all } } } } proc menu_free_tape_name {} { variable taperecordings_directory set existing [list] foreach f [lsort [glob -tails -directory $taperecordings_directory -type f -nocomplain *.wav]] { lappend existing [file rootname $f] } set i 1 while 1 { set name [format "[guess_title untitled] %04d" $i] if {$name ni $existing} { return $name } incr i } } proc menu_create_hdd_list {path} { return [prepare_menu_list [ls $path "dsk|zip|gz"] \ 10 \ { execute menu_select_hdd font-size 8 border-size 2 width 200 xpos 100 ypos 120 header { text "Hard disk images $::osd_hdd_path" font-size 10 post-spacing 6 }}] } proc menu_select_hdd {item} { set fullname [file join $::osd_hdd_path $item] if {[file isdirectory $fullname]} { menu_close_top set ::osd_hdd_path [file normalize $fullname] menu_create [menu_create_hdd_list $::osd_hdd_path] } else { confirm_action "Really power off to change HDD image?" osd_menu::confirm_change_hdd $item } } proc confirm_change_hdd {item result} { menu_close_top if {$result eq "Yes"} { set fullname [file join $::osd_hdd_path $item] if {[catch {set ::power off; hda $fullname} errorText]} { osd::display_message "Can't change hard disk image: $errorText" error # TODO: we already powered off even though the file may be invalid... save state first? } else { osd::display_message "Changed hard disk image to $item!" menu_close_all } set ::power on } } proc menu_create_ld_list {path} { set eject_item [list] set inserted [lindex [laserdiscplayer] 1] if {$inserted ne ""} { lappend eject_item "--eject-- [file tail $inserted]" } return [prepare_menu_list [concat $eject_item [ls $path "ogv"]] \ 10 \ { execute menu_select_ld font-size 8 border-size 2 width 200 xpos 100 ypos 120 header { text "Laserdiscs $::osd_ld_path" font-size 10 post-spacing 6 }}] } proc menu_select_ld {item} { if {[string range $item 0 8] eq "--eject--"} { menu_close_all osd::display_message [laserdiscplayer eject] } else { set fullname [file join $::osd_ld_path $item] if {[file isdirectory $fullname]} { menu_close_top set ::osd_ld_path [file normalize $fullname] menu_create [menu_create_ld_list $::osd_ld_path] } else { if {[catch {laserdiscplayer insert $fullname} errorText]} { osd::display_message "Can't load LaserDisc: $errorText" error } else { osd::display_message "Loaded LaserDisc $item!" menu_close_all } } } } proc get_savestates_list_presentation_sorted {} { set presentation [list] foreach i [lsort -integer -index 1 -decreasing [savestate::list_savestates_raw]] { if {[info commands clock] ne ""} { set pres_str [format "%s (%s)" [lindex $i 0] [clock format [lindex $i 1] -format "%x - %X"]] } else { set pres_str [lindex $i 0] } lappend presentation $pres_str } return $presentation } proc menu_create_load_state {} { set menu_def \ { execute menu_loadstate_exec font-size 8 border-size 2 width 200 xpos 100 ypos 120 on-open {osd create rectangle "preview" -x 225 -y 5 -w 90 -h 70 -rgba 0x30303080 -scaled true} on-close {osd destroy "preview"} on-select menu_loadstate_select on-deselect menu_loadstate_deselect header { text "Load State" font-size 10 post-spacing 6 }} set items [list_savestates -t] lappend menu_def presentation [get_savestates_list_presentation_sorted] return [prepare_menu_list $items 10 $menu_def] } proc menu_create_save_state {} { set items [concat [list "create new"] [list_savestates -t]] set menu_def \ { execute menu_savestate_exec font-size 8 border-size 2 width 200 xpos 100 ypos 120 on-open {osd create rectangle "preview" -x 225 -y 5 -w 90 -h 70 -rgba 0x30303080 -scaled true} on-close {osd destroy "preview"} on-select menu_loadstate_select on-deselect menu_loadstate_deselect header { text "Save State" font-size 10 post-spacing 6 }} lappend menu_def presentation [concat [list "create new"] [get_savestates_list_presentation_sorted]] return [prepare_menu_list $items 10 $menu_def] } proc menu_loadstate_select {item} { set png $::env(OPENMSX_USER_DATA)/../savestates/${item}.png catch {osd create rectangle "preview.image" -relx 0.05 -rely 0.05 -w 80 -h 60 -image $png} } proc menu_loadstate_deselect {item} { osd destroy "preview.image" } proc menu_loadstate_exec {item} { if {[catch {loadstate $item} errorText]} { osd::display_message $errorText error } else { menu_close_all } } proc menu_savestate_exec {item} { if {$item eq "create new"} { set item [menu_free_savestate_name] confirm_save_state $item "Yes" menu_close_all } else { confirm_action "Overwrite $item?" osd_menu::confirm_save_state $item } } proc confirm_save_state {item result} { menu_close_top if {$result eq "Yes"} { if {[catch {savestate $item} errorText]} { osd::display_message $errorText error } else { osd::display_message "State saved to $item!" menu_close_all } } } proc menu_free_savestate_name {} { set existing [list_savestates] set i 1 while 1 { set name [format "[guess_title savestate] %04d" $i] if {$name ni $existing} { return $name } incr i } } proc confirm_action {text action item} { set items [list "No" "Yes"] set menu_def [list execute [list $action $item] \ font-size 8 \ border-size 2 \ width 210 \ xpos 100 \ ypos 100 \ header [list text $text \ font-size 10 \ post-spacing 6 ]] osd_menu::menu_create [osd_menu::prepare_menu_list $items [llength $items] $menu_def] } # Keep openmsx console from interfering with the osd menu: # when the console is activated while the osd menu is already open, we want # to prevent the osd menu from receiving the keys that are pressed in the # console. variable old_console $::console proc console_input_layer {name1 name1 op} { global console variable old_console if {$console == $old_console} return set old_console $console if {$console} { activate_input_layer console -blocking } else { deactivate_input_layer console } } trace add variable ::console write [namespace code console_input_layer] namespace export main_menu_open namespace export main_menu_close namespace export main_menu_toggle } ;# namespace osd_menu namespace import osd_menu::* openMSX-RELEASE_0_12_0/share/scripts/_osd_nemesis.tcl000066400000000000000000000067411257557151200224130ustar00rootroot00000000000000set_help_text toggle_nemesis_1_shield \ {This command (de)activates some Nemesis 1 toys: - A shield around the players ship: enemy bullets will bounce off this shield. - Allow to move the ship with the mouse (click right mouse button to activate, click again to disable). } namespace eval osd_nemesis { variable move_active false variable after_frame_id variable after_mouse_button_id proc toggle_nemesis_1_shield {} { if {[osd exists "nemesis1"]} { disable_nemesis_1_shield osd::display_message "Nemesis 1 shield deactivated" info } else { enable_nemesis_1_shield osd::display_message "Nemesis 1 shield activated" info } return "" } namespace export toggle_nemesis_1_shield proc disable_nemesis_1_shield {} { variable after_frame_id variable after_mouse_button_id osd destroy "nemesis1" after cancel $after_frame_id after cancel $after_mouse_button_id } proc enable_nemesis_1_shield {} { variable move_active variable after_frame_id variable after_mouse_button_id osd_widgets::msx_init "nemesis1" osd create rectangle "nemesis1.shield" \ -fadeCurrent 0 -fadeTarget 0 -fadePeriod 2 \ -image [data_file scripts/shield.png] set move_active false set after_frame_id [after time 0.1 osd_nemesis::after_frame] set after_mouse_button_id [after "mouse button1 down" osd_nemesis::after_mouse] } proc after_mouse {} { # click to capture and click again to release variable move_active variable after_mouse_button_id set move_active [expr {!$move_active}] set after_mouse_button_id [after "mouse button1 down" osd_nemesis::after_mouse] } proc after_frame {} { variable move_active variable after_frame_id osd_widgets::msx_update "nemesis1" if {$move_active} { # move vic viper to mouse position lassign [osd info "nemesis1" -mousecoord] x y catch { ;# when the cursor is hidden, -mousecoord won't give sane values poke 0xe206 [utils::clip 0 255 [expr {int($x)}]] poke 0xe204 [utils::clip 0 212 [expr {int($y)}]] } } # vic viper location set x [utils::clip 9 255 [peek 0xe206]] set y [utils::clip 0 192 [peek 0xe204]] osd configure "nemesis1.shield" -relx [expr {$x - 9}] -rely [expr {$y - 9}] for {set i 0} {$i < 32} {incr i} { # set base address set addr [expr {0xe300 + ($i * 0x20)}] # get enemy id set id [peek $addr] # enegry pod / flash pod / life pod / score pod / # endless score pod / explosion / end brain connections if {(17 <= $id) && ($id <= 26) && ($id != 20)} continue set a [peek [expr {$addr + 6}]] set b [peek [expr {$addr + 4}]] # Not in contact with shield? Then do nothing. if {((($a - $x + 6) ** 2) + (($b - $y + 7) ** 2)) >= 961} continue # ground turrets change into razor discs :) if {$id == 1 && $i < 16} {poke $addr 2} # homing ships and walkers if {$id == 4 || $id == 5} {poke $addr 21} # change color of sprite when in contact with shield # poke [expr {$addr + 13}] 15 # Shield routine (hit front/back/top/bottom) set shieldstrength 2 set dx [expr {($x > $a) ? -$shieldstrength : $shieldstrength}] set dy [expr {($y > $b) ? -$shieldstrength : $shieldstrength}] set xn [expr {$a + $dx}] set yn [expr {$b + $dy}] poke [expr {$addr + 6}] [utils::clip 0 255 $xn] poke [expr {$addr + 4}] [utils::clip 0 255 $yn] poke [expr {$addr + 8}] [expr {$dy & 255}] poke [expr {$addr + 10}] [expr {$dx & 255}] # make shield visible osd configure "nemesis1.shield" -fadeCurrent 0.5 } set after_frame_id [after frame osd_nemesis::after_frame] } } ;# namespace osd_nemesis namespace import osd_nemesis::* openMSX-RELEASE_0_12_0/share/scripts/_osd_widgets.tcl000066400000000000000000000204211257557151200224050ustar00rootroot00000000000000namespace eval osd_widgets { set help_text \ {The command 'osd_widgets::msx_init' takes one parameter, this parameter will be used as our base layer which will be scaled according to the MSX resultion adjusted for 'set adjust', 'scale factor' and 'horizontal_stretch'. All these compensation factors ('set adjust', ...) can change over time. So it is needed to 'regularly' (e.g. in a 'after frame' callback) re-adjust the msx layer. This can be done with the 'osd_widgets::msx_update' proc. Example: osd_widgets::msx_init baselayer osd create rectangle baselayer.box -x 10 -y 10 -h 16 -w 16 -rgb 0xffffff ... osd_widgets::msx_update baselayer This will display a white 16x16 box at MSX location x,y == 10,10.} set_help_text osd_widgets::msx_init $help_text set_help_text osd_widgets::msx_update $help_text proc msx_init {name} { osd create rectangle $name -scaled true -alpha 0 msx_update $name } proc msx_update {name} { # compensate for horizontal-stretch and set-adjust set hstretch $::horizontal_stretch set xsize [expr {320.0 / $hstretch}] set xoffset [expr {($hstretch - 256) / 2 * $xsize}] set ysize 1 set lines [expr {([vdpreg 9] & 128) ? 212 : 192}] set yoffset [expr {(240 - $lines) / 2 * $ysize}] set adjreg [vdpreg 18] set hadj [expr {(($adjreg & 15) ^ 7) - 7}] set vadj [expr {(($adjreg >> 4) ^ 7) - 7}] set xoffset [expr {$xoffset + $xsize * $hadj}] set yoffset [expr {$yoffset + $ysize * $vadj}] osd configure $name -x $xoffset -y $yoffset -w $xsize -h $ysize } set_help_text create_power_bar\ {The command 'osd_widgets::create_power_bar' supports the following parameters: -name == Name of the power bar -w == Width of the power bar (in pixels) -h == Height of the power bar -barcolor == Powerbar color -background == When power declines this color is shown -edgecolor == This is the edge color (try white when I doubt which color to use) Colors must have the following hexadecimal format 0xRRGGBBAA. The power bar is initially created outside the viewable area, so we need to invoke the 'update_power_bar' command to make it visible. Use 'hide_power_bar' to remove it again.} set_help_text update_power_bar\ {The command 'update_power_bar' uses the following parameters: -name == Name of the power bar -x == vertical position of the power bar -y == horizontal position of the power bar -power == fill rate of the power bar in decimal percentages (10% == 0.1) -text == text to be printed above the power bar} proc create_power_bar {name w h barcolor background edgecolor} { osd create rectangle $name -rely 999 -relw $w -relh $h -rgba $background osd create rectangle $name.top -x -1 -y -1 -relw 1 -w 2 -h 1 -rgba $edgecolor osd create rectangle $name.bottom -x -1 -rely 1 -relw 1 -w 2 -h 1 -rgba $edgecolor osd create rectangle $name.left -x -1 -w 1 -relh 1 -rgba $edgecolor osd create rectangle $name.right -relx 1 -w 1 -relh 1 -rgba $edgecolor osd create rectangle $name.bar -relw 1 -relh 1 -rgba $barcolor -z 18 osd create text $name.text -x 0 -y -6 -size 4 -rgba $edgecolor } proc update_power_bar {name x y power text} { if {$power > 1.0} {set power 1.0} if {$power < 0.0} {set power 0.0} osd configure $name -relx $x -rely $y osd configure $name.bar -relw $power osd configure $name.text -text "$text" } proc hide_power_bar {name} { osd configure $name -rely 999 } set_help_text toggle_fps \ {Enable/disable a frames per second indicator in the top-left corner of the screen.} variable fps_after proc toggle_fps {} { variable fps_after if {[info exists fps_after]} { after cancel $osd_widgets::fps_after osd destroy fps_viewer unset fps_after } else { osd create rectangle fps_viewer -x 5 -y 5 -z 0 -w 63 -h 20 -rgba 0x00000080 osd create text fps_viewer.text -x 5 -y 3 -z 1 -rgba 0xffffffff proc fps_refresh {} { variable fps_after osd configure fps_viewer.text -text [format "%2.1fFPS" [openmsx_info fps]] set fps_after [after realtime .5 [namespace code fps_refresh]] } fps_refresh } return "" } set_help_text osd_widgets::text_box\ {The 'osd_widgets::text_box' widget supports the same properties as a 'rectangle' widget with the following additions: -text: defines the text to be printed, can have multiple lines (separated by 'new line' characters). -textcolor: defines the color of the text -textsize: defines the font size of the text} variable widget_click_handlers variable opaque_duration 2.5 variable fade_out_duration 2.5 variable fade_in_duration 0.4 proc text_box {name args} { variable widget_click_handlers variable opaque_duration # Default values in case nothing is given set txt_color 0xffffffff set txt_size 6 # Process arguments set rect_props [list] foreach {key val} $args { switch -- $key { -text {set message $val} -textrgba {set txt_color $val} -textsize {set txt_size $val} default {lappend rect_props $key $val} } } if {$message eq ""} return # For handheld devices set minimal text size to 9 # TODO: does this belong here or at some higher level? if {($::scale_factor == 1) && ($txt_size < 9)} { set txt_size 9 } # Destroy widget (if it already existed) osd destroy $name # Guess height of rectangle osd create rectangle $name {*}$rect_props -h [expr {4 + $txt_size}] osd create text $name.text -x 2 -y 2 -size $txt_size -rgb $txt_color \ -text $message -wrap word -wraprelw 1.0 -wrapw -4 # Adjust height of rectangle to actual text height (depends on newlines # and text wrapping). catch { lassign [osd info $name.text -query-size] x y osd configure $name -h [expr {4 + $y}] } # if the widget was still active, kill old click handler if {[info exists widget_click_handlers($name)]} { after cancel $widget_click_handlers($name) } set widget_click_handlers($name) [after "mouse button1 down" \ "osd_widgets::click_handler $name"] after realtime $opaque_duration "osd_widgets::timer_handler $name" return "" } proc click_handler {name} { if {[osd::is_cursor_in $name]} { kill_widget $name } return "" } proc kill_widget {name} { variable widget_click_handlers after cancel $widget_click_handlers($name) unset widget_click_handlers($name) osd destroy $name } proc timer_handler {name} { variable opaque_duration variable fade_out_duration variable fade_in_duration # clicking it might have killed it already if {![osd exists $name]} { return } # if already faded out... don't poll again and clean up if {[osd info $name -fadeCurrent] == 0.0} { kill_widget $name return } # If the cursor is over the widget, we fade-in fast and leave the widget # opaque for some time (= don't poll for some longer time). Otherwise # we fade-out slow and more quickly poll the cursor position. if {[osd::is_cursor_in $name]} { osd configure $name -fadePeriod $fade_in_duration -fadeTarget 1.0 after realtime $opaque_duration "osd_widgets::timer_handler $name" } else { osd configure $name -fadePeriod $fade_out_duration -fadeTarget 0.0 after realtime 0.25 "osd_widgets::timer_handler $name" } } proc volume_control {incr_val} { if {![osd exists volume]} { osd create rectangle volume -x 0 -y 0 -h 32 -w 320 -rgba 0x000000a0 -scaled true osd create rectangle volume.bar -x 16 -y 16 -h 8 -w 290 -rgba 0x000000c0 -borderrgba 0xffffffff -bordersize 1 osd create rectangle volume.bar.meter -x 1 -y 1 -h 6 -w 288 -rgba "0x00aa33e8 0x00dd66e8 0x00cc55e8 0x00ff77e8" osd create text volume.text -x 16 -y 3 -size 10 -rgba 0xffffffff } incr ::master_volume $incr_val if {$::master_volume == 0} {set ::mute on} else {set ::mute off} osd configure volume.bar.meter -w [expr {($::master_volume / 100.00) * 288}] osd configure volume.text -text [format "Volume: %03d" $::master_volume] osd configure volume -fadePeriod 5 -fadeTarget 0 -fadeCurrent 1 } # only export stuff that is useful in other scripts or for the console user namespace export toggle_fps namespace export msx_init namespace export msx_update namespace export box namespace export text_box namespace export create_power_bar namespace export update_power_bar namespace export hide_power_bar namespace export volume_control };# namespace osd_widgets # only import stuff to global that is useful outside of scripts (i.e. for the console user) namespace import osd_widgets::toggle_fps namespace import osd_widgets::volume_control openMSX-RELEASE_0_12_0/share/scripts/_psg_log.tcl000066400000000000000000000057111257557151200215310ustar00rootroot00000000000000# Binary format PSG logger # # (see http://www.msx.org/forumtopic6258.html for more context) # # Shiru wrote: # # Is it possible to make output in standart binary *.psg format (used in # emulators like x128, very-very old version of fMSX, Z80Stealth and some other)? # # Header: # # +0 4 Signature #50 #53 #47 #1A ('PSG' and byte #1A) # +4 1 Version number # +5 1 Interrupts freq. (50/60) # +6 10 Unused # # Note: only signature is necessary, many emulators just fill other bytes by zero. # # Data stream: # # #00..#FC - register number, followed by data byte (value for that register) # #FD - EOF (usually not used in real logs, and not all progs can handle it) # #FE - number of interrupts (followed byte with number of interrupts/4, usually not used) # #FF - single interrupt namespace eval psg_log { set_help_text psg_log \ {This script logs PSG registers 0 through 13 to a file as they are written, seperated each frame. The file format is the PSG format used in some emulators. More information is here: http://www.msx.org/forumtopic6258.html (and in the comments of this script). Usage: psg_log start start logging PSG registers to (default: log.psg) psg_log stop stop logging PSG registers Examples: psg_log start start logging registers to default file log.psg psg_log start myfile.psg start logging to file myfile.psg psg_log stop stop logging PSG registers } set_tabcompletion_proc psg_log [namespace code tab_psg_log] proc tab_psg_log { args } { if {[llength $args] == 2} { return "start stop" } } variable psg_log_file -1 variable psg_log_wp "" variable psg_log_reg proc psg_log { subcommand {filename "log.psg"} } { variable psg_log_file variable psg_log_wp if {$subcommand eq "start"} { if {$psg_log_file != -1} { close $psg_log_file } set psg_log_file [open $filename {WRONLY TRUNC CREAT}] fconfigure $psg_log_file -translation binary set header "0x50 0x53 0x47 0x1A 0 0 0 0 0 0 0 0 0 0 0 0" puts -nonewline $psg_log_file [binary format c16 $header] if {$psg_log_wp == ""} { set psg_log_wp [debug set_watchpoint write_io {0xa0 0xa1} 1 { psg_log::psg_log_write }] } after frame [namespace code do_psg_frame] return "" } elseif {$subcommand eq "stop"} { debug remove_watchpoint $psg_log_wp close $psg_log_file set psg_log_file -1 return "" } else { error "bad option \"$subcommand\": must be start, stop" } } proc do_psg_frame {} { variable psg_log_file if {$psg_log_file == -1} return puts -nonewline $psg_log_file [binary format c 0xFF] after frame [namespace code do_psg_frame] } proc psg_log_write {} { variable psg_log_file variable psg_log_reg if {($::wp_last_address & 1) == 0} { set psg_log_reg $::wp_last_value } else { if {$psg_log_reg < 14} { puts -nonewline $psg_log_file [binary format c2 "$psg_log_reg $::wp_last_value"] } } } namespace export psg_log } ;# namespace psg_log namespace import psg_log::* openMSX-RELEASE_0_12_0/share/scripts/_psg_profile.tcl000066400000000000000000000034401257557151200224050ustar00rootroot00000000000000namespace eval psg_profile { set_help_text psg_profile \ {Select a PSG sound profile. Usage: psg_profile Shows the current profile psg_profile -list Shows the possible profiles psg_profile Select a new profile } variable psg_settings {::PSG_vibrato_percent ::PSG_vibrato_frequency ::PSG_detune_percent ::PSG_detune_frequency} variable psg_profiles [dict create \ normal { 0.0 - 0.0 - } \ vibrato { 1.0 5.0 0.0 - } \ detune { 0.0 - 0.5 5.0 } \ detune_vibrato { 1.0 5.0 0.5 5.0 }] set_tabcompletion_proc psg_profile [namespace code tab_psg_profile] proc tab_psg_profile {args} { variable psg_profiles set result [dict keys $psg_profiles] lappend result "-list" } proc equal_psg_profile {values} { variable psg_settings foreach setting $psg_settings value $values { if {$value ne "-"} { if {[set $setting] != $value} { return false } } } return true } proc get_psg_profile {} { variable psg_settings set result [list] foreach setting $psg_settings { lappend result [set $setting] } return $result } proc set_psg_profile {values} { variable psg_settings foreach setting $psg_settings value $values { if {$value ne "-"} { set $setting $value } } } proc psg_profile {{profile ""}} { variable psg_profiles if {$profile eq ""} { dict for {name value} $psg_profiles { if {[equal_psg_profile $value]} { return $name } } return "Custom profile: [get_psg_profile]" } elseif {$profile eq "-list"} { return [dict keys $psg_profiles] } else { if {![dict exists $psg_profiles $profile]} { error "No such profile: $profile" } set_psg_profile [dict get $psg_profiles $profile] } } namespace export psg_profile } ;# namespace psg_profile namespace import psg_profile::* openMSX-RELEASE_0_12_0/share/scripts/_quitmenu.tcl000066400000000000000000000007751257557151200217530ustar00rootroot00000000000000namespace eval quitmenu { proc quit_menu {} { set items [list "No" "Yes"] set menu_def \ { execute quitmenu::quit_menu_exec font-size 8 border-size 2 width 120 xpos 100 ypos 100 header { text "Really exit openMSX?" font-size 10 post-spacing 6 }} osd_menu::do_menu_open [osd_menu::prepare_menu_list $items [llength $items] $menu_def] activate_input_layer quit_menu } proc quit_menu_exec {item} { osd_menu::menu_close_all if {$item eq "Yes"} {exit} } } openMSX-RELEASE_0_12_0/share/scripts/_record_channels.tcl000066400000000000000000000176241257557151200232360ustar00rootroot00000000000000namespace eval record_channels { set_help_text record_channels \ {Convenience function to control recording of individual channels of sounddevice(s). There are three subcommands (start, stop and list) to respectively start recording additional channels, to stop recording all or some channels and to list which channels are currently being recorded. record_channels [start] [ []] record_channels stop [ []] record_channels list When starting recording, you can optionally specify a prefix for the destination file names with the -prefix option. Some examples will make it much clearer: - To start recording: record_channels start PSG record all PSG channels record_channels PSG the 'start' keyword can be left out record_channels SCC 1,3-5 only record channels 1 and 3 to 5 record_channels SCC PSG 1 record all SCC channels + PSG channel 1 record_channels all record all channels of all devices record_channels all -prefix t record all channels of all devices using prefix 't' - To stop recording record_channels stop stop all recording record_channels stop PSG stop recording all PSG channels record_channels stop SCC 3,5 stop recording SCC channels 3 and 5 - To show the current status record_channels list shows which channels are being recorded } set mute_help_text \ {Convenience function to control (un)muting of individual channels of soundevice(s). Examples: mute_channels PSG mute all PSG channels mute_channels SCC 2,4 mute SCC channels 2 and 4 unmute_channels PSG 1 SCC 3-5 unmute PSG channel 1, SCC channels 3 to 5 mute_channels show which channels are currently muted unmute_channels unmute all channels on all devices solo PSG 2 mute everything except PSG channel 2 } set_help_text mute_channels $mute_help_text set_help_text unmute_channels $mute_help_text set_help_text solo $mute_help_text set_tabcompletion_proc record_channels [namespace code tab_sounddevice_channels] set_tabcompletion_proc mute_channels [namespace code tab_sounddevice_channels] set_tabcompletion_proc unmute_channels [namespace code tab_sounddevice_channels] set_tabcompletion_proc solo [namespace code tab_sounddevice_channels] proc tab_sounddevice_channels {args} { set result [machine_info sounddevice] if {([lindex $args 0] eq "record_channels") && ([llength $args] == 2)} { set result [concat $result "start stop list all"] } return $result } proc parse_channel_numbers {str} { set result [list] foreach a [split $str ", "] { set b [split $a "-"] foreach c $b { if {![string is integer $c]} { error "Not an integer: $c" } } switch [llength $b] { 0 {} 1 {lappend result [lindex $b 0]} 2 {for {set i [lindex $b 0]} {$i <= [lindex $b 1]} {incr i} { lappend result $i }} default {error "Invalid range: $a"} } } return [lsort -unique $result] } proc get_all_channels {device} { set i 1 set channels [list] while {[info exists ::${device}_ch${i}_record]} { lappend channels $i incr i } return $channels } proc get_all_devices_all_channels {} { set result [list] foreach device [machine_info sounddevice] { lappend result $device [get_all_channels $device] } return $result } proc get_recording_channels {} { set result [list] set sounddevices [machine_info sounddevice] foreach device $sounddevices { set active [list] foreach ch [get_all_channels $device] { set var ::${device}_ch${ch}_record if {[set $var] ne ""} { lappend active $ch } } if {[llength $active]} { lappend result "$device: $active" } } return $result } proc get_muted_channels {} { set result [list] set sounddevices [machine_info sounddevice] foreach device $sounddevices { set active [list] foreach ch [get_all_channels $device] { set var ::${device}_ch${ch}_mute if {[set $var]} { lappend active $ch } } if {[llength $active]} { lappend result "$device: $active" } } return $result } proc parse_device_channels {tokens} { set sounddevices [machine_info sounddevice] set device_channels [list] if {[lindex $tokens 0] == "all"} { foreach device $sounddevices { lappend device_channels $device [get_all_channels $device] } return $device_channels } while {[llength $tokens]} { set device [lindex $tokens 0] set tokens [lrange $tokens 1 end] if {$device ni $sounddevices} { error "Unknown sounddevice: $device" } set range [lindex $tokens 0] if {($range ne "") && ($range ni $sounddevices)} { set channels [parse_channel_numbers $range] set tokens [lrange $tokens 1 end] foreach ch $channels { if {![info exists ::${device}_ch${ch}_record]} { error "No channel $ch on sounddevice $device" } } } else { set channels [get_all_channels $device] } lappend device_channels $device $channels } return $device_channels } proc record_channels {args} { set start true set device_channels [list] # parse subcommand (default is start) set first [lindex $args 0] switch $first { list { return [join [get_recording_channels] "\n"] } start - stop { set start [string equal $first "start"] set args [lrange $args 1 end] } } if {$start} { set prefix [utils::filename_clean [guess_title]] # see if there's a -prefix option to override the default set prefix_index [lsearch -exact $args "-prefix"] if {$prefix_index >= 0 && $prefix_index < ([llength $args] - 1)} { set prefix [lindex $args [expr {$prefix_index + 1}]] set args [lreplace $args $prefix_index [expr {$prefix_index + 1}]] } } # parse devices/channels set device_channels [parse_device_channels $args] # stop without any further arguments -> stop all if {!$start && ![llength $device_channels]} { foreach device [machine_info sounddevice] { set channels [get_all_channels $device] lappend device_channels $device $channels } } set retval "" # actually start/stop recording foreach {device channels} $device_channels { foreach ch $channels { set var ::${device}_ch${ch}_record if {$start} { set directory [file normalize $::env(OPENMSX_USER_DATA)/../soundlogs] # create dir always file mkdir $directory set software_section $prefix if {$software_section ne ""} { set software_section "${software_section}-" } set $var [file join $directory ${software_section}${device}-ch${ch}.wav] append retval "Recording $device channel $ch to [set $var]...\n" } else { if {[set $var] ne ""} { append retval "Stopped recording $device channel $ch to [set $var]...\n" } set $var "" } } } return $retval } proc do_mute_channels {device_channels state} { foreach {device channels} $device_channels { foreach ch $channels { set ::${device}_ch${ch}_mute $state } } } proc mute_channels {args} { # parse devices/channels set device_channels [parse_device_channels $args] # no argumnets specified, list muted channels if {![llength $device_channels]} { return [join [get_muted_channels] "\n"] } # actually mute channels do_mute_channels $device_channels true } proc unmute_channels {args} { # parse devices/channels set device_channels [parse_device_channels $args] # no arguments specified, unmute all channels if {![llength $device_channels]} { set device_channels [get_all_devices_all_channels] } #actually unmute channels do_mute_channels $device_channels false } proc solo {args} { # parse devices/channels set device_channels [parse_device_channels $args] # mute everything, unmute specified channels do_mute_channels [get_all_devices_all_channels] true do_mute_channels $device_channels false } namespace export record_channels namespace export mute_channels namespace export unmute_channels namespace export solo } ;# namspace record_channels namespace import record_channels::* openMSX-RELEASE_0_12_0/share/scripts/_record_chunks.tcl000066400000000000000000000101671257557151200227310ustar00rootroot00000000000000namespace eval record_chunks { set_help_text record_chunks \ {Records videos in doublesize format, but has extra options to record for a limited amount of time and to chop up videos in chunks of a certain number of seconds. Usage: record_chunks [-chunktime ] [-totaltime | -numchunks ] start filename record_chunks stop The chunktime parameter controls the maximum time for each chunk (default: 14:59 for YouTube) in seconds and the totaltime parameter controls the total time to record in seconds (default: infinite). Instead of the totalchunks parameter, you can also use the numchunks parameter to control the total time as a multiple of the chunk time. The options are mutually exclusive. The chunks are recorded with a chunk-number suffix behind filename. Do not use an extension with filename, just a name is enough. Always provide the options first. Examples: record_chunks -numchunks 1 start simplegame Records a movie of 14:59 (maximum time for YouTube) to simplegame.avi record_chunks start longgame Records an infinite number of movies of 14:59 (maximum time for YouTube), until you use record_chunks stop. Names are longname01.avi, longname02.avi, etc. record_chunks -chunktime 60 -totaltime 230 start partgame Records movies of 1 minute until the total recorded time is 3:50, names are partgame01.avi, partgame02.avi, partgame03.avi and partgame04.avi. } variable filenamebase variable iteration variable chunk_time variable total_time variable next_after_id proc record_chunks {args} { variable filenamebase variable iteration variable chunk_time variable total_time variable next_after_id # the defaults set chunk_time 899 ;# max time for a YouTube video set total_time -1 ;# record until someone says stop .. set num_chunks -1 ;# .. or till we recorded this many chunks while (1) { switch -- [lindex $args 0] { "-chunktime" { set chunk_time [lindex $args 1] set args [lrange $args 2 end] } "-totaltime" { set total_time [lindex $args 1] set args [lrange $args 2 end] } "-numchunks" { set num_chunks [lindex $args 1] set args [lrange $args 2 end] } "default" { break } } } if {($total_time > 0) && ($num_chunks > 0)} { error "You can't use both -numchunks and -totaltime options at the same time." } # do this outside of the loop, so that the order of options isn't too strict if {$num_chunks > 0} { set total_time [expr {$num_chunks * $chunk_time}] } switch -- [lindex $args 0] { "start" { if {[llength $args] != 2} { error "Expected another argument: the name of your video!" } if {[dict get [record status] status] ne "idle"} { error "Already recording!" } set filenamebase [lindex $args 1] set iteration 0 record_next_part } "stop" { if {[llength $args] != 1} { error "Too many arguments. Stop is just stop." } if {![info exists record_chunks::iteration]} { error "No recording in progress..." } after cancel $next_after_id stop_recording } "default" { error "Syntax error in command." } } } proc stop_recording {} { variable iteration unset iteration record stop puts "Stopped recording..." } proc record_next_part {} { variable iteration variable next_after_id variable filenamebase variable chunk_time variable total_time set cmd record_chunks::record_next_part set time_to_record $chunk_time if {$total_time > 0} { set time_left [expr {$total_time - ($chunk_time * $iteration)}] if {$time_left <= $chunk_time} { set cmd record_chunks::stop_recording set time_to_record $time_left } } record stop incr iteration if {$iteration == 1 && $cmd eq "record_chunks::stop_recording"} { # if we're only going to record one movie, no need to number it set fname $filenamebase } else { set fname [format "%s%02d" $filenamebase $iteration] } record start -doublesize $fname set next_after_id [after time $time_to_record $cmd] puts "Recording to $fname for [utils::format_time $time_to_record]..." } namespace export record_chunks } ;# namespace record_chunks namespace import record_chunks::* openMSX-RELEASE_0_12_0/share/scripts/_reg_log.tcl000066400000000000000000000057631257557151200215240ustar00rootroot00000000000000namespace eval reg_log { set_help_text reg_log \ {Logs the contents (e.g. registers) of a given debuggable or replays such a log. The state of the debuggable is saved to a log file every VDP frame. Note that it does not take into account different VDP interrupt rates. Usage: reg_log record [] record to (default: .log) reg_log stop stop recording reg_log play replay log in Examples: reg_log record "PSG regs" start logging PSG registers to "PSG regs.log" reg_log record memory mem.log start logging memory to file mem.log reg_log stop "PSG regs" stop logging "PSG regs" reg_log play "PSG regs" my.log replay the log of "PSG regs" in my.log } set_tabcompletion_proc reg_log [namespace code tab_reg_log] proc tab_reg_log {args} { switch [llength $args] { 2 {return "record play stop"} 3 {return [debug list]} } } variable log_file variable data proc reg_log {subcommand debuggable {filename ""}} { if {$filename eq ""} {set filename ${debuggable}.log} switch $subcommand { "record" {return [record $debuggable $filename]} "play" {return [play $debuggable $filename]} "stop" {return [stop $debuggable]} default {error "bad option \"$subcommand\": must be record, play or stop"} } } proc check {debuggable} { if {$debuggable ni [debug list]} { error "No such debuggable: $debuggable" } } proc record {debuggable filename} { variable log_file check $debuggable stop $debuggable set log_file($debuggable) [open $filename {WRONLY TRUNC CREAT}] do_reg_record $debuggable return "" } proc play {debuggable filename} { variable data check $debuggable stop $debuggable set log_file [open $filename RDONLY] set data($debuggable) [split [read $log_file] \n] close $log_file do_reg_play $debuggable return "" } proc stop {debuggable} { variable log_file global file data if {[info exists log_file($debuggable)]} { close $log_file($debuggable) unset log_file($debuggable) } if {[info exists data($debuggable)]} { unset data($debuggable) } return "" } proc do_reg_record {debuggable} { variable log_file if {![info exists log_file($debuggable)]} return set size [debug size $debuggable] for {set i 0} {$i < $size} {incr i} { puts -nonewline $log_file($debuggable) "[debug read $debuggable $i] " } puts $log_file($debuggable) "" ;#newline after frame [list reg_log::do_reg_record $debuggable] } proc do_reg_play {debuggable} { variable data if {![info exists data($debuggable)]} return set reg 0 foreach val [lindex $data($debuggable) 0] { debug write $debuggable $reg $val incr reg } set data($debuggable) [lrange $data($debuggable) 1 end] if {[llength $data($debuggable)] > 0} { after frame [list reg_log::do_reg_play $debuggable] } } namespace export reg_log } ;# namespace reg_log namespace import reg_log::* openMSX-RELEASE_0_12_0/share/scripts/_reverse.tcl000066400000000000000000000400221257557151200215440ustar00rootroot00000000000000namespace eval reverse { # Enable reverse if not yet enabled. As an optimization, also return # reverse-status info (so that caller doesn't have to query it again). proc auto_enable {} { set stat_dict [reverse status] if {[dict get $stat_dict status] eq "disabled"} { reverse start } return $stat_dict } set_help_text reverse_prev \ {Go back in time to the previous 'snapshot'. A 'snapshot' is actually an internal implementation detail, but the\ important thing for this command is that the further back in the past,\ the less dense the snapshots are. So executing this command multiple times\ will take successively bigger steps in the past. Going back to a snapshot\ is also slightly faster than going back to an arbitrary point in time\ (let's say going back a fixed amount of time). } proc reverse_prev {{minimum 1} {maximum 15}} { set stats [auto_enable] set snapshots [dict get $stats snapshots] set num_snapshots [llength $snapshots] if {$num_snapshots == 0} return set current [dict get $stats current] set upperTarget [expr {$current - $minimum}] set lowerTarget [expr {$current - $maximum}] # search latest snapshot that is still before upperTarget set i [expr {$num_snapshots - 1}] while {([lindex $snapshots $i] > $upperTarget) && ($i > 0)} { incr i -1 } # but don't go below lowerTarget set t [lindex $snapshots $i] if {$t < $lowerTarget} {set t $lowerTarget} reverse goto $t } set_help_text reverse_next \ {This is very much like 'reverse_prev', but instead it goes to the closest\ snapshot in the future (if possible). } proc reverse_next {{minimum 0} {maximum 15}} { set stats [auto_enable] set snapshots [dict get $stats snapshots] set num_snapshots [llength $snapshots] if {$num_snapshots == 0} return set current [dict get $stats current] set lowerTarget [expr {$current + $minimum}] set upperTarget [expr {$current + $maximum}] # search first snapshot that is after lowerTarget lappend snapshots [dict get $stats end] set i 0 while {($i < $num_snapshots) && ([lindex $snapshots $i] < $lowerTarget)} { incr i } if {$i < $num_snapshots} { # but don't go above upperTarget set t [lindex $snapshots $i] if {$t > $upperTarget} {set t $upperTarget} reverse goto $t } } proc goto_time_delta {delta} { set t [expr {[dict get [reverse status] current] + $delta}] if {$t < 0} {set t 0} reverse goto $t } proc go_back_one_step {} { goto_time_delta [expr {-$::speed / 100.0}] } proc go_forward_one_step {} { goto_time_delta [expr { $::speed / 100.0}] } # reverse bookmarks variable bookmarks [dict create] proc create_bookmark_from_current_time {name} { variable bookmarks dict set bookmarks $name [machine_info time] # The next message is useful as part of a hotkey command for this # osd::display_message "Saved current time to bookmark '$name'" return "Created bookmark '$name' at [dict get $bookmarks $name]" } proc remove_bookmark {name} { variable bookmarks dict unset bookmarks $name return "Removed bookmark '$name'" } proc jump_to_bookmark {name} { variable bookmarks if {[dict exists $bookmarks $name]} { reverse goto [dict get $bookmarks $name] # The next message is useful as part of a hotkey command for # this #osd::display_message "Jumped to bookmark '$name'" } else { error "Bookmark '$name' not defined..." } } proc clear_bookmarks {} { variable bookmarks set bookmarks [dict create] } proc save_bookmarks {name} { variable bookmarks set directory [file normalize $::env(OPENMSX_USER_DATA)/../reverse_bookmarks] file mkdir $directory set fullname [file join $directory ${name}.rbm] if {[catch { set the_file [open $fullname {WRONLY TRUNC CREAT}] puts $the_file $bookmarks close $the_file } errorText]} { error "Failed to save to $fullname: $errorText" } return "Successfully saved bookmarks to $fullname" } proc load_bookmarks {name} { variable bookmarks set directory [file normalize $::env(OPENMSX_USER_DATA)/../reverse_bookmarks] set fullname [file join $directory ${name}.rbm] if {[catch { set the_file [open $fullname {RDONLY}] set bookmarks [read $the_file] close $the_file } errorText]} { error "Failed to load from $fullname: $errorText" } return "Successfully loaded $fullname" } proc list_bookmarks_files {} { set directory [file normalize $::env(OPENMSX_USER_DATA)/../reverse_bookmarks] set results [list] foreach f [lsort [glob -tails -directory $directory -type f -nocomplain *.rbm]] { lappend results [file rootname $f] } return $results } proc reverse_bookmarks {subcmd args} { switch -- $subcmd { "create" {create_bookmark_from_current_time {*}$args} "remove" {remove_bookmark {*}$args} "goto" {jump_to_bookmark {*}$args} "clear" {clear_bookmarks} "load" {load_bookmarks {*}$args} "save" {save_bookmarks {*}$args} default {error "Invalid subcommand: $subcmd"} } } set_help_proc reverse_bookmarks [namespace code reverse_bookmarks_help] proc reverse_bookmarks_help {args} { switch -- [lindex $args 1] { "create" {return {Create a bookmark at the current time with the given name. Syntax: reverse_bookmarks create }} "remove" {return {Remove the bookmark with the given name. Syntax: reverse_bookmarks remove }} "goto" {return {Go to the bookmark with the given name. Syntax: reverse_bookmarks goto }} "clear" {return {Removes all bookmarks. Syntax: reverse_bookmarks clear }} "save" {return {Save the current reverse bookmarks to a file. Syntax: reverse_bookmarks save }} "load" {return {Load reverse bookmarks from file. Syntax: reverse_bookmarks load }} default {return {Control the reverse bookmarks functionality. Syntax: reverse_bookmarks [] Where sub-command is one of: create Create a bookmark at the current time remove Remove a previously created bookmark goto Go to a previously created bookmark clear Shortcut to remove all bookmarks save Save current bookmarks to a file load Load previously saved bookmarks Use 'help reverse_bookmarks ' to get more detailed help on a specific sub-command. }} } } set_tabcompletion_proc reverse_bookmarks [namespace code reverse_bookmarks_tabcompletion] proc reverse_bookmarks_tabcompletion {args} { variable bookmarks if {[llength $args] == 2} { return [list "create" "remove" "goto" "clear" "save" "load"] } elseif {[llength $args] == 3} { switch -- [lindex $args 1] { "remove" - "goto" {return [dict keys $bookmarks]} "load" - "save" {return [list_bookmarks_files]} default {return [list]} } } } ### auto save replays trace add variable ::auto_save_replay "write" [namespace code auto_save_setting_changed] variable old_auto_save_value $::auto_save_replay variable auto_save_after_id 0 proc auto_save_setting_changed {name1 name2 op} { variable old_auto_save_value variable auto_save_after_id if {$::auto_save_replay != $old_auto_save_value} { set old_auto_save_value $::auto_save_replay if {$::auto_save_replay && $auto_save_after_id == 0 } { set stat_dict [reverse status] if {[dict get $stat_dict status] eq "disabled"} { error "Reverse is not enabled!" } auto_save_replay_loop puts "Enabled auto-save of replay to filename $::auto_save_replay_filename every $::auto_save_replay_interval seconds." } elseif {!$::auto_save_replay && $auto_save_after_id != 0 } { after cancel $auto_save_after_id set auto_save_after_id 0 puts "Auto-save of replay disabled." } } } proc auto_save_replay_loop {} { variable auto_save_after_id if {$::auto_save_replay} { reverse savereplay -maxnofextrasnapshots 0 $::auto_save_replay_filename set auto_save_after_id [after realtime $::auto_save_replay_interval "reverse::auto_save_replay_loop"] } } namespace export reverse_prev namespace export reverse_next namespace export goto_time_delta namespace export go_back_one_step namespace export go_forward_one_step namespace export reverse_bookmarks } ;# namespace reverse namespace eval reverse_widgets { variable reverse_bar_update_interval 0.10 variable update_after_id 0 variable mouse_after_id 0 variable overlay_counter variable prev_x 0 variable prev_y 0 variable overlayOffset variable invisibleTime +inf ;# bar is invisble past this time, +inf means it's permanently visible set_help_text toggle_reversebar \ {Enable/disable an on-screen reverse bar. This will show the recorded 'reverse history' and the current position in\ this history. It is possible to click on this bar to immediately jump to a\ specific point in time. If the current position is at the end of the history\ (i.e. we're not replaying an existing history), this reverse bar will slowly\ fade out. You can make it reappear by moving the mouse over it. } proc toggle_reversebar {} { if {[osd exists reverse]} { disable_reversebar } else { enable_reversebar } return "" } proc enable_reversebar {{visible true}} { reverse::auto_enable if {[osd exists reverse]} { # osd already enabled return } # Reversebar set fade [expr {$visible ? 1.0 : 0.0}] osd create rectangle reverse \ -scaled true -x 34 -y 1 -w 252 -h 8 \ -rgba 0x00000080 -fadeCurrent $fade -fadeTarget $fade \ -borderrgba 0xFFFFFFC0 -bordersize 1 osd create rectangle reverse.int \ -x 1 -y 1 -w 250 -h 6 -rgba 0x00000000 -clip true osd create rectangle reverse.int.bar \ -relw 0 -relh 1 -z 3 -rgba "0x0044aa80 0x2266dd80 0x0055cc80 0x55eeff80" osd create rectangle reverse.int.end \ -relx 0 -x -1 -w 2 -relh 1 -z 3 -rgba 0xff8000c0 osd create text reverse.int.text \ -x -10 -y 0 -relx 0.5 -size 5 -z 6 -rgba 0xffffffff # on mouse over hover box osd create rectangle reverse.mousetime \ -relx 0.5 -rely 1 -relh 0.75 -z 4 \ -rgba "0xffdd55e8 0xddbb33e8 0xccaa22e8 0xffdd55e8" \ -bordersize 0.5 -borderrgba 0xffff4480 osd create text reverse.mousetime.text \ -size 5 -z 4 -rgba 0x000000ff update_reversebar variable mouse_after_id set mouse_after_id [after "mouse button1 down" [namespace code check_mouse]] trace add variable ::reverse::bookmarks "write" [namespace code update_bookmarks] } proc disable_reversebar {} { trace remove variable ::reverse::bookmarks "write" [namespace code update_bookmarks] variable update_after_id variable mouse_after_id after cancel $update_after_id after cancel $mouse_after_id osd destroy reverse } proc update_reversebar {} { catch {update_reversebar2} variable reverse_bar_update_interval variable update_after_id set update_after_id [after realtime $reverse_bar_update_interval [namespace code update_reversebar]] } proc update_reversebar2 {} { # Hack: Put the reverse bar at the bottom when the icons are at the top, # otherwise at the top. # It would be better to have a proper layout manager for the OSD stuff. if {[catch {set led_y [osd info osd_icons -y]}]} { set led_y 0 } if {[catch {set led_w [osd info osd_icons -w]}]} { set led_w 0 } osd configure reverse -y [expr {($led_y < 10) && ($led_w != 0) ? 231 : 1}] # Set time indicator position (depending on reverse bar position) variable overlayOffset [expr {($led_y > 16) ? 1.1 : -1.25}] set stats [reverse status] set x 2; set y 2 catch {lassign [osd info "reverse.int" -mousecoord] x y} set mouseInside [expr {0 <= $x && $x <= 1 && 0 <= $y && $y <= 1}] variable invisibleTime set now [openmsx_info realtime] switch [dict get $stats status] { "disabled" { disable_reversebar return } "replaying" { osd configure reverse -fadeTarget 1.0 -fadeCurrent 1.0 if {[reverse viewonlymode]} { set color "0x00aa44a0 0x22dd66a0 0x00cc55a0 0x55ffeea0" } else { set color "0x0044aaa0 0x2266dda0 0x0055cca0 0x55eeffa0" } osd configure reverse.int.bar -rgba $color set invisibleTime +inf } "enabled" { osd configure reverse.int.bar \ -rgba "0xff4400a0 0xdd3300a0 0xbb2200a0 0xcccc11a0" if {$mouseInside || $::reversebar_fadeout_time == 0.0} { osd configure reverse -fadePeriod 0.5 -fadeTarget 1.0 set invisibleTime +inf } else { osd configure reverse -fadePeriod $::reversebar_fadeout_time -fadeTarget 0.0 if {$invisibleTime == +inf} { set invisibleTime [expr {$now + $::reversebar_fadeout_time}] } } } } if {$now > $invisibleTime} { # Optimization: reverse bar is completely faded out, # don't bother keeping it up to date. return } set snapshots [dict get $stats snapshots] set begin [dict get $stats begin] set end [dict get $stats end] set current [dict get $stats current] set totLenght [expr {$end - $begin}] set playLength [expr {$current - $begin}] set reciprocalLength [expr {($totLenght != 0) ? (1.0 / $totLenght) : 0}] set fraction [expr {$playLength * $reciprocalLength}] # Check if cursor moved compared to previous update, # if so reset counter (see below) variable overlay_counter variable prev_x variable prev_y if {$prev_x != $x || $prev_y != $y} { set overlay_counter 0 set prev_x $x set prev_y $y } # Display mouse-over time jump-indicator # Hide when mouse hasn't moved for some time if {$mouseInside && $overlay_counter < 8} { variable overlayOffset set mousetext [utils::format_time_subseconds [expr {$x * $totLenght}]] osd configure reverse.mousetime.text -text $mousetext -relx 0.05 set textsize [lindex [osd info reverse.mousetime.text -query-size] 0] osd configure reverse.mousetime -rely $overlayOffset -relx [expr {$x - 0.05}] -w [expr {1.1 * $textsize}] incr overlay_counter } else { osd configure reverse.mousetime -rely -100 } # snapshots set count 0 foreach snapshot $snapshots { set name reverse.int.tick$count if {![osd exists $name]} { # create new if it doesn't exist yet osd create rectangle $name -w 0.5 -relh 1 -rgba 0x444444ff -z 2 } osd configure $name -relx [expr {($snapshot - $begin) * $reciprocalLength}] incr count } # destroy all snapshots with higher count number while {[osd destroy reverse.int.tick$count]} { incr count } # bookmarks set count 0 dict for {bookmarkname bookmarkval} $::reverse::bookmarks { set name reverse.bookmark$count if {![osd exists $name]} { # create new if it doesn't exist yet osd create rectangle $name \ -relx 0.5 -rely 1 -relh 0.75 -z 3 \ -rgba "0xffdd55e8 0xddbb33e8 0xccaa22e8 0xffdd55e8" \ -bordersize 0.5 -borderrgba 0xffff4480 osd create text $name.text -relx -0.05 \ -size 5 -z 3 -rgba 0x000000ff -text $bookmarkname set textsize [lindex [osd info $name.text -query-size] 0] osd configure $name -w [expr {1.1 * $textsize}] } osd configure $name -relx [expr {($bookmarkval - $begin) * $reciprocalLength}] incr count } # Round fraction to avoid excessive redraws caused by rounding errors set fraction [expr {round($fraction * 10000) / 10000.0}] osd configure reverse.int.bar -relw $fraction osd configure reverse.int.end -relx $fraction osd configure reverse.int.text \ -text "[utils::format_time_subseconds $playLength] / [utils::format_time_subseconds $totLenght]" } proc check_mouse {} { catch { # click on reverse bar set x 2; set y 2 catch {lassign [osd info "reverse.int" -mousecoord] x y} if {0 <= $x && $x <= 1 && 0 <= $y && $y <= 1} { set stats [reverse status] set begin [dict get $stats begin] set end [dict get $stats end] reverse goto [expr {$begin + $x * ($end - $begin)}] } # click on bookmark set count 0 dict for {bookmarkname bookmarkval} $::reverse::bookmarks { set name reverse.bookmark$count set x 2; set y 2 catch {lassign [osd info $name -mousecoord] x y} if {0 <= $x && $x <= 1 && 0 <= $y && $y <= 1} { reverse::jump_to_bookmark $bookmarkname } incr count } } variable mouse_after_id set mouse_after_id [after "mouse button1 down" [namespace code check_mouse]] } proc update_bookmarks {name1 name2 op} { # remove all bookmarks to make sure the widget names start with 0 # and have no 'holes' in their sequence. They will be recreated # when necessary in update_reversebar2 set count 0 while {[osd destroy reverse.bookmark$count]} { incr count } update_reversebar } namespace export toggle_reversebar namespace export enable_reversebar namespace export disable_reversebar } ;# namespace reverse_widgets namespace import reverse::* namespace import reverse_widgets::* openMSX-RELEASE_0_12_0/share/scripts/_rom_info.tcl000066400000000000000000000045721257557151200217130ustar00rootroot00000000000000set_help_text rom_info\ {Prints information about the given ROM device, coming from the software database. If no argument is given, the first found (external) ROM device is shown.} namespace eval rom_info { proc tab {args} { set result [list] foreach device [machine_info device] { if {[lindex [machine_info device $device] 0] eq "ROM"} { lappend result $device } } return $result } set_tabcompletion_proc rom_info [namespace code tab] proc getlist_rom_info {{romdevice ""}} { if {$romdevice eq ""} { set romdevice [guess_rom_title] if {$romdevice eq ""} { error "No (external) ROM device found" } } if {[catch {set device_info [machine_info device $romdevice]}]} { error "No such device: $romdevice" } set device_type [lindex [machine_info device $romdevice] 0] if {$device_type ne "ROM"} { error [format "Device is not of type ROM, but %s" $device_type] } if {[catch {set rominfo [openmsx_info software [lindex $device_info 2]]}]} { return } dict with rominfo { # dummy info for missing items foreach key [list year company] { if {[set $key] eq ""} { set $key "(info not available)" } } if {$original} { # this is an unmodified original dump set status [format "Unmodified dump (confirmed by %s)" $orig_type] } else { # not original or unknown switch $orig_type { "broken" { set status "Bad dump (game is broken)" } "translated" { set status "Translated from original" } "working" { set status "Modified but confirmed working" } default { set status "Unknown" } } } return [list \ "title" $title \ "year" $year \ "company" $company \ "country" $country \ "status" $status \ "remark" $remark] } } proc rom_info {} { set rominfo [rom_info::getlist_rom_info] if {$rominfo eq ""} {return "No ROM information available..."} append result "Title: [dict get $rominfo title]\n" \ "Year: [dict get $rominfo year]\n" \ "Company: [dict get $rominfo company]\n" \ "Country: [dict get $rominfo country]\n" \ "Status: [dict get $rominfo status]" \ set remark [dict get $rominfo remark] if {$remark ne ""} { append result "\nRemark: $remark" } else { append result "\nRemark: None" } return $result } namespace export rom_info namespace export getlist_rom_info } ;# namespace rom_info namespace import rom_info::* openMSX-RELEASE_0_12_0/share/scripts/_save_debuggable.tcl000066400000000000000000000146661257557151200232070ustar00rootroot00000000000000namespace eval save_debuggable { set_help_text save_debuggable \ {Save (part of) a debuggable to file. Examples of debuggables are memory, VRAM, ... Use 'debug list' to get a complete list of all debuggables. Usage: save_debuggable VRAM vram.raw Save the content of the MSX VRAM to a file called 'vram.raw' save_debuggable memory mem.sav 0x4000 0x8000 Save the 32 kB content of the memory debuggable starting from 0x4000 to a file called 'mem.sav' } proc save_debuggable {debuggable filename {start 0} {size 0}} { if {$size == 0} { set size [expr {[debug size $debuggable] - $start}] } set data [debug read_block $debuggable $start $size] set file [open $filename "WRONLY CREAT TRUNC"] fconfigure $file -translation binary -buffersize $size puts -nonewline $file $data close $file } set_help_text load_debuggable \ {Load a raw data file (somewhere) into a certain debuggable (see also save_debuggable). Note that saving and reloading the same data again does not always bring the MSX in the same state (e.g. the subslotregister). Use 'savestate' and 'loadstate' if you want that. } proc load_debuggable {debuggable filename {start 0}} { set size [file size $filename] set file [open $filename "RDONLY"] fconfigure $file -translation binary -buffersize $size set data [read $file] close $file debug write_block $debuggable $start $data } set_tabcompletion_proc save_debuggable [namespace code tab_loadsave_debuggable] set_tabcompletion_proc load_debuggable [namespace code tab_loadsave_debuggable] proc tab_loadsave_debuggable {args} { if {[llength $args] == 2} { return [debug list] } if {[llength $args] == 3} { return [utils::file_completion {*}$args] } } # TODO remove these two procs? # They were meant as a very quick-and-dirty savestate mechanism, but it # doesn't work (e.g. because of subslot register) proc save_all {directory} { puts stderr "This command ('save_all') has been deprecated (and may be removed in a future release). In contrast to what you might think, it cannot be used to save the machine state. You probably just want to use 'save_state' instead!" foreach debuggable [debug list] { save_debuggable $debuggable ${directory}/${debuggable}.sav } } proc load_all {directory} { puts stderr "This command ('load_all') has been deprecated (and may be removed in a future release). In contrast to what you might think, it cannot be used to load the machine state. You probably just want to use 'load_state' instead!" foreach debuggable [debug list] { load_debuggable $debuggable ${directory}/${debuggable}.sav } } # for backwards compatibility proc vramdump {{filename "vramdump"}} { #puts stderr "This command ('vramdump') has been deprecated (and may be removed in a future release), please use 'save_debuggable VRAM' instead!" # I prefer to keep this command as a convenience shortcut save_debuggable "VRAM" $filename } set_help_text vram2bmp \ {Dump a selected region of the video memory into a bitmap file. vram filename filename [start address] [width] [length] usage example: vram2bmp RawDataDump.bmp 0 256 1024 } proc vram2bmp {filename start dx dy} { set file [open $filename "WRONLY CREAT TRUNC"] set data_len [expr {$dx * $dy / 2}] set file_len [expr {$data_len + 0x76}] set file_len_HI [expr {$file_len / 0x100}] set dx_HI [expr {$dx / 0x100}] set dy_HI [expr {$dy / 0x100}] set data_len_HI [expr {$data_len / 0x100}] fconfigure $file -translation binary -buffersize $file_len puts -nonewline $file "\x42\x4d" ;# BMP FILE identificator puts -nonewline $file [format %c $file_len] ;# <-File length puts -nonewline $file [format %c $file_len_HI] ;# <-File length puts -nonewline $file "\x00\x00" ;# last always always zero because maximim file length is 128KB + header length puts -nonewline $file "\x00\x00\x00\x00" ;# unused puts -nonewline $file "\x76\x00\x00\x00" ;# offset to bitmap data - always same for 16 colors bmp puts -nonewline $file "\x28\x00\x00\x00" ;# number of bytes in the header puts -nonewline $file [format %c $dx] ;# bitmap width puts -nonewline $file [format %c $dx_HI] ;# bitmap width puts -nonewline $file "\x00\x00" ;# bitmap width - unused because maximum width is 256 puts -nonewline $file [format %c $dy] ;# bitmap height puts -nonewline $file [format %c $dy_HI] ;# bitmap height puts -nonewline $file "\x00\x00" ;# bitmap height, if negative image will look normal in mspaint puts -nonewline $file "\x01\x00" ;# 1 plane used puts -nonewline $file "\x04\x00" ;# 4 bits per pixel puts -nonewline $file "\x00\x00\x00\x00" ;# no compression used puts -nonewline $file [format %c $data_len] ;# RAW image data length puts -nonewline $file [format %c $data_len_HI] ;# RAW image data length puts -nonewline $file "\x00\x00" ;# RAW image data length - always zero, because maximum length is 128KB puts -nonewline $file "\x00\x00\x00\x00" puts -nonewline $file "\x00\x00\x00\x00" puts -nonewline $file "\x00\x00\x00\x00" ;#how many colors used puts -nonewline $file "\x00\x00\x00\x00" ;#important colors #set colors (16 colors [0..15]) for {set col 0} {$col < 16} {incr col} { set color [getcolor $col] puts -nonewline $file [format %c [expr {[string index $color 2] * 255 / 7}]] puts -nonewline $file [format %c [expr {[string index $color 1] * 255 / 7}]] puts -nonewline $file [format %c [expr {[string index $color 0] * 255 / 7}]] puts -nonewline $file "\x00" } set cur_addr $start for {set i 0} {$i < $dy} {incr i} { for {set addr $cur_addr} {$addr < ($cur_addr + $dx / 2)} {incr addr} { puts -nonewline $file [format %c [vpeek $addr]] } set cur_addr [expr {$cur_addr + 0x80}] } close $file } set_help_text save_to_file \ {Helper proc to easily write something to a file. Typically used in combination with another proc. For example: save_to_file my_data.hex [showmem 0x8000 20] } proc save_to_file {filename data} { set file [open $filename "WRONLY CREAT TRUNC"] fconfigure $file -translation binary puts -nonewline $file $data close $file } namespace export save_debuggable namespace export load_debuggable namespace export save_all namespace export load_all namespace export vramdump namespace export vram2bmp namespace export save_to_file } ;# namespace save_debuggable namespace import save_debuggable::* openMSX-RELEASE_0_12_0/share/scripts/_save_msx_screen.tcl000066400000000000000000000140221257557151200232560ustar00rootroot00000000000000set_help_text save_msx_screen \ {Lookup the screen mode and save the current screen to a MSX compatible binary file. This file can for example be loaded in MSX-BASIC using the BLOAD command. This script was originally developed by NYYRIKKI, see also this forum thread: http://www.msx.org/forum/msx-talk/general-discussion/taking-sc5-snapshot-games } proc save_msx_screen {basename} { set directory [file normalize $::env(OPENMSX_USER_DATA)/../screenshots] # Create filename with correct extension (also gives an error in # case of an invalid screen mode). set fname [file join $directory [format "%s.SC%X" $basename [get_screen_mode_number]]] set name_base [expr { [vdpreg 2] * 0x400}] set name_base_80 [expr {([vdpreg 2] & 252) * 0x400}] set name_base_bitmap [expr {([vdpreg 2] & 96) * 0x400}] set color_base [expr { [vdpreg 3] * 0x40 + [vdpreg 10] * 0x4000}] set color_base_2 [expr {([vdpreg 3] & 128) * 0x40 + [vdpreg 10] * 0x4000}] set pattern_base [expr { [vdpreg 4] * 0x800}] set pattern_base_2 [expr {([vdpreg 4] & 60) * 0x800}] set spr_att_base [expr { [vdpreg 5] * 0x80 + [vdpreg 11] * 0x8000}] set spr_att_base_2 [expr {([vdpreg 5] & 252) * 0x80 + [vdpreg 11] * 0x8000 - 0x200}] set spr_pat_base [expr { [vdpreg 6] * 0x800}] switch [get_screen_mode] { "TEXT40" { lappend sections "VRAM" $name_base 0x400 ;# BG Map lappend sections "VDP palette" 0 0x20 ;# Palette lappend sections "VRAM" [expr {$name_base + 0x420}] 0x3E0 ;# Fill (BG Map) lappend sections "VRAM" $pattern_base 0x800 ;# BG Tiles } "TEXT80" { lappend sections "VRAM" $name_base_80 0x800 ;# BG Map lappend sections "VRAM" $color_base 0x700 ;# Blink lappend sections "VDP palette" 0 0x20 ;# Palette lappend sections "VRAM" [expr {$name_base_80 + 0xF20}] 0xE0 ;# Fill (BG Map) lappend sections "VRAM" $pattern_base 0x800 ;# BG Tiles } "1" { lappend sections "VRAM" $pattern_base 0x1800 ;# BG Tiles lappend sections "VRAM" $name_base 0x300 ;# BG Map lappend sections "VRAM" $spr_att_base 0x500 ;# OBJ Attributes lappend sections "VRAM" $color_base 0x20 ;# BG Colors lappend sections "VDP palette" 0 0x20 ;# Palette lappend sections "VRAM" [expr {$color_base + 0x40}] 0x17C0 ;# Fill (BG Colors) lappend sections "VRAM" $spr_pat_base 0x800 ;# OBJ Tiles } "2" { lappend sections "VRAM" $pattern_base_2 0x1800 ;# BG Tiles lappend sections "VRAM" $name_base 0x300 ;# BG Map lappend sections "VRAM" $spr_att_base 0x80 ;# OBJ Attributes lappend sections "VDP palette" 0 0x20 ;# Palette lappend sections "VRAM" [expr {$spr_att_base + 0xA0}] 0x460 ;# Fill (OBJ Attributes) lappend sections "VRAM" $color_base_2 0x1800 ;# BG Colors lappend sections "VRAM" $spr_pat_base 0x800 ;# OBJ Tiles } "3" { lappend sections "VRAM" $pattern_base 0x800 ;# BG Tiles lappend sections "VRAM" $name_base 0x1300 ;# BG Map lappend sections "VRAM" $spr_att_base 0x520 ;# OBJ Attributes lappend sections "VDP palette" 0 0x20 ;# Palette lappend sections "VRAM" [expr {$spr_att_base + 0x540}] 0x17C0 ;# Fill (OBJ Attributes) lappend sections "VRAM" $spr_pat_base 0x800 ;# OBJ Tiles } "4" { lappend sections "VRAM" $pattern_base_2 0x1800 ;# BG Tiles lappend sections "VRAM" $name_base 0x380 ;# BG Map lappend sections "VDP palette" 0 0x20 ;# Palette lappend sections "VRAM" [expr {$name_base + 0x3A0}] 0x60 ;# Fill (BG Map) lappend sections "VRAM" $spr_att_base_2 0x400 ;# OBJ Attributes lappend sections "VRAM" $color_base_2 0x1800 ;# BG Colors lappend sections "VRAM" $spr_pat_base 0x800 ;# OBJ Tiles } "5" - "6" { set lines1 [expr {[vdpreg 23] > 24 ? 256 - [vdpreg 23] : 232}] ;# Store 232 lines, even set lines2 [expr {232 - $lines1}] ;# though only 212 are visible set base1 [expr {$name_base_bitmap + [vdpreg 23] * 128}] set base2 $name_base_bitmap lappend sections "VRAM" $base1 [expr {$lines1 * 128}] ;# Bitmap (part 1) lappend sections "VRAM" $base2 [expr {$lines2 * 128}] ;# Bitmap (part 2) lappend sections "VRAM" $spr_att_base_2 0x280 ;# Sprite colors + attributes lappend sections "VDP palette" 0 0x20 ;# Palette lappend sections "VRAM" [expr {$name_base_bitmap + 0x76A0}] 0x160 ;# Fill (Bitmap) lappend sections "VRAM" $spr_pat_base 0x800 ;# Sprite character patterns } "7" - "8" - "11" - "12" { set lines1 [expr {[vdpreg 23] > 16 ? 256 - [vdpreg 23] : 240}] ;# Store 240 lines, even set lines2 [expr {240 - $lines1}] ;# though only 212 are visible set base1 [expr {$name_base_bitmap + [vdpreg 23] * 256}] set base2 $name_base_bitmap lappend sections "VRAM" $base1 [expr {$lines1 * 256}] ;# Bitmap (part 1) lappend sections "VRAM" $base2 [expr {$lines2 * 256}] ;# Bitmap (part 2) lappend sections "VRAM" $spr_pat_base 0x800 ;# Sprite character patterns lappend sections "VRAM" $spr_att_base_2 0x280 ;# Sprite colors + attributes lappend sections "VDP palette" 0 0x20 ;# Palette }} # open file set out [open $fname w] fconfigure $out -translation binary # write header set end_addr -1 foreach {type addr size} $sections { incr end_addr $size } set header [list 0xFE 0 0 [expr {$end_addr & 255}] [expr {$end_addr / 256}] 0 0] puts -nonewline $out [binary format c* $header] # write data sections foreach {type addr size} $sections { puts -nonewline $out [debug read_block $type $addr $size] } close $out return "Screen written to $fname" } openMSX-RELEASE_0_12_0/share/scripts/_savestate.tcl000066400000000000000000000112221257557151200220700ustar00rootroot00000000000000# convenience wrappers around the low level savestate commands namespace eval savestate { proc savestate_common {} { uplevel { if {$name eq ""} {set name "quicksave"} set directory [file normalize $::env(OPENMSX_USER_DATA)/../savestates] set fullname_oms [file join $directory ${name}.oms] set fullname_gz [file join $directory ${name}.xml.gz] if {![file exists $fullname_oms] && [file exists $fullname_gz]} { # only when old name exists but new doesn't set fullname_bwcompat $fullname_gz } else { set fullname_bwcompat $fullname_oms } set png [file join $directory ${name}.png] } } proc savestate {{name ""}} { savestate_common file mkdir $directory if {[catch {screenshot -raw -doublesize $png}]} { # some renderers don't support msx-only screenshots if {[catch {screenshot $png}]} { # even this failed, but (try to) remove old screenshot # to avoid confusion catch {file delete -- $png} } } set currentID [machine] # always save using the new (.oms) name store_machine $currentID $fullname_oms # if successful, delete the old (.gz) filename (deleting a non-exiting # file is not an error) file delete -- $fullname_gz return $name } proc loadstate {{name ""}} { savestate_common set newID [restore_machine $fullname_bwcompat] set currentID [machine] if {$currentID ne ""} {delete_machine $currentID} activate_machine $newID return $name } # helper proc to get the raw savestate info proc list_savestates_raw {} { set directory [file normalize $::env(OPENMSX_USER_DATA)/../savestates] set results [list] foreach f [glob -tails -directory $directory -nocomplain *.xml.gz *.oms] { set name [file rootname [file rootname $f]] set fullname [file join $directory $f] set filetime [file mtime $fullname] lappend results [list $name $filetime] } return $results } proc list_savestates {args} { set sort_key 0 set long_format false set sort_option "-ascii" set sort_order "-increasing" #parse options while (1) { switch -- [lindex $args 0] { "" break "-t" { set sort_key 1 set sort_option "-integer" set args [lrange $args 1 end] set sort_order "-decreasing" } "-l" { if {[info commands clock] ne ""} { set long_format true } else { error "Sorry, long format not supported on this system (missing clock.tcl)" } set args [lrange $args 1 end] } "default" { error "Invalid option: [lindex $args 0]" } } } set sorted_sublists [lsort ${sort_option} ${sort_order} -index $sort_key [list_savestates_raw]] if {!$long_format} { set sorted_result [list] foreach sublist $sorted_sublists {lappend sorted_result [lindex $sublist 0]} return $sorted_result } else { set stringres "" foreach sublist $sorted_sublists { append stringres [format "%-[expr {round(${::consolecolumns} / 2)}]s %s\n" [lindex $sublist 0] [clock format [lindex $sublist 1] -format "%a %b %d %Y - %H:%M:%S"]] } return $stringres } } proc delete_savestate {{name ""}} { savestate_common catch {file delete -- $fullname_bwcompat} catch {file delete -- $png} return "" } proc savestate_tab {args} { list_savestates } proc savestate_list_tab {args} { list "-l" "-t" } # savestate set_help_text savestate \ {savestate [] Create a snapshot of the current emulated MSX machine. Optionally you can specify a name for the savestate. If you omit this the default name 'quicksave' will be taken. See also 'loadstate', 'list_savestates', 'delete_savestate'. } set_tabcompletion_proc savestate [namespace code savestate_tab] # loadstate set_help_text loadstate \ {loadstate [] Restore a previously created savestate. You can specify the name of the savestate that should be loaded. If you omit this name, the default savestate will be loaded. See also 'savestate', 'list_savestates', 'delete_savestate'. } set_tabcompletion_proc loadstate [namespace code savestate_tab] # list_savestates set_help_text list_savestates \ {list_savestates [options] Return the names of all previously created savestates. Options: -t sort savestates by time -l long formatting, showing date of savestates Note: the -l option is not available on all systems. See also 'savestate', 'loadstate', 'delete_savestate'. } set_tabcompletion_proc list_savestates [namespace code savestate_list_tab] # delete_savestate set_help_text delete_savestate \ {delete_savestate [] Delete a previously created savestate. See also 'savestate', 'loadstate', 'list_savestates'. } set_tabcompletion_proc delete_savestate [namespace code savestate_tab] namespace export savestate namespace export loadstate namespace export delete_savestate namespace export list_savestates } ;# namespace savestate namespace import savestate::* openMSX-RELEASE_0_12_0/share/scripts/_scc_toys.tcl000066400000000000000000000306611257557151200217270ustar00rootroot00000000000000# thanks to bifimsx for his help and his technical documentation @ # http://bifi.msxnet.org/msxnet/tech/scc.html # # TODO: # - optimize! (A LOT!) # - support SCC-I set_help_text toggle_scc_viewer\ {Toggles display of the SCC viewer in which you can follow the wave forms and volume per SCC channel in real time. Note: it doesn't explicitly support SCC-I yet and it can take up quite some CPU load...} namespace eval scc_toys { #scc viewer variable scc_viewer_active false variable scc_devices [list] variable num_samples 32 variable num_channels 5 variable vertical_downscale_factor 4 variable channel_height [expr {256 / $vertical_downscale_factor}] variable machine_switch_trigger_id 0 variable frame_trigger_id 0 variable volume_address [expr {$num_samples * $num_channels + 2 * $num_channels}] #scc editor / PSG2SCC variable active false variable cur_wp1 variable cur_wp2 variable latch -1 variable regs [list 0xa0 0xa1 0xa2 0xa3 0xa4 0xa5 -1 0xaf 0xaa 0xab 0xac -1 -1 -1 -1 -1] variable select_device variable select_device_chan 0 proc update_device_list {} { variable scc_devices variable select_device set scc_devices [list] foreach soundchip [machine_info sounddevice] { switch [machine_info sounddevice $soundchip] { "Konami SCC" - "Konami SCC+" { lappend scc_devices $soundchip } } } if {[llength $scc_devices] == 0} { #if no SCC is present try to plug in SCC if {![catch {ext scc} errorText]} { update_device_list } else { error "No SCC devices present and failed to insert one: $errorText" } } # for now, always select the first device set select_device [lindex $scc_devices 0] } proc scc_viewer_init {} { variable machine_switch_trigger_id variable scc_viewer_active variable scc_devices variable num_channels variable num_samples variable vertical_downscale_factor variable channel_height update_device_list #set base element osd create rectangle scc_viewer \ -x 2 \ -y 2 \ -alpha 0 \ -z 100 set textheight 15 set border_width 2 set inter_channel_spacing 8 set device_width [expr {$num_channels * ($num_samples + $inter_channel_spacing) \ - $inter_channel_spacing + 2 * $border_width}] #create channels set number 0 set offset 0 foreach device $scc_devices { osd create rectangle scc_viewer.$device \ -x [expr {$offset + $number * $device_width}] \ -h [expr {$channel_height + 2 * $border_width + $textheight}] \ -w $device_width \ -rgba 0xffffff20 \ -clip true osd create text scc_viewer.$device.title \ -rgba 0xffffffff \ -text $device \ -size $textheight for {set chan 0} {$chan < $num_channels} {incr chan} { osd create rectangle scc_viewer.$device.$chan \ -x [expr {($chan * ($num_samples + $inter_channel_spacing)) + $border_width}] \ -y [expr {$border_width + $textheight}] \ -h $channel_height \ -w $num_samples \ -rgba "0x0044aa80 0x2266dd80 0x0055cc80 0x44aaff80" \ -borderrgba 0xffffff80 -bordersize 1 \ -clip true osd create rectangle scc_viewer.$device.$chan.volume \ -relw 1 \ -z 1 \ -rgba 0x0077ff80 \ -borderrgba 0x0077ffc0 -bordersize 1 osd create rectangle scc_viewer.$device.$chan.mid \ -y [expr {$channel_height / 2}] \ -h 1 \ -relw 1 \ -z 3 \ -rgba 0xdd0000ff osd create rectangle scc_viewer.$device.$chan.mid.2 \ -y -1 \ -h 3 \ -relw 1 \ -rgba 0xff000060 for {set pos 0} {$pos < $num_samples} {incr pos} { osd create rectangle scc_viewer.$device.$chan.$pos \ -x $pos \ -y [expr {$channel_height / 2}] \ -w 2 \ -z 2 \ -rgba 0xffffffb0 } } incr number set offset 10 } set machine_switch_trigger_id [after machine_switch [namespace code scc_viewer_reset]] } proc update_scc_viewer {} { variable scc_viewer_active variable scc_devices variable num_channels variable num_samples variable vertical_downscale_factor variable channel_height variable frame_trigger_id variable volume_address if {!$scc_viewer_active} return foreach device $scc_devices { binary scan [debug read_block "$device SCC" 0 224] c* scc_regs for {set chan 0} {$chan < $num_channels} {incr chan} { for {set pos 0} {$pos < $num_samples} {incr pos} { set scc_wave_new [expr {[get_scc_wave [lindex $scc_regs [expr {($chan * $num_samples) + $pos}]]] / $vertical_downscale_factor}] osd configure scc_viewer.$device.$chan.$pos \ -h $scc_wave_new } set volume [expr {[lindex $scc_regs [expr {$volume_address + $chan}]] * 4}] osd configure scc_viewer.$device.$chan.volume \ -h $volume \ -y [expr {($channel_height - $volume) / 2}] } } # set frame_trigger_id [after frame [namespace code {puts [time update_scc_viewer]}]];# for profiling set frame_trigger_id [after time 0.05 [namespace code update_scc_viewer]] } proc get_scc_wave {sccval} { expr {-($sccval < 128 ? $sccval : $sccval - 256)} } proc scc_viewer_reset {} { variable scc_viewer_active if {!$scc_viewer_active} { error "Please fix a bug in this script!" } toggle_scc_viewer toggle_scc_viewer } proc toggle_scc_viewer {} { variable scc_viewer_active variable machine_switch_trigger_id variable frame_trigger_id if {$scc_viewer_active} { after cancel $machine_switch_trigger_id after cancel $frame_trigger_id set scc_viewer_active false osd destroy scc_viewer } else { scc_viewer_init set scc_viewer_active true update_scc_viewer } return "" } proc init {} { variable select_device update_device_list set_scc_wave $select_device 0 3 set_scc_wave $select_device 1 2 set_scc_wave $select_device 2 3 } proc update1 {} { variable latch set latch $::wp_last_value } proc update2 {} { variable latch variable regs variable select_device set reg [expr {($latch == -1) ? $latch : [lindex $regs $latch]}] set val $::wp_last_value if {$latch == 7} {set val [expr {($val ^ 0x07) & 0x07}]} if {$reg != -1} { if {[catch {debug write "$select_device SCC" $reg $val}]} { # device gone? Let's deactivate toggle_psg2scc } } } proc toggle_psg2scc {} { variable active variable cur_wp1 variable cur_wp2 variable select_device if {!$active} { init set active true set cur_wp1 [debug set_watchpoint write_io 0xa0 1 {scc_toys::update1}] set cur_wp2 [debug set_watchpoint write_io 0xa1 1 {scc_toys::update2}] return "Activated." } else { debug remove_watchpoint $cur_wp1 debug remove_watchpoint $cur_wp2 catch { ;# may fail if device is gone debug write "$select_device SCC" 0xaf 0 } set active false return "Deactivated." } } proc set_scc_form {device channel wave} { set base [expr {$channel * 32}] for {set i 0} {$i < 32} {incr i} { debug write "$device SCC" [expr {$base + $i}] "0x[string range $wave [expr {$i * 2}] [expr {$i * 2 + 1}]]" } } proc set_scc_wave {device channel form} { switch $form { 0 { #Saw Tooth set_scc_form $device $channel "fff7efe7dfd7cfc7bfb7afa79f978f877f776f675f574f473f372f271f170f07" } 1 { #Square set_scc_form $device $channel "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f80808080808080808080808080808080" } 2 { #Triangle set_scc_form $device $channel "7f7060504030201000f0e0d0c0b0a0908090a0b0c0d0e0f00010203040506070" } 3 { #Sin Wave set_scc_form $device $channel "001931475A6A757D7F7D756A5A47311900E7CFB9A6968B8380838B96A6B9CFE7" } 4 { #Organ set_scc_form $device $channel "0070502050703000507F6010304000B0106000E0F000B090C010E0A0C0F0C0A0" } 5 { #SAWWY001 set_scc_form $device $channel "636E707070705F2198858080808086AB40706F8C879552707052988080808EC1" } 6 { #SQROOT01 set_scc_form $device $channel "00407F401001EAD6C3B9AFA49C958F8A86838183868A8F959CA4AFB9C3D6EAFF" } 7 { #SQROOT01 set_scc_form $device $channel "636E707070705F2198858080808086AB40706F8C879552707052988080808EC1" } 8 { #DYERVC01 set_scc_form $device $channel "00407F4001C081C001407F4001C0014001E0012001F0011001FFFFFFFF404040" } 9 { #SPACY set_scc_form $device $channel "808ea0c0e000203f3e3c3a373129201c1000e6c0d000203f10e080c000200090" } } } #SCC editor/copier proc toggle_scc_editor {} { variable select_device if {![osd exists scc_viewer]} {toggle_scc_viewer} # If exists destory/reset and exit if {[osd exists scc]} { osd destroy scc osd destroy selected # Let's assume the user doesn't have the SCC Viewer active toggle_scc_viewer deactivate_input_layer scc_editor return "" } bind -layer scc_editor "mouse button1 down" {scc_toys::checkclick} activate_input_layer scc_editor osd create rectangle scc \ -x 200 -y 100 -h 256 -w 256 \ -rgba "0x0044aa80 0x2266dd80 0x0055cc80 0x44aaff80" \ -borderrgba 0xffffffff -bordersize 1 for {set i 0} {$i < 32} {incr i} { osd create rectangle scc.slider$i -x [expr {$i * 8}] -y 0 -h 255 -w 8 -rgba 0x0000ff80 osd create rectangle scc.slider$i.val -x 0 -y 127 -h 1 -w 8 -rgba 0xffffff90 } for {set i 0} {$i < 32} {incr i} { osd create rectangle "scc.hline$i" -x [expr {$i * 8 - 1}] -y 0 -h 255 -w 1 -rgba 0xffffff60 osd create rectangle "scc.vline$i" -x 0 -y [expr {$i * 8 - 1}] -h 1 -w 255 -rgba 0xffffff60 } osd create rectangle "scc.hmid1" -x 63 -y 0 -h 255 -w 1 -rgba 0xff000080 osd create text "scc.hmid1.text" -x -2 -y -12 -text "7" -size 8 -rgba 0xffffffff osd create rectangle "scc.hmid2" -x 127 -y 0 -h 255 -w 1 -rgba 0xffffffff osd create text "scc.hmid2.text" -x -5 -y -12 -text "15" -size 8 -rgba 0xffffffff osd create rectangle "scc.hmid3" -x 191 -y 0 -h 255 -w 1 -rgba 0xff000080 osd create text "scc.hmid3.text" -x -5 -y -12 -text "23" -size 8 -rgba 0xffffffff osd create text "scc.hline0.text" -x 0 -y -12 -text "0" -size 8 -rgba 0xffffffff osd create text "scc.hline31.text" -x 0 -y -12 -text "31" -size 8 -rgba 0xffffffff osd create rectangle "scc.vmid1" -x 0 -y 63 -h 1 -w 255 -rgba 0xff000080 osd create text "scc.vmid1.text" -x -20 -y -4 -text "+64" -size 8 -rgba 0xffffffff osd create rectangle "scc.vmid2" -x 0 -y 127 -h 1 -w 255 -rgba 0xffffffff osd create text "scc.vmid2.text" -x -10 -y -4 -text "0" -size 8 -rgba 0xffffffff osd create rectangle "scc.vmid3" -x 0 -y 191 -h 1 -w 255 -rgba 0xff000080 osd create text "scc.vmid3.text" -x -18 -y -4 -text "-64" -size 8 -rgba 0xffffffff osd create text "scc.vline0.text" -x -25 -y 0 -text "+128" -size 8 -rgba 0xffffffff osd create text "scc.vline31.text" -x -22 -y 0 -text "-128" -size 8 -rgba 0xffffffff osd create rectangle selected return "" } proc checkclick {} { variable scc_devices variable select_device variable select_device_chan #check editor matrix for {set i 0} {$i < 32} {incr i} { lassign [osd info "scc.slider$i" -mousecoord] x y if {($x >= 0 && $x <= 1) && ($y >= 0 && $y <= 1)} { debug write "$select_device SCC" [expr {$select_device_chan * 32 + $i}] [expr {int(255 * $y - 128) & 0xff}] osd configure scc.slider$i.val \ -y [expr {$y * 255}] \ -h [expr {128 - ($y * 255)}] } } #check scc viewer channels foreach device $scc_devices { for {set i 0} {$i < 5} {incr i} { lassign [osd info "scc_viewer.$device.$i" -mousecoord] x y if {($x >= 0 && $x <= 1) && ($y >= 0 && $y <= 1)} { #store device and channel picked from the SCC_viewer in memory set select_device $device set select_device_chan $i set abs_x [osd info "scc_viewer.$device" -x] set sel_h [osd info "scc_viewer.$device.$i" -h] set sel_w [osd info "scc_viewer.$device.$i" -w] set sel_x [osd info "scc_viewer.$device.$i" -x] set sel_y [osd info "scc_viewer.$device.$i" -y] osd configure selected \ -x [expr {int($sel_x) + $abs_x}] \ -y [expr {int($sel_y)}] \ -w [expr {$sel_w + 4}] \ -h [expr {$sel_h + 4}] \ -z 1 \ -rgba 0xff0000ff set base [expr {$i * 32}] for {set q 0} {$q < 32} {incr q} { set sccwave_new [get_scc_wave [debug read "$device SCC" [expr {$base + $q}]]] set sccwave_old [osd info scc.slider$q.val -h] osd configure scc.slider$q.val \ -y [expr {128 + $y}] \ -h $sccwave_new } } } } } proc get_val_matrix_column {sccval} { expr {$sccval < 0 ? $sccval + 256 : $sccval} } proc get_scc_string_from_matrix {name} { set sccstring "" set outputfile "$name" set output [open $outputfile "w"] for {set i 0} {$i < 32} {incr i} { set a [format %02x [get_val_matrix_column [expr {int([osd info scc.slider$i.val -h])}]]] set sccstring [concat $sccstring$a] } close $output puts "$outputfile writen to $name" return $sccstring } namespace export toggle_scc_editor namespace export toggle_psg2scc namespace export set_scc_wave namespace export toggle_scc_viewer } ;# namespace scc_toys namespace import scc_toys::* openMSX-RELEASE_0_12_0/share/scripts/_showdebuggable.tcl000066400000000000000000000031261257557151200230570ustar00rootroot00000000000000namespace eval showdebuggable { # # show_debuggable # set_help_text showdebuggable \ {Print content of debuggable nicely formatted Usage: showdebuggable
    [] } set_tabcompletion_proc showdebuggable [namespace code tab_showdebuggable] proc tab_showdebuggable {args} { if {[llength $args] == 2} { return [debug list] } } proc showdebuggable_line {debuggable address} { set size [debug size $debuggable] set num [expr {(($address + 16) <= $size) ? 16 : ($size - $address)}] set mem "[debug read_block $debuggable $address $num]" binary scan $mem c* values set hex "" foreach val $values { append hex [format "%02x " [expr {$val & 0xff}]] } set pad [string repeat " " [expr {16 - $num}]] set asc [regsub -all {[^ !-~]} $mem {.}] return [format "%04x: %s%s %s\n" $address $hex $pad $asc] } proc showdebuggable {debuggable {address 0} {lines 8}} { set result "" for {set i 0} {$i < $lines} {incr i} { if {$address >= [debug size $debuggable]} break append result [showdebuggable_line $debuggable $address] incr address 16 } return $result } # Some stuff for backwards compatibility. Do we want to deprecate them? # I prefer to keep this as a convenience function. set_help_text showmem \ {Print the content of the CPU visible memory nicely formatted This is a shortcut for 'showdebuggable memory
    '. Usage: showmem
    [] } proc showmem {{address 0} {lines 8}} { showdebuggable memory $address $lines } namespace export showdebuggable namespace export showmem } ;# namespace showdebuggable namespace import showdebuggable::* openMSX-RELEASE_0_12_0/share/scripts/_slot.tcl000066400000000000000000000151021257557151200210530ustar00rootroot00000000000000namespace eval slot { # # get_selected_slot # set_help_text get_selected_slot \ {Returns the selected slot for the given memory page. This proc is typically used as a helper for a larger proc. @param page The memory page (0-3) @result Returns a Tcl list with two elements. First element is the primary slot (0-3). Second element is the secondary slot (0-3) or 'X' in case this slot was not expanded } proc get_selected_slot {page} { set ps_reg [debug read "ioports" 0xA8] set ps [expr {($ps_reg >> (2 * $page)) & 0x03}] if {[machine_info "issubslotted" $ps]} { set ss_reg [debug read "slotted memory" [expr {0x40000 * $ps + 0xFFFF}]] set ss [expr {(($ss_reg ^ 255) >> (2 * $page)) & 0x03}] } else { set ss "X" } list $ps $ss } # # slotselect # set_help_text slotselect \ {Returns a nicely formatted overview of the selected slots.} proc slotselect {} { set result "" for {set page 0} {$page < 4} {incr page} { lassign [get_selected_slot $page] ps ss append result [format "%04X: slot %d" [expr {0x4000 * $page}] $ps] if {$ss ne "X"} {append result "." $ss} append result "\n" } return $result } # # get_mapper_size # set_help_text get_mapper_size \ {Returns the size of the memory mapper in a given slot. Result is 0 when there is no memory mapper in the slot.} proc get_mapper_size {ps ss} { set result 0 catch { ;# for multi-mem devices, but those aren't memory mappers set device [lindex [machine_info slot $ps $ss 0] 0] if {[debug desc $device] eq "memory mapper"} { set result [expr {[debug size $device] / 0x4000}] } } return $result } # # get (rom) block size # e.g. for a 'Konami' rom mapper it returns 8kB # TODO it would be nice to generalize this so that it also works for memory # mappers, or even all memory mapped devices (whether they have switchable # blocks or not) # proc get_block_size {ps ss page} { set block_size 0 catch { set device_list [machine_info slot $ps $ss $page] if {[llength $device_list] != 1} {return 0} set device_info [machine_info device [lindex $device_list 0]] set romtype [lindex $device_info 1] ;# can return "" set romtype_info [openmsx_info romtype $romtype] ;# fails if romtype == "" set block_size [dict get $romtype_info blocksize] ;# could fail?? } return $block_size } # # pc_in_slot # set_help_text pc_in_slot \ {Test whether the CPU's program counter is inside a certain slot. Typically used to set breakpoints in specific slots.} proc pc_in_slot {ps {ss "X"} {mapper "X"}} { return [address_in_slot [reg PC] $ps $ss $mapper] } # # watch_in_slot # set_help_text watch_in_slot \ {Test whether the address watched is inside a certain slot. To be used only to set watchpoints in specific slots.} proc watch_in_slot {ps {ss "X"} {mapper "X"}} { return [address_in_slot $::wp_last_address $ps $ss $mapper] } # # helper proc for pc_in_slot and watch_in_slot # set_help_text address_in_slot \ {Test whether an address is inside a certain slot. Typically used by pc_in_slot and watch_in_slot.} proc address_in_slot {addr ps {ss "X"} {block "X"}} { # get current page and slots set page [expr {$addr >> 14}] lassign [get_selected_slot $page] pc_ps pc_ss # check primary and secondary slot if {($ps ne "X") && ($pc_ps != $ps)} {return 0} if {($ss ne "X") && ($pc_ss ne "X") && ($pc_ss != $ss)} {return 0} # need to check block? if {$block eq "X"} {return true} # first (try to) check memory mapper if {$pc_ss eq "X"} {set pc_ss 0} set mapper_size [get_mapper_size $pc_ps $pc_ss] if {$mapper_size != 0} { set pc_block [debug read "MapperIO" $page] return [expr {$block == ($pc_block & ($mapper_size - 1))}] } # next (try to) check rom mapper set block_size [get_block_size $pc_ps $pc_ss $page] if {$block_size != 0} { set device_name [lindex [machine_info slot $pc_ps $pc_ss $page] 0] # note: I'm assuming that if get_block_size returns a non-zero # value, then the "romblocks" debuggable always exixts. # Is that correct? set pc_block [debug read "$device_name romblocks" $addr] return [expr {$block == $pc_block}] } # no mapper present, ok (or should we check block==0 ?) return 1 } # # slotmap # set_help_text slotmap \ {Gives an overview of the devices in the different slots.} proc slotmap_helper {ps ss} { set result "" for {set page 0} {$page < 4} {incr page} { set device_list [machine_info slot $ps $ss $page] # This 'if' introduces a parsing ambiguity (list of elements # without embedded spaces versus single element with embedded # spaces) though usually this looks nicer. # Though this means the output of this script should not be # further parsed in scripts. if {[llength $device_list] == 1} { set name [lindex $device_list 0] } else { set name $device_list } append result [format "%04X: %s\n" [expr {$page * 0x4000}] $name] } return $result } proc slotmap_name {ps ss} { set t [list $ps $ss] foreach slot [machine_info external_slot] { if {[lrange [machine_info external_slot $slot] 0 1] eq $t} { return " (${slot})" } } return "" } proc slotmap {} { set result "" for {set ps 0} {$ps < 4} {incr ps} { if {[machine_info issubslotted $ps]} { for {set ss 0} {$ss < 4} {incr ss} { append result "slot $ps.$ss[slotmap_name $ps $ss]:\n" append result [slotmap_helper $ps $ss] } } else { append result "slot $ps[slotmap_name $ps X]:\n" append result [slotmap_helper $ps 0] } } return $result } # # iomap # set_help_text iomap \ {Gives an overview of the devices connected to the different I/O ports.} proc iomap_helper {prefix begin end name} { if {$name eq ""} {return ""} set result [format "port %02X" $begin] if {$begin == ($end - 1)} { append result ": " } else { append result [format "-%02X:" [expr {$end - 1}]] } append result " $prefix $name\n" } proc iomap {} { set result "" set port 0 while {$port < 256} { set in [machine_info input_port $port] set out [machine_info output_port $port] set end [expr {$port + 1}] while {($end < 256) && ($in eq [machine_info input_port $end]) && ($out eq [machine_info output_port $end])} { incr end } if {$in eq $out} { append result [iomap_helper "I/O" $port $end $in ] } else { append result [iomap_helper "I " $port $end $in ] append result [iomap_helper " O" $port $end $out] } set port $end } return $result } namespace export get_selected_slot namespace export slotselect namespace export get_mapper_size namespace export pc_in_slot namespace export watch_in_slot namespace export address_in_slot namespace export slotmap namespace export iomap } ;# namespace slot namespace import slot::* openMSX-RELEASE_0_12_0/share/scripts/_soundchip_utils.tcl000066400000000000000000000167511257557151200233210ustar00rootroot00000000000000# Several utility procs for usage in other scripts # don't export anything, just use it from the namespace, # because these scripts aren't useful for console users # and should therefore not be exported to the global # namespace. # # These procs are specifically for sound chips. # # Born to prevent duplication between scripts for common stuff. namespace eval soundchip_utils { proc get_num_channels {soundchip} { set num 1 while {[info exists ::${soundchip}_ch${num}_mute]} { incr num } expr {$num - 1} } # It is advised to cache the result of this proc for each channel of each sound device # before using it, because then a lot will be pre-evaluated and at run time only the # actual variable stuff will be evaluated. # We cannot cache it here, because we don't know the names of the sound chips in the # machine you are going to use this on. # @param soundchip the name of the soundchip as it appears in the output of # "machine_info sounddevice" # @channel the channel for which you want the expression to get the volume, the # first channel is channel 0 and the channels are the ones as they are output # by the record_channels command. # @return expression to calculate the volume of the device for that channel in # range [0-1]; returns just 'x' in case the chip is not supported. # @todo: # - frequency expressions for (some of?) the drum channels are not correct # - implement volume for MoonSound FM (tricky stuff) # - actually, don't use regs to calc volume but actual wave data (needs openMSX # changes) # proc get_volume_expr {soundchip channel} { switch [machine_info sounddevice $soundchip] { "PSG" { set regs "\"${soundchip} regs\"" return "set keybits \[debug read $regs 7\]; set val \[debug read $regs [expr {$channel + 8}]\]; expr {(\$val & 0x10) ? 1.0 : ((\$val & 0xF) / 15.0) * !((\$keybits >> $channel) & (\$keybits >> [expr {$channel + 3}]) & 1)}" } "MoonSound wave-part" { set regs "\"${soundchip} regs\"" return "expr {(\[debug read $regs [expr {$channel + 0x68}]\]) ? (127 - (\[debug read $regs [expr {$channel + 0x50}]\] >> 1)) / 127.0 : 0.0}"; } "Konami SCC" - "Konami SCC+" { set regs "\"${soundchip} SCC\"" return "expr {((\[debug read $regs [expr {$channel + 0xAA}]\] &0xF)) / 15.0 * ((\[debug read $regs 0xAF\] >> $channel) &1)}" } "MSX-MUSIC" { set regs "\"${soundchip} regs\"" set vol_expr "(\[debug read $regs [expr {$channel + 0x30}]\] & 0x0F)" set keyon_expr "(\[debug read $regs [expr {$channel + 0x20}]\] & 0x10)" set music_mode_expr "$keyon_expr ? ((15 - $vol_expr) / 15.0) : 0.0" set rhythm_expr "\[debug read $regs 0x0E\]" if {$channel < 6} { # always melody channel return "expr {$music_mode_expr}" } elseif {$channel < 9} { # melody channel when not in rhythm mode return "expr {($rhythm_expr & 0x20) ? 0.0 : $music_mode_expr}" } elseif {$channel < 14} { # rhythm channel (when rhythm mode enabled) if {$channel < 12} { set vol_expr "(\[debug read $regs [expr {$channel + 0x30 - 3}]\] & 0x0F)" } else { set vol_expr "(\[debug read $regs [expr {$channel + 0x2E - 3}]\] >> 4)" } switch $channel { 9 {set mask 0x30} ;# BD 10 {set mask 0x28} ;# SD 11 {set mask 0x22} ;# T-CYM 12 {set mask 0x21} ;# HH 13 {set mask 0x24} ;# TOM } return "expr {($rhythm_expr & $mask) ? (15 - $vol_expr) / 15.0 : 0.0}" } else { error "Unknown channel: $channel for $soundchip!" } } "MSX-AUDIO" { set regs "\"${soundchip} regs\"" set ofst [expr {0x43 + $channel + 5 * ($channel / 3)}] set keyon_expr "(\[debug read $regs [expr {$channel + 0xB0}]\] & 0x20)" set vol_expr {(63 - (\[debug read $regs $ofst\] & 63)) / 63.0} set music_mode_expr "($keyon_expr ? [subst $vol_expr] : 0.0)" set rhythm_expr "\[debug read $regs 0xBD\]" if {$channel < 6} { # always melody channel return "expr {$music_mode_expr}" } elseif {$channel < 9} { # melody channel when not in rhythm mode return "expr {($rhythm_expr & 0x20) ? 0.0 : $music_mode_expr}" } elseif {$channel < 14} { # rhythm channel (when rhythm mode enabled) switch $channel { 9 {set mask 0x30; set ofst 0x53} ;# BD (slot 16) 10 {set mask 0x28; set ofst 0x54} ;# SD (slot 17) 11 {set mask 0x22; set ofst 0x55} ;# CYM (slot 18) 12 {set mask 0x21; set ofst 0x51} ;# HH (slot 13) 13 {set mask 0x24; set ofst 0x52} ;# TOM (slot 14) } return "expr {($rhythm_expr & $mask) ? [subst $vol_expr] : 0.0}" } elseif {$channel < 15} { # ADPCM # can we output 0 when no sample is playing? return "expr {\[debug read $regs 0x12\] / 255.0}" } else { error "Unknown channel: $channel for $soundchip!" } } default { return "x" } } } # It is advised to cache the result of this proc for each channel of each sound device # before using it, because then a lot will be pre-evaluated and at run time only the # actual variable stuff will be evaluated. # We cannot cache it here, because we don't know the names of the sound chips in the # machine you are going to use this on. # @param soundchip the name of the soundchip as it appears in the output of # "machine_info sounddevice" # @channel the channel for which you want the expression to get the frequency, the # first channel is channel 0 and the channels are the ones as they are output # by the record_channels command. # @return expression to calculate the frequency of the device for that channel in # Hz; returns just 'x' in case the chip is not supported. # @todo: # - implement frequency for MoonSound # proc get_frequency_expr {soundchip channel} { switch [machine_info sounddevice $soundchip] { "PSG" { set regs "\"${soundchip} regs\"" set basefreq [expr {3579545.454545 / 32.0}] return "set val \[expr {\[debug read $regs \[expr {0 + ($channel * 2)}\]\] + 256 * ((\[debug read $regs \[expr {1 + ($channel * 2)}\]\]) & 15)}\]; expr {$basefreq/(\$val < 1 ? 1 : \$val)}" } "Konami SCC" - "Konami SCC+" { set regs "\"${soundchip} SCC\"" set basefreq [expr {3579545.454545 / 32.0}] return "set val \[expr {\[debug read $regs \[expr {0xA0 + 0 + ($channel * 2)}\]\] + 256 * ((\[debug read $regs \[expr {0xA0 + 1 + ($channel * 2)}\]\]) & 15)}\]; expr {$basefreq/(\$val < 1 ? 1 : \$val)}" } "MSX-MUSIC" { set regs "\"${soundchip} regs\"" set basefreq [expr {3579545.454545 / 72.0}] set factor [expr {$basefreq / (1 << 18)}] if {$channel >= 9} { #drums incr channel -3 } return "expr {(\[debug read $regs [expr {$channel + 0x10}]\] + 256 * ((\[debug read $regs [expr {$channel + 0x20}]\]) & 1)) * $factor * (1 << (((\[debug read $regs [expr {$channel + 0x20}]\]) & 15) >> 1))}" } "MSX-AUDIO" { set regs "\"${soundchip} regs\"" set basefreq [expr {3579545.454545 / 72.0}] if {$channel == 14} { ;# ADPCM set factor [expr {$basefreq / (1 << 16)}] return "expr {(\[debug read $regs 0x10\] + 256 * \[debug read $regs 0x11\]) * $factor / 10}";# /10 is just to make it fall a bit into a decent range... } else { set factor [expr {$basefreq / (1 << 19)}] if {$channel >= 9} { #drums incr channel -3 } return "expr {(\[debug read $regs [expr {$channel + 0xA0}]\] + 256 * ((\[debug read $regs [expr {$channel + 0xB0}]\]) & 3)) * $factor * (1 << (((\[debug read $regs [expr {$channel + 0xB0}]\]) & 31) >> 2))}" } } default { return "x" } } } namespace export get_num_channels namespace export get_volume_expr namespace export get_frequency_expr } ;# namespace soundchip_utils # Don't import in global namespace, these are only useful in other scripts. openMSX-RELEASE_0_12_0/share/scripts/_soundlog.tcl000066400000000000000000000017731257557151200217350ustar00rootroot00000000000000namespace eval soundlog { # Backwards compatibility: # The 'soundlog' command used to be a built-in openmsx command. # Reimplemented now via the 'record -audioonly' command. set_help_text soundlog \ {Controls sound logging: writing the openMSX sound to a wav file. soundlog start Log sound to file "openmsxNNNN.wav" soundlog start Log sound to indicated file soundlog start -prefix foo Log sound to file "fooNNNN.wav" soundlog stop Stop logging sound soundlog toggle Toggle sound logging state } set_tabcompletion_proc soundlog [namespace code soundlog_tab] proc soundlog_tab {args} { if {[llength $args] == 2} { return [list "start" "stop" "toggle"] } elseif {[llength $args] == 3 && [lindex $args 2] eq "start"} { return [list "-prefix"] } return [list] } proc soundlog {args} { if {$args eq [list "stop"]} { record stop } else { record {*}$args -audioonly } } namespace export soundlog } ;# namespace soundlog namespace import soundlog::* openMSX-RELEASE_0_12_0/share/scripts/_sprites.tcl000066400000000000000000000076171257557151200215770ustar00rootroot00000000000000#attributes #expr {([vdpreg 11] << 15) + (([vdpreg 5] & 0xf8) << 7)} namespace eval osd_sprite_info { #todo: more sprite info / better layout (sprite mode/ hex values/ any other info) variable title_pos 0 variable max_sprites 0 proc sprite_viewer {} { bind -layer sprite_viewer "keyb LEFT" -repeat {osd_sprite_info::sprite_viewer_action 1} bind -layer sprite_viewer "keyb RIGHT" -repeat {osd_sprite_info::sprite_viewer_action 2} bind -layer sprite_viewer "keyb ESCAPE" -repeat {osd_sprite_info::sprite_viewer_hide} bind -layer sprite_viewer "keyb SPACE" -repeat {osd_sprite_info::sprite_viewer_action 0} activate_input_layer sprite_viewer osd create rectangle sprite_viewer -x 5 -y 100 -w 128 -h 190 -rgba 0x00000080 osd create rectangle sprite_viewer.title -x 0 -y 15 -w 128 -h 32 -rgba 0x0000ff80 -clip on osd create text sprite_viewer.title.text -x 24 -text "" -size 18 -rgba 0xffffffff osd create text sprite_viewer.index -x 70 -y 38 -text "" -size 8 -rgba 0xffffffff osd create text sprite_viewer.refresh -x 8 -y 170 -size 8 -text "\[Space\] Refresh Sprite" -rgba 0xffffffff osd create text sprite_viewer.escape -x 8 -y 180 -size 8 -text "\[Escape\] to Exit Viewer" -rgba 0xffffffff sprite_viewer_action 0 } proc sprite_viewer_action {action} { variable title variable title_pos variable max_sprites if {$action == 1} {incr title_pos -1} if {$action == 2} {incr title_pos 1} if {$title_pos > $max_sprites} {set title_pos 0} if {$title_pos < 0} {set title_pos $max_sprites} #show sprite matrix show_sprite $title_pos #fade/ease osd configure sprite_viewer.title.text -text "Sprite $title_pos" -fadeCurrent 0 osd configure sprite_viewer.index -text "In mem: $max_sprites" ease_text sprite_viewer.title.text 0 $action } proc sprite_viewer_hide {} { osd destroy sprite_viewer deactivate_input_layer sprite_viewer } proc ease_text {osd_object {frame_render 0} {action 0}} { #Ease in length set x 16 #Intro Fade if {$frame_render == 0} { osd configure $osd_object -x $x -fadeTarget 1 -fadePeriod 0.25 } if {$frame_render > $x} { #leave routine return "" } #ease in if {$action == 2} { osd configure $osd_object -x [expr {$frame_render - $x}] } else { osd configure $osd_object -x [expr {$x - $frame_render}] } incr frame_render 4 #call same function after realtime 0.05 [namespace code [list ease_text $osd_object $frame_render $action]] } proc show_sprite {sprite} { variable max_sprites set sprite_size [expr {([vdpreg 1] & 2) ? 16 : 8}] set factor [expr {($sprite_size == 8) ? 1 : 4}] set max_sprites [expr {(256 / $factor) - 1}] if {($sprite > $max_sprites) || ($sprite < 0)} { error "Please choose a value between 0 and $max_sprites" } osd destroy sprite_viewer.sprite_osd draw_matrix "sprite_viewer.sprite_osd" 7 56 7 $sprite_size 1 set addr [expr {([vdpreg 6] << 11) + ($sprite * $factor * 8)}] for {set y 0} {$y < $sprite_size} {incr y; incr addr} { set pattern [vpeek $addr] set mask [expr {($sprite_size == 8) ? 0x80 : 0x8000}] if {$sprite_size == 16} { set pattern [expr {($pattern << 8) + [vpeek [expr {$addr + 16}]]}] } for {set x 0} {$x < $sprite_size} {incr x} { if {$pattern & $mask} { osd configure sprite_viewer.sprite_osd.${x}_${y} -rgba 0xffffffff } set mask [expr {$mask >> 1}] } } } proc draw_matrix {matrixname x y blocksize matrixsize matrixgap} { osd create rectangle $matrixname \ -x $x \ -y $y \ -h [expr {$matrixsize * $blocksize}] \ -w [expr {$matrixsize * $blocksize}] \ -rgba 0x00000040 for {set x 0} {$x < $matrixsize} {incr x} { for {set y 0} {$y < $matrixsize} {incr y} { osd create rectangle $matrixname.${x}_${y} \ -h [expr {$blocksize - $matrixgap}] \ -w [expr {$blocksize - $matrixgap}] \ -x [expr {$x * $blocksize}] \ -y [expr {$y * $blocksize}] \ -rgba 0x0000ff80 } } return "" } namespace export sprite_viewer } ;# namespace osd_menu namespace import osd_sprite_info::* openMSX-RELEASE_0_12_0/share/scripts/_stack.tcl000066400000000000000000000005271257557151200212040ustar00rootroot00000000000000set_help_text stack \ {Show the top most entries on the CPU stack. If is not specified, 8 entries are returned. Usage: stack [] } proc stack {{depth 8}} { set result "" for {set i 0; set sp [reg SP]} {$i < $depth} {incr i; incr sp 2} { append result [format "%04x: %04x\n" $sp [peek16 $sp]] } return $result } openMSX-RELEASE_0_12_0/share/scripts/_tas_tools.tcl000066400000000000000000000521051257557151200221050ustar00rootroot00000000000000namespace eval tas { ### periodic refresh stuff variable callback_list "" variable callback_frame_id variable callback_realtime_id # TODO move to utils? # Register the given callback to be executed # - (Once) when it is registered # - Every MSX frame # - Or at least 10 times per second (e.g. when MSX emulation is paused) proc enable_periodic {callback} { variable callback_list variable callback_frame_id variable callback_realtime_id # on 1st callback activate the after-stuff if {[llength $callback_list] == 0} { set callback_frame_id [after frame [namespace code periodic_frame]] set callback_realtime_id [after realtime 0.1 [namespace code periodic_realtime]] } # add to list of registered callbacks lappend callback_list $callback # execute callback for the first time $callback } # Remove a previously registered callback. After this proc returns, the # callback is guaranteed to not be called anymore. So it's (no longer) # required to add a check in the callback proc. proc disable_periodic {callback} { variable callback_list variable callback_frame_id variable callback_realtime_id # remove from registered callbacks set idx [lsearch -exact $callback_list $callback] if {$idx == -1} return set callback_list [lreplace $callback_list $idx $idx] # if the last callback was removed then deactivate after stuff if {[llength $callback_list] == 0} { after cancel $callback_frame_id after cancel $callback_realtime_id } } proc periodic_execute {} { variable callback_list foreach callback $callback_list { $callback } } proc periodic_frame {} { variable callback_frame_id variable callback_realtime_id periodic_execute # postpone 'after realtime' / reschedule 'after frame' after cancel $callback_realtime_id set callback_frame_id [after frame [namespace code periodic_frame]] set callback_realtime_id [after realtime 0.1 [namespace code periodic_realtime]] } proc periodic_realtime {} { variable callback_realtime_id periodic_execute # reschedule 'after realtime', no need to handle 'after frame' set callback_realtime_id [after realtime 0.1 [namespace code periodic_realtime]] } ### frame counter ### set_help_text toggle_frame_counter\ {Toggles display of a frame counter in the lower right corner.} proc toggle_frame_counter {} { if {[osd exists framecount]} { disable_periodic framecount_update osd destroy framecount return "" } osd create rectangle framecount \ -x 269 -y 227 -h 7 -w 42 -scaled true \ -rgba "0x0044aa80 0x2266dd80 0x0055cc80 0x44aaff80" \ -borderrgba 0x00000040 -bordersize 0.5 osd create text framecount.text -x 3 -y 1 -size 4 -rgba 0xffffffff enable_periodic framecount_update return "" } proc framecount_update {} { # Heuristic: A 'ex (sp),ix' Z80 instruction takes 25 cycles. If we're # not more than 25 Z80 cycles past the start of a frame, then indicate # we're at the start of the frame. (But it could as well be the 2nd or # 3rd (short) instruction in the frame.) set inside [expr {([machine_info VDP_cycle_in_frame] < (6 * 25)) ? "" : "+"}] osd configure framecount.text -text "Frame: [machine_info VDP_frame_count]$inside" } ### frame advance/reverse and helper procs for TAS mode key bindings ### proc get_frame_duration {} { expr {(1368.0 * (([vdpreg 9] & 2) ? 313 : 262)) / (6 * 3579545)} } proc get_start_of_frame_time {} { expr {[machine_info time] - [machine_info VDP_cycle_in_frame] / (6.0 * 3579545)} } set_help_text prev_frame \ {Rewind to the (start of) the previous frame. Useful to bind to a key in combination with next_frame. You can also go back N frames if you add N as the optional argument.} proc prev_frame {{count 1}} { set t [expr {[get_start_of_frame_time] - $count * [get_frame_duration]}] if {$t < 0} {set t 0} reverse goto $t } set_help_text next_frame \ {Emulates until the (start of) the next frame, then pause emulation. Useful to bind to a key and emulate frame by frame. You can also go over the next N frames if you add N as the optional argument.} proc next_frame {{count 1}} { set t [expr {[get_start_of_frame_time] + $count * [get_frame_duration]}] after time [expr {$t - [machine_info time]}] "set ::pause on" set ::pause off return "" } set_help_text start_of_frame \ {Rewind to the start of the current frame. See also prev_frame and next_frame.} proc start_of_frame {} { prev_frame 0 } set_help_text advance_frame \ {Emulate forward for the duration of one frame. The relative timing position within the frame remains unchanged. You can also advance N frames if you add N as the optional argument.} proc advance_frame {{count 1}} { after time [expr {$count * [get_frame_duration]}] "set ::pause on" set ::pause off return "" } set_help_text reverse_frame \ {Emulate backward for the duration of one frame. The relative timing position within the frame remains unchanged. You can also reverse N frames if you add N as the optional argument.} proc reverse_frame {{count 1}} { set t [expr {[machine_info time] - $count * [get_frame_duration]}] if {$t < 0} {set t 0} reverse goto $t } proc load_replay {name} { reverse loadreplay -goto savetime $name return "" } ### Very basic replay slot selector ### user_setting create string current_replay_slot "Name of the current replay slot." slot0 proc list_slots {} { set slots [list] for {set i 0} {$i <= 9} {incr i} { lappend slots "slot$i" } return $slots } proc menu_create_slot_menu {} { set items [list_slots] set menu_def \ { execute tas::set_slot font-size 8 border-size 2 width 100 xpos 100 ypos 100 header { text "Select Replay Slot" font-size 10 post-spacing 6 }} return [osd_menu::prepare_menu_list $items 10 $menu_def] } proc set_slot {item} { osd_menu::menu_close_all set ::current_replay_slot $item } proc open_select_slot_menu {} { osd_menu::do_menu_open [menu_create_slot_menu] osd_menu::select_menu_item $::current_replay_slot for {set i 0} {$i < [llength [tas::list_slots]]} {incr i} { bind -layer slot_menu "$i" "tas::set_slot [lindex [tas::list_slots] $i]; tas::unbind_number_keys" } activate_input_layer slot_menu } proc unbind_number_keys {} { deactivate_input_layer slot_menu } proc save_replay_slot {} { reverse savereplay $::current_replay_slot } proc load_replay_slot {} { load_replay $::current_replay_slot } ### Show Cursor Keys / 'fire buttons and others' ### variable keys proc show_keys {} { variable keys # get joysticka values set joy [debug read joystickports 0] foreach key $keys { show_key_press [dict get $key name] [eval [dict get $key check_expr]] } } #move to other TCL script? proc is_key_pressed {row bit} { expr {!([debug read keymatrix $row] & (1 << $bit))} } proc show_key_press {key state} { set keycol [expr {$state ? 0xff000080 : "0x0044aa80 0x2266dd80 0x0055cc80 0x44aaff80"}] osd configure cursors.$key -rgba $keycol } proc create_key {name x y} { osd create rectangle cursors.$name -x $x -y $y -w 16 -h 7 \ -rgba "0x0044aa80 0x2266dd80 0x0055cc80 0x44aaff80" \ -bordersize 0.5 -borderrgba 0x00000040 osd create text cursors.$name.text -x 2 -y 1 -text $name -size 4 -rgba 0xffffffff } # you can setup your own key definitions by loading a script which defines a proc like this: # # proc define_custom_keys {} { # variable tas::keys # # set tas::keys {{ # name up # x 20 # y 2 # check_expr {expr {([is_key_pressed 8 5] || !($joy & 1))}} # } { # name shift # x 186 # y 8 # check_expr {is_key_pressed 6 0} # }} #} # proc toggle_cursors {} { variable keys if {[osd exists cursors]} { disable_periodic show_keys osd destroy cursors } else { osd create rectangle cursors -x 64 -y 219 -h 26 -w 204 -scaled true -rgba 0x00000000 if {[info procs ::define_custom_keys] eq "::define_custom_keys"} { define_custom_keys } else { define_default_keys } foreach key $keys { create_key [dict get $key name] [dict get $key x] [dict get $key y] } enable_periodic show_keys } } proc define_default_keys {} { variable keys set keys {{ name up x 20 y 2 check_expr {expr {([is_key_pressed 8 5] || !($joy & 1))}} } { name down x 20 y 14 check_expr {expr {([is_key_pressed 8 6] || !($joy & 2))}} } { name left x 2 y 8 check_expr {expr {([is_key_pressed 8 4] || !($joy & 4))}} } { name right x 38 y 8 check_expr {expr {([is_key_pressed 8 7] || !($joy & 8))}} } { name space x 60 y 8 check_expr {expr {([is_key_pressed 8 0] || !($joy & 16))}} } { name m x 78 y 8 check_expr {expr {([is_key_pressed 4 2] || !($joy & 32))}} } { name n x 96 y 8 check_expr {is_key_pressed 4 3} } { name z x 114 y 8 check_expr {is_key_pressed 5 7} } { name x x 132 y 8 check_expr {is_key_pressed 5 5} } { name graph x 150 y 8 check_expr {is_key_pressed 6 2} } { name ctrl x 168 y 8 check_expr {is_key_pressed 6 1} } { name shift x 186 y 8 check_expr {is_key_pressed 6 0} }} } ### RAM Watch ### # TODO: # - be smarter with coordinates, by using negative ones # - maybe put description on separate line to make window narrow again? variable addr_watches [dict create] ;# dict of RAM watches # TODO move to utils? proc dict_insert_sorted {dictname key value} { upvar $dictname d set i 0 dict for {k v} $d { if {$key <= $k} { if {$key == $k} { dict set d $key $value return $d } break } incr i 2 } set d [linsert $d $i $key $value] } proc ram_watch_add {addr_str args} { variable addr_watches set type_dict [dict create byte {peek 2} b {peek 2} \ u8 {peek 2} s8 {peek_s8 2} \ word {peek16 4} w {peek16 4} \ u16 {peek16 4} s16 {peek_s16 4} \ u16_LE {peek16 4} s16_LE {peek_s16 4} \ u16_BE {peek16_BE 4} s16_BE {peek_s16_BE 4}] set format_dict [dict create dec "%d" hex "0x%0SX"] # sanitize input set addr [format 0x%04X $addr_str] if {($addr < 0) || ($addr > 0xffff)} { error "Address must be in range 0x0..0xffff, got: $addr_str" } set addr_already_watched [dict exists $addr_watches $addr] # defaults set desc "?" set type "byte" set format "hex" if {$addr_already_watched} { # start from the previously set values set desc [dict get $addr_watches $addr -desc] set type [dict get $addr_watches $addr -type] set format [dict get $addr_watches $addr -format] } while {[llength $args] > 0} { set option [lindex $args 0] switch -- $option { "-desc" { set desc [lindex $args 1] set args [lrange $args 2 end] } "-type" { set type [lindex $args 1] if {![dict exists $type_dict $type]} { error "Unsupported type: $type. Choose from: [dict keys $type_dict]" } set args [lrange $args 2 end] } "-format" { set format [lindex $args 1] if {![dict exists $format_dict $format]} { error "Unsupported format: $format. Choose from: [dict keys $format_dict]" } set args [lrange $args 2 end] } "default" { error "Invalid option: $option." } } } lassign [dict get $type_dict $type] peek_method num_hex_digits set fmtStr1 [dict get $format_dict $format] set fmtStr2 [string map [list S $num_hex_digits] $fmtStr1] set exprStr "set v \[$peek_method $addr\]; set r \"\[expr {(\$v < 0) ? \"-\" : \"\"}\]\[format $fmtStr2 \[expr {abs(\$v)}\]\]\"; set r" # add watch to watches set old_nof_watches [dict size $addr_watches] dict_insert_sorted addr_watches $addr [dict create -desc $desc -format $format -type $type exprStr $exprStr] # if OSD doesn't exist yet create it if {$old_nof_watches == 0} { ram_watch_init_widget } # (possibly) add one extra entry if {!$addr_already_watched} { ram_watch_add_to_widget $old_nof_watches ram_watch_update_widget_size } ram_watch_update_addresses if {$old_nof_watches == 0} { enable_periodic ram_watch_update_values } return "" } proc ram_watch_init_widget {} { osd create rectangle ram_watch \ -x 0 -y 0 -h 240 -w 320 -scaled true -rgba 0x00000000 osd create rectangle ram_watch.addr \ -x 257 -y 1 -w 62 -h 221 \ -rgba "0x0044aa80 0x2266dd80 0x0055cc80 0x44aaff80" \ -borderrgba 0x00000040 -bordersize 0.5 osd create text ram_watch.addr.title -text "RAM Watch" -x 2 -y 1 -size 4 -rgba 0xffffffff } proc ram_watch_update_widget_size {} { variable addr_watches set nr [dict size $addr_watches] osd configure ram_watch.addr -h [expr {8 + ($nr * 6)}] } proc ram_watch_add_to_widget {nr} { osd create rectangle ram_watch.addr.mem$nr \ -x 2 -y [expr {8 + ($nr * 6)}] -h 5 -w 16 -rgba 0x40404080 osd create text ram_watch.addr.mem$nr.text \ -size 4 -rgba 0xffffffff osd create rectangle ram_watch.addr.val$nr \ -x 19 -y [expr {8 + ($nr * 6)}] -h 5 -w 17 -rgba 0x40404080 osd create text ram_watch.addr.val$nr.text \ -size 4 -rgba 0xffffffff osd create rectangle ram_watch.addr.desc$nr \ -x 37 -y [expr {8 + ($nr * 6)}] -h 5 -w 23 -rgba 0x40404080 -clip true osd create text ram_watch.addr.desc$nr.text \ -size 4 -rgba 0xffffffff } proc ram_watch_remove {addr_str} { variable addr_watches # sanitize input set addr [format 0x%04X $addr_str] # check watch exists if {![dict exists $addr_watches $addr]} { # not an error return "" } #remove address dict unset addr_watches $addr set i [dict size $addr_watches] #remove one OSD entry osd destroy ram_watch.addr.mem$i osd destroy ram_watch.addr.val$i osd destroy ram_watch.addr.desc$i #if all elements are gone don't display anything anymore. if {$i == 0} { disable_periodic ram_watch_update_values osd destroy ram_watch } else { ram_watch_update_addresses ram_watch_update_widget_size } return "" } proc ram_watch_clear {} { variable addr_watches set addr_watches [dict create] disable_periodic ram_watch_update_values osd destroy ram_watch return "" } proc ram_watch_update_addresses {} { variable addr_watches set i 0 dict for {addr v} $addr_watches { osd configure ram_watch.addr.mem$i.text -text $addr osd configure ram_watch.addr.desc$i.text -text [dict get $v -desc] incr i } } proc ram_watch_update_values {} { variable addr_watches set i 0 dict for {addr v} $addr_watches { set exprStr [dict get $v exprStr] osd configure ram_watch.addr.val$i.text -text [eval $exprStr] incr i } } proc ram_watch_save {name} { variable addr_watches # exprStr property doesn't need to be saved set filtered_watches [dict create] dict for {addr v} $addr_watches { dict set filtered_watches $addr [dict filter $v key -*] } set directory [file normalize $::env(OPENMSX_USER_DATA)/../ramwatches] file mkdir $directory set fullname [file join $directory ${name}.wch] if {[catch { set the_file [open $fullname {WRONLY TRUNC CREAT}] puts $the_file $filtered_watches close $the_file } errorText]} { error "Failed to save to $fullname: $errorText" } return "Successfully saved RAM watch to $fullname" } proc ram_watch_load {name} { variable addr_watches set directory [file normalize $::env(OPENMSX_USER_DATA)/../ramwatches] set fullname [file join $directory ${name}.wch] if {[catch { set the_file [open $fullname {RDONLY}] set new_addr_watches [read $the_file] close $the_file } errorText]} { error "Failed to load from $fullname: $errorText" } ram_watch_clear dict for {addr v} $new_addr_watches { ram_watch_add $addr {*}$v } return "Successfully loaded $fullname" } proc list_ram_watch_files {} { set directory [file normalize $::env(OPENMSX_USER_DATA)/../ramwatches] set results [list] foreach f [lsort [glob -tails -directory $directory -type f -nocomplain *.wch]] { lappend results [file rootname $f] } return $results } proc ram_watch {subcmd args} { switch -- $subcmd { "add" {ram_watch_add {*}$args} "remove" {ram_watch_remove {*}$args} "clear" {ram_watch_clear {*}$args} "load" {ram_watch_load {*}$args} "save" {ram_watch_save {*}$args} default {error "Invalid subcommand: $subcmd"} } } set_help_proc ram_watch [namespace code ram_watch_help] proc ram_watch_help {args} { switch -- [lindex $args 1] { "add" {return {Add an address to the list of RAM watch addresses on the right side of the screen. The list will be updated in real time, whenever a value changes. Syntax: ram_watch add
    [...] Possible options are: -desc describes the address -type datatype: byte, word, u8, s8, u16, s16, u16_BE, ... -format formatting: dec, hex }} "remove" {return {Remove an address from the list of RAM watch addresses from the list of RAM watch addresses on the right side of the screen. When the last address is removed, the list will disappear automatically. Syntax: ram_watch remove
    }} "clear" {return {Removes all RAM watches. Syntax: ram_watch clear }} "save" {return {Save the current list of RAM watches to a file. Syntax: ram_watch save }} "load" {return {Load a list of RAM watches from file. Syntax: ram_watch load }} default {return {Control the RAM watch functionality. Syntax: ram_watch [] Where sub-command is one of: add Add (or modify) a new RAM watch address remove Remove a previously added address clear Shortcut to remove all addresses save Save current list of RAM watches to a file load Load a previously saved list of RAM watches Use 'help ram_watch ' to get more detailed help on a specific sub-command. }} } } set_tabcompletion_proc ram_watch [namespace code ram_watch_tabcompletion] proc ram_watch_tabcompletion {args} { if {[llength $args] == 2} { return [list "add" "remove" "clear" "save" "load"] } switch -- [lindex $args 1] { "add" {return [list -desc -type -format]} "load" - "save" {return [list_ram_watch_files]} default {return [list]} } } ### Lag counter ### variable lag_counter variable lag_counter_wp variable previous_frame_count 0 variable input_read_in_frame false proc space_read {} { variable input_read_in_frame set input_read_in_frame true } proc toggle_lag_counter {} { variable lag_counter variable lag_counter_wp if {[osd exists lag_counter]} { osd destroy lag_counter debug remove_watchpoint $lag_counter_wp return "" } # we set it to -1, because the updater is always called at the start # and there the incr is present, so it will always incr. We compensate # for that by setting it to -1. See also on other places where we # force a call to the updater. set lag_counter -1 set lag_counter_wp [debug set_watchpoint read_io 0xa9 {[expr [debug read ioports 0xaa] & 0x0f] == 0x08} {tas::space_read}] osd create rectangle lag_counter \ -x 269 -y 220 -h 7 -w 42 -scaled true \ -borderrgba 0x00000040 -bordersize 0.5 osd create text lag_counter.text -x 3 -y 1 -size 4 -rgba 0xffffffff update_lag_counter ;# can't use enable_periodic return "" } proc update_lag_counter {} { update_lag_counter2 after frame [namespace code update_lag_counter] } proc update_lag_counter2 {} { variable lag_counter variable previous_frame_count variable input_read_in_frame if {![osd exists lag_counter]} return set new_frame_count [machine_info VDP_frame_count] if {$new_frame_count == 1} { # reset lag counter if frame counter is reset (e.g. after reset, or loading replay) set lag_counter -1 } # GFX9000 also triggers frame ends, so we should check if we # actually advanced a V99x8 frame here # Also, we check whether it was reset to force updating the display if { $previous_frame_count != $new_frame_count || $lag_counter == -1} { set previous_frame_count $new_frame_count set col [expr {!$input_read_in_frame ? 0xff000080 : "0x0044aa80 0x2266dd80 0x0055cc80 0x44aaff80"}] osd configure lag_counter -rgba $col if {!$input_read_in_frame} { incr lag_counter osd configure lag_counter.text -text "Lag fr.: $lag_counter" } else { set input_read_in_frame false } } } proc reset_lag_counter {} { variable lag_counter set lag_counter -1 update_lag_counter2 return "" } ### movie length display ### set_help_text toggle_movie_length_display\ {Toggles display of the movie length in the upper right corner.} proc toggle_movie_length_display {} { if {[osd exists movielength]} { disable_periodic movielength_update osd destroy movielength return "" } osd create rectangle movielength \ -x 269 -y 234 -h 7 -w 42 -scaled true \ -rgba "0x0044aa80 0x2266dd80 0x0055cc80 0x44aaff80" \ -borderrgba 0x00000040 -bordersize 0.5 osd create text movielength.text -x 3 -y 1 -size 4 -rgba 0xffffffff enable_periodic movielength_update return "" } proc movielength_update {} { osd configure movielength.text -text "Length: [utils::format_time_subseconds [dict get [reverse status] last_event]]" } namespace export toggle_frame_counter namespace export prev_frame namespace export next_frame namespace export start_of_frame namespace export advance_frame namespace export reverse_frame namespace export toggle_cursors namespace export ram_watch namespace export toggle_lag_counter namespace export reset_lag_counter namespace export toggle_movie_length_display } namespace import tas::* openMSX-RELEASE_0_12_0/share/scripts/_test_machines_and_extensions.tcl000066400000000000000000000062651257557151200260330ustar00rootroot00000000000000# TCL script for openMSX for easy testing of known machines and extensions. # (c) 2012 Filip H.F. "FiXato" Slagter # For inclusion with openMSX, GNU General Public License version 2 (GPLv2, # http://www.gnu.org/licenses/gpl-2.0.html) applies. # Otherwise you may use this work without restrictions, as long as this notice # is included. # The work is provided "as is" without warranty of any kind, neither express # nor implied. set_help_text test_all_machines "Test all known machines and report errors. Pass 'stderr' as channel argument to get the return values on the commandline." proc test_all_machines {{channel "stdout"}} { set nof_machines [llength [openmsx_info machines]] set broken [list] set errors [list] puts $channel "Going to test $nof_machines machines..." foreach machine [openmsx_info machines] { puts -nonewline $channel "Testing $machine ([utils::get_machine_display_name_by_config_name $machine])... " set res [test_machine $machine] if {$res != ""} { lappend broken $machine lappend errors $res set res "BROKEN: $res" } else { set res "OK" } puts $channel $res } set nof_ok [expr {$nof_machines - [llength $broken]}] set perc [expr {($nof_ok*100)/$nof_machines}] puts $channel "" puts $channel "$nof_ok out of $nof_machines machines OK ($perc\%)" if {$nof_ok < $nof_machines} { puts $channel "" puts $channel "Broken machines:" foreach machine $broken errormsg $errors { puts $channel " $machine ([utils::get_machine_display_name_by_config_name $machine]): $errormsg" } } } set_help_text test_all_extensions "Test all known extensions and report errors. Defaults to using your current machine profile. You can optionally specify another machine configuration name to test on that profile. Pass 'stderr' as last argument to get the return values on the commandline." proc test_all_extensions {{machine ""} {channel "stdout"}} { if {$machine == ""} { set machine [machine_info config_name] } # Start a new machine to prevent any conflicts machine $machine set nof_extensions [llength [openmsx_info extensions]] set broken [list] set errors [list] puts $channel "Going to test $nof_extensions extensions on machine \"[utils::get_machine_display_name_by_config_name $machine]\"..." foreach extension [openmsx_info extensions] { # Try to plug in the extension and output any errors to the # given channel (defaults to stdout aka the openMSX console) puts -nonewline $channel "Testing $extension ([utils::get_extension_display_name_by_config_name $extension])... " set res "" if { [catch {ext $extension} errorText] } { lappend broken $extension lappend errors $errorText set res "BROKEN: $errorText" } else { set res "OK" incr nof_ok remove_extension $extension } puts $channel $res } set nof_ok [expr {$nof_extensions - [llength $broken]}] set perc [expr {($nof_ok*100)/$nof_extensions}] puts $channel "" puts $channel "$nof_ok out of $nof_extensions extensions OK ($perc\%)" if {$nof_ok < $nof_extensions} { puts $channel "" puts $channel "Broken extensions:" foreach extension $broken errormsg $errors { puts $channel " $extension ([utils::get_extension_display_name_by_config_name $extension]): $errormsg" } } } openMSX-RELEASE_0_12_0/share/scripts/_text_echo.tcl000066400000000000000000000023641257557151200220620ustar00rootroot00000000000000# TODO: add a way to turn this off... namespace eval text_echo { set_help_text text_echo \ {Echoes all characters printed in text mode to stderr, meaning they will appear on the command line that openMSX was started from. } variable graph variable escape variable escape_count proc text_echo {} { variable graph 0 variable escape 0 variable escape_count 0 debug set_bp 0x0018 {[pc_in_slot 0 0]} {text_echo::print} debug set_bp 0x00A2 {[pc_in_slot 0 0]} {text_echo::print} return "" } proc print {} { variable graph variable escape variable escape_count set char [reg A] if {$graph} { #puts -nonewline stderr [format "\[G%x\]" $char] set graph 0 } elseif {$escape} { #puts -nonewline stderr [format "\[E%x\]" $char] if {$escape_count == 0} { if {$char == 0x59} { set escape_count 2 } else { set escape_count 1 } } else { incr escape_count -1 if {$escape_count == 0} { set escape 0 } } } elseif {$char == 0x01} { set graph 1 } elseif {$char == 0x1B} { set escape 1 } elseif {$char == 0x0A || $char >= 0x20} { puts -nonewline stderr [format %c $char] } else { #puts -nonewline stderr [format "\[N%x\]" $char] } } namespace export text_echo } ;# namespace text_echo namespace import text_echo::* openMSX-RELEASE_0_12_0/share/scripts/_tileviewer.tcl000066400000000000000000000105721257557151200222570ustar00rootroot00000000000000namespace eval tileviewer { proc checkclick {} { if {![osd exists tile_viewer]} { deactivate_input_layer tileviewer return } #check editor matrix # TODO instead of checking each cell in the matrix, calculate the cell from # the mouse coordinates in the whole matrix for {set xm 0} {$xm < 8} {incr xm} { for {set ym 0} {$ym < 8} {incr ym} { lassign [osd info "tile_viewer.matrix.${xm}_${ym}" -mousecoord] x y if {($x >= 0 && $x <= 1) && ($y >= 0 && $y <= 1)} { # TODO we shouldn't use the state from the OSD elements (all OSD info commands), # instead we should vpeek from VRAM and recalculate the vram address #lets chop this up in small pieces set fc [osd info tile_viewer.fc${ym} -rgba] set bc [osd info tile_viewer.bc${ym} -rgba] set tc [osd info tile_viewer.matrix.${xm}_${ym} -rgba] #toggle switch osd configure tile_viewer.matrix.${xm}_${ym} -rgba [expr {($fc == $tc) ? $bc : $fc}] set tile_add_val 0 for {set i 0} {$i < 8} {incr i} { if {[osd info tile_viewer.matrix.${i}_${ym} -rgba] == $bc} { incr tile_add_val [expr {0x80 >> $i}] } } vpoke [osd info tile_viewer.matrix.text$ym -text] $tile_add_val return } } } } proc showtile {tile} { bind -layer tileviewer "mouse button1 down" {tileviewer::checkclick} activate_input_layer tileviewer set addr [expr {$tile * 8}] osd destroy tile_viewer osd create rectangle tile_viewer -x 30 -y 30 -h 180 -w 330 -rgba 0x00007080 osd_sprite_info::draw_matrix "tile_viewer.matrix" 90 24 18 8 1 osd create text tile_viewer.text -x 0 -y 0 -size 20 -text "Tile: $tile" -rgba 0xffffffff for {set i 0} {$i < 16} {incr i} { lassign [split [getcolor $i] {}] r g b set rgbval($i) [expr {($r << (5 + 16)) + ($g << (5 + 8)) + ($b << 5)}] } # Build colors set color_base [expr {$addr + ((([vdpreg 10] << 8 | [vdpreg 3]) << 6) & 0x1e000)}] set pattern_base [expr {$addr + (([vdpreg 4] & 0x3c) << 11)}] for {set i 0} {$i < 8} {incr i} { if {[get_screen_mode] != 1} { set col [vpeek [expr {$color_base + $i}]] } else { set col 0xf0 } set bg [expr {($col & 0xf0) >> 4}] set fc [expr {$col & 0x0f}] osd create rectangle tile_viewer.bc$i -x 242 -w 16 -y [expr {24 + $i * 18}] -h 16 -rgb $rgbval($bg) -bordersize 1 -borderrgba 0xffffff80 osd create rectangle tile_viewer.fc$i -x 260 -w 16 -y [expr {24 + $i * 18}] -h 16 -rgb $rgbval($fc) -bordersize 1 -borderrgba 0xffffff80 osd create text tile_viewer.matrix.text$i -x -50 -y [expr {$i * 18}] -text [format 0x%4.4x [expr {$pattern_base + $i}]] -rgba 0xffffffff osd create text tile_viewer.matrix.color$i -x 188 -y [expr {$i * 18}] -text [format 0x%2.2x $col] -rgba 0xffffffff } # Build patterns for {set y 0} {$y < 8} {incr y} { set pattern [vpeek [expr {$pattern_base + $y}]] set mask 0x80 set bc [osd info tile_viewer.bc$y -rgba] set fc [osd info tile_viewer.fc$y -rgba] for {set x 0} {$x < 8} {incr x} { osd configure tile_viewer.matrix.${x}_${y} -rgba [expr {($pattern & $mask) ? $bc : $fc}] -bordersize 1 -borderrgba 0xffffff80 set mask [expr {$mask >> 1}] } } } proc showall {} { if {![osd exists all_tiles]} { osd create rectangle all_tiles -x 0 -y 0 -h 255 -w 255 -rgba 0xff0000ff for {set x 0} {$x < 256} {incr x} { for {set y 0} {$y < 64} {incr y} { osd create rectangle all_tiles.${x}_${y} -x $x -y $y -h 1 -w 1 } } } if {[get_screen_mode] != 2} { for {set x 0} {$x < 256} {incr x} { for {set y 0} {$y < 64} {incr y} { osd configure all_tiles.${x}_${y} -rgb 0xffffff } } return } for {set i 0} {$i < 16} {incr i} { lassign [split [getcolor $i] {}] r g b set rgbval($i) [expr {($r << (5 + 16)) + ($g << (5 + 8)) + ($b << 5)}] } set color_addr [expr {(([vdpreg 10] << 8 | [vdpreg 3]) << 6) & 0x1e000}] set pattern_addr [expr {([vdpreg 4] & 0x3c) << 11}] for {set y 0} {$y < 64} {incr y 8} { for {set x 0} {$x < 256} {incr x 8} { set ypos $y for {set yt 0} {$yt < 8} {incr yt} { set pattern [vpeek $pattern_addr] set col [vpeek $color_addr] incr pattern_addr incr color_addr set bc [expr {$col >> 4}] set fc [expr {$col & 0x0f}] set mask 0x80 set xpos $x for {set xt 0} {$xt < 8} {incr xt} { osd configure all_tiles.${xpos}_${ypos} -rgb $rgbval([expr {($pattern & $mask) ? $bc : $fc}]) set mask [expr {$mask >> 1}] incr xpos } incr ypos } } } } } ;# namespace tileviewer openMSX-RELEASE_0_12_0/share/scripts/_toggle_freq.tcl000066400000000000000000000004341257557151200223720ustar00rootroot00000000000000set_help_text toggle_freq "Switch the VDP between PAL (50 Hz)/NTSC (60 Hz)." proc toggle_freq {} { vdpreg 9 [expr {[vdpreg 9 ] ^ 2}] poke 0xFFE8 [expr {[peek 0xFFE8] ^ 2}] set freq [expr {([vdpreg 9] & 2) ? 50 : 60}] osd::display_message "Frequency set to $freq Hz" info } openMSX-RELEASE_0_12_0/share/scripts/_trainer.tcl000066400000000000000000000076171257557151200215520ustar00rootroot00000000000000namespace eval trainer { set_help_text trainer " Usage: trainer see which trainer is currently active trainer see which cheats are currently active in the trainer trainer all activate all cheats in the trainer trainer \[ ..\] toggle cheats on/off trainer deactivate deactivate trainers Examples: trainer Frogger all trainer Circus\ Charlie 1 2 trainer Pippols lives \"jump shoes\"\ When switching trainers, the currently active trainer will be deactivated. " variable trainers "" variable active_trainer "" variable items_active variable after_id 0 proc load_trainers {} { variable trainers if {[dict size $trainers] == 0} { # source the trainer definitions (user may override system defaults) and ignore errors catch {source $::env(OPENMSX_SYSTEM_DATA)/scripts/_trainerdefs.tcl} catch {source $::env(OPENMSX_USER_DATA)/scripts/_trainerdefs.tcl} } return $trainers } set_tabcompletion_proc trainer [namespace code tab_trainer] false proc tab_trainer {args} { set trainers [load_trainers] if {[llength $args] == 2} { set result [dict keys $trainers] lappend result "deactivate" } else { set result [list] set name [lindex $args 1] if {[dict exists $trainers $name]} { set items [dict get $trainers $name items] set i 1 foreach {item_name item_impl} $items { lappend result $item_name $i incr i } } lappend result "all" } return $result } proc trainer {args} { variable active_trainer variable items_active set trainers [load_trainers] if {[llength $args] > 0} { set name [lindex $args 0] if {$name ne "deactivate"} { set requested_items [lrange $args 1 end] if {![dict exists $trainers $name]} { error "no trainer for $name." } set same_trainer [string equal $name $active_trainer] set items [parse_items $name $requested_items] if {$same_trainer} { set new_items [list] foreach item1 $items item2 $items_active { lappend new_items [expr {$item1 ^ $item2}] } set items_active $new_items } else { deactivate set active_trainer $name set items_active $items execute } } else { deactivate return "" } } print } proc parse_items {name requested_items} { variable trainers set items [dict get $trainers $name items] set result [list] set i 1 foreach {item_name item_impl} $items { lappend result [expr {($requested_items eq "all") || ($i in $requested_items) || ($item_name in $requested_items)}] incr i } return $result } proc print {} { variable trainers variable active_trainer variable items_active if {$active_trainer eq ""} { return "no trainer active" } set result [list] set items [dict get $trainers $active_trainer items] lappend result "active trainer: $active_trainer" set i 1 foreach {item_name item_impl} $items item_active $items_active { set line "$i \[" append line [expr {$item_active ? "x" : " "}] append line "\] $item_name" lappend result $line incr i } join $result \n } proc execute {} { variable trainers variable active_trainer variable items_active variable after_id set items [dict get $trainers $active_trainer items ] set repeat [dict get $trainers $active_trainer repeat] foreach {item_name item_impl} $items item_active $items_active { if {$item_active} { eval $item_impl } } set after_id [after {*}$repeat trainer::execute] } proc deactivate {} { variable after_id variable active_trainer after cancel $after_id set active_trainer "" } proc deactivate_after {event} { deactivate after $event "trainer::deactivate_after $event" } deactivate_after boot deactivate_after machine_switch proc create_trainer {name repeat items} { variable trainers dict set trainers $name [dict create items $items repeat $repeat] } namespace export trainer } ;# namespace trainer namespace import trainer::* openMSX-RELEASE_0_12_0/share/scripts/_trainerdefs.tcl000066400000000000000000007364551257557151200224250ustar00rootroot00000000000000# Cheat definitions - Version 3.2 # # Copyright 2005-2013 Albert Beevendorp all rights reserved # Copyright 2005-2013 Patrick van Arkel all rights reserved # Copyright 2005-2009 BlueMSX Team # # The definition of cheating according to google: # - Defeat someone in an expectation through trickery or deceit # - Deceiver: someone who leads you to believe something that is not true # - A deception for profit to yourself # # Still not satisfied with these trainers? Try BiFi's IPS archive @ http://ips.tni.nl/. # # Cheating can enhance or ruin your gaming experience, use it wisely # # Updated @ 2013/02/04 # create_trainer "1942 - MSX1 Version" {time 1} { "9 Loops Left" {dpoke 0xee81 9} "Lives: 9 Lives Left" {dpoke 0xed2f 9} "Stage Select: Stage 01" {dpoke 0xed20 1} "Stage Select: Stage 02" {dpoke 0xed20 2} "Stage Select: Stage 03" {dpoke 0xed20 3} "Stage Select: Stage 04" {dpoke 0xed20 4} "Stage Select: Stage 05" {dpoke 0xed20 5} "Stage Select: Stage 06" {dpoke 0xed20 6} "Stage Select: Stage 07" {dpoke 0xed20 7} "Stage Select: Stage 08" {dpoke 0xed20 8} "Stage Select: Stage 09" {dpoke 0xed20 9} "Stage Select: Stage 10" {dpoke 0xed20 10} "Stage Select: Stage 11" {dpoke 0xed20 11} "Stage Select: Stage 12" {dpoke 0xed20 12} "Stage Select: Stage 13" {dpoke 0xed20 13} "Stage Select: Stage 14" {dpoke 0xed20 14} "Stage Select: Stage 15" {dpoke 0xed20 15} "Stage Select: Stage 16" {dpoke 0xed20 16} "Stage Select: Stage 17" {dpoke 0xed20 17} "Stage Select: Stage 18" {dpoke 0xed20 18} "Stage Select: Stage 19" {dpoke 0xed20 19} "Stage Select: Stage 20" {dpoke 0xed20 20} "Stage Select: Stage 21" {dpoke 0xed20 21} "Stage Select: Stage 22" {dpoke 0xed20 22} "Stage Select: Stage 23" {dpoke 0xed20 23} "Stage Select: Stage 24" {dpoke 0xed20 24} "Stage Select: Stage 25" {dpoke 0xed20 25} "Stage Select: Stage 26" {dpoke 0xed20 26} "Stage Select: Stage 27" {dpoke 0xed20 27} "Stage Select: Stage 28" {dpoke 0xed20 28} "Stage Select: Stage 29" {dpoke 0xed20 29} "Stage Select: Stage 30" {dpoke 0xed20 30} "Stage Select: Stage 31" {dpoke 0xed20 31} "Stage Select: Stage 32" {dpoke 0xed20 32} "Weapon: Four Middle Shots" {dpoke 0xee83 1} "Weapon: Four Middle Shots + Right Shot" {dpoke 0xee83 3} "Weapon: Ghost Plane Left" {dpoke 0xee76 1} "Weapon: Ghost Plane Right" {dpoke 0xee66 1} "Weapon: Invulnerable + Four Middle Shots + Right Shot" {dpoke 0xee83 251} "Weapon: Invulnerable + Left Shot + Four Middle Shots" {dpoke 0xee83 253} "Weapon: Invulnerable + Left Shot + Four Middle Shots + Right Shot" {dpoke 0xee83 255} "Weapon: Invulnerable + Left Shot + Two Middle Shots" {dpoke 0xee83 252} "Weapon: Invulnerable + Left Shot + Two Middle Shots + Right Shot" {dpoke 0xee83 254} "Weapon: Invulnerable + Two Middle Shots + Right Shot" {dpoke 0xee83 250} "Weapon: Left Shot + Four Middle Shots" {dpoke 0xee83 5} "Weapon: Left Shot + Four Middle Shots + Right Shot" {dpoke 0xee83 7} "Weapon: Left Shot + Two Middle Shots" {dpoke 0xee83 4} "Weapon: Left Shot + Two Middle Shots + Right Shot" {dpoke 0xee83 6} "Weapon: Two Middle Shots" {dpoke 0xee83 0} "Weapon: Two Middle Shots + Right Shot" {dpoke 0xee83 2} } create_trainer "1942 - MSX2 Version" {time 1} { "9 Loops Left" {dpoke 0xee81 9} "Four Middle Shots" {dpoke 0xee83 1} "Four Middle Shots + Right Shot" {dpoke 0xee83 3} "Ghost Plane Left" {dpoke 0xee76 1} "Ghost Plane Right" {dpoke 0xee66 1} "Invulnerable + Four Middle Shots + Right Shot" {dpoke 0xee83 251} "Invulnerable + Left Shot + Four Middle Shots" {dpoke 0xee83 253} "Invulnerable + Left Shot + Four Middle Shots + Right Shot" {dpoke 0xee83 255} "Invulnerable + Left Shot + Two Middle Shots" {dpoke 0xee83 252} "Invulnerable + Left Shot + Two Middle Shots + Right Shot" {dpoke 0xee83 254} "Invulnerable + Two Middle Shots + Right Shot" {dpoke 0xee83 250} "Left Shot + Four Middle Shots" {dpoke 0xee83 5} "Left Shot + Four Middle Shots + Right Shot" {dpoke 0xee83 7} "Left Shot + Two Middle Shots" {dpoke 0xee83 4} "Left Shot + Two Middle Shots + Right Shot" {dpoke 0xee83 6} "Two Middle Shots" {dpoke 0xee83 0} "Two Middle Shots + Right Shot" {dpoke 0xee83 2} "Lives: 9 Lives Left" {dpoke 0xed2f 9} "Stage: Stage 01" {dpoke 0xed20 1} "Stage: Stage 02" {dpoke 0xed20 2} "Stage: Stage 03" {dpoke 0xed20 3} "Stage: Stage 04" {dpoke 0xed20 4} "Stage: Stage 05" {dpoke 0xed20 5} "Stage: Stage 06" {dpoke 0xed20 6} "Stage: Stage 07" {dpoke 0xed20 7} "Stage: Stage 08" {dpoke 0xed20 8} "Stage: Stage 09" {dpoke 0xed20 9} "Stage: Stage 10" {dpoke 0xed20 10} "Stage: Stage 11" {dpoke 0xed20 11} "Stage: Stage 12" {dpoke 0xed20 12} "Stage: Stage 13" {dpoke 0xed20 13} "Stage: Stage 14" {dpoke 0xed20 14} "Stage: Stage 15" {dpoke 0xed20 15} "Stage: Stage 16" {dpoke 0xed20 16} "Stage: Stage 17" {dpoke 0xed20 17} "Stage: Stage 18" {dpoke 0xed20 18} "Stage: Stage 19" {dpoke 0xed20 19} "Stage: Stage 20" {dpoke 0xed20 20} "Stage: Stage 21" {dpoke 0xed20 21} "Stage: Stage 22" {dpoke 0xed20 22} "Stage: Stage 23" {dpoke 0xed20 23} "Stage: Stage 24" {dpoke 0xed20 24} "Stage: Stage 25" {dpoke 0xed20 25} "Stage: Stage 26" {dpoke 0xed20 26} "Stage: Stage 27" {dpoke 0xed20 27} "Stage: Stage 28" {dpoke 0xed20 28} "Stage: Stage 29" {dpoke 0xed20 29} "Stage: Stage 30" {dpoke 0xed20 30} "Stage: Stage 31" {dpoke 0xed20 31} "Stage: Stage 32" {dpoke 0xed20 32} } create_trainer "3D Bomberman" {time 1} { "Lives: Lives" {dpoke 0xe80f 3} } create_trainer "A Life M36 Planet" {time 1} { "Life" {dpoke 0xc527 99;poke 0xd3c2 99} } create_trainer "A-Na-Za - Kaleidoscope Special" {time 1} { "Credit" {dpoke 0xc01d 153;poke 0xc01e 153} "Fire Super Shot" {dpoke 0xc03b 6} "Monolis" {dpoke 0xc020 8} "Speed" {dpoke 0xc03d 4} "Power: Power" {dpoke 0xc016 16} } create_trainer "A.E" {time 1} { "Lives: Lives " {dpoke 0xe209 6} } create_trainer "A1 Spirit - The Way To Formula 1" {time 1} { "Cheats: All Combis With Konami Carts" {dpoke 0xe1de 2} "Cheats: Escon Cheat Active" {dpoke 0xe1fd 1} "Cheats: Hyperoff Cheat Active" {dpoke 0xe1d6 1} "Cheats: Maxpoint Cheat Active" {dpoke 0xe1df 1} "Player 1 Settings: Player 1 First Position" {dpoke 0xe331 1} "Player 1 Settings: Player 1 Full Fuel" {dpoke 0xe310 255} "Player 1 Settings: Player 1 No Damage" {dpoke 0xe328 0} "Player 2 Settings: Player 2 First Position" {dpoke 0xe3f1 1} "Player 2 Settings: Player 2 Full Fuel" {dpoke 0xe3d0 255} "Player 2 Settings: Player 2 No Damage" {dpoke 0xe3e8 0} } create_trainer "Abu Simbel Profanation MSX1" {time 1} { "Open Passage 1" {dpoke 0x4fd5 1} "Open Passage 10" {dpoke 0x4ff5 1} "Open Passage 11" {dpoke 0x4ff6 1} "Open Passage 12" {dpoke 0x4ffc 1} "Open Passage 2" {dpoke 0x4fd7 1} "Open Passage 3" {dpoke 0x4fd9 1} "Open Passage 4" {dpoke 0x4fda 1} "Open Passage 5" {dpoke 0x4fdd 1} "Open Passage 6" {dpoke 0x4fe1 1} "Open Passage 7" {dpoke 0x4fe2 1} "Open Passage 8" {dpoke 0x4fe6 1} "Open Passage 9" {dpoke 0x4fed 1} "X-pos (0-255)" {dpoke 0x6850 0} "Y-pos (0-100)" {dpoke 0x6851 0} "Lives: Lives" {dpoke 0x6872 99} } create_trainer "Abu Simbel Profanation MSX2" {time 1} { "X-pos (0-255)" {dpoke 0xca69 0} "Y-pos (0-100)" {dpoke 0xca6b 0} "Lives: Lives" {dpoke 0xccd9 99} } create_trainer "Action Game 1 - MSX Magazine 2003" {time 1} { "Invulnerable: Invulnerable" {dpoke 0x9000 255} "Lives: Lives" {dpoke 0x9005 255} } create_trainer "Action Game 3 - MSX Magazine 2003" {time 1} { "Blue Enemies" {dpoke 0xc803 5;poke 0xc807 5;dpoke 0xc80b 5;dpoke 0xc80f 5;dpoke 0xc813 5} "Lives: Lives" {dpoke 0xc833 9} } create_trainer "Action Game 4 - MSX Magazine 2003" {time 1} { "No Damage (de-select Before Goal)" {dpoke 0x8193 0} "Time: Time" {dpoke 0x8665 200} } create_trainer "Action Game 5 - MSX Magazine 2003" {time 1} { "Lives: Lives" {dpoke 0xc87e 9} } create_trainer "Action Game 7 - MSX Magazine 2003" {time 1} { "Lives: Lives" {dpoke 0x885e 9} } create_trainer "Actman" {time 1} { "Axe" {dpoke 0xe1b4 2} "Bonus Infinite Time" {dpoke 0xe1e9 20} "Only One Bear" {dpoke 0xe247 1} "Only One Bird" {dpoke 0xe248 1} "Only One Blue Monster" {dpoke 0xe24c 1} "Only One Fish" {dpoke 0xe249 1} "Only One Red Monster" {dpoke 0xe24a 1} "Only One Snake" {dpoke 0xe24b 1} "Viewing Mode" {dpoke 0xe2a9 8} "Lives: Lives Player 1" {dpoke 0xe2a7 6} "Lives: Lives Player 2" {dpoke 0xe2a8 6} "Weapon: Sword" {dpoke 0xe1b4 1} } create_trainer "Addicta Ball" {time 1} { "Ammo" {dpoke 0x0619 64} "Floor" {dpoke 0x0aeb 8;poke 0x0aec 8;dpoke 0x0aed 8;dpoke 0x0aee 8;dpoke 0x0aef 8;dpoke 0x0af0 8;dpoke 0x0af1 8;dpoke 0x0af2 8;dpoke 0x0af3 8;dpoke 0x0af4 8;dpoke 0x0af5 8;dpoke 0x0af6 8;dpoke 0x0af7 8;dpoke 0x0af8 8} "Flying Ability" {dpoke 0x618 3;poke 0x61b 2} "Fuel" {dpoke 0x0617 64} "Shooting Ability" {dpoke 0x061a 2;poke 0x061e 1} "Lives: Lives" {dpoke 0x0616 10} } create_trainer "Adven'chuta" {time 1} { "Time: Time Left" {dpoke 0xe048 255;poke 0xe049 255} } create_trainer "After The War 1" {time 1} { "Time (de-select To Get Bonus)" {dpoke 0xbbc7 9} "Energy: Energy" {dpoke 0xba9a 7} "Lives: Lives" {dpoke 0xbcb2 9} } create_trainer "After The War 2" {time 1} { "End Boss Without Bullets" {dpoke 0xc697 4} "Shots" {dpoke 0xb950 14} "Lives: Lives" {dpoke 0xbd5c 3} "Power: Power" {dpoke 0xb956 32} "Time: Time" {dpoke 0xb4ea 9} } create_trainer "Afterburner" {time 10} { "Missiles" {dpoke 0x5e23 255} "Lives: Lives" {dpoke 0x5e4e 100} } create_trainer "Agigongnyong Dooly" {time 1} { "Power: Full Powerbar" {dpoke 0xe101 255} } create_trainer "Akumajyo Drakyula - Vampire Killer" {time 1} { "Cheats: Game Master Combo" {dpoke 0xe600 255} "Debug Pos: X-pos Hero (0-255)" {dpoke 0xc427 0} "Debug Pos: Y-pos Hero (0-192)" {dpoke 0xc425 0} "Enemy: Boss Dies After 1 Hit" {dpoke 0xc418 0} "Enemy: Enemies Frozen" {dpoke 0xd010 1} "Enemy: Enemies Unfrozen " {dpoke 0xd010 0} "Enemy: Hide Enemy 1" {dpoke 0xc805 128} "Enemy: Hide Enemy 2" {dpoke 0xc885 128} "Enemy: Hide Enemy 3" {dpoke 0xc905 128} "Enemy: Kill Enemies With 1 Hit" {dpoke 0xc80d 1;poke 0xc88d 1;dpoke 0xc90d 1} "Item: Activate Map" {dpoke 0xc70f 3} "Item: Black Bible" {dpoke 0xc702 64} "Item: Boots" {dpoke 0xc431 8} "Item: Boots + Wings" {dpoke 0xc431 24} "Item: Candle" {dpoke 0xc702 1} "Item: Candle + Black Bible" {dpoke 0xc702 65} "Item: Candle + White Bible" {dpoke 0xc702 129} "Item: Flashing Screen (gold Cross)" {dpoke 0xc43e 255} "Item: Have Yellow Key" {dpoke 0xc700 255} "Item: Hearts" {dpoke 0xc417 153} "Item: Silver Cross" {dpoke 0xc440 255} "Item: White Bible" {dpoke 0xc702 128} "Item: White Key" {dpoke 0xc701 1} "Item: White Key + Gold Shield" {dpoke 0xc701 32} "Item: White Key + Gold Shield + Hourglass" {dpoke 0xc701 96} "Item: White Key + Gold Shield + Hourglass + Map" {dpoke 0xc701 224} "Item: White Key + Gold Shield + Map" {dpoke 0xc701 160} "Item: White Key + Holy Water" {dpoke 0xc701 8} "Item: White Key + Holy Water + Gold Shield" {dpoke 0xc701 40} "Item: White Key + Holy Water + Gold Shield + Hourglass" {dpoke 0xc701 104} "Item: White Key + Holy Water + Gold Shield + Hourglass + Map" {dpoke 0xc701 232} "Item: White Key + Holy Water + Gold Shield + Map" {dpoke 0xc701 168} "Item: White Key + Holy Water + Hourglass" {dpoke 0xc701 72} "Item: White Key + Holy Water + Hourglass + Map" {dpoke 0xc701 200} "Item: White Key + Holy Water + Map" {dpoke 0xc701 136} "Item: White Key + Holy Water + Red Shield" {dpoke 0xc701 24} "Item: White Key + Holy Water + Red Shield + Gold Shield" {dpoke 0xc701 56} "Item: White Key + Holy Water + Red Shield + Gold Shield + Hourglass" {dpoke 0xc701 120} "Item: White Key + Holy Water + Red Shield + Gold Shield + Hourglass + Map" {dpoke 0xc701 255} "Item: White Key + Holy Water + Red Shield + Gold Shield + Map" {dpoke 0xc701 184} "Item: White Key + Holy Water + Red Shield + Hourglass" {dpoke 0xc701 88} "Item: White Key + Holy Water + Red Shield + Hourglass + Map" {dpoke 0xc701 216} "Item: White Key + Holy Water + Red Shield + Map" {dpoke 0xc701 152} "Item: White Key + Hourglass" {dpoke 0xc701 64} "Item: White Key + Hourglass + Map" {dpoke 0xc701 192} "Item: White Key + Map" {dpoke 0xc701 128} "Item: White Key + Red Shield" {dpoke 0xc701 16} "Item: White Key + Red Shield + Gold Shield" {dpoke 0xc701 48} "Item: White Key + Red Shield + Gold Shield + Hourglass" {dpoke 0xc701 112} "Item: White Key + Red Shield + Gold Shield + Hourglass + Map" {dpoke 0xc701 240} "Item: White Key + Red Shield + Gold Shield + Map" {dpoke 0xc701 176} "Item: White Key + Red Shield + Hourglass" {dpoke 0xc701 80} "Item: White Key + Red Shield + Hourglass + Map" {dpoke 0xc701 208} "Item: White Key + Red Shield + Map" {dpoke 0xc701 144} "Item: Wings" {dpoke 0xc431 16} "Player: Invincible" {dpoke 0xc42d 255} "Player: Invisible (blue Crystal)" {dpoke 0xc43a 255} "Player: Invulnerable (sapphire Ring)" {dpoke 0xc434 255} "Player: Lives" {dpoke 0xc410 153} "Player: Power" {dpoke 0xc415 32} "Weapon: Axe" {dpoke 0xc416 3} "Weapon: Blue Cross" {dpoke 0xc416 4} "Weapon: Chain Whip" {dpoke 0xc416 1} "Weapon: Holy Water" {dpoke 0xc416 5} "Weapon: Leather Whip" {dpoke 0xc416 0} "Weapon: Sword" {dpoke 0xc416 2} } create_trainer "Albatros" {time 1} { "Every swing is your first" {dpoke 0xf160 0} } create_trainer "Alcazar - The Forgotten Fortress" {frame} { "Item In 1st Pocket (0-15)" {dpoke 0xf07e 8} "Item In 2nd Pocket (0-15)" {dpoke 0xf07f 9} "Item In 3rd Pocket (0-15)" {dpoke 0xf080 10} "Item In Hand (0-15)" {dpoke 0xf081 7} "Lives: Lives" {dpoke 0xf058 5} "Power: Power" {dpoke 0xf082 127;poke 0xf209 1} } create_trainer "Ale Hop!" {time 10} { "Mood (stay Happy)" {dpoke 0xdb68 4} "Time: Time" {dpoke 0xdb5d 9;poke 0xdb5e 9;dpoke 0xdb5f 1} } create_trainer "Aleste" {time 1} { "Invincible" {dpoke 0xc810 255} "Maxed Up Normal Shot" {dpoke 0xc012 8} "Maxed Up Special Shot" {dpoke 0xc019 3} "Scroll Speed Fast" {dpoke 0xc4ad 10} "Scroll Speed Insane" {dpoke 0xc4ad 20} "Scroll Speed Normal" {dpoke 0xc4ad 5} "Scroll Speed Slow" {dpoke 0xc4ad 0} "Scroll Speed Turbo" {dpoke 0xc4ad 29} "Lives: Lives" {dpoke 0xc010 98} "Weapon: Always Keep Weapon On 99%" {dpoke 0xc01b 99} "Weapon: Weapon 1" {dpoke 0xc018 0} "Weapon: Weapon 2" {dpoke 0xc018 1} "Weapon: Weapon 3" {dpoke 0xc018 2} "Weapon: Weapon 4" {dpoke 0xc018 3} "Weapon: Weapon 5" {dpoke 0xc018 4} "Weapon: Weapon 6" {dpoke 0xc018 5} "Weapon: Weapon 7" {dpoke 0xc018 6} "Weapon: Weapon 8" {dpoke 0xc018 7} } create_trainer "Aleste 2" {time 1} { "Player: Invincible" {dpoke 0xbc18 255} "Player: Lives" {dpoke 0xc840 99} "Weapon: Full Power On Primary Weapons" {dpoke 0xc841 10;poke 0xc8f8 10} "Weapon: Full Power On Secondary Weapons" {dpoke 0xc84e 50;poke 0xc84f 5} "Weapon: Have Weapon 1" {dpoke 0xc84a 1} "Weapon: Have Weapon 2" {dpoke 0xc84a 2} "Weapon: Have Weapon 3" {dpoke 0xc84a 3} "Weapon: Have Weapon 4" {dpoke 0xc84a 4} "Weapon: Have Weapon 5" {dpoke 0xc84a 5} "Weapon: Have Weapon 6" {dpoke 0xc84a 6} "Weapon: Have Weapon 7" {dpoke 0xc84a 7} "Weapon: Have Weapon 8" {dpoke 0xc84a 8} "Weapon: Secondary Weapon Has No Time Limit" {dpoke 0xc84d 255} } create_trainer "Aleste Demo" {time 1} { "Maxed Up Normal Shot" {dpoke 0xc812 8} "Maxed Up Special Shot" {dpoke 0xc819 3} "Scroll Speed Fast" {dpoke 0xccad 64} "Scroll Speed Insane" {dpoke 0xccad 128} "Scroll Speed Normal" {dpoke 0xccad 32} "Scroll Speed Slow" {dpoke 0xccad 16} "Scroll Speed Turbo" {dpoke 0xccad 255} "Lives: Lives" {dpoke 0xc810 98} "Weapon: Always Keep Weapon On 99" {dpoke 0xc81b 99} "Weapon: Special Weapon (1-8)" {dpoke 0xc818 1} } create_trainer "Aleste Gaiden" {frame} { "Invincible" {dpoke 0xc930 27} "Option 1" {dpoke 0xc820 255;poke 0xc822 172;dpoke 0xc823 76;dpoke 0xc832 14} "Option 2" {dpoke 0xc840 255;poke 0xc842 93;dpoke 0xc843 77;dpoke 0xc852 14} "Lives: Lives" {dpoke 0xd080 99} "Weapon: Weapon Power 0" {dpoke 0xd00c 0} "Weapon: Weapon Power 1" {dpoke 0xd00c 1} "Weapon: Weapon Power 2" {dpoke 0xd00c 2} } create_trainer "Alibaba And 40 Thieves" {time 1} { "Always Bonus Time" {dpoke 0xe014 0} "No Blue Thief" {dpoke 0xe134 1} "No Red Thief" {dpoke 0xe135 1} "No Yellow Thief" {dpoke 0xe133 1} "Lives: Lives Player 1" {dpoke 0xe003 10} "Lives: Lives Player 2" {dpoke 0xe002 10} "Time: Time" {dpoke 0xe025 0} } create_trainer "Alien 8" {time 1} { "Counter To 9999" {dpoke 0xd837 136;poke 0xd838 136;dpoke 0xd839 136;dpoke 0xd83a 136} "Lives: Lives" {dpoke 0xd81b 10} } create_trainer "Aliens" {time 1} { "Bishop Ammo" {dpoke 0x0439 50} "Bishop Health" {dpoke 0x045d 16} "Bishop Life" {dpoke 0x0445 0} "Bishop Location (1-255)" {dpoke 0x0436 1} "Bishop Status" {dpoke 0x0457 0} "Burke Ammo" {dpoke 0x0441 50} "Burke Health" {dpoke 0x045f 16} "Burke Life" {dpoke 0x0447 0} "Burke Location (1-255)" {dpoke 0x043e 1} "Burke Status" {dpoke 0x0459 0} "Gorman Ammo" {dpoke 0x0431 50} "Gorman Health" {dpoke 0x045b 16} "Gorman Life" {dpoke 0x0443 0} "Gorman Location (1-255)" {dpoke 0x042e 1} "Gorman Status" {dpoke 0x0455 0} "Hicks Ammo" {dpoke 0x0435 50} "Hicks Health" {dpoke 0x045c 16} "Hicks Life" {dpoke 0x0444 0} "Hicks Location (1-255)" {dpoke 0x0432 1} "Hicks Status" {dpoke 0x0456 0} "Ripley Ammo" {dpoke 0x042d 50} "Ripley Health" {dpoke 0x045a 16} "Ripley Life" {dpoke 0x0442 0} "Ripley Location (1-255)" {dpoke 0x042a 1} "Ripley Status" {dpoke 0x0454 0} "Vasquez Ammo" {dpoke 0x043d 50} "Vasquez Health" {dpoke 0x045e 16} "Vasquez Life" {dpoke 0x0446 0} "Vasquez Location (1-255)" {dpoke 0x043a 1} "Vasquez Status" {dpoke 0x0458 0} } create_trainer "Aliens 2" {time 15} { "Invincible" {dpoke 0xe707 255} "Life Bar" {dpoke 0xe247 16} "Twin Pulse Ammo" {dpoke 0xe28e 250} "Weapon: M40 Bombs" {dpoke 0xe28f 250} } create_trainer "Alpha Blaster" {time 1} { "Lives: Lives" {dpoke 0x0575 9} "Time: Infinite Time" {dpoke 0x0579 0} "Weapon: Laser Always Operational" {dpoke 0x057c 0} } create_trainer "Alpha Squadron" {time 1} { "Full Fuel" {dpoke 0xe016 0x99;poke 0xe017 0x99} "Lives: Lives" {dpoke 0xe000 9} } create_trainer "Alpharoid" {time 1} { "2d Shot (none)" {dpoke 0xe3b7 1} "5d Shot (none)" {dpoke 0xe3b7 2} "7d Shot (none)" {dpoke 0xe3b7 3} "Enemy Dies After One Kick" {dpoke 0xe8f7 0;poke 0xeb0e 231} "Hyper Shot" {dpoke 0xe3b6 1} "Invincible (in Space)" {dpoke 0xe399 4} "Regular Shot" {dpoke 0xe3b6 0} "Regulate 2nd Shot (none)" {dpoke 0xe3b7 0} "Wide Beam" {dpoke 0xe3b6 2} "Lives: Lives" {dpoke 0xe3a2 98} "Weapon: Ballistic Bomb" {dpoke 0xe3b6 3} } create_trainer "Altered Beast" {time 1} { "Only One Spirit Ball Required" {dpoke 0x5b4d 5} "Energy: Energy" {dpoke 0x5b31 4} "Lives: Lives" {dpoke 0x5b30 5} } create_trainer "American Truck" {frame} { "Disable Collisions" {dpoke 0xf29a 255;poke 0xf2a7 0} } create_trainer "Andorogynus" {time 0.1} { "Backpack" {dpoke 0xec2c 2} "Big Bouncing Balls" {dpoke 0xec3a 5} "Big Shots" {dpoke 0xec3a 2} "Enemy Shot" {dpoke 0xf044 255;poke 0xf04a 255;dpoke 0xf050 255} "Normal Shot" {dpoke 0xec3a 1} "Pod With Up/down Shot" {dpoke 0xec3a 3} "Shot Strength" {dpoke 0xec3b 3} "Speed" {dpoke 0xec2b 16} "X-pos Enemy 1" {dpoke 0xef87 255} "X-pos Enemy 2" {dpoke 0xef03 255} "X-pos Enemy 3" {dpoke 0xef24 255} "X-pos Enemy 4" {dpoke 0xef45 255} "X-pos Enemy 5" {dpoke 0xef66 255} "Lives: Lives" {dpoke 0xe01b 255} "Shield: Shield Always On" {dpoke 0xeca1 255} "Weapon: Laser" {dpoke 0xec3a 4} } create_trainer "Angelo" {time 1} { "Lives: Lives" {dpoke 0xe00b 153} } create_trainer "Antarctic Adventure" {time 60} { "Blue Sky" {dpoke 0xe0e1 0} "Difficulty Level (1-9;16)" {dpoke 0xe0e0 1} "Faster To The End" {dpoke 0xe0e5 0} "Red Sky" {dpoke 0xe0e1 1} "Short Runs" {dpoke 0xe0e6 1} "Time (de-select To Get Bonus)" {dpoke 0xe0e3 17;poke 0xe0e4 1} } create_trainer "Anty" {time 1} { "Lives: Lives" {dpoke 0xe17d 246} } create_trainer "Aquapolis SOS" {time 1} { "Fuel" {dpoke 0xe112 79} "Lives: Lives" {dpoke 0xe13b 3} } create_trainer "Aquattack" {time 1} { "Damage" {dpoke 0xe3c4 0} "Fuel" {dpoke 0xe0f4 99} "Time: Freeze Time" {dpoke 0xe280 96} } create_trainer "Aramo" {time 1} { "Armor A" {dpoke 0xc057 255} "Armor B" {dpoke 0xc058 255} "Axe" {dpoke 0xc053 255} "Blaster" {dpoke 0xc060 255} "Bottle" {dpoke 0xc064 255} "Bracelet" {dpoke 0xc05f 255} "Exp" {dpoke 0xc01c 255} "Jet Boots" {dpoke 0xc05d 255} "Jump Boots" {dpoke 0xc05b 255} "Knife" {dpoke 0xc052 255} "Lamp" {dpoke 0xc05a 255} "Medicine" {dpoke 0xc066 255} "Pendant" {dpoke 0xc05e 255} "Potion" {dpoke 0xc065 255} "Ring" {dpoke 0xc059 255} "Turbo Belt" {dpoke 0xc05c 255} "Key: Key" {dpoke 0xc063 255} "Power: Power" {dpoke 0xc02b 255} "Shield: Shield A" {dpoke 0xc055 255} "Shield: Shield B" {dpoke 0xc056 255} "Weapon: Fire Gun" {dpoke 0xc062 255} "Weapon: Gun" {dpoke 0xc061 255} "Weapon: Sword" {dpoke 0xc054 255} } create_trainer "Arc" {time 1} { "Laser Doors: Open Laser Doors Hi-tech Stage" {dpoke 0xc020 255} "Location: All Areas Are Accesible" {dpoke 0xc030 255} "Power: Power" {dpoke 0xc019 255} "Timer: Time To 00:00:00" {dpoke 0xc000 0;poke 0xc001 0;dpoke 0xc002 0} "Transform: Transformation Possible" {dpoke 0xc063 255} "Weapon: All Weapons" {dpoke 0xc061 3} "Weapon: Set Weapon 1 : Beamslash" {dpoke 0xc01c 0} "Weapon: Set Weapon 2 : Bullet" {dpoke 0xc01c 1} "Weapon: Set Weapon 3 : Wavegun" {dpoke 0xc01c 2} "Weapon: Set Weapon 4 : Mega Laser" {dpoke 0xc01c 3} } create_trainer "Arkanoid" {time 0.1} { "Ball Adjustment: Magnetic Ball" {dpoke 0xe324 1} "Ball Adjustment: Normal Ball Speed" {dpoke 0xe255 12} "Ball Adjustment: Second Ball" {dpoke 0xe262 1} "Ball Adjustment: Third Ball" {dpoke 0xe276 1} "Bat Adjustment: Always Fire" {dpoke 0xe551 1} "Bat Adjustment: Long Bat" {dpoke 0xe0d7 4;poke 0xe0d8 14;dpoke 0xe0db 12;dpoke 0xe0dc 8;dpoke 0xe321 2;dpoke 0xe550 2} "Lives: Lives" {dpoke 0xe01d 99} "Player: Invulnerable" {dpoke 0xe24e 1;poke 0xe54b 1;dpoke 0xe54e 0} "Stage Select: Open Door To Next Round" {dpoke 0xe326 1} "Stage Select: Round 00" {dpoke 0xe01b 0} "Stage Select: Round 01" {dpoke 0xe01b 1} "Stage Select: Round 02" {dpoke 0xe01b 2} "Stage Select: Round 03" {dpoke 0xe01b 3} "Stage Select: Round 04" {dpoke 0xe01b 4} "Stage Select: Round 05" {dpoke 0xe01b 5} "Stage Select: Round 06" {dpoke 0xe01b 6} "Stage Select: Round 07" {dpoke 0xe01b 7} "Stage Select: Round 08" {dpoke 0xe01b 8} "Stage Select: Round 09" {dpoke 0xe01b 9} "Stage Select: Round 10" {dpoke 0xe01b 10} "Stage Select: Round 11" {dpoke 0xe01b 11} "Stage Select: Round 12" {dpoke 0xe01b 12} "Stage Select: Round 13" {dpoke 0xe01b 13} "Stage Select: Round 14" {dpoke 0xe01b 14} "Stage Select: Round 15" {dpoke 0xe01b 15} "Stage Select: Round 16" {dpoke 0xe01b 16} "Stage Select: Round 17" {dpoke 0xe01b 17} "Stage Select: Round 18" {dpoke 0xe01b 18} "Stage Select: Round 19" {dpoke 0xe01b 19} "Stage Select: Round 20" {dpoke 0xe01b 20} "Stage Select: Round 21" {dpoke 0xe01b 21} "Stage Select: Round 22" {dpoke 0xe01b 22} "Stage Select: Round 23" {dpoke 0xe01b 23} "Stage Select: Round 24" {dpoke 0xe01b 24} "Stage Select: Round 25" {dpoke 0xe01b 25} "Stage Select: Round 26" {dpoke 0xe01b 26} "Stage Select: Round 27" {dpoke 0xe01b 27} "Stage Select: Round 28" {dpoke 0xe01b 28} "Stage Select: Round 29" {dpoke 0xe01b 29} "Stage Select: Round 30" {dpoke 0xe01b 30} "Stage Select: Round 31" {dpoke 0xe01b 31} } create_trainer "Arkanoid 2" {time 0.1} { "2 Bats" {dpoke 0xc789 11} "3 Balls" {dpoke 0xc789 7} "6 Balls" {dpoke 0xc789 6} "All Bonuses Are Frozen" {dpoke 0xe2d1 0} "All Destroying Ball" {dpoke 0xe2e6 1} "Bat With Extension" {dpoke 0xc789 8} "Invulnerable (de-select Between Rounds)" {dpoke 0xe01b 1;poke 0xe442 0;dpoke 0xe44a 0} "Long Bat" {dpoke 0xc789 5} "Magnetic Ball" {dpoke 0xe2de 1} "Mini-bat (hard Game !)" {dpoke 0xc789 12} "Normal Ball Speed" {dpoke 0xe01d 8} "Open Doors To Next Round" {dpoke 0xc78e 0} "Round (right:0-34;left:128-162)" {dpoke 0xc780 0} "Lives: Infinitive Lives Player 1" {dpoke 0xc78a 40} "Lives: Infinitive Lives Player 2" {dpoke 0xdf8a 40} "Time: Time" {dpoke 0xc794 0} "Weapon: Invisible Laser" {dpoke 0xe2e2 1} "Weapon: Visible Laser" {dpoke 0xc789 3} } create_trainer "Arkos 1" {time 10} { "Lives: Lives" {dpoke 0x5bf2 10} } create_trainer "Arkos 2" {time 10} { "Lives: Lives" {dpoke 0x5bf2 10} } create_trainer "Arkos 3" {time 10} { "Lives: Lives" {dpoke 0x5bf2 10} } create_trainer "Army Moves 1" {time 1} { "Fuel" {dpoke 0xceca 255;poke 0xcecb 255} "Jeep Does Not Fall" {dpoke 0xceac 47} "Invulnerable: Invulnerable" {dpoke 0xceae 0} "Lives: Lives" {dpoke 0xceb8 58} } create_trainer "Army Moves 2" {time 1} { "No Fall In Water" {dpoke 0xceaa 0} "Invulnerable: Invulnerable" {dpoke 0xcede 0} "Lives: Lives" {dpoke 0xcec6 58} } create_trainer "Arsene Lupin 3 (missile)" {time 5} { "Invincible" {dpoke 0xccce 1;poke 0xcccf 255} "Time: Time" {dpoke 0xcf23 59} } create_trainer "Arsene Lupin the 3rd and the Castle of Cagliostro" {time 2} { "Bullet" {dpoke 0xe16f 153} "Life" {dpoke 0xe18e 40;poke 0xe18f 40} "Missile And Rings" {dpoke 0xe1ca 1;poke 0xe1cd 3;dpoke 0xe269 14} } create_trainer "Arsene Lupin the 3rd: The Golden Legend of Babylon" {time 1} { "Invincible (makes Game Unplayable)" {dpoke 0xc09b 255} "Power: Power" {dpoke 0xc07f 200} } create_trainer "Ashguine 1" {time 2} { "Access To Jewel 1" {dpoke 0xe03c 1} "Experience" {dpoke 0xe025 153;poke 0xe033 153} "Herb" {dpoke 0xe027 1} "Jewel 1" {dpoke 0xe028 1} "Jewel 2" {dpoke 0xe02a 1} "Jewel 3" {dpoke 0xe02b 1} "Jewel 4" {dpoke 0xe02c 1} "Jewel 5" {dpoke 0xe02d 1} "Knives" {dpoke 0xe026 9;poke 0xe034 153} "Magic" {dpoke 0xe024 153;poke 0xe032 153} "More Power Points" {dpoke 0xe0a2 255;poke 0xe0a3 255;dpoke 0xe0a4 255;dpoke 0xe0a5 255;dpoke 0xe0a6 255;dpoke 0xe0a7 255;dpoke 0xe0a8 255;dpoke 0xe0a9 255;dpoke 0xe0aa 255;dpoke 0xe0ab 255;dpoke 0xe0ac 255;dpoke 0xe0ad 255;dpoke 0xe0ae 255;dpoke 0xe0af 255;dpoke 0xe0b0 255;dpoke 0xe0b1 255} "Vitality" {dpoke 0xe022 153;poke 0xe030 153} "Power: Power" {dpoke 0xe023 153;poke 0xe031 153} "Weapon: Sword (this Cheat Does Not Allow To Get The Knives)" {dpoke 0xe029 1} } create_trainer "Ashguine 2" {time 2} { "Item: Keys" {dpoke 0xc020 9;poke 0xc021 9;dpoke 0xc022 8} "Item: Red Rotating Shield" {dpoke 0xc003 13} "Item: Shoes" {dpoke 0xc019 2} "Item: Yellow Rotating Shield" {dpoke 0xc003 10} "Stage Select: Set Stage 1" {dpoke 0xc000 1;poke 0xc000 2} "Stage Select: Set Stage 3" {dpoke 0xc000 3} "Stage Select: Set Stage 4" {dpoke 0xc000 4} "Stage Select: Set Stage 5" {dpoke 0xc000 5} "Stage Select: Set Stage 6" {dpoke 0xc000 6} "Status: Enemy Power" {dpoke 0xc052 1;poke 0xc062 1;dpoke 0xc072 1;dpoke 0xc082 1;dpoke 0xc092 1} "Status: Experience Level" {dpoke 0xc023 9;poke 0xc024 9;dpoke 0xc025 9} "Status: Life" {dpoke 0xc016 255} "Status: Time" {dpoke 0xc039 255} "Weapon: Sword 0" {dpoke 0xc001 0} "Weapon: Sword 1" {dpoke 0xc001 1} "Weapon: Sword 2" {dpoke 0xc001 2} } create_trainer "Ashguine 3" {time 1} { "Bag" {dpoke 0xc599 1} "Experience Level Up After Killing One Enemy" {dpoke 0xc0dd 206} "Knife" {dpoke 0xc59a 1} "Life" {dpoke 0xc0da 210} "Mirror" {dpoke 0xc59b 1} "Money" {dpoke 0xc0df 255} "Stage (0-4;4=end Demo)" {dpoke 0xc0d4 0} "Talisman" {dpoke 0xc598 1} "Power: Power" {dpoke 0xc0d9 210} } create_trainer "Astro Blaster" {time 1} { "Lives: Lives" {dpoke 0x0575 3} "Time: Infinite Time" {dpoke 0x0579 0} "Weapon: Laser Always Operational" {dpoke 0x057c 0} } create_trainer "Astro Marine Corps" {time 1} { "Mega Ammo" {dpoke 0x469f 255} "Triple Shoot" {dpoke 0x46ab 255;poke 0x46b7 255;dpoke 0x46c3 255} "Energy: Energy" {dpoke 0x99a5 10;poke 0x99b8 10;dpoke 0x9a86 10;dpoke 0x9a99 10} "Lives: Lives (de-select To Get Bonus)" {dpoke 0x9979 9} "Shield: Invisible Shield" {dpoke 0x9a84 9} "Time: Time" {dpoke 0x9986 57} "Weapon: Bombs" {dpoke 0x998b 255} } create_trainer "Astro Marine Corps 2" {time 1} { "Flame Thrower" {dpoke 0x4b18 255} "Energy: Energy" {dpoke 0x99a5 10;poke 0x99b8 10;dpoke 0x9a86 10;dpoke 0x9a99 10} "Lives: Lives (de-select To Get Bonus)" {dpoke 0x9979 9} "Time: Time" {dpoke 0x9986 57} "Weapon: Back Laser" {dpoke 0x4b00 255} "Weapon: Bombs" {dpoke 0x998b 255} "Weapon: Front Laser" {dpoke 0x4af4 255} "Weapon: Up Laser" {dpoke 0x4b0c 255} } create_trainer "Astro Monsters" {time 1} { "Front Shot And Back Shot" {dpoke 0xcf90 1} "Level (0-2)" {dpoke 0x994d 0} "Life" {dpoke 0xcf86 9} "One Front Shot" {dpoke 0xcf90 4} "Round (1-7)" {dpoke 0x9961 1} "Three Front Shots" {dpoke 0xcf90 3} "Two Front Shots" {dpoke 0xcf90 2} } create_trainer "Astron Belt" {time 1} { "Time: Timer" {dpoke 0xfd42 99} } create_trainer "Athletic Land" {time 0.1} { "Hide Enemy: No Enemies" {dpoke 0xe158 20} "Hide Enemy: No Red Balls" {dpoke 0xe159 0} "Hide Enemy: X-pos (0-255) Hero" {dpoke 0xe135 0} "Hide Enemy: X-pos Bee" {dpoke 0xe101 0} "Hide Enemy: X-pos First Fish" {dpoke 0xe0dd 0} "Hide Enemy: X-pos First Red Ball" {dpoke 0xe0e9 0} "Hide Enemy: X-pos First White Ball" {dpoke 0xe0f1 0} "Hide Enemy: X-pos Second Fish" {dpoke 0xe0e1 0} "Hide Enemy: X-pos Second Red Ball" {dpoke 0xe0ed 0} "Hide Enemy: X-pos Second White Ball" {dpoke 0xe0f5 0} "Hide Enemy: X-pos Stone" {dpoke 0xe109 0} "Hide Enemy: X-pos Third Fish" {dpoke 0xe0e5 0} "Hide Enemy: X-pos Third White Ball" {dpoke 0xe0f9 0} "Hide Enemy: Y-pos (0-108) Hero" {dpoke 0xe134 0} "Hide Enemy: Y-pos Bee" {dpoke 0xe100 0} "Hide Enemy: Y-pos First Fish" {dpoke 0xe0dc 0} "Hide Enemy: Y-pos First Red Ball" {dpoke 0xe0e8 0} "Hide Enemy: Y-pos First White Ball" {dpoke 0xe0f0 0} "Hide Enemy: Y-pos Second Fish" {dpoke 0xe0e0 0} "Hide Enemy: Y-pos Second Red Ball" {dpoke 0xe0ec 0} "Hide Enemy: Y-pos Second White Ball" {dpoke 0xe0f4 0} "Hide Enemy: Y-pos Stone" {dpoke 0xe108 0} "Hide Enemy: Y-pos Third Fish" {dpoke 0xe0e4 0} "Hide Enemy: Y-pos Third White Ball" {dpoke 0xe0f8 0} "Lives: Lives" {dpoke 0xe050 99} "Stage Select: Scene (0-99)" {dpoke 0xe054 99} "Stage Select: Stage (1-99)" {dpoke 0xe051 1} "Time: Infinite Time (de-select In Beta Version To Get Bonus) " {dpoke 0xe003 0} } create_trainer "Auf Wiedersehen Monty" {time 1} { "Fly Without A Ticket (activate Only When Playing The Game!)" {dpoke 0x9387 0} "Invincible To Monsters" {dpoke 0x8456 1} "Lives: Lives" {dpoke 0x8431 255} } create_trainer "Avenger" {time 1} { "Do Not Die" {dpoke 0xbdfa 0} "Green Sun" {dpoke 0xbdf8 255} "Red Sun" {dpoke 0xbdf9 255} "Shuriken" {dpoke 0xacd5 255} "Key: Keys" {dpoke 0xacd3 255} } create_trainer "B.C's Quest for Tires" {time 1} { "Speed (0-7)" {dpoke 0xe045 3} "Lives: Lives" {dpoke 0xe03e 9} } create_trainer "Bacillus" {time 1} { "Lives: Lives" {dpoke 0xb359 5} } create_trainer "Back To The Future" {time 0.1} { "Always Have Girl" {dpoke 0xf27d 2;poke 0xf27e 115} "Always Have Green Boy" {dpoke 0xf273 2;poke 0xf274 115} "Enemy 1 Y-x-pos" {dpoke 0xf102 200;poke 0xf103 255} "Enemy 2 Y-x-pos" {dpoke 0xf109 200;poke 0xf110 255} "Enemy 3 Y-x-pos" {dpoke 0xf11c 200;poke 0xf11d 255} "Enemy 4 Y-x-pos" {dpoke 0xf129 200;poke 0xf12a 255} "Enemy 5 Y-x-pos" {dpoke 0xf136 200;poke 0xf137 255;dpoke 0xf150 200;dpoke 0xf151 255} "Lives: Lives" {dpoke 0xf235 99} "Time: Time" {dpoke 0xf232 13} } create_trainer "Badlands" {frame} { "Lives: Lives" {dpoke 0xe050 99} } create_trainer "Balance" {time 1} { "Lives: Lives" {dpoke 0xe101 4} } create_trainer "Ball Out 1" {time 60} { "Always Access To Exit" {dpoke 0xaa4b 0} "Time: Time" {dpoke 0xaa40 99} } create_trainer "Ball Out 2" {time 60} { "Always Access To Exit" {dpoke 0x9ab3 0} "Time: Time" {dpoke 0x9aa6 99} } create_trainer "Ball Out Special" {time 60} { "X-pos (8-228)" {dpoke 0x9565 8} "Y-pos (28-168)" {dpoke 0x9563 28} "Time: Time" {dpoke 0x9560 99} } create_trainer "Ballblazer" {time 1} { "Time: Time" {dpoke 0x0110 9} } create_trainer "Ballout 1" {time 60} { "Time: Time" {dpoke 0xaa40 99} } create_trainer "Ballout 2" {time 60} { "Time: Time" {dpoke 0x9aa6 99} } create_trainer "Ballout Special" {time 60} { "Time: Time" {dpoke 0x9560 99} } create_trainer "Bank Buster" {time 0.1} { "Ball Above Bat" {dpoke 0x4b01 peek} "Lives: Lives" {dpoke 0x63fd 99} } create_trainer "Bank Panic" {time 1} { "Extra Time Player 1" {dpoke 0xf5b0 119} "Extra Time Player 2" {dpoke 0xf5e3 119} "Level (1-9;16-24;...)" {dpoke 0xf586 1} "Lives: Lives Player 1" {dpoke 0xf587 2} "Lives: Lives Player 2" {dpoke 0xf5bf 2} } create_trainer "Barunba" {time 1} { "Shot (experiment With The Value 1-4)" {dpoke 0x699a 4} "Energy: Energy" {dpoke 0x6989 6} } create_trainer "Bastard" {time 1} { "Life" {dpoke 0xb608 255} "Money" {dpoke 0xb5f6 255} } create_trainer "Batman - Rescue The Rovin" {time 1} { "Electric Bolt" {dpoke 0x19d9 153} "Jump" {dpoke 0x19da 153} "Item: Get All Items" {dpoke 0x19d8 255} "Lives: Lives" {dpoke 0x19dc 153} "Shield: Shield" {dpoke 0x19db 153} } create_trainer "Batman the Movie" {time 1} { "Life" {dpoke 0x613d 255} "Time: Time" {dpoke 0x5daa 58;poke 0x5dac 58} } create_trainer "Batten Tanuki no Daibouken - Raccoon Dog" {time 1} { "Power: Power" {dpoke 0xe2ed 128} } create_trainer "Battle Chopper" {time 1} { "Power: Power" {dpoke 0x8335 0} } create_trainer "Battle Ship Clapton 2" {time 1} { "Lives: Lives" {dpoke 0xe0ff 9} } create_trainer "Beamrider" {time 5} { "Level (1-99;0)" {dpoke 0xe222 1} "Lives: Lives" {dpoke 0xe223 12} "Weapon: Unlimited Bombs" {dpoke 0xe22c 99} } create_trainer "Beamrider CAS" {time 5} { "Level (1-99;0)" {dpoke 0xe224 1} "Lives: Lives" {dpoke 0xe225 12} "Weapon: Unlimited Bombs" {dpoke 0xe22e 99} } create_trainer "Becky" {time 1} { "Lives: 9 Lives" {dpoke 0xe1a7 9} "Time: Infinite Time" {dpoke 0xe1b3 38} } create_trainer "Bestial Warrior" {time 1} { "Energy (disable At The End Of A Sector)" {dpoke 0x7418 88} "Part 1 Of C70-magnum" {dpoke 0x73c0 1} "Part 2 Of C70-magnum" {dpoke 0x73c6 1} "Part 3 Of C70-magnum" {dpoke 0x742f 1} "Protected From Gyrones" {dpoke 0x745a 255} "Protected From Small Enemies" {dpoke 0x745b 255} "Teleport 1 Activated" {dpoke 0x73d5 1} "Teleport 2 Activated" {dpoke 0x73cb 1} "Teleport 3 Activated" {dpoke 0x73b9 1} "Lives: Lives" {dpoke 0x7417 8} "Weapon: Weapon (1-5)" {dpoke 0x7410 5} } create_trainer "Black Beard" {time 1} { "Ammo" {dpoke 0x8e29 153} "Moves (0=normal 1=drunken)" {dpoke 0x8e28 0} "No New Enemies (when You Have Fired The Cannon)" {dpoke 0x8e37 0} "Torch (de-select To Fire The Cannon)" {dpoke 0x8e58 255} "Energy: Energy" {dpoke 0x8e25 191} "Invulnerable: Invulnerable" {dpoke 0x8e3c 255} "Lives: Lives" {dpoke 0x8e26 9} "Weapon: Weapon (1=pistol 2=knife)" {dpoke 0x8e27 1} } create_trainer "Black Cyclon" {frame} { "Enemy Power: Enemy 01 Power" {dpoke 0xc049 0} "Enemy Power: Enemy 02 Power" {dpoke 0xc089 0} "Enemy Power: Enemy 03 Power" {dpoke 0xc0c9 0} "Enemy Power: Enemy 04 Power" {dpoke 0xc109 0} "Enemy Power: Enemy 05 Power" {dpoke 0xc149 0} "Enemy Power: Enemy 06 Power" {dpoke 0xc189 0} "Enemy Power: Enemy 07 Power" {dpoke 0xc1c9 0} "Enemy Power: Enemy 08 Power" {dpoke 0xc209 0} "Enemy Power: Enemy 09 Power" {dpoke 0xc249 0} "Enemy Power: Enemy 10 Power" {dpoke 0xc289 0} "Enemy Power: Enemy 11 Power" {dpoke 0xc2c9 0} "Enemy Power: Enemy 12 Power" {dpoke 0xc309 0} "Enemy Power: Enemy 13 Power" {dpoke 0xc349 0} "Enemy Power: Enemy 14 Power" {dpoke 0xc389 0} "Enemy Power: Enemy 15 Power" {dpoke 0xc3c9 0} "Enemy Power: Enemy 16 Power" {dpoke 0xc409 0} "Enemy Power: Enemy 17 Power" {dpoke 0xc449 0} "Enemy Power: Enemy 18 Power" {dpoke 0xc489 0} "Enemy Power: Enemy 19 Power" {dpoke 0xc4c9 0} "Player Lives: Lives" {dpoke 0xd15a 8} "Player Power: Power" {dpoke 0xd13e 255} "Weapon: Weapon 1 : Wave Gun" {dpoke 0xd125 0} "Weapon: Weapon 2 : Needle Gun" {dpoke 0xd125 1} "Weapon: Weapon 3 : Spread Gun" {dpoke 0xd125 2} "Weapon: Weapon 4 : Bomb Gun" {dpoke 0xd125 3} } create_trainer "Black Jack Hentai" {time 1} { "Fast Game" {dpoke 0xca70 144;poke 0xca78 0} } create_trainer "Blade Lords" {time 1} { "Have Blades" {dpoke 0x0424 255} "Invincible" {dpoke 0x0429 255} "Lives: Lives" {dpoke 0x0430 153} } create_trainer "Blagger" {time 1} { "Air (de-select To Get Bonus)" {dpoke 0x9a85 255} "No Enemy 1" {dpoke 0x9ec5 0} "No Enemy 2" {dpoke 0x9ecc 0} "No Enemy 3" {dpoke 0x9ed3 0} "No Enemy 4" {dpoke 0x9eda 0} "X-pos (12-199)" {dpoke 0x9edc 12} "Y-pos (0-118)" {dpoke 0x9edd 0} "Key: Keys" {dpoke 0x9a04 5} "Lives: Lives" {dpoke 0x9233 153} } create_trainer "Blocus" {time 1} { "Time If Direct Gaming" {dpoke 0xa1dd 255} "Time If Gaming After More Than One Other Option" {dpoke 0xa1e7 255} "Time If Gaming After Only One Other Option" {dpoke 0xa1e2 255} } create_trainer "Blow Up" {time 2} { "Ammo" {dpoke 0x6e6a 48} "Cosmic Cheat Active" {dpoke 0x403b 1} "Level (0)" {dpoke 0x4023 0} "Level (1)" {dpoke 0x4023 1} "Level (2)" {dpoke 0x4023 2} "Level (3)" {dpoke 0x4023 3} "Level (4)" {dpoke 0x4023 4} "Level (5)" {dpoke 0x4023 5} "Lives: Lives" {dpoke 0x401e 6} "Time: Time" {dpoke 0x6e7d 80} } create_trainer "Blue Warrior" {time 1} { "Shot (1=normal, 2=fire, 3=electric)" {dpoke 0xaacd 2} "Lives: Lives" {dpoke 0xaacb 8} } create_trainer "BMX Simulator" {time 1} { "Time: Time Player 1" {dpoke 0xb812 48} "Time: Time Player 2" {dpoke 0xb816 48} } create_trainer "Boggy'84" {time 5} { "Lives: Lives" {dpoke 0xe693 4} } create_trainer "Bokosuka Wars" {time 1} { "Commander Attack Stength" {dpoke 0xd77c 99} "Commander Power " {dpoke 0xd77e 0} } create_trainer "Bomb Jack" {time 1} { "Invincible" {dpoke 0x6aa9 1} "More Points" {dpoke 0x6a89 3} "Round (1-5)" {dpoke 0x6a7f 1} "Lives: Lives" {dpoke 0x46b9 52} } create_trainer "Bomb Jack (MSX1)" {time 1} { "More Points Player 1" {dpoke 0xc063 4} "More Points Player 2" {dpoke 0xc086 4} "Only One Frozen Enemy" {dpoke 0xc010 55} "Round (1-50)" {dpoke 0xc064 1} "Lives: Lives Player 1" {dpoke 0xc061 4} "Lives: Lives Player 2" {dpoke 0xc084 4} } create_trainer "Bomb Jack (MSX2)" {time 1} { "Invincible" {dpoke 0x7d4a 1} "More Points" {dpoke 0x7d2a 3} "Round (1-80)" {dpoke 0x7d20 1} "Lives: Lives" {dpoke 0x483a 52} } create_trainer "Bomb Man" {time 1} { "Time (dsk Version)" {dpoke 0xbb6f 30} "Time (rom Version)" {dpoke 0xc074 30} "Lives: Lives (dsk Version)" {dpoke 0xbb1f 9} "Lives: Lives (rom Version)" {dpoke 0xc024 9} } create_trainer "Bombaman" {time 2} { "Increase The Detonation Time" {dpoke 0x1dc5 3} "Lives: Lives" {dpoke 0x226b 9} "Time: Time" {dpoke 0x18a2 58} "Weapon: Amount Of Bombs You Can Place" {dpoke 0x1fec 2} "Weapon: Bomb Power (increase The Value At Your Own Risk :p)" {dpoke 0x1fee 5} } create_trainer "Bomber House" {time 1} { "Drills" {dpoke 0x97bb 1} "Stairs" {dpoke 0x97bb 2} "Lives: Lives" {dpoke 0x9777 9} } create_trainer "Bomber King" {time 10} { "Stuff" {dpoke 0xc0b2 1;poke 0xc0c4 1;dpoke 0xc56b 1;dpoke 0xc571 1} "Energy: Energy" {dpoke 0xc5c1 236} } create_trainer "Bomber Man Special - Deluxe Edition" {time 10} { "Walk Faster" {dpoke 0xd00b 5;poke 0xd016 2} "Invulnerable: Invulnerable" {dpoke 0xd01b 255} "Lives: Lives" {dpoke 0xd00e 99} "Time: Time" {dpoke 0xd020 199} "Weapon: Bomb Strength" {dpoke 0xd015 255} "Weapon: Detonate Bombs Pushing Z" {dpoke 0xd018 1} "Weapon: Max Bombs" {dpoke 0xd014 8} "Weapon: Walk Trough Bombs" {dpoke 0xd017 1} } create_trainer "Boom" {time 1} { "Lives: Lives" {dpoke 0x4023 5} } create_trainer "Booty" {time 1} { "First Boot Is Frozen" {dpoke 0xd0d6 0} "Fourth Boot Is Frozen" {dpoke 0xd0e2 0} "Second Boot Is Frozen" {dpoke 0xd0da 0} "Third Boot Is Frozen" {dpoke 0xd0de 0} "Lives: Lives" {dpoke 0xd007 4} "Weapon: No Countdown For Bombs" {dpoke 0xd0ee 120} } create_trainer "Bop!" {time 1} { "Balls" {dpoke 0xa138 4} } create_trainer "Borfesu and Five Evil Spirits" {time 0.25} { "Ball" {dpoke 0xe0a6 1} "Boomerang" {dpoke 0xe0a2 1} "Bottle" {dpoke 0xe0ac 1} "Bow" {dpoke 0xe0a0 1} "Celtic Cross" {dpoke 0xe0a3 1} "Containers" {dpoke 0xe01a 250} "Exp" {dpoke 0xe045 200;poke 0xe046 255} "Invincible" {dpoke 0xe37d 201} "Kettle" {dpoke 0xe0ab 1} "Life" {dpoke 0xe37c 255} "Money" {dpoke 0xe043 255;poke 0xe044 255} "Pot" {dpoke 0xe0aa 1} "Scepter" {dpoke 0xe0a5 1} "Space Suit" {dpoke 0xe0a8 1} "Staff" {dpoke 0xe09f 1} "Statue" {dpoke 0xe0a7 1} "Vase" {dpoke 0xe0ad 1} "Whirlwind" {dpoke 0xe0a1 1} "Key: Key" {dpoke 0xe0a9 1} "Weapon: Sword" {dpoke 0xe0a4 1} } create_trainer "Bosconian (Star Destroyer)" {time 1} { "'attack' Alarm Doesn't Sound (makes It Harder)" {dpoke 0xe810 0} "Life Bar" {dpoke 0xe00d 99} } create_trainer "Bouken Roman - Dota" {time 2} { "Full Ammo" {dpoke 0xe49e 10;poke 0xe49f 10} "Have Always 9 Keys" {dpoke 0xe470 9} "Have Wings" {dpoke 0xe475 3} "Energy: Full Energy" {dpoke 0xe49c 32;poke 0xe49d 32} "Shield: Full Power Shield" {dpoke 0xe504 10} } create_trainer "Boulder Dash" {frame} { "Exit Is Always Open" {dpoke 0xd9b0 1} "Invulnerable: Invulnerable" {dpoke 0xd83c 0} "Lives: Lives" {dpoke 0xd98f 255} } create_trainer "Boulder Dash 2" {frame} { "Exit Is Always Open" {dpoke 0xda60 1} "Invulnerable: Invulnerable" {dpoke 0xd8ec 0} "Time: Time" {dpoke 0xda3c 255} } create_trainer "Bounce" {time 1} { "Level (0-49) Always Preceded By First Level" {dpoke 0xddb7 0} "Lives: Lives Player 1" {dpoke 0xddb5 7} "Lives: Lives Player 2" {dpoke 0xde5a 7} } create_trainer "Bounder" {time 10} { "Lives: Lives" {dpoke 0x96f 101} } create_trainer "Bousou Tokkyuu Sos - Stop The Train" {frame} { "Always Have Snake" {dpoke 0xe32c 1} "No Bad Man" {dpoke 0xe342 128;poke 0xe34a 128} "No Ghost" {dpoke 0xe362 128} "Lives: Lives" {dpoke 0xe308 3} "Time: Time" {dpoke 0xe304 255} } create_trainer "Bozo's Big Adventure" {time 1} { "Life" {dpoke 0x5544 100} } create_trainer "Break In" {frame} { "Always Fire" {dpoke 0x6f24 90} "Frozen Guardian" {dpoke 0x7c19 0} "Invisible Guardian (hard Game!)" {dpoke 0x7c21 0} "Long Bat" {dpoke 0x6f25 16} "Open Room 2" {dpoke 0x76c3 28;poke 0x76c4 28;dpoke 0x76c5 28;dpoke 0x76c6 28} "Open Room 3" {dpoke 0x77e3 28;poke 0x77e4 28;dpoke 0x77e5 28;dpoke 0x77e6 28} "Open Room 4" {dpoke 0x7903 28;poke 0x7904 28;dpoke 0x7905 28;dpoke 0x7906 28} "Lives: Infinite Lives" {dpoke 0x60c8 5} } create_trainer "Break Out" {time 1} { "Infinite Balls" {dpoke 0xd44b 0} } create_trainer "Break-In" {frame} { "Always Fire" {dpoke 0x85d5 90} "Ball Above Bat" {dpoke 0x8487 14} "Frozen Guardian" {dpoke 0x8a7b 0} "Invisible Guardian (hard Game!)" {dpoke 0x8a83 0} "Long Bat" {dpoke 0x85d6 16} "Open Room 2" {dpoke 0x85ff 28;poke 0x8600 28;dpoke 0x8601 28;dpoke 0x8602 28} "Open Room 3" {dpoke 0x871f 28;poke 0x8720 28;dpoke 0x8721 28;dpoke 0x8722 28} "Open Room 4" {dpoke 0x883f 28;poke 0x8840 28;dpoke 0x8841 28;dpoke 0x8842 28} "Lives: Infinite Lives" {dpoke 0x7d9e 5} } create_trainer "Break-Out Adventure" {time 1} { "Lives: Lives" {dpoke 0x863d 5} "Power: Power" {dpoke 0x873e 12} } create_trainer "Breaker" {frame} { "Ball At Same Height As 2nd Bat" {dpoke 0x922c 14} "Balls" {dpoke 0x953d 99} } create_trainer "Breaker Breaker" {frame} { "Always Fire" {dpoke 0x818f 90} "Frozen Guardian" {dpoke 0x8635 0} "Invisible Guardian (hard Game!)" {dpoke 0x863d 0} "Long Bat" {dpoke 0x8190 16} "Open Room 2" {dpoke 0x81b9 28;poke 0x81ba 28;dpoke 0x81bb 28;dpoke 0x81bc 28} "Open Room 3" {dpoke 0x82d9 28;poke 0x82da 28;dpoke 0x82db 28;dpoke 0x82dc 28} "Open Room 4" {dpoke 0x83f9 28;poke 0x83fa 28;dpoke 0x83fb 28;dpoke 0x83fc 28} "Lives: Infinite Lives" {dpoke 0x7958 5} } create_trainer "Bronx" {time 1} { "Energy: Energy" {dpoke 0xfd17 255} "Lives: Lives" {dpoke 0xfd14 5} } create_trainer "Brother Adventure" {time 1} { "Lives: 3 Lives" {dpoke 0xe812 3} } create_trainer "Bruce Lee" {time 1} { "No Green Yamo" {dpoke 0xe04d 0} "No Ninja" {dpoke 0xe065 0} "Energy: Energy" {dpoke 0xe041 153} "Lives: Lives Player 1" {dpoke 0xe111 153} "Lives: Lives Player 2" {dpoke 0xe112 153} } create_trainer "Bubble Bobble" {time 1} { "Big Boss Power Left" {dpoke 0xe0e8 1} "Extend Filled Player 1" {dpoke 0xdaf4 255} "Invincible Player 1" {dpoke 0xdadd 200} "Invincible Player 2" {dpoke 0xdb6b 100} "Shoot Bubbles Player 1" {dpoke 0xdae9 0} "Shoot Fire Player 1" {dpoke 0xdae9 1} "Shoot Lightning Filled Bubbles Player 1" {dpoke 0xdae9 128} "Super Bobble Player 1" {dpoke 0xdae8 255} "Warp To Last Stage (activate Once In Game, Then Deactivate)" {dpoke 0xd069 98} } create_trainer "Buck Rogers" {time 1} { "Go Trough On Portal For Next Level" {dpoke 0xf171 1} "Lives: Lives" {dpoke 0xf16e 255} "Time: Time" {dpoke 0xf172 100} } create_trainer "Buggy Ranger" {time 1} { "Ammo" {dpoke 0xa941 126} "Fuel" {dpoke 0xa93a 126} "Energy: Energy" {dpoke 0xa939 255} "Lives: Lives" {dpoke 0xa938 9} } create_trainer "Bull and Mighty's Slim Chance aka: Inspecteur Z" {time 1} { "Blink Yellow" {dpoke 0xe31a 2} "Coins" {dpoke 0xe055 153} "Stay In State" {dpoke 0xfca2 255} "Lives: Infinitive Lives" {dpoke 0xe001 153} "Weapon: Bombs" {dpoke 0xe054 153} } create_trainer "Burgerkill" {time 1} { "Level (0-98)" {dpoke 0x41fd 0} "Pepper" {dpoke 0x41fe 99} "Lives: Lives" {dpoke 0x420b 9} } create_trainer "Bytebusters" {time 1} { "Lives: Lives" {dpoke 0xcfc9 8} } create_trainer "Cabbage Patch Kids" {time 0.1} { "Enemy: No Cabbages" {dpoke 0xe159 0} "Enemy: No Enemies" {dpoke 0xe158 20} "Enemy Position: X-pos Bird" {dpoke 0xe101 0} "Enemy Position: X-pos First Cabbage" {dpoke 0xe0e9 0} "Enemy Position: X-pos First Fish" {dpoke 0xe0dd 0} "Enemy Position: X-pos First Spider" {dpoke 0xe0f1 0} "Enemy Position: X-pos Second Cabbage" {dpoke 0xe0ed 0} "Enemy Position: X-pos Second Fish" {dpoke 0xe0e1 0} "Enemy Position: X-pos Second Spider" {dpoke 0xe0f5 0} "Enemy Position: X-pos Stone" {dpoke 0xe109 0} "Enemy Position: X-pos Third Fish" {dpoke 0xe0e5 0} "Enemy Position: X-pos Third Spider" {dpoke 0xe0f9 0} "Enemy Position: Y-pos Bird" {dpoke 0xe100 0} "Enemy Position: Y-pos First Cabbage" {dpoke 0xe0e8 0} "Enemy Position: Y-pos First Fish" {dpoke 0xe0dc 0} "Enemy Position: Y-pos First Spider" {dpoke 0xe0f0 0} "Enemy Position: Y-pos Second Cabbage" {dpoke 0xe0ec 0} "Enemy Position: Y-pos Second Fish" {dpoke 0xe0e0 0} "Enemy Position: Y-pos Second Spider" {dpoke 0xe0f4 0} "Enemy Position: Y-pos Stone" {dpoke 0xe108 0} "Enemy Position: Y-pos Third Fish" {dpoke 0xe0e4 0} "Enemy Position: Y-pos Third Spider" {dpoke 0xe0f8 0} "Lives: Lives" {dpoke 0xe050 99} "Player Pos: X-pos (0-255)" {dpoke 0xe135 0} "Player Pos: Y-pos (0-108)" {dpoke 0xe134 0} "Stage Select: Set Scene 99" {dpoke 0xe054 99} "Stage Select: Set Stage 99" {dpoke 0xe051 99} "Time: Infinite Time" {dpoke 0xe003 0} } create_trainer "Camelot Warriors" {time 1} { "X-pos (0-255)" {dpoke 0xee45 0} "Y-pos (0-255)" {dpoke 0xee46 0} "Lives: Lives" {dpoke 0xee84 9} } create_trainer "Cannon Ball" {time 1} { "Lives: Lives" {dpoke 0xe331 9} } create_trainer "Capitan Sevilla 1" {time 1} { "Capitan X-pos (0-255)" {dpoke 0x5e87 0} "Capitan Y-pos (0-255)" {dpoke 0x5e88 0} "Invulnerable (but Don't Fall In Water)" {dpoke 0x5e91 0;poke 0x5f80 0} "Sausage" {dpoke 0x5e84 6} "Lives: Lives" {dpoke 0x5e80 3} "Power: Superpower" {dpoke 0xfd85 255} } create_trainer "Capitan Sevilla 2" {time 1} { "Capitan X-pos (0-255)" {dpoke 0x5e87 0} "Capitan Y-pos (0-255)" {dpoke 0x5e88 0} "Sausage" {dpoke 0x5e84 6} "Invulnerable: Invulnerable" {dpoke 0x5e91 0} "Lives: Lives" {dpoke 0x5e80 3} "Power: Superpower" {dpoke 0xfd85 255} } create_trainer "Capitan Trueno 1" {time 1} { "Money" {dpoke 0x803a 153} "Energy: Energy" {dpoke 0x8010 99} "Lives: Lives" {dpoke 0x803f 8} "Weapon: Three Magical Swords" {dpoke 0x8034 3} } create_trainer "Capitan Trueno 2" {time 1} { "Talents" {dpoke 0x803a 153} "Energy: Energy" {dpoke 0x806e 99} "Lives: Lives" {dpoke 0x803f 8} "Weapon: One Little Blue Sword" {dpoke 0x8034 0} "Weapon: Three Big Blue Swords" {dpoke 0x8034 6} "Weapon: Three Little Blue Swords" {dpoke 0x8034 4} "Weapon: Two Green Swords" {dpoke 0x8034 3} "Weapon: Two Green Swords + One Little Blue Sword" {dpoke 0x8034 5} "Weapon: Two Little Blue Swords" {dpoke 0x8034 1} "Weapon: Two Yellow Swords" {dpoke 0x8034 2} } create_trainer "Captain Chef" {time 1} { "Chefs" {dpoke 0xcb15 10} } create_trainer "Car Fighter" {time 30} { "Fuel" {dpoke 0xe080 9;poke 0xe081 9;dpoke 0xe082 9} "Weapon: Bombs" {dpoke 0xe30c 153} } create_trainer "Casanova" {time 1} { "Lives: Lives" {dpoke 0xb489 255} } create_trainer "Castle Excellent" {time 1} { "Air" {dpoke 0xe344 255} "Light Blue Keys" {dpoke 0xe33b 5} "Map" {dpoke 0xe321 9} "X-pos (0-28)" {dpoke 0xe334 0} "Y-pos (0-18)" {dpoke 0xe335 0} "Invulnerable: Invulnerable" {dpoke 0xe343 255} "Key: Dark Blue Keys" {dpoke 0xe337 5} "Key: Green Keys" {dpoke 0xe33a 5} "Key: Purple Keys" {dpoke 0xe339 5} "Key: Red Keys" {dpoke 0xe338 5} "Key: Yellow Keys" {dpoke 0xe33c 5} "Lives: Lives" {dpoke 0xe336 255} } create_trainer "Castle of Blackburn" {time 10} { "Invincible" {dpoke 0xda0e 255} "Lives: Lives" {dpoke 0xda32 99} "Weapon: Unlimited Swords" {dpoke 0xda2c 99} } create_trainer "Castle, The" {time 1} { "Air" {dpoke 0xe344 255} "Light Blue Keys" {dpoke 0xe33b 5} "Map" {dpoke 0xe321 9} "X-pos (0-28)" {dpoke 0xe334 0} "Y-pos (0-18)" {dpoke 0xe335 0} "Invulnerable: Invulnerable" {dpoke 0xe343 255} "Key: Dark Blue Keys" {dpoke 0xe337 5} "Key: Green Keys" {dpoke 0xe33a 5} "Key: Purple Keys" {dpoke 0xe339 5} "Key: Red Keys" {dpoke 0xe338 5} "Key: Yellow Keys" {dpoke 0xe33c 5} "Lives: Lives" {dpoke 0xe336 255} } create_trainer "Caverns of Titan" {frame} { "Oxygen" {dpoke 0xd4e4 255} "Walking Speed" {dpoke 0xd6dc 1} "Stage: Stage (0-19)" {dpoke 0xd4e6 0} } create_trainer "Chack'n Pop" {time 1} { "Maze (1-8)" {dpoke 0xe06d 1} "No Monsters" {dpoke 0xe00d 255;poke 0xe012 255;dpoke 0xe017 255;dpoke 0xe01c 255;dpoke 0xe021 255;dpoke 0xe026 255;dpoke 0xe02b 255;dpoke 0xe030 255;dpoke 0xe035 255;dpoke 0xe03a 255} "Lives: Lives" {dpoke 0xe06f 3} "Time: Infinite Time" {dpoke 0xe06b 0} } create_trainer "Chase H.Q." {time 10} { "Turbo" {dpoke 0xa170 5} "Time: Time" {dpoke 0xa17e 153} } create_trainer "Cheating Wives" {time 1} { "Attempts (dsk Version)" {dpoke 0xaec2 9} "Attempts (rom Version)" {dpoke 0xc032 9} } create_trainer "Chicken Chase" {time 1} { "Lives: Lives" {dpoke 0xb115 8} } create_trainer "Chiller" {time 1} { "Power: Power" {dpoke 0x9ab8 25;poke 0x9ab9 0} } create_trainer "Chocobo Racing" {time 1} { "Speed Player 1 (0-255)" {dpoke 0x8127 55} "Speed Player 2 (0-255)" {dpoke 0x8137 55} } create_trainer "Choplifter" {time 1} { "Only Tanks" {dpoke 0xe27b 0} "Lives: Lives" {dpoke 0xe24d 0} } create_trainer "Chopper 1 v1" {time 1} { "Ammo" {dpoke 0x4c90 255} "Maximum Fuel" {dpoke 0x4c96 7} "Lives: Lives" {dpoke 0x401d 9} } create_trainer "Chopper 1 v2" {time 1} { "Ammo" {dpoke 0x4c24 255} "Maximum Fuel" {dpoke 0x4c2a 7} "Lives: Lives" {dpoke 0x401d 9} } create_trainer "Chopper 2" {time 1} { "Damage" {dpoke 0x6a9e 0;poke 0x6aa2 0} "Heat Seeking Missile" {dpoke 0x6397 9} "Tracking Missile" {dpoke 0x6398 9} "Weapon: Machine Gun" {dpoke 0x6399 232;poke 0x639a 3} } create_trainer "Chubby Gristle" {time 1} { "Lives: Lives" {dpoke 0x7532 255} } create_trainer "Chuckie Egg" {time 1} { "Big Bird Always In Cage" {dpoke 0xb281 39;poke 0xb282 8} "Big Bird Is Slow" {dpoke 0xb284 0;poke 0xb285 0} "Level (1-99;0)" {dpoke 0xb222 1} "Only One Egg Is Required" {dpoke 0xb216 1} "Lives: Lives" {dpoke 0xb21d 7} "Time: Time" {dpoke 0xb317 8;poke 0xb318 9;dpoke 0xb319 1} } create_trainer "Chuka Taisen" {time 1} { "Alternative Firepower" {dpoke 0xa681 11} "Invincible" {dpoke 0xa683 1} "Lives: Lives" {dpoke 0xa685 99} "Power: Fire Power" {dpoke 0xa67f 7} } create_trainer "Circus Charlie" {time 0.5} { "Acrobatics" {dpoke 0xe052 4} "Balance On Ball" {dpoke 0xe052 3} "Fire Hoop" {dpoke 0xe052 1} "Flying Trapeze" {dpoke 0xe052 0} "Hoop/ball" {dpoke 0xe150 0} "Tightrope" {dpoke 0xe052 2} "X-pos Monkey 1" {dpoke 0xe170 0} "X-pos Monkey 2" {dpoke 0xe1b0 0} "Lives: Lives" {dpoke 0xe050 99} "Time: Time" {dpoke 0xe003 1} } create_trainer "Coaster Race" {time 1} { "Infinite Time (de-select In Goal)" {dpoke 0xc0ce 99} } create_trainer "Cocos" {time 1} { "Dark Blue Ghost Vulnerable" {dpoke 0xa125 171} "Dark Orange Ghost Vulnerable" {dpoke 0xa129 171} "Green Ghost Vulnerable" {dpoke 0xa124 171} "Light Blue Ghost Vulnerable" {dpoke 0xa128 171} "Light Orange Ghost Vulnerable" {dpoke 0xa12a 171} "Middle Blue Ghost Vulnerable" {dpoke 0xa127 171} "Red Ghost Vulnerable" {dpoke 0xa126 171} "Speed (0-9)" {dpoke 0xa77a 0} "Yellow Ghost Vulnerable" {dpoke 0xa12b 171} "Lives: Lives" {dpoke 0xa776 3} } create_trainer "Comando Tracer" {time 1} { "All The Colors" {dpoke 0xb09d 2;poke 0xb09e 3;dpoke 0xb09f 4;dpoke 0xb0a0 5;dpoke 0xb0a1 6;dpoke 0xb0a2 7} "Energy (de-select For Final Countdown)" {dpoke 0xb3cf 11} "Invulnerable (value 0 Required For Final Countdown)" {dpoke 0xb222 255} "Time (de-select For Final Countdown)" {dpoke 0xb30f 144} "Invulnerable: Invulnerable" {dpoke 0xb225 255} "Lives: Lives (de-select For Final Countdown)" {dpoke 0xb3f2 9} } create_trainer "Combat" {time 1} { "Time: Time" {dpoke 0x8b93 153} } create_trainer "Come On! Picot" {time 2} { "Lives: Jean Lives" {dpoke 0xe008 4} "Power: Jean Power" {dpoke 0xeb70 100} "Power: Picot Power" {dpoke 0xec43 250} } create_trainer "Comic Bakery" {time 2} { "All Machines Active" {dpoke 0xe057 112} "Beams" {dpoke 0xe120 99} "Sleepy Beavers" {dpoke 0xe111 255;poke 0xe113 255;dpoke 0xe115 255} "Item: Items Done" {dpoke 0xe060 16} "Lives: Lives" {dpoke 0xe050 99} } create_trainer "Computer Billiards" {time 1} { "Balls Left" {dpoke 0xe050 3} "Endless Shots Left" {dpoke 0xe053 2} } create_trainer "Congo Bongo" {time 1} { "Infinite Time (to Use When The Game Begins)" {dpoke 0xf365 0} "Lives: Lives" {dpoke 0xf34b 3} } create_trainer "Contra" {time 3} { "End Bosses And Underground Stages Get Easier" {dpoke 0xe50b 200;poke 0xe51b 200;dpoke 0xe52b 200;dpoke 0xe53b 200;dpoke 0xe54b 200;dpoke 0xe55b 200;dpoke 0xe56b 200;dpoke 0xe57b 200;dpoke 0xe58b 200} "Invincible" {dpoke 0xe31e 200} "Power: Power Bar" {dpoke 0xe2c9 32} "Weapon: 2 Way Gun" {dpoke 0xe032 5} "Weapon: 4 Fragment Gun" {dpoke 0xe032 7} "Weapon: 4 Way Gun" {dpoke 0xe032 6} "Weapon: Circling Gun" {dpoke 0xe032 4} "Weapon: Laser Gun" {dpoke 0xe032 1} "Weapon: Machine Gun" {dpoke 0xe032 3} "Weapon: Normal Gun" {dpoke 0xe032 0} "Weapon: Rotating Gun" {dpoke 0xe032 2} } create_trainer "Corsarios 1" {time 1} { "Power: Power" {dpoke 0x96f2 99} } create_trainer "Corsarios 2" {time 1} { "Power: Power" {dpoke 0x979b 9} } create_trainer "Cosmic Battle" {time 1} { "Player 1 Blue Fire" {dpoke 0xc024 3} "Player 1 Green Fire" {dpoke 0xc024 2} "Player 1 Red Fire" {dpoke 0xc024 1} "Player 1 Yellow Fire" {dpoke 0xc024 0} "Player 2 Blue Fire" {dpoke 0xc025 3} "Player 2 Green Fire" {dpoke 0xc025 2} "Player 2 Red Fire" {dpoke 0xc025 1} "Player 2 Yellow Fire" {dpoke 0xc025 0} "Lives: Player 1 Lives" {dpoke 0xc01c 7} "Lives: Player 2 Lives" {dpoke 0xc01d 7} "Power: Player 1 Power" {dpoke 0xc026 9} "Power: Player 2 Power" {dpoke 0xc027 9} } create_trainer "Cosmic Sheriff" {time 1} { "Lives: Lives" {dpoke 0x9a81 255} "Time: Time" {dpoke 0xa3f6 255} } create_trainer "Cosmo Explorer" {time 1} { "Fuel" {dpoke 0xdaa9 255} "Photon Torpedoes" {dpoke 0xdaa7 99} "Power: Power" {dpoke 0xdac1 0} } create_trainer "Cosmos Circuit" {time 1} { "Time: Time 999" {dpoke 0x97b1 29;poke 0x97b2 29;dpoke 0x97b3 29} } create_trainer "Craze" {time 2} { "Ammo" {dpoke 0xc059 80} "Life" {dpoke 0xc054 20} "Max Shot" {dpoke 0xc064 255} "Something" {dpoke 0xc05a 6} "Shield: Max Back Shield" {dpoke 0xc069 255} "Shield: Max Front Shield" {dpoke 0xc067 255} } create_trainer "Crazy Buggy" {time 1} { "Lives: Lives" {dpoke 0xc1c5 3} "Stage: Stage (1-12)" {dpoke 0xc1c4 1} } create_trainer "Crimson" {time 1} { "Darkness Ball" {dpoke 0xc043 1} "Defence Arnold" {dpoke 0xc0a7 239} "Defence Hero" {dpoke 0xc067 240} "Defence Isabel" {dpoke 0xc087 239} "Dexterity Arnold" {dpoke 0xc0a8 255} "Dexterity Hero" {dpoke 0xc068 255} "Dexterity Isabel" {dpoke 0xc088 255} "Equipment 1 Arnold (1-37)" {dpoke 0xc0e9 1} "Equipment 1 Hero (1-37)" {dpoke 0xc0c1 1} "Equipment 1 Isabel (1-37)" {dpoke 0xc0d5 1} "Equipment 10 Arnold (1-37)" {dpoke 0xc0fb 1} "Equipment 10 Hero (1-37)" {dpoke 0xc0d3 1} "Equipment 10 Isabel (1-37)" {dpoke 0xc0e7 1} "Equipment 2 Arnold (1-37)" {dpoke 0xc0eb 1} "Equipment 2 Hero (1-37)" {dpoke 0xc0c3 1} "Equipment 2 Isabel (1-37)" {dpoke 0xc0d7 1} "Equipment 3 Arnold (1-37)" {dpoke 0xc0ed 1} "Equipment 3 Hero (1-37)" {dpoke 0xc0c5 1} "Equipment 3 Isabel (1-37)" {dpoke 0xc0d9 1} "Equipment 4 Arnold (1-37)" {dpoke 0xc0ef 1} "Equipment 4 Hero (1-37)" {dpoke 0xc0c7 1} "Equipment 4 Isabel (1-37)" {dpoke 0xc0db 1} "Equipment 5 Arnold (1-37)" {dpoke 0xc0f1 1} "Equipment 5 Hero (1-37)" {dpoke 0xc0c9 1} "Equipment 5 Isabel (1-37)" {dpoke 0xc0dd 1} "Equipment 6 Arnold (1-37)" {dpoke 0xc0f3 1} "Equipment 6 Hero (1-37)" {dpoke 0xc0cb 1} "Equipment 6 Isabel (1-37)" {dpoke 0xc0df 1} "Equipment 7 Arnold (1-37)" {dpoke 0xc0f5 1} "Equipment 7 Hero (1-37)" {dpoke 0xc0cd 1} "Equipment 7 Isabel (1-37)" {dpoke 0xc0e1 1} "Equipment 8 Arnold (1-37)" {dpoke 0xc0f7 1} "Equipment 8 Hero (1-37)" {dpoke 0xc0cf 1} "Equipment 8 Isabel (1-37)" {dpoke 0xc0e3 1} "Equipment 9 Arnold (1-37)" {dpoke 0xc0f9 1} "Equipment 9 Hero (1-37)" {dpoke 0xc0d1 1} "Equipment 9 Isabel (1-37)" {dpoke 0xc0e5 1} "Experience Arnold" {dpoke 0xc0ab 152;poke 0xc0ac 1} "Experience Hero" {dpoke 0xc06b 152;poke 0xc06c 1} "Experience Isabel" {dpoke 0xc08b 152;poke 0xc08c 1} "Flame Ball" {dpoke 0xc044 1} "Gold Medal" {dpoke 0xc04a 1} "Heat Ball" {dpoke 0xc042 1} "Ice Ball" {dpoke 0xc046 1} "Light Ball" {dpoke 0xc045 1} "Magic Arnold" {dpoke 0xc0a5 255} "Magic Hero" {dpoke 0xc065 255} "Magic Isabel" {dpoke 0xc085 255} "Maximum Magic Arnold" {dpoke 0xc0aa 255} "Maximum Magic Hero" {dpoke 0xc06a 255} "Maximum Magic Isabel" {dpoke 0xc08a 255} "Maximum Vitality Arnold" {dpoke 0xc0a9 255} "Maximum Vitality Hero" {dpoke 0xc069 255} "Maximum Vitality Isabel" {dpoke 0xc089 255} "Mini-boat" {dpoke 0xc04c 1} "Mirror Of Truth" {dpoke 0xc04b 1} "Money" {dpoke 0xc0fd 255;poke 0xc0fe 255} "Monsters Die Faster" {dpoke 0xc554 1} "Strength Arnold" {dpoke 0xc0a6 240} "Strength Hero" {dpoke 0xc066 240} "Strength Isabel" {dpoke 0xc086 240} "Three Stones (de-select To Reveal Stairs)" {dpoke 0xc047 1;poke 0xc048 1;dpoke 0xc049 1} "Vitality Arnold" {dpoke 0xc0a4 255} "Vitality Hero" {dpoke 0xc064 255} "Vitality Isabel" {dpoke 0xc084 255} "Key: Black Key" {dpoke 0xc03e 1} "Key: Blue Key" {dpoke 0xc03b 1} "Key: Green Key" {dpoke 0xc03c 1} "Key: Red Key" {dpoke 0xc039 1} "Key: White Key" {dpoke 0xc03a 1} "Key: Yellow Key" {dpoke 0xc03d 1} "Power: Badora Power" {dpoke 0xc03f 1} "Power: Gorai Power" {dpoke 0xc041 1} "Power: Rapnos Power" {dpoke 0xc040 1} } create_trainer "Crimson DSK" {time 1} { "Darkness Ball" {dpoke 0xaa03 1} "Defence Arnold" {dpoke 0xaa67 239} "Defence Hero" {dpoke 0xaa27 239} "Defence Isabel" {dpoke 0xaa47 239} "Dexterity Arnold" {dpoke 0xaa68 255} "Dexterity Hero" {dpoke 0xaa28 255} "Dexterity Isabel" {dpoke 0xaa48 255} "Equipment 1 Arnold (1-37)" {dpoke 0xaaa9 1} "Equipment 1 Hero (1-37)" {dpoke 0xaa81 1} "Equipment 1 Isabel (1-37)" {dpoke 0xaa95 1} "Equipment 10 Arnold (1-37)" {dpoke 0xaabb 1} "Equipment 10 Hero (1-37)" {dpoke 0xaa93 1} "Equipment 10 Isabel (1-37)" {dpoke 0xaaa7 1} "Equipment 2 Arnold (1-37)" {dpoke 0xaaab 1} "Equipment 2 Hero (1-37)" {dpoke 0xaa83 1} "Equipment 2 Isabel (1-37)" {dpoke 0xaa97 1} "Equipment 3 Arnold (1-37)" {dpoke 0xaaad 1} "Equipment 3 Hero (1-37)" {dpoke 0xaa85 1} "Equipment 3 Isabel (1-37)" {dpoke 0xaa99 1} "Equipment 4 Arnold (1-37)" {dpoke 0xaaaf 1} "Equipment 4 Hero (1-37)" {dpoke 0xaa87 1} "Equipment 4 Isabel (1-37)" {dpoke 0xaa9b 1} "Equipment 5 Arnold (1-37)" {dpoke 0xaab1 1} "Equipment 5 Hero (1-37)" {dpoke 0xaa89 1} "Equipment 5 Isabel (1-37)" {dpoke 0xaa9d 1} "Equipment 6 Arnold (1-37)" {dpoke 0xaab3 1} "Equipment 6 Hero (1-37)" {dpoke 0xaa8b 1} "Equipment 6 Isabel (1-37)" {dpoke 0xaa9f 1} "Equipment 7 Arnold (1-37)" {dpoke 0xaab5 1} "Equipment 7 Hero (1-37)" {dpoke 0xaa8d 1} "Equipment 7 Isabel (1-37)" {dpoke 0xaaa1 1} "Equipment 8 Arnold (1-37)" {dpoke 0xaab7 1} "Equipment 8 Hero (1-37)" {dpoke 0xaa8f 1} "Equipment 8 Isabel (1-37)" {dpoke 0xaaa3 1} "Equipment 9 Arnold (1-37)" {dpoke 0xaab9 1} "Equipment 9 Hero (1-37)" {dpoke 0xaa91 1} "Equipment 9 Isabel (1-37)" {dpoke 0xaaa5 1} "Experience Arnold" {dpoke 0xaa6b 152;poke 0xaa6c 1} "Experience Hero" {dpoke 0xaa2b 152;poke 0xaa2c 1} "Experience Isabel" {dpoke 0xaa4b 152;poke 0xaa4c 1} "Flame Ball" {dpoke 0xaa04 1} "Gold Medal" {dpoke 0xaa0a 1} "Heat Ball" {dpoke 0xaa02 1} "Ice Ball" {dpoke 0xaa06 1} "Light Ball" {dpoke 0xaa05 1} "Magic Arnold" {dpoke 0xaa65 255} "Magic Hero" {dpoke 0xaa25 255} "Magic Isabel" {dpoke 0xaa45 255} "Maximum Magic Arnold" {dpoke 0xaa6a 255} "Maximum Magic Hero" {dpoke 0xaa2a 255} "Maximum Magic Isabel" {dpoke 0xaa4a 255} "Maximum Vitality Arnold" {dpoke 0xaa69 255} "Maximum Vitality Hero" {dpoke 0xaa29 255} "Maximum Vitality Isabel" {dpoke 0xaa49 255} "Mini-boat" {dpoke 0xaa0c 1} "Mirror Of Truth" {dpoke 0xaa0b 1} "Money" {dpoke 0xaabd 255;poke 0xaabe 255} "Monsters Die Faster" {dpoke 0xaf14 1} "Strength Arnold" {dpoke 0xaa66 240} "Strength Hero" {dpoke 0xaa26 240} "Strength Isabel" {dpoke 0xaa46 240} "Three Stones (de-select To Reveal Stairs)" {dpoke 0xaa07 1;poke 0xaa08 1;dpoke 0xaa09 1} "Vitality Arnold" {dpoke 0xaa64 255} "Vitality Hero" {dpoke 0xaa24 255} "Vitality Isabel" {dpoke 0xaa44 255} "Key: Black Key" {dpoke 0xa9fe 1} "Key: Blue Key" {dpoke 0xa9fb 1} "Key: Green Key" {dpoke 0xa9fc 1} "Key: Red Key" {dpoke 0xa9f9 1} "Key: White Key" {dpoke 0xa9fa 1} "Key: Yellow Key" {dpoke 0xa9fd 1} "Power: Badora Power" {dpoke 0xa9ff 1} "Power: Gorai Power" {dpoke 0xaa01 1} "Power: Rapnos Power" {dpoke 0xaa00 1} } create_trainer "Cross Blaim" {time 0.25} { "Ammo: Bullet For Bazooka (1)" {dpoke 0xeeb8 255} "Ammo: Bullet For Gun (1)" {dpoke 0xeeb6 255} "Ammo: Hand Grenades" {dpoke 0xeea6 255} "Item: Engine 1" {dpoke 0xeeab 255} "Item: Engine 2" {dpoke 0xeeac 255} "Item: Engine 3" {dpoke 0xeead 255} "Item: Power Container 1" {dpoke 0xeeae 250} "Item: Power Container 2" {dpoke 0xeeaf 250} "Keys: All Keys" {dpoke 0xeeb0 1;poke 0xeeb1 1;dpoke 0xeeb2 1;dpoke 0xeeb3 1;dpoke 0xeeb4 1} "Money: Money 999999" {dpoke 0xeebb 153;poke 0xeebc 153;dpoke 0xeebd 153} "Power: Power" {dpoke 0xead4 255} "Weapon: Big Laser Gun" {dpoke 0xeea2 255} "Weapon: Big Laser Gun 2" {dpoke 0xeea3 255} "Weapon: Darts" {dpoke 0xeea7 255} } create_trainer "Crusader" {time 1} { "Power: Power" {dpoke 0xe491 255} "Weapon: Get Sword" {dpoke 0xe471 1} } create_trainer "Cure" {time 1} { "Hearts" {dpoke 0xdd44 153} "Invincible" {dpoke 0xdadf 4} "Kill Enemy With One Hit (end Boss 3 Caution!)" {dpoke 0xdb8d 1} "Next Stage (-1)" {dpoke 0xdd46 1} "Key: Get White Key" {dpoke 0xdae1 1} "Key: Get Yellow Key" {dpoke 0xdae0 1} "Power: Power" {dpoke 0xdae6 64} "Time: Sand Of Time" {dpoke 0xdae4 1} "Weapon: Optional Weapon - Daggers" {dpoke 0xdae5 2} "Weapon: Optional Weapon - Holy Water" {dpoke 0xdae5 1} } create_trainer "Cyberbig" {time 1} { "Lives: Lives" {dpoke 0x783b 99} } create_trainer "d-day" {time 1} { } create_trainer "Daidasso" {time 1} { "Lives: Lives" {dpoke 0xd448 255} "Weapon: Always Blue Bombs" {dpoke 0xd472 19} "Weapon: Always Green Bombs" {dpoke 0xd473 2} } create_trainer "Daidasso - Great Escape" {time 1} { "Lives: Lives" {dpoke 0x814b 255} "Weapon: Always Blue Bombs" {dpoke 0x8175 19} "Weapon: Always Green Bombs" {dpoke 0x8176 2} } create_trainer "Daiva Story 4 - Asura's Bloodfeud" {time 10} { "All Kind Of Ships In Stock" {dpoke 0xc4ee 99;poke 0xc4ef 99;dpoke 0xc4f0 99;dpoke 0xc4f1 99} "Life On Planet" {dpoke 0xd408 255} } create_trainer "Daiva Story 5 - The Cup of Soma" {time 1} { "Battle Ships In Stock" {dpoke 0xd0b8 99} "Cruisers In Stock" {dpoke 0xd0c2 99} "Damage On Planets" {dpoke 0x950a 0;poke 0x950b 0} "Missile Ships In Stock" {dpoke 0xd0d6 97} "Money" {dpoke 0xd7ab 255;poke 0xd7ac 255} "O.m In Stock" {dpoke 0xd0cc 98} "Stop Timer On Planets" {dpoke 0x94ee 0} } create_trainer "DangerX4" {time 1} { "Mice Left" {dpoke 0xd026 1} "Scene (1-99;0)" {dpoke 0xd028 1} "Lives: Lives" {dpoke 0xd018 9} } create_trainer "Darwin 4078" {time 1} { "Weapon: Max Weapons" {dpoke 0xe8e6 144;poke 0xe8f0 135;dpoke 0xe9bb 144} } create_trainer "DASS" {time 1} { "Invincible" {dpoke 0x9abb 255} "Lives: Lives" {dpoke 0xae51 4} "Power: Special Power" {dpoke 0xaea1 162} } create_trainer "David 2" {time 5} { "Lives: Lives" {dpoke 0xe194 255} } create_trainer "Death Wish 3" {time 1} { "Bazooka Ammo" {dpoke 0xa47b 99} "Invincible" {dpoke 0x5b91 1} "Pistol Ammo" {dpoke 0xa479 99} "Shotgun Ammo" {dpoke 0xa478 99} "Weapon: Sub Machine Gun" {dpoke 0xa47a 99} } create_trainer "Decathlon" {frame} { "Top Speed" {dpoke 0xe190 255} } create_trainer "Deep Dungeon 1" {time 1} { "Life" {dpoke 0xc157 255;poke 0xc158 255} "Max Exp" {dpoke 0xc159 255;poke 0xc15a 255} "Max Gold" {dpoke 0xc160 255;poke 0xc161 255} } create_trainer "Deep Dungeon 2" {time 1} { "Life" {dpoke 0xc17e 255;poke 0xc17f 255} "Max Ac" {dpoke 0xc182 255} "Max Ag" {dpoke 0xc184 255} "Max Ap" {dpoke 0xc185 255} "Max Gold" {dpoke 0xc18a 255;poke 0xc18b 255} "Max Level" {dpoke 0xc17d 99} "Max Luck" {dpoke 0xc189 255} } create_trainer "Deep Forest" {time 2} { "Jump Higher" {dpoke 0xea08 255;poke 0xea0d 255;dpoke 0xea0f 255} "Money" {dpoke 0xeb1b 99;poke 0xeb1c 99} "Untouchable" {dpoke 0xea0e 255} "Power: Power" {dpoke 0xeb1a 255} } create_trainer "Demon Crystal, The" {time 1} { "Key: Keys" {dpoke 0xf005 9;poke 0xf006 9} "Lives: Lives" {dpoke 0xf009 9;poke 0xf00a 9} "Time: Time" {dpoke 0xf00b 9;poke 0xf00c 9;dpoke 0xf00d 9} "Weapon: Bombs" {dpoke 0xf007 9;poke 0xf008 9} } create_trainer "Demon Of The Dark Castle" {time 1} { "Key: Keys" {dpoke 0xce5c 6} "Power: Power" {dpoke 0xce5d 255} } create_trainer "Demonia I" {time 1} { "Weapon: Max Weapons" {dpoke 0x806c 255} } create_trainer "Desolator" {time 1} { "Energy: Energy" {dpoke 0x8d06 255} "Lives: Lives" {dpoke 0x8cec 101} "Shield: Shield" {dpoke 0x8d05 255} } create_trainer "Devil Zone" {time 1} { "99 Start" {dpoke 0xd652 153} "Clock" {dpoke 0xd656 1} "End Boss Power 1" {dpoke 0xd654 1} "Green Ring" {dpoke 0xd65a 1} "Hourglass" {dpoke 0xd655 1} "Invincible" {dpoke 0xd63e 255} "Life Bottle" {dpoke 0xd658 1} "Torch" {dpoke 0xd659 1} "Energy: Energy" {dpoke 0xd653 255} "Lives: Lives" {dpoke 0xd651 153} "Time: Full Time" {dpoke 0xd64c 153;poke 0xd64d 153} "Weapon: Bomb" {dpoke 0xd657 1} } create_trainer "Devil's Heaven" {time 1} { "Attack" {dpoke 0xe03c 153;poke 0xe03d 153} "Defend" {dpoke 0xe03a 153;poke 0xe03b 153} "Life" {dpoke 0xe038 153;poke 0xe039 153} "Money" {dpoke 0xe03e 153;poke 0xe03f 153} } create_trainer "Dig Dug" {time 1} { "Lives: Lives" {dpoke 0xe700 2} } create_trainer "Digital Devil Story - Monogatari Megami Tensei" {time 1} { "Life" {dpoke 0xd271 255} } create_trainer "Disc Warrior" {time 1} { "Power: Power" {dpoke 0x8513 255} } create_trainer "Discovery" {time 1} { "Fuel" {dpoke 0x723e 80} "Shield: Shield" {dpoke 0x723f 120} } create_trainer "Dixdaef" {time 2} { "Card 1" {dpoke 0xb8bf 1} "Card 2" {dpoke 0xb8c0 1} "Card 3" {dpoke 0xb8c1 1} "Card 4" {dpoke 0xb8c2 1} "Card 5" {dpoke 0xb8c3 1} "Card 6" {dpoke 0xb8c4 1} "Card 7" {dpoke 0xb8c5 1} "Card 8" {dpoke 0xb8c6 1} "Defense 1" {dpoke 0xb8b7 1} "Defense 2" {dpoke 0xb8b8 1} "Defense 3" {dpoke 0xb8b9 1} "Defense 4" {dpoke 0xb8ba 1} "Life" {dpoke 0xb9ca 64} "Life Container 1" {dpoke 0xb8bb 255} "Life Container 2" {dpoke 0xb8bc 255} "Weapon: Weapon 2" {dpoke 0xb8b0 1} "Weapon: Weapon 3" {dpoke 0xb8b1 1} "Weapon: Weapon 4" {dpoke 0xb8b2 1} "Weapon: Weapon 5" {dpoke 0xb8b3 1} "Weapon: Weapon 57" {dpoke 0xb8b5 1} "Weapon: Weapon 58" {dpoke 0xb8b6 1} "Weapon: Weapon 6" {dpoke 0xb8b4 1} } create_trainer "Dizzy" {time 1} { "Back (de-select To Get Bonus)" {dpoke 0x9bcd 99} "Level (0-13)" {dpoke 0xd8c8 0} "Moves (de-select To Get Bonus)" {dpoke 0x9bce 99} "Next (de-select To Get Bonus)" {dpoke 0x9bcf 99} "Time (de-select To Get Bonus)" {dpoke 0x9bc9 59} } create_trainer "Dizzy Promo 1" {time 1} { "Back" {dpoke 0xc7a2 99} "Moves" {dpoke 0xc7a3 99} "Next" {dpoke 0xc7a4 99} "Next Puzzle (0-4)" {dpoke 0xc7a5 0} "Time: Time" {dpoke 0xc79f 59} } create_trainer "Dizzy Promo 2" {time 1} { "Back (de-select To Get Bonus)" {dpoke 0x9bbd 99} "Moves (de-select To Get Bonus)" {dpoke 0x9bbe 99} "Next (de-select To Get Bonus)" {dpoke 0x9bbf 99} "Time (de-select To Get Bonus)" {dpoke 0x9bb9 59} } create_trainer "Docteur Galaxie" {time 1} { "Energy: Energy" {dpoke 0xcad6 255} "Lives: Lives" {dpoke 0xcc1c 5} } create_trainer "Dog Fighter" {time 1} { "Fuel" {dpoke 0xe33e 255;poke 0xe33f 255} "Shot" {dpoke 0xe340 255} "Lives: Lives" {dpoke 0xe304 4} } create_trainer "Doki Doki Penguin Land" {time 1} { "Eggs" {dpoke 0xe111 100} } create_trainer "Donkey Kong" {time 1} { "Lives: Lives" {dpoke 0x94ed 9} } create_trainer "Dorodon" {time 1} { "Kill All Enemies" {dpoke 0xc9fb 1;poke 0xc9fc 255} "Lives: Lives" {dpoke 0xc6f3 5} } create_trainer "Double Dragon" {time 1} { "Always Have Full Power" {dpoke 0xcb72 5} "Invincible" {dpoke 0xcb6a 255} "Low Enemy Power" {dpoke 0xcb05 1;poke 0xcb25 1;dpoke 0xcb45 1} "Lives: Lives" {dpoke 0xcb75 3} } create_trainer "Double Dragon 2 - The Revenge" {time 1} { "Power: Power Player 1" {dpoke 0x4079 15} "Time: Time" {dpoke 0x2918 153} } create_trainer "Dr. Archie" {time 1} { "Exp" {dpoke 0xd3c9 60} "Gold" {dpoke 0x404 153;poke 0x405 153;dpoke 0x406 153} "Life" {dpoke 0xd3c8 64} } create_trainer "Dragon Buster" {time 5} { "Exp" {dpoke 0xc2e2 153} "Life And Attacks" {dpoke 0xc312 153;poke 0xc313 5} } create_trainer "Dragon Knight" {time 1} { "Barbat" {dpoke 0xc3ea 3} "Breast Plate Armor" {dpoke 0xc3ec 3} "Cap" {dpoke 0xc3ea 1} "Chain Mail Armor" {dpoke 0xc3ec 2} "Defence" {dpoke 0xc3e4 231;poke 0xc3e5 3} "Enemies Die Faster" {dpoke 0xc42a 0;poke 0xc432 0;dpoke 0xc43a 0;dpoke 0xc442 0;dpoke 0xc44a 0;dpoke 0xc452 0} "Experience" {dpoke 0xc3dc 255;poke 0xc3dd 255} "Full Helm" {dpoke 0xc3ea 4} "Gold" {dpoke 0xc3d8 255;poke 0xc3d9 255} "Gold Knife" {dpoke 0xc3e8 1} "Leather Armor" {dpoke 0xc3ec 1} "Level" {dpoke 0xc3e0 99} "Magic" {dpoke 0xc3f2 231;poke 0xc3f3 3} "Maximum Magic" {dpoke 0xc3de 231;poke 0xc3df 3} "Maximum Vitality" {dpoke 0xc3da 231;poke 0xc3db 3} "Preit Armor" {dpoke 0xc3ec 4} "Salet" {dpoke 0xc3ea 2} "Strength" {dpoke 0xc3e2 231;poke 0xc3e3 3} "Vitality" {dpoke 0xc3f0 231;poke 0xc3f1 3} "Shield: Backler Shield" {dpoke 0xc3ee 1} "Shield: Paveese Shield" {dpoke 0xc3ee 4} "Shield: Squioom Shield" {dpoke 0xc3ee 3} "Shield: Terju Shield" {dpoke 0xc3ee 2} "Weapon: Broad Sword" {dpoke 0xc3e8 4} "Weapon: Long Sword" {dpoke 0xc3e8 3} "Weapon: Short Sword" {dpoke 0xc3e8 2} } create_trainer "Dragon Quest 2 - MSX2 Version" {time 1} { "Gold" {dpoke 0xe624 255;poke 0xe625 255} "Hp" {dpoke 0xe63b 255;poke 0xe63c 255} "Level" {dpoke 0xe63e 22} "Magic" {dpoke 0xe63d 255} "Max Exp" {dpoke 0xe633 255;poke 0xe634 255;dpoke 0xe635 255} "Max Hp" {dpoke 0xe630 255;poke 0xe631 255} "Max Stats" {dpoke 0xe636 255;poke 0xe637 255;dpoke 0xe638 255;dpoke 0xe639 255} } create_trainer "Dragon Slayer 4 - MSX1 Version" {time 1} { "Armor" {dpoke 0xe09c 99} "Crown" {dpoke 0xe0a9 1} "Helmet" {dpoke 0xe0a3 99} "Jump High" {dpoke 0xe097 40} "Life" {dpoke 0xe093 100} "Life Potion" {dpoke 0xe0a6 99} "Magic" {dpoke 0xe094 100} "Magic Potion" {dpoke 0xe0a7 99} "Money" {dpoke 0xe095 100} "Pick Axe" {dpoke 0xe09d 99} "Red Globe" {dpoke 0xe0a8 99} "Scepter" {dpoke 0xe0a4 99} "Spear Hook" {dpoke 0xe09f 99} "Spike Shoes" {dpoke 0xe0a0 99} "Spring Shoes" {dpoke 0xe0a1 99} "The Glove" {dpoke 0xe09e 99} "Wings" {dpoke 0xe09b 99} "Key: Keys" {dpoke 0xe096 100} "Key: Master Key" {dpoke 0xe0a2 99} "Shield: Dragon Shield" {dpoke 0xe0a5 99} "Weapon: Strong Weapons" {dpoke 0xe098 99} "Weapon: Sword" {dpoke 0xe0aa 99} } create_trainer "Dragon Slayer 4 - MSX2 Version" {time 1} { "Armor" {dpoke 0xc070 99} "Crown" {dpoke 0xc07d 1} "Helmet" {dpoke 0xc077 99} "In Game Player (try Different Values)" {dpoke 0xc050 0} "Invincible" {dpoke 0xc08b 255} "Jump High" {dpoke 0xc06b 40} "Kill Enemies With Body" {dpoke 0xc08d 2} "Life" {dpoke 0xc067 109} "Life Potion" {dpoke 0xc07a 99} "Magic" {dpoke 0xc068 109} "Magic Potion" {dpoke 0xc07b 99} "Money" {dpoke 0xc069 109} "Pick Axe" {dpoke 0xc071 99} "Put In Slot 1 : Pick Axe" {dpoke 0xc060 2} "Put In Slot 2 : Harpoon" {dpoke 0xc061 4} "Put In Slot 3 : Crown" {dpoke 0xc062 14} "Red Globe" {dpoke 0xc07c 99} "Scepter" {dpoke 0xc078 99} "Shoot Far" {dpoke 0xc06e 40} "Spear Hook" {dpoke 0xc073 99} "Spike Shoes" {dpoke 0xc074 99} "Spring Shoes" {dpoke 0xc075 99} "The Glove" {dpoke 0xc072 99} "Walk Faster" {dpoke 0xc08e 255} "Walk Trough Air" {dpoke 0xc08c 2} "Wings" {dpoke 0xc06f 99} "Key: Keys" {dpoke 0xc06a 109} "Key: Master Key" {dpoke 0xc076 99} "Shield: Dragon Shield" {dpoke 0xc079 99} "Weapon: Strong Weapons" {dpoke 0xc06c 255} "Weapon: Sword" {dpoke 0xc07e 99} } create_trainer "Dragon Slayer 6" {time 60} { "Money: Max Gold" {dpoke 0x208c 255;poke 0x208d 255;dpoke 0x208e 255} "Player Stats Gale: Experience Gale (max Exp)" {dpoke 0x23cc 255;poke 0x23cd 255;dpoke 0x23ce 255} "Player Stats Gale: Life Gale" {dpoke 0x23c4 15;poke 0x23c5 39} "Player Stats Gale: Magic Gale" {dpoke 0x23c8 15;poke 0x23c9 39} "Player Stats Gale: Max Life Gale" {dpoke 0x23c6 15;poke 0x23c7 39} "Player Stats Gale: Max Magic Gale" {dpoke 0x23ca 15;poke 0x23cb 39} "Player Stats Ro: Experience Ro (max Exp)" {dpoke 0x238c 255;poke 0x238d 255;dpoke 0x238e 255} "Player Stats Ro: Life Ro" {dpoke 0x2384 15;poke 0x2385 39} "Player Stats Ro: Magic Ro" {dpoke 0x2388 15;poke 0x2389 39} "Player Stats Ro: Max Life Ro" {dpoke 0x2386 15;poke 0x2387 39} "Player Stats Ro: Max Magic Ro" {dpoke 0x238a 15;poke 0x238b 39} "Player Stats Runan: Experience Runan (max Exp)" {dpoke 0x234c 255;poke 0x234d 255;dpoke 0x234e 255} "Player Stats Runan: Life Runan" {dpoke 0x2344 15;poke 0x2345 39} "Player Stats Runan: Magic Runan" {dpoke 0x2348 15;poke 0x2349 39} "Player Stats Runan: Max Life Runan" {dpoke 0x2346 15;poke 0x2347 39} "Player Stats Runan: Max Magic Runan" {dpoke 0x234a 15;poke 0x234b 39} "Player Stats Selios: Experience Selios (max Exp)" {dpoke 0x230c 255;poke 0x230d 255;dpoke 0x230e 255} "Player Stats Selios: Life Selios" {dpoke 0x2304 15;poke 0x2305 39} "Player Stats Selios: Magic Selios" {dpoke 0x2308 15;poke 0x2309 39} "Player Stats Selios: Max Life Selios" {dpoke 0x2306 15;poke 0x2307 39} "Player Stats Selios: Max Magic Selios" {dpoke 0x230a 15;poke 0x230b 39} } create_trainer "Drainer" {time 1} { "Discs" {dpoke 0xc4c7 3} "Lives: Lives" {dpoke 0xec97 99} } create_trainer "Drome" {time 1} { "Lives: Lives" {dpoke 0x4032 5} "Time: Time" {dpoke 0x403a 0} } create_trainer "Druid" {time 2} { "Ammo 1" {dpoke 0xc024 99} "Ammo 2" {dpoke 0xc025 99} "Ammo 3" {dpoke 0xc026 99} "Death" {dpoke 0xc02a 99} "Golem" {dpoke 0xc029 99} "Key: Key" {dpoke 0xc027 99} "Time: Timer" {dpoke 0xc028 99} } create_trainer "Duck Hunt" {time 1} { "Always Winner" {dpoke 0xc298 10} "Endless Game" {dpoke 0xc29a 0} "Infinite Shots" {dpoke 0xc296 0} "Round (1-99;0)" {dpoke 0xc284 1} } create_trainer "Dustin" {time 1} { "Alert" {dpoke 0xcedb 99} "No Red Alarm" {dpoke 0x5b6a 0} "Item: Item 1 (1-15)" {dpoke 0x5b61 1} "Item: Item 2 (1-15)" {dpoke 0x5b62 2} "Item: Item 3 (1-15)" {dpoke 0x5b63 5} "Item: Item 4 (1-15)" {dpoke 0x5b64 7} "Item: Item 5 (1-15)" {dpoke 0x5b65 9} "Item: Item 6 (1-15)" {dpoke 0x5b66 11} "Item: Item 7 (1-15)" {dpoke 0x5b67 14} "Item: Item 8 (1-15)" {dpoke 0x5b68 15} } create_trainer "Dynamite Dan" {time 5} { "Lives: Lives" {dpoke 0x01f2 9} } create_trainer "Eat Blue" {time 1} { "Energy Game A Or Player 1" {dpoke 0xbaa0 153} "Energy Player 2" {dpoke 0xbaab 153} "Energy: Energy Game B" {dpoke 0xbaa6 153} } create_trainer "Eat Blue 2004" {time 1} { "Blue Key (not On First Played Level)" {dpoke 0xc05b 1} "Eggs (not On First Played Level)" {dpoke 0xc05f 1} "Food (not On First Played Level)" {dpoke 0xc053 99} "Level (1-30 To Select Only At End Of First Played Level)" {dpoke 0xc04f 1} "Red Key (not On First Played Level)" {dpoke 0xc059 1} "Yellow Key (not On First Played Level)" {dpoke 0xc05d 1} "Lives: Lives (not On First Played Level)" {dpoke 0xc051 99} "Time: Time" {dpoke 0xfc9e 0} } create_trainer "Eat Blue 2004 User Levels" {time 1} { "Blue Key (not On First Played Level)" {dpoke 0xc061 1} "Eggs (not On First Played Level)" {dpoke 0xc065 1} "Food (not On First Played Level)" {dpoke 0xc059 99} "Level (1-30 To Select Only At End Of First Played Level)" {dpoke 0xc055 1} "Red Key (not On First Played Level)" {dpoke 0xc05f 1} "Yellow Key (not On First Played Level)" {dpoke 0xc063 1} "Lives: Lives (not On First Played Level)" {dpoke 0xc057 99} "Time: Time" {dpoke 0xfc9e 0} } create_trainer "Eggerland Mystery" {time 1} { "Blocks Collected" {dpoke 0xd1f4 1} "Bullets" {dpoke 0xc811 153} "Door Is Always Open" {dpoke 0xd1f2 0} "Time In Special Stages" {dpoke 0xc81d 255} "Lives: Lives" {dpoke 0xd0d0 153} "Stage: Stage Number 1" {dpoke 0xc81b 1} "Stage: Stage Number 2" {dpoke 0xc81b 2} "Stage: Stage Number 3" {dpoke 0xc81b 3} "Stage: Stage Number 4" {dpoke 0xc81b 4} "Stage: Stage Number 5" {dpoke 0xc81b 5} "Stage: Stage Number 6" {dpoke 0xc81b 6} "Stage: Stage Number 7" {dpoke 0xc81b 7} "Stage: Stage Number 8" {dpoke 0xc81b 8} } create_trainer "Eidolon, The" {time 1} { "Power: Power" {dpoke 0x012f 16} } create_trainer "Eindeloos" {time 0.5} { "Big Enemy 1 Y-pos" {dpoke 0x9486 200} "Big Enemy 2 Y-pos" {dpoke 0x9482 200} "Big Enemy 3 Y-pos" {dpoke 0x948a 200} "Lives: Unlimited Lives" {dpoke 0x9c91 99} } create_trainer "Elevator Action" {time 1} { "Lives: Lives" {dpoke 0xc08a 99} } create_trainer "Eric And The Floaters" {time 1} { "Lives: Lives" {dpoke 0xe30f 13} } create_trainer "Escape" {time 1} { "Frozen Robots" {dpoke 0xf100 201} "Level (1-99)" {dpoke 0x9d74 1} "Time (but Overflow Risk In Score !)" {dpoke 0x9d6f 96} "Lives: Lives" {dpoke 0x9d65 9} } create_trainer "Exerion 1" {time 1} { "Always Charge Player 1" {dpoke 0xe120 153} "Always Charge Player 2" {dpoke 0xe122 153} "Lives: Lives Player 1" {dpoke 0xe108 3} "Lives: Lives Player 2" {dpoke 0xe109 3} } create_trainer "Exerion 2 - Zorni" {time 1} { "Always Charge Player 1" {dpoke 0xe120 153} "Always Charge Player 2" {dpoke 0xe122 153} "Lives: Lives Player 1" {dpoke 0xe108 3} "Lives: Lives Player 2" {dpoke 0xe109 3} } create_trainer "Exoide-Z" {time 1} { "Invulnerable (and Less Enemies If Touched)" {dpoke 0x21c 15;poke 0xe21b 0;dpoke 0xe2a6 0} "Maximum Bonus" {dpoke 0xe0b3 100;poke 0xe0b5 100} "Lives: Lives" {dpoke 0xe0a9 9} } create_trainer "Exoide-Z Area 5" {time 1} { "Invincible Green" {dpoke 0xe33e 255} "Invincible Red" {dpoke 0xe30d 255} "Silver Color Ship" {dpoke 0xe313 15} "Lives: Lives" {dpoke 0xe00b 153} "Power: Power" {dpoke 0xe1d3 100} } create_trainer "Exterlien" {time 1} { "Exp" {dpoke 0x9e33 255;poke 0x9e34 255} "Power: Power" {dpoke 0x9e2d 15;poke 0x9e2e 39} } create_trainer "Exterminator" {time 1} { "Lives: Lives" {dpoke 0x4505 57} } create_trainer "F1 Spirit - The Way To Formula 1" {time 1} { "All Combis With Konami Carts" {dpoke 0xe1de 2} "Escon" {dpoke 0xe1fd 1} "Hyperoff" {dpoke 0xe1d6 1} "Maxpoint" {dpoke 0xe1df 1} "Player 1 Always First Place" {dpoke 0xe331 1} "Player 1 Fuel" {dpoke 0xe310 255} "Player 1 No Damage (bitmask)" {dpoke 0xe328 0} "Player 2 Damage" {dpoke 0xe3e8 0} "Player 2 Fuel" {dpoke 0xe3d0 255} "Player 2 Position" {dpoke 0xe3f1 1} } create_trainer "Factory Infection" {time 1} { "No Mistake" {dpoke 0xc29c 0} } create_trainer "Fairy" {time 5} { "Lives: Lives" {dpoke 0xdf04 5} } create_trainer "Fairy Land Story, The" {time 5} { "Lives: Lives" {dpoke 0xe1c0 153} } create_trainer "Famicle Parodic" {time 2} { "Money: Eggs" {dpoke 0xe025 255} "Player Select: Hero 0" {dpoke 0xe318 0} "Player Select: Hero 1" {dpoke 0xe318 1} "Player Select: Hero 2" {dpoke 0xe318 2} "Player Select: Hero 3" {dpoke 0xe318 3} "Player Select: Hero 4" {dpoke 0xe318 4} "Player Stats: Invulnerable" {dpoke 0xe30e 255} "Player Stats: Lives" {dpoke 0xe003 153} "Stage Select: Stage 0" {dpoke 0xe317 0} "Stage Select: Stage 1" {dpoke 0xe317 1} "Stage Select: Stage 2" {dpoke 0xe317 2} "Stage Select: Stage 3" {dpoke 0xe317 3} "Stage Select: Stage 4" {dpoke 0xe317 4} "Stage Select: Stage 5" {dpoke 0xe317 5} "Stage Select: Stage 6" {dpoke 0xe317 6} "Stage Select: Stage 7" {dpoke 0xe317 7} "Stage Select: Stage 8" {dpoke 0xe317 8} "Stage Select: Stage 9" {dpoke 0xe317 9} "Weapon: Full Weapons" {dpoke 0xe050 4;poke 0xe051 4;dpoke 0xe052 4;dpoke 0xe053 4;dpoke 0xe054 4;dpoke 0xe055 4;dpoke 0xe056 4;dpoke 0xe057 4;dpoke 0xe058 4;dpoke 0xe059 2} } create_trainer "Famicle Parodic 2" {time 1} { "Hero (0-4)" {dpoke 0xc009 2} "Option 1" {dpoke 0xc864 1} "Option 2" {dpoke 0xc874 2} "Invulnerable: Invulnerable" {dpoke 0x9745 201} "Lives: Lives" {dpoke 0xc008 99} "Power: Power (0-2)" {dpoke 0xc15c 2} "Stage: Stage (0-6)" {dpoke 0xc007 0} } create_trainer "Fantasm Soldier 2" {time 2} { "Invincible" {dpoke 0xf976 255} "Life" {dpoke 0xf937 255} "Pearls" {dpoke 0xf969 99} "Shot Strength" {dpoke 0xf977 4} } create_trainer "Fantasm Soldier Valis, The" {time 2} { "Life" {dpoke 0xf064 255} "Stage End Demon Power" {dpoke 0xf1bb 1} "Weapon: Max Sword" {dpoke 0xf294 3} } create_trainer "Fantasy Zone 1" {time 2} { "Money" {dpoke 0xe20b 153;poke 0xe20c 153;dpoke 0xe20d 153} } create_trainer "Fantasy Zone 2 - The Tears Of Opa-Opa" {time 1} { "Money" {dpoke 0xe599 153;poke 0xe59a 153;dpoke 0xe59b 153} "Lives: Lives" {dpoke 0xe5ad 153} } create_trainer "Feedback" {time 2} { "Have One Red Missile Ready" {dpoke 0xd21a 1} "Invincible" {dpoke 0xd21b 255} "Keep Missile On Screen" {dpoke 0xd10a 255;poke 0xd11a 255;dpoke 0xd12a 255;dpoke 0xd13a 255;dpoke 0xd14a 255;dpoke 0xd15a 255;dpoke 0xd16a 255} "Life" {dpoke 0xd213 16} "Missiles" {dpoke 0xd214 99} "Red Missile" {dpoke 0xd17a 255;poke 0xd17e 2} "Speed" {dpoke 0xd212 10} } create_trainer "Fernando Martin Basket Master" {time 1} { "Energy Player 1" {dpoke 0xf0a0 74} "Energy Player 2" {dpoke 0xf0a1 74} } create_trainer "Fernando Martin Basket Master Executive" {time 1} { "Energy Player 1" {dpoke 0x9415 22} "Energy Player 2" {dpoke 0x942f 22} } create_trainer "Feud" {time 2} { "Bog Bean" {dpoke 0x58da 7} "Bones" {dpoke 0x58d8 7} "Burdock" {dpoke 0x58d5 7} "Catsear" {dpoke 0x58db 7} "Concoctions" {dpoke 0x58e1 7} "Feverfew" {dpoke 0x58de 7} "Hemlock" {dpoke 0x58dc 7} "Knap Weed" {dpoke 0x58e0 7} "Life Full" {dpoke 0x5885 40} "Mad Sage" {dpoke 0x58d9 7} "Mouse Tail" {dpoke 0x58df 7} "Ragwort" {dpoke 0x58d6 7} "Skullcap" {dpoke 0x58dd 7} "Toadflax" {dpoke 0x58d7 7} } create_trainer "Final Fantasy" {time 1} { "Player 1 Black Belt" {dpoke 0xc000 1} "Player 1 Black Mage" {dpoke 0xc000 5} "Player 1 Fighter" {dpoke 0xc000 0} "Player 1 Red Mage" {dpoke 0xc000 3} "Player 1 Thief" {dpoke 0xc000 2} "Player 1 White Mage" {dpoke 0xc000 4} "Gold: Gold" {dpoke 0xc255 255;poke 0xc256 255} "Level: Player 1 Level" {dpoke 0xc026 99} "Level: Player 2 Level" {dpoke 0xc066 99} "Level: Player 3 Level" {dpoke 0xc0a6 99} "Level: Player 4 Level" {dpoke 0xc0e6 99} "Life: Player 1 Life " {dpoke 0xc008 255} "Life: Player 1 Life" {dpoke 0xc009 255;poke 0xc00a 99} "Life: Player 2 Life" {dpoke 0xc048 255;poke 0xc049 255;dpoke 0xc04a 99} "Life: Player 3 Life" {dpoke 0xc088 255;poke 0xc089 255;dpoke 0xc08a 99} "Life: Player 4 Life" {dpoke 0xc0c8 255;poke 0xc0c9 255;dpoke 0xc0ca 99} } create_trainer "Final Justice" {time 1} { "Energy: Energy" {dpoke 0xe411 100} } create_trainer "Final Zone Wolf" {time 1} { "Ammo" {dpoke 0xe11c 255} "Grenades" {dpoke 0xe11b 255} "Power: Power" {dpoke 0xe024 255} } create_trainer "Fire Rescue" {time 1} { "Always Water" {dpoke 0xe62e 1} "Flame 1 Is Blue" {dpoke 0xe75f 2} "Flame 2 Is Blue" {dpoke 0xe767 2} "Flame 3 Is Blue" {dpoke 0xe76f 2} "Flame 4 Is Blue" {dpoke 0xe777 2} "Flame 5 Is Blue" {dpoke 0xe77f 2} "X-pos (0-27)" {dpoke 0xe628 0} "Y-pos (0-18)" {dpoke 0xe629 0} "Invulnerable: Invulnerable" {dpoke 0xe610 0} "Lives: Lives" {dpoke 0xe7b1 9} } create_trainer "Fire Rescue CAS" {time 1} { "Always Water" {dpoke 0xe92e 1} "Flame 1 Is Blue" {dpoke 0xea5f 2} "Flame 2 Is Blue" {dpoke 0xea67 2} "Flame 3 Is Blue" {dpoke 0xea6f 2} "Flame 4 Is Blue" {dpoke 0xea77 2} "Flame 5 Is Blue" {dpoke 0xea7f 2} "X-pos (0-27)" {dpoke 0xe928 0} "Y-pos (0-18)" {dpoke 0xe929 0} "Invulnerable: Invulnerable" {dpoke 0xe910 0} "Lives: Lives" {dpoke 0xeab1 9} } create_trainer "Flash Splash" {time 1} { "Invincible" {dpoke 0xe001 0;poke 0xe009 255} "Lives: Lives (might Not Work 100%)" {dpoke 0xe000 255} "Power: Power" {dpoke 0xe008 64} } create_trainer "Flicky" {time 1} { "Round (1-40)" {dpoke 0xe0e7 1;poke 0xe0e8 1} "Lives: Lives" {dpoke 0xe0ee 3} } create_trainer "Fly-Boat" {time 1} { "Lives: Lives" {dpoke 0x9b0d 57;poke 0x9b0e 57} } create_trainer "Frantic" {time 1} { "Beverage" {dpoke 0xa320 1} "Cover" {dpoke 0xa316 153} "Job (1-6)" {dpoke 0xfc83 1} "Shoes" {dpoke 0xa318 153} "Vitality" {dpoke 0xa31a 33} "Water" {dpoke 0xa31c 28} "Invulnerable: Invulnerable" {dpoke 0xfc84 1} "Weapon: Bombs" {dpoke 0xa314 153} } create_trainer "Fray" {time 1} { "Auto Big Shot (hold C To Hold)" {dpoke 0x2286 49} "Bottle" {dpoke 0x22a4 99} "Life Bar Full" {dpoke 0x2010 200} "Money 65535" {dpoke 0x2289 255;poke 0x228a 255} "Scepter 1" {dpoke 0x2295 2} "Scepter 2" {dpoke 0x2296 2} "Scepter 3" {dpoke 0x2297 2} "Scepter 4" {dpoke 0x2298 2} "Scepter 5" {dpoke 0x2299 2} "Scepter 6" {dpoke 0x229a 2} "Scepter 7" {dpoke 0x229b 2} "Scepter 8" {dpoke 0x229c 2} "Scroll 1" {dpoke 0x22a5 99} "Scroll 10" {dpoke 0x22ae 99} "Scroll 11" {dpoke 0x22af 99} "Scroll 2" {dpoke 0x22a6 99} "Scroll 3" {dpoke 0x22a7 99} "Scroll 4" {dpoke 0x22a8 99} "Scroll 5" {dpoke 0x22a9 99} "Scroll 6" {dpoke 0x22aa 99} "Scroll 7" {dpoke 0x22ab 99} "Scroll 8" {dpoke 0x22ac 99} "Scroll 9" {dpoke 0x22ad 99} "Slice Of Bacon" {dpoke 0x22a3 99} "Slice Of Bread" {dpoke 0x22a2 99} "Shield: Big Iron Shield" {dpoke 0x22a0 1} "Shield: Gold Shield" {dpoke 0x22a1 1} "Shield: Iron Shield" {dpoke 0x229f 1} "Shield: Medium Shield" {dpoke 0x229e 1} } create_trainer "Freddy Hardest 1" {time 1} { "No Flying Robots" {dpoke 0xe2b8 20} "No Hormigoids" {dpoke 0xe298 20} "No Koptes" {dpoke 0xe2a0 20} "No Ovoids" {dpoke 0x2b0 20;poke 0xe2a8 20} "No Snakes" {dpoke 0xe290 20} "Lives: Lives" {dpoke 0xd974 6} } create_trainer "Freddy Hardest 2" {time 1} { "Always Fire" {dpoke 0xe47c 5} "No Enemies" {dpoke 0xe4f3 0;poke 0xe4f4 0;dpoke 0xe4f5 0} "Lives: Lives" {dpoke 0xe481 6} } create_trainer "Freddy Hardest In South Manhattan" {time 1} { "Almost No Enemies" {dpoke 0xdfae 0;poke 0xdfb3 0} "Energy: Energy" {dpoke 0xdfaa 127} "Lives: Lives" {dpoke 0xdfc1 9} } create_trainer "Frog" {time 1} { "Lives: Lives" {dpoke 0x4044 3} "Time: Time" {dpoke 0x406c 0} } create_trainer "Frog Ace" {time 1} { "Lives: Lives" {dpoke 0x96dd 255} } create_trainer "Frogger" {time 2} { "Difficulty Level (1-10)" {dpoke 0xe082 1} "Only One Frog Required By Stage" {dpoke 0xe081 5} "Time (de-select To Get Bonus)" {dpoke 0xe053 16} "Lives: Lives" {dpoke 0xe002 99} "Stage: Stage (1-99;0)" {dpoke 0xe084 1} } create_trainer "Front Line" {time 1} { "Lives: Lives" {dpoke 0xc001 255} } create_trainer "Fruity Frank" {time 10} { "Lives: Lives" {dpoke 0x4144 4} } create_trainer "Funky Mouse" {time 1} { "Eat Only One Cheese" {dpoke 0xe088 1} "Infinite Bonus Time (de-select At End Of Scene)" {dpoke 0xe20d 99} "Lives: Lives" {dpoke 0xe211 9} } create_trainer "Future Knight" {time 1} { "Lives: Lives" {dpoke 0xb4ce 231} "Weapon: Weapon (25-dart/23 Beam/24 Fireball)" {dpoke 0x8075 24} } create_trainer "Galaga" {time 2} { "Lives: Lives" {dpoke 57358 99} } create_trainer "Galaxian" {time 1} { "Lives: Lives" {dpoke 0xe071 9} } create_trainer "Gall Force - Defence Of Chaos" {time 2} { "20 Hits" {dpoke 0xccec 15;poke 0xccee 15} "All Galls" {dpoke 0xc447 255;poke 0xcb12 7} } create_trainer "Game Over 2 part 1" {time 1} { "Invulnerable: Invulnerable" {dpoke 0xb9a1 0;poke 0xbd3a 33} "Lives: Lives" {dpoke 0xbfe6 57;poke 0xbfe7 57} } create_trainer "Game Over 2 part 2" {time 1} { "Has Access Medallion" {dpoke 0xc5c0 0} "Has Proton Loader" {dpoke 0xc858 0} "Energy: Energy" {dpoke 0xc718 0} "Lives: Lives" {dpoke 0xb9b9 57;poke 0xb9ba 57} "Weapon: Has Ionic Turbo Laser" {dpoke 0xc2a3 0} } create_trainer "Game Over part 1" {time 1} { "Protected From Enemies (de-select After 3 Robots)" {dpoke 0xd9cb 1} "Protected From Mines (de-select After 3 Robots)" {dpoke 0xd9c7 0} "Special Shot" {dpoke 0xd9cd 2;poke 0xd9cd 4} "Lives: Lives" {dpoke 0xd9bb 10} "Power: Power" {dpoke 0xda1b 255} "Weapon: Bombs" {dpoke 0xd9bf 100} } create_trainer "Game Over part 2" {time 1} { "Access To The Lake" {dpoke 0xd9c5 1} "Access To The Top" {dpoke 0xd9cb 1} "Lives: Lives" {dpoke 0xd9bf 10} "Power: Power" {dpoke 0xda27 255} "Weapon: Laser" {dpoke 0xd9c3 100} } create_trainer "Ganbare Goemon - Samurai" {time 2} { "Cheats: Cart Combos" {dpoke 0xef00 255} "Cheats: Invincible" {dpoke 0xc4a7 255} "Item: 3 Permission Passes" {dpoke 0xc279 3} "Item: Armor" {dpoke 0xc272 5} "Item: Bamboo Hat" {dpoke 0xc275 5} "Item: Candle" {dpoke 0xc278 1} "Item: Charm" {dpoke 0xc273 5} "Item: Food" {dpoke 0xc277 5} "Item: Full Vitality" {dpoke 0xc481 255} "Item: Helmet" {dpoke 0xc276 5} "Item: Seal Case" {dpoke 0xc274 5} "Item: Shoes X3" {dpoke 0xc270 3} "Lives: 99 Lives" {dpoke 0xc260 153} "Money: A Lot Of Money" {dpoke 0xc265 255;poke 0xc266 255} "Weapon: Catapult" {dpoke 0xc271 1} } create_trainer "Gandhara " {time 1} { "Exp" {dpoke 0x9117 0x99;poke 0x9118 0x99;dpoke 0x9119 0x99} "foods" {dpoke 0x9114 0x99} "Foods" {dpoke 0x9115 0x99;poke 0x9116 0x99} "Hp" {dpoke 0x911a 0x99;poke 0x911b 0x99;dpoke 0x911c 0x99} } create_trainer "Garbage Man" {time 1} { "One Bag Is Enough" {dpoke 0xa5ba 0} "Lives: Lives" {dpoke 0xa5b5 6} } create_trainer "Garbage Man Promo" {time 1} { "One Bag Is Enough" {dpoke 0xa289 0} "Lives: Lives" {dpoke 0xa284 6} } create_trainer "Garyuoh - Dragon King" {time 1} { "Extra Invincible" {dpoke 0xe06d 8} "Invincible" {dpoke 0xe0b7 255} } create_trainer "Gekitotsu Pennant Race" {time 1} { "0 Fatigue" {dpoke 0x1c3c 0} "3 Out (both Teams)" {dpoke 0xe19a 3} "Always Have 2 Strikes" {dpoke 0xe198 2} "Inf Gold" {dpoke 0x1c96 255;poke 0x1c97 255} "Player 2 Always 0 Points (end Total)" {dpoke 0xe196 0} } create_trainer "Ghostbusters" {time 0.10} { "Credit" {dpoke 0xefa8 153;poke 0xefa9 153} "Ghost X-position" {dpoke 0xf0ed 128} "Ghost Y-position" {dpoke 0xf0ee 160} "Tries To Get Past The Marshmallow Man" {dpoke 0xf13a 255} "Energy: City Pk Energy" {dpoke 0xefe9 153} } create_trainer "Ghostbusters 2" {time 1} { "Courage Part 1" {dpoke 0x6180 0} "Egon Energy Part 3" {dpoke 0x6244 63} "Fireball Energy Part 2" {dpoke 0xd9db } "Peter Energy Part 3" {dpoke 0x6223 63} "Proton Beams Part 1" {dpoke 0x6189 99} "Ray Energy Part 3" {dpoke 0x6202 63} "Rope Strength Part 1" {dpoke 0x6185 0} "Slime Part 2" {dpoke 0xd9b4 22} "Winston Energy Part 3" {dpoke 0x6265 63} "Lives: Lives Part 1" {dpoke 0x617e 3} "Lives: Lives Part 2" {dpoke 0xc7d6 0} "Lives: Lives Part 3" {dpoke 0xc1c3 0} "Shield: Shields Part 1" {dpoke 0x618b 99} "Time: Time Part 2" {dpoke 0xd30d 1;poke 0xd30e 1;dpoke 0xd30f 4;dpoke 0xd310 5;dpoke 0xd311 0;dpoke 0xd312 0} "Time: Time Part 3" {dpoke 0x6280 0} "Weapon: Proton Bombs Part 1" {dpoke 0x618a 99} } create_trainer "Girl's Garden" {time 1} { "Flowers (very Easy!)" {dpoke 0xc0be 10} "Honey (water)" {dpoke 0xc00e 5} "Lives: Love (lives)" {dpoke 0xc00f 3} } create_trainer "Girly Block" {time 2} { "Player 1 Fuel" {dpoke 0xe031 255} "Player 1 Level" {dpoke 0xe032 255} "Player 1 Life" {dpoke 0xe030 255} "Player 2 Fuel" {dpoke 0xe0f1 0} "Player 2 Level" {dpoke 0xe0f2 0} "Player 2 Life" {dpoke 0xe0f0 0} } create_trainer "Gofer no Yabou Episode 2 - Nemesis 3 The Eve Of Destruction" {time 0.5} { "Activate Expand" {dpoke 0xe39e 1} "Activate Find" {dpoke 0xe39c 1} "Activate Good" {dpoke 0xe39d 2} "Activate Hard" {dpoke 0xe39b 0;poke 0xe39d 1} "Back Shot" {dpoke 0xe630 2} "Blue Map" {dpoke 0xe394 1} "Choose Option (1-3)" {dpoke 0xe37e 3} "Choose Vixen (0-3)" {dpoke 0xe380 3} "Double Way Missile (with Vixen=3)" {dpoke 0xe642 16} "Down Shot" {dpoke 0xe630 4} "Extra Sensory Device" {dpoke 0xe397 1} "Green Map" {dpoke 0xe395 1} "Guided Missile" {dpoke 0xe632 19} "Hawkwind Missile" {dpoke 0xe632 20} "Napalm Missile" {dpoke 0xe632 18} "Normal Missile" {dpoke 0xe632 16} "Normal Shot" {dpoke 0xe630 1} "Only For Stage 4" {dpoke 0xe363 0} "Options" {dpoke 0xe608 2;poke 0xe610 1;dpoke 0xe620 2} "Photon Missile" {dpoke 0xe632 17} "Red Map" {dpoke 0xe393 1} "Set Speed To 4" {dpoke 0xe36d 4} "Shot 1 Big Fire Blaster" {dpoke 0xe630 12} "Shot 1 Extended Blaster" {dpoke 0xe630 8} "Shot 1 Fire Blaster" {dpoke 0xe630 11} "Shot 2 Down Double" {dpoke 0xe631 4} "Shot 2 Normal Beam" {dpoke 0xe631 1} "Shot 2 Tail Beam" {dpoke 0xe631 2} "Shot 2 Up Double" {dpoke 0xe631 3} "Up Shot" {dpoke 0xe630 3} "X-pos Enemy" {dpoke 0xe806 0;poke 0xe846 0;dpoke 0xe886 0;dpoke 0xe8c6 0;dpoke 0xe906 0;dpoke 0xe946 0;dpoke 0xe986 0;dpoke 0xe9c6 0} "Lives: Lives" {dpoke 0xe360 153} "Shield: Choose Shield (1-2)" {dpoke 0xe37f 2} "Shield: Shield On" {dpoke 0xe600 3} "Shield: Space Fighter Shield" {dpoke 0xe396 1} "Stage: Stage 01" {dpoke 0xe361 1} "Weapon: All Weapons And Upgrades" {dpoke 0xe36f 7} "Weapon: Shoot Or Laser (1-12)" {dpoke } "Weapon: Shot 1 Laser" {dpoke 0xe630 5} "Weapon: Shot 1 Meteor Laser" {dpoke 0xe630 6} "Weapon: Shot 1 Ripple Laser" {dpoke 0xe630 10} "Weapon: Shot 1 Screw Laser" {dpoke 0xe630 7} "Weapon: Shot 1 Vector Laser" {dpoke 0xe630 9} "Weapon: Shot 2 Down Laser" {dpoke 0xe631 14} "Weapon: Shot 2 Up Laser" {dpoke 0xe631 13} } create_trainer "Golvellius" {time 1} { "All Caves Are Easy" {dpoke 0xe05b 0} "Do Not Get Paralyzed When Hit By An Enemy (after Frame)" {dpoke 0xd01a 0} "Exit Any Cave To See Ending" {dpoke 0xe01e 255} "Get All Crystals (bitmask)" {dpoke 0xe05f 255} "Leafs" {dpoke 0xe0a4 3} "Max Gold" {dpoke 0xe050 255;poke 0xe051 255} "Max Health And Full Bar" {dpoke 0xe022 240;poke 0xe03d 240} "Open All Holes" {dpoke 0xe096 1} "Item: All Items" {dpoke 0xe01d 7;poke 0xe01f 3;dpoke 0xe020 6;dpoke 0xe021 2;dpoke 0xe03c 1;dpoke 0xe03f 5} } create_trainer "Golvellius 2" {time 10} { "Air Boots" {dpoke 0xcb05 1} "Blue Diamond" {dpoke 0xcb10 1} "Blue Potion" {dpoke 0xcb0f 1} "Candle" {dpoke 0xcb0c 1} "Fairy" {dpoke 0xcb13 1} "Find" {dpoke 0xcbac 153;poke 0xcbad 153} "Fruit" {dpoke 0xcb12 1} "Gold Ring" {dpoke 0xcb09 1} "Golden Lost Ring" {dpoke 0xcb15 1} "Harp" {dpoke 0xcb0a 1} "Heart Pendant" {dpoke 0xcb0b 1} "Herb" {dpoke 0xcb18 1} "Holes Are Always Open" {dpoke 0xc06f 2} "Life" {dpoke 0xcba1 0;poke 0xcba2 2} "Max Hearts" {dpoke 0xcb9d 0;poke 0xcb9e 2} "Maxed Out Find" {dpoke 0xcba6 153} "Maxed out Find" {dpoke 0xcba7 153} "Mirror" {dpoke 0xcb0d 1} "Necklace" {dpoke 0xcb14 1} "Scepter" {dpoke 0xcb06 1} "Silver Broche" {dpoke 0xcb11 1} "Silver Ring" {dpoke 0xcb0e 1} "Water Boots" {dpoke 0xcb04 1} "Key: Key" {dpoke 0xcb17 1} "Shield: Bronze Shield" {dpoke 0xcb08 1} "Shield: Iron Shield" {dpoke 0xcb07 1} "Weapon: Bronze Sword" {dpoke 0xcb02 1} "Weapon: Document To Get First Sword" {dpoke 0xcb16 1} "Weapon: Gold Sword" {dpoke 0xcb03 1} "Weapon: Iron Sword" {dpoke 0xcb01 1} } create_trainer "Goody" {time 10} { "Invincible" {dpoke 0xaeaf 8} } create_trainer "Goonies r good enough" {time 1} { "Life: Power Andy" {dpoke 0xeef9 48} "Life: Power Brand" {dpoke 0xeef5 48} "Life: Power Chunk" {dpoke 0xeefa 48} "Life: Power Data" {dpoke 0xeef7 48} "Life: Power Mikey" {dpoke 0xeef4 48} "Life: Power Mouth" {dpoke 0xeef6 48} "Life: Power Stef" {dpoke 0xeef8 48} "Status: Invinsible" {dpoke 0xef2f 255} } create_trainer "Goonies, The" {time 1} { "Always Have Key" {dpoke 0xe121 1} "Experience" {dpoke 0xe065 80} "Extra Experience" {dpoke 0xe179 1} "Extra Vitality" {dpoke 0xe179 4} "Open Door To Next Stage" {dpoke 0xe130 7} "Protected From Falling Stones" {dpoke 0xe178 1} "Protected From Most Enemies" {dpoke 0xe176 255} "Protected From Water, Fire, Bats And Shoots" {dpoke 0xe177 255} "Show Hidden Items" {dpoke 0xe2ed 2} "Vitality" {dpoke 0xe064 80} "Stage: Stage (1-5)" {dpoke 0xe06c 1} } create_trainer "Gozilla Kun" {time 2} { "Energy: Energy" {dpoke 0xe336 255} "Lives: Lives" {dpoke 0xe30f 101} } create_trainer "GP World" {time 1} { "Level (1-3)" {dpoke 0xe00f 1} "Round (1-9)" {dpoke 0xe005 1} "Time: Time" {dpoke 0xe1cb 0} } create_trainer "Gradius (SCC version)" {time 2} { "Always Hyper" {dpoke 0xc202 8} "Deactivate Normal Shoot" {dpoke 0xc20c 0} "Double" {dpoke 0xc133 1} "Enable Double" {dpoke 0xc20d 2} "Enable Missile" {dpoke 0xc20f 2} "Enable Option 1" {dpoke 0xc220 1} "Enable Option 2" {dpoke 0xc240 1} "Missile" {dpoke 0xc132 1} "Option" {dpoke 0xc135 1;poke 0xc20b 2} "Speed Set To 4" {dpoke 0xc10b 4} "Use Unlimited Hyper" {dpoke 0xc06e 0} "Use Unlimited Per Stage Hyper" {dpoke 0xc071 0} "Lives: Lives" {dpoke 0xc060 153} "Shield: Shield" {dpoke 0xc136 2;poke 0xc201 10} "Shield: Shield Off" {dpoke 0xe200 1} "Shield: Shield On" {dpoke 0xe200 3} "Stage: Bonus Stage 1" {dpoke 0xc061 9} "Stage: Bonus Stage 2" {dpoke 0xc061 10} "Stage: Bonus Stage 3" {dpoke 0xc061 11} "Stage: Bonus Stage 4" {dpoke 0xc061 12} "Stage: Stage 1" {dpoke 0xc061 1} "Stage: Stage 2" {dpoke 0xc061 2} "Stage: Stage 3" {dpoke 0xc061 3} "Stage: Stage 4" {dpoke 0xc061 4} "Stage: Stage 5" {dpoke 0xc061 5} "Stage: Stage 6" {dpoke 0xc061 6} "Stage: Stage 7" {dpoke 0xc061 7} "Stage: Stage 8" {dpoke 0xc061 8} "Weapon: Enable Laser" {dpoke 0xc20e 2} "Weapon: Laser" {dpoke 0xc134 1} } create_trainer "Gradius - Nemesis" {time 0.5} { "Always Hyper" {dpoke 0xe202 8} "Deactivate Normal Shoot" {dpoke 0xe20c 0} "Double" {dpoke 0xe133 1} "Enable Double" {dpoke 0xe20d 2} "Enable Missile" {dpoke 0xe20f 2} "Enable Option 1" {dpoke 0xe220 1} "Enable Option 2" {dpoke 0xe240 1} "Missile" {dpoke 0xe132 1} "Option" {dpoke 0xe135 1;poke 0xe20b 2} "Simulate Twinbee In Slot 2" {dpoke 0xf0f4 1} "Speed Set To 4" {dpoke 0xe10b 4} "Use Unlimited Hyper" {dpoke 0xe06e 0} "Use Unlimited Per Stage Hyper" {dpoke 0xe071 0} "Lives: Lives" {dpoke 0xe060 153} "Shield: Shield" {dpoke 0xe136 2;poke 0xe201 10} "Shield: Shield Off" {dpoke 0xe200 1} "Shield: Shield On" {dpoke 0xe200 3} "Stage: Bonus Stage 1" {dpoke 0xe061 9} "Stage: Bonus Stage 2" {dpoke 0xe061 10} "Stage: Bonus Stage 3" {dpoke 0xe061 11} "Stage: Bonus Stage 4" {dpoke 0xe061 12} "Stage: Stage 1" {dpoke 0xe061 1} "Stage: Stage 2" {dpoke 0xe061 2} "Stage: Stage 3" {dpoke 0xe061 3} "Stage: Stage 4" {dpoke 0xe061 4} "Stage: Stage 5" {dpoke 0xe061 5} "Stage: Stage 6" {dpoke 0xe061 6} "Stage: Stage 7" {dpoke 0xe061 7} "Stage: Stage 8" {dpoke 0xe061 8} "Weapon: Enable Laser" {dpoke 0xe20e 2} "Weapon: Laser" {dpoke 0xe134 1} } create_trainer "Gradius 2 (beta)" {time 2} { "Back Beam" {dpoke 0xe436 2} "Deactivate Normal Shoot" {dpoke 0xe430 0} "Double" {dpoke 0xe431 2} "Double Missile" {dpoke 0xe433 2} "Enemy Slow" {dpoke 0xe439 5} "Fire Blaster" {dpoke 0xe432 6} "Napalm Missile" {dpoke 0xe433 3} "Nice Colors" {dpoke 0xe283 14;poke 0xe408 15} "Normal Missile" {dpoke 0xe433 1} "Option Ring" {dpoke 0xe439 3} "Options" {dpoke 0xe40b 2;poke 0xe410 1;dpoke 0xe420 1} "Reflex Ring" {dpoke 0xe432 5} "Rotary Drill" {dpoke 0xe439 4} "Speed Set To 5" {dpoke 0xe402 5} "Lives: Lives" {dpoke 0xe200 153} "Shield: Shield Off" {dpoke 0xe400 0} "Shield: Shield On" {dpoke 0xe400 2} "Stage: Core Stage" {dpoke 0xe201 9} "Stage: Final Stage" {dpoke 0xe201 0} "Stage: Stage 1" {dpoke 0xe201 1} "Stage: Stage 2" {dpoke 0xe201 2} "Stage: Stage 3" {dpoke 0xe201 3} "Stage: Stage 4" {dpoke 0xe201 4} "Stage: Stage 5" {dpoke 0xe201 5} "Stage: Stage 6" {dpoke 0xe201 6} "Stage: Stage 7" {dpoke 0xe201 7} "Stage: Stage 8" {dpoke 0xe201 8} "Weapon: Double Laser" {dpoke 0xe432 2} "Weapon: Down Laser" {dpoke 0xe435 2} "Weapon: Extended Laser" {dpoke 0xe432 3} "Weapon: Normal Laser" {dpoke 0xe432 1} "Weapon: Up Laser" {dpoke 0xe434 2} "Weapon: Vector Laser" {dpoke 0xe439 7} } create_trainer "Gradius 2 - Nemesis 2" {time 0.5} { "Activate Cheat Menu (pre Release Only)" {dpoke 0xf0f6 1} "All Konami Cart Combinations Activated" {dpoke 0xf0f5 255} "Combi: Penguin Adventure Mode" {dpoke 0xf0f5 4} "Combi: Q-bert Mode" {dpoke 0xf0f5 1} "Combi: The Maze Of Galious Mode" {dpoke 0xf0f5 8} "Enemy Slow" {dpoke 0xe439 5} "Metalion Mode" {dpoke 0xe446 1} "Nice Vic Viper Colors" {dpoke 0xe283 14;poke 0xe408 15} "Stage: Bonus Stage 1" {dpoke 0xe201 9} "Stage: Bonus Stage 2" {dpoke 0xe201 10} "Stage: Bonus Stage 3" {dpoke 0xe201 11} "Stage: Core Stage" {dpoke 0xe201 8} "Stage: Final Stage" {dpoke 0xe201 0} "Lives: Lives" {dpoke 0xe200 153} "Stage: Stage: Stage 1" {dpoke 0xe201 1} "Stage: Stage: Stage 2" {dpoke 0xe201 2} "Stage: Stage: Stage 3" {dpoke 0xe201 3} "Stage: Stage: Stage 4" {dpoke 0xe201 4} "Stage: Stage: Stage 5" {dpoke 0xe201 5} "Stage: Stage: Stage 6" {dpoke 0xe201 6} "Stage: Stage: Stage 7" {dpoke 0xe201 7} "Weapon: Weapon: Back Beam" {dpoke 0xe436 2} "Weapon: Weapon: Deactivate Normal Shoot" {dpoke 0xe430 0} "Weapon: Weapon: Double" {dpoke 0xe431 2} "Weapon: Weapon: Double Laser" {dpoke 0xe432 2} "Weapon: Weapon: Double Missile" {dpoke 0xe433 2} "Weapon: Weapon: Down Laser" {dpoke 0xe435 2} "Weapon: Weapon: Extended Laser" {dpoke 0xe432 3} "Weapon: Weapon: Fire Blaster" {dpoke 0xe432 6} "Weapon: Weapon: Napalm Missile" {dpoke 0xe433 3} "Weapon: Weapon: Normal Laser" {dpoke 0xe432 1} "Weapon: Weapon: Normal Missile" {dpoke 0xe433 1} "Weapon: Weapon: Option Ring" {dpoke 0xe439 3} "Weapon: Weapon: Options" {dpoke 0xe40b 2;poke 0xe410 1;dpoke 0xe420 1} "Weapon: Weapon: Reflex Ring" {dpoke 0xe432 5} "Weapon: Weapon: Rotary Drill" {dpoke 0xe439 4} "Weapon: Weapon: Shield Off" {dpoke 0xe400 0} "Weapon: Weapon: Shield On" {dpoke 0xe400 2} "Weapon: Weapon: Speed Set To 5" {dpoke 0xe402 5} "Weapon: Weapon: Up Laser" {dpoke 0xe434 2} "Weapon: Weapon: Vector Laser" {dpoke 0xe439 7} } create_trainer "Green Beret" {time 0.1} { "Bullet Gone " {dpoke 0xf156 255} "Enemy 1 Gone" {dpoke 0xf1f5 0} "Enemy 2 Gone" {dpoke 0xf20d 0} "Enemy 3 Gone" {dpoke 0xf225 0} "Have Bazooka" {dpoke 0xf0c3 4} "Mines Gone 1" {dpoke 0xdfc2 254} "Mines Gone 10" {dpoke 0xee8e 254} "Mines Gone 11" {dpoke 0xee92 254} "Mines Gone 12" {dpoke 0xee96 254} "Mines Gone 13" {dpoke 0xe022 254} "Mines Gone 14" {dpoke 0xe026 254} "Mines Gone 15" {dpoke 0xe02a 254} "Mines Gone 16" {dpoke 0xe02e 254} "Mines Gone 2" {dpoke 0xdfc6 254} "Mines Gone 3" {dpoke 0xdfca 254} "Mines Gone 4" {dpoke 0xdff2 254} "Mines Gone 5" {dpoke 0xdff6 254} "Mines Gone 6" {dpoke 0xdffa 254} "Mines Gone 7" {dpoke 0xee5e 254} "Mines Gone 8" {dpoke 0xee62 254} "Mines Gone 9" {dpoke 0xee66 254} "Note: Maybe Those Mines Gone Should All Be In One Cheat" {dpoke note: maybe} "Lives: Lives" {dpoke 0xf120 57} } create_trainer "Griel's Quest For The Sangraal (extended version)" {time 1} { "Has Holy Cross" {dpoke 0xc041 7} "Has Magical Staff" {dpoke 0xc041 6} "Has Nothing" {dpoke 0xc041 0} "Round (1-58)" {dpoke 0xc042 1} "Lives: Lives" {dpoke 0xc04f 3} "Weapon: Has Sacred Sword" {dpoke 0xc041 8} } create_trainer "Grog's Revenge" {time 30} { "Stones Collected" {dpoke 0xe089 255;poke 0xe08a 255} "Tires Left" {dpoke 0xe08b 5} } create_trainer "Guardic" {time 60} { "Speed" {dpoke 0xe00f 6} "Wave" {dpoke 0xe00a 4} "Lives: Lives" {dpoke 0xe027 255} "Power: Power" {dpoke 0xe019 255} "Shield: Have Shield" {dpoke 0xe00c 1} } create_trainer "Gulkave" {time 10} { "Energy: Energy" {dpoke 0xe2ad 255} "Lives: Lives" {dpoke 0xe2c5 6} "Weapon: Weapon (0-16)" {dpoke 0xe2a7 8} } create_trainer "Gun Fright" {time 1} { "Bullets" {dpoke 0xd05d 5} "Invincible (player Disappears)" {dpoke 0xd078 1} "Money" {dpoke 0xd052 99} "Lives: Lives" {dpoke 0xd05e 3} } create_trainer "Guru Logic (MSX1)" {time 1} { "Level (0-17)" {dpoke 0xc1f2 0} "Time: Infinite Time" {dpoke 0xc1e2 0} } create_trainer "Guru Logic (MSX2)" {frame} { "Level (0-4)" {dpoke 0x9bc5 0;poke 0xcc00 0} "Time: Infinite Time" {dpoke 0xa68c 17} } create_trainer "Gusano" {time 1} { "Lives: Lives" {dpoke 0x8de9 48} } create_trainer "Gutt Blaster" {time 1} { "Cosmic Cheat" {dpoke 0x4038 1} "Level (1-99)" {dpoke 0x4028 1} "Single Shoot" {dpoke 0x6a96 0} "Triple Shoot" {dpoke 0x6a96 1} "Lives: Lives" {dpoke 0x402c 6} "Weapon: 2 Way Bombs" {dpoke 0x6a96 3} "Weapon: Round Whirling Bombs" {dpoke 0x6a96 2} } create_trainer "Gyro Adventure" {time 1} { "Fuel" {dpoke 0xc15f 100} "Invulnerable (in The Sky)" {dpoke 0xc010 128} "Lives: Lives" {dpoke 0xc007 7} } create_trainer "Gyrodine" {time 1} { "Get Parachute When Hit" {dpoke 0xe5e4 250} "Lives: Lives" {dpoke 0xe5e0 10} } create_trainer "H.E.R.O." {time 5} { "Unlimited Power/time" {dpoke 0xc174 100} "Lives: Unlimited Lives" {dpoke 0xc031 4} "Weapon: Unlimited Bombs" {dpoke 0xc032 3} } create_trainer "Hadesu no Monsho - The Seal Of Hades" {time 1} { "Air" {dpoke 0xe057 64} "Hearts" {dpoke 0xe054 153;poke 0xe055 153} "Homing Shot" {dpoke 0xe32e 6} "Invincible" {dpoke 0xe03a 255} "Invincible (partly)" {dpoke 0xe33a 4;poke 0xe33b 60} "Shot" {dpoke 0xe058 64} "Lives: Lives" {dpoke 0xe001 153} } create_trainer "Hammer Boy 1" {time 1} { "Invincible (de-select To Get Score)" {dpoke 0x4971 0;poke 0x4972 0} "Lives: Lives" {dpoke 0x497f 10} } create_trainer "Hammer Boy 2" {time 1} { "Invincible (de-select To Get Score)" {dpoke 0x4d26 0;poke 0x4d27 0} "Lives: Lives" {dpoke 0x4d34 10} } create_trainer "Hang On" {time 1} { "Course (0-8)" {dpoke 0xe029 0} "Level (0-2)" {dpoke 0xe00e 0} "Time: Time" {dpoke 0xe04c 100} } create_trainer "Happy Fret" {time 1} { "Unlimited Power" {dpoke 0xbea0 41} } create_trainer "Hard Boiled" {time 1} { "1st Item: Alarm Clock" {dpoke 0xd04d 16} "1st Item: Alpha Key" {dpoke 0xd04d 19} "1st Item: Another Half" {dpoke 0xd04d 41} "1st Item: Bad Guy" {dpoke 0xd04d 36} "1st Item: Beta Key" {dpoke 0xd04d 20} "1st Item: Blue Diamond" {dpoke 0xd04d 8} "1st Item: Bottle Of Wine" {dpoke 0xd04d 28} "1st Item: Bunch Of Grapes" {dpoke 0xd04d 34} "1st Item: Candle" {dpoke 0xd04d 11} "1st Item: Clothes Peg" {dpoke 0xd04d 18} "1st Item: Coffee" {dpoke 0xd04d 12} "1st Item: Cold Beer" {dpoke 0xd04d 14} "1st Item: Cork Screw" {dpoke 0xd04d 29} "1st Item: Cup And Saucer" {dpoke 0xd04d 33} "1st Item: Delta Key" {dpoke 0xd04d 22} "1st Item: Gamma Key" {dpoke 0xd04d 21} "1st Item: Glass Of Wine" {dpoke 0xd04d 37} "1st Item: Good Guy" {dpoke 0xd04d 47} "1st Item: Green Dice" {dpoke 0xd04d 26} "1st Item: Hammer" {dpoke 0xd04d 10} "1st Item: Joystick" {dpoke 0xd04d 9} "1st Item: Key-card Next Sector" {dpoke 0xd04d 39} "1st Item: Knife And Fork" {dpoke 0xd04d 15} "1st Item: Light Bulb" {dpoke 0xd04d 23} "1st Item: Lot Of Fuel" {dpoke 0xd04d 0} "1st Item: Low Hit Bullets" {dpoke 0xd04d 5} "1st Item: Money" {dpoke 0xd04d 43} "1st Item: Nice Apple" {dpoke 0xd04d 32} "1st Item: No Saucer" {dpoke 0xd04d 46} "1st Item: Nothing" {dpoke 0xd04d 27} "1st Item: One Half" {dpoke 0xd04d 40} "1st Item: Pepper" {dpoke 0xd04d 7} "1st Item: Petrol Pump" {dpoke 0xd04d 44} "1st Item: Portable Radio" {dpoke 0xd04d 3} "1st Item: Question Mark" {dpoke 0xd04d 38} "1st Item: Red Telephone" {dpoke 0xd04d 1} "1st Item: Safety Pin" {dpoke 0xd04d 25} "1st Item: Screw" {dpoke 0xd04d 31} "1st Item: Tv With Antenna" {dpoke 0xd04d 17} "1st Item: Vertical Bullets" {dpoke 0xd04d 4} "1st Item: Wooden Shoe" {dpoke 0xd04d 30} "1st Item: Yellow Banana" {dpoke 0xd04d 24} "2nd Item: Alarm Clock" {dpoke 0xd04f 16} "2nd Item: Alpha Key" {dpoke 0xd04f 19} "2nd Item: Another Half" {dpoke 0xd04f 41} "2nd Item: Bad Guy" {dpoke 0xd04f 36} "2nd Item: Beta Key" {dpoke 0xd04f 20} "2nd Item: Blue Diamond" {dpoke 0xd04f 8} "2nd Item: Bottle Of Wine" {dpoke 0xd04f 28} "2nd Item: Bunch Of Grapes" {dpoke 0xd04f 34} "2nd Item: Candle" {dpoke 0xd04f 11} "2nd Item: Clothes Peg" {dpoke 0xd04f 18} "2nd Item: Coffee" {dpoke 0xd04f 12} "2nd Item: Cold Beer" {dpoke 0xd04f 14} "2nd Item: Cork Screw" {dpoke 0xd04f 29} "2nd Item: Cup And Saucer" {dpoke 0xd04f 33} "2nd Item: Delta Key" {dpoke 0xd04f 22} "2nd Item: Gamma Key" {dpoke 0xd04f 21} "2nd Item: Glass Of Wine" {dpoke 0xd04f 37} "2nd Item: Good Guy" {dpoke 0xd04f 47} "2nd Item: Green Dice" {dpoke 0xd04f 26} "2nd Item: Hammer" {dpoke 0xd04f 10} "2nd Item: Joystick" {dpoke 0xd04f 9} "2nd Item: Keycard Next Sector" {dpoke 0xd04f 39} "2nd Item: Knife And Fork" {dpoke 0xd04f 15} "2nd Item: Light Bulb" {dpoke 0xd04f 23} "2nd Item: Lot Of Fuel" {dpoke 0xd04f 0} "2nd Item: Low Hit Bullets" {dpoke 0xd04f 5} "2nd Item: Money" {dpoke 0xd04f 43} "2nd Item: Nice Apple" {dpoke 0xd04f 32} "2nd Item: No Saucer" {dpoke 0xd04f 46} "2nd Item: Nothing" {dpoke 0xd04f 27} "2nd Item: One Half" {dpoke 0xd04f 40} "2nd Item: Pepper" {dpoke 0xd04f 7} "2nd Item: Petrol Pump" {dpoke 0xd04f 44} "2nd Item: Portable Radio" {dpoke 0xd04f 3} "2nd Item: Question-mark" {dpoke 0xd04f 38} "2nd Item: Red Telephone" {dpoke 0xd04f 1} "2nd Item: Safety Pin" {dpoke 0xd04f 25} "2nd Item: Screw" {dpoke 0xd04f 31} "2nd Item: Tv With Antenna" {dpoke 0xd04f 17} "2nd Item: Vertical Bullets" {dpoke 0xd04f 4} "2nd Item: Wooden Shoe" {dpoke 0xd04f 30} "2nd Item: Yellow Banana" {dpoke 0xd04f 24} "Money" {dpoke 0xd04b 43} "Vertical Bullets" {dpoke 0xd04b 4} "Item: 1st Item: Coin" {dpoke 0xd04d 42} "Item: 1st Item: Cup" {dpoke 0xd04d 45} "Item: 1st Item: Egg" {dpoke 0xd04d 2} "Item: 1st Item: Pear" {dpoke 0xd04d 35} "Item: 1st Item: Salt" {dpoke 0xd04d 6} "Item: 1st Item: Tea" {dpoke 0xd04d 13} "Item: 2nd Item: Coin" {dpoke 0xd04f 42} "Item: 2nd Item: Cup" {dpoke 0xd04f 45} "Item: 2nd Item: Egg" {dpoke 0xd04f 2} "Item: 2nd Item: Pear" {dpoke 0xd04f 35} "Item: 2nd Item: Salt" {dpoke 0xd04f 6} "Item: 2nd Item: Tea" {dpoke 0xd04f 13} "Item In Labyrinth: A Lot Of Fuel" {dpoke 0xd04b 0} "Item In Labyrinth: Alarm Clock" {dpoke 0xd04b 16} "Item In Labyrinth: Alpha Key" {dpoke 0xd04b 19} "Item In Labyrinth: Another Half" {dpoke 0xd04b 41} "Item In Labyrinth: Bad Guy" {dpoke 0xd04b 36} "Item In Labyrinth: Beta Key" {dpoke 0xd04b 20} "Item In Labyrinth: Blue Diamond" {dpoke 0xd04b 8} "Item In Labyrinth: Bottle Of Wine" {dpoke 0xd04b 28} "Item In Labyrinth: Bunch Of Grapes" {dpoke 0xd04b 34} "Item In Labyrinth: Candle" {dpoke 0xd04b 11} "Item In Labyrinth: Clothes Peg" {dpoke 0xd04b 18} "Item In Labyrinth: Coffee" {dpoke 0xd04b 12} "Item In Labyrinth: Coin" {dpoke 0xd04b 42} "Item In Labyrinth: Cold Beer" {dpoke 0xd04b 14} "Item In Labyrinth: Cork Screw" {dpoke 0xd04b 29} "Item In Labyrinth: Cup" {dpoke 0xd04b 45} "Item In Labyrinth: Cup And Saucer" {dpoke 0xd04b 33} "Item In Labyrinth: Delta Key" {dpoke 0xd04b 22} "Item In Labyrinth: Egg" {dpoke 0xd04b 2} "Item In Labyrinth: Fuel" {dpoke 0xd047 32} "Item In Labyrinth: Gamma Key" {dpoke 0xd04b 21} "Item In Labyrinth: Glass Of Wine" {dpoke 0xd04b 37} "Item In Labyrinth: Good Guy" {dpoke 0xd04b 47} "Item In Labyrinth: Green Dice" {dpoke 0xd04b 26} "Item In Labyrinth: Hammer" {dpoke 0xd04b 10} "Item In Labyrinth: Joystick" {dpoke 0xd04b 9} "Item In Labyrinth: Key-card Next Sector" {dpoke 0xd04b 39} "Item In Labyrinth: Knife And Fork" {dpoke 0xd04b 15} "Item In Labyrinth: Light Bulb" {dpoke 0xd04b 23} "Item In Labyrinth: Low Hit Bullets" {dpoke 0xd04b 5} "Item In Labyrinth: Nice Apple" {dpoke 0xd04b 32} "Item In Labyrinth: No Saucer" {dpoke 0xd04b 46} "Item In Labyrinth: Nothing" {dpoke 0xd04b 27} "Item In Labyrinth: One Half" {dpoke 0xd04b 40} "Item In Labyrinth: Pear" {dpoke 0xd04b 35} "Item In Labyrinth: Pepper" {dpoke 0xd04b 7} "Item In Labyrinth: Petrol Pump" {dpoke 0xd04b 44} "Item In Labyrinth: Portable Radio" {dpoke 0xd04b 3} "Item In Labyrinth: Question-mark" {dpoke 0xd04b 38} "Item In Labyrinth: Red Telephone" {dpoke 0xd04b 1} "Item In Labyrinth: Safety Pin" {dpoke 0xd04b 25} "Item In Labyrinth: Salt" {dpoke 0xd04b 6} "Item In Labyrinth: Screw" {dpoke 0xd04b 31} "Item In Labyrinth: Tea" {dpoke 0xd04b 13} "Item In Labyrinth: Tv With Antenna" {dpoke 0xd04b 17} "Item In Labyrinth: Wooden Shoe" {dpoke 0xd04b 30} "Item In Labyrinth: Yellow Banana" {dpoke 0xd04b 24} "Lives: Lives" {dpoke 0xd047 5} } create_trainer "Haunted House" {time 1} { "Candle" {dpoke 0x7480 1;poke 0x748a 1} "Cross" {dpoke 0x7482 1;poke 0x7490 1} "Mirror" {dpoke 0x7481 1;poke 0x748d 1} "Rosary" {dpoke 0x7483 1;poke 0x7493 1} "Shots (f1)" {dpoke 0x747f 4;poke 0x7487 1} "Key: Keys" {dpoke 0x747e 3;poke 0x7484 1} "Lives: Lives" {dpoke 0x4028 6} "Time: Time" {dpoke 0x402c 9;poke 0x402d 9;dpoke 0x402e 9} } create_trainer "Hayabusa - Moonsweeper" {time 1} { "Invincible" {dpoke 0xe170 255} "Lives: Lives" {dpoke 0xe136 255} } create_trainer "Head Over Heels" {time 1} { "Ammo Player 1" {dpoke 0x2243 153} "Electric Bolt Player 1" {dpoke 0x223d 153} "Jump Player 2" {dpoke 0x223e 153} "Lives: Lives Player 1" {dpoke 0x2242 153} "Lives: Lives Player 2" {dpoke 0x2241 153} "Shield: Shield Player 1" {dpoke 0x2240 153} "Shield: Shield Player 2" {dpoke 0x223f 153} } create_trainer "Heist, The" {time 1} { "Level (0-2)" {dpoke 0xe29e 0} "Key: Keys" {dpoke 0xe2aa 153} "Lives: Lives" {dpoke 0xe2a8 153} "Time: Time" {dpoke 0xe2ab 89} } create_trainer "Hercule" {time 1} { "Hercule Vitality" {dpoke 0xfed7 8;poke 0xfed8 9;dpoke 0xfed9 9;dpoke 0xfeda 9} "Monster Is Dead" {dpoke 0xfe7a 3} "Monster Is Fast" {dpoke 0xfe7a 2} "Monster Is Slow" {dpoke 0xfe7a 1} "Monster Vitality" {dpoke 0xfedf 0;poke 0xfee0 0;dpoke 0xfee1 0;dpoke 0xfee2 0} "Invulnerable: Invulnerable" {dpoke 0xfe37 10} "Lives: Lives" {dpoke 0xfe7f 9} } create_trainer "HERO Cas version" {time 5} { "Lives: Lives" {dpoke 0xe038 6} "Time: Power/time" {dpoke 0xe17b 100} "Weapon: Bombs (de-select To Get Bonus)" {dpoke 0xe039 6} } create_trainer "Herzog" {time 2} { "Base Damage" {dpoke 0xd034 0} "Blow Up Player 2 Base" {dpoke 0xd087 255} "Max Money" {dpoke 0xd035 255;poke 0xd036 255} "Own Damage" {dpoke 0xd033 0} "Wait Off Cheat (ctrl-esc-f5)" {dpoke 0xd1b3 255} "Lives: Lives" {dpoke 0xd040 99} } create_trainer "High Way Star" {time 1} { "Cars" {dpoke 0xe211 10} "Fuel (de-select To Get Bonus)" {dpoke 0xe20d 255;poke 0xe20e 5} } create_trainer "Highway Encounter" {time 1} { "Power: Power" {dpoke 0x8f18 151} "Time: Time" {dpoke 0x8f1a 0} } create_trainer "Hinotori - Firebird" {time 2} { "1st Middle Stone" {dpoke 0xc8bc 1} "200 Money" {dpoke 0xc845 200} "2nd Middle Stone" {dpoke 0xc8c0 1} "3rd Middle Stone" {dpoke 0xc8c4 1} "4rd Middle Stone" {dpoke 0xc8c8 1} "5th Middle Stone" {dpoke 0xc8cc 1} "Autoshot" {dpoke 0xc4e1 2;poke 0xc85c 2} "Bug (red Beetle?)" {dpoke 0xc85c 9} "Combo With Game Master" {dpoke 0xc110 1} "Compass" {dpoke 0xc884 1} "Fifth Main Stone" {dpoke 0xc8ec 1} "First Main Stone" {dpoke 0xc8dc 1} "Fourth Main Stone" {dpoke 0xc8e8 1} "Ilovehinotori" {dpoke 0xc4e2 1} "Leaflets (?)" {dpoke 0xc878 9} "Lower Fifth Stone" {dpoke 0xc8b4 1} "Lower First Stone" {dpoke 0xc8a4 1} "Lower Fourth Stone" {dpoke 0xc8b0 1} "Lower Second Stone" {dpoke 0xc8a8 1} "Lower Sixth Stone" {dpoke 0xc8b8 1} "Lower Third Stone" {dpoke 0xc8ac 1} "Packages" {dpoke 0xc870 9} "Scrolls" {dpoke 0xc874 9} "Second Main Stone" {dpoke 0xc8e0 1} "Third Main Stone" {dpoke 0xc8e4 1} "Top 1st Last Stone" {dpoke 0xc88c 1} "Top 2nd Last Stone" {dpoke 0xc890 1} "Top 3th Last Stone" {dpoke 0xc894 1} "Top 4th Last Stone" {dpoke 0xc898 1} "Top 5th Last Stone" {dpoke 0xc89c 1} "Top Last Stone" {dpoke 0xc8a0 1} "Turbo" {dpoke 0xc850 3} "Lives: 99 Lives" {dpoke 0xc160 153} } create_trainer "Hole In One Special" {time 2} { "Always Have Hole In One (very Lame)" {dpoke 0xc0da 1} } create_trainer "Hopper" {time 1} { "Lives: Lives" {dpoke 0x8816 3} "Time: Time" {dpoke 0x883e 0} } create_trainer "Howard The Duck" {time 1} { "Backpack" {dpoke 0xb6be 255} "Flyer Move" {dpoke 0xb6cb 10} "Move To The Right" {dpoke 0xb6cb 4} "Move To The Top" {dpoke 0xb6cb 2} "No Mutants" {dpoke 0xb949 1} "Protected From Stones" {dpoke 0xba48 255} "Walk On Water" {dpoke 0xb6cb 1} "Lives: Lives" {dpoke 0xb6a2 255} "Time: Time" {dpoke 0xb6a5 89} } create_trainer "Humphrey" {time 10} { "Lives: Lives" {dpoke 0x6797 9} } create_trainer "Hunchback" {time 0.5} { "Arrow 1" {dpoke 0x9c6a 100} "Arrow 2" {dpoke 0x9c6e 100} "Guardian 1" {dpoke 0x90ba 10} "Guardian 2" {dpoke 0x90be 10} "Guardian 3" {dpoke 0x90c2 10} "Y-pos Ball 1" {dpoke 0x9c72 100} "Y-pos Ball 2" {dpoke 0x9c76 100} "Y-pos Wall Crawler" {dpoke 0x9c92 120} "Lives: Lives" {dpoke 0x9114 6} } create_trainer "Hundra" {time 1} { "Axe Always Available" {dpoke 0x8ff8 0} "Has Three Crystals" {dpoke 0x8ff7 3} "No Damage When Falling In Water" {dpoke 0x8ff6 1} "Protected From Enemies" {dpoke 0x8ff9 255} "Energy: Energy" {dpoke 0x8fee 229} "Lives: Lives" {dpoke 0x8fc5 4} } create_trainer "Hustle Chumi" {time 1} { "Level (1-99;0)" {dpoke 0xe106 1} "Lives: Lives" {dpoke 0xe153 9} "Time: Infinite Time" {dpoke 0xe1d7 90;poke 0xe1d9 0} } create_trainer "Hustle Chumy" {time 1} { "Level (1-99;0)" {dpoke 0xb106 1} "Lives: Lives" {dpoke 0xb157 9} "Time: Infinite Time" {dpoke 0xb1db 90;poke 0xb1dd 0} } create_trainer "Hydefos" {time 0.25} { "Lives: Lives" {dpoke 0xd404 100} "Power: Invincible" {dpoke 0xc10a 8} "Power: Power" {dpoke 0xc118 100;poke 0xc11c 255} "Speed: Speed" {dpoke 0xc106 16} "Weapon: Laser Pods" {dpoke 0x111 199;poke 0xc110 199} } create_trainer "Hydlide (MSX2)" {time 1} { "Blue Crystal" {dpoke 0xa7d5 255} "Cross" {dpoke 0xa7d1 255} "Fairy 1" {dpoke 0xa7d8 255} "Fairy 2" {dpoke 0xa7d9 255} "Fairy 3" {dpoke 0xa7da 255} "Green Crystal" {dpoke 0xa7d7 255} "Lamp" {dpoke 0xa7d0 255} "Level Up After Killing One Enemy" {dpoke 0xa7be 100} "Pink Crystal" {dpoke 0xa7d6 255} "Stats Max" {dpoke 0xa7bd 100;poke 0xa7bf 9;dpoke 0xa7c3 100} "Tea Pot" {dpoke 0xa7d3 255} "Water Can" {dpoke 0xa7d2 255} "Key: Key" {dpoke 0xa7d4 255} "Power: Power" {dpoke 0xa7bc 100} "Shield: Shield" {dpoke 0xa7cf 255} "Weapon: Sword" {dpoke 0xa7ce 255} } create_trainer "Hydlide 1" {time 1} { "Level Up After Killing One Enemy" {dpoke 0xe004 100} "Power: Power" {dpoke 0xe002 100} } create_trainer "Hydlide 2 - Shine Of Darkness" {time 1} { "Life Max" {dpoke 0xe01d 153;poke 0xe01f 255} "Magic Max" {dpoke 0xe025 153} "Money" {dpoke 0xe054 153;poke 0xe055 153;dpoke 0xe056 153} "Start Points" {dpoke 0xf10b 153} "Strength Max" {dpoke 0xe022 153} } create_trainer "Hydlide 3 - The Space Memories MSX2 Version" {time 2} { "Agility" {dpoke 0xd02d 255;poke 0xd02e 255} "Armor Class" {dpoke 0xd02b 255;poke 0xd02c 255} "Attack Points" {dpoke 0xd029 255;poke 0xd02a 255} "Charm" {dpoke 0xd036 255} "Dexterity" {dpoke 0xd030 255} "Exp" {dpoke 0xd039 255;poke 0xd03a 255;dpoke 0xd03b 255} "Gold" {dpoke 0xd087 255} "Intelligence" {dpoke 0xd032 255} "Luck" {dpoke 0xd034 255} "Magic Points" {dpoke 0xd022 255;poke 0xd023 255;dpoke 0xd024 255;dpoke 0xd025 255} "Max Life" {dpoke 0xd018 255;poke 0xd020 255} "Mind Force" {dpoke 0xd038 255} "Unknown" {dpoke 0xd019 255;poke 0xd01b 255;dpoke 0xd03d 255;dpoke 0xd0a9 255} "Power: Current Power" {dpoke 0xd01a 255} } create_trainer "Hype" {time 1} { "Invincible" {dpoke 0xc00a 255} "Programmer's Mode (try B/l/numbers/function Keys)" {dpoke 0xc00d 255} } create_trainer "Hyper Rally" {time 2} { "Always First Place" {dpoke 0xe05b 0;poke 0xe05c 1} "Fuel" {dpoke 0xe065 255} "High Gear" {dpoke 0xe086 0} "Low Gear" {dpoke 0xe086 1} "Speed" {dpoke 0xe085 143} "Time: Time" {dpoke 0xe068 0} } create_trainer "Hyper Sports 1" {time 1} { "Always Qualify" {dpoke 0xe059 153;poke 0xe05a 9} } create_trainer "Hyper Sports 2" {time 1} { "Always Full Power" {dpoke 0xe101 255} "Always Qualify" {dpoke 0xe088 153;poke 0xe089 153} "Unlimited Arrows" {dpoke 0x111 8} "Time: Freezes Time" {dpoke 0xe1a6 15} } create_trainer "Hyper Sports 3" {frame} { "Top Speed Cycling" {dpoke 0xe0ad 255} "Top Speed Long Jump" {dpoke 0xe121 255} "Time: Freeze Time" {dpoke 0xe0d0 0} } create_trainer "Ice" {time 1} { "Lives: Lives" {dpoke 0x4018 9} } create_trainer "Ice King, The" {time 1} { "Energy: Energy" {dpoke 0xda0d 23} "Lives: Lives" {dpoke 0xd009 42} } create_trainer "Ice World" {time 10} { "Lives: Lives" {dpoke 0xe00b 5} } create_trainer "Iga Ninpou Chou Mangetsujou no Tatakai" {time 1} { "Lives: Lives" {dpoke 0xe00b 153} } create_trainer "Iga Ninpouten 1" {time 1} { "Lives: Lives" {dpoke 0xe00b 99} "Time: Time" {dpoke 0xe080 9} } create_trainer "Ikari" {time 2} { "Player 1 Primary Weapon High Explosive Tank Bullets" {dpoke 0xc41d 6} "Player 1 Primary Weapon Red Bullets" {dpoke 0xc41d 4} "Player 1 Primary Weapon Regular Tank Bullets" {dpoke 0xc41d 5} "Player 1 Secondary Weapon High Explosive Tank Bullets" {dpoke 0xc41e 6} "Player 1 Secondary Weapon Red Bullets" {dpoke 0xc41e 4} "Player 1 Secondary Weapon Regular Tank Bullets" {dpoke 0xc41e 5} "Player 2 Primary Weapon High Explosive Tank Bullets" {dpoke 0xc43d 6} "Player 2 Primary Weapon Red Bullets" {dpoke 0xc43d 4} "Player 2 Primary Weapon Regular Tank Bullets" {dpoke 0xc43d 5} "Player 2 Secondary Weapon High Explosive Tank Bullets" {dpoke 0xc43e 6} "Player 2 Secondary Weapon Red Bullets" {dpoke 0xc43e 4} "Player 2 Secondary Weapon Regular Tank Bullets" {dpoke 0xc43e 5} "Rapid Fire" {dpoke 0xc418 0} "Lives: Lives Player 1" {dpoke 0xc415 99} "Lives: Lives Player 2" {dpoke 0xc435 99} "Weapon: Player 1 Primary Weapon 3 Way Shot (not Deadly)" {dpoke 0xc41d 2} "Weapon: Player 1 Primary Weapon 7 Way Shot (not Deadly)" {dpoke 0xc41d 3} "Weapon: Player 1 Primary Weapon Hand Grenades" {dpoke 0xc41d 7} "Weapon: Player 1 Primary Weapon High Explosive Hand Grenades" {dpoke 0xc41d 8} "Weapon: Player 1 Primary Weapon Nothing" {dpoke 0xc41d 0} "Weapon: Player 1 Primary Weapon Regular" {dpoke 0xc41d 1} "Weapon: Player 1 Secondary Weapon 3 Way Shot (not Deadly)" {dpoke 0xc41e 2} "Weapon: Player 1 Secondary Weapon 7 Way Shot (not Deadly)" {dpoke 0xc41e 3} "Weapon: Player 1 Secondary Weapon Hand Grenades" {dpoke 0xc41e 7} "Weapon: Player 1 Secondary Weapon High Explosive Hand Grenades" {dpoke 0xc41e 8} "Weapon: Player 1 Secondary Weapon Nothing" {dpoke 0xc41e 0} "Weapon: Player 1 Secondary Weapon Regular" {dpoke 0xc41e 1} "Weapon: Player 2 Primary Weapon 3 Way Shot (not Deadly)" {dpoke 0xc43d 2} "Weapon: Player 2 Primary Weapon 7 Way Shot (not Deadly)" {dpoke 0xc43d 3} "Weapon: Player 2 Primary Weapon Hand Grenades" {dpoke 0xc43d 7} "Weapon: Player 2 Primary Weapon High Explosive Hand Grenades" {dpoke 0xc43d 8} "Weapon: Player 2 Primary Weapon Nothing" {dpoke 0xc43d 0} "Weapon: Player 2 Primary Weapon Regular" {dpoke 0xc43d 1} "Weapon: Player 2 Secondary Weapon 3 Way Shot (not Deadly)" {dpoke 0xc43e 2} "Weapon: Player 2 Secondary Weapon 7 Way Shot (not Deadly)" {dpoke 0xc43e 3} "Weapon: Player 2 Secondary Weapon Hand Grenades" {dpoke 0xc43e 7} "Weapon: Player 2 Secondary Weapon High Explosive Hand Grenades" {dpoke 0xc43e 8} "Weapon: Player 2 Secondary Weapon Nothing" {dpoke 0xc43e 0} "Weapon: Player 2 Secondary Weapon Regular" {dpoke 0xc43e 1} } create_trainer "Illusion City" {time 1} { "Defense" {dpoke 0xc34d 255} "Exp Kash" {dpoke 0xc33c 255;poke 0xc33d 255} "Exp Mei Hong" {dpoke 0xc29c 255;poke 0xc29d 255} "Exp Old Man" {dpoke 0xc2ec 255;poke 0xc2ed 255} "Exp Tien Ren" {dpoke 0xc274 255;poke 0xc275 255} "Extended Agility Mei Hong" {dpoke 0xc2a3 99} "Extended Agility Tien Ren" {dpoke 0xc27b 99} "Extended Defence Mei Hong" {dpoke 0xc2ac 3;poke 0xc2ad 231} "Extended Defense Tien Ren" {dpoke 0xc285 231;poke 0xc286 3} "Extended Offence Mei Hong" {dpoke 0xc2b0 231;poke 0xc2b1 3} "Extended Offense Tien Ren" {dpoke 0xc288 231;poke 0xc289 3} "Hp Mei Hong" {dpoke 0xf636 231;poke 0xf637 3} "Hp Tien Ren" {dpoke 0xc282 231;poke 0xc283 3} "Level Mei Hong" {dpoke 0xc29f 99} "Level Old Man" {dpoke 0xc2ef 99} "Level Tien Ren" {dpoke 0xc277 99} "Money" {dpoke 0xc267 255;poke 0xc268 255} } create_trainer "Illusions" {time 1} { "Faster Access To Next Round" {dpoke 0xf07c 1} "Time: Time" {dpoke 0xf07d 9;poke 0xf07e 60} } create_trainer "Inca 1" {time 1} { "Gold Player 1" {dpoke 0xde8c 255} "Gold Player 2" {dpoke 0xdebc 255} "X-pos Easy Start (de-select To Move!)" {dpoke 0xdf23 20} "Invulnerable: Invulnerable" {dpoke 0xdf2d 0} "Lives: Lives Player 1" {dpoke 0xde86 9} "Lives: Lives Player 2" {dpoke 0xdeb6 9} } create_trainer "Indian No Bouken" {time 1} { "Boomerang" {dpoke 0xe610 99} "Lives: Lives" {dpoke 0xe60e 255} } create_trainer "Indiana Jones and the Last Crusade" {time 1} { "Whip" {dpoke 0x7abe 10} "Lives: Lives" {dpoke 0x7ac1 9} "Power: Power" {dpoke 0x7ac0 255} } create_trainer "Indiana Jones And the Temple of Doom" {time 1} { "Lives: Lives" {dpoke 0xc234 59} } create_trainer "Indy 500" {time 1} { "Always Fuel" {dpoke 0x7e22 0} "No Damage" {dpoke 0x7e23 0;poke 0x7e27 255} } create_trainer "Ink" {time 1} { "Always Red Crabs" {dpoke 0xe46f 3} "Demo Mode (all Stages Are Cleared)" {dpoke 0xe320 0} "Exxon" {dpoke 0xe315 16} "Invulnerable (de-select At End Of Stage)" {dpoke 0xe3d1 2;poke 0xe3d2 0} "No Octopus" {dpoke 0xe4a0 1} "No Starfish" {dpoke 0xe4b0 1} "Panic" {dpoke 0xe313 255} "Stage (1-24 Normal Or 1-25 Hard)" {dpoke 0xe310 1} "Lives: Lives" {dpoke 0xe31c 5} } create_trainer "Ink CAS" {time 1} { "Always Red Crabs" {dpoke 0x715f 3} "Demo Mode (all Stages Are Cleared)" {dpoke 0x7010 0} "Exxon" {dpoke 0x7005 16} "Invulnerable (de-select At End Of Stage)" {dpoke 0x70c1 2;poke 0x70c2 0} "No Octopus" {dpoke 0x7190 1} "No Starfish" {dpoke 0x71a0 1} "Panic" {dpoke 0x7003 255} "Stage (1-24 Normal Or 1-25 Hard)" {dpoke 0x7000 1} "Lives: Lives" {dpoke 0x700c 5} } create_trainer "Inter Stellar" {time 1} { "Lives: Infinite Lives" {dpoke 0xf10b 3} } create_trainer "Invasion of the Zombie Monsters" {time 1} { "Low End Boss Power" {dpoke 0xd72a 0} "Time Stays @ 99" {dpoke 0xc012 99} "Weapon: Weapon Power Level 0" {dpoke 0xd887 0} "Weapon: Weapon Power Level 1" {dpoke 0xd887 1} "Weapon: Weapon Power Level 2" {dpoke 0xfd88 2} "Weapon: Weapon Power Level 3" {dpoke 0xd887 3} "Weapon: Weapon Power Level 4" {dpoke 0xd887 4} } create_trainer "Iriegas - Illegus" {time 1} { "Can See In Dark (f2)" {dpoke 0xee9a 255} "Stamina" {dpoke 0xee99 255} "Time: Time" {dpoke 0xee5a 0} "Time: Time In Dark" {dpoke 0xee7c 12} } create_trainer "Iron Star" {time 1} { "Time For Boss Battle" {dpoke 0xe341 60} "Invulnerable: Invulnerable" {dpoke 0xe306 0} "Lives: Lives" {dpoke 0xe2fb 55} } create_trainer "Issunhoushi No Donnamondai - Little Samurai" {time 1} { "Life Bar" {dpoke 0xe1b5 64} "Lives: Lives" {dpoke 0xe001 0x99} } create_trainer "J.P. Winkle" {time 2} { "Axe" {dpoke 0xe0b0 30;poke 0xe0bc 255} "Bible" {dpoke 0xe098 152} "Blue Lamp" {dpoke 0xe0ae 255} "Cross" {dpoke 0xe0ac 255} "Hammer" {dpoke 0xe0b1 20;poke 0xe0bd 255} "Invincible" {dpoke 0xe0ab 255} "Red Lamp" {dpoke 0xe0ad 255} "Wings" {dpoke 0xe0af 255} "Key: Keys" {dpoke 0xe032 152} "Lives: Lives" {dpoke 0xe007 152} } create_trainer "Jack the Nipper" {time 1} { "Power: Power Bar" {dpoke 0x2c56 0} } create_trainer "Jack the Nipper in Coconut Capers" {time 1} { "Do Not Die While Falling" {dpoke 0x8826 0} "Invincible" {dpoke 0x881d 60} "Lives: Lives" {dpoke 0x8834 9} } create_trainer "Jackson City" {time 1} { "Fuel" {dpoke 0x98b5 127} "Invulnerability Time" {dpoke 0x989d 99} "Lives: Lives" {dpoke 0x989f 9} } create_trainer "Jagur" {time 1} { "Money" {dpoke 0xe020 255;poke 0xe021 127} "Power: Power" {dpoke 0xec00 99} } create_trainer "Jaws" {time 1} { "Invincible" {dpoke 0x84a5 255} "Max Hits" {dpoke 0x84a7 128} "Lives: Lives" {dpoke 0x8078 153} "Time: Time" {dpoke 0x806c 255} } create_trainer "Jet Bomber" {time 1} { "Always Fuel" {dpoke 0x406d 0} "Lives: Lives" {dpoke 0x4044 4} } create_trainer "Jet Fighter Eurosoft" {time 1} { "Always Fuel" {dpoke 0x4080 0} "Lives: Lives" {dpoke 0x4057 4} } create_trainer "Jet Set Willy" {time 0.5} { "Y-pos Enemy 1" {dpoke 0xf14b 170} "Y-pos Enemy 2" {dpoke 0xf154 170} "Y-pos Enemy 3" {dpoke 0xf15d 170} "Y-pos Enemy 4" {dpoke 0xf166 170} "Y-pos Enemy 5" {dpoke 0xf16f 170} "Y-pos Enemy 6" {dpoke 0xf181 170} "Y-pos Enemy 7" {dpoke 0xf178 170} "Lives: Lives" {dpoke 0xf25d 17} } create_trainer "Jetpac" {time 0.1} { "Enemy: Easy End Boss" {dpoke 0xee61 1} "Life: Lives" {dpoke 0xee36 0x9} "Life: Power" {dpoke 0xee67 112} } create_trainer "Jimmy Quest" {time 1} { "Lives: Lives" {dpoke 0xa9d5 9} } create_trainer "Joe Blade" {time 1} { "Ammo" {dpoke 0x8d98 255} "Let Enemies Think You Are One Of Them" {dpoke 0x8dbe 1} "Max Hits" {dpoke 0x8d97 128} "Key: Keys" {dpoke 0x8d8e 57;poke 0x8d8f 57} "Weapon: Bomb Defusing Timer" {dpoke 0x8dc0 41} "Weapon: Bombs" {dpoke 0x8d90 57;poke 0x8d91 57} } create_trainer "Jungle Hunt" {time 1} { "Lives: Lives" {dpoke 0x732d 255} } create_trainer "Juno First" {time 1} { "Lives: Lives" {dpoke 0xe062 99} "Time: Ininite Time" {dpoke 0xe065 96} } create_trainer "Kageno Densetsu - The Legend Of Kage" {time 1} { "Lives: Lives" {dpoke 0xc03b 9} } create_trainer "Kenja no ishi - Stone of Wisdom, The" {time 2} { "Intelligence" {dpoke 0xe040 55} "Life" {dpoke 0xe044 55} "Power: Power" {dpoke 0xe042 55} } create_trainer "Kick It!" {time 1} { "First Guy Is Frozen" {dpoke 0x50f5 0} "Fourth Guy Is Frozen" {dpoke 0x5101 0} "Second Guy Is Frozen" {dpoke 0x50f9 0} "Third Guy Is Frozen" {dpoke 0x50fd 0} "Lives: Lives" {dpoke 0x402a 4} "Weapon: No Countdown For Bombs" {dpoke 0x5254 120} } create_trainer "Kiki kaikai - Mystery" {time 2} { "Max Shot" {dpoke 0xc070 9} "Lives: Lives" {dpoke 0xc025 255} } create_trainer "King & Balloon" {time 60} { "Lives: Lives" {dpoke 0xe490 255} } create_trainer "King Kong 2" {time 2} { "Bosses Die After 1 Hit" {dpoke 0xd00d 1} "Bridge Over Gap (area 8)" {dpoke 0xc810 1;poke 0xc811 72;dpoke 0xc812 72} "Kill 1 Batt To Get Meat (area 8)" {dpoke 0xdfd1 1} "Kill 1 Bowler To Get Axe (area 2)" {dpoke 0xdfc2 1} "Kill 1 Bowler To Get Message (area 3)" {dpoke 0xdfd6 1} "Kill 1 Caveman To Get Fireballs (area 8)" {dpoke 0xdfd8 1} "Kill 1 Clingman To Get Message (area 8)" {dpoke 0xdfda 1} "Kill 1 Crow To Get Message (area 5)" {dpoke 0xdfd9 1} "Kill 1 Dungroll To Get Stones (area 3)" {dpoke 0xdfd3 1} "Kill 1 Firetotem To Get Club (area 3)" {dpoke 0xdfc3 1} "Kill 1 Grassogre To Get Rod (area 1)" {dpoke 0xdfc6 1} "Kill 1 Ratt To Get Herb (area 1)" {dpoke 0xdfd2 1} "Kill 1 Sealouse To Get Herb (areas 2 And 6)" {dpoke 0xdfd5 1} "Kill 1 Thickskin To Get Whirl Spell (area 2)" {dpoke 0xdfd7 1} "Kill 1 Watcher To Open Stairs (area 4)" {dpoke 0xdfc5 1} "Kill 1 Whitefire To Get Liquid Of Lamera (area 2)" {dpoke 0xdfdb 1} "Kill High Centipede 1 Time To Get Message (area 2)" {dpoke 0xdfc1 2} "Kill Low Centipede 1 Time To Get Message (area 2)" {dpoke 0xdfc0 2} "Most Enemies Die After 1 Hit" {dpoke 0xd08d 1;poke 0xd10d 1;dpoke 0xd18d 1;dpoke 0xd20d 1;dpoke 0xd28d 1;dpoke 0xd30d 1;dpoke 0xd38d 1;dpoke 0xd40d 1;dpoke 0xd48d 1} "Open 1st Barrier (area 3)" {dpoke 0xc596 0} "Open 1st Secret Cave (area 1)" {dpoke 0xc587 0} "Open 2nd Barrier (area 3)" {dpoke 0xc597 0} "Open 2nd Secret Cave (area 1)" {dpoke 0xc589 0} "Open 3rd Barrier (area 3)" {dpoke 0xc598 0} "Open 3rd Secret Cave (area 1)" {dpoke 0xc586 0} "Open 4th Barrier (area 1)" {dpoke 0xc599 0} "Open 4th Secret Cave (area 2)" {dpoke 0xc588 0} "Open 5th Barrier (area 4)" {dpoke 0xc59a 0} "Open 5th Secret Cave (area 4)" {dpoke 0xc58b 0} "Open 6th Barrier (area 7)" {dpoke 0xc59b 0} "Open 6th Secret Cave (area 6)" {dpoke 0xc58a 0} "Open 7th Barrier (area 7)" {dpoke 0xc59c 0} "Open Access 1 To Whisper (area 7)" {dpoke 0xc593 0} "Open Access 2 To Whisper (area 7)" {dpoke 0xc594 0} "Open Access To Area 2" {dpoke 0xc583 0} "Open Access To Area 3" {dpoke 0xc582 0} "Open Access To Area 4 Part 1" {dpoke 0xc59d 0} "Open Access To Area 4 Part 2" {dpoke 0xc581 0} "Open Access To Area 5" {dpoke 0xc585 0} "Open Access To Area 7" {dpoke 0xc595 0} "Open Access To Area 8 Part 1" {dpoke 0xc58f 0} "Open Access To Area 8 Part 2" {dpoke 0xc58e 0} "Open Access To Bigflower (area 8)" {dpoke 0xc59e 0} "Open Access To Bigguard (area 4)" {dpoke 0xc59f 0} "Open Access To End Part (area 8)" {dpoke 0xc584 0} "Open Access To Final Boss (area 8)" {dpoke 0xc580 0} "Open Access To Kong's Tear (area 7)" {dpoke 0xc592 0} "Open Access To Landing Field (area 8)" {dpoke 0xc590 0} "Open Access To Savage (area 7)" {dpoke 0xc591 0} "Protected From Bug's Poison" {dpoke 0xc146 0} "Item: Agnos Key In Inventory" {dpoke 0xc2d0 20} "Item: Amulet In Inventory" {dpoke 0xc2d8 24} "Item: Axe In Inventory" {dpoke 0xc2a8 5} "Item: Book In Inventory" {dpoke 0xc2e4 33} "Item: Boomerangs In Inventory" {dpoke 0xc2b2 10} "Item: Club In Inventory" {dpoke 0xc2a4 3} "Item: Dorian Fruit In Inventory" {dpoke 0xc2d6 23} "Item: Fireballs In Inventory" {dpoke 0xc2b0 9} "Item: Harpoon In Inventory" {dpoke 0xc2b4 11} "Item: Herbs In Inventory" {dpoke 0xc2c8 16} "Item: Iron Keys In Inventory" {dpoke 0xc2e8 36} "Item: Knife In Inventory" {dpoke 0xc2a0 1} "Item: Kong's Claw In Inventory" {dpoke 0xc2ac 7} "Item: Kong's Eye In Inventory" {dpoke 0xc2dc 26} "Item: Kong's Fang In Inventory" {dpoke 0xc2cc 18} "Item: Kong's Roar In Inventory" {dpoke 0xc2de 27} "Item: Kong's Tear In Inventory" {dpoke 0xc2d4 22} "Item: Lakuna's Orb In Inventory" {dpoke 0xc2e0 28} "Item: Letter In Inventory (see Remark)" {dpoke 0xc2e8 32} "Item: Liquid Of Lamera In Inventory" {dpoke 0xc2c6 15} "Item: Makapora Spell In Inventory" {dpoke 0xc2c4 14} "Item: Meat In Inventory" {dpoke 0xc2d2 21} "Item: Money Bag In Inventory (see Remark)" {dpoke 0xc2aa 6} "Item: Nemuri Spell In Inventory" {dpoke 0xc2c2 13} "Item: Papyrus In Inventory" {dpoke 0xc2e2 29} "Item: Power Orb In Inventory" {dpoke 0xc2ca 17} "Item: Radio In Inventory" {dpoke 0xc2da 25} "Item: Radio Is Not Broken" {dpoke 0xc14e 1} "Item: Rod In Inventory" {dpoke 0xc2a2 2} "Item: Stones In Inventory" {dpoke 0xc2ae 8} "Item: Sword In Inventory" {dpoke 0xc2a6 4} "Item: Temusa Spell In Inventory" {dpoke 0xc2c0 12} "Item: Water Sandals In Inventory (see Remark)" {dpoke 0xc2e8 30} "Item: Whirl Spell In Inventory" {dpoke 0xc2e6 34} "Item: Whisper In Inventory" {dpoke 0xc2ce 19} "Item: Wood Key In Inventory (see Remark)" {dpoke 0xc2e8 35} "Item: Yellow Stones In Inventory" {dpoke 0xc2b6 31} "Received Items: Always Boomerangs (don't Select Before Taking First Boomerang)" {dpoke 0xc349 153} "Received Items: Always Fireballs" {dpoke 0xc348 153} "Received Items: Always Herbs" {dpoke 0xc34f 153} "Received Items: Always Iron Keys" {dpoke 0xc363 153} "Received Items: Always Meat" {dpoke 0xc354 153} "Received Items: Always Papyrus" {dpoke 0xc35c 153} "Received Items: Always Stones" {dpoke 0xc347 153} "Received Items: Always Water Sandals" {dpoke 0xc35d 153} "Received Items: Always Yellow Stones" {dpoke 0xc35e 153} "Status: Days Passed" {dpoke 0xc127 0;poke 0xc128 0} "Status: Enemies Are Frozen" {dpoke 0xc147 255} "Status: Experience" {dpoke 0xc12b 153;poke 0xc12c 153} "Status: Invulnerable" {dpoke 0xc325 255} "Status: Level" {dpoke 0xc12e 153} "Status: Life" {dpoke 0xc129 153;poke 0xc12a 153} "Status: Magic" {dpoke 0xc12f 153} "Status: Money" {dpoke 0xc135 153;poke 0xc136 153} "Status: Number Of Continues" {dpoke 0xc16e 0} "Status: Speed" {dpoke 0xc2f0 153} "Weapon Select: Selected Shot 1 - Axe" {dpoke 0xc140 5} "Weapon Select: Selected Shot 1 - Boomerangs" {dpoke 0xc140 10} "Weapon Select: Selected Shot 1 - Club" {dpoke 0xc140 3} "Weapon Select: Selected Shot 1 - Fireballs" {dpoke 0xc140 9} "Weapon Select: Selected Shot 1 - Harpoon" {dpoke 0xc140 11} "Weapon Select: Selected Shot 1 - Knife" {dpoke 0xc140 1} "Weapon Select: Selected Shot 1 - Kong's Claw" {dpoke 0xc140 7} "Weapon Select: Selected Shot 1 - Long Sword (see Remark)" {dpoke 0xc140 6} "Weapon Select: Selected Shot 1 - Rod" {dpoke 0xc140 2} "Weapon Select: Selected Shot 1 - Stones" {dpoke 0xc140 8} "Weapon Select: Selected Shot 1 - Sword" {dpoke 0xc140 4} "Weapon Select: Selected Shot 1 - Yellow Stones" {dpoke 0xc140 31} "Weapon Select: Selected Shot 2 - Agnos Key" {dpoke 0xc141 20} "Weapon Select: Selected Shot 2 - Amulet" {dpoke 0xc141 24} "Weapon Select: Selected Shot 2 - Book" {dpoke 0xc141 33} "Weapon Select: Selected Shot 2 - Dorian Fruit" {dpoke 0xc141 23} "Weapon Select: Selected Shot 2 - Herbs" {dpoke 0xc141 16} "Weapon Select: Selected Shot 2 - Iron Keys" {dpoke 0xc141 36} "Weapon Select: Selected Shot 2 - Kong's Eye" {dpoke 0xc141 26} "Weapon Select: Selected Shot 2 - Kong's Fang" {dpoke 0xc141 18} "Weapon Select: Selected Shot 2 - Kong's Roar" {dpoke 0xc141 27} "Weapon Select: Selected Shot 2 - Kong's Tear" {dpoke 0xc141 22} "Weapon Select: Selected Shot 2 - Lakuna's Orb" {dpoke 0xc141 28} "Weapon Select: Selected Shot 2 - Letter" {dpoke 0xc141 32} "Weapon Select: Selected Shot 2 - Liquid Of Lamera" {dpoke 0xc141 15} "Weapon Select: Selected Shot 2 - Makapora Spell" {dpoke 0xc141 14} "Weapon Select: Selected Shot 2 - Meat" {dpoke 0xc141 21} "Weapon Select: Selected Shot 2 - Nemuri Spell" {dpoke 0xc141 13} "Weapon Select: Selected Shot 2 - Papyrus" {dpoke 0xc141 29} "Weapon Select: Selected Shot 2 - Power Orb" {dpoke 0xc141 17} "Weapon Select: Selected Shot 2 - Radio" {dpoke 0xc141 25} "Weapon Select: Selected Shot 2 - Temusa Spell" {dpoke 0xc141 12} "Weapon Select: Selected Shot 2 - Water Sandals" {dpoke 0xc141 30} "Weapon Select: Selected Shot 2 - Whirl Spell" {dpoke 0xc141 34} "Weapon Select: Selected Shot 2 - Whisper" {dpoke 0xc141 19} "Weapon Select: Selected Shot 2 - Wood Key" {dpoke 0xc141 35} } create_trainer "King's Knight" {frame} { "Top Speed" {dpoke 0xef44 255} } create_trainer "King's Valley 1" {time 2} { "Doors Always Visible" {dpoke 0xe1f2 1;poke 0xe1f3 1} "No Map" {dpoke 0xe051 1} "No Monster 1" {dpoke 0xe16a 6} "No Monster 2" {dpoke 0xe180 6} "No Monster 3" {dpoke 0xe196 6} "Pick (de-select To Jump)" {dpoke 0xe144 32} "Pyramid (1-15)" {dpoke 0xe055 1} "Lives: Lives" {dpoke 0xe050 99} "Stage: Stage (1-99;0)" {dpoke 0xe054 1} "Weapon: Sword (de-select To Jump)" {dpoke 0xe144 16} } create_trainer "King's Valley 2 - The Seal Of El Giza - MSX1 Version" {time 2} { "Door Always Opens" {dpoke 0xe2f5 0} "Festival Cheat" {dpoke 0xe255 1} "Little Pyramids Always Visible (see Remark)" {dpoke 0xef11 1} "No Monster 1" {dpoke 0xe500 0} "No Monster 2" {dpoke 0xe520 0} "No Monster 3" {dpoke 0xe540 0} "Try Again Cheat" {dpoke 0xe217 1} "X-pos (0-255)" {dpoke 0xe284 0} "Y-pos (0-170)" {dpoke 0xe282 0} "Lives: Lives" {dpoke 0xe240 153} "Stage: Stage (1-60)" {dpoke 0xe242 1} } create_trainer "King's Valley 2 - The Seal Of El Giza - MSX2 Version" {time 2} { "Door Always Opens" {dpoke 0xe2f5 0} "Festival Cheat" {dpoke 0xe255 1} "Little Pyramids Always Visible (see Remark)" {dpoke 0xef11 1} "No Monster 1" {dpoke 0xe500 0} "No Monster 2" {dpoke 0xe520 0} "No Monster 3" {dpoke 0xe540 0} "Try Again Cheat" {dpoke 0xe217 1} "X-pos (0-255)" {dpoke 0xe284 0} "Y-pos (0-170)" {dpoke 0xe282 0} "Lives: Lives" {dpoke 0xe240 153} "Stage: Stage (1-60)" {dpoke 0xe242 1} } create_trainer "King's Valley Plus" {time 2} { "Doors Always Visible" {dpoke 0xd9f2 1;poke 0xd9f3 1} "No Map" {dpoke 0xd851 1} "No Monster 1" {dpoke 0xd96a 6} "No Monster 2" {dpoke 0xd980 6} "No Monster 3" {dpoke 0xd996 6} "No Monster 4" {dpoke 0xd9ac 6} "Pick (de-select To Jump)" {dpoke 0xd944 32} "Pyramid (1-60;does Not Work With Some Pyramids)" {dpoke 0xd855 1} "Lives: Lives" {dpoke 0xd850 99} "Stage: Stage (1-99;0)" {dpoke 0xd854 1} "Weapon: Sword (de-select To Jump)" {dpoke 0xd944 16} } create_trainer "Kinnikuman" {time 1} { "Life Player 1" {dpoke 0xf002 255} } create_trainer "Knight Lore" {time 1} { "Days Spent" {dpoke 0xd81a 0} "Lives: Lives" {dpoke 0xd81b 9} } create_trainer "Knightmare III - Shalom" {time 1} { "Power: Power" {dpoke 0xe3f6 255} } create_trainer "Knither Special" {time 1} { "Big Key (makes The Game Boring)" {dpoke 0xf057 9} "Cracker" {dpoke 0xf052 99} "Fire Wave" {dpoke 0xf053 9} "Life Meter" {dpoke 0xf060 9;poke 0xf061 9;dpoke 0xf062 9;dpoke 0xf063 9} "Key: Small Keys" {dpoke 0xf056 9} "Weapon: Fireballs (bombs)" {dpoke 0xf050 9} "Weapon: Thunder Sword" {dpoke 0xf051 9} } create_trainer "Knockout" {time 1} { "Difficulty (0-7)" {dpoke 0x9c72 0} "Grogginess" {dpoke 0x9c6f 255} "Energy: Blue Energy" {dpoke 0x9c61 0} "Energy: Red Energy" {dpoke 0x9c5e 0} "Time: Time" {dpoke 0x9c78 90} } create_trainer "Kobashi" {time 1} { "Infinite Power Computer 2" {dpoke 0xd460 50} "Infinite Power Player 1" {dpoke 0xd448 50} "Infinite Power Player 2 Or Computer 1" {dpoke 0xd454 50} } create_trainer "Koma - MSX Magazine 2003" {time 1} { "Item: Black Item" {dpoke 0x869c 30} "Item: Red Item" {dpoke 0x86a1 30} } create_trainer "Konami's Boxing" {time 1} { "Enemy Ko After One Hit" {dpoke 0xe21a 9} "Lives: Lives" {dpoke 0xe218 0} } create_trainer "Konami's Golf" {time 1} { "Shots" {dpoke 0xe106 1} } create_trainer "Konami's Soccer" {time 1} { "Score Team 1" {dpoke 0xe0f5 153} "Score Team 2" {dpoke 0xe0f6 0} } create_trainer "Koneko no Dai bouken - Catboy" {time 1} { "Big Cat" {dpoke 0xe31d 255} "Lives: Lives" {dpoke 0xe00b 153} } create_trainer "Kong" {time 1} { "Only One Ball On First Scene" {dpoke 0x5ca4 74} "Time (de-select To Get Bonus)" {dpoke 0x5c8f 42;poke 0x5c90 49} "Lives: Lives" {dpoke 0x5ca0 3} } create_trainer "Kralizec Tetris 1" {time 1} { "Always The Same Speed" {dpoke 0xc857 0} } create_trainer "Kralizec Tetris 2" {time 1} { "Always The Same Speed" {dpoke 0xe757 0} } create_trainer "Laptick 2" {time 1} { "Always Have An Exit" {dpoke 0xe020 7} "Lives: Lives" {dpoke 0xe018 10} } create_trainer "Laydock" {time 2} { "Power: Power Player 1" {dpoke 0xa168 153;poke 0xa169 153} "Power: Power Player 2" {dpoke 0xa179 153;poke 0xa17a 153} } create_trainer "Laydock 2 - The Last Attack" {time 1} { "Power: Power Player 1 " {dpoke 0x6817 32} "Weapon: Player 1 Weapon 01: Bullbup" {dpoke 0x6a0e 0} "Weapon: Player 1 Weapon 02: Mine" {dpoke 0x6a0e 1} "Weapon: Player 1 Weapon 03: Merry" {dpoke 0x6a0e 2} "Weapon: Player 1 Weapon 04: Double" {dpoke 0x6a0e 3} "Weapon: Player 1 Weapon 05: Alpha" {dpoke 0x6a0e 4} "Weapon: Player 1 Weapon 06: Killer" {dpoke 0x6a0e 5} "Weapon: Player 1 Weapon 07: Tora" {dpoke 0x6a0e 6} "Weapon: Player 1 Weapon 08: Condor" {dpoke 0x6a0e 7} "Weapon: Player 1 Weapon 09: D-aden" {dpoke 0x6a0e 8} "Weapon: Player 1 Weapon 10: Fire" {dpoke 0x6a0e 9} } create_trainer "Leather Skirts" {time 1} { "Energy: Energy" {dpoke 0xb406 255} } create_trainer "Legendly Knight" {time 1} { "Armor" {dpoke 0xe30a 1} "Bible" {dpoke 0xe303 1} "Diving Suit" {dpoke 0xe30b 1} "Fire Arrow" {dpoke 0xe302 1} "Glasses" {dpoke 0xe307 1} "Holy Water" {dpoke 0xe306 1} "Invincible" {dpoke 0xd083 255} "Kill End Boss With One Shot" {dpoke 0xd08c 1} "Magic Crystal" {dpoke 0xe304 1} "Ring" {dpoke 0xe308 1} "Rollers" {dpoke 0xe309 1} "Thunder" {dpoke 0xe305 1} "Key: Key" {dpoke 0xe30c 1} "Power: Power" {dpoke 0xd07b 1;poke 0xd07c 32} "Weapon: Short Sword" {dpoke 0xe301 1} } create_trainer "Legendly Nine Gems" {time 1} { "Blue Vase" {dpoke 0xb447 255} "Book" {dpoke 0xb442 255} "Candle" {dpoke 0xb443 225} "Drums" {dpoke 0xb445 255} "Gold" {dpoke 0xb39b 153;poke 0xb39c 153} "Hat" {dpoke 0xb449 255} "Magic Card" {dpoke 0xb441 255} "Red Botle" {dpoke 0xb44a 255} "Red Liquid" {dpoke 0xb44c 255} "Red Vase" {dpoke 0xb448 255} "Shell" {dpoke 0xb446 255} "Whool (?)" {dpoke 0xb444 225} "Yellow Liquid" {dpoke 0xb44b 255} "Key: Key" {dpoke 0xb44d 3} "Power: Power" {dpoke 0xb399 153;poke 0xb39a 153} } create_trainer "Life In The Fast Lane" {time 1} { "Infinite Jumps" {dpoke 0x92d3 0} "Infinite Shots" {dpoke 0x92e2 0} "Lives: Infinite Lives" {dpoke 0x92ca 9} } create_trainer "Lode Run" {time 1} { "Keys (dsk Version)" {dpoke 0xbf72 2} "Keys (rom Version)" {dpoke 0xc031 2} "Lives: Lives (dsk Version)" {dpoke 0xbf6d 9} "Lives: Lives (rom Version)" {dpoke 0xc02c 9} } create_trainer "Lode Runner" {time 1} { "Exit Always Visible" {dpoke 0xef70 0} "Fifth Red Man X-position" {dpoke 0xef23 0} "Fifth Red Man Y-position" {dpoke 0xef22 0} "First Red Man X-position" {dpoke 0xef1b 0} "First Red Man Y-position" {dpoke 0xef1a 0} "Fourth Red Man X-position" {dpoke 0xef21 0} "Fourth Red Man Y-position" {dpoke 0xef20 0} "High Speed" {dpoke 0xef61 0} "Level (1-76)" {dpoke 0xef66 1} "Normal Speed" {dpoke 0xef61 3} "Second Red Man X-position" {dpoke 0xef1d 0} "Second Red Man Y-position" {dpoke 0xef1c 0} "Slow Speed" {dpoke 0xef61 6} "Third Red Man X-position" {dpoke 0xef1f 0} "Third Red Man Y-position" {dpoke 0xef1e 0} "Lives: Lives" {dpoke 0xef68 230;poke 0xef69 3} } create_trainer "M-Kid Promo Action" {time 1} { "Diamonds" {dpoke 0x5399 9;poke 0x539a 4} "Invulnerable (but Don't Fall)" {dpoke 0xf987 1} "X-pos" {dpoke 0x51a1 0} "Y-pos" {dpoke 0x51a2 0} "Lives: Lives" {dpoke 0x4ff4 9;poke 0x4ff5 9} "Time: Time" {dpoke 0x5605 0} } create_trainer "M-Kid Promo Puzzle" {time 1} { "Diamonds" {dpoke 0x5104 9;poke 0x5105 4} "Has Always Red Key" {dpoke 0x6d3a 1} "Open Passage To Next Room (1 Or 2)" {dpoke 0x5b00 1} "X-pos" {dpoke 0x4f29 0} "Y-pos" {dpoke 0x4f2a 0} "Lives: Lives" {dpoke 0x51b3 9;poke 0x51b4 9} "Time: Time" {dpoke 0x52ad 0} } create_trainer "Mac Attack" {time 1} { "Level (0-98)" {dpoke 0x41fd 0} "Pepper" {dpoke 0x41fe 99} "Lives: Lives" {dpoke 0x420b 9} } create_trainer "Macross" {time 1} { "Invulnerable: Invulnerable" {dpoke 0xd819 0} "Lives: Lives" {dpoke 0xd820 9} } create_trainer "Mad Rider 2" {time 1} { "Full Speed" {dpoke 0xc577 255} "Time: Timer" {dpoke 0xc510 59} } create_trainer "Magical Kid Wiz" {time 2} { "Diamond" {dpoke 0xc098 200} "Explosion" {dpoke 0xc093 200} "Fire" {dpoke 0xc094 200} "Flask" {dpoke 0xc096 200} "Guardian Angel" {dpoke 0xc095 200} "Speed Up Potion" {dpoke 0xc092 200} "Staff" {dpoke 0xc08f 200} "Lives: Lives" {dpoke 0xc00c 255} "Power: Power Shot" {dpoke 0xc090 200} "Time: Time Stopper" {dpoke 0xc091 200} } create_trainer "Magical Tree" {time 2} { "No Caterpillar 1" {dpoke 0xe208 0} "No Caterpillar 2" {dpoke 0xe20b 0} "No Lightning" {dpoke 0xe217 0} "No Owl 1" {dpoke 0xe1fa 0} "No Owl 2" {dpoke 0xe1fd 0} "No Owl 3" {dpoke 0xe200 0} "X-pos (3-239)" {dpoke 0xe1b4 0} "Y-pos (0-152)" {dpoke 0xe1b3 0} "Lives: Lives" {dpoke 0xe050 99} } create_trainer "Magnar" {time 1} { "Ammo: Ammo Weapon 1 : Normal Gun" {dpoke 0xca52 255} "Lives: Lives (255 Is Game Over)" {dpoke 0xca7f 250} "Power: Power" {dpoke 0xca09 24} "Timer: Invincible" {dpoke 0xca44 1} } create_trainer "Magunam" {time 1} { "Ammo" {dpoke 0xe048 255} "Lives: Lives" {dpoke 0xe043 9} "Time: Trigger Timer" {dpoke 0xe062 4} } create_trainer "Majikazo" {time 1} { "Time: Time" {dpoke 0xebf2 100} } create_trainer "Majyo Densetsu - Knightmare" {time 0.25} { "All Exits Directly Available" {dpoke 0xe006 8} "Arrows - Double Attack" {dpoke 0xe609 0} "Arrows - Triple Attack" {dpoke 0xe609 1} "Double Arrows - Double Attack" {dpoke 0xe609 2} "Double Arrows - Triple Attack" {dpoke 0xe609 3} "Double Boomerangs" {dpoke 0xe609 6} "End Boss Dies After One Hit" {dpoke 0xe034 0} "Fire Arrows - Double Attack" {dpoke 0xe609 10} "Fire Arrows - Triple Attack" {dpoke 0xe609 11} "Fire Balls - Concentrated" {dpoke 0xe609 9} "Fire Balls - Dispersed" {dpoke 0xe609 8} "Hide Enemies" {dpoke 0xe103 210;poke 0xe113 210;dpoke 0xe123 210;dpoke 0xe143 210;dpoke 0xe163 210} "Nuclear Arrows - Double Attack" {dpoke 0xe609 12} "Nuclear Arrows - Triple Attack" {dpoke 0xe609 13} "Speed 0" {dpoke 0xe60a 0} "Speed 1" {dpoke 0xe60a 1} "Speed 2" {dpoke 0xe60a 2} "Speed 3" {dpoke 0xe60a 3} "Speed 4" {dpoke 0xe60a 4} "State: Invisible" {dpoke 0xe60c 2} "State: Red Hot" {dpoke 0xe60c 3} "Triple Boomerangs" {dpoke 0xe609 7} "Invulnerable: Invulnerable" {dpoke 0xe600 0} "Lives: Lives" {dpoke 0xe060 153} "Shield: Shield Active" {dpoke 0xe60c 1} "Stage: Stage 01" {dpoke 0xe061 1} "Stage: Stage 02" {dpoke 0xe061 2} "Stage: Stage 03" {dpoke 0xe061 3} "Stage: Stage 04" {dpoke 0xe061 4} "Stage: Stage 05" {dpoke 0xe061 5} "Stage: Stage 06" {dpoke 0xe061 6} "Stage: Stage 07" {dpoke 0xe061 7} "Stage: Stage 08" {dpoke 0xe061 8} "Time: Infinite Timer" {dpoke 0xe60e 153} "Weapon: Double Swords" {dpoke 0xe609 5} } create_trainer "Makaijima - Higemaru" {time 1} { "999900 Points" {dpoke 0xe515 153;poke 0xe516 153} "End Bosses Die Instantly" {dpoke 0xe0bc 255} "No Enemies To Kill Before Entering The Gate To A Boss" {dpoke 0xe539 0} "Untouchable" {dpoke 0xe026 255} "Item: Get All Items" {dpoke 0xe519 255;poke 0xe51a 255} "Key: Get All Keys" {dpoke 0xe517 255} } create_trainer "Malaika-MSXdev06" {time 1} { "For Super Mario Mod" {dpoke 0xe9cd 2} "Hearts" {dpoke 0xe98d 4} "Invincible" {dpoke 0xe9cd 1} "Invincible Timer" {dpoke 0xe9ce 16} "Pearls" {dpoke 0xe985 153;poke 0xe986 9} "Lives: Lives" {dpoke 0xe982 153} } create_trainer "Malaya no Hibou" {time 2} { "Enable Fire Shooting" {dpoke 0xcd25 31} "Have Fires" {dpoke 0xcd74 99} "Have Potions" {dpoke 0xcd77 99} "Life" {dpoke 0xccd3 15} "Money" {dpoke 0xccd4 255;poke 0xccd5 255} "Key: Have Keys" {dpoke 0xcd75 99} "Lives: Lives" {dpoke 0xccf3 255} "Weapon: Have Bombs" {dpoke 0xcd76 99} } create_trainer "Manbow 2" {time 0.5} { "Lives: Lives" {dpoke 0xc113 9} "Power: Blue Power Bar" {dpoke 0xc155 78} "Power: Red Power Bar" {dpoke 0xc13e 74} } create_trainer "Manes" {time 10} { "Lives: Lives" {dpoke 0xdb93 5} } create_trainer "Mappy" {time 5} { "Lives: Lives" {dpoke 0xe043 11} } create_trainer "Mario And Fruit" {time 1} { "Lives: Lives" {dpoke 0xa5af 9} "Time: Time" {dpoke 0xa5aa 218} } create_trainer "Mario And Fruit Promo" {time 1} { "Lives: Lives" {dpoke 0x9785 9} "Time: Time" {dpoke 0x9780 218} } create_trainer "Mashou no Yakata Gabalin - Goblin" {time 1} { "Invincible" {dpoke 0xc0b8 255} } create_trainer "Masters of the Universe" {time 1} { "Have All Cords" {dpoke 0xccd4 255} "Lives: Lives" {dpoke 0xa4e3 9} "Power: Power" {dpoke 0xcb01 90} "Time: Time" {dpoke 0xd23c 9;poke 0xd23e 9;dpoke 0xd23f 9} } create_trainer "Maze Master" {time 1} { "Lives: Lives" {dpoke 0x49ad 9} "Weapon: Laser" {dpoke 0x49ae 99} } create_trainer "Mazes Unlimited" {time 1} { "Lives: Lives" {dpoke 0x881c 0} "Weapon: Laser" {dpoke 0x881d 99} } create_trainer "Megalopolis SOS" {time 1} { "Max Defense Lon" {dpoke 0xe111 0} "Max Defense Mow" {dpoke 0xe113 0} "Max Defense Nyc" {dpoke 0xe110 0} "Max Defense Par" {dpoke 0xe115 0} "Max Defense Pek" {dpoke 0xe114 0} "Max Defense Tky" {dpoke 0xe112 0} "Lives: Lives" {dpoke 0xe109 0} } create_trainer "Meganova" {time 1} { "Lives: Lives" {dpoke 0xb9e5 255} } create_trainer "Megaphoenix" {time 1} { "Invulnerable: Invulnerable" {dpoke 0x7baf 3;poke 0x83a2 0} "Lives: Lives" {dpoke 0x7b5b 9} "Power: Power (0-3)" {dpoke 0x7b5c 3} } create_trainer "Meikyushinwa - Eggerland Mystery 2" {time 1} { "Containers Left" {dpoke 0xeb88 0} "Invincible" {dpoke 0xef90 2} "Shots Always Active" {dpoke 0xeb8a 97} "Timer In Special Stages" {dpoke 0xec56 99} "Lives: Lives" {dpoke 0xeb89 97} "Lives: Stay Alive" {dpoke 0xeb94 0} } create_trainer "Metal Gear" {time 1} { "Bosses Die Faster" {dpoke 0xd00d 1} "Electrified Zones Without Danger" {dpoke 0xc616 0} "Enemies Can't Hurt You" {dpoke 0xc199 255} "Enemy 1 Gone" {dpoke 0xd005 0} "Enemy 2 Gone" {dpoke 0xd085 0} "Enemy 3 Gone" {dpoke 0xd105 0} "Enemy 4 Gone" {dpoke 0xd185 0} "Keep Most Guards Asleep" {dpoke 0xd046 255} "No Extra Guards When Detected" {dpoke 0xc602 0} "Open All Doors And Passages" {dpoke 0xc3d1 0;poke 0xc3e1 0;dpoke 0xc3f1 0} "Scorpions Can't Poison You" {dpoke 0xc139 0} "Stop Destruction Timer (de-select To See End Demo)" {dpoke 0xc13d 153;poke 0xc13e 153} "Ammo: Always Ammo For Handgun" {dpoke 0xc500 1;poke 0xc501 153;dpoke 0xc502 9} "Ammo: Always Ammo For Sub Machine Gun" {dpoke 0xc504 2;poke 0xc505 153;dpoke 0xc506 9} "Item: Always Ratios" {dpoke 0xc584 22;poke 0xc585 153;dpoke 0xc586 9} "Item: Antenna In Inventory" {dpoke 0xc54c 8} "Item: Antidote In Inventory" {dpoke 0xc560 13} "Item: Binoculars In Inventory" {dpoke 0xc550 9} "Item: Body Armor In Inventory" {dpoke 0xc530 1} "Item: Bomb Blast Suit In Inventory" {dpoke 0xc534 2} "Item: Box In Inventory" {dpoke 0xc590 25} "Item: Card 1 In Inventory" {dpoke 0xc564 14} "Item: Card 2 In Inventory" {dpoke 0xc568 15} "Item: Card 3 In Inventory" {dpoke 0xc56c 16} "Item: Card 4 In Inventory" {dpoke 0xc570 17} "Item: Card 5 In Inventory" {dpoke 0xc574 18} "Item: Card 6 In Inventory" {dpoke 0xc578 19} "Item: Card 7 In Inventory" {dpoke 0xc57c 20} "Item: Card 8 In Inventory" {dpoke 0xc580 21} "Item: Cigarets In Inventory" {dpoke 0xc544 6} "Item: Compass In Inventory" {dpoke 0xc558 11} "Item: Flash Light In Inventory" {dpoke 0xc538 3} "Item: Gas Mask In Inventory" {dpoke 0xc540 5} "Item: Goggles In Inventory" {dpoke 0xc53c 4} "Item: Mine Detector In Inventory" {dpoke 0xc548 7} "Item: Oxygen In Inventory" {dpoke 0xc554 10} "Item: Parachute In Inventory" {dpoke 0xc55c 12} "Item: Selected Item - Antenna" {dpoke 0xc135 8} "Item: Selected Item - Antidote" {dpoke 0xc135 13} "Item: Selected Item - Binoculars" {dpoke 0xc135 9} "Item: Selected Item - Body Armor" {dpoke 0xc135 1} "Item: Selected Item - Bomb Blast Suit" {dpoke 0xc135 2} "Item: Selected Item - Cigarets" {dpoke 0xc135 6} "Item: Selected Item - Compass" {dpoke 0xc135 11} "Item: Selected Item - Flash Light" {dpoke 0xc135 3} "Item: Selected Item - Gas Mask" {dpoke 0xc135 5} "Item: Selected Item - Goggles" {dpoke 0xc135 4} "Item: Selected Item - Mine Detector" {dpoke 0xc135 7} "Item: Selected Item - Oxygen" {dpoke 0xc135 10} "Item: Selected Item - Parachute" {dpoke 0xc135 12} "Item: Selected Item - Ratios" {dpoke 0xc135 22} "Item: Selected Item - Transmitter" {dpoke 0xc135 23} "Item: Selected Item - Uniform" {dpoke 0xc135 24} "Item: Transmitter In Inventory" {dpoke 0xc588 23} "Item: Uniform In Inventory" {dpoke 0xc58c 24} "Key cards: Selected Item - Card 1" {dpoke 0xc135 14} "Key cards: Selected Item - Card 2" {dpoke 0xc135 15} "Key cards: Selected Item - Card 3" {dpoke 0xc135 16} "Key cards: Selected Item - Card 4" {dpoke 0xc135 17} "Key cards: Selected Item - Card 5" {dpoke 0xc135 18} "Key cards: Selected Item - Card 6" {dpoke 0xc135 19} "Key cards: Selected Item - Card 7" {dpoke 0xc135 20} "Key cards: Selected Item - Card 8" {dpoke 0xc135 21} "Life: Power Bar" {dpoke 0xc131 48} "Rank: 4th Class Rank" {dpoke 0xc132 0;poke 0xc132 1;dpoke 0xc132 2;dpoke 0xc132 3} "Weapon: Always Grenades" {dpoke 0xc508 3;poke 0xc509 153;dpoke 0xc50a 9} "Weapon: Always Mines" {dpoke 0xc514 6;poke 0xc515 153;dpoke 0xc516 9} "Weapon: Always Missiles" {dpoke 0xc518 7;poke 0xc519 153;dpoke 0xc51a 9} "Weapon: Always Plastic Bombs" {dpoke 0xc510 5;poke 0xc511 153;dpoke 0xc512 9} "Weapon: Always Rockets" {dpoke 0xc50c 4;poke 0xc50d 153;dpoke 0xc50e 9} "Weapon: Selected Weapon - Handgun" {dpoke 0xc134 1} "Weapon: Selected Weapon - Mines" {dpoke 0xc134 6} "Weapon: Selected Weapon - Missiles" {dpoke 0xc134 7} "Weapon: Selected Weapon - Plastic Bombs" {dpoke 0xc134 5} "Weapon: Selected Weapon - Rockets" {dpoke 0xc134 4} "Weapon: Selected Weapon - Sub Machine Gun" {dpoke 0xc134 2} "Weapon: Silencer In Inventory" {dpoke 0xc51c 8} } create_trainer "Metal Gear 2 - Solid Snake" {time 2} { "Do Not Sink Into The Swamp" {dpoke 0xcb29 32} "Get C4 Explosives" {dpoke 0xd618 1;poke 0xd619 153;dpoke 0xd61a 9} "Get Camouflage" {dpoke 0xd620 1;poke 0xd621 153;dpoke 0xd622 9} "Get Gas Grenade" {dpoke 0xd624 1;poke 0xd625 153;dpoke 0xd626 9} "Get Grenades" {dpoke 0xd608 1;poke 0xd609 153;dpoke 0xd60a 9} "Get Landmines" {dpoke 0xd61c 1;poke 0xd61d 153;dpoke 0xd61e 9} "Get Remote Mice" {dpoke 0xd628 1;poke 0xd629 153;dpoke 0xd630 9} "Get Remote Missiles" {dpoke 0xd614 1;poke 0xd615 153;dpoke 0xd616 9} "Get Silencer" {dpoke 0xd62c 1;poke 0xd62d 1} "Get Surface To Air Missiles" {dpoke 0xd610 1;poke 0xd611 153;dpoke 0xd612 9} "Get Zippo" {dpoke 0xd634 1;poke 0xd635 1} "Invisible Until An Alert Is Triggered" {dpoke 0xca3c 1} "Life Bar" {dpoke 0xca53 32} "Open Path Trough Jungle" {dpoke 0xd430 2} "Set Avoiding Time To 0" {dpoke 0xd42b 0} "Weapon: Get Gun" {dpoke 0xd600 1;poke 0xd601 153;dpoke 0xd602 9} "Weapon: Get Sub Machine Gun" {dpoke 0xd604 1;poke 0xd605 153;dpoke 0xd606 9} } create_trainer "Meteorite Kiss" {time 0.5} { "Invincible: Invincible" {dpoke 0x8c59 32} "Lives: Infinite Lives" {dpoke 0x33cc 99} } create_trainer "Mike Gunner" {time 1} { "Injuries" {dpoke 0x7fd8 6} "Lives: Lives" {dpoke 0x7fda 2} } create_trainer "Mine Clearer" {time 1} { "Lives: Lives" {dpoke 0xc940 4} "Time: Time" {dpoke 0xc9a1 255} } create_trainer "Miner Machine" {time 1} { "Time (de-select To Get Bonus)" {dpoke 0xa024 255} "Lives: Lives" {dpoke 0x9fef 9} } create_trainer "Mirai - Future - MSX2 Version" {time 1} { "Power: Special Power" {dpoke 0xaea1 162} } create_trainer "Missile Command" {time 1} { "Left Base Ammo" {dpoke 0x5bd8 10} "Middle Base Ammo" {dpoke 0x5bd9 10} "Right Base Ammo" {dpoke 0x5bda 10} } create_trainer "Moai no Hibou - Pascua" {time 1} { "Life" {dpoke 0xe00b 10} "Pillars" {dpoke 0xe30e 255} } create_trainer "Mobile Planet Suthirus" {time 1} { "Invisible" {dpoke 0xe33a 1} "Stars" {dpoke 0xe054 153} "Lives: Lives" {dpoke 0xe001 153} } create_trainer "Mon Mon Monster" {time 2} { "Extra Power Bolt Shot" {dpoke 0xe02d 1} "Invincible" {dpoke 0xe31e 255} "Life" {dpoke 0xe038 3} "Rocks" {dpoke 0xe02c 99} "Rotating Shots" {dpoke 0xe02e 1} "Lives: Lives" {dpoke 0xe001 99} "Stage: Stage 1" {dpoke 0xe010 0} "Stage: Stage 2" {dpoke 0xe010 1} "Stage: Stage 3" {dpoke 0xe010 2} "Stage: Stage 4" {dpoke 0xe010 3} } create_trainer "Monkey Academy" {time 1} { "Infinite Errors Allowed" {dpoke 0xe057 0} "Level Player 1 (0-4)" {dpoke 0xe153 0} "Level Player 2 (0-4)" {dpoke 0xe154 0} "No Crab 1" {dpoke 0xe115 32} "No Crab 2" {dpoke 0xe11d 32} "No Crab 3" {dpoke 0xe125 32} "Only One Problem By Stage" {dpoke 0xe054 2} "Time (de-select To Get Bonus)" {dpoke 0xe055 0;poke 0xe056 5} "Lives: Lives" {dpoke 0xe050 99} "Stage: Stage (1-99;0)" {dpoke 0xe051 1} } create_trainer "Monster Hunter-MSXdev06" {time 1} { "All Armor" {dpoke 0xe124 1;poke 0xe125 1;dpoke 0xe126 1;dpoke 0xe127 1;dpoke 0xe128 1;dpoke 0xe129 1;dpoke 0xe12a 1;dpoke 0xe12b 1;dpoke 0xe12c 1;dpoke 0xe12d 1;dpoke 0xe12e 1;dpoke 0xe12f 1;dpoke 0xe130 1;dpoke 0xe131 1;dpoke 0xe132 1} "All Magic" {dpoke 0xe133 9;poke 0xe134 9;dpoke 0xe135 9;dpoke 0xe136 9;dpoke 0xe137 9;dpoke 0xe138 9;dpoke 0xe139 9;dpoke 0xe13a 9;dpoke 0xe13b 9;dpoke 0xe13c 9;dpoke 0xe13d 9;dpoke 0xe13e 9;dpoke 0xe13f 9;dpoke 0xe140 9;dpoke 0xe141 9} "Blue Ball" {dpoke 0xe15e 18} "Bottle" {dpoke 0xe15a 12} "Cat" {dpoke 0xe162 0} "Coin" {dpoke 0xe15b 13} "G-coin" {dpoke 0xe158 10} "Glasses" {dpoke 0xe14f 1} "Gold" {dpoke 0xe10b 255;poke 0xe10c 255} "Gold Ball" {dpoke 0xe15d 17} "Green Ball" {dpoke 0xe15f 19} "Heart" {dpoke 0xe159 11} "Items In Inventory" {dpoke 0xe16f 20} "Letter" {dpoke 0xe15c 16} "Red Ball" {dpoke 0xe160 20} "Thing" {dpoke 0xe161 21} "Key: Black Key" {dpoke 0xe156 8} "Key: Blue Key" {dpoke 0xe151 3} "Key: Bow Key" {dpoke 0xe153 5} "Key: Gold Key" {dpoke 0xe154 6} "Key: Green Key " {dpoke 0xe152 4} "Key: Purple Key" {dpoke 0xe157 9} "Key: Red Key" {dpoke 0xe150 2} "Key: Silver Key" {dpoke 0xe155 7} "Power: Power" {dpoke 0xe10e 255} "Weapon: All Weapons" {dpoke 0xe115 1;poke 0xe116 1;dpoke 0xe117 1;dpoke 0xe118 1;dpoke 0xe119 1;dpoke 0xe11a 1;dpoke 0xe11b 1;dpoke 0xe11c 1;dpoke 0xe11d 1;dpoke 0xe11e 1;dpoke 0xe11f 1;dpoke 0xe120 1;dpoke 0xe121 1;dpoke 0xe122 1;dpoke 0xe123 1} } create_trainer "Monster's Fair" {time 1} { "Give Motha Some Balls" {dpoke 0xd7cb 99} "Mothas" {dpoke 0xd81c 9} "Power: Power" {dpoke 0xd7e2 50} } create_trainer "Montezuma's Revenge" {time 1} { "Lives: Lives" {dpoke 0x7049 11} } create_trainer "Moon Patrol" {time 1} { "Lives: Lives" {dpoke 0xf920 4} } create_trainer "Moonrider" {time 1} { "Invulnerable: Invulnerable" {dpoke 0x4c54 0} "Lives: Lives" {dpoke 0x41c2 4} } create_trainer "Mopiranger" {time 1} { "Dragon Without Fire" {dpoke 0xe340 0} "Y-position Big Razzon" {dpoke 0xe220 7} "Y-position Blue Enemy" {dpoke 0xe1f0 7} "Y-position Grey Enemy" {dpoke 0xe160 7} "Y-position Red Enemy" {dpoke 0xe190 7} "Y-position Yellow Enemy" {dpoke 0xe1c0 7} "Lives: Lives" {dpoke 0xe051 153} "Time: Time" {dpoke 0xe003 1} } create_trainer "Mouse Games - Disc Station 00" {time 1} { "Infinite Balls (block Blaster)" {dpoke 0xcc0e 9} "Infinite Game (clay Shot)" {dpoke 0x9cbb 0} "Infinite Time (mole Attack)" {dpoke 0x9ce0 99} } create_trainer "Mr. Chin" {time 1} { "Lives: Lives" {dpoke 0xe101 8} } create_trainer "Mr. Wong's Loopy Laundry" {time 1} { "Starch" {dpoke 0xf33b 7} "Time (de-select To Get Bonus)" {dpoke 0xf35b 57} "Lives: Lives" {dpoke 0xf320 7} } create_trainer "MSX Game" {time 1} { "Balls" {dpoke 0xb791 255} "Money" {dpoke 0xbc9f 255} } create_trainer "Munsters, The" {time 1} { "Power: Power" {dpoke 0xaa7a 255} } create_trainer "Mutant Monty" {time 1} { "Lives: Lives" {dpoke 0x9ad1 5} "Time: Time" {dpoke 0x9ad4 29} } create_trainer "N-Sub" {time 1} { "Round (0-99)" {dpoke 0xc142 1} "Lives: Lives" {dpoke 0xc13b 3} } create_trainer "Namco F1 Racing" {time 1} { "Damage" {dpoke 0xc828 255} "Start Points" {dpoke 0xc701 11} } create_trainer "Narco Police" {time 1} { "Aa-a Ammo" {dpoke 0x6d68 255} "Bullet Proof Vest" {dpoke 0x6d78 255} "Medical Kit" {dpoke 0x6d74 255} "Mp 607 Ammo" {dpoke 0x6d72 255} "No Enemies" {dpoke 0xcd2d 1} "Standard Ammo" {dpoke 0xcd59 255} "T-71 Ammo" {dpoke 0x6d6a 255} "T-72 Ammo" {dpoke 0x6d70 255} "T-73 Ammo" {dpoke 0x6d6e 255} "Teleport" {dpoke 0x6d76 255} "Thor M2 Ammo" {dpoke 0x6d6c 255} "Lives: Lives" {dpoke 0xcd54 99} } create_trainer "Nausicaa" {time 1} { "Infinite Fuel" {dpoke 0xf366 255} "Lives: Infinite Lives" {dpoke 0xf367 9} } create_trainer "Navy Moves 1" {time 1} { "Invulnerable: Invulnerable" {dpoke 0xc5a8 0} "Lives: Lives" {dpoke 0xc674 8} "Time: Time" {dpoke 0xc679 57} } create_trainer "Navy Moves 1 spanish" {time 1} { "Invulnerable: Invulnerable" {dpoke 0xc5b5 0} "Lives: Lives" {dpoke 0xc681 8} "Time: Time" {dpoke 0xc686 57} } create_trainer "Navy Moves 2" {time 1} { "Bullets" {dpoke 0xa3a0 60} "Flame" {dpoke 0x9a44 58} "Lives: Lives" {dpoke 0xa393 9} "Weapon: Bomb Timer (value 1 Required For Final Countdown)" {dpoke 0xa39e 0} "Weapon: Weapon (0=rifle 1=flame Thrower)" {dpoke 0x8b9a 0} } create_trainer "Navy Moves 2 spanish" {time 1} { "Bullets" {dpoke 0xa3ca 60} "Flame" {dpoke 0x9a6e 58} "Lives: Lives" {dpoke 0xa3bd 9} "Weapon: Bomb Timer (value 1 Required For Final Countdown)" {dpoke 0xa3c8 0} "Weapon: Weapon (0=rifle 1=flame Thrower)" {dpoke 0x8bde 0} } create_trainer "Negrooli Panda" {time 1} { "Power: Power" {dpoke 0xe2ed 99} } create_trainer "Night Flight" {time 1} { "Lives: Lives" {dpoke 0xef04 5} } create_trainer "Night Shade" {time 1} { "Power: Power" {dpoke 0xd04f 3} } create_trainer "Ninja Kun" {time 1} { "Item: 50 Bonus Scrolls" {dpoke 0xcb2e 99} "Lives: 99 Lives" {dpoke 0xcb34 99} "Weapon: 4 way fire" {dpoke 0xcb7a 4} "Weapon: Bombs" {dpoke 0xcb7a 3} "Weapon: Boomerang" {dpoke 0xcb7a 2} "Weapon: Throwing stars" {dpoke 0xcb7a 1} } create_trainer "Ninja Kun (Ninja II)" {time 1} { "Protection Scrolls" {dpoke 0xcb2e 255} "Lives: Lives" {dpoke 0xcb37 99} "Time: Time" {dpoke 0xcb35 99} } create_trainer "Ninja Princess" {time 1} { "Partially Invincible" {dpoke 0xe200 0;poke 0xe343 0} "Lives: Lives" {dpoke 0xe047 99} } create_trainer "Ninja Youmakor" {time 1} { "Damage" {dpoke 0xd46d 0} } create_trainer "Ninjya Kage" {time 1} { "Shot Power Up (try 1 Trough 7 As A Value)" {dpoke 0xe542 4} "Unlimited Magic" {dpoke 0xe547 255} "Lives: Lives" {dpoke 0xe532 99} } create_trainer "No Fuss" {time 1} { "Frozen Shape (select At Good Moment; De-select At End Of Level)" {dpoke 0x6028 4} "Level (1-60)" {dpoke 0xd570 1} "Time (de-select To Get Bonus)" {dpoke 0x602a 255} "Lives: Lives" {dpoke 0x7ffe 99} } create_trainer "Nonamed" {time 1} { "Higher Jumps" {dpoke 0x5b09 1} "Lives: Lives" {dpoke 0x5b18 6} } create_trainer "Nosh" {time 1} { "Boomerang" {dpoke 0x96ff 1} "Catapult" {dpoke 0x96f7 1} "Cherry" {dpoke 0x96f9 153} "Frozen Enemies" {dpoke 0x9705 1;poke 0x9707 8} "Lighter" {dpoke 0x96fb 1} "Super Shoes" {dpoke 0x9701 1;poke 0x9703 8} "Water" {dpoke 0x96f3 5} "World (1-5)" {dpoke 0xf3aa 1} "Invulnerable: Invulnerable" {dpoke 0xf3ab 1} "Key: Key (1-10)" {dpoke 0x96f1 1} "Weapon: Bombs" {dpoke 0x96fd 153} } create_trainer "Nyancle Racing" {time 1} { "Candy" {dpoke 0xd215 255} "Damage" {dpoke 0xd217 0;poke 0xd21e 0} "Invincible" {dpoke 0xd73e 255} "Time: Time" {dpoke 0xd213 255} } create_trainer "Oberon 69" {time 1} { "Ammo" {dpoke 0x12a5 136} "Energy: Energy" {dpoke 0x1281 96} "Time: Time" {dpoke 0xab50 59} } create_trainer "Oh Shit!" {time 1} { "Ghosts Are Blue Before First Contact V1" {dpoke 0x601f 1} "Ghosts Are Blue Before First Contact V2" {dpoke 0x9b27 1} "Lives: Lives V1" {dpoke 0x402a 4} "Lives: Lives V2" {dpoke 0x8117 4} } create_trainer "Oil's Well" {time 1} { "Almost No Enemies" {dpoke 0xe0d2 1} "Time: Time" {dpoke 0xe0cc 90} } create_trainer "Operation Wolf" {time 2} { "Bullets" {dpoke 0xa316 32} "Grenades" {dpoke 0x9ae6 9} "Invincible" {dpoke 0x9ae9 0} "Mag" {dpoke 0x9ae7 9} } create_trainer "Operation Wolf-MSXdev06" {time 1} { "Always Fast Shot" {dpoke 0xea51 9} "Ammo" {dpoke 0xed3e 32} "Hand Grenade" {dpoke 0xed3d 4} "Power: Power" {dpoke 0xea55 255} "Stage: Hostages Freed" {dpoke 0xecb5 5} } create_trainer "Outrun" {time 2} { "Time: Time" {dpoke 0xc093 99} } create_trainer "Pac-Man" {time 1} { "Ghost 1" {dpoke 0xe230 1} "Ghost 2" {dpoke 0xe250 1} "Ghost 3" {dpoke 0xe270 1} "Ghost 4" {dpoke 0xe290 1} } create_trainer "Pac-Mania" {time 5} { "Blue Ghost 1" {dpoke 0xcd60 255} "Blue Ghost 2" {dpoke 0xcd74 255} "Blue Ghost 3" {dpoke 0xcd88 255} "Blue Ghost 4" {dpoke 0xcd9c 255} "Blue Ghost 5" {dpoke 0xcdb0 255} "Blue Ghost 6" {dpoke 0xcdc4 255} "Blue Ghost 7" {dpoke 0xcdd8 255} "Blue Ghost 8" {dpoke 0xcdec 255} "Unknown S (mandatory Though)" {dpoke 0x9f2d 255;poke 0xc953 255} } create_trainer "Pachipro Densetsu" {time 1} { "Money" {dpoke 0xe22d 153;poke 0xe22e 153;dpoke 0xe22f 153;dpoke 0xe230 153} } create_trainer "Pai Panic" {time 1} { "Lives: Lives" {dpoke 0xe90b 99} } create_trainer "Pair Logic" {time 1} { "Time (dsk Version)" {dpoke 0x99dc 255} "Time (rom Version)" {dpoke 0xc017 255} } create_trainer "Pairs" {time 1} { "Level (1-12)" {dpoke 0xc07c 1} "Lives: Lives" {dpoke 0xc081 5} "Time: Time" {dpoke 0xe804 154;poke 0xe805 4} } create_trainer "Palette Editor 1.0" {time 60} { } create_trainer "Palette Editor 1.1" {time 60} { } create_trainer "Panic Shoot" {time 1} { "Fire" {dpoke 0xa482 5} "Lives: Lives" {dpoke 0xa47d 5} } create_trainer "Panique" {time 1} { "Always Oxygen" {dpoke 0x4995 0} "Lives: Lives" {dpoke 0x4cd5 4} } create_trainer "Parodius - Tako Saves Earth" {time 1} { "Disable Normal Shoot" {dpoke 0xe430 0} "Enable Double" {dpoke 0xe431 2} "Enable Missile" {dpoke 0xe433 2} "Enable Option 1" {dpoke 0xe410 1} "Enable Option 2" {dpoke 0xe420 1} "More Bells" {dpoke 0xe251 255;poke 0xe253 255;dpoke 0xe254 255;dpoke 0xe256 255} "Option" {dpoke 0xe40b 2} "Speed Set To 4" {dpoke 0xe335 4} "Stage (1-6=normal;7-9=bonus)" {dpoke #0xe24 1} "Lives: Lives" {dpoke 0xe240 153} "Power: Full Power" {dpoke 0xe268 4} "Shield: Shield" {dpoke 0xe402 2;poke 0xe40a 15;dpoke 0xeb07 15} "Shield: Shield 0=off 2=on" {dpoke 0xe400 2} "Weapon: Enable Laser" {dpoke 0xe432 2} } create_trainer "Pastfinder" {time 10} { "Deradiator In Use" {dpoke 0xe130 1} "Deradiator Reserve" {dpoke 0xe134 9} "Heavy Metal In Use" {dpoke 0xe12d 1} "Heavy Metal Reserve" {dpoke 0xe131 9} "Keep Radiation Low (de-select Between Sectors)" {dpoke 0xe126 1} "Scrambler In Use" {dpoke 0xe12f 1} "Scrambler Reserve" {dpoke 0xe133 9} "Lives: Lives" {dpoke 0xe004 10} "Shield: Beam Shield In Use" {dpoke 0xe12e 1} "Shield: Beam Shield Reserve" {dpoke 0xe132 9} } create_trainer "Peach Up 1 Quizz" {time 1} { "Always Winner (select Only In Stages)" {dpoke 0xb3a8 3} } create_trainer "Pegasus" {time 1} { "Big Pegasus" {dpoke 0xc009 4} "Stage (change Value To Get That Stage)" {dpoke 0xc007 1} "Power: Power" {dpoke 0xc006 43} } create_trainer "Penguin" {time 1} { "Lives: Lives" {dpoke 0x4018 9} } create_trainer "Penguin Kun Wars 1" {time 1} { "Enemy K.o." {dpoke 0xd026 255} "Enemy Stuck In One Position" {dpoke 0xd020 15} } create_trainer "Penguin Kun Wars 2" {time 1} { "Enemy K.o. After Being Hit" {dpoke 0xb126 255} } create_trainer "Penguin Race" {time 1} { "Fishes Player 1 In 83 Game" {dpoke 0xc113 123} "Fishes Player 2 In 83 Game" {dpoke 0xc114 123} "Player 1 Is Winner In Classic Game" {dpoke 0xc107 3} "Player 2 Is Winner In Classic Game" {dpoke 0xc108 3} } create_trainer "Pepper 2" {time 1} { "Lives: Lives" {dpoke 0x7078 99} } create_trainer "Phantis 1" {time 1} { "Invulnerable: Invulnerable" {dpoke 0xb7d6 33} "Lives: Lives" {dpoke 0xc413 57;poke 0xc414 57} } create_trainer "Phantis 2" {time 1} { "Has Access Medallion" {dpoke 0xc372 0} "Has Proton Loader" {dpoke 0xc60e 0} "Energy: Energy" {dpoke 0xc4ce 0} "Lives: Lives" {dpoke 0xb76b 57;poke 0xb76c 57} "Weapon: Has Ionic Turbo Laser" {dpoke 0xc055 0} } create_trainer "Phantomas 2" {time 1} { "Open 1st Cellar Door" {dpoke 0x51ac 0} "Open 2d Cellar Door" {dpoke 0x52cd 0} "Open 3rd Cellar Door" {dpoke 0x62f1 0} "Open Back Tower Window" {dpoke 0x447d 0} "Open Back Window" {dpoke 0x454e 0} "Open Central Tower" {dpoke 0x4ead 0} "Open Central Window" {dpoke 0x4822 0} "Open Front Door" {dpoke 0x4164 0} "Open Front Tower" {dpoke 0x4c45 0} "Open Highest Window" {dpoke 0x498b 0} "Open Left Tower Window" {dpoke 0x48cf 0} "Open Right Tower" {dpoke 0x4a4e 0} "Open Right Tower Window" {dpoke 0x4a3e 0} "X-pos (0-130)" {dpoke 0x960b 0} "Y-pos (200-70)" {dpoke 0x960c 70} "Lives: Lives" {dpoke 0x9492 9} "Power: Power" {dpoke 0x9491 99} } create_trainer "Pharaoh's Revenge" {time 1} { "Ammo" {dpoke 0x62a9 99} "Potion (invulnerability)" {dpoke 0x81a8 1} "Key: Key" {dpoke 0x81a6 1} "Lives: Lives" {dpoke 0x401e 5} } create_trainer "Picture Puzzle" {time 1} { "Stage: Stage (1-45)" {dpoke 0xcf52 1} "Time: Time" {dpoke 0xcf4b 0;poke 0xcf4c 0} } create_trainer "Pinball Blaster" {time 1} { "Cosmic Cheat" {dpoke 0x4061 1} "Players (1-4)" {dpoke 0x4059 1} "Lives: Balls (lives)" {dpoke 0x4049 9} } create_trainer "Pine Applin" {time 0.1} { "Bat X-y-pos" {dpoke 0xe020 0;poke 0xe020 1} "Bridge Always Closed (just Walk To The Next Stage)" {dpoke 0xe250 5} "Level" {dpoke 0xc023 9;poke 0xc024 9;dpoke 0xc025 9} "Snake X-y-pos" {dpoke 0xe040 1;poke 0xe041 0} "Turtle X-y-pos" {dpoke 0xe030 1;poke 0xe031 0} "Energy: Energy" {dpoke 0xcc62 255;poke 0xcc64 255} "Lives: Lives" {dpoke 0xcc66 99} } create_trainer "Pipi" {time 1} { "Exit Always Active" {dpoke 0xd710 2} "Invincible" {dpoke 0xd623 1} "Invincible Timer" {dpoke 0xd654 255} "Lives: Lives" {dpoke 0xd478 11} "Time: Time" {dpoke 0xd716 20} } create_trainer "Pippols" {time 2} { "Invincible" {dpoke 0xe11b 255} "Jump Shoes" {dpoke 0xe1a9 1} "Running Shoes" {dpoke 0xe1a8 1} "Lives: Lives" {dpoke 0xe050 153} } create_trainer "Pitfall II - Lost Caverns" {time 1} { "Power: Power" {dpoke 0xe058 9;poke 0xe059 9;dpoke 0xe05a 9;dpoke 0xe05b 9;dpoke 0xe05c 9;dpoke 0xe05d 9} } create_trainer "Pitfall!" {time 1} { "Lives: Lives" {dpoke 0xe012 255} "Power: Power" {dpoke 0xe1d6 9;poke 0xe1d7 9;dpoke 0xe1d8 9;dpoke 0xe1d9 9;dpoke 0xe1da 9;dpoke 0xe1db 9} "Time: Time" {dpoke 0xe1d4 184;poke 0xe1d5 60} } create_trainer "Pleasure Hearts" {time 5} { "Invincible" {dpoke 0xb0d3 255} } create_trainer "Point Crisis" {time 1} { "Fire" {dpoke 0xa489 5} "Lives: Lives" {dpoke 0xa484 5} } create_trainer "Polar Star" {time 1} { "Do Not Explode When Hit" {dpoke 0x9d61 128} "Missile Is Always Ready" {dpoke 0x9d44 1} } create_trainer "Police Academy" {time 1} { "65535 Bullets" {dpoke 0x4d39 255;poke 0x4d3a 255} "Mistakes" {dpoke 0x5285 0;poke 0x528a 0} "Energy: Energy" {dpoke 0x5278 245} } create_trainer "Police Academy 2" {time 1} { "Bullets" {dpoke 0xc003 255} "Mistakes" {dpoke 0xc007 0} "Energy: Energy" {dpoke 0xc006 0} } create_trainer "Pooyan 2" {time 2} { "Shoot One Wolf (very Lame)" {dpoke 0xe006 1} } create_trainer "Poppaq The Fish" {time 1} { "Life Player 1" {dpoke 0xe122 0} } create_trainer "Poursuite" {time 1} { "Time: Time" {dpoke 0x870a 152} } create_trainer "Power Drift" {time 1} { "Always Qualify" {dpoke 0xe434 2;poke 0xe435 0} "Credits" {dpoke 0xe431 9} } create_trainer "Predator" {time 1} { "Bullet" {dpoke 0xc232 9;poke 0xc233 9;dpoke 0xc234 9} "Hand Grenade" {dpoke 0xc235 9;poke 0xc236 9} "Mines" {dpoke 0xc230 9;poke 0xc231 9} "Power: Power" {dpoke 0xc207 255} } create_trainer "Princess Maker" {time 1} { "0 Fatigue" {dpoke 0x1c3c 0} "Infinite Gold" {dpoke 0x1c96 255;poke 0x1c97 255} } create_trainer "Project A2" {time 1} { "Power: Power" {dpoke 0xc476 100} } create_trainer "Protector, The" {time 10} { "Life Bar" {dpoke 0xe024 5} "Lives: Lives" {dpoke 0xe016 9} } create_trainer "Psychic War - Cosmic Soldier 2" {time 5} { "Power: Fire Power" {dpoke 0xf07e 255;poke 0xf07f 255} "Power: Power" {dpoke 0xf07a 255;poke 0xf07b 255} } create_trainer "Psycho World" {time 2} { "Esp" {dpoke 0xa122 100} "Extra Mode" {dpoke 0xd40d 5} "Ice Shot Power Up" {dpoke 0xa214 5} "Life" {dpoke 0xa120 100} "Max Up Mode" {dpoke 0xd40d 3} "Normal Mode" {dpoke 0xd40d 2} "Normal Shot Power Up" {dpoke 0xa213 9} "Sonic Shot Power Up" {dpoke 0xa216 5} "Unlimited Power Ups In Power Mode (push 1 Trough 9)" {dpoke 0xd401 255} "Power: Fire Power Up" {dpoke 0xa215 5} "Power: Power Mode" {dpoke 0xd40d 0} "Weapon: Get All Weapons" {dpoke 0xa212 255} } create_trainer "Pumpkin Adventure 3" {time 1} { "Bishop Max Mp" {dpoke 0xd5f9 153;poke 0xd5fa 153} "Bishop Mp" {dpoke 0xd5f7 153;poke 0xd5f8 153} "Black Opal" {dpoke 0xd492 1} "Blue Opal" {dpoke 0xd490 1} "Book" {dpoke 0xd49d 1} "Butterfly" {dpoke 0xd498 1} "Cane" {dpoke 0xd4a2 1} "Cross" {dpoke 0xd48a 1} "Crow Bar" {dpoke 0xd47d 1} "Damien Experience" {dpoke 0xd634 153;poke 0xd635 153;dpoke 0xd636 153} "Damien Life" {dpoke 0xd62c 153;poke 0xd62d 153} "Damien Max Exp" {dpoke 0xd62e 153;poke 0xd62f 153} "Damien Max Experience" {dpoke 0xd637 0;poke 0xd638 0;dpoke 0xd639 1} "Damien Max Mp" {dpoke 0xd632 153;poke 0xd633 153} "Damien Mp" {dpoke 0xd630 153;poke 0xd631 153} "Defense" {dpoke 0xd5c9 153;poke 0xd5ca 153;dpoke 0xd602 153;dpoke 0xd603 153;dpoke 0xd63b 153;dpoke 0xd63c 153;dpoke 0xd674 153;dpoke 0xd675 153} "Female Insect" {dpoke 0xd49a 1} "Gas Can" {dpoke 0xd481 1} "Gloves" {dpoke 0xd487 1} "Gold Cross" {dpoke 0xd4a4 1} "Green Opal" {dpoke 0xd48e 1} "Jeff Experience" {dpoke 0xd66d 153;poke 0xd66e 153;dpoke 0xd66f 153} "Jeff Life" {dpoke 0xd665 153;poke 0xd666 153} "Jeff Max Exp" {dpoke 0xd667 153;poke 0xd668 153} "Jeff Max Experience" {dpoke 0xd670 0;poke 0xd671 0;dpoke 0xd672 1} "Jeff Max Mp" {dpoke 0xd66b 153;poke 0xd66c 153} "Jeff Mp" {dpoke 0xd669 153;poke 0xd66a 153} "Level" {dpoke 0xd5c8 153;poke 0xd601 153;dpoke 0xd63a 153;dpoke 0xd673 153} "Male Insect" {dpoke 0xd499 1} "Marble" {dpoke 0xd482 1;poke 0xd49e 1} "Marble 2" {dpoke 0xd48b 1} "Marble 3" {dpoke 0xd48d 1} "Match" {dpoke 0xd4a6 1} "Max Defense" {dpoke 0xd5cb 153;poke 0xd5cc 153;dpoke 0xd604 153;dpoke 0xd605 153;dpoke 0xd63d 153;dpoke 0xd63e 153;dpoke 0xd676 153;dpoke 0xd677 153} "Mirror" {dpoke 0xd489 1} "Mirror 2" {dpoke 0xd4a1 1} "Money" {dpoke 0xd4a8 153;poke 0xd4a9 153;dpoke 0xd4aa 153;dpoke 0xd4ab 153} "Necklace" {dpoke 0xd486 1;poke 0xd49c 1} "Orange Opal" {dpoke 0xd491 1} "Red Opal" {dpoke 0xd48f 1} "Red Scrolls" {dpoke 0xd4a3 1} "Scroll" {dpoke 0xd47f 1} "Silver Cross" {dpoke 0xd4a5 1} "Soup Bowl" {dpoke 0xd485 1} "Space Suit" {dpoke 0xd4a0 1} "Steve Experience" {dpoke 0xd5c2 153;poke 0xd5c3 153;dpoke 0xd5c4 153} "Steve Life" {dpoke 0xd5ba 153;poke 0xd5bb 153} "Steve Max Exp" {dpoke 0xd5bc 153;poke 0xd5bd 153} "Steve Max Experience" {dpoke 0xd5c5 0;poke 0xd5c6 0;dpoke 0xd5c7 1} "Steve Max Mp" {dpoke 0xd5c0 153;poke 0xd5c1 153} "Steve Mp" {dpoke 0xd5be 153;poke 0xd5bf 153} "Suit" {dpoke 0xd483 1} "Torch" {dpoke 0xd496 1} "Wallet" {dpoke 0xd4a7 1} "White Keycard 2" {dpoke 0xd49f 1} "Experience: Bishop Experience" {dpoke 0xd5fb 153;poke 0xd5fc 153;dpoke 0xd5fd 153} "Experience: Bishop Max Experience" {dpoke 0xd5f5 153;poke 0xd5f6 153;dpoke 0xd5fe 0;dpoke 0xd5ff 0;dpoke 0xd600 1} "Key: Black Key" {dpoke 0xd493 1} "Key: Blue Key" {dpoke 0xd494 1} "Key: Key" {dpoke 0xd47c 1;poke 0xd480 1;dpoke 0xd488 1;dpoke 0xd48c 1;dpoke 0xd49b 1} "Key: Key Card Green" {dpoke 0xd47e 1} "Key: Key Card White" {dpoke 0xd47b 1} "Key: Keycard Red" {dpoke 0xd484 1} "Key: Orange Key" {dpoke 0xd495 1} "Key: Weed Key" {dpoke 0xd497 1} "Life: Bishop Life" {dpoke 0xd5f3 153;poke 0xd5f4 153} "Weapon: Weapon" {dpoke 0xd5cd 9;poke 0xd606 9;dpoke 0xd63f 9;dpoke 0xd678 9} } create_trainer "Pyramid Warp" {time 1} { "Air (de-select To See Teleport When Room Is Clear" {dpoke 0xc077 255} "Death Is Frozen" {dpoke 0xc023 0} "First Black Monster Is Frozen" {dpoke 0xc033 0} "First Red Monster Is Frozen" {dpoke 0xc02b 0} "Second Black Monster Is Frozen" {dpoke 0xc04b 0} "Second Red Monster Is Frozen" {dpoke 0xc043 0} "Lives: Lives" {dpoke 0xc07c 99} "Weapon: Gun" {dpoke 0xc05e 1} } create_trainer "Pyramid Warp CAS" {time 1} { "Air (de-select To See Teleport When Room Is Clear" {dpoke 0xbff3 255} "Death Is Frozen" {dpoke 0xbf9f 0} "First Black Monster Is Frozen" {dpoke 0xbfaf 0} "First Red Monster Is Frozen" {dpoke 0xbfa7 0} "Second Black Monster Is Frozen" {dpoke 0xbfc7 0} "Second Red Monster Is Frozen" {dpoke 0xbfbf 0} "Lives: Lives" {dpoke 0xbff8 99} "Weapon: Gun" {dpoke 0xbfda 1} } create_trainer "Q-Bert" {time 2} { "Always Protected" {dpoke 0xe345 255} "No Enemies" {dpoke 0xe321 255} "Lives: Lives" {dpoke 0xe110 153} "Time: Infinite Time" {dpoke 0xec51 153} } create_trainer "Quebert" {time 1} { "No Enemies" {dpoke 0xb01d 1} "Pyramid (0-15)" {dpoke 0xb002 0} "Lives: Lives" {dpoke 0xb005 99} } create_trainer "Quinpl" {time 1} { "Black Pin" {dpoke 0xe020 9} "Blue Pin" {dpoke 0xe023 9} "Bonus Stage (1-12)" {dpoke 0xe019 1} "Club" {dpoke 0xe028 1} "Diamond" {dpoke 0xe029 1} "Duck" {dpoke 0xe024 9} "Final Mode For Normal Stages" {dpoke 0xe013 1} "Heart" {dpoke 0xe027 1} "Invulnerable (don't Use F1 !)" {dpoke 0xe003 1} "Normal Stage (0-51)" {dpoke 0xe010 0} "Pin Size (0-2)" {dpoke 0xe025 2} "Red Pin" {dpoke 0xe022 9} "Spade" {dpoke 0xe026 1} "White Pin" {dpoke 0xe021 9} "X-pos (0-255)" {dpoke 0xe701 0;poke 0xe70b 0} "Y-pos (0-190;+16 For 0xe70a)" {dpoke 0xe700 0;poke 0xe70a 16} "Lives: Lives" {dpoke 0xe011 5} "Stage: No Bonus Stage" {dpoke 0xe026 0;poke 0xe027 0;dpoke 0xe028 0;dpoke 0xe029 0} "Time: Timer" {dpoke 0xe231 153;poke 0xe233 153} } create_trainer "Quinpl Disk Version" {time 1} { "Black Pin" {dpoke 0xbc20 9} "Blue Pin" {dpoke 0xbc23 9} "Bonus Stage (1-12)" {dpoke 0xbc19 1} "Club" {dpoke 0xbc28 1} "Diamond" {dpoke 0xbc29 1} "Duck" {dpoke 0xbc24 9} "Final Mode For Normal Stages" {dpoke 0xbc13 1} "Heart" {dpoke 0xbc27 1} "Invulnerable (and No Suicide !)" {dpoke 0x7c26 1;poke 0xbc03 1} "Normal Stage (0-51)" {dpoke 0xbc10 0} "Pin Size (0-2)" {dpoke 0xbc25 2} "Red Pin" {dpoke 0xbc22 9} "Spade" {dpoke 0xbc26 1} "White Pin" {dpoke 0xbc21 9} "X-pos (0-255)" {dpoke 0xdc01 0;poke 0xdc0b 0} "Y-pos (0-190;+16 For 0xdc0a)" {dpoke 0xdc00 0;poke 0xdc0a 16} "Lives: Lives" {dpoke 0xbc11 5} "Stage: No Bonus Stage" {dpoke 0xbc26 0;poke 0xbc27 0;dpoke 0xbc28 0;dpoke 0xbc29 0} "Time: Timer" {dpoke 0xbd31 153;poke 0xbd33 153} } create_trainer "R-Type" {time 1} { "Invincible" {dpoke 0xe703 1} "Missile" {dpoke 0xea24 1} "Pods" {dpoke 0xea29 2;poke 0xea2f 2} } create_trainer "Raid On Bungeling Bay" {time 1} { "Energy: Energy" {dpoke 0xe0f5 0} "Weapon: Bombs" {dpoke 0xe037 9} } create_trainer "Rally" {time 1} { "Fuel" {dpoke 0xe039 64} "Lives: Lives" {dpoke 0xe035 5} } create_trainer "Rally-X" {time 1} { "High Gas" {dpoke 0x8682 1} "Maximum Speed" {dpoke 0x8687 19} } create_trainer "Ramasse-Miettes" {time 1} { "Time: Time" {dpoke 0x84d5 153} } create_trainer "Rambo" {time 2} { "Food" {dpoke 0xe812 6;poke 0xe817 24} "Hand Grenades" {dpoke 0xe815 10} "Life" {dpoke 0xe811 24} "Weapon: Activate All Weapons" {dpoke 0xe80e 255} "Weapon: Arrows" {dpoke 0xe813 10} "Weapon: Bazooka" {dpoke 0xe816 10} "Weapon: Machine Gun" {dpoke 0xe814 10} } create_trainer "Rambo 3" {time 2} { "Life" {dpoke 60ee 0} } create_trainer "Randar 3" {time 1} { "Life Mick" {dpoke 0xd074 3;poke 0xd075 231} "Life Player 1" {dpoke 0xd014 3;poke 0xd015 231} "Magic Mick" {dpoke 0xd076 3;poke 0xd077 231} "Magic Player 1" {dpoke 0xd016 3;poke 0xd017 231} "Money" {dpoke 0xd185 254;poke 0xd186 255;dpoke 0xd187 255} } create_trainer "Rastan Saga" {time 2} { "Life Bar" {dpoke 0xd91c 160} "Weapon: Get Fire Sword" {dpoke 0xd919 3} "Weapon: Weapon Expiration Timer" {dpoke 0xd91a 255} } create_trainer "Red Zone" {time 1} { "Shield: Shield" {dpoke 0xe0af 33} } create_trainer "Replicart" {time 1} { "Developer's Mode" {dpoke 0xc0ad 1;poke 0xc20c 33;dpoke 0xc4b8 1;dpoke 0xc4bb 255;dpoke 0xf1a3 255;dpoke 0xf1a4 255;dpoke 0xf1a5 255;dpoke 0xf1a6 255;dpoke 0xf1a9 5;dpoke 0xfbf5 31;dpoke 0xfbf7 28;dpoke 0xfbf8 31} "Freeze Time Before Crystal Moves" {dpoke 0xc181 53;poke 0xc182 53} } create_trainer "Rescue The Planet - MSX Magazine 2003" {time 1} { "Time: Time" {dpoke 0x8855 255} } create_trainer "Return of Jelda" {time 1} { "Damage" {dpoke 0xc724 0} "Power: Power" {dpoke 0xc725 100} } create_trainer "Rick & Mick's Adventure" {time 10} { "Shield: Shield" {dpoke 0xce13 255} } create_trainer "Rise Out From Dungeons" {time 5} { "Can Open Exit Door Without Key" {dpoke 0xec11 255} "First Red Man X-position" {dpoke 0xed11 255} "First Red Man Y-position" {dpoke 0xed12 191} "Second Red Man X-position" {dpoke 0xed31 255} "Second Red Man Y-position" {dpoke 0xed32 191} "Lives: Lives" {dpoke 0xec06 152} } create_trainer "River Raid" {time 1} { "Fuel" {dpoke 0xe178 172} "Lives: Lives" {dpoke 0xe135 153} } create_trainer "River Raid CAS" {time 1} { "Fuel" {dpoke 0xe17a 172} "Lives: Lives" {dpoke 0xe137 153} } create_trainer "RNFF-MSXdev06" {time 1} { "Bolts" {dpoke 0xed93 152} "Key: Keys" {dpoke 0xed92 153} "Power: Power" {dpoke 0xed94 153;poke 0xed95 9} } create_trainer "Road Fighter" {time 0.25} { "Invulnerable: Invulnerable" {dpoke 0xe049 0} "Lives: 99 Lives" {dpoke 0xe083 220} } create_trainer "Robocop" {time 1} { "Lives: Lives" {dpoke 0x7752 99} "Power: Power" {dpoke 0x75bb 100;poke 0x7746 255} } create_trainer "Robot Wars" {time 1} { "No Special Robot" {dpoke 0xa8ad 1} "Lives: Lives" {dpoke 0xa816 9} } create_trainer "Roboy" {time 1} { "Dragon Is Frozen For Player 1" {dpoke 0x9b14 127} "Dragon Is Frozen For Player 2" {dpoke 0x9b1f 127} "Lives: Lives Player 1" {dpoke 0x9b0a 3} "Lives: Lives Player 2" {dpoke 0x9b15 3} } create_trainer "Rocket Roger" {time 1} { "Fuel" {dpoke 0xf300 0;poke 0xf301 0;dpoke 0xf302 0} "Lives: Lives" {dpoke 0xf31f 58} } create_trainer "Rolling Blaster" {time 1} { "Lives: Infinite Lives" {dpoke 0xf108 9} } create_trainer "Romancia MSX2" {time 1} { "Defense: Shields" {dpoke 0xd066 64} "Item: be able to receive Unlimited Potions" {dpoke 0xd09b 1} "Money: Gold" {dpoke 0xd067 63} "Power: Hearts" {dpoke 0xd063 255} "Time: unlimited time" {dpoke 0xd075 0x99;poke 0xd076 0x99} "Weapon: Swords" {dpoke 0xd064 255} } create_trainer "Rona" {time 1} { "Invincible" {dpoke 0xa3e1 255} } create_trainer "Rotors" {time 1} { "Level (1-99)" {dpoke 0xc081 1} "Lives: Lives Player 1" {dpoke 0xc086 5} "Lives: Lives Player 2" {dpoke 0xc090 5} "Time: Time" {dpoke 0xe804 154;poke 0xe805 4} } create_trainer "RuinMaster2" {time 1} { "Life: Life 999" {dpoke 0xd001 0x99;poke 0xd002 0x09} "Money: Gold 9999" {dpoke 0xd006 0x99;poke 0xd007 0x99} } create_trainer "Rune Master" {time 1} { "Money" {dpoke 0x61e2 15;poke 0x61e3 39} "Power: Player 1 Power" {dpoke 0x61dc 255} } create_trainer "Sa-Zi-Ri" {time 1} { "Always Access To Next Part (? Key)" {dpoke 0x21ad 1} "Experience" {dpoke 0x21a7 98} "Faster Experience" {dpoke 0x21a6 16} "Flowers" {dpoke 0x21aa 98} "Glasses" {dpoke 0x21ac 98} "Vases" {dpoke 0x21ab 98} "Vitality" {dpoke 0x21a5 100} "Key: Keys" {dpoke 0x21ae 98} "Time: Time" {dpoke 0x18d1 255} } create_trainer "Saimazoom" {time 1} { "Remove Time Limit" {dpoke 0xd9d1 0} "Slot 1" {dpoke 0xd9e8 102} "Slot 2" {dpoke 0xd9e9 102} "Slot 3" {dpoke 0xd9ea 103} "Slot 4" {dpoke 0xd9eb 106} "Water" {dpoke 0xd9d8 153;poke 0xd9d9 153} "Lives: Lives" {dpoke 0xd9cf 9} } create_trainer "Salamander - Operation X" {time 1} { "Combinations: Simulate Nemesis 2 In Slot 2" {dpoke 0xf0f5 1} "Help Items: Scroll Stop (only For Part Of Stage 1)" {dpoke 0xe309 1} "Help Items: Spark Light For Planet Lavinia" {dpoke 0xe630 3} "Invincible : Invincible - Activate" {dpoke 0xe202 0} "Invincible : Invincible - De-activate" {dpoke 0xe202 1} "Lives: Lives " {dpoke 0xe300 153} "Stage: Next Stage Stage 1" {dpoke 0xe301 1} "Stage: Next Stage Stage 2" {dpoke 0xe301 2} "Stage: Next Stage Stage 3" {dpoke 0xe301 3} "Stage: Next Stage Stage 4" {dpoke 0xe310 4} "Stage: Next Stage Stage 5" {dpoke 0xe301 5} "Stage: Next Stage Stage 6" {dpoke 0xe301 6} "Stage: Next Stage Stage 7 - Operation X" {dpoke 0xe301 7} "Weapon: All Special Weapons " {dpoke 0xe310 7} "Weapon: Weapon Option 1" {dpoke 0xe400 1} "Weapon: Weapon Option 1 In Position 1" {dpoke 0xe402 1} "Weapon: Weapon Option 1 Player 2 (0=player 1)" {dpoke 0xe401 1} "Weapon: Weapon Option 2 Active" {dpoke 0xe410 1} "Weapon: Weapon Option 2 In Position 2" {dpoke 0xe412 2} "Weapon: Weapon Option 2 Player 2 (0=player 1)" {dpoke 0xe411 1} "Weapon: Weapon Option 3 Active" {dpoke 0xe420 1} "Weapon: Weapon Option 3 In Position 3" {dpoke 0xe422 3} "Weapon: Weapon Option 3 Player 2 (0=player 1)" {dpoke 0xe421 1} "Weapon: Weapon Option 4 Active (not In Dual Mode)" {dpoke 0xe430 1} "Weapon: Weapon Option 4 In Position 4" {dpoke 0xe432 4} "Weapon: Weapon Player 1 Arming Ball Missile" {dpoke 0xe341 4} "Weapon: Weapon Player 1 Double Laser" {dpoke 0xe340 6} "Weapon: Weapon Player 1 Extra Level After One Energy Pod" {dpoke 0xe31b 20} "Weapon: Weapon Player 1 Hawkwind Missile" {dpoke 0xe341 2} "Weapon: Weapon Player 1 Homing Missile" {dpoke 0xe341 3} "Weapon: Weapon Player 1 Laser" {dpoke 0xe340 3} "Weapon: Weapon Player 1 Meteor Laser" {dpoke 0xe340 5} "Weapon: Weapon Player 1 Normal Missile" {dpoke 0xe341 1} "Weapon: Weapon Player 1 Normal Shoot" {dpoke 0xe340 1} "Weapon: Weapon Player 1 Option Hold " {dpoke 0xe334 1} "Weapon: Weapon Player 1 Ripple Laser" {dpoke 0xe340 2} "Weapon: Weapon Player 1 Screw Laser" {dpoke 0xe340 4} "Weapon: Weapon Player 1 Ship Speed 1" {dpoke 0xe337 1} "Weapon: Weapon Player 1 Ship Speed 2" {dpoke 0xe337 2} "Weapon: Weapon Player 1 Ship Speed 3" {dpoke 0xe337 3} "Weapon: Weapon Player 1 Ship Speed 4" {dpoke 0xe337 4} "Weapon: Weapon Player 1 Ship Speed 5" {dpoke 0xe337 5} "Weapon: Weapon Player 1 Ship Speed 6" {dpoke 0xe337 6} "Weapon: Weapon Player 1 Ship Speed 7" {dpoke 0xe337 7} "Weapon: Weapon Player 1 Trampling Missile" {dpoke 0xe342 1} "Weapon: Weapon Player 1 Triple Laser" {dpoke 0xe340 7} "Weapon: Weapon Player 2 All Special Weapons " {dpoke 0xe390 7} "Weapon: Weapon Player 2 Arming Ball Missile" {dpoke 0xe3c1 4} "Weapon: Weapon Player 2 Double Laser" {dpoke 0xe3c0 6} "Weapon: Weapon Player 2 Extra Level After One Energy Pod" {dpoke 0xe39b 20} "Weapon: Weapon Player 2 Hawkwind Missile" {dpoke 0xe3c1 2} "Weapon: Weapon Player 2 Homing Missile" {dpoke 0xe3c1 3} "Weapon: Weapon Player 2 Laser" {dpoke 0xe3c0 3} "Weapon: Weapon Player 2 Lives" {dpoke 0xe380 153} "Weapon: Weapon Player 2 Meteor Laser" {dpoke 0xe3c0 5} "Weapon: Weapon Player 2 Normal Missile" {dpoke 0xe3c1 1} "Weapon: Weapon Player 2 Normal Shoot" {dpoke 0xe3c0 1} "Weapon: Weapon Player 2 Option" {dpoke 0xe3b4 1} "Weapon: Weapon Player 2 Ripple Laser" {dpoke 0xe3c0 2} "Weapon: Weapon Player 2 Screw Laser" {dpoke 0xe3c0 4} "Weapon: Weapon Player 2 Ship Speed 1 " {dpoke 0xe3b7 1} "Weapon: Weapon Player 2 Ship Speed 2 " {dpoke 0xe3b7 2} "Weapon: Weapon Player 2 Ship Speed 3 " {dpoke 0xe3b7 3} "Weapon: Weapon Player 2 Ship Speed 4 " {dpoke 0xe3b7 4} "Weapon: Weapon Player 2 Ship Speed 5 " {dpoke 0xe3b7 5} "Weapon: Weapon Player 2 Ship Speed 6 " {dpoke 0xe3b7 6} "Weapon: Weapon Player 2 Ship Speed 7 " {dpoke 0xe3b7 7} "Weapon: Weapon Player 2 Trampling Missile" {dpoke 0xe3c2 1} "Weapon: Weapon Player 2 Triple Laser" {dpoke 0xe3c0 7} } create_trainer "Sammyu Densetsu" {time 15} { "Lives: Lives" {dpoke 0xe016 9} "Power: Power" {dpoke 0xe024 5} } create_trainer "Sasa" {time 1} { "Shots" {dpoke 0xe07f 255} "Lives: Lives" {dpoke 0xe005 5} "Power: Power" {dpoke 0xe06f 255} } create_trainer "Satan 1" {time 1} { "Power Unlimited" {dpoke 0x8462 0} "Some Gauge" {dpoke 0xc88f 100} } create_trainer "Scarlet 7" {time 1} { "Power: Power" {dpoke 0xe106 10} } create_trainer "Scentipede" {time 1} { "Lives: Lives" {dpoke 0x4062 5} } create_trainer "Science Fiction" {time 1} { "Lives: Lives" {dpoke 0xa465 9} "Tears: Tears Shoot Down" {dpoke 0xa460 1} "Tears: Tears Shoot Up" {dpoke 0xa461 1} } create_trainer "Scion" {time 1} { "Extension" {dpoke 0xe100 1} "Lives: Lives" {dpoke 0xe044 16} } create_trainer "Scope On" {time 1} { "Lives: Lives" {dpoke 0xea19 5} } create_trainer "Scramble Eggs" {frame} { "Keep White Egg 1 In The Middle" {dpoke 0xe2a7 128} "Keep White Egg 2 In The Middle" {dpoke 0xe2aa 128} "No Purple Egg" {dpoke 0xe2ad 0} "Lives: Lives" {dpoke 0xe2f8 3} } create_trainer "Scramble Formation" {time 1} { "Help Planes" {dpoke 0xc102 5} "Lives: Lives" {dpoke 0xc101 153} } create_trainer "SD-Snatcher" {time 1} { "All Locations Accessible" {dpoke 0xcdc0 255;poke 0xcdc1 1} "Kill Little Spiders In One Blast" {dpoke 0xc820 0;poke 0xc840 0} "L. Angel" {dpoke 0xc4b0 1;poke 0xc4b1 1} "Lingu Disk" {dpoke 0xc490 1;poke 0xc491 1} "Mars Coupon" {dpoke 0xc478 1;poke 0xc479 1} "Milkyway" {dpoke 0xc438 2;poke 0xc439 255} "Newtrits" {dpoke 0xc459 99} "Open Door To The 3rd Church's Floor Is Open (24 Candles Blown Out)" {dpoke 0xce9b 24} "Rancher" {dpoke 0xc410 2;poke 0xc411 255} "Red Spider" {dpoke 0xc468 1} "S. Grade" {dpoke 0xc4e9 255} "Stingray" {dpoke 0xc3a8 2;poke 0xc4a9 255} "Storm" {dpoke 0xc3c0 2;poke 0xc4c1 255} "T Blaster" {dpoke 0xc418 2;poke 0xc419 255} "Walk Trough Walls Off" {dpoke 0x92b6 0} "Walk Trough Walls On" {dpoke 0x92b6 201} "Ammo: Ammo For B. Hawk" {dpoke 0xc3d1 231;poke 0xc3d2 3} "Ammo: Ammo For Big 9 Matrix" {dpoke 0xc3f9 231;poke 0xc3fa 3} "Ammo: Ammo For F. Ball" {dpoke 0xc3b1 231;poke 0xc3b2 3} "Ammo: Ammo For G.hound" {dpoke 0xc3d9 231;poke 0xc3da 3} "Ammo: Ammo For I. Cepter" {dpoke 0xc3e1 231;poke 0xc3e2 3} "Ammo: Ammo For K. Sprint" {dpoke 0xc3b9 231;poke 0xc3ba 3;dpoke 0xc3c9 231;dpoke 0xc3ca 3} "Ammo: Ammo For N. Point" {dpoke 0xc3f1 231;poke 0xc3f2 3} "Ammo: Ammo For S. Grade" {dpoke 0xc3e9 231;poke 0xc3ea 3} "Ammo: Ammo For Stingray" {dpoke 0xc3a9 231;poke 0xc3aa 3} "Ammo: Ammo For Storm" {dpoke 0xc3c1 231;poke 0xc3c2 3} "Gun: B. Hawk" {dpoke 0xc3d0 2;poke 0xc4d1 255} "Gun: Big 9 Matrix" {dpoke 0xc3f8 2;poke 0xc4f9 255} "Gun: N. Point" {dpoke 0xc3f0 2;poke 0xc4f1 255} "Gun Skill: Skill For B. Hawk" {dpoke 0xc3d5 100} "Gun Skill: Skill For F. Ball" {dpoke 0xc3b5 100} "Gun Skill: Skill For G. Hound" {dpoke 0xc3dd 100} "Gun Skill: Skill For G. Matrix" {dpoke 0xc3fd 100} "Gun Skill: Skill For I. Cepter" {dpoke 0xc3e5 100} "Gun Skill: Skill For K. Sprint" {dpoke 0xc3bd 100;poke 0xc3cd 100} "Gun Skill: Skill For N. Point" {dpoke 0xc3f5 100} "Gun Skill: Skill For S. Grade" {dpoke 0xc3ed 100} "Gun Skill: Skill For Stingray" {dpoke 0xc3ad 100} "Gun Skill: Skill For Storm" {dpoke 0xc3c5 100} "Important Item: 3d Goggles" {dpoke 0xc470 1;poke 0xc471 1} "Important Item: Blue Spider" {dpoke 0xc4a0 1;poke 0xc4a1 1} "Important Item: Movie Ticket" {dpoke 0xc480 1;poke 0xc481 1} "Important Item: Red Spider" {dpoke 0xc469 1} "Important Item: S. Grade" {dpoke 0xc3e8 2} "Important Item: Vip Card" {dpoke 0xc4a8 1;poke 0xc4a9 1} "Item: Bomb" {dpoke 0xc400 2;poke 0xc401 255} "Item: C Killer" {dpoke 0xc440 2;poke 0xc441 255} "Item: Card" {dpoke 0xc488 1;poke 0xc489 1} "Item: Chaf" {dpoke 0xc430 2;poke 0xc431 255} "Item: Comet" {dpoke 0xc428 2;poke 0xc429 255} "Item: Dball" {dpoke 0xc408 2;poke 0xc409 255} "Item: F. Ball" {dpoke 0xc3b0 2;poke 0xc4b1 255} "Item: Flare" {dpoke 0xc448 2;poke 0xc449 255} "Item: G Mine" {dpoke 0xc420 2;poke 0xc421 255} "Item: G. Hound" {dpoke 0xc3d8 2;poke 0xc4d9 255} "Item: I. Cepter" {dpoke 0xc3e0 2;poke 0xc4e1 255} "Item: Junkers" {dpoke 0xc451 99} "Item: Jyro" {dpoke 0xc461 99} "Item: K. Sprint" {dpoke 0xc3b8 2;poke 0xc3c8 2;dpoke 0xc4b9 255;dpoke 0xc4c9 255} "Key: Master Key" {dpoke 0xc498 1;poke 0xc499 1} "Status: Max Deference Level" {dpoke 0xce87 64} "Status: Max Life" {dpoke 0xce81 255} "Status: Max Out All Stats" {dpoke 0xce82 255;poke 0xce83 255;dpoke 0xce85 255;dpoke 0xce86 255;dpoke 0xce88 255;dpoke 0xce89 255;dpoke 0xce8b 255;dpoke 0xce8c 255} "Status: Max Rank" {dpoke 0xce80 64} "Status: Max Speed Level" {dpoke 0xce8a 64} "Status: Max Strength Level" {dpoke 0xce84 64} "Status: Money" {dpoke 0xce8d 255;poke 0xce8e 255} } create_trainer "Seiken Acho - Kung-Fu Master Taekwondo" {time 1} { "Power: Power" {dpoke 0xecad 255} "Time: Time" {dpoke 0xecc6 2} } create_trainer "Seikima 2 Special" {time 10} { "Life" {dpoke 0xe060 255;poke 0xe061 255} "Money" {dpoke 0xe062 255} "Weapon: Weapon (try 0-5)" {dpoke 0xe048 5} } create_trainer "Seleniak" {time 1} { "Level Player 1 (0-99)" {dpoke 0xfd05 0} "Level Player 2 (0-99)" {dpoke 0xfd19 0} "Lives: Lives Player 1" {dpoke 0xcd06 5} "Lives: Lives Player 2" {dpoke 0xfd1a 5} } create_trainer "Shoot The Flying Windows" {time 1} { "Score (select Only When Game Begins)" {dpoke 0xadf4 255} } create_trainer "Shout Match" {time 1} { "Power: Power" {dpoke 0xc001 128} } create_trainer "Silviana" {time 1} { "Gold" {dpoke 0x8ca1 255;poke 0x8ca2 255} "Power: Power" {dpoke 0x8c9d 255} } create_trainer "Sinbad" {time 1} { "Lives: Lives" {dpoke 0xeae3 6} "Power: Power" {dpoke 0xead7 14} } create_trainer "Sink King" {time 1} { "Level Player 1 (0-99)" {dpoke 0xf705 0} "Level Player 2 (0-99)" {dpoke 0xf71a 0} "Soap (de-select To Get Bonus)" {dpoke 0xf754 63} "Lives: Lives Player 1" {dpoke 0xf706 5} "Lives: Lives Player 2" {dpoke 0xf71b 5} } create_trainer "Siryousensen - War Of The Dead" {time 1} { "Bullet" {dpoke 0xc03f 153} "Lives: Lives" {dpoke 0x1020 50} } create_trainer "Ski Command" {time 1} { "Lives: Lives" {dpoke 0xe90e 4} } create_trainer "Skooter" {time 1} { "Sheet (0-15)" {dpoke 0xc3bd 0} "Invulnerable: Invulnerable" {dpoke 0xca18 10} "Lives: Lives" {dpoke 0xc3bc 99} } create_trainer "Skramble" {time 1} { "Always Fuel" {dpoke 0x010b 0} "Lives: Lives" {dpoke 0x010e 4} } create_trainer "Sky Jaguar" {time 2} { "Double Shoot" {dpoke 0xe1d5 1} "Single Shoot" {dpoke 0xe1d5 0} "Invulnerable: Invulnerable" {dpoke 0xe1ce 0;poke 0xe1cf 0;dpoke 0xe1d0 0;dpoke 0xe1d1 0;dpoke 0xe1dd 0} "Lives: Lives" {dpoke 0xe050 99} } create_trainer "Sky Vision" {time 1} { "Lives: Lives" {dpoke 0xf33b 9} } create_trainer "Skygaldo" {time 1} { "Super Explosives And Full Power" {dpoke 0xf327 255} } create_trainer "Skyhawk" {time 1} { "Lives: Lives" {dpoke 0xa693 58} } create_trainer "Smack Wacker" {time 1} { "Power (de-select To Get Bonus)" {dpoke 0x55d4 44} "Super" {dpoke 0x4877 4} "Lives: Lives" {dpoke 0x485c 99} } create_trainer "Snail Maze" {time 1} { "Time: Time" {dpoke 0xc01d 154} } create_trainer "Snake-It" {time 1} { "Bonus Maximum (de-select To Get It)" {dpoke 0xd704 99} "Bonus Multiplier (de-select To Get It)" {dpoke 0xd706 9} "More Points To Eat" {dpoke 0xd701 255} "Round (1-10) Effective After Esc" {dpoke 0xd700 1} "Lives: Lives" {dpoke 0xd703 99} } create_trainer "Sofia" {time 5} { "Hearts" {dpoke 0xe6eb 5} "Invincible (except Lava And Water)" {dpoke 0xe6ed 255} } create_trainer "Sonyc" {time 1} { "99 Rings... Hooray :p" {dpoke 0xd311 153} } create_trainer "Soukoban Pocket" {time 1} { "Lives: Lives" {dpoke 0xd410 4} } create_trainer "Soul of a Robot" {time 1} { "Lives: Lives" {dpoke 0x17f1 9;poke 0x17f2 9} } create_trainer "Space Busters" {time 1} { "Lives: Lives" {dpoke 0xd233 9} } create_trainer "Space Busters v2" {time 1} { "Lives: Lives" {dpoke 0x4076 58} } create_trainer "Space Camp" {time 1} { "Left Base Ammo" {dpoke 0x5bd8 10} } create_trainer "Space Invaders" {time 1} { "Lives: Lives (for The Space Invader (1984) (taito) Version" {dpoke 0xe046 4} } create_trainer "Space Manbow" {time 1} { "Easy End Bosses" {dpoke 0xce82 0;poke 0xce96 0;dpoke 0xced6 0;dpoke 0xcf16 0;dpoke 0xcfd6 0} "Finished Space Manbow 1x" {dpoke 0xca04 1} "Finished Space Manbow 2x" {dpoke 0xca04 2} "Finished Space Manbow 3x" {dpoke 0xca04 3} "Invincible" {dpoke 0xca53 03;poke 0xca54 03} "Player Speed 1" {dpoke 0xcb01 1} "Player Speed 2" {dpoke 0xcb01 2} "Player Speed 3" {dpoke 0xcb01 3} "Player Speed 4" {dpoke 0xcb01 4} "Lives: Lives" {dpoke 0xcb0f 153} "Stage: Stage 0" {dpoke 0xca10 0} "Stage: Stage 1" {dpoke 0xca10 1} "Stage: Stage 2" {dpoke 0xca10 2} "Stage: Stage 3" {dpoke 0xca10 3} "Stage: Stage 4" {dpoke 0xca10 4} "Stage: Stage 5" {dpoke 0xca10 5} "Stage: Stage 6" {dpoke 0xca10 6} "Stage: Stage 7" {dpoke 0xca10 7} "Stage: Stage 8" {dpoke 0xca10 8} "Weapon: Have Flash Bomb" {dpoke 0xcb1d 1} "Weapon: Missile" {dpoke 0xcb48 128;poke 0xcb49 3} "Weapon: Option 1" {dpoke 0xcac0 2;poke 0xcac1 3;dpoke 0xcad8 253;dpoke 0xcb51 1} "Weapon: Option 1 Shoots Backwards" {dpoke 0xcb50 6} "Weapon: Option 1 Shoots Forwards" {dpoke 0xcb50 8} "Weapon: Option 1 Shoots Upwards" {dpoke 0xcb50 7} "Weapon: Option 2" {dpoke 0xcae0 2;poke 0xcae1 3;dpoke 0xcaf8 2;dpoke 0xcb59 1} "Weapon: Option 2 Shoots Backwards" {dpoke 0xcb58 4} "Weapon: Option 2 Shoots Downwards" {dpoke 0xcb58 3} "Weapon: Option 2 Shoots Forwards" {dpoke 0xcb58 2} "Weapon: Power Bar" {dpoke 0xcb08 16} } create_trainer "Space Rescue" {time 1} { "Energy: Energy" {dpoke 0x4067 224} "Lives: Lives" {dpoke 0x4031 5} } create_trainer "Sparkie" {time 2} { "Do Not Explode When The Fuse Is On Fire" {dpoke 0xe005 0} } create_trainer "Spelunker" {time 1} { "Cancel Blue Ghost" {dpoke 0xec05 0} "Flares" {dpoke 0xe091 10} "Stop In-game Timer" {dpoke 0xe945 255} "Lives: Lives" {dpoke 0xe084 10} "Weapon: Bombs" {dpoke 0xe090 10} } create_trainer "Splash" {time 1} { "Level (0-7)" {dpoke 0x9bc5 0} "Mouse Is Frozen" {dpoke 0x9bd4 8} "Mouse Is Stunned" {dpoke 0x9bd5 63} "Tools" {dpoke 0x9bc8 99} "Lives: Lives" {dpoke 0x9bc6 99} } create_trainer "Stairs" {time 1} { "Lives: Lives" {dpoke 0xa14f 4} "Time: Time" {dpoke 0xa19f 255} } create_trainer "Star Blazer" {time 1} { "Lives: Lives" {dpoke 0xe409 99} "Weapon: Bombs" {dpoke 0xe415 99} } create_trainer "Star Fighter" {time 1} { "Fuel" {dpoke 0x7096 8} "Shield: Shield" {dpoke 0x7097 128} } create_trainer "Star Force" {time 1} { "Lives: Lives" {dpoke 0xe405 100} } create_trainer "Star Soldier" {time 1} { "Get A More Powerful Shot" {dpoke 0xce84 3} "Invincible" {dpoke 0xcf20 255} } create_trainer "Star Wars" {time 1} { "Left Base Ammo" {dpoke 0x5bd8 10} "Middle Base Ammo" {dpoke 0x5bd9 10} "Right Base Ammo" {dpoke 0x5bda 10} } create_trainer "Starbite" {time 1} { "Lives: Lives" {dpoke 0x408a 5} "Time: Time" {dpoke 0x4092 0} } create_trainer "Starbuggy" {time 1} { "Invulnerable: Invulnerable" {dpoke 0x4c54 0} "Lives: Lives" {dpoke 0x41c2 4} } create_trainer "Starquake" {time 10} { "Fire" {dpoke 0x4064 128} "Life" {dpoke 0x4062 128} "Steps" {dpoke 0x4063 128} "Lives: Lives" {dpoke 0x4061 98} } create_trainer "Step Up" {time 1} { "Lives: Lives" {dpoke 0xe601 5} "Power: Power" {dpoke 0xe41e 120} } create_trainer "Stepper" {time 5} { "Have Shot" {dpoke 0xe904 1} "Lives: Lives" {dpoke 0xe901 99} } create_trainer "Strange Loop" {time 1} { "Charges" {dpoke 0xc48d 99;poke 0xc48e 99} "Patches" {dpoke 0xc48f 99} } create_trainer "Strategic Mars" {time 1} { "Money Maxed Out" {dpoke 0xc33e 255;poke 0xc33f 255} "Energy: Energy" {dpoke 0xc1bc 14} "Shield: Shield" {dpoke 0xc1bb 14} } create_trainer "Stratos" {time 3} { "Exit Always Open" {dpoke 0xe042 0} "Hearts" {dpoke 0xe054 99} "Invulnerable (de-select On Exit And Press On Esc)" {dpoke 0xe000 2;poke 0xe020 6} "Time (de-select On Exit To Get Bonus)" {dpoke 0xe052 99} "Stage: Stage (0-12)" {dpoke 0xe040 0} "Weapon: Bombs" {dpoke 0xe053 99} } create_trainer "Street Neo Fighter 2" {time 1} { "Player 1 Invulnerable" {dpoke 0x3c0a 100;poke 0x3c0b 100} "Player 1 Will Not Win" {dpoke 0x3c0a 0;poke 0x3c0b 0} "Player 2 Invulnerable" {dpoke 0x3d0a 100;poke 0x3d0b 100} "Player 2 Will Not Win" {dpoke 0x3d0a 0;poke 0x3d0b 0} } create_trainer "Super Boy 1" {time 1} { "Big Mario" {dpoke 0xe372 16} "Coins" {dpoke 0xe233 152} "Fire (on Graph Key)" {dpoke 0xe373 16} "World (0-3)" {dpoke 0xe044 0} "X-pos (0-255)" {dpoke 0xe36d 0} "Y-pos (0-160)" {dpoke 0xe36c 0} "Lives: Lives" {dpoke 0xe043 5} "Stage: Stage (0-3)" {dpoke 0xe045 0} "Time: Time" {dpoke 0xe049 153;poke 0xe04a 153} } create_trainer "Super Boy II" {time 1} { "Big Mario" {dpoke 0xe372 16} "Coins" {dpoke 0xe233 152} "Fire (on Graph Key)" {dpoke 0xe373 16} "World (0-3)" {dpoke 0xe044 0} "X-pos (0-255)" {dpoke 0xe36d 0} "Y-pos (0-160)" {dpoke 0xe36c 0} "Lives: Lives" {dpoke 0xe043 5} "Stage: Stage (0-3)" {dpoke 0xe045 0} "Time: Time" {dpoke 0xe049 153;poke 0xe04a 153} } create_trainer "Super Boy III" {time 1} { "Big Mario" {dpoke 0xe190 255} "Coins" {dpoke 0xe18b 152} "Fire (on Graph Key)" {dpoke 0xe191 255} "Invulnerable (don't Fall)" {dpoke 0xe177 255;poke 0xe192 255} "Stones" {dpoke 0xe18a 8} "Lives: Lives" {dpoke 0xe26b 99} "Stage Select: Round 01" {dpoke 0xe26c 1} "Stage Select: Round 02" {dpoke 0xe26c 2} "Stage Select: Round 03" {dpoke 0xe26c 3} "Stage Select: Round 04" {dpoke 0xe26c 4} "Stage Select: Round 05" {dpoke 0xe26c 5} "Stage Select: Round 06" {dpoke 0xe26c 6} "Stage Select: Round 07" {dpoke 0xe26c 7} "Stage Select: Round 08" {dpoke 0xe26c 8} "Stage Select: Round 09" {dpoke 0xe26c 9} "Stage Select: Round 10" {dpoke 0xe26c 10} "Stage Select: Round 11" {dpoke 0xe26c 11} "Stage Select: Round 12" {dpoke 0xe26c 12} "Stage Select: Round 13" {dpoke 0xe26c 13} "Stage Select: Round 14" {dpoke 0xe26c 14} "Stage Select: Round 15" {dpoke 0xe26c 15} "Stage Select: Round 16" {dpoke 0xe26c 16} "Time: Time" {dpoke 0xe18c 255} } create_trainer "Super Cobra" {time 2} { "Fuel" {dpoke 0xe51c 128} "Invulnerable: Invulnerable" {dpoke 0xe50e 0} "Lives: Lives" {dpoke 0xe050 99} } create_trainer "Super Cooks" {time 10} { "Dish" {dpoke 0xcfac 153;poke 0xcfad 153} "Life" {dpoke 0xcfa1 0;poke 0xcfa2 2} "Max Hearts" {dpoke 0xcf9d 0;poke 0xcf9e 2} } create_trainer "Super Laydock - Mission Striker" {time 2} { "Infinite Docking" {dpoke 0xe37c 200} "Power: Power Player 1" {dpoke 0xe2f3 255} "Power: Power Player 2" {dpoke 0xe2fb 255} "Weapon: All Weapons Player 1" {dpoke 0xe480 255} "Weapon: All Weapons Player 2" {dpoke 0xe481 255} } create_trainer "Super Pierrot" {time 1} { "Have Ball" {dpoke 0xe7f0 1} "Lives: Lives" {dpoke 0xe046 255} } create_trainer "Super Rambo Special" {time 2} { "Explosive Arrows" {dpoke 0xc168 255} "Flowers" {dpoke 0xc16c 255} "Hand Grenades" {dpoke 0xc169 255} "Handgun Bullets" {dpoke 0xc165 255} "Prevent The Sidekick From Screaming And Moaning" {dpoke 0xc216 255;poke 0xc225 255} "Shotgun Bullets" {dpoke 0xc167 255} "Summon Sidekick" {dpoke 0xc218 1} "Key: Keys" {dpoke 0xc16b 255} "Power: Power" {dpoke 0xc155 255} "Weapon: Arrows" {dpoke 0xc166 255} "Weapon: Bazooka" {dpoke 0xc16a 255} } create_trainer "Super Runner" {time 5} { "Invincible" {dpoke 0xccce 1;poke 0xcccf 255} "Time: Time" {dpoke 0xcf23 59} } create_trainer "Super Snake" {frame} { "Lives: Lives" {dpoke 0xe00c 153} } create_trainer "Super Tritorn" {time 1} { "Exp" {dpoke 0xd023 255} "Life" {dpoke 0xd024 255} "Red Life" {dpoke 0xd025 255} } create_trainer "Survivors" {time 1} { "Earth Droid Power" {dpoke 0xae13 6} "Rock Droid Power" {dpoke 0xae15 6} "Tele Droid Power" {dpoke 0xae14 6} "Lives: Lives" {dpoke 0xae10 9} "Time: Time" {dpoke 0xae0c 2;poke 0xae0d 0;dpoke 0xae0e 0;dpoke 0xae0f 0} } create_trainer "Swing" {time 5} { "Lives: Lives" {dpoke 0xe30f 100} } create_trainer "T-Virus" {time 1} { "Power (de-select In Second Part Of Each Stage)" {dpoke 0xc044 255} } create_trainer "Takahasi Meijin no Boukenjima - Wonder Boy" {time 2} { "Power (de-select To Get Bonus)" {dpoke 0xe0b3 32} "Invulnerable: Invulnerable" {dpoke 0xe009 212;poke 0xe01d 0} "Lives: Lives" {dpoke 0xe0b2 10} } create_trainer "Tamaire - MSX Magazine 2003" {time 1} { "Time: Time" {dpoke 0x8c76 60} } create_trainer "Tank Battalion" {time 1} { "Next Level After One Kill" {dpoke 0xe04c 1} "Lives: Lives" {dpoke 0xe04d 153} } create_trainer "Tawara" {time 0.1} { "Flame 1" {dpoke 0xe0ff 255} "Flame 2" {dpoke 0xe110 255} "Flame 3" {dpoke 0xe121 255} "Goods" {dpoke 0xe130 16} "Oxygen Level" {dpoke 0xe02e 32} "Lives: Lives" {dpoke 0xe12f 5} } create_trainer "Teenage Mutant Hero Turtles" {time 1} { "Invincible" {dpoke 0x5f66 16} } create_trainer "Teki Paki" {time 1} { "No Game Acceleration" {dpoke 0xe104 32} } create_trainer "Tele bunny" {time 1} { "Snake Y-position" {dpoke 0xe018 0} "Lives: Lives" {dpoke 0xe064 255} } create_trainer "Tengoku Yoitoko - Heaven" {time 1} { "Invincible" {dpoke 0xe31e 255} "Status: Attack Power" {dpoke 0xe03c 0x99;poke 0xe03d 0x99} "Status: Gold" {dpoke 0xe03e 0x99;poke 0xe03f 0x99} "Status: Virtue" {dpoke 0xe03a 0x99;poke 0xe03b 0x99} "Status: Vitality" {dpoke 0xe038 0x99;poke 0xe039 0x99} } create_trainer "Tennis - MSX Magazine 2003" {time 1} { "Balls" {dpoke 0x89c7 80} } create_trainer "Tensai Rabbian Daifunsen" {time 10} { "Lives: Lives" {dpoke 0xec23 255} "Time: Timer" {dpoke 0xec27 255} } create_trainer "Testament" {time 1} { "Hand Grenades" {dpoke 0x59de 32} "Map" {dpoke 0x59e3 1} "Strong Bullets" {dpoke 0x59d9 255} "Lives: Lives" {dpoke 0x59d7 144} "Shield: Shield" {dpoke 0x59dd 1} } create_trainer "Tetris" {time 1} { "Next Block Always Bar" {dpoke 0xd28a 1} } create_trainer "The Maze of Galious - Knightmare II" {time 1} { "Item: Armor" {dpoke 0xe086 1} "Item: Bell" {dpoke 0xe083 1} "Item: Bible" {dpoke 0xe08f 1} "Item: Boots" {dpoke 0xe080 1} "Item: Bracelet" {dpoke 0xe08d 1} "Item: Bread And Water" {dpoke 0xe098 1} "Item: Candle" {dpoke 0xe085 1} "Item: Carpet" {dpoke 0xe087 1} "Item: Cross" {dpoke 0xe07a 1} "Item: Crown" {dpoke 0xe07d 1} "Item: Dagger" {dpoke 0xe095 1} "Item: Decorative Doll" {dpoke 0xe081 1} "Item: Earrings" {dpoke 0xe08c 1} "Item: Feather" {dpoke 0xe096 1} "Item: Halo" {dpoke 0xe084 1} "Item: Harp" {dpoke 0xe090 1} "Item: Helm" {dpoke 0xe07e 1} "Item: Helmet" {dpoke 0xe088 1} "Item: Invulnerable" {dpoke 0xe518 1} "Item: Lamp" {dpoke 0xe089 1} "Item: Magnifying Glass" {dpoke 0xe075 1} "Item: Necklace" {dpoke 0xe07c 1} "Item: Oar" {dpoke 0xe07f 1} "Item: Pendant" {dpoke 0xe08b 1} "Item: Pitcher" {dpoke 0xe093 1} "Item: Ring" {dpoke 0xe08e 1} "Item: Robe" {dpoke 0xe082 1} "Item: Sabre" {dpoke 0xe094 1} "Item: Salt" {dpoke 0xe099 1} "Item: Shield" {dpoke 0xe097 3} "Item: Triangle" {dpoke 0xe091 1} "Item: Trumpet Shell" {dpoke 0xe092 1} "Item: Vase" {dpoke 0xe08a 1} "Item: World 01 Items" {dpoke 0xe063 240} "Item: World 02 Items" {dpoke 0xe064 240} "Item: World 03 Items" {dpoke 0xe065 240} "Item: World 04 Items" {dpoke 0xe066 240} "Item: World 05 Items" {dpoke 0xe067 240} "Item: World 06 Items" {dpoke 0xe068 240} "Item: World 07 Items" {dpoke 0xe069 240} "Item: World 08 Items" {dpoke 0xe06a 240} "Item: World 09 Items" {dpoke 0xe06b 240} "Item: World 10 Items" {dpoke 0xe06c 224} "Misc: All Combos With Konami Carts" {dpoke 0xf0f8 255} "Misc: Bible (ctrl) Uses Left" {dpoke 0xe531 255} "Misc: Fall Down When Hit" {dpoke 0xe50b 1} "Misc: Screen Stays Frozen" {dpoke 0xe0d6 64} "Misc: Unlimited Bible Uses" {dpoke 0xe531 1} "Misc: Zeus Cheat" {dpoke 0xe027 1} "Status: Arrows" {dpoke 0xe046 153;poke 0xe047 9} "Status: Coins" {dpoke 0xe048 153;poke 0xe049 9} "Status: Keys" {dpoke 0xe04a 153;poke 0xe04b 9} "Status: Max Exp" {dpoke 0xe051 1;poke 0xe055 1} "Status: Vitality Aphrodite" {dpoke 0xe052 255;poke 0xe053 255} "Status: Vitality Popolon" {dpoke 0xe056 255;poke 0xe057 255} "Weapon: Arrows" {dpoke 0xe070 1} "Weapon: Ceramic Arrows" {dpoke 0xe071 1} "Weapon: Fire" {dpoke 0xe073 1} "Weapon: Mine" {dpoke 0xe074 255} "Weapon: Rolling Fire" {dpoke 0xe072 1} "Weapon Active: Arrow" {dpoke 0xe510 1} "Weapon Active: Ceramic Arrow" {dpoke 0xe510 2} "Weapon Active: Fire" {dpoke 0xe510 3} "Weapon Active: Magnifying Glass" {dpoke 0xe510 6} "Weapon Active: Mine" {dpoke 0xe510 5} "Weapon Active: Nothing" {dpoke 0xe510 0} "Weapon Active: Rolling Fire" {dpoke 0xe510 4} "World 10 location: Location At Start" {dpoke 0xe06e 3} "World 10 location: Location Left Tower" {dpoke 0xe06e 2} "World 10 location: Location Middle Tower" {dpoke 0xe06e 0} "World 10 location: Location Right Tower" {dpoke 0xe06e 1} "World 10 location: World 10 Activator" {dpoke 0xe06d 1} } create_trainer "Theseus" {time 1} { "Enemies Are Frozen" {dpoke 0xedf2 255} "Level (0-12)" {dpoke 0xede7 0} "Ring" {dpoke 0xedf5 255} "Time (de-select To Get Bonus)" {dpoke 0xeddc 255;poke 0xeddd 10} "Key: Key" {dpoke 0xedf4 255} "Power: Power" {dpoke 0xede2 153;poke 0xede3 9} } create_trainer "Thexder" {time 2} { "Disable Killer Missiles" {dpoke 0xf2ec 255} "Level (0-9)" {dpoke 0xf2d8 0} "Energy: Energy" {dpoke 0xf2d4 255;poke 0xf2d6 255} "Shield: Shield" {dpoke 0xf2ca 0;poke 0xf2dd 0} } create_trainer "Thexder 2 (Firehawk)" {time 2} { "Power: Do Not Loose Power While Shooting" {dpoke 0x12d5 255} "Power: Full Power" {dpoke 0x12d5 250} "Power: Max Energy 500" {dpoke 0x12d8 250} "Power: Shield Power Does Not Decline" {dpoke 0x12f8 255} "Power: Unlimited Power" {dpoke 0x12d6 250} "Sub Weapons: Dart Missiles" {dpoke 0x134e 99} "Sub Weapons: Ecm Gas" {dpoke 0x1351 99} "Sub Weapons: Flashers" {dpoke 0x1350 99} "Sub Weapons: Missiles" {dpoke 0x12e6 99} "Sub Weapons: Napalm Bombs" {dpoke 0x134f 99} "Sub Weapons: Stoppers" {dpoke 0x134d 99} "Weapon: Get Extremely Powerfull Laser" {dpoke 0x130f 255} "Weapon: Get Normal Laser" {dpoke 0x130f 1} "Weapon: Get Very Weak Laser" {dpoke 0x130f 0} } create_trainer "Thing Bounces Back" {time 1} { "Oil Level" {dpoke 0x8e4e 180} } create_trainer "Three Dragon Story, The - Muffie version" {time 10} { "Lives: Unlimited Lives" {dpoke 0x5b94 25} } create_trainer "Time Bomb" {time 1} { "X-pos Easy Start (de-select To Move!)" {dpoke 0xdf23 90} "Invulnerable: Invulnerable" {dpoke 0xdf2d 0} "Lives: Lives Player 1" {dpoke 0xde86 9} "Lives: Lives Player 2" {dpoke 0xdeb6 9} "Time: Infinite Time" {dpoke 0x9151 9} } create_trainer "Time Curb" {time 1} { "Level (0-4)" {dpoke 0x40b1 0} "Plasma - F5 On Level 5" {dpoke 0x40d4 255} "Play One Level Without End" {dpoke 0x40c8 0} "Lives: Lives" {dpoke 0x402a 3} "Weapon: Laser - F5 On Level 4" {dpoke 0x40d5 255} } create_trainer "Time Pilot" {time 2} { "Difficulty Level (1-5)" {dpoke 0xe180 1} "Faster To Main Enemy" {dpoke 0xe120 0} "Invulnerable: Invulnerable" {dpoke 0xe052 0;poke 0xe145 0;dpoke 0xe383 15} "Lives: Lives" {dpoke 0xe002 99} } create_trainer "Time Rider" {time 1} { "Level (0-4)" {dpoke 0x40b4 0} "Plasma - F5 On Level 5" {dpoke 0x40d7 255} "Play One Level Without End" {dpoke 0x40cb 0} "Lives: Lives" {dpoke 0x402d 4} "Weapon: Laser - F5 On Level 4" {dpoke 0x40d8 255} } create_trainer "Time Trax" {time 1} { "Energy: Energy" {dpoke 0xae19 128} "Time: Time" {dpoke 0xa4ab 0} } create_trainer "TNT" {time 1} { "Ammo" {dpoke 0x489d 255} "Ammo (bootleg Version)" {dpoke 0x4894 255} "Lives: Lives" {dpoke 0x320e 255} "Lives: Lives (bootleg Version)" {dpoke 0x7074 100} } create_trainer "Tower of Druaga" {time 1} { "Always Have Key" {dpoke 0xea0e 1} "Pick Axe" {dpoke 0xea0d 250;poke 0xeb64 1} "Lives: Lives" {dpoke 0xe9ef 5} "Time: Infinite Time" {dpoke 0xea05 128} } create_trainer "Track & Field 1" {time 1} { "100 Meter Dash" {dpoke 0xe016 1} "400 Meter Dash" {dpoke 0xe016 4} "Always Qualify" {dpoke 0xe057 0} "Hammer Throw" {dpoke 0xe016 3} "Long Jump" {dpoke 0xe016 2} "Sprint Time Player 1" {dpoke 0xe0a5 0} "Sprint Time Player 2" {dpoke 0xe0a9 0} } create_trainer "Track & Field 2" {time 1} { "110 Hurdlers" {dpoke 0xe016 1} "1500 Meter" {dpoke 0xe016 4} "Always Qualify" {dpoke 0xe057 0} "High Jump" {dpoke 0xe016 3} "Javelin Throw" {dpoke 0xe016 2} "Sprint Time Player 1" {dpoke 0xe0a5 0} "Sprint Time Player 2" {dpoke 0xe0a9 0} } create_trainer "Traffic Jam-MSXdev06" {time 1} { "Moves (do Not Leave Active)" {dpoke 0xeb3d 0} "Time: Time" {dpoke 0xeb3f 232;poke 0xeb40 3} } create_trainer "Trailblazer" {time 1} { "Jumps Left In Arcade Mode" {dpoke 0x8721 9} "Time On Cb:a9 (leave This Value Alone, It Checks For S)" {dpoke 0x866b 4;poke 0x866c 4} } create_trainer "Treasure Of Usas, The" {time 1} { "Cheats: All Combis With Konami Carts" {dpoke 0xc205 255} "Cheats: Free Cles" {dpoke 0xc2d0 0} "Cheats: Free Wit" {dpoke 0xc2b0 0} "Cheats: Invincible Player" {dpoke 0xc256 1} "Enemy: Stage Boss Door Open" {dpoke 0xe328 1} "Enemy: Temple Boss Door Open" {dpoke 0xd510 0;poke 0xd514 48} "Enemy: Weak Enemies" {dpoke 0xc412 1;poke 0xc492 1;dpoke 0xc512 1;dpoke 0xc592 1;dpoke 0xc612 1;dpoke 0xc692 1;dpoke 0xc712 1;dpoke 0xc792 1} "Money: Money" {dpoke 49753 153;poke 49754 153} "Mood: Cles's Mood Angry" {dpoke 0xc2b6 3} "Mood: Cles's Mood Happy" {dpoke 0xc2b6 0} "Mood: Cles's Mood Normal" {dpoke 0xc2b6 1} "Mood: Cles's Mood Sad" {dpoke 0xc2b6 2} "Mood: Wit's Mood Angry" {dpoke 0xc2d6 3} "Mood: Wit's Mood Happy" {dpoke 0xc2d6 0} "Mood: Wit's Mood Normal" {dpoke 0xc2d6 1} "Mood: Wit's Mood Sad" {dpoke 0xc2d6 2} "Special Power: Wit's Unlimited Air Walk" {dpoke 0xc266 255} "Status: Cles's Stars For Jumping" {dpoke 0xc2d2 2} "Status: Cles's Stars For Speed" {dpoke 0xc2d1 4} "Status: Jump Rate 1 Coin" {dpoke 0xc2da 1;poke 0xc2db 0;dpoke 0xc2dc 1;dpoke 0xc2dd 0} "Status: Life For Cles" {dpoke 0xc2d5 255} "Status: Life For Wit" {dpoke 0xc2b5 255} "Status: Speed Rate 1 Coin" {dpoke 0xc2bc 1;poke 0xc2bd 0;dpoke 0xc2d8 1;dpoke 0xc2d9 0} "Status: Vitality Rate 1 Coin" {dpoke 0xc2b8 1;poke 0xc2b9 0;dpoke 0xc2ba 1;dpoke 0xc2bb 0} "Status: Wit's Stars For Jumping" {dpoke 0xc2b2 2} "Status: Wit's Stars For Speed" {dpoke 0xc2b1 4} } create_trainer "Tritorn" {time 1} { "Exp" {dpoke 0xe43b 100} "Life" {dpoke 0xe439 99} "Magic Balls" {dpoke 0xe43c 99} "Item: Have All Items" {dpoke 0xe431 255;poke 0xe432 255;dpoke 0xe433 255} } create_trainer "TT Racer" {time 1} { "Fuel" {dpoke 0x70e4 252} "Temp" {dpoke 0x5ad9 0} } create_trainer "Turmoil" {time 1} { "No Enemies" {dpoke 0x5b19 0;poke 0x5b1a 0} "Lives: Lives" {dpoke 0x5b1e 57} } create_trainer "Turtle Mania Demo" {time 1} { "Back Shoot" {dpoke 0x4046 1} "Boomerang" {dpoke 0x4047 1} "Double Front Shoot" {dpoke 0x4045 1} "Double Player" {dpoke 0x4048 1} "Normal Fire" {dpoke 0x4047 0} "Single Front Shoot" {dpoke 0x4045 0} "Single Player" {dpoke 0x4048 0} "Triple Front Shoot" {dpoke 0x4045 2} "Triple Player" {dpoke 0x4048 2} "Invulnerable: Invulnerable" {dpoke 0x76ee 0;poke 0x776a 0} "Lives: Lives" {dpoke 0x4038 153} "Shield: Shield" {dpoke 0x4049 3} "Weapon: Bomb" {dpoke 0x4047 2} } create_trainer "Twinbee" {time 1} { "No Small Air Enemies" {dpoke 0xebb1 255} "Partially Invulnerable" {dpoke 0xe078 0} "Player 1 - Always Arms" {dpoke 0xe08d 1} "Player 1 - Double Shoot" {dpoke 0xe083 1} "Player 1 - Exclusive Fireball In Dual Mode" {dpoke 0xe083 144} "Player 1 - Fully Invulnerable" {dpoke 0xe097 255} "Player 1 - Options + Double Shoot (no Options In Dual Mode)" {dpoke 0xe083 3} "Player 1 - Options + Single Shoot (no Options In Dual Mode)" {dpoke 0xe083 2} "Player 1 - Options + Spread Shoot (not In Dual Mode)" {dpoke 0xe083 10} "Player 1 - Single Shoot" {dpoke 0xe083 0} "Player 1 - Spread Shoot (not In Dual Mode)" {dpoke 0xe083 8} "Player 2 - Always Arms" {dpoke 0xe08e 1} "Player 2 - Double Shoot" {dpoke 0xe084 1} "Player 2 - Exclusive Fireball In Dual Mode" {dpoke 0xe084 144} "Player 2 - Fully Invulnerable" {dpoke 0xe098 255} "Player 2 - Options + Double Shoot (no Options In Dual Mode)" {dpoke 0xe084 3} "Player 2 - Options + Single Shoot (no Options In Dual Mode)" {dpoke 0xe084 2} "Player 2 - Options + Spread Shoot (not In Dual Mode)" {dpoke 0xe084 10} "Player 2 - Single Shoot" {dpoke 0xe084 0} "Player 2 - Spread Shoot (not In Dual Mode)" {dpoke 0xe084 8} "Speed Player 1" {dpoke 0xe081 3} "Speed Player 2" {dpoke 0xe082 3} "Lives: Lives Player 1" {dpoke 0xe070 153} "Lives: Lives Player 2" {dpoke 0xe073 153} "Options: Player 1 - Options + Shield + Double Shoot (no Options In Dual Mode)" {dpoke 0xe083 7} "Options: Player 1 - Options + Shield + Single Shoot (no Options In Dual Mode)" {dpoke 0xe083 6} "Options: Player 1 - Options + Shield + Spread Shoot (only Shield In Dual Mode)" {dpoke 0xe083 14} "Options: Player 1 - Shield + Double Shoot" {dpoke 0xe083 5} "Options: Player 1 - Shield + Single Shoot" {dpoke 0xe083 4} "Options: Player 1 - Shield + Spread Shoot (only Shield In Dual Mode)" {dpoke 0xe083 12} "Options: Player 2 - Options + Shield + Double Shoot (no Options In Dual Mode)" {dpoke 0xe084 7} "Options: Player 2 - Options + Shield + Single Shoot (no Options In Dual Mode)" {dpoke 0xe084 6} "Options: Player 2 - Options + Shield + Spread Shoot (only Shield In Dual Mode)" {dpoke 0xe084 14} "Options: Player 2 - Shield + Double Shoot" {dpoke 0xe084 5} "Options: Player 2 - Shield + Single Shoot" {dpoke 0xe084 4} "Options: Player 2 - Shield + Spread Shoot (only Shield In Dual Mode)" {dpoke 0xe084 12} } create_trainer "Undeadline" {time 2} { "Have Axe" {dpoke 0xd2ab 1} "Have Boomerang" {dpoke 0xd2ab 5} "Have Fire" {dpoke 0xd2ab 2} "Have Ice" {dpoke 0xd2ab 3} "Have Knife" {dpoke 0xd2ab 0} "Have Triple-knife" {dpoke 0xd2ab 4} "Have Vortex" {dpoke 0xd2ab 6} "Invincible To Monsters" {dpoke 0xd2b7 255} "Lives: Lives" {dpoke 0xd2a9 2} "Power: Power" {dpoke 0xd2a8 255} } create_trainer "Universe Unknown" {time 0.1} { "Lives: Lives" {dpoke 0xf2c4 233} "Power: Power" {dpoke 0xee54 255;poke 0xee55 128} } create_trainer "Untouchables" {time 1} { "Power: Power" {dpoke 0x70de 255} } create_trainer "Uoo-Kuns Kakurenbo - MSX Magazine 2003" {time 1} { "Time: Time" {dpoke 0x960e 60} } create_trainer "Vampire" {time 1} { "Open 1st Cellar Door" {dpoke 0x51ac 0} "Open 2d Cellar Door" {dpoke 0x52cd 0} "Open 3rd Cellar Door" {dpoke 0x62f1 0} "Open Back Tower Window" {dpoke 0x447d 0} "Open Back Window" {dpoke 0x454e 0} "Open Central Tower" {dpoke 0x4ead 0} "Open Central Window" {dpoke 0x4822 0} "Open Front Door" {dpoke 0x4164 0} "Open Front Tower" {dpoke 0x4c45 0} "Open Highest Window" {dpoke 0x498b 0} "Open Left Tower Window" {dpoke 0x48cf 0} "Open Right Tower" {dpoke 0x4a4e 0} "Open Right Tower Window" {dpoke 0x4a3e 0} "X-pos (0-130)" {dpoke 0x960b 0} "Y-pos (200-70)" {dpoke 0x960c 70} "Lives: Lives" {dpoke 0x9492 9} "Power: Power" {dpoke 0x9491 99} } create_trainer "Vaxol - Heavy Armed Storm Vehicle" {time 1} { "Power: Power" {dpoke 0xc148 255} } create_trainer "Vectron" {time 1} { "Item: Item 00" {dpoke 0xd830 0} "Item: Item 00 X99" {dpoke 0xd831 153} "Item: Item 01" {dpoke 0xd832 1} "Item: Item 01 X99" {dpoke 0xd833 153} "Item: Item 02" {dpoke 0xd834 2} "Item: Item 02 X99" {dpoke 0xd835 153} "Item: Item 03" {dpoke 0xd836 5} "Item: Item 03 X99" {dpoke 0xd837 153} "Item: Item 04" {dpoke 0xd838 6} "Item: Item 04 X99" {dpoke 0xd839 153} "Item: Item 05" {dpoke 0xd83a 7} "Item: Item 05 X99" {dpoke 0xd83b 153} "Item: Item 06" {dpoke 0xd83c 10} "Item: Item 06 X99" {dpoke 0xd83d 153} "Item: Item 07" {dpoke 0xd83e 11} "Item: Item 07 X99" {dpoke 0xd83f 153} "Item: Item 08" {dpoke 0xd840 12} "Item: Item 08 X99" {dpoke 0xd841 153} "Item: Item 09" {dpoke 0xd842 13} "Item: Item 09 X 99" {dpoke 0xd843 153} "Item: Item 10" {dpoke 0xd844 14} "Item: Item 10 X99" {dpoke 0xd845 153} "Item: Item 11" {dpoke 0xd846 15} "Item: Item 11 X99" {dpoke 0xd847 153} "Money: Full Credits" {dpoke 0xd906 255;poke 0xd907 255} "Power: Full Power" {dpoke 0xd905 48} "Power: Invincible" {dpoke 0xd821 255} } create_trainer "Venom Strikes Back" {time 1} { "Power Slot 1 Filled With Penetrator" {dpoke 0x2c33 1;poke 0x2c34 153} "Power Slot 2 Filled With Lifter" {dpoke 0x2c38 6;poke 0x2c39 153} "Power Slot 3 Filled With Jack Rabbit" {dpoke 0x2c3d 5;poke 0x2c3e 153} "Lives: Lives" {dpoke 0x2bb1 255;poke 0x2bb2 255} } create_trainer "Video Hustler" {time 2} { "Balls" {dpoke 0xe050 99} "Errors Always Allowed" {dpoke 0xe053 2} } create_trainer "Volguard" {time 1} { "First Plane Invulnerable (select When First Enemies Appear)" {dpoke 0xe345 1} "Mission (1-5)" {dpoke 0xe300 1} "Power (0,8,16,23)" {dpoke 0xe392 16} "Second Plane Invulnerable (select When First Enemies Appear)" {dpoke 0xe34d 1} "Speed (0-4)" {dpoke 0xe39b 4} "Third Plane Invulnerable (select When First Enemies Appear)" {dpoke 0xe355 1} "Lives: Lives" {dpoke 0xe31c 7} } create_trainer "Volguard CAS" {time 1} { "First Plane Invulnerable (select When First Enemies Appear)" {dpoke 0x8045 1} "Mission (1-5)" {dpoke 0x8000 1} "Power (0,8,16,23)" {dpoke 0x8092 16} "Second Plane Invulnerable (select When First Enemies Appear)" {dpoke 0x804d 1} "Speed (0-4)" {dpoke 0x809b 4} "Third Plane Invulnerable (select When First Enemies Appear)" {dpoke 0x8055 1} "Lives: Lives" {dpoke 0x801c 7} } create_trainer "Vortex Raider" {time 1} { "Cosmic Cheat Active" {dpoke 0x402b 1} "Enemy Cant Shoot" {dpoke 0x4241 1} "Lives: Lives" {dpoke 0x401f 6} } create_trainer "Vscreen Game" {time 1} { "Lives: Lives Version 0.5" {dpoke 0x12cc 153} "Lives: Lives Version 0.6" {dpoke 0xcf02 153} "Lives: Lives Versions 0.7-0.8" {dpoke 0xc78e 153} "Lives: Lives Versions 0.9-1.0" {dpoke 0xf983 153} } create_trainer "Warp & Warp" {time 1} { "Lives: Lives" {dpoke 0xe089 99} } create_trainer "Warroid" {time 10} { "Power: Power Player 1" {dpoke 0xc58b 255} "Power: Power Player 2" {dpoke 0xc5ab 1} } create_trainer "Who Dares Wins 2" {time 1} { "Invulnerable: Invulnerable" {dpoke 0x8259 0} "Lives: Lives" {dpoke 0x8293 9} "Weapon: Bombs" {dpoke 0x828a 147} } create_trainer "Winter Events" {time 1} { "Bobsled Speed (1-16;de-select Before Finish)" {dpoke 0x9a06 1} "Down Hill Speed (1-16;de-select Before Finish)" {dpoke 0x9a0d 1} "No Damage In Slalom" {dpoke 0x9a12 0} "No Red Flag Touched In Slalom" {dpoke 0x9a13 0} "Skate Speed (1-16;de-select Before Finish)" {dpoke 0x9a0a 16} "Slalom Speed (1-16;de-select Before Finish)" {dpoke 0x9a0c 16} } create_trainer "Winterhawk" {time 1} { "Cosmic Cheat" {dpoke 0x4038 1} "Double Missile" {dpoke 0x62e7 2} "Normal Shoot" {dpoke 0x62e7 0} "Single Missile" {dpoke 0x62e7 1} "Lives: Lives" {dpoke 0x402c 6} } create_trainer "Wizard's Lair" {time 1} { "Ammo" {dpoke 0x9e2f 255} "Diamond" {dpoke 0x9e2d 99} "Endless Color" {dpoke 0x7c09 4;poke 0x7c1d 143;dpoke 0xbc06 4} "Gold" {dpoke 0x9e30 255} "Golden Ring" {dpoke 0x9e2c 99} "Invincible" {dpoke 0x7c1e 99} "Walk Fast" {dpoke 0x7c1f 255} "Key: Key" {dpoke 0x9e2b 99} "Lives: Lives" {dpoke 0x9e31 99} "Power: Power" {dpoke 0x9e2e 255} } create_trainer "Won-Si-In" {time 1} { "Always have wings" {dpoke 0xe137 1} "Invincible" {dpoke 0xe138 200} "Unlimited Power" {dpoke 0xe132 5} "Lives: Lives" {dpoke 0xe130 0x99} "Weapon: Weapon 1 - Small Fire" {dpoke 0xe136 0} "Weapon: Weapon 2 - Axe" {dpoke 0xe136 1} "Weapon: Weapon 3 - Boomerang" {dpoke 0xe136 2} "Weapon: Weapon 4 - Big Fire" {dpoke 0xe136 3} } create_trainer "Woody Poco" {time 2} { "Carry up to 16 items" {dpoke 0xe792 16} "Max Candle Burning Time" {dpoke 0xe7bf 99} "Max Food" {dpoke 0xe77b 255;poke 0xe77c 255} "Max Money" {dpoke 0xe77f 255;poke 0xe780 255} "Shot" {dpoke 0xe7b9 255;poke 0xe7ba 255} "Time Of Day: Day" {dpoke 0xe785 9} "Time Of Day: Night" {dpoke 0xe785 0} "Time Stands Still" {dpoke 0xe786 0} "Power: Max Power" {dpoke 0xe777 251;poke 0xe778 255;dpoke 0xe779 251;dpoke 0xe77a 255} } create_trainer "Xak 1 - The Art of Visual Stage" {time 2} { "Armor: Enchanted Armor 1" {dpoke 0x1c19 99} "Armor: Enchanted Armor 2" {dpoke 0x1c1a 99} "Armor: Enchanted Armor 3" {dpoke 0x1c1b 99} "Armor: Enchanted Armor 4" {dpoke 0x1c1c 99} "Armor: Enchanted Armor 5" {dpoke 0x1c1d 99} "Armor: Enchanted Armor 6" {dpoke 0x1c1e 99} "Debug Mode: Super Latok Mode" {dpoke 0x1fd4 1;poke 0x2473 68} "Item: Benura" {dpoke 0x1c2e 99} "Item: Blue Xak Deaple" {dpoke 0x1c34 99} "Item: Bread" {dpoke 0x1c25 99} "Item: Cloak Of Fire" {dpoke 0x1c2d 99} "Item: Cloak Of Life " {dpoke 0x1c37 99} "Item: Dragon Ring" {dpoke 0x1c2a 99} "Item: Dried Meat" {dpoke 0x1c26 99} "Item: Esp Medal" {dpoke 0x1c2c 99} "Item: Gas Mask" {dpoke 0x1c39 99} "Item: Gemil Potion" {dpoke 0x1c2b 99} "Item: Glasses" {dpoke 0x1c27 99} "Item: Green Xak Deaple" {dpoke 0x1c35 99} "Item: Guantlet" {dpoke 0x1c28 99} "Item: Herb from the Ruins" {dpoke 0x1c42 1} "Item: Kettle With Soup" {dpoke 0x1c38 99} "Item: Key - Blue" {dpoke 0x1c3c 99} "Item: Key - Yellow " {dpoke 0x1c3b 99} "Item: Medicine" {dpoke 0x1c3a 99} "Item: Medicine Room Key" {dpoke 0x1c43 1} "Item: Nill's Box" {dpoke 0x1c3d 99} "Item: Pirate Liquor" {dpoke 0x1c41 1} "Item: Protection Ring" {dpoke 0x1c29 99} "Item: Rabby" {dpoke 0x1c3e 99} "Item: Red Stone" {dpoke 0x1c40 1} "Item: Red Xak Deaple" {dpoke 0x1c36 99} "Item: Royal Crest" {dpoke 0x1c3f 1} "Item: Treasure Key" {dpoke 0x1c44 1} "Scroll: Death Spell Scroll" {dpoke 0x1c30 99} "Scroll: Disintegrate Scroll" {dpoke 0x1c31 99} "Scroll: Dispel Magic Scroll" {dpoke 0x1c32 99} "Scroll: Lightning Scroll" {dpoke 0x1c2f 99} "Scroll: Teleport Scroll" {dpoke 0x1c33 99} "Shield : Enchanted Shield 1" {dpoke 0x1c1f 99} "Shield : Enchanted Shield 2" {dpoke 0x1c20 99} "Shield : Enchanted Shield 3" {dpoke 0x1c21 99} "Shield : Enchanted Shield 4" {dpoke 0x1c22 99} "Shield : Enchanted Shield 5" {dpoke 0x1c23 99} "Shield : Enchanted Shield 6" {dpoke 0x1c24 99} "Status: Exp" {dpoke 0x1c60 255;poke 0x1c61 255} "Status: Gold" {dpoke 0x1c62 255;poke 0x1c63 255} "Status: Life" {dpoke 0x1c52 255;poke 0x2337 255} "Weapon: Enchanted Sword 1" {dpoke 0x1c13 99} "Weapon: Enchanted Sword 2" {dpoke 0x1c14 99} "Weapon: Enchanted Sword 3" {dpoke 0x1c15 99} "Weapon: Enchanted Sword 4" {dpoke 0x1c16 99} "Weapon: Enchanted Sword 5" {dpoke 0x1c17 99} "Weapon: Enchanted Sword 6" {dpoke 0x1c18 99} } create_trainer "Xak 2 - Rising of the red moon" {time 10} { "Experience: Max Out Experience" {dpoke 0x6e10 255;poke 0x6e11 255} "Life: Max Life" {dpoke 0x6dfe 255;poke 0x6dff 255} "Money: Money" {dpoke 0x6e14 255;poke 0x6e15 255} } create_trainer "Xak 3 - The Tower of Gazel" {time 1} { "Latok Life" {dpoke 0x69ef 15;poke 0x69f0 39} "Latoks Defense" {dpoke 0x69f5 15;poke 0x69f6 39} "Latoks Max Life" {dpoke 0x69ed 15;poke 0x69ee 39} "Latoks Max Mp" {dpoke 0x69f1 15;poke 0x69f2 39} "Latoks Mp" {dpoke 0x69f3 15;poke 0x69f4 39} "Latoks Strength" {dpoke 0x69f7 15;poke 0x69f8 39} } create_trainer "Xanadu MSX1" {time 1} { "Hit Points" {dpoke 0xf033 255;poke 0xf034 255;dpoke 0xf035 99} "Infinite Elixirs" {dpoke 0xf04e 0x99} "Max Food" {dpoke 0xf041 255;poke 0xf042 99;dpoke 0xf043 255} "Max Gold" {dpoke 0xf044 255;poke 0xf045 255;dpoke 0xf046 99} "Max Hp" {dpoke 0xf036 255;poke 0xf037 255;dpoke 0xf038 99} "Max Mgr" {dpoke 0xf039 255} "Max Str" {dpoke 0xf040 255} "Slot 3 - Corrosion" {dpoke 0xf049 10} "Slot 3 - Death" {dpoke 0xf049 16} "Slot 3 - Deg Corrosion" {dpoke 0xf049 14} "Slot 3 - Deg Deluge" {dpoke 0xf049 8} "Slot 3 - Deg Fire" {dpoke 0xf049 9} "Slot 3 - Deg Meedle" {dpoke 0xf049 1} "Slot 3 - Deg Mittar" {dpoke 0xf049 5} "Slot 3 - Deg Poison" {dpoke 0xf049 13} "Slot 3 - Deg Thunder" {dpoke 0xf049 11} "Slot 3 - Deg Tilte" {dpoke 0xf049 15 } "Slot 3 - Deluge" {dpoke 0xf049 3} "Slot 3 - Fire" {dpoke 0xf049 4} "Slot 3 - Meedle" {dpoke 0xf049 0} "Slot 3 - Mittar" {dpoke 0xf049 2} "Slot 3 - Poison" {dpoke 0xf049 7} "Slot 3 - Thunder" {dpoke 0xf049 6} "Slot 3 - Tilte" {dpoke 0xf049 12} "W.exp" {dpoke 0xf030 255;poke 0xf031 255;dpoke 0xf032 99} "Armor: Slot 2 - +2 Full Plate" {dpoke 0xf047 14} "Armor: Slot 2 - +2 Leather" {dpoke 0xf047 11} "Armor: Slot 2 - +2 Reflex" {dpoke 0xf047 15} "Armor: Slot 2 - +2 Ring Mail" {dpoke 0xf047 13} "Armor: Slot 2 - Banded Armor" {dpoke 0xf047 8} "Armor: Slot 2 - Battle Suits" {dpoke 0xf047 16} "Armor: Slot 2 - Chain Mail" {dpoke 0xf047 6} "Armor: Slot 2 - Cloth" {dpoke 0xf047 0} "Armor: Slot 2 - Full Plate" {dpoke 0xf047 10} "Armor: Slot 2 - Half Plate" {dpoke 0xf047 9} "Armor: Slot 2 - Leather Armor" {dpoke 0xf047 1} "Armor: Slot 2 - Padded Mail" {dpoke 0xf047 2} "Armor: Slot 2 - Reflex" {dpoke 0xf047 12} "Armor: Slot 2 - Ring Mail" {dpoke 0xf047 4} "Armor: Slot 2 - Scale Armor" {dpoke 0xf047 5} "Armor: Slot 2 - Split Mail" {dpoke 0xf047 7} "Armor: Slot 2 - Studded Mail" {dpoke 0xf047 3} "Weapon: Slot 1 - +2 Battle Axe" {dpoke 0xf052 10} "Weapon: Slot 1 - Battle Axe" {dpoke 0xf053 5} "Weapon: Slot 1 - Broad Sword" {dpoke 0xf054 6} "Weapon: Slot 1 - Dagger" {dpoke 0xf055 0} "Weapon: Slot 1 - Disrupt Mace" {dpoke 0xf056 14} "Weapon: Slot 1 - Dragon Slayer" {dpoke 0xf057 16} "Weapon: Slot 1 - Giant Slayer" {dpoke 0xf058 11} "Weapon: Slot 1 - Halbred" {dpoke 0xf059 9} "Weapon: Slot 1 - Hand Axe" {dpoke 0xf05a 3} "Weapon: Slot 1 - Lance" {dpoke 0xf05b 8} "Weapon: Slot 1 - Long Sword" {dpoke 0xf05c 4} "Weapon: Slot 1 - Luck Blade" {dpoke 0xf05d 12} "Weapon: Slot 1 - Morning Star" {dpoke 0xf05e 7} "Weapon: Slot 1 - Murasume Blade" {dpoke 0xf05f 13} "Weapon: Slot 1 - Short Sword" {dpoke 0xf060 1} "Weapon: Slot 1 - Spear" {dpoke 0xf061 2} "Weapon: Slot 1 - Vorpal Weapon" {dpoke 0xf062 15} } create_trainer "Xevious - Fardraut Saga" {time 2} { "Lives: Lives" {dpoke 0xc502 99} "Weapon: Have All Weapons And Shield" {dpoke 0xc005 255} } create_trainer "Xevious Demo 1" {time 2} { "Invulnerable (select After Mission Start)" {dpoke 0xc800 129} "One Front Shoot" {dpoke 0xc805 0} "One Front Shoot + Two Back Shoots" {dpoke 0xc805 8} "Three Front Shoots" {dpoke 0xc805 4} "Three Front Shoots + One Back Shoot" {dpoke 0xc805 12} "Lives: Lives" {dpoke 0xce08 99} "Options: One Front Shoot + Two Back Shoots + Shield" {dpoke 0xc805 10} "Options: Three Front Shoots + One Back Shoot + Shield" {dpoke 0xc805 14} "Shield: One Front Shoot + Shield" {dpoke 0xc805 2} "Shield: Three Front Shoots + Shield" {dpoke 0xc805 6} } create_trainer "Xevious Demo 2" {time 2} { "Invulnerable (select After Mission Start)" {dpoke 0xb400 129} "One Front Shoot" {dpoke 0xb405 0} "One Front Shoot + Two Back Shoots" {dpoke 0xb405 8} "Three Front Shoots" {dpoke 0xb405 4} "Three Front Shoots + One Back Shoot" {dpoke 0xb405 12} "Options: One Front Shoot + Two Back Shoots + Shield" {dpoke 0xb405 10} "Options: Three Front Shoots + One Back Shoot + Shield" {dpoke 0xb405 14} "Shield: One Front Shoot + Shield" {dpoke 0xb405 2} "Shield: Three Front Shoots + Shield" {dpoke 0xb405 6} } create_trainer "Xyxolog" {time 5} { "Dunno" {dpoke 0xe062 153} "Something Else" {dpoke 0xe061 153} "Power: Power" {dpoke 0xe060 153} } create_trainer "Xyzolog" {time 10} { "Lives: Lives" {dpoke 0xe418 100} } create_trainer "XZR 1" {time 1} { "Exp Max" {dpoke 0xd000 255;poke 0xd001 255;dpoke 0xd002 255} "Invincible" {dpoke 0xd0ac 255} "Life" {dpoke 0xd018 255} "Max Money" {dpoke 0xd035 255;poke 0xd036 255;dpoke 0xd037 255} } create_trainer "XZR 2" {time 1} { "Exp" {dpoke 0xd0a8 255;poke 0xd0a9 255} "Life" {dpoke 0xd0a0 255} "Max Money" {dpoke 0xd180 255;poke 0xd181 255;dpoke 0xd182 255} } create_trainer "Yie Ar Kung-Fu" {time 5} { "Always 9 Hits In Bonus Stage" {dpoke 0xe275 9} "Always Bonus Stage" {dpoke 0xe058 1} "Always Normal Stage" {dpoke 0xe058 0} "Blue Level" {dpoke 0xe059 1} "Chen As Enemy" {dpoke 0xe054 2} "Green Level" {dpoke 0xe059 2} "Grey Level" {dpoke 0xe059 3} "Kill Enemy With One Hit" {dpoke 0xe117 0} "Lang As Enemy" {dpoke 0xe054 3} "Power (de-select To Get Bonus)" {dpoke 0xe116 72} "Red Level" {dpoke 0xe059 0} "Tao As Enemy" {dpoke 0xe054 1} "Wang As Enemy" {dpoke 0xe054 0} "Wu As Enemy" {dpoke 0xe054 4} "Lives: Lives" {dpoke 0xe050 9} } create_trainer "Yie Ar Kung-Fu II - The Emperor Yie-Gah" {time 5} { "Han-chen As Enemy In Mode 1 Player" {dpoke 0xe066 6} "Invincible For Bosses" {dpoke 0xe29e 255} "Kill Enemy With One Hit" {dpoke 0xe102 1} "Lan-fan As Enemy In Mode 1 Player" {dpoke 0xe066 1} "Li-jen As Enemy In Mode 1 Player" {dpoke 0xe066 7} "Mei-ling As Enemy In Mode 1 Player" {dpoke 0xe066 5} "Po-chin As Enemy In Mode 1 Player " {dpoke 0xe066 2} "Power Player 1 (de-select To Get Bonus)" {dpoke 0xe100 72} "Wei-chin As Enemy In Mode 1 Player" {dpoke 0xe066 4} "Wen-hu As Enemy In Mode 1 Player " {dpoke 0xe066 3} "Yen-pei As Enemy In Mode 1 Player" {dpoke 0xe066 0} "Lives: Lives In Mode 1 Player" {dpoke 0xe055 153} } create_trainer "Youma Kourin" {time 1} { "Damage" {dpoke 0xd64d 0} } create_trainer "Ys 1" {time 2} { "All Armors Inside Tower" {dpoke 0xcccb 31} "All Armors Outside Tower" {dpoke 0xcfdd 31} "All Books Inside Tower" {dpoke 0xcccd 63} "All Books Outside Tower" {dpoke 0xcfe3 63} "All Items Inside Tower" {dpoke 0xcccd 31} "All Items Outside Tower" {dpoke 0xcfdf 31} "All Keys Inside Tower" {dpoke 0xccd2 63} "All Keys Outside Tower" {dpoke 0xcfe4 63} "All Rings Inside Tower" {dpoke 0xcccf 31} "All Rings Outside Tower" {dpoke 0xcfe1 31} "All Special Items 1 Inside Tower" {dpoke 0xccd3 255} "All Special Items 1 Outside Tower" {dpoke 0xcfe5 255} "All Special Items 2 Inside Tower" {dpoke 0xccd5 15} "All Special Items 2 Outside Tower" {dpoke 0xcfe7 15} "Enemy Power Inside Tower (de-select On Final Boss)" {dpoke 0xcb1a 1} "Enemy Power Outside Tower" {dpoke 0xcdba 1} "Experience" {dpoke 0xcfc9 255;poke 0xcfca 255} "Life" {dpoke 0xcfc3 255} "Money" {dpoke 0xcfc7 255;poke 0xcfc8 255} "Shield: All Shields Inside Tower" {dpoke 0xccc9 31} "Shield: All Shields Outside Tower" {dpoke 0xcfdb 31} "Weapon: All Swords Inside Tower" {dpoke 0xccc7 31} "Weapon: All Swords Outside Tower" {dpoke 0xcfd9 31} } create_trainer "Ys 2" {time 2} { "All Armor" {dpoke 0x0133 255} "All Magic Rods" {dpoke 0x0135 255} "Exp Max" {dpoke 0x0102 255;poke 0x0103 255} "Gold Max" {dpoke 0x0196 255;poke 0x0197 255} "Have Magic Activated" {dpoke 0x0143 3} "Magic Mp" {dpoke 0x0104 255;poke 0x0105 255} "Max Def" {dpoke 0x018f 255} "Max Hp" {dpoke 0x018c 255} "Max Str" {dpoke 0x018e 255} "Item: All Items" {dpoke 0x0136 255;poke 0x0137 255;dpoke 0x0138 255;dpoke 0x0139 255} "Shield: All Shields" {dpoke 0x0134 255} "Weapon: All Swords" {dpoke 0x0132 255} } create_trainer "Ys 3" {time 10} { "Armor" {dpoke 0x7fa5 255} "Experience" {dpoke 0x7fa0 255;poke 0x7fa1 255} "Gold" {dpoke 0x7f9e 255;poke 0x7f9f 255} "Life" {dpoke 0x7f97 255} "Rings" {dpoke 0x7fa9 255} "Item: Items" {dpoke 0x7fab 255} "Power: Ring Power" {dpoke 0x7ead 255} "Shield: Shields" {dpoke 0x7fa7 255} "Weapon: Swords" {dpoke 0x7fa3 255} } create_trainer "Yumetairiku Adventure - Penguin Adventure" {time 15} { "Always Green Hearts" {dpoke 0xe0c0 2} "Always Yellow Hearts" {dpoke 0xe0c0 4} "Armour" {dpoke 0xe172 1} "Armour In Secret Bag" {dpoke 0xe0d7 18} "Bell" {dpoke 0xe166 1} "Bell In Secret Bag" {dpoke 0xe0d7 6} "Blue Boots" {dpoke 0xe16d 1} "Blue Boots In Secret Bag" {dpoke 0xe0d7 13} "Bracelet" {dpoke 0xe167 1} "Bracelet In Secret Bag" {dpoke 0xe0d7 7} "Can Fly" {dpoke 0xe203 15} "Crossed Tiara" {dpoke 0xe170 1} "Crossed Tiara In Secret Bag" {dpoke 0xe0d7 16} "Faster To The End" {dpoke 0xe08e 0} "Fishes" {dpoke 0xe089 153;poke 0xe08a 9} "Flying Cloud" {dpoke 0xee92 112;poke 0xee93 14;dpoke 0xee96 116;dpoke 0xee97 14} "Get 8 Dancing Penguins" {dpoke 0xe0dd 8} "Goggles" {dpoke 0xe16a 2} "Goggles In Secret Bag" {dpoke 0xe0d7 10} "Golden Feather" {dpoke 0xe16f 1} "Golden Feather In Secret Bag" {dpoke 0xe0d7 15} "Green Shoes" {dpoke 0xe160 1} "Invulnerable (not Against Dragons)" {dpoke 0xe1f1 1} "Kazumi Cheat" {dpoke 0xf0f7 255} "Kill Dragon With One Shoot" {dpoke 0xe53c 19} "Level 0" {dpoke 0xe082 0} "Map" {dpoke 0xe16c 1} "Map In Secret Bag" {dpoke 0xe0d7 12} "Metal Helmet" {dpoke 0xe163 3} "Metal Helmet In Secret Bag" {dpoke 0xe0d7 3} "Necklace" {dpoke 0xe169 1} "Necklace In Secret Bag" {dpoke 0xe0d7 9} "Noriko Cheat" {dpoke 0xf0f7 254} "Pause Counter For Good Ending" {dpoke 0xe0de 1} "Priest Robe" {dpoke 0xe171 1} "Priest Robe In Secret Bag" {dpoke 0xe0d7 17} "Propeller" {dpoke 0xe161 1} "Propeller In Secret Bag" {dpoke 0xe0d7 1} "Protective Vest" {dpoke 0xe165 3} "Protective Vest In Secret Bag" {dpoke 0xe0d7 5} "Red Shoes" {dpoke 0xe16e 1} "Red Shoes In Secret Bag" {dpoke 0xe0d7 14} "Ring" {dpoke 0xe168 1} "Ring In Secret Bag" {dpoke 0xe0d7 8} "Score" {dpoke 0xe086 153;poke 0xe087 153;dpoke 0xe088 153} "Silver Helmet" {dpoke 0xe164 3} "Silver Helmet In Secret Bag" {dpoke 0xe0d7 4} "Time (de-select To Get Bonus)" {dpoke 0xe08b 153;poke 0xe08c 9} "Torch" {dpoke 0xe16b 3} "Torch In Secret Bag" {dpoke 0xe0d7 11} "Wings" {dpoke 0xe0bd 2} "X-pos Secret Bag (de-select To Get Item Only One Time)" {dpoke 0xe0db 221} "X-pos Wings" {dpoke 0xe0bc 221} "Y-pos Secret Bag" {dpoke 0xe0da 124} "Y-pos Wings" {dpoke 0xe0bb 124} "Lives: Lives" {dpoke 0xe090 153} "Weapon: Gun" {dpoke 0xe162 1} "Weapon: Gun In Secret Bag" {dpoke 0xe0d7 2} } create_trainer "Yuureikun - Mr.Ghost" {time 10} { "Attacks" {dpoke 0xc28e 255} "Invincible" {dpoke 0xc291 255} "Money" {dpoke 0xc01d 255;poke 0xc01e 255} "Power: Life Power" {dpoke 0xc3a6 32} } create_trainer "Zaider - Battle of Peguss" {time 1} { "Damage Bar" {dpoke 0xe1dc 0} "Psyco-g1 Ammo" {dpoke 0xe09b 255} "Psyco-g2 Ammo" {dpoke 0xe09c 255} "Psyco-g3 Ammo" {dpoke 0xe09d 255} "Zaider Damage" {dpoke 0xe0b5 0} } create_trainer "Zambeze" {time 1} { "Get Leafs" {dpoke 0xc011 1;poke 0xc012 1;dpoke 0xc013 1;dpoke 0xc014 1;dpoke 0xc015 1;dpoke 0xc016 1;dpoke 0xc017 1} "Lives: Get Lives" {dpoke 0xc025 8} } create_trainer "Zanac A.I." {time 1} { "Invincible" {dpoke 0xe305 128;poke 0xe31b 255} "Super Shot" {dpoke 0xe10f 48} "Enemy Power: Enemy 01 Power" {dpoke 0xe3b9 1} "Enemy Power: Enemy 02 Power" {dpoke 0xe3d9 1} "Enemy Power: Enemy 03 Power" {dpoke 0xe3f9 1} "Enemy Power: Enemy 04 Power" {dpoke 0xe419 1} "Enemy Power: Enemy 05 Power" {dpoke 0xe439 1} "Enemy Power: Enemy 06 Power" {dpoke 0xe459 1} "Enemy Power: Enemy 07 Power" {dpoke 0xe479 1} "Enemy Power: Enemy 08 Power" {dpoke 0xe499 1} "Enemy Power: Enemy 09 Power" {dpoke 0xe4b9 1} "Enemy Power: Enemy 10 Power" {dpoke 0xe4d9 1} "Enemy Power: Enemy 11 Power" {dpoke 0xe4f9 1} "Enemy Power: Enemy 12 Power" {dpoke 0xe519 1} "Enemy Power: Enemy 13 Power" {dpoke 0xe539 1} "Enemy Power: Enemy 14 Power" {dpoke 0xe559 1} "Enemy Power: Enemy 15 Power" {dpoke 0xe579 1} "Enemy Power: Enemy 16 Power" {dpoke 0xe599 1} "Enemy Power: Enemy 17 Power" {dpoke 0xe5b9 1} "Enemy Power: Enemy 18 Power" {dpoke 0xe5d9 1} "Enemy Power: Enemy 19 Power" {dpoke 0xe5f9 1} "Enemy Power: Enemy 20 Power" {dpoke 0xe619 1} "Lives: Lives" {dpoke 0xe10a 99} } create_trainer "Zanac A.I. - 2nd Version" {time 1} { "Invincible" {dpoke 0xe305 128;poke 0xe31b 255} "Super Shot" {dpoke 0xe10f 48} "Enemy Power: Enemy 01 Power" {dpoke 0xe559 1} "Enemy Power: Enemy 02 Power" {dpoke 0xe569 1} "Enemy Power: Enemy 03 Power" {dpoke 0xe579 1} "Enemy Power: Enemy 04 Power" {dpoke 0xe589 1} "Enemy Power: Enemy 05 Power" {dpoke 0xe599 1} "Enemy Power: Enemy 06 Power" {dpoke 0xe5a9 1} "Enemy Power: Enemy 07 Power" {dpoke 0xe5b9 1} "Enemy Power: Enemy 08 Power" {dpoke 0xe5c9 1} "Enemy Power: Enemy 09 Power" {dpoke 0xe5d9 1} "Enemy Power: Enemy 10 Power" {dpoke 0xe5e9 1} "Enemy Power: Enemy 11 Power" {dpoke 0xe5f9 1} "Enemy Power: Enemy 12 Power" {dpoke 0xe619 1} "Enemy Power: Enemy 13 Power" {dpoke 0xe629 1} "Enemy Power: Enemy 14 Power" {dpoke 0xe639 1} "Lives: Lives" {dpoke 0xe10a 99} } create_trainer "Zanac-Ex" {time 1} { "Get High Score" {dpoke 0xcbf8 0x99} "Invincible" {dpoke 0xc405 128} "Set Timer To Max" {dpoke 0xc416 255} "Lives: Lives" {dpoke 0xc012 100} "Weapon: Primary Weapon Max Level" {dpoke 0xc013 4;poke 0xc016 4;dpoke 0xc03f 3;dpoke 0xc040 3;dpoke 0xc104 2;dpoke 0xc161 2} "Weapon: Secondary Weapon Max Level" {dpoke 0xc041 2} } create_trainer "Zaxxon" {time 2} { "Fuel" {dpoke 0xe176 16} "Lives: Lives" {dpoke 0xe00b 6} } create_trainer "Zenji" {time 1} { "Time (deactivate @ Round End)" {dpoke 0xe192 48} "Lives: Lives" {dpoke 0xe1b1 153} } create_trainer "Zexas" {time 1} { "Fuel" {dpoke 0xe5f0 136} "Invulnerable: Invulnerable" {dpoke 0xe650 11;poke 0xe660 0} "Lives: Lives" {dpoke 0xe5ef 99} } create_trainer "Zexas Limited" {time 60} { "Invulnerable: Invulnerable" {dpoke 0xeed5 0} "Lives: Lives" {dpoke 0xeecf 99} } create_trainer "Zombie Hunter" {time 1} { "Exp" {dpoke 0xc7e6 255;poke 0xc7e7 255} "Life Bar" {dpoke 0xc7ea 255;poke 0xc7eb 255} "Max Level" {dpoke 0xc7ee 31} } create_trainer "Zone Terra" {time 1} { "Main Shot: 1x Main Shot " {dpoke 0x57e2 0} "Main Shot: 2x Main Shot " {dpoke 0x57e2 1} "Main Shot: 3x Main Shot " {dpoke 0x57e2 2} "Main Shot: Max Main Shot Speed" {dpoke 0x57e1 8} "Power: Full Power" {dpoke 0x57af 255} } create_trainer "Zoo" {time 1} { } create_trainer "Zoom 909" {time 1} { "Fuel" {dpoke 0xe021 0} } create_trainer "Zukkoke Yajikita Onmitsudoutyuu" {time 1} { "Life" {dpoke 0xe060 153} "Something" {dpoke 0xe061 153;poke 0xe063 153;dpoke 0xe064 9;dpoke 0xe065 153} "Something Else" {dpoke 0xe062 153} "Item: Item 1-8" {dpoke 0xe066 9;poke 0xe067 9;dpoke 0xe068 9;dpoke 0xe069 9;dpoke 0xe06a 9;dpoke 0xe06b 9;dpoke 0xe06c 9;dpoke 0xe06d 9} } # File Created in : 1.495 seconds. 752 trainers - having 5786 pokes openMSX-RELEASE_0_12_0/share/scripts/_type_from_file.tcl000066400000000000000000000042061257557151200231000ustar00rootroot00000000000000namespace eval type_from_file { proc tabcompletion_normal {args} { set possibilities [utils::file_completion {*}$args] if {[llength $args] > 2} { lappend possibilities {-release} {-freq} ;# duplicated from C++ } return $possibilities } set_tabcompletion_proc type_from_file [namespace code tabcompletion_normal] set_help_text type_from_file \ {type_from_file filename Types the content of the indicated file into the MSX. If you want to specify extra arguments for the normal type command, you have to specify them after the filename. } proc type_from_file {filename args} { # open file set f [open $filename "r"] # process all lines in the file while {[gets $f line] >= 0} { type {*}$args "$line\r" } close $f } proc tabcompletion_password {args} { utils::file_completion {*}$args } set_tabcompletion_proc type_password_from_file [namespace code tabcompletion_password] set_help_text type_password_from_file \ {type_password_from_file filename Types the content of a single line from the indicated file. If index is specified, the line at that index is typed. The first line of the file has index 1. Blank lines and lines starting with # are not counted. This command is useful to type in passwords which you have stored in a file. Example of an input file: # ------------------ # Usas # ------------------ # 1 juba ruins # 2 harappa ruins # 3 gandhara ruins # 4 mohenjo daro If this file is saved as 'usas.txt', the 3rd password can be type like this: type_password_from_file usas.txt 3 } proc type_password_from_file {filename {index 1}} { # open file set f [open $filename "r"] set line_index 1 set found false # process all lines in the file while {[gets $f line] >= 0} { if {[string first "#" $line] == 0} continue ;# skip lines with # if {[string length $line] == 0} continue ;# skip blank lines if {$line_index == $index} { type -release $line set found true break } incr line_index } close $f if {!$found} { error "No line found at index $index" } } namespace export type_from_file namespace export type_password_from_file } ;# namespace type_from_file namespace import type_from_file::* openMSX-RELEASE_0_12_0/share/scripts/_utils.tcl000066400000000000000000000074511257557151200212420ustar00rootroot00000000000000# Several utility procs for usage in other scripts # don't export anything, just use it from the namespace, # because these scripts aren't useful for console users # and should therefore not be exported to the global # namespace. # # These procs are not specific to anything special, # they could be useful in any script. # # Born to prevent duplication between scripts for common stuff. namespace eval utils { proc get_machine_display_name {{machineid ""}} { if {$machineid eq ""} { set machineid [machine] } if {$machineid eq ""} { return "" } set config_name [${machineid}::machine_info config_name] return [get_machine_display_name_by_config_name $config_name] } proc get_machine_display_name_by_config_name {config_name} { return [get_display_name_by_config_name $config_name "machines"] } proc get_extension_display_name_by_config_name {config_name} { return [get_display_name_by_config_name $config_name "extensions"] } proc get_display_name_by_config_name {config_name type} { if {[catch { set names [openmsx_info $type $config_name] if {$type eq "machines"} { set keylist [list "manufacturer" "code"] } elseif {$type eq "extensions"} { set keylist [list "manufacturer" "code" "name"] } else { error "Unsupported type: $type" } set arglist [list] foreach key $keylist { if [dict exists $names $key] { set arg [dict get $names $key] if {$arg ne ""} { lappend arglist $arg } } } set result [join $arglist] # fallback if this didn't give useful results: if {$result eq ""} { set result $config_name } }]} { # hmm, XML file probably broken. Fallback: set result "$config_name (CORRUPT)" } return $result } proc get_machine_time {{machineid ""}} { if {$machineid eq ""} { set machineid [machine] } set err [catch {set mtime [${machineid}::machine_info time]}] if {$err} { return "" } return [format_time $mtime] } proc format_time {time} { format "%02d:%02d:%02d" [expr {int($time / 3600)}] [expr {int($time / 60) % 60}] [expr {int($time) % 60}] } proc format_time_subseconds {time} { format "%02d:%02d.%02d" [expr {int($time / 60)}] [expr {int($time) % 60}] [expr {int(fmod($time,1) * 100)}] } proc get_ordered_machine_list {} { lsort -dictionary [list_machines] } proc get_random_number {max} { expr {floor(rand() * $max)} } proc clip {min max val} { expr {($val < $min) ? $min : (($val > $max) ? $max : $val)} } # provides.... file completion. Currently has a small issue: it adds a space at # after a /, which you need to erase to continue completing proc file_completion {args} { set result [list] foreach i [glob -nocomplain -path [lindex $args end] *] { if {[file isdirectory $i]} { append i / } lappend result $i } return $result } # Replaces characters that are invalid in file names on the host OS or # file system by underscores. if {$::tcl_platform(platform) eq "windows" || [string match *-dingux* $::tcl_platform(osVersion)]} { # Dingux is Linux, but runs on VFAT. variable _filename_clean_disallowed {[\x00-\x1f\x7f/\\?*:|"<>+\[\]]} } else { # UNIX does allow 0x01-0x1f and 0x7f, but we consider them undesirable. variable _filename_clean_disallowed {[\x00-\x1f\x7f/]} } proc filename_clean {path} { variable _filename_clean_disallowed return [regsub -all $_filename_clean_disallowed $path _] } namespace export get_machine_display_name namespace export get_machine_display_name_by_config_name namespace export get_extension_display_name_by_config_name namespace export get_display_name_by_config_name namespace export get_machine_time namespace export format_time namespace export get_ordered_machine_list namespace export get_random_number namespace export clip namespace export file_completion namespace export filename_clean } ;# namespace utils # Don't import in global namespace, these are only useful in other scripts. openMSX-RELEASE_0_12_0/share/scripts/_vdp.tcl000066400000000000000000000111711257557151200206650ustar00rootroot00000000000000namespace eval vdp { set_help_text getcolor \ {Return the current V99x8 palette settings for the given color index (0-15). The result is format as RGB, with each component in the range 0-7. } proc getcolor {index} { set rb [debug read "VDP palette" [expr {2 * $index}]] set g [debug read "VDP palette" [expr {2 * $index + 1}]] format "%03x" [expr {(($rb * 16) & 0x700) + (($g * 16) & 0x070) + ($rb & 0x007)}] } set_help_text setcolor \ {Change the V99x8 palette settings. See also getcolor. usage: setcolor 0-15 0-7 } proc setcolor {index rgb} { if {[catch { if {[string length $rgb] != 3} error set r [string index $rgb 0] set g [string index $rgb 1] set b [string index $rgb 2] if {($index < 0) || ($index > 15)} error if {($r > 7) || ($g > 7) || ($b > 7)} error debug write "VDP palette" [expr {$index * 2}] [expr {$r * 16 + $b}] debug write "VDP palette" [expr {$index * 2 + 1}] $g }]} { error "Usage: setcolor \n index 0..15\n r,g,b 0..7" } } proc format_table {entries columns frmt sep func} { set result "" set rows [expr {($entries + $columns - 1) / $columns}] for {set row 0} {$row < $rows} {incr row} { set line "" for {set col 0} {$col < $columns} {incr col} { set index [expr {$row + ($col * $rows)}] if {$index < $entries} { append line [format $frmt $index [$func $index]] $sep } } append result "${line}\n" } return $result } set_help_text vdpreg "Read or write a V99x8 register." proc vdpreg {reg {value ""}} { if {$value eq ""} { debug read "VDP regs" $reg } else { debug write "VDP regs" $reg $value } } set_help_text vdpregs "Gives an overview of the V99x8 registers." proc vdpregs {} { format_table 32 4 "%2d : 0x%02x" " " vdpreg } set_help_text v9990reg "Read or write a V9990 register." proc v9990reg {reg {value ""}} { if {$value eq ""} { debug read "Sunrise GFX9000 regs" $reg } else { debug write "Sunrise GFX9000 regs" $reg $value } } set_help_text v9990regs "Gives an overview of the V9990 registers." proc v9990regs {} { format_table 55 5 "%2d : 0x%02x" " " v9990reg } set_help_text palette "Gives an overview of the V99x8 palette registers." proc palette {} { format_table 16 4 "%x:%s" " " getcolor } proc val2bin val { set binRep [binary format c $val] binary scan $binRep B* binStr return $binStr } variable mode_lookup set mode_lookup(00000000) 1;# Screen 1 set mode_lookup(00000001) "TEXT40";# Screen 0 (WIDTH 40) set mode_lookup(00000010) 3;# Screen 3 set mode_lookup(00000100) 2;# Screen 2 set mode_lookup(00001000) 4;# Screen 4 set mode_lookup(00001001) "TEXT80";# Screen 0 (WIDTH 80) set mode_lookup(00001100) 5;# Screen 5 set mode_lookup(00010000) 6;# Screen 6 set mode_lookup(00010100) 7;# Screen 7 set mode_lookup(00011100) 8;# Screen 8 set_help_text get_screen_mode_number "Decodes the current screen mode from the VDP registers (as would be used in the BASIC SCREEN command)" proc get_screen_mode_number {} { set mode [get_screen_mode] if {[string range $mode 0 3] eq "TEXT"} { return 0 } elseif {$mode eq "invalid"} { return -1 } return $mode } set_help_text get_screen_mode "Decodes the current screen mode from the VDP registers (and returns it as a string)." proc get_screen_mode {} { variable mode_lookup set val [expr {(([vdpreg 0] & 14) << 1) | (([vdpreg 1] & 8) >> 2) | (([vdpreg 1] & 16) >> 4)}] if {[catch {set mode $mode_lookup([val2bin $val])}]} { return "invalid" } if {(($mode == 8) || ($mode == 7)) && ([vdpreg 25] & 8)} { set mode [expr {([vdpreg 25] & 16) ? 11 : 12}] } return $mode } set_help_text vpeek \ {Similar to the BASIC vpeek command, read a byte from the video RAM. This command has the same view on the VRAM as the programmer sees (as opposed to the physical VRAM content): - The whole 128kB address space is visible, if the machine has less VRAM then some parts will either be mirrored or unmapped. - Depending on the current screen mode, the VRAM addressing is interleaved or not. This command follows that addressing scheme (IOW, normally you don't have to care). See also the 'vpoke' command. } proc vpeek {addr} { debug read VRAM $addr } set_help_text vpoke \ {Similar to the BASIC vpoke command, write a byte to the video RAM. See the 'vpeek' command for more info about the VRAM address space. } proc vpoke {addr val} { debug write VRAM $addr $val } namespace export getcolor namespace export setcolor namespace export get_screen_mode namespace export get_screen_mode_number namespace export vdpreg namespace export vdpregs namespace export v9990regs namespace export vpeek namespace export vpoke namespace export palette } ;# namespace vdp namespace import vdp::* openMSX-RELEASE_0_12_0/share/scripts/_vdp_access_test.tcl000066400000000000000000000064731257557151200232560ustar00rootroot00000000000000namespace eval vdp_access_test { # you can tweak these to test for other stuff # TODO: make this flexible so the timing can be specified for each combination # of ports and read/write variable ioports {0x98 0x99} variable cycle_max 29 # if you set this to true, you can also see OK I/O, but there are many of those! variable debug false # if you set this to true, openMSX will break when too fast access happens, so # you can investigate what's going on with the debug commands (or debugger) variable enable_break false variable last_access_time 0 variable last_access_type variable last_access_port variable is_enabled false variable watchpoint_write_id variable watchpoint_read_id variable address_list set_help_text toggle_vdp_access_test \ "Report in the console when VDP I/O is done which could possibly cause data corruption on the slowest VDP (TMS99xx), i.e. when time between successive I/O was less than $cycle_max cycles and display was active. Note: this script is not 100% accurate! Keep testing on a real MSX as well. The script has some tuning options; edit the script to do so, it's explained at the top what can be tuned." proc check_time {access_type} { variable last_access_time variable last_access_type variable last_access_port variable cycle_max variable debug variable enable_break variable address_list set port [expr {$::wp_last_address & 255}] set current_time [machine_info time] set cycles [expr {round(3579545 * ($current_time - $last_access_time))}] set screen_enabled [expr {[debug read "VDP regs" 1] & 64}] set vblank [expr {[debug read "VDP status regs" 2] & 64}] set pc [format "%04x" [reg PC]] if {($cycles < $cycle_max) && $screen_enabled && !$vblank} { if {$pc ni $address_list} { set valuetext "" if {$access_type eq "write"} { set valuetext [format " (value 0x%02X)" $::wp_last_value] } puts [format "VDP $last_access_type on port 0x%02X followed by a $access_type${valuetext} on port 0x%02X on address $pc and time $current_time with too few cycles in between: $cycles (< $cycle_max)" $last_access_port $port] lappend address_list $pc set address_list [lsort $address_list] puts "Sorted list of addresses where too fast I/O is done: $address_list" if {$enable_break} { debug break } } } else { if {$debug} { if {!$screen_enabled} { set reason "screen is disabled" } elseif {$vblank} { set reason "in vblank" } else { set reason "last access was $cycles cycles ago, >= $cycle_max" } puts [format "VDP I/O $access_type to port 0x%02X OK on address: 0x$pc, $reason" $port] } } set last_access_time $current_time set last_access_port $port set last_access_type $access_type } proc toggle_vdp_access_test {} { variable is_enabled variable watchpoint_write_id variable watchpoint_read_id variable address_list variable ioports if {!$is_enabled} { set watchpoint_write_id [debug set_watchpoint write_io $ioports {} {vdp_access_test::check_time "write"}] set watchpoint_read_id [debug set_watchpoint read_io $ioports {} {vdp_access_test::check_time "read"}] set is_enabled true set address_list [list] } else { debug remove_watchpoint $watchpoint_write_id debug remove_watchpoint $watchpoint_read_id set is_enabled false } } namespace export toggle_vdp_access_test } ;# namespace vdp_access_test namespace import vdp_access_test::* openMSX-RELEASE_0_12_0/share/scripts/_vdp_busy.tcl000066400000000000000000000024001257557151200217220ustar00rootroot00000000000000namespace eval vdp_busy { variable curr_cnt 0 variable last_cnt 0 variable after_id proc reset {} { # each frame copy current counter to last counter and reset current counter variable curr_cnt variable last_cnt variable after_id if [info exists after_id] { set last_cnt $curr_cnt set curr_cnt 0 after frame vdp_busy::reset } } proc sample {} { # 100 times per frame, sample if VDP command engine is busy variable curr_cnt variable after_id if [info exists after_id] { incr curr_cnt [expr {[debug read "VDP status regs" 2] & 1}] set fps [expr {([vdpreg 9] & 2) ? 50 : 60}] after time [expr {0.01 / $fps}] vdp_busy::sample } } proc display {} { # a couple of times per second update the OSD variable last_cnt variable after_id osd configure "vdp_busy.text" -text "${last_cnt}%" set after_id [after time .25 vdp_busy::display] } proc toggle_vdp_busy {} { variable after_id if [info exists after_id] { after cancel $after_id osd destroy "vdp_busy" unset after_id } else { osd create rectangle "vdp_busy" \ -x 5 -y 30 -w 42 -h 20 -alpha 0x80 osd create text "vdp_busy.text" \ -x 5 -y 3 -rgb 0xffffff display reset sample } return "" } namespace export toggle_vdp_busy } ;# namespace vdp_busy namespace import vdp_busy::* openMSX-RELEASE_0_12_0/share/scripts/_vdrive.tcl000066400000000000000000000071021257557151200213720ustar00rootroot00000000000000set_help_text vdrive \ {v-drive functionality of blueMSX implemented in a Tcl script for openMSX version 1.0 What does it do: It is a simple Tcl script that bound to a hotkey (f.i. F9+ALT) allows users to swap disks without using the commandconsole or Catapult for file selection. This is especially useful for games and demos that span over multiple disks. Preparations: 1. If you have software that spans multiple disks, you have to name them according to the scheme: name+digit+extension for example [metal1.dsk, metal2.dsk, metal3.dsk] Of course they may all be compressed with gzip, so you'll end up with [metal1.dsk.gz, metal2.dsk.gz, metal3.dsk.gz]. The script recognizes 'dsk', 'di1', 'di2', 'xsa' and 'dmk' extensions, with an optional '.gz' or '.zip' suffix. 2. bind the vdrive script to a hotkey, for instance type in the console: bind ALT+F9 "vdrive diska" bind ALT+F10 "vdrive diskb" vdrive will default to 'diska' when no drive parameter is specified. Note: the two bind commands above are already the default key bindings; you only need to execute them if you want a different key binding. Using: While emulating an MSX, and the sofware asks for the next disk, simply press the hot-key. The script will select the next disk in the sequence. If the last disk is reached, the script will select the first disk again. So you actually are cycling through the entire disk set as shown in the diagram below _"disk1.dsk" => ALT+F9 => "disk2.dsk" => ALT+F9 => "disk3.dsk" _ | | <= <= <= <= <= <= <= ALT+F9 <= <= <= <= <= <= <= <= <= <= <= <= credits: original alt+f9 v-DRIVE idea by the blueMSX crew. copyright 2005 David Heremans } proc vdrive {{diskdrive "diska"} {step 1}} { # Get current disk if {[catch {set cmd [$diskdrive]}]} {error "No such drive: $diskdrive"} # Skip for empty drive or 'special' disk set options [lindex $cmd 2] if {"empty" in $options} { error "No disk in drive: $diskdrive" } elseif {"ramdsk" in $options} { error "Vdrive not possible on ramdsk" } elseif {"dirasdisk" in $options} { error "Vdrive not possible on DirAsDisk" } # Remove (dsk|di1|di2|xsa|dmk)(.gz)? extention set base [lindex $cmd 1] set ext "" set tmp [file extension $base] foreach i {".gz" ".zip"} { if {[string equal -nocase $i $tmp]} { set ext $tmp set base [file rootname $base] break } } set tmp [file extension $base] foreach i {".dsk" ".di1" ".di2" ".xsa" ".dmk"} { if {[string equal -nocase $i $tmp]} { set ext ${tmp}${ext} set base [file rootname $base] break } } # Split on trailing digits if {![regexp -indices {[0-9]+$} $base match]} { error "Name doesn't end in a number" } set i [lindex $match 0] set num [string range $base $i end] set base [string range $base 0 $i-1] # Calculate range (number of digits) # Trim leading zeros (avoid interpreting the value as octal) set digits [string length $num] set range [expr {int(pow(10, $digits))}] set num [string trimleft $num 0] set fmt "%s%0${digits}d%s" # Increase (decrease) number until new file is found. set orig $num while 1 { set num [expr {($num + $step) % $range}] if {$num == $orig} { # We're back at the original. Explicitly test for this # because the original file might not exist anymore. return } # Construct new filename (including leading zeros) set newfile [format $fmt $base $num $ext] if {[file exists $newfile]} { # New file exists, insert in the disk drive diska $newfile return "New diskimage: $newfile" } } } openMSX-RELEASE_0_12_0/share/scripts/_vu-meters.tcl000066400000000000000000000114141257557151200220230ustar00rootroot00000000000000# VU-meters, just for fun. # # Search in the script for 'customized' to see what you could customize ;-) # # The volume calculations are the volume setting of the channel, unless nothing # is played, then 0 is output. This is done by looking at 'key on' bits, as far # as they exist. # # TODO: # - optimize more? # - handle insertion/removal of sound devices (needs openMSX changes) # # Thanks to BiFi for helping out with the expressions for the devices. # Thanks to Wouter for the Tcl support. set_help_text toggle_vu_meters \ {Puts a rough volume unit display for each channel and for all sound chips on the On-Screen-Display. Use the command again to remove it. Note: it cannot handle run-time insertion/removal of sound devices. It can handle changing of machines, though. Not all chips are supported yet. Note that displaying these VU-meters may cause quite some CPU load!} namespace eval vu_meters { variable vu_meters_active false variable volume_cache variable volume_expr variable bar_length variable machine_switch_trigger_id 0 variable frame_trigger_id 0 proc vu_meters_init {} { variable volume_cache variable volume_expr variable bar_length variable machine_switch_trigger_id # create root object for vu_meters osd create rectangle vu_meters \ -scaled true \ -alpha 0 \ -z 1 foreach soundchip [machine_info sounddevice] { # skip devices which don't have volume expressions (not implemented yet) if {[soundchip_utils::get_volume_expr $soundchip 0] eq "x"} continue # determine number of channels set channel_count [soundchip_utils::get_num_channels $soundchip] for {set i 0} {$i < $channel_count} {incr i} { # create the volume cache and the expressions dict set volume_cache $soundchip $i -1 dict set volume_expr $soundchip $i [soundchip_utils::get_volume_expr $soundchip $i] } } set nof_soundchips [dict size $volume_expr] set bar_width 2; # this value could be customized set vu_meter_title_height 8; # this value could be customized set bar_length [expr {(320 - $nof_soundchips) / $nof_soundchips}] if {$bar_length > (320 / 4)} {set bar_length [expr {320 / 4}]} # create widgets for each sound chip: set vu_meter_offset 0 dict for {soundchip channel_dict} $volume_expr { set nof_channels [dict size $channel_dict] # create surrounding widget for this chip osd create rectangle vu_meters.$soundchip \ -rgba 0x00000080 \ -x ${vu_meter_offset} \ -y 0 \ -w $bar_length \ -h [expr {$vu_meter_title_height + 1 + $nof_channels * ($bar_width + 1)}] \ -clip true osd create text vu_meters.${soundchip}.title \ -x 1 \ -y 1 \ -rgba 0xffffffff \ -text $soundchip \ -size [expr {$vu_meter_title_height - 1}] # create vu meters for this sound chip dict for {channel volExpr} $channel_dict { osd create rectangle vu_meters.${soundchip}.ch${channel} \ -rgba 0xff0000ff \ -x 0 \ -y [expr {$vu_meter_title_height + 1 + (($bar_width + 1) * $channel)}] \ -w 0 \ -h $bar_width \ } incr vu_meter_offset [expr {$bar_length + 1}] } set machine_switch_trigger_id [after machine_switch [namespace code vu_meters_reset]] } proc update_meters {} { variable vu_meters_active variable volume_cache variable volume_expr variable frame_trigger_id # update meters with the volumes if {!$vu_meters_active} return dict for {soundchip channel_dict} $volume_expr { dict for {channel volExpr} $channel_dict { set new_volume [eval $volExpr] if {[dict get $volume_cache $soundchip $channel] != $new_volume} { dict set volume_cache $soundchip $channel $new_volume update_meter "vu_meters.${soundchip}.ch${channel}" $new_volume } } } # here you can customize the update frequency (to reduce CPU load) #set frame_trigger_id [after time 0.05 [namespace code update_meters]] set frame_trigger_id [after frame [namespace code update_meters]] } proc update_meter {meter volume} { variable bar_length set byte_val [expr {round(255 * $volume)}] set col1 0x008000C0 set col2 [expr {($byte_val << 24) + $col1}] osd configure $meter \ -w [expr {$bar_length * $volume}] \ -rgba "$col1 $col2 $col1 $col2" } proc vu_meters_reset {} { variable vu_meters_active if {!$vu_meters_active} { error "Please fix a bug in this script!" } toggle_vu_meters toggle_vu_meters } proc toggle_vu_meters {} { variable vu_meters_active variable machine_switch_trigger_id variable frame_trigger_id variable bar_length variable volume_cache variable volume_expr if {$vu_meters_active} { after cancel $machine_switch_trigger_id after cancel $frame_trigger_id set vu_meters_active false osd destroy vu_meters unset bar_length volume_cache volume_expr } else { set vu_meters_active true vu_meters_init update_meters } return "" } namespace export toggle_vu_meters } ;# namespace vu_meters namespace import vu_meters::* openMSX-RELEASE_0_12_0/share/scripts/_widgets.tcl000066400000000000000000000077361257557151200215560ustar00rootroot00000000000000namespace eval debug_widgets { #TODO: Help Texts proc toggle_show_palette {} { if {[osd exists palette_viewer]} { osd destroy palette_viewer return "" } osd create rectangle palette_viewer \ -x 4 -y 4 -w 56 -h 194 -rgba 0x00000080 \ -borderrgba 0xffffffff -bordersize 1 for {set i 0} {$i < 16} {incr i} { osd create rectangle palette_viewer.$i \ -x 20 -y [expr {($i * 12) + 2}] \ -w 10 -h 10 \ -rgba 0xff0000ff \ -borderrgba 0xffffffff -bordersize 1 osd create text palette_viewer.$i.text \ -x -16 \ -rgba 0xffffffff \ -size 10 \ -text "" } update_palette return "" } proc update_palette {} { if {![osd exists palette_viewer]} return for {set i 0} {$i < 16} {incr i} { set color [getcolor $i] set r [string range $color 0 0] set g [string range $color 1 1] set b [string range $color 2 2] set rgbval [expr {($r << (5 + 16)) + ($g << (5 + 8)) + ($b << 5)}] osd configure palette_viewer.$i -rgb $rgbval osd configure palette_viewer.$i.text -text "[format %02d $i] $color" } after frame [namespace code update_palette] } proc toggle_vdp_reg_viewer {} { if {[osd exists vdp_reg_viewer]} { osd destroy vdp_reg_viewer osd destroy vdp_statreg_viewer return "" } set fontsize 9 # note: this method of VDP detection will fail on e.g. MSX1 machines with V9938 set vdpreg [expr {([debug read "slotted memory" 0x2d]) ? 47 : 8}] set vdpsta [expr {([debug read "slotted memory" 0x2d]) ? 10 : 1}] osd create rectangle vdp_reg_viewer \ -x 0 \ -y 0 \ -h 480 \ -w [expr {$fontsize * 8}] \ -rgba 0x00000080 osd create rectangle vdp_statreg_viewer \ -x [expr {($fontsize * 8) + 16}] \ -y 0 \ -h 480 \ -w [expr {$fontsize * 8}] \ -rgba 0x00000080 for {set i 0} {$i < $vdpreg} {incr i} { osd create rectangle vdp_reg_viewer.indi$i \ -x 0 \ -y [expr {$i * $fontsize}] \ -w [expr {$fontsize * 8}] \ -h $fontsize \ -rgba 0xff0000ff \ -fadeTarget 0 \ -fadePeriod 1 osd create text vdp_reg_viewer.labl$i \ -x 0 \ -y [expr {$i * $fontsize}] \ -size $fontsize \ -text "R# [format %02d $i]:" \ -rgba 0xffffffff osd create text vdp_reg_viewer.stat$i \ -x [expr {$fontsize * 4}] \ -y [expr {$i * $fontsize}] \ -size $fontsize \ -text "[format 0x%02X [debug read VDP\ regs $i]]" \ -rgba 0xffffffff } for {set i 0} {$i < $vdpsta} {incr i} { osd create rectangle vdp_statreg_viewer.indi$i \ -x 0 \ -y [expr {$i * $fontsize}] \ -w [expr {$fontsize * 8}] \ -h $fontsize \ -rgba 0xff0000ff \ -fadeTarget 0 \ -fadePeriod 1 osd create text vdp_statreg_viewer.labl$i \ -x 0 \ -y [expr {$i * $fontsize}] \ -size $fontsize \ -text "S# [format %02d $i]:" \ -rgba 0xffffffff osd create text vdp_statreg_viewer.stat$i \ -x [expr {$fontsize * 4}] \ -y [expr {$i * $fontsize}] \ -size $fontsize \ -text "[format 0x%02X [debug read VDP\ status\ regs $i]]" \ -rgba 0xffffffff } update_vdp_reg_viewer return "" } proc update_vdp_reg_viewer {} { if {![osd exists vdp_reg_viewer]} return # note: this method of VDP detection will fail on e.g. MSX1 machines with V9938 set vdpreg [expr {([debug read "slotted memory" 0x2d]) ? 47 : 8}] set vdpsta [expr {([debug read "slotted memory" 0x2d]) ? 10 : 1}] for {set i 0} {$i < $vdpreg} {incr i} { set vdp_stat "[format 0x%02X [debug read VDP\ regs $i]]" if {$vdp_stat ne [osd info vdp_reg_viewer.stat$i -text]} { osd configure vdp_reg_viewer.stat$i -text "$vdp_stat" osd configure vdp_reg_viewer.indi$i -fadeCurrent 1 } } for {set i 0} {$i < $vdpsta} {incr i} { set vdp_stat "[format 0x%02X [debug read VDP\ status\ regs $i]]" if {$vdp_stat ne [osd info vdp_statreg_viewer.stat$i -text]} { osd configure vdp_statreg_viewer.stat$i -text "$vdp_stat" osd configure vdp_statreg_viewer.indi$i -fadeCurrent 1 } } after frame [namespace code update_vdp_reg_viewer] } namespace export toggle_show_palette namespace export toggle_vdp_reg_viewer } ;# namespace debug_widgets namespace import debug_widgets::* openMSX-RELEASE_0_12_0/share/scripts/autoplug.tcl000066400000000000000000000031601257557151200215740ustar00rootroot00000000000000# if this machine has a cassetteport, then automatically plug # in the cassetteplayer # on a Dingoo, automatically plug in the keyjoystick and if there are real # joysticks, plug them as well for non-Dingoo platofrms namespace eval autoplug { proc plug_if_empty {connector pluggable} { if {[string first "--empty--" [plug $connector]] != -1} { # only when nothing already plugged plug $connector $pluggable } } proc do_autoplug {} { # do not do any auto plug stuff when replaying, because a reset # command in the replay will trigger autoplug and will cause the # replay to get interrupted with the plug event. if {[dict get [reverse status] status] ne "replaying"} { set connectors [list] catch { #can fail when you activate an 'empty' machine set connectors [machine_info connector] } set pluggables [list] catch { #can fail when you activate an 'empty' machine set pluggables [machine_info pluggable] } # cassette port if {"cassetteport" in $connectors} { plug_if_empty cassetteport cassetteplayer } # joystick ports if {[string match *-dingux* $::tcl_platform(osVersion)]} { ;# Dingoo if {"joyporta" in $connectors} { set ::keyjoystick1.triga LCTRL set ::keyjoystick1.trigb LALT plug_if_empty joyporta keyjoystick1 } } else { if {("joyporta" in $connectors) && ("joystick1" in $pluggables)} { plug_if_empty joyporta joystick1 } if {("joyportb" in $connectors) && ("joystick2" in $pluggables)} { plug_if_empty joyportb joystick2 } } } after boot [namespace code do_autoplug] } };# namespace autoplug after boot autoplug::do_autoplug openMSX-RELEASE_0_12_0/share/scripts/callbackprocs.tcl000066400000000000000000000074051257557151200225450ustar00rootroot00000000000000# Callback on read from uninitialized memory (UMR). Not enabled by default. set_help_text umrcallback \ {This is an example of a UMR callback function. You can set the "umr_callback" setting in openMSX to "umrcallback" and this function will be called whenever openMSX detects a read from uninitialized memory. It might be a good idea to go into 'debug break' mode in this callback, so that you can easily examine what is going on which has caused the UMR. @param addr The absolute address in the RAM device. @param name The name of the RAM device. } proc umrcallback {addr name} { puts stderr [format "UMR detected in RAM device \"$name\", offset: 0x%04X, PC: 0x%04X" $addr [reg PC]] } #set umr_callback umrcallback # Callback on changing the VDP command registers while a command is still in # progress. Not enabled by default. set_help_text vdpcmdinprogresscallback \ {This is an example of a vdpcmdinprogress callback function. To use it execute set vdpcmdinprogress_callback vdpcmdinprogresscallback After changing that setting this proc will be called whenever openMSX detects a write to a VDP command engine register while there's still a VDP command in progress. Often this is an indication of a bug in the MSX program. This example callback proc only prints the register number, the value that was being written and the program counter. Feel free to write your own callback procs. For example it might be a good idea to enter 'debug break' mode in your callback, so that you can easily examine what is going on. @param reg The VDP command engine register number that was being written This is in range 0..14. Note that it's OK to write register 12 while a command is in progress. So this callback will not be triggered for writes to register 12. @param val The value that was being written. } proc vdpcmdinprogresscallback {reg val} { puts stderr [format "Write to VDP command engine detected while there's still a command in progress: reg = %d, val = %d, PC = 0x%04X" [expr {$reg + 32}] $val [reg PC]] } #set vdpcmdinprogress_callback vdpcmdinprogresscallback # Callback on setting invalid PSG port directions. set psg_directions_warning_printed false proc psgdirectioncallback {} { if {$::psg_directions_warning_printed} return set ::psg_directions_warning_printed true message {The running MSX software has set unsafe PSG port directions. Real (older) MSX machines can get damaged by this.} warning } set invalid_psg_directions_callback psgdirectioncallback # Callback on DI;HALT. proc dihaltcallback {} { message "DI; HALT detected, which means a hang. You can just as well reset the MSX now..." warning } set di_halt_callback dihaltcallback # Callback on too fast VRAM read/write, not enabled by default proc warn_too_fast_vram_access {} { message [format "Too fast VRAM access PC=0x%04x Y-pos=%d" [reg PC] [machine_info VDP_msx_y_pos]] warning } proc debug_too_fast_vram_access {} { warn_too_fast_vram_access debug break } #set VDP.too_fast_vram_access_callback debug_too_fast_vram_access #set VDP.too_fast_vram_access_callback warn_too_fast_vram_access proc sensorkidportstatuscallback {port value} { message "Sensor Kid port $port has been [expr {$value ? "enabled" : "disabled"}]" info } proc sensorkidacquirecallback {port} { set time [machine_info time] switch $port { 0 { # Sine return [expr {int(sin($time * 0.1) * 127.0) + 128}] } 1 { # Sawtooth return [expr {int($time * 10.0) % 256}] } 2 { # Triangle set t [expr {int($time * 4.0) % 512}] return [expr {($t > 255) ? (511 - $t) : $t}] } default { return 255 } } } set sensor_kid_port_status_callback sensorkidportstatuscallback set sensor_kid_acquire_callback sensorkidacquirecallback # show message (also) as OSD message set message_callback osd::display_message openMSX-RELEASE_0_12_0/share/scripts/cashandler.tcl000066400000000000000000000033251257557151200220430ustar00rootroot00000000000000namespace eval cashandler { user_setting create boolean fast_cas_load_hack_enabled \ "Whether you want to enable a hack that enables you to quickly load CAS files with the cassetteplayer, without converting them to WAV first. This is not recommended and several cassetteplayer functions will not work anymore (motor control, position indication, size indication). Also note that this hack only works when inserting cassettes when the MSX is already started up, not when inserting them via the openMSX command line." false variable old_value $::fast_cas_load_hack_enabled variable hack_is_already_enabled false proc set_hack {} { variable hack_is_already_enabled # example: turboR.. # for now, ignore the commands... it's quite complex to # get this 100% water tight with adding/removing machines # at run time if {[catch "machine_info connector cassetteport"]} return if {$::fast_cas_load_hack_enabled && !$hack_is_already_enabled} { interp hide {} cassetteplayer interp alias {} cassetteplayer {} cashandler::tapedeck set hack_is_already_enabled true puts "Fast cas load hack installed." } elseif {!$::fast_cas_load_hack_enabled && $hack_is_already_enabled} { interp alias {} cassetteplayer {} interp expose {} cassetteplayer set hack_is_already_enabled false puts "Fast cas load hack uninstalled." } } proc setting_changed {name1 name2 op} { variable old_value if {$::fast_cas_load_hack_enabled != $old_value} { set_hack set old_value $::fast_cas_load_hack_enabled } } trace add variable ::fast_cas_load_hack_enabled write [namespace code setting_changed] proc initial_set {} { if {$::fast_cas_load_hack_enabled} { set_hack } } after realtime 0 [namespace code initial_set] } ;# namespace cashandler openMSX-RELEASE_0_12_0/share/scripts/create_user_dirs.tcl000066400000000000000000000007231257557151200232600ustar00rootroot00000000000000# pre-create directories in the openMSX user dir, for ease of use set subdirs [list "systemroms" "software" "scripts" "skins" "machines" "extensions"] foreach subdir $subdirs { set directory [file normalize $::env(OPENMSX_USER_DATA)/$subdir] if {![file exists $directory]} { set sourcedir [file normalize $::env(OPENMSX_SYSTEM_DATA)/$subdir] file mkdir $directory if {[file exists $sourcedir/README]} { file copy -- $sourcedir/README $directory } } } openMSX-RELEASE_0_12_0/share/scripts/keybindings.tcl000066400000000000000000000033071257557151200222450ustar00rootroot00000000000000# Since we are now supporting specific platforms we need a # solution to support key bindings for multiple devices. variable is_dingoo [string match *-dingux* $::tcl_platform(osVersion)] variable is_android true ;# TODO # cycle_machine bind_default CTRL+PAGEUP cycle_machine bind_default CTRL+PAGEDOWN cycle_back_machine # osd_keyboard if {$is_dingoo} { # for Dingoo assign the start button to show the keyboard bind_default "keyb RETURN" toggle_osd_keyboard } elseif {$is_android} { # Android maps one of the virtual keys to WORLD_95 # listen to that one in order to show the keyboard bind_default "keyb WORLD_95" toggle_osd_keyboard } # osd_menu if {$tcl_platform(os) eq "Darwin"} { ;# Mac bind_default "keyb META+O" main_menu_toggle } elseif {$is_dingoo} { ;# Dingoo bind_default "keyb ESCAPE" main_menu_toggle ;# select button bind_default "keyb MENU" main_menu_toggle ;# default: power+select } else { ;# any other bind_default "keyb MENU" main_menu_toggle } # osd_widgets if {$is_dingoo} { bind_default TAB -repeat "volume_control -2" bind_default BACKSPACE -repeat "volume_control +2" } # reverse # note: you can't use bindings with modifiers like SHIFT, because they # will already stop the replay, as they are MSX keys as well bind_default PAGEUP -repeat "go_back_one_step" bind_default PAGEDOWN -repeat "go_forward_one_step" # savestate if {$tcl_platform(os) eq "Darwin"} { bind_default META+S savestate bind_default META+R loadstate } else { bind_default ALT+F8 savestate bind_default ALT+F7 loadstate } # vdrive bind_default ALT+F9 "vdrive diska" bind_default SHIFT+ALT+F9 "vdrive diska -1" bind_default ALT+F10 "vdrive diskb" bind_default SHIFT+ALT+F10 "vdrive diskb -1" openMSX-RELEASE_0_12_0/share/scripts/lazy.tcl000066400000000000000000000106201257557151200207120ustar00rootroot00000000000000# List of Tcl-scripts that can be loaded on-demand. For each script you also # needs to provide a list of Tcl procs that it provides. # (preferably keep this list sorted on script name) register_lazy "_about.tcl" about register_lazy "_backwards_compatibility.tcl" {quit decr restoredefault alias} register_lazy "_cheat.tcl" findcheat register_lazy "_cashandler.tcl" {casload cassave caslist casrun caspos caseject tapedeck} register_lazy "_cpuregs.tcl" {reg cpuregs get_active_cpu} register_lazy "_cycle.tcl" {cycle cycle_back toggle} register_lazy "_cycle_machine.tcl" {cycle_machine cycle_back_machine} register_lazy "_disasm.tcl" { peek peek8 peek_u8 peek_s8 peek16 peek16_LE peek16_BE peek_u16 peek_u16_LE peek_u16_BE peek_s16 peek_s16_LE peek_s16_BE poke poke8 poke16 poke16_LE poke16_BE dpoke disasm run_to step_over step_back step_out step_in step skip_instruction} register_lazy "_example_tools.tcl" {get_screen listing get_color_count} register_lazy "_filepool.tcl" {filepool get_paths_for_type} register_lazy "_guess_title.tcl" {guess_title guess_rom_title} register_lazy "_info_panel.tcl" toggle_info_panel register_lazy "_metal_gear_overlay.tcl" {toggle_metal_gear_overlay} register_lazy "_mog-overlay.tcl" {toggle_mog_overlay toggle_mog_editor} register_lazy "_monitor.tcl" monitor_type register_lazy "_multi_screenshot.tcl" multi_screenshot register_lazy "_music_keyboard.tcl" {toggle_music_keyboard} register_lazy "_osd.tcl" {show_osd display_message is_cursor_in} register_lazy "_osd_keyboard.tcl" toggle_osd_keyboard register_lazy "_osd_menu.tcl" { main_menu_open main_menu_close main_menu_toggle do_menu_open prepare_menu_list menu_close_all select_menu_item} register_lazy "_osd_nemesis.tcl" toggle_nemesis_1_shield register_lazy "_osd_widgets.tcl" { toggle_fps msx_init msx_update box text_box create_power_bar update_power_bar hide_power_bar volume_control} register_lazy "_psg_log.tcl" psg_log register_lazy "_psg_profile.tcl" psg_profile register_lazy "_quitmenu.tcl" quit_menu register_lazy "_record_channels.tcl" { record_channels mute_channels unmute_channels solo} register_lazy "_record_chunks.tcl" record_chunks register_lazy "_reg_log.tcl" reg_log register_lazy "_reverse.tcl" { reverse_prev reverse_next goto_time_delta go_back_one_step go_forward_one_step reverse_bookmarks toggle_reversebar enable_reversebar disable_reversebar auto_enable} register_lazy "_rom_info.tcl" {rom_info getlist_rom_info} register_lazy "_save_debuggable.tcl" { save_debuggable load_debuggable save_all load_all vramdump vram2bmp save_to_file} register_lazy "_save_msx_screen.tcl" save_msx_screen register_lazy "_savestate.tcl" { savestate loadstate delete_savestate list_savestates list_savestates_raw} register_lazy "_scc_toys.tcl" { toggle_scc_editor toggle_psg2scc set_scc_wave toggle_scc_viewer} register_lazy "_showdebuggable.tcl" {showdebuggable showmem} register_lazy "_slot.tcl" { get_selected_slot slotselect get_mapper_size pc_in_slot watch_in_slot address_in_slot slotmap iomap} register_lazy "_soundchip_utils.tcl" { get_num_channels get_volume_expr get_frequency_expr} register_lazy "_soundlog.tcl" soundlog register_lazy "_sprites.tcl" {sprite_viewer draw_matrix} register_lazy "_stack.tcl" stack register_lazy "_tas_tools.tcl" { toggle_frame_counter prev_frame next_frame start_of_frame advance_frame reverse_frame toggle_cursors ram_watch toggle_lag_counter reset_lag_counter toggle_movie_length_display} register_lazy "_test_machines_and_extensions.tcl" { test_all_machines test_all_extensions} register_lazy "_text_echo.tcl" text_echo register_lazy "_tileviewer.tcl" {showtile showall} register_lazy "_toggle_freq.tcl" toggle_freq register_lazy "_trainer.tcl" trainer register_lazy "_type_from_file.tcl" {type_from_file type_password_from_file} register_lazy "_utils.tcl" { get_machine_display_name get_machine_display_name_by_config_name get_extension_display_name_by_config_name get_display_name_by_config_name get_machine_time format_time format_time_subseconds get_ordered_machine_list get_random_number clip file_completion filename_clean} register_lazy "_vdp.tcl" { getcolor setcolor get_screen_mode get_screen_mode_number vdpreg vdpregs v9990regs vpeek vpoke palette} register_lazy "_vdp_access_test.tcl" toggle_vdp_access_test register_lazy "_vdp_busy.tcl" toggle_vdp_busy register_lazy "_vdrive.tcl" vdrive register_lazy "_vu-meters.tcl" toggle_vu_meters register_lazy "_widgets.tcl" {toggle_show_palette toggle_vdp_reg_viewer} openMSX-RELEASE_0_12_0/share/scripts/load_icons.tcl000066400000000000000000000250601257557151200220510ustar00rootroot00000000000000namespace eval load_icons { set_help_text load_icons \ {Load a different set of OSD icons. usage: load_icons [ []] is the name of a directory (share/skins/) that contains the icon images. If this parameter is not given, a list of available skins will be printed. can be one of the following 'bottom', 'top', 'left' or 'right'. The default depends on the selected skin. example: load_icons set1 top } set_tabcompletion_proc load_icons [namespace code tab_load_icons] proc tab_load_icons {args} { set num [llength $args] if {$num == 2} { set r1 [glob -nocomplain -tails -type d -directory $::env(OPENMSX_USER_DATA)/skins *] set r2 [glob -nocomplain -tails -type d -directory $::env(OPENMSX_SYSTEM_DATA)/skins *] concat $r1 $r2 } elseif {$num == 3} { list "top" "bottom" "left" "right" } } variable icon_list variable last_change variable current_osd_leds_set variable current_osd_leds_pos variable current_fade_delay_active variable current_fade_delay_non_active variable fade_id proc trace_icon_status {name1 name2 op} { variable last_change global $name1 set icon [string trimleft $name1 ":"] set now [openmsx_info realtime] set last_change($icon) $now redraw_osd_icons $icon $now } proc redraw_osd_icons {icon now} { variable last_change variable current_fade_delay_active variable current_fade_delay_non_active variable fade_id global $icon # handle 'unset' variables (when current msx machine got deleted) if {[catch {set value [set $icon]}]} {set value false} if {$value} { set widget osd_icons.${icon}_on set widget2 osd_icons.${icon}_off set fade_delay $current_fade_delay_active($icon) } else { set widget osd_icons.${icon}_off set widget2 osd_icons.${icon}_on set fade_delay $current_fade_delay_non_active($icon) } osd configure $widget2 -fadeCurrent 0 -fadeTarget 0 catch {after cancel $fade_id($icon)} if {$fade_delay == 0} { # remains permanently visible (no fading) osd configure $widget -fadeCurrent 1 -fadeTarget 1 } else { set target [expr {$last_change($icon) + $fade_delay}] ;# at this time we start fading out set remaining [expr {$target - $now}] ;# time remaining from now to target set cmd "osd configure $widget -fadeTarget 0" if {$remaining > 0} { # before target time, no fading yet (still fully visible) osd configure $widget -fadeCurrent 1 -fadeTarget 1 # schedule fade-out in the future set fade_id($icon) [after realtime $remaining $cmd] } else { # already after target, fade-out now eval $cmd } } } proc load_icons {{set_name "-show"} {position_param "default"}} { variable icon_list variable current_osd_leds_set variable current_osd_leds_pos variable current_fade_delay_active variable current_fade_delay_non_active if {$set_name eq "-show"} { # Show list of available skins set user_skins \ [glob -tails -types d -directory $::env(OPENMSX_USER_DATA)/skins *] set system_skins \ [glob -tails -types d -directory $::env(OPENMSX_SYSTEM_DATA)/skins *] return [lsort -unique [concat $user_skins $system_skins]] } # Check skin directory # All files belonging to this skin must come from this directory. # So we don't allow mixing individual files for one skin from the # system and from the user directory. Though fallback images are # again searched in both system and user dirs. set skin_set_dir [data_file "skins/$set_name"] if {![file isdirectory $skin_set_dir]} { error "No such icon skin: $set_name" } # Check position if {$position_param ni [list "top" "bottom" "left" "right" "default"]} { error "Invalid position: $position_param" } # Defaut icon positions set xbase 0 set ybase 0 set xwidth 50 set yheight 30 set xspacing 60 set yspacing 35 set horizontal 1 set fade_delay 5 set fade_duration 5 set scale 2 set position $position_param # but allow to override these values by the skin script set icons $icon_list ;# the 'none' skin needs this set script $skin_set_dir/skin.tcl if {[file isfile $script]} {source $script} set invscale [expr {1.0 / $scale}] set xbase [expr {$xbase * $invscale}] set ybase [expr {$ybase * $invscale}] set xwidth [expr {$xwidth * $invscale}] set yheight [expr {$yheight * $invscale}] set xspacing [expr {$xspacing * $invscale}] set yspacing [expr {$yspacing * $invscale}] # change according to parameter if {$position eq "default"} { # script didn't set a default, so we choose a "default default" set position "bottom" } if {$position eq "left"} { set horizontal 0 } elseif {$position eq "right"} { set horizontal 0 set xbase [expr {320 - $xwidth}] } elseif {$position eq "bottom"} { set ybase [expr {240 - $yheight}] } set vertical [expr {!$horizontal}] proc __try_dirs {skin_set_dir file fallback} { # don't touch already resolved pathnames if {[file normalize $file] eq $file} {return $file} # first look in specified skin-set directory set f1 [file normalize $skin_set_dir/$file] if {[file isfile $f1]} {return $f1} # look for the falback image in the skin directory set f2 [file normalize $skin_set_dir/$fallback] if {[file isfile $f2]} {return $f2} # if it's not there look in the root skin directory # (system or user directory) set f3 [file normalize [data_file "skins/$file"]] if {[file isfile $f3]} {return $f3} # still not found, look for the fallback image in system and # user root skin dir set f4 [file normalize [data_file "skins/$fallback"]] if {[file isfile $f4]} {return $f4} return "" } # Calculate default parameter values ... for {set i 0} {$i < [llength $icons]} {incr i} { set icon [lindex $icons $i] set xcoord($icon) [expr {$i * $xspacing * $horizontal}] set ycoord($icon) [expr {$i * $yspacing * $vertical}] set fade_delay_active($icon) $fade_delay set fade_delay_non_active($icon) $fade_delay set fade_duration_active($icon) $fade_duration set fade_duration_non_active($icon) $fade_duration switch -glob $icon { led_* { set base [string tolower [string range $icon 4 end]] set image_on "${base}-on.png" set image_off "${base}-off.png" set fallback_on "led-on.png" set fallback_off "led-off.png" } throttle { set image_on "" set image_off "${icon}.png" set fallback_on "" set fallback_off "" set current_fade_delay_non_active($icon) 0 } default { set image_on "${icon}.png" set image_off "" set fallback_on "" set fallback_off "" set fade_delay_active($icon) 0 } } set active_image($icon) [__try_dirs $skin_set_dir $image_on $fallback_on ] set non_active_image($icon) [__try_dirs $skin_set_dir $image_off $fallback_off] } # ... but allow to override these calculated values (again) by the skin script if {[file isfile $script]} {source $script} # Note: The actual width and height are irrelevant since this is only # an anchor to relatively position the icons to, but by checking # for a zero or non-zero value the orientation can be queried. osd configure osd_icons -x $xbase -y $ybase -w $horizontal -h $vertical foreach icon $icon_list { osd configure osd_icons.${icon}_on \ -x $xcoord($icon) \ -y $ycoord($icon) \ -fadePeriod $fade_duration_active($icon) \ -image [__try_dirs $skin_set_dir $active_image($icon) ""] \ -scale $invscale osd configure osd_icons.${icon}_off \ -x $xcoord($icon) \ -y $ycoord($icon) \ -fadePeriod $fade_duration_non_active($icon) \ -image [__try_dirs $skin_set_dir $non_active_image($icon) ""] \ -scale $invscale } # Also try to load "frame.png" osd destroy osd_frame set framefile "$skin_set_dir/frame.png" if {[file isfile $framefile]} { osd create rectangle osd_frame -z 0 -x 0 -y 0 -w 320 -h 240 \ -scaled true -image $framefile } # If successful, store in settings (order of assignments is important!) set current_osd_leds_set $set_name set ::osd_leds_set $set_name set current_osd_leds_pos $position_param set ::osd_leds_pos $position_param foreach icon $icon_list { set current_fade_delay_active($icon) $fade_delay_active($icon) set current_fade_delay_non_active($icon) $fade_delay_non_active($icon) } # Force redrawing of all icons set now [openmsx_info realtime] foreach icon $icon_list { redraw_osd_icons $icon $now } return "" } proc trace_osd_icon_vars {name1 name2 op} { variable current_osd_leds_set variable current_osd_leds_pos # avoid executing load_icons multiple times # (because of the assignments to the settings in that proc) if {($::osd_leds_set eq $current_osd_leds_set) && ($::osd_leds_pos eq $current_osd_leds_pos)} { return } load_icons $::osd_leds_set $::osd_leds_pos } proc machine_switch_osd_icons {} { variable icon_list set now [openmsx_info realtime] foreach icon $icon_list { trace remove variable ::$icon "write unset" [namespace code trace_icon_status] trace add variable ::$icon "write unset" [namespace code trace_icon_status] redraw_osd_icons $icon $now } after machine_switch [namespace code machine_switch_osd_icons] } # Available icons. Icons are also drawn in this order (by default) set icon_list [list "led_power" "led_caps" "led_kana" "led_pause" "led_turbo" "led_FDD" \ "pause" "throttle" "mute" "breaked"] # create OSD widgets osd create rectangle osd_icons -scaled true -alpha 0 -z 1 set now [openmsx_info realtime] foreach icon $icon_list { variable last_change osd create rectangle osd_icons.${icon}_on -fadeCurrent 0 -fadeTarget 0 -fadePeriod 5.0 osd create rectangle osd_icons.${icon}_off -fadeCurrent 0 -fadeTarget 0 -fadePeriod 5.0 trace add variable ::$icon "write unset" load_icons::trace_icon_status set last_change($icon) $now } namespace export load_icons } ;# namespace load_icons namespace import load_icons::* # Restore settings from previous session # default is set1, but if only scale_factor 1 is supported, use handheld if {[lindex [openmsx_info setting scale_factor] 2 1] == 1} { user_setting create string osd_leds_set "Name of the OSD icon set" "handheld" } else { user_setting create string osd_leds_set "Name of the OSD icon set" "set1" } user_setting create string osd_leds_pos "Position of the OSD icons" "default" set load_icons::current_osd_leds_set $osd_leds_set set load_icons::current_osd_leds_pos $osd_leds_pos trace add variable osd_leds_set write load_icons::trace_osd_icon_vars trace add variable osd_leds_pos write load_icons::trace_osd_icon_vars after machine_switch load_icons::machine_switch_osd_icons load_icons $osd_leds_set $osd_leds_pos openMSX-RELEASE_0_12_0/share/scripts/mode.tcl000066400000000000000000000073151257557151200206660ustar00rootroot00000000000000# TODO mode descriptions are not yet used in any way # TODO tab completion for the mode setting, so we can remove the hardcoded mentioning of the existing modes namespace eval mode { ## Mode switching infrastructure variable modes_info variable old_mode proc register {name enter_proc leave_proc description} { variable modes_info dict set modes_info $name [dict create enter_proc $enter_proc \ leave_proc $leave_proc \ description $description] } proc mode_changed {name1 name2 op} { variable modes_info variable old_mode set new_mode $::mode if {![dict exists $modes_info $new_mode]} { # New mode doesn't exist, restore variable and give an error set ::mode $old_mode error "No such mode: $new_mode. Possible values are: [dict keys $modes_info]" } # Leave old mode eval [dict get $modes_info $old_mode leave_proc] # Enter new mode eval [dict get $modes_info $new_mode enter_proc] set old_mode $new_mode } ## Define normal mode proc enter_normal_mode {} {} proc leave_normal_mode {} {} register "normal" [namespace code enter_normal_mode] \ [namespace code leave_normal_mode] \ "Mode that is most general purpose." ## Define tas mode variable old_inputdelay variable old_reversebar_fadeout_time proc enter_tas_mode {} { variable old_inputdelay variable old_reversebar_fadeout_time # in tas mode, set inputdelay to 0 set old_inputdelay $::inputdelay set ::inputdelay 0 # in tas mode, do not fadeout reverse bar set old_reversebar_fadeout_time $::reversebar_fadeout_time set ::reversebar_fadeout_time 0 if {![osd exists framecount]} { toggle_frame_counter } if {![osd exists lag_counter]} { toggle_lag_counter } if {![osd exists cursors]} { toggle_cursors } if {![osd exists movielength]} { toggle_movie_length_display } reverse_widgets::enable_reversebar false # load/select/save reverse slot # Note: this hides the MSX keys 'SELECT' and 'STOP' bind_default F6 tas::load_replay_slot bind_default F7 tas::open_select_slot_menu bind_default F8 tas::save_replay_slot # Set up frame advance/reverse bind_default END -repeat next_frame bind_default SCROLLOCK -repeat prev_frame } proc leave_tas_mode {} { variable old_inputdelay variable old_reversebar_fadeout_time # restore inputdelay set ::inputdelay $old_inputdelay # restore reversebar_fadeout_time set ::reversebar_fadeout_time $old_reversebar_fadeout_time if {[osd exists framecount]} { toggle_frame_counter } if {[osd exists lag_counter]} { toggle_lag_counter } if {[osd exists cursors]} { toggle_cursors } if {[osd exists movielength]} { toggle_movie_length_display } # Leave reverse enabled, including bar # Remove reverse slot stuff unbind_default F6 unbind_default F7 unbind_default F8 # Remove frame advance/reverse unbind_default END unbind_default SCROLLOCK } register "tas" [namespace code enter_tas_mode] \ [namespace code leave_tas_mode] \ "Mode for doing Tool Assisted Speedruns, with TAS widgets and easy ways to save replays." # Create setting (uses info from registered modes above) set help_text "This setting switches between different openMSX modes. A mode is a set of settings (mostly keybindings, but also OSD widgets that are activated) that are most suitable for a certain task. Currently these modes exist:" foreach name [lsort [dict keys $modes_info]] { append help_text "\n $name: [dict get $modes_info $name description]" } user_setting create string mode $help_text normal trace add variable ::mode write [namespace code mode_changed] ## Enter initial mode set old_mode $::mode after realtime 0 {eval [dict get $mode::modes_info $::mode enter_proc]} } openMSX-RELEASE_0_12_0/share/scripts/reverse.tcl000066400000000000000000000063061257557151200214140ustar00rootroot00000000000000namespace eval reverse { variable is_dingoo [string match *-dingux* $::tcl_platform(osVersion)] variable is_android [string match android "[openmsx_info platform]"] variable default_auto_enable_reverse if {$is_dingoo || $is_android} { set default_auto_enable_reverse "off" } else { set default_auto_enable_reverse "gui" } proc after_switch {} { # enabling reverse could fail if the active machine is an 'empty' # machine (e.g. because the last machine is removed or because # you explictly switch to an empty machine) catch { if {$::auto_enable_reverse eq "on"} { auto_enable } elseif {$::auto_enable_reverse eq "gui"} { reverse_widgets::enable_reversebar false } } after machine_switch [namespace code after_switch] } } ;# namespace reverse user_setting create string "auto_enable_reverse" \ {Controls whether the reverse feature is automatically enabled on startup. Internally the reverse feature takes regular snapshots of the MSX state, this has a (small) cost in memory and in performance. On small systems you don't want this cost, so we don't enable the reverse feature by default. Possible values for this setting: off Reverse not enabled on startup on Reverse enabled on startup gui Reverse + reverse_bar enabled (see 'help toggle_reversebar') } $reverse::default_auto_enable_reverse user_setting create float "reversebar_fadeout_time" \ {Time it takes for the reverse bar to fade out when it's not in focus. Set to 0 for no fade out at all. } 5.0 0.0 100.0 user_setting create boolean "auto_save_replay" \ {Enables automatically saving the current replay to filename specified \ in the setting "auto_save_replay_filename" with the interval specified \ in the setting "auto_save_replay_interval". The file will keep being overwritten until you disable the auto save again.\ } false user_setting create string "auto_save_replay_filename" \ {Filename of the replay file that will be saved to when auto_save_replay is \ enabled. } "auto_save" user_setting create float "auto_save_replay_interval" \ {If enabled, auto save the current replay every number of seconds that is \ specified with this setting. } 30.0 0.01 100000.0 # TODO hack: # The order in which the startup scripts are executed is not defined. But this # needs to run after the load_icons script has been executed. # # It also needs to run after 'after boot' proc in the autoplug.tcl script. The # reason is tricky: # - If this runs before autoplug, then the initial snapshot (initial snapshot # triggered by enabling reverse) does not contain the plugged cassette player # yet. # - Shortly after the autoplug proc runs, this will plug the cassetteplayer. # This plug command will be recorded in the event log. This is fine. # - The problem is that we don't serialize information for unplugged devices # ((only) in the initial snapshot, the cassetteplayer is not yet plugged). So # the information about the inserted tape is lost. # As a solution/hack, we trigger this script at emutime=0. This is after the # autoplug script (which triggers at 'after boot'. But also this is also # triggered when openmsx is started via a replay file from the command line # (in such case 'after boot' is skipped). after time 0 {reverse::after_switch} openMSX-RELEASE_0_12_0/share/scripts/screenshot.tcl000066400000000000000000000020701257557151200221100ustar00rootroot00000000000000namespace eval openmsx { rename ::screenshot old_screenshot proc screenshot {args} { set args2 [list] set sprites true foreach arg $args { if {$arg eq "-no-sprites"} { set sprites false } elseif {$arg eq "-guess-name"} { set base [utils::filename_clean [guess_title untitled]] if {$base ne ""} {lappend args2 -prefix "$base "} } else { lappend args2 $arg } } if {$sprites} { old_screenshot {*}$args2 } else { # disable sprites, wait for one complete frame and take screenshot set orig_disable_sprites $::disablesprites set ::disablesprites true after frame [namespace code [list screenshot_helper1 $orig_disable_sprites $args2]] } } proc screenshot_helper1 {orig_disable_sprites args2} { after frame [namespace code [list screenshot_helper2 $orig_disable_sprites $args2]] } proc screenshot_helper2 {orig_disable_sprites args2} { # take screenshot and restore 'disablesprites' setting old_screenshot {*}$args2 set ::disablesprites $orig_disable_sprites } namespace export screenshot }; # namespace namespace import openmsx::screenshot openMSX-RELEASE_0_12_0/share/scripts/session_management.tcl000066400000000000000000000125131257557151200236150ustar00rootroot00000000000000# this file implements machine session management namespace eval session_management { set_help_text save_session \ {save_session [] Saves the state of all your currently running machines as a session with the given name. Any existing session with that name will be silently overwritten. See also 'load_session', 'list_sessions' and 'delete_session'. } set_help_text load_session \ {load_session Restores the state of all machines of the session with the given name and reactivates the machine that was active at save time. Note: all machines already running will be silently destroyed! (Unless no single machine succeeded to be restored.) See also 'save_session', 'list_sessions' and 'delete_session'. } set_help_text list_sessions \ {list_sessions Returns the names of all previously saved sessions. See also 'save_session', 'load_session' and 'delete_session'. } set_help_text delete_session \ {delete_session Delete a previously saved session. See also 'save_session', 'load_session' and 'list_sessions'. } user_setting create boolean enable_session_management "Whether to save your session at exit and restore it again at start up." false proc tabcompletion {args} { return [list_sessions] } set_tabcompletion_proc load_session [namespace code tabcompletion] set_tabcompletion_proc save_session [namespace code tabcompletion] set_tabcompletion_proc delete_session [namespace code tabcompletion] proc delete_session {name} { set directory [file normalize $::env(OPENMSX_USER_DATA)/../sessions/${name}] # remove old session under this name file delete -force -- $directory } proc list_sessions {} { set directory [file normalize $::env(OPENMSX_USER_DATA)/../sessions] return [lsort [glob -tails -directory $directory -type d -nocomplain *]] } proc get_machine_representation {machine_id} { return "[utils::get_machine_display_name $machine_id] @ [utils::get_machine_time $machine_id]" } proc save_session {{name "untitled"}} { if {[llength [list_machines]] == 0} { return "Nothing to save..." } set result "" set directory [file normalize $::env(OPENMSX_USER_DATA)/../sessions/${name}] # remove old session under this name delete_session $name file mkdir $directory # save using ID as file names foreach machine [list_machines] { append result "Saving machine $machine ([get_machine_representation $machine])...\n" store_machine $machine [file join $directory ${machine}.oms] } # save in a separate file the currently active machine set fileId [open [file join $directory active_machine] "w"] puts $fileId [format "%s.oms" [machine]] close $fileId append result "Session saved as $name\n" return $result } proc load_session {name} { set result "" # get all savestate files set directory [file normalize $::env(OPENMSX_USER_DATA)/../sessions/${name}] set states_to_restore [glob -tails -directory $directory -nocomplain *.oms *.xml.gz] # abort if we have nothing to restore if {[llength $states_to_restore] == 0} { error "Nothing found to restore..." } # sort them in order of ID, to guarantee consistent order as saved set states_to_restore [lsort -dictionary $states_to_restore] # save the machines we start with set orginal_machines [list_machines] # try to load file with active machine set active_machine "" catch { set fp [open [file join $directory active_machine] "r"] set active_machine [read -nonewline $fp] close $fp } # restore all saved machines set first true foreach state $states_to_restore { set fullname [file join $directory $state] if {[catch { ;# skip machines failing to restore set newID [restore_machine $fullname] append result "Restored $state as $newID ([get_machine_representation $newID])...\n" # activate saved active machine or alternatively, first machine if {(($active_machine ne "") && ($state eq $active_machine)) || \ (($active_machine eq "") && $first)} { activate_machine $newID } set first false } error_result]} { append result "Skipping $state: $error_result\n" } } # if restoring at least one machine succeeded, delete all original machines if {[llength [list_machines]] > [llength $orginal_machines]} { foreach machine $orginal_machines { delete_machine $machine } } else { append result "Couldn't restore a single machine, aborting...\n" } # if the active machine failed to load, activate the first machine (if available): if {[activate_machine] eq "" && [llength [list_machines]] > 0} { activate_machine [lindex [list_machines] 0] } return $result } variable after_quit_id # do actual session management if {$::enable_session_management} { # need after realtime command here, because openMSX needs to have started up first after realtime 0 {load_session "default_session"} set after_quit_id [after quit {save_session "default_session"}] } proc setting_changed {name1 name2 op} { variable after_quit_id if {$::enable_session_management} {;# setting changed from disabled to enabled set after_quit_id [after quit {save_session "default_session"}] } else { ;# setting changed from enabled to disabled after cancel $after_quit_id } } trace add variable ::enable_session_management "write" [namespace code setting_changed] namespace export save_session namespace export load_session namespace export list_sessions namespace export delete_session } ;# namespace session_management namespace import session_management::* openMSX-RELEASE_0_12_0/share/scripts/shield.png000066400000000000000000000004271257557151200212110ustar00rootroot00000000000000PNG  IHDR TgPLTEބ"+"6! & KXtRNS0JtEXtSoftwareAdobe ImageReadyqe<IDAT(ϵ1@ ԌQP"P/PSLi5Ð˄+Hi>n:S|3OW0*漂.DiD߂1܂xjAނcbZm@5mȵRTݾ OIENDB`openMSX-RELEASE_0_12_0/share/scripts/tabbed_machine_view.tcl000066400000000000000000000062241257557151200236770ustar00rootroot00000000000000# This script visualizes with 'tabs' in the OSD the different running # machines in openMSX. # It only shows when there is more than one machine currently running. # Because of that, it shouldn't be necessary to turn it off. # # Feel free to tune/improve the color scheme :) # TODO: # - needs events to know when a machine got deleted or created... now only # updates when there was a switch... # (Luckily, that is very often... but not enough!) namespace eval tabbed_machine_view { variable curtab_bgcolor 0x4040D0C0 variable curtab_text_color 0xFFFFFF variable inactive_tab_bgcolor 0x202040A0 variable inactive_tab_text_color 0xA0A0A0 variable background_color 0x00000060 variable text_size 12 variable tab_margin 2 variable tab_main_spacing 3 ;# the width of the space between tab-row and main content variable total_height 20 variable top_spacing 2 proc update {} { variable curtab_bgcolor variable curtab_text_color variable inactive_tab_bgcolor variable inactive_tab_text_color variable background_color variable tab_margin variable tab_main_spacing variable total_height variable top_spacing variable text_size osd destroy tabbed_machine_view if {[llength [list_machines]] > 1} { # init osd create rectangle tabbed_machine_view \ -relw 1 \ -h $total_height \ -rgba $background_color \ -x 0 \ -y 0 # create new ones set rel_width [expr {1.0 / [llength [list_machines]]}] set tab_count 0 foreach machine [utils::get_ordered_machine_list] { if {$machine eq [activate_machine]} { set bg_color $curtab_bgcolor set text_color $curtab_text_color set display_text [utils::get_machine_display_name $machine] } else { set bg_color $inactive_tab_bgcolor set text_color $inactive_tab_text_color set display_text [format "%s (%s)" [utils::get_machine_display_name $machine] [utils::get_machine_time $machine]] } osd create rectangle tabbed_machine_view.${machine}_rect \ -relx [expr {$tab_count * $rel_width}] \ -x [expr { 2 * $tab_margin}] \ -relw $rel_width \ -w [expr {-2 * $tab_margin}] \ -y $top_spacing \ -h [expr {$total_height - $top_spacing + -$tab_main_spacing}] \ -rgba $bg_color \ -clip true osd create text tabbed_machine_view.${machine}_rect.text \ -text $display_text \ -size $text_size \ -rgb $text_color \ -x 1 incr tab_count } # create the bottom 'line' for the visual tab effect osd create rectangle tabbed_machine_view.bottom_rect \ -x 0 \ -relw 1 \ -rely 1 \ -y [expr {-$tab_main_spacing}] \ -h $tab_main_spacing \ -rgba $curtab_bgcolor } after machine_switch [namespace code update] } proc on_mouse_click {} { set x 2; set y 2 if {[osd exists tabbed_machine_view]} { catch {lassign [osd info "tabbed_machine_view" -mousecoord] x y} if {$x > 0 && 1 > $x && $y > 0 && 1 > $y} { set machine_index [expr int($x * [llength [list_machines]])] set machine [lindex [list_machines] $machine_index] activate_machine $machine } } after "mouse button1 up" [namespace code on_mouse_click] } after realtime 0.01 [namespace code update] after "mouse button1 up" [namespace code on_mouse_click] } ;# namespace tabbed_machine_view openMSX-RELEASE_0_12_0/share/settings.xml000066400000000000000000000003251257557151200201230ustar00rootroot00000000000000 openMSX-RELEASE_0_12_0/share/shaders/000077500000000000000000000000001257557151200171725ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/shaders/HQ2xLiteOffsets.dat000066400000000000000000001000001257557151200226050ustar00rootroot00000000000000``````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````p````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````p````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````p`````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````p```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````p```````````p````````````````````````````p`````p`````p`````p```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````p```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````p```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````@@```@@`````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````@@```@@``````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````@`````````````````````````@````````````````````````````````````````````````````````````@@````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````@`@``````````````````````@`@`````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````@@```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````@````@````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````@````@````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````@```````````````````````````@````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````@`@`````````````@`@```````````````````````@`@```````@`@`````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````p```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````p`````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````p````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````p``````````````p````````````````p``````````````p`````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````@````````````````````````````````````````````````````p``````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````p```````````p`````````````````````````````````````p```p````````p``p````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````p``````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````p```````````p```````````````````````p``p``p`````p``p``p```````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````@````````````````````````````````````````````````````````````````````p````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````@``@`````p`````````````p````````````````p``````````````p````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````openMSX-RELEASE_0_12_0/share/shaders/HQ2xOffsets.dat000066400000000000000000002000001257557151200217700ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/shaders/HQ2xWeights.dat000066400000000000000000001400001257557151200217740ustar00rootroot00000000000000@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@ @@@@@@@ @@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@ @@@@@@@@ @@@@@@@ @@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@``@@@@@@``@@@@@@``@@@ @@``@@@ @@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@``@@@@@@``@@@@@@``@@@ @@``@@@ @@@@@@``@@@@@@@@@@@@@ @@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@``@@@@@@@@@@@@@ @@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@ @@@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@ @@@@@ @@@@ @@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@``@@@@@@@``@@@@@@@``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@``@@@@@@@``@@@@@@@``@@@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@ @@@@@@@ @@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@ @@@@@@@@ @@@@@@@ @@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@``@@@@@@``@@@@@@``@@@ @@``@@@ @@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@``@@@@@@``@@@@@@``@@@ @@``@@@ @@@@@@``@@@@@@@@@@@@@ @@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@``@@@@@@@@@@@@@ @@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@ @@@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@ @@@@@ @@@@ @@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@``@@@@@@@``@@@@@@@``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@``@@@@@@@``@@@@@@@``@@@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@@@@@@@ @@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@``@@@@@@@@@@@@@ @@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@@@@@@@ @@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@``@@@@@@@@@@@@@ @@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@ @@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@ @@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @``@@ @``@@ @``@@ @``@@ @ @@@ @@@ @@@ @@ @``@@ @``@@ @``@@ @``@@ @ @@@ @@@ @@@ @@@@@@@@``@@@@@@@@@@@@@ @@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@``@@@@@@@@@@@@@ @@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@ @``@@@@ @``@@@@ @``@ @``@ @``@ @``@@@@@@@@@ @``@@@@ @``@@@@ @``@ @``@ @``@ @``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@ @@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@ @@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @``@@ @``@@ @``@@ @``@@ @ @@@ @@@ @@@ @@ @``@@ @``@@ @``@@ @``@@ @ @@@ @@@ @@@ @@@@@@@@``@@@@@@@@@@@@@ @@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@``@@@@@@@@@@@@@ @@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@ @``@@@@ @``@@@@ @``@ @``@ @``@ @``@@@@@@@@@ @``@@@@ @``@@@@ @``@ @``@ @``@ @``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@ @@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@ @@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@@@@@@@ @@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@``@@@@@@@@@@@@@ @@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@ @@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@ @@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@ ``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@@@@@@@ @@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@``@@@@@@@@@@@@@ @@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@ @@@@``@@ @@@``@@ @@@``@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@ @@@@@@@@@@@ @@@@@ @ @@@@@@ @@@@@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@``@@@@@@@@@@ @@@@@@ @@``@@@ @@``@@@ @@@@@@@@@@@@@@@@@@``@@@@@@``@@@@@@@@@@ @@@@@@ @@``@@@ @@``@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@ @@@@``@@ @@@``@@ @@@``@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@ @@@@@ @@@@``@@ @@@``@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@ @ @``@ @ @ @@@ @@@``@ @ @``@ @ @ @@@ @@@``@ @ @``@ @ @ @@@ @@@``@ @ @``@ @ @ @@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@ @@@@``@@ @@@``@@ @@@``@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@ @@@@``@@ @@@``@@ @@@``@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@ @@@@@ @@@@``@@ @@@``@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@ @@@@``@@ @@@``@@ @@@``@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@ @@@@@@@@@@@ @@@@@ @ @@@@@@ @@@@@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@``@@@@@@@@@@ @@@@@@ @@``@@@ @@``@@@ @@@@@@@@@@@@@@@@@@``@@@@@@``@@@@@@@@@@ @@@@@@ @@``@@@ @@``@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@ @@@@``@@ @@@``@@ @@@``@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@ @@@@@ @@@@``@@ @@@``@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@ @ @``@ @ @ @@@ @@@``@ @ @``@ @ @ @@@ @@@``@ @ @``@ @ @ @@@ @@@``@ @ @``@ @ @ @@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@ @@@@``@@ @@@``@@ @@@``@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@ @@@@``@@ @@@``@@ @@@``@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@ @@@@@ @@@@``@@ @@@``@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@``@@@@@@``@@@@@@``@@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@ @@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@ @@@@@@ @@@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@``@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@``@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@``@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@``@@@@@@@@@@@``@@@@@@@``@@@@@@``@@@@@@``@@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@ @@@@``@ @@@@@@@@``@ @``@ @``@ @``@ @@@@@``@ @@@@``@ @@@@@@@@``@ @``@ @``@ @``@ @@@@@@@@@``@@@@@@@``@@@@@@``@@@@@@``@@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@``@@@@@@``@@@@@@``@@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@``@@@@@@``@@@@@@``@@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@ @@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@ @@@@@@ @@@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@``@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@``@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@``@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@``@@@@@@@@@@@``@@@@@@@``@@@@@@``@@@@@@``@@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@ @@@@``@ @@@@@@@@``@ @``@ @``@ @``@ @@@@@``@ @@@@``@ @@@@@@@@``@ @``@ @``@ @``@ @@@@@@@@@``@@@@@@@``@@@@@@``@@@@@@``@@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@@@``@@@@@@``@@@@@@``@@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@@@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@ @@@@@@@@@ @@@@@ @@@@@@@@@@@@@@@ @@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@ @@@@@@ @@@@ @@ @@@@@@ @@@@ @@@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@@@@ @@@@@ @@@@@@``@@@@@``@@@@@@@ @@@@@ @@@@@@@@@@@@@@ @@@@@ @@@@@@``@@@@@``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@ @@@@@@@@@ @@@@@ @@@@@@@@@@@@@@@ @@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@ @ @ @ @ @ @ @ @ @ @@@ @@@@@ @@@ @ @ @ @ @ @ @ @ @ @@@ @@@@@@ @@@@ @@@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@ @@@@@@@@@ @@@@@ @@@@@@@@@@@@@@@ @@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@ @ @ @ @ @ @ @ @ @ @@@ @@@@@ @@@ @ @ @ @ @ @ @ @ @ @@@ @@@@@@@ @@@@ @@@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ @@@@ @ @@@@@@ @ @@@@ @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@ @@@@@@@@@ @@@@@ @@@@@@@@@@@@@@@ @@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@ @@@@@ @@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@ @@ @@@@@ @@@@@@@@@@@@@@@``@@@@@``@@@@@@@@@@ @@@@@ @@ @@@@@ @@@@@@@@@@@@@@@``@@@@@``@@@@@@@@@ @@@@ @@@@``@@@@@``@@@@@@``@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ @ @@@@@@@@@@ @ @ @@@@@@@@@@@@@ @@@@ @@@@``@@@@@``@@@@@@``@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@``@@@@@``@@@@@@@@@ @@@@ @@@@``@@@@@``@@@@@@``@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@``@@@@@``@@@@@@``@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@``@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@openMSX-RELEASE_0_12_0/share/shaders/HQ3xLiteOffsets.dat000066400000000000000000002200001257557151200226110ustar00rootroot00000000000000UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUuUUUuUUūUUUUUUūUUUūUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUeUUUeUUUUUUUUUUUUUUUUeUUUeUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUuUUUuUUUUUUUUUUūUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUeUUUeUUUUUUUUeUeUeUeUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUūUUU˫իUUUūUUU˫իUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUeUUUuUUUeUUUuUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUիUUUUUUիUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUuUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUuUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUūUUUUUUūUUUūUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUeUUUeUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUūUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUūUUU˫իUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUuUUUuUUUUUUUUUUUUUUUUUUeUUUuUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUūUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUuUUUuUUUUUUUUUUeUeUuUuUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUūUUU˫իUUUūUUU˫իUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUeUUUuUUUeUUUuUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUիUUUUUUիUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUuUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUuUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUūUUU˫իUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUeUUUuUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUūUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU;pUUUU;pUUUU+ 5UUU+ 5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU;pUUUU;pUUUU+ 5UUU+ 5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU;pUUUU;pUUUUkUUUU;pUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUKUUUUUUUUKUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU;UpUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUUUUUUUUUUUUUUUkUUUU;UpUUUUUUUUUUU;UpUUU+U`UUUUUUUUUUUUUUU;UpUUUUUUUUUUUkUUUU+U`UUUUUUUUUUUkUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUUU;pUUUUkUUUU+ 5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUUUUUUUUUUUUUUUUUUU+U U5UUUUUUUUUkUUUUkUUUUUUUUUUUUUUUU+U U5UUUUUUUUUkUUUUkUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU;pUUUU;pUUUUkUUUU;pUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUUU;pUUUUkUUUU+ 5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU+`UUUU+`UUUUkUUUUkUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUKUUUUKUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU+`UUUU+`UUUUkUUUUkUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUKUUUUKUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU;pUUUUUUUUkUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUKUUUUUUUUKUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU;UpUUUUUUUUUUUUUUU;UpUUUUUUUUUUUUUUU;UpUUUUUUUUUUUUUUU;UpUUU;UpUUUUUUUUUUU;UpUUU+U`UUUUUUUUUUU;UpUUU;UpUUUUUUUUUUU;UpUUU+U`UUUUUUUUUUU;UpUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUUU;pUUUUUUUUkUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU;UpUUUUUUUUUUUUUUUkUUUUUUUUUUUUUUUU+U U5UUUUUUUUUUUUUkUUUU+U U5UUUUUUUUU;UpUUUkUUUUUUUUUUUUkUUUU+U U5UUUUUUUUU+U U5UkUUUUUUUUUUUUkUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU;pUUUUUUUUkUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUUU;pUUUUUUUUkUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUU;pUUUkpūUUU;ūUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUeUUUeUUUUeUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUUUUUUUUUUUUUUkUUUUUUUUUUUUUUUkUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU;UUUkUUUUUUUUUUUkUUUkUUUUUUUUUUUkUUUkUUUUUUUUUUUkUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUU;pUUUUpūUUU;pUUUUUUUUUUUUUUUUuUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUeUUUeUUUUUUUeUUUUeeUUeeUUeUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUpUUUUUUUUUUUUUUUpUUUUUUUUUUUUUUUpUUUUUUUUUUUUUUUpUUUUUUUUUUUUUUUUUUUpUUkUpUUUUUUUUUUUpUUUpUUUUUUUUUUkUpUUUpUUUUUUUUUUU`UUUpUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUUU;pUUUUkUUUU;UUUUUUUUūUUUūUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUeUUUUUUeUUUeUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUUU;pUUUUUUUU;UUUUUUUUUūUUUuUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUeUUUeUUUUUUUUUeeeUeeeUUeUUeUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUUU;pUUUUkUUUU;UUUUUUUUūUUUūUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUeUUUeUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUUUUUUUUUUUUUUUkUUUUUUUUUUUUUUUUkUUUUUUUUUUUUUUUUUUUUUUUUkUUUU;UUUUkUUUUUUUUkUUUUkUUUUkUUUUUUUUkUUUUkUUUUkUUUUUUUUUUUUkUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUUU;pUUUUUUUU;UUUUUUUUUūUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUeUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUUUUUUUkUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUUUUUUUUUUUUUUUUUUUUUUUU;ūU;UU;pUU;pUUpūUUU;ūU;UUūUUUUUUUūUUUūUUUkUkUU;pU;pUUkpūUkUU;ūU;UUUUUUUUūUUUūUUUeUUUUUUUeUUUeUUUeUUUUUUUeUUUeUUUUUUUUUUUeUUUeUUUUUUUUUUUeUUUeUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUkUU;pUU;pUUkpūUkUU;U;UUUUUUUUUūUUUUUUkUkUU;pUU;pUUpūUUU;pU;UUUUUUUUUūUUUUUUUUUUUUUUeUUUUUUUUUUUUUUUeUUUUUUUUUUUUUUUUUUUeUUUUUUUUUUUUUUUeUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUU;pUUUUpūUUUkpūUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUeUUUeUUUUUUUUUUUUUUUUKUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU;UpUUUUUUUUUUUUUU;UpUUUUUUUUUUUUUUU;UpUUUUUUUUUUUUUUU;UpUUUUUUUUUUUUUUUUUUU;UpUUU;UpUUUUUUUUUU;UpUU;UpUUUUUUUUUUU;UpUUU;UpUUUUUUUUUUU+U`UUU;UpUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU;pUUUUpūUUUkUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUeUUUeUUUeUUUUUUUUeeUUeeUUUeUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU;UUUUUUUUUUUUUUUkUpUUUUUUUUUUUUUU;UpUUUUUUUUUUUUUUkUUUUUUUUUUUUUUUUUUU;UUU;UUUUUUUUUUUkUUUkUpUUUUUUUUUU;UUU;UpUUUUUUUUUUkUUUkUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUUU;pUUUUUUUUkpUUUUUUUUUūUUUūUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUeUUUeUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU;pUUUUUUUUkUUUUUUUUUUūUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUeUUUeUUUeUUUUUeeeUeeeUeUeUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUUUU;pUUUUUUUUkpUUUUUUUUUūUUUūUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUeUUUeKUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU;UpUUUUUUUUUUUUUUU;UpUUUUUUUUUUUUUUU;UpUUUUUUUUUUUUUUU;UpUUUUUUU;UpUUU;UpUUU;UpUUUUUUU;UpUUU;UpUUU;UpUUUUUUU;UpUUU;UpUUU;UpUUUUUUU;UpUUU+U`UUU;UpUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU;pUUUUUUUUkUUUUUUUUUUūUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU;UUUUUUUUUUUUUUUUkUpUUUUUUUUUUUUUUU;UUUUUUUUUUUUUUUUkUUUUUUUU;UUUU;UUUU;UUUUUUUUkUpUUUkUUUUkUpUUUUUUU;UUUU;UUUU;UUUUUUUUkUUUUkUUUUkUUUUUkUkUU;pU;pUUpūUUUkūUkUUUUUUUUūUUUūUUUkUkUU;pUU;pUUpūUUUkpūUkpUUUUUUUUUūUUUūUUUUUUUUUUUeUUUeUUUUUUUUUUUeUUUeUUUUUUUUUUUeUUUeUUUUUUUUUUUeUUUeUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUkUkUU+`UU+`UU`իUUUkUkUUUUUUUUUիUUUUUUUUUU;pUU;pUUpūUUUkUkUUUUUUUUUUūUUUUUUUUUUKUUKUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUuUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUuUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUopenMSX-RELEASE_0_12_0/share/shaders/HQ3xOffsets.dat000066400000000000000000004400001257557151200217770ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/shaders/HQ3xWeights.dat000066400000000000000000003300001257557151200217760ustar00rootroot00000000000000@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@ pp @@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@ pp @@@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@ @@ @@ @@ @@@@ @@@@@@ @@@@ @@ @@ @@ @@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @ pp @ pp @ pp @ pp @@@@ pp @@@@@@@@@@@@@@@@@@@@@@ pp @ pp @ pp @ pp @ pp @@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ @ @ @ @ @ @ @@@@@@@@@@@@@ @ @ @ @ @ @ @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @ pp @ pp @ pp @ pp @ pp @ pp @ pp @@@ pp @@@ pp @@@ pp @@@ pp @ pp @ pp @ pp @ pp @ pp @ pp @ pp @ pp @@@ pp @@@ pp @@@ pp @@@ pp @@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@ pp @@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@ pp @@@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@ @@ @@ @@ @@@@ @@@@@@ @@@@ @@ @@ @@ @@@@ @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @ pp @ pp @ pp @ pp @@@@ pp @@@@@@@@@@@@@@@@@@@@@@ pp @ pp @ pp @ pp @ pp @@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ @ @ @ @ @ @ @@@@@@@@@@@@@ @ @ @ @ @ @ @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @ pp @ pp @ pp @ pp @ pp @ pp @ pp @@@ pp @@@ pp @@@ pp @@@ pp @ pp @ pp @ pp @ pp @ pp @ pp @ pp @ pp @@@ pp @@@ pp @@@ pp @@@ pp @@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@ pp @@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@ pp @@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@ pp @@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@ pp @@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@ @@@@@@@@@@@@@@@@@@@ @@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@ @@@@@@@@@@@@@@@@@@@ @@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@@ @@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@@pp @@@@@@pp @@@@@@@@@@@@@@@@@@@@pp @@@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@@@ @@@ @ @@ @@@@ @@@ @ @@ @@@@@@@@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @pp @pp @pp @@@@@@@@@pp @@@@pp @@@@@@@@@@@@@@pp @pp @pp @pp @@@@@@@@@pp @@@@pp @@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@@@@@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@@pp @@@@@@pp @@@@@@@@@@@@@@@@@@@@pp @@@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@@@ @@@ @ @@ @@@@ @@@ @ @@ @@@@@@@@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @pp @pp @pp @@@@@@@@@pp @@@@pp @@@@@@@@@@@@@@pp @pp @pp @pp @@@@@@@@@pp @@@@pp @@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@@@@@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ @ @ @@@@@@@ @ @ @ @@@@@@@ @ @ @ @@@@@@@ @ @ @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @pp @pp @pp @@@pp @@@pp @pp @pp @pp @pp @@@pp @@@pp @pp @pp @pp @pp @@@pp @@@pp @pp @pp @pp @pp @@@pp @@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@ @@@@@@@@@@@@@@@@@@@ @@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ @ @ @@@@@@@ @ @ @ @@@@@@@ @ @ @ @@@@@@@ @ @ @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @pp @pp @pp @@@pp @@@pp @pp @pp @pp @pp @@@pp @@@pp @pp @pp @pp @pp @@@pp @@@pp @pp @pp @pp @pp @@@pp @@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@ @@@@@@@@@@@@@@@@@@@ @@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp pp @@@pp @@@pp @@@@pp @@@@@ pp @@@@@@@pp pp @@@pp @@@pp @@@@@@@@@@@@@@@ pp @@@@@@@pp @@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@@ pp @@@@@@@@pp pp @@@pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@pp @@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp pp @@@@pp pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp pp @@@@pp pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@@ pp @@@@@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@ pp @@@@@@@@pp @@@@@@pp @@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@ @@@@@@ @ @ @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@ pp @ pp @@ pp @ pp @ pp @ pp @ pp @@ pp @ pp @@ pp @ pp @@@ pp @@@ pp @ pp @@ pp @ pp @@ pp @ pp @ pp @ pp @ pp @@ pp @ pp @@ pp @ pp @@@ pp @@@ pp @@@pp pp @@@pp @@@pp @@@@pp @@@@@ pp @@@@@@@pp pp @@@pp @@@pp @@@@@@@@@@@@@@@ pp @@@@@@@pp @@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@@ pp @@@@@@@@pp pp @@@pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@pp @@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@@ pp @@@@@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@ pp @@@@@@@@pp @@@@@@pp @@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@ @@@@@@ @ @ @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @ pp @ pp @ pp @ pp @ pp @ pp @ pp @ pp @ pp @ pp @ pp @@@ pp @@@ pp @ pp @ pp @ pp @ pp @ pp @ pp @ pp @ pp @ pp @ pp @ pp @ pp @@@ pp @@@ pp @@@pp pp @@@pp @@@pp @@@@pp @@@@@ pp @@@@@@@pp pp @@@pp @@@pp @@@@@@@@@@@@@@@ pp @@@@@@@pp @@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@@ pp @@@@@@@@pp pp @@@pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@pp @@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@@ pp @@@@@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@ pp @@@@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp pp @@@pp @@@pp @@@@pp @@@@@ pp @@@@@@@pp pp @@@pp @@@pp @@@@@@@@@@@@@@@ pp @@@@@@@pp @@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@@ pp @@@@@@@@pp pp @@@pp @@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@pp @@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@@ pp @@@@@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@ pp @@@@@@@pp @@@@@pp @@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@ @@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@ pp @@@@@@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@pp @@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@ pp @@@@@@@@@@ pp @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@pp @@@@ @ @ @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @pp @@pp @pp @@pp @@pp @pp @@pp @pp @pp @pp @pp @@@pp @@@pp @pp @pp @@pp @pp @@pp @@pp @pp @@pp @pp @pp @pp @pp @@@pp @@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp pp pp pp pp pp pp pp @@pp @@ pp @@pp @@ pp pp @@pp @@pp @@pp @@@@@@@@@@@@@@@@@@pp pp pp pp pp pp pp pp @@pp @@ pp @@pp @@ pp pp @@pp @@pp @@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@ pp @@@@@@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@pp @@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@ pp @@@@@@@@@ pp @@@@@ @@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@pp @@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp pp pp pp pp pp pp pp @@ @@@@ @@@@@@@@@@pp pp pp pp pp pp pp pp @@ @@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@ pp @@@@@@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@pp @@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@ pp @@@@@@@@@@ pp @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@pp @@@@ @ @ @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @pp @pp @pp @pp @pp @pp @pp @pp @pp @pp @pp @@@pp @@@pp @pp @pp @pp @pp @pp @pp @pp @pp @pp @pp @pp @pp @@@pp @@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp pp pp pp @@@@ @@@@ pp pp pp pp @@@@@@@@pp pp pp pp @@@@ @@@@ pp pp pp pp @@@@@@@@@@@@@@@@@@@@@@@@pp @@@@@pp @@@@@ pp @@@@@@@@@pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@pp @@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@ pp @@@@@@@@@ pp @@@@@ @@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@pp @@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@pp @@@@pp @@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ pp @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@openMSX-RELEASE_0_12_0/share/shaders/HQ4xLiteOffsets.dat000066400000000000000000004000001257557151200226120ustar00rootroot00000000000000pPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPpppPpPаpPpPpPpPpPpPаpPpPpPаpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPppPpPppPpPpPpppPpPpPppPpPpPppPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppppppPpppppPpppppPppppppppppPpppppPpppppPpppppppppPpppppPpppppPpppppppppPpppppPppppppppppPppppppppppPpppppppppppPpppppppppppPpppppPpppppPpppppPpppppPpppppppppppPpppppppPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPPPPPpPPPPPPPPPpPPPPPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPpppPpPpPpPpPpPpPpPpPpPаpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPppPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppppppPpppppPpppppPppppppppppPpppppPpppppPpppppppppPpppppPpppppPpppppppppPpppppPppppppppppPppppppppppPppppppppppPppppppppppPppppppppppPppppppppppPppppppppppPppppppPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPPPPPpPPPPPPPPPpPPPPPPPPPpPPPPPPPPPpPPPPPPPPPpPPPPPPPPPpPPPPPPPPPpPPPPPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPаpPpPpPааpPpPpPаpPpPpPааpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPааpPpPpPpPpPpPааpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPАpPpPpPpppPpPpPАpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPpPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPpPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPаpPpPpPpPpPpPаpPpPpPаpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpppPpPpPppPpPpPppPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPаpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPаpPpPpPааpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPpppPpPpPppPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppppppPpppppPpppppPpppppppppPpppppPpppppPpppppppppppPpppppPpppppPpppppppppppPpppppPpppppppppppPpppppppppppPpppppppppppPpppppppppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPPPPPpPPPPPPPPPpPPPPPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPаpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPpPpPpPpPppPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppppppPpppppPpppppPpppppppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppppppPppppppppppPpppppppppPpppppppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPpPPPPpPPPPPpPPPPPpPPPPPpPPPPpPPPPPpPPPPPPPPPpPPPPPPPPPpPPPPPPPPPpPPPPPPPPPpPPPPPpPPPPpPPPPPpPPPPpPPPPPpPPPPpPPPPPpPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPаpPpPpPааpPpPpPаpPpPpPааpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPааpPpPpPpPpPpPааpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPАpPpPpPpppPpPpPАpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPpPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPpPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPаpPpPpPааpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPpppPpPpPppPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPаpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPpPpPpPpPppPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpP0PpPpPpPpP0PpPpPpPpP00pPpPpP00pPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPppPpPpPpPPppPpPpPpPPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpP0PpPpPpPpP0PpPpPpPpP00pPpPpP00pPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPppPpPpPpPPppPpPpPpPPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpP0PpPpPpPpP0PpPpPpPpPpppPpPpPpP0PpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPppPpPpPpPpPpPpPpPppPpPpPpPpPPpPpPpPpPpPpPpPpPPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppPpppppPpppppPppppppPp0pppppPpppppPpppppPpppppPppppppPpppppPpppppPpppppPp0pppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPP0PPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPPPPPpPPPPPpPPPPPpPPPpPpPpPPP0PPPpPPPPPpPPPPPpPPP0PPPpPPP0P0PpPPPPPpPPPPPpPPPPPpPPP0PPPpPPPPPpPPPPPpPPPpPpPpPPP0P0PpPPPPPpPPPPPpPPPpPpPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPpP0PpPpPpPpPpppPpPpPpP00pPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPPPPPpPPPPPpPPPPPpPPPPPpPPP0PPP0PPPpPPPPPpPPPpPpPpPPPpPpPpPPPPPpPPPPPpPPPPPpPPP0PPP0PPPpPPPPPpPPPpPpPpPPPpPpPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpP0PpPpPpPpP0PpPpPpPpPpppPpPpPpP0PpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPppPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPpP0PpPpPpPpPpppPpPpPpP00pPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpP00pPpPpPpP00pPpPpPpPpppPpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpP0ppPpPpPpP0ppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpPppppPpppppPpppppPpppppPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpP00pPpPpPpP00pPpPpPpPpppPpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpP0ppPpPpPpP0ppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpPppppPpppppPpppppPpppppPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpP0PpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPPpPpPpPpPpPpPpPpPPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppPpppppPpppppPpppppPppppppPpppppPpppppPpppppPppppppPpppppPpppppPpppppPppppppPppppppPpppppPpppppPppppppPp0pppppPpppppPpppppPppppppPppppppPpppppPpppppPppppppPp0pppppPpppppPpppppPppppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPP0PPPpPPPPPpPPPPPpPPPPPpPPP0PPPpPPPPPpPPPPPpPPPPPpPPP0PPPpPPPPPpPPPPPpPPPPPpPPP0PPPpPPP0PPPpPPPPPpPPPPPpPPP0PPPpPPP0P0PpPPPPPpPPPPPpPPP0PPPpPPP0PPPpPPPPPpPPPPPpPPP0PPPpPPP0P0PpPPPPPpPPPPPpPPP0PPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPpP0PpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPpppppPpppppPpppppPpppppPpppppPpPpppppPpppppPpppppPppppppPpppppPpppppPpppppPpppppPpPpppppPpppppPpppppPpPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPP0PPPpPPPPPpPPPPPpPPPPPpPPPpPPpPPPPPpPPPPPpPPPPPpPPP0PPP0PPPpPPPPPpPPPPPpPPPpPPpPPP0PPP0PPPpPPPPPpPPP0PPPpPPPpPPpPPPPPpPPPPPpPPPpPPpPPP0PPP0PPPpPPPPPpPPP0PPP0PpPPpPPPPPpPPPPPpPPPpPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpP0PpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPpP0PpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpP0PpPpPpPppаpPpPpP0PаpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPppppPpPpPppPpPpPpppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppppppppPpppppPpppppPpppppppppppPpppppPpppppPpppppppppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppppppppppppppPpppppPpppppppppppppppppPpppppPpppppppppppppppppPpppppPpppppppppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPPPPPpPPPPPpPPPPPpPPPpPpPPPPPpPPPPPpPPPPPpPPPpPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPP0PPPPPpPpPPPPPpPPPPPpPPPpPpPPPpPpPPPPPpPPPPPpPPPpPpPPPpPpPPPPPpPPPPPpPPPpPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpP0PpPpPpPpPаpPpPpP0PppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPppPpPpPpPppPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppppppPpppppPpppppPppppppppppPpppppPpppppPppppppppppPpppppPpppppPppppppppppPpppppPpppppPpppppPpppppppppppppppPpppppPpppppppppppppppPpppppPpppppppppppppppPpppppPppppppppppPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPpPPPPPPPpPpPPPPPpPPPPPpPPPPPPPPPPPPPpPPPPPpPPPpPpPPPPPPPPPpPPPPPpPPPPPPPPPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPpP0PpPpPpPpPpppPpPpPpP0PpPpPpPpPpPpPpPpPаpPpPpPаpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPppPpPpppPpPpPpppPpPpPppPpPpPppPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppppppppPpppppPpppppPpppppppppppPpppppPpppppPpppppppppppPpppppPpppppPpppppPpppppPppppppppppppppppppppppPpppppppppppppppppppppppPpppppppppppppppppppppppPpppppPpppppppppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPPPPPPPPPPPPPpPPPPPPPPPPPPPPPPPpPPPPPPPPPPPPPPPPPpPPPPPpPPPPPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPpP0PpPpPpPpPpPpPpPpP0PpPpPpPpPpPpPpPpPpPаpPpPpPppppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPppPpPpppPpPpPpPpPpPpPppPpPpPpPppPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppppppPpppppPpppppPppppppppppPpppppPpppppPppppppppppPpppppPpppppPppppppppppPppppppppppppppppppppPppppppppppppppppppppPppppppppppppppppppppPpppppppppppppppPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPPPPPPPPPPPPPpPPPPPPPPPPPPPPPPPpPPPPPPPPPPPPPPPPPpPPPPPPPPPPPPPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPpP0PpPpPpPpPpppPpPpPpP0PpPpPpPpPpPpPpPpPаpPpPpPаpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPppPpPpppPpPpPpppPpPpPppPpPpPppPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPPPPPpPPPPPpPPPPPpPPPpPpPpPPPPPpPPPPPpPPPPPpPPPpPpPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPPP0PPPpPPPpPpPpPPPPPpPPPpPpPpPPPpPpPpPPPpPpPpPPPPPpPPPpPpPpPPPpPpPpPPPpPpPpPPPPPpPPPPPpPPPpPpPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPpP0PpPpPpPpPpPpPpPpP0PpPpPpPpPpPpPpPpPpPаpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPppPpPpppPpPpPpPpPpPpPppPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPPPPPpPPPpPpPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpP0PаpP0PpPpP0PpPpP0PpPpPаpPpPpP0PаpP0PpPpPаpPpPpPpPpPpPpPаpPpPpPаpPpPpPpppPpppPpP0PpP0PpPpPppаpPpppPpP0PаpP0PpPpPpPpPpPpPpPpPаpPpPpPаpPpPpPpppPppPpPppPpPppPpPppPpPpPpppPppPpPppPpPpPpPpPpPpPppPpPpPppPpPpPpppPpPpPppppPppPpPppPpPpPpppPppPpPpppPpPpPpppPpPpPppPpPpPppPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpppPpppPpP0PpPpP0PpPpPppаpPpppPpP0PppP0PpPpPpPpPpPpPpPpPpPаpPpPpPppPpPpPpppPpppPpP0PpPpP0PpPpPаpPpPpP0PppP0PpPpPpPpPpPpPpPpPpPаpPpPpPppPpPpPpppPpPpPppPpPppPpPppPpPpPppPpPppPpPpppPpPpPpPpPpPpPppPpPpPpPpPpPpPpppPpPpPppPpPppPpPppPpPpPppPpPppPpPpppPpPpPpPpPpPpPppPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpP0PpPpPpPpPаpPpPpPpаpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPppPpPpPpPppPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppppppppPpppppPpppppPppppppPpppppPpppppPpppppPppppppPpppppPpppppPpppppPppppppPpppppPpppppPpppppPpppppPppppppPppppppppppppPpppppPpppppppppppppPpppppPpppppPppppppPppppppPpppppPpppppPp0pppppPppppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPP0PPPPPPPpPPPPPpPPPPPpPPP0PPPpPPPPPpPPPPPpPPPPPpPPP0PPPpPPPPPpPPPPPpPPPPPpPPP0PPPpPPPPPpPPPPPpPPPPPpPPPPPpPPP0PPPpPPP0PPPPPPPpPPPPPpPPP0PPPPP0PPPpPPPPPpPPPPPpPPP0PPPpPPP0PPPpPPPPPpPPPPPpPPP0P0PpPPP0PPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpP0PpPpPpPpPаpPpPpPpppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppppppppPpppppPpppppPppppppppppPpppppPpppppPppppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppppppppppppppPpppppPpppppppppppppppPpppppPppppppPppppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPP0PPPPPPPpPPPPPpPPPPPpPPPpPPPPPPpPPPPPpPPPPPpPPP0PPPpPPPPpPPPPPpPPPPPpPPPpPPpPPPPpPPPPPpPPPPPpPPPPPpPPP0PPPPP0PPPPPPPpPPPPPpPPPpPPPPpPPPPPPpPPPPPpPPP0PPPpPP0PPPpPPPPpPPPPPpPPPpPPpPPpPPpPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPpP0PpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPpPаpPpPpPаpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPpPpPpppPpPpPpPpPpPpPppPpPpPppPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppppppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppppppppPpppppppppppPpppppPpppppppppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpP0PpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPpPpPаpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppppppPpppppPpppppPppppppppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppppppppppppppppPppppppppppppppppppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPPPPPpPPPPPpPPPPPpPPPPPpPPPPpPPPPPpPPPPPpPPPPPpPPPPpPPPPPPPPPPPPPPPPPpPPPPPPPPPPPPPPPPPpPPPPPpPPPPpPPPPpPPPPpPPPPPpPPPPpPPPPpPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpppPpPpPpP0PpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPpPаpPpPpPаpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPpPpPpppPpPpPpPpPpPpPppPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppPpppppPpppppPpppppPppppppPpppppPpppppPpppppPppppppPpppppPpppppPpppppPppppppPpppppPppppppPppppppPppppppPpppppPppppppPppppppPppppppPpppppPppppppPppppppPppppppPpppppPppppppPp0pppppPppppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPP0PPPpPPPPPpPPPPPpPPPPPpPPP0PPPpPPPPPpPPPPPpPPPPPpPPP0PPPpPPPPPpPPPPPpPPPPPpPPP0PPPpPPPPPpPPP0PPPpPPP0PPPpPPP0PPPpPPPPPpPPP0PPPpPPP0PPPpPPP0PPPpPPPPPpPPP0PPPpPPP0PPPpPPP0PPPpPPPPPpPPP0PPPpPPP0P0PpPPP0PPPpPPPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpP0PpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPpPpPаpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppPpppppPpppppPpppppPpppppPpppppPppppppPppppppPppppppPpppppPpppppPpppppPpppppPpppppPppppppPppppppPppppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPP0PPPpPPPPPpPPPPPpPPPPPpPPPpPPpPPPPPpPPPPPpPPPPPpPPP0PPPpPPPPPpPPPPPpPPPPPpPPPpPPpPPPPPpPPP0PPPpPPP0PPPpPPP0PPPpPPPPPpPPPpPPpPPPpPPpPPPpPPpPPPPPpPPP0PPPpPPP0PPPpPPP0PPPpPPPPPpPPPpPPpPPPpPPpPPPpPPpPPPpPpppPpppPpP0PpP0PpPpPаpPpPpPpаpPppPpPpPpPpPpPpPpPаpPpPpPаpPpPpPpppPpppPpP0PpPpP0PpPpPаpPpPpPpаpPppPpPpPpPpPpPpPpPpPаpPpPpPаpPpPpPpppPpPpPppppPppPpPppPpPpPppPpPpPpppPpPpPpppPpPpPppPpPpPppPpPpPpppPpPpPppPpPppPpPppPpPpPppPpPpPpppPpPpPpPpPpPpPppPpPpPppPpPppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPpPpppPpppPpP00pPpP00pPpPааpPpPpPpppPppPpPpPpPpPpPpPpPpPааpPpPpPppPpPpPpPpPpPpP0PpPpP0PpPpPаpPpPpPpppPppPpPpPpPpPpPpPpPpPpPаpPpPpPppPpPpPpppPpPpP0ppPpP0ppPpPАpPpPpPpPpPpPpPpppPpPpPpPpPpPpPАpPpPpPpPpPpPpPpPpPpPpPppPpPppPpPppPpPpPpPpPpPpPpPpPpPpPpPpPpPpPppPpPpPpPpPpPppppPpppppPpppppPpppppPpppppPpPppppPpppppPpPppppPpppppPppppppppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPppppppppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpppppPpPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPpPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPpPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPPPpPPPopenMSX-RELEASE_0_12_0/share/shaders/HQ4xOffsets.dat000066400000000000000000010000001257557151200217710ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/shaders/HQ4xWeights.dat000066400000000000000000006000001257557151200217770ustar00rootroot00000000000000@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@`@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@`@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@`@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@`@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@`@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@`@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@`@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@`@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@`@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@`@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@`@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@`@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ @@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ @@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ @@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ ` @ @@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` `@@@ @ @@`@@`@@@ @ @@`@@`@@@ @ @@` @`@@@ @ @@` @`@@@ @ @@`@ `@@@ @ @@`@ `@@@ @ @@` `@@@ @ @@` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ ` @ @ @ ` @ @` ` @ @` `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@@@ @ @@@` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `@@@ @ ``@@@@ @ ``@@`@@@ @ `@@`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@ @ ``@@`@@@ @ `@@`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@ @ ``@@@@ @ `` @@@@@@ @ `` @`@@@ `@`@@@@ `@`@@@@ `@` @@@ `@` @@@ @ ``@@@@ @ ``@@@@ @ `` @@@@@@ @ `` @`@@@ `@`@@@@ `@`@@@@ `@` @@@ `@` @ @ @ @ @ @ @ @ @` @@ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @ @` @@ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ @ @ @` @ @` @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ @ @ @` @ @` @ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @ @ @ @ @ @@ @ @@ `@ @@ `@ @@ `@ @@ `@ @@ @ @ @ @ @ @@ @ @@ `@ @@ `@ @@ `@ @@ `@ @@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ ``@`@`@`@`@`@@@@`@`@@@@`@@@`@@``@@@`@@``@@@`@@@@`@@@`@@@@`@`@`@`@`@`@@@@`@`@@@@`@@@`@@``@@@`@@``@@@`@@@@`@@@`@@@@@@@ @ ``@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@`@@@ @ ``@@`@@@ @ `` @`@@@ @ `` @`@@@ `@`@@@@ `@`@@@@ `@` @@@@@ `@` @@@ @ ``@@`@@@ @ ``@@`@@@ @ `` @`@@@ @ `` @`@@@ `@`@@@@ `@`@@@@ `@` @@@@@ `@` @ @ @ ` @ @ @ @ @ @` @ @ @` @ @ @ ` @ @ @ ` @ ` @ @ @ ` @ @ @ @ @ @` @ @ @` @ @ @ ` @ @ @ ` @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ @ @ @` @ @` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ @ @ @` @ @` @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ `@ @ `@ @ `@ @ `@ @ @ @ @ @ @ @ @ @ `@ @ `@ @ `@ @ `@ @@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @```@`@`@`@`@`@`@`@`@@@`@`@@@`@`@@@`@`@@@`@`@`@`@`@`@`@`@`@`@@@`@`@@@`@`@@@`@`@@@`@@@@ @ ``@@@@ @ ``@@`@@@ @ `@@`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@ @ ``@@`@@@ @ `@@`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@ @ ``@@@@ @ `` @@@@@@ @ `` @`@@@ `@`@@@@ `@`@@@@ `@` @@@ `@` @@@ @ ``@@@@ @ ``@@@@ @ `` @@@@@@ @ `` @`@@@ `@`@@@@ `@`@@@@ `@` @@@ `@` @ @ @ @ @ @ @ @ @` @@ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @ @` @@ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ @ @ @` @ @` @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ @ @ @` @ @` @ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @ @ @ @ @ @@ @ @@ `@ @@ `@ @@ `@ @@ `@ @@ @ @ @ @ @ @@ @ @@ `@ @@ `@ @@ `@ @@ `@ @@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ ``@`@`@`@`@`@@@@`@`@@@@`@@@`@@``@@@`@@``@@@`@@@@`@@@`@@@@`@`@`@`@`@`@@@@`@`@@@@`@@@`@@``@@@`@@``@@@`@@@@`@@@`@@@@@@@ @ ``@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@`@@@ @ ``@@`@@@ @ `` @`@@@ @ `` @`@@@ `@`@@@@ `@`@@@@ `@` @@@@@ `@` @@@ @ ``@@`@@@ @ ``@@`@@@ @ `` @`@@@ @ `` @`@@@ `@`@@@@ `@`@@@@ `@` @@@@@ `@` @ @ @ ` @ @ @ @ @ @` @ @ @` @ @ @ ` @ @ @ ` @ ` @ @ @ ` @ @ @ @ @ @` @ @ @` @ @ @ ` @ @ @ ` @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ @ @ @` @ @` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ @ @ @` @ @` @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ `@ @ `@ @ `@ @ `@ @ @ @ @ @ @ @ @ @ `@ @ `@ @ `@ @ `@ @@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @```@`@`@`@`@`@`@`@`@@@`@`@@@`@`@@@`@`@@@`@`@`@`@`@`@`@`@`@`@@@`@`@@@`@`@@@`@`@@@`@@@@ @ ``@@@@ @ ``@@`@@@ @ `@@`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@ @ ``@@`@@@ @ `@@`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@ @ ``@@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``` @@@ ``` @@@ @ ``@@@@ @ ``@@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``` @@@ ``` @ @ @ @ @ @ @ @ @` @@ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @ @` @@ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @ ` @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @ ` @ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @ @@ @ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @ @@ @ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ ``@@``@`@@``@`@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@`@@``@`@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@`@@@ @ ``@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@`@@@ @ ``@@`@@@ @ `` @`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@`@@@ @ ``@@`@@@ @ `` @`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``` @@@@@ ``` @ @ @ ` @ @ @ @ @ @` @ @ @` @ @ @ ` @ @ @ ` @ ` @ @ @ ` @ @ @ @ @ @` @ @ @` @ @ @ ` @ @ @ ` @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @ ` @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @```@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@@@@ @ ``@@@@ @ ``@@`@@@ @ `@@`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@ @ ``@@`@@@ @ `@@`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@ @ ``@@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``` @@@ ``` @@@ @ ``@@@@ @ ``@@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``` @@@ ``` @ @ @ @ @ @ @ @ @` @@ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @ @` @@ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @ ` @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @ ` @ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @ @@ @ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @ @@ @ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ ``@@``@`@@``@`@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@`@@``@`@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@`@@@ @ ``@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@`@@@ @ ``@@`@@@ @ `` @`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@`@@@ @ ``@@`@@@ @ `` @`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``` @@@@@ ``` @ @ @ ` @ @ @ @ @ @` @ @ @` @ @ @ ` @ @ @ ` @ ` @ @ @ ` @ @ @ @ @ @` @ @ @` @ @ @ ` @ @ @ ` @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @ ` @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @```@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@@@@ @ ``@@@@ @ ``@@`@@@ @ `@@`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@ @ ``@@`@@@ @ `@@`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @ @ @ @ @ @ @ @ @` @@ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @ @` @@ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @@ @ ` @ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @` @@` @` @@` @` @@` @` @@` @` @` @` @` @` @` @` @` @` @@` @` @@` @` @@` @` @@` @` @` @` @` @` @` @` @` @@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@`` @@@@@`` @@@@@`` @@@@@`` @@@` @@@` @`` @@@` @`` @@@` @`` @@@` @`@@`` @@@@@`` @@@@@`` @@@@@`` @@@` @@@` @`` @@@` @`` @@@` @`` @@@` @`@@@ @ ``@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@@@@@@ @ ``@@`@@@ @ `` @`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@@@@@@ @ ``@@`@@@ @ `` @`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``` @@@@@ ``` @ @ @ ` @ @ @ @ @ @` @ @ @` @ @ @ ` @ @ @ ` @ ` @ @ @ ` @ @ @ @ @ @` @ @ @` @ @ @ ` @ @ @ ` @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @ ` @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ ` @@` ` @@` ` @@` @@` @@` @@` ` ` ` ` ` ` ` ` @@` ` @@` ` @@` @@` @@` @@` ` ` ` ` ` ` ` @@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@`` @@`` @@`@@`@@`@@`` @@` @@` @@` @@` @@` @@` @@` @@@@`` @@`` @@`@@`@@`@@`` @@` @@` @@` @@` @@` @@` @@` @@@@@ @ ``@@@@ @ ``@@`@@@ @ `@@`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@ @ ``@@`@@@ @ `@@`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @ @ @ @ @ @ @ @ @` @@ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @ @` @@ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @@ @ ` @ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @` @@` @` @@` @` @@` @` @@` @` @` @` @` @` @` @` @` @` @@` @` @@` @` @@` @` @@` @` @` @` @` @` @` @` @` @@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@`` @@@@@`` @@@@@`` @@@@@`` @@@` @@@` @`` @@@` @`` @@@` @`` @@@` @`@@`` @@@@@`` @@@@@`` @@@@@`` @@@` @@@` @`` @@@` @`` @@@` @`` @@@` @`@@@ @ ``@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@@@@@@ @ ``@@`@@@ @ `` @`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@@@@@@ @ ``@@`@@@ @ `` @`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``` @@@@@ ``` @ @ @ ` @ @ @ @ @ @` @ @ @` @ @ @ ` @ @ @ ` @ ` @ @ @ ` @ @ @ @ @ @` @ @ @` @ @ @ ` @ @ @ ` @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @ ` @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ ` @@` ` @@` ` @@` @@` @@` @@` ` ` ` ` ` ` ` ` @@` ` @@` ` @@` @@` @@` @@` ` ` ` ` ` ` ` @@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@`` @@`` @@`@@`@@`@@`` @@` @@` @@` @@` @@` @@` @@` @@@@`` @@`` @@`@@`@@`@@`` @@` @@` @@` @@` @@` @@` @@` @@@@@ @ ``@@@@ @ ``@@`@@@ @ `@@`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@ @ ``@@`@@@ @ `@@`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @ @ @ @ @ @ @ @ @` @@ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @ @` @@ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @@ @ ` @ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`@@@ @ ``@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@@@@@@ @ ``@@`@@@ @ `` @`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@@@@@@ @ ``@@`@@@ @ `` @`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``` @@@@@ ``` @ @ @ ` @ @ @ @ @ @` @ @ @` @ @ @ ` @ @ @ ` @ ` @ @ @ ` @ @ @ @ @ @` @ @ @` @ @ @ ` @ @ @ ` @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @ ` @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` @@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` @@@ @ ``@@@@ @ ``@@`@@@ @ `@@`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@ @ ``@@`@@@ @ `@@`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @@@ @ ``@@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``@@`@@@ ``` @ @ @ @ @ @ @ @ @` @@ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @ @` @@ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @@ @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @@ @ ` @ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @` @@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `@@ @@ `` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`` @`@@@ @ ``@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@@@@@ @ ``@@`@@@ @ `` @@@@@@ @ `` @`@@@ ```@@@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@@@@@@ @ ``@@`@@@ @ `` @`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``` @@@@@ ``` @@@ @ ``@@@@@@@ @ ``@@`@@@ @ `` @`@@@ @ `` @`@@@ ```@@@@ ```@@@@ ``` @@@@@ ``` @ @ @ ` @ @ @ @ @ @` @ @ @` @ @ @ ` @ @ @ ` @ ` @ @ @ ` @ @ @ @ @ @` @ @ @` @ @ @ ` @ @ @ ` @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @ ` @ @ @ @ @ @ @ @ @ @` @ @ @` @ @ @ @ @ @ ` @ ` @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ @ @ @@ @ @ @ @ @ ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` @@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``@@ @`@@@ @``@@ @``@@ @``@@ @``@@ @``@@ @``` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` @@ @@@`` @@ @@@```@ @@@```@ @@@`` @@ @@`@@` @@ @@`@@``@ @@`@@``@ @@`@@` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `` @@ @@@`` @@ @@@`@`@ @@@`@`@ @@@`` @@ @@@@@ `` @@ @@@@@ `@`@ @@ `@`@ @@ `` @@ @@@`` @@ @@@`@`@ @@@`@`@ @@@`` @@ @@`@ `` @@ @@`@ `@`@ @@ `@`@ @@ `@ @ @@ @ @ @ @ @ @@ @@@ `@ @@@ ` @@@ ` @@@ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @ @@ @ @@ @ @@ @ @@ @@ `@ @@ `@ @ `@ @ `@ @ @@ @ @@ @ @@ @ @@ @@ `@ @@ `@ @ `@ @ `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ` @ @ @ ` @ @ @ @ @ @ @ @ @ ` @ @ @ ` @ @ @ @ @ @ @ ` @ @ @ ` @ @ @ @ @ @ @ @ @ ` @ @ @ ` @ @ @ ` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@@`@`@`@`@@@``@@`@@@``@@`@`@@@@`@`@@@@`@@@`@@@@`@@@`@@@@`@`@`@`@`@@@``@@`@@@``@@`@`@@@@`@`@@@@`@@@`@@@@`@@@`@@@@`` @@ @@@`` @@ @@@```@ @@@```@ @@@`` @@ @@`@@` @@ @@`@@``@ @@`@@``@ @@`@@` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `` @@ @@@@@@`` @@ @@@@@@```@ @@@```@ @@@`` @@ @@@@@ `` @@ @@@@@ ```@ @@`@@``@ @@`@@` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `@ @ @@ @ @ @ @ @ @@ @@@ `@ @@@ ` @@@ ` @@@ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @@@ ` @@@ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@ `@ `@@ `@ `@ `@ `@ `@ `@@ `@ `@@ `@ `@ `@ `@ `@ `@@ `@ `@@ `@ `@ `@ `@ `@ `@@ `@ `@@ `@ `@ `@ `@ `@ `` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@`@@@@@ ``@@@@@ `@@@ ``@ `@@@ ``@ ``@@@@@ ``@@@@@ `@@@ ``@ `@@@ ``@ ``@@@@@ ``@@@@@ `@@@ ``@ `@@@ ``@ ``@@@@@ ``@@@@@ `@@@ ``@ `@@@ ``@ `` @@ @@@`` @@ @@@```@ @@@```@ @@@`` @@ @@`@@` @@ @@`@@``@ @@`@@``@ @@`@@` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `` @@ @@@`` @@ @@@```@ @@@```@ @@@`` @@ @@@@@ `` @@ @@@@@ ```@ @@ ```@ @@ `` @@ @@@`` @@ @@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `@ @ @@ @ @ @ @ @ @@ @@@ `@ @@@ ` @@@ ` @@@ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @ @@ @ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @ @@ @ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@`@@`@``@@`@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@`@``@@`@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@`` @@ @@@`` @@ @@@```@ @@@```@ @@@`` @@ @@`@@` @@ @@`@@``@ @@`@@``@ @@`@@` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `` @@ @@@@@@`` @@ @@@@@@```@ @@@```@ @@@`` @@ @@@@@ `` @@ @@@@@ ```@ @@`@@``@ @@`@@` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `@ @ @@ @ @ @ @ @ @@ @@@ `@ @@@ ` @@@ ` @@@ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @@@ ` @@@ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@`@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ `` @@ @@@`` @@ @@@```@ @@@```@ @@@`` @@ @@`@@` @@ @@`@@``@ @@`@@``@ @@`@@` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `` @@ @@@`` @@ @@@`@`@ @@@`@`@ @@@`` @@ @@@@@ `` @@ @@@@@ `@`@ @@ `@`@ @@ `` @@ @@@`` @@ @@@`@`@ @@@`@`@ @@@`` @@ @@`@ `` @@ @@`@ `@`@ @@ `@`@ @@ `@ @ @@ @ @ @ @ @ @@ @@@ `@ @@@ ` @@@ ` @@@ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @ @@ @ @@ @ @@ @ @@ @@ `@ @@ `@ @ `@ @ `@ @ @@ @ @@ @ @@ @ @@ @@ `@ @@ `@ @ `@ @ `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ` @ @ @ ` @ @ @ @ @ @ @ @ @ ` @ @ @ ` @ @ @ @ @ @ @ ` @ @ @ ` @ @ @ @ @ @ @ @ @ ` @ @ @ ` @ @ @ ` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@@`@`@`@`@@@``@@`@@@``@@`@`@@@@`@`@@@@`@@@`@@@@`@@@`@@@@`@`@`@`@`@@@``@@`@@@``@@`@`@@@@`@`@@@@`@@@`@@@@`@@@`@@@@`` @@ @@@`` @@ @@@```@ @@@```@ @@@`` @@ @@`@@` @@ @@`@@``@ @@`@@``@ @@`@@` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `` @@ @@@@@@`` @@ @@@@@@```@ @@@```@ @@@`` @@ @@@@@ `` @@ @@@@@ ```@ @@`@@``@ @@`@@` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `@ @ @@ @ @ @ @ @ @@ @@@ `@ @@@ ` @@@ ` @@@ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @@@ ` @@@ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@ `@ `@@ `@ `@ `@ `@ `@ `@@ `@ `@@ `@ `@ `@ `@ `@ `@@ `@ `@@ `@ `@ `@ `@ `@ `@@ `@ `@@ `@ `@ `@ `@ `@ `` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@`@@@@@ ``@@@@@ `@@@ ``@ `@@@ ``@ ``@@@@@ ``@@@@@ `@@@ ``@ `@@@ ``@ ``@@@@@ ``@@@@@ `@@@ ``@ `@@@ ``@ ``@@@@@ ``@@@@@ `@@@ ``@ `@@@ ``@ `` @@ @@@`` @@ @@@```@ @@@```@ @@@`` @@ @@`@@` @@ @@`@@``@ @@`@@``@ @@`@@` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `` @@ @@@`` @@ @@@```@ @@@```@ @@@`` @@ @@@@@ `` @@ @@@@@ ```@ @@ ```@ @@ `` @@ @@@`` @@ @@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `@ @ @@ @ @ @ @ @ @@ @@@ `@ @@@ ` @@@ ` @@@ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @ @@ @ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @ @@ @ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@`@@`@``@@`@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@`@``@@`@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@`` @@ @@@`` @@ @@@```@ @@@```@ @@@`` @@ @@`@@` @@ @@`@@``@ @@`@@``@ @@`@@` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `` @@ @@@@@@`` @@ @@@@@@```@ @@@```@ @@@`` @@ @@@@@ `` @@ @@@@@ ```@ @@`@@``@ @@`@@` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `@ @ @@ @ @ @ @ @ @@ @@@ `@ @@@ ` @@@ ` @@@ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @@@ ` @@@ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `@ `` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@` @ @@@`@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ ``@ `` @@ @@@@@`` @@ @@@@@```@ @@@@@```@ @@@@@`` @@ @@@@@ `` @@ @@@@@ ```@ @@@@ ```@ @@@@ `` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `` @@ @@`@@`` @@ @@`@@`@`@ @@@`@`@ @@@`` @@ @@`@ `` @@ @@`@ `@`@ @@@@ `@`@ @@@@ `` @@ @@`@@`` @@ @@`@@`@`@ @@@`@`@ @@@`` @@ @@`@ `` @@ @@`@ `@`@ @@ `@`@ @@ `@ @` @@ @` @ @` @ @` @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @@ @ @@ @ @@ @@ `@ @@ `@ @ `@ @ `@ @@ @@ @@ @@ @ @@ @ @@ @@ `@ @@ `@ @ `@ @ ` @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ` @ @ ` @ @ @ @ @ @ ` @ @ ` @ @ @ @ @ @ ` @ @ ` @ @ @ @ @ @ ` @ @ ` @ @ `` @@@@` @@@`` @@@@` @@@`` @@@@` @@@`` @@@@` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@@`@`@`@`@@@`@`@@@`@`@`@`@`@`@@@`@`@@@`@`@`@`@`@`@@@`@`@@@`@`@`@`@`@`@@@`@`@@@`@`` @@ @@@@@`` @@ @@@@@```@ @@@@@```@ @@@@@`` @@ @@@@@ `` @@ @@@@@ ```@ @@@@ ```@ @@@@ `` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `` @@ @@@@@@`` @@ @@@@@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@@@ ```@ @@@@ `` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `@ @` @@ @` @ @` @ @` @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ ` @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@ ` `@@ ` ` ` ` ` `@@ `@@ `@@ `@@ ` ` ` ` `@@ ` `@@ ` ` ` ` ` `@@ `@@ `@@ `@@ ` ` ` ` ``` @@@@` @@@`` @@@@` @@@`` @@@@` @@@`` @@@@` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`@@ ``@@ `@@ `@@ `@@ `@@ ``@@`@@`@@`@@@@ `@@ `@@ `@@ ``@@ ``@@ `@@ `@@ `@@ `@@ ``@@`@@`@@`@@@@ `@@ `@@ `@@ `` @@ @@@@@`` @@ @@@@@```@ @@@@@```@ @@@@@`` @@ @@@@@ `` @@ @@@@@ ```@ @@@@ ```@ @@@@ `` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@@@ ```@ @@@@ `` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `@ @` @@ @` @ @` @ @` @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ ` @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ `` @@@@` @@@`` @@@@` @@@`` @@@@` @@@`` @@@@` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`` @@ @@@@@`` @@ @@@@@```@ @@@@@```@ @@@@@`` @@ @@@@@ `` @@ @@@@@ ```@ @@@@ ```@ @@@@ `` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `` @@ @@@@@@`` @@ @@@@@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@@@ ```@ @@@@ `` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `@ @` @@ @` @ @` @ @` @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ ` @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ``` @@@@` @@@`` @@@@` @@@`` @@@@` @@@`` @@@@` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@ ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `` @@ @@@@@`` @@ @@@@@```@ @@@@@```@ @@@@@`` @@ @@@@@ `` @@ @@@@@ ```@ @@@@ ```@ @@@@ `` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `` @@ @@`@@`` @@ @@`@@`@`@ @@@`@`@ @@@`` @@ @@`@ `` @@ @@`@ `@`@ @@@@ `@`@ @@@@ `` @@ @@`@@`` @@ @@`@@`@`@ @@@`@`@ @@@`` @@ @@`@ `` @@ @@`@ `@`@ @@ `@`@ @@ `@ @` @@ @` @ @` @ @` @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @@ @ @@ @ @@ @@ `@ @@ `@ @ `@ @ `@ @@ @@ @@ @@ @ @@ @ @@ @@ `@ @@ `@ @ `@ @ ` @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ` @ @ ` @ @ @ @ @ @ ` @ @ ` @ @ @ @ @ @ ` @ @ ` @ @ @ @ @ @ ` @ @ ` @ @ `` @@@@` @@@`` @@@@` @@@`` @@@@` @@@`` @@@@` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@@`@`@`@`@@@`@`@@@`@`@`@`@`@`@@@`@`@@@`@`@`@`@`@`@@@`@`@@@`@`@`@`@`@`@@@`@`@@@`@`` @@ @@@@@`` @@ @@@@@```@ @@@@@```@ @@@@@`` @@ @@@@@ `` @@ @@@@@ ```@ @@@@ ```@ @@@@ `` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `` @@ @@@@@@`` @@ @@@@@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@@@ ```@ @@@@ `` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `@ @` @@ @` @ @` @ @` @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ ` @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@ ` `@@ ` ` ` ` ` `@@ `@@ `@@ `@@ ` ` ` ` `@@ ` `@@ ` ` ` ` ` `@@ `@@ `@@ `@@ ` ` ` ` ``` @@@@` @@@`` @@@@` @@@`` @@@@` @@@`` @@@@` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`@@ ``@@ `@@ `@@ `@@ `@@ ``@@`@@`@@`@@@@ `@@ `@@ `@@ ``@@ ``@@ `@@ `@@ `@@ `@@ ``@@`@@`@@`@@@@ `@@ `@@ `@@ `` @@ @@@@@`` @@ @@@@@```@ @@@@@```@ @@@@@`` @@ @@@@@ `` @@ @@@@@ ```@ @@@@ ```@ @@@@ `` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@@@ ```@ @@@@ `` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `@ @` @@ @` @ @` @ @` @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ ` @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ `` @@@@` @@@`` @@@@` @@@`` @@@@` @@@`` @@@@` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`` @@ @@@@@`` @@ @@@@@```@ @@@@@```@ @@@@@`` @@ @@@@@ `` @@ @@@@@ ```@ @@@@ ```@ @@@@ `` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `` @@ @@@@@@`` @@ @@@@@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@@@ ```@ @@@@ `` @@ @@`@@`` @@ @@`@@```@ @@@```@ @@@`` @@ @@`@ `` @@ @@`@ ```@ @@ ```@ @@ `@ @` @@ @` @ @` @ @` @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ `@ @@ @@ @@ @ @ @ @ @@ @@ `@ @@ ` @ ` @ ` @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ``` @@@@` @@@`` @@@@` @@@`` @@@@` @@@`` @@@@` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@`` @@@ ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `` @@ `` @@ ```@ `@```@ `@`` @```@` @```@````````` @@ `` @@ ```@ `@```@ `@`` @```@` @```@````````` @@ `@@@@@@` @@ `@@@@```@ `@@@``@ `@`` @``@@@` @``@@@````````` @@ ``@@@@` @@ ``@@```@ `@@@``@ `@`` @```@` @```@````````@ @@ @ @ @ @ @@ @ @ @ @ @@ @ @ @ @ @@ @ @ @ @ @@@@ @@ @ @@ @ @@ @@ @ @ @@ @@ @@ @ @@ @ @@ @ @ @ @ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@`@@`@@@@@@`@@`@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@`@@`@@@@@@`@@`` @@ `@@@@@@` @@ `@@@@```@ `@```@ `@`` @``@@@` @``@@@````@@````` @@ ``@@@@` @@ ``@@```@ `@```@ `@`` @```@` @```@````@@````` @@ `@@@@@@` @@ `@@@@```@ `@```@ `@`` @```@` @```@```@@@```@` @@ ``@@@@` @@ ``@@```@ `@```@ `@`` @```@` @```@```@@@```@@ @@@@ @@ @ @ @ @ @@ @@ @ @ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @@@@ @@ @ @ @ @ @@ @ @ @ @ @@ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@ @@ @ @ @ @@ @@ @ `@ @ `@ @ @@ @@ @ @ @ @@ @@ @ `@ @ `@ ` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @```@@@@`@@@@`@`@`@`@@@@`@@@@`@`@@@`@`@@@`@`@@@@`@@@@`@`@`@`@@@@`@@@@`@`@@@`@`@@@`@` @@ `` @@ ```@ `@```@ `@`` @```@` @```@````````` @@ `` @@ ```@ `@```@ `@`` @```@` @```@````````` @@ `@@@@@@` @@ `@@@@```@ `@@@``@ `@`` @``@@@` @``@@@````````` @@ ``@@@@` @@ ``@@```@ `@@@``@ `@`` @```@` @```@````````@ @@ @ @ @ @ @@ @ @ @ @ @@ @ @ @ @ @@ @ @ @ @ @@@@ @@ @ @@ @ @@ @@ @ @ @@ @@ @@ @ @@ @ @@ @ @ @ @ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ ``@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@``@@@@`@@``@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@``@@@@`@@`` @@ `@@@@@@` @@ `@@@@```@ `@```@ `@`` @``@@@` @``@@@````@@````` @@ ``@@@@` @@ ``@@```@ `@```@ `@`` @```@` @```@````@@````` @@ `@@@@@@` @@ `@@@@```@ `@```@ `@`` @```@` @```@```@@@```@` @@ ``@@@@` @@ ``@@```@ `@```@ `@`` @```@` @```@```@@@```@@ @@@@ @@ @ @ @ @ @@ @@ @ @ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @@@@ @@ @ @ @ @ @@ @ @ @ @ @@ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ `@ @ `@ @ @ @ @ @ @ @ @ @ @ @ @ @ `@ @ `@ ` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @```@`@`@`@`@`@`@`@`@`@`@`@`@@@`@`@@@`@`@`@`@`@`@`@`@`@`@`@`@`@`@@@`@`@@@`@` @@ `` @@ ```@ `@```@ `@`` @```@` @```@````````` @@ `` @@ ```@ `@```@ `@`` @```@` @```@````````` @@ `@@@@@@` @@ `@@@@```@ `@@@``@ `@`` @``@@@` @``@@@````````` @@ ``@@@@` @@ ``@@```@ `@@@``@ `@`` @```@` @```@````````@ @@ @ @ @ @ @@ @ @ @ @ @@ @ @ @ @ @@ @ @ @ @ @@@@ @@ @ @@ @ @@ @@ @ @ @@ @@ @@ @ @@ @ @@ @ @ @ @ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@``@@`@@@@``@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@`@@@@``@@`@@@@``@@`` @@ `@@@@@@` @@ `@@@@```@ `@```@ `@`` @``@@@` @``@@@````@@````` @@ ``@@@@` @@ ``@@```@ `@```@ `@`` @```@` @```@````@@````` @@ `@@@@@@` @@ `@@@@```@ `@```@ `@`` @```@` @```@````@@````` @@ ``@@@@` @@ ``@@```@ `@```@ `@`` @```@` @```@````@@````@ @@@@ @@ @ @ @ @ @@ @@ @ @ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @@@@ @@ @ @ @ @ @@ @ @ @ @ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@ @@ @ @ @ @@ @@ @ @ @ @ @ @@ @@ @ @ @ @@ @@ @ @ @ @ ` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @```@@@@`@@@@`@`@`@`@@@@`@@@@`@`@`@`@`@`@@@@`@@@@`@`@`@`@@@@`@@@@`@`@`@`@`@` @@ `` @@ ```@ `@```@ `@`` @```@` @```@````````` @@ `` @@ ```@ `@```@ `@`` @```@` @```@````````` @@ `@@@@@@` @@ `@@@@```@ `@@@``@ `@`` @``@@@` @``@@@````````` @@ ``@@@@` @@ ``@@```@ `@@@``@ `@`` @```@` @```@````````@ @@ @ @ @ @ @@ @ @ @ @ @@ @ @ @ @ @@ @ @ @ @ @@@@ @@ @ @@ @ @@ @@ @ @ @@ @@ @@ @ @@ @ @@ @ @ @ @ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ `` @@ ``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@``@@`` @@ `@@@@@@` @@ `@@@@```@ `@```@ `@`` @``@@@` @``@@@````@@````` @@ ``@@@@` @@ ``@@```@ `@```@ `@`` @```@` @```@````@@````` @@ `@@@@@@` @@ `@@@@```@ `@```@ `@`` @```@` @```@````@@````` @@ ``@@@@` @@ ``@@```@ `@```@ `@`` @```@` @```@````@@````@ @@@@ @@ @ @ @ @ @@ @@ @ @ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @@@@ @@ @ @ @ @ @@ @ @ @ @ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ ` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @``` @```@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@` @@ `@@@@@@` @@ `@@@@```@ `@@@``@ `@`` @```@` @```@````@@````@@` @@ ``@@@@` @@ ``@@```@ `@@@``@ `@`` @```@` @```@````````` @@ `@@@@@@` @@ `@@@@```@ `@```@ `@`` @```@` @```@@```@@@```@@` @@ ``@@@@` @@ ``@@```@ `@```@ `@`` @```@` @```@@```@```@ @@@@ @@ @ @@ @ @@ @ @ @ @ @@ @@ @@ @ @@ @ @@ @ @ @ @ @@@@ @@ @ @ @ @ @@ @ @ @ @ @ @ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@ @@@ @@ @ @ @ @ @` @ @` @ @ @@ @@@ @@ @ @ @ @ @` @ @` @ @``@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ `@`@@@@`@@@@@@@`@@@@`@`@`@`@`@@@`@`@@@`@`@`@@@@`@@@@@@@`@@@@`@`@`@`@`@@@`@`@@@`@`` @@ `@@@@@@` @@ `@@@@```@ `@@@```@ `@@@`` @```@@@` @```@````@@@@````@@` @@ ``@@@@` @@ ``@@```@ `@```@ `@`` @```@@@` @```@````@@````` @@ ``@@`` @@ ``@@```@ `@```@ `@`` @```@` @```@````@@@@````@@` @@ ``@@`` @@ ``@@```@ `@```@ `@`` @```@` @```@````@@````@ @@@@ @@ @ @` @ @` @@ @ `@ @ @ @@ @@ @@ @ @ @ @ @@ @ `@ @ @ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @ @ @ ````````````````````@```````@``````````@```````````````````````````````````````````````````````````````@````````````````````````@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@` @@ `@@@@@@` @@ `@@@@```@ `@@@``@ `@`` @```@` @```@````@@````@@` @@ ``@@@@` @@ ``@@```@ `@@@``@ `@`` @```@` @```@````````` @@ `@@@@@@` @@ `@@@@```@ `@```@ `@`` @```@` @```@````@@````@@` @@ ``@@@@` @@ ``@@```@ `@```@ `@`` @```@` @```@````````@ @@@@ @@ @ @@ @ @@ @ @ @ @ @@ @@ @@ @ @@ @ @@ @ @ @ @ @@@@ @@ @ @ @ @ @@ @ @ @ @ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@ @@@ @@ @ @ @ @ @ @ @ @ @ @@ @@@ @@ @ @ @ @ @ @ @ @ @``@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ `@`@@@@`@@@@@@@`@@@@`@`@`@`@`@`@`@`@`@`@@@@`@@@@@@@`@@@@`@`@`@`@`@`@`@`@`` @@ `@@@@@@` @@ `@@@@```@ `@@@```@ `@@@`` @```@@@` @```@````@@@@````@@` @@ ``@@@@` @@ ``@@```@ `@```@ `@`` @```@@@` @```@````@@````` @@ ``@@`` @@ ``@@```@ `@```@ `@`` @```@` @```@````@@@@````@@` @@ ``@@`` @@ ``@@```@ `@```@ `@`` @```@` @```@````@@````@ @@@@ @@ @ @` @ @` @@ @ `@ @ @ @@ @@ @@ @ @ @ @ @@ @ `@ @ @ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @ @ @ ````````````````````@```````@``````````@```````````````````````````````````````````````````````````````@````````````````````````@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@` @@ `@@@@@@` @@ `@@@@```@ `@@@``@ `@`` @```@` @```@````@@````@@` @@ ``@@@@` @@ ``@@```@ `@@@``@ `@`` @```@` @```@````````` @@ `@@@@@@` @@ `@@@@```@ `@```@ `@`` @```@` @```@@```@@@```@@` @@ ``@@@@` @@ ``@@```@ `@```@ `@`` @```@` @```@@```@```@ @@@@ @@ @ @@ @ @@ @ @ @ @ @@ @@ @@ @ @@ @ @@ @ @ @ @ @@@@ @@ @ @ @ @ @@ @ @ @ @ @ @ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @` @ @` @ @ @ @ @ @ @ @ @ @ @ @ @ @` @ @` @ @``@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ `@`@`@`@`@`@`@`@`@`@`@`@`@@@`@`@@@`@`@`@`@`@`@`@`@`@`@`@`@`@`@@@`@`@@@`@`` @@ `@@@@@@` @@ `@@@@```@ `@@@```@ `@@@`` @```@@@` @```@````@@@@````@@` @@ ``@@@@` @@ ``@@```@ `@```@ `@`` @```@@@` @```@````@@````` @@ ``@@`` @@ ``@@```@ `@```@ `@`` @```@` @```@````@@@@````@@` @@ ``@@`` @@ ``@@```@ `@```@ `@`` @```@` @```@````@@````@ @@@@ @@ @ @` @ @` @@ @ `@ @ @ @@ @@ @@ @ @ @ @ @@ @ `@ @ @ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @ @ @ ````````````````````@```````@``````````@```````````````````````````````````````````````````````````````@````````````````````````@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@` @@ `@@@@@@` @@ `@@@@```@ `@@@``@ `@`` @```@` @```@````@@````@@` @@ ``@@@@` @@ ``@@```@ `@@@``@ `@`` @```@` @```@````````` @@ `@@@@@@` @@ `@@@@```@ `@```@ `@`` @```@` @```@````@@````@@` @@ ``@@@@` @@ ``@@```@ `@```@ `@`` @```@` @```@````````@ @@@@ @@ @ @@ @ @@ @ @ @ @ @@ @@ @@ @ @@ @ @@ @ @ @ @ @@@@ @@ @ @ @ @ @@ @ @ @ @ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @``@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ ```@ `@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`@`` @@ `@@@@@@` @@ `@@@@```@ `@@@```@ `@@@`` @```@@@` @```@````@@@@````@@` @@ ``@@@@` @@ ``@@```@ `@```@ `@`` @```@@@` @```@````@@````` @@ ``@@`` @@ ``@@```@ `@```@ `@`` @```@` @```@````@@@@````@@` @@ ``@@`` @@ ``@@```@ `@```@ `@`` @```@` @```@````@@````@ @@@@ @@ @ @` @ @` @@ @ `@ @ @ @@ @@ @@ @ @ @ @ @@ @ `@ @ @ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @@ @@ @@ @ @ @ @ @@ @ @ @ @ @ @ @ ````````````````````@```````@``````````@```````````````````````````````````````````````````````````````@````````````````````````openMSX-RELEASE_0_12_0/share/shaders/default.frag000066400000000000000000000004651257557151200214640ustar00rootroot00000000000000uniform sampler2D tex; uniform sampler2D videoTex; varying vec2 texCoord; varying vec2 videoCoord; void main() { #if SUPERIMPOSE vec4 col = texture2D(tex, texCoord); vec4 vid = texture2D(videoTex, videoCoord); gl_FragColor = mix(vid, col, col.a); #else gl_FragColor = texture2D(tex, texCoord); #endif } openMSX-RELEASE_0_12_0/share/shaders/default.vert000066400000000000000000000004571257557151200215260ustar00rootroot00000000000000uniform mat4 u_mvpMatrix; uniform vec3 texSize; // not used attribute vec4 a_position; attribute vec3 a_texCoord; varying vec2 texCoord; varying vec2 videoCoord; void main() { gl_Position = u_mvpMatrix * a_position; texCoord = a_texCoord.xy; #if SUPERIMPOSE videoCoord = a_texCoord.xz; #endif } openMSX-RELEASE_0_12_0/share/shaders/fill.frag000066400000000000000000000001001257557151200207500ustar00rootroot00000000000000varying vec4 v_color; void main() { gl_FragColor = v_color; } openMSX-RELEASE_0_12_0/share/shaders/fill.vert000066400000000000000000000002641257557151200210240ustar00rootroot00000000000000uniform mat4 u_mvpMatrix; attribute vec4 a_position; attribute vec4 a_color; varying vec4 v_color; void main() { gl_Position = u_mvpMatrix * a_position; v_color = a_color; } openMSX-RELEASE_0_12_0/share/shaders/hq.frag000066400000000000000000000020511257557151200204410ustar00rootroot00000000000000uniform sampler2D edgeTex; uniform sampler2D colorTex; uniform sampler2D offsetTex; uniform sampler2D weightTex; uniform sampler2D videoTex; varying vec2 mid; varying vec2 leftTop; varying vec2 edgePos; varying vec2 weightPos; varying vec2 texStep2; // could be uniform varying vec2 videoCoord; void main() { float edgeBits = texture2D(edgeTex, edgePos).z; //gl_FragColor = vec4(edgeBits, fract(edgeBits * 16.0), fract(edgeBits * 256.0), 1.0); // transform (N x N x 4096) to (64N x 64N) texture coords float t = 64.0 * edgeBits; vec2 xy = vec2(fract(t), floor(t)/64.0); xy += fract(weightPos) / 64.0; vec4 offsets = texture2D(offsetTex, xy); vec3 weights = texture2D(weightTex, xy).xyz; vec4 c5 = texture2D(colorTex, mid); vec4 cx = texture2D(colorTex, leftTop + texStep2 * offsets.xy); vec4 cy = texture2D(colorTex, leftTop + texStep2 * offsets.zw); vec4 col = cx * weights.x + cy * weights.y + c5 * weights.z; #if SUPERIMPOSE vec4 vid = texture2D(videoTex, videoCoord); gl_FragColor = mix(vid, col, col.a); #else gl_FragColor = col; #endif } openMSX-RELEASE_0_12_0/share/shaders/hq.vert000066400000000000000000000011161257557151200205030ustar00rootroot00000000000000uniform mat4 u_mvpMatrix; uniform vec3 texSize; attribute vec4 a_position; attribute vec3 a_texCoord; varying vec2 mid; varying vec2 leftTop; varying vec2 edgePos; varying vec2 weightPos; varying vec2 texStep2; // could be uniform varying vec2 videoCoord; void main() { gl_Position = u_mvpMatrix * a_position; vec2 texStep = 1.0 / texSize.xy; mid = a_texCoord.xy; leftTop = a_texCoord.xy - texStep; edgePos = a_texCoord.xy * vec2(1.0, 2.0); weightPos = edgePos * texSize.xy * vec2(1.0, 0.5); texStep2 = 2.0 * texStep; #if SUPERIMPOSE videoCoord = a_texCoord.xz; #endif } openMSX-RELEASE_0_12_0/share/shaders/hqlite.frag000066400000000000000000000015711257557151200213250ustar00rootroot00000000000000uniform sampler2D edgeTex; uniform sampler2D colorTex; uniform sampler2D offsetTex; uniform sampler2D videoTex; varying vec2 leftTop; varying vec2 edgePos; varying vec4 misc; varying vec2 videoCoord; void main() { float edgeBits = texture2D(edgeTex, edgePos).x; //gl_FragColor = vec4(edgeBits, fract(edgeBits * 16.0), fract(edgeBits * 256.0), 1.0); // transform (N x N x 4096) to (64N x 64N) texture coords float t = 64.0 * edgeBits; vec2 xy = vec2(fract(t), floor(t)/64.0); vec2 subPixelPos = misc.xy; xy += fract(subPixelPos) / 64.0; vec2 offset = texture2D(offsetTex, xy).xw; // fract not really needed, but it eliminates one MOV instruction vec2 texStep2 = fract(misc.zw); vec4 col = texture2D(colorTex, leftTop + offset * texStep2); #if SUPERIMPOSE vec4 vid = texture2D(videoTex, videoCoord); gl_FragColor = mix(vid, col, col.a); #else gl_FragColor = col; #endif } openMSX-RELEASE_0_12_0/share/shaders/hqlite.vert000066400000000000000000000010441257557151200213610ustar00rootroot00000000000000uniform mat4 u_mvpMatrix; uniform vec3 texSize; attribute vec4 a_position; attribute vec3 a_texCoord; varying vec2 leftTop; varying vec2 edgePos; varying vec4 misc; varying vec2 videoCoord; void main() { gl_Position = u_mvpMatrix * a_position; edgePos = a_texCoord.xy * vec2(1.0, 2.0); vec2 texStep = 1.0 / texSize.xy; leftTop = a_texCoord.xy - texStep; vec2 subPixelPos = edgePos * texSize.xy * vec2(1.0, 0.5); vec2 texStep2 = 2.0 * texStep; misc = vec4(subPixelPos, texStep2); #if SUPERIMPOSE videoCoord = a_texCoord.xz; #endif } openMSX-RELEASE_0_12_0/share/shaders/monitor3D.frag000066400000000000000000000002231257557151200217060ustar00rootroot00000000000000uniform sampler2D u_tex; varying float v_color; varying vec2 v_texCoord; void main() { gl_FragColor = v_color * texture2D(u_tex, v_texCoord); } openMSX-RELEASE_0_12_0/share/shaders/monitor3D.vert000066400000000000000000000005011257557151200217460ustar00rootroot00000000000000uniform mat4 u_mvpMatrix; uniform mat3 u_normalMatrix; attribute vec3 a_position; attribute vec3 a_normal; attribute vec2 a_texCoord; varying float v_color; varying vec2 v_texCoord; void main() { gl_Position = u_mvpMatrix * vec4(a_position, 1.0); v_texCoord = a_texCoord; v_color = (u_normalMatrix * a_normal).z; } openMSX-RELEASE_0_12_0/share/shaders/rgb.frag000066400000000000000000000016401257557151200206060ustar00rootroot00000000000000uniform sampler2D tex; uniform sampler2D videoTex; uniform vec4 cnsts; varying vec4 scaled; varying vec2 pos; varying vec2 videoCoord; // saturate operations are free on nvidia hardware vec3 saturate(vec3 x) { return clamp(x, 0.0, 1.0); } void main() { float scan_a = cnsts.x; float scan_b_c2 = cnsts.y; // scan_b * c2 float scan_c_c2 = cnsts.z; // scan_c * c2 float c1_2_2 = cnsts.w; // (c1 - c2) / c2 float scan_c2 = scan_c_c2 + scan_b_c2 * abs(fract(scaled.w) - scan_a); const float BIG = 128.0; // big number, actual value is not important vec3 m = saturate((-BIG * fract(scaled.zyx)) + vec3(2.0 * BIG / 3.0)); vec4 col = texture2D(tex, pos); #if SUPERIMPOSE vec4 vid = texture2D(videoTex, videoCoord); vec4 p = mix(vid, col, col.a); #else vec4 p = col; #endif vec3 n = p.rgb * scan_c2; vec3 s_n = n * c1_2_2 + saturate((n - 1.0) / 2.0); gl_FragColor.rgb = n + m * s_n; gl_FragColor.a = p.a; } openMSX-RELEASE_0_12_0/share/shaders/rgb.vert000066400000000000000000000006221257557151200206460ustar00rootroot00000000000000uniform mat4 u_mvpMatrix; uniform vec3 texSize; attribute vec4 a_position; attribute vec3 a_texCoord; varying vec4 scaled; varying vec2 pos; varying vec2 videoCoord; void main() { gl_Position = u_mvpMatrix * a_position; vec2 tmp = a_texCoord.xy * texSize.xy; scaled = tmp.xxxy + vec4(0.0, 1.0/3.0, 2.0/3.0, 0.5); pos = a_texCoord.xy; #if SUPERIMPOSE videoCoord = a_texCoord.xz; #endif } openMSX-RELEASE_0_12_0/share/shaders/sai.frag000066400000000000000000000031661257557151200206150ustar00rootroot00000000000000uniform sampler2D tex; uniform sampler2D videoTex; varying vec4 posABCD; varying vec4 posEL; varying vec4 posGJ; varying vec3 scaled; varying vec2 videoCoord; void swap(inout vec4 a, inout vec4 b) { vec4 t = a; a = b; b = t; } vec4 sai() { vec4 A = texture2D(tex, posABCD.xy); vec4 B = texture2D(tex, posABCD.zy); vec4 C = texture2D(tex, posABCD.xw); vec4 D = texture2D(tex, posABCD.zw); vec3 pp = fract(scaled); bvec2 b1 = bvec2(A.rgb == D.rgb, B.rgb == C.rgb); bvec2 b2 = b1 && not(b1.yx); if (b2.x || b2.y) { vec4 pos1 = posEL.xyzw; vec4 pos2 = posGJ.xyzw; vec2 p = pp.xz; if (b2.y) { swap(A, B); swap(C, D); pos1 = posEL.zyxw; pos2 = posGJ.zyxw; p = pp.yz; } vec4 E = texture2D(tex, pos1.xy); vec4 L = texture2D(tex, pos1.zw); vec4 G = texture2D(tex, pos2.xy); vec4 J = texture2D(tex, pos2.zw); vec2 d = p / 2.0 - 0.25; float d2 = p.y - p.x; bvec4 b5 = bvec4(A.rgb == J.rgb, A.rgb == E.rgb, A.rgb == G.rgb, A.rgb == L.rgb); bvec4 b7 = b5 && not(b5.yxwz); bvec4 l; l.xy = lessThan(vec2(d), vec2(0.0)); l.zw = not(l.xy); bvec4 b8 = l.ywzx && b7.xzyw; if (b8.x) { return mix(A, B, -d.y); } else if (b8.y) { return mix(A, C, d.y); } else if (b8.z) { return mix(A, B, d.x); } else if (b8.w) { return mix(A, C, -d.x); } else if (d2 < 0.0) { return mix(A, B, -d2); } else { return mix(A, C, d2); } } else { return mix(mix(A, B, pp.x), mix(C, D, pp.x), pp.z); } } void main() { #if SUPERIMPOSE vec4 col = sai(); vec4 vid = texture2D(videoTex, videoCoord); gl_FragColor = mix(vid, col, col.a); #else gl_FragColor = sai(); #endif } openMSX-RELEASE_0_12_0/share/shaders/sai.vert000066400000000000000000000014501257557151200206500ustar00rootroot00000000000000uniform mat4 u_mvpMatrix; uniform vec3 texSize; attribute vec4 a_position; attribute vec3 a_texCoord; varying vec4 posABCD; varying vec4 posEL; varying vec4 posGJ; varying vec3 scaled; varying vec2 videoCoord; void main() { gl_Position = u_mvpMatrix * a_position; vec2 texStep = 1.0 / texSize.xy; posABCD.xy = a_texCoord.xy; posABCD.zw = a_texCoord.xy + texStep * vec2( 1.0, 1.0); posEL.xy = a_texCoord.xy + texStep * vec2( 0.0, -1.0); posEL.zw = a_texCoord.xy + texStep * vec2( 1.0, 2.0); posGJ.xy = a_texCoord.xy + texStep * vec2(-1.0, 0.0); posGJ.zw = a_texCoord.xy + texStep * vec2( 2.0, 1.0); scaled.x = a_texCoord.x * texSize.x; scaled.y = (1.0 - a_texCoord.x) * texSize.x; scaled.z = a_texCoord.y * texSize.y; #if SUPERIMPOSE videoCoord = a_texCoord.xz; #endif } openMSX-RELEASE_0_12_0/share/shaders/scale2x.frag000066400000000000000000000015541257557151200214010ustar00rootroot00000000000000// Scale2x scaler. uniform sampler2D tex; uniform sampler2D videoTex; varying vec2 texStep; // could be uniform varying vec2 coord2pi; varying vec2 texCoord; varying vec2 videoCoord; vec4 scaleNx() { vec4 delta; delta.xw = sin(coord2pi) * texStep; delta.yz = vec2(0.0); vec4 posLeftTop = texCoord.stst - delta; vec4 posRightBot = texCoord.stst + delta; vec4 left = texture2D(tex, posLeftTop.xy); vec4 top = texture2D(tex, posLeftTop.zw); vec4 right = texture2D(tex, posRightBot.xy); vec4 bot = texture2D(tex, posRightBot.zw); if (dot(left.rgb - right.rgb, top.rgb - bot.rgb) == 0.0 || left.rgb != top.rgb) { return texture2D(tex, texCoord.st); } else { return top; } } void main() { #if SUPERIMPOSE vec4 col = scaleNx(); vec4 vid = texture2D(videoTex, videoCoord); gl_FragColor = mix(vid, col, col.a); #else gl_FragColor = scaleNx(); #endif } openMSX-RELEASE_0_12_0/share/shaders/scale2x.vert000066400000000000000000000007411257557151200214370ustar00rootroot00000000000000uniform mat4 u_mvpMatrix; uniform vec3 texSize; attribute vec4 a_position; attribute vec3 a_texCoord; varying vec2 texStep; // could be uniform varying vec2 coord2pi; varying vec2 texCoord; varying vec2 videoCoord; float pi = 4.0 * atan(1.0); float pi2 = 2.0 * pi; void main() { gl_Position = u_mvpMatrix * a_position; texCoord = a_texCoord.xy; coord2pi = a_texCoord.xy * texSize.xy * pi2; texStep = 1.0 / texSize.xy; #if SUPERIMPOSE videoCoord = a_texCoord.xz; #endif } openMSX-RELEASE_0_12_0/share/shaders/simple.frag000066400000000000000000000015321257557151200213250ustar00rootroot00000000000000uniform sampler2D tex; uniform sampler2D videoTex; uniform vec4 cnst; uniform vec3 texStepX; // = vec3(vec2(1.0 / texSize.x), 0.0); varying vec2 scaled; varying vec3 misc; varying vec2 videoCoord; void main() { float scan_a = cnst.x; float scan_b = cnst.y; float scan_c = cnst.z; float alpha = cnst.w; vec3 t = (vec3(floor(scaled.x)) + alpha * vec3(fract(scaled.x))) * texStepX + misc; vec4 col1 = texture2D(tex, t.xz); vec4 col2 = texture2D(tex, t.yz); float scan = scan_c + scan_b * abs(fract(scaled.y) - scan_a); #if SUPERIMPOSE vec4 col = (col1 + col2) / 2.0; vec4 vid = texture2D(videoTex, videoCoord); gl_FragColor = mix(vid, col, col.a) * scan; #else // optimization: in case of not-superimpose, we moved the division by 2 // '(col1 + col2) / 2' to the 'scan_b' and 'scan_c' variables. gl_FragColor = (col1 + col2) * scan; #endif } openMSX-RELEASE_0_12_0/share/shaders/simple.vert000066400000000000000000000010661257557151200213700ustar00rootroot00000000000000uniform mat4 u_mvpMatrix; uniform vec3 texSize; uniform vec3 texStepX; // = vec3(vec2(1.0 / texSize.x), 0.0); uniform vec4 cnst; // = vec4(scan_a, scan_b, scan_c, alpha); attribute vec4 a_position; attribute vec3 a_texCoord; varying vec2 scaled; varying vec3 misc; varying vec2 videoCoord; void main() { float alpha = cnst.w; gl_Position = u_mvpMatrix * a_position; misc = vec3((vec2(0.5) - vec2(1.0, 0.0) * alpha) * texStepX.x, a_texCoord.y); scaled = a_texCoord.xy * texSize.xy + vec2(0.0, 0.5); #if SUPERIMPOSE videoCoord = a_texCoord.xz; #endif } openMSX-RELEASE_0_12_0/share/shaders/texture.frag000066400000000000000000000002221257557151200215270ustar00rootroot00000000000000uniform sampler2D u_tex; uniform vec4 u_color; varying vec2 v_texCoord; void main() { gl_FragColor = texture2D(u_tex, v_texCoord) * u_color; } openMSX-RELEASE_0_12_0/share/shaders/texture.vert000066400000000000000000000003001257557151200215650ustar00rootroot00000000000000uniform mat4 u_mvpMatrix; attribute vec4 a_position; attribute vec2 a_texCoord; varying vec2 v_texCoord; void main() { gl_Position = u_mvpMatrix * a_position; v_texCoord = a_texCoord; } openMSX-RELEASE_0_12_0/share/shaders/tv.frag000066400000000000000000000017351257557151200204720ustar00rootroot00000000000000// TV-like effect where bright pixels are larger than dark pixels. uniform sampler2D tex; uniform sampler2D videoTex; uniform float minScanline; uniform float sizeVariance; varying vec4 intCoord; varying vec4 cornerCoord0; varying vec4 cornerCoord1; vec4 calcCorner(const vec2 texCoord0, const vec2 texCoord1, const float dist) { vec4 col0 = texture2D(tex, texCoord0); #if SUPERIMPOSE vec4 vid = texture2D(videoTex, texCoord1); vec4 col = mix(vid, col0, col0.a); #else vec4 col = col0; #endif return col * smoothstep( minScanline + sizeVariance * (vec4(1.0) - col), vec4(1.0), vec4(dist)); } void main() { vec4 distComp = fract(intCoord); gl_FragColor = mix( calcCorner(cornerCoord0.xy, cornerCoord1.xy, distComp.w) + calcCorner(cornerCoord0.xw, cornerCoord1.xw, distComp.y), calcCorner(cornerCoord0.zy, cornerCoord1.zy, distComp.w) + calcCorner(cornerCoord0.zw, cornerCoord1.zw, distComp.y), smoothstep(0.0, 1.0, distComp.x)); } openMSX-RELEASE_0_12_0/share/shaders/tv.vert000066400000000000000000000010201257557151200205160ustar00rootroot00000000000000uniform mat4 u_mvpMatrix; uniform vec3 texSize; // xy = texSize1, xz = texSize2 attribute vec4 a_position; attribute vec3 a_texCoord; varying vec4 intCoord; varying vec4 cornerCoord0; varying vec4 cornerCoord1; void main() { gl_Position = u_mvpMatrix * a_position; intCoord.xy = a_texCoord.xy * texSize.xy; intCoord.zw = -intCoord.xy; cornerCoord0 = vec4(a_texCoord.xy, a_texCoord.xy + 1.0 / texSize.xy); cornerCoord1 = vec4(a_texCoord.xz, a_texCoord.xz + 1.0 / texSize.xz); } openMSX-RELEASE_0_12_0/share/skins/000077500000000000000000000000001257557151200166705ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/skins/ConsoleBackground.png000066400000000000000000000214541257557151200230060ustar00rootroot00000000000000PNG  IHDRXLPLTE ///,,, www```}}}®---{{{...ggg222 +++*** mmm000111ppp|||~~~444zzz KKK666333ooorrr"""555sssqqq%%%cccYYYIIInnn)))???FFFPPPSSSfff<<>>DDDEEEJJJLLLZZZ___xxx777XXXlll&&&(((:::OOOWWW[[[ttt'''GGGaaavvvyyy AAABBBQQQ===TTT\\\eeeNNN=tRNS$IDATx_I N Fc r *"" T@ x/jV[zi{_b>;OK̳3>< ( LR0M us' @UiRͶz͖T8E+))f[:Ua$ pNeh"*tmrM3-43C,-DeiZ?[K[[߷,-'B+-Z5i 5ӋeN,qoexOVX+fak\c-̄Amvn=LP#LѴ;#`LMJQ+鎥cꎥc-ӱ6Y;51ޢ;Mǚ`7%뎥CѱtmCvmcٳ+]2_:Tw!ݰBw,zizKGKcַB12(UgK7y,=A\ݞWC1ڴ^IbVQT8ȕw˪)YF\+n>{nXDv=BhN uf˝x-F39߭5߅qb8ܑ!2.Q_ԽmQy'NK<,FWoˏQiͱFN(Zō%ȡ ZoxhǠ_Ն|Arb~J汸vϝvAGcpTt yИg!=+m YNvjMǮM(qSb n6xV-9!5_ Ǎ o 9w{s+J `ﺄ J<# 8Hy;G$QīXX ;/4 sIS&Av['c 6EcH~{Jq=*O觔c%wi w^Y澝<XWg[)coi|%o -(%6pOAŶ2aT\nyeѰ o>ݹS&0$;s c_(pެqHy~Q1=Ym+!b{cx w|SL2Uq$;~eBFY& ckVF1LJ,'{< XdC;$شB0(x:> G2bte2NU !M\[OzaAsيļ\ʕ)Xi/)I u%`9IAaMt-+rДM!*4oSI֭%q7P"wgh }`Mts-Q^p}Q/K?xg L g 1:A) brLQSp!"JƴŢ҂P^T{lr\V"ݫs? VRZafplG f'⵺R>4k!dž p/ngLA;*2ZnxA&z'ѯQJ %WCg-2Lry2K~kIܧ^LcLEa=SE*A.W'_ŭ8 pNO+rHF֝RH<@/@>$N}Lֳ*y#eP4`~+S/Q:ܻ@׸<){keݺ4]s*TZl<:%!v4L#BJ6=`u5Ub,mVE*S\yM:*t0.h'{L!j[T8Ǝu6zH5`eA HPPK ̱7a*|2^X_,x4oW79*P N7GS7t%2tkgzCfJ-\'6t`v![lTܔ@{XLTLJ0)j'gWО特>Д޹o2BhU͠g yDma+YׄL۫8^ߊ6p`ų sB?͌,Z*&APM{X}լfs]ie)~ K[vx)0/dt6#([#'3 3#X* bVC,?q0sHB Bܔz2uR@!U~j}Ǣ+c]W̠eWY%V$yk\ 3 rk|m3+_xk/㛸xL`h Um|'ǟPOAvd]s,:\/^F j%?ўՐ]`P2K>چƎ?~S*zmǎ`9ݽڽqGNYD\Vm|@|H :Q!B֣yLMVv+TijƊxw3TLq4Ia|2J@> k w9{@wIw)7鋇ȯ!.}A@NN^+vu2EN/ne\?9 ատxm*}1",bR,4ko7""ԁ+xP╘@%82L$ \mfO=]DeYTg JRɍw_M]NrOrX9($_@I@`LLBT_:h*VQ_]ZvƉ H`ߓ{C|X+p>{2%2v&J!8xF PImZ~W rbtG'jY9~r} BH%F=ro8jDϖ*[YVoe-yw ,ܩ QuЅ;}]ugjr4#iN5a|Mjz : K"U-j1PZT#OFRrMJzᶹ-ii4<t~~ƄpiE?eĨylS0:޳~D+^Xt#a?0FxA+>rnU"g^4F2cm2:qSh8cZLQ6 )n!7{A'ˀ`׉9R&y[|L7K0l stcÊHgtH0wz{ :mH6!#ar1te'8/:P ~om2E F]›]Q\{f&ZڼKw YiF tۑPDzs}C(`[A!Wmw(@~zR֯+^XQk?Yr@5*!chv wTT+U~#٫!T ?J#&5d/*?9%k(Di?dgy~|W8VYH&P[(n`B0zgrv;Ӓ#'?S8? Vxv,|/ʻ\qԙ |C(,uBV$RLfjiǾէ ٸ}߆B.Y%M퓴J3]`E(LY%xYɄZ`dɒQHz,E3Og~ Ն|90^3N&vMY >ͪVA\-:ShSӃ#Y.EQI FކIs a3SPq EF)hV'~ n$Eٙ@.sDz7{dq3 Fvg7(vA=V~}M6R3a43y0} 'y``ToHQDݿ5L!n8J[íeg1Fakjq0X7ra~S8L6G᫼WPO { N:BwK U77{ԉ~bɔ /x!@fʵV,~8W0؞u((OL4T`idj_هE{k9Rm&3ԖG,)anD ǺPrgNR҉8I(YJ0Ճ".&y "ٜ,B4S !/R8N܇B?uAdM) XSeR* M"O: 8)('WEA f<>~PFKٙbŠB@{5s^ZHGK\eWpR0ul~{,q"N&2я.b9U3Z:%4w`mnjO 0-ص藜eB*9e3D\j? Tl1$POhz8 E-֨XRW+!-i?X =cR߂\ %,vok?煈,q@DAVW$@)hu͓Q_eqm7RUBd9j/1^`8Ǐ}q?#G$T#06LDgC{)GYx_ y,X[I 0 ~TV'Vt~ mm- ˠjxz[=`x]l)bM-:^#/-'g &UF5Ya =|){C''T =XwV&ɺx cqZ`͛|G!f3PC2<žծ.xPlۚ:TOZV`cL.X7#u &^!Ue6+#ݔ=d=bxNףhTM I/)=H|4"֚}8TTB:sA=y[0P:pBE\IgJx2a'frG<`_- 0̏ayA=R><06C?b<` B7 !ŬZ_1GgNOsRUjq/K ms v<)A;!fIXdbYdտx7+jNtx*v .%U\ g֝%YWo/`\>$RZs/_ xk 0V XW/c'$#74ӹ0ۮR+7Tfr bܞRtG+ɶe-7`Pl;|gޗ N<zY :SH">Y ^zVBhޙ"1'%&Vy>>```OOO}}}(((TTTjjj222GGGyyyDDDqqqYYYggg'''555HHHXXX333sss888NNNSSSdddiiitttwww @@@BBB___fffhhhnnnuuuvvvxxxS>b@tRNSc IDATxn6E뒒zxƳg& -HHT%bUC8X$V"H$ID+Hb%X$V"J$ID"Hb%XD+J$ U 3 32DgGpb1i Cё?9')%>M97`*0LKCAzD`ȓ7YpbHNfC^i)\ OX=8H4fʗrbk6?NZt)'u Ch~%-ZRN4bJ"< F')Q@X=xHy_~H,#,*'f'dK("Y0td*0ӚYt,rRNDb\4w{4vG1X'pݵ9K_0+]q"*\6*4 'PʉJ| ce4<頩).6SC!/9SdbA-2 f\wиrA4_[*w4L\ @/AQlbm {^T;hrبUuXCk(D,EOXY{($ˡW~Ӹ:hZO,, #ʰ$]*jU8jF,nݐE!S]b#^}:<WtbaQlQ{XF).KF@25Qò8'XLe1IějXUO5^pGtB_O{ Q Iq%dױKj7-Rh/o4Zs޽' 3N6+ ]bo*s;R`P:(.$8>%+Y\osһX@Q86)py/5G,ٱBV"З]rE:_@6o 6cC_N厯A, \sIYQu ,o֭HӈXMs$$+d%]M~_XgFNin#{4<҂(buB&?d%߯,$TX?)Yt"Yng `b$Z#1{$8= Y3n h|88O5$g?i6oT,V`Q -<=-PXx$oVxxbrp]bi^V^XZVo+[xhA*+X@B/8}b9z?k' k?V)礐g=#d)c8kϹbM{QXY wP]CǞUPXI'1+< )2S+:Yn=~)R$(OHlbF&VV[-dby1 u*ZFI5do@ƞS/{5'#wSN|ǧ{2@~WXC:zHٟ|G#V"?b%Jy={PXؠ!+o2i 10dM@*(U#։؀! nIbVbQ_9EI܀+2sА׺>_:r{BBv(*#:zJs8k v÷gher8b,_|c3P%1wRSb9FC ]CH.u|1)F,~Ūܱ wߗ.cg·Wsn)ljސҖ-Vds(v7wfNe X! hZo:1CVWmv% d]B  q"8t8H)=>Jߖ+V̊݉Y` HD*7o ̞Xab&abeXe&ab&aXeXab&abeXe&abeWa6IENDB`openMSX-RELEASE_0_12_0/share/skins/ConsoleBackground2.png000066400000000000000000000173031257557151200230660ustar00rootroot00000000000000PNG  IHDRX? t[ pHYs k k JtIME;8tbKGD̿TIDATx{[ŕǏbQ1/킪@Myi OIi=F l$u"v ByYRރdKWg?'=j|g @b}HqB F_nT X, 5,ں5Z Y,KXpՀ:Z\*bXcAuxcZC5L5.mRHOd&*Y= D-ꇫJID]767iX,$ ن4^+V ZՕ_]ց{  !np mBۼuMX,[u[Zܸ)r3d HHk8Wu `WJdV! nxinG!PbX؁H" I#2FYr6\]ĻٗF"A/v'vOQw=R3]/ŊfjnɏH;(;$nAʤ6"unC偲+jǕȭc&`cx~GB: ,}!"MF܉tFY XM`5\WY \݈p+?.$.9rR_yv(,;hRCH]]3H2?DlGdp ,5`uW»VfW"@ XHL!_ڕ~X,Q됤Ra,R$/E@k'fZ"kGxYr5`I$5~^,^n~1x@cEw:P%8c?Rdi2T+u/@ &r5`uWLcE.%&tH⥞@~'Q`w]bu蠔 $!Q!YDZC>0c!Zac~_m` ʝW{#^0`(Ǟڔ{X,H D`1$H=Y;:SHmk9VcUv".V"IwV#Gx= 8<'Dw[l),Iqq)gAHG2D u>"J"D^*LRҿ1AV"cGL,xزn)s, g`NiYRCz,"E#M*DG<RvRH֊LV" kF_Lb(t^Y2g޹22:t\ K^J5.R5Plʋ3F{PpZBVHfGzE,#MNJd=y I9Nϭ͢0b ͂ ~ A7ayg%.%x~{u]zD%Jbjf)ʊ'g󮚽W+/!9.HlE>K|VMƋd^a$K>>o\5IJH}z I"sHg0%$cHH@y =%5|_i?),O"zS>Ű>J~5P )?f,5RP(E#qWCIp]l"$ȱDQIɦ~?Gͅ &X`yӷx_}WV"/^_B$USXTF rESrW7}>Cj|9֛H*g.H`=iau.s1G8,/i>/}%׉!r)葤G2C.d~Z)6eY '܇%u[o`Ż0%pP.֜˂#L>mڻԩKKKM\)豔GRQ|j,cSb]4~N\~D֧HdQ:$˲'<ڍ) XjAzNJ`]W x>*TWM\ń.4MO2-|SSc)׸OIKJ7+b]hf] dcȻHW*X'2XsH6dO`G-6t>20d&UTSȱ#=_-qNt d f|.jXHCH^d`iSUX3"I1BCVj̑d$J0:u}N9x ,a*%K1e+&L )NH/^#5?Ep֋Hkz;uK ,}-12}U6 kZ%6Tǽb` L^(N FVFz&^(KJi ˄ީe6ۭsV(~ځi<Gp^-bsVWգrICRӦ81h#3)KrPĺ(׺%u鲈y iVe@52)80bϿb$"``( f5F"vV3>F Kg$偣X`<LDQ (:XB| ί@~ h:XXUq b1TlPE`5=yN8[;Y\X`ovnHѮjxeq`3bRzR, `e⵭Vє^:;X\PMVXb- {OTo`eϥz ;X`T 1^>]VL" X dddrX> V%ak=)HhNV+ ,QqDrEicʉVcK(HFg "8v+hX6!ܜXL7"d U[tKh)`PckR =^2,fXmI6cQgU`kH`e D1:"Z`UZWqDVV@-HR`%|nV TL7m@+VO?D$y`U @$Vxko}9.hy`z;XYsYkaNe7.k2XJX6Whz fɸGbءJ8=~b\+ L  Ef%27gIk{EX`M E3B' 9'kBX `Xq6Cӱ?IS>dq"b*)X|t9!4KUQxnЍVF9U!+e6 M`%n͉8;XJ=s`a'.2J7?g,ܳU53Ydpg`srb`1ls{ZCcLr-3vDw3Dbk-GKݻ _vX.>9-2zJL`sںEQnC˯a`rwC>Z`3JDl տf؊ef6pf|T8}-.FK`KHmVf~H!މ mҡT{}>EwD!. F,J95d=PLdR6iYHZTNāV˼DRoUYjGN. +cs K=U`BI?4lV}g,tXNMk 2]G}Zq,*͖. 3NX Ə bZ@\N:XE" Q1 pQX~QV-EX^Xו#kEBzGg#cyGiBJ5j它( hܲA5Q`ޑ:2`ws'tDSkZ)k[q9Y4Ձ#.nP+OXr,`!Mp;!^!S["~QUdQV,o3Ne 3D~jYMۨ0 4G*VNIdjROi XDŽg?USY-q)JEX'wP RiY7]<FH02t*Ld*@3r9X3h+ `,8ke<'> #6K8 ӘGVXZ~)%$0YOe5% ֜+w'EX"`(?h{>҂lVT\=!+eqg9^fbaQOeeάf ,7Owls6|b7cֿsEtG?Q IT~/"Q)?Yt <4Y&yMR8T|8K,h/F8X]leX2o;fA* dO%4u1MjgɊYԁKb}jI9vrx_VDAa15XԱF+/E[S|Pˆf͹XK =ASaM|s}wkFiS.VCt(s=G ,?5 ,rXMV90gF!H`{0 }͸X3V-dh. ,j7DEx)E`ygE͆,3r̂b9uMj(E BF`HW;Xre=V__{U 0֠XcF[mR2' PEovHlfir ,c0'CrAh4\2"WF+x 9g^`Mև<:M&  8l@+RݫP,;5Y/y1`2D3 %md/Z ʍ{a2>kc0yGaO1]A\ɢS?q'˶s DVa_fkh+8kP},C15V$>Ҕ%@9elMtp+3ָIrOG3EdLv5uk"3JFRbX#kEȢpu(vh/` XogX4UXW"?JXʡ4VZ X;|_)SNsXcP\j٭akw+R=4 X*Ef!&X` y iawz]J{ŵ-z}V4 X~ߣ 5SRc-֌#SR^ kLĚnZ۴HkOi `+%2qҷ.54 Xzø>O;,'-PKGBy(\K$ľX}`k!@CcD\rgM!~ŕPb٭,F I3X.^. H 'dⴙJx]fb]i~Hu$WJn4^+kWݱXZr8lc ـSH,E\-hFqZ'e} ,`I7_ XM,~>T,`ŒT7+_+9 T #su6?uhHS,If1FmŝĐ80Zbz|5j[N>rwJ4VXrQc•S=VJk7w6QsXa%d,a r0ɋZ]8NwJc {ci-k,`CEt@2nE)X۾eus*g`C%\6{ŔmQ2&C}loS,,uZh SuayVX<,+ 'yԎrf Xy>-UƁ#]jm,Ij9NCVXZ!WXcAŇ~+^ X Y ¯ߢVXK$e`ݿvI`N!,`JFeXVsX%+Vy.7JBIENDB`openMSX-RELEASE_0_12_0/share/skins/ConsoleBackground4.png000066400000000000000000000050131257557151200230630ustar00rootroot00000000000000PNG  IHDRX*!PLTE7777777_` tRNS] IDATx=6`1^,W$%"z⛴ٙHl!dhdfI cyy^1IB_`}cXXsmb5S ]zX9VXp!b%3 b!b<V2߇%fĚ#VX ƍhpvLUC c#Xu  x0kVcv^)%F3eĚӷw?b?gc~oo^ȧ\TXbծ-$p+j1;NZKUk=7Bcװdbc+=?X%j* "FTyCX: H±ԋb5rdՁIX2g&V,rbyįcbb.IeVGKgf,"V\5ma#5g⾁lhOYN,]X.˅EXd X0+PL6%t_ ]2fUXT+s`ĺ+X5b,R;t=XXDsz?GXMaYXQ!`))g| YZvkgذb͎5hm</9C#&`L9m.{`,tݷAaYsOy  WxUMJ<>+ jMͪƂXQHo)y%b>+ꪱY) kQQT?UqbbӰ"u%#ŭVk-}LU_K8SmE OcUX"'Z4VYǒVXXE_CoTwyQ,ŲV#:iXHYsU[X{~cG|SX``{x,%hLhuui+vb #e-μПK# zù`pϋuIo 9н)1^bՁoO^WX(,^wU-,G{Xo ZX*YM27 5{o(E!A~|tƁWtTO/Zgp4xګ. >u:9g,_gec߭ .4x,xz߄XX]Xjƈ:K#O,tԳ"yuItwVgN˅u6Q}I bw(8MZEQgZ{YoLVdb?B %&S"z|>:CfISMb8V eՉzey() (~!>B+n,Kw5HcɁb:LX ]jM, hNJ`\Cqau|=w|X,sె O/GNl/OJqҀY>'+E0,3󪤛:OLYyL",ų~xxo>w +|^Σ b)Ek^N5vvy.kז޼[6jZMjAXA }uȞewUZF za^]ZO6kΏ=R+Tt-^X[}NeǪkl>0Rl1:b!X,e%K2Zc*VAukIFØC?$#d좿_oxS+5p6]b /WqHxX0# 'tZ Ȇ"e AEx_^bW ﮖ6b+[Z\Xe_|qLnaQ+Bb!uʓ꟩=B5:NS rv,J]yEXeCe6@,·^dC,juk@H7Hd/ܟVKgC@Fka5:R cNqgψ寅X5eLb!Vਆo|b1(.bABy yB{ }(b!bu )b!VѐV+@jbIENDB`openMSX-RELEASE_0_12_0/share/skins/ConsoleBackground5.png000066400000000000000000000056721257557151200230770ustar00rootroot00000000000000PNG  IHDRXfPLTE777777__77____7äჃ777_7__77__Ã__7l"tRNS IDATxmsఝ" _ݹ d˱-S@RS3?      O':qus`9Ւ]}- ` 7OOXtӝnV ``,$kXGr5s+z4bf!xb=yK`9[W͑|RXc= ,uK:RW-ܙ XݥKȍ\وi"QXc=4ˢbٓm|L*yɮ|*^/r_aHEy%eym&Y_j9W`%,r RlB(oL? VA~buVTӎ G^ Yy+p b[)Br5.&XQZb;\k1ZwTeVW]֙Sk-"m{Y@A9eVp|A"A[( yHau@gZׇ"S ,VӰXEĜpLX9+@!0fs6 iV;UKVukf[ eju}X+@7GoU+˴H=-H镥_Z a=Ǭc%k\U;>^ؚ2]{mGk,k^\j֗ʚkH>{j^ vC8kr5jjH$ VOZ`uF<Zc5Ӻ&yOy,UWCX)z*q J Ϛ\qI)1>VU +sث6UrC+}4b=7x~KpJt}J^z9i|ASukbu9zb9lt U-z`JL;Zʧ㉵_X.1sŬ[`qJ`|kL5fѽrQ VYh [,],CUKk{1k۝ՊK5k'ǖ6.ޭZny,Gb +WkxsMXk Wwɉ ^\ & s%Y^4CJ3p6SgmEK(Gf\ުk~-ʹiXtWTm%򭰽A,z"@Tϟck, L痗A`=0z4XL˹\[^``mzL-də XXu)+cVZBl k,˸i3j 2D}!e_6|'4``m`us ߂e`]  k;,5.] 6jk,,w45׏o;X`}1/8ӛWBCg,X[3VQu!vam5ar:;n, a: Mf y ks,,Nk;s6tktK3_4`+gv\\`i|-bssvz4`uZ`m\Cud:Z4 gi{ڛ8 Da8NDB{.*WnQ.Fc'Cޣ⚉RU5Jh5*ZEE}7`}VD XOr4hmMuaŨ_ =4*ZEE&ht4;hmM,7ĴʷB)++*rJGW *,.,`Y>X]Dz kV3 2|(,`+ڸpA*` XX,m@9e45Sxƚ +["Z.`IhB+VMȐ{mqXW`kXz)h# 7üB- Xx XiR/a)bNk`,`+-Je,` Xa:`,` X,`S<{,` XkǬVը,`=+`L OU2F]KUVJ,cdzR XVEZ -RzTc X* k%ƬօS:J/~J1+9.$r뛓 V[,! XVJBd\XfKh,`,` X~,`kr, X=,?X^?m@IENDB`openMSX-RELEASE_0_12_0/share/skins/ConsoleBackground6.png000066400000000000000000007631101257557151200230760ustar00rootroot00000000000000PNG  IHDRXgAMA7tEXtSoftwareAdobe ImageReadyqe<IDAT˲[gwc9Z{?T*Sl(JP3.#o7@DP () [6Xi+k9ߡZy]@B(" `!D88 8 (0A"@@B"!  Cp@ p  A P B %p@qA!p !p@$ !BPC0pA$AA @ H 8 DpB%  P"! Ѐ@ @@! @!@ ! D @ @% ! @ !$8@CH@BD *B"  B*a"8 " A! 888 NhD  8 8B"A  @@@(BA BH@D !DD" D@"@""A "@@8  ($<@ P@ !E@%" $:  "Bx 8*C  A@@TA< `#(8@@H JD(X"4_6?D(vwdp, o;kFA Ft"ӢPPex'!pCΉ15Cu8R\P1\+va xNƩH(x@&t`.@GsR AXd O vrd*BB)ܔJGƪ]z((w!S8! ttB4'G&&M 28ȁ6!ZqDwE 4ABđ1P! A UJO%*!4  *Jˌ"4(JA@qWFR;Ai N#D*0c ""Z(jFK`2@C$!pZc֠j9I M蛠6  Bq%Qb4\ :JN +q t FV bp4aaꉎc'dLI )!  TA\U HtU=PMJ 4)'O0B j]6( B`: j "B1 $Id@seNCA 0p'T$ %I1Q&@+ 2 ww Fh<Q* )0Q``!9>q' 8 M2:xad 0A{!4(F!E&g4$W:)\Puagw')HP 49ih 2p}`tU# vaHWx/(h < lp BB!"D)*g;B0#Peb(:H'5i!``*D0g !j0_!_KO)0B%_wNnO^׍xtٸ~C}s4kD!)S̥ ՌNe!yVK@h2hE;zuANsLRi -8x%r# 6FXʅGɔs^W,5/|كjDnA©s+Tx֝W 03C+]MB%G7'YE=t ElৠOWȵgME'Ol§T Z8CaW0s8]h# q\?Wq  J{-'Y?hlFB ew̨T#XH-:$~`B.st ߈&PۨۍBcož+O>ҝ041=Bw>QDB_H ;N?Sc^+1 J& oADvaiL-An@n6EqF}\Azm:)v :gl7*T(o#諲 THS3#/ʔ:J:cDD)Gg$c9!w qC18y  fAJ>„,'}J 7Dj :=0eCV#bθ,H2F[(6fW@%Ί/(Ԍˊ6a'o ,)خk{&LߡiPn?]]/"?:Ƿ!V]OacBlČx:q]3' ށ%'a >\ ٰd)S) T6JbĞI2târ&Nt`1F4F^+7k78 Igb4cXV8W*;8mϓ]L'랹Oa7zld˜e፝& 4W1)+E]7P ;6u@ĠJyf0/6ueUX8)`~MK@aIBP{ E@J"vd)x@ՙeA[䨍:~T$dԾsO$ sӻ 2aSoAďc ȝS@de)B[®97 J{\/4۩쎦ޱz3>'\aNǙQ@E8]?n|gZ^Ѓq>/WlOlqϤ̜T$ BgE篤NhƋ}fµ`MЖخO4!749!X9v0k rJ'f;seJ{#yB ©Of@i3䆷R2 '(]hJ+߉z!~! %# B` CÚx dW6YP@Gf Ib)9[*Du ˂#d+&6` LBѓ2l"mFZφ7Ap(y!"0NZ\#DKQv>ӆnBo2˝>^`Z{I¸9[1Q)&&y~~E?.4/z#F B̊Gޜ5}AI7[΅W{H%R`.dUH`9L HdkB%e7lj9;vd*Tj#Ra\7CHBN#0n&aBF Lň(F[F%1ρ°B+T Źv/0I3cZ"QoàN+UB*(q+s\A?1 1ϐ2 "ALD +¼D_4xKT)O^B ;f(Xj/`+帲Ld'yMdSj`<CNAm91y18 ^c圌1I!t#;x$6]:Х6} Y1{q^ Ja?f*frI׃l hv(/Hd ^lSxVؽJBF7#H@>ɏ_9_R+,-g2sqNݱ CiDI_@#q b9d 2^WX}$&pXtXtʅ168ư %]Ylp O)u.L*+ވa/Ա@O|N>cp%:u$tX0*gCI QNނ8a)R'[3ŌrOV5b s!$ΓL+36R)'恦DA=Y2o$NIsGEUAJH²㇒D |B]+XahC۠i +O ^NX3N0$H3sqaBʁ]x1)6HA`hD(Ҥs/q/>h w㠳g\}-HAm"FmftwAK\ēD);`"9a'g0 %ewGg#Ԡ*IATl΋7Z keƑVkp3oS+H3 !HR̡59!iu0΄,Wpѹ+9)ñU[F5s02 $jřF<:[tHpd0.vMRXYa*`&5)³ @ѬhH3$EL} Cp&S~v 4WJS`Ch{ Lq4hd&.87 ةH0*I ÍiHp  1xtj8B:;a )[G-( d?.<]:ƍ ?F̋ҘLgA։NBxՂ>*}>G8V;Rq<厦+9Oՙz!\,D?is>a4B3E]!JTEڃcQF;vzFg/-߄zVR3:GS/۟D?@R=A>qSBR&z~dO %!)H҉慁vGBsl 9Yy'g5jdH$-ifzeFt$.haq(!>im:d .H||Dj ЕUG $7j#nN a"IA'I߬jZq"|༡)XD#coA!Phh>+C3;VIxcLʹV q:k4g&xxEdc329>sIF`JL* c7$Chy0Qj }A©'7N $70SJ /L~4t2+b`?(*h(XHmg:+omt!ਖ਼ӹ΅L ( 'a-2e $Klz'Í끗|@FR<:ʇf4)܊m Y^ƾ\. xMHy@v ԂFE\ ab5)iMlu#"h!ar"AyƢ` Bq(-8M as $6ⳡh*Z0 hEBhotVA`TTz z RhsJOt:#B (+D#IW7 2jv%Se|h^b 5HNf)c‹1QbґDj|w|J'tjgbA46t^-h늍ɏWy0V%3 D =AҦ_1r[K 2V)f no >mb3htJI2X>er b: )Ȍ=C+ԸEPB}|\q3 Gb-F;l&FNL'҆a!L)+W|]Ylc3ۀ yt\f.Se߮2xDʡރs :H ͤP뒩53J3=N@0}'DU:je8^ܝe`L#q\D>{fiΩ  $|q=gCj"x XΝj(xKBΘ銉ѱPxtz[N ,aȬ5OA);ì:eaL2A_o186:|jõOXf~E0y)^Oz(Z؋)ۊdN~00va1| N͡|CێN8I`\1AT'`z"B9B4M:/d*q;Ib5=Ӈ抌X'"-4olE 6>="##7<}!E.A&dp F\ ϔiv ѐ;of?+u%)qnN@t1:1v̍^IlW\Q3d⌦x2GPM{,jO8a\5x2` %yDi(Z*3ӴN\ 2 WOD!r4"%.]8Γ 8)wjWBaudb=b!\.>HP@ێDωɾlf1@bm-iQXϙ;vI8K~RFei18As - #ׁ~+Wh1qte9=˼oy_[bj8++\v`ς?ym{a,>3] $IT S-|6X1.FDஔp4V>I4Q`WMNڌ$IS&.9g Ӥ40 ڀ~8gwt;ҡ˅1I1B*0CHIp %5bfX I(=[gјw,1#3-K<.ʲd>P6PCZLÖ7ֱ.o 7CrƢf FJ:+,."h` AA‘փ2 "9 aqs(O/){}9|Yf¸WRZ9Na>5hˬҙOeB.\'C0J؂A,ļ0x0W4c2i h̐w&dnB 3^+V?I % t.xaPqZ 2i^ ~tĝu<<('Q5s;cWbѱ!>'g6ؿ lL͐)Qm#jCn#*KψO?\qnA,Š̄1ĵ+ǔIX²`!)Ӯt9WJi }K1t'?o\S^ehe'ҍ0ORXTNz$FY#~),bev疔fB{" ?4Y Q -HNڙ酹̐w'l09?a{籾2<2FS'0'Cם|aF'͸>)%sm/DDAH@+ + %QscL+yz zLєv$2GQ1 b# ±J:HVrtv# 9C0tNXfy Rʝ n|=Ygٗ+<8XrA-lym_D6Xi9r@JJ_inF[\}N3%R$6Ykލ''aݝĻ楒ı >un(V:5fJu*)R` |vdTX] )9 9%b ԱhI 1~(YW!9N1r_@1&zC(skǂߐ$]5Pd <69om;Q6grZ~rc6dO`_O |;ґ1s#~,Gc>)^tC)Fwlͯ/ |t2qpRߐiޘ%3-$Ϋӟv^&r ÿSVw,2'v{^gԎ2N@R &{LKy`hwZQx?]ɨ+߸ZT8h@Wt%*B c _!pl$P)6v<DJOA;C@Iˉr*_v}j_ý_fl$LisJ_A àIA^}Dgv0Zib4&3UIgCJz6L.͟t;XsApYVᆤ?8Ĕ&jg^i @MW,#B'JAdpՂX`k}a<!QƟ7PE#ghdHi_V^$FY5w^p\^I~#W U8c6 zFب\F}rA Hm㸽בxnH]YҠځmv/l\ JIQ _I'1D_sU&؞RXk82w} ӂOQO&EDW|i~' 2:c6 cy:3ŽeՉ%!OV&B*G JlRJo+ĉ[(>4a!uz55bA9dPñ`NLX{-Bb(LcP Ҙ2@QptnE2qd@*>!^1Q&Oz2va,BA_fPx~rJ.B`7ۅ{ŋc)h' $|44#Lfho6 ~ : Og𜔯GLFA9yP'J'!9T8U h\i˃'L JBQD!y(ues9P{CH$ƫ34]<8u8Ds؜= kj*}Qy\πI zti)p̍6n\&N,1W]HA*:b-X̯qA{O 'QQ+H2z R$7z'ZhzI }A$31V4K;c&$h@qcv^V'Z ^Jwwѷ@@±93r";B yS tD#6Gf$iVe-FD\'d0+T1$?~2cihX>2ï ~ꈿ2Սǔ8'>np(k'uŞ/ dSQ Ӏ+i;\a|qљ:jMgR-#BڃHԿȟ 0lgep>ߍ}8 .' = ;i_86gNY:3].DMSҥ " B3~(_w+N{DvS@JX&:!@zor>\"M\9+ IIQ2D-ΚfB udȩ#蝑% mNm/|K_'j~@Y*Ank:aZXj4.ymζA`ˉ5vt:O6SiCi D d*NZ`53KcMH)tĨ8idȊ427 f ݾv! =aۂ;:&rػ})e⣟m2g;™ ̑ItOxSACƴ&NpI.iIZy#cД)S0&2e/ėֆuųrvc>s ct fz?(Uhf$Q@x > L0> h7,:c;2s8 X2f9ڕdOaȷJ2"F3ߔ?\F(Ħp*gb_@'όie;?yKF&+ :8r7Ny8l&J3‚J2rן?ZZ*c?h_q0:rJfȔ`@{AO;}v̊Lso:VηkOJMyX%8sZE%#=c c%lUq>?ieb<;>@(sQF =튾V8lFA$C+ؑ`?Wq`UiY5_:7`gN2t t?7jYۅ`JeO#n5'+"% ,NY*W&z«DҊms -߄8[sC ;z("|IϙmU98+3$QZhSA7G!0QFRPbC'Ӷˌl3kbO^A?a :_a!>/;.ٓr3A T~V$ _$Q:1^d }t:,c︮RaD^0i%s Ճky‹=, IbH*a02>#}_XOx@9+FYrW89F o' 2?Py]Hۑ&$w@1v7X 3 `\>)3xG ✘|x⢊8GfF4CYIz ͐s~Ķ)ߟ =3dR0'˱sN{eD0:F.0w_s+7S؜53B x':3L`ǫq!EǞNnI'O$KL`f $EvIK?twEIbkj(2#[ϓ2#X݁@!\Wx^:1p.ܑE㞞`+;1#AP wFȑe>9gTpk/YJD=zڻ!kWjr p@S#O'~͝=Y2>ܡ"=*J*%g`襐@SBm>)8\"b+?l,ӫ-d>xXƒß v0wG߹9exG*=$9P-Bf/Yú CNC"`G"woJ~_(e,aj߈Zs*|t)kG  Q'SÝ3@?WFk9?/7^N8 Tcvۓl Ž×"*B+c Jy6JhL~DyGDH)`j\ 4? Y &XA&fqW(g݃~T@{xi )gdB3msO'-6%0|+NN$_q\#`bN%]縰6L5 ם*p80Y?+収cy9W'D3!] Wy(+6$)p#@c^Tw]feB A>)17 !A2n0W ЦDyĴ U'_#2ℎk3ǑkpL$Ce(86vd d,89J=ȁfc0, 夠 :b)<""=\ɴ([2M#7H2q#bs0 eϟGb|R35[s঎sNxlU&d`iex FFgHx+!݈%! G$_LyoOWeFܕ&2H1He&@zk=*aY0uxjw=pۑyM#|:2㵒f(-r,ˡy:LGk] )XŸ'3nDD hUrt J}HNx 9FR}#H`FJWSOXGs$ 5 @Y!zcW잋 UJ,'943bnư 2w>+tC>TS<5.Qhݨ*gD3Қ`Hq,LƠ]g`HN>/x*,g9CL|=v M~LׁLE([%v bmK!.nZqJe2rѼb? >Xt*"SGa3&s8n^81,R_/6G*5, _ ơ XrhTN=)]ᗙvD e&- 3jY1њ0$6b'4 wr 7?~ cTD"&Pq#^<&'bHTjdlSL'fpI>Q읖UFD޲Ȏd:NqIQ3rL,ȴ0`̜"u13ho)@jc+{ij3y:Ԏ'Mi| ;Ў!ڄ8agnN#KDζ kg-#?wD|Q_GZgt2ʅ fF_K#- J7'A/^jow9q{s4 W!-@wl/lo/{–ѠlҙIi7ZN.Ye <zLx¬vd\}̑rҟH !t-8:Q9gNqd$x`<b)[By&=iMQ}!vmbS9PuxR$w0 OEk Ft4*Zt%6ɸݐ0^hQ3]i:\3] k\i(9 mYGcX.}w]NTc \jU^m02@5^? C ehs8BpptD="e ,f|j3Ri آv" \?B5aHTC$8sSK>*Jk[|Z:1iQQ. ,,r8J4Empb"Ø&-˹J;0Wsx2Q}3/}&tb7yۋJL]&#C~^ ݰ KB/ 9S"><;֔.'._HSl 83%O,R1-Ž~kJ^HGiČ/+@HQs,Nw{DT"K1/nZd'WF\zԅVpgW6 z넶,;ELzTWA,J~)8V]/Y4!c8'S)~Sd ^`Ay96Q!Io BN WG}2yAڿ25 :2өx@ㅮJo|19,lšt3a#eaMlX+?F+s$~ڐ3qL0_ = >_v)C'Mr ^r[B 궡ޓ)pcV [C#_#~ʕȃ痄'v#B Wdl,P2}G()EڑVo{LoaHɯ4] - c`#@6St䑡^CI3+6PMPw;dOQf$4r ("y- Z=z3ۉ![XO`xN m64\s` Y!uÕ+WQ{MB_X|Ky8^;  rb'yhNI6p]!dTf ?#J:GL Oz6(H`LN(D{m" D1Ѡ\)P ̗e>WT x " .+bIJc,N*|cv$04৆eac:Y$pAO&ˠoZRc N<(Ӎk\O!2 ʃ}ϳrИp p1vy Qg7t#be$m Q%DzwN+ iB=>ig 3V#rNhI׀ӎhCg+ʲ Ly:Oz(vḻmRB2Dw]#7,Oc%Nn<>7'XP̄'sha`Nu8HSAZ;;b8"සv+Xҁ@(XP_hp_NJT19ƃLp:<7C@+ԟyĹQҀI|蝺w:iGC.!mL9Z@[ s9+}r[ ̎+c.Lm0Lm}1;F r!g9ZHL`~@Cȱ!iE2ODqQ8׈q tt|!NoFn,{7mqO?~wK_ B9 Fa' S>w+R3b9spNv'& aˉ* FAڟLDƌM!xsciB/ӂPqa!5ec p.5;Y>&vnjI +ZBFՕNXNX I n7e%IcnS\:Aa&W+G|;QiŎt#L98e0"zi]"AMB~vdCD+v9, |f˃Oht_0/5S,bˉ@&$G &'Nf`$Ng%´#}˃e}@Wwz NfFu!ˆ ѿ,o$;=Lfsk"d)LeVG'gݩ &˜ixn;=-(; 'UC:Ϥwϊ+Av8ERQ73Ǝ#pL#aĵW3~!{M8$DO^놯x=Wa;I3fmI\qc)uwX>ө݉3cw#Ў+m=d88N1E`GܖG(FV?~nwelxY;t^Mxkք(Ff%5nmyփ8 OSEˁ Vw.c\ZF-Вƪ?ʳ8z8qc&nBxex.{̍OG왤GE$R3p:R00qp:Z@ASS(* C^):`JN:´G-Ov&PV"NL½ tT.k<ϨtiNIq}f# X+;-:2BE\Q*vfۨu|I`>0JT>.\6:!xNQԉ´48z O'#yt {v5d+ ȱB_a> _e|V(#&4kD uG] |'F(nJ;3ԇ2چO3h׍nvGv h%4sh/"q=aП*6&Lh)3%p+{:tTiCr>wn2S6^NJ 3! UZ8 _;ۄ?nn?~/vVc`T9 V 躂|iIh@ob 燣OOn{߰Ixy`J Il qHH*l%#HgD` }N9΅B Bˤ\)2%]WJc\:]ہc L)Q8qyRBFJ/ ?5kKVe&ADdyk+H%8菌 }QhzR"'u ),>1U\f#{ibU)xl|YdD=6u:;c"'WstDp]z2:DutD쨤4(rCό3"댮'~ŨIE{&3 UOyrlc\H!cSņc τSǍaNbxpQEIkNV=X /c%܉Cɻ^#s]rD+D ǦH3F+a҅!PMI&(n-vlёDi]iz(  _-OʙP$!,.T NjP!'7 RdǠ/%gE}2#ޓ~xvLFk+mnوgn\Ȼ ~Us.0˝hǘv$\)|5Cd:&и9FF$SjB#"FN|"h¾ c`ųG@i+}U1h}#̊u al#DἑC\ƼNjXM,ncwcD@K#ʅ?/q0/K3Vf$;Ix~23|@25cB! Y2 B']g Q=jxInڠޮA\!'2T~3= XNO5QN`AAauL蒉>;w꩸k4"bz\:TAHE&SVƋ1Lj@VG'e`$O+α(lrݸhn!wNqk#$aD(F3V  @]gߥݾxAA36N˜B , U0g#Nx#xgwkǰ,D2G$8aOju4[O鍬']!d۰dyR#1<oe {e@8I& }QHUٓJEcdrOGT$_)mFa3/D nz2!UQ<wKC~AarRS=1$NA]MAOns}v^;y>8@'0jS큨3u{']WStBs2;[˥k]4zDhEXhλqTLg4 q7Vl5 8isB^U{ ϓ6,'TLMc2ӥ4ai%0jg܅'/7J]H/puJ݌&esAs%vVB1qC \2$wXl7,zlXI8- Mh(CN ^Wh'6W: R^4{"90E?hy2(t5kydA7c&'ynqe=r(yHB?H~f68<1a WnJ+ clXOuKŽ|_.~wp3킺+%ys]'*Om#9\7ڔȫm [?{Oך`ꑽ*7Zxe'.a372!xm+jCND2}!;H!, !l+48>El+=2?GQc@lq'ֈv}"/\ zrm㐉g8wRG[aΐwO /wrpxMvb\e %bӓQW,0^.3v7\;SWTŬSCTRQ/;63rbY7JNPcW^^~&4 ,L1h[&LJ ?l$suw#> <*9cp`&a8v7V̈́Pj$;c\p3Q3ZBb˓L׿#LL%.~6fgT'7:\n8`\ox2v!a>bì"zE ^nV:Sgn u>YpaR4K`hFJupOF 8S#-z# U<>*JlL(-m<]9GewgӁ\.&ƶc)6x|;\Fh9^&xVwNid3kDoHi1Qk'#L_+ *J`B%a WDcFV{ġ6alT?p(m1bJ-<8]![bΜTTvO\\A3#J>;S0 Q_6/o}KJs4T=:j7Fׁ$n5~C*MHr%=8n >t*Υ$NAOK81bBN>#q#ܴKDF^n,Rp-}1*Bi> =X,m\7#h8 <_&D,[ p!A  c 8K;_9wSa(d++y~=g\nhKxmƲLX 1Gi\cgd8 VIYI',e 烶]ȿt4 >SBd6fzLg;17{NLta:z"HKc;0s5sx3Bq@#¥vTyorƕXRLّ0R9 6ww-tc6&-9ֵѷ\}ig4U®$t51s ˴#sA #ez#̆ ȃ:\rFpF 6qB{ǍFN 7G$ʊ` JK3晋tZ;b7'ݘb98lDFphQqT,Uh Ʃ]Ó"B%;1cwh7r*C\ʨwy1ӝ@JŅHGg ' lƸlS0}𝕟f!|16kxowdRؚ1aJ⤝WKPimP݅ԅ7+uf܅]?+δ(8`bs5r`Z%BO WȊFV8)0fT> ̈́x^z%|vR:n>Xƍrq)aƝ]iܑg{>. uev=sԅy`H.U<8f!A:]}0,$0aA* =O/oS7~nD o126y\#JFaiwOw7d~D6aJVnӮ n!Qppޕ෫c`^F⍡ ʉD/Ky8|r£ ,Lu7[B h#π#;HamAQkbmF*FɌP:Đ;+fZ8h7Zul2Qy&>p9RoBg8q=`9ҳǽ%q ۉ1lOF'^ )']-tF+_%3B5B?fVou anj7.4nK8XHTɧǁ\=`.o E6z4b56c?}jXh ܡlLԾO\w/#\pwT .7\~ +qCõ_SA;&\1[gTqfᦓ&z?L;R}& >$L -g_ G1[Ƒp#(ch2$<Ѽb#(sF^6ׅO u 3}su4T2XVDŽσ6w̽M7%H#MCnC<\cNFEq׈;*TGS .4txzqA炩4@1nX@cM)E1~ 3g;?F29 ze'# [No$`opuRWot)̤6igR+| ú yW4c;- ÔUt>@zi1ILq!30 $ɶ9d!7eI:3X2G7™Xb QS%E\Z`$nTi%19V0GQ.%5œQH @hIib HLiR@&3CB7sF]dey7V|ŮKH[T̂T f8;ia=|Ck#m 4H 'Ь!EQ>wd$ʹ2cxo0_Is8&$wmwNY5Ef,0_}rIWz~$@ A2_R)Ҍ01uzZDΨ##䠬㴠BHЃCJc4EpN7!yO? pxNw3> N1}Ǐ!vF 8axeE?x)`}m-18c!X`TtaS`:Y2L$ JvDœWvuD;53xΆKQ.LGI2teѭ 2) LY`7K_{R3A !2&q7ӓãaǝʹz>u.IWn3گpq8oBa2Nuޯx˘D8teqƍej3#`)~pJ%؅foU&+m<,q^ J/OKġ _R( ww)z^8/IZ'ZQ2dGQ9 [!䴡.2Y$ d)+T oA30ˠ SiXJ;/`'[_`Dg)0uW} BAWO8fYr4~7?O?/?߾ŵ[BXw6p{ӟ?1w2Pܷ+ˉ|19?!RތHyiEmLle:E-v9F& 3)"+Y+Jn%` )=%1.ぅ^ؒb6>(~%Q݉@8u\`|8R¥[F`gӈH~|1f<"$l6s Ę *# b=d|3 ;(#˓!&њ#0\1Ήt]T i@M{l(y:|P zAOO\?!7 AWB;>ox!R /BЄ m>Ya/Xh$a_ |FH,Fs؉27FNi FK(<$q !Pvv#]qa,e[nO+HI}rpQi `+8aM;/7/b ~gOu쉮 1aH;Wd$C3=%zTN!Yyđ̰ib 1!5MLѻĉ4F 7;p#."$H}".М"S>as9!9#W$,]&WN#*팘p#p #*A^< R!UlpzDA|sZ(1lpI*9mt6VA[,pP4%2HD\E>S'7N*GQC螠}4ԈE6 ']*3|viG jBJ8STNg87ىC(GK9tBpv afH҂ qBAvy~x=TN2}_ TN7P34F$ a(5;|<1c݅6*Z:+7uDVD?q%VEZ! (%z'4ɣV|qmLh{WA#kx GŠ2oFo A Q%&N9iaJӅ|$}"Hq_eA,1owU6#֙^.nQ#C~͑V#QtH.w (va~Jϋ{/?`O%<;н??uE@]/ 73gH 3ou#كHp; 狧#ףP|vABxύvK3#,';& Nߜ:8,?# ;#.cHH_9᫐$PmAt4 bpXjR,Nz5 &6ψm 'V"HJsrAǎƼ ϙc)}٠sD8N 'ah B_bBqN;mrY|,al~5*T"g[īcBgU炥ݱ-4-T*mZq[go7Ρ\^#bG1 Șk 7wq% <ђd331jH6ť~zGZbZ5ʛ>ϘiT}P{3Ce gܩ;V83a{`|儫w WxIqV\#u!8AB]U|0\Y}'; SFnJL)?iy/x3)"63%`D; :hsay4AzlQY> 3J#A+TmԻ0&)^:g8$hʢL̘40b%ƻ,@4M~Y`]}ҥhI<~4HZд-k%p*tfrDW8:]97) dj+"C/BW2~eW0m|Djx3Ir1d'Oq)$4O{("!+vGŞZD9sG# zKF4X?l =+9( Ԍ4+MEX.bޔvj-ä1;tFv.;dVi7З}ޡGDńzqQqD?D؄,&Vp,;3nugqI;%º="{fzpH>.s3;"?wELR(%"Z {DK+=(S\ ~?O v+B }EbSF͘ H;}xyPxGj_9h /r:&#;_M|ˍQ#'lp#SD&#vG823 @/ځ@ whu^qt&YSUNԐ9rgd19H];N ,(v&wK-F]grϘxMd/\?_oޓWnSHtmlΒ:/zcpcƧb`xcƧ>叄 o }<YLiHꩣc'R?&Dz y'gG#OKn!EޑWmmkj0i4(eg_B =~gF=9*`C^ ?#'J#}l ƀw1o`x$%B ⦕H"VR\ȿ.3(~!ٕVuh (~aqF =Jd,G㬣NxBWkTJ8x Б̗񸓜GW&OnkHc0́DZďY`oVYmFs'B܂cÈ 'C3ZbJu$oy5NLOWya{v+a58&ƽL~?Szeg( 30&!+kّγShDGC}oӿ{z~kq3Fuv!B7e~AF2S]$;]' BXq c|΍9^:{NuJJ#:!n q:3c:E:ר8"gژN \)6 C#hK #ה:8,(8p}?*5+KV b%TC@^#`$kҰJqOgȁ*0J- g B[Sĉ1gp\`q38}Z ~(: +Wwycͨc4#ôrFIsNKAn'uw-Bg:BW^zƵ ;|=NZwF_lll׊6ǎ5t\!O SRmcgo \' 8එ~gτtC;֟n0\)P*"7#k=)\k3q4PF9eS' ;uw,=d;`3i4P?^V e(;D^>.ݝ06<|ӉP1ˏ<9;2 iѼ0&|ћ '>#w;㈘W0hDolMɺ*6e9ny|U-,1r6. lpw2l"= D\#vB3+Jέ:NL督>Z:lq7y`rz=q1nc~ak9z}L{&#;?1$5 y+8Tj@.#f+IYyTaaw V-ܨ6wPgy86zxR-Plc¾'2;ͤ)m xςJ)&cE*ŏ7QiMd8RN}w7qtpd[(G0lGB\n{) ,b#8O #CyM__{~e|B| %9¿ J_| J*L /Wr. w*[]0^XQq!0\¡ģ l(~}@h3٣bXc\\;m#9XmO)0ف. i*CY:p9be '@p( c0|c@g`xq7B3/WN{9C zțNğy{pn S-EJTY)ML"quM҉mFҠ wN~TCy4,X4-a0_D{X"yv0"UG }`ӄ@JQ*Zc0bbcC 3Gt 9FF†WR[pK~Zq㎉2O  _a?1i8F#x)D8V1(x7 f3,"SZ#,;oJo{pώ6ѷ!X#7%3XI;⯴#ald'wNS [C.vg~)5a”:1z"Y3Z3I=<*~bV !FX*lVH1^VW\ԓ'^6 ~@񊒩T#NXWTٗ wL!0 Mpj<>*9.͸Zc?*ؙs+3mTGJBOƷX#o9d=GAG| 4o) 2%zɴ:P$3g*l Ϣ%vh5܍L8=swYĶ% @ .8鱳ƚɯkc1#Fcb5M((e1+<݁:!BQ:)r8&exPUٛF;"glͣb,ETp$oDMca>4[,zGB.JʉMvNѱo؄ʁ8q ^Alpe_N,%6*ӟ<> *!4wy8€qx|C/x_`3*-~^Hqſ͌J6I'$#}vZ(q؊Z`W:1^ U1Mv/`?pY ^*O㎟ډqmcNmFMwBėe/hK0w F_ΌeCC^B|c,<YI$A v&U@AmqO_?n" 34.? ~+x_hG|eeT/1c~0B?6aQ ϬY׃{Tq+x#3#\"+F| W9q"噶D >Fo+*]QM%N*gWwsy9_>/,Kq rZJL܅4ճr`w"tc ,hx>S@qsOni1QQXpc9FMF蝡`D8QwϑMP4w";%TX.lGbBUQ=8֣ϩܒ+2{&B#aq>B # ujC0N~PNɷƒg:V9WTPޑG (ns37Z$NT;p_}~L:D͟ҘD[wl$ړ +D@ԃfƈ )pmfP`N<;uX7\*x;(F5섑>=[#aFn@Vɓwd<,paF +Xk3 ^+ʒ` j;i\Sd9<5(}Pa6`] Q jQ>5v=qle"艬dR uR\19FsB23FP-l=Ŕ% bx7pzDQG8:vBSy 0 6"´\7PG1Xe`2Ygd8tjҮ:rarhMBˁvdX~B*娴prRL$|H37;إ1]+5&GIߠ{q.t'3Q]>L;z^IpENx%fE6=~ÿqo{i|@qP+q<1 w/ ýxJ3c ԰3ڄvG=I {ΜM<^e\Hh7mPC㠇d'"CF`ցgtt?Cʹug8ÃvCZ3=|gAh19ʪ,]̏AF/V7й}6,3L=`/ aL:q?"^ Ujr ZшM`  yYm:`~c2s;Uc4zPubzi'o9]`*vfp<ѢA7ÿvPlL3dc)rj侐wF| >ݑ=%Kq sݕ":+b;O_ ;("L(;U2'~.)mr^p;A[Z Fc <~Z{"_CÕ:sZ` @̀k+5'|(WRD߰~#ڃc!?nH +e_&?W.}Y+<|s.(q#z/~fpwc(\ Ys=;uO'\1{Z wjVdH4{YѾ@a ,T ga}B=VLwzF%l c? zO?DF10_9#Pe9d L@24k['Cj J7\]Y'aߍ=u,sY§E~6xB<ٹ+IvmΔp!aKc:xY*˴b=pLMu9::70OυY#Al=1tFz+}[#o EsnN~P)7@{݉V,1Cs.$9nM=۝܋Rz~,Gdmf<4V[pa8sQ`R!艃3I<HOI#K qv:;17Gp,l` 6 By88H8wzO2O󜗉#]a;=9=3Ȱiy /BdWUEM(W81>KcsGD݌iZ1UD`/]hA9w81w(g| ӉFa|Ț '.sDU1Vݝqw܍ +7v`Im ڠ~.RiAɣCCNf}ǜc oyIJw) CΕ;>G4 lJz:C;ưc^:YC`9ytzpB3yB&7LN%?p12_\Q9QkGc瘝 2B (*G#ňF'؈$Qds+q*e Wf1p8Eb6zs~.@{ DDJDVJ_(_*qB;=#g\L^Op6z:X{̅>*]<9{QA Buo*|xEhP#%:d\!4 QTuOx%<|S%'2D(aoWz?rpfR=mae5 z=^78ˎ̱盧P?vz<᭰pԆ¿w_mqO_oRD@w ȸek択M ;*SnQ)e܅1Oc/޿'nT. "3A>AӜ8n;4oAJ(&zNO8A!Wg|3z—ҽR GGN'}maYڄL mF c4{\xbqBylЕ!Wvi p=d=p?v\S<Xtx?H߄/ʝG7OBp5놳D°pWh«:cNeҁ 6x^۽Sk14H؈ 1h521yTivgn_Jy͍,S!3f'eri jiP;g6dG%'7_~%{&fjwKtP=59q)ܕ2X<1Y\@SN CA ,F4w=0)8AZ+NPÄS*xWDwB7#veK!f XBFI~4c:ڕ2:#F3 $ߘO#|n3F*u:|qt7Gs&NI_"~4z< 6u}ȁ|lHL@;s#|W)|x\0[b+gj wk%^Vb=K0й9Od{]*1 YPIu$'nDI-}fgȬ6]kc̕` 7AFpyWYCD2 wP䏎;Um'ze  Ό)τĦ'7(~ebǧ!63qF/g͠6Bw&!eDVAEHRo ; .TLf ݁JɍNW7~3,8 EDi|ÿ7ly.J4f&xj\3u6"*^*733UZmeaydZRF}_u¬B8m4aiG`‚޿rgm0SsFo+AjFEa7NXDqGzĉD"'T.ĩU#`: ` =]zuE8 yOՃiD8Ǚh34XG#vP(2ꗀ?X =t7*N|];qdHquN2B+fȕ̄nP] )8qZoC!_:ɱ38̧N^9>w Z*kdt·No:w>q ,J_Bz~ǝ _wL0$Tl$wLO9>Rb% 7 o~___?n}}!tPPx}F')#t];g։ S̈}tYp |lX儿7z:c?.ySpyPk0$2Ly“u ;+6e82kDP OkWA4lnp̄iqC E=ԡVg pib$)ⳣD ¬ߍύ!S'3>]İH+lbX ɑ+}3({ ǁ'ScZh:zBҕ{@::$gv`rg%ʶH*L|2hï+8+Ӎjc{ьvG{ew^9ѿ 2QNE|8q!Dw3vC9}RʯH8C.ix;`B?H9./I}FNH&4,s %OMnT+gjuxDɹણ3j=j}eW=~ q# y|#MK;" Z_f@L3+q:?m 4-KU l'B̟tq5)aFY*no1p-´Ppnn?xN>">Nhd.j:g8.+8slJ 7a!xYy{ύy'4f6Û#'閸L{Q Ldj ٳl2x g&ho˜Vxx~5ޖ*ISdf`3Xv"y\Uyf\htJwV9h\ıAoıY+`+.*%x XHwX{PcA\_"n̬dtكەe2qM<IŌ,F1i`h@pɄ1!0ݾ3#11f|{\2 b/ɄJwo#p3W^(zPF˜h/kF6H# ]"-`OuMb)7RnL*gUI215"G'I??˯eҟbViΣ\EsA“[`ˁs)y GTatBTb{.N,)1fEV}\nW&>?:HPnXz" 3B$fHą1`t9䛡zxf?H~? !>W~h DoG=p+'?#;ބӼQ\7[Ja7RNiS\y80 7^sx1j*t6Yn1:A>&;]'\\hFB?:C{Iyڈ{Qe~`?)B:H,N&JQܽ!OھcD7wcRB+>h 9>@j7i%qL~%O4p, m0ْ̰'Վ;x^| |a8\||_pp8=Cm3BBN=bxGxkFG0)րpc wj9{t]8m>\NwgUR\yn>݄qZ.-yNt 8%}żbNz#r#r2Fz+AgF.Hr6Z[''Q`FQ<6Rd8 FUAqEN4A7%F{百Klp#r4G! aDAm6^(3*%:t?Q$0tb=T =!B'7Ãu !8f H/|er !F xYӀ0gAq2(`}0 N)##Ӏ#~QU1É1:pc~!; 0\-hwGw36>@"-&y9(zF\b}oZRQg%HQ"~>S1#AZ:| (Sh8;3EB鄶Ӎ'=#hkE)x|^tQJmT{ !=B }$&`&B#M|ng#g<;h)#6TZY8+N}$[f|h1b4# vZ8D~[吂K3fT(~_mәFh;gtg6UX~,qМbx$^~ fst*6n8qİP [ =rЗHe|)A_3iF!"(ēr1ђ xl//_yF 's'c=RZ IXA"7(Hw_ސDB3fM }G$vzͬ;{Ր)ǯX=1]&W 3H.VmqWF46V^`p?"sup^hzۓGP9k&YNqa] 'nf'B-gf=[&'ƨ D*sgFuy~, ہ̬[ᕳE2FeJ<ģeg,3GbLwZX~t, #qJFk3 qh3_ҍku<4 Ntόqz;̋1OFlɂO9#-█/ACT0S<u.!ߨ[M߄]3q+L}`=b~CIlF? &­+с Qi##䄻FL8RS6$gPBkL8ax:F7Щ=A?-53]V~(=e<\eE(¨8uf=AB\OsE~&.Fq:l ~p,_NglkN'zHӏF'@R脛ϸfV鄿c}A7eF({`iq~}x}9+nJňѳkg\n "f=;m!Ghw^:~e\ʅ=T< Gհ6y 5C7@ S8=2}8} 7#\V$͈'O3rA͡cBAã;"dHV۠ew9ϋ~ÿ"a hUAa_{e^!D#=};^;D~ThT7*<"rٱ[fP?+RIsdc M~T*̝A2 o&Avۉx3c(=gLy[*\W#h6HyHBv ]D 'ߨN e/vLP PZEBnO,fk~u)f6^'Gr8E"g3n/,L,'.7=Ju;q:@M1FPǟ >1>س%RGE p;/x>}h;2n,xaL_0?#|0oe ey;͘(V,JX! %<ڀџ'ͨQH=/q6j_py |O3҅Ȍy-= XӀpc}dˉ+tJ }#})F!+2NdDz'!HHxfw'8+m'A >HlB-NXDG9l@xf _B_?!!?<-?S;)q.}]Եc\>o<́SWHߍ)WLyT4pCp9oZ(qNwBZ})1`p.?xk Y;qavy}R>ž_3spt"4p3jdZA|* .3+p ױxdx ǒ8p1}No]PjN7>Z/7;hN_yvGkDo0) >%Y9@p\H+b+Ä@3 pm҆ȝ㝑CΤ?S Br|o?h 5,XȍP+{ 'AHƊ2=Ŀf':;2yBQ3, K| L-; = <p~iHW#s'ȗ;nȶZ2n[XccF'Ը6x^x ף1O 鍂Ocu lGpe(n2p-CaAg޸/oq=a+Lt,9"\@ć6ur=`q!}{%,+!UcK;Dvӆμw= -DRΈvYQ"+~en) V|z0F#J^Hu%ƀ^1'p8fd!\G*epyq ŧ U|d!aRRY[YO<8FU Jl0&c. "a6~K0B&o uG%н0Sx  {Io v7ܤi#;5qD|7!g=c!we"QmIHN2e]Y,ş ^kcb&)R XjdfQx -ٲ,1Z9."#2 (8_/PD@A U(d^w3;n } LZq`츳!"81gC>ON^ ZQeD'`y>9ҳẃƿߤâ_o矋񈘀ك >@(鉹=Dg~B蔹CRNeYq7-t~fDh~0N} م#$nW SlߐJB&B;q6h>qOFCN{,!Oaa{^AN92˴R BlIE݁n\|~v<%ߍS:Bɟ2:+m8.;feEF!x&c`eѓCwtsJni16HK+ҹ %oTyD1?UNX}f 9XwttsP߮$6{ugL78Ћg6AFKYו2uzdL'~v:~ww^$H?Bוqqぴ_ϱ%!$SB0 !2}r>+LmzfؠpogDN\XD2i-O4!t%ţhIuЦ qQ>F(nW'?c`dPB3x;s2‰PTe'yR,>т6ڷ}NLFHXi0NC>>5l'jhx=,/E;3Ԙ8]cWujHdP9L x>q%@QyRSc=!L.IZ=K#߭JN#+˳D #ts+H9q oxFJ*VycB urF2(39(DԌ ]nn{ S2vN ۔9`2dCz`Ͱ)CWf#3Ia` 8:WUNWl/s\#(5 x7c3gbNو7 dp^I̔јuМ0Z;#g.'6יP7ebqge M3mt:_h CW&cDT a)3 Ղ*{EeD}PZ} cgt+V)MT |i>w7&=9u37Glϕl`xD`ol‚(Q 7>a-i;"-Ӡ)c'N&uLyej_æcAI Ht ];: 9|3F /3rydq3R !un! [B:%֝gm-kVO3;=H](q2ND=Cl=2:Lpf1A@i -Bd-c_iOɠ5FJP$>%AN!Lm95QpwN}zuZZfw:10/oQ9 /F qM \rPFwp@Uɳ?e5k4_y'AZ!P$'5/dc$v'܆Å'4N7N HNe0GeBO,Kv`QUq Of/)@  igßgZ<r;nt4c{(C cuߕ ~p*`lh-l,*tHL6j`n焜'Ձ;ffkW•)hw`Upd΍= 6c'WC?F2 GLIX#hۨlmSN*ڐZi= gZأ^fx`a߈^~"3}"fuǝJvo W;¦/z~Vd:pk$w2=.੺t}c} غ!wWV}!yic ]1tmu0 Az:'yq^. ]29.)KTfY!F@Uq+0%RC` ZމJ'mg`&zl;|Egb9Q#nq@:ETS.dª /;=^&ϸ{ ޱ3!'bIJBi$ims!&sBҨ `OjXm[ďN;~Q1TdWc90*8keM{aIgğT?<-5C#n?Q`uC-(AԁN;RR.䯨+}B(E -R$"2wҷFdh6WZv'^pW kC\}*uxXİ,FCDшh%(C_ )7O\UлKǹFo$M[!@_tnxQx_IB>!acz1В&짉q4o(_|4>?=P/DgO=tg/lW%CĿOp!P7ܫUϔsQUoEipe!jX5,O/k?6z?q7gGJ)c'8_@vh[|L]iɳՓ!g܋q*qX]s恎LU#=v^<;qsċ6*?'A[㵮xVΘ{%C5e^+>96F1V 7 WQ Ó J]?(' y&[R&*mb';?9Th1 r")`mEAG z%:h4DoRtbJ0^hnj+>ȹA S_i;@($]QJ vTgF|tebJgAvŐzwR.ce=ioA]oع0><5!\ST^?aw1 #FXgQs$CXmWB.ad`T.;٭܉ΰzDd@l6ag\)@Eψ u3;RD)4 qN0<>xr7PcO!oFyz-<·eplTƦU#9fCHaQWL%"24C$?V8Ca(cL/[ƿ q7+=}G7 l?qq@/_9p[vgG5<ΙxV- F#:ޱ)L;{1WN?ScZgPF Y#49S|yCgAꩩ*SSv)kP)u!HИiG2h;h]hB7/ ۟#Rnv|\򴕵7Xm{?.o)v" q\( ,>8V< wҋroL}PoNQÖ#"c}C )>hB;k$4uFZ2F9f%#6lۇۅE3gڰ.Nc µscI BƱ#N?c _M™_+ J;K9h8§흳+ތu(3gɊИx n T7tRfJfzxg i }~/pVn(* *qNZ*ܹ- S册0W|uhJcFǎ0:C*K_szDݣ! } ؽحSlsĞ_KHx? ;D ~ako;X!zO\F~%ܔg52xE\5 M)1+N Zޘ8xcHQtEW&Ч&AwO}e1ゼ o%gW@/ n`Z9`ўsb!۠!+O{?튤d;#bpWa&j}om`>N~7O_-]'BXA^e4 07xP@>B5`ʁLjYw#H\q#Z=uhaϋpJp,)rD4Pt'@!'h`^;K§(a!;19n"쎔9y Z <|!@WV7B [笎I`S/ wpK~cHO+*c4Z{bI8# zb?U47'n!W >۠ΑHatd9Io8\z>1'x6EI|I}#:?=: ɟ+a?K 8lf#"o~*l^($U10$v3|S* حӎO3!'g;^񬊻<Նw N3 %J= ٨N~i7Py46"Ɖ/C}ǿW_8M8ӄ3#6w uLjjϕR su0\&-^_xy]`]#@?=rG|VZgBPM4Z$ʁ✒Kâ#.{\2d+`) 3c(*XClBU\RF7&=+8p H. COj!ʀňu.?υc,\oGW#(977xV_!bw>+y}p6>'O8 p͉snB@֊7aqF'k(3pRĚ譒C3(@>a6k \=M7TvxMԱ6p0rJF05P h\ Dj ex^p7u8H-gOvF`௕Ќ*ʺr;(ž/wB,Қ`= 0Z% %KDc%=N6Qa/mga ΃|E5\r''lB^^I )|""N0FUƙP9m_G{FQ4Tj@y:U w 1v+3oCi#JԸsp 5\ xȴ{^AKvٴ C2^^7Wf%0qROaNʓA( p.R…TOz RU=K%Y79TgŘjWEq D%fe8!{h'&gVGeZIo2L%0:NN{t2A3{&: 9τ"Bw"Ұ.'-FT+Hȅ*;1\Ƚ ݡrfEn+AOvda9vaF6;H,X ;oHmtّ0)t7њgҍf ~|PD|Ư81$>Tgp^h,ߡ -TJ\/< k3yf9q>ߘ.m:`;1.\|gk> _NͿ}ˇ?_& Ԥ|Mcd>3Z2N؂=8pj<:q~滿OXV4EGsǙ'zLїvcęڌ]L /~P| mAo&HI;7ء3᧊ɉҍ2o͘K'G ( ZD(~ri$?kO]ɢϟq=u~c&'Oy",߰{x4R'*Ӓ>(͘B5irQLXucTElFOq}ɗ^Nү]d;%bn;"՞CYJ'u4<G!ß2F.medU x ?UraQ1qX4&z C@˯`$ue錸haWcH@ÆfO<"\4rSœx32J:UlL!`}PzGc7֮.@9E ɴ@._.(>Sq]" Et:MLxjjRnÝ~GzrΊx~ǧϠ0BA ã3 sE^=e">>4Qf4f\_ F@Zziu_)_3Y;ݸFe~֟W|Zh]Py '`I?nav­ o^?Ϣ/_>_ۣ L-W\p ]h鉅CC zi pc@'a^W;KtA1 Fu̗Fc@FST+:>[g2&L7GƵx5 IF9V2z"~YwǸe%@ B(hDFN6vBOd"b;^h_矐IY?q!p# Kz=23DBӃ606n:i*6J"[p#Q;̸#1:p1 ܩNp;MT3ڌFj?aK׊J֙*Wr4Ufv~xc;E*=$ gNg{Ɩt!guW}1O%&cDGF?1''D쁱` j Ʉ6PN|ƹHo#zّ jQq-9;7L2("RT4,K"GNqǁx@#"E (k/tT剗ߐIvotLA`#Ì9b_r˃d 4c^2m7w%2UBv2n`THQT cwNw55{Fk /S#@ ! Vg\`gΥ|.Jt?!q _/fG|x¢o'E#r9 SSkz렞0w:̈́9%пXnx*\l]3xV x6K\o)56f)ӎn\I=Iه'7D0񔂖@F@gOL5ᩐ+)$@3"P܄>&Xgb -D遳 ~T B!/"VѾmHARO{gtKh9XyON|^cFЈ<437*_tpL=i$SDN#1(m2*}ǩg0Uڰ?|lJ82/9c!plqGڛ^0笕arS/^:ja'Ծ\Dmx;#nhgc 87?~~ka}e"'G +U84K `Qim͡ᅙ̼,Ĺ6 ,Q_q;*o D/"UT!SXlAŁwLqnph8Ε&]al wPuUxU6XsA ;1!S=R0= -,Poy5VdW8(4z˂zϢO}qR\!#qAZa8GO z9aGcLYgι\m BwoHȔ3Mؐq3LZ^npC2wss 1Az +Xb8 Fo#aaL˄kcwG2`Dzktc,3'jLLW}c DxvDJ5>?Η'‡Ƌp^(2"{)@Rq20P"_:Zo zW-ZBT9aޘnb7"Ra|g멌$QHGe٠H` hr*X(% ab;Ri%>n56N3 }xd8\䣧 GVa'gUIV/aSYiR`Cݠ5EiUp) oƶx#o{G~P'AC]KDuXw at Mc~8d2B)  WhxuóOC\(2В f8wa^#!"\d0qH0z1V7SƀXi G|lfh:&> 51 \ԖXLgPKiKwPSձQ%ZB7h}} J􁱁Ips$(Rb JXpm솏Թ@t(<С# :^:Z`Raҕ@pvգgOxۄƢe໒,:QãӄH,;sq o 3'd&Jv-@1s@)y4xXs4Q8 +a߹ $&0c9SW8LN;Z_pRpgP5.6o4ŐzulÓA+p=8c#f!>fP+5"`\lxxQ*b~Ggg~>8:ep:l-A?,L?7N* A MD/0؏7d?pg-k'T(G%Z!hoXOpQTEc%10Eh_XM2V> LR 7$勇Gt 8uJOߤE_>~o?|/Ǐ[@";[p3 eF]FBiet2#2O+}OO*=`*2Y 1Lcɤ?pԓ>lJ=OofY䅷IcwD:$ `8ю^Tԙ;\KBL;ڌYnTډ׌ƀʅr٩!g$$L8Ӊm;~@zeyF`$ïHΨZPμt jI+`aTs5)PSls#5d@Od w!vX<'~ Lڔs~%Jc>fH/A3ۡ\-6(QqQ+íAphx Bd4xɧu}RNJu",՝TfZ$r PVN@!zܽSSO !gx͆ p iT؀vCsk?5nµ"zOls÷ꌳ7pW\R.Rt TZL)y؛1ώs1Zb`Y؇bb˜Op4:4Nug/V:nҹѥQE~ FNN>X%) y$}D JçD{ K 2WaflhFS-@8AN}2!H7q.J*1Bсz3 x̔0Xn@q!Ho4A:ûx3;yLvb0x"!l0R$IyVuĤɤ3k螶&'rrT~] 4`_p?׀3ۅf42oL&FhoNN^-Tk,mNЂRܔ}2D G뎶+e=qYU@ 1)Ձ: Ќ+.\QHmCVEF'\Ļ9'` :nbJu2(ALcN+ c4@\c 3QNZ&D2"uRh{>'RB+Fe,3Wa b'^XA~3lb̗–q2 k_X %pVA\<pC>!_.Dg~7H㢷/G r~i?'I` c#@ QIΟ3q2] Ұ"ߨ6a) 'E' Tܐz.3- 4h$R8;6f$Nj/l1 ;N w~hZqhmԗuJUfAFZ M' O aI]h6>Cwb3wf~l_ip9?o"72`ՉGI_1ho0W* 791+EF>gO W@D9FJ%Qpۅ>ATp9C!aš/dU2;H8h w$6 УA>ay:)@Dp2єL}P/X!FO;GHV$LJ}r4e `Иsd?r5 ;~\8Ϛ uu_1}1:uZL.WA "5vE08qd2%ẉP2eH1H,mI]&'.N8όgn-?sPl XL*EE#_xJJ]7$1s'WN +&z31@ VG%SY)B˝:FB V*] I:&8ЋgH&pd/efz鬱QJk`FjG9rʇ>s.FWYp$G뼺u$Tiapr-ځ~J5u1N[ĩYO4 ",{@r/n<ă<TI#!S|![,7 1cp$\i{`dL] O(= 1eȪ+ j,t;gHPO=OLFxҵӲ DYJYPb7JRܞ@z#n<º렶20I8~5e:urH!rPug prRaYe܂i >6< ){f- S%zΉ7l0G0Xg hH 6rRga=`g S2f}N~b\?Lo/˘>ૃP~E"! mxbĻBĹ):F ѺC]gFtp,K'/^q6:% 5'BA|L鍑~h ?T9|Ǧr9I;=Q0hyw)^#DSF7gzEn@ q𴝥֯+r9)y/6㊅o^ٜ'^;zհ rҤLY>]aFvG3uƹ2Ž\c!UmyRp#G\7*Nt}@q A__&Qգ렇 ]&:@o,x6ׂ{u9a,Ήf{@F]ƥ%7xҎ8GLg6l3L4spR{F+/ cugzD[9syD\j|N/7`RM `n1IJFWC:)9Zq(^< 'ObO`4?1D59 /Zh=R-2F) /1OGGl8&"x&4~>6.N.wt$ 7Ouj~:&oF2.Sa 1pW\;26QksXx;A\ǩBPBN:ߦƚ6,ゔީK|H"kN7Jc~p`cxa¤}+,!QZG%HNnQK&p!_ᳰ;ylEܙyLiBT)S7>{סD0_8mWڇV<Eځ]pmRx/I ZgʀaF,#PژK3w*Ž@~k'Sg]E+L4 ~WLq0F==sI ?⦂A :fZ)`+51 KDiL_.Q#;"yj'8>zo:2F 0?gbV&INuf3}UmdF?~9#U`UO)}dX%蝚 TEp3u̸+̈́Jofd3C#'2oI WiVykBaT2ʴ4R}(򢄻.+YRA`F/3ܹ_ӺJr+pa6uL=Qv5z1tOx;O/zYI ?#m"<<@ӎsHz邋_i8*ciHhq8# \ P 7 PP q^ 9v4bTg|HTO҈TTANhC ̝8fR пV;dQy(uY}?0B KhD :QObcsů5F)Sfcz>&4 'q3V3tQxq`ʔYo:tX82]8C0J dk9PNυґiBj5Jegx,]8/'߰tH @͎Z)_=ґK@ x77.v}x /3#6++g|#};w86ChøH]vM0!0;G1O}*46{Vƙ0WLsf;;^?z G6sp3>Q흏x iY9oDK-;뾵ۿ̓6U`@H+ږ%YMY Pui2#ov<::#6.y+yppm-"_(ۈ-a<Ñ)!x9gnc̃/,:CxoYM<ycJF' cY:1/ica' `JІ!уΐ%OJ k)H#8JLL$sHdqqd*R ̗Φ9>nxٕve^р1JDnEgB4mACb:Hn-Bv6E*Jg҅v` /6NbSdhIwvyʏ!FNfs!2*"tPC| X\IeJsl5 NI;g\SֹiDGbJwd' B3BwÉ"+Px<0O$}0$rZbE$P$2צ33 6 $[+~N<=whoe"oj9%В#)XdEV*S۠$nMגx*.d)B|%-Vܑ8F ]4j(\]C7J]`Ɵ'ZIg2#sai~MgB> 93*\pa$Ly!Fw^d 5MQy8+0` G%62= XfxL,op2+{P OTdLb'7:gltUT==*K GRak9c" Y# %vJ+@ ONՑ^ ҅hF`7蝑ҟ8Yo "I;<>/,_QCԨ1Ĉ+>Qn ]2a cD#>#{Dq ,y,m#>W$vp ɈkH<0. &!?s8g&amDxȧI+y9E #CǹDsWJ]@BU# | y[å#ؿ_B,!Q0Jc'V1AgX>!^ك5GsB:g.L5sёL 6D1}A^6iI=l}lxUbMS x'X]B2\|do͐Qzd)xmwzޙ-P%ӨzO=gۓǣ3 GVl /DyeAu샑g|&CzC4Т`?K$ I#Ee`jI&Xc ["ތ;?2񘍩ZI*ho;$6?i5A"y_3qE gGAKY-QkD]Gt\hq\"eTS\pusI%bs҉TCe.IA̠ψdSBKőHu抗8 OO2OB.>B+ ;vHWhWqc̅i62~ L~@~=v"q~0>9v2nWԎVgJNΉQ@BER``3;hRֹDySt^8xkƤZ=ꃘd|r0//goM'a ANzw' F#BxA֊|x4Dt"͝p·{b8?ژ pQ11#|V+ i12>A`] (iG}Q8:A791~}Mdэ0Tca@=-#Da;.8L=+A^BgB3GKeƒ$pfhjLJ=\PlXMthK@B XftZ36\6DݡD~Hq'!53Twl\_QH\+|c|zH'w12rEhipã 9\L>SMhϊ8W›P1@;`3nM;&JL ܁ R=!L< ZWvژ#"‹2f$Ƽ3&䶒Im >bAp3N3B%A''*+ z@ %xǷ6w^Uiv5Af8I JφyO8:aj9GG; k2ĮX ?fN3ZH KH A'oqtԩ4Ncta`w .9B1pw8"W,8'ݰfAݝ|DJ@:BVE$^Ձ?f$wDF_ ܡT+ SoUڤ̇bU+|S*Z314TMhpfT6:⌁NDŽI:݄%No+/I7}"lGNqw7Oh{uݿoUo$(C ixbXIc>ώg,ߡۂ+IM6Zӓf/قNaHR\PoT*q[o?pú6$sB)nhLd -HޱMVߨgf8G l);鲠ߞBTcퟹ> pA#&GlShTe!Ox3F|ڄ'`Eӟe@ꕓ"-ψ{A"2DϢӅr\7Ԗ0(vK$7 ?1vAA8񆹯H)n0Ailyp>R*}#fCcWqkD>(yVt/$**S43|w¸}'>fZB@mǹL&FhH82xE?2ZW}"K"h;Z6b[ԋgj2AxC_7+sdXaد4;K_o'JfkNŔcrď;KE\8vf⣧Ƨmfq C <$< >8l U;:/ +XV%J0~R',9!CÝo82b IS'X!DuS>dz3331,q6 2vӊ_ql甑=@ UmYrP@Af u~ĩsw gjRDmeJ3y(̕V=џ}?I8f:8ԅa1q N^)% #K@qq/&hDS`mK]p =qz!sÈ6lTmyǷ V2Mch8ݿlGWpゆ$HXiA?W,3/-&G P  inV"ӭpXh]&N,23f t9>)9t#6'Zkd_" i? 3̡&ZO_!Z*0{AG00hFEd2 !>)lq%#=&:a-J *h +lzA]uy_qz['4G4_(Kav|%"s#ˍ]p>>N#SEŔ}fbM6XƂ~*wO=:e=@=tψ Flxm偫In$GD/W 1Kq!0S7<1TtmdqϐH5/3ٝhi/3RŎVZlܤr uOBϨDB1ƃ,L?~ys =CcQٿ.$݀ sebǍ nRF < ~PF!+8y8bh]q biHsz* [$ yzce'HWGу_qmPc΃ }=&disGG&$=w̃б=cWݑcf |kJZ*]/L?&`c WD:>ΈF&kBߐ 1 \L6hAG>'|\l \9^)ɕ둽9q#[g2$tڈ9Dx)b(f0XS[/L:]-rx,нg1ڧ. +v zpWFUΥ}fҕߕ1{n$FNhh{^8)C'ܦxg]:Ћ!- 㙑ظ"t\x{ 4ice <}DL9a*x y?SܣgF!GGW6RfP5154M\^DG"w"69lq0ǯu/Q%$2_'…'Y_+\0!a9x y`p$z[ <FsL_e0 k!g$UjqBBH` k0?ە>}dZ4ni.,u Fox:!F06}b55k\wCAЌ9t'"_8 cp(Bĵzh(6,:- sKYN}c~3#)D UV#h2ؑiJꞭ΄bm@WAL9> =W&|,K'aB}}x\~o3}ĵz$B++NDǚ/÷ [=HF*>ziy!B?81, V ӌ:,tF]k <IJ8>"Ҙ`} $..)U>P'Kwu %`rG:˓3>.8t}^q$V@aD̟yWHMh"11!p%"0Ch c+&\G÷_IxLO ~0[FL!fGuJd(OꠇJ [6P0ڒ`(S6ĝ͂; @ʬ>:DBW^\=M $Al'*u-H Wr޷O> rB+L;Q"_0^#߲R]9n;&KW{wIѹe!Q=Dzd=-VƟK5%uHL. r Or-\gĩw4#>&9cliPxk;=`Ցq?q@Ak":~0b FNܼVyqnj<ēH^*gW4fѱ"<i;m}"'Nso犝5(:yRQC IÑEHnNtNfp@sB G1üa U$0A3At 52Ai4HRh W2aIxڄmzm 䅑vbɔsM7 DDp)87}[ 5pMp~GS/;,}˄40Bd@ j fݠB{# myeZnX@cDӃ0p<.p>YJ([CD_ :r*|.2DH L䨕馜[&. /ЩJ\ZVN3):U)>:^x^=piϕ5tFQg|g"Ӭr3\!5>ѻ 8wjYeq93 D1M]+SyVīy dI.U`ae|r(|J95 =9r.LFA­ڇcN$M[,XWoZ[ do>L='1yFL's|¤K(egz$X"O`%T>DΒ(}ĉC=:2e;'a"PS Ke/)Dg@vt .1{nH3z\署8783.H<:¾(~qfO2>߉}A{hzτ %:!T +.8>NLl<΁ffJJVaȉ+%0v:{05)ϰB3')pQ0!Nf,}'Xs8 g=lDHNXz`GE6,ީC6\y`HS8 ؓ?7tR:#RZDB'2.;H)츲c< wT"SR* HpP'l@u0i*:wơnBK<͓KR.y&NZFNwe/_n_Uw740"CXhǍ4aH),h*x&|ęt0υoAHCɵsƌeOw? 8ׯj SBφ,<i"Zq O9ڀ)+ gV~#mՅB4<_ M/!gF DY?UXY\ m&'e~ݓn fӬ"4^a_6F4RLs .?gdy%L!.'tz_qG^\sk3J^9K@H^Ǔӓ㪃%a~}!9w{/B݄@}!0v4,nS1hvZ'Akj/[}XOXiMY#;9Y=.Fm"ƅ6L6M܉gB9IZp^7\ B =s=(HK'1O?/.\ 2}VA_*i>q}lH3qu}]Hgn 1=)Kd381 <zxm)z9gF8I)R8ΚɋN 6d(>Dh@{OH\Ir/!OA@@띿"ޟ4xk3ӝLF1ax2F3KOx9>M S~{,|:y8babhe ΛϚ ϯz9G?/ٱta nLhxRrĵVYu8W?}IXf$?_#28Qy%gz">'.KpW#TLOA |Ye,3V7 =:07sة`u㏯O-R9&V<fO/?_BW1: r|=Pxvϔpbf8-xwsâ%6-[M*P?A w30wEӆCi%M 7; SR XǯO2 qC/;Z*!z-wD<8&Jw8# y5UvtA>sD鏙/Է ]Ndy#> ?nD[%ߐ5f}AH H3܏dP?cj栘^F3Vt4ا̤;#+fyV#ƀ`pA}^F{:Ul$\7zh(27c~E1*c6#b|5YxB\!ZBB[Sswoc:Yc%`A]2q бND~oo 9 !26 ds  =ˁ#ҞκuA eC=OAy|-r+fʂ\>vý̴؀$M`hU_QqvQ3BBpAN%jQwF;OkG$\N`2618km. '?VmHRGZ#UH}0BE=A3]ܱ>]+q_Oܶ.FuxB7ϧb{#j,3ߔys& < )Ll8;:r8|OFsO,6-4&Kј<+whfuֱq&8P%Ɨz0wB9! -yֲ#1@#c¥8E1AN09{9SJڡH_N,8ঢ়VڦI-l u4AЮ*/Olh(|c|K 5G6N 7 1;DD -_ e&9^CX'fB|촱%$I\#z#CA$ƣ{o/1#2Ai}w_מVfyŸ;U*q Q7Bxo#0A/sul 8!)p_@(:=_s0qO^p" >+4xaOߌ@%SJh- vR gX3p1A(r?`Dduz\(+-ح T)ȏdxaLSrdF2LD0}{7.-v jL 5E}CCD/P`"P*[5#O $oꔹ9v/Vh=X̷D- ;rZʍ4!UE\3qu:¾"H L8p-HaۃU'tЂ|a]=9l|||mD:cH!5=[d퉾΅p}?. i~CLDq-Sw&Q 8:MQ?uIqHq8\'j`=e`J"!61к%qYf<*Ոό^SJrJ̔k'89%TgˆvRNz=,xgY?!N|.L`$(vܒ gA<16O q6% 9W'?3Lb,q]l׆2Wd35f셽%HP%}†|H:+C|A5|T-8w~NdGC΃'ElT^y,)#XE"Wx},fWzdvx>Nuބ e;z~]gLq"Ng@LZI.I[Hpos8\b'=X}6e|Sfj3E k 53+̍A=or5N#NH#DCԄi7a1c$,np8iηn45d}tey L+ xO(qF::xOj Ż.c#2?< uPAmPs?#=Cd8{"qF:g'ְ݈B;0/~hW#q:=RF#)cf46# Á/Iq UEA^SHЎnHxS8bg1vr1Kp+vB>0XSpXB)bu7mO23U@vG@|Yt7Fh-$7zp:1`'D8c@UQ ';* ˟@vSz!I`DМJFZğTftGFvIlՑ{㹝zX>#A#㜡 ρLNppMĊ#:1*^f~_A W:g4a+9xyVwpxX- t ص@Kf2я|q0N8})FlG.`~oX=> Hv$Fc0pZי0w\thz˓ 5{9!Zx:<:X(Ћa2QBEb)b)5NOFmB>B~ )Z_)B 'yfT1AO<9}Dr茁gY2nU]-+Ώ!wX:l'?w=烯EB2Q8AGGXz=#"ty|4'78eO<? ǗX֌v#&ܴ-Sb[EC̿\80Baz.1AccO'eH㶕*fP&z[c3$TFɓ SPjWZ|i5|⮍Y<:Nt(+JjHp,׎SJ /) IhsjYD13ǁ R'jits$/4#?}oq ю]/9uѵ0H#57eE~ike 7'FnT:w/ P8:p;~ˌ?aEMq3|3߽ytH'.+*_Kg_Td&$): pC2zLDzDN 覜y]eã- 0ZLKXfG&]ۏ{n~Vt&5$ 1WM %^]i,y[h>٧N/i@kFLQߡXponHy]{t{ }TiY)ª*WrPV\qhu_  }#OƝg$\i-YwT_ [b8WzFH@/mA.;Yh?@o̜|G0A3` Y j'Q"TEbG2hw$fH=2}"v2q$B-ΎIOpLYfDŽdO:szW za&5ϐ[8LO$^.aS>Ӹ ,Rq%SGr l=]%l9<2eF8M/A?a^qvo^qnN% 'U{2WlGLW uGB x|kcF^:9hBX9gl7R~LMF?Lxߙ3bPKɄO( -"E(2YJ/L@ΩW$ڍ y >*y~'L| _p2AȄM=8yg # Wie]LjFO'2xM ;O^&sg% er}E"w5aۀs\cQLX0"\„O _:~C}Ĝ0I2SdERVT#ΊBGqϜKgJBf4ܹ3xeC:~?%œj D;exqm Ir:eRI1q9q=~bT`,c`-D7| AG8-p*ֱoH*EǟYGO)sfeQOa0wӓEh行Y,4jD`j+ÃTz/8pu`z8)00x0R8:/pҚcVA{~܌^vb}uU9Rc+^ +clި &ZP#ɷH~Z3?~_ݿ?s=OxaGdP'7a  O|7?9|&JnSh6"TM,X8< 9A~EuW3EZ|c+=8 DчP#;{^p%26jv #PLt@{[I(+cx.!8rp5-\[Đֺp> ~p䅠'per'2+'=eKHj*4!ho &eO3${! @r'+nN@zb xd?L3"ewIH^i@%l¦qyLA>3LQ9NNB._+m$GdzZ b(t>0U2]71+:%7YkѨp;N *8w|951WZ{2ٍ?HSA?_vuf,V2 -!n+kudɸ$jaֈN }b'}lcaqRDîԣ^ؿpOwe|td z[dbe7^ye3,pV6H3.G.'>B#ǦL1=>O;sw6OH5/T0tM\kc\R &xfS38ӳͱXȫjeQ~t n5bv`IAx8ekl! 55F3;&7HN&`b`SR ]pZޔ}x|o&ThOw#Q gt&ddɤƘ$U3]$Θ -Qocw\gXF 7dITv5vN~_nq(щ>-m@o8ԩZ;3{uuuƕ&C>b,(`ĎJ =p yCOF=( + cC'v;Fj\b%:]b&;si#QɟDiCe4ż1iP+Co2S!s.1yxνy^x ١ s=O w o ,0<̹Cc؂tN B"󑌹L e# .00tq(JiFY9~pSiEIIqa8 <‰wBPO=c2GphF4•^*z=*<39HePr}F?3&rώוh`'&Wwt%ʼnD/qOs/ wjL%2^Ojm/# C)ƌza'+k@Éy=+Ze,CBRs xw, y%/"Tѧ|1f4\hgǓſP;j ?u`|ݍ*=E$ BJllYo/ϝ8%:gc!gaI+**EGA'*8-̃~bq}" ˕p)1nJ}VO/ h7ڻ9W)˵q6V!&/Z_ܯ̬ 4!M8{fLW;MUK '9C v8vl\cB^n6@"!8,T$$9֌4"] 21a$|>:ᥳo%|r %="|w҅aڹ!1G}qc3j$O!jt4JuLpO<6Bʦ'oPx<[gVjIBIخ-ж[;EgFme~Lƹ+6x&|>qP ;NX<5,|AQc;:J1R7r Rh6H6EkŁӅ+N3x]>voN?'bt y ~|q'H.J \T&l~0ʼnϐX>w!*m~ohB-rQܑжA;O.}NpƑqShsjB'=ց1< FLhHtbb.Uzs\|cB! 53z<#!3F{RDG5ADLh%. oo~LŽ|oJFώDr2H<$׌~r;cΣz@wxs3b.5[L\"ތJ=SSj̈w(NBhǝ;~8NC_YxuƻiǡS|@LpO9Hu!nZ8k% s!gUfq %79|zvW2pD>FNLNwD_oˎW.@mP `XyPn$=cJ0G }BvPLl+aߌ6;F !w -cLߔF$:N:,ӟ RćDvljq;Ҍ&Td\>7n3C9H{*;:BZQw0_ RdrFu6ǐ~mڐh7BzcЌ2#N<Ӑ {aҊOW{ 0Pi яwZ! bTc'~Oe P r;"WS}a;Qh'QT⸰μJI+C71:0GG4p9道F/'> lqf9EҞ¡zVx^4e/}3NQU>ƓkqXn(F@T3U ~^8seI$t`zƶi&RRE~t"Zf>^xw"KW ҘQDΥy two Quƨ#9p85C2Q pm`RWj8HpDƣ5Ǔ4fVoC! x>!wK~-v&Qu4Vzdgàw`X['QW\<e^lpm,]͸4H V;qRjV*2 kM+yI7y4_ p O·"wF{}t HA9Sb坱:$*,7g<~7A9;tb")Bҩ zb}B+,H+Gza/D1o3:<=;zv Ff:`%A/,Q\bdCaGkb>P0_8Z/vl*|Ȃ9:~ONޤF鍬T̺#Ȧ,9'Eye;{}/υpq{e/Od{cLƺU~!JAd-AO D#aCҿ(!EC'E_[N3e3fq!,Q1c@ʨpz| PQQ?=`ۍ!\'SJ *(?~d'#DIot$.V5p/ B\rWh=#ӓpy\ؖhD}+XBa-1]?0ڕ%\=c| ej"S+ \29.ф=qd$,= 2U &Tu2qTQ0;֙5\vx4nE>j/hn`]NdK0┣2`t)&rp.&A\ ’y8s 1+^K0.8h%P@q D2 EG{Nu39&3scO,R9+8eTF+؜ m?֝ 1;5cpgg< Tԡ π[`<v=+%Zl͜K$'ŜB/ tzIf=4̄Cqu7'Wma4Dr)dDD7[+i' :5(2ɞ` ,fvA Jt:.*~ rJ?2#1',. P [G偓A1  ;fJ\}#{Y]kB/ԗwRF掮 N,gN_}l>WܼJ]@?A"2vr?YKKp;`?wty!NpNX/^yOD#jb dGvz]_hJXGE]% 2^h'3RvJY;A4GFIہf46Z0YعD :ŌƀSN.!e.3/jd90SqYQQd28Hn1c`"[/1pIsBi$H #6119Q7x"F'5RrR[m W L[VN F'iT.|+9/xh}Ьh=D1*XU N@h#2U l(k5s3s?C5v.2d3WwyFx~{\r:x=!iB1 ~&M@JQ`RGՂAϬB. N`4]8qs E_ ,T+\Q0Z D*}ԽbDN̝5sxe ! CB EijqƎۭPm-/|&=:ӥvŊC(M;Ӯ.6BrPz x-$q$K|pv41\ GpB؆# (ąTyә5%ts5_qȏ;:8<<(FQ{&$X5!]2ZWZ;3?#7 @Yۯ${#~0ubg &y<И>z6ILV^:ߑcvJ#4q7ፔ }\gFF_vsl [ѹ`yBF)' ]ׂwxY);eg2O+}sHg&|F A0Gߌn>To걘h}`` 4hwxi}/Ln =hۄN;݃l0/O:|Aohz 'k \ N";nyCAo+Kșqvү8*q;!fOȔCqk \ӌU}-N_!0| tRp/鷯;NW0<.T7*A5v|S榸>H\ ܾ IWd CO2'c==/%񳧸y W1Q/RO8R i&=$t&,,t bvv.^(*s::TBW|0'۹j;&7(ȧ II2v&0}!;hJQv5R0zgǨ|E:H2DȂB?˂ mָke֓+vb!ׁM߸-Www?3 x'X~ J) =yŴ˃g0p[Aţs?¦wpe<S'DcIh_i`ݸq(mgSnjҝhwE/ܨLKD(O|Ht]ImƯYC7G ɴL>6o};TE=r){`^ sVx=Ė61Vrr|lo^1<#`1j(8SrzX$D)Xˉԉ`2pcn3e ^K㬑E"tc`pM EA;gV4ltݩ#њKb~„xD9HY} Za9F:"].8$(u >c6pYRemp\]yODpc޹^:G^77h W*v(uzPқ09cQ_<0eS wP((etvQzDuPCH͉*9>2W\*y\9xU5Gъ m ~#BؾR~G?{1Cl`pՐn _ Fq,_%sm$|DKBe31q3$p R*+䩮J#ibz OX#48&F\ϓOb{þu91ZcnubzEȭc`]b`4'Pc*~DDC'=:urb4z1{_o/? %2}mMp]hCp@ff9#SLynրX'* ggȸ`!/Y <ՠZizo=`4T=’{cĄ^WBVifȤTY^;}L/‡t. C~C#9 sp'M*|~K`+[ Q8'%FHDEi6Gw Kl ̒+qNQi Nq䩓15{eJgRh ׋POA'zJlqډ/La+#?*LݨNY9Ies-ypZ'  bcPDjt'hVr6QIلT 7qV AE1#"_?7㞉h;ΐI~q16 C_>pc= o</>їމ 4=nɣK^?E9 3N"V+- ttkt32 %;/dcE\b~,`ꩲ0ʰSSóׁP>aAiݓ^WTV t h-x1 L>^Ɛθ]GR>B'&K}|ci1_VjcbTOnrcV㔁m2pWbgDj<oxYɱ{B v* `Mǂ u)rJ 3ya"4q,b!t9!:b Tf{'|Dm &ƴ⟃|zt0?pw) g8dYaჸ/JEB/Z} Ex65O7jw\-p"%zlL[~]yq047R?;FT";Z@ ˇ~6ac0o6_#F4dS>@z5ƱA~0_g}6Q&І r`=^:;qyc¤sFe|!$ xKu~552j2ʹ:pOG^X!M ϰLL_inOFŭ>$O9^?IOqٕ,E'W0;͙Tt#{^1qzƩ4Xz`s;׆BX.A\xj0Ƅo>LA3NZgṊ/3J+ax,iD A<iܝaD^Ϣv^we9:_)A:9tq}q/S )ˊ6:/5ǁW5GB5(:uc@;0cT:!Gd^#~>I'M2a PP\>\Gلds09"$Q6pSǺC>a@[5|vf!lOrwp_?{cf: g7.GWba7DI\f;%2r<O^8c8OڠO;h{RG(Onإ3#u0q/U&h'-8Zȸm9J[rL9Q[dž2ƊOr؁7pwK"ЖA?VL\0]Q6?<9r"nA!s}?,_{.$'$Vg"-v@=7J0R& W}B{E|wr 5O%Ş(#C$ tk+DmsΣ|r/7 f4b 7Xz効F…vC&Vօ E8v 8 jY~༧ !ރEO|\`Kȷe<߸y *S tq ;~DT>l섥 ]i5Xc}»0pyIeA~ωДˋ=r] * 8*{ǿ54z'W )E*P4^ e45 fÝ?}P1 }V0{Ƣas ] ^:zy<9N‘gWVB<`\ /do>`o^;鮴pK$T~ARo.%d*^#md7F:\o⠿ .j Oa]>{4+oXe!cgVBqb3SFqP6r5֘ zo{r`DI57h+mBxG=HsB;N`-_7* 3<) z HV38p$X}/zѳp6p 6)홉S{+?負Z? cx:^u0|qe: ?G5|Rz§?g;yJ{[]A6P("H1ԮT~F&,Fلj v\B-wʹp"_3\=#IJQAVP9;DZ wk#;hs|r%I{|#C]oYq.;2cbSCCc†GR–ߔF Tg$XG n 0uh[ݠd2XQ7A2ft'Y;;yD\s䡤*83 |*f'd#Yg0錠3RFG]& i; =DhkgW!%EbnP dJv\\_Sq@ 4D+ ןϔ<cLqI8W>(L{bk;;)_%Δa)\j3"0bZkF+xQ 2`ӗ M足ueq?z~۔fT;#̈́I g3^-28: LTNotP (|<$:*D6X7U f<_g# (rBV4 dFSZq|~pY+1gl@O Z!<e|vU POJ2G"Ht4yhG'@ӂl VIt*m;\ iuOOs g QG %Ҵ2>_8߾3p'҇#_;'tډv";p@w#^GML/>413|1?{&ބ8@g W4ӟ_!B~{RFNzMP;y<eب,Fv=Ou|hU2ۈt>K'{O}:G>1Pm)Ѫ2?3eڊ1Ak ?Oo?˯E+UZ qd2bFp}2#"cK0)˓kSj+gb~M` k%dBy 렙΁$Б]$UO󑦅'[8d&^`75O.6Fu g:_IXcDoT.+(3?7\,lgz⎙ܧd 19:ƨj;|L'* k]/+OtIyN O@7^=:cĬ`ánகvv銖A z։B@+;#Wܤ'Yp&O\BW豑fh3FءH eѥao>rc\P@b$ly(MX19Sd''`D;鋧~zd~4ֽR/+=ܹLDrH#Lʎ6ubu+P7J8s֩. 7n/h9JFyb` 4fl|3EĂ eVwX&ѷhȈ8&2`Y8[deC3EHuP9/R2n N-<7vxmDdٽ1D2⋣nN| +L蒈S<3ǚ6Xx5MMX8zc;X3߹?p&ՅB%x% ;;:sq'rxv1bl?R{e+g.,I۝rx⑈3zex@Xx0SkyCw>vKQn@G&O Kp_wl┤ Ќ7x"@>Q[9<>g{\( eLѡӓ?=ѧ7J&|vn'cspN1Ȏȅ:h Ǔyu {DEgK;%{;6o\|% G ȊÇ{7 p7&,1XsNN@k- 1s8ùwd3%֔D&OB(}7B_AV~ 3w>Ω3+팹)2S`iwB%? ی RN%3`~~ߋzǿ/j%G<^^:&W~2+~#. xcuCQ;6>$ZX[bLM˂;DQ|Ld/bH( 7O^2=wFY ur8 kDA|6 "?uD \I0(bH}[1_` f賧XV hOp%'z.h|0v ۉ“ap=2لr0φՌ.@ x3ҋb@#`{;`Ot Q<܄ %I!|[qZiaklx:q6| ' -# 'Ȁ |ۙOk'(:;&qxy wh+$e))`';6ZH@&}rAlLh &N07}"7&#s2G&J:m$LGuFY{a$tnBSqz$( ,;‡9U 晘(DLjz2<Ȕ IL x<:%\2B;)>sʯazp~pGc!ʻ~RǴ&~=n?ЯO4m8U !-ӈL@0r4z2O;]Bp veNq+ĮIpx0D%~0d"{Pfgh3|>nfL=D8f)2_ v)DUZ͌Xgp|` 9EX٫\&JdQxwKʆqD p4x~[T5{cYq=xݡS!mAE= k ΁1Pzq28F3S\ W"\Rdx=d3FJBB7pƅ4:5R,}GȒY{ Lک(w ?ю'af )–"<(23&3aiB]lY< i 'fMJv5 V~ -д+*C }tLox]ئQ?(/߈#N(c!g}%،o_QD7h s;EtP`Ws`l'd,0Lx}AJzS\ˌKǛrGr(H)m /L:m90VTWb'JU >r5X'#Izr*v8 .`|$*;?oeO_j+%$bH3,8tc;B^#:T \1Nx=,͡BJ=LnsA$Rn"GC;8gI![0?]htڛMIPb"Xq7FwE)WdxPwklSIpؿ./w oˎ+>H[p9lw Q B_i.5l(NL(!,N(,#=&ΣI1F ^3I~nM_&4\>C¬m0}-E1qPtE @FDCPĔh&sg]u8dnFB~@j;)݃nXT."5>X3Y R) (C98.s[N6{ėr i./H)恕H o#v9,(|8Xg|}CeY$,'qF0Ed"+8nNE..2G*4ڎ~ۨ=ZN:gpD'D:9 c+ %pQq-bͳϙ:y?'tP[$Cb68>.LcPJ'?Ya? K"50C=UI| 8nvT,4N &TUØ=?uĭ1dbL .AmJ3(SrhΈaA;RfT;Kg ,`*譑Ȕ>q'%;v|'Y a etw3w.wTA*i ՄJR2ZX5" _qR(#d> l%f\gzz3sb @F^(oyƗtsYRp.bu%`0qpţA+\Y>3In ~__~oUoRts $Be oBw q^&TA}@QMq>˃hl'Ofs@F#9K=%fJ#%>`J GG[ D>ujlмL6( %Seq5µ#tA EqN|<cHgK/3'~ 'Bk4cՙ|pb?|{+/:Ӧ63~ĂPD/GCS"VeG I 0FJ9Bٕvn 'wºb!rO-2X& 5N WK?!79L'5%\Q,,Jqρ9*E'.3kyP2eGyQ=ru搁QWރ s&/ebC zpi DHZah2\p@s>pƶ1›,$n礄F@ UaD&L@ȅ(5A3 te2Sʠ0Nw+P0*9$]1 '[mP\Bc5Ȓ٭Hs~K}n}Ņsw F%@c,7lCG"wZόHtw/h7qˁ =0MiptGt(:.<*#T!w3K(+<x'LCOxIߐf Qy5,@hjv N7e'܋wD='UvD9M-~Qrŝ Ɍ]aR&Apf u qN|0UOa!w,W 3nU54B0'z\h#L6ސk<4 -SDD,Bf oɱzE ciB_Ka.ƣd&L,gI^#n䩠Eіpӄp Ξ{HtrExG~؞ ̞v g?5)vx4sGc9QPi'>ḩCZFXWέr[M\ gƂ!kG?"q(+aNdzDZʾx3ĈtFf4Pː=#v_>rTJW9e ðְ &`11`аR#: B"611bP/}&xx,J GJ/ + +KilN|1\zv6WON ĉYz^}KT-/Td`TJ 'xqrA½"9 ^v m`ExD2rN(<TqsDZNuǙNdoXʅ͎X_G'y55'v5\jA~6.qԓx\9Y;2*=2 UGOJBH oCGctep 93Ep'~r0#)2a1rz<&,q E 0AVH+iSbBn>E -yHV9 uE V;šՐfhL6D *L N 2c9g!Z *sd B3cr3cI\hHuXq61P` ㌐OwHTR)wHWB<lUh ]Hd5RupmvIq'(53;9'Z_nWAUBTyRʇeHN'F?oY˧7򗚟~5炴Gdyx\GJsQn3Χzx; G>ns73+Lғ&k|2oz 3D$}LR|?e9sGųFgHg# Dži! FN/L *9t` 8:V&\; 螥E3:Á8b.@ =A&7 Hȭ4cr_%:?GkN-`8yw qx`,oAjB#?sJcfDb)+W>?02*)EЍII~9}ye VN6i/Ong:.\-hHϘ8 *Y35XyG~9<`_&.[G41: 3o8/5|0\YЯ -+ah )s! *53G. ˎ+:q?Ym+4zJHϤmo|cm;G1$1\Þ"n*3bFAzf&\ߩA9ѹ?Am!q<3QJΊS??rFFtp`/Fw];xĔ#U:b uແjoL~q+ΐ'\)_60>c_,3tR@QǂȳGl$pP}cM92+^bpuyk"xeKE'{6Ӷ\_%zЪ9{#}Co 03:*f3v$WfBo;{xZ25?Ra~{ǽ~! wX6|(>EXO@,'aB2BI ?naDYģJ=;O-FǕdwb<1#' W "# &4C2=xq.P2uir F 7$]ӑџA38J~2.fۉJBt5^vꉋ'1 wDWu,h'Gs`+I<mdb2PFUіo'azXL挪u;;2"E:* 6㬑D0)pGK==L "1\9Tb#NȬUqSE[!Wd3{N/3ˉYdȏt~Dx.9n=\9Q0a2SY†X*-&p<= ={Eil>')u晆|g 'F83w\C@5"g SjТjv63>9}+oNx'Rq,W pd+ۼ4rZau$ ⅳx?LX\o$A/}=( 86's3yK\pc5яJNy^e;[O9k< 7P2+?džH!~L3!XsxQ7qtF9 dߠ'бf8-[WƷq=BoXtXMHP JB\dB;#2zbےNs\>uFRQcǃQ {ljpC0W_G9Fڌîx븹U%h".t"QrPr ϋb⾬,(YDLm7dWd0.0ꉖ!aSb+bpwp{LSv?&??}?k=g4#nXTσ"脋Ru :%2kj>l+h”H7~x/°۔~QJ虊B{@A>zE肮h=⌲ΤވNcƉ% O:=ܑ Du;A;hٯ,_ Q$ +WRI6t`L{7|_";{rNg>x9Ew<^tO;?2YO mpy1xGǗS4gB6=,M)~a>W(sSsF퍸-B\- 3V'6\GFir0|"΁c?xR~b:N%Gsi.2MP/O>7 _09u E=pP/pbAȥ1 "Zq~]:* N317OcÕ[23CUDeMG iK$ܧtefxQ_a*dI#H[Ņn kNpr9%~ƍ?O{A}G@^n8'y$ 08dj< .aRPd8C88B2& ̞*ygG;SE=r; 0n߉.҇qD;9\͂ ! d0DTi&!PYV, N LR/+& _+c8U›F?@jI ]AYjLx9o7k$5|3]~I ]@B\=({2h5 QHDw z *2 p οDmxP:\n0'_Vԯt?D&X2S\%zr~,xg^}ɰylj_0д`ZAI_w{Bh|xc6BR0GA(07|^)M3r]Ɉ-C Jt 2A+98K܊Ʃ RfNWj"-§G嫃\Ce څV<9:ߩ>S{TS`4ȏAM65OiP" 6(10E{e\RÄQz.\R:56<6!_hcK /AfZTCc#SLzdX )̍pF.>p60\[ag؉Fs{%nM,9qP0SL,qj#f/0 H%CȓCNPG2~4нbO9Ƨo{؉qƞOO ."o夝h>HSL&0+<q{ m`f8>"AX "C A &ĥ4j@]P=g ;3"Y9 T6gЈh+_+DR:.:\aP׸={*GC!'.2!.xwg`g}G42Oy6ozǿώV B*8Gq<۽aO'z1 4Ss#7 {`;CD ᎽC.iށ87p]9}CD*-Ak*X#? 6[Rҙq8ΚTy$H8`X?!vibS8+kiDH1b< a&PG~c:Hv l"}&AUXt1*!.8X`B3 >brΌTx܉U4ΐ )RӀmFé,X',vN K 9mM#\DӠ;Qq`̝z:о2zE׀cD`>vLKF'=}':pfS>u:s \Xp}CW4N|v{e1~_p8͔(P:+b5!ƈw 1+@$ʘܘJ$H)Lz{bθpY1Qhn,IJHF >'J;`E<~= 1r nNϘbx{A|ARĆ'Ղaf. pY(q! Q瀌 ttG;q' 8(z#ƅA=^gw4G>Ίw !wF1lh;š'5$w&|x%z'րHu&~FB{}^}Zq /43jN35v.Zq}@px^=oe?; t , E'K[Oq3[`2]wBux8lc2^}oIVҸ"jJv:9S xQ`>8:Q3"1]ad("|,Fy),@+…W9\FdžJ~݃֟As,8ri+A!M3+&NoQّN''C#㴅ئ[qw 9í0OF{E]GyPve;13L+]B*n$rqGm&0ƄhJ|3Js/8cd 7,̨T Eˉs7?3MR9H)4K, 5T1}|ﴫcRp~wG1$#;*•3wBfT ?EXi$TqpQu:eӿ>}?|{eIᙵ0.41 {A\)^RPpqDә8ki˔I6،ոp-dMգX1:$1mD;p4b~0Ι2ίG*{oHYrDq בIm3.YNJЕ\r< f3}ٍiзHM"gUZx?*,tWH k*?$tA}t#h҄y#S +:yѰ3܂%Ξ Hp.nFnF:_QHR)(^*{u2jT)ܘ@o<FdRЂXy³ 'Is&|+Hu ቪ߁*E~O>}߾VIQfDb ktʈ}1l[ 8{'$a@K40zx>SqF'!d&9aEqB wDΧa;@v]+0; >VLHd+OܙlL 1JcJ 6#aB[d&SwJF{GM0ώCO;^I~=yȷ p_qn< S1"jP=䄌jO=n1sS'OyI[&9<~vB;ow귈[*/$8]D&pjh%yJlׁىO~B* |Gx)VzqEq'*@a,)#,Tpw!9Y9|[BwA ab@9s~eğB%+NLiE #|q3yC@$ 8hULƴg~a颌JJK]`U'$q@]Nq4IÁs>f  ޚ1sX4wcQ'q0SLȘ{c4{)mbO DTYy)Lmo \r,QY]{"4 &prCe}W#C|YQXyM>/j}A18^ k+ 7¹Ӧo UHBpn>WF#^z cfZ.,6~|P~rG 3dgk2lZ)OQvq&j]pYP.Ft!E3">ݘrఉT#%3+~|%4H&/ϿkNWZȘ)IL10HiI}nL 7MqЎyj);"Nb},ЃJZ&dTh8\ bK'?"(Zyʑ=vPbOd1(/ /͌χIPI]#c&V0wZ-f;nNԷwc~Fbc%a](gW%BÇ@OosG԰!#Bw yoDʹϝA{N ݨq#zn\-3.:7Z`aeX!36:!Ϳ$'ZApg Z?ȌgЙO+yњ?NL;E (~T(xY:X0qR`?1gюVfǢR*6%<T1ĿyA7G6,%`G#;?<fU2Teds .I3ёzJ"[02y u LGNY @wqw,tm|Re_.#'?,ٲL_[8""2 h <`h1`FTuuU!9Z>P O=PE8>2qS.pY& %:qpO &S0J\AfpO<|gHTHRg^3[L܋A2X[ 4> &fD:Gpc~[8sH³w4'hp؛}` P M ODw:?!~q pql􂳃R;)D4:45b1wM(j_ HKKCrn~8𾰏̋m܋|?0r,Hrҝa'(;ig8],;/3RۨyB]az]V;ۉSPpFc\jH3& mǯ/^!!"Id$?Pі3'jz?  0nlJ[mˌWtxD !̞pG ?Ҟ17Ѧ, z33?<Р(ɘ݆]h~/M` Rr|HLuʁ>sw'_yR`Q!,Sf4Ft &3GPA4zLTO I*-O#PF0>V2.(;.Lߘ nnf>5(2oxΞ#u6&FOX1DEy<OysYdJ4:87 Ti%4 T7,1Bj98z}hqaekq) K59cr(', qwK$=:f)64x'_g jv# ܱ*V(׈SL :7hfZu,P_,,m:N.8pyh#Ygtyæ0*e`}#Q׉OནNO_3Q m?遖o!z?uWH4;w]"Fzz*(H#X]&$9z4*߰;CyW$x ;31 7EKLjr>"N`bɓ޿ێd0S Bϴ _hԨqYC#2|gl2yČ>"^4{jyk&O&:)Z{Kg^ =} 3vC}O0 >L'$ر@$L H^6 >NǁLwa5? +\p_T"O mYЭsy)š2!Ka>8Ѓ0ft}w9ViC\92TJ+:W $$PZT=a= kǽpyB\1z2/1y|Xndž;#˜=j B9{Ƈ <f#qRq~SD 7r4(v';M/ 13V!6`zZL䢤ogd5M,Rqti$ȤFHQ7-#TN zȩ ҃ǧO̷`<+oX9B-0WAtplB:NxĘaP'cZҌn>\q cOI=e|8z0osƳV̑1s8RTsS4suq<ډ,[LSo1'9M|渲x<*9'fvݹ@"j5sh3#I%zv4(ؚXPN-k5ꔈv 8Z'ec57.#gqY#Hw(lBtZC^<[G~# =2ƆJcq [&Kힸ seB&G'KR=n[=pKG V8LM{X nP3TH~/FL)ぷc:;nOr*w)g}gOʍ8 Dxlo᠏!ցg(g8#}EGjȡ$Qg|xۡ'jqs$qo: o?pi]i߾W|"Z.ti,(uF7Gtbv#n⣧%!lɟnP@?iח qUp'vl|j+=P9b i@SQ 2ĦC!dҵaosW81HP?WT&q!JrL4з+s ߡNKWmcMWop>O8s<ēG_n53Q[!I's>(]: 1zZ6KعB@܎m7ҋG 7 m8hBGJ~ANߐ`TE lŭ3pP#38GqQCkd7*[PM$ {}P -3+pΘnv54xC,_ <ШX^4?2ゅtEz/ 8lwxsuǼ 6QXJ܉ gl[CGB`!eRJe`HXx,X=%w&3#3i&#R?<qG{ dDrCDX跂[A9'p4]~#-"gϓSJ],t.S@Zd\n5w: \+ݮpkG/LYzp$ n(.~ٱqY&"#g߈$C C)^$vB#glBx&G M ;δl`D| l0S9| ky}!BEXnjוXxbSOJ p"oG#N^M`Ձ~{!['DjSvc&&^S|\<_ ŹHJ`\YK5n](vbD%VG@1p@hW Y#㞄(5P&3eC7G;rwXf'U4D3O~g8AktWR9 2Wsp=GL)jGN/xQ1pÑNWpAm1 Jw ?̨ύc86{[@ۂiFpFÎ#&;l6v k:0}FZ :n1 ?7Х4F|!׀bZq)4`B'7dN:XbH6V2A`҉zJ(Y !:_?om&gҗN/̈́Iy6`8Sp %& .^߉>QFBL+&\89\PB8!!'8VOIxt#EGڅ6 _e>}r3((0LHeed~:Q.֌WA@7Fl㜧 ݸ?^v#6xN VneL;}>Ԅ(%#rޕIM=VC+':X6q;eNhኄ [u\'|q)%F2|%4)JrYXW\*)q>'S52~*ݞ`><.=o`1ѝm@D}3Ɂ ZX k:QIS"S#HL_Ѿ0Z86:V%ޕ`;tb>X_jޘІc}j;F B4 ]BN;nUn =KEbG+Cu>88KFxl3dV8H)7'ѵ⎕F&V# Gn 8T͓N qGDrOt{G]bLgT )ry_?}~?R|Œວ iz,hQ6n-xqO3D{ X )˜[A%=/jbqOqKyGn/*QD A Յf߮JKig O;ӜA~3 DS/?I/o O%'zlTxlݐóf˟#~wӠfh.Z[rcFٱ q(z8x3vrÎt X;7OyW0A@ (8:<>1h=Q#mvB#tm8t~ -b2(eHx=8{VYYգdSL*/: AhFE_3v14-җi+f(n;Jo;T3f#г7 w)3' |G`KB1JOW^bAR 1*W*mq CFՅ 鳠4%\ǎdG7ㅢKe ώl#1Ras3v/Zi)8'Fp*3R6Gɣ[Źdw{a}Lٖ/Loj;3^ HB̔V8*eMN fsi؄*ޕMyq Հvnkȹs;9FGrT!X[9ig7$^`I.R@tŅxZm O>Mh=+p"o: B|9K;Sv1 h;Dx Lq"9lĚ & OPu.1#D2so-q f`w:ahڸ%%cKg-:\#%&`2gG=qrxI3@u~gb=Xys I'r`cC[>W>|O=?vQGe Wݱsʒ#9H71w, Lb P0 F8 0,D@3%33#~P%Nu N`F )~bO+y cFq2" }9c;1uޕ;xƄT#]Y_0 goiVb}~%z)%ۅ} td]pRe6hI-Cad] &/P~pi/{uq&YF%&Et&`l7rECe:Jy@0F DO7C:$'.{@+zްp ),l(tb95P?9W M!)!q@0N?dS 1._)Dw/Fġl+>8o]uŦ/c6CW,t&3%vEbv|$%AqR̓瘸8%UOl}%(C 3C#e!#12s ;)XQDrJ?4Hs\XFTAM?@|B/~SS Hff a*ױ1$q3#'PayX01P4BFm]|qPii\n }gF) mLS2y}ÏT @Jh;7v @Q* *,&#T&ʺq6 o7i9yUP G+p8Of2٠et0r69!_0ɸ7~?}m_wꅖ+Ab'6H>CXU{AWϠ1LweG>.ؓڮ7GL7qh5a}`癈aܽ0ω9 XkLEo_ Wv';X~xwʕN:'H']Wݰc_#㗀W =%i ԡp%<5?EnpAe|]#P^Q t?\I` cFawHN>`"{G#H%W7Cg œ#WN@9nr'GIܔpm7D XAPΎL;.%0[qퟑpO;3S8%N24y4 #9 aT`KJ+ej\"i{@ eD%ѿ)!7ޗOAsGĖD~)%pBfp6:&&cc0ن11F36NN8p: !e2p fHS -!~1Q+HՊtTŠEw6Q..>mHsGÝەp>Xi3ve>EGå7t?Ӆ_<:kDfҗ,?ag1WfR7/Wܥq;zO0U$ka\27B1G%Mgy,{#%L?ON75$~7&`.~ĨD;m L%!^at>/7FOkiO]PF8d =Jvz9Ãfѝ)gHV͈gBxcyb-lc!=1O }Ym?<􎵁[=NQi4ndawA"pBƬ-dx֍5V8\<=EZlWHZ9F0p#uMdAd" pqc'挔vHq* klށ4wi FDf{9@k+&::{ꈜb >BIa}a-=}w!?Q g8D8 idؿxC&|@JX2/pOP.X(P t{$n\SW&u̹3wUnȞ *Bv22W4NFV#KTB JN;1Mg|ّ$ <:@!W]:c@ҌFxQ` t=V2hzNt(;DX[U)@T &EOr\$Qhk!-+-wmgL 9}/3|f+Q"}eQ'F8j%:8}KZNl 옥'Nc c`e?-#lx6h11 rV_AHJ+wDN  n(97|33޾ >7ol<}"Vk!-ZDVq;{Rf_n嘾#zA&!(8p/ FmZ9Ɏh 4L،f$ՈK"[r9Vp':ᦂ{1zE sOyH@5;egI~@7W`DOw d}"'h [<&yd7@=V7ï3 +{OZu~F['N3^Wom"lE5zn;Wb_l_ie8.rAavfĩ18cvು^BX L9v^?|׿|iڙT\|P"e_A~EG&^VF =RL6"m+xPAyip;\AJiiqGaXF݆dp?7>p_7Bo>5f.X! ~4Rt7F +|sycS|qD3O"Yn}}I [:Op;qdY;/K튝PtYY~?GʄH%0ⱳ^ƬuD~ca50R' 3Z', lj83qҨpPc$=4#^ ;e܈@o >u+*o}91pZ{)82> 3l72ԫr "c:aoL=5m6[ w$ oLeFR! +(H;;GdΨsxܔܐN_rJ=vPD9y0bn3ouMLN!Nii/\6\47h?Bי&(̉\~EFJ+;*s2 UZdo$< &w7m.(WxD>6 =A\G~qk$w>R7j& (} KC*<4m$8Ǝ<GH \ Me 0¾&utM<;tRTvLN"$אc&|LJ#Ddhq#% mDT }DR FQ$ti9r н{H8ޔ# iP}p%DbB`w'H\ "#F7a):L ~u9@p { ,tgF o,ZJffK|OTĊY`&7B}`Jw-]O_SaJi< > i~ԡ&x*gM|#2dA' _S N6z'O~4p8/W+z^፲ul>vh7jLF _ %w<\c^&vm3FYP(u c= K^Hx*W{Z(JADKX3P*e +cx@=%ڑȱ1d/=`;gN2X:pB+p#3!D`|?G`.dy,Ν'[\og}bO"[=>9N @;AIƃ×O|?~3MXu5@i}Ųc f}_/t@J w ۝ ZGEп)vvD%cGmif膵LqX~mO+9lx;hu&^|SwK%Bp#2IZ g:4<#|t<^@N~`,hhA4W[eq?ЧC?p= wv4$p਑e1+-d7HqqSlnpA>vH;!*%6Bӆ!GntװU/MI!qN Z>'Ϣeb ObGH_#|{E}M~%++)k.c#Fs3{!#@R&o#Y? x6??3rRBcR0 #dps )>#>Wo9bΓP":fS'Sgd>8e-rk5EPe/zu=:N&&g<]IɽaɱY"9#f0'G M4mLIŌf+18RQ03t]zΉgDÚapsQA}|h%YW/x{@簔;Dؠv%O҄蕌RQlC{GJw\  9-9BŹD'D."zDUL `@%m#2b;Әk|Kc{LDIlwx*㬝8I[T.|Pi!Ӌ0H/3o2 Ӟ rØDA"q~fy<+i/RCPHQ G O~OA8)m(L p +/;{F;2?/8tP{b$3x򆥕0 4[eB[:vsؖWG 14br0fv\ksF~ `cFS gNq"ZĹ] 3vj(,#!2@$-JNҾ+aFMSoZD LJ#G T1d#@]Ot͙ 31Pp }Ȅs~o] ?w8Ʌ16r\i3džC |3I87qT.8@#c` b#a\5l!K1#ZHSr& @BJh@xÁt+w Z l#Ц h)05>wo|X1?Yʢ>&Hqvo#}APņPqBфEaDO ) Ett|G܄hB.ţ(eIH@o ?FFw)M0@kd;{c-rxH.|%L@20W $+}2"#>VN$rȡ/zpMⶉijjh?䘰Sſ8܃|_B:0"[CGeil{Iua ?e<8+Lu&65g,_iGӉPWXF>jY"Mr,Nƿ?}/Sd+}(.D9U8HuB-ϔYYp+ھ!uN37d[ߙ5k'9P| L_W$iSJQ)xuqЋ'pp\hا[dTp}.T\Ɍe8r0A)2FƸrL첒~=2mu0L7ROk=Bx<61,qe NwB}{%c3{q ҄c/aqRp|A|"y809eoSQX"h'L@c>> PF $99RcrDwg1;OuB X18B77b&@$R40 ;f$ BajV3h1{'?#Щ !,`0غ#bqA=3<"MI,Qpl+|6֑8>po+; sxّGFc뙰7 I=з;vLaCQqt1&Fb2wOo Zc hPMB+?8M4I zcgB27FMJ}xlU$t<'[Eqׄ pwy0 i +H"RIzL op w ٛakf [ Њ _ )'ϧIR4 g4튞*ʙ[6F ̸`LC{edANix2kCIBUwsP,g,d駅-׉T=ygGrN02A*.h=&qV%huxT׆}*I.~:+@bAVN܈KdÓ!uBs+VB= 憝@J\m>)RHq7u p'ΙQaFPDސ%0Y`։P*d]_|LKK3hmFF+h3q3qBFoZ#ɉoА*I`iQ:nlm!֍ԘWRx W%Ooę} WdZPN4iЩ{B†IrfL?L4Ky"t!qXg|q؍'tQ˜v G`uFKQ* mqׯJZ/u=vMp܄*Ǘ8_\RiLw\Đ}BWqguJR"\ͣi0@nO#?FѴ0qq#Ѷ`䁔we ;z#l=ԉaw`GhU)sWl9,PRIig=aXN?!~Y\PU跁kfFs 1^SgWu+8؎4IԾυlwj ̓#& 8NRxUp~#-w~@`JJ~Ks󌹈˜:%xjwz#.Fbh 08Τ^a֡ZYXƠ i%_ j;x?la7kFc8yfNwbY`h!{'*Lr20"ɢؚ30]20ͬQeyǐD F{yƙPwEkdsz''P_ȷ;9@ rCrE&i9lBtϝSy6"=T.1lM^IYgO+ [qH*Rf4u,.{ŨS`ih$װQabq6b2LA8'2GB F- ^ܨ;21}L=G x?Īv3T™4ZN8!~\˃#=XE~\w#Qv%N<Bmڎ0pan&P;i+a g Dˆef?'~t's`UcT㡁 H}2.FAʍWj~Ɵw56,IJ8( ?P}v>7eA9R3Rm~9]vT?'.-z;O=6Km坚!+)9Dh4q 7o6o{zK.x&)Y>>R~cgoJwP=c]O訵,0b$0hu`3yţAp rX6֋# G#NNjC6AmhJ/\Ag^/}S'K7zwð:aƊ7 2+!PgǨIv?`F?8hfH(()arq_YyzqϘPlef[2KbHǗ!;]!lGCϊ+O I2~qz06 /'tNlt)mWp me*~bwzLs *H /ƭzRXTwB"Szpv06!h?h8C=seL˃y=U|+qN0<(qO1ic4O_A?_!Ғ2tǚቋ  (R`lpӍq=D wBWF 񷊕~Lcq0J2ו n4=!7_x=`>niWƹдDsFԙps 8hiJ׉mx'$q =2UJ0A{ Dr2<mxyx68N ʌ:FH0c Q9as]q#3^XY~JtLQzgy=ؾ]йp΍u3QtG'Vav 􆸄tA 2Ndm3e ,vZ #  (z%Q`+4O+0 ?EX8;,ZFcpI#p?8A_l8 pL,rV5z!L1l偟9^NO0_ݸ:4Δpl¤M+3f*KC'2[}!:}T@~r`lkڅZ>FG#<niCbK; 6T~_2ǘ6ﯔ8w#Q0ؘJǨ%eF9#ǝYw~t ȊʁjB4"5#nGyP+9vhư#]Xf}ixaZ}*G@:nDf̸( By$d>OrgWR P'r_灻:45t0O4RH ~GGN_pA~,_+=&Ji/ׯzC᧍RXl2ǃk ߝ:q)d?0|~}׎{4TqY?}A/3 S+ee +IqűzOv{& m;Crx}= w+| (cE''H8؅Nu3ƧB'M ~aYA:`\&C7h/%36T=;=xxu׈t|li(a.0JJ@3ʣ^LM~l?Q.yB^2\q6ڔ} I(Ƀf \Z*giՉ2 A`=0Ô |mCc!OP6Gu"4u*(f$:3o.3Ld iBڑo[^b$?*m9B"e) R lt` 7ǙK5\9J;\f-AV$ 6Kg[ gB9}`a^Qs/Ǖґ(N9Vtg2g4&;_)S&H p1&+]! pIhrХ1QF#DY6,OR[ɾ%P)#]lAq0-qr+PA&xPǂOB4&|T#"$6kq0FĴ F eܑ{"W?ֲEC@>=SC9ч2(,yF.2&Fj "IwZ0`'3dǧ'F –"hd+ݠyKT50owE!{_*I!B;3K!yhbԭ6ϼ JMX|`Wgi3C6ʘ 9Szb&7p;9,OΣB஌9&nׯۿ[Gd  Dbf}c*ԁ"Ռ^1,ӜÙ`LS\J! _&䤔݈."Ѻ!rPp \ ~pCvT fwp-ׂD m 8Iۈr'gD;/N$PJ_Nȇa'%{6G%o5ArЧn$0@VqېC0ԱGٌ/h+Wpmt#. %&90|w0B܅Ưxz%k;2W IZ %eTxa;0] *ˤ>|6`ha #ϬSgc O#2Ě=—b#P8~SF0)R"q8z-l}`T6yv?32I:ieN =.=D3PL P퓔BCAhxܸ29#gK`Ύy^^;p| uul{I//+i:XCc"i7P^Qo T.HF,!.{, V`(t-IHQBYa"!^} p 9#q#3M VYk8a}]=P"=(ZphP<,Jvr\q[!WlP`PL `=,̣0me (4,T)\Nql=4 \~i@{MXɑp< _6f_pjlyK?#-BZpĈ;n8T᷆=M'Lw.zD: գ#ZӘ{%p4җNٿB|hjd`Hz`F/0Ws&-\yӯMog|L80Ei234 k?K_yLхq0 6l4a']Zp|r3aS - lF2Own9T$Zܩ_.?>XN ǿ3}wFa;p7x<ȁO?X'e_*cSɯt 4 2 0߱P#SKŶ g:8_Gt6b "7m/K~p7.hZ>8]V,i.;bOpZfh12.&j{&?3NѐSg/+\_frV IWz{0ptJ;S^:*xut^y@h7GT`"Aљz۝Ox4(Ho8"r7ESrQLY׈{`VBŁftGw儬+ujP\1ewR|A|Gng'Ziݡ`z0@EωSoC?accANc:E@'hxsXn<u\86:߹pG1n85GPt.m#n;ad L@L<v$)hR"XYt\DeH(נR4uHqhi#fe Ãl}欍.(rq|P'y9x>d>y:݄XCbچ͂?"Ab8˔iޘZA@@$,l.}EKǙT# 3#0x4%J ;E9DQ Iwň ӂ^/XQ'Gi,4PD?ҧwS%As }@_.gTE G+ ؑ k32.$& L9KY7>1a0qbL9\6O U@.o9=m"|>8t12xM=~i?ZgDƱ~CyоN.R fD90S%ZЖ !JUNg ]  v$B;ߝ./=AÎ(FOĹSL;'7W#-.nIUD-cOOq~^*/hԗfG3#AbBLJ&xya4{N:"B#{/|WSNpڱ.Q7zR%ӽ0ƉZ?XB_ 7%>}BjxlM6K(N?%R!BV#%u IN u~~aNM Wy|DDN?vV2Aw&lލqtՎ p͡Qqg}Ղ{<֍E;qӊo+B ;%++є! t#Pa E䫠C/0++;\7m4l{õqۅ/zbO]W=(776亠[ωĘމB/mxk> QQ{afNz4<`J'l7u`֙}$ƭLH1¥?3 up!"r4e8ؠ !8418sd #ta:zAjO0T賠Yqqȵ0d+,?*z%([WHh"=qx`Nh л ܔTgG˃"I\ JB'dYM kuDQc++|] kQl#ݟ!V2:c{@;g_z诂N `d1!%Z7X!;°JK<F4$O (Xq~Cr9ʼn6 gϦtUc:X?'87}&7/ˮdH=.gCٵTۄ~>ZNHfO?%3O]^{q3gxJtV49=#/ `JL`zL7PFwuBqnKǺív QO0aC〧Dqr *}pФ kcG/Ѭ2 Hw\g/()BSΜThV?!W2tL`pZ06 x,$Z{ u"=KbcS~ʼJ`4ߩ;,)1I2(jJhưiq#̃: Z2Ɗ+T{@&afׅc `jc@ׅ5A Yn ^-t\wtY@OaXbh4EnM %x{GSFˠ@=jсyT.㆝TqOQ_3lX `x4a3EX{iR-8b,"|\Ԥ hݱi&hXgPfti+; :ʠ?x)830ju5_>J\ 5QB# n;~Z-1_:7rpMH)S,Pxغ1ZGXXndt`j~Ƨ/﨏8eF:<} p   : B+NzM L*<=dwCwW,|`JjZNPh;؀["f԰dPF*uTXֆ46K±dV]Tiªy}_ {3g3JLIc૒_&‡`ۍq~%++}JG8#pѩ6c!Gf 1c<*( z6bp"O {I5P3e2RwJ[qCsX}G wDRGa3N\;M>{#+.ٓhi ar~,b "6tV˔A1<1# G7rbuBz׍ +QpG>+"=.I~GF)1L}0?;LM_#'ho<A.3@?•`ȼI6s ;m 5aHn>A_=C!9H ^FG<#$#]@ 'ޔiڡ GX(W209fd}P`P.7 2&5e=A`VpSCۊȝ6G:;`8w1^"Ȩ3|BLKg%W|m s;r$dLD ydF( h}Y# xE#HwE(FϠU )vJ:x&s+gtp6Gy "a_(^[&m J}MpJ]">>\kG ABIe>9j}*xV^^+1mV ( 1WEKƍcw0iԼ݄W{ˎ3/ .V|Mdt1,)^3-ȨL/c.<-3g4`NpzP D;*#KHoiARXjn&| skzg4 wO۝f >4 +.[M vpWJ;^~UGI5@ߨq%֊807͘RgO;s<9t}>1O;v >1I#+qGPh stZvܑi6HK'\ " 4%d \ˤQIsqI\DBp f'0- 2uZ`=20ȱPvϏ)!QOG.adNDD?p=RqGMS~bQp#h{ytDxب,=Q]ѲCccF'vj(đpΟIM+s+ط>i `Ӊ(~x&vטսqNhڈqg,i\CfGoའ *f|s0x"fT5Ɠ8>2,c.iۣQHQ5cD !GdVPѳ1 oB; ~$FT5Ԋc:GGVa1)rs; ұNԓ'pUb~wt24d?X7]Y9Dv!SXZSBx~7J ;%lu6p{=sHYu;U[ᷙwIop1%"y$Rǝ9&Dս܂/8ycD0kh|EZcTE &cg?:Ϟ2*8[s.JDzH4sz+)NWXP[Ƈz¾(Fv1z7×}qϯw?^L͊$ŗJ;OHjL_LX٣'O#yhKcd)n?o45~QiD<UIR>R4h&BoU+WAW4('ԁk Qw!(m?% ׍~;Q:'FIBK@+ 1`t]C:BXw?ѧ%jw&8hYn@L跀>>ɧajoh&1wz~SGg*<É`+6*N!VRƯplPD82;ihS>f!zA)Op$uv!-#J~ <̻OJ@cKb] N0;cz mdg, FQ#vlQHnx.eN4"!eha> 0.G;# NMxZPi/7ϕ 1_;c3ri?.{~?/%~ ~n  ~Xo,,tlU;֔I4bP4Q }Z(0 ~Ӂax9CƑQ}yENtEHC'PŻ;3ό!%mϿoc#,`|8b=hFc5;ĸ* ]GŏFHn4rфU.kdԅ|?gV0h ctw u ]mȈts0)V?m%Dh%za33Ot+5"*.;=cWx>BG}g9]Cy$vgliymq#(Pbhl[!Кbve,N,N@sv:uqD$nC#-:|ى}kTy68UGѧN܅>{MR'("8I-)љT>aē!4"0`3}+fgDv|Wb)SGGRQcjPc %#?saKޅaL3c{iBoFO%qBL:.\jǂreinh4yP ]/ ihO4w\'Yn%,= xr 42 KDz w)R] 5̔Μ.r3g;ږ!葺]!H0['3#Tf5(2LP^ a2Ag$9&ZLw!? [$b"_=|\R uj9B(d,8a;AJ3KI=:^&oSJ^ 1p+/P0} U;y|hҷbZ8׉:3\a"?FuÅbp5b?ꄓAdˆ32MX:?D+tƬx!>Ax:wcʹ \hH;G3EF`^khS43vLаYDfpN: '(?;EJ Jh;Er ]c*uap#R/(_>A nv=2eOOYB5a`lH OI_j6$ H0Nד_ȩPl ~ˆi-s#.r~٠Tk$pn;+5Lr'0" JxQ 4ϓ+r'33>"+m+̹Љ9ω1)72蛐T))!)ٱū BſFlS 4K/hcC$A^P q[8kD]9F K˄gL2Jr2r믄+z(ϙ饠iܯ5>@< *329n+E;`D_-va+zz9OKBΗ 15b=a^ыylioO. Go$:N@ V7vb}L83ulK$($[#^Ҵx"v"uBڎO\{.$.aN\ADш*|*j@ZY"8=qS,:s'S8e-X;P.̼ \#й1kz2R+*\@^ɯp'Ax [j3V/d Y+Y/Lg~ D8U# b~cW~ 8hy)}ވ\GO4 GsaCmi٠&#I8o\'#T"VY.'GK(¬BF=fV8@:;$ DO-:% ahCɒ) mxPlI,<.`̫qnΉgLt%; S5p&lot)ޙ%BwJ ̚)I5Nj 3!TO3N̙ޠC"hjHSn/-P"ĺRs(8?,ie "0`:q? NIC%`m{/ZQ=0};9.d 8YFcB{#4a|DXcu&FOO±M(U^]1Įw :dwIwyEGB2 躡!Yhǃ&w\x=PsLIa #sCt0 iq7%Obp+N-+H}}amDN2Jz $rob_~ǿ7~o?/'*PdDu Ɉq~w>Z6b="Sŀi'axȞn3΍^:JBJI_Oӑ1Al0:eI Ch`N\0LC$"cqBd䜰iO1Re)XZ!QiI_ϴ Si8@+:u(AGg̉R4'䗀?S Ν[`mF_H\.e5G%IB+gj B w;OgxH|&(ׅ44wЊ|6g?tP+htiT 0q zB* $B++*)FWp̌J3.ہi/XvlWZ0W(},D sԻBgZ#)&o52f [:Cn`:et $􌔍2A? +}O0~u.LHNVlÈ$GtfI^ѥ![OL0$TJP##_yTaΐ፴,~5|rjfgK/= Yzfo؜E9G\~.  -ĶL{\IOڏ絑n}$,YyA3 E *cbFH8#% iiǏWZ.+k`WΟDI1fV+s"xM NϝӰaFc2켪s :+#E;CYOh HcDpLQj`H9Iñ@DdžW68AeoΣ(Rrmsγ |D*ж+e*T7s@17e|*H'́};#SwƝ#Z*z`'I8g_d(;F%V޹QDEIQ^D[q}Jܝ>ݱ!#%;XL_V)pFeeׁ8pJ"g _p稍[7Dﯨ|t~bqBqCg:<]:"k%LQH^~GF Akj,%5)dnhNh>2ENBi˗J|eJ/bvzPbT sBFPF $Aa8<v0}NW'vrޕ@θvRp z$FpfZ >AH1LJ&ϓyV$  ̺?[iFj#LfdӘʂ*؞XqK Czlfųe?/("шQIJct0ʍ!'iN]awɫ*u(cdg:/ZkIiWWu~b7W_ 8:Vrwjdb_p=h7ΦQ:d I J&xn :G Xf֕NiDw\\푘;e1Rw`DE~&u@}0_UChq~g }BUL~|Mѵ+xaN6GqĘ;2t3=Oۂ e[8gXB`^YB̨'9N'G-R9BFt0$FgẸͼJD y 4:kGL? ^"+ #Q;NW#zv2#Z0QJ~ke2gfo;yJgyȘPlw&Uވ}+KGWX/'m_xI;7,t|ӟ7| Z'=w*1iX~\k9ع4Zl4uψyPk #Eν6n$8 ɩ+C T+,8PBIrXRz";e'yY#B5%U%ic0ve@{Y WRXSzf%–cݰh5 JM0GIX0T!FˁH\+ɍ.PUN艍Js \j]Au3k|:EpGJdU7 3roK=9Z@T$Uˀbſ !xu\gZـޜ^(8өS_1F˓i\ pj$pre.}.q.3hO:t[! Q*|ɓ9;GVzF'}u⟜qNU'Ds-'e)ܙB`+F\37Zψ t}4:H稝gL+hwJr:#6-qԃ.Hw5 s??-?ԫȾAubq̜~/ [p i:0BV ׃9@2Ih^:( NR t)#l@ tJuQR S;N &ƽatPpdeFdgBNL5E=/ u ^WO c$=Veip(ݔxxpsedW||1Xh8E`.sD/9Wϔ\)=x,^ؿT{nq(hn3~kg:260Fȅel|_a fDO$@pwDx 1bӈYk>Os=FL&{¯}J'b[#D;R5?_ pd´_ q6s09ӼIʑ .K3抷@0R 2cdvXzPwJgyllIm?F0T [G4qt*\-'^|Kx֜n60;Q2 i8et$B9DD>>y7Y̘r2HO昱pZ@= MziE(@kFԙz:x;(K.Ti x XlxYu*|+y`J+Ot!i?}& mBNσ`FN=#1̃0< ~K,/R c#1 :̩7 <(IV)Wzc2#k]Z; &GB uV:n\@3t0$A+=XSݝ<,;ՌW#p?3yGk">*Ԑ[c JmOW_NE&BqAɝSf1x/I`i|:vg ,\9hgc7cI :soO;Q35a+(=X%gU)H(>)@I. ?l";!%dD;w "HɰIg /|jRiʘepρqmC8 NdK3q0'S!^:˾pX؀MW|B"b[Oe`by& z)ImJlodCybԓ!]跓DưmqibTS[bOʼS%Heco =[eCb trh\xRK#bk @H$bϕ<1P䪜PsՃMtBl#ܦ;R @[;jxY}O'usbND;Ӎ1)!c8FÂ!ҿLߌR?e/?nO?XN`rΔ}A9AFF? rfrʡ7?$HqC\#T mewZ\E: ALF$7 kP%"AH'_gAA:;qYDڊ_U_:foy݉y䎗 +5_з'捱NH)"uPF<3uB ~h-'Fsax"\2~UV?r8}00Ibi^ ޹>+HGJXtKLQL &kRXT5fqj^etB l+#e1<`aB{%@׎bFܯh\aS$c `3}ғ`g>P4?HqI0i\߄.A9pNS\qcIS$igÿ} {`bGۓ#߄mLɫCD|KdKA0/JF R#=ta0Q[gĮORfhA4q,_ĸw~~53 Zubl=$hpP}FE[NFdA?—ggCF*> !e89FEgjqb/c Qi^yW<4TګJGy }|"ovzֈ +R#a{T9БI9 * ,[#(#M\Aj*c1h `(<뉖3*_JCs7##X)Yt% 36;eT,gL*N@Q5>h^pFX/l.5nyl{-VqE19e&-1 ga;_dq18BnJ %r5xB8Nta ubV|hk&ψ L}_g#8HHQ_&};ӎfc$£Ӏ 3AptPFdmp]`wJncb6'N? =7&Y8ACj5ǮM`3  cy/<)2?TTaIU:ghKh 7=SNqe~m3Ka<: J@8sJx,5n+Cq4f_}b/?_m׭+'վ1{&J H[OrpϜ'T,爅*=/v|æ G Ƹ\_WhFypv;ٰNW,moIZ[' qb6:ڄHS>92 d&\|/!p)!uFBgB|$b+l1" Bj,B -&+z/ 6nėA/=n4qziʸ?I r샖vDEF. 'c6\oaJS{C'A^%Zaՙx0ҌE'1 O[μ=;ѕ?71# +,(VBt>F7S䃶Egd< /;iycr2J)SOW8eYe3e>;G_̑~rbw!]"A1ثIj$;8#E>Q_Y<6g|PBǏmnlb%a+A[yW8饢)-$(F&hUG^zsAXj9$5RM X'>%}ό 㤥I HX52}RIOwwzKl1H+wdI- "<ϝ30=!bWavGb62铥_0*vW4VݨQPGÓqʽuLaXԅT#&<2FcH,qƋr\o +ŰΦWbN W xG{bLƳ\_c R>;;ލ\6 )a2|zF :ݨ>#ڰ:hS7S4;Pˎɒ25vt4Zw~cm]t1o8 ^4JANZT kM`W\"&R_?b_ǿ i;mX>b0l+;&j^SgjnX*@QiF4^7&퉷l~mT G8)2 ͙/;|#L`׌ɯ82yeWaL y*)@Ph !e n0jcA'w%.T{ّ{ΉiŞa3>hg@='+>9b}2LCځT9$bs؅<`UX":s5RXrS'cOWvhM^oJ9 +;6a2-p<J]+A?Dwv*6"!6'ك2A4'|T07Bp B mw'') !bpN>^`~ёgBFï+>+dB?IL[332;8Bv|8DrGFOgݘ/ |{D(hIc0O;%@wȯ|W&5Byp3}ۘq炶` cr#B;1;cyיz4|LN NFj*N1|,Xufpb>hS"ۀ9֓!Fh 9%z؇- 񹓟'uy \܌'?kaz]!bubml℞SZ#M Ա{AItJ_hNnN.{]/Wmbͅߊ{ݾK2N/1 =_''Q=ZE`d8s:J '}tT&rV>Z%ȁGR4a ˹eЅs8h#8/kf*Q8 :4bϠ,E t, SYCNڕ 'C$!y(7EQiPㄊ衐qxi3?D(ͰKFT>clYS1ǠVAQ<"t&Rt7Bh3Y+DTsqԝz:(gC3[Lh3APԍ29<;V*e]q]jNj m%>{|V/Y؛D-s = ^ ' <"$Qjb'݌o_"zd['QA:evYHNѵ.NBmUFHL*NcvOB%>4Q3S"%gZ0h426G :\OhKD2Y;o^|2]nPwoqQ ׉9k5я!ijoBP=)2>%Lv(2 ?euv`_? ̡74p_/?8Jf A}(2HSD C+)$F _r?t Tv3v{b_Qq]1q|~BĖcqeJ"5a\#1Uj54Ő]12OZJ0Ңp ,X{<ݹ?'dZ.wXwz|e''#r5!OA>|h?ޘڠe'cEgGDaIr~Z 䙺4.^)%2Pd4?OD8WTɈ^ر_9P4|]Vaw\xII2я /T|u($zm0zb{aJaTi%FD˄v\ecQ>ј?"~rJdO%& I;Ӕ\29}Sf"un]ٛ"~b6S|0>H1FeP!&:N,+r&2}皌1;vPtKs 2 3,4sՐ!L6%va @y6'|O ca\AZd!&F eFO<DvKk5?0͔ E fA; #0쓤ABD r2oFU#>;|YݐKm\o獾YNJ')gބtvG.OAx[%lkW2͍kBL;": U0ް,Ę8uNbq6EɨJPˁSgxAyH#jwsx!g+g:AWhai^ι؜"u`B͸ uB/QLz[CqL9"T#I2u=1wbzqLCHS炝o$o4=8R 0j2lSVa 9X{jć!V=1B!{[4EYShseJ7gA/E&bxOqCG[y 7|AaF;A_֌n䕭BNPy\nW־lNȆ1&WΤHuǓ[ƫCkSԅvAy4Ҭ96h(Lmiڈ=@mAf~Nzt,LOr\qݜz&ZsT;ьmr/N-!<%\h8gijHbiGv8K@Lz<$#֌#]y?ɰ鵢j$w4] ~0Bī>(M(Q`0xP3p ԱQe%L g\IF+F͍P2>Ė86!SgȢ@Ll:23}~$B|^)Yѷd7NJ ʀFwrUg0~Kl.bp2>\hZL D^PR?_c;AWv'eai#|\?ǍSOӮ4.gcxS?oT:> B2QOjuaOY^2\9Ij|F "?ɥ!$ #s[BNLFdQ#>JBu㜍7C#lF@DD:a祯33%XG&l͸*et<tZ%]_kS;ij擽ߘs^6}6HSȯӘ;DKyFj 3WrVA<&$l6A䶓,[U< 4(#{4;<A;+ Ica"ġ k'zsх)^bⓁFguuJKq πy1uR0RW sK|QS/\mLeWOg7BoO|D5ND|mC*tOx(CV\%O\Nߣv9;*}+?+[}(|CT:-\9i۾)[O~r:*>2Ü+co7Ɠq\#1Ӑa*#`qZMwFAaI _#!)bFyFk,a˝v?oKC! ԙ6FrXT/^ǿ+pF0lBzCwaLY+O3^m/'/aB2M)}'qL?GtH&Pʄ"v?׀;z2΄ ݜ2fvP8@&Nkc@)1U:i6C(3|V3N,@ Ə51x * ch8ye1Z1u%>()B u3%2B X6!'nY"VWH΃$|l Z,${lA4.xUrϓ(&OƦP0dKÞ'M&RZp c;Xېf/usz L74a!Oe\dO ZD"I [:\@ :X "I#< )cRv-R4@lL}´>0ex.[g/NaZ0@:n`wĕ342?y&gɑ8i³L铡NhW_2' j—Ak ."WL&n_$M3$Ad>ub ݔ1g­2"}[*ቖYvW~`fYU"W^ZWK jt$ 5zrg썼$DtT=9eT86k`n:w1wO3 CQ#cܔ`7!Zf+'/Cq$H& p`U8Ƣ/l) ystJ-"J]g= 𻷉Ri]O}?hS.+M$/8۰j'&g)7v[vN gjt9l)4N2#hπNA?3+AvLXq0`AY<7OL J tLk8UD2# uEG` 0WEPEB#q/ěyF(~FuD(ӌXgF s:o0}ûK¾*A!t| Dq ~I9C,3.{^^2l vˉ;2'? J?Z514 $g<x=c\<+{x{Ě#v;×+Jr 8[vSҠ;PHJ8G$o8]9 M(X3?zZ,ʀdD1>akeݕrS]8c*fQ.@xcaoh8xqp5&F jd?RXLM7?~J gn8g,  <߰@I oY\'Ξ{G8dD)qisf$ .b. W|'G#̨ ˍh)wtSe&z2y,+k xr"Ch~o 'i,^p>cWv! N2ȍA<eyzڒiӃOit}G}9<+¸+o~#4 k|}XXN$qtHikfS%5ݑ8&7̨I:֌py MDBZ>WN}8tAe!m|d-Խᤲ~= W$DBt4`oj` qFWfIQe&mdxo W|c)ij`,ԩ$3Ɓ&!;Ut8$VzKH A{{E7c#Q_N;g &|~ 9Q":\\/RD q(blw9֨2!t$) lGfgz= ƝgSOz' }7PCV3o;m,D{eēq83b,`_+" 1"}%RŊAv_O?A8O8F`xmD'qZ׉-ͤUO9,Jo;W5áM0lO/)3h}op[c pyr+ۋ2}ό 8xpJ#A3pNōH} 'RY" y 3^cz%F+2 &i]XԴÆ6ПqU4zRRr T=YDDߘﰷ':UmcArþ{F:wAJ;; M z;11/_8X X@G\ nd5ZȄ u9|:P V K]h#D/#MA*6 츐Аe@Ls4st7ϸ+x,ؕ1}' ρ.}a1 zROG#NүL_)z!I%́GRTzۂuD402#=us(! A.Ȳ:u ;'p %T&52nq.P7~|+znDw0/ J<9,-3KOw4:Nm7عy22ʱE5)"TLVV9(GzS.B[: ,,F 9)b !TXB) g%c bkbqeV~4ttyzp}y*3.$|u(21!Rk霯1xh3|>;?|eMXU~4et[h#S%V q5Au2.)wxd8#r ܟ؜18D21ˠIH!L $l~!'>!*Gn99Xyй͝(= )È@3[c9;:x8 p@-9[ b/|ɜ{萳B'7~rW^(|'GlfB>jXT\x+m Vj mTO a}:Ȱ ,]T w*|Y_;D;Z0 \i@~;Ͱa4aҍ3&^5_^* umNq1,j4 fgȼ ܸee0_0r?"nvbӀ:4`'OWe =bv"y}k&Vcz 3I.u2psB '/dlˁw*qǿe~3{ (0W판N4aG2H6|) @`J.>Vڬ$hQ͈47 āF$ȌT/T=0; 58C}k^ [k;:Ĕ!@RǏ)wd΄zā;2:U9π7#c.`WQ*(=:8_4J}M}%}@;PguP_eNXpy ] i4HKXD4Ffw ^}^x!ʧ_+Tt1~dWaKpq{aȄsӆ.ư1xvGgGzTuBF[k}߱<3BxĻoW:.Bƹm30LXW%U+/6ZrrL!V#Sj{%FBFc 7[@N輓%jHA) G'.y?pFe`%_`ʱn FZANS3Vt dž B]F'錴X\Z\./dQ%]׀k8Ye# ~*h .9| OJ(:Y-1[bt>ZIa8.g=;dzbW֡ǖ= rpW16O s?cb5)Tco>-Qz ~=ݕ^"y*؋΅W`:yq{ Đ-G528zEmІÿDx48D*,IMGz\!2ѿ`X>9P3c5th]abTDJHc)rG6>Ɠ=]_7|:ӏ8ObA3cyrkӞ<%<i9 d2%\rh//k ;rVZPzV.q#U N03V ^2Q1I03L}Zx680qs@ϊz1=Bm0Euܺ7EV{O!oȡ\KF/HtB=|??%=3~8}0MquI'߅F]+_Q7B:;g ')\!xp=:=$b/ ch(LN#2?u1 @(8YԳ?6\Iw)?qU xp΃ ]XBs'/q4ع%kuT2Z|ᮝᑬx~ng|u9óy+mF+I?QFgB{̔q +HGED0.DBc6%6/TJB͈/*ǁt"n5R~ J!uC-2DO%p^~CbˌcE߾5{@vu_ȮpR n vrP^; 1jxf2ǝ02U^HBɌзW|o+R>hyeʅ񜱩"![BF<=fCj%@S!X D?pgc{ehfN+S" ^<h#LeKFWe:˄a%$]:1Q3T`3;n p$|GrDx\n8~s)ca*Қ#Ju9~/H3<IVT[OY;wh 5?m8mBE ຑ-aI](G+o +k<@C;1( aȋ2 SAK ̴vJUXsD'De0x K-,τK :wδF:i:,~>}1>}Oy[2PT||T MaܙHNI2*˴cD &g$^f[N<2x0: "UGH;Lx*)$j\F@1PN$/t"?U!@83JsuJ,+a #tTpiaZ%Y@'G?̿o9|#[u<׈B;w=! iA؞`WF 2ډ8ADߑ1Ũވa8WAippj3~B>w0>p∛ᣡ=C8px &eo3R*yz[pip+m4UdM%{f<{6b{)pGw# )P ̙( SPd5f] ̜h9yfz`:1GƓ(-+-CpPU\ocgai(q:z[*3% Q=9ڨXݘ5`NnrPQ(v0r IÑ$am ȿQÕ*rF(ɡ5܉iF ~E4y1rX"+<:6QKgjl"\lL@xj+rF"hA?I7__>?ǭ2_Ik c$i1FOYgBVwtt[Vpd!4=O #(pALjT<`ςbu ov4Ly UO# O{ +k%=: >sn%7Ho,cpf DZ C_3qg l yǀe).A3ڕ~LEt܊ڍ!:cτR‚bDB\iGeˤHxC #0^ 7t##.np?%N:$7?iqxy8 .KA=dO,\"D_!p@QOPEǭ8@%Ѽ0&#S%gE>9d8]aCmû7#wžyk`|{IL{̩W=H2A<:DU"慮 I{ML[7l9>!4;>i%BfR#2E qdjW=X+LǓfLypU%81P5Va3#DdPr}X!Q[[kC!#b҈~Ux^6~v ?e&@('`0%!D2 u'8`H).@GkSiyVg4m-*n\,X 4681TRXQFE(gxƽw uO(n}ˉ#aó]㤇NFD6{[x~IcWx~PR`P\PR8L+BnoȮ0dGOӁ?n˝2 !1xB\W#0a=*nR<6Lqv">; ZȪ3\1U~ل",eSSektx@#C`ks _(F;}p`X$`8a[qـy|õ@ }!ꉟ*0Vq=,%`#?S4265`b)!S&6O k_?z3="^3._qy،ce$!^1[,ԑ2BFn mjv'u<"yxWKGZg;flf Ƴ|' c9.'V g{p qBZ$6q؄Ӣڰ< q}b`\sn #pwz 3=+;s`/a%Ö 1>@9gd.HqxIka =) Wle5v2Ή㾳h^H:ˊm<<͜;SK +]ȗ3 ^]6v'fk̾s ~)(i&8,E3ñ-,0]fnaA9}VGv NীzN{6,R4G?V;1 RKǷ#` MC$ᩢ0{iO\ qNp9'tcSG)M45)e[B]'[aziȖ~FɈ+?!BXNDQ`u[C93[O|ȷW_NR͌sWNxC[W!8tg@Cpf0)nO%ی=>$G|M+u,+xiD`CGo{Mw.7Dp^#҉R"wV枑"+6_ӀLB2sJBBu3ةp!2ē|' c7!qD#Ě9&v78FeWVBWw9*;<&~6G%U+ 'g84y8PGWC){'xE$m1gOIu2>D]ܨc<<(+{xRpzagTA1!3C";`8ŐP3琚821DmtUꍠ=#>zs x =>v\j\q13G%Ml:7yfR;KDcm;'>'$Bv j-W!>%|Bʈ$D{.'1@'-Cb"8"%'aju$ uf\Xw8`Jiq#LH@ ?_ǎ pV"6)C=U)c` $qfqf҃u b)|0Na`&lv^XeYgB 6ulo:8l:CY}gW#Q$ G7)ܿ h'ďҌӃ;F5$yz1\ah'nt }GBz< G^16aJFd`HC(-}1E[I? /(rd@ fJxDqK="|Θܠ:J>!`U 7•FosSa hNq>Ikhy 6N. []֟dVbk²uKC+a\gF:) æ mVhfߕΗ{",K)k@bKa`tu&h 'qd~5&k-|ƺ nIӛ#'ŃL vD.RL>:5wУ0nQ!M l_e' 頝j+F428=p ER+1v,ӳB+i5a|>uf3ę']+c$%c1Z"FQDG3VDfB[v)%_EyxVΔx\BEAG?=*ol{ E2eCJ7jFGF>QYqkyyLAL/ћǥqOy뎛2aUڻE]L0Fr'm}0OF=#HJp,#\i (3-0 * E,b^q~QxLC?㝑{}Ȍr*52Kf 0OOq\ O<앴GZX"]%ڀ:9dyA'<+u5B!xxc=+^9K%9/}V7?glUBOԢTn.d fرxk? !_Q <:5f\yP6!>hO[Зo]^06p;BBkԦ0/ec|s9Hx3#NzDaUu>x[i}4_0NE>;*z4Lc`W>Y9!mǵ ]qyAD IϠvZ洍3rd jYoNicu\_8ӓ/>=oDܐpGDřA9;sJvR$(L>:9XgL%3*s aBZ:xg7ҹu!OȊ9#,QfUv-B_N׉tMv2|ŀ/.{CS"L;M;9f t@h37J:ݡl$#1P`zƀ hk l7~O4Ƒ^mN2'V+uB@ Jw:`6ըQkU΀ eChPйbNŽxa*W|`1Y Dm qI!@D5tV J fnĔ;ѸGpѮ`OX_b9%>xs8:y>1F Ҍ;UV\D'x+Kt >X9JcɊ6ljc4NQРE`x0eR4_Y`LGTY m:ɢoSrFXK!>G̩QwP\F&!K<ٗL UptjXĩ[saV> ␰ R1'zB\c0vH6 GJ 8elV5VhW?sQV'H0ׂ΃H: #RZ#0!%b6Q)QV+ w<3/Jhc9bG'``%(>dW㬃={3^V8zw}F DlB \bɔ DGJQ "|yk\ DKB" R\bu܌`c`#7X F5f4Q%!?NR2ϏXw[4L^鶠QV)hm'eJh i; 샞3Ѕ B Zְazd0vyk`:w]cC4Q(Ko?-_~}ָdv[YH#RM$Dz$h.iNWUpr#o XYI"yĿT;J=У8ӕW87%΀kOzSt45+ƨ7m#eW/߿"+NObӉJ8vN}Ĵ O MpG6í>>{7l?kƩX8ﰾ o!踐)1hJ 4 n س3Ƅw0ֆ= _2jFBo`j+}#\sA'Wysى-Ap`L' 5ъzъB$VJ'k 4hBqቮoȱ!C wXmT1p4hH1̈t7yG^HKB  !#9"3-ǟHxC%klMh1Rbi3E O mwU6ƌM (oC^iMԃ≠ؘg*mjMv2QJcEeq7}/*:C<)DׄF:Sb}!XJeӣVtr)3H%qOK|G/NX88DD#rI3P7{$[cwp\>LCnbO 􌏂K@Z&rp2")ړZqJ,eql,j}tA8؇t! WfN7#iz{ϻk92$-VRQ)F!BKgL#6NP"zmч2Dۉsb˞;b}z4Xɘ^q8NHS;['Lׁ|x. xl1E|-[2 p]o{m3F3@|$Rƞ;; XT\ Xb@qg|_1Хp__;VzUr]pˁ@{BA M:5*&4d{ ;>~!05,̸DB^w't'x)8W/D'n4XC 2pπ+$c\O6 k(8Kl}P#\OGA$頏pp'22$.Q|70wPJ'|"x^A }Q27X*7D$[H7=!H8v JQ/\Qj`6["YA8*~(n˞^W[f<> Kv# 4AZ<,~"-&EK+~:v.0(hZ(YJ +2)+Gt O4}2WΨ,C(A}bʅ" GG b$:A=4U6GFJkp jexH·5 3|O-Wꉻ1R~2[Z@(D&wSle:)aɜl.!N}УQc^ӈ܏4)c|!\·T]w* Agtg3Ƞ{L#`mB8;lghOv>!tZy?7_~7zQ'ȄcU8'HsKe:W/'Y] U #1΃3-9?|hF'tH5EТFqیpp5j#*Hqb'fTa6)0%q%24/t sgnC1{zf>-g7b3I^/ض|H>\\J4gD2]gZ.cC^7Ɂ)ӎ/e}%^\v'X!b  d^Ns8:] Ԁ;Odc-3*$]MvB<*Q˄7^ $|_h3cB@w 9:5 4a @  )VT0iWeGK FÐƙmwALT!Jas3$`H#%R/{Ds\A:=2:`?&oc/*2&!38$ }x+Bx ;/Q#uƝJFF)WtF'c [kb OJuwjh\PZ'e e6ޛ"1Fa7cǍ B6.ЙipJ&p~#ɹLX~ M|3v'A:)~?**|?IƘ'\U -`{dk\npf08Gϕ8zƅ Ls$P13=RTwSΨG ,Y#ѐ8mJց/< \I_7yA1ѭUeZѡ+Ꮺ 7j}`$&W@wBDt§Lfx=p,17(#2r'aN =N_VBh v~D2*9 -FKw51%nh)r> n'XIcgDOCHc׉*ʏ$N ]nN9GOFtBd"6r(ѣuyLX82YǠh¦@h"Չ3Qg^1I84SW;z4tUJPt"p˕\qU'=+6 D qaX65+Ӏpi2vf`ɣ[DrR=_Bu#? M*P# f_~w?j|Fb5HSXA1$64*~v' n5C +[/ޟ%*{`ǂXW B0'~ȿ;} }am~eBWN9З+`č/?Xzx.e`pJ^"1ol-;9W7q8M6 Ѱ%U$3t/&Q$@fs"NQwE~lI75t7UVJ { ub0$| |h3焉 㪁NU\_!ň4YX;ZVf!;T1qB$gNHwAJs#c2L5QLP p?.LOC;|{e|T /یO(ʄs;#;oHćN?\(T_st].kDC$JoF } ~'eAᲡHOBLPPc@)g˂8R!H;t,SChXrGn+Nn<О`& +i(a{Ȩ/-?t&_o u C44$A ruaz2I2c d p (uteZ`;pnŦD+Y*jrFҟ~_<;ゎJڠ`TEqx{q'GmO3.QG%uCG+)*=8xx@LX;qm+:w;>0 K954StJ¸z9La>rgC|ˊcJxqԣy6-#agV4?#LN9dO| s<)]C&cPTWxкų\KqMoU 2=Fq2>]g%fǮoEXj4 ^Of`[ ۔@^;)l#6 (ȻǖWY4< 6)c|'  M1pЬ]pxg`odAyxga Yy7Y~E8ԟ nt9|"';BqgЮlڱ3|c𪸪T?PN@Qx3dO%4$?q="yLE5R"gD'W%LwhVpOOLF=cC_(ljcD MyGEi\Ju8hIe'?BcȂN<獴%{d4l8_mKhg'7%R*:,}CjDƐ$__$HiJv_at;"(sy'$fzsL稝J^0L:VhT'\Thar3]*b`DwF2Jj}lEφIʑp͠eOR dMI%:AJ 8ݍ!'7XpO?.|nJuBS  E Ϟy\8*Ƨi>p/1IQ&zE>ӊ]:S aWg_KLYvO:I0O%^.3WNf9 ?zu|3QKȸ}Ojc<laj#RH$ 61HxQ:&<(Q G)4Cf)ĘeBMp6\il/F=N;êʷYQf~8u%><ӵPJsT + \̀;$\h;:zS8S$&,tz 4\BPO 1! +boPQJw~q/ݟL'$_' " ЦBQiSC]H`ӅV'Yжr@ f2=uFuJ )}c~T2D)Vz:i\8T_iׁkq4Ң4KĽ\}*[ulckς> Gn1<)>Dzcå'j?1aĹhh}0`HL+Y8-㛣*!` kBT$DZ{6wSG`ĆKDLn +L7 dˆ;tSWyRF#IJ| B$Wa )H 1وMv6|.hp(1ണ!!1|G{ eBsLיo0 0{i@I č;E0m+ >A"C 3% DΈ_"f - b36w( \'hChZڅUө}`8ǽf 㗅:vKV\ÍpSYγ]FYGB@</oʚ.˜>92VA?ʧ?x#p%|p ka TPi' ||Ԃ4"ֱ٨l0ڄpZiFsg3U\ x1s|8 ˃H+Dx6X;h~BQ)aӀm֊ Yئ)CH PYS'ԧ#. џMHfØRbڄgxvL-S'ĉ/v Qu_ z+8 2}x0%⩦sC8!^h=0/2J̦9'eƶ }C4"I>EѮb2Sʧ9S$[ Tyi 735胲D|~ | կLw^>}?^q.һ ~õJuqeg)Oy Os`^#M$yyF+_ #[Dzym7RȌQ'VZqxLʭht-8Mf ʠըa; |a8528l~%/1ē=1+̸4|BAшØ. mzn^j^$=NHZ(/گ;b{BgC w($F̈v;~43 n iqB'L@}GR"<y0q>o'v r?bGNY'>g,1jewA2_p=Sԣ#p6GGpoL+ =m2g>iA8oHE^K=bk?#qƝ;5 N㉆K;nT;$` ! Rm+Y:#78cOJ}RQ.Ӊ lMhu4cy_A"&4)La`Wg; SD;oWjH]jqr+r͑PN(0RIDN&<q乵N`O%S!/ fm0"OC1ވI9c!{G'<ÓzaJǏה!2$RCyH-LJۊh Nb[&cTo,!}4eZ ZG$ɉ =쌡{t4xy.,s,D)Q̈Ur;5+a:I9-RVcDr4[;\ܻ1DLtƕ2!J8iLH O|$qy,?{3yFThㆹ X:M D<ɻVcˉs~{a!(ޡc&v}O"7^{AZ{,O~FR@0C Bt0Jx>epbD+88`0!]*KM9<@$7r0I;Ws!q W¹yQC,'a) \;(Nr#*#baC1ڎnr)͟~?Ž|w7˴~Ov0GR[(SV(NYWM'U<1;68U$oFɃ4`u i'[3> bo!ǂFFrp~uQ(i;f<&u vihsggl3P0 rc!͌WϺĘ8G0;3OΦeP|_ nsV&-ׄYėwW{aFCcつIng P%JW;^Hm']/#74I'^Y ߠ􁮃3_m)[f>$uZh$gz[#ϊs ڐe&wZTP΍:;iؚwt$Nwj.Եa?-$X.AXeŸ'A:+WevL@$3B]SxOj(L/ho@s'JZ-16.G0])jt 4|b6mfПxHnoOXpfNJuɡ8)uBSGiZёɡ~e k/;*sar^A\P\e,sƆ2G9.: 9+e&Gf/3} S?h;D5+x_(TNCL\(σ4;@9 izy쭱b>adDIOLjF Ʉn S˃pNй3ASDxcJ(f'0A SI<6UPRǂ|[qF$򑑐ɐ캣Dz@тpBG$2 YC~[d#F Z8b,* 8BHɨDFADw _cKOp>np&O,#Ne7c6.2Bd\Q+LèR3uL{f@d< 3H{q(f:pZLA(T|1}0 sLT'c%r0Qt;JHQ˜*b/eB* yx pp`#о3/ _ ,8ibv/<)8eRn ulߐga]~B7w 8 V|esU6» PB/D*Z@!?9C`@kgZOF?a1 ';b)1 nq(qd^ gXk7c >I$QgDw7Jyü#Jc_ܱVHz fƭ*\> 3C*z\ px};v> \zT ~kT~ a0?NOU&i;d2u׍?3SBL=7zB:}#m1H3\ȷgZ\Q c"ߐł\O`/XoY̨[`3H S\BϨ{Đ]Aݸ 5=㧄id[t \i4h( -Bp#1|׎tOXH $z'\ M OˈkgŌ#.s5ER0sKB3<&xJAY 9|DB@*NKfy)2eTC8h&lH Qwx儔]=9ѳLIJ0hrXLuEiNU%ڌic3Bud@UQx?+ƅdo,'Aꒈ^y{@I!7r:Z E;EȸЙ q 2p2ۃm8&SfQ>b#O*Nn\eYAP饣H\>Q=]"BD;D!D=;S7z5(_Ma 60/6T/3y@kݘ}(%,=<=•2*8$fxwkl;N.l c) r؉vڸL+(h+$Rhp^ $zxJ¤k:у1.O 'BD+'^ G^Sý)M=_h{" OXSƕJOӎ=|\Ҡ?<9=BhqܴRuH.d!\[ek;6,,-RHyِ8ty+n9p6cVRM=ڨN(uP`{"lh7%ퟥw~qO/o^\2z28zgˠ 4t5L#|~Þ*%|QGʈ;#WKW>1.a37o?iFH~[Y J{}:=7Za]h @xq{YD .gjʸv6vJG coㆻ8w2s(C2o_)ӿ$?23{R np+ʇ/wMF_#}yd i'̿752&9B|"Um;L97m;'9% nH}a^^iyVAr!hU0 :t8m gʤ7<3˃o >3|qSVrY iB+5pfXēȨ8Z&HB3lq"S+Ss1'=.sVEY@ T'tU2Q%{S_VV>xXk[L#(!ˈ*f!mc34Ru_4*z "ZۘQ:!(*CW=7|P` 1AwxĈ§RGi.5 wxXvG^v6EΣs8xXw.CxO/d#Je-pj†H]lBO .X|u&䈳ǪgW)!! dzBt\~P䫧N{y'zawxٙ' Lw\itV#iL02¹z㧈z9 +R c$tEKK`;!\`A|êǵDg?8_ oҘA+H>:6( ⑹&O{fu |b.J ɣU냳Pjjg5e0'4Yf#I&$y1E[%ǁK BۂOpWҦɣ- O2E9̌VLVr27 -[O._8A\4cBsLKU0^2Bmbpt+<@n[] L2.a +H TyƫJ=FY£7ÓFqJ',{Srо0O47&GtZImP$]P'4<Jü#<'d>8deu橭3䄎 (HŽFOWK}HN ~ybr7-M3BA/ts扣QJUg$g7!΍<4Qם43Ɋ&Gs؍EF[s'7eꓠ9?p\(A+. *KSS1'R J]0C!{-u.DjO87A1>q;HӖR7"E|rGJ\+]W=DqObU+q`vCҁ/mT|7XGP;陿(!BܽsaJyX#P/` S P;\-uDfr"!Dφw/ 5O$##%V drtR'JoW  ih_#OvLO[4>#G._mp ^w|6^i>&:Hu;;u$bT=0}4w mXX;ѭ1{eԌ_zeх|y&XpTK1J$.Ov Q{FB2}Đ ;FQ;no1l7[pRΊK/z R 4ƶ'$x#0$&b'78g7jT'f@߄Lt/ =6.;.*Wrz8I:-wJ?gƒ n.?:K&xI@+x1wN!o.qRFZ 8̌9[Ao?^??+"}&ꁍai]‚:pOOY#/={%tgg86Qoo; '3w 5*f( W`oJ! vœث'>nᡍ0W ukU#n/wpHI`| BFsp:k`}|H{\ CW T8:Fd坸T kwLf4ZNZiоHy9_/gLNF' mW'$|$"bܺEIVY/SDZ6f),1|K Qee;q/'1cCO/Hp+M3qJ"Vy3ID 9"N7' HZ8"vgu|/-'AWr`S0PہψrnwdKP`yrGaLtw:Kx#`^N]ȵ@f362^geN'r B@1;Mn64θs 1uHbHPs0<ApP,v#] xDڅLe s0r̅4MipU 1 &SL4C4wFT|8|׭srA%d\c#ئ[ $8{%up5G!HƂZ,%\S4_XTuB|Aʵߨ9CaF*+"&oȸqۀgGYG)|?)H3ʹIN׈} v2J( \eLLUpg L3liń/zKnCwEtR`ANrA4qt27psi <j7Vi4u$c#Х#лK2 Nyy|Wp8tr,Ƶz6+;6aԎyN Y.Ⱦ#6B;Zƹcc_3fNRG=*mJD+?R<[ eЪ1:B3e>Dhd4vz@+hv8iTn"o g$6% e?0UD }z d(?,~V#uħrN =mPGG>W?;č<&CTi8d%ϙ]_׉Tlʼ6{fO/lzpuyVl~"_=c @<|o2P,?q_Dvc%'؁G4^D21D'Ng3).i ;iS$o$<=yg< wdP ,; kgzϰTL:WCh^HS}y HA5FJK oQ%aew[Q|4`yCЋ.5t4\@JE6kYWҷ',eD! |X0$.3=6 ӸQnOG`Yȁ'dpj /uِ d]sg,3w|b18'8 DZFwH:q*9` @+L Os،o`e#Q<,#St”&t(=73ݠˆ3^֎؅0@zڐqR$ 3sZdPhs#^+69# chŢdžk%[D^ٺv_Y$XD{cm ^r xrqȵ9BX<=nty1;<"ẋDƘ%<ƋΓ.74 YVBx}R*8W|v\:α;;R7_ ʠzSɲh }f\qsbL_.IG Uϝc d}оvDw7\z];R +ȅI .WXvb&8F7Lo1` ؠJ\ȬJo'%T++r`/hm#BkH-%ҤѴY0in J.3N2H]籃Ӏ3V>) Zʒ 7L NƼ8oָ$J kBꁹ;-lB :&BhJuKg א < nOdbN0VtoCMwbH=u+OOb3mExW{c BÅ 22c` &rQ<#dO7ة^<5OOkhZËB~H#=I^[^ai˄+*Ν %bAW$3-Hm0Duj Ƞya֓VWUћQF';,!3-UrGƔ-NEgh˓8UT_SC \ݓȚa$ZfTx(>/?9_1yEFtg!%aHј1}Y9F3 vW&P7r `)=1L+MEo_??5(f?/}/n_Njض!o~s~Wȿ?Hҕo sr*[^\2zNU q _~_2ph A3nhk{9(D,_qw7c+GM(^Xpc8z Ct'$HARP#Ƈ2ʕ9}c[@g]D@P<T=!.3ҶpDAk"Nf.!RQ7xeFlFO3|#WB'Z3"%buQHG.W|"~?%.4!?|mAfo Ht`m e`2 m S+{Qu&5¸{ܷ &R)k 7DBhR#}ôL )%pPXGkB !)f] hj 'c}{9R3ݓ^ ~BT5V_9}c=,n8NB?^HGC -ҵ Xax E[Z振~Ar :5pq,a렻󄔊 ,F>*ۣ^{$͆(\: iFF[?#];G\ubJ gjܝf|a<Ǻ#DŽt]88z@4Oqm0$p$4\PRZWn *cqZ^'ev@A4>0[˹q &D.Bl \IGLK_Rog>qY;nq yhCxD:^\(NXS"QADxAbY!UM=M4U'Eb$EaF2}Bd" &1Zp2I3n4zF=29%y:psLTgR=g8#rm|sO@'}e:F "`B`!jbOMG%Y&" &эfG'O BMrb c5(!X)8 *Nd H3GR"Z(L鰧pg&yP\C%\xG'Ov{.$U \Z:Uh" ͂ HDz42:4sKՂ2do "jk567RHP@b@-kC4ew&YZheJ(Y'ՈL6,Tݐ@N`{2hCO$ ʟ &&Z+ &%i9cC/! $ѫ ."#*JȮCaqQAaO<2 T(xԲn8r@%Jt֑"9Ou,0g62;ĬX.)6Jp,W:pZVdne MT!KlB *PK :q: \ł3\*.Y¦(E&O$:+BjMUL( H^өR Pł(XKтX)H<mD MbFVK F #vp%~$Gڱ$I Rys˅- 7YsB*IYʂ *Ddj(R/3X,l YL+ +qsj$1S+1,"9Ɵbl$rdVghi9RF,%G Bɂ+4B*fQqF"bkPd*9 2~dɦ2UvAT);&y@'IL },~]0oQV#=e¦L$FK1Kj+M3)%K4R%F} BO8l9#kTAYXzl@rbE3yBnZc$u Di-H)QRVd̢.Q#C$5:j8fZ3'm `Bu[GcXadwXWj/ eU lEAQpM%=ZNT}Agq#ێeh`bAOkBT}#GtAТ{တw(dZGN\ jqɫ1M 5n9Rd!{t@RŮ9eGG`(i 0VQEAڌ7B10Yǚ$rvt2m# M 2̂[hE$J%"Y$D3Lk04&2ha9F%ku/nӧ?9zO{n<<s8PAgAWٲ)0J,Z|&9|H6;uqy{^x#v9it1c"ZEҚW  !&4ɂXQޒՈNDoȺа FNu>MfR; D0&"PA@Bc%rJY|k>dD $PITt}G ,/jƞWLj+|1c|O"*e %49e:#'Mi -D R@rKB#Zm l)\hEC9}!9 Yآ5JE ?qGPr@ 4䤑#L-/F -eh'(jGN'Y#[UB A;OrGuEPO7;(c+&{>%%FO#y.hȍDPWȾb07 m*VYL`Y"nےn,lֈgbhYȅ~I3 PÉ'ҨP'D{AI+*u)T1GIq,@1aX)ٓjk B{^QFhV,5b* H9JZEkVq/ na--tQa5'=\ IUS"I(t5d"&'Dk! "PC1Œ6!DdP=w,zϦv`4,W*3RZs% cHU+PCETZ]$XYڂk3J;jnؔʁDC@VppC{XpkdSGY/8Ѱ,Mc3ix*mP"tB*Id[v3 Ǫ LW5+OSPEu3Z@Cq(0i"&NaPɀ Q7^_Ʊ qɢәZ(-YbT4j&>X ٲk0Ȧ}J VPBʂF@0Z hpR0z FV9 dI |/(hd 8v9Ҷ&.乒PQ'8/K"&Gc *$L1S*֊>VcBi4&"mbINS&J8epslJXB+JJVS'EXjPڌ΂xFO }p3VY\-"e=5GVI~ʸQQ yAxA<-A#Xb1*]UrĐij“IV ՜!ΤE<6frdW/~sK:r]ۈS7^h FN1ݻ֭C*lfN++j!r^j9̩9_zozyhD+:gOK[ ۝ỹ`<{=,RK-Bh+NЋ:uR $U8V)Zln8Z[uLKMVU0 V* " =|Cϸw>/;W_G7fO*h#J$Ah"s^|W6<9ӧ?Giׇ)>\tz~rS(0T;n3/NO:޾~K>[x_mXm'?ok֭K۷k^p)?s:Cuj>xs6Oo~wV__?W~_qc~^ҋ7}'NgoIߺ{]}4b~_>NcE+*X)BK"s_xW;IPFu"v&(O(eP[MC ^T1SKt0x*5!uJ-N PT2& M9C}{mV&9#&/+Է䭤xڐKa3"zEkoq:<`,ߐIU5 i{Q7U ; M{S]QH]֤"ljf\u)E3*qQ80K$fcTeWth*nfA(ejpX*ft q #5s}Q*krxFa%DF~݆8G\PF *[^D{ꙏi+ %V>?#.(}O(3*ˈlnQ_hPDl sA̞95.КuB3f1fT J? j>JOqI6g6}ί~7_*:1 =D17*FA)⡡w u51&RIAvK5sK~W>1}xfc*pok_y'>O~_'_y?-wbTH -R)GJ1(kdj+Pcf: $)Q$s)VD3+I@,d{Ugł`JKXzFޓ$ Ej$|RLU=KⲁT~G7Ѵ=s d#Ӏ GF!ˆ%+*][`JvL+DȮ"45GA85NXR&P3ȏ-5> sPd)5XbRG򠦌Z1Vv+V(` Qݡ(rcQ v8#bK a)NT}C.vƈZ1:>K5"VEG4=)wr 3sXi"KΚˍT.&Z9q>; tbT.9a.2zrE#`w>{R,0YNdaH,tR!F&}E+ fѯBUG$()B I"hgHX$ cUd}& aLt$3FQMfY% `K(OB2T)zZh NS'$95Xe-hBA呸2}-j&E >ʾ!CFtREV/AKJr XRD-Zrn0Em$HGFcԳY JdI@?We,u) QcvlI3[B+E jSZ8O 3*KV KɬJN3[\ܖ3. d+'E$Ӎg%$qc rh#uC *$ ]6D)0%ae5l+bka5Yst{)"cF[tBSgE|bYGqlwƯۆM)ƱJms@QKZ p 3R+6tlAGm]rQ"rX"BJdL,Z*9J d2(zI!Lq0[VRp0rLfMo9Yfc٣uV 2< ]VZi[MtUF7W fF<{]MfZHXNt.;9DB:sI*6mѸg##u.wF >TʰĂt U$LRǐ$\# @2Ǚh +!hS)V e3'dY Vȴÿ[[jԘI⌵[cZJ5erb S xrJ݇ԝڵrյqFYKQ,o FӖLZMS նB5ZvM`-JB,HmϝV)eSZrwgK/6{[xisojS;?ͷ+_ ?ӏ?+#?};#!ȑZʢk],NQHڠ,ɠ@ P%}* B.p7 )zSfi*#HU؄) Bgj=k<0ڃA=)Zăx=Tti=A\x 9rL3qȅ0t@Ll) ҏzNLN"Ec(Qa8" 25Ռ \C?pBao Y L8f`49pǙQ$ƟLypE.L[G<;}mkK3ܓ@3,L`:-w5kS,q?"a=/|e%ʸ[QӈM@ ʊW4PLG4Y21 d&*HvTN7bGrd|Tn2[D8͌~#DO9Һ;>/ezhՉfYbZPt%Fbv8C"ͱP[HY D@7nȗe p-ʜ"`%lT&< V9uO[Yɥ`SYlo+^c!Cێ2zԮ] r]9%,j׵w|Ã̫qϾo[]r Ma 5dfNh4Tc>#Hb̑PdU49jdUIB\ BUR"FKO)Ԡ I%#ƵیHL yQɐLia<6BSs n;Tx/iOIrxc7p/5+?9XOӯ|t\B݅BoW7>[O?W}CwT ooV響g~wz_Eksh):իwͧi?iYug?Ws,oO^+ko?OsoZRqi~oOy^_p}ó/~w>7nTD֧Z/0oߝ>W~3T()Hz݊,p% |{7S;҇tzxZ{R*&е+1&dms!-]T  >G(͙`-5 l*#)6cR-@{ '٠&ݜi |7򔸺DP Ui !؞vD rH{fj{7yN ZA B%Se}sD.q{.i5#E@ UZ4 "1ɀ5 EVy(Bg(,^CdꈻY(gj-J GP5Z8mbB1C2i}AV^ﰻ .9"}O29[ S#.QsFWMeQ]DH{8"eE)K4 4xfV qsNױL7`+t#e$b9GF-ĪEB_$B u(2fVòg0,יNY0@Ϟ*Q`a-6dCYũ a3*4ȚYpNŒQ$K\F$Z]ꈬ36P$y"u0A:03dZ,8cI6.]#iPGTri^gKt|闿~!?ϿsOo߹ ~/k_z|[|\?__so=:!ًsO{o's//+0k}qnmK~t}Wn/~yc?tQxswnZS/=/=p`VWW?{O̿8~G^7ŝ[C2}!"W?J_Fg c$C|蹻Oz'"=5􃤪 I1[w5^#@rBKTu@OSeJ#E\P<֙5J$ lieѧ-1xfc-4BIa{?X $>&P fq{V.HreT&T \!np-Ό&(ba0TZ)~E-XWGΧsry9uGDEÉB-nvH'LVԒ&te[?㲂&>npϔwﲶ\@~Y~p4zMϛP%jJ4nΩҨLV NgT0n&t= tAg9aj/ Nq9!w(PFcKǑY`{^^q2bʆ\BR:E6Z(io%*ˈ@UOP)86_ b~3) "A QX{@%˨ yT5`EȌpטZ/4VR@d6u9aAɱtOо4>qvuq?JVD }S R (YGԁ)i:AՊ N/d\6e4cv~\ޥ{Jm$ cidg+iinGRDž%T6|k]it<ðHj_Z!MNgE+G&A.LAqў."o z -Na9r|EV,\%;/h٣ׅX!Dv B0̮$)AM~Q))M n8%y(t5<gy˾h׼ڋ͛O?VX`_l>G?zOt7'?ӿC?ܷt׼½`2<*gﴟ?y.7 3w{ك!TB%Z{m+jy#?JJHx_=kgog?|oU(XIi Y'j*1E Q(T= 5ՙSi,H@;RY^ ؑ%2 b논s$*+y/(N HO)!if䢙'NVCTD!xt v<pyp" "iVWoIYd7„6(bIC(,#NL D9KtUP3p/a~zQvYY9D B89J5Z.! d,AAGʰEVcDzQuHBL!`|A$ =Y&'1h* ;LGM?Ka&rZWԸ"zϟ%Ɓ  DL']MeSǔ9Ss©[C #ysFlY64Ilkib3o1‘g8 GkC_C>H8C[OԼ%f'n=hIH.伙H;:|qQqn:*qr(Ӑ TfOq0H (]A^6`Z,9Nx$HٿKkrIfhBaL]sF`sS&-͆VCX )SU*"؉E*@)4(9BZ@vՒ [ٌ+D>1{QpUR$m.NeGwЦ{8-Hv@MY6Ҩ8嬨Z 3d{CDKLPPy¬:,M*{x̬왦ix\r Bqb=VEMw:Bs١:BADikCL4THqB fE7G*3vRՄv[r)dyLh6ԜP犊C+ipm$L' 㔨6Q̖t"ׄzRLl%h<T1MI-'%%f+cRumu@ 4S5 ٶpVpƐ96 EqUJŨ}I싁c7QܡY2z:8՞R =GC[YXC#S #g:vR):Bݢ#U9jW8V"O{cO 2Zhĉ’Z:U1hyˮx6V 9e*2c 7bHA$&X %Ngb #UT3[A {\ҠLe^MЃϮSv QԽ lIwUBL19´P.+O?#`wԏitH~__ޓKhf0;_?.I_qcO'œ_~?y󓇟_?gi9|J?Kr.p[BM?G?ÿFKOSWZ8R8T9_}7_}哿o)/?oݿʳ]krFd,QU%(!T@YdV !&ir0Sp! $6WK2#rs,T-%epV\POڢK0>9jkM@gIHj,6JPGRcPW+".yOQ|ƝA$ź嚔 uTCd,:t91Dl-[sI+sgHsPzw,A#g/`XG-^ T'١|A+uBƇ%`4 gqao /ZšpU:Q^RsEܮe"qw{2&,{"ׁp*,dT PzF8LvP#KPz. ϒIN(9Z bĆ1`'54,uDڲJ`*5Y(y ]hC UqTжa%#ET0s==H+SsSjdZFW'[j2h;)l4aĉ}(NG)ی"W3vH'` <&ylUsګ;g8B~y.M'%3F}N9^{yG?awmcS+'ǿ'2茖O>89RU^q=w}ミz%;bʵvZˏ~ks{'?>7S)?wѵ7^w/:e@PТPvw{^GIfrAniC m,'V[Pb CsIf2y-aP&3TT;Ci1L% %"Hw4yfJ cC7(XLޭ%`UA 4# [AR+D.`:t Z `'`Qm"|l?F\wPjݣCZ4*UD} KlPE]JAv;ZHA⪠ (bPH.q%AhW4zr9dY!s+gAS.@ $ȥPv"Ф~Gi ]q27zv]gwb'Oƚ2HK:A|Bm{L]ݡZol;|a:: WDS8Rd& m:ba S1)oDv756zЄVP܍Kc2"p]ACfʎm?N#F\:TTa\{A ^4>/{׈F7QC7žg{a/GO@'gl3#ڑX#6GMmճ!̹0ʎrʹL8Q 4jԢI ]ɑ{CBFQvJ2I&C)" >FM}#'*W׮+yQΕ6vZ<5WM͓Sf׌B!)3l SݸFr#fµ\N>, ݐS4A;,Cq ~$UCGϼv?#8x\]V e#*Wȵ rY=y\|%p;xBCt:۵O/ԋN E2_Ԁ+BQ0kh5l HvHA`y_jw˿ʟ_}xOOw~y-7~|g/Mwql?__<ʿ߾3?W~[_og~v7';^|~z^w"OƯ}禶nV}]}%wǭ&y/c}8/~}_hi)ޭҿs뎫]7~3_<}kw0Ix"[##Ygln_/?~e v;b˕~;`N!=bk:!hajHxte)2-e,0 m/v. [*Al ꂍ iƙAts@r'ԅ~ ~a<* MG=)8% .Rܼû>a~cˀ2QBjvHLQ[j LwR96C˴扥]@w|$C"g,Gtr7/H![>$4|{O%"ӊ< ቞7ȗ隩 ^n65a!]lyq+q{psb=wwM'[bo}>ձ')o&z)-_&i!|/7v1$r _Y;PKgBpqi:r\qyub9yQ1v]r'Gvsa-J廈~+(~Pќ NqG+^ n̵Ӽgp\3:cتPe b:o.3%W#'Ln0tvqF-0Xa0B덵5cd%F`<) \0%e{e2kD5w\C ed?;'mg>be_ gyZE; ޥ]MR:zf]ux^ :&B1Jm:'mNu#J*vزpLe| \wAN=nn!rͱDNWHZyWGnBxc% I>Ʌ}stXp8ݐM|&"J[='aj$iAiDlh97",37V=^W*Fg"]0?Tl: }O?|:?Ng6WQw</.8䯿oV;ߝo{//~'o~w/?O.~!dYگ׿o?y9~񖥣o]W6"e̗_k?_?9ٿ&{6]݅31´?UqVx.C|m bew^(-a'hV:.m ;J G .1" }lWdCO>xl1o#- /:eEM~QinfA9Sӕ_Ɨgz9H ;/ !iS)WʵnBَQ *ˆ<X &]5/j+-H@b3kǥDԱW ! 3ԯd\#B=0NPQ.ENxLÁ6|NyC/Nm%V`^Po6Vwa&} zKG{cC)Lw  ʺ7焥 w֩:&Gaƞ.xKg+;%A̤fcٳf!9B#I+ 3;z&0u)tN coEq%L,g:چsBy :Vc6> }K 7gDt3Ԍ MbN4s܆eu *=A<*GTqq|Gҩc5Gr0 j`7-w# yAo Eɣ@qT0qh3}2;Z2*J!f :3O m%wU-:_/WhF_#iIi/ɔaZvЅ}Xt)u*t 8Z䬁y(Jnc|3k0|[*mbVg:R69S%ؗJ ifurmA13)UIkm8r7J hkquϢ;b.h⤬N8JM\ܫQ`#dL% |-d`#q,ardΏ3ׯv8PP>{ۊЩ>8YIer;C/7.ȉxB^^sA\!Wh>r/_Zy)5O$g+\X6<3ٓO;6B="Ѻ11,v|K ړg '?&Կu[9u'* ĝ:/pJ- [{AA.J&l7liHMP{&</wS_(_??Cݷw'l?d>_+x?xZ‡oO_G_svB>)rK򅧤oڟ3z^'Z0{O˯/eGw/xg_&?'jud^hyrO|}W(w?xa>+.~ٻ2?]+`0pt{xA6>XʊoׅxЧ #낔#N1>+{3?1l/>=a@熌W^L)=p'z ӑ&'lo8!}Z+m|;JLHI |!l7#iK iޒ%T:[e>#Tudi}mz[ؼV(8*=Ǎ!< VpBYգ0`J~_n3 t ]i\JNR>2zv{tEkGa 0&dx`nJ?(B{#&m- LY/N}68Fq# \+p2` W <7: ::Ī Ep[jbW]c>%z_Iq PeucR#LjXq[e s~R+:q7$Z3KmV(m 'Gz;H+shzC!&! Ju]iFi_TTWEƒ"WZaERk9~.Cƹ涞YN3Qųw7˕nm];qx}9 w4^Tmln8Fe[qdL>bqpR^)ɍύQ+}w>o9O{m:~t9sWãWm.#}2Dh ׉p*7GBm/o#2\ٵ E:b2rWW}`r u'밠Z{LcVpWE[C3s跬>jp%0JUd“;\#ٙ~5b+g~ĮC)x~꼐2;ԉ}Ƽ ۑufy3/ W\Q:RQHwn88,N6|S1yWE݁>r-#>r=ĘŁ go7s2%GщɟI~C!q&LGѣ8L7h =~""G8"< ! N gsq]̨t?`팈# vRlSzqf~=}޴wEG"_{UhL|'۷[?a%\ԅ.CPuBR?B2c+y2=0uVzmK#SO}I錆y0dNyWKe]+K!Zb=茭G9N &A>}Yv 9gګўI[j:EBx!~@f_.L=bZFQ50[>-wZ9p݄ݠX {GV#7qyI#Q_Ort !5`vF|;u _4i j5"7/C;GS/Xd!p Od'?f;jMTA 5VlWbI4qaц#*FLt-9Q4?o!*ϕL螹iG=*i_vL/;*ɈDWi2:hz)-d`EѳvǸ:YN+Eh6zT`H"<38̎-EnkKDwv]~ ϑk/1 2=(X*`@ ݂D{~~5a}a (FW4 %;?W)mVViҼԣL֛u+ K/TQW:5vO?]8nz?-PB;b5vfQOW}OU^:Cç+WB\ふ.q`3u= AHRay|{2 *apz>_pbm"G-L{gθW'BU<ؘώQ]%|KL~<U`|P/|<e/GÂH$FR}Tk vÍV~%Srܗ: {S0әYldܸ!4X rg ; XYxZxQoLX bWa< x?r>gDyH+{R 94xB Cmg,SB>f;H{GY ndZiω<]aaOПxeW3u۳Hznj=..lS-yT|捴Xi"6!VfhH%Tz8->5,HjP&tƻ 3DO)GrEXO{.ׁARꞫ?⌭ AW%*ƧԔz^R@hsL}HqD%O .P >ⴠ@-JrKkځf|+qbo~4 :t/ ow:wx|2Չ ׿_]cD [ᇆ,KptsGh5S\n6 |S)$B|ė(~Ks7Ѝt+`rKN02j!Us(lgn7;~ wy"^;5°PNv3 3$6Y@# 'DN#ZFk# (4kkc\!FmV$ m];"7vN#=}2b@:/ Qhc4zjY^^Y|Cz$D\[WCBs:Cp" *lRDgs-R~Bp!UգZ]VOI4zJVwH[[eUe.\C.`#Ub (gՂ]ynQOPfm^8#=⫐HpZwP 4pb,O Wj Y1MC(NNc%fGN1䕭++3MKDظ섰!ifOF8L-Ͱ a}j&Z- Y0 Z9|#4C==5JNhXz\ +;vQ7*#@`eW':3.9iAץ1iWI'$"BN>D;R7h $3*:\CȞ\Nvf?>?/~k[SxH{0/}'Ou$W+Wnͫ;GsڷȴYGWېg?ѧ}Hڔ8`|xwW7~9[~p{G?xsO߽~oe 2׾~n^/?K搨tGtD`;n_C.H+Vn .TGdTR.zC'"8a FR3\Ňg(=tgh;fl|/ u *u@E3xCm iYwh8Vc |ieSFlstZNpJT3rUg8`b%GJQ&ﰺRDnN!S 8Bv &ƻ*\e'ܙrN)=z6"*HYᝍ쒒|Mx<4|6E֡uX֑`8<+SЌ"Iq6 LP%Qz@Z'EyήkF*V6$t~q3l(\zcW; 8:'{:[/#BBTJ@%3cmBHt&b)hȔi[E\W]@v_@/ ~d|JBqgRɁlnc惣Gw<ߟ߼aJJZ{sKo~䥱3=(|_{{n?&Q7~6Fbi8Ï| =,>%K~w5>7O?ϽT>ڃ=F ;7ria$_?u+ggwu0Mp&6f>GC|2nf+4SBF_[1 犋]"Zh7}3"J!.q+L ܗnp aUgu#{C?VG9[=PD7DZ ymOn;]@[!'B5 !/o;E<#+t\L<a o=N^* 6 \q!igFpQ%~@ذL-?ґ69"0~ |FwwYBй^v% MSȶǵq=L`#EH{/N+Uv4Y0WXĶ&Z2GP /#Ed԰bPktEqiF|E!#!#j}4I da>j$vpie9 ӆH} 2F@qO?^EީcÊ'R'6g+% %s+-\0GF%owR(rHHӵ>c툔8P*.yhQr }(3~[ԯ,vBNE. ٭_?=eI#+z@ LӅ~ -B<d|%C0NHJO g |>J 5`* Ϳ)ҘkA[3˃CC*B(#=ITt;q rPeL9ޑڪ }'6#UVVxⅶ\b}8jyZ/CCk?FYA+~R48 T|lȚC8Yh"2w'FP1##GlflR3zG+wo~iKZC!L:^?M:zqCcǝr(4;dNmQ#+pBH7τ̞>n81u6/kA_$Zl1ݣs`X 1qH1P#} ?U;-M}ѻ qd/`"admDĹ .2 ,JI o0Ȟk1[Q,:~VxQ{G/g4<#["+A.bGU눦3[:^ +b@#%>5bA RB/FNuI[DZѦtj$oF&!zf HlShcmcV#aVӓ;Ýh XƣwɆ;U5#=_XWfz@e:(j*5C6#u#Yi%1!+1Α$֍y5óȊJ©*KF=;צLt%-#όɪӜy8ruJ(rzd:,,yeC"(tBTҪXҧ993qWaה*5&ʥ$n\c'y]u{J×DJ:K[Y|d׫c/2 wWVOl".C F7U|;N+ل 8;zOK ]mΊkJdY`*.>SN3!0\nq GOT`,^fGW{`·K?ߛ/xlɴtza'2QoiGZ2aTH{P*~Y2cq9Pn'gBE( x6u\)zu SKB) `mN#6u@tC3)_ɇ|-x_mN+rtt˝!4jk?,t, !_)IFI %cK"nN]we" {RZw3|tp>N9FPvShCEF J`eh -lA\ Fl0وWFok=6 W"q8ӝ [}?]fOyG!,D|BBr{a"6>W2UX# 4_H >,s ST1Ulwi ↻NخWG9 $C݃H)q8,>9B sBmFl$U(s9tFP@hmW&&Wijwk45| (hMlZh %1Lϴت7zfK.L͌leQzb+&z˰elir#y|Ρו* .+#'ulq]gSuv\2++ԡڈPlXQ҂s`'-V7~(oyiOoO|.)a@0b_i*ޡC̬] #C0з!IԑAbTkxqxXv>/M}yKLsX$  %NTMH*FK zqIË`sE]"+Z+Bj ϝz^\q %$ŨiOk+rN-19,{\x:HOgtrXo!F ~Q7\ M2 */t?ih6Ig m68 ӻ\AL~Ӟĉ\ {\+I8晲rCS8+NЄ'#Enm[Ft*CeB&,$?cm+:Z:H#ѸjC&-ox9Zhv GvR, \Q$ARBEq u$^v$DT"nv؇ef;93Żw!6\pykErL0f)i`VV߉FGRx"xӫ,[EQX ȐuضJhh;dn 4QRh_bVr  e>bɱ&XO~ƼcB +C!!ќL )3xtحPQ+J7GƥWƲA*>le$'u7*9q9ɾ1^*{q W6J҉TB,gF~)X)畇AJǪcL bP@5lm Ċ]s8khTmܬsk0°:XW%'5nH&;Oo!\3z+{S {Vטc[ATkF\cpY$3YuJGtь G`aÛ*"܉"R'΄9"I=e=#>`mˈKF3 8oJËRkŇM VcR2nE=ӌJ ƴIճ+CvC3\q-чZ'\JXw`m&{e!ی9$ w`HhY Ty֖ BGJzBg*Gz[' {z%)C(/WAqȶ7 vIh=r%z2r\@|{?͟Uglc#X}ƹ#Wd6" )+LXzƱõB%:G#I%[ 6#BڑUT~,[P*mT<\womⷀ#;?juh2jZc>H1D a 0wdD (9\ ViUIm%!ca*uSɎzb&ql5SnP1ԅPw0_I3+kF#.wt|b%}ŵd6bnjf!aD3>bvimL /4m‡|ɉW\>J@!,i5(+6tmFBDQ17ҵc&љHgyX7!oP"}!-#SZY 'RȐ;U #>6zܸ9y6+D+5!%DkwLk% DHkG+ňިApY euw88[#veqM "i1-L"lwZ=2+H9@n TPA7}a^r'02R3V:vE#7#oI=6l8:PٲqvzX,lI V;Mޯ0+".pqtzj8i.a0djL(#=券N+e^}Β7 4&R-VS2zV5!􅢕ՅcyRvkDBx@_RQrp/-VXEIי<) {6#np:SiX|@/ߑ ?+=jiE.+N" #+m}WeDڌ uRS+t&b_) XK~gCJ:UJN k0t謫-u ZgOօ275Ĉ1č:; ( 8GUZ8^=7z X7NsPH4,<Rw˂ 術eC#&_Ý:vX0gS鲡CȰ]cSBFmY' 'ô!ٚcW[ԒP_p#y$_WBɣO< 7a}7ӏ'?*f N];jBE]m!nC\%:tء l:p&>3Q eO/8-BJ'`Y~q0PꅦTlzA k&8!zXSCܸ= h65#BG%a_.󎬁llcDz&FnР#~0oHUHFYxsjDHL))  ['!]*8Z+=+@7C$)[8VvXQ낵w_; b("1Pcb)"F5EU0(u1L kC` LA:f`t@D p007-N?79܍9W+2R$`)$l9mIdJ3b=cv[dLVCC.nQ"%hƤ&u3p_/Vw* ΘEer3?sDD2Cjg/vEtD\2W3{u‰~:}Vej0ծLHƨ! %ge3rD,Ylu„['1\hjw$k6CȐb"]Z@ޘ`hhUO1Gě l4+Ja1BR*阎ZQiVS5جFZ?;MAj.D|G3[#: u–AQlISrVvtyb/uvHMUdQIPNE0t$PPQU\,iI5daGN)Y:]& %]Iը2IE]NQ|8t>n̄^T.q[cJUS6R:tbV#]&h2KS GܔKq);qvmPl4ÒEE ;+sLdj߫YR#w%C >F'̲WQ˻y ]5g4{h/KP.' CCJ1[RB1r Jc~p.E:IENDB`openMSX-RELEASE_0_12_0/share/skins/ConsoleBackgroundBlue.png000066400000000000000000004010631257557151200236140ustar00rootroot00000000000000PNG  IHDRX pHYs  tIME( bKGDIDATxڴmvU6ƘcS0P4"&MR| 6CRHC~:R""RIƘbC#U `HO4{1akk{?6DB}5לc\zя?@%dWGU~;GK/_˾/Da?p\_VST|Aqy-_BEk/|xBonWfm aVA\E ƵU^\Ǐ<\Z'}{ ^X z~Z/ BZ맴ټ{>W=[xu6GTz+[EE4? _A/_@[6VvſwY3[·fGK{[6gїx{ֿΥo~>J˵k/oz O;?wJ$Nqb^&2!f&>xD 7h33ML ExWi_z٥S^N8uKTH8oP4D|7"q11{!"!QR!jo̔^0:{xKeA=u]t]x|zP8Vf&*CUf]5km4o۽ME׃}l[=YOny/C?^}U>52#z.k/3ﱞQa{a/5!˪!EĶCtX>QgTz)D +־sǴ,myKMZxtЗ ;Ӈػҿn߻ x j_G>7|?HEx|\l3WZkVqxlغ듸e?ʕE8̶Ѥƶ D|.h06}"\<3Zo{YGz}0Y~l/p>eEkgԭo<5}Da3"r;77~w/~wDtlGn7s9t1Ucf;"QXuf(^C6@O:6EjZ_rҝ.S\/FBœ_,6`/vv>oæt/:ĥЗ~3,eBoQji.H.#B朽Y!T&YlVLUZ#ދש8דy0ZZcφc_gST(CuMj|y^ ck_vbqʻA^?hj ?|If>?WEaym#X{&**7AhGT"k/r8HꚘ [kX>/"puB{ > }B-cTLLΘ ݠȵ,uݪCg hפ+ں^t1ֱDsC ,v]l_Fk4Gֵ󾭵Mg#9<ߑe!4fޛu=>b:9`?ỻL 9rM{B=v#aE `fM.}:_ AP7,a\bg&&Hh <395Ld̵*o{7w?3_9C>JLEޜtr s K{ËUᏑ9*k=B5UF kJE|6be9. d=Xs}W0Q8+V'C*XWx>U)G52A".sĠ̹ lm&wKzcX("{kKY :)yXWwL 5|5GIW.HfH86FS4Ŭ(>T Ia⁓n58QT\\kB*'qB>/n z8<:U2CTC*C}nU>:d}MzMQ3oܛY4E!0"#ÓYjFc)'Bי]Fc2q¨,Θ *I/~>kGG4˚9gt 4t]\b'Qد# kyOJ)ZtwZ=Rz|yfs_m}瓫W8!yJncϻcq)sQCέ7QA)58WɍX EZ zC""e \k`X4Q{Hv\X07q7Y y?N c:6  f*juD8ѡ%1CdY-!gw wa 3Flb::ݠx5.]${(! AQ0;L`lScSXA(!K j GlS<ցU. ᮔmIK{hW>!4,d7?U9Xې9=7bZ} BFFΙJidVt*tZ{aD<r6L?3B_1@nuTUȊ- (-4  r=z#Byl@hׁ m*M4qdAVkHpķ19YqoJL< S5TyGRLΘb1 *N#""P%}zX0iQAӽCh{Hs<#fد(sq- etoުU!hu d d\2De^͙wBsG o͔ǔw?7[_cQ3ׯ壷']ܟeN1L^==s5bA,5/]`7%t2k4|g|7m GD"cd;Q~EMz+ZD}'ڸ\(>vJ`dijY@|pgcȡ&wE"fD6,ABu"'c` Iڌ0QhT ܀xw{DT_qJ'}9Q̜KGp\9=Xf뺭s5`3ByOD7Z#u37%ay80KĩKG O$b,1@_ τ_Bxa,EBCD'Hrs˚uoG"Qaܹ+9:72r"(m^ TRy:A(G("]LD1gMu1yfE(!D;d 9]NqbTPKF?_J1n,܇3c a>`DϜmB(l%63Dv)V=ežpO ņZ(ϕ?)yLmKx_\qwd!|48aC4לZe5]kk=k~ߑ_Gk9}ʿy,yӫ'QQ3ե "K7%Q  œL|?9wyI y;<,^ȇ|lph_<χt;bgý$F Rꂐ&_C6n^h2kUG>* BqrnPQ ~,Fj\^ǁh`nͰQ;V߿,L%GB]L-B^dyv<]]V+M6b&_̾ہњ^(<{OR)*`U- 1z_dWT7wqz]i j:eagqLK~-t8PM`RQaC?R뺍|Ɗk"^M8.4*RcNݔZ^ݑܟ3ؚEKsWэ 5<} <匐۰zjV%/5`~F[`4?jT ~'# vpZ]D('DddnI#*ŨfYАRb"_;SكHM6ixn&HCEgt uByJFZyƱ{' El7ԞJn+@xsc!)Zr&J"{EȻqg'O|YWO&>eQ/IB^6Lb;UUnjfDlՑ!oUH@|R cJ~)Pt"ŢK $*_O,kdR'^_Run WDZz-J$?jr;x?g)"*7h$!X976#ز*Ԍ׊UQ6W +~_Fu\Xa$u@A";jR,e;h4p 6a>hJJB%rv/ C?J"|cbx܎}N9~RY0ݼî#F&Tn%2 GFu!C-YH{Xמ6!8A}8g>Z>弟776mfj5XO6eS$.خH"і XeΣyV%mBn$ţnv{ĉ>bydo 2]KUOƇ})򌩶tj[[GVZrӭ@5W!CmISJ6Q8OGÒm $|R-jͯ}҈e3SvTⱐgPG}Y]'BwtkdpA $APҿeJ[4fZe^~K2(V7*KYMYH aZ= Y.rf1Ԥp[i֦ŭ1m 4 \o={\j隡x*ؠ4.H1-)EQ^Uȇffě=~VRyfR%~XnipAw*]+l[e֎$/>#|A[6Xn߼u4-XzvT[E֡F3ܜI9V?ma# Hʏll uAڍD@hU5KJ (R5Y>YLDicCѵ܁ ȅMG6ijU0Z65@+mF!}8Xڙ) 13ߜYIsDw&s~3a {T?{f&'ų M8nV篖^J& F3nK&R I~5nma:uQ]AÁ~/;(D:~VmÂfٮJWQu6>xahh+"Z,F,9#$bm#Gb3.b7]{uoVA*vBgP&O_cݓ #һpM6ONY5W*@#BiFc |&%̬ V℞O_ 7' $z͌J#gŮ0]|)lF(nK/7>>/_1sʜ6osE7"ۃihX ct!HWnC^Evb!U gaR;YBv?./&+gnRgj΍Q&?ꄮPY!) /nR7ߢ7" -yA5_8t X|-ZMXޟ4'N x!9\Rd)+rt,=dI^g싖c*MþH܉mCzes5/ǫ"m" ñ1wC Ԋ9n 4ذ1iZriIëe=\ԸL?"Xrjk†Z]!-Yn& kzU*&7{T cdD.AV4BH9# ىṭ6%(-y\,X<*uFc1Q9[A^Ym`aʡCnɫa? }?_y@[)KDHR5lBƁ"z[bO.{6c6bS׳;3)Qݛ0P1jr'i@)>hCW)G9FzjGg)~UfrABŨ"0yͫ鏽WJ""Ml{qj1URC$uj 5l*VR} $ZŦ02b?Q! ]IP"0~s¤'Lypˁ̾Ml@ FaerJTDC,PFA9)\u)ǮtF68<&KƱxastl~M];#;~&?!JYt:`߰j|N@W;!"5Xq$Rh {"+<%Pl䑑h"F\y*h|n3 ﱬ?o (KO*-6uࣄpe݄ ɲ@xA4.D`.r ۮ5Vۈ-ofc3!Z0qG 6i>eZ#E2O6P@l=|]}I7ҝlZ K^ 7FA #kfE~ysVOͬĞx%' 8OA 3$l!Q>^6Z|˺ڑ? o P?Zm 3H9 3BxfQkGVeW.D5|J6 *2, M (MvnP%{}&N=xs?+_?ّiiwv( '2o0棛EC IfǘtnC͙"$$UȤuhU!v>@!xy=Jd+8<ǫu5}!ߌ$igPR.Ve?Jw]\ C˗e.ΩBwEXT903[%e]ߕwUEjl!co mg0=ʽ<6/݊TI8'! ]2ң$h$:9أA02ug4&srŮoUP:3?I$GQ˸dLeB9w7DrrK'+/:YhEF$2Ofcؗm$LXσ$hfq oi2 ʳ6j )Aa %LTF'sh/4μ\{FѾkE&)|fhTf):gY(#sW{:CQH]49#IQ9cV d8< D6ن$s Ǯ*3!s gBjR_׋G0+;75^b41/',T}`}ཇ->g?~'??y1PHL5trw ^@xE*K}"GuĔtLXJy|hv#Pn "A:"Ĉi7ɾAvĪ$)B|(a|'&;!wpN;I䑪KࠥU} ~d.>Ƴb+G;7ãPxV{)HϘ})%+w~܎ #Y|YtL( ]ۀa\cR8l=APf߼؝ڊ1BH=q-8g&͘vPkɒ'Q3X` ZɲT'`d eyb FJb7-/ -DT$ ҰEhDW J G09cЧ1r'b+^IN&NlZ23I74j5X\uճ3G[]a9(dxo!H)l/؁hbDpT&ұ<}5R7&WkLp"[n4r0j0+K!X/*Oɮfq}zePrV+gKo#\`5"_?M^1LM~;?!K$'?iI`' r.q?Чt."$v/7o@+p<>ï /[wq8(3glM$;U % ׬HJx?rͬm͂.X.,*SO)JIbUF#YF@9uE bb+_ .(FC[_:BऍT,Z[E"'Hiz0 Ž̾OebAN\|ml. qج[,Ր׵8g??at覞ےma(PPa}tyzfFHNHIՅ Qen"$RkSJԑ.NL44oh#-{e2%:xeQkx?\B4BR$RN:g ~ JZK۰v쿟3 3V{Jʑ<z 4 $] +7lBZJnsm"LMh|C^CfDh)#=n…3H9 S JN%B(h6!f9(YYEddw<0+fdL#u? ɠU.&!=n{??+_V F41R~2LiZhHSZe4b*S\0i" ȼ!K;2S9>Ay^UA!tAྍ !{E.Q M5LP^;r=N W^~g^QQ!!v8xs =AaTamsC-~ce8IM@pFv(~)rd<*?:MI7MM9b\scBM<]tzZ6#;Oyu#˕wݷ%Cc;9:|!< Vx@)Ԏ#7^V&3/<9a;Ks-vٰF`xn?wU2z O82MbzGܩ`S[8~P@vz~G6h[U=̚mK00d9$|~A[LWVd [@*'1?$ϳjl`!8,3yf|x>qk"v.|]?ʯɿ?LLιM\7IFwYQ5JJ48TxӆeK!aUA&\2%vdm:\ Hͺ+&(z4̿G/6r'{>#f) M[ G%$ 'qRp󞗨T!N I.US.Y\vsR[\myT0yxX@IeXtf)0{QFƪ\pHuAȢHR;sNmF %Ow9ɫhu *]իbeB.U Hc!@%bzRR.$Ӈ]ֽ4 ԽDcV]D<\:ϰ*%5\OQ>S #RFN"N8Ȕ*$e()LR! 3g1cfOcը,6|)CK +j59p8r KhNJXH:T 6Et 3"g ט;R\'SO{,˳.q2MX;6 HA#2m,ԮGKiQ sziRƎVy7h4b↼GV<>ǾWH??/9O9ƐXDy9tPvErZL)N편pwP*GFq-ȄHL@~(՗$ +$-ʥΤt*W7WOuH)[\/kR+ iit)s! 2V$ E8mS.6 ,`k.-Cƪ?ʓ V/VQ:Q 0^=*$bQFGv-!15x)Q)($5brns'Ϯ’)Ky| =lTfW;-ΖXn躥Ex8L>rw3T5#G{T٣==GԨU !񡠱h(4DzqxFqjjLnMBCE^((?sV૱\o_%`<u,,qX)X N"}FxA0bU MjBaR\ ]LQ]ᱫ$=)e*~>^Jl)VqD8-@ҳcʍ<+B/ÿ,[bK]j$UCs o֗!fߨME5▤,mHڰAndKVF7Zui{5@!TazR1 o?%oz)?%.<^RXvtԫ10aAy #Ʃ?Jej^ ]\e?_3mk!K~gl(E(^ÚLˡϱzB#GM R4 kf),>GJLY| 2qe~XׄHPj>=r͐*yUFb scu o* >ZSFS)(y܀X<5f7YnxGad5[>)㵆بHE?8t(s{>:ɀST[,O}{UVp .IZ8nX#K.*Hm6IyEaU8?SYOcȇg[n⯾/}?S tUYcqV3}s^Rup7O_s4G/<6[CˆQ a]ү&䗕3Ӳ2B_czR Gv(%wSmU\]+(Ȳ2}MaٓnBhlaldؚHE>SuCܥK{ȹH >Ihlbzel( Pl˭yck4?Qt\{N \нhRߟsY\EGF4橤MTFQ\))-lsDV7(YQQ)${=F~MK66qFC u`^(Bڸ%Ng9筨dhvl_\A␼˛,Dp #8Ɛ9<M3!x~Oo"s*bؐXsqP4P9z̐&]ݦvl7ՄGXMX@ji>Izv TD&#^s RP"be3ts$Y&îsrZ$a<42. Ĥ8Jnrg.Z*!yDc$kkΗt1G ބ.`:.(^ JJ<3B ,&s.>/иQ~0Eq5 &.Οv$ϤT໣\B2 , ̦ū z$5nggJc*3YGjmaeJ*.}]QY"FI%`z)z3z93VF9Lu02Hjj9g2N `ʦjD H BٵpHH316 6= $O%賬}iU|cz,:_y0yEnȍG;-  q3HpF3gq3EHr^gmiih[8LEFZ\b/OcSQ2BSʿ1bL;~ɽlr<( >?3߈e<5!)N?7nTjWܨ6c>xb\b&AIVMd*CM\cC":/"巋̱aCҤks\t?h4BQ=i C%VeKͥZAJ%Xi #EZs@-k\6.$&@P/5Z~>Xgp;?Wqzm$L2pRpר-vjŗm n1} E- =6g=~Ut=Μhzql,bPk/Mx ǽ:U`s >p1yc~J3PU6ke甫yXM/<1D8V9dPc3&L'nOVi)@7{ӱ&uoY lHu\F?o;>[nDZKmǂ\UC7"+#Ļ7F6?vUCFvڊsț |9O2,&{!d)oˤ 8yf!3Oe"c)n(*gtx+e/(:+'! @3 2-5J0fٚ&ョΜ{TGٶ L*q08X3:<]d`WR ctiaH u 7wۉa%i>:LlB?Z= +Yjؼ6>/P+[!"RMK8xJCԝ@DX=jqQb2 9`0 cW<Iσ58WPb^xdd.yWxP{w\UQ^H5SmJ4e>ar1lnNʐ^"CfS$ Nm lP9`ES# 1!H UqBVű%J'IcfT5P-ۤ@ăkC9i ,*Q\Y6BS"+u -Wk:sFțsU7^e6Ew߄6*mK!@';cpd118iz,UN z_QTh" 55P!3^@by-5 сff΢3ED[]Qdづr)T+yfƈNbWjrIe!9\~iqj|g6t,_;*c$ف0T7OiM_|Y4Q2T[0NV@XL9i3JٽEj6ڦ!^.\#+k2 +Ѩst0:GUcQ@x)d\!Zw^hy+w~MvF:R$4Ԋ-BҒ8$hϦ"-fT%X9!$b.\Ax&b+x{9F^H8ڲm tS :3"IhؐƕJ%OdpxC]3Ƀ"xT/枝6ŜYz3 +q1y8 $]dz<7IPF2kҨѹ/[\ QJZQ^s:,Fuw[!+ Bpؔ]LrXƀ\W] fMYWW,xNts#AjR]\d&Cz Ǡ O8?qor w#}<_U ܎b3Gܞ8(6GefoM G=@*G!:t)Ơf㒃(DBnDpq~%6bqɧ,Ty:\)60L.een.tʄmuT+DgEGs6&)9["87rF"VTNAHd\l˱5AWc̳nU8 mT!6QVF9ZZ"&όזRRVީeTh ffe[A[S̏nV!JfQh$ܟ?Rb8D6 tye$V|CM^&.Z [s$ 7qooo{䋿;nr^?=~yO(њΦx xs|v$LJ}rL gHG@gfUndy1hIy`%"7 #xOmlwWL@NfCoRb5N^Z!+pvoGGU 9 X-8Z:cGAѲLX&(|ALȇhS|Gb 3* >d!G(b},9/#bFFGn؁X! 9J֜J HP,(Q"m@hap@^·H%!)e rxbGhYUZR1B7!]X^>eU B.Hm4$)^ Y<6FTR'į@`iSq^KUi.ͧm1(HG)2F XLcu* y$(Fk<CC-Ta?wA.mz˔% rW3W,وʅƵAyTnZDn(blOe(;eBnަbF-ފbrZ-;Y$A)(`^>"7bznb*Fږ$eK$|5/ )z߸|->|}z&2D~;ޑx?mȗgIpDR:z;-p^(ľt&|ZXn r,$6w"8 яeWaӍT\-}gR^M*ɩXzDis̈́ ~6h>( D uw7c314%@K!R̳M={{&q֧1Ҹ َxmż戈v(WP5!cc-Ɖ8YD-d̻*,8Aio VĻԃxh&KhHS"R<5o$@U7OT^lrgX+`1|YOw^tY ;;Z<4I4䰝V)K9>dgrs ݝqiרw|2FJRq/f-^QFs0f+IGQ ֜#G d2bAsr2="u_Jrˑi+lC%URv^{I;+%i9S:Tam0d5XF}3dê % GCƩS:̓- gGzG"(JG['?P g#5kqIPCكO  "<@0L I\F,9g=F)w}K$H8ك#YH{+eۈM^![G/*~?yW*{ѕo#;@V/tlɂ* JYYm@Fr;>kW-R'. ?~-t5㢛VnlzYQ3\"QiZI,*Omg Ș F WupB/\6'2E]9Qaq "{& ؾ(00%F<R5 =<,E?ִ$^fVdy׵ʲ+O(RRv_8x?84%F}]ݰOyհ/NweGR.l$ HD#\*up[(*Y "S6G3\ţIO'ҷM0y}; 7P?f0)yw ]/ /_>!Hls@s3GvQe!.X|=drg >61jm`j>߽V6ƄmE]' B/^b1  $L/] H!;5Ur Qvh~20Qd/xHiLT6ښkm#ea\~WJ@;Ӎ} ـߥ7Bv9JgvcFb*oT<'`Za@qd^L]<>.>6am`5wMϞ x@j螄7زv}5 DžƲ(^hrq"J4dt";s~|U%~@)MW.$6׌˸)oExat{m[Vcε9z%1\ȱM(%("%pl WFJQRX;"W{9gxmu($(s>zoX!TXio^k:d_zfUkƤ ֎ wR(0W[D*Vb?&$`'Zt `Uo#_Z[ =zo!x8dߔ#FsV!rF]WWY(]:l>z[`rj{׌֤r`(,E-/qhd"[#?9̄ypuRx>BFqvG9c4, SΘ5"g M@4J}`ڃ4hDh;Mljb{f8 b ҮIe9, ccH١a|*kN1ш.l jQ3o=k5Vt! ɦgEn{c"[ K$z${`u:L,c3,LX8vI uÎ> 5 `hP7_NWU 9m]8E~{c68sɶ6){]:燑[ :C6fT8W\򤶿u+xni+f7F{ِ?Zwؘ |5ŏvT+-}i<ƏoֆlZz&>~N:y& Nz: W0ivbbl? (Cd0o.ET b -8F+ktzV PFƈ }%uo~^j>.$vd7aD/qDGƲZ(5+lՃ·hSf7nom5n,Fd)2201|%"YafzMlj/{nKn +7Ɩa:9"۴uJ1έV n8ˢ4tQIoB; up3gaUѱu "k*a@} QX@ .W9fes88sgzZmN~Ft9*6ZJA7'nPRq֖|l{8ݜ f\n-uZA`LA[{-uH+fH uO*=aswJ K6( Pii8sA ]L#gq@|+EO}K֕#ib6!K$tқ3ekV[åd/j4w;ʛk.?R¾lyZ[hZ,-ZA(dS`qYJctGdNh:l4qrgp2L+*%Yl6y_Bar5Zˊnvxl/>/HF[q5M%+`uI@hNr"+ AL Jp}AEn!8RX{ *iޡoofyo=`yL RT6fgtl4= '`sCCp-0`}%zc9WL tg2Dli5=hƒ<2sb4p_Ąs)vqmٽ5{4}d[ksƫ}%%$=0nT'Ҙ"{xcgbSȑ)̕6(ӺF8M);ϺS`Scc4" v `I61|p@fSvC8]܂=C gp' >!ɆiDwuGp#~ےeN|B=qD {ZS6l{k3b[L&^Oۆ]}tzq 7ێ-%/*~v4> ӽ7٩^4T"" 1Y7x zPvգ]E3Ys:+d'[ƻdѠ%憊B+DH3ƱTpSEv |#]K]4B5Q S! ?? G^!T-`)Zae3Jo]V(ZafĬ/eA-(&Q%kIx0f&t.8I-J*gtVmM朅3OVc[upnzͲ] #8О-S5S d!sV=M;% uuu:oGf-8"ů$w^Ԫk[jÇtS.:GXX ,^v B:q%+"Yg8#EւZk /.ZxglVPqY5&)[N3 Ͳ k'{k,\㾰Zpxjom┛:+/lHq$i H+سNwV|$9.(s,/RLbY%ȷhW7w0lBM ^\J^~^Zç;QXWU7߃_Y_o7Y=D99\$4npQ ,u ⥑yRx*:rXS$ 90 .C+YXp sq! i-Sul W Q9cV $9n][Gvb 6>夑D!lf`̍:ri猧\tbh7#P!4JZuBN= Gms;n??e47~Rl_UbR6h,Q("<1K X-Ər5g RDŽz Dfըwm#C$'P3xk]";daw ڇ# L܁Iy#QQB(袉8]Hj_jO*>,8ύ'kc)fc| jcdM[]l=Ȥ2ηX[9u Mj@Rscg-k==]JIQiliw30[ǺtAf!4E8xHNwkR ٠c qcP[:!(i6jɦS=4J׼2 DK.xs,SmtXK.sHwF+ e wc O:AÆy`v`$L+.eՔؕ34uH)SyOi&҂B%ǎ0vnv3D@љ) 0웆0wwJh*r]M+}pT0PLDw?3o 8ZX{F~ן3,k)xڊtzqJ|OKؚGG~~荜{'<1@N$i):#רHCT6-;lY]f.X.\En:>"]=FuB)tזbR1tv]N@BvN>Tuv 7eZ)8Vw-3XY1(R)Ca<9-k(J/Z3%PwPbxºͰÉA>E&/^h7Hch?^ߕF-=i) ZU3+@[q֠KMডȠϴS1iA5kn`r&6g4tE!nڦ d[x:o>AX{V{ɞ]蟕 +j+ _| Ljjh\>q)yA ,Z0rQS <NBQ=O̅^J};.][{S]}/^|:@3DO_^(4d3%Z<<3dxPRVCPCQpBDA6GԡQ/LO^op`:kkFQVp(\Ϗ:kS{fGX]ڕ.4w<6Y(Icqk^ 7>4-S d[Ybb!+l!sr*>wU _*oш.GhXbVM;H΁~} Wf!ϱ*I>XxQZ~nS,iEÞ ͼ[LZ8wjI8W壔t8gɴi i9t Q6nj]xE6Huc⹮Dm4a5Q~Xƽܙ;7dW41ѳ]뙇{w=Ez.F4;KلFV9MO kWy[@HxSAeF ^XQQN1o,)=)Mⷉ[칪 +[ͥ/w2$|r)(!iUx7,Lv7aI Q贀T󲎔ibX"7` =c߷9&4%-Xcں|v:sk)iI nΥL@eGȢ6\"C-k8o.۔!1RKfQBE;/ e[?3㮑=6`qz{ %.On@&!>$1 d&G9j~8qu_ IIȒAۆKP4ր$$I HifRWvǁ^_UWǿo|)k;-A9%\CV @11|3 Vjk ){ݜ&E klDy~- gLq L15NHT9)e+#/FeNNwz&`3'5*#ہ)gB3ā7V耤ngQMc͏뤅3̆:-; Y,mX}B 100эNxQc/X+46{9PCLl^|$4XQ.#ܲmDAâDoqY[ǠC,j'7"S?aDů6fɢc: ~gz2\w\߉Ǟ'E~` r͚[;/x5bOm~QѤeݴ(hqv,k k9knܞ3^_6\JϕdZw2d*qtF`mt<·Ԅ)n.##5/\HY9*g44(t, JEx.˱$\ EZtRÃ|ET~1p|~uY~ɰuuVJbs^ b'}=\ɗAf;]DgO[Y^4 B9+h:U%pC#_"/~o~Qku UC`N4ږ(NUmCsZxsʴr& @౸~yx_\.b6 6ckh#ox_…޽5"Jh~W~IÞk'$-;\ 7E7RbnAF C\BV"c'z6DD&=ivSrp6@yi jWk O{C4r,{À!6no.܁ | N +ثeXA@Z"#ES:[]A z>J [|E{n8:6Z."_uM] KZS, utUuw;)׳Ns(DJu1 , h9VQ"i;zR [u->[Fm%Bq#mwa/ֱI|7V&2pӗE8ƤժQA%zcG=6%eqaZr,eԹ\RL#W: ՊCGD$TѯJS@[}C٥|Z515Lk}8w-KX)!dTVa#r~c?Kqun<rٱLyp@hbu(ɆfZ+>p׽S\MRbè U7'YS«m%e|]pY=Ay};qo8FX _k?ſFjaal 7'YfdN)s(Њ>R{0#{ ] t."#xki*Ss'f2 h݊g̉STvtaz6hkXQD5}[Hz1!qt#L(ꤠ x)puP!6/ҝױi;C삥Vc vTsv!&QQ]0P}Cŝs$ D72-nhqfsA.ZG&=ڜ~ZRřKhj-; Z]mŠ:C+ht5$ũw[c!LfuхŁ~}2Tд#"ӼzNPN]$>2Mu"xOEVM);Y<: k>9\RT$tB=t K.x*C|} Ǭzx=Y.aD礻݌H, uߵ(G""Km G=Md_oʹH#e-؂ZИ8Yf|{m·p"<@#UF`bDicS6ސ&cCh wqh<ϡ]A+,Q!ƚ D߽9ꇸYnTP[NxsXH9ƌbXu<3Y#\bzrgjvu5YZ{tXeזIKf VE9}O `k)+#9&+֛aEAz[\rƫ}þeGi!SX}>GA#,r!.8.Nf&ZqgԂI )g\?*]{ AN]sXa>O볱) z+ˮH6A>.DA%Kҵ5|Ј؃ ?UrӶaO ?ɟha?+ډv+urt:Yu967F(suByaB踠GcR{ʴb>X0f SKFo6fx'K-@J5*uݼ9n%(0?ysDD2vg&VʜiI-(Ɓ6VMĽunEj? [|W}B V g<{*cM[p)Bz (43#a+Yg[NItzl[B`ܚTr=Yb6 Z/(ʀ{iI+=aŹBl{;ofW9R4bUQbZl Z(YXѱZ뙜ޜk#+aVm>f6"E1eHwt{K7o,\wRUdo19gm&dYW2npck>"5pU,ålDEnwzRsV+q>)#Lr?i9e"jWr0G)K2غN0QPbx6;Tus5LU'Fe;6sY`nɶiH )<%O A$&I4 p2_tTDz۱ov)}g[#=0NYQkQԁ}kT6eZ;_"ocf\^\809\xk] ʑ  #aK*Jh#]0!pJpPU Lo<Ƿ)s3c0&֐T [`ӈ$aBR Pŧߓ]c}?ObodN9ΘD $ qĞrv7^@!QkFj&4=VIk-o$n|F:zzɪSdh,8\8@o2qg,2y[_B?Pf9fШTx!Il$įFԣ7[ȯ(qrIv>δ΄݊ӡuCga4ۅDg5eF׭qoMaoa ] ;!J7C^KW^]1?ېIņ7F42\[7x?}y "jrEz+x6tk`:cWJz= ɀ?d8u켫'5ҋҨyXxfXRGΠ̼5V%%J5s#̇Q&Y4ueݺG'"_JuSRNX'KJ0PJB},B>1Q}c|KBt$Gԅ,ŅDO6^˘G劉 ZjhZ֛r,3.gi( :VL;#hU,˯.([z v-ٔ%qy6c &XvC({x?aO/W}d:c,gV ݮOuח O[@]mAa)͖gEޔ}E9Q-4}g_mR:X#%'{M" E 0l`A(u?1UâIȮ+|d!|?jl&c13) l~fvKXy ؼ;nxi/B1,IDč,nKVa{{>?Ƃ{\#I܇} |VJ%s{g$~/Q9,Vy E?ї*Ң/zt0ɜufDxp!sY:$3Ic. !lafOO%-qZnb[u;<ʜ%1'jm9σF>ZEoDcgKx3/ocPf :! K(]TN;벌PZVe֎ĸ~Gprct`tly؏6#ҍ5,˯d鐊 V߅N–KN悗V70"݇Iq_?VF<6 stA,}^X\J\BzxHl  $j}j^Q&b0(Zl}w%EΝ$C}<0CF1W~w}.n7(]gzMI3̢3%^}%YQ`+ rA[֎kM2;hbpd47ft{֥ d̊}]ۃNUM҈A2,2P:gtl a]io]OXX,U+{oxY]!C~mx7q'¡NΈt2ABvRqmNV;f^sˆ^,+2:CG ~ɤAcFMrvKx ?7ioEԪ-XJ9!Ҍ@E!l\`ChɃYA̱xXp`yTc8RF(fA #50b^"SOEide&1QpkZZSvQήձutZH5ɓ:Z46n w4Q!&ڰ45 Oy( Q_BsIG y(2;!hdmaX23p;~v.ѱqowBDE/1"fdPDE{imbKt.yc\Oك6*K)x{@|WbDo,~wPsLyD`@1Gm[I ,쨭җ0O)1: gCN>048|ġeS5iQ $x kZrj?ٝ5 ?4`['BLnm)ոS]qB"rPCĢt~N }ȚS_$ A6q pM*DN6 4S;4H2)K؜p:FUG[S>Ƨ>Vw[Z&OŠM\0ΡHC72j#rN;]9QQ DSc7jz;iW}ra߯U:66t6գ%1I3g;6iQ0-.NmbqyX6#Uޢ;d(ښ,t{ Iek&r*:SJھxlO<  2->z9E3 ~ cRC `av8Z0qD ϗ_YtǘD=څa b,HZjўf, I8zç/7|~? >/c ¥%H̾ݼ|O]YSc30T 5e;Gyw~',vOPߧmRQ -P뤫tPs%R)F2CX$/h b%7_ÿOcpmg>i6e!;yvph`>YČǻaG^^krP?#\Gp46?ӤUjŜcP$KθE?MWǿ?1 ,I۾M@Qݐjm gρ:qe`LӜݡ}6t2i0T$5kS|7'H)*Y( Hl`i*,wWШx GH|Ϲ@k񜈻'X/kvzq]м=ӶuNc'8yXb~ )b"F"\B=ȱߡqj*WGO{8ugew.fW馩T*/AI^t#msQ6j9uBԵuil$n^$$"%8P}ILGGb uQ[M:;Q˝etβ PhqbQx~( x}eXYtYQ~#ޝc{mx> P$-SΪ k~גEAusp6]Lk^ĺû IQ]AA_n 78ǢyՠXmu[4>5 ޴Oyٸv,wM XmC|8*NNC%j;ͶᩔSG'b^bMgOoME Ւl*w3 7fn"5<3Wu z Ԫ[=\KWȗ> o=}Ckm>m"ϿT\K'HZ?ɰR0X ^NAaVt^)ooQ uVh=sΓqd/6鹜4>[uS#hqdvlG /tWx"`I|H΢jAfDpnZƭy;i]5Av\ ֽ "ϓd'hTnښXnɀ&c;fAB0cGVʥˆkIV$盵+ݎD]E9YEġ/^.d>:@xmK._J[Ž@H1 OWh _r) {ʺόw,ʼnp=,{$$rɘVYsSRO-vZv7(WL,x_︩ϩ>,ꚾ䬢vThxa mq3S\ ww-w{~="[bFq٧Ӷ{*n=e^zAke_E?/pu SƾmR½w1y BX\Y`u 4}Hno,|"3@[}&oJ{4\GXphËUn`XےS`B0gm9y"N.z8A$Kwrt!VIcyȓFXQGg…Cd tA>''U"mz']$F{&cjm#"{,Dp\#px.0r s1D֜ {ћa >bd gD6CCw( Vu۴>x,:"ss U{ M &%KIxo10qj;u)~ϊvZb@n+Rf4Sk}QV9%oǥd39;\Wxh8}{2Ooy\u{beUźC[tӒ{hh)l{zrcDi0' q-]}ba@U6doii٦^ªoސETi`mVjjxx{!8~/ů} O~;" }ǥLq:7ݬ$`4W8k#KЛ9F3l1(h{9ѕO;8(yBˎV 2%C>y'#SI5zI ICfh^m8(-50?2w}_kh}-JAI%!$t|,܉uы܁vhK2 hljN!K/pO+Toh`My.V,/}KKmlAIRtX~1AKcNdyĥm`[UO4=`Ԣ%S]254eѢ*#皚>/ޚ89 {w ٪dnCƸ0dMb%R(i8F_4lK?kVؽ;o4ML1vjk7m- K`ʖxA.'݉F:Sq͎JX3{k~ *ϰÄ`y<ᴛM;kl)^*I(hƜNlp]-a+yarw"qFQz>cJQWX3Epvbz}eeN+KNXVt?ELaFѵ.!9^@+c8ɞM{xkw6+GZ(F|ZhTpkCBK:|&Хgvn>ut]EVT2l.4\OӊE7X<M59KĬHsq3̈ng H<<.O9RJ,6׃9p{->KL8۰dҎY7*n0@JқN>Pu/$iW{}`\Khhڝe͙U${ >ߡB7͈' mM{(Yv/hᇾOW _oO-6W*v7 qSpy/Mgƶ w ''_K25G:o}M$2O˗{x5.&LC"eG,621Xo.O׬1!+_Czç>\9rJ%b$rv<Ц:3c,} F{:ʟ|U&g6BB*F7X =%uTtrݍX 3;PcSP7A@Fh:3E/CgK -?I꒬8*ƀSg -FϦp韥 :):`Qp߻i|%aXflL g:x9yox>= G[\ԫ{+8h[#%%nAnaKE]ZA{A,zf!"p("Q.0օx3g9Ѵu:sSbk&:لcLDR/d(6+ &vIGJ)=",kJ D <EAI(K!3踤YdP]$OPt>V!Ԓ13 1,1 rgʙn SYO0&6\eɳTb$g˭vzJeD뀺K"_=\JhN**|5ҧcO?yu˸\R{ՙg!V$8Ρ>ѕq^ _kXcd]:A w]"[!RY@P_] Q<ƴ]c D ݜK?~tc394ha?ȇ'ri0ũ*;;oyY~۲^M#]\-<2:l.Yehu+~ γqD\H;A_\2 ) M߃bJr7/aD5ުh0 tyz:O374LÇhwm- zy3㹵K5X2*$$5P ޙI]֟[rQs]J+e>:6R‡zn0׿U"oL hhrִS#X heO(EDX,b햻JhK+32oϷGV^lvNZ,*kXcNfG)I7x`Mn>Aca܏ᄏͶvVL&I Ꞻf &[wågB[Exh Ct陈sz"qǟX$ 9Mس`߳eq8pcPrn7pZh&YDGd5!teV;^E4XiiuJJ2u:Ɯ[I«] "<:٘+\15rL.SKu2y,peb,!^$}ƈ֠2+>ܫ)hxqB%$Nח "KDpɎ] A}$W{3p|xQj]?w:i/\)uVM;.]O=5QD*Z\kǴ}EfIFs}þ%_4g.czySuiDc@6.|yLљs Bj`SY`]3^ N4 ;/7ہFΥb -Pxr*e}K 8Bn:,EN/"('Y\c̟\趿XSQpXk)\䆇{/ W'&~86nB<챔JGp>՗z:8Gg n[H \72-.6򝹔_zs| ᪳3*,Ӓvt<*y'&-+DE' m6n ^D"[Lg0XĢ-۬XRPO{%2PqP`PQz Ea$8BX:ߗ4.IFanח O{rpæ rY3a}֎i/ 1Ae{F(:p$>5U1ý-7Wqcr<cy(FQ5نM{_ Í/e۷ ڻHpT4޵ i^m׭ $X*6qdNt3Iπ4ǃvys[RYN`'MX^|y yQՄ΢ɀ]ϾN0̍ŮcW6 rqϐ^ch WgNj۱kCm>:G({81NfC%., Fc]~6PjUeAGZ1GM/GP+^z ^9KZfT%{mx_e9J\O{?ߝ#¿7tf AFH: K#=. X("%9y4b`AtQ@:ede{{!{0E1z":3nM7 /m I~0C[Vtݖ" '(ƿN[H8'5n /:5xidBƏvpUؤɺmIy<H`;A:~8]mr$w:бEOg'yf[mxt K5 -%gXtxbJ]4[)QdF(m#tVF# ml?( uQvc h@NKWxVLpOAc[s:J'(TҩkYm9 W^]BkKjh<ܫaDdbl#>g*\[/k8Yٌf˾WV8w%:9 Ukf!9)IFpʊgwz8mu:4xP$h0Y¿28zx}*wI) Ù[ wL\m,Eagz4L$ 9J)M6}K%#r%Mw]$jcFΆBuk$U#i۱%VdN8ƥZ`v5g|*eh֑ˆ='}ׯN7/f+QkGw"pB$yiqd{yN VXntIؑ4yx!h99Uwkra pg%AIdVZd:2+tz#㐵 4Xݐͳg"Ή*v ||q \""!D,YaZPJ">PZlo8hxnېn[XٕNzhs2U5ǑBnGÇ[-}'Hfr(ys_z}eɐ3߸nOe9- fqQjdJxc-(C4%ڟ>}>hӸy]VLb:3g72Q;fn9 J.(wo#  lz@-؟k}9#PVK6Nz.c3춹2ER4M).c.@[>]"]^:Mvtע.#._jKkCjV,9Y==i܎>|1IZ(̛g Ѻ"^ީ:G;}oBnD׾4_| C/ɓ 9Ml oti ʒi׈ޘ8߷ʖ P%%兙Sq̔ zm Cn//7_ { o,~55FrR)-k0"aZkG%fm088k1Akg|I/_ f'α+ dBlEMl7;-r9@ )rFsAݿ7hzOvvRq{l)Q?jjxϛ{}͇Â[ zcLw]Zf ,xuU!r[LCoN\ c3H)$dOw~ltN>9͸9K:'ѼuTUi 8;k_z;˜.y4SR #>o}!{, # ht6$0@XE /,/8fƺY7KKh&Z^yfN;;l'[(qȡcA:sgNB&w_nS`Ќ(Pr% ^!'4 p;}|(12*W@M|啎OECQ5/YNx0b>L `<6S=(PEu^ b -Daa:zbz0YAt {=X\v<:O$ZSJw f-yUsvs! $e4F` ()@JDs ђBIb@.+j蜳^͜cT՗k[@ZsQ53F 1aEӔ15o*ϸo;يY<խ&$> Ne|wWA1d;Φ$ ԥv [o(qʄo|?Q`};_K=ۯZ(.Z-x_`v.Y:<K:?ȳnPTD|`'&~y,FI8&VXلDohU)c?TA2ҲUiLjHY1tA! B6B7utsSQ pN߫_PfxJULYc]/L{zO, t9$Kѻ/\D&QXu'u5VٸPCg7%'YퟮacDG%[Mѡ r z$,t 7ArEd H»n=.Š<,I8$Wt/NL4I'=L)PW!cMybg=ՠeC;=[UM; neA[/㟙^ryLPIۙhkRiZ>9!Ú ct5ɳN/(Q蛻r?)8FչF-y{pGjI۬sÝshlۙ xGF;uOA.0YjbJSh@cE1dc#CqiMf!34b.b0.%g"ve?_rFo Tro}o~ .4N*j5qy &u{ !(^ܧC(Hѽ(!l!cƋ ӽg-䵞tQʜOB^VlP4J)%tUѧwы;Lo&[䎗5AnA6&]V;N)՜g6g}h|!{r]?3.U\σ]ZXi=lGqvu4#aθa(#jXJ09BkUL]*.& b*\$cughsɸYeVVo2Nam!zՉ 'ZL5c.!u""l,n:h)TpcmX' mNk'.fuU%n)1 MC#8b,1\併)J#^d=j`.XKJ0v^t^yق3pazI82C^d$ݫQtLV41%&Vo@`̾)h\ȊSU3#7f$|aiݎ9nJM)ޱdF@#nZ}5nx mJ]#~H53КNZŵU$:P'"v]Z˻ՀRcpmx "p. Ͼoү'W`4g߃6Y%kڻ*:Pw%f 3M,AgÁS9zsk|A^h|Q#cwfh׮ڴT@)iǶbTKv.%./nl`GB_gt}+Gc)cyxL%‹'P~0"E{ҟHϕNe2@@vVTRf5r5;b]V8y gPڻi/TiJ8qByw9$p*Zc46#^t)N'IdaB_eM5氎u XK z6u聸]$<;ʐivb~(VʞJիeёߜS>;KL/}r% +MPR ]R;qpg۳O2X!l(>B) 4&K|y2W jXu8! k$S&?h43Xr`5Jʄ顣h \f64w^kŵi5"s?p0M Iw&O'H2&Jz:yee({N Qg橐 ׆|z΂5?p h2 CÊTO5jdlqʊFs*%@-YF\ʝྖS*xjU lY$o_+ЗO'"r1(p8qom ƺd|Jg]牸J3QmN)BbpL4N%UvFYU<{cأzY n"y'FA=*g`D>&Ή'.[R]\ID=>$AD&S?5.&W;Zpw7eh2+^,Kg0L|O +Zм0IśԵH5b8xb$Dx*Vz)DRT:bQ%?$dJV}Ϻ?.b&RPƣ&$V)M ٓŨ ՒT=| -]eg9a덦Phi&hե r-`ŨQ xПP1!Myt^22]uECQ3'7`Z„6pZ:c۵`4-ȒLb23oؓ蓾8j)axTflu{7E .DE㜚.Y]`ϵFW]:0 u8x· ,ڳ﷋iFiO2c{hi-I!33YX>1ٝYR +;W.9匵aP{CԟS9gۿK'X_mARhZohM|>NC4d]I 87sL jx Of_9dSQ6r_w* qXeO$m-´Zc+ (Ka%X+]-4eD-$uI&*MMYFӓQN7&lyHUVL _V,6,!.g+@] fGSB & U0_9~s\RxLbERpyŰț I 6%բMZƩ3$k@V+6)lrclm2h._Mr$*"òI8~Sm)i84#D0ka0Lj yQ(P?siZpq՟XWz+F[e[ۀ5bqoZ5DedqtW\臠/o}:z  Ī,˂U{鶴]:謬#i Ed38)$D(-?& 3 ][ XU~s# j)u.q^Fd$yV@t>&FKp?-.$eYӨ NGUmDypP?4aIG~ ]{*T 5vpR 0=[=QDڱa2EAp"  a5ʦ: O7邝R1] Hؿ;dLPB_CzINrrFxF[Ql Z척X7q8+[0#B9FR;{* `}dlga(RV eއ&9;7xVL?>Wk1j K źh^d*H6B ^u[+(/0z n3x2U&US0%xp(]w~&w L3ϡ2k] ŲHT6g9 *6@X-.k`5PppnJ.9M.qU @T&i3mgrմf'KANkk:G?T7+7\ e/Uet 5faӑ &{G xuQӢ.S-8s?Kz?C~]ipiyک2: mM P t&>AK:B$LFH(XS;!S|Hxdz|]yD$YSd«I]Iu?Y0GZ_߄uFD$,K_ax* w#*YmE!⤅jO^K\7}cǁdOQc"۱paC ^4tF\&(uo}g#ժڠ`nAU>˛$B䒲»hlS2;9C wԍ}=2i4iRj:ݕ@}|{pCeyOš q:ErQTVlXxu=L{C K/Mf?-5->eb.UX4xHp&VPE3aŲSxx(>&J-sbVBη#sQ7:4]Q"pv݄gXc0=knQ_4Kk"֜p3R.(y`:.SRy';EqmUa%'ܕ|%џZ@s=R%bͺRpXTiU5HJ>`å+Ym+0]R \1U1#/yN#pYz\Xr\>ϸZ SlÜqXqݪs;<f @D#n85MOkJ&&&P9h+"crW{a~~1tL)uxڼwsIy}"+=)C$:=j59zǵ2[3 l.327+NyzgJ 5f8r"3$尸X>{83O{+D%;/ȉ:v桱)r`20~&g" fw)UN{ jIWo!V!> )=cUݞ"'X̊ s2N\f}YˢIKueM4$U2taB~G̝hB%\ vp34(!]o#:vf<AG9K9Y,s6+L\>?ܤhQꨗqm -G&SJ)'7$DALE}>SXsVSU4!0&3ȺBj,y%굛c6'$΋)S $ 1Bqg@xʹ[xlb4U'Nm 8BJ8:ݞeJdК2Zg\.WijւRW᧿OoWɟ5uݺ$t&N`b]zcʐ2^-( hNcifiz:q /\IMuGi+Ɖjt u`4>C8FFSy3B>cs@$ԈP=[kUpK_>vc8Yyichbh%E(\i̐)I2`P Ei-(i5xŕN!NK: +/{w3ZЦڐ\Pd[>|حZ dlL2>^yN]8)"wmgsőA*„shRXw*3/MnC״Pd=,B+K$N>ߪL,-;C؅ .,S"Kƫj!3*ys(u-b5Z"v ӓ\|< 'G %To/9K)S"_IE䂚QŃQ:-䮽#j lu,(tz6Ӳa6p $jWw!%:!]6$ƫgÉ 3  5<)5WϦa"U+"fA'Y>"& gVfDd)O (i4mVҶN'FvX lE!(2qQ>R.Q•;뎜Fdާt..]:% >V/uEk X{WT =)Nڜs)D.iu7"Hk(t`Jq`f}z ,)"liL{ }TMTHu[\ /n3zheDE`c,*cQf $Bo{mZ)p:eI^GJq1 4)d1衜Bn[}%"'K] Xmx\ db`\FM%exۡ&RUZA$8,9CN e0M2pXL4t99koồp<0^xTdVx9n5@a Eޘ<0԰__iFpa1 '9['V:A"5Rޞ V ݍQiL3ق6''\l=frćO\yb1>] ܦiq Bpr ep#;nk8jؔk:.Qp-%UME7$iK)~Y.ǚ4+A>TDł,O qCG(-{P=='ޣs+&̑;$N)&FcGȶlZIsvq19V5dY5EQn5 O1 g{:7DO@>Ȋ"9+tͫ ddg=Ocx-5v!׎mkMÓYRtsOh}>;񕓋Sey.QN.ԗph :nO n V #l3RĎIdRp9iLx/X9c@cG97'H3f*9l}ڶWAsu$A*NJm&CST Do.p$0?B)r JAKC6,?1 Vޅ CNȋ0; Ժ.gRS fJΖEzhc2qe5b LQw'YSɲ`EzNbO T2J, }BZǵXrI BuCmFH1db8YPwe \qNzr=H45hKI̳41Cs۹+]zcoq>!x&hNw.+"dh|ZQ'scHwTPX$SS.ؚMTdN{$'7k>z&E*l76YjZGqœK%أ8 tmf򕿹[ xwf_5i,l.Q!7O"uydU2pEg`# 7+Cwh"S;웵fɶ\d"P{p~#VB'soi!A˂U".]r^ߞ@rXk׾uvrј-Ŧ# Fʓnz#%6IM[Œ/2xFgN(U7 fDEn\Pc y̲fDBp/6גm]]ED`w W%!ryV>?x pc%g*) /T#LRT;Ei%jUc!>PPj}& u%>+)>h5#UgKGip2&OXא h.u}WM9\ u:*`6|E{~0$4!t ՕkXKE)<?:w~c=SB a*F2$ JI(<ŵsbGJ' Oյ1Zj*+I&7I>:<43Mh8ֽ f()t]%&L`۸ t@oI 2ve3+I(=dyZD$z96Yb9,qXOșe]M */PZp*1@m&̭W  JY$Nfxt/oWO;.xuWt:Bcu>=≳$Gݹ _\]$CG{i&KPYy.p-NreRv^Yt2LviMouj҄HZwv t͋hݜ,7T+J1n]`e,Is/Kd+7$t8KʨXRF6M8l-LLDnR"B)_7( sgO(۳~ |ȴmcޜ]o ^0ME >mXn+P?#ϩu)3:L¹OanHҪv54WKhVVڙp_t1:*w)F118)k8{d(7yΚ4K';8/), z>D{`ݸED,i**.ьrF,9Q;[G_Q9?37򗨭.3; LcN9-~ Dȱ3<.ܷ;jt*:=K_ I.vB) /n5iTX:jx)ݕXS%`cNc֝ﯤSs֠L?ljƍ5[rq8N\nT hIZ+׻31:wO`qRNGw x+@S>nc/ {`qxԐuŶ $ܞ J U ]r-sɱм0 RH OuwHsǛd6#܋ܝmDp6I|Ƿ7eD01V3 N6j\QI2J YNɪx| V)<bl]pRx&Exoٸ22s@PfҠ\RB9.*$Sng|ŊQzNԎۆE>gj*ѵJֵwnRLk\K[uŒ3:vn2EȦ?֔sbc*z撻LpSX N0ujGr[ia$NV񹣢;b7~1)J}C$mRsLj'a)eM#벷n?o+$x44d}TX]%jMpOdq|ͫz~XU w]_j8$sXo'  ZH ͓!y[v||X?ٝ黋Tj-$BnÑ, |8_"cfQ0&eu/ާhzfѕ:/`"]ߥ odi_rH͚p^9hPks/F[,Gt,_|q:f%tV1K]QgӉ1pm-'$b'aP^ ^ xv>D (4֢Y߅Uq).fbf k@vglU4M Ds}6MAᱎbd- OR&!wة3]B`*p$N9-gCh>nHދ[}7=C8+Kj@S)ILO{dtaD!H!'+69%e(">H`f|$UUH*v$ynONҥ$ >ra:").и-Q yYwoIixswz޹{.ERQi60$ɦM5>'ca[GV]]Sr1L9:ufGsY/]ݮ ,@;9JS<Ɣ-:3xܪtdT:]iEZ]P~nVoLML AD%ш^@:h`@4rpbr(ǸJ猷jSIxyZqգ$waB-"|-J84(2hLRmIc=J%KV)gš\$nmz1P+\D|w%&wܮe%1>h,>R MUa'A ;AtKۣS|҂`\@[tT(*CҁgjF)cj!(b ZChi)4rb9BYaIK)apoLsCe~ne=17ʜ!}𩪚Zi裹Ͻ)G?7?@&1CHGn2[ 3DvTQӊf0_Rxx<I#\@Cʳ3Ջuxd@>]8| 1V'_9' L n/f׮>$"LLܙQ9O?_B@4Ϋhup{X JflӿQ|.N{oyg$S[7A@ae ]wvun5+8$9UEYCkΦٳՔz.\7dQԷ:Apm5Pp4S]f,jߎzxVO+>]-huMHPwYڧ7sg 7!7_|[|ۿ_**/a7ΤYb@fAG 1iC*2mjS>?q  /nF/Tp2uڧ sm*%s(<̺3 !~ JA#\3Օ fy!Zᰳõm-on?BBG͒]Cnǧd3PwL n@X'дr YJjHDN<׍`: EGu)?p63}sBw4OEeALaRKKg8Y{ N 棻6C rh-0L$9gJ@L>/<ߵ5P PX)mZ.IuW}B IdS.t`ߔ6E xy^GV[))h)5RB*?\D2Ŗ4BއHu#[KV%~@Yw]}\'r,Rn`rt|N9Ghb-Cw$zؖD q) {Bn/N{ػOBOԊ3IibӀ2n?ɺ{]T($%\;7>tݫi3幉%ݶwS* {)d5Y-<1z`sgil?R^kt3|5Ed)!"?p/(Γ\?։U(pۓ{HH<[i]9lI8xW4!]WZfr  q:9 Jޣc|U4_kT׿k?R& `4(Y +-^ZQ|y4jW)] d;9[.M3DúLOyR 5㴎gc   йS )F/=80b axr\6nf~A"wz52-; 6墡fo3d1=Z/*L͚L)(]>{[jdO<ں :T*~uvYnI>RI!.H5=8\*"8ixKXsWR2%)aIöef-Tm3uPJ`jJaD䄗7zH8\:W,9#cSM)\fž]y⇰˦[ WV)C/7en&MN>_NZ@J/ 䒱wȔQٚ\g? :QVZ3j.R >`ٚ)2Z?τH\>wYM >1! Zű{59hYӰ‹ u_It 0 eC]Rh +$ (`#e-p eP ^̡agm5ttYcA?}S/g5ZRǰ0[&i͸=@q/#E9qzgu\.d|1U Л RA0I~4)  I@3dMi)=g:;fp`)NX%$32.!!WnڒcWcDkº}@Jֵ|LEat'3XTݛ'*wLťbK2ry<"H̃8-&G'7n;+HO dT_地,+="'-o}ļp0OCa_=n@"tBV0xN֫g b,BCR<5Z+4xR_E{g qh6{2LQ/CEonOض.,:6 A`B˂vN+ƚy٫E_Zl{lBF#aω\ .ͳ,D6$ \ĒW@!U.nc^r6^댽 ܚӗR[LIi܏59z!u#eI $mvZ)b,)UݙQn}]' Riŋvݼ}u.1tWhVSaęT*⦠3Lha3]tR6E +,FuY2曯C/P ,!R`ENmc@9 &9mc4]3bPs,J[ʒ06t} cnj=[ @ J?=! ;w=oքz {pFwܝ1ZRFfn텻CsJ/12IXh?c攰 3Bє&A.Fx# ugҺ0w1{N. @ɣ`zpEJy3;[|jy>n6(ձ&"d+4 @T7;JYiK@ vEo{06s1A>v+TZ.@s[+uY Uˆ]Y*yZem Ѹ)B blɺ,xP=SK*a |=g6nhs&mI$IۺO'Y4[jI?3ɢO۳#U2Vjlg+d9}&46GSV0ʰe"ȷZȋ^T]uǬ0YJ*FY 5YyqnWl$RCxu6HΐfnU dq42_~%jX2U!^GMj&\*:M[gRqUvsPʷ; "⥡+(1F5L ԧ;ZfA$fz&x,ė,"$(!eq+IHQf &թOy^8ɀwI:В f50:-7+Pk|}f,r~kct]Ɍ@azm{M3ZhI9in pw}"L7'm\88XyVm¡@̼}|v2:t> .AQXR`|:$|gW%st0dzEAN%dյ'mS*SQ@58<VzH߬Ex.'_&;{Vr6x(n"PێN "oθ\/u> Ii6ʺjYODKᰕ3 ¾jx:u@@>SGL{.,IV:$l|&"ltivqԁ):0V>Z4Z0<1aM#uIޔ'6 Qᮺ\ȑcfu[NhIcPـ g_C]2W`Qixz-#,7_W׾]џ ex:dwqr+\/41J@f $'iۺ*:.& ~'ԈA]g'H_GaG}.bA(F HQ섧gVI$G{ ><>MErΦ9"uǘ۱=#u{궀{?LJ 4_j.xNqZ2zկv uS)"VvuEpf|8(E(,"ȋrѤklWc1 =lv*MIL qN;%}5?Oz Λ{uAvwM,0ؠ?4"˽]APJvF^0k* 90 X{l?ViӂrUKRd᠟1Ed=qof?{Ɩ$\ց(궁BcaE~өi}rƌڅVwk¾vE\4HK@[s:lTS$ϖ=L{H"jO]IdM^:SJ2.DA]4Mr9S)XrB{mVЫd)s7&b{@upB;R£sMhrň2(bbaӚ3nszxg4%>[&_͢ qd3pL> ײlTkYLݸzZTU0p:R έ +|[k42ԄNBytc1I.\d_yZW+^ieg5L}šy1tY`EY(w L_Ώ@go~{77HD֊*Rƥ<\Y7炛5v(.mgFmnwFY|*+)0=,`/>DkW+.O(`; }Ҹ9ic.y&ϦMRKiÉM]8C5]Fwurg %>I|G/\r 4:ىL $N'?1@L ŢsvAhXKE2ּ./qp%N+?;,L10/.SRt 2KX>JF|,lqw^&H&Myy`<\鱬+>}ܑK$u 9cYWlbO4-B2mw̛q-MgMV&v:/M])|xyZ&=UEDYV{HA_;c6)T)x%dENL CqT~q&'J@y{bCh&1=ynQؼ,+.q;Y$o7Y[7sMeze$l}uR_YRu7%9`<"ciK_ =tjيBgu9l:&FZ'SWܙlrz[ P+zkHsLn,|͍kE]2lzlf_+0(*KQi!K,B p/|۟_?wjŒ ֥/HvU"sN= b*ml ʁ;$Ng]`:RiB ض"u-k  A4[u=0?͎9 {N{rIaX? ;ZS.D!]r;D=^@XI`U>t ]J E rI2>c [\.O33N>&>xF1,t}ɦ*Ymk*56RUWu5 aqJ u4?㣻!F:Φ4u,x9]a]NY($"e[o]$ɻs[L \y+>{>W&qm˓F.εY .hF|OCW)TqRB'M83ٸ'o4}$wy˔EcVDſ~Ze)x]I Pv͍LS,lҠ"LZmJjz5U>g)_N4MdsB3 4M[WKrqը뱼xmz  o4g`oKABG4oǓvNA^EnWOH q榙eAsoӧ4'L,:.mZǥUabf|.uYUj] uH «7qlmHƛ}a<(޻k!JHw9j~1K)R@K=\4$C d!&& 1aͳBoMBànp@:I]w+y_0b!Uxxup2kyD,@@Ap4a򒱷kuGE4~ .O!J^)e`'8|{=J IK)IUAh`AVO{/LYYG:)mrqU[s0ۊkr#Qx썲{ƙ<0ל_ Mtqt)L6σ9 EO2ձ>w,bcՕ~˲޽N+EN=Ί$P6,a2u̹pj=GRӊWsGՁC{G MX`C3^7wSz v+e_kEQVsyyZMz(Ѓ\Skbr)' oD yԩ'X9kO?@Ff8D=;NKKUGqL2@0~0%e Rp3ߋ{4 %0a)x/u P 4}?4Μ|圧3OM]W~h/ںPȥ ] 궠]odբ/ ) >w2rw7;eE0#%:b?Ymn 25֬2W4=CtF)e+xrŶ_}\-LaNϬ:MXZ`<"?ԑf6M$_ܮHhH=X{ӣtV΄Z:U/oÛ(d]S?#y.M6 !qᒂHCIʫ_HΡ3<9\Аxݝ,vGDܝ v5BJ>h sއy $Lc %=<fドdOvZEĺCNI)%eh"7i<ۛ/;͊ͲAicb2*u8::ْhrT鄄͗cwt &w Apx) wqr5[fw:]fFd45ŖgQva'UA褁[a6=s̤RCjxᘭf̹]޻"چ!,hk;_oġ c% <:b]E{vKMY`Lȇb> %wol:S4כ<эˇ!K$%^6ɳ}: ];T[`®9 >8H0 Eh*Zp1LHs-Rݔf <*l\KeSy."s^VTR Ӏm]M?0$#tAdkya\#wg֠'smUj96/*0aq6=m=ӗp 5 -z_;xJp'>`:O#q@7G2.0JmÝ4^%Bx\L9rM3N](@u.t8p" +V!0^d}c'*9EFs`xM<0K߄W"nyI|Tmh.c'_'!\sG|o\[Ty 4 d"C8ke\_ƏW?~q^巘x㩎eҕ=U[UlޜޥsXjt~4AZa{WF^mi 5"GFYfLaR fd>t=LXXŦ̢ƅ%%:>Fdiluy3~Ljĺn(=%!nH @.ZXЩlџqdgԅ)dStS6SRIJmEet"wg ݨޏ=nc5(hX002yz:mmt\\ mN9},G1M{Y_#ى5$v-~0IErUt TB{ :bF0z LjA8uq]yp!NVPIM^t]ή@e@b.uqp:d˪.ۮ9JޘDR0'WI8avZM2V#y9N|ׇ <2=]t)wuqΉ'،wÿ˿/7XEa\ȕ/g=nس; L%|LNrCj8R`EU 15XWr8CaE!E{&LƒuQq"[fùCeZRFB0dDa;'b:4),Nz<m17hmVG&%$qt۰qCjyp] .EC&3*9 4|:7/^- n暣6C3*+ʴ-/=аmǦdGӌUf<<3g]G^àA}!S%zܖXm qXk+'{gFSy#c6gF2bfͱN]Nxig:lĥJbali},(RG=CnC|m߹Eқ+Wl2u&j\^F+870RI~p_+ӌ4EM?Y1wfVtD_w)N} k3qͮ.S!S= %r;ӈAY7vﰭKVEQ$j=I#;=$ˤ(B&#xHFMOoҖ__~kQ {拙*ׇ]X] =ۊ`Lz HϑKJ-Wɞ_wW)[чVMAi|׺ H?N6 BPx|zLK"U{#f {<rkfvκkN`//^ѓ8S0灰,9ZӃŒiޫ~I"~n6w<:]ct狀;"U~鉐cgSd> cy,@g 66JK^Յ-5 -V#rzX%]-%F臩v9>=U: =<h0Dnb2W-h\mOjHQg0b8ϧ]dVrn^K,a1I[ܛ"nZdC),Jq(aM6vaeaIMA+kXE7P)rZ#U_8ErM"x"^֨?+\$d˄rx;]Qc"VY%Ki2/cL: yq5nJE/݆GCV7y'3c"JNϩ n@"mM'<]XO@l,70y! ([ô'q)ĪnKF{lp;uEmR;)`ǔ3ѼFb`e~Uy '$>HM,2M՜ QT&?;W\_wR {Tzϧ[)d ueM W-o]%eLFkb2jzuH_(aD9Fm[xlm@" ֜El1;=9;G?XلJ+˽YI\sJ86|UTh9$Mm͖o=SG!9w1ܖɦkJܚȝ+C|3@܈\E-,t Xr cYN;ڔY !v4: Жyl&bl֎4QGAu)#Cso2Xd Є:"l? c9a.lb<3a35j=ea]f&|[dޕ f]Jv$bjT'i~ENIݷd2E2#>WK1+"©95F'%Ա-DaĜ=aЂBc|rm[9&Wd !r*WuXcT_F[GYC &W:rͺ0#^ mg~nn݉MFI3'%rC9}!gmj c";8b|l4vϢ~gAi|/SL̙7D{NMgהrpwMuQC^Y6RkMtÈmmDoyظJAV!Uw50,4u>|8աp:Z^9spIUDbme8O(2c`K׺MC N6# O^xUFqqŔwtdCOBG+ciW͈t.s8! Mk\ن;F qC.땓L6Hs8)+m{g%CkM98+w$/-; =t}RKn{zq=-Y`/5@77tƖ2=髰[4Fto{TDH Σ׎ $v0"+20H8l|CV)6/Fd8{,L@VP$\7;p L AY2AJYH>T*8 (4uVO> 0x,MjL۹Rľ52Zw5rǁB _NME5%Tj:MXs"jsXܪdr11 vM Ei@Ӕ>bˎ?ACQhnq~5\`Rtyǹ t4.UX2=;CF}v~K Z[oE6p ȑ羦F2OMqE-È4%7{ي#8/[EͤMHM\HBNM)S ] af4Ce!kEeyq@ٕQ"6KSΤf9֠{S8XF ^T5M'|At$T8[)BCOVtݐB%b&NQ0>C)DF'oDDxc˼qv]UO8"Ѩ"ڑb^O=eVW44eaS]GT1Nv#<^/w.'MvEJs'S*sgU~^+P|ae-B$.qW3BIt_*S m&|q\o*eUZy;LM"ryXYas6Wp_L^Ih=9u}e߱:)HXoB(#ebM_P ?v9=r;s?ֹGV!ODTZVI\k丛T?(~°?y.8N XA=Esn{͆N!~*n$`M*)5^ N3>; 15>NUOOPmPEHK_G?95jSM+UK4X|y )=!2im=hڨ Tqze]<[9D-A 2!ʎnMm)ˌ'1kncrƅYJz v4d#fG8"նRu<~0`A$\#@ѹ"򖸕&T25Dpw}Ԯkbc߰J5cX 4ZBD M >6XEyǧKa9{Χy #:/yh(y>tزS#ƾosm[_"\,`ek͹~' g-C}?bsG=,%Nnc5njAå ybc対2~ h/M s CU{hUWP6cN)|Ng|im0-|>HY67>\pX ݵ~Aμ~2o0e,r۱ayo"㸡sm?Xk']iN7ԐʽbK^kwo%4$tH,HO9|12Nngע14PB/,"$nVc]glqsXQ E{TrfSXॶctaKښ dmܛB62IDF[\(x[O[LMkbev,K%0 ՙ Fbptmp?ѱ3W TǺ tT>l1!v&+w{,V5QXmcx7 xr DtLUP tӺcy2ҕMZՌ!4ew/Ŏc7jĺ8# Ab9Qa=mT* (;աk}st}FESwO;#{?+]aVډty1ni кIuJSE;k#Ȧ1xr oT4&իX'}p x@vgR74g+ U`j]{V@rJS.I]k$001AgƁz9>r8M,%^} s40bo+_Wm*=ѥE,Mc*tojEk,֡7q|L&աd%wS$GPΉs[&7X\g:bU[Ux&+K?,B`@ LRE'*z*{y!c^cS6:`/Ӏ'nW >׎AY2y}Sl^=YB<]o0뢔w.C{v@m*xP¶+T1?%P"rKtZ2.qA1p/C[Cm& ;rq$%WCpKA~ެ`O a ~0ŜT 1D\W߾#zuk鸚Eɞ0Ng|/+UCeskE[7*mt h9EvE hg ׫=ecQ;QPodJ-R]iD7pwx:pIy2QU#DbqikmkSСX_MۃVaN}2N U SZf)3"xF:lAU(oƔmR!?1Ndƅ(v*0:]v|w%8"VsTE7w}Y[#J'iBƶhQr#U2MGXI1Z<1 W1V!u]N=:qVVG`um.tN3kb6$ ԑ(?D'^CsȈ̵]w2k3s*Μ!BK!9, :p: !ƣICWpwtkHi.|6 RRM}@p8hj#ߡR)7υimMeDlZ8Hk#y`9,fP0z0ǵIsۺqӵP5οcoȊ[%T\BVQ#ҩ kÞX-.pfܭTGi_r^D*ٍCx$f~+56MIc<0o玧ŚyɅ7cӔIyq]6LV4j?T֎3 %Mh`TM18b\EVd4kaHF:<[sJq5aθ{զƥ^jL۪㘴%sD&ّ[XQ*ٯ#_9\ޣ'`"q1ĘCOgp:rƁ8DgMTء ~tˎ⩑}@xe j@^Fb1,6x\;}X={QQ=]$ JN aܗ]$ ;#Nh @_ͷ[Ci5fW1~,_M@}(#$^˫N]-R9x>u\AMa"[f I2$}?.`[m榰28Zlhm*͡ Z󄗗gfY5n%78vB`nF+O2H1#pS?794юf);$d{ɖp'IAls({G T6Ϙ@`M0B3!GU58Pۦs +QG_ts:PgJ,V  N5bJcdR\(uIB"j:n[sxrƖ1YmXƒJ0ȔG kw5w" oֱP1bLį} |Ov lS7o-r9y{>u?~p*-]ah҈;30աj-^fX[ni:a^Ď ̵uj,K̢mԍgeOI)b-t.TtI@et){}wFk]"l*X4nr 9wxHswHOڹ{m~),6[taTnJAcC^+Z,"LhO}WD"fAe :2cϞc;j۾Ƚi;\ftZ+(ڞ\"2 kqI5˘$l#Do?7kr8.'|;L *An283Sai*tˉ&,Z6EF ưPqs̒7hbRW {HspތNIR36I(Ϧ#UAV:|AUxtӈymTy4왬׼m_:xy~iQ,UOS;VM)y O ^~>8H${}#h;sOd\B_XkP3ډ77w cM5us&zOEoǔAxK +#-猯~`W??A`,ޚ׌=Z;GfH9N*D*cO QbJ = 8KW- ⨜@X^4xOCE[P:xx<ܮ/|Q+$*,ێ\3AkgN)\e^fNV.㱥D^iun ;@nJ7 bkޡTĐԶSn:\;[xk>s _l]c ?ȘTYe"_dž`mþorhԉIye휤TJf7ʇzطI!i[݂umآsS_lǹwXnwfHF!)fkN ]HuX>x֛X N#ۋ1!jW0I0/8$0(HŸ]k %oO:Kk@ƶΚ>!J[KtQ:$ 9w4ysp^҄NgRZAqMZ lݢa +8b}jrŬS)^ ACKH> vmH!sw]\DPjfg=h䰎\ 8ϸŽ0א J;.gllxo`aj= ԷY'f_(S1G}QIG /N,O{,MGD}e[rGO~~Wɟ1pǖ5%]i k+s2~%΋U8vC úضa$we} vt:y) 2ȴ@Fy!cou<O&ymND#>y ;D*e6MD&*gֻ}@A+ I/\9aYVl#e|mM.s:n[|RC+[,WDbfWTrUÈwU/'M8׹%AtEYAfdS+0 7h XP;8|-M-22qĞ0F1nGAaK'ѢuڮץYέ9ע\U!O:w ȫ/ˆ}@6$pLPxc֫˸>,I3d^kZI0IjrtZ+隫dm2,uovEqFy7nE2Gj8x~臨[E)U . 絊d5Si$yټee%'n&Wh<<7גi6ҲOI7ač 1/ic;HhD53kwany;Nm:(>G4yMd!K(^_C'x:S&VtSXUYnܮ;@kJ#}U2IQ҇>o$㶊ͭ ׌T hR,[#K;Lfl]]TMGN!Ax:㶁8)[]5J:pIV5͹>;{.){1xwV9X/~ "_BdE70 'Lczo /}"Gf[@dX0d 0!2QJ5#rϗv1 0`F.v' ʹblbfi@;mtLo jN=*G˙Hj/#a'9`b˵O/ OLBEK2&<:̀S f;vYs#:- e}mff)>ե0N( ʄɧq8Nc٢~j_Qi{S 8ӧ2/&j¹=q¼% \7ETM#*ëHˑy7^J $8 Y%Rf_:ЌXS̢g9SRUhx㵕tS7wC|!hWw`(gCQ]XpI8ӈy+dFc!*"kʇiOѺ3NH'%>U_U62*d6Q>dF>EVt0+]Hjm` YrCar$B$) \2/NM1q_`fD~N:Ѝn1VDN1OLG ٕ]}.d4&cT+}7Ŗ8|qЗСF|ZX)V aY r:Tn}/ W~?h-z…" <ax|]gʇ9Vr6}ni$rɺk.Sv]x@P:-R8%bYw&洒0VJZӵsFBn"!q5-K !}ŨR͙2#4|]m' \Un i͐D,EQj:E8{c|lgf)i,̦]=qC0;m{F|%":cB <tT*j WFgש hc)d\{Jere0zvJ<X%gCD:KqwF ÄZ"N<&yַnޯ,݌jMYcLsmnc먜d\;6uc4GDC6gۣJ9D/ gC6M\oWDZIuǍygkD@d!tYOL;~RB fpتQu{(I91"pl̅?  /~)H(]69%| =qg I0S8t5P abltLfҡ0|\0gAd:\ d"/ }ΜOX:]p@0CYf{tb``ae.)% 3.={ 廊?j(f hrFٿ)Ͽs:rSv\>OuYo KV&MU fyjaevC-㤁1q:=N->GCdGͶ;̗>U񾎸n[֜]mWynܐX,Xpޓj;OFg@ċ vV 2|_խ#bD+Oj ~0(Y޶sR\rB^q0`pi^&A%g(QsJx߃EsЌ{ԑ G|MţTZ@{oX׵ymU:t_&܊APnn1e$ 0\D"pOP APbֽjD{ PA\W<}1NLEXx9Jzx>}T)MN\PORdJAL#SXG xgN8;`ƝX͈0)S~ݞR3x:OX+'{vJΌ ٹuM)#rQ dfڱ$K Xʞmx^22*gtL:M1B.]M#9(#~vZ4xwC=n;yq`WN*DuV}%(/gGfsS>я}"@="1 |Fⷦ qA}mC'GNzOyk"Jٻ9!x-x0ڷe&l:*-):,/;CWQ]ܠ6 y֢Zsa 48~h!xTcjtX1`j-|FLn)cQmѤq'sUjs͎؆cas96t̆nLT\x8 aӇd \ʢ3~ZhCScveH)#.ɕ *&MedF21 ~@x˼W߹|p%C?RrGIkZ/sA+OI@|Y:wJy;mzfwnKBn'l~I9tyRtZҼnJ먦 x~yQ]HNb4LP1vai^)gã![X9}grVWU64,d3@_1 < Vƒ8pa$|25b&ATndeLKm][,_ qRA,κn lqE6ݿEaOIdU2LtJf:8 c7 O|B n7,քuW`;OĻ-52L|jr,N߱Ӛ0un[M$-bmf5#. /wrJM4i |IƬh*ti#|W*rK8wƀMۼ:4Xb '뮕vdrmA[Xݜs1Q! sԜ=(MLb6pxV-TuWq*އJ+ࢫ(7g%6BŽdqB$7tVC&e)#/M7fC&.'\#k'ry#/l0lzSR7kF64;!6-ba#.T+ ^60O:;o<>77 a@rk=:#< Sr2h9-[݌ێHaPai\C-Ž|_8B[3|q (U j`vEzwS6T yRsj8h'ufYxX*kPӮf$=xF{/2JtAB׃.1Kv)̇x5苦%`$X0y۹g\wF0nDf [})cSx{RJ5cZ!"b*sNsvs]YyN9"&dL;z}ڽ eE_݇/?/YOи(1 ϓG+e{+ȜƁTYDAeBu޵nf#)"lpa #drAJy3R6I)(;*_ng:ܽQsŨ'ŒAݰvW}&/*N[3J>.egqsM+VหY6j F_0xS2{_mAC..x OǎXIz2O=^:kRIe!#$2;S 1:ۼ+E͍yv" ->un9IrjDw3Z$E Ǯ95|pByj<9tXcb}ql1G0`V" 615JH㪠Eieϧ+)@mcTȊݩbm'yL\-oWS9f0)X]'Z:pݢjG?dp+IA:xv{>=aMX_֟~kX lo[;Cvrp:eYAU*(Gyͪ 4v{svW6uSJ5VW瓤lXw6o20%WJE΅T)<^k3TBv&5k#|`umGC[rJ2Rq͊CZ%Yֿds nu+QU yWpTiRɵQbYX˿;>MqzS()՛W.ʘz/zvBv+<7 <>>`n1H4='E-:΢!L- uk~?5aӼmx⬩{닲dZx^y;orв4v3 cIclBM_.0TKp)zb+)xh N4v:jbr<-qƀY$y[DsJ\6C1UTuRՄ-W׆@ e }]3䐠K*oU*yox{|909=W,2# j:sn3d"lWidns3,4I266R!zͺN #ܷUv!)n// ۮs;sK2('1UKXxcsU'IH(vj_9wp*2* Gf"mcШgT|~o~?u$$kYD:>Q qEc2NcDquzy޴ a0ud" 2/9@6{qu,h`56pfkO CokwS15F6lh:נ.hԜ)S}ag|2G>[%5坄'~)!4kGޢ7J; ]K{.> z0B޺LNglaٷfBun1}vd}пn5^{_-bwT4qv( W5 UD^Re2>C1ɄDͨsBnHAANUUʼ5{\WѝɪL:oE TRdQ(aإhkLWܮi$'FqU[#w]|#n}0 LPwBgdIN3<*-HD;Zhjxxx;9%-B_/]q1&%x'^»Ǐ\ݏOJfynF6sG -xxaKu jJAb I?l ѐ *%\l\Џnڮ F6v#pUB&BjkH5cSRgSJ5eN ˶: M]x!߉8kA#ypyù/ZofTjʯ8e*o-QǚWE9tXR5Cyxd:1Cc@t>!7{ll܍r͌I0zC~ɬEd "@|-y~Y%|BAnjyBV23TOR*w)85ZiҼUg z 2V T|Kyc@^Cѥe a S2?_ ~_+O9;~48_gӞ2iú.,hD,^I"dR=H'-2|(DLYˢ&§@Ce׎>^۶*nٝe5E[tLYouYG mL>"&Q=siNckز7Dl:V&ձo u\X9XWP9lۂe^ڼDc{usl) yScM$RK.ҖҽGv`vCi<'GS]f3##O >⎠|2kQt˼dQec7iD}/zjt 2XǑ!]М22޽/nk8I, i _K;7obu`252i˖tf5dPװv|pZJާq':"{~w +%ƄKcVGf})>㼶,J\U'Ky)CuUha_ğgX:`}N2k9̳b]R٬9ލΓn vqD{,q˺:Γ(IGr<$NH uC"3ŇR}@aqvJ*锗iɓNϩe ٓϟEA8Azfے"H 6AtɒAcL?$@_!N 2B @"ӝ^*rxsÎ9{*+}Wk0!Vhq۶FrwC>A3H?^-5]kfo{'f1:k~7 };d){ NN\/<&/bi_ص5QI߼1@X0ݝ]3쐸%VX2՝'$B}ۇa:+&T/t|$pG斻 'Vm9u,.xPeċKNWo^XVRШNs\ܜ8JM o]";{^'=ߗL Ogl=ߡ]G+dy-~ǘ@^[Fj݅,Bs) : mkeܮ' Z>c4!Oi^=e/0[$I 乢XٶNG۠Ax@Z܄O#5r$hUzMD+lHȐQf0}!iL( עJZߝhsDCw  ѫ_ͪq`jtW[&I;`dbjFϞivzn2D"a$/w;=N`՝9sH@JpY`z&LìH{Vei{:i٦ف볭zK[ga5pV.Q@L7zl:a+ށX!$]焖 sw&ٶ/͐1&0\0t߬͝c[v,d ӻG-斍=vjtɒ^ݺAc1s/}?#V YuA/LӛׯO#A*٢UFK[n6Q_Buit䎶vn?.#%Or?5dg}Zƛ bVtBҍ._pm/OJNNoޖ y% Ԑd] e>Pts ;W4k cNOױČjxq~] [7c6DCwI[haAJǾ/qd E#$ wnS0)e4CN, 2c ,Mh.tLH5t8#s2GPץ5z*B|i'ށ\>٠'fw^F :Iib|;ǠjI?|1.!"Bƌ jEz}Ko{!QQ > (D F&)|ѶmFCcԂ ȄHu͢hNWY*`3 $mR'O{z8t݄z[دt7G?߻.{1D:I^vYPڏI1KyT: h5ҜWԱܨmu.JkԚIOt5tj4Ǡ;uf[xg`9#y@u37iBZwSV1&]Ybt23NJ\E+; W"X7ABGwb={>{+Hu8#M 昺N[dЛ7oI鲔K 8B*8 D #ϵ`n?qm\K7%@'>tb3zu=y}ހTphˍ(2/r$s߅WD u"ҩӤۇ|Kqnʏ0]*[Bo&t1s !`ypD貭8] =uY3iއc#z)g nsot=&ulس(FO@optÂ􍮯_E!83yxM]xwOJLlPý+I+BC"C96k M$S_6_煎G{ ZÌ&k>✸f٧%(ݺ$mr7LB˟H3\d"eDŽ[n °X5;wFw+$<d8s]7Fo{mIZ6&\ҵ _5 %NB/F4k"֍RO4a+F+Q x^l^yob!~>mM8ѥ`d5eVCϒQ&@i2)yA9: syfQƁ6R<4]2%Ie.Do+]G2f;MT1CE q{k4aZK$| :>%݇3`utʗd: ]*G/sP@8ڳblfYI# ڜr׹7Xؔ.r M̈ȗ,nΘD@O1jaHh2nLt5VJ .!K1eP K{&f! 9Y `bv-h2֤ͤf@A^ySuV^$ 2i꣖z^]ƷAW.eg Zj'QH?*Zjt( b7ׇHh5aAsBs74j-Wk$Q:5cs=f[FY7>G+Q8$ཌྷ0Fvpq!t]/X9Ò@tCA,e4 ;G kq&%)vIDZp+Fx,䘑,ș`-=1] "Ldx PbpVSBrg,ݥQ8FDZg7"[ʄmƘDL2G#9I.|V\ѱrƞ?t1P(D{G[C˵GHTҶF[NZD\H;qaA fx4qEEQPUpݑ`Y&ֲw{f] sՍ=b˥[i ڏc=sh# بOd {kǠ23ŰwAWA`:wu)|W7,ݚDd 3]'U-:&6rJX!BlDK= B7p!;'[f>ĹjX]s'ey Obx:uf|Zόc۰9 ..b'e p@NgX[fD*!?:&L=nH r&b3`_[iIUΤ-šj:r$;0Wb9=(:6a\JQ,𻥾)(i·€[1裻DLHDkL=4B$25̵L?,ߌCb1 4`}riL87ΈnI͛A~{ j+h#,X:pQ&9v:?z;[tz?32|x W\0S1#"7D܌41 CBG=&]]@|&Lԃ1 #E-\|Ņ>䓛žV52E7Mγk<5fi%`Dݮ7ᆎ@ب[#fIA_NtaH`%c+!]m3Z޵]&Dv8Cv@`k'Xq(:ZV;Js|8f^( # 93,7dFеf1h}4䴡(9c΢”^8r(IakA6']Jh@LP|D|h|F״o-ax.ҩ32Q]@=C득Yx\ݏ."iq3P8A ?{sB&vH@&2bqDœ lsqZTǖ "wNKit)uW%Fz\ǵ~ʅa8I}漊iniɀۙtYCz84o}oi_?o~_ ȥD>S7,n=fn93I- (AV{X$i"yS[̻f9eؘK$7lBw];jXmfzq7o3d!!뱇8\,?-:pjрtknx'˖I\'^(tK W 0s*hOZoFCR)ccR5"?)6S~p&2]f)`6ܗz*ݜrPYrJOZnF]^$ޅ2d[+pu唹+ªR\)Qeۄ"Go^GDZxvW@3#a%N[T b % wᄎݔBזյAIńn[6/i/Z`piM̋}XȁaND:Pv"VO\4U,}73;T$) c[~S(l9{lz 8= Kwa -j1Iݹa / 0 RD>z(pOvVsF4(n^!yS8}$8Ӫ5XygNm5bj\Gı^] oj=zuRͻp \V@ǜu`SP!-¦m,.+? @Y<"'&`k ۇifw! Y:Q泈2 d)u1,Ԋ{tءdf s̵{1̠'#ʋT׶(!k%Qp&:* h1k Q=dH:*a.D MO&3xTtgGWy !BEVUVCZYc%%eW^*HHsAYSp_dw(2_x>ͨ#9ߓIz_ǟ>=jF؟jN9+=1RtD"^1klv;Zl B-ӎ(dm1*]z3 b(49du(0ܮn:B?B#q.nM+9ASC3!\N ݖXJQk4W+!N[nyM\ӥ1p`+$:z*Ua X]ucDIe`9!('ao.> ;%SپsGY9VZb0iMsnjё=濻ٟ`X:=֥I CK _!T>Vy)pЌ0d=t -yQOp=‚6'Mɉ_<\Z/D,B^we'85^9` :9p8s NǓQ,H[NFq{֕M8ME3q;#2wo47 ot*a" 7=:B \YcF϶ s?)? 4K:ښhQrutfY!/6PK̏"fT3d' 8*PU]YqȈhYC(O>D`%QsZg͛IϽ+'jnvܜq(R#RMn"^Z gˑԴۼе)E[-XJg 8MhKkiu*KBEjBç>xQ:)UM4',)1tt:YAT]!yȝ/^b4`̝k|x[3qi6ѿ,0JoGr(:%ɬ18,[` iĦ;[?wDa:)/hDd5FyGFSiOzk4\9z}7g4.#/KAgj{@pӲxii\`u1M ṽﻬBԡa1¦wOc\_8-cneQETe 3ŀ{1XP l|AÈQI v2M4 .ٌE\ALդJ-C;| Oum\wy%BEyrbz]t4*Q)|`O{gws IV~8.?Jp@ZQ<€ZRۜΘ⭯2s&B%%80Ft(HO`PM҈^N8OiڱO==3Ǥ_k-8aI'5j$͂'SNd7NV8x&ᔠ#u[|[T@a@[J ~Á%Ԃ_t䓭YB@mʁwؚb=r( Q63+լ3IPgͩ$#]/D9K??ֵfL%iYn6t'a!32)Ҝ4Wqw@'ל9p# Lm^ػ 5 iwgak /xKpmhoV")oGˠL0)-xv|\g F실/:qqdД$zp2Hjp%Sp?j=/CXgj1бPz@|S3Oa'g1@o]Sx+IА]eiѽ1'+"^vc/zw{'h/@ gBFIF{YB`q)Z4 L Atr m,p:%I"FhhL >?sp9Yz*cz=Nmk9 }RB1 ^&nh$50~I(ڎ>u cG(P} S|bq ۽V明0ܬt.t-J- T/`$V<щU$xgŠdjk_,X(m,`(V>Ԛ~Z7Y +ˇc԰_SÛ5`x-RPЮMHƴϑǞÈc`x (<[ }sn (Cά8ayds6Aח(*'e+zG4yqZ Lg*l3F5)pvdCpMV hhE3uVTDY頼pEw%6&*1qXGG.ga7P*)!NOmg}9yw%vAj@`GT 3H)/wc4nƑpRa:QL IەVT1:h %lVr?wDþÓB&ts7ksJ/|`} ,o۶4LDz㋠1P1F X¡ۯ_eNl&c˕c'F{w=r3rX̋ݤƧ0s ?.)geH|G 8LY}/'/e<6Z:aF'n"1u_;n'wXZ.rlP ̜#JGbvoJ˲uADk@6a`ˠSsudFߧ >_76 :mk4"ƹ1h"kB|,.6/"vB#?@Ӡ=t5( o T#wg7L+AZD \ SxFl~|wI7443l_nbB욁\9*e<4`t^1o%2/(j|?XW be"`($SGD磸C4{9l+aF!2NXK1DsB0:v-IUo[FNqA"6z/Yo$pV,oeTt^t9 U&,p ީ~l7OQhEPŠ;> ^<ܺظl<ưh|@2Y/F% .L,9oXlc*=HXl @DD.D4~*mӌ1itn.WSQTσ``)^Nj1(Z#D8atm}<>pLF~1 ZEq)|W$,ſ -嫩SU0J_'&ҤתM-;0nE _, q#뽑h:rq|,E,E\]! _\ (.| 0 gLdRq&ks!D@H+H fd:M\"i~OYlzSF:v+`ab ǙNi"Z 뭨=KJz 54 hlqtB|6s2Z(ՉBfk]ϋV]fr?[_k{Wj YX:-D@_lܨմֈ:0x@4v?'6Me^{G-vc^kF8AZ2,]0fCNpL4ms\g53w]}fyqtW5ƮLY#q;5&=1̩*b3E|;::bD~mu8@lGk2ܵF϶r8^y4t}}ϾO 4__v~H5" #NK#qs4 q =51oBi2 ΧBm=wF>O?b)I"K#IO"Uo^Xp.1/^F`/v[ aEYhGRޖB Uk ,W#EOk 84ǝ\ LYNZ(<0ϺND$ u :;3;6tV#ƞ8Ɓ犫!SnvBNEƄpdc˯GCE(bX6xzhStJWFD~G6tPD6p?r5кIaV)]q`ΐwдȒFX;}c3c,5F ER8uWnM|Ll@]} Abu gxI)ovKnL-ۄVaD:V`2˳0l1I2KI sͯabZ&M쟏2r^.0OW?W MVbZfyw(tV$r} c ܭ\ a9ag4 'a =XHvcݵZs,w*/|~~GtS'Nsh6L9x9{GowD>jGhl$^l)ζ$4V(UxB,pFaQp5LnEƭtN@7'n= {-܋󟨺2ztVdB30'Yn 0L',=b3$F$~7/͝CxžHhxb:t6bVL)%wpq I` ryh^<,~Fly}xrWwf0D7c6;S,BkU)]#d0xGiXέOtm&cU;qZ4gLڃfhMP?ag\Y({ o5 nSLX֔HkBÈqaJX kL>t./w()4lSL,AqM G8cd?+IZP~PZ -XvHj 1^K{s_Rq\g޵NAg'FC)oԅW5aM ]},]ό18[޿_~;{_ǧ0GH4 GHiܨs9EŶ$t*BMǨQW)CI!Ύ[Odd @_<Ыzb@s;-ϟ ,{Av&d CHEVpk sGAnpdt5k{)QQMh?BbtI{'BmU Z3G0BkK tFi9}>k{#k}$E;Qđtp»N ]sc:@t>I7(413|Z-'=0-yID 98GQztDP,5Akƀ;m;m>=#= -{K_\蝻 }@?{>"jY\fm6QCTqqM>j+,.GX`1\>hU-JynVBz3#Hw:_#{n\y"L>}zKrE D* xRXLVx>g3a9w"igW04#; 88ZWa>4!A'g#VIAc>zqu[')걨xR= Yi(R_O^P64~GDT% 3AHaF 7A)$'$;^(64867iAo,0cuAgS=˩A p]o|0txbِ0ܽYrpK8^>0aD7m#$̈́uRz57R ~_u=R2 +G|xGro褠L|B;(C`lU6 <@{oL,Ƅ"fn9GzqOy~Dg֑ Djx>wEvqV(-logѝTzkNJSc^ oh+a ".N׈=xw#+<.;W/?ϦkG?)tg|{o-l4s dqr{㾶}ImIJGRIs9_2ًYO>λKL1mQt\.t9|}xidr +NB \OSga ZVU˂]IGmS6 [_j|,%,@ZPȰ X!?Ap2!T(xTDavH#+T5 6ЄL*v 7[n}P-'V%uPVf4bfQ(dN0txM]q/""Hp d;a'c }s 1]mr]oQX<"9Ea1ZϱA\i 1q+[k"9@IL7w[ʹbm̼BUϋ}$kܓ|"ލCS6 8ޔI@w]1b]}¹Wc((|,#_>*y=Py^wbKUZ{;Lw刐 Qd4&jFIΩuV ">ч9 #d8#* ZQX!XP Ts T קysPm_@uèt~Ce)Qu@ܵZ(cGղ@ 9FT`pp"rP:8y gܪ |KvԴ=s VH0[-3,h(/HL4-R$j?@±@ mpB51TLG](t\1ñ "77jh,ѡYG((`Hjwwԥ'׷vuzu;_f?lj4\.ԛu?e5bZm[mTwg:2"$O F(VԐy`,ѓF:s9FT^X }GDᄏ( C/4 rZmk-Nc|B*$,<Xpv[2ρȜΤ(5ƻN6}ٜZaSㄱg5+9W|g xC,Ydt{7be܄KUSm?8/kM[YaA`\nn2ԖEM#q ޺APpsئ/ǭB,'>/ <(FV LdvIn;9o1;T,lP+h2|]aPGQ 䘓s'h[r?f 6s,wulFzg9[%$74h2`Iyn8* н#0M2ۈC$9Cc qV! 51: lEs SV]S{GY+:>syA_~.;ƈxԌxjܟ,{^^wڄ~5W+))}/L~c"~pL:bs_p0S"D~VpepEͶWH+q ٬-3.V?BlzF_~F}ֻ?FH/YKSKe@)+(z_ؘ̌h2YOYvNvJE~>~GIh$C'LPކ=KU3C\~ytBmnPT)t]@LS0 L:Л3.*óc:SsQHtOkM͡Ӊ#8E'<a"~}+N"Y[ -F^u(bxwos ahvp 2F#zz˧8--XtUj]Rˀui\-ncJ0xIVP`y gi1(/0rDȫ-E:}Mzo!WV0DdG#h:)V~ݳd7@PW4!8ا#HO̯\pM73⧠85}n"33~'S i~f4𩪏43u&I3ՊtY$|J{c̔퐄ѣB7A/+ 3ݵNw['U7uq] o~K7~o2݅1an$N τ6)ÁmS2 O+h9^\I[%ʞ^OQ0,;rRAԣPDދ;G 'Ķ9S<:\gyMY#Fڪ4H::,"*AEBL*+FC)ZScG3v+oj,3-_i~OF~g \;7` 3-á7d=Lk蛹  9͘ y4㇭7sgs#qv RRB 1ES`M%4>flBHo$ߖkl¾D"&@mQ^nw]Owzr2vZ s(ZĊmƵܩݤeZݥe#b!8"/q&X 8,z7xɣRX4\0Rsh':DsTBw0]T3r[|~,Aǁ:Q4>gt>h햰q%cbU*wf ru@|HCN!Z2'\,\!1v![FcNz_iWocPa?2o~w=K/^_LO{_vO6ᵸhfgKɏC,bUP <Ɲf&K4^\o @ 9 Q'|>0 7hg'yb3rʴJ祖oqO^Л YSL[PhvLzX2ī钁*l8=^ 1B&5Bq`؆c3IR-qFs}֢17P0SmU6մE$,B YlcJ|aG@GpjjtxC"s;sX%. 8k{-7ׁQe{& #~:Vٹ{idN韐cnӿBnQ;hVa75C9/3:2L9gt "<Izx`nυ˼:A;nco 8,Z./{kэI'h,cqsikf GHz4,塠F !C5N JB"a1G\Li ú~zDĎaUm7>taNm !1 ӫJtiKxKGМDK_`;o*c.TY4Csfc jst Fp\ M8{SV!\,JMmu"~)pYmtЧ}:26.sX4PZ65={vOo(7`^T sԭ ǁ /vnNJTrz!]zcu!Vujgc X(b"UCvQb@X\#.3t46ܩ0c@vMu] ?TXЙWJWg{]zBC4[skh ;jtX뽥uenPѹ̨[_clPՁR|ru1F< nn,D}G~~+r/mx1Vp:.\2\tdTI[w}54BquÂ1٧z0w Kl؎uuVol49q,Q,;/J Zp(F sW<1fhEks4WgtoynwЅ-hd@)°m]6vj!JԚ3@A4iY'[Q;l͋Sw~ݱafSfpCSO) CEKP:`MVA1g F쯽%`o }/֩K\[hD&q2TC nF@@f_ 48u5 >"TXňS+cwwv:#I7+iIC"Ҩm>|a sf6UPjSaT$$Uᚸ4|oЮBf4Eп*N :WBX:AwhzE%3Qʉ]J Mv7!p aRbKӜ,ZWnhPע:}TϠ1>v;X<'](} !0( dХxa Lhb:(qۦE/]{#hc9b2&aȹ!hN2oєE ҽ3k[ Cd~ȟ(ЬxӈI*ēK#P(ʱRftHs4f̶Fq\rfp_Q??!=B2MK'),\م4s)Qfa;#&T*QGeM g9A88ӘU<#LgZi҅7 9]aݱC"WlN F܅1MOGsB<Ƅu(6aU߿"mccUqqڏSH=3%v wtn5yAɒ]d9с1YIێ0F&~+tal= IqVTm6a8$ۯc'|B)PR1]з$7KSOKR@qc ; "rL*C.e 8 2N[-4cX OwfV"ubb$}Qi18h Ͳ.oj (џK`G5GqlM.WwI#N$Mi)uo9gЍT2"YvM >g5R9rSRpY2H.=cu[:Frk;ǖyLs (‚rtvfhL3jņ Ip.nG%6cV݂;XwZ8%ͺH'XWqBDA9f]l,2/B;:lfb;b,N5ꨕk>!b{ XK!7c{9C OƵæ;F6bCL'kBk>,p5 &."Bank{vwt SKw s:Lz?C[£?S{Wcj0* 1yxiii$`,4]Qe=|( \:(>tf.̶Y&~ժz0 |\:ZXMSq  ]>fS8WQ0NH k'b< pz}Z"veo BqKd%fq6D4$-@Gű+/ҵ# /#1Bގ\X@+::?Y51 F>,=LdN5B u|1URER}ۑhbO(B@Х)1)32:'_QOݾ ՌiZ$;C}ek ]ammc>#s>51  NZyBpCW &F^t(kZk7q*(Ԯ@Z3#dݺc['hA&҇aN& 1,:P0Acj29to%xj >slƘBðca[gdخs{Av.G\. 㮥չu]pyd-3Gޠ]2S :pNtlM"Kx;F_Co }bB(ʷoҤ؇WIbT#h}WJ/.L,Ow53N¦jϢc<kAfN(Bpbڥ*@pX;`a"QVXaaA+OL-Jdp*Ml]t~PT8XTxmth3k*fD0RFDآpczϧ؉(t,XpmGG qION,BZ(grŨRIգlŻ*ƽ]y@4&r8li6!\ +wDŽ (7 k[Ċ!wX?^x@l k&<=vHp$hOot`j:a4ܜ@$:wƵCVSrT_wŠVl4A'n&#z|8|u,QiMR;^9fk.'(ܛ.`gB$ރ΀_+<&r)SrSGz7%}jM kjcRӞGI<ƸnJLc֚ѹY s0L.a"¥dZv 75 tD0 Mh)@P:"ucNӯF]t]zy?)Ow~~y$GC>%qRa(F1Y(U[BV%3.+8βr('N'sRJ} fxrrڧ*a|ӯBF-x/NQO[g "8ft)\UMZ)8:cxZdpįW`Ds M1NDεͺfqL<9tB^iKf[:Baѓ}4>1g;.Q=Y^,W+t9gGO0jGUbT$3yć1G`qvߢN@dƴ8 ,sYvky{V( XY@K61ߑ(:fft81_*V85Vi=h ?7!M uebd~֡ Y[sYYRE-de֩,y`eЍ* S2k{ wעv?Ԉuc\/yh=1g7:E3T@iVV|VGY[P_t6&xfnv#عB!s}B-ݣ}L>Psl-E×F#2T 6&ox_=:3 5"귾C/KqN-)<'{AQ17>mӱ:3F_yL-ĊD5Pƈ|0/n`U_+N)[ڧ>+n2d QH;\"uQi$dxGb#@d@M˖㲩gb<'Ԛ e;8:csY;Mx @Xgλro#`˼cfb< C5}GR)OGS$a\ 1z]N$٘`=| $͏8e=Dm` @\06AZ (L/;YuJROBLs,&rc';3p]vrHb> Rsք!$ W]ڊәO؈~=JQ#ԗcNFh־kG+rZ>}dcLFw3NR"^y-2iu,3=Y=I$$gmBhu-4$x|N[] fscqcvdhv)E^Z@3uu1v&Lop jťc}1Ar'W -&n2'Lv0M*L] NœŢԉ=JԕVX1Dv[z;ݵNC^^h74i7~~O@KUB4g=RuV(.wE 3uL &XPh`Nx-|H:AZ-nuQA^HA\z>>2"B$62JS#X?.IL2D X4(+]dfNHO݃ZABvGPKv ;‰V{y¢{[/>qkP_!̚fȄlhw[ai(nN&CQ/cSS1QkZBMqq<ɷGnkti]po|sdZv <2IK <&[I QPuBInUq⡳bTY#K ` Fpj˘" vZu48쳂A`~,v{2Tx(spe&Ӄ7znafI9לnPG:Z͞/{A=it%wwmRT]BB^ ; .q4aesg9ʻ^3k:M90Oryںi ,h0aE "?Ct e•*s4ä/{3 Oz'bWtғϿH,¯};SeH⢽rjmtL!5u ~KT-B\lc9F b'Nng@h6+CYUsĦ} XN1晃bUs 428Wpb"V-Bc>6S5'ЉEش˥v뼃Tƕ>ekEB^\,&qEt&],5*FOK``o|D Y<] du3I1ZBd2 `ѐ *qM҂Uignm!u`z^7A\i޵h+X1A"*b^x+ E΁RCaYI3>Q uNNAdCjzuˊSk{jnAhM8C!#3$:s@qNl&4@h"wrWU=Uks.Ixϻ?ZUO=Ua@.מFv(t`uW m/cA9l$zF!u_Ga%"m .ٯx]q\w|>SE|6GݫNB]yl8j8a%XɓA6{DRx, w9 p*ҨcNqg&Wǵ̱BR/o_+W= kd{:/N V2Bf"~ؘS'_. < ͕8 I[c@P).PO+ (4I(*YPݾҎB؊5CǮ~ƨ#FX{-< .jx.2W%<6*tP!1a$Q4 c+ [CuXAτ.#η#[q(|4o2t3.Mӵo3L;x(֧=t=onqC)]da]dkdAD2t8TX*԰ܗT3E\2϶|tO/T1GdD*c`%1k"VιgIڶU# vpg~Qd 7)?kt)#? ]U|܉|HcFRrK c9FGbڈb8o>L [pQ'n[N7Wޫ]Ƃ8` T;4"ev)1n%-ʆ`٩ݲ=6.$83bHTma{ 6wvF62ߞq%}EF$;RA+ \M=/_Zbyc0*=TY<͉16}oԎ9NTlQ|(^sA<~ugAWΕBRd7oVǯayrr~ 3hL ^\ޣ1ygFl5aė/$/oCfgH'S2 NU UU]ǁ@ AQ-َNNgK0\LǦjc"+ҭ@M~?Š K|>O]LrƶҼ\Α-Cx\aÐ0{og8C^ﹰ9B@x|m+\ +=j;¥!߶xGd^e0 rӶ -ԌB>npM2h+ 1j3IzsCPFGGl?J́}nO|/P~s~UscU}+qyeXiHyטx؄c,A>n 0d@FV2\; 27!~A11^s&)k.CumB3(~Q1fBv<ʳkTt#n9EL;c4 ߊol( %E6# 60d<ܒp`vh<ފSC,:%b掠JsJGQxq_'5q:J#wmk+/@ce#Z_m\,6+Y:/S,,b/;[åmn{x$46+r SB.G ߔ,?$֛qʮ銎rqES4,)6a42H >'Fh) #. Dža~ոG͆5 A;ndAcGÞ{4# I=F]4Z~f8?/ۑ"Jm]'zaEayq_(^@^>>jԵT#K\nv mk#8 Dnŕ*;ycY%g/ja5 ElîF, g<*L)ۼH=>nQ*~RPI1m4Ȋ!9߶垆i>j 8x:{ňa_I e"SA3ȕrfdܷ(l*a:F6틏Q{߱EѸ^bO̤8F5FrlHkߡ"6FiaO0w<w&~O#˯)_ݷD6y+1QP[0`ђDUF<:3'eiI@b;)^]N_U=B=d>GpAPxɍ_eqS恵7[;r8wvaz=g)|ZQc"*Yqh2|_߇3i޴<Ҏ…h'ZFt]&D.RtUe_Α|>FV^և oݘx;3 5&'cRY9Mٯ߉DT㱉G˽q$J#d'PSw3z bvxg*T$RB]&9tq&ي6kfu.k+R 4Bʐ=;Uc"Mvg6T-c߿M+ם88t,'YD^/=/^*. NYmr1Ie*Eg:ס$!y}y}te~D#;J,-E]1aDN'_"L06vl\FIH|85L{IHË^$wy qtuvI;7 Z&J.;4FRΥKrW.4 f+J>,= 2o۳F鸰e2~h"S@D| I7gS<ɀ` ))(WIwUV1 e{Jڑ6i 5eF HXnx3Z7>u4.፺y*Z@Hs'@h6֭PNy?^H7lNt:SB JHBJju sI:2Α"Kj؆"U* Q&X9{MR01֫:M:hj/*R.[w{Z$N N#s:Gw qQ$DpOM.5KAZ YvHCN7a#1v.X"ٌJ\bT: YFy >d4띂 8;H&bK-|7qꌣ߂ *Qn˙@ʖD*/𨣑R[uaP`и=W-8H4,FE]vVTu {/,R@4udBeweBgI{r2{ S]43I N(K7n" z'*uGN9qƅDnye0zJq 0;}F6Aߎ)EN pɿPQ%$a- 5)8uz2ZhFnF%[.08ǎ!%F' VjA%dj祈X2ը(.4:ӓg#jT4&-5fA/cUؙ֧ "x!Cɲ(yX ua5ms.T6"Ƅб`' D *bS 5csV):HӦz1-#J_KLt:ji-K 1(Cdw鍯?~TdpZ ð8 .21zEx2g]y#% Z2S*k8 8r,=)a;տB%&*-uD)WRѩSS@ŸQ'f!FJ^g`Bu\/@z4bZ~@y|dJ9ـ2V|! a(g;ɄJC*tV78H&.`#i鞻k{UC-ĝGo<0R>:R8ldTJ ;Vk%ÔZ>4;m 4~1jbIdٵnl`%$lJR 6"ur&ht'u=fD{`4czkyzVV6U0^u+e(gI`;U)ߗ +/nBE[r@M6/[dFؙ{nNjJ":ʋJ&Z g>'{6&>]8<paU%tf7!r#l <~/_K/}yp<}d1D|pv_)0u R@Q);"X!JV!K5y۸MoBlj}'VLh 'ɯG-)ja53Bp?6YѢxiڒkk?[sã㙑Yx`RN"Cw홬6havĩBA-h@0\E,6FV@SHP +! zXIa83ݍwaT+7J/2˺Ś}&%IՌ>RCǎdޕ$ '>7}"}(]wmBV}E-s<Ҽdtu?Y(r:tZ)!cߜ)0Z$Eo}'DnÔ Q49~3i4ʧL؈V[22C; "fVTճrnA Ga-D,DKj" ޳.&`_'GkZFے?.v2iҾw;oa֗2^=2.L \@4ʳmBJH+,xBh6%L3.ya<2:]ij:`Bؓ+C2*C2NQFu!齗 qbK0%[>BJI%Żsg!vpn[n'bmNHxx0qƀuXdO/ЂGsK"3a>XI:!YYˤY%\I=輦r(~'j pR@Djx{71L/ǶN3-BTBݵ VLGfo"h2J^'(Or4ڏwoz'#`XjQCHœ&JVum/i<4HEfX +b ȩ,tB/Q*J{N*J}R;a3&Gae(Kd$ I40]qD657yODr)A|&3֫ݷT3<,qM*26Cؒ<;>]͜'YDe p j=is̤ݐ?y/MË/}oGülKfBV贤QX5z+v 6@j ѭ"%XDٜCIihǁi3)V&T r_W5Sq 'UٽlƒȈߟ3D ȢX^0GGL_l sK d'E%+*)fUdPx)b a&Biu<i<ԁ֩wtitLOR`n.B"z?*YL#Z(wkB VIEY~uH- &cjVz;F':5U F[ʄ9hbHѡ6zʗ iMDiEG1nfJKVrV}B;TK/uۘ^M̟:S %DPM 霪C҇DΧڽiAt΀((8pHͨRY&X7Epjdlj8ͷjywV^9JkA3 S9IrPjI(S;+bB VKRNuunf5#f7B$E$!dg[GtA8۸e8^JMuL2k~ahO+c$["l鰙#?GrBYM]٢C]tA|殳/lK!pu*o@sX :L\\6gTM{wӈj'h+~!Y-YCkbl\3_s~O8"MKdW:eTϪzMxa$twiieaGf[LUr8ϦMBb\OjE{wRB VمFn ABz%:f[̖ExoI~Gs[|ͯB^~C\RX_{iה\}3jvē@;tmph.ÖBL: /gZRic $G┝xGC Nz,Ma[HtWKuHa1T$s^_xV̑*Krl[΄dYp'aA%*A?jaϑ2хPKu(=<&ScձpI>G.L NL V] cŊC<34l VWtmR[s9Ej\tmnYQGw9ZsPNc=7h}-‰kcdɯSDcQ .}DTUj)'cd NtˍN{Z}؟d X&7NVƵ5%oM ~PvzqƺCUywGu8p X O_|x;П9*6*Jm"Vo@-AEV˨YtlzPs,*sy#*+mdYytb{k.#{X|6m4yH߄/2QӃ1 niIOP|,]<プG1Ϟ9{CWbҖލ-&[VţymAH͖|geyIX.G*8Q 4k3zcEVւVmEV3B7"g+C(WdW,DfO* Gq#k{]OA`dFrNVɱim⏒Z dХFs.Y!J(딸ҵg:j=Ñw,AS^R޺*g|M.b\*HJD\,` i<†p 6<'7q\6@^y'> o}_$&h,x"̒ȊCřb9pDZL"*]zf|gӥb!ZI+˃ES e2+Qy9rs;fĤkGBϮkޫy8%Bu%+؏%)TeA#ݓ^٫ LeՉt*+IS]qU;Jxer{i][6D0n)wryx4ʘ:: EXzLi IzV]u\RQ\I/< };eeCWR `N.Ƥў|_6y-g4=s~fZlê>aIK1n{߻VQ y=\ĵL1RD ;b_Xt6-~>fkmWk^\|7߀|~KCS@hR.AH 32{$>CH#W %n+?jLGYLBNVADD),fSo"-oZ%_Vg( ~kWL-Tףe}~O~eOT(dU5W Z$B&N4ΩhQUt'r~M5NYn'z Х=ǚoГyτ]+QiYF!s}=cy?҅h;z 傶mo~`+bIENDB`openMSX-RELEASE_0_12_0/share/skins/ConsoleBackgroundGrey.png000066400000000000000000002067611257557151200236430ustar00rootroot00000000000000PNG  IHDRX IDATxԝuUuF$^@@ Ҩ@QD@bEĤҙJc%X\ù@/5*yTY}{k>{L+g\t/[ٵ{y1s4rΜݷs8N^eگ>9)JgּG.ߤʯ={_#e{˵ȹu=}s׹RicOW}Ӄ^Ͻ<鑯O}Ws O2&2FHOoud^gϥ&2߹yEO^"ߛ1tq|NN?'eW"^g+kוݯ7ݟo8%Lkm,wYέw٪?{/+yy{]x쳯mΜ~G=%4u-#M-O sOnsM7¸oOW嵞z}:Y2 {-/o<]tzO7u\{])y7v;?{]Կ{N7ȻcS6uMЖr8##y8#~R2{ f}x~^x#_d9Τo~^ynq=>!yzF&)Iz\~Rg>N:Bc- s,I@?&ɜյ^D0'{o{B9r\{a1߃^ޏv?\}_1uYVs3Bmw?xޔ/w$Ǣ\ɛy.ڰw ϒ1Ca#f>ѷwM||wE]~_y̍yGɧEyO'~r㧻b>'=uf~y<_i쫿>]}>O/}={!?܉~O/>?ůЀ|o\yO7G5ySg>G,{aʧ|;2.L=ƛ)_kn7uwB}[~+۵|-C[.S9\K}k4gђuVk9ܷΟHjfG##)1˲Z6Y\P>sMI'識?R[j]oyo hї[ +49ug\ m~zYɞ @fn_/7*}2DPueύkm`bxn+Yݣ ` .m<q$Of[dQΑ>Ii~;!4u cK37K=Up ~ke5 V0c)IJ鹹XuH"ؼL4dزJ JЛ2 mU*_oO{?@ͅ3?< ֖L굇f,Q۞e5OeU|}h ?G00{/<3 JcEI_.W=z`v'pYq*oO05`ZJ~OL0rYnI<0=êo?YڦI/Xcx W˹ʾYem8,m}3z Ws,aXU = C]e?#G~TMhR3<,4X Xs39?͚IMx2XA2X{B< Uk1X1N_F>/3Gk/ ī 4Wj(G h*,f.C5&}2 Qo $M+dʦk -2V*l^> :yZ6_$ܑFT;djڭM1X5/`D4| srf[`<77:e)i>ڙyVkQR%\1w}x~ R.Jl9n{scv \?Q7~ُ`nױoR~ãZ3aPm2$S| 7Dr>È}`yRLoؐ2`eFukhB6[&e >۰976g+djKHFY/a͵REzHંՔFT1B#^ K6֞U)Ckh`~r}՟[5{?~!9L7 G+@snm~VM; of$k<|mܾ?#?>j&;h){6*b`BVzVw l\'^04X,v~ksM+ sg=wCB1&7kwX5 tݼgv`y ƨ5/7$9TOj[& (gȴn`} zXбgsSq E~i$`H6s{@s~8Z(qJ/}l V9x <8vʧd#7|e`gLssn_Չ5kdla2u؃5MOi sr;_CU=X>0AxfԴa3 VV@k0YcS>臏6V(kV+pف1`Qᔓ^ՠAPs`[{;}H+L 5;-;ɯ* Vge͢)4XYKcM OOL>hM5^H Ve75)$ǰOpF 8T%49qhoS ֘91Gobm$5- ˂aOq!q ?e>2r ho@ӦX]șרAa+dV 6r>}#j)yx2. [|- #\ُKX;baشV6YRWG(Dw{(ڭEo+(C|Z0`oi柗~Oo~V6q( . d-B.N1&AOW'. V `#(4,xN-2g5 AA} W6~ViYL&;c;?Z&;72\5,"ĢJvd]4W9f2XxvSԥΡ;3Nf&X y6JM@>26ʹ5j&5EL?vDzjfqkVy_*5 Vc5[ng̷_i+ZœSMg:65_,*mSwl@ 1Xa(q1@Dj[}=}y`6YXlTڇJ]fR4XtRvNVjezm!+ iqsKjk Tic V5 ɏמtvas`?79`6s92* ë́j@,[$n{>b`D1A{V;{WV X]F>eY4 >~2v)kW>: Vi>U ԶE WY$F c jn%ĴM5gdJV'erX5Vݷː' ڞaĪ921p5Giu.[?t&ᝄ =֌f\ogEŮ:5*tmSq? ڞu%W{5z}v1rtX5pϐ+ αy~L 0$r :+ڤ׈k6B~ioSX!G''گP{# ~pOg-A!V:29i;@bG`5",VGomR7$a(U\0?C׏<`BqZR 0XS 6$&\Im˯ V>gd <*$a'96RMj [fX{d_yG`8Uݱ&":\yLߋe62xL6]G ځfBa j &9}܆{2 0 tj-s!C<ʲ}1`.x ϣ(cSv~U李f6?lFKMO6 OMu~D@3@42e\-OڍFX~[=}i` rf?[ל\ >j,דZ-b_Wn˫Iŋ'/`]{]'+_`V@;sl*r`ql<#xdz[}RkeHi``N+M32kFӰasU`N4 V/R, ͜t Ys p0A5V<ΦVCm:D ;i@Fo7מyMSd^L3e긯հC 73_g1ޚDk5hrVf+`ix^ !mӟ፱+XQIP fMa!{ }T7y9񪜁y87|jN8\gPjp?;d{.tv=XѾ?޷H 5? ]`Z{|=xmZV N v^bt{_(߄"};$j HnPd[GhUp62ʙ2[s_v3ݷהy $뷀ilgUN#'>[|3r`q P6ϐH{2>Zk<t3-63cZ4&dv` M7 0 1< '{ZZl&^ô<+}Ul`rg_.ٳ=d^lNd:@N[kiVAfۉ֌ WQ{jp-h;/AOxQ޴X v/W1l7W{O^ok\c͙GK{5oWH PX1̧[+ V~l@~ZLpU4bN|\'~Uvv0z̷~uz}z{Y? pDžp`G gSS@q0 W*kXkj[G񭐝(eo۾ZSy Wp凓 >V1XNfs4Z|O1! j}m@{hKn*4_dDygwY%Թ᢭np+sඟ|0~MMkWٱW&vұ 2mikB&b^`7:Xn5Ygm`EYJ+\ωdr=#Wi~u`Řs^bM kn8XlߦesZ5V)s9R_']XAZb탢xUɯ2hgkO?Z#ف5gmo V| VGڧ^MMbto_9qb]O;ߕٿǎDIV۱NeiZ~V1D:"cw` 36zmmB(8dgL֨QצT E*5e/ւ5 -O׵p-3hHowޱc?ZpXh`-`/?䡵zտJPi3XQf";mV lva/ H յX8^-D%r䔿 J|2$~`;zq18 *hjzV'C_!~,m$42'TOsd߀>룑C# 3޼^GeY 3ҿ1G >oXnհЖm;ףj\IZ؀2 lsZg41h^>^g Vާ4ۑnpX[-*@>aHe?+;Zu5Z^|}n߉ŻQT!o?!G`4GSӄNfe"9j` p#U~uoY"؆LZO@;X!m\yn Uk7Ve;?J܇Ĕ>9 ɗUuT9:< kP`e,0 !oZڟ"`ȧh++Ӧe.6lΛ͎="AaNQ蠱ia5<~fPC֙gvSs JMOQT՘Y~OSi@Xo=,6`u 9.7POeLA{ʱ~L'oiEyix=Ҁ,}5 iB +v(ɣJkLM[s2N) hشw+r3=0U2ai Vp )P76N-@-hݴZWt0o؛?ui_-ij~T >5gs#Hdzt:;({`E՘G{qrpbhtk4Xs ۟OC=z3n+Đ? f3Xym J=4 l$9i"dJT28zܜ@>C|ֳ36PfH6IXZ@C}jhC=Rx.M|67u05\34VmMk;+ yxxRO昰d>W`e֌h=eg[sыDTМ(x(^~*5>H4(&As!(x 6dTΞ?z{jTҀeXo&.Ýy0[;46Թ90vbychmP! pB3d P#AkmXy`=jGXa*8XC(Ӑ''X$X owlVb|li`h_>? Q.sϪmL*oi$6ivk^nװyyBAE{gCw)ʴ k<+|H:B Sa/Y5Mp\p?l`Zȥ}u\Zr>7JSRȰys ڑ0 *!kiG ܷi571|Ծ\>V{b?- N®p>)`QeЊalZIdaDr?i"䤣͚49ZbB :Y'm˄=-OvoyAYUh`VMb(ڛ7X.BPѴVl󳲏ۿ{:9>Ǹ~Vn[ Iv9C6?>H9M9NkP-)k Oͯ_@_\E[SA5XY`E7XQ=*fgEso=5۴$} dK8i;́j@A*3gSld7 fc5} 0~Z?|>Xs,|^Y_tGgӶ]\_6ܾ<}'$e>j̶|b3Z3ANm[=7jPФ=֦ormg`ۤl>fymhG45|AeJ3 r6y6u ja߱}Wj:},́O*\xAL&nZN/bk`b]]`vQ^~VLKx~+dqdGcU͐ @L 0a4?mնse (8\S"wX!>ڼ4v+I/7jا+`e~+ChKhimo+/̤_Y}yՈo@vY{Ḱ>fsxY?́vvVW9En7X39o蚹ѻwC GsNU3X9o+fqyυ1-Pf01\<53͘ [in ŵtٯ,M= VoSy HU{5 Jk'u`ACg>g]8 G{5}'_o `Lļh~rŹ垥}p,xB5MwM+8֕q|.@\Yilj\k2P/SIG2r_&RC/ͅ75 V#_Vi&uD;#60uIIrsbOx2sz+Qk&l6Gr̩54^_Z+lt?k PA;+n}34XV-~VvTձ-˫k ]\X!US4 |#e}s Gk5p5`5 YL0~V+ͪ>;»li -*ߴ?WK gNNti>X uMYgkז,wEOxgkr[<-u ڭ+lO}Lϊ`$o1iM(׀ F VܱȂtvQrv Kj`e$`s++e 4\m4$X. _yOJϪ78`pHxv4d+Sx6*CRU^c6\*ˤ *6uB_ `Ⴥ9 C6sYSȵ3ыeoԠAuԘ]Ꞑ:3(mhzG @b3p7MV+k Դ}c7$Rw2>/qך)i/e(޳mȿEdFE1~;`w`r7\Yc(cOBHCH?b}]!dpƾj C쥱j^ )[4 5̃4>~3R6p Joa< BEÕ5V0fDvXm% 0&BE.R*k-Oqhqt"nv`W͙iI{ W%I_#ߓ'~n1îza4^ns+o8B\qYкw ۜ#>1h7?=b:K`Ѡmp*0rdP@f]7!ʦiMn&ב ?QAl }g`E rWQkl3X Je B~is2A`3nO 4V*6` KBe\7 e‘>j 5N_E?1 T2;Hͷjۺm\{7^MݎY^Z</1'is]\z)azgjmpv@``7SmHv;V v&i X-4bHc1OA}ંbŜ[˱ V+eBopPfG+0g@룁!qHwVpln]g5G =&gG{ ,@(wb"}L?+ nK|5zNE8aÞ}4N•ų:XxQޜMe@ۣ@9X4 Jo͛isܟ b .&][L .˜wk l]fǶ#gƘ[/orL~뫝_B0DHa~6m^AW78E1V79kA4[duØ{X"ćy F 3ͤqj?aț_ɭѲƧG*ef Dop659'k Bz-u h lFi+rY7p`M= cU6퍆kv~0 P~V_Y;QޭP: W69ݷu| 2m+õ_qri|Akstsw&̟m6ڵ e=wn Z =7Xus`?p1lKWʡ'rݴ-F!ME ;@Ҡ5QԐ`5bD odѳXkkZ>7<3X!9h-N~}}ȋQjZl-GcE yeh(NDŽअ;~#gXQnAh`4Gi979pvNk|T8bh H`aMA1|}9_Z hX5mv_ߝg;.Ym`Cޏ:os<45 …lkN3JkRL9slPḱֲmw+i +n>.$ieW]Q(XY[f?,s9{gǔ\ C[vl:X(41Nm7XuS,6w{5i\n&Skx0kš֋2|/|'Uyû 1$﬋W &Œ(ni }T. } XB5=7Xelpᴶb(W`i77MT+s!W6]gnG??n(ܕUvGRߎjibi:sg5Zk:l?! m{vg;@|U`_MF3zlIin"5 -)`m 4!Pu1͙ ]N k2O9yUΧ|n^k ]͌hHJ͚*r!sxZmGpV! ӤO4~o'{N3:{W+|wN͇#}#w38n`>žAAև4(~!Z{z9ʋ\_`SeByEKǫv'OI| J1wNmQ-2x6q<HCbC1fyg% : O RT́vO;+0Sk<>ZOW >Xc"X}ӽVᥱܶտ]4.͜i>c l|p.v zqnBCV~z gc0dls4GOVog5rMk #k_rBs lNOu)Oڎij}NB$́n%醚iEbNݚ@,́s5KYs}U~NiY]OG62/'gqfb݁5a ́`e2qsgSf`k[?~7?a"ڛrǠ/3ܷ#'Xo2kxt׺vWyXCgn\;ܐmqt6-ߵ#]-B~FCogl0&|8s9O[wC6x֤45/;r/jH1xScm9300+붨n~E@Ahڸ*v. u>ߑ϶Yst2v`9sE/ `b}|fs{ss F`Qs?O@w*0b8%G"m^@7 QiZ Ƕ.-xݚiNm|1^;nm٭Y2_ԟ¸Da&mۨ`CF"2p\5>1xwcW3mK_81s\ `5߹BBdB 22e'mځDَԱUgE8ZCY^yw&#i&$7X}7uP@齙JLo(|ǴzV*>:a7}}g{gsu{(w<;qV0ilz|"mLޑ4em VȹHgm3?cLs$`gߢRؒtd2n1 Ds09n<| @#O(JpJ@,l1-Ai9 3-y3:xgipÙ@f^pB (g'O~_F{+i@Xw?.B~ V@JR +TT3Pn6}n&LG\\]a+^I78}lq@-~HٴF[• w%\Sk3]vlVC*őέEn |سV V[l=ddΏ8a?{ 03`ѷvI MPM6s-qd24a 3cFo~[Mk #ci`E Ch@d9=iuTА%c}9ډ}@ a`}`#9&̳9EM^C5`>(kjdj8j|G?&`&w0V1.B]kcHD/@۠FuV0vZ\r9JeϰKf ]}_gmxf0١eX9{b$XXSBmy `k09qZ)go4emL'êԷYQ71PLwbګLB(??M iZ ILymBaBАմ{L6YD4lXY5b+G<= #<kx{elBLxv=+|OG*g +bsh߲޴3M,4&E X<_ X HM:&jOT T\175A"l~0) [X˲}|wn}ƻ=-r4ߦ ȹޮ]|O+U+8OE/h[3#.6VզJ̈́6k(gbg` @bja" Vo`E}9Ϲ2  UCm2{_V-&gI LJ 1|y`fe$O(E*tku fS}q>K$KюA`$0sߠ @Prܲ3m&MbR6!ghW36G}Og"Ш7 Vݼebw 4AXiX]ftvli÷B Fz9;a`<,N; cˋǿw=6-װB; V9^`52(24ɻ}'mګfCƊ\ħ>%0m; K ɓ4p4-L%` 4K{'bb5#6"y{2p!m|Hˆ? o[KX q_66\t? !=~XX+>F #A~ѴiG|5pXi2W\sBkV5t>5 (&Ϫ,T81tyU \f/, ڜn1P8*ᄑ4Vל  >7)ːܢJ= !d$M!}܀0N̈́gE_=svm~}*}ry[}ɻ`0plⅱpYcvfA3TY IU2Xm`C P Ddg%9*c@t.#vfGlRi}ڜg3W-w2ln`E=hg x eNmcj0 2F#|p|d~]y9^oꀵEv=n`%9ʱ6OB*n?+S+д9u-ˁߍ;:󿂰R*5> Aͯym8w>:M]AjuCi_ rn \6fX-nv wsbL:#6 -0b;s/!m +d\c4PPjJ:gΦNFq WZ+hXy^Fܿ p:Gcdb~N$Z(I<~ƑH~̄ه58inGhJw`%h%px1XiL',`}'%/sGjl) xG5/ZrϕLmͼwLW_X T3חi27m5=y ;٦-:,l; VNk`{/u^7%`բ I e.AbxJ*aސHyXHn4Ҳ{88(o5e57cpZlθK Oc`?- m6X5y# V)7e->!_ڷ9;! \?"iE܎tx5p VerW!\6̂>p>1GOщ`c:#qvjw u&6hxb> Nq?B8cmD )cq_n͛*_5п 5yUo6>Ӧ!q7\@)h9Wkmbk gaj2S7Xp U|l%g?/e ֬X;f A><8(;m E]Μ.Y!$#H@c!(xJ_.~Ugg뮪ƊAカ\i `:}[ksMtSZ<B6S6_:H (@WclgtjU74X@0B NjvM*; I WHw" V:ye&Vl \W8n>i2gմ 4\g;,  Bm~5upŸ4r{6K Q f} 7Ԛ<[bvk &Xm18Z+߯cjH i4MWGF8G+1k2,V20*ѧSӑ&L7IwBlds HH*$oAi-XLm V7qv]/}Z*+s8V܁o#i S c0 @#ǠpV!=+ӳ%5HiilfFU}x,twf"z )W榁Zs5w⯜ Aխ]V_gÖ!kPNV@B: m^3r-J.bV}N>pd&(4q>h]YZ(M.BrY-Ϝt ٗk$!Iy!FnV+I簜Hnf\7Ҵ\saJ x$FCUw-Sm&7ʥfs7}haZ:-})a^`^;N_X!Yjc&0C'X>~XN㉟)_~)»Il3]ϊs`"ج}5lnbA9DWԱv^;wb~UlՎO0eYK7zo?5ý{K.d<''XyLm" &Ύ=A3_6N X4s ͤQz{&P 8hu 6áҤjAt36q[#sq$t 8yVLɎX%Xw r>ɦ @5S*S36׵c#7GxV alX;8VȔH`KH8;'_mb3X%ZMbYn|k ɀٱzrc"̊3omiPmVz7{bX8b&o5JmVL f ݗſfAN8Xkg#dGÔb)`#aGxi"sZ2F6284[@ڡy4faϱl`~nɽ9?ɾmNl6Ě@8hM;e Ü W6l0H.0Lki5&XQp5e 0+!jؙl^E+r"0:y5 Wl#~yn~VnV$Z\ɚ +Vr=ra\GWvG\zWߟ~V'$lVw'`3/Ҽ:}=69}>\+MKr<#k5 yǚM% (J3>8s i 8mbh|>&lÜ4"J{F@Ckc3ZcVF /GzkrӤ~VrKFDv_?X{e,997PM V^9XXiYL=*0Jim'u(i - !c1 ]Yˀ9CFGڪ>D1t&a /M! ?me@)>5iR96[5ʹVvyg]ẃљcנ]zϪ{c6|RjPE>Kp>́3t/0g3ə~ޔnj{+_l>f¦bcǽNLlE;}ͱ2Xy@Ʀʠ[3Փ{J-jp>HrIl25 `> C"d́35'W l{BBvDپpyyȝZ ^`xVnKiL m\Xif7YcO06fGYټ.l7mo@ "kӡ;˫ ]ͽZy1 i:m/c$f3AL ف9gk-c <}y,c2M_X"^57.ӥ4/_[W0$96"<Eoߡ/j[\ALmRt٣zmXgڋVr:ӕğ+ɞz汶 ~c5 $Y~foU#bvcb V2؇m^h5%@i ޙ9bwK'edLf- Ȟ|fcΣn2͵ vpĜQP /mB7$TlZ2`d ZYx.;G-*0r0Vy^:f*MlʽFұ=W" 3\G9?̨ HiI~ǖ٨'rI ]`ՠl3Y~ Xb؉}cB*&O쨿Ϙ^8sw 1^Ö5W6U&, (aiNApNYknF6 67 ɕ-wī31`)qƊk~f+|F0S>baXj|~kz2,uF-3soĎ9ՕXuų^ V жg\)o^m 5i3g37n*|{Ԓ #me5@o._?K?mAW 9[,kWkU,lgѰ9JS8P`LpNۢ2|=CǞ'|[Ճ^ P˘aNm IC{7OLM._/Wڷ'?'uN!y4y/l42* l U.~͐(˕SJ> V򘝲 CִP5.Ďߎ 1jv0CxL k豺Lm&j4#9aPgJKՈMۘe/J0l8ȧҴn/?l_lv*YCc 4aӟoHcqXL Rkec۾<鿵Uj^Xxd6XĹ۱Z k;B?\ch8lV-gKMG{X9r;'@uño|o':ilmqsu&e @ۜmьop Wk? \eh׿_57f:LhB4?jCai:,O'/c"%'>0\zE5?ǐpi1́'TXmv#ƌqCx3ǛULg<0iXk6jp |jd=ia ? o~.3oLXRga t3!iFJb{ Oa+j+R̐;<_hPBpj$'tnej-kB }IMKB cPݓL>c`1k8h0y)) X%ypmF|1ִ6w c$/r"x Vs0LŚG5&`Q ׀56#6e7xqvb`0f⟛}ԉˁT [oНt,d Vєv$($xi M/ eXjN+(7{A` } 1^բ *2kjz}9LSoJ(1_o 8&X)֖˰Ĵ9S&"&ԜMٜv>3m_ лئ? !~ mWy2XژgJxqPMP|Acgs&yR)7=pq_x6hO\3X>d V+x9 (G~@*z Vh7ӿ_i}^D0[6Spg ɥF`+Å'L%L| 9r2{5P;U;fOn[RypG{TBj-kv8fMVmUڶeEN~n}#cm̘Tg`ՙ6mZ,0\~/e\\ [WkXc8͒QGFGfqxeH ޅvY:8?o'| ̓'V |SjS VwJݮOJ=mP0 VMР,Ad[XCU럯˳qEq=+k`/ܑsJV&RnkL5ۂs?8e8ۂ{"O?O) (47Xu Y$Vڠ0m@& Ц\wum\ \y~}썴j+,i榉S*ܦ&#yI Pf:$=uҎ矱3T/ۦd55/|/c{r㾓l#^  VO'褹0@W&\YC95b^vfN9}W`0H1#9c9q3`/2ԁd^yr%li_B7&(;W"4>Y∙ 3hXIHbĽ 4% g|ӻ)ZմV$C]s1*C:0v/hGn`ɹ5Z$& 4 ʹwIޟVk̓Z=+Cٟ LW)|z]q=T/`X8 `sEr3#n&@ NaP2m̜~V^ *i5X'k/6z]m2"LFrƵiR<$h RDZ}fB`*e3^Z+ٽXgр Bk9--gh V 9|<]>VHˀh%pehL1ޑqrq~g `Eh D+>f0\m#C7Z$dV,F5 c8Pmni.r"hkM|ЌXCjlbs& VSߩxQjZQ35 R; Ҿ`>gN l+w,XqOs,D42&xb k츜@n&6-Wb+6}`,f͞|U8][;~g@|,5mY;:3liͿ~e'k<-k0 5{a*噔{k@k D0%gq&AKf!q\G.os`sBN0I.;VѨ!O~m] B!ɀʐՀǭ"u߽R02aaB9X8fʐ i'?S9oZ\!%+,ꀜŸ_$ 8y9pb [c٠ͿeXk@+j%^3i.juEQOj(}Oj ,3G.S m0D"㼄YйG_;.F+015x2XxK $X~JJ 8?o[cep HWu\[h?W EHL;g[UCa02ڼ́S@㴙3 `}|Gϋʚ/6\O6~jjZ-ɲm,X%\% -&*Yce2JŚS~Iвi ?>/AZ؇ Fy=hkeb2mKEYT;f;SZ..0dwsns 8Ys:^Sd&PH& Vc=vJFs,́sA}6եvbo~VY&%p]c>xvo eSyVm ƚhmbd 'H& ,83jܯ)1Ty3Xmڇ,3! It*7H@ W)5C+k&벉DŽx4Mc W l{7`^aL93ʓ}i0s\^ρ2ӚPUӳ)Ӡm?|-p,wo7jVv&Ձ#aFe4+i&>|y ^\t o^(b?@ Beip嶶ʶ55~V, BaLu m5%u VloZ8c:tܫcS42ƪ`!N>OmN\ ļ_G5EZ%LjZ ͑-c5CH P5!"X5@`0}m8 Nyf1em<ޗp_6y悁p5KP% !4`onG '>~)ka Vw;Ч qBg hC )"~zŹ3$`vj+駚x^ V=k}=dɝO;Æ록n҂*inD+̗}ߧ9l},A+P@6 D Loa\Z1aB6XVV֥!ک qKedoFlNSPw+ڔEy',ƙwx;,R匉2X94(̱- ڋNhy yÒ7x3Xyr^8b5%tZkqzj66%_&#OƮs}9ͧv a[tMk Qm:]3R06sAMd'2D2o6&V˖$@@;ZBewYiɉ`|&.~c˱|̲l`@ 30:>W>:5?svkΡ210}j`>wk'ύK1j8l6ffө*ZBC}{rO;f${6`9 r?j^R[c'@8: Ѥp:[2fBt=m[%`j#&*skܓܭ NϼlTR&|OYiѦjqGJ0MHk>Ef.XY|)~hVmZnЯFvp;Ҏe3!p4bDfht|3O>XQ>%'5 Vi4+dˑ꛿vd烶d ciLAhxV-֔ ZThhZ+JLpF7XyaD=hgvOkϘgEQܝd8_6$犴/)Ip9@*5)R`yAU]BKdSP:m0nW`uJoL+~mBtx &~;pV-x!^q4n {B`Z<1\i9N߆Iխߚ2)2DB'H :+1 Hk}J-M8fi[Aes`N fiY8Z?U1@6d 8pNXĶ6#EPEA9/c镥XTu?Sީ~?O;2f@uz7 `gkUD!!'tHFNrqX% EHג  s5t&Ygks %X!ϕ!&n`um`Ֆ 9!*!aCm+ 6a ǗF`shǹ6tm+בE^`rI2\ć`@mJ3lfLh!&5hL)t-1i/h4wkRr}OitTB|W#$\6s C4V<όe)M3` Aޒ"Mbm|bӠ|nDľ)r7[.w♰_,Z63-@S4椗XRhۘw,zD2||Ïo>X$el1p-zɑ}\LԞ쬦Orxn6'&DW~V'ly|<d}|Qdn_VW1.'w_VK ;hBZ>[S|+ ܇nG ~OZgʹceXm-^廟uUg "5iB3[@ PWɖbŚ+k5!δ}8<!WVpmJ~igKE_q8ԩ]%4,[{U3F]`3zmn>zMu/u(SwcxUאq9KX}=e`2;˾ۿD)@|V^﹯|Өpsx(sz Uk/fzRsn1/7Hݓ_S`8D.Bx;px*Au;wJm星`HY~Eq_uǕF~aނڽڠ+`nJhմq1`d9y鋕dLn}pFino͌65s+S,dԟ}+9Xi˱U WmدL 6Z^rf]G&9S:l)-P V5P/Ո vc,e,p4'ࠤ96k>D8+497^&4lv퓖(6&̈́>AOvqڭtNVB`$u'1T#\`Mjj o#$liA2%'+ogi#61l~Xely \6{b 8r jpBW[gE[0ΝfD8S&4s3+YKL) #S5Lfi+ ;ץ9Vb@SO+Yc0s_+ R-36Vnש-3y}o ,^M~)ͽ83}n ݔ2{fg?v]<^[\%YQs&qQnï p7A~:-2{Z˯-9wŲ05bfe؏L)8j-JgUjB fvedtK1 JO^g]pC?'?_M2;pKcVo6ۅ 6W9)$Vx.l2On:\_52NEb/>%{IѰor*߃|6!C5iRi h,6O@rW^ڬk ̧ Z.&"xw4")&hrWC!3B{u֊uf4h;ɸ21D9ubh6">KZJerxy['O:~Z6iوimfyz:6.!,6:{jhqMH C Dig1`IjoўO Z4M ޴| `U6X97\783`m5S`5'-2e΃ִj+CM㚥Jrە:xLg]uN5ZY _K#ieI@޴C#k(_Uj/P~5T!k䱙f\7,k~Ar{'ϫ_u׌lq0y˚P3bQ[:/bU66%zKt`6g_, ~o'wp$(]%lp`S/3Xk=nP9MI 0 V*ކ &DWz?M[5ea}hvvgE,3ăBN\;"mrl ?hv+kg WMMC c?&kU'qU5FIc8 @۔:;=͞R[g G8 ɏ{{: lsܗV<ۺ?3~Pcj::0o$ޅه5K $9V1+Gl6w| %b˧kyf /"pa٬ny7 V8i:3XNF۟Yg8`vY>!Ń>ʾ ψAJmLTAv{FI$M{V#X>Bj izv ˘( O ``UE~'/LLo6l|%ŸvN+Z6}ߊзt[93`.!1 UNiIgt`Bڥ7syE4T6潣\sk,]&0O>kޥfйZ-iGܣ _uZ/>fUj^f~4v߲N:;H&vI[vo%ٱ&`//[  U;L2- iNVmc9K vFX$8sB{N31 *Яk s. T: -2=5VX94Gܝۆ4&$@pߢ=my Vx/O큚- s'snKӭ՚77fWC6MV69>jNtj EegXlx*k!ǘpY=km9ikVu35V6p1ֵ-ܻ9>HK.@$]/c\9nN.K8n8 Hj`g%Aɾƀe7;)oRm&vۭ8xO | v8~V-Dzc+,8w}9VHuirt!$bπH&͗ LX!*ߚ}:Xqnw%#S6ߎe-ſfgAmzD֦-rI?M2ENl`F1--j`U`}-s t= ͊I⨰真_\r`=5>PX`ɵ uV*NQ lbtW[sqsNvBW߯E 890H6 ˎMu,x|5R5P5N86 ' vFvF#]J|dnAl5XXOϊ-C+J3!׵i%!e _$- jN-iZKLr^r6mţJ MUK|d~i؊DH: |f`e1'3{݃M vfkTߔ+ϡ25iֺDyEr"|ĵهYkTbm#缕'.Wh/)M̎d26X"[-Y5yn<g QwLFN;pt0,5Ƿ(Bj6F_XEw_Kc夨s u=Y-E7֠X&0V{:@paG1-~ӽU׬uړf2vWd*aX?dM$Ƿ3a?6k%lInBXf4Ƶ-5iW31Đו6ѝArgNЦdvO$H*!&B`\t* >v\@¿ƛob̈y22p-"~JjfĖ KǙ6!@?' e_/m"u i^u8qr" |{R&hX ~YVf5 \\H>?lf́|2{وͪloXm~E-j 6x:ak!}LSM/cG i#f߬`&Bk/i%?j޲R3#y'|LVy= V\Cnς V];j}y762uw+伥Akj+l-K+L< .e88]_3۟ϿޣѪ(eHtb_X\vۻV|7&v㷇D` w;_+͑vp޶-'ws.ɯ0I^e ZS=#/UJ\v2g{^>x֊~gbb4T0;JFȶԘk[eN(-pE?0>ݮV$먶;_ogܓzhs&Tg}BZ\߷l^@00nAJ+g$Sٌpdi5 F#¾YζitЎ}&+!6}[ܟq0Oc"|Ie#  `|}w;f`R:|ޜr!- o@Ofo Z+k6dM%agZ@{}oZH`s]ֶz_7Z**P6-s8UB=9} a^B]TCk+d7qR.a,2Z~ӂ2 1X`14} :r d̠ʶH+P\ʊ}V!gpY~p+C j@rt Q[0цͶ[g8zyDE<%H%#AZy9!@ud XŃaլĝ Aїv` ^Z+ 6X҄14X"؞,nxq?7sO,oPoˠs[a- 58ZkeJMwP9x뾤OZ G>9j3;Ym3l3컗$٨͟W-TCXsvi̤yg $>=khKڴ]not`mkf?-㡱m|}.-ULilc}}j ~V-s:UXZ-crVֳoVF :%K>K. >m^ӏIVV'<#.<_S.2/cX6~X6n"l<ӑ_ގskYq;9n#A,pr4j[k[ٴy|?/)lf+ŶV:(MXa=&x\9c4 r@p{|Pfx̄rkY4X5MpՈy.Z2,TG0%0Raf Uw`;# lpmj`csl'm3w$ajO1X5W1]:MHS}{820ng{nOE7bvlna1 li͜i.MarTS"Z݄o\CFզRT{3Wo3H%SCj}V-V g j;Jvfg4^0i٢]4M+, =KjHi9b J[۶S5b,O->X `5sӾOIa,Z%,I/?Ji`eSFrlб8œݕˋzOzc" }7xG O`ՀɁrN+WwY\BgdKqs} Z:E-_`AICWhsVퟶ=f_ ~?SaQ<0֢#65voZ$F<THF9r H" ,k/"~Ij[>&ޗ h`. Cr4Kjbƹ<>D\5M5Z'NotVw wE@̥ic]5c;h?%YFG:UG:q`iK-F-5 "򔃨N̽w̓{e\'W YLuJ+51@JJs G )@kS`$Gʘ6^*$ly4X] Һ N+=fir^[l0P?״-^.͇HBX4$kZ(Ŀ`-6pmN#d.2f/xZ3*Qy7Xܹ&MYu&/b_KL}*[5;`oDdcq1<{&V^e^brZF*)ZĪ#- hٲk Eܧi{WAr6qUh޴#341l4|SNsaqn [)X%?[NDNqmnmc` C^m1Xy~h* IM֖ǁl\gąs[䌏\\zLh/oo@0ۻQѦak\TzAOl&Cme!rJޛ.bϘ7rf64&(03s6A@?=U^s6,&z\OPom])Vn@6-sέvuKF2|W=59NtkSOg<_Vi Sfi^o9u-Nr vf:ئVC`5ʨ–Ѻ/i_;AD&o2D~ 8,h휚匏0=Pmfu]1h|li9>a3-i{˲.Q;H*ϘM aťX=׌q4 n LJqMB_\Ȉ3?-ls5lؕ0{d^<E|鄐ָZHExpQ[UG6s#~sZYQμZ ʠ{Uw䢢mz gdgڽٮc*g֞;7MM&I}oL&4> L\y kj+ CVM#M?AO" \H;?ʸ H췿/^, h`eLS E[Fk ֏+39_>Wu& a E h`u`sα&!c2dLe$֛noӘMu6oZ/Ɨ!D1[cֹfo|d SY;;˯X<vڝ^L HiE[o N-6K&SZ^`߻yviy}Ư}4XXDj٘.}NGګ P3o|جiw V۽`sIػVNK0G S ,-UʣjK ] HJ8شhc m`epwY/XzV2};SZ|v3dh`Ly#u+gSKxṁ%&au%h3-fa"ZQ4U#폦ȧžŌgk`n~-˕u>91XbVj7bi` ٧Mm W[wk`g{ߴVHMv@ b8c"T8#H굨>WE`eGtR9*2WNi2Hxqjȏ2Ujp}$mύy=G9&;7<5-ZsNdeǶV6_y.~L 9`|wznmwUoXJ<M^`PByu AE;7:G V, 󥱺>5>לyQ6LX+ v6C9S.j<`}6_VW>5G&hG ;ji^5dAxm~Z8XKjD€As|"ܴ,WMbx b[{ѐhq["l:9'J-@27ʚ c#ۚ**ߑgPtml)DA%'ElKu|9b<~+ %o29Ui@d-lo@ ,X5[?2~ l4AšfH"O4vn7K\h`1Mf`q)# 7iIZ ln X5MaB%GR++ȭf-vy`6 hǁ3Hh'#d]zI߇Hn.k Bl4XXb~6홡 :s5l>@G]ʂV-xS Ѽ.DZ#Ҫ 4*p66!!lG.k<`pҺg Lc"l58TN{,HfowT5igBX]@6:fwcX`l77sl&K߷{iǺv՜ hche;@)c FE~2,! 77l*#R`ϖXS[=.U|c3< "#\|?lιivXP+p3#v'k<9 i0gj3m=۴Ye k€ U0#-~[lrV#{Yn1VIk=OHS&$"]x98a)A! - *N`alg`ilj O#!55ϙss\4VkhQ^ؑWRlq!< Y Ozc00N"J ;,~JPح&sixb?Ü͇ 5myy|e+Ǹ팊ifX5k v:h`"˼ئ)5JZ5 yRK ́ :C#H[:gr3N\QײI1;6+/%~-n`J Pc\ W[QzwsGuo[=5II4:"VWڂIm`r6|nBME!>hÀ証^ 4I\&E~T+k\N #X5-LΡ6mM;xϷk+ DMiVy^ {m̳S6w--`->k9h.?i4>à` D̂ESl%zP缙8>A6ύE%LgF5 ƁΝ` 뚨dة8l!4pI؝shײiFgڞsq={wpG֌?Xz5FL), ֪ˮx}8{ͦ@U7q0@nMe˱smA$,\}SuQq'hlu_ݎkf 4}: R2é4cx+dԯU71R36DmiͿjsDwb 6}ZZ] ,a 慅:ѩ˱pSlk XŠ6qVN풵 Xn+2y&}X 7A^DGAG!Z@g~7Dup>C%д4]  l!ۢ 䦦vk`e f8:IPـđ,y^E b0'HV  Fޕ08`lRsRڜq>9C} 䡺*!kö , V*)ih V[TG_VMԡf̬m޾U)< VQf&r4]}O ЁZt/j'j8ʶ`ٮB&mAtPX)4q\v}m9.ӸMnΩc/ӧirߌ[!/65-!Y[-52>7L@Ai3TXFgl59|] Vͷj D56#ATtg`fMMKɐ3⠡Lus35L>HgQO-V\j }ӄQ }MH@-L Z=Lo7d4U:4` Xq.?ܸ ȢYy|ؿ3Tu&H@f>+@O>_2km]Cpڼamm>D[$dz6j?z, Zkg?1v 5,{D{""? !lKgmŚ<@Kfmħquߑ°V;Q{fx,\?㦭BF' }ܛ#5ZfŠ-80O2<^S}[ 'e`1x^1Xڟ'g5q{gn2wjrQㆽs5I,qef[?Bh+g To71Kj7#XyEiB>hf|3eTk|~" ܎^sm-E7h`ER_ตMbh^_=l 33-)j;^_]Hƭn@2~N)۴P>s uks/Nvp-6~EcI4 `]`ٷE!* `@ .`mys,~r@e#!# 0/kq{C~09w -aӼz;1&2 ӆc|/ dX=pl 5Slo-T@1:eg_vN)m>mBn6MymKKgsݒRV m0OdmfM+ cG64 ߻ GGWEWA[^`"b~M-KL8~Ay8#U `EK- <Uϫ9Ojя .Qd? dqٗXa&L an0<}}4&ci@ CfF+Wn42 Vs ʃe7ܟ!`E[dbo -kj5vߗbz:qj9"&F/ t.)6U}L-$HK\/$N~ x|ͷl%Gu^*K4m^G ~8 [Made* LG쐎vɹ(o++/+ _}f]'q{֫ Tտs_5k:$gb /R3;k/xf1[g^rs')*X0n44KHc7X~•s|Zmm`ep4_+lr CeҵzQV hb/ڔN~~qI.aJ+ΧowEk,X@s.#XU‡E_D Aj " d3ϭsz-䌣S)ڿ=|K"@SDZ.CrsܮUJ&H"ʹd@:.ϋy6r[K-y |J_@LIbiE6& Qi-/eퟓ 0 fq),75~mYZ%8oZ ZwڂVt6QHan+ Pl+@]ƕ'׋$x"XZM\ϧW_wU eD~Vbc6ش&VSe֦ŠsaXY2(>׽E$6PzN m`׬nc2h92Fge-J)X [mV/?3Wo^O7#lͬEdo wSl%؄c yq~Oç خwǥlCrdm>6)L`g-/Tj5kֺFl>E;=( ~`ѢZD$Ǵ^49G]39Nb;VJ#ho?ibqB\@$6(}{l+N.4x6|Xr >sj'ޞZ(?XYSׂybl|ϗ3sY]`eGk4E |U yvm\_ڷy* (13zOsx;IVpeMK33 $j9yE հ_WAlrnylii/-9.qF(6- \rsNr4l45k|l@u6rGCZ̹2?m])$\m ̈́ vY"Pl~_XE:Z,qb@لq.؇Ֆܒcjqf3P{"z!J>ώdXp,m@jyZ:X:|mھ?jAyQ|X2A\]S8D\L8j3`S6S}BO.הik8Wg_a2hT#$%-qrgweX& n 8oPlm>K> p0(8 6i6lP P2dZ+æBi϶3?~zN+fI\Z Z 7~VCs;g0Gv#| 0`9lfz'M20q{+lfQ)TPyn^ϒU5hNa 󸖈6s kSG? UFٺs➹*31Xv \lcq魯o/͈s ,$#g3mdƌt䣿k~KΓŦN ^t TU8?B\0\֮2eC^Մ}6`spB V\TDž<ϣGm>o_OUmnK~ͧk+PFlKÒnڙENyF.b_y<7^ ?w.|,kbBx+0-mٜV*Eꑷɚ+Pcn:ۧ ǯA}R5IjY @@#z-Q&Ccvfګ`lLqLS3`c@cPab;1ZhOV,^s![V ~:TsB~?mrTWլq\<7&a "ffӂ!nO/}U=f3 173ޏة;0>o@ c|f$#fi6i376 Q_ m9}hުϖh]iӚ? V=0VnNjVDcc}>]%5mf,KeNaGp]՞-lZO[Yk`1hHq}72HN B)f3 &@| V/hZ ss.iI^' _ 3hȃɋMa܀vFZՈC?~b- s&-5gk(|eXZ{|  /jJm°iv/^)mމ ly F5(v36)&H|jِ F N1hZo){3$ڗ9.~V0Eѐ{C43;cnpгicHdzIr;V+ ?\V˪3|74;J- t()TRPEȡ^b#Pw`{kqrH3Sͽ4G+ %k򳈶imkԮΤ8J9_K>1>"Ys)&Z0R9$_\| V/Dp2Z+f.B?}-x;w'Vl">r q-z9&Ҳ+ YK!Y38aHy Q{8!2fÔ@lMG8S.&澁#d2%Qaᙔvi6B}mZ'BB+˼祝Ce+CriZt $]ǧa,mptqGk ߌXd_o,@À-/g{{KlA )žԂoNՆ3/#b?Dž s}N_PDhHVivva֐s <;i!o[ 6i*\n6X6nhjI Om92tbj."2WZr|]ٵ96CBii Pd%͹#5p4o-" ,-"/F_il`5Ӧ('(*sX<!!hEj{\{5nfn%dܒ6- D\  ۵{xj>O'zEz^mc>k4}% T^#W:GB FxVCբ-[{}yW4w3G]ڑWZܦBZ[!Jqn+DyghGKx؜ly1,{aj4 V !~ |^0mM{Ğ3Foxr+O!nV[ЀӃ$ :PZ#Aj `ʅˋ do\,7 @`JL(NLIqW"6hplÑy=YipdIP^6'#|o`ł2/h3X:y~Ӌke"ǷjF_WV& a +fWZG"cGPp[Z oj$R{ẗ́ bd\[2||T0|GMc\ -QZ-Wxn\ZϒE=E# WA +.MQyj[r-Nh?x)Eº9fm6X_ډyZkMV>6mϋ[7He#>s6W ?sgb@Ukeo%mqޖ{cݏ%f{ʾ-f$0e']#\/mk)FGeG?^r3[ 8E}̟ Ǒ@1ܭoն۔FԴV~QKz=NU0J  .䚾n%j &|7Zwh1eC| ű)YhAʀET!55㥛PBׯ{&(VM#\XtO-g2oE]\&ǚ̉\ ^02saeS5 V[[Mc%1/voۉΗ>:[+}/Ys;͉} $β2fѷO/  poHI>=gӹcel<[6? JIR9gOVqM[ Dp!6rՕ8ZQ; ?ɼ>Yxyfxv D1N0lcL8l6 a$K0I~Zo0W>'4csu!o*'3whQ+XqH(}(j-$ 6\e#nK lE3P$aG8Zk8_d[YD 6!b.\ BWERdB*:Y_k%(mCOεyͥ`|$h6bjsd,6ߤ؄Z-Oo3Al%^h׉S۾̿3a5 y04pCZHAMnK/h_EMcM \[|$5XW-DG%'PHwE"sLK <DZ ^S=V ̸?"JL`ZR8t8qB?nt##\mcmE rZ9ho ~ 6v^RSj mX|s*y<&i}H9r3 zXZTf4Mkm";:_m3)n^+b߯V53x316f죕ݡ=o&${ynbBv[B̫l @vV:" ~o\{tDQw6Ǎ1|7H?Ś4ϫ[|lc+|x.Obw?w7}hHX?l(ʗ_ʫl.V֪\;689|+j}0}?kl? 8 -ʏ8=;C~kAi+mڞ 8 Hp%Mhm1_/o!ʠJlؿIfGcVJci7HV.w:\5mmZBjjLr\HkSf;<}h~ !%c!Ð6O/!:]Ww{ xX/6C-n_Ω_Y9+Z#ub6 H_ۜ%}6!+/WJh`UK6zI+dGiC/E=0{L~4~kF:o#a [~~O OQ'wk!޾ewԊ%6Yssv6 sp2mܭ| e h-M .r_-ŁA+5+`" FBE}>ۄؠfS,v`ûRXeNCIJi\ V]zE.@4-ϗ߼pRk՜^mV`EYẂ+e|3x -i%|VH{Մ~N ݶ!m L5S`%`FW0Ml-D`*XJ9ߠҢ7;U n9וj$fs$ܹ̉ciPCW6%,BkNgٍq/hg+S 7?fNC 38k ~}Ѷ9Ww'/fRu V#[a,Mm_Ź ,c~O7(B{"Gpr•T]ڱC{U sދ~W# `,+O{oYtiņo'XmpEsu%_q۴V6qU잀~#GޖQY>>rl| $Κm>㴆U/^m UesQKl&6_i6ZeZޫ4m+B\_CdY_E#kZIvmZta2<` >kW|k V`?Z~GTu&N8YE؎5yUJ0>o͘M~֪r7XyLhsymTG V6;}6zeڒt`mkWtz ]+2Hk_OsĂ15wCإBxٴwi~6h`il64б@{>q X4˟*3ro'>x́yΜ|svԦOJrt*2\9`5qѿEц!s˂m 5s8& hy Ξ;UN;}:ix`Ex5S랤6'acs{rY44j%i82Od>] .} Zv7{]x6nÖfXSW?=Qز}kZ Mk7ti.jɲMd3X5gu`cө Rہ_!AZTLW;d 7\oH[ vV9\Cj` E90`k6Eגk " Est4*5#y~~Ґd"ωH7fxk$Ax"g yC &,{  y8qڔsY[*X].k1xn]VW..~+%=H ]6`dkulL"Χk9{k41 Ɗ[[z ,E9sޒvgN4اʰwd,tJq5oKɶX\ ΄7UX9MMӀZDӥXLnn%Ea/vmFȰ_vGx VyLkAn-#WgO46]-͗i+ۀ+ ֢ ['ʅɑ-V9󽙒&l [Tb;C1VtxN6>֥dl&C$ʀ@ڿ,=uODl(B'}O2Ѯk:'%؆oEXQ-K4mX~Uﯹ2$tnn CoϹAq%9$"1rKs g V;4`k8X) X:Xv@7 YnsG?k*@5'rs mryL5~R @ >Ȗ9m[h |7U.~6r-IeZx m~V|j=[ 6(gr16bk5(mi V+5k R VּY5j͢3ƪF)cv+ϊZs,`EDC5,%s'r䧜>yk?'m|DZT`u$*WX1`q\fpB\y9Zm2XI=q_~V VW'_+f3҂]V-fHրڽ,n}72mZV VhXje_)l &=_ njUܿ4CSGnu7G5Qcp%sSMb3y36ob&̄$!cmou=>CV ꣁ/F^ns_s B-# Vcٖеl}-4|C~M 6bDM5 c\GeXΡڊY{jWOآ[#3'UOW$zHk3}\}6x8Ƶ VlZ#|jf@kw@BV i}'XYk4{gQ V}5Ypra}_ ֟"(,b hl6-WD'ٯjLLα7̍4H]mqjkm2Tw>O.GrEyag cyT[^1niArfB4V6n%Nl X V\x;U}i=gepm!` HK5V=m!6%T>kT;Ƕk=]ŦE';Mp|@mn4L| M58zlMt<=>O*Lmy?f"?;aem]SVl A ffBUc7UO!6;ljͿe˺4 q1"6gekVOсwh+owzvi꣙ V43` q}YH֚-C͉v:w@O|U 0J. V?aA} {rQO_oi<^D O>5Z ;|7|m ،ٗhHӄ!k3kX币>Ǚ V +GV8E<9Ǣ9XpFPc'6&I4Qp|o@ 4eeq_Ú7X] ܰ'溧~]k|lqm\}_lXxs]/$*8!$v` K;s+VҠ'ZS.lʂ߮!:ܶqMQ|s*:Xu(b̙@ 1Xyy]fKl.2JhߛMgX9 ]V q;55+k Z4V W@Й4 vk6VyN/*f!eF{́VlDLl[@YVY$ uw2>+oP5QCUJ•4L|V[~ˑ[+e`vI룁rA!BW-oQ~˽psnCBB+? OAM@+}{8cɗ yN& 5\]`97 <ג=ye`oQpl3$$\mPSBN*溙w6OkPd3!gk,6m #Lf_}ZqgX V6&8.@|d9~Õ<D'X9Aj`&F2~k?yg"4 $x !v*J7PYhoB{*P|S-C*6ŗD$G';W.]Yk:U5lNV3ޮq`u!b+̪Wܥ,-~|- &ܾIm4 g^[0BWͷMMkdSpc4XOHJcb{KĀHQzO, Z[؎#b3R}k)ҌAC VA]/l)!<_::( #α\caI4:8H;^K:/x炙yIvf94jFc$ۀaH$Ò @a†?Q>2,ȱr Gk23*+އS_GDE&3۹k/}OFr~8-U=pԮUYpl+W TǗ{\*@RV5\Q߹Yi)*P ZE)*A*_a[m\=&!!=^oi9XAV-=LZHUmЌ?BϺx C)'kj+ehz~;:XY;Xv$u:<+ޘ FУ]O`wSplFQWo)Xf:8y<ݥ6&F"nKa vzV< ͷPXhs^QXcdOQcnf;,ʼ/ÕCu3qzo6<6H)GyV*=s{8`T`c<=P~ӗBZUZUC{&Vzتi HUGK7]z : d^+ \Ea.0oXegW:˼?z{uT0?æU9!-+LY&UoѾY*CFp5;eJAa B龄h;*m^x{ x>k=,^_Cxy@Y:IE4!ZMYh|CVPVnT,Oh6UcPV? +ɳ -LpCndTX@W2VlnU3KRd{jNwگ VEIޛ{+ʠGm XǍm7sFp>s‹U#}VߘUGpU̓dy|w,BVYXyK`TJF"t_W(URe GyVWy_V\KUW+P>AL!,zXg502[1 ^\ըZNU+򾃕k;|es݃yy~B=׿LXej˘*jupU߳{ r/J[~=]ǫtWyVU8Yšk8w8gL ϳRȷBj&X9XeozzXZe-yh˽} e^z#e VY rȀږ='+?RO@w2e zj@*~u54*ƪ<^)1^RzVqwTC`>U X9˶B"{ ⲗ&̳ `_zyPuXbPAwː},!\5K&}zMK7cQV#?};췪s6wժ*9 [',黀o(ͱYypH.;U"cV^T=hz\'K΀.lsQ|xCVE>x^֯BBFM{H+BQJXz#Kˮڞ߳:5|7נWke~m0A_;x98+X1Eoh??_9ſՇUYqoJeF٪2O(X{7 7F`7 LJ8Q V͂P`*]6OBe'Q%^khHWspoLe 4=WLz(++hTbYgʼ{rgMTA΁zXY}3f 9XqRe^++>jRy"I `o4f[qm8y JViȾY"& zEԾ 4A!U}],ě9PV UxЃ4JJp&U`EEU<>(aw`-B V6zN}Rˠ׮a[XȻkWҔBCzWerqzUAy~5mΩN+"]cTBnC{w{Ш$\j;&HSE__!<0YИUdT+{f`5[ϮeRje|cO^`^(rGxV!V"~g{VoQgJ[3jVv"9\ثZPYUhVWKp|۝osR@ߥWr-TX(guz[3``Iߚw`G71j@Y2sƾ3@`2( 7+ˁI IW^2*i>,׬32ߑ^fu6PR9pLgPI`]tʼ;Ֆ=;PyBVXA$>\=!&FV=> tzy8X`o1rUVW k̀#4? 6GTVV=jX}eʨʫӮ0_OAstqpbp req5tPo9<<+O`6;)e8:KA^;Wb+O8}y|2 6+Ku~;8Հzci]߽1 RYf~T`UKs-JJ9@ +kAWU}~MT 6BYaOroP8"@Uz.7=^FMV ӖmsTש*q*A& yf0p zsQFۧ {>{4Q Τ஀kd3ϯ( is{fM^+WS:yZCm " t,sWή7 y_򘩪c mo3ls79=N6X)=j%txV~,)88DR)XXW{EFф(IBwTroe}ME9Xij߱U2*70C|uUF 4zSm:v/d%ͫhCF{-+qCM^;V*{JjP:6m%ChS}8</rxì@sis@q/e9ޚ7^#Vy2mM^+*+hBkҼ׾u$?>ӻt|r:|b:x& 6?^|:|C9Fmǡ{<6_c7KƼM9=D16߿}kbѫkl?L^b^v t|=DC1zf70ho^̅ߎ}1o>8>~0G\Lr:y= >uy:?9b n?yt نc8z>f?#uu;?sߋ^h!D'_Lg\NC'?h9a?=>lbГf!$4O>tB{?!~$Źaki }G ;P;l?b:$ӸO.b 4Ǣ=q}(=g1~: ZbyIOb>'?]NCwz1gŚvӦ柅bc>mci9cW_]Lwrl7=؞~9뤷g؝̺:o /loG{Ka?Nz2݉3aŹ#㯱OS؝߸h:iCCO/1Oڻ7Mw`/9,͸7q?}2п?SJoZ]Eggbߺy\qߺ@aysoG{}́~q5=5_`^ӥnw al@LdN?ۗsW8yL8s~b?twhht+th1}p1݀ZjbNj*%lv>֎˱f( qM u -nnbC Ě8ⱐmll󜰅}-guΖy>[WMW1^t5`BD-0|Zؠ-n nnVt2shs ]o@[Ϯbb3gحV.cd,6ss (qͰb-,y=y&r>F /]6݌7a BݹEgmjmڼvr~imxUko[DΉsx(х8^ڼ7r?ڊ~Ʊ)}Y>ij}5 }B˦[чz5>[̣v_|pt+[b⚡[}c5w~}>9ǜ~,Ƃx+ۡ|\<q- ފ{y'vOh筰~bڋcVouA-z{N⼇|\nO#hᄏch|݉c=s~jG ({P{{slnh/cBmhy{?\s>XaNl[?0.@U鐵za޿j0QV+_jTaڹu. %7@hWK+*"X\"\V z^h'E[@j= j+ļBW-i0 {@Vъ}+(k;ӵB8"\0`^7F?`5E0B  P`#XbNByV&B^ ahQ(Z@`Pp.1aEjZC V@ݡbL\5pڇ%*`M3XA+Wl]*W Ylz 8-+;! -]7a `豢b-=VpqXۀk v^F 2`Cj5 P@p5ha- `E͠B>pڞ*uukKak@W5x`lM'dTz`W EUOuo Xua+ VMCz\8&a5i?1`EB+p`E~t W-baL Xu[OU[ZV( V{+&ՓisW/+jhM*ޭA V*!T+z w>E^+-z80A WGT! R\G <}$< Cڏu #0\΢E-j+ Nc[ j"VBEo Zj`p5ZB(kCU1uCP̏cxWkER 6zn*\aMӛ`EC`j``E \5Z+@ vDLP]b -X=jy3XV j} \ k``"X5{cz ܫpœ  t`3{8p#JKpW \8`.yD PEaij 5 ϰaH PE:s2 mVV+0^1) cU+/.z9?_M?փr`݃Vk$+`k ]7KK:YJAHC6p+C̳ZBfl R-zF`rUŖU_5HҜM|-1^+Bajn؟KpY0LH[IsBm0bbuRU7 P5p}BBXX` }zBbВR9* ce9\1HbNB'\)TAoCZ (zB^p q+.4mu|,`L؂V+X.’rq SB[5ÄU@ Kw"P[;V# J(6Qk }x'Xu>EbS*lK`DqL*j+ XEKx0^оPE1l- ZA]* V-`r:X--zD"`y|> B9Z=,qh9`ű9PEVWhZV"x)XNXAX_k1B X! puUWW Zp9X``9ꨅ [V|yV v-DKJ=S^&BW˱ľ.yZ9h)X8ÇV&X8#XћbZZ\1f)\q?j bPЇ{>T Vѧ* F -DbΕ ЅR `„&ڷ EچaDJDJÂUgU 'kyZVdu &P>lCڲPz܋ /`cÇ \X *!BUoX$ѣ%`1 "Lިm1-W V N \8 .•xlnm+K_I/xD-K8cԍ~/HJ0ٴ؉?+ 6:`=bXWX36W6Vcxd;1DT:Kʓ,+:kX) b1YXQ l"ra 1< ccxTxG 5Fw`>8Z@aG.E5auuQ$c&bU'YXXr36V+ cuKXZu|+LS1UլD%cqz%SR^Nc9&c6Vh6WDjew͕|ִ.E>"#l8"21R֙Iu se0W(#ىaF:'UBs֦*Sr?i}j.+jh{ۅXBͤS,͗S8S5cъ{a *  0LO}bq`>Hk:9 0N>"t`*ŘX.B(f<&$kv"j&6V`\ԪoN\0Y"xyz`cE m0P:Zqcą BKoG̏|]+@~p X{X Mp*>ZTkMdR2W2.0U6V fm|:\)K6V6M6^q`ԡ }sS+ L:Ɗ8OW V3 ;O~,+?Lov< sX== .}יlDx:BOjdkOnq/>\h~1S+S_z7*X+O{+ZL~Yzqj庋lr.jq*^+ fN5>dc.kDGd&zDh0QYUdc:!UBLT |Е1LPVA|=a}:Ee򛅾oF c?X#d2VPXT)J#B1F*c5Rw36Vq q&ceF](1R6V#^6WJfFhϔ(a*s|?rjUB3KQb5V>N+Ú?^x'b1w^%*=6qt͕OV+_hPA2[N҉uJ扙]؄U6Qa.N-`-{Y\ #U 5 zw@9 2YcS6VSOxDhl?Ѱ%z`xXXy&k+:YxaǂnmB.`cVfXDL8 ~J5y&fESe|5\ CA8vN }/Dl>h1#VV~j9'Vpq7 6X6aQgE XK| bXX0Wa͔OB,~ɗ/x4XX?Zz쿚12f]2WG+/SEݛQ-PC=iQffe=:{Dޚ24K{.ψM[ "0SWqȣRhl 48PjY/MkAq(7'd2\-%b.2ʜLP8Cw^|yDI2cqu-b9Y~}2F5`蟣E= -2aZK9b 52`SL('N&CixK(7d`:*ՇLP6u漍hG{ʌb|5H=:8z*0y ks, K=6h2X3fefb¤=wτj3QS/H-e,Nĩx2X2e/Nzp̾`FOOL8o"t0mC1EwL}\cVL3>Q;/&cuk]_^FMµ戋Z_1/%-d٩ɫeW8)="̓Y( uubzu7 qcAzr(k]K8!Ty߫W4@Ltf#:f_H9`Νw!Œi^lL֩A>f.13'B`[K~ϭa}Mox F`|/ zчVw:]k?K Dop=wsߌ5e ꤛi[֋c"/M9mqDiF7#m@ Κe z3=3=m4SZP Gt/ '-֖oq`F1=sF-u=oҳtI P'̮YĚ8tO\b"mxO(cf'2Pœ`D@'ӢE9Y3odzf4[Amܓ>c͗ 15G牯ب| YsSe PCɓ^>IOb6>knݷMW{Kω(ӫ'&Cfkgzc/ڵfE\]s=:a-QkXxXϠkCgCO3̑ m{DtS\G5=f]{uh@s5׮FQDf]k^@Y&h[u@D3cZ@jx\/>s}@/Zbtckk򣈦 '@\F9f>, uxMbQtL?LLĜ r#m66.3 -ߋ#准uA_y`m&K͊ua]D^k s~ 7d}6eUͰbteP#C~.^w1{CbIûW9zpO?Gwg@~PA\'W8j }T3z-KC@=?GGAfB㭲}8X-AnΑP3|72g3>fB5t͡'mHg8 MGwDޞߝb}*}_MMd=47^7;~j~lIno{ق?415Ι9jtE {5۠4ȟZ U?O QWI3A=й0k=k!sϺ55#e/SQwiZfxs)ZV}dT#s:̖4ĬUѴh{c[QF+7<`_jrsV5ѸDz.E =^+ }m-j15{^ sOD쵽3ʬsC^%Fm)'zzD^%LE^Wځj5ϒτ h i7Pz|܄ggQa9Zo2e^3w 87;xDlu4 c Zg>Xs|kR= =dk|Ss 5;XWMڟ'jz›_5\%vﻪU{Z~kV$jMuʳVצޗku̦356Yհ t^nV|j=;Y9\/Tk5Y5+{oڞ {ߵ_kr~k!4j3_j UI=Sλg`n-뎫{PsMT^~z^wUk@]uOzf4fG "k4<IENDB`openMSX-RELEASE_0_12_0/share/skins/Vera.ttf.gz000066400000000000000000001163551257557151200207360ustar00rootroot00000000000000άGVera.ttf xUE8w>ݗN’B * KdIBQAPD DT `8020@rxuM̼{饺N>BDb>2%m}Ws;dԉG&oSgjB^I7u\͓YD6Bʊg IU(M3 _$ZrI\}oo$7m܅ڇ%d!(,}fypҩy,Ăy )^ϜYwhjޏb^~BWA]:QM|)k5wŴ=dAQñAGʆw gАAwp4jsT۝{ĨA}G=;zD~PAV?b]GuB {Ԉ=n :#2gШF=ԹkF=ja>+ wHAC;#[|i%/pWaG=2A8hD0'(1;|o2?6 }30.gZP;hd#( 1y|rJ}׿p$G7yQ/ZX6˷o{$*T퀒U w͆MSi_hZuq U AVpc+%8*ٿj:3o&hAA7e%PdAy\P)yZ^g}&Vnǿ ,V™:ArnE|]GMݯK纋xiyqisuOIY`A| @Eb2%nҎL%INf*I|&} \RG9)$ydl z&#Z_]e,M`<8 p6\m.Pw)_&΀!{Y%\L)+'d ~b p_4V_14XjaKSǡ: nl,_UZ(Zµŏb_(s.b*ŶfANoxn/3{*h;Ce  e|sSA+X_ӧf"x~[[rK E V2<ij\|:: 8wgB,@9a9NYֶGoM/pB~y] (wѡ)PS97Uor^lZ$zmRڣHYS P}(Gi2- mtoNc*VyK/^MŶ [F(_<uD+V*V#̆s}#▼RvG=жV2H <5{uF9JQ+yEoj^wP?tY҇Y8 KpL5OySr}[ [lԒ*@l9fH1=K+oKP2ovU>uHZvnǝpL,WzO"^nןR"qs)[({Uc`cZ}{ oycn4rDZ_y|ZrHپ]^KڵD[{#fj{7~Wp,Di}YNzl}[G%>?EuGT9/R+qke(3浏!Z߫|X볏EPk m 'ok8EƂ9 47v#R!z~=GX85'.o#C ۍ!0Oas=Rw_>^/.N Rp r/ [-1 F@}W{C݃>?ւFf^g_h⩣;Dz>{}׽}x9Ũ/|B<](Ă4ʗލ^̆<Z:hŃLK#Cύ)7~~ٹ k"Gñ>xSsHKm1-FcOʝ;IRC0Hc?%+}}{L iCݾG{>ꍴo텗}or}[x=Tc?ꍼB8~0F|-J_8_t.۷r#[m_M}%oIzm}mtn[¼nך^:jɊgnވW{Jm=ٽ>aE#-ZfSؖ6v=+q{]^?3=Z,32V\7ϗ/m\~,ߗFrwoUe'_-Vp YqUxmEKѿ󯹀Mwզ_' I F__E9R-%(]-terr"GYH`|) EAY4f)Ha>q_Ad'1ҋpd#4(~ d?tvH'ZVCVD(YGvJ%*R) 'd+Ovz;HWI& vzu\%+(. P'P~+t.4G:J2G~9@V8^ЋFRMNl>$Ɗ/:/EtUڅZC~(P#hs0zTD@AzЇj_ ((5(Kׄ ޗ:td: +gJW6[:p|$R$^Z_XyCgصxkfv+:g<}3~y.mf.^g?_gtSwcξOcߝ)}GsM]gMa}nfgty)Ig\NK:=gajRgLv=cb۞ڤCV{ٖfi6&(i3{bEzha_gKt~$i}=[L\Xn[glNZn>қ~(`*HXUVVƳAx.v\gtV3.ҥlI*b-B [`b l^gYu6:+Yflf4>DgӖbQBluםM&$ݫ :? il>l6id0Y ٰ6iΆ WA6i 0KllݥYͬ YznYztHXl{lM+afunR,M `]3RW0.,:4u6jf)F֩&u'K XrCJgI.1^jכ%Ƴx`eFXXY43,:.D3  ,: BG΂ XP*HgP(0t33;އٖ2kl :3AnS 3`cTȦL `rbt&o#6FtFӂ!.MY۶GTs8G;vۦ=E7oä{=,^#~{q=j?$Cz~Z"}X$$($coU*A$)O]Ll.GE"i`aM˯(^_uЈcU q*e` iYYIc=QwC(@3Fړ]t;e4ѣϹt]:V0n .mJ$(tӛҏ^!?Qa;}v}`en5O7fi}}q97)IAI%-ot''XMwz48qӣ 6tIטehs{Kt╋KW/qhΧ]>)K^BJ㇏t|i  fXiOȑB-$ڦǧXgk҉S=u럩>XߴecM߱Aa>i+6UC_ yx'\?eWoCq9Ii$~lJn\i0:vm%\.@@]AVj$22%DG N K`wU5i+<è1"Y;En(o n) k-NC'E#DK$Hح0*aζ&SmzyE_aKyo[nMS3xJK4J'y*F)FK '4J2&dN8'tacB脨ői>%lQ776nycfLݔhj,i!S=< HiHv&sк89i!w OC'hMKn*OIG7tIڦlSw[[MӪeM"(9L鲠D !|HMO 4Ŏѧ{q^IzMAM릝_wӕ+S nWq!#kuu~ceNYܑ7S:K/BKѪd͉⭦D3Z)1{\iT&% k*e1$b1+9d4XlgΧ]sv\Y j9+ntYp8b.Lr+޼-{?=~E((Ev 4ҧn ;J3% ar-)tSin9GUt0(]: } J+I}nS$Dd]v l1Gp*>@F O||ad ?:Bo:Dm1d s lys}#W(eZV$vWNh@kNXW Y!꽢u~u7{Qj;zH(#e) ݂Z+Y\ϮyN\L\ զ Z5‚+|0V'v#= !1m-@aͶggA57nh8vzR_WG">{-!荁qaբP--WHFxQ#갗 iWS BCÈCޡʬ0)3.eħst0OtpbO7C˃ObHh %Ic{:`Hba19 giCdɫ3(@#pXGH-(U! (1di=eq {R_| 'F\뉠ff&fTa\& UT6h&4繚jqWm(Gu$I2HƓDlP$H~ӹsL$2, i0u ͨ{%ZK>9mތKij3j82甉yT/تMc67h!p* d<*M[pOģQ7R36-EOC`ĺBN v>cpPmC>:iwƷT/Uo/\6is?Aa/c'~U\>}!ei+LKv .OLb̂d0Z(n`Q/8,TʒJl#+ FK_f5$՛ӥc/o,z⃁rudjdžȵϧ]qV ?ktX*N;_D:i;;HHo\'k AӟDCX֯6?sl ‘Vm}z媭c{~c>\?~C7+G(1JT]IB /du`TmCx%<<Ibb(0ߪ}//C^{E!pR8@b2QNJҽR@|;d 'FAmy]Mn6'KQ %Lc&R@4;{#-~I} BюpE^!VU pspqI:Zp_Kk$C. /DH!P76n&nםj{9K%,}d'YDF*/JDRI(K H |1?Z2|n .An*uI` 7 N/+ fpm"tA$ZTߗ빮^\s =m6&m6m,,{*/ӗYN[P=TݙA9آ(o)/)oYFh{@"Hd}\&HV:m 5khP18KB3耖棌˸tUr $6qU hņno c.'1i30#v0!+}M~x'KITeY醭1CV~M#=ZdI 61mW+bn}5uWո]}w %r]hv sEu'ԇ#PAS7 8[hŸOXE K3LFP>j})(#ILNE># JNƁ^ [Rŋ Oş^)c/xTu3U*,6mR'-[鈷Ow({~ck_ة:G]{Ȋfwׯ8bMoO; ӧ{Oi^K頷ߤVR9m\` 䠧7n̉=Wj70 pgeٙAQ6qGJ Tj<8Sm-_+{&n%|RFI 3M !:N5iB:_XBu}XXfzRxmh+/ zwB7iꮷUrI`A/BUonņMmc9dU%KxI#1+ >{:βRZcwy$>kiyymĆ OFA%^/hQhl>jڇ"Wi{msO~gˊD!'ΰN%u" P%LRlE!RYâ@%>_k#PJ,ၛDLmCy8IݛTٱ={c/o&\o)LuӍkzxu-1 u2"yKuI APܬaF&F(=dIR,?koń௥ǜkb^gaVlo'欜 Mnqf|}cO;b N#5 YrljYWܕ٫2(hjNjf ].]:Z( : iR0H`Nj/'+]ŮRW9CI5f=ǔϦXY ְ5c:ei+{F~FyaG/OWa2iІFsmN0_mN^-,htNJ"  g݉#]޻8-UUe# _4>ra"z0* *bfH>E`*[55S$pncmNu>nAoq!ho} SU .!@qŭ$܆.JDX"T* ˄ @Xځ%.4Qktmtl}\ v#]Jh#zQtIe^o/E5_'c騅y";]J2UpHMk )m,(<]n@.D(R L5ECt< iH~>"o[F ڎ̦6aartF7pe&XY赉q1DB͔9C~ NGxTV׿ӿӁ4с;{|Cwf^$>6I{qTAV)RWyEq>Xqw{>ݨ?uz7 c)W|s_k˜>JDdMRdf5҄ Ҙ#aVtC@~q`;dj<6sDYm%p rXzg_`$D6}tp'|]wE=ooԁ4ͨIa }#PXq^_~˲ʋ嵇Vm4_%vl¨_7)xK;ʇ7ø+&2cGRm7q% PI(b4VдIwDit8KOKxk/h$hNHMYP v4bغ8(:';4{}0&ӿ<ɓٚ4AUL$3֞uGa"M4H'gA\ KfwwIR)2QUz9@Cd@$1Ij7NӅl1[ʔ0H$%'c"D,0IZ$.'9 FL4 ? bc7G{dTMFzWbWvg2$h! $KbH5 J} xa0C*6L1T UJZf,l" 2R)T4XBKt!&-FKn٭X-ot[ܖl;ӥTe1Zt~ oգz~&c'7Zb'KS)@+0*l8WZ$/ReBSʲRXWKiY;,-rKYĹ~@ag}Z:p!^x }STف]*ȿ46 b}ࢊL gۃnQog[Bc 5Ș&Ԯƻqҽj0C] ,0 5C'u1 *HC lNHb R;%QK2{]=r͘c 73g.$t:n\f2ΏRʰи,Gud=}"oR7ww=ᜡ˿tޗ[ _\^M ИM1@O O929᎜,(G]hKYm"ْ dB*H#x$zptZ(<(ll4Ehˤݘvv!WnO|(tbP 7p<d+ ϧ=awfC?$ }D":ooiِ%z r%XIT5Ub d͘b`Yj.6XnƫED-5.` *cI+MMFOX:FiVq89HU%v`2vXr.r;mC_|Yv]kZwuТʀ~Yǎu~g]{Ѐ.x=6xH_Qٸ`w-۝a΀sP9׿Ee+p2\aTRlg!@)ը6 '}Б1@k}(xk nIu:ޛZ.=G^Y?*^5r{<@ZJCE#t>xu+A!ObH1H]Aݽ2`A&Ă͚jbj@ױS` >w=NHLJuF$z"<(ɍȍ̍uF&%>*rU*b'V'^N9%j{JtYdYT,zY䲨eem%Ps-wᝯ_]^ԁs=Txi˔7G3ៗ/% i^^SsoZөSMbbv$ aLV@kuؖpl蹧]y~KoN\YO]*@r5K??Z屛7U!?CQCy/297ϱ!$FV҇EJÆv@~t %>y - [V&6q/ v=̰/Lj#G?'fھO>9׾}M\tB{,P '6/BKI]kO]vjZ<0.x l{?=זyךl/ co ]| *]>^U@Fă3e6#NQ-JX1)!k6&T56|u2{)2o \2[]=%GmвOǂ-`J >`O <' IR{u3BX = S7 -G#3(XibZǣ:e+ۢ԰7#e1Gvy`;уM%\WVżE7-q|&^d/Ur>iS"`Đ ١[L"lءBVUs57<LQ,.z`gga\.gj\n\Yj{7븛qȤu[lQWDw[x@ΞE_a_> }y88Ix8I[ˡ;o3pq Pqi?:0m+=9!RR*T+MU*KVetT\ߺ~eOʦ2u.]?46G ?m?/zMC᧎q/׹ɥR5öFrv g4A**IZ)Z Z" bl bZ XZ}QaEQbX% _)s1I"Wf pS'p-:;zh= ==LF7q81ٙJl.*ɝҰҸҴ; l¬BX( cbt_RUҲIIa8p)p)_ {yO~̢X['hS׮ݨQAіVo{36ܝoWq}^&HA?hT+E`~'&7o kS+؃;& %R>ܪUv뚫Wۺsa:ڋ\b8vʹ@V 5,ݮ6R+6A;)Tި~h*_\ƣPN!2uxouȐl'D汎wyTH{|) [Q|UwZ@aC-OOͦɋt""w);M-ozi;&~,-BA i׿Df3Ώlst vs@rtgo v&*K@]a]3Pe4 iv>E Q8S?E~X[VaQTLWT4!Tt =ΆXe0.+s w[ /]nyC!D 1$j!x-6wY,S0~y =|d Xi<^sͥ慴4}ByT?3miNkʄX  K~K^%MI毛ߧ`!PCy,'x\Zs*hvb'j[l7 a0UmנVhxemcZ[PZ OT 9a[9aCNu/'.DRe Af9֜adnfM0L72/3o4; paiA%Dd0B-D&-$o3ƙɖd۞ 6CHSnƮn,K5ޛxG0 >Z]AAV}#fb.pm4pm6hxxk %{вк<=d|ȴڼڲɸɴղպӸg7aB37_ÞXqQzʞW5=DƁW#.rTFH1,vFJ;o.JTAdT`WƮ,KM5rGUDz})tPĦS|ʸ̸vK|XHc~}tR#hX[Okp_SW#GJTqvOר@U 3Bf$dW^uPu$4TIrGxҦ%ul<)mZ 'R+Cw=tr4^ҵwƬuca!=ccm<{ySź[~9G@OJ_$zlf"Nq[s)X#m|9Tө3v\N'B>gd c{9 KTQlIhl>1&ޢtv$ Ufv>d;"+JgxW!|xWvn-W.\W0x@ ~{^+6WJf+j{E\92GN~u+< lz\W劭͕|Wì~&Y-WL+\ǼW|`N{Zd n-=+}\-W2\ oq-TΕ{[\y wE$fր=x LlDjrE|v7O~ӗR}}@ԍIOl뉫Cc?p&6i a ߺFP`>ɝ=;7wvp:Xj~4wYÇ-ܩcyڷj%> JO=b5>3hR{o9|,5. Dֵk&}Ctg`Ɲ;^=AWq?WIɬZJNU3! @X |Y #SUt?#{`{LD>KRIҥ חʒ}ƹL=U߇A-X2Ibb;u >wYTPEGy싟(V$G] Ώ]*x)d'cbIn"& =L>8v2iwt%rg @l1ۇNyu}%~w|B~cU&Z=bzĬdg!6DbĄsHT{ШAуbGMw އ֐! (ǿb[^;)ߞ<<(wm {N2-9<\/Q+ܓ?W q߽9zeXuoիekVC"'}BN]雓801kpߏMYLqdduy]͞",k%9OyI{o#_=+@{]QJ3v"%00((r1ɊNߘ5k6SɭTs=_!9 )C}}bJ#Z"}= =~qv9v䣸FSf^5HMv 9FjZ8HC!">zԺB[P;P>+O{znӠO~$w"࿖Gw?.**"KFy>7@zy â1 E5)iFTks݅# 4WN 6WBl8ng͜29Z( zz^?ASpd=5WxwZ;_;>+kOEܞ|! `I ?Wc*OTZ@/gNaWB&|ekؼ{OНyדy<;1LeL(duMw熅Gꋎm1ij ܈1 LL$dg6lSIj>aS` R}-3)`A3ʌ/Z[ZPt]ח4bE_\7 2$ dfZ-fbHqnm"&Vk6$Y DTe!ag [d_x^k7;g}m}!fzW][Ld4̏{:gO9k7іM:2PtD4U{ݓjniq2'%brBd))tC;$ܑ%"ɦD)!鶐 [\]3y%my'v}1OX256dttWatk~hms<N -!i[bM hzQ@: 4$o6z3&. zsbaS-\QͰ\в>W,k˟n)ۿӗ?Rv"V[w;v>cz Ң ]hW5:r#2$>*zޑI"_zNI4`YVip .mM).ե R+G.?~C;?4 V۞K+'oRMXURrUqN/\pપ_ydŷ/ܥe-+ +l9Q6w2.l4޹-[oq['q/Q_Z/+j/[_^(O %eFU)bUl/7H_5y{ %pS_K{iY -mjm،&NP@G!N }6t~o 7`RGsC h`gMڭȿlVլZYc5B'ddf3/LIVr9I! 7B .G^]*48U Axx7R)ќhIsdZlL;^4`[x'|dqES)zJ]p(6Y2Japs?POv}8 b2L:@~˝9m(~=ڻ"w&i">g7ߌY |% ^hȪ'6폍Ibm/Z 7<£MegMO>|ذ/|M{_H"Hh-LNt0t)HG\pԱ3Nm>M}>gA2ICjÁKQ%UCb>..u}?_~ 0>1f}+vꕯ__)KuT< H=A{LD )QL 3e[t`;| 4v=l+M0T26_'I 24Awj*u:H\&sş&F3 MN79Y|ohJ!,|!VY1[ pל緉/7~My|m7/5b %G=+*@e̟Ma!Ar ()1M??tiwƝ]99B-7po 4Q`fV7U`5ZMVbZ}f`vi 5lQCX;@g@{l;>(cY߾d>ĂWYbd;GFf_fk^`=d%QU-*Ymq),ee ' W$3Z$hzD5]PuCAuLD'F.dF@$,9<:膁 #ӻ9cI|Fxs# _(7^ ~H5ڇ/ӜhLSǞݻwoec}_޽kh~P (ʛRtҋ2OĎ=-feW|Jq <ʎKٰ?*dm|NJ3Õ c?xi>u6c>ߔ⒆xAvyjS;z{:x:z:yR_ڴtiүCi۟}ӿx' 'QFF]rE .ɚ)zωCyyOv QH0y"G=I6GP^!7O0=cOY;q|c]EURڬ2U*Cª«""ܕU1UUqU Ol9 |6:bBR,9b2p#7o{.62ø dpXͱzHRqLL.e3`i׮I;SY3E7InGmMJٝ =4Nׇ~>0NUW`VzRܡ !ooMε嘐@M$4F#4&<:;Z~#,6uqn{sŹ\y2?\qimDJƷ]uf+dN=}ŬaL~x7ƏKMP>0vwNJ-moMǷ&EU ^yk•ք'}I{(_kw;rQs:Z?1%}&7c 0 v?|/MRZLhDѷ0#M4*TvEs]!4 \:AV179G<HZMfRT쑑!}BLNha|}.+}|/b W"#2+q8vɷϖ#'}3]-ӗ*&_ g`D;Q}IBBn)!4J R͊Y+ ʉpK֝5?; |1:zB:: xr$)*+tq:gm9#IGk6.Rc~w"X_|͚U|k\2fP=D R!${G9DUHS#k%|ǣb -A b?U?zW=]#!G#HΏf_ͬG yѢ$AaT2([7i72\m7A\O/1cBocկWޥύϵzzTӏffЌ?U1ATU~% z#]^$ÞT*ԠRFʠ'M0wܡ'wA87LzCNҽ31Vuь6d6W$ mVH| #:FBHOA .Qf-a3d!NDbMN]!(F6JWw>\Mߥ/[[KhE__E>@[AOgE]j'Ӂ@g;v@\E&15+Q;;{;w4y5jyZ\KSs2(ؑY :JI*j&q$o]>çހ$o3;P NM(s׉ מ3u^{IxgP=k,M3ggW}ПD",+vDf~ݺsD$Mpu/i}r=yFR~bp4)[CQQ ;u̦Dt&!ft+Z_uklLxJL&Ԧn7!%uKq/W;B$1lDV淢^}Ajl;%.\l)m^6>᳣/f+7ʓ< _uh0W.r|)w7X|Q QN nU/7 z,$úc5gnO+>KӗmZV\B*剿E^4&w PKG OZS%4mm]OoiO;c@v*K;YhD( j1CBqCl/oM [E6n}>'wnj قm!bjXn22۲a<{o{Je>Zt=_;p?Nxk7,ɻzUh5Ud];d$bPJ ب2$w.{.{ȯ-3,3. :Kp`jY?-U޺@ D~zb0-`%f:V`%zx҂[bH]nNDsݩO|>0ֱ>^XA[[9at|AOlz>O"|ikF|x96 ˵Z&,m*ᚆ6VI@{V3J_1 =i9nR]i㑏{3NiYa#D%Ŀ۷/d׿ ǛR,g02G(Rb5BoVj!kLe:?WF_x-1;>hK{76p$Ld6eF℥Bѽ [~I2F?t9-Uh[/˯.~[0F%%,y ̿z,n1jWO*Hrm|c1S 7l.C֭_O(*pmY&|J>i_7"EjRo=vL^%[PdԽy}?&cL 1`)>«[x_mN9xk'yz3n񎊊 <<͋ 06Fy,l0Ss["xԒ3Tah Č@NFev]81Szdث$u& 5S؞+0zpBx첰O$ˆD&DkVrtjy׫`$ ic5Z ?-Wx WLagIr_б}fWw~+2zj}`;)vM>0imM҃2a&&* 7{D}p?F 8ܤt" o9:}|}\+>Ȼ'Ҭ\}E'~+n~RRٸ"; 1ah".jX}|SF/N6vbCecVCW>"48aM焭?%1ڽOd jwܷeE~#Lϖ2`|ZLj)FXQIO߈CEIH$YPmg$;N55fpU$|g*Rq۴]F3pͱbTn90DL3*Y"vaOsU c;ww 5ky] _j'r>F7%z>(b ¡u@,MLLVFLlD2]Ęؘ(wTէ]bB|L;JdXv L;ZZRBnyHlr8|+01Z&|'*CySa;oSrŤĦ%wL蘘ܮkqq|yyb0NHHLl52Ώ"UJX*}[ rT9*]B\˜ɉ|f 'Z%JAA6.uo_;p%y~4 y|6m_rh!NH3o^oԉ6FNf;<=\OG1؎ $uբκg]گϺݐ$!'L{n^{by4$ƶ}YY0M,|絬,;ka1'׮A2 Nqd+`QXTaw̟ 7)Ue@-k |9rݗ#N>cacp?1y \1=rIm3?4懫y)X>ˎ~B-2ŀZn@[ݦ̬6e- /~W@'7*F ̶-F!ī?MAه{ڜ ) pS6݅!=Pt[*tƥכ~g7EZ>48ckB }VʆIZZ̚]Np*v}pZV {mʹu>9iGۼCCV9w F: }4-;Y a]"N-ݻ虏B;G?SM?dJz1dGbݝ+?PECYeP;6%Rs>eaIҗӾ1SpfF;ўx'YQݧT᜺C;<-EѣGό 1P 0HևEʘh;l{7os[nYp *]a:-4+(ESH !|ySUWX`'|Wptʜ&>#mJ1-7 "[lƨPa/Acq -(S\czfX ţbzHYcZGp-,lJO ؀&j PCYJ';JAHLHd4Avd7Мh᧾4%ܻ+̽x |(,=J'~n,7'ȅ BbQ`x:HW޽+6n,Q6`6Mb[ !'K԰wǮ}_}Vڿ M_m{OO'͙ܲ/0ecȍxU7r{3H#h ͢65Vό(oZz'!J(%J2F0"w#ɽB yA"ovłݥg4#UR>Io>?Eި̲of2cQzKX•i-bu#rRnDgo^~k׬n *DZ"+MZ\7J_}^5||E(2%7<~:%YQ5dXmv3厎~Rr;vNK^w={ӷ_w 4!C ϽgQnj7~½'MoJɟZPXTn7>i'g~wK/yں{^7|k~|QG}gOO:/埿:s/_CD;q" %TvE;>t$ͣ qA8v Tww11rLdL1k$PC5N/"i*MGPv)PuB,,+X(Ka~'8o5}oNYt+BCߌz:O˜GȟE{WxFu{R7;>/U< d3=t!N%YA6(B&}l'5ڿs|RGT|D>&c)qr&&')2\ NJ 2$I5)%sH)'dK',&H%YJYRYFALt_On_59r|W %I"{#Y ߿?/"!((q~ O|,f:rDGKAFJ=r!۵"!=pW^B *8c3(׳Xr{!損*MZJͤBNo iF9~QȏH?@&B*ēoiU⹛Au*?~R% iM= >yU#@*Á9E2G8AR ,u+9rw ŒLV'z!c?8)a/7o7ﳿO"/~mi=_gU#8(?9IH |g\Cζ'GKI$Dm7#)r| 㲇eCHO(n^x"?8}up@@~b< $?:"#݁tv8އxЈy=÷ G@Y\6N~ i8n[|eynC@Y.\F|g; ^:AV1|}wezAPorrx 8kHﷰ7۷h[ x0{>( | q'`v+\#D&1$'ϟDI{ҁt$H I%aVNsԕd׃d|ԛ!}I?ҟ i D2 #I. #(2!c82L dL#SHڄE~&٠@s=?4= A/-㹆M+A?? Mk=T[qM}rmh6^%ςuz"y .Zk!MV[XzowȻ=>؅R!`ݼV3h܎X/},ؚ|Kv;=]ul%Ѧ,rZ7Vskv̋wDOG4jF)ZEiʦζL-=@?TڵKF]SvF0aW!Bޤo ]>c/KȒ|2DyKiR KZ6B Q{7LM%%*̓[lg=^fk^ \.돁1U}7xY!! B^CG>UaI3#WE-}>%)F I38flLA̜c<Bޘwc|}?cI96461KlaN}".qG/OH8xwķ9ۍkA}O';밮ks;Vwtѩ&eUjԷ;K;v,m`⴯Gҷequ]r͌ 3^]7v$o̟ngY³f͚5=kAփYg?HYt^ٽw={|F|3L)",a%%"\y#bO7"Ɋs6"Ћxz^p^a7#<Gx?Cx\ko & n 0%$px kbJ}߀#pW`JF^ a$B9)׉0x.5k!ҞnXO1ů6"=MV] `p* ZS; ~29@8,a9V \5/FXp ¥uw#VD_G0lDx@9“أSO#eHL~ @;4i(i oY k44 igr? 1 1SrMB:h-. ϻ#:h_Gt^[iDxBxySCJ`.bEERJm [#`lfyJCh;fy~PO'!91gP9#p&Yg#,òs#@b \~U!\purPcRp #4"?c؋f] F+Вî1O#Ŕ:1ǔ?GxU@ȽLW?v3_QjdȽLO8@ p !L2G'|,sŘNG8LFXpr!\Kޏ-V!\p9]7B' },pzkY0F8GzfB~{YgHi5IwLRwzTw O44avʼgex? 3B8a0a9 .FXp ¥Ƕ.C.lq:0 gvϹuj}+@.Q=`$xd0bZs#@Jc#^jZG=PJ{<'~ 04ϐM"gF/4lW}Aoqlmf Z矎p™g! kaE#DRcU!\plQ-Sp7!qȯl٨ѷF2}l{cO==Qzbm=R $9@8' p> A(K6@.?0a8Kt3D8 lesr!\KޏT!\p9ƒxg,bGpܪރ{G;Y?(>]x^@OG9 Q@~#c`La.{<|4q< M5hRAXBg/FXp ¥z.C!hhԫc#:{<῱&S8hr;h ǣD:ǣd.C9& '` 柀'N'{1nv/}#bJAX"V"\p)&,#HqM=az"b21LDL&'A3r?e&|Ac *-l>r>16| cl0R| \/%Gn>JK>16| c+v> V8ǹ^>qs|\/z8GGGGGG`>:cl0c`>16|hF!g 8kG?3cǘ[>sGc-9[2VPnrZ\`h r >+@qR3bb g8-h1^+h1@qS#g8-h1@q: 5 }iN~MþLþLC!',C0a9 V Qs.DMuBp!9\sEjEXjZ\s1ZcXj1ZcXj1RXKUbJ,U*T%RK,K-RK,K-RKR,K-RKR,K-RKR,K<1j쫑b1) ҈8?GŸ^?cًro z[&`J"BN =k5Hg g ҳYjr^lj5Hg ҳYAz =k@p B̹b \p5k:LoDxO wXO/`1"\ExS8=kE-Wi[YtEWZm_-ھZ}HZZ}hjբEWm_-ھZ}hjբEWEJ֢EWm_-ھZ}hjբEWm_-ھZ}hj(EW_m_-ھZ}hjբE"Gj#hjբE[Sm_-y-ڝZN-ھZCN!Suȩ:Tr9UCN!Suȩ:Tr9UCN!Suȩ:Tr9UCN!Suȩ:Tr9UCN!Suȩ:Tr9UCN!Suȩ:Tr9UCN!Suȩ:Tr9UCN!Suȩ:Tr9UCN!Suȩ:Tr9=9FNFa7zs؍nv{=9Fa7zs؍nv=}$z< Z'!LƔxa')j:w <Y#|߇ϷCd>qFs~>' 01,b 1",a%%"\BZn zħ?Gb_?E*@  "oyɃ883zlcd#٨F=F61Qzlcd#٨F=F61Qzlcd#٨F=F61Qzlcd#٨F=F61Qzlcd#٨F=F6GiF=F61Qzlcd#8#h M|$#w:y 4̟]<#$K`wG@>yz6p} {0}C|fu#x^p!\p1JK.Ez,[n zħ?Gbv1*ـmAj h 4-h@[ЀmAڂ h 4-h@[ЀmA h 4-h@[ЀmAڂ h 4-h@[ЀmAڂ h 4-h@[ЀmAڂ h Ps6-h@[ЀmAڂ h 4-h@[ЀmAڂ h 4-h@[ЀmAڂ h 4-hDN5"SȩFT#r9ՈjDN5"SȩFT#r9ՈjDN5"SȩFT#r9ՈjDN5"SȩFT#r9ՈjDN5"SȩFT#r9ՈjDN5"SȩFT#r9ՈjDN5"SȩFT#r9ՈjDN5"SȩFT#r9ՈjDN5"SȩFT#r9uqǑSjG}qjGp{tqjG=yqjG} lx[<-O`' lx[<-O`' lx[[[[[[[[[[[[[[[[[[[[<ߓ8>Io9I3I!DK} KR),u KR),u KRi,uKRi,uKR6pxpx99#Cm9C 59y9C AP N $lԐ  \~Tfr*@N(P~&`CE;6t2`18W#GhעY0Z e1C`dUj\ehȨa*0$e$b gf2f1|#&daRf}K+GL\2 <ɫ;ЃK 4Z VKRVMMxlVb@BfV I[/HFT@ڨrB0d2AY=$\=&/=CA&. fnL>zpld>4Ƒ6큜\c&Z\{u`0H胉 j`(VՆqz@Nȥ# $iCMd5 1 =! $\ NEmf@![>([Du4+ k6/l0Yx;%c ٹ*\Xydi.bd?y#0/nØ-\W.$@O Cfî@@MܘIC86c#|TfSMܜ$F!`#EZv3.]280Y  U G:4mSʉ ]C㗑-|;vT+&zX (p_,N1M 6z53p(@x f y[z8C9=TfrX僲ep+f;b ƹdYP|Ly?=L7Jt:aL86.&g~ڠI'k1N|ЄoPWrrzG#7Fj$CR[ >zZ$0nvZys/AZ h[m&lZ0vh6)8̬r⭘PlY6z 5O`LؠI;VdTP v !7f1[P7pI6@cXyf16p E`zoKl &: eq@O WFH N'5 . DtpXl&CFaZ d! T|Є:MDWO;@ <zٛ$HNస`|@hnrz8h5[aqzU_r `fm!Ll=@Bz@m ~:Z@h1-(GFG[u`U[0%u$e0:]Xke >X?fmA6 lt 4Cz jq=Ypr ;^ #pOވt'((tDtBn:e1i @8BMMQ@Et9/.#'1@L Ih.q;0l`ܭO1 'wۖ [`9T] "@Vzh0 ZZt7rB0DA~@w`dĠ&rnHFĶ2᝜ ᪏N+e SnvqzZ> e;m@[%` ۬{j-]bl6©dw҆6`gKaĊmß8 M fb5 X6 , 1NÈz:L=9 wx%7AkCzC|*bolYBO@9=l#ascm*! vރN/-(^|BCCAG@Z@^Bm j 3rz E R. "maU4S 1`Wy֫aq6jdukc Ӏٲvҁl.'0;A I-(t-zr8WF+FxyP0@hRܦ?@+=\9szA$jMBϝ=;@xݠ4#B GX꧇;[ a vJ e65 0sFzauh (pnzm pZ.WjApK`d% z4(9W &8ACq[pILtZ@lt@5`p!:|!76_9 vLJ,{.'.5openMSX-RELEASE_0_12_0/share/skins/VeraMono.ttf.gz000066400000000000000000000664621257557151200215720ustar00rootroot00000000000000ܪtIVeraMono.ttfy|E8Wuug=3&L!AI @!DD@"DEdQ# CYFtE]vTuO}~_Τ{|ꩪ窧*#p=&!y=?FhW,-W|V܉?Zw*=Bv5B\bټoϋG(7,TEGt9셇8PJ*< ?Y%/s$7fA>\x(UQ@x̭Q܄_؈sćUϾ 1z}^UMm+SsHa^u~}RClD#.x9$Y3w{U(" Fʳ %ʳuʳhgCɘ[fQ6@yV[fVl:bi UՎR H]*Ϳj ;"VAhgPVW] %UXGM٥3jisͨ,)G;"WW %B@CirfV14kz7QܣU02Uեm~^iY1Tg|Eq=⹵0-.)a+WJJkgV2@ΛUC3QZ< 9ZPTҾNRw2Kjᝳ1p z'O5Ka>rh*tA-Gy0Ɋ-r[:fVqcVq XZ+P]/qU( -rsTͥub\JE`x+1x&4 ceU' Vpsd8/r=h\Va#wp!9CYc=<1.h軋0+hcPGV!9c8F:rG@Xn༻sdCEQEPhhU)*7g -lTN𚕝[4!14(9 rd;/Qpwa19P(6?7h!Ԓ3* ]00wXT¬!9 GRGC ,I<@ e8rcg9sdi)vEqtwhJVv^ 4ep^VXǐQYrtUB)B0,'?0+/1 gp.}< .b)<cr 9 h@ fCsi9E :A;&'֑U;0p4Kr6 K~t@*[i<(p wiY0t^-$$#2e#W&0U#i_TkQaJF`Wp}@ k(I9RE6߁VT)x.T:S, j:9q^u9d_]^ $Q\+,ZaYr+ե5cW:>VS ),P7:fKU3g뗐0 y "B3P4&Ri:plHSjF$XE>\:@ZUJHrh,KQ Oc^r*}zojoZo8 i(w.KOkw$9 n3M]rҧZR1%hU z2+X5p?ŕ*j(2[Y Aa a *e%?VPB\9kҞ ƽe2(h]Un3X H)-a 3 gңQ( ,# [~k\3 O>\4j)eh?e4- cW tdRk|RVcNh iw\VQVWb\V?a>w9T4ሖL 7z7@1 Y2 C!^nK@ [u"HIZH3姵d2d^.dx2c;kڟ_kɂz0V¨qt7k_hVC6XLYحW3|~a5e1mKۍo XrX1H"\ n2q/nH{.5GSY w=[! W+Rᬫף(=VƱReӉlR  YOz7C;䲼u!l<)^n7?''.IvIen4d Sa,m-Be:-.,w;.՜co҈Le]$\dvY&Rd>R)gݴjm-_ WnYֲd9I`?q[5y˵gϵBWߢ%Wߢeo}mjaʗJ՝ZN(X\-5hi~'2 ǫ|Zh !pkt]:-c!+ňcϱt-^e'%tx: yNmB[X&-#9t} O y9ė]v a2qڎ_BUh1\!BC+mV [_DPC:H(ׁ/kP.n WCi& ]@5 ¶{|=r𾎓p~(Ʉ|zmH*^ k/8a 3ln •` APGp-nZj ׹%ocS6 %Ǿ<}݊pg3ٳa? Ynl@MpסdkYyJ F=,yNXBgD$t,"!䷃Ek/( µFrm  g5O $DL66M"m-7znFD~C"~,|+o<|%$_~(|y|H.m KD>OL"O6?>GktG~E~\GKߴ9MKJ9E8B'Ir-IXDޕ;y["7V[yS"G䈞~pX"o"q?t-Bynr@"o -MYk?W}y-!/+yI"/JDvK/VK"4 'F\a!'yG6<#O [KOSflfdD6'⅍y"4CȆ $8Ǘ=B%D7id4eڵd4B@c i5Z"$R"+[Yn!2,'L%J<^H}6n7af^쥎N F@\DTmmW=o'^v|kW, Kj+'""~ЬT%""OZzj ^ +}|V]\vbzɩxbe$EQ?cqB8t@EFbsE7DP00Tl9A#w]s`G77$'Bֲ6yһ{O vIC٤ CU{C`ߗ x؆lE'L l93fFx+pRƻHhTM܂6 *D" !Wr.3j"k">tR|ٲ[{Y^+pk;ʍ媰NxmoK*7˩\i> 6i/mouMwp#75tPM,'ظkFġ@D+#џecDxOĂrvH;]X| eH{j=Lڔ jpcOI+qޚq K+Odѽ|=u=[gTD(DT߰ j7;!Cvч]`r%N^ym CHi/ Nk7 gx1|Tp`$X V1TK8M/ܲ;_$HkޏqP%K_ -Z /fC~9\l#lׄ#i_1J`z gP 6)+m_]KhOs$]ۍګ5RR2_#1pn~(PHBUZFGJg `PP<5*b ORDnY* HgUր2ƝcxP  s(.IHm i8?IK)hzMi*r[Zr[;k:;i17.q\[EJxD O*BH!ys4u45< # lh*A/ q;((0;4Hx1Є} ;4obwQ~x #T%jUIW߃Uxinࢊ-LM j}x`A *Nzdցω8~^i 'Mr(gM]40AIVKo}ȶӉמ O?]1i>˖N&& Nt\\\4hޢ{Kv|l=EuI0Q'(׿^57k6A>z$&Qк-D Cєe7-Omt gIF5xkFG x Wc Ƣ<~bfj6ȤQd}*̜h0ک>+;9bp,>2,N w䉛hZ H]7p<ƍBdǡCCJhYM =褁:N(vTsGSf讎D <%kP/R22TVl\܆#UҎͧhywu3}+( 8? jOaaz,Q•ΒˆcW/}e{pw#o.oZ8rnURΌʕ@.؝x*C{3L^n^*m4\&xdrexH2KVOXp%cw:2(O@"+ 7PFOrTHD#(dh?3w7/VR-(cӫMf*FFC0{1}q銈t}3*&5^i7/lO!ڭ7"+'ڭQbrJ8B>H"BLIѬN)yS M!DӸ8xM wP^1r n$r*(RcT~$ &qxqjz,PـA7 * 8ȍ*n}1{5\h͇Кf?ـa$ L@K,SႢ]|O{b ᴘZG[Њ"QA^vZՀMZ :@I:Z3HhRWK@aqj0@-s J6J?f_?+P6+P,|ʂ^$ÛMO)6L{0U_8WiDXsqx&%Rfo#뱛HR.H߻ÿw#hfP^Gl"~LP>ML0#9{- 0t{&WnڸrM+q_p.~"}qt?WxjQl fDj֪9 ɆIܑ(1ܲFcNyBBǙǖ/_O/c'T뤟X{-Lu0LYz Bͭ ̴.nM\*H} 6 Yzi%䗞>Cf{hBDp<yGxU:^kD%b$dޒ 'lΒaQ( r#:5I"ʈYŢܯ>;j7OװL_/[oifJj{ 7C+V$H'@3a0Siv{XVf+4[+9ٽ1*LPDp1WEt)mqthUHӌ8Le0x, vV0,2>@%W'>iGvTfmUn_aåm\I{qqYp}[Hh{6G EO|x |wJ~s1Z% 03N7Z*)̚wNR`cLKKsETNMfOM8MCw.587<ѮNY_ٿ_4V'"E9Efrv6>4\HIl6?x n*n4gi]B  ,l.ї!5Z N Ty% jsN؍Z`j"gP"(KT(AYlz5(PSiy}cW;(l7q(uƭЧrH*OK"<OcI$Uzfn~zkӹ`pZR1#.vounwKGN<t{ o?|=o x9̤=#5n0?^I4i7ӂ0't:!x`AQM/&Z*G\nlE0ߊ[po:Gh_HEx^jڤNbn]*u\ g$ *LPyb r,eKKJuZR:ۿu,"6:6ZMV5RӶ))'LOx.MDt)b @qOZVS^$ rx-GC^U#PCs/9~]a.lQk윑~"E_,a_|ۏ^ Fvftigf%˒gƋM7ݺ8ơ[Vq'X@qP3筙ǯ f4PB+SA=p}IiN~5"-nh2?͌<'X.̽+݋ RǨ#,^E-G\ie] 0ǣv,(+5 R&y4zw=-ܣ)X4iykI;E|ne*@3TC] P5Z #[7phԊ!^j,{&0~UZ2cKh=ёL ߊVV6[NKz*UƗ815U L 1a;0;t޹yKw涃oOYXJKoBKa@KCP'(@!&MF́jŠвdyوHTn'̒Ζ""p$V-_+*vs|wdc|K '^8'܂ Kt|ܘ5c0!?~^AT8hEI _Zu6sS'75@^z>?=[h>53֫m6oi96c͊7U25h'[)huS#yv(TZ"]1Ͽ"nk挦r)Ei8oUpOd>,8RC >0Ff:V@$ꇉVs8LV̗/|ZB2n>\@[RSĕKWmZxe˕ovubhֹZ3~xǟHۥMaV~c'C @ ooMXD# LJyȦ6Yd2lqg߻%ig_w7}/+Eߎf|Ii{U%ryâFjUF˲OpPeӈS\{~++q%H67.Xbz kx?17Ǭ:5LhDGk2dYeQW\x?KO)KnxܖE⟿le&K646l*5$@ .o͊EEqے[D%;%UT,9ڟ ԩ\!M=+ ju P!P RHOPG\qj)rU=Y ԋ[0mW_Hð4}aѷ}!>+Me/2[Z~q_< i?-(Le^dR4I>ϯJKxn9j0iOdդU(4XTp[fԣ|(S~HjvY8p*[*7`3,ODU:QMeg22323C2C3Ö%B^Y]ko5465746>d708Lx:Kc"OH'^"4a6bòC5ࣼoрF >l*mF\%f3n!SFuv$p^c-ڈk6f왇_:G?99q/>1#GA[8rNM1aIg_KNh2Zx f`'(AAzVp0(VcOV漨x oN;]T$j;NqӚ E 27j[z:Gg^^u[CZ{_Oz;N:olY\%ha? RO4Pm ND"MZ:[]yrTl~xo$SԸ&j87yx*9s#R[E-WFK:-D5dcM6غs޺1ꝍ9k74Ύ8 "!| !ZVԱ .M3T)ufn~zfn+'e|0[S_X~@S3i9j, Z6q \0K[LgOn;_畽{d\Y&ګyIz3d ?9LlzPkm-?:-VcfkU\U`i}Z5Pùy?U- E:q$?-M)Hs㴥{,ZVOf4a4z*YlV m&3?h0ٌFCǀF1-dV`ښk񡊸VFSxϖ2s2Z>o̩-jk>a0Zr Zj[Uʈc0Ci;A6#<ɏU5y4ÎYuG_o?FҖ+t{B jG-89ZJQdGKH4WCFǑR2[]GWk"qeewaq*ƽP'5ݚ _Np8 נm;~}^u5Z >/rԚwks:ӈDQ.IJSd]OvQ\A~7ƻ'UVM#]WsmS/o'kX9C?WVw"5;廫ݳ=vwޤxAP`v;W!A~&(7Ќtؤ k6w4N8BBCN*A` F&02$:nred[C7|#H מ%vJ|6>7K KK„)=T^A2=gfNx:cGLzǨn1)1;n&,,c P311wY[wG[;b[t;VZ->fc>cGLz/Le ċ=bb*a|[SZOIP]6Yi4/JїGqpAƳ6ԑ0nj(9<ӚLц1bu>XYery"WʪK8b徖士*F;O1)>:O[ҪQM:(;X-!U 4!@-o4U6=k0kN8qڔuVd@-Q݌ĭG>(t,݃\P/v|fCںO˷ovÉw3n\}]ǐi*.shǡV~_7Х f}4:UBY$ηz}A+}~^G,2hq lOi{O/eƾr&)a.|t=́ JFp HYzqpSagl ϟI`t,t2Rxn_qFƸ#3/ʌN'`sk7N3^`SFwkdԫb6)Lvi(^C w=QvŀL4 (4p/4 GF XcRT:1OgP-+[h\lRSR켜71!orz_EW2Yw,+ыca[.'vߓ3w/z=ٽBglԷ΀\ U"gDqtFgvkӉa~i{qC5G|؆oe/IbВ_rXIty@|o[POFgc^cu8#5W(qyHiW˦a_t,]u ;"HGmޙ>_s9+$kɓmﭯ>{(I=e{aw gDd壗5zYÍEY!yMn'd4~FMj5zhYdFRG =;kS&'[&kɤa*u|kBB7fgƝ,qw:,$Wnu{]m6Wds)]Ӆb(>yskIˍ_~Msƥ?􏲲#F Yĉ'MɃ>'~WG5jdޫ_7r|aS^,-}/GZT8?Olt.d_P0px,}6.ǔYu1{0y̑iխl|>+[o907$%Z4N?s;$̼d=_|,O ƝEQ5uŨ!dg6,i:4t7GLh.RdU*eE;Yb̕񡞻V۷z-;.LB7td))s[7ѵۭS wm&O}0ld9.b3'0@Z(@d/5[:P#uҁӅ@ͪVDJ~.7]6:mq`(fz nQm4nɘ-4]Ō*Uey٠`"^-r3Zcʻ\r*ջW&dNx (GF~"Nå_J[K['qjK<^,-vӽ= i(jo ;|wDi26Glh:\޾6B uY1ٖfIv2Ϲ΃sdCQ3ݟ፤<"Llex_o=T\n5aˊ6j;._k>LFQo`̽/5/d4=rU\)]wKI+ Jog]wڞŧha:Ȟ (+@Gtݤ2.R %a֒މ̥G@R2;ݤԝu ݈?caf; <~1KtOi*fUɊsծ|vn-tsK=0?~ 7F=ԇ{4_T[ ʉ8H͇QQkf˾F9e|:ݐm< ƕtߵ_/Kڇv\z3(/1_U[բGQ.Rpt<-kԃy;Ȗo%]]-,]O'~Mt VQ*u+[٨;/zw"S7vW73y qh/>nٜJ0뿵jm·~*V3BD~N,5h-h)SFh oKX'5ݜ0"AJzo뤪Sҽit߲\ǙfWUUN_j+ R?Ah'"  ivYk".yd`L֔\!΢1i9ֲjNMhHĐA#'L^+kwִoWkx?ztA~^ASv淼MC0y(ҩ #D3ׂ mo}/¬zϟt1~W6a@;M3*ű h%#LHK8uq[Cq+֌-XU{veAӥoЏ}I_.3 1vlKy@ Љ}:AAG  =:N`6έ6"vI88O{!lpqZɤ~e@m*d 4yt5P*;&+g-;Og6w<.婶:Ui)7?ё-S}orߣ|Qϲ$>35Ude~$9CVcպ 5IXkRѾ&>L/7v'->O4,Lz9u/ 2yDh ]Gx-m]6jTOZEI)%L.z' J<3t<7ZGL4itun CEYi.߬1Y}3CC,E`I| :`F%4:QDQ@?"GAoq~%ˏ #Ȧ!Ql;[[vONf'3(ezj4eba4{0wNFc+@G7|YSSҲ¥?.e!+3[vwtC=jBJyY٫j6'CBvwٞȫ‹n`nhG~CHIG4Msls{/eM. *)+ɿ:y/- \հC/]:xi 6̯!;q%j$73‚{ 6UU*a``nvZؒ]ILIMbJIF_r F 6?/ĎΪJI V۴ vC8WIJ.-߽;~ H|Zn&'6_ FM܁Dj6X akՍ iGLSC8kgt| bj`Tj*(v yߣILMLKLOO]~ łӈsp檌+ُ-=L5n\qK4qG-Ͼ{nԾ4O&=vi‹P7i^ɘ&+GRRvGW/X Az3V~H=kZP7 _gah :16&dO#ŒmP]eu 1[mbGETX9EeSht٭S6'u2wMHB?8X;lcR{F~~<+kCac#okU DBq=3<A}AJ@vR>Ż\~JMܷm?8k=yrL|Qv~mx@!vUG,>)>?E?\.6w+d|C" (R4ZVzT l_Iˆ{/n\NiVuA'ƺTEt=ç_Ɗ:z Fn"%)>X$.]\4-Y!ǂf\h'_m  UPv`۠u6́z* i}p(&M!^aAzEVQk{9hw5Q9GtRQMntjJ}*-$eI%ڱORۏ%~C҉ҵo۝OX)I+ǀHGRb I VvlM7)16exz܎rz]XM-΋#tu3fFR-A̐ɱrlx@ϋ.m=h^Y/JOK0o^ZD=r[pB9<=voZ#rAwj^"A챀))rYN$=})4]?vyGkx+4oWFN/hVu>mpyW,L^~B;wzMl o Ǝ%(+,5f+Xш,)zc׮7&,Ip?4Guͺ~翰K~,zԔ1уd-(t+ʠAYT :\=;ۏ:xޖOHHנ)MBJP9{B# (NKY yU=`HAz|P;[UuɖMӥT靍;gCg])])9!ϊUFb3i;fSPP6A}b>]pPv+8)z*2E6LͮnIELE.VTL%h_W)ZID5eFaz(Ɓ+'2Q\@+?G͌= ?m%Nw:Vr‰:jPQP#{) 87KZ/ 3 }8{Oe%Cg@;;r=t# h'l3F‚5qN?O47`zw$mKGrlV3h "Zo֋ vv,]qKUHks,CZUj.ZRM@;B;8-qP?[6q‰=kwLّ4%1yR܎ѳ#eJ|wW! yߎvs{ku24;%l% PfGg 2_oǍrS`޾jܯym7k2@ o@"MQGYr^nc l9Qj [ؑ,<=Y2ߌQr񮙥7[p't~(2$iDo1\DJD#L9C?]9JIꂇѵOLOo.ҧ2$ 5zUR?@eRP&j=F¹  4c7sߍY7<wsP_B ߧ-/0o)D%zkA p%Y>;9C0Ķߟz;}#ۇߓ3d9yͯߞWS !;ر~ 6}sĥ㧧uV<#g?C;ȹb|#|Xq0@\'&թNR|/#=wC<(v49[GL~#{ib)uN7)S-Fuv_?lSpL}tCSA+s#zqI7"{EM38^5v, m=<؎]µ mMA[:aB]Iy a_ot'Mw4|SyJ7bD^# q FHhE÷ᚧ^{4nIäq{MjZ0Kh@ L {X#z7*h۵..o-|kj^87/\ٺf7ETGanW4pHG4fd~Xz58Bm|C}S7.6|dB'LYLa.WxQj'53S9~ϡwN )E/ζ՜_W'FHm dm EgԪZ/;dFavz83LD(A*5VeCD_ނDAt C޹lwYL"@:0_( FR:[\#?~+'nV'*v΄ayޒaytσ8*m3I@OL0蔼y Oӥen-w;vjdJ=8mZz@wV15'$MboNEmfvlMS1A`Y̬yPtv" ү!ǹʧgJ1wdr_Qgc`4y ݮol~?_fEa^FVO@bBH+j!+ʑmV>4{je5DDD )6ts7n|29GH߅|UV~p^ӻ}@ (;yzNli=T9m-2uȣOkG~ϫيu-XѬUsV=M}}=z\rɧW{ZEV'=HoO=SRI *K'ygǿl2Oo|zVZ׹h FuPq0>F~Ww--ǻU':v[7F8{4!;UrzhѿPsF zyE'X#r̔X]PRq:u0ހJjT|O Oc7;I4ߍ綀q iQFG8W<`-p* lЩqd*f25" v.}]SIkɆZS=>h/6/`=M0, T!#MP$h(z13@@V6Xd\}cK3T!+w&ݛU󉱺 kQH9(C5d o ?bO"" 6DhXa^L쾚hr 9kow /hө3ܯνqUVd2}ڸysO[ ?࠻ț9ofboư)0c"ݍ$9E-cS ?D⧛-Ko%FP v ȭC“dƥWei;< F> `-1gF":iDV;Ѭ4kb< G9, {4"jp {BC u]$@p7eZmZ;` d?/|]li9rg2~23rS|Aym)"[+!Q.+^m3'%*2):.L|L6c 4|ɑ>avׇNV@W1Gglz D+V*)ÃЊ+=%"dtWDlT~DLB#1.O^\?/&aЇ0ޑD`}foU:;xЙQ؁C*q&kn.6uLvc /}^Ew6:-||)'}"'缊[i=۸{UD k9-g9;!=4@;NכkүL7w(MGˆ6`uz4aߢ+Γ! A¼Rrlҙ&j&-)@p.Tj3Mvb}n8Mę7 rAެrs]zXB) ` E350)eG-ʊN.9N,fЧD%̀zz WcN=iH{h q.h &WkS< ѧUV- Ur>ty:NBx:RbyuHƹn~ۣr"59#N .. ֟tf&4ū_j5yG9/{Jנ`+?̦- \?0x@?g&ܔE: aCzky0I<:^SC˧P(M9JNnڇExt[KT8PK3d`̀{A-kģu3h2La;N/)Sl{G~MH;Y}Sv>fa_'Dpn\r<.KX;*FS bpW/wDdTtظ$f?`LOV!9C 12oT肻 =v 'M2ihҲgϙ[QY5ں/\dC|Unh\u Olܴy˓O=mnv>/_x=/}e߫_kyCo>rͷwO?_ hi? D7A.`;Nx . Q"yuD8bxXxLRJ؊Ep"… / G,`yp; 7o~p}֛S>欛TE?E _,b4{?O%aGWiBcו.~Ǭ8[ ؍EB|V=GۡЇhZO yޏ#z B~F ^D *AP)zDgiz%4 C9tBߣ6Fh@sQ%ڊнhF5բ|ZGh!Z@-zg Z~@PDoe!}Og}Wtk~WzV*q}vMp߈C 0(=[}Cg9t{q<3ape@w5w5Uw^r\&%ep-Ӽ*\p2-ByJ:%mR' Kk ?kU) F.Or<) 1*ױn{:W\[X)lƕ(Q|u SV6W`F ۭױ[mw7xτ_ږC]S%n+Zyۭ=4)cר%_s:XJ|:W@ N;c7_tQO@{5T{3)4gC=ށ%>[|]h~o7̶w|86pc@С3/ ~89x{OuO7C!!!)!Q!CfԆ, Y9dgȫ!o ,PЂн7v}la z(gxWuג^p׹X|+jDTYTs_SY+{E̜%1żX}lt%kb7ˌ,oĿPP81acŽW&I· Wq1181*qtIФ1I KɃk%2 %7el”}֧ }SsRMH6)mnZ}ڊi[^H;N/M7ou:cdFC2~׫_~1`@@#T TLxUJk7?An h0?-rcLq:QC<%dF޼5P'JxՍro6 } \3?[Z]20i4HAfix?>zê! d ?6x?(|GɄL|d6#X叏#`%t}WӀ] ^~ߚ+ܼ&*U]&HopenMSX-RELEASE_0_12_0/share/skins/breaked.png000066400000000000000000000011041257557151200207670ustar00rootroot00000000000000PNG  IHDR DgAMA a pHYs  tIME!.(tEXtCommentCreated with GIMPWPLTE#%+$+,  "$(*2:>IUWmxy{pphi tRNSVWXETbKGD70GIDAT8O0Ѐ .*{/?5&3l/{f3X T/ F^XI>%>T#{%X׋xu7` d:G#LSlr;ysH>@JM9 u'.5Ҡ]aDDKAjs0;3< C@|>=)>1zR^ +^ VQ*0IENDB`openMSX-RELEASE_0_12_0/share/skins/fancy/000077500000000000000000000000001257557151200177705ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/skins/fancy/frame.png000066400000000000000000007210511257557151200215760ustar00rootroot00000000000000PNG  IHDRjygAMA abKGD pHYs ftIME  K,tEXtCommentCreated with GIMPW IDATx[eU>͹U}ObWNF`@M~C^̃E! ͋&B4$6&`ksJ߻+U:Zss9sJ|۬ MwWO}Ss=hӿs1"}#BwBgh>s.9KO?Oſ>~>3}v 3GOF/,9}&>[ϝ>]ιo]]/}63?'-/]7>>O-^zy|s?<N|g2dGy}rZ,׮;|]hq!_!V/:?k]g4Z/?|ϝ6"ϧ7'ms@u>uutXhMKy/Ky;}CyCgt=mK}wOIC]~묖ڃj}5Nz{'[{sK~7ښKX߱b3Q;˭uj}4XV1֭[! '''!`Z^j]1+Ʋܑh~eeߙvڜ #֚DqښkQkkd->ӮSm.iV.aŘ2kQ&koڳ|Xw4jz inl68??G8<[g<ϊd !yx9}W[kҺ?kmi?;A6j9SUˇA;4A#y`<9#q4f'Y9|0u🷞|&V.簆5H=Zh K&EHF&rA}mRÜ>m^;k /L{1}zv |P*7r9K '>)6? ,LC\Kli2@T;H ?[&ZbHעw#Z.>2غ1) UW6imѵ}m"XsC[ˠ@@>ʪ-qk̓ɡ09*E vH{Ljg\%YKj&剥VגuI5.$(1Uy9i4l"UL,d*8V+m[&v;X׉`=-L9ċ,P(sxQŠuk [)w а @1s'VA[+j1jile>}C󙟵Ib1bk`"ńq[ @C0 ͹ tV&괶y֭[fS"31k'04 W,T }BӺCdΥ9̵xTڜ̥T~?3&[vԘ|N*/sb*BY,#cZǥSY,F ?hʫF㬊,kIlsj @ kP<о:ȭ:8dĺmZv(@8 ôZj Wm35 [K@c>j64b-IK2ުjLՂW"=eBk }+=5ھI(Ybk Jh^^׊-1}9;;K,sb٤d8٭L}A0j1ńђ&_Xxj/پOKʹ'YJ2޴ΌZ?XFZe=kDcc[mpV ZFR^!xՊZVzk DJ 0k^VT oݦ.Tl]sÚf=?v-O>ࣖ+ظv+W~a謵kb'=$מiq,ZD}ʸk'Z6k %TX7 rPbnYMG+htm+ih^`j_0S88褵Vk%+rrXm !џeEZpZ@MF4]j5Ԫ|@& ri:1VmڻkWN& RK5iwpօ|^Cfh4~tY -2րAmPŦPiɺV]֮\@)@ƚvh-#Z~%YV{y;" 5C@v;6=Oim.1`%RVõEkW " e1O5g0Y Ub ׊ 4 v&%WY|+h-=sZN5ޒ",-NPciRW3bHXZ=}985jz^{-;hGGG%(V!RٴI4*Fkl;ɊeA}.Y I||_VY6s+Zөj~&񶺹Z{%J=gXk/\p^ ͛7w~~33pB.yҁ8d.Y̻1ZQrZG hڊK,S *2rZ>ɘԊk:õuHV4(y6Wд,MJ#gi9\@ n1+vA NŇxٙ0K6Vays Z l Jf_V[>ϱSj>&)Nx[I Lђ}K ٧5WX=V%ƪ6n.Ps$vI`K|MCҏycpjZ6CFhi΁&s,;qڳ ڜLU9*]xFi܋5Kd~mX+8 1~4l*YO?}73njL- 挪{2HC-񯱠dVMUd1X 187JaM/MkԀZA=Z :܁l>ScYNV@[3+;OʲhfAZ~/?C29ː~ڵk n3-|/q pb>{>kE+ziqgԥ&o1k* ~v9>KGX)祵k,5} V+%yjZ rV-E XVH-Sѐ:LWE$ ֩k@vɼT;WB9`] w nڼz& ca,Yƍjd<ִ7X@3u I uy}Q?k<11AjlkJeɎY"%)E c\|Y3 &?Ƌ] ŋwO}*jZKŪsZvZEԂkVz5`j144tk97֨|H-ߴ .i +KȽv\ćjպa}w5MZl1(9׮tXZXI ;m \`Ԍ)âkaKSU%RD[_& Z5gut^䳬9\Y?m.i UɶGm8w躉w@oᑢR 0j/|5f垭1:3J/X—ܗ|mHYkBk"\\Tcܮf> }'s<ƾZ͵VcRb-֖h]m[}~Cmg$\Vbgŵ5G {%q.3rԀ= skH{j=ךq\jIb T3k4]ʿ,-@s;qKjcXYD ?KAeh?b!5~,5}tIY| ^:-3Z\Yӎ*[U`Y+Z΁A>V9X} oo~ꫯʕ+x?ڶ!{9}Y\-_ux /_e X"5ZlM3]\}L?9 DjjZZU"5G6F h7)A^AO\KZXZEmv e v"֢b ^Z+g>k1mx@T"=Pc~vR;XkIX}-}7g}syNTؠ벖߮ۡC 䏎`895ɜg{RͪjIHޕ.sLAKHZ[s@R>/cV,R9+Eȵv~VΝcV;[8f{VW$ormfE@dQa-^lSLq=$F (Z-ЏdٶNjJ># f*a%{xE_TZ䳕;kʲ_|)I-iՒV)QӅۇAbEavLzT]û߃h*j5rq@=}{⋹|1^n V6MbbThyDBX-lט bZŖ޹;B!D dX@Əokgk>Z`NtNNj"9?4@_;5).x7Ċ}iz5fʨd,c=VLK.˸y&??.\oomo{qw{ .?au?XJ3 %N<,뮻V´Г @L(9Ф{C8) +Ht4.d' ZxV{F X-jyqnē!pwUk`Fq挪 UMcJ>(a0&;p5AWf w!uA0a$0@՘c@tx; D:YRgItZPop}; NUhJ`LC5Tq53% ,wKk9҆„ y= A{VTiOҝ!w1)ϜO&Oc8;;K :]ab@9B8<%7nRά$vjOA"z1"FBLgt HpF]kKt<x&3Cs~._+ÞGEcuI@%]kɷX@hf(y5'ԙ+i@*'XD6e 97b:*fz *P_4I`ծ:{ƪ+FYl^d1U@{xYs +g"D q|%ȯ'׊,"ۓ,P)=1v5a;E x0<  S:9v sQ,LS̾O hCw.5BLqky"8@?jmgR0 GӰN!i$cWb4F8}ٷimu~%gdSCve>;։ cimM4n V'0h WccZGl>wwӣOE!`1b/ؔlsOơ%,|PJwMt^;)(tLl]PBkYϙVM$#UHXul^Ĝ®4hVJf4ڨ$E[gq5կHXr~HIPIG`q HL!̒myƒ\)EҸFFf@ ?3Cjw^syZd:tv><8L?\N>뽇KL.:Z^ v@V[ZxG@s`m[МQH44zj.j79V-gE4^>}&BGbK$J\8Лܑ]Xsn.mH*^kbb)+]$逞_$(@brk+(g?~F-ؼYBGۗ]!t: $8 ad> aֹXhs?gp:1p.6‹0cO3g_HqA}2WJ+|J-MI_NŖgğiFѳ1k+f9C+U[{_wj* :'72}t֥NϽm&PQgkԴεw_D_΂-֘җ%樂?vPҘ.ܺ~PqF7pICӠkr~SO= Í7܁=u ڶwߍWs_y{Q8*~ʉnܸERDe0]"Y^s]M M< /1"%gdnT[)LS>@y"Zw\25凹3w1&--A[s-1ak`PI!x lGg-;M;J{hiYV!j! c5ԋ!p݃Zť^I.%o)~**mVgi("©cIJ̗J/8T|q<jE۴ #bt%S-Z6lKr ͘ T@~>O^(?-sISGK/~i~~ _c=G}[~*C {S{*dPq3σ ES$M=`ӲcBKx|ƚXy۸\#wbDt_6_ѼaZRީFLtbAL>ibW 5}N6SYKVQ4?%bꚑӊkRf*ϒ)ζuӮU"XQ@R@EA |Tƴ;,d7 9^~$ BhaO HCxᮻ{^\rO??+RZ;1B}/iZ84,p* D:Vۆ%( |z "fVpzH`I& S^ZtP`;h aE)f>% tlUخ2lNUU{LЕo΄vLjjLd5.1СB'!J晽CSт| Kf\u5ddU HajӘg ހT"ơ*ScH ,ۉ4ptm3Vs3%PS3 & ߎFT_Iz]o qpxX$m? zUT!/bs65Wjπǘς`l\(9h![yL^?$; *OcϹZ𪛂`r"i[k猌 CM$2Ϡɶm}904KS!7c0s6I,`L IEZpMӢiJGXZm`KkNO31}h%o%]\Ӗz$]j*}I{-Oq G/P^{Kׯ=( ӆk@s#^܌NKIPxV1pcQJsCs@(s Fz\6D BDs/ 4]dlԴMFzmzZmGL-c{MIߴ_gG59qKc&5_#kb@>2_]gYOO㓟$]24^|#wѠvP:,'gy4$ p }?^{ 9ڷ-ɒpcopakWe_e\e+1YVXM61TagrWmK3 IŮ@3(jF4/xTYi2'[4 o(Q66H[/P[R,wviiD6<  2cѪ\Lq)eCzޡ$@$0#Oc1+:3@:1#ŐZǵ`vuڅH<}nףnvz 0 rBeD zص`A?Đ"F;|TImY+pNӘYa]Tyˁ(xfQ[7 9ZdC23 ȫC;$Nz/boqx?+@ڇ$G67sy"rYS^"n l}tfLP"wjEEuT0?įdC)IwKR_ɼӣi}v9XهLEΚ1ʯ{۹"bdOp#1lEILĉ%#y:jhϒh ' ܠKi~%PؿyXBtN;U3H0J7Q\ʩQ3nI7{zrp?- 㯬CHKJM1_5^)$Ws}b!Nlx-Ή2N9PM\Rq35$`Ws2칤a,:5v 5W$^e] yg:Ûq%4ulR^|ktQHfiz?a< h ؤv3gۖxWn~y R1I&5Mwַ{|2 1ᆽqC-h =`q|7廠n72fZ̙͙ޚBwKt 䇖&sKgV3qb cwc@׷,Ryhbҟq+4^ӟq` -|m=RNbQjv~Gpi-{hEk/%l Iخbm)XhFr2䶮?i?lڒCCPK=Io,045BT#' ʹZ7fkROoNI 1k ucV_l.r sUОVm$yrwۖ"UiXo.&|Ѵ0 Kl9x"yQVgrp1rQql!"1Fm[R&9Fb7M3H(7L`m/XcL̟I|WU[3r.ގ~7غkj>>ßOƘ,ZM[ VYвӢS|m[PsŕM|"m`Ar}l *kq9p_6HTvqAC~r{_"~{Dnq||!<tR}K_>1\r%#% U<Ǯxpxx/`z8 &V9,.1— d'2&Da,k/I060d|$#><,ܡ)±ژtr}C^G&_j 4IhOc K;,sshM=y*c(s`+66)`tOݧb&dD82O.8 e tl\bm}(r Eщi@iXpX.f_.X.,Wdw;lɠ_P' }SmՔ|԰j\ѲsV}}+]=, ,hbvjB,4dg800%cMnFYNH=\l] Rld,d]j19qN[BbĀ#f<]!d>m:1fڪZU3ņ粳ɚޣ6'-Lcy6j!~7 =Ԛd:2_0<]fZPaiܹ>٤&KΗՂ̟mb*ʼn4sVF@@%TɍWWMt'7.4u%q.gM.=5}ł31&-U0*52)b581t| }P۾"D4 p17\ʑ5c~Cq%  lT}Whl}'Fm&^G0cs+zLҜA@}Y$nD$o%^ƹ36eRBvh@Sg kqgFZ|M+i^Z!q_4M] ±Tl#NH R;ksg5}손L|*}9Gy}`qT0$cL:b&}&+([Yw4MQMFڑe+}' ©lǚư/toߦ+{Ut4>a ]R[S('p`bi&P̏jz&L<^@X.Z–R+ vu&'1(! QPk y?9MG^bbNrRbz"GĴN$@,sEeNjxKGudfrHr \&$.-yNk0Oؿqf\Stff.kxbZwA& in4MC`^vL(װѦ=p*R\@lc #uyjuK# q8['1 >QĜDE\*h4wD :?/2hl{Oqed2ޤZM4r r>BEҋ5S ̔`a 6G/; okcgv-^]2 s|Mھ~-?a<={z*ެKkg0H08==n͛7ZnwۼA5WSif[}Ce稜6ъ("qrg@Q:MQt!^XEk k[ta7PPcpQ]*91a\9;;q!(jZcπI|is--F^8{ZS8e@hV2F`vT*|yoˤU=pLXT,t687L7 -nanpJXӄ|ȵZcM{T,&n}u6r IC\[˱&AOZK&Z0_iɑ)MW*ӺcL ޠB%\WO4&bxpb_$Y'*#'XqSP%6A{KBN@sjcD׵gWK lӞ=`$$2="{\^,YJPRuۤF'Ғ?ŒRbxBdEeWq@)^dt cbFLjo694 pT$va2spg]ޒPyIA%K.$brR\E37s{ ,k@^hM=y2Ip5s^GOrA*犎b-63I;%c%~>4J׀\"CD!3&6 D$KqܳbcOjyl;}jJbuqc!/dIg?R;riꪙvAƌmݰsEyƂskWtU5YV&ccG}) To؃@.E+bf=R/kٳԪ.χX&3)FMn>JxH+ANpqxx~O<^~彻WK/4s_WwKiOa @ucZ޻wGKÂdz 8QحO-uO.y P8ãFos*;Bo8dfW9"u^DlE&e>%k_rCpH`f1mJ. {\ng$ =Iװ03 4&Vͬ %QeZdL *3{tYR59"Im+DBE8r`v̲®K'nYK x7dq9.&\f ;'9JDVj-c<*^X,.IIP4vKjNLjÃ$1δDMjr6,. ôpZ\pLQ:q@Z2-s. IDAT@RGMݛؗ=|}}nbJC:1qy:fB$*lPMc Z7cҀ$D*&:8P]xWj54od(S6ZH,Ƶu=NNn;}9N?*,h\k7jΦQg0U2&Ћ $@9Bb 4}%e8$x1IZqOj=6b)v)9"9db /yA^%z="?Șc.cDtsr ka[Kr qvkMWZu{n"́0ֲ,qͿz #?}xtU& %85kNk8cd,UYܤ|3M5c;GJ.*Z>4ҙ*&Z"dlMl'p.LudJ#D{Y'e>$~ke2[0w'QXI2`2ϘӵF̂z{w|me+ 8P?O{b @w݉ӓSr򶻈4ZV..:8~jy^ڛ)cf$}P&P:b Jl,we7(ˍQXQFIJ ,{ڤPBXU\DrB?u QT͝ls&odŒ#s)%1&7Jx9lTلT\ ?H d4v Kn6:Ac@O$ql/,O#?֋%ݛQhd BSk>N&ڦ% 'iT81HM t6?$ v9NS|jW-Y12ɡ9U+C@C-b됉%8/J#aEk* v怜kAkIzz} eh˖$ #gC8 bIZ⥵MIY~?\&E?ZPs0pR ] lsҶx*1j۶cZ,1w$AIy7pWԀmVi/Yd UKΫErlOsԶęVcO(,'m :,+co<\3?.B܀m,t9+vgd{ KΕ3N5{1L72Ã^m14XʻݮF9XL [` C`=y.y{ݚKټN˥XZd󂞌M#`Tova;9[F5#Y]1m gAʳ*N%'ڡE`@~Ju`vS4baϓ2nHWx.i?<:CL kgfz\AS~_իW gߣNOOqtt__tʙ.\T`%v~48;<[N=eQwfHWhz: wO@$$#p^w~ӇLEkh5@;w>aOnSki n EK`c5ROZh|g3懬turѩZs 鬵mgWy,[dE?)ut;o'z{^̞*i`a~c"iM yĺʜAT wɾ }仌~[ֶ&j*ι2{OhyE O",mٖ 8(0!S+5@uژX[ÜJ/&nD<,hqloKAkqҥKXV 1rtt}E?3}wrrGĺYw'[9wܡ.9J,SDѐs8WI(zL/G}P[wƤӏZdA@d0j Hs(z{reQM-ȀbV4F<~t ÿq_+6J"߃%ݙ3Zmj`Q^~,ؒ]¨dYP@&wy§|>rg_Sbݶ-Z^-2.%!Y1, JiA{,9B`-͝9x+2n}w& ̖;hE41/ޟ֒omۘV">us$[Vc%{qR0Z9}1 Q{Ԥ$"֧"c82VW\ T2{rmNsm[l6Tv`1b L Cpey4:ŘkT|K,T;NRgށ|g3+9S2SP8ft}`=m, 8BnDQwq'abϑL4o|)`!;7̙3 =oEha 7q#!IR0b^%N_ 2kEhE- ̞r7 Ї'7p5oīZ(IV+q8>>Ɲwމi㥗^fQϺx1Fo\{ \6 |qxp]-=v@޺6V 71=ȂbHX!*~TIRY,KWS(dUR!58䬭(iڑkԄV %~ ̊SЮ!{3YAnT(&ә3gMhȨR# &M8]_ Fa2kQԊUKkV庖˳ "i{2Q}6!ZgZ]P2C7q?-a.u6z+Zbyhzg Xヲ+\3Fqq6vc ]ab*vM3%WNjKph2'5^yIah WY+ski0۩8qPKV=C渖S;(oc|_ȑdtΡmډŘxtni{[j.Yȼj߆ӵ|EΗ^RT_nj5 c2-b%{1ͮb6M-&5ƞqYYZg3A)oa-u+ 88mp U339U:/މ Ugs&9vJ]L{HMܠ -Eέosv:T;8ީq&D/;r 0 h؇Uؚ:Gy3~ooj,_el6igV)d@իY]>1q/ ȱ^yqvݸL yE..YPN )U͢ႅ8غبmgL¤τVa^ l%M')2`T`EѪ\h p T-bȅPoIj2w*MS.PʜUPIAGbnYܹh8bE >lsb(R1;o.e"|<@&ײ$o(%g]&=m^q89FXjkx [Q(+-yt0@ 0`4]27!임T9N;dP@e3B*PɎ13O8r^GR>YM"[]%w}D8Ð i`Z-Y>E\@\3y&Jve&A9T-l9t @"ρksD*5mQQZ'>}.IFkM$ K"m?Xώrk  vEpfeaf$;:L<[M)/c9ju^(q@%&gw*s,!*Pl`8[g'<EfA^Ƭpk~ZcIJ2k'+v  Yf[f1iNu3Z+qS[kad“+I)cKk! ]Z+ZCӞI|. )m1Ĭ̀ϙM,! %yJB.ZE;:Ǵˁ}jxC:$6<"Ј RyxqsegX"sqQa_l~FQ4_`<2\@ ɖwp{,ӥ9vأ5Ec@Y,DU}))''pu;YoUA]& n? UgJI hgŘ,F7ډFumZyJOV- C]8F7p=U?&|'#/5ŮG"9?jޕlo?P1C CS4Eu8 eE]CvZ" ,>+B<{T 8 Lk9yUC1CDK^m3,mۡwdѮZSLۏ>v/~͑ԼIi-&;B^Z}(<4HmNE3r,ڽXjV|Rw^E$ˢĜRZG\{~fwviXcZܠř՛L4+RHsH)MAsIupl^]~__w܁zaMqttil} ?_q<0E}W^V- !*l!O T96% Cz|6\Wb&FSrѴrEZ"+m;0sJ2GiҢ92[I~=< j -&V}g0ɴwOWY{>6Fs uf-L./|k-0Xb.L& -(&X^HS *,pi)+DӲ g&sΥ33k KVD>Wb΁if--VkΎ~W[ּ*ֹ8\ɭˎ*CBz(YXviPcPh.ǜ鮝A|Ԁ댴33u {]3mrIYh<+} 1^\ԌTƊՎ4y$K;kYc #5Պ5 ŠK6M:88fIzs綦X{zٚaJmO椭̵3wVzN翭n}fasm"⨢`Qū^ܟ&uYZ+[52Zcp4G:!6fT֬V~~"6̳9Fk^y1_WqrrK_ַ]zڶj@Mi\p!u'O?kK.'LRn.7'\~=uHi!qlOE[ӚtR7D5Џm0`]"bd'#UNOOk% 4!1x5 o17eWc1Ԫj޵v`[I h-8ۗ+u_j_v>1vZ@9 Z};, ĩ%ѵ9RK嶸o>U'[D$G<xU^{[$P3g.ܵ@h+YHF̾`mhs ^jƜ*iGz&5T+KSp"̬3ܷAZ|i, sEJЀ =q죑x;66}}ׁV}s5uDxu ZL7w1Hy[Ay5M]KBBr=8gHp.o#&@w].>b#xR;5vUd[5sMԀi$%[k9 +J3ޖ<3̽jU2DubjC\pa Mr*BMӪ@\w1nיm:|Ļn|{NOOB׿u\p> l6\8}O۶ =:i; IDAT:KFnngX L\_!34Sd !$vo$O]81>`>} 3iFcI]D3C]ԎZL&ioI's5k.~>` wӏkv?a0#=yObkdRn*I˽Nu;k/"|Ao/:î\7xӛޔ ϣ01?8^{5|#C=OBV%M ҳ888BpttW8I;ʔde5444wJZiXҥܐ8WPYAYuYKg>e,cX2e,cX2g>OӟYשE) I])}umtq;GxgCᮻ~g7q a^=6ܸqGGGuMA$.l=ppp^{Xׯ_G+]4wrkvIi9ej}U D2}7ZdqQ@: ,|2+"n矧g-bX2e,cX2e/oW}{c̼#iEɉWAkHdx~2cih$#~ !FM 8::;/bd?Bu 1$mvmrZ2e,cX2e,cxC'm> iT,%2pj&&f+ Q 1fXOy?IϓK涬eĚz2i{d@|w]!z[n:\9GGC/m v{9h.4w\{*\H,q'ŧ~:kŕhrF E0`'cz> |Bv]oX2e,cX2enG@:!8Af*k9vK}PGNFVBͱ[jߚ2:Q.ܴKLĦi0u¹At}j 0> :9.C;4[םs{p||+W~7fGxG𶷽 Ǹq>0gOT <2B #`P&}vLڜe,cX2e,cX2zggzlo(=0kUZKȌv)1]?#CÚV&G!3c;`۶Xwwߍv^NFyTo8Q]׫Ct^xN.S! ڵkh!}9Q5'լ8D6-5 Ҷ-_O=-bxxgp ڶ_vNNNж-|>9\~wK?DY3:B&>MY \2e,cX2e,cxmqpp0L5z$]=R#ea0t ?`pZΌ 6v$Vj1,>.'E14-kCnGՈsxڭ[hctl92꜃kV7:oi ^xhpC@GK/~nE,C.cX2e,cX2e^Hj ? < ۄc]ߩ  td߂sdZW<A׫E0F .mb}à x=m;-i΢ r ZO~ @~8<O?S|ߝ{޸YZ:}d+ {]6)KmAj/cX2e,cX2e #Vr/v[&?M҅ǜ ڶ>Ui-"I?o803#B^:x9`І' ޖ/c8źՕn{ë n޼+!)m(Ģk|;o} ?G<Ȼ蔤^F[e6E“>'L|rX2e,cX2e,4hVч>uCҟKYbH9G#qI!Ss-:.=BסSd؇$!0t:Y+t(0'܆'ϒFcnסi<B͛7M7řJbq`i|k_jurXv؅ҥKH/} ptm$ضڊKZÃ*gjDF0^e,cX2e,cX2q4MO5o0]8֔1Q1 "fʍA&<ڂGR3Ȉfd6#| ~mK?an2|DX[GC<3V5'Uq z%S_"6;q8;;Kg;==>7o,w.^G}?qzzZLׯp}cO=8} wՐ\4e,cX2e,cXsl[lDF׻A^/1;o4Y< 4B!PO{=v]NOOжm;<8'aPC х?9>*}7ڵkx3`f*%㳟,VUacvE4?~ժ|?}ݧNvj񩝚Jzf-cX2e,cX2e^GuEd%Or3 pHC ˂sp Z~5qv" ͥX|iXzMLht>7IօUH7MpgggXVh+M@d](ps8<>:w;wpkȮճ5pg$YeŤf*VZݻh0UJuU@ƜOsV/cg?C_,>}͓OO.ċ/5ul-SgEՙsm_(BH !B!B#IZk׵ئu#jQZwͬi{d}>oKܼТa$M/ٍqٍ㈶mN UnExSPf7+zC۶x|;w{_|/*< b:Vo~o6BXWk׮:mi^qxx^t 7ݬOӳJd*uAJ1Q)1F/Imۢ:F !B!r.YV888@qturΦUTXsY괂փaG :8("k]59cX3WUVUa@۶hw;7.6J;e"X4y!;E8+޽{nիWq"h%~ibZaZ:\x.\˗4 yܸqͦ|II֊r6Bs@@!xf1[+kת}Gy`v!619{r ?/ _'''Y^qpp,g?&ɟ ݻW3R|Ԋ{hӹzOugB!B!4MS:_⚠l)U3w4xkZx9~ hѕ-h "~DЈ 1Fܿ?\.hK;`!B|W/W\A۶x6\^ .`^c^9w_{キ~7?8… ރd9j*eEHknu]|B!B!0XBlLEVg km v;貆}& #bHc14,b''=ڶEߧc7ѳj*k17xƛoYwqEV+v;ղ,h ~ӟx"9~f>lm< lMZ5ȼar; !B!By{? UTZxst - dSi^٩;2byB#jcttWn@WZd.iy8:@8M\mEq7"zM_oG7>s/2\C|K_ݻԧ>^z o^{ <~~zv=H)41{޽tSNI[l}:NAB!B!_l5 YC~(ٴ5575mlF-8b^ҧ'^aQZĒUtWR6I]~VKGԟ2/;jWdBRrl׶W,-EGb=[4Mv><~?O?____޽{vZn=>>?ŏxjگ '>_,Y皐WK')K6QB!B!I}jbL[p u .֤$W XyXŎ{B=Mk`Z, g#h5ymMɱa84]MCL)Xrv٦p6LgYkM48{iׯk7n͛߇< .]~tKB4vK;<`F}8l5M6Wٔ1=a.B!B!#ZS9-fǛ f*=:MfiYF/Bfa1oY8"da6-f}^DϧSFk]jȻ~Ҟ5Mw8>>EiEaIg@E| ^_G]ᡇF޾}{"0ad-cBL+@}Ϣ.em3\iU.IWw-v].5!B!B9oE!;EN>]*L.qFjudW'} #'(;mT識tG^k:6C5}M/Jz)Pg9aQd۞|\й^QxѮIlW6.g$mZqnN6rJu;Q;5EFFޣ)Εj=?}_Zm8_rP#qq} B!B!$iJ-h4#>cv,}ˮ8i5W"0{lkvZ#ӿF鹄Ei fɗrlL\< IDAT!`>LFi.V+ 63UⰃ*qa&6ӱBB_0"6P]niZЫ|9pϽB!B!GSmnn(=&k5-ٔsg>5fvi:܁&a?.q|6 ZKQ"; # O \pX5ckY+ׇ(ʢ:iqF\ӛUЮ 碬E[J:~1j_B({ȳ[TJ8 !B!B~imf .E9+P l#NJ{p-6YFm\tgvuD4c݇~=!Ĭ~`Ǧi0*GDEdhwn vM8,*1\%!7.QتԄ>YBʗƞ+#@j@"b%B!B9?itU;"4Wݵc]1"ĹHDÒLբ]#yExg0Mfڙ' !4L;Bwi>]סm`Y8#˗qЧxgE6MikAyd&+eȾF?}K/Q[Ċ O_bh~iŴwFdd$B!ByEV#Ln74g>Ӷ,M`֊7&fv"gs:9 ƀFFωI+ B+ 5Pj Uu*kc--JDP%7_@s:DXЧ7R(B"B@]B!B9HjMPg= jM&Y}6jeyڼvqzN`B굉M45(}4ǃZJ,c{_[$˜eN7?k-5֪(lŌӢsa["jjk !B!B+kF(DsҚvY'֮TdbϩSݴ"4WV_Z;\w`jt=`\uBy&؈tgUfr !B!B+TLQVW-񶆝'&7ǷO`E98 ㈱Ia眃oDA覛sqD?5>aBjP16Q?{l|!M3]V-([Et`=,LAy8DYj2ꪅ=]tB!B!<"Eb `jV9Ffi_`?#syW}}22ąUS{WM#qa Iu^Gm۶ɵ &U!RfgYzV`C2õVKA#!_"sP)up@B_@bc x~.sv]m5!B!By!(eRIJ1+ٰ6a skqR.  HSjoVZm(VsGAĴ.pnn)̉(c,"6z}SG,+>5bqXF{诤QG.(zpxGGGxGΕ?`WWN3\6G{sv;HͻnCӟB'aQfA{?眷VV%F&$59{0LlCѩuo[4 gB!B!B@}gǚ h֬84SDX9>XBsX;e}3VsLJj֙x3}l5QGYjBu'qB@+'[nޔf]M0*%,:=tїf5mS6msUDdԢ' CɛTڳ{##}MEB!B!传[Ρ0D(a"rn_18 :8-"%!s[,DE)2lhx\9ǰj-NOOqrrv#B!B!TQ!yr^ғf)OIV/ig+u$/}>8;iQZ㪉uhG]zڶ0 q|!wɡ7t jZ lr,#06n\"PlZFFY-;|N_Gy:+~CW7%nfB!B!QJD[mV]t:!*4$xO%zDu }¡h=JS(1: N0Hɲ[q%c-hLxY쌶: kJn![6+~~Zȫ/^>]q3[&9 ӛ0e2 kieT~/,SK<- 6B($q.לguYC3n>8fg_}3e({ńG9#N!B!ri0c6a,@ !Ni k:P nr٨fҝpG?B(6QjvKtK.a^?L% D,d+Z1"LM\\SpJxa%;>tDW:\1Q|֝XhDzmѶ-K@!B!r.nnR-j GYDlho }Gyj:cD Jz0j9i-'O+LwSiw>R&gB2 Àfϡ=B崛i7ʢ\!Xl˰-M.xQ/]iߗH]950>t`;OvB!B!獦IjSv\ &szq0 YgJY=uQ}KWtEIoC|sZ_eo08= h۶]FwۘXFb{ 'R ـ@R@C,culvKp"j5sJ7ZߋĢE5gQEH!B!r8 <%-iz\6>]KJ!Msp{Ӓj?mYN@2:W:\P1dQ˹d|]9G}4Z5'abr]W$kG#,o0Yz u.k뜃o|VZms~ĩeHB]VWO-aXC!B!rX-u]o/aa6!iL0,{ k!3 kV:~I'_9, ö®G_S~Scj8n]ĿpMcEPu<7 V kpYy-چ.+> gstH;P?ʼncDP{'kDX-˗@lzB!B!7a)-n[8D׮'@72q4)`#:s dC5u~Y5:7FIzV Skr ` #Cu49iB{pp/7%7Ԇ1壵s/aTSkc}8g) Um]µu(qhnGt瘲daIK_i4!iB!B!2#G9\pm; 12.-ܜB1(vQ0JfReAa,R_M3/px$B!ByE,t]WAB̮"1Ehn0tJa|}E!8VfL:1oavΥj%r͹T$%$z5潊 KF5<'lqrrmX.V/Z+Z(^-+-"yCmXEӖxH l7|! L3oKy _6C0,#ׄB!B!95:bŌ=ѥTb\a=iҚ|瀮o|fZ\>g!{s,Dw%LLl8bz\| /_A=fl1RJnLfߥ +7fRFBoM"`\j&6V B8F`{j{0L׆I!uNUE{O`ZUB!B!/dD6[3)ui `lEB՘ִn W{L jGR#ƐgKqGWkDDv;{3C2/*U/Xfg:0jUTݔnQ IDAT\z46Ia7λ)-/:m7p~C;|uE /t\gw-I!B!r6D LYZb|tԪmduE)-Yǟu#L֪v5= 9zGys<ҥ#… hOO7W[eSb=}"sفXSm:s(m1{A;/pmp,3k_48r !B!B+.\z/1F4IͻH/i6Xn.촮7I> )(9KCM̮9?cdfk6 nK.===Jzբ2QjU3W,G,\z$[]SI5EVlU֪nq< ^?5%h̚66,g Ll`B!B!KDxm*d-EGpK)S0S┛~pgr{+-1y]֮cNk8+ m!m&n>p㤯yaYm6w).TJtJ}9{o[ PDqJ|#eC|I_Юж!iQhsdq8/K@!B!rQNJPo M&&CW0TgF~՗d=5M9W*/ jS}EXfK2m: ܷ 0C۶B ); V}AO|3Юيvz' ۶M.탵_8}_nz_lB!B!传E?5rUyD_7ggCSz]Vm~hH4l *R-1bUŋ1=88Xܼ٬}YM6l_[ͩ_s`?y}ƍSO==(mv:.wڶeB!B!~nqzzZ8l9f8v:4|ώǴiKVqsF㳴6=;P-9Lq_u>+jǤxo͵7 1\Ók^RAxֹm3L8V߸qp5 Àq4h* 0RB!B!A.M[4jf䀹Y'b^9q]b'jiVё,]/qa+HjW~2{mZZ!=+k7`Mg`+ސsy82wcsw||k׮(ecU+B!B!獓ܿvM&6kGΕ%RiF\5.{)df7>u5KZ(ӠZXG{X\imnxnjljd;CQYbKV%o+)fsQc5[pX49B!B!mZnr^,fZͨ9}n2{kJ~κh`Le:/Pֳ7[1ed9'b`ҷs7 a@}?{v==ZTOۖوm"Qu{睇\s1Ծ"|i{|lB+`Jヒz 8ֵ8+7MPr !B!ByC@1N a֑>fj@.u^;NZw-pJ4SkM1dᾔ{^VC@3w;zhb)VEŴtFuRWAo"7 +g mϜb~hWРɕnNVE9th| '|2+sx~!zk]vB!B!'d_贶3B,tڈӟYi`|)[ p*8}>e]2,"Ù$ַ1#ڦA׵1m~8pܐZ8;[:ʡ5-6q!T{=K뭹d1gZ;/L\X{c|TEV1It1}!B!B}ߣ{v[ À]1MDD4M3Pca1ŬJS%1q6M*֩X3YlQ^2!^=O_K'"YJ"|ݠ4v zMۢ_lTWoVk|l!ƜFn[1e)q}Oݲ&L7=/޴wxs\vpU5LC-c !B!B')ܢm[l+6̩Nmʢ1 @LkDRUɈ\[J]k}_Es Ȳ'B9ӪWb ! XѿbfL?6@כ2_vh+mEsk62 ٓ6Т] k C)_|4y3..IvB!B!c2x :u[O2KNw9B[WMNњtZ/\Qk%7y]}6SC3^Rfu-(Esp-6P6ъ{3r\=Tb/@Lx$R&xn)fڇ֬0ؗ.?OOi4]nο1B!B9؂mV5*}\mAف"c2Z 7O"{*#d ^8 Es9~ *ವ `eHz",v^¨gڇnVD =b!/F1IF$B!B9d$!/%6kTx.XTCpKe ym<{ikzF_2.j-M>?UûY_V]3wa> xR8ܻbb.6H[Y]z+ J}ŕVq7i;#xw'.2NB!B!Fu8880 XV ^Tyx(LgAqs b7 gR\yl0=/%l}1K7*qOFgp d@L.b s x4>sooڋk6a9.oN섲!4Y3|!-.U1?r|~;7g8:" !B!B}'''8ݜ.en-j#m^zyDxgtKl9ʙs{l>ѠY@SG]u'}VElOBdMt7f$M!G>GBs~QΜ"[Ƃgk1‰#¤b{4#B!B!]u<_CH^[B0 $p !B!B3] 9sۨs+v]z?_48<'(]Em,ٖڵk'cf#ZB W#jIm&p[ݐbmvP6v/-ϘѱhFaoZ|V}a;Ki[`_kמ*8 !B!r_e9jSO>+PSR駟Jm?kI[[ZCC):.}:33P%fb:4kDωVUS.?,7:5AKS"q=ʵ/3칭wz\Jڊ5-,Z@tMnp]B!B!dn_۶hMkpŐ CaI 92+W]uqӴ JGǃi.R8cl7Q';pv0=CjB QGSZem52$Q7v6VcBݷ}ˢغiM2 _|B!B!7b a4VjM.$y4/΋?wh|J;sM;fAt~Nt,f^Gͬ&Ϳ\c2=K?\r6Oͅ0-/\W#"+-X{k*Y^M%&*\(z zB9Ram?t7P+B!B! 'fB%4C pabn.BӉV)SRեI 1MZt@~}~ia@"Gv{v!Dܿ?OYZI,!dU8mlIp hMNz8]|rN~M-b\25XWm Oxꩧr!gB!B!anjZn6+g5B!B!Uqf2  -+O+=N,YіJ>nQ+y$΍}"F:]. hsͯ)ư'ƈmkf: Vf#db7|Am.nLL+P/f #bC{I}ɚl45ϒ*#!B!BEd}5IPJaj9.1;0 aSRsJ/F!N:be^5 I%ؔv$eBMny\E4OmZhvZp0bݥp1B")ZԹi]wwgڶ!yC 9'5R C2LĜ-b ^BC?ŋlppp-B!B!獦iMFa,Y\ء Z9Yr#\wJ mDXaNZ @{4tJJXtZx"ڏ:bg=C򗿌o|naݾ} W>^=@ʾ'W}q`Il#3"֖?';dU: !B!ri>na(Wi2{4/JZ1{*su cr9um6 #kKV a491ID1v_"^lpz4&=.\?{||\x^DA |f]f6a7oo֭8==E 'OgQG}x޾}/߿z*/prr \t ׯ_ǭ[pzzau._k׮gϿ/O!ƈx=|_O?]!r!B!B'MӠk[4M9jIK;FM~M c*qU5sXD@<.ŋ1 #._6p_#p X87#b|=ݻWuw]gƍq^{5o~a<x NOOo~_O<cժ܄qq<@nTſ_Hj5VU'B!B9/t] . g9id[' P6MSKZL"^R3]K( vAC,Zn>ƣ>{oڶ>99|[*^O/Bް+WK_9>Ưʯ`CP/|'߇7oy v._/EWW 7xO !B!B%MӠ)9,x =s.YtOwXš ]~ڒN}V,D9MYS}ޠ_Ӎ8fqQ!M ?Y{Lg?cǮ^EŗҥKxb޽{x g qʕ+w܁s|}{gOM} Wm0乀B!B! ۶E۶9;Dr$jH@ZD˨f s;h[4Of$ -zx*$xC\n/ >F9=o[xqzz h5PoU&׿u]*^" .]z^я~>?ּ~Ef_~{!B!Bya`}g])bLRa\qwqk-q!iJFSJ2NceMw.P3H_}I#b: _Q46[,?Ӷ-'C5S 6nq…b9KpxxM۷o翯^[ng?͛h۶[?4EvSB!B!smס88aw"A=k cvڸ-En#8w-GXDDDُ0E=Ms&nL ?bY w/ JArJ{뭷pttT\Ν;D8::BW^ꫯ… xG[o!\x'WpGbnqYτB!B!B&, q,$GmXe5*àё]9GEZ..\HSYDZcr?%lgvA&m#1`g9qppjusԧ>Uo.^gy]w[|'ĥKO?t!jqիhO[2[뚳8݊J`j1áЁ~ ]c18]VhGGG{l4P*N񢨜on}'x>h.1W^+R=o۶x饗y{챽?iptt(<3~z"Bt H8߾{Xm*&B!BE:2T!Asp.*O0`6I|8ڐM68sJT[zZ3T6݉‘ZE9ؐ5hnCrNk]oZ|Vh%|oބtSΟ`[VU`Z|ޙ_C|K_… z'<#{*>c_<%a~Y%B!B9fG Er,VJڒ6E!#fBs4g(2,yv\ěk4h:۶-9C5> ㈶:ucAe(j&A[[f.ʂz_~Ǹ~:n߾fV\k׮瞛6O6='XGGG8<<_zofq=X/}Kuz-ܺu M`^=^n_YU]+B.HJI#KX0d?v]a;wn=Y%iJ;N&6R"Mw_V&[?(r`tSyPjXZ'cQ'ܾEm-AH!B!Cb.NKJ+:S3VD$X6=6{zlO@;y\kHkZZ.X][A`q<`t,xзOH #ބaE7~5qHiFT<l,uAG!B!mOΜ9bi"Ů>x6-e,C/G! \IDysZ.$ aY*2*hEkZ/2@ b|7@6>Ie3L?ֻotH.v>wFj@g\n9/Nn5BQ;!B!B!b>0,#9R,b΋ieq 4 6w^[{BY ]XFQJ¢5"X|)؆TZ0 HWE8T] m'`4$밙c蹗 '.:B!B!5ʲt: &)ِΦA<NaHETKC^P%,⡲.|꯿Ɵ*PWkvqΆ0UU#GP6MQa&#(o#m<@y~S҇>\-0G((- PKN\sհ<#j{N?۶ ҘfB!B!ÂFʲЙD?'!eDH+ʴKUkn>3s%# ʹ3Ɖ8Ns(ɰL7t! R󛒊Yi2I R7lt趮S:ɻXi܈1ar"*=4XVʲd 0!B!B=BUЄܗ0v\cLLeWHj023W'[Ņ9J PtWҠ_\g ӚgΜAYu4ĥS3QLrSܸEE5zd,9 %\w)2忾_gB!B!zQiYe}R-7Ԛ"]HHqgtSW3 z lf\_?Xh5>ݧ, Z[[(}\2'ӇԎ(yE6}P9\IT}l۶VBB!B!|)vj=^o~]_ǼhhANzcccJ (rnAi<r2y}j+ϓsNDk}"ٛX,5~dE;lL2e<,{;fM7+})Q:)CкjH!B!l6ˑVU[ l V^h<"uHaZɶx>mh5n]빕pMzIsTJR鹍5:DJn`=rJ- ё%r I7ߒ~c .*W뺆u]kB!B!PFsr-K ^fJ[EQd34ܽ6pJ<[?hZ~˲DkZwʉqcG4 JpKTJ'y;/G.MeYFrU,l1f쿏՝5nH)nj@˲ ד$Jg!B!B9L9^D˙R1ʾz~L]X,Jǐc͓&Ǘ.˽OZasܥ6ȱp52X~nnn>P5֘Nɓ'q=z4LD;$9xKeQkg'_ݶ-B!B9<]U__aoo/ҜPU)N ^ߟ.6ݵpӑ֑ukE9+'Ok1߷L+lMk` (4 &3aZ9fqLtmnn⭷2c [n믿 . 앩*j*4W\&TT-"g*"FÉVf\Gt !B!B!)5^z%[!cn:Z5Lb10IV赫J\A)BFKT3s|oeYX&smb2cߘ.S?c_4~9Sw4X}pE/Fۺ`j/s ѧ1P%P$B!Bȡc4Mb|vYeNhl`LptN[ks?QøE&~\ֶmuc4 Inд-90uE,/ڵkomm =Ξ=Uc7oʕ+ف1";A*/A gN❍D3R*,EiB!B!}coow@˥uƩH&i[oE:u ?PUeq1=z< >Ӱ٩ؘ5q4́n&cḁ1*zyB!B!ÈgE+\AoR>rZuC|l,pQW,(UG%upd2IU9e7wΞا~zܼyw4 ? .Gvloڶ'|۷oc>c2ɓxgqzf֭[ /19qΞ='ND9AUut !B!B+EQ`2`Xs,U׋\KwN{ǪE_Yk?g brⰷR8l)L(Ҧimk` ܹlroo/t#j Yz'|ӕ)Jk4y_iŵk裏^ _S/G}m|>quKxꩧœ߿_ר:Z||2}6! 2yB!B!䰑z-W t5Ƣ(bq/\"Iu՝\yZe{[P0'EGklYk]?BЌX]uhg3qn L1ƄsMPZ/ \]]vc/)a6,ރΜ9C i(rM/.M~tz GqI(; Ŀ1ڶ I \m5H!B!C. H~1Ts"&hJ-[ (AڃrsK-0"FO?Vj^4 DJUmۺBDtNw\Ndw=zOx7ߏ}饗pEE{ƍL&(@7t.UB!B!rPJAtQ`ZBA[' y+N~^1 1.<4V,! `]ȗqc Mi5Ơ@6h6QiEQirXHcA diYnnso4VU~;qu]ckk n)A$!ڗ*bcЏUkQJ++N&XNQESS d38Hk-)o;;;X]]ҿb%\z7ofY'goo냱%ϟBLxxGF|)Ν;W_|ȑ#8}4x ʕ+g}KoR~m:{+!B!B! Ty#YjvqA;KuTd%.Q8:0Tei+PE+>l'F} }s2N݉@_u_EQ80[D*g6{옵 s$O?>,zw?gg,8{EbPU._mlnn/ӟ'|++e[T-,C@!B!rhp'ZN\C{Ӭ,^ts)Jj=iR eQv.v|ŜӿCϻ>7-|MKEȴ,ː4omm eQkkPDUU(e}s,V!;Y nRX/^{@~?kV@8<`ss3:ĵ0\{mۆ1A?ӧv%ڎ=ʋݜ 1ӣuߦK,%B!BaZtSBduh]Dث<h&;Εs|iRf&+h`?g9 IDAT~No3wzQ(0LB*gWQz*ذy`Қd //۷o{9={kkk0`ggO^{ դXd2 [[[裏"-8?cTU{ UYûF<#(:W/K=s'NB0ݻw2}uΉ M.B!B!bl6l6|>F*d1xCT|ҪQo24M砀BU: Rܳ<&@5,Sp?éS#bzѥM%J=][K_olMӧOG}\S>_|K#/tQ8vX\k[Ƃf65X,)cWN!B!rWQZE2&)oRʙJ1M"xr|)I/ o56L?ޢ(~%XeT,{ ݃o/; Jist:.& e4]H>aDa7|~y< a>Rt޽;9s 9͛71P%=O>$~_`<.\s?۷1QUNNh[9F4Z9m](m 9te![yTc95Eb' c-)VVV5&0DisIxS:䦧V~>hH7!]dccgq 8qD4W)2dǏ^ƱU%TUU, TUP˄B!B!_7 ??C:pkse7-Mϗv?vA!pIn䑥^p@n۶("TL\=JY{{{X,Ix !B!BieeG*ɒ]eqS@qA.yt"9w1R_%ڶ77oDY娫-Pꤏ[Sf\ {rrʓ}I)le\oigYa+++*-'B!B9,HзK{R41lTcx-թrbjrBZjK;YnKS[!ֳ,"-}zXeYcmk]7PK]z 3T6.ʉĽKa fYw>6u=~rr1dלۯPZke!B!Baka{{,EM 0[V{RT7²e^zYΛ3Ecp9m›1zXa)ɒZ9P+6F݊Rz4Yҍ*1 eR^֘s%t8|$ ~ݓ4B!B!G:u*8# ]Ee[߁^\S[KPj?޹gQ7չA@ NM4t sҿ-iQ@'~J+h *K("le!B- 'EIɍ𶷜-1em@j<Z0"(OnA/ ʗvY:iʲ= !B!rXY[[~HMvsS)nѸJjryq_sk*d\.e>9 uio܄# j?ژ{:q%>ǂ8$R;zMIFb},b@XimuPB!B!|ݑXeǮ "BbZT{4oy ip}ULySZsUU M`J^ ~Xr3doN{rsZ0"Pʲwe{bpT28@׼{+B!B!dmf^v;/ܤKO>,C& T3"NKϓ31 Ur+l/7zNJ`:+eU.#{.Hذ J#5Oz19|7w`D/)ymMZ4VA468 $B!B1"LOLfCH_Qg]` թk JAe})cis|O@hV~-TMJXc^iqQX , <ԓ( Si.Zشoj8~e}s/Wn+PAM5F^Φګ=sj5OB!B!B3N*3-:}ƿE1ZY֞m-`#Qk4\ =3`gTˉc%С un\rUG07x*Ĺ p˪ԄyKdNMPMCI-4 ?~͍*cyu( $B!B9,F=ʲ.Zi;@4yw,+deZ;RF9 c1m^7Ck9֡dc J^LkmC=UעoR(UQ??dyoO>$ڌcǎ?1iP<$2#*E$~bNu)_L F7 .\ vpuuN'B!B!.L& /4Mڍtҥ᰹k1Eti2U^K147hfl<' 0 3 !EBy_?\jc^X4:[cW\SrMiTebo>Ǐ?!RX,0ޡ6ٽO\朋=Y7ӗ6@ʲDUUPeB!B9l>GS׃s^"gN$4_wr9^Zp„:їjK^*ANo2f9J@*XΫir.Xn__qڵSN(2|*k>Te7G%Ȯ1_v勒6o܃vѴd9vM&L&eI !B!B-> 6f9\jsڎzK\xp(zbG J L`[luz#a1 ZmM״-9ڶ.4VWVʱ uр7''^iFyn.XZH Ɲ*;N.vwwYB!B!CF(* ۶ ڏ+ԟRm(}jEa=i":zvA'9\VJ}JߔK5 2c &W7hUa1c2:`?ɜPuP4 ~`ss3:裏W_!lnnݻ>AQXYYqE;wn4 "u?c >#ܺu UUɓty!_~7o޽{رcpΝ;>R:1 !B!Bڐ\5ڶ,z0'A/1Ғ,#A-0=:=&3Yx@/A>`8C)cǎagge.$|!<֛ wcΟk "mOwmڵk8w~D{H}>Ht\,q677Nu_y\~gϞ+^&WBUUNL&ЅJ/!B!Bׅ|f! AjPNraRI+8IQA` ;)2M7u"??'Tj>R ft\Lm6g]gϢl6n1B cd*]xQB۴Eaq?^/&yG1~ӟ_ԛG/3P-S !B!B8uvvvPM&n#5*i’BR4ssd.S&c>W;wt zdG &8۸ssùM6} 4D)UJ_qy>}먪 +W>c|wP/ߥKO( Dmacc#رc8wN<aaW⣏> _r/֑e5!B!Bak, W4] i=W*(kFx40]_BoR{ƽ|۸\p,ISAH*abk]8Ʒ{_*EmqqL4Ƈ/'`୷wlAP˗cXs=k׮a{{;PwѣG703}^ ^~%24htF4ϱi<>âs7{l@\]]i`4 >3ܼy[[[c>>D?X%C@!B!r()brQP`L bI k -Ns-ߩ`vNCpS U.F]8 Dn~ Z;g?Y9*EScL4h-\Bp`:ԉR-7Bc5򦹇|߮\b_~yTo~>Kt:6TE"g}cc }Fvww_+@P\},B!B!|=]H4Q؆V:b`Qa|SJZg AcyaժvEkv'<@hacƻ^GQqZc:*u-r2HGo_x"oƟ,y d%MӠ=ywѣxWn ׿+C[nE|[O(._Ν;??!ʲDW3^ 6>H(J゚T[C0wH6M|mh˲ WB!B!䰲XԘh&ʌĴN s_T a#`'DHo΀Ǘ4Z6*tpbw%`}Ce{r;!EZ[UU8(J a"JgP_[`1VC;q._7!p"o_=;}4nܸywbZzzPv&`-N:7o~yeY*kL& |B!B!&!!*^ZT{  NBH'gx WNbtf/?YӚeFR%ɩX__A۴( qr1rAiO=s>ڏvI?Ư(-޽{x7wpH(={ <lL‘CmX/ݻĉZ)ҋ' !B!Ba*M= ]9zNw~~ޔ%yּ˫;*+Lg;B9/r eYa@iX,C0|'(Ag!axo  39ض-㷿-fY8_X[[OS|l6CQ8z(.^o|x7{Zee.@ѣx{׶-L7^ѽpM]k1gB!B!#iMʀl@ԇ/蔙:) ~|yTʉeYzwŸw=r^ӕGw(Z A(|nwMv]1m`:ȑ#8wJN"ܐE(Ǜ? qcǎ:(^zipoO~-Rn}~ Uy5|{5<-\ 1@`6m= !B!rqڈIWA PPZ D*˪ֱ>< kMTJ܋ ZLSiUrzY2,ND쫕DUUWWWp1_mONepZSti-ظR<Y#.?et&W;"]mܸB_]hU UB!B!kmK],f/w>/R]2 վR i'Lڗ T \tZНhEQ@iִX[;m4-J U )%ң-qDTzdne[i IDATRs6&ؼzdB09&rXA]=wYjm2B!B!mÿi1H3.D#']ɗҎRˑRɿi8>ܣ;_R[;S>(w*813ڹ=,Uc)\+'ƩЗb˜ߠO ֮*T)B߿L=0uH~t2JC";®VYnD(MqRZ̒F5\iqZ~f-DUF0sAu]d2d2]H!B!5|?8}4ڃQ4B'Mjx+=w & ~K;6w0y>NwmRA.SlAt7Q_>nO#XyMZ*'ӈ!k[ӯFueBՖˬK$L?1u]c>cXB!B!_C>\_,ȥ&Ԓl\Kvƹ%c0 ԍgRgv"ݒKkE5~着1+n\r}uYXlhK/Hng=l:n(t)ӱf*X(Gt/B!B!|ˤ@+ִYrR`SKk3;ʜHҶoF5v/+u1Ú/>׺|UU*̥s Ug:Mru*5SSW*`Lqi2KޱKD@,u2Zb}EiMz 弽d)QB?y0N5 +RMEQl1oKd Mq䦦qc|ijsl4M0B!B!*b9HؔR.Bviy2'͑z!/-rY%x,2Kjeoo˝}@)N}Q(2|cn7k- e7'PEX>\xRXA JkbYDR:JN動9=_)(,i:B!B!ufeePU|5j.M~"_]ڛLb5( `KcXC0'ug·tV:sҵkL[uz5&Uc ӺTy2Y?X(eZ Ei\%R }iu";칇_ a&uBk]kB!B!ZuHH&W" ZLGo<&w- ?1uX q/gDЗ9=v"6m`a>ݦ{cξhi R w7K~uu'NO<ӧOKcCs9`R>PkX:I!B!B<=N1Lc6pjO]tQמ|Iy1,ɓ8(Zz>W;jY*kqהPȓP6hdm۠,J9{% Miڰb$TKnilooc{{W^?~K=fO5eJ|IRb`/n%p/b !B!BOcohXt\l13VbB~~>AjL}k<c9)1pi4)Azi;;߷0&4)>v|>6dN(˲}P*9-_z*VVVo{zܸg*"044Ce8=W/ ה#NIoM@x!!B!BאR u@ӴY-r),5uJ5hK[jJu?:X(ovaPAEy[(i~, U*`1_( L&h]`uN'X]YEA Ζxwq֭pΟg<(2ڌ 4Κy8oxQ0ximT,$rHbJ;50u- UUa}}u]c2D̄B!B!i:GZnжd@kLԪ.uydp׼d\@ rrJpLKCAӚ{Ϸ4z1"S#RW`[QַZkLSsܺ}ۅ 5Sѱ:e, }}} X,aS677q̙Z) \Ǒ4^i°k7^ 6eg.GS 9f3fPL!B!rmۢkuݹKV[ 2Y8Lw0\˙p=k Ӷh.e^~6+8\0pn^aN,&hr!b1$9D:ݻ~&ݻmf34M(cǎ… xGM4'X__oݻ//b6O?۷id2N83gɓHx]|gs4 ѣGq<!;dtF B!B!|gwQ7N J*wZHбAijFJ JnӀٴ, N$#z: ǣ^ Q΃ {Rֵ̳EOXXjImۢmZRwc0 5,k-,MgɉEQ@:8gىΛNկ~uõm]8w^}f24Q>_~%~ rmޥwm|xqi4Mwy`b;wΝ;W_}ǏGۥ=N9 !B!BNS*ӕ+[W{]Yͩ\loNToUW*(X7Ƅ?oO`4/"FBK=~~ z\5Cŝ۷Q.9o\F\6(Laq~{P7ܸq>Fˑs5֢{ܸq?񏱱9BYB!B!Pg$P97N+Msa}+2XK>sPoc9yB.v^w믓%~ƘТϏeyEQ,+m#,J+.ةc="5gN~o~Ǐq)Tώ=\r|~//~Eh] w뵵59r$|Fd2c,7Nƽ{p E/"ʲ /sΡ*㣏>g}9{xW5Y?B!B!jʲD (hUERÖ4ESg@@>8y?4ןE Z@$$R E>m#%װrqJOF,0NPUUJ*aoضm0?K.m[?6wLS<7qu{acc#;| M3677Q%ְcǎZc=TYx饗p]߸]ƮܫM|PB!B! \fk87zji2WDrE(U~JsJgs,Z4]¡t0j]DM(VNb>cEQ`:i^J?fLx/`EU8qO<N:%~?7o҄bT VeRVqʕp/_|9rOƓO>'N/7ۿ5[kl];!B!Bag:r[QY̯-դ&5Tȿ{K'cOk@q. C'^v'ei^ t.@o[]]b[ 'TN<R\|}CDu]/;.]l6͛7W\/.*EYb:b2 !B!B-XYQi?BCh&9~UZg|^Zk4M4xwwߩ؏  ]t:2!%1iILɾjWVv{{{Zjʉ˂>dUUa2D!,`޷z+,3)4:l6l*^{5bss[[[^({/|4I#|%B!B!&ff9F4X,J9|/l׋Ovw#E56ҟ0&ul{ շNE)ExnA"a2'nt|9N?? :j, | -*'Brݺu+ҥKx'/~z,᷽p{a?z9rO?t|gq_|ϟϾd(=3ӇB!B!*666\0%z#Bz0+ӗJa.3p>)< ..d9Z(ջƴG ڶ*JQ8{.dOCr49O-1`gg|I$s9:裏pQݻxsZpY8qG @۶{nŋq__/̙3XYY1;;;wnݺg}.\B!B!F: fxQ lL%{e"U)hћo77ُpNTL{2+yDXcU1HTJaeeu]LkM}7"K KS>|.}ݥ/1&|Gq޽^( |K{g"e6m0I!B!rκmH&ki>AiQW/R\BVK=LZ9L >҅2d&O4ocH/"+2Zklmm Ko*7ǃgpڵw1"j/c2W^ok׮=ԵicJ2!B!B9lmVRա W%XJ2iN˹y^UUKm\>(8yYr@ Y۴eYI5AUUXYYSO>k `<~UwaqO~?f(x/R /_G}ׯzǏO<{ a77uݻBUUӧc}}ZL&꫸s>3ܽ{!ceexGpIF16k.&Kx8蝀Z)N+/:?k ̤9z#o (H9iâ(A)pY٬/hC 2*J)Z)Ӎ^^y__g%)?<۶hU'mll駟^Vy?s Μ91"lbmkL!B!Éfywimc*cA(z1}0H70.N΍~P7E'&tٙ|/{%Jӿ4ЅTU{u]cQ5vЗ.WdtHUP!%"-)J&N8ZN(c-`{pe{r" 5$Be. ieL!B!C}?cMT(M+R1 K4lx wdxq'8j]3"- #WN~h1y,%%R̜fED `6;ɟ;(,|)o*Űn͎J'gI_B!B!\#~ж-~ॗ^n5݌QxVE®Þ۵qZP ՠE\֮0W5E: RK%!G8ܻw4 zc9VnP̶Gɉu'ӵz:θvV6BDtFèGU0֕(.7LB!B!׌#=~'0U7e0snkUEs dTz㮍oRr$PK@\ nvY@ȵ^tidGmk!660m"(|R<T[mB!B!\7whqSz˶l5g2]|%pTЬykBcֵ_hd˞~:eb̹ZrW9;=ɖfݞp8.F`dCHa>6yCAxfM0XkREL2F7=՚q4: !B!r]9??f`m]QR+j歚Vb|qU;~+py3}+]gZD:V]/ql6y7J NckeY}մ0W`vSNؗG[H|3sjZunU1g/!B!B/tX}>Wx&Qiuk:ݎ.a9FM log,LzTN:8>%0nr2Zr؊5R d@Y^g:VX[mh*oPo*k.m*ʛ|n4-ϱqv~k !B!BUWS:3Ѷ*4ukZ*,_G͹1 \nKZժghYTǦi?]VcD4lRr۶he)'8ezЫ7["[=}15Qf/U:dYI~q=ե;$cf69C\W՜B!B!|yq!lv3OkQV{jbiT5u쉃nBHNǭfs_.7bBtkc֨aks[Zl/J랄5b__ƍ6.?^WUc5y8˿[M!B!9o1EnϦMj)*e{Zu=+ʅ2-ia:_u~>iڶ˜Y_+ܧ{!`L7 rPsyuPjeYSumF}_BW:$EֽaL= F!B!rxm[v]NWEI[麮ZC]aW+9.lXf͑'"D9Sé ]ބ:tTZ}j8b}Rk>Voj3 .7Bn^?߯VӋic=gk"#/TB!B!\#0889dV.>`!8(5լ5^V3iwb-?Beb\B"4^ 漮M9l[v; IL,6rLoO)ODo} ׿uܾ}{FXWO"Y;^#fsEjȸHk67jB!B!\7vbs'X#I9]5k^nMιԾMz2%4ZI&.{*H8S$CPTUY (ᲶTmۯ\}~!߿~7ݻwꫯԵ"#C@!B!r-9x" ͖.Bjy 3jj20TM\Vʽc|я~??/o6?H=|B!B!#Zh! l"^٪SaDȓ`%N UMn;Bn^˓!{kNFU;f Id"mEsyZE%1)5N.&5g88єG֎ɓ?c/rEw7oΝ;;ݻw _ş֭[ˍ,aB!B!SY|߲ 瀶mc@)6TC m\*kp }Š XѢbL&ᝇ1̢\[WRSݮitTud/I ׼GL!$O)٨cZ ZiTr۶E۵(rf9~ݹs^x}?pw~w+`w'O߿//ӟsxw/mx)d}Ϧ&.7 B!BL0 Eӥ8꒠%ih[[jTrZCs.ke56^uqr)zDD4A9;/xU1fgC!8n8?? aYG|='.4^Z$Q1kh(2|&*YZ=cA88ֲ !B!B#94S`voנ-eTfcmT,L-9y7!kZC25M۔YStpaK-I[066-hǣ)U?r i75BH ( nJq|Bt& G7muh~o?nѣGE/ܼy?<>|g?n޼Yɓ"@^xjjB[6 !B!BZq E>Q+Oz?P:%97Znsd\='85wORmp{&g0gn99)ea-Fj'V'xƍU8;;9߿|;G+5U%?q h| ܾ}xW yJnۜ o{m۠m[: !B!r- aT߈G}LVsMD3n(=$7.htO> HWʘt`Q\ϊuX9{9@3"Wl6Yg\Ӵh&5VKwӄg+(T-!rZbӌBy]|~o}[W@܆6*{.+?01F{x׵mw}7=i!!B!BL?? _BV^v #!B!Bmڬ Ia-$C~!r| y Ihpl:*zJuXB\sEr{`\=hH{ZkJ,2XAꍯ嫰Ʉi9_/pyym //}K/cHhw8y/2~7>ݻီp書}T;޶- }>Cܿ!l6/!B!Bȵa?&]$kN*rH]cY\Ki2wխr]Ko]M,A-J RӸSNn9q7z̲+&`/mVɬb;AR=O? 9_?B0tfŲ&74*pLo}R#~dl6h۶j%B!BBHNaxB)c%V`:ND\8D:6_NMЈ}VcZӢg X\jTzr~8;;C۶۸sZl'$`oTC@jcڱO_KNN=kXAP ka"qT҆k翌#B!Bȵe}!BiQ6V&SMyksKAfT^s71U} ) o-N%nܻwZRЖ/ņb:bs\s^NouibVVE.b~!R=J!B!r8x =znWl WeGy6hӟ~kD%%OPvybؔ`{>&G'9'O}qDBXʬN}R@HAϯ5᭦?T:萿uHNE<W{!Zڶ8ٚI!B!rp{g/~񋸼Ր5OLc6WqժIyXxLtkc]usk5i;BvZSuh[6Etbw\7=ѵ$ݬQ~0@Z1rn鐂GJq,Qf9_!5ElB!B!א7 =>CG.T㖂\ijtP[ nIRRk ZQq^Z0e-l^Wo Èa|nkE.+\tPV $ lW% z+>~aBpJ0N"`P1Htri{W#hx<.HB!B!뀄qH([\ 5L N'KW X8{@9*`y*&!kUVnq#...fP~ ]HMu6CI4A& 8ﺯ_ڰӁozB\o_Z7խwIL#C@!B!r&3vE۶VQ֐fg԰kH):Z1xYѱlN: }kp6} F4m =anC+Vӎ5 Ǖ äqZMk18iD,ER5#+HܔJo4kYK!B!kM<}@F@Lg aPes\Y &$ǚAD eǪ38 yVa,B e~'vvMA;u5qJeķv,D+곝kWJbDDuv]^4"r97vqA9B!B!wvvaVB KHFU;*2N*[ErUI̴ڎ/5᩠jX.>}.yܳrlQMͭYQCp%Odӭ&ʒC+a&JWY[{|]ORfwO$B!Buwc3"xc ds?vvC!$bKʩހ>Jc6cyCͭg-k"`~euoUí?՗R_۶h6YK&B!Bu] +xݷ*t#?pbU/ڕN&dHTYnMd1Ĺ]%v0+&aPZͭ7αlҤPvb X 8 }3\yRõG?gNM-V !`>B!B!/ӶmOqc֓v# +kZKvjoמBaΈ=m ! gzcX^rfp0b(DI$q ;<~^U.߁|կ~wޕN=׶-p-Kxu]'~wLc⋸{nnҘ͋'1cglϿZKB!B!k]fw"AI 4^U~Vu!ĩ4)eT{zxW&MHq+GBκLલfh 0: dyqH:~RZ"Hɦ_eQ>Oh ÀaCg???7[oU3||[owީ]EZmۆ4Mfpvv"7%8B!B!\^s<8aJM"ެk l8By]A2Fm]Vuae6C!L!_?c\ir{4-릛maЖ1o$Ҧ 6WaG k_ßb.^_g} ]u9վ`=>}} B!Bnla˧88Ǭ^|xbꁗž:-^~,.[VL6-|UO9אon[ڹSƹl,"R8f4Mv'|o_ײ{6bKn4 ...p<:: !B!r-ax?/}iĮ60`DžJ!B!r]wZ}x<9>Wc[C6_ r 97JP@󩟡F*qCaD*8'foV8zI?ĈұKl\69Tz.EqPkoy]XcۇVDDO/93 ݜ9ҰN0ON]tXrY}7 ѣG8qƕ"kGpyy|L+X+@RB!B!\7$5)O"ޥI(Jw%=Xj3etqL qZˀ1@YJݑV`mru7T<~<~fYg3E֘%*}[:"޽*M vzPgggu?~nܸikmqM 2i37+J298td2ڶUǮ'06"m儔4f/YJҔb4(=nŌkP{`wBtXD 9;;+nW9/R3m{mgxݢjDB!B!;nl6l6֦=¬Y枀("z zﳶd .҃Ohp/J"0tȺ%|-Zk_^Zȩ~[,֯/5 qY=1^p)! ǡ_u"B!B!|ޑ` UZZb|bmM7;NYZ3i,t4ιlRtɯG܄5d]U("ha]p8d]u Qg]K=%}[ͭ)k"L*ݮ8~~~PKDoX> 7ol5Qf+VMJ7np8뺓HB!B!+0`x9[S"0(eCkOy0qq~vC۶!_L@wu2x|~YkU5YqG?*?s8;;CL!Ƭ 7hp1y5F*6~JHL!B!r]!{aJ!31,\sLd92"6©W7xIR)F~2oJ @?g4^x!qh'QUѭV=V}ՑŧT+sz-tm g,Gi>|7Z\$e숛^n˧OB!B!4 6 _jFLڼf?MX,6~Du#1okeډ' q(?`we> AL6]{}_hƣ RFվX+Z[d>jH<]?#Nm$suymH Z^o0"ĈqGV>n6yEbYi'6=j3=h;Oc^⟤Ŝ͛}6޽fS<]r|Uin1^_lzaG` M/YQ&S !B!riM:Ȩ5(AHa "bnl&&7,CDaoP;+]}IR{ʐS1ޫ|@r2:L=ˁ"T3#i]ۢF6۷q<q8hE*gh1W E~ߜ1Lj8ۼQ?.^5ݲad.{Ks1s/smܜ㕺lK !B!B}?LG6>z62z5hqkmmJnCd]kD\siΒ蛴)aܸ-f=,:hNV=Ek;??~sO-wՂr MYs-L.+#*D$) .zS>kXwb !B!rQ9(uLvJG)A/ʖlX0!ש A&aBO0dןTv]q=...R r@wɲ(!\sYg) /$ؗi5LW SCX;(/ԃS6"%@B!B!\OtJm1[GՌhz[HB<&<=UC=D߭eC@;g2wޚ۰`FC7!KܸqV"b'=Yu,؝*)Ń#>1>^-Y[Sg"f~ꥩaNJ08B!B!0 v=z~~C:*+tWykj3'k}Lժ5-u&WVf^z{_x?m2?"}n!ƈ܆O?E{vv/PKXNS54ZpQsCŸ9G2^'ka2Ԝr~"~aDR ӾB!B!ׄ|{Ý;wɓ 1V)eIGyqQk b֚ O.M1X-\k']r49 b۶x)zzP]LmQkk"юu<{<+a&,Zc_{ՇpQ&- #c"6ֲ^M!B!rxG0րt΂]sl-V3ɩ[f!QEtBU>π{pb hǣqT1o Wo$@zjTՋg]vmӬ-SnUاǹ +2ZoCbM۠m[lOzB!B!\'}m6hιjkaӷei3$ 8rL ad0yykC|R"Щ-b] L&whP!i\ {ƍU5KTs),s4n cTesƅ.Y3QY*\_ptqmRژNzj/EtbB!B!ב3㈶m1 C7t0+ N2ׯ:өy9.# mm=ZιlhkMZ4i}䜰U_K+TMaWxX)=[?8Ȇ_֦i?!B!BuO>zgY)z5j:} PF FEjzy0)bncGT,uo"7d_}՛9>o-2Z_Mx S΅sG6eoDˆr,,/$WԄB!B!G) 0躥0qCq,JH:*ZմٳFڽҲj"㩹kU m8"3ʼn* Z3E]ƈv ZOCU~2C_>)Y-bCR}٠m[ B!B$iBunBu]uCĸY뱭r?kb^C= VPG:Gkm]I-"mB!B!ׁfvaׄSnZ^sYОnQ3r͹9S㜺{@۶-WSardX/Z!V=f7jV)b9yaL_ArE}ϸ( UܪЧdS[/4MnH!B!r(մVmeOokeYVi3%vtU4e:2A(s;d\CqqyyRC 9VyG#DYt^U|}WEn=!{8۴u#̙2xZS!ʤM~8]aӧ8'_B!B!)aw~ws@EKf.-ժ6k r|I'r( ڸL(8ZH X: \ښ7Ā79Ecas819.=67fUb[pS{ T< Sh6M.UǴu]Q}qH&=ܺu3 $_}ūIänHښi=+7c>oRx CjcgT1ozY VI928k8b B!B!׉Ǐv8YNWB?a(۩ l5cuf hClu{u19#MlUBD*3YUa80ÇhFHrպ ip UǠGc64WaV)ez7bqOw!몝_kP{l[t] M'iB!B!r8??EQA `j E{֭j*_;9 ^Z*tzsMas70"IkA8Kz6M 9!D{D\-0ʺXEY鳖vWך, _mΩ eZGM/j5vmVhw.//twoB!B!8ZYr?+@え"< 6dM 9pze \υNt0UY,^#87 q8?;K! p(k6+ٸt]i0&Ua`Qu?:F)$H$J̉ȄB!B!#6=2(lyPg"& [0^zV L2r^vks*uˀ"]R*z(0m;J]1#ΧO6xi*. NYsɁ7Vk@}ll`٬QlzClp0ՀbkM%ky0LJm!mqmCi`U^io@B!B!\KqLcߣj5qQfL sTsmV (;`՚X+H0B!B!דp=˹mlpJ-0yxsͅڗMgu+qY7Dj95=w,LQBi>5iYZ0s0?|)5RGK;w"nJz)+"t:Rƈ洒 *bʽC o=6MN}װ҂@B!B!\Ca~nx,v+SU+ڻOWr y,Ӌ{E:_rSg[H>V&qZsM[i[lܹ..nmX_MIH7ՋU9 y s.g'1Pk%kӢ~qD~a.G B!BȵCĴiJ̽g04Mc4ZL=)cZ5+()cSohR_ 'o8;.4#˜ΕTRm?fV=Z(55ok"7+(s\yȧE2<u1U^K }_ly诿?%vޡ:4M-B!B!\f%.vQܖ/Q,1mMD^Mִ%JToʺ"k%)]ksU4q4 1mעnRKV]qBӃ̿O kʬlP&X1Y~O>$k4B#[ocPB!B! B=iŸtY?~m^)ϵdC5*sj#[!N; IDAT)9LU$9~#oiyv6ֵm {}^I,2z\`׶mGY%u'הdԽtO?P#._Dn:rHEJl6o*MĖt B!B!亱nqEnn\o^Ղl=` E:eʴMe6Hֶ>gw|ifJ r7nm[l[xqvv],*܀vsirY09=nD>G]_K?5IBqh!sgaWH -7 ڶśo](NB!B!p~0 UQN,;!TE?-Y(Cm^i̮Z\'=Rƃܻ֓U19w*A$SiR'pΥ`93l&CŪqj}7'6}ڧl\)8!"S;~CJMMcVax&QB!B!6iɿKY Ԍ8?`ibVc \#c=*ڦ-4]yU BտDOׇW%gSZUzH~Q=,{7sreEPf~7 sx7q8jVB!B!+&u5I9$~R}CbE~$cȨmVv*A*BkږnWjbrfc0 y&ZEDVܩZ KQy\_w7lR8kK8mhۻM:7 " Y4لBTW@FsJ|r ]U{8\[;֛e'&-6M=,]j&1ib#/YMZwm?K XѶm~_}U}v]W7mzunjF|(l֑7TpKVpdyBZ jȸMdSW_yT}mxx"e޸% !B!B?8/˪s8o$^co^Ź b5iSZ`U̜B}l) E=?=JFhJ;A!УoN! ~EYJn*ԎiU@z@OrsYțUT;=!e \R=zdU!Mm~tB!B!~ À8]SYכY o`59JOJ;lgޡ fku6>(Օ`+$b* \KeIdDMX\;#01afO"_KoA=^TBHq !B!B}ߣi[qDӶfJvIՋpF4bqtn,_JUӹtΨCS)ˍE!}b~!u5^*X$l B!B'F, i6<$*&&͆0Wڮ]{W_ix\ qY @j#Vk2]\\O>A(5u0+L$\x3=jj[n^ k3mhX4M!=@hSCBhlDt}X<75B!B! AD\h7O Xg^r\cr[y,WhZH6] 1;,X?juWm{I,\eKIV=+ZND{!B!B5c4M ]R~ 4Ioa^5 iIM3v4]49tspFkMZVyz~Zw%z\"|: e6m[caukiZKOmE ]Aϋ+܆ ޤ.^$S~ro&hU; ayQv B!B!\'=>}~~s",]Y19`) fo+)~\xIiaIէcK@ z-A$08JZ#C@fmbRx cZj/w!NE+j'`=:B!B!r1x<&!00"eВ^B)oxJ)=8ƈ1F p-İ*4g{Tm[jqSoXs(̬)TN:ܼu 4W("]Y+MK,u*-d  7p)!4P(ԭnf0;fǎ[d{"3nnQ=Wϛv6-h3c0AL~X,bVԥ'wRލ9V]ŗҴx#B!BS{|WMgU;bՂȫu}]x#l(ÊxeKb}ֿ$j\Uؾ#=mz[ sԴ'_Ѱ$|{\߽˗->C tԶr37O.enj?/%r\k9>Gkg|3+ciN݋2Ru6=(X?Wr9B!B!vo[C%t$NN@]sЋDtۮ8M}V>ou#W~t#ВV'Q6=uA뱾zWS_^^$nH)oחģ ',PNz 9uٚf=}o&ncm.UiN@B}Sx~hlT9<gΖL{#5T]o!B!B#%&I X B ضm^ӌpYw;%0LlNt&LZgGSR9蟇]Ãb&{ݻ~.޽b6kl[DܶmWwjqwrCUwUXtFvhdC B?sQG S:)چto!jۥ3su+R!B!B˶r8\.6͟4[E6W]uwծ+Un_ġ|kpn%O~8]*Ҵ1ng?y]~}߃s(u8Y~jJȭ7wvN*ڹ|xRBs }Z8Ϫ%ElJc60Sk#~?~kdzmց(ȼ@@OkB!B!g#6ǑPK>`6z'P\U~ [_Dޞ+aNg']o:fDmMz=f6O3NȜض { E/~(V ,De'&ސ"]CiBE.^ aF}VYnȃWJNuydE<: B!B!nx}}+n}nWN|7 v k&'c&ĵ1jhҒ> :Y/ޙ⏶%Gň}8{+qxZiG}VMm&_fQb :+N$٩̱mAUsڼOǢ4?/L7A2Oתd7Zve^+ذJسnv7SeczP' n)wt2?38\r%B!Bȓr[/ԩ ♞'Xaj.t) UT(N\xNMKA:+g?}NNZB\X=(":ف;O)wgPDA|_-,똳$eEM|xyyߕ?|_򗈗e zqgJGD; j^d g?W/zlED=nȾ6f}C[oQhWs=cڗR 1!B!B3!<'=f 2SrKX>NJ7f]JDZy둑Y)L"5ѧF(\YZG*P)H;;n7WUgaVZ<in9aVafl< {SXE'5V9gu{h{nf]!O-3Mn(L!B!SEZl ~OUJ-@hJGF`Kq3uPWП-JLcZy7ZZ% ;x_FjPJjn wqd\NB0?.ٴv64hYc9J;ZzB!B!䙸n3Zz'ak!pعʒ<Wފo1ٵ̵zUS;1%:: oW—>00WYa.p*Ԁ|w: Sݲ }J[5Y1ï91KI):H8s؏O .B!B!OO)_ ///8';WZR7Y]6$/|uS۔@5ڕ>~Mk_zp=Tt/kReTD IDAT#bضǮ_ n۶PYjkL[)@zulĴQJe!OOma[VUr9mb^)l:. K@!B!ч;Pv]t ~޵㮄GƭU!>Σk< qd =զi'nD_ǩ -VYkk ?W㋈r=*"7=x1iUc]r$B!B!<HL*CFimuLn~(iӪGh5);hޡ6@qm_^^Nϋb1j;nJQ]|WtQoY'ymofFe`]k> B!B!f۶]~o#Z:XNJ~ muccDzX.nPJǁG*߶5aZ}\~pM&e$FtkO_~Q?+YEz$q^\./!B!Bg3HqJ4щu7hlzq.\1o]4| AVzi1.T݃n=򗿬`+;F-Vya`.υӮ4RZԶsiRRsKzq47[lAI ȪW~}{}T+gYf%C;gEǮ?<.8',v?ONG[2e (!Mң:gB!B!l PWJF"79Q FR0iXU:RчG iNWND}l?Wd"+*zD|FKƶmըQ8CTJee;C9([Ļ5޻}PNn+4qJP9y7ήc&[]v(jQu>@B!B!36qjC<@ ;赏~Y9N䨱c=}v5[n@|yy꩙WNRj6]X떝kM *MQ GrE˷֫ҢIbƮv(3A~B!B!πwǾ`Wkv"ZI5z$02Z9Sž.k(%Ӥ=خ&MW\P)r~@>#~I.ފ[V}C]1nyJA $f!oZK)(JzEa֛T᚜PɔQ0iZ?D:CN!B!l8cB*FY-^⫆ER$9oFu u|֊tr.<r"oqa{ Z\ŰGJ{m9y@ck˔XfQrb2` l6n%O2E 3|4(DՍyKavqԛ" 8B!B!τw=|-tS^rBTOC`wgR++2ӯ:ZIs^W< z:< -At$cXkm߽{3bwƕYܸܳ J@CuaDijn>03oU!LJ6ʢ܄~kcSчiaR^m/b!B!BRJDؖN>?9~+CpNNkZf Pҹ5լvL:Y=e녈ޝfK)x!`6#x]mJo٠Q3N' h 5FeenDl.؛b9Ȥ+*}wzX02kVJ>8^Ņ !B!B =Bp;m;EcEX0}CoX*ce.֪V P\FRƳ^YMȾs{isBͷH]X2Iݠ(J$l,M䳍mscԍYgekz{ι%83:L!B!S%FT歑j֯DW:Gv.Q^9TFŭӚӉO+tG^Hws8.ܾW\^B81FpH9ujuA9XDMoS5IuM*nSTMm쪯?6ϦӃZBUv>- 1O$HZ+*K@!B!wn~Wqa9ZNg咧1n:+cϚEw.7Js[: Y)r%Y8cDVec8|yڨ,msZ;{iuM7ǶʹZ)ޕER߬s/ #*bk>YejL !B!B˶Ç]+@KIx0hM&P2FnGL]"՟a#\wΟĵS+ff$` )Wm؝mf/VnjpLN9]ԱHjES_ȰDb*x*j3Wm=?P?"zyR:B+8-nB!B!< }zImOf Ugr7嬓P9wp +qt fw.E 33q{1H,QK\qZꂬms5?PObȶe%Tmg׽j^d+c N-\o.a!B!ByF|x}}q]בQjr۵Ys}0J6i{#OՊtX:=!X̕LkP@3YۢYX ~ҼP˗(BŖ>'k1NkB%37]_d\DZ5+Uv^}W6_2J^ڹha1%f?O!B!,|?~4EhVJ׉&'^]R8}\kh1rz&kﯩ5ktms[H.6\ }߱mrʸ%:4 ĩDO7!,uy䢫(yڜZ|+hsڮo|fVh3K)>eۺuƬ !B!B تM]JZR&ccU)uw~JxHQ)(%u cY=Ѳλ^k}߀(>xowx3_HfqVD'nAl&Pm|!)Log;݊(8@ Y+ҍ{ 1i"ғTM!B!LgwG18&mTCt@}ǢLbi|[J(5 sxBaWeZ/5cS#~.?񏈽rRRUV}qbd/Ꜷ[8HV}W 6μZ zCҺͯ"B!B!>}j$o6syj~rp&kH6 [}Ӵ!L$R RU'JZF)-^{  _pC-|mȢ6rƒ3J؛W%AD4p(%8dԪL۞b裸c5z!i^\Vu E B!Bۭj[Hfo: M]u6xVbW=/PuWi*Ws:UC{Tȫ%wͭLMY}b1>!xB튺 pfzKPQT J.HCL9ƤȪ8y*m[m8W-Ex~pڬmXD~MW5YE B!B2cs~h;Ej&z-+-j_#z h= u;GW`ZyώQ@"jn K[ΥQ @$.W_uGINh6yڭ'pQW0{xQBUfRBݧs.}SrNmZ6b_Wg6wVjGNAB!B!:9W8&&r99WΦ;,zm5l*ӆՆ kgF6ۀbg "ڙh_4El[n_<3{,mGsupc/!B!By6}+|kZt+MQ%b*b~콘U'Y{:S4iV#l3P(SuU3bu~{w FCL"^W"ƀx4 Qb! EJ6o-vEyy1Wa1qX,B[RvaMs:nF`RIh!#z-ލ}^;n}ǾB!B!qx}y VQJ-ՙK;O>}hJ+M![ 6&*XLwSO֮?$,% ![ ޽÷ÇEYsek|):+=l l]V=UBrM89T!~kݝ,Z< >ض !B!Bo_ 5oIQv=*?x:k[aa-ÎSW tUΊQ(RMg3JՄVJךr]}PFŋw).j3U!Jx'[퓹̫g:;\ѪXǐcRm`\2 RrB!B!vtڌ&]~X#_$@տouAxiϯ?Z*j3WSj+̺ĽD bWUSK[c JG.^y'n~%ޜUyG=v}p}pPYO?~= 8gRW/#qsDס݈B!B!φ۶f$6Fl ̩/PK4g&-OLA4ĥ DǬYӫԈ/?r{4(;rAIהSOG)(h l3ڧ|+ӦO"Yn[C5m!QCz~hrEt8ՐHNdB!B!*L`$eU|QqoqVƹrڴϨ"t~=+k-\oGJc9bme /Mz2vvF4؍=?e%uaU;-Ɠ9=x%prf5r`B!B!<+_5>VN6`1j;;2Ҡ" 8V/^^e:VS0h<=>~BB fSf_`)QW;OVESt9WkjR 6]+ܹ$Ǎ0,>I9->ɜrlSun`˾K#B!Bȳ;p{Քڬ?(6 Pyҽ|ˤzV.?@p8k?o5?Ni {/]k ˢȣמVYk͘sWas?6yUץ֟q2'~Ɗ5UW {#8SH݃}O14B!B!?URJzTr9tgJc)c_lu=yl@;6n[.}k r7bbE5~9W[© "\5b1Ommի'l0MijP(kpDK1e:ܾ8e;^6RB!B!Y)r{ߵGմ`ق?әX^˱v5R#^|svF v_矸!3~me9%Mo(5Rt!6o M̳kQa8!@iU4l@'mMtqQf s3Rv1n>?}q'!!B!B3ݻi ]U[S!͢Mbo[** WHM!ʵ3tԦ/-`R+\ {;e%tndk@m~Pxzk>V>^yWܚnڄyCbn= D[8uu?/o]lBo չ3.KÙ(r .j?=+)B!B!s[͚DDlZu:[c)cYe3שSv{{^+#}ǟg\.}߇uϪkڜkk2) ۋ>If]W^:\rub yV'BV7 ĊkX` E48n[v)cޜQmɺl\h6HC.?e9]=(⩳܄B!B!+?_|AJ\KIj$t>ttVvNš֪=Z)O[40BU<*1ׄHq6Y86p!.Vd^וRRn3\Nп3_dEKMu-[f\o| ,kwHo"EXT,_Z{iJ qz+ !B!BwӧO]gߣ`8֣fd2ڔ>>ݷ[{XT}t]>]ix*2^~C) ?kIXC*7xo߫PLdcxx*7A?|SN$u haqaaI,oڸjm3ʰ̶B!B!?u$fWcgiqO3ңhԶO^ Qro]c[fr q~2ߺ??|?F1k.aj;fbNMY(n;XwVc5zh=e8<\ 0$JȆ*,"’ @NiګN!B!B)qQ" YZLVB]~JyxٓGl{)igul#e/]ڙq$yZCr"猏?W_}(4[H1uɵm+I@1KU5-]AGH)λ~kl@-܍]|P%%em-1|z\_7+R76=UN,!B!BӲ;^{7^΁tA')bgKDr'Ûӣ1gOJ: }IV7 j'x EDD=˥__%+X+c1sR1J:(Ey疍M+0E\=GiqDᅸ*Ys뷪 ^a ǒU1p4B!B!H.WV] ļf U-yl)CZŠ#™(hsJ2<#z(\)}8+7!>|_\'Z5TP%%ݘ*7rwd>ou~^_Eŀ(y*bKm=>;kYI+ vr9y9d]@^TsB!B!< {T0)I_N1i_C1=X5v۪Sm5.BZ9\Mj;}osK-Bi V䝊~e1}!nu#;.S!;N{p5Sb@.Ɨ\Sulg5lB!B!grڊ)RU(SE8WjMߠ@&d m"QyB77=Jœ;QfYJՎ^u %+G}Qj{ nWGۆ- ~/`~L((^)d.a(8a@ƯXs(LrFi3\-(jQta{q@UAUnB!B!<1F\.rdju!-g[)+DZ8zHǐ5:?2:܏-h-rz/Ƶ5mۆ#%\gn n7M5jNb2O2>xrqppA ͆Meك%#NlǏ C]tSߨ몫mBhAND2k< {R!jܘUZ_B!B!ϟ7|y~o3ГG߬'3VߓyR7}F:QYx7 :߀N{SwGF)nv۶n&1/˜1OlXK=.Ln̹oG6ec(z{{&[fi糸v NP YŐwy8d*y3~qS !B!Bȳp\ݻފG-zuiMs+ЩV[=_ rs7YsZ_gk.ɹlWk BO]72][ |jO߿* ~ĔrN261t;?7Y6K@|jpU\Ppi>g$@ƷYUخe5y];&K{F|X4CAqMA1 B!Bs)-‰csJKs׵1IICMmr\x;-ƹXϖJ;xYo$:ZS ԭQET͵ǘ?7f2 ZVҝv ڵi34xهZ=(9`B!B!HJ% k~?9t}M s896%E4d~"(ZQ1#Z[JL)O$?F)%%j77ބ02E͜ 3DH,UM}Eӱ/bZܼLsBݷdה2Pm2WaR().妈eZ< !B!Bw8RzfYtJtѪ.puη[3mr=pډ`'F- Z0|n>zD沈r>; peR{n7>nwzrٖw42M 9wH2򻈆N5﵇-O\r9l! ingB!B!)aMW uU+-+i0מvY7t8;UH]\)UCyMDwSJ#w-dqM_'r51yPb*@yXR/v}4@M*i\WڵpHn r}}g !B!BFCA1Dp7Q|~mjY{!Z(T~t* QҲ+C]G"x9۶ )mE}cFP?!5M5Rgere  ?7ϵub߄C pk"77+ R2(솪m{#B!ByJD1"8rK8wO, YҬ[?F鸬6e圻X?8Ďh7m2řt͵/a[j]Wa)%qg_zpص Ly춑!-a)\Ŧ) c.`[.4reʍM7V k !B!E hWYzVcL^)> q(p;h1{@E|彀0TAuF :RR\XNK c[]6\ytOt˥/rWVI/cmX'Q0z›ۆ&i;h[dA u DkŸwpg|mB!B!HG7PpG.GcWME7"=}Μ׺\r˹~i{/LZQV+B`Fyἇ!믿F9WwbtV壂i:7JFjPC٠ޫs9ZcڧWp4ztqsK\|]Fu4!"GC* !B!B~\W| T͵y\q)힅6K/~kzԫi_KǺ2u<&\$ڵz]b&;}Panۆ#8RB)%C b@2Ms*NKQ—\AK.i(ckŋsu̸kO7)zwSMsu7읾賾>ׇ?|H!B!,(Rq8c24eAk̈́Um<!pкu_Iיc4G] q{$K;Ǐawobye]rjգ:O6k=Stp衇1'p@7(8àsP\@ɀGWp<4켃w% B!ByJd_)eB6J!`-ډ`68hšJ;c!VhX:nE.9/KVz0itI܉!\öDZfJyՅj:NGew"9jƕ:J9&M?c/g=,&*]VQ ^=Z{XCh ã~eS&fk쯄B!B!πuo[Buʕ#e6:[Zg;$DBzUΈ*:ˏ{~2IcqxF+v)6SO)v#81n~8[uy%itwS}Wxg;#%i7aw9sT4h9ʁj{NKRGXZ鈴Ҙh3!B!B3pn[7Fɿh' 7eu79DyS7F<LvR۶mW8(&Ǟvsf?‹Ѯ9r}cPER ^o^1K#f(Ra6#3^DUU7VLf%iUq{ NZSs:妷 ~tcz>jh5׸~6{B!B!pu>Ie5)֫{)>NMZΊr@ղr>'0u|],|N{U\Js]5*ci/K_ۆmǿ_\g%҂"CkV.*p95( ,!a1SZ(1?̦cx8/믢ǢX 9߈٪9f7hucz;ѫي&nrS$B!BbX¶m:PӬ62]Z`bn9 AvϺj%pPR:95uA4E>mUw_"tT Nf&Nm#ۮZ%6jn>nvTNyJpO1c ~yrics[qVu RN%u f>l,HLB!B!<96/~߻xUlU{ego_yDQbt,F4M[hGbJ*l'|TҪ][^CT]B/ !K]XB޶EqH{/p$ےlΥ o9y8lUm|RZqpݧO܍Y )Zm}{p컲zcǎ֕`B!B!grݻwm;^g$6f*P_A;u4}}^ au<f ,Hkȹ_5qr+dIqӷ$uZ4ɡW6K˭E!w۫Nu(>)/N.扼y&|lfZsFR7!xN^B!B!< ށУM-*2z M>-)rk9cPb9i6<6& 8L YOx4mY=b0+:Oٜ2+ƈq ^^^{| QT\ \ގ&z+cۗFכx6xK=fU "&fCӝiSBFqA{nݘ!X9_\al irV pyD# uڻU?K|\ JFlʬS9A!B!ByV WrKRNZqj9KƨR;"q(ovj$ۤ-d}Eit&aŋ9 =.ۆA<)h0¦zgDTipi7lY{s͚)syD3{/V cVu0J@2{#M2]lOKkp>-B!ByFn^__OqFRcDOo1Sk=6;l4W$XoG9}>J{{6Vzo[WS;^^^G///x,tV(M>}hk DwW畊cl=7Jyj[qZBld)!bϴl RK"U$Qhz/LQaB!B!gC"miz@Z'L-+X}L }$#&ۉg8p\pa+\t:rk~R༇+"prڣXř2\_|%ط/ z\)!ITY=R3 !B!}"b;b8bVHe㢞/1Is[m[]˜67ĕIԟ@3W1z'!}3^1"X[[}B؄\ieg- ڍxm׾W l=Jr@סYV(]l+ꇪjFC)@{yzFl!gi]?s}(~X_^^6B!B!qTQMGmZR;l,VбUVqR,?QM_oZ%#ܣ:Bfcnu'8w{ι'*CJ?L+I#}isIv5ɅUbp#*:6FE\u#:()(Eu} #ڹFIiǰ撑SFΩ+جZ+RB!B!l!G:qfǶ-\练O~+(5YڄF} xKn^xiꔨ>WʩhaTI(-_|10-fE"Iծ|[^Q[k#3(M+fu@[Ϙ'a{}zpr=B]"+7?H# ay 'B!Byr)H)dg.&u=%E4y8?>vKclMB\f\y1֥GّpزwsZG:A[gH wb5q{^Zȹ+0hRpz"_ .oLqݺ<[olY8=veDss%b(Ҕ5'B!By&ض7l[D) eXi8U)tumBl$1w69*f܏%ZЃZkdBjnHm._]?~ bNE9T6dDÛ Z|(Y甭RuMeĄ}C˔>z7猒JdV 0毡N2:p1(0Yܜ˶Q#3!B!BO8uJDұ^X()Ù#Z0 )mNAn;Pp@8XSKMJ7-eG9@s 0T6b=5OTZsu2 <\gy 7QB`G Q\7mltYf.YyVM.ιpɝnqc {oI"DŽB!B!?ed,ZJ6 :$[w`%= q9m|خ+o?9{^aOiXVxvl9g"5eC<u=MG醚x&lB>MBbxP)n&,mR!zOymLmK z_;!B!Bȳcz V~څ5?FMUBUTӶm nÎIQ&2rv3nrIqUs-My*{P#]t?R8///H9!_/Kk6d9K=f2QbלvCl߯[dEz0Bf[ǷIbl/V @;݈zC׫Ɠs<To5OiВOYśuJoEV"m[_Ǿ={;ww\߽`ߴ"?)hm ,e3UPylȓRl\!Evӵoub4A6SUB"+;D3ܝU7 82&B!B)s^q\p@dqkt,qDLVl6MTd&K)=Ԡb(qWwSIN@XÎh=;3xqۺ ۟۾FDQ m罛J2DV ln9X1EJ$<}E6^s:u'p7dqe㨹:O@B!B!<#FY,x(H4 ٮmUә`|:{++5 '5J.˭K;0 |OzLR Qwl׆//K.7\ƀT$`C2 c\5Ec\9S Y:~nmjb--,uo$gDֲqü+/m!TWn~T)M!B!\&5Ț ~")s$_9ne_a]yӚZy^ߪo6zO~dYȋ1< Ĝ1CoexsF}|\'DW|ԖKmA4PQkK*cl. <(IZ4o7AМs%ѕcy}Sx? N7Q"5?b:ƬKO!B!Bmp^kbVpӳDM !lErS1ȣ6hXMhu')Ҧqy?F9;"r%lmnkf J2[sr(|_!)ud5S~n:{f/&-yp8ϓc˞Hrɭd}煴mb16p^N+)Q78I̮6zqyXaE+LÄB!B!}qq߫r̂3g-o5kzez+faLsuq[0Jw9r1£߯}Wlۆ_7Qѫm!Jo)W3E dqeg=<6(}``- y|R{va:0{Wj}~?? ΄B!B!vRJx}}RIכD@T{6odfzc(EدwN'^pP\[]:WлЋ'GmBx??#/ʹ&`̖n~r}ӛєNblkYU~.(jU;P:1[,V)Ms-Yo>8-m!B!Bgkk8Z̪0֋pqkKT>iOwUv. DzpbM^ wܫ0i/@wRɷ6YWlU83XNǴO/X,~\rob-[.XF^ܾ@.]׫A+vOB!B!φ%- Dr;krDRHX؋kF\c]>U\:'gٷ;R cD.OSM@S6Cty+n9c:gjOXfӑ3@L ),4Ŭ$}czҿ23+KҶ?W;b˶s.ܲoUWs--M1a8ǎ1= 1F:YTq{/ y&B!B!ُRxEB- Vah }"c.iA!#|JջZy-9ͧxRJRx<ض )Лzm!U ]}qFg.;;c K0>E?+MwuwpX~% i"g0wI@v-)=mk! 7'kB!B!ömRJ:H7^i)klv)>86$=s {ms*4n+A/>jcjZ|wFGmC7B^QN 9z1jêqڪ~ -M5"d.>@aK,D蜎ȋY]`4wdRXlbhܣDŽB!B!/_/8|'mi2Vm^3s]TQl$֝ˌ_mb-W LejZw"ZBծLv!"nۆ^_^li1Oκզ^rū,Ҏa`kG͐_}x^4qRЀ>[Rƅ1=(B!B!~?໷7|fH,Is`h,޻Fii>! 濷󿯎õƙ=scI\CލuӅvQ_JiCc &5=[%ik4%+GqE/P/> \K=ӭPoˏ̴k-K[GtM\3r0l1}9ChU!)I!4ŻPB!B!Oƾ~s*l^y- a~]j&>^Eu9| wW./Pn܆9koNB[9!sm#}v*~tk/bh9pvx>AUO"<;{%8+h5O2S} UtǕcPRP\?B!B!jn>K=?oS,k⚴_Jq7?&)j8+:4-#80DMu5/3kDr{um۰s=;g]=W 8YVqݞ&}+kXkk$j}4nזQM! JQliCJ 3)&B!B!|L;wf?׎x.5m*ii+7w&/(rךU}'Cy4Z톖wvӊsp~< Q`;oJ 1D4\yVfѮҾfrض)"S>>,MERzgk~v?LNԴY梥ԛ6noѴ=B!B"LU/O^e2|y]eu+]66Ύtx݌LKDzѬ1!>nMl1DVkhNf-]#~|EEt ~rKVF`nxk8/KUWo܏) sTF~Ĉ-7̈[5dXsUF+OSdex'B!B!3a)mHi?\FKXGCN.x}lL]^)kdDscŅ ӼcӧzsTݬ}*Xx1P|QJA.Tyw^#lz,mDm뷩X" -#-*(3њSDAI&g`u!LBmvIq7N1!uH%B!B ;|a4\]6'Ys)mLW~$.Xym89nªLiO ‹ j$sGn*Z*UDXR/@UD !4 L;8[#;Zz"M.K6w`v-,ж(p]߃qS+AAk<ޒ\JiUϥ;VZLjh%!B!Bg ©#!xV b#h.ܬt3V*MC=KMv IDAT7>ֵjS>sV#?R.DްW66֍+'mtUf-#g &z+B,VW[ +poU-1vu !B!BgׯO&9WZ17憧8udȪ5'[n0Eعjf֯k[?z"ֶ"m )rPׅ@7O] `""Zmi ZX@ h7[E8pq-lVn[ӱ=D~%bNq_B!B!톔} QL[]Cj)uޚ.Ix`xW:pj~"0{~=W]gW^s~+{UzbDw$fm%)] `1Woěۆ-Gn y~N㮠X-#:EQJ"Y4^CaztoUkmM&Hv3+ʒK ,-$B!BLa?h.<8k5Pʆ^U`$XCGmQq% \WBp2’NJz1zKwmh#zN֚woPU<)tru=N|;[X{Y)IKu7[S\o} OAؔOkm1£ܮM6@Q m3U8p䌜|GVn gB!B!cFN7YySΗh׮VΎBo޵cvrRĤŚ5?@`| NO\(3IW@]c;NMɘ+,A2L!B!HJ /wՕxuvuw Xл~c4;oK.㶓'Cν8&1ߗkk愭#/Wd@J)%|Z 99Ub4bh},FSK{ږz1enشsIsY(16rS @B!B!|F}Z G&)H jQsMAl!L28E8&mR?2Ŗ #׏cH_ewh,e94oxZ2V :K+Fwaaip"Y"/.2n{:9M|&v[go~Xw*b}s'B!BlEb 7''WG+qA?"|+auu[صxI_a=h߉1n1@KMRW5///դVZӭ “U6omf\JWTGEl oH--n] xnY88z:N\KJbP̤mPJ@{'j?b6B!B!^_^ZpG&OcJNk^hX59mjL-xf2q%#ԏ*h[L٥Z™L7Ϯ%8 q$-Cn(Ʀi)E67v봾y%&ҭDsMiТ?`3[*.5j8=~N>XPF׫WU8pq#B!Bt|?|!5%[sXĻ `"fquk"4^ADH{O b&V>놰YFy6tc/*_-t U}bxt<&D});5^T\_hU=,US;PGp]p|}b}SpbUCKqZ4K?z: !B!B>///TxtSkABV}G{i-}/CNE^Ǒ(wt\rR M *dمWlMp^ )%䜱m[O@J:"*.tqmRoIL;Mg֩k ;Wm̀LhX mN;]XAE.uocn/5\=xP~r̪9}4YЋkf\gf?Nbj+ as\C;J)S x'|ےJ!B!kz|C(r.eKIV[oZF]Jbxv*"S^+EkP`1 ۶54CBؑsK@=Bw_WL5mIsE@YR6]u1s"F+QHTE)&G _~F ы4%}uVO"Y皲Y@gVG/ )r:2ErmUla*2ݶ@kHw #l+HK)%O"s5웆|B!Btl K/0Ls2CLj4,E/]sk%R[i^!Liֵ: SY&N q03ax; An^^_dVz֑9V_=qck^[Mgj蛄GnX 7By:%rMS-h@-($m d k3 =dv4"5G51P"Df 톔@!B!OG)9WӾ1 @+!Աkr! -*Q9 sNxJ>}jՑlϛ[ou2ӘLG굟u/ݘt';~3~&كap^ܫKj" JraѤ{LԢƲ,|Ryj*Jy!ZTj/Z>gh;Ǿ}B!B!3ųmKҤʘWx!N\CUb-g2a$Wb:`hB:[ǎSiecX JcqgzЎYZLy`_ޡH}Q&-gLB ]Mlf޳}=s5ɺjͺizc@lYzYz^%ƩzrMbc.TD5t3 !B! 1/09 saf#j xnhRP7}-@;=Ҝ\dů(Ah:iI7߯sEa{{#Zؾs")W<OH~LU޽gfkOU!: ëgJU6x}oh/3QǀE~.]÷6BTT{;iu顱VU$B!BȧvPct)n^a_ˡRdMYyG {*>~Y*٘;];vJ¨6E;oJ ݌qvCn&حx YV<7ӯ}m8T⺦Uƹ63enp(q8.kYP8nB!Btǁlbp]pM{,Î{0M~ fue m C0uZ0 ~}Lg?Vz:O:fU~΋%@Jiyf[]3&ڟOb=+Vj;mңWqd= vۭXC: F !B!XuWbI+ӗEgfy]JB7dĻǟ_K?*o?3~۞Yl8猗|w+~쭜Bu"E Ct<B!B!sr5gwNhJ !@ڹ܀!ך]5Ԯ 9t],G0R"8c? ]v'Fuy;:ΚomP|Gֹz|w/^a]hߤF*[I+}s>^cGQسr֢@. u0jmVќsb>5+7ޔY=B!B!|LǘgF/uiN"skx2n*p֘/x>)" DO.E'Gѝ_䒛A.tm+G- \[6v.[H,)^-O7y:9 ݛqƅ"mל80oS P뛵0)9F yUx}}E]!=wF !B!iYts}?}~Va;\ӟ۶],kr &٬>CQլCZ+RYs@+ݶ  H,E3 hYls2UH[776n׵פzp]=twqԫXR}߫~z;h.~["B!B!Bm_j7Dk&:Qj=W`=w ql.&ʍ)LK]\>jQuq|ǦIO(eT}/ Ā-jJK?}GjuiH!0Z{kޕ\LvA]sͱSH];]%vGf5Lsv`+c [ !B!)!kvik?~$2z {n a]-;Lbq&Y)QzPtԜ_?> ?Ai}׋_#J[gaX[yEfkg;oёYoP,(P{D0ܣ09:o&܍h"XHxpm$^GB#B*p#B!B8R ]aMerV^'d =gap6ӱL0BCXFڵ tӷKB9bJq-]lE!}˥:C1BUqx3Å8(Q`Bq^SU6"lK[dl]o+s~9[D ˊaiʓ6JAL\Jx<B!B!䳑R rxyyp.)][#6k*hUW z+.+iֲӼLc~j[ cͬ%4m~Cx{{wo!-\rT8!6 AZy49W?E؍mQa> tPubmKkUhlkWǁ8{W͢t0!B!B>q_??SԶogPP `뼾):79ZH:V=%Mqj>im$Ԩm(Rjsiw2R41n 6O>o MD*fi¡7bShf}Rzg}sM y///H)v!5 aR b c4!B!Bȯ7tq'M4hdZE*<\lfP_a͕7]5(_Me i[&1y-mE&4k&:Z#;?Cė/_ӱdsn=4 ULBs܍"5Aneۼ> JB$M]@0Xo lj/Ș-ȹ *m1|6!B!Bg;WTKLwVkQ٩OuaAҳu#ADXzgB{DXR+R3n”J)x{}+^n7 m9Zt˓$C `Wa$li'Ɣ|nFf1hꬵ4e2(tJ.&cbڨ ,8&fPH!B!0mX)HZSp4sM([]"U^ ?M]X,g<]L C¬-yf<ĀiOM8k97W`bx}x<HBFKWmG IE~BUo>s&RffT-SnϧS)sBD:\5|ˈ#3=enܓޖ:id_?[?4B!B!R |tqfEvdRUż0XJf3˥L<aP;6C<{{s E2m oooǁ?HO_Q`3.㑡6`vvDYw%$J;+b~N_826DR\B :9 29+)eB!B!L)}qmw{tfTJ"mR5_3Y9vL trqtG&OH۶26|c.h T .޳-s=^wrc<4Ͳ4 ~?lMqv͏^B!B!٨҅SI,f-'NKL;0U o3qwE:r`/XBjV nЫ_\αҔSϱZ۴ GU!!?ܽ:ZZߨԬv) IDAT윾ZDmI!B!0l 7 tMxjN[)Z&~UV3!4o5<Îcثɥ!o?5*?, N:YS).!^W M"YB7˰77.öF QvSծ Z|uIBvթ fqtwTDP7P lC͹^jZ = xq aH=R51yu < uԏk2DYQ74؏}8eYI@ɥ+1>>kpR$B!Bg$8.5ŋtk {-ӟA9ggkZGg}h$.F'wUwNkߋiTǁ#gH6U$U?[BɥT1on)SԷb)J! }{pkԝFz`Iݪ(֞DM o"\YZCjC?|A6~ؑsƾsZ{o&B!BLz ĪѠoC.G@stֻL:؜vN* ЧkoN2Z;"&s6bRSJ}}Rgk\g4AlT\E!2GxBdH"6`Aj)ͣL{ApKu|5n\3_@K]OT۶Mz3G !B!Bg8<;;>>>pi*buzͬ^̻r'A/T{.X Ўԟ@dpvvWz3nuRdjpksa2 \9nM:[.AL{Q]~N$9PC,=G8Zs0!B!BgCUq;frEg<{z~xL3]G|D8,FR\A)C_2PimN ֖d,Yjm 1\}=2ێKXknv=,*ܚ26v7͊yW'+_У~k8E}rk3Vk_WԡAߌa-N>Qգ܄-B!B|TZDJBxt:1YڤFd:P7S6μx2x^o@ō;4k;S8+nbJx}}f&N,uΟH@M71ēoRGgs3c)Bq]煵RʩþkaMg&RFYDi:@8 GhħIz*PJ-[끔"R F(U1ư |#3[oH&fU`h\lQ療?z9F7غ%;nK9/7js5WJk=4*B!B!q^#L5*iN=X뵉JB87̀)yhex?ᲴMp\Q,"gaW3ӕ܉UJ ///P:Y`Z-4 f5;쵢?CükM5/\Ax(uݚV+XҦ*g.JlE)Ϭvb \4B!B!τi$)mnɤU5Q,;RZu%4}ȱ:sMQc 7|* NAmt]5c\5ULD M`ҢZ'%W*w|||?妬]2끌 10pw9X5g?@9_O;h)ϹDmSf !B!B>;Ty Zb)9.p2Vp4Y+/"4/G&N;KɫU6>6;IXg%O;FPė|Hoooc[հB1:MBqsNH*-8JCخPCom߿߉~ Sy |΁s//xyyA)7{E;%rSKw㭳udu_.@Ak0q.ZD"GDYMg ٦i=q] IXUx<cifHe nm_-MM\g%©¢6Mbgg>>Rsγj.Cג.=jGmnou14?Pʼnh3 $ kZC\̳v8ܿ ^^^RvTrf_&FfWC"lBΝ'׊4O. >7(Trw,:Q$tQOCϘ_C1G!6`1%*mMA|B !B!B>۶a6&"})9wU ;"0DAK $Qc«˯> !@Ku-yU: wrb\ӣ'm/_zȑn7//HS90, >ׂF4oޛc=V~wnMؙKn3Ь:ޯ M quUZvJ:ʩUFY[RyGwGW5ntUux%!B!Bg T)9㻦;*MqL%Mcν5ݹ8:AH+0}l ^/B_4cq8'?B -묅EV~B}vcEj^Nf74اR̪͉T/*@5 橌 '&D=s~9k_B!B!ٸ=xJ?0ypcQq|^n:fZ{왫6Q,cx>+e5x8"^Sw;'B!B,O_|||]g\{`1a̾ 8\ں[Wkf>7^8Xtݫu ]jH{6"pn76碛_BQ)]@ w׾JsVon9瓝]XuŅ]VmĄ2L^ڐ!AT_MUɭ"B!B!B@t.6/fbb9O3^Q\y%2[, .cώag+1юyc&+v۰G ?"ygz& sjZR҅DtvU,m.0J<.y.l-?O0\B %'f#m{S\\T-R~?Uw1] @!B!OGAbV lXl+f]zD:/6^u^[vitrL;w+Tc};/XjmRJԵ#?"-:J;X:TA3jZP]}_ENY77z֗׋hV Ejk K[`䐻3vsm몫E;nS#B!BgAFt v5_)hZ  (%w4*ڹGw]_566t\hWsq>}IsFJG}*.2z4lhHs"WuMZF$i mS\?E%^4^v`^ǣ|6B!B!JMi}{I 0]HV8[B%v$ֲXQ\gW6kVz ine6jN:04 !DGaR ȋ+iK TLKoh?̉'us۵Վraqm:yfgοY^[G0"s.>Xvpj:k->mԔRw! gB!B!Ԩ:K)}ߪ4 89sMv yr)pW1,xo85:tC7sӫR(d gjao)ׯNooGFuN`KS}|{ϜoW*0얆@k6=Pqn?R%B!B,l^^^uUHq _7Tkw=j[~{Y| /!ε!k]]Jgk9mbj(x<~dgD/gs؆6O{Q R[{Emi[ۺMbaq]܄[,C2 7Ն\Qی8B!B!ns&]B "8UjZsW--e𚒽M۱8L^_#I(SgE˰ʶl@6cm-mH"V+w/*>Sbלrp^lN鬿n^abo5kK)ڵl3ڠjm֠B&AC9b,RNۆmXB!B!OIǣr)Ө?+&.+mX)edZU1D䒧Vyko--RL7lqry~{5i.L ƈc?d"@2zj(A4o-]5Y/gv1:}.znԵ~ˢ}9|hqP zNAT @@p:=4B!B!  J|kHݼKpgjܜ`ؾv^!t&c^/]5۹.6sI?p\! XH)V5gRC??!͋u%"޷-3gʭ}e4_m:Oimn,7|_^WUiUkԸ}7ՔXSi疚`.@B!B!|B|ܵLn&ûUq$.†bu4׷EgkUGEoy -jǛ˫p}V ""x䣝Oxh BB_uǕ%#n)b :x H*F-$<̛vӦ"* :Nι[BxGr !B!B>#Vs~pNfZKUd T}E̕Xt,:j =blkRuԛukrER빵ӶݐR}ow ZWVE s颗Pچz-ȥ HUW67e| Ɏ/; WsT-m"]_L)ˤhkW cǴWJ.!B!Bg2:kw=kýurktp6ZY<4'%KUZZknUߺ&h`.MO*:E{?;mCZySfk]Lw9mwcڌ1thw""Et'h{QW!&5 O4_)V2Zle"9X{r.8nI !B!B>+ݽl+hH*,ħw^Z[GӊoǼBs 1mH7EksJsqePu$QK[mld*w\ tR=(r.}j7smN!jۣ(a8o༙"׌R{y^S G{XSPrll7?|R m%+B!B! v6`XpE!XBKއ!NcqFwvj/۰?3)Jo05{R2EeخE%qt_KɈ^[x9~}{C>R vKb2sE\h- ( V6TTa:Qm4>e(&'q+͸>S}fW߲R RJSB!B!|FL$nr$'K)]α~ |n5:q>1Z.0F˾ Мm=y ?LdE6:V|@v ǑP" 'V]"n@YK+ӘNQP`@* ۾SJ*ͳ؎57 +Xc`7 5|a]6xݺ;6=B!B!8p#]EW'+Z; Y>f|.iT ;[EgUf%cm>FK8O&#tmHkϋYxZYpjE([<"<džW[2}SqupȰM$,u@)mҍ IDAT~`K"C+*r)8J3ǾCUt;\_M!B!I1b۶֝P QM2AY+iIc_Yf)BMUv2bO?Hɉ_ϙ[WUzY)1/bK @ϵ2eOW5DwCbEA6S\4"α)1>pqC(ӃOB!B!kݶmr{N)KXgLYlH=oF|G)z9:ʡGja?B-)yK@1Jhn7|W2^_^r>xݜ>I̷fY}*]\*Nv~B8Gu"JR3j)VIBT8;b]6"|iC0 !B!B>G-=3u_W(e!?QO>lOe_Z5tj] @f&ÐK+mj362Z?~H"s;.䢞9Z;SW"o}ε87^ :BQS\%^B!B@,]Lv-@6tWJڋZ]Ύi^йl.I@[ݩ,DkCWZ)R)M?XgR#ƈ(\-, 6۲ض]`s7_PD67Qaz9ӈb؏ N«y|_75hk seߴ.B ;~b5rki7p_:pY-/!B!B>3]kzBXqW~]爆7;C\{va4,Tu$/s3DBzYIJ۶l۶ FY7ք¶i(;I&{jśP篛1zf;j-hjIwYy-rH!B!0vy~Wn6 Fms16͜q~ wX`&Fs=,Yө.mꗙJWmzԥ9'M{;;iqٓp; mJs3n[)E;uQ`.ɡdݐRJm*RNoa3!Cq4M๙0%3 {Vcq- ) hfZC#S sUP/1F~mmG9-\ȹ?B!B! `:fwqp9nyWC!M#q 9e?+Z9cso𘡀u(B?^mZg10<ݱmn\v3}m?!B!B>ohd 3n39DVR%Wj, @7sa9jN[k=9 '嗻0XJiѵ8%$70\cΉv}zauYvs28mSItjTx+䦛6ck=|0QU[F܍w"⩀dِd;F3O xهrՔ؊tTa9S=Ͼ>Rs!GqYj/V{XFɊZkўl/؍O){zz)M!B! xL5u=Jgcګa[,S4'7cAHN %|MewaM4ف8Pzh}قfkLJLjRLW4l9eOJ8Q?+?a[> _hXfݥp:v!:BQ,@Cg@,ξZ*E )I8.밇m~2Ĭ8B!B̘|}rڏUҡK5"LX"L]*SH,Eiku.ݨm_-I"UIi~!x5ynm2mE}ı^{.=[mfZ>ܘ, *]9 Lvǃ*r;[1D֘ ɞ7+ه&֐'B!BLq]ۏu8;/$k)]ӭ3!7: δmBK<{}IcǺ> X_GhZ17C'Qlg7W_SEth$^ k߳?H!B!WNJ)<<&<.M]; C?w9$\HťS視#aMٮecqtNbfX_7n^@o,sŅOD1flOh^fG$Gb^?@[1&*@%G'{I:9M{2 4#Q k8? 9I=۶۷o9c3C3/;:~ 1/YUtċ{~@F|0X|lnJx`۶Ӎ57B!B!p^7rUŭsW |:"?:\vL?,vnKs=/k|>'mR.nzI1] OZQh3 1} \my3ыHc~R \m$Ւ0!B!B>#}U=&)߫pz7ié>3I \5[?c ,R|<*oyg)ZZ%Azo[lۆ-%G@f9!gM>ھ_@tQPro#Q[4S]Ik/ ͷnl]Hok-5ٔRPQSƁUQB!B!8;^^jcJv%OA1ŎeE9;_(Uk.amx\?w*VVQȘ7L'[85XsE)RfaYu$ r&xTVtW-f`L<2E+1qYڰĸRJ+Ihl!_h'WG2 H9uyZqe۶!<=>O/фB!B! c@+Q2 ekj=s1k߳߷mLcVmr*:Nqk9%M{碌u]3hZ+j벨s G)M#/lBmbP1(`}9m{,4U3x8_[|6on9ΗC!RU<@BKZCq>nˌ%!%B!Bg}רX*0`+r0Q޳r[Y)eOVKS-ӓKOE% ji-eWlF 9w}Ǘc8%5]Z/\ xg…<.ěs\>qwuӕ[|R,)yp;:7Ǐ2407>r?vtgw+V8s<^bRjA^|3j-!Hmm* !B!BZlۆ}ODz ͖F`Uh-p֦NPr尳 ij0æpnnض|?쿔F"5f tZ+-{qx#6w@v]0ߢ.?lj k֔>I2+?ͺOk\7_M,P,u] ^$R5l?kn7bni7* !B!B>#)%$I4L9l;;םоn:`Ǜ5W'G:'~5ϰz\lQoԋǽ"CpIғ~~_lMh.{j03O:b!WЅp=E]m hq$ittW,]5X{} EIܴuh]9 ;!B!B_cw^/<ߟR2.TQHS=Hk[4QquH)I\X]7 WsuZ<0oxSVq8|F G!B!ϊmv;b ֦D]TZʤ-LUEy^ߊfQSrkY&͑_yw^&SfR|$;~5X)PU|*8<{Հr>WK؍E!K䅲kWnv^9}_kNZ3S& K M5qr%pRQS ah+!B!BgzgFIK=6 Hݱ5+1McD ] RI)HqLqtazj+\8->y `*_/쵶vn8V!6Q "saDPʫ6m2Sv#%TgnXr3">0h]nyPJ ,ͫGB!B!KRgc4+7IِfNs0aR d~8q='ˆ7o^`*6 iTlw):α5Ǟ"& tm_Jܝ@u t@a#}֞5)4) jÏyִ͛Sp]+UY*"q\>F*syvmېs[B!B!Df}h+V\hCPa%"Pb2 7SCp[>P eĦgYkpc9sQ󪵢tw/_ "s%i|qcm]qÚl72!w+5Tb|)}5uV/+MV&J{$nrUSju.tZ^v&J1UJB!B!|~'7q!uU2zDyk0FU?\HNĹIj]E8@k肸c yݐsF)??cO<+>~8!}ŮqI+N6u=gZ^[zR`AKÆAhK!B!x>x{{r#ĻURC񽨥rըAMZK<@95 e3箥W% IDAT<7%tMQ`Iկ~^G &nJ\"chneRHc-q !M(Weːlp׳b{REqp4=t~>n?!B!B>v rvsՀV#U}6R"ym-wեG|$sϩ؏^&}08p?p&=/glrd&PԪ-)f~ɚonz1tqdGE/.%f!M@rC5^INlDǁ녜nEcB!B!9UV1~S(EV 2W^NCZ]Wܕ =k!G{bnjU\֊8|67`N"㴹Qyfൂ䦺{^סvs 5LcˉxWǍv$YDW}Z6Jid!L@RP&؊>ti8cB!B!3|>|>zPzw=kobk}qc/?aŖu3W"q ^F4_?-|ݖYȳEK:Jum-Y~m؃'<4]]N67prim)Q88s+V<䜧![ !B!LB^,-"U1#~4 EEU#]r8Qd$8_}kn<5W1dyӱN"eh l&B!Bgz]~x3:D[WưY}2w=+0NE(X; "ݔYžyaԊ#RJ؎:,tWܺ!Vm1ao_-Zdl\Ep&>ۢǮڿ3u]\_lGCaI]CWʲZ?W!!B!Bg#mp~(] h&e IԫY|2:ۨyimWPqm"`6Fk)%j)@7%^/bD ۔RyU*b]snd*Eƣ Gu1'^W!: J$Sp'6 $^\abDlc8km i)ARerr y?֊X<램s+ZWc7~ݦɲUP;UvRdMeHi6U~SXcɺ-z:LQ^oe.m@2{{MT J @B!B!|ZRJnw"݌qMaW^H=Z.r0dLXzf Gj>1"kw**:PtEWX;RPk~T}q O27dQ%Wa0ހ -@ `sdY~5GBߨyzoURQRxMB!B!Ft8wI\kQ>iVHڂvҜio;a;/-s pt+9S d>sӮg51BtY%ࢪwE7/_`K)A%mmm#'f?/h%\v,@L3 4rN3Kpij4vhXJmkxH;~[qĮ35B!B!x>jM{7B!B!3RBCaUAqk$ֱ9z9BgI% GYWбK\gݫ$5)j~N:t9sJنqn/oZ6wPUlG)ɇS<,,vQ3\j)vsG㆗u~3\>xvcs`+9gwW\)F;ضRC!B!(9:4CsUXR/2:L||A1Y?d(E;&{6G-y/m} ^x<]@RQuM[m)V J aiQ] j Cj-yBycsBkfr/\ @im%  "mNߦ<V˿B!B!|&Fnj.>diXRrOړ8Gu0]<_)TXP{u($ƹAIL9D%^Hr,9tr۶6 wlG9&a̬pWӍ 5oހ.ANM] 伵cᎱW$Qáʚ#py6WA9 Ql)I=Ǟsvsg3l>j$B!BgdtiD!~?dB`,5q^[dif[& - b3E:lxЍLgl]{:JK~ ]97,@hrӀITPrAk;KI26dU|׵eS8 wpSJ(qyk~ڳzr2jS2[ !B!_/^Vҋ@""U@. f܁}I'&=scČ6XD4w:ڵ4mk4h:iLho#e!qXV87)((}Qǎvk-1vFji(v#*t0Ԕة!*[o, `2lVh>V_w'K%dw) x4`<;GU~GC-cJq&B!Bl<| tUӝV]h%kJ5j3^.:+MYT&ݧ.οyW2iJBOn $%:m.(x2mZP@)eH)]`m7]a.t69es>9v1zB!B!(E4.,uLAHpnSc>o@5>wlMڛ~wuz+Q.܉ =9n)͈;j;_rVj[v1n7/>#1go@a7i͈OmP&H 5^K@bu4/UZgW߾x{{d 0!B!B>%MP\k"Wt ukj,עxE(IJ1V8MzxL>pս)W舌E%-S"vo?8Gg 7s-gJV\|EjdZܱ#c +HS[M۶omϭ--B!BZ;g+9 U1_w#Xα5Yi"[]PUTs8:P0-=ciH{RD hv|k%.43Z@1'a14k˗/L#k-%/U=V>h$YGǹi^ּqmWQe9m?4;j{=n7vM)?B!B!|^`s5^r5Oko8$Lɴ%-v{55{%ZZf4 럸*k]*ƌCk>?3mk;bHN^:NʦFgz16zn֍7bcuhN86DBjin2?WKE훞R 7B!B!3`1_Oܨ\+VJƓ.U˵!yK)7:,[~!&g}dLmJ).R8߾}6-z9WnIN@- %*(t"œsZ44T<&CT`kI(UހlQAh, m nv~fB!B!.FQ(v"29b݀uhƵB~/L%&)WˑRjSl{1Zzﱕr@5xT'ݹ\O !B!B> wMp rlU '?(Iy4 GA n#Fzk Z6@㚭'"y};#FSF&3Zw@2!RRskcHZQ!&`B謠~TwlkY_FpwvKf #n N1\|~9}L֐/ڐZݐR}o~SK)G9F B!B!$R0fEu+(sJAP33"]dTz)Rko~,Kl WxaTeiz}B!B!H6sY)0/}:>?t!/z NqL˜U/z 0~\lfrY rLbzQZrZ[8|z6;22% jPRM e"V b@44nИw^өbotu,M Fp):6^שh63߀B!B!W3M2 87,}j֨#a]q1ӯ@bg͸*ΌݔG=OZmܞ].I֢ϿPEnqk"$ag\g EEųaMcWZK8V,B_أǡ7*M(\ e. Z3'wbL~s(E B!Blۆ}GJ9\9|Jj|عFJ{u2؍x3&iL4[MlJo3k~xD)'/뵇(mܐҔc<~JЊ"Z|<ͩ':\zO=]4*heCmcH8ǝ#&ۥVE)j>rX6Z9B!B!ĶmömS~jFovb %q&C99 3Q(lY`TxjY&ڭLj$om;k-}važ/fm\<HMN=U4C]5ƳqX&m`a MvM=2*]C =mR"WsYhoWWcكRG!B!?0z:rng=Ӌ]8W qv+alڈط33mMrXf v^>uxUP<ah IDAT} Eq6wow|>}?lV!>zDN4VEwF+1q +Ltk]_K[&:f!#3k^;Z(Al0P "+^?#E"̦Tǁ}?z_a~R`B!B!|6TGp޶mCƱ|!O 5X/6/#RWcyʛ܈KQJTi O*Jw3ھ卑۷o-8G )W񦬛b7x?bյ'tLMso)QR*To\$X<U-R(Ks--,1N!B!Y$~b]V&Uu.`j*;&"$I0NR.QmsnKHiXcwك(ƿEG9P⩊o߾?cdDqS2kz}mjN7Hۂ ,)[)*šzɇ /6Q;;!u-޼6CVje WcWx>ޙB!B!ѵRtX %-㬾Ӽt!K։mWkk\t^8w+朱m_|M6 q`u\7Ak߸q:V?k/?i ib]yOk;e!Zk !QjU {QkcB!B!|VLWQk;8^.{@r̶zc#sw՝2ܩk#dߏ?'ޫײmمGʎxWlp!8 q6oUۄ۵؄BoKAًE@)cA^nB!B!|:w""Wgߪ;G+>kqpҌYdviź.lhXjjn9݋K^("# BY\z֟vsDy\ŸGH&72"ǀHq2HbǾ| 7$B!Bl^/?JwZ5: ݶ I7_~)FR:8LK0SG\G׵^kU[D[x۰;)_ۻ) ^Jc^)W:9>Z#hE( Zz53:竽}gS8A^)S k>$Rx<ڠp@^'_n_&B!Bkׯr9^K]TOߩBR+o:린;7gQl:T%6(f_rNKch^H4v51MLn8)elqLbjQax?Dp*RCPLZ`t \sEs71Rk('o V:9k?aaztQ.ZA$>A00@B!B!|J̥f1UsaIXƟWIԏfIw]D{*왎 tY,LPBcҕxh}yg˗.j<>m_,B8zs$u樂QeRwө[GX* }]r giTSxMs!&I2]?\?S1ϪMH!B!H)51+oy d$xşѸ&nSXMekb: ]1^^Ϲk51d2ק_cmo:Xm^;f]kANǃNj}cFL7I8\ӺǞxkowpzm0Cs_R!B!B>#VaHLL뭽lyϬv!l0{kGEwJ5\qݳkl ѣ 7)*\RQ" |(Pj@tלV (4dXEsGnjID.D9Wk3?j+_|p1>~odxZ7jʓw6Ӱ5KB !B!B>#RF*r1IRQ)J`Pr WaN X&ZƢ8G7TO1x5 /? [v.d r*.l'ߛ).*FAЅ=M)5?ުC-Uh+߷9fA킥="n7!3 @B!B!|FD-#硏D1j7! A(j+YBӸRJ>&guMǎwD-l- HJ4ڎm}Ql-5 ʾ\kvwГdֱmwlHjQޱl-&~s.s&{ܨV1q9'p͂HD xKE/Yp*7LuݰmTmq'!B!Bgx5rEgΎ;\nbHf^,9 ÝyD-feסPm`!}? ݑǾ# / *'631/]VePѓК?ҲgeXU[y;FrQ,>qoH/ {n[[" MI4 ,vvk-<B9_ !B!9~7T-ȹLqۘwE!Ѓntwٔ$igZwZo(srKׄ--;X9wXB{2IQh1%Z+7[l)uPīc6 &K"PLˆ).s %]CN+Wξuhf99Mڌ}[W-pu: ,@ kjY?!B!B>-qgVRQJ4+ڋ3zjF3YӔ2R5P9j"bĚetyLmhrJs _~7f{$Ey~] ꜝ8- E%^A;μ1&Zh1"RW>/m. >XRg U$!B!B"mېsv':-dcf= l*=.h>{PU_%;EA]3Ԝb2pЦUb*m%;R}^i ǁc:O-enz mׄ@)ډU,mni E| gY$8mkz }ƋxB{rwEc4KytHߛh%B!Blm4uH5ne)BmvmߍĆU$1=c-tx W("sMp%V0"Ga>XgL2SJ)xC)߿69,:-Pz=1qW[Z >Ъ*a2` A6Sp.Y:$#|ChJ}3Al}VQ\mn๶\WWB!B!&'^qݵҢ>4M]uu"cp?͙mGsJz*j1LUY2yQ,v GTz̹&c{^]u#wZ2w3nLw *tvQJ7*܈XJJElO 4$ E< &uZ zv38]2ijv)PUoY8}L!B!Oj!jʵ|J](BH%he۶!omrR Da ^<^Kd/8։6.tJ+,ID.6q}Ƕ YQ]RlI錶̕KE)4bˍn3EHߟP= <d14eʧW/}fj_q( !B!B> kH,lrsGh:rjZ~uۚ/ lQGHi2hq,]KnuRBVޫ;LFZیx%k0˂x>R?;: "]$HhC߀Q9⡠@%6y?j~m }j*vM!B!׎XC6DčSQkעз֑jUzx;z_\۶ I(z :αd3|Ŕk%:0IWm]\djRdQ ES:]%iׯ_MLw90Qeڅxx#1ۮ•-!/}F8\D3T!^gyf$diE T嘢0E,*zL!B!OI3H=k>}˘j bٜ} q@@hBZ6"ݵ']ܶ 2ZN2Ɇ{o#{F[mhݮ]$eQRsn7qp>e{ >EwX8["(A80:jS.(W9kkml6ߥC1 v=Ћ)ښa-oN !B!B>!- P 85bi4q2XMӮtᬛǎ8\E$iҍ}ǁR+j)n3bGMˇm&v^FFFnr:bߐS;6wלhWIjK;䢢k;f VhԚBS޳!-;SpHMxGcU[XCۃc8#q@B!B!|ZRnۜT ӌMKµ3t-~2;zirnb]ӫ-~u] a] <'SW\n׏~[|2GSl[6$ Ї`7~]yz:6SkZ*>݆P@뼿xe㡩Cq2MGwD|uOC-"B!B!F)z*]q,qp?Id,Dӄ@)e%eW iyqe#B }6_ yĞ _7J)~14p"0oޮlqmb~LlSLh!FmMԴ jp1bVZ|݋Z{XQ9ؔRW__˗/B!B!/_Lml,Gٕ(h(ڱ}a̙[UJ->ڮ0M@i溾hmk~1h^hdrяwrE7.*E;6 .t_Tä@'ԯRԹX[1Ή^ߋR mf3.R`B!B!|FJ)|MEr8ۯ-"\ qr= C?e'P}d_,JatYqu焨]qyt^$O߾63Z^>8uz d8UcIi m# Ѹch,%cPlආ$76ͅlk.D$/=.] ,&?kP\cm=BsnۆB!B!|N,^kQ3==8Lcrn֜]I)ǗWU_-CJV8r2Nܩ1SDǢKϧW!0Op?sMrMZqJ[Fk-z>]j ""]MD__B`U4pm2eRARGqIWDM`R69v~Q^ "-+9CQCɪ2;cǖh>RGÍ@B!B!|Jr9c6&';>b^Jq' 2ĭ8dP(@m q|XNL₅/-NzqmQ\3ٹ88"-y|>QK6_8lxb?/ayb1"%8oݬM\6]l?WMM1]cǢ˨03cܔvPJm ,x>B!B!bF}?N3 ^QJR ܛ{U!I|Dhis5FT YlӃF']{ at:9b!6߰U9:4'^eb7BkEŹ6M__iC9ѧ*q%PZ:zC˂f,4}?7r8Kʚm=S])i@ zܵc9 pso. )uΟgBB!B!7NZJZ7iF&5=s1J3.-hͿvD!yMiAJӱu6{tvx9 ]E7Q.]:҆ } S\N9OWgkG _JwxFl-2/S \ ?UEC}{{7YZ^,ݖN/!B!Booo콜pg3Y1qX]1MZ#Q۶!eg! -T&S|;aVZXv1!Quj{RR a89gϟcFug_l.6x;oXߪUcWkXlЈbDz9 rE IDAT]HVc}0wx<?BՄNkYzB!B!ql@kQb$x5dEr .#.ӛhxO|=mjolI>y.-.qqaګVᜂ1nJV52ELpv"$ݔB!B!J)SH/fϾĕs큜zc,YWX"Wvei}c!,R 5OZcI<]>/mͽB+|^8el40U)mUN&53zz)ћ‘Fܗ5J 7[kɹǟCky`B!B!|KjVskM^@NŰ,$ᆭ,qc3Sr.H|z-:Rzw2]vQ{4wd9v>7lpnaxybmmv]}CFr[7r!A35Xo]dl -˃R Fbq;.߳:fm !B!BCQ*Xʹ)NB`tĿ<[n/Ϊu]Ur7'mMCjm9󈹪E٨M6!H9a˹ P|B xq.ߪJ&ɄACdwƕWtpCr}%(^w"8cL""7 Tkrs//s}]fEqo6U}~`B!B!cꂝ%Sng@-]:BWDJ_/xmPK7bić']FӨ폖5lHU(|@bY^`+t1(R/5j q@DYXH 2b%/O/>nvbWUSs""MH](Qd7)m'rcAhRJ,P5H!B!|8 q.:ͼgM?Lñ8lN8U$q7b_ H h^,֦ ɾx1܎9[h0a).QD܏t/s&;qOoЪط}$Z9 Mx$8lj4Bbɬ]7+ܽ/qotq9|rq/_< YQ6QګqkhYWjspRG$B!Bw8Sap VۊcXfZP7ԊlVĸ ǍmS8]3P#B!Bx{{; w&ܕRxnQS4Jr¾?lAeSX_ʤhfnzLfJ>6i~ajk -eŖmxJ)B!B!"=Z Pɤ hzMl1_o|.䚋кFp4^ \K^UnUUG:u:^}V^5k.sc`L9ض "Vk6ӆV!"pt+:*k8mZ׺Zkeq Q#+Jsx!*kݴkmJ0EWd2L!B!qzDT~jP|kG,M2 bפU7ƺ5]bC/v;Idl&#"`Y 2܀M[m{![  ۾jY}Ggkba5WwPnmXbY'fY7Sh6j^oz}1d1ZjU䜽, ̱:ۥcwB!B!Q8+V 70tgٌ<;TU]cR]kH+:}Ǯ׎߼-O1-ޚM>Z[X!R-# ?ͶxEnFev%Yp䔚ErW!n)qCM]o1[%)禂c8O??aB!B!B4 (J5fQkiZSlVJpA;\Qy n],>ӈw_Wj_c-jM"@u0vRŮ"ҿ,ӂm3Bن)&Xx 4U(ɓJCT[11Do/nAr.N`W'ǖU;*؄B!B!G >*ܼwq mN ۶֮Ϭ; 󙋔KjTƅГQaXOwxYMK)AEq -T6 cyݞ6LB  kΛ~?9W J7bn[{6YTF{mJW eI+%zy~<7ZC2L߫f)M҆4bS-%hB8U_B!B!|;boE|M'Ad[[MGћpbz۶yY3oڜ`wEIcX`8 Flk)i^/Q3)ƘkR~a U] 8oniv`;f?vҦ@LNAT[,6b 1 L"KhAY͕4Q!a~},rSb%{B!B!+B=qn̆({}m:4cUYX &@X݉SeYcVfN.cDgg,P"_~5-FkmdY6W`8Q@ /#2l[3bBs]DZ74Ķ<\TB͔0܅jR%qy(, sV Z;wB!B!)781Mj3.ҕmkA%B+@Bdmx;RΣ,dhU$yH( N4[U|$iO=k7p-:Z|!X[sv_6Ș U[JU _>brYPG GǞ`7SVWF+IB]bJ=-?׮8B!B!|G̵Sqk)l7 BZ6 gY 6lt VӧjhKwYvqߊm87b2[J }M]MIuםg )C\ML~WFZIh{DIPP.-]h ꬽ6cޟl#[JAV7}D| ?/B!B!߁/||¯_l\QLcI43J-b-ʨ(%Gq1%IrsnyCvR*';XwIH.?;4-y8NOYmv<1tZ{jrQuϟ?{ HNy3spق\z `f=,2YG47a0u?0`q'd\ xhH\a휗c q,L4eY@ 3a @B!B!|W'>>>O)zk㸌H9wm̚1)VΚdi^:{W4gI'\Sщ`Py м$DKH0SM! Oy3WB:|PNv|'I/k2TW`sv᭿A)JNKrmbr/=6@B!B!|g~ox<H)pM)Lͼ@Зz#k_Bxf̦>.nYG2!N)w-΄@y81/\DSD{-97w31,0L-R"S.\b״V|~ܡZQ_r%N5$yCgܪVۉ8q] x\o"}goE<;;m*8Z!x=ž8Bt̰;gl}!\yR$B!BȷxfU/4w$K=9=5֊Y˄23j]LvқbmԷ);⎾zYz$}\/l鉔2.N Fb)^~q:ɨƋ֮ʒz/P;t8|h"*خuΦc9gO3Xy qL:cB!B<l?mߝ1kwrjXQn[ضmv*8XGAm2Jseg)jh6S%QpK!B j+>??[ʾm7fN7_ 2k3٘g!`kyYMˆ 7!* '1plp+Q,gqH\[ !B!B#x֡D&nHbN3)w^3mawVq=j³! [0P/cKX V7pE$oO͇.͵`Bn~?z! &Lẚڿͷg'3ܬ6(qUCoz$ʮߋ.׳aF*t~h*}B!B![!Aj0V˰\uZ+RPwH)/B[;K+Sl,: =&ܝ)hpDQm4Ӱ};IsRJ0]D/IآtB QPÈȨ@v/, ~ƐAtI& 0r2_Lؼ*V NuŹ 0^?b.J|97B!B!|dzA{K9TGla}"܀=sؑ I7=7}sLsZS٭km4wXDS]Ԟ)-e4(۷_ydš")co6gk:IӼg2+2d Q'f65rmhsfI&kq/E$7 ckNŅ U`7gim&Z @B!B!|G'>>> !h:srEKo]W0'0kŰ,BJ,k#Zuծ?ws5W俒1u_jۖ n, )O-U^(V<({/|9^k;ۆM?Xa ϚVKho .TtV'dlE\JEI2hry'6+aB!B!ߑRij۶i=7K"ƁbU{-F}u1@X8CunuލK)ytJdovuǮaY eAhoՊm!6I X+znKݱ;SkzHMve &*6waY%ƌ 52bb:Za;هYJ!B!B1v$&$#- j@RVT%`Ă;p†9g1c'Ҵ'|`4?k5P'Yu-ʫR~!AsژD5@ 9ږ9oW_\˺خ@Ci,i\"&I@8w[{%F$ % dc81tilTwCkqt>2UawѦK5=̮ N[{#TEsi`1jsUXv HꔭCBiE|\/ڝsBuصV?:*(G)mXPJmVMUEUEĽ =HdK5B!B!Ъ΂X4DӨDWtyrk5V,+9AsnV;z]UtdhN|x賂 @AۋZxo"} -)Z+J& ?m2FnpE3fˡ ĒI^b0Xn7 p4R][ !fTi$?Dؐ^ֱVGsq"m5Wg)'!B!hcJ@z,W\~ܛL-23dt'-i3":llpvty/x, z Bhki2)Q~w|=jCnr OEa_7VTrj:;k=ِ9]}F^h^[8%yx9Ŧ>j [ަB!B!QwF:J:w6't3%!8i3Jjd1y]^x=V,` فUwSr1 `Xvo4h`Lk3ae7ejyY>@A2nZSq$'Y7m_>%>ܱ~8p|0r.bԇSvڭ7%6'䔱VC @B!B!|KJ)8R4v^rޚN47BΉ !$m+S]_SSy_I$V<@გ$ΕR!~d jFL[+]օ0iӋD͌BTM_#܎SǬA;_ Kx(l"0"(휐&PN©6)&FTwu%!B!B7!3RC+I[rt(i'ё'"ض cwR8:4jޝ!}͊g6 IDAT h{,ҵY?%ZFٵ g$^Mcq`rb[GD@sx'b?R[M21DzIU/B@ffax_[CS"F}beB!&z\Lt]4Z4ߓ!B!Bȿ=?~ϟ?W/wڌ5!z7WoLsx2dg$cMoǶߥUWm-vX@9s^KS3r׬ܖRWo4f,R{vZh&1NsXKKmvwmGR /LcnF]lVl"ֵ҇[ni-)˙J)q8ϓE B!Bȷ|s@L/ƪ4}UFb'I$Gu>nuk7y Zxɋ}͍hQ4b7=FSo>=3ڽ=ްYjšEe}|f^l!1_8+q7āůA\aU(v%1'=9ed~6߾%ǧmFm۶Mߋm)'@!B!oGJ58.=3hdhS:V']SJ(rW`Nr*I͗Ԩ kU[/jR5E!}kq3^܄#LGΥ6/R{xu5fa6^V|"iK)5w:Vd;j)ry.B!B!|WVoj :Luw5>'hfX r0!Tug`,]64."P\rgX[iKqu"tbw)[K޷ g9q<;rȱ宊94J)Yyv&D\nEnL׎Ȕ2 @ҌT xCZVpB!B!ʫrkqlfߝ`Iw.AӏLZLӲ^jŘj#^U+mb\ܝ-hwE%97>>PJ  !B!1=$FUhܮHzV Y@3Eқ|eZ'y0l֟7Xft~H:oz@QkwY6ʮb;Ӆ-3٩`,;9qfiheŵX<7\L)s[;:xr\cǏs)v\hB!B!qZW_q1X7i8 ihuoen ^N@M^O^C~1kf$M.Tm>PsnjxbZr{{Hڔ.Iu.QOsRvqξ1/핾no-$dC&뷽).8<쟣e RsFZ I BcUԫMZu̾#"6>$ qf|?QJjž'>>>,<^W]zSbm_Pi&ʪIR&A썛p%,kg٥}rX(GLE_3ik)=,:!B!Bwxෟ5Zg.ffRXeELn]#.03`;OES7O6v P@η`H+ [jRdvu j'腹P΂O<|~aI/ 4 h|b6qjF(UNݲsTG2gclȨsJ˵4]lbi2WŻ-Gp+:`rJ)]3awD+!B!BwBI'Iމ_u.H]\qFKg(`^ ͺ5f 6]H PKc}6^^c~5Qz)~E>_<[_(Zւm͍{ob aKKJ;JZwl~ot>=H^!MKMPBw0Jv$yڹ}r= U-=;e^`ź*bnR&0gAWbE=3ƔSF{Qmx(%aT\&yL@3kd 'k" O"d/~"]* 3msj0EkK2S)'Z=4`B!B!|Wx6ԍ,x4UŖ3JppeO f.yöqm݉wWkYRVCYYMD7N!B1GKc#IG46ʯւefoƮco5ɳюh_b0f &R9|Q|s{f`m_=xCc/eOFtTyL|AG!B!h-O{Qk VyBUq'RJx{Ö)2o{ cT"M8|NucT.1[ eҨ&q3XxUv5 z}c=KUt|Pb}Ǿ_StB!B!omxxȋD b0=Lf+E!0[̹Ki/m]wYHmKnOtiD(K4qDI<;.ec6(J2UEΨaNzjaV&r},ܲ݋g"^"5 ܂9_`!i ɓ9h& xQEL!B!į_OrzlƎ:k3N_ݕZ`JVFݭ)e)3Au%rMVCZr~ĶB5uS|Cm1O!Fěpd3U6{߿Gu8Y8|'_?q'}oBVb"ҔRGB!B!oxVB HiC ~N8 iM0^1wTx-XZ+& \ҴU/y;p.<$z <|gmA+ "!҆ v/%A)V@4 <;V`猊jc%הdw58}7zuym;~w]Th[kPH!B!G 8rMy9,6wk tk]ׅ@fHL'՞Tm lnz1_6_uǣ(X W5U5ąnJlnF*>>-?;@z "HIr*z%sIW5tR7mvMI.B*5SRzs).qSbsvčuSOƱ^ ;"M;J>NNu9X~~ONusAPZ*@)mQsi\DTBÉl֛;@D\fV aoTG|ER+<tKFh>)lQ&v.8}9Oa)H!B!JLEn[nc(ŷu$v1n|#y wZf2"M0O"6#{a j\ s]k33Zls`*PsU&eݴ/*w6;\NC ܌w{8FL?SUmx<l`B!B!|Gu-A+OY;:#<<ڇ Gm8>Sڎ7#Y^w>nVY$M"=*Ҷh?m~FY|qnnZ7@\xv"r^fqP~7A4%`f)("Ъ7$mgUE-quc4B!BKSϘ4mgu],ϾrF-H) x~r:H/ EFGͬ68E=u/ ucŶXLffJd]\.TD\DOqf Rh½zzw9'z-+ASN 3} W;يQM͡ANyÃ4-3v-1K*ۃ|>'%B!B!; 0 4(]6JNz"5 cm8Z{0\\Q`kP9kzSíe `2 iDz$8Wmk]|=-y㳁f. ]B%}HB@і !Z15K3q|w%"1ijc.20x<9~s<PU ǃE B!BȷR$ֈ]smɽ/VcfIogC7J9v,\;`˘vCe}lt_eiӪ@nvK18$yrEq̋4X,zə8UkLK ׾< =<ۏS4w$.ȯ?L#7^Y)Jx;*a(iXB!B!8_'>?>|>da'nHYk'񬦱.LݜSvs֤Ss5zXвL;o~Yo[-.y˞9yǼmmcmjRr\xջ)zW..zYֻj ӚTzBx>?jv#[$(*0[ e5=kUGy&>ox4,2R$ j-Ʊ?FUVgAg'm+{)B!B!;ts cF!E3N8Q.h4 IDATL4tҰq&B4}I%:\e]o lr륳Uu&9QRRk :3ph]TmN_U,4݄xn%CX|1/=Z[sdž%낡@Mu:8p9J_$sD!0񸴹8] !B!I)a ])DnjND8B ú&tnnFLh1ΈC<֮Gםa LbfiEN"t֊Ć̋W6YG5l9e Ys2܀anE)(cSNOoz/&/v\ղWkqU3ϮBB!B!|7j-%i1`kvtf4OMANvFEO`0sn8gű>N^ae)0nٓ@uE \ i9aj2yRNx<bSug)!DqCg Meȵ)emF!f Enz0{31+ W9T"Fޭ_/7e߶msxfB!B!|?~?jE6.hR+!J;b [օ8Ԝ}9c6<<';0:QZ_@S%Uh#1N1m%zs8Kض9ԺcM9{7Y$^s/ Wz#sͱ0c~PVrݶؙqqN@F'I+ι&d\YtW_ E`ZB!B!|' It+qZӅ}]1pݮ&H>͈<5[ؤ'NM_$or׉ coDܴ4^7L ӵ3_Ec[ȼyo&6ǜJ`fH- $Isgw#}m=ZX???/{G!B!PfMeHi̲[ϋ8.TݤUkEQ1'as:n+X nd)VmbXԧ,&PU9D<L=Vy𳽒7<lykubVubm^\2%\p!@dJo}^[bb~;IXƠ<{+-___.[!H\S| !B!B#۶a<'/mDrmtZ #[4 3emۆ-o] LY<9#Sj   ^8ܠ$ܺHw)-["_Q?67`9 ƐRYQlo6Z3թn< &TR sZ @ )Ģݩk\1iācۯhMo0r`ueUjiJk 5B!B!߉mx?wJ4CLiI9L|~Ij [H#Sv͵7!apSyׯ_MlZqc#jW6O@*Ca9r$oҋ^rԭ&0wڄB._f&T@2{a-2:vnE 3 Q ֞\&RJf\___jB!B!|''8@ЗrXM3 W`۲?4Vhq]1+A,gkE'j1 ,8]4{\)ZEb ŬO8l "<*[n{kj W`چ!FT(ֆYvZc(72O8Nؖ`q ɨ\"B!BF?Sz2nff)az9 @HOJh١hHKlXkonsV5%cٸ?KmZ :U\&oŋ_ScKW"eZD´l~lX{/OsJ˧rp7PŸ/ _{Tݍ(^a(TU$IB!B!~s>r/ ;$nqqYak!=2<7{5\窱ݕ%b|&})?&]=XI@v7]k~so,u&'>6 o hkI2K0>_ӝXU!jUf˜7.X d$B!B8|TZIL)niGσz5 XȾ9ϏY{Ю(piNKw6Y'QT>^(ؑ2j7_6)!:e6bRo& ڣӶ(P@sP˘RU q,:I;&5z/ FU9s#_(WFB!B!i&"L+,֦Ï[J)g}wƱk]cQSg]J)}m+n7r1^pJŹu|혛lQ۬s 3Z{96'JSkiN] Х?냔 r)%l7B!B!a3W!6 D\$77bh:[ Fސrwy4\;H^a%Gȵb׆X1 K5Nq-^S뚐s[I3Й˱Vy|(;psRׅM*˪l<b}05h*:톤u,N^c_µ ]M`Va7@B!B!|?rضGH{rʣl6t'}`fL_-yYji_Nm;+0mRd645٩x${EU7M8d?lf4M'-19`E}abU,aX45؍x N HZ͆k40q2k&؍Z rxeB!B!ߕ8O|||xw$Ls3j@fنLx ͈Q^kȽۢ{O,]x/'}9 #} }wl֊'ZS6)ZGW~S[n @97Ɩx19gHN%$X>7 q8|\KEY쥱J:Gp1@ZHd9]L vmx fTh)#t#4nm:Ϛn[&Go6z&̉_L巯gp]; Efvq<ZmVMBjjz9xa\]w8qqo\ :lb%(.@kאrv(RM0$*~믗ѥV8ϳLĆ`67B!B!w7nho,&"BQjH9yq^iBAd1Uۜ>5%wfCg1:[ oU<ްo*mÖRjt )Xj φ^y%{U~[E A’h9Gj}=l@J;ݾB!B!|"M;h]h8&YKi !mj눮\\c2'vU[}V` |Uf;3+N]I/a1Y 61~~b梳M%,֤{W b UFtq8qM[1$qZ9Z민Nf̏*w5ѲMg9j?D!G)!B!Bw#J7kgm d*WQUEZ\ =&Ɉjt#ixC)KGřCZh2P_!f ǁ7lGW}_?جft[;H6P6/2 4r\ÔZydCh9,joߘUh9ѯ/EFzHSrwm>p@?o!B!Bni??|>ݚI!B!0!N+JAJ)So3saEYzmt;0`wl9~*Jм;?c)ŒqF4SZh"~lg6oSۘithB`^6=N rΓh &$m"ZKU҅Ͱ8Y?_a a`G\k?6߶qÇr9Я?qw%QkuEuSM!B!Pme0+e]uu_-fMYtdiѠ .<(iKafsG7-LZj>Rh dtQԥ+!TT?[asm]lz$68LRXiqaL+ 9^ܲ9v[IZ>ׇE&]@JJL=^~|O~QleIB!B!ߑmx<b4z 抆Q:4tŎ5רC`Gq $f<",6 o:-Iiv i8N}8FJ!XNҥcbYfq,u0\eB. [9 A'3_)IkO4+ na9#:u+<ߚo'B!B4߁//LF ]f1+V.ο!sui6M`Za6Β'J]Y֊SKwu_]sM@5nEj|/Xxe(0kꎭ!/.\TL w՝0yl &`Z 8 w`qlk=*eY.Vwz; gl3:Qܶ{Y!/{o];$iΉ[Y_gYVƉI{R9U+i Ȋ-37B!B!(xTh5\C2.rB:ӥ$Tc?vȮ&,Hp$l/HjLfjܱjfns ӵe.|_œq/]f.4?hb17(Ę:W:k֮Ц`!_ ]}&^$RQ]lخ-<=B!B!p&Yi(`T|7N^9!: XK>F9{4f6HZc[oԉ\ŷanA7;xNzNsʞB4x\2m@R YPi 9ݠQw-VJicT=n1o(G]uϥh C,!|FRK|]B!B!`p֎87FTެZ.RP6I{zgeNjBو-Ư~?(l6ldv5di%j|@l[f6*}y]PFUձAIv.>|'~xr~tVT'4k/T"Vmɪ(RueΣ5> yr_69m%?B!B!0>??dECLT(wQVb%QjVqmgxZk^'mkFxZ7x"ȏQb]&v~ MWZr< `n-x5xw[--{7>jd/758dk 98j#mۦr !B!B^}q9ߔeF_Q9{8c.;$%P{RRk3Ƭ 53ĩ1rۄu9c8n][Kj߇$iKӵaRjmVU?VAcúkhN֤R jU럛NF56fcEFt4洛RumB!B!48y7 ݝ γteB`%MY&(j&/Ƌր|C\<>krc\9m}=ݸ O>領.4lhU }+S2F7|")uXh@3&7xjh+ADGZ<]kqx>sO۰qfQ=Oq0B!B!~Oug:l!R'(zpJ$Eg2]ƽskY:4Z/E?7 ЋvzZ"aWl󲸡 о#}>}~&_oT7[׋ m5soB_E:ځ LGKޘr9fE~R!n6B!B! yvsZjⷑҷ=f>Zl)0[56&~a 3}_Q@E =Q:3U&"NRJZpcv lZwX 2,MPLn#pqnuQ` =EnXNQĈ^Q)^UR ny6h|Eظ쯄B!B!DGڌ[6vZ r`Co >5'4yϟx<b*!p%jL"tFEo# [LlnT$lcIT{+THaT>Pb*-*mC)Z[Ɉfv:Ì{n(xGsk!QۃBR8oF,fVt?HTRƖovm]Ib!B!B^k᭵DZ댻$yσ@U?YgiH9I3)<}86ȹ8ȯ\`sYԳ^[O2E IDATh4bɿb(q ,5yv&ךsO:-41OsPzf{I%Zp]ku\-a vp7 jg9Z!ڜkшYQ#B!B!y86W^ԋ8<.4$ g=GmwX~ T眠?|$}gks`*M9 'kdD4o[>RjʮSoؚI+jk*-$+b`m/M7^έݞ+组o<}QPXCH|XpiyE۶ͭB!B!ƾq-YqLY%z|غ+̻7GV r)R+&n餖qz@dVBzzrtҞ̥+ s@Hy<T%}@vGp5 $3ԬҚx] Q,PTw  63@ N,sεT݊u\O ZT4b[EB!B!ض ?МtbXLDڸP}Ŋ<tΒ&`_^Dta%t @iOfʋN9EJʫ#o4v>e#kWhNA.wb۶!ybCoMI}ȡsV0#_b2l%(T6CpqڦF4nJm T{)M$sQc=\Uj>lm˰WչB!B!8~>>><Uˍa!7 `.QWR"!5CXCثZd4jw z8dMKX ]ÍpMj;⺁N1;}^9,̶I A]w|ؔ0@fެq^RuRRewhWK)8GϷ@)dAZqǨ&B!By1n F+ )4"ޛScAڋ=s"Sz$mwo9 e#P2%Lᵷz~ BZ}RƏ?zss`/⚎q"Z/WOǡ6KֱV;B5HzI.C(.q rhUhRd`s_]hH,/C듡Y;تmdSs8ym3fe&ͳk܅3?Iީ)YHxQ_7>ZI]Ĵ(hԤ^":/~Pw0q&B!BzVha*= YZ=sƶmFe]dwYRk5MK]~(*H2(jG/@,Mߴoɳ%m6zÒ/"&ޙ9D;ހN^V=Y')Q4_mv Z5@0i-3ǣպzM!B!w48;$8FR1iF3$*rZ7eM+7P2P%XW at}NۯjhZZݐ-,(eU<߰~{ F~/a~`40o>(^]*۠E㵑wb 7:فτ;_x#7Hďeyx>ۼrB!B!/q8p' Y-EDk"͵s[:U,]f]3tP43shcM&M>Z[+6M7&LZ0/;mwU aޕD!#ǘj\MjKk^Z J(#4T˴.|3 !B!zx<|>r#=&-][y9ňۃM V> i}߱(06_~;(&eڎCnW/cė!imL&&.cCUEerQj~2[]x x0,7k>j вekd4Ɗ禂FMpQH=.??q Yz7] 1q8L}V@5`Jڹ*bݲyh U0MzaVqkP63߱UtZ㺂U6\ zIMI/АlMp/½遈|p2v%Jat3m'B!Bym'y^D#]{ w!si_ֺ[xxk)^^2Te]2 t)۱xXײ{qSkSf6't'6P̙c@ޜr2Hs\1bsŹ|QPDq*F #/YDAtW*r8qBPb5biB!B!6|y|ǰW-gY3Ƙ ktLqa`scE1WG_1DZk:}a?-M*^܋y1ks3b6]vQ`v3ma]6Tس$܌kS2 jm7"ڢ-C@YB!BMp;j }4oMiމti*(H^pXK iJƮBŀ쥶!QAMښ sCկVO0~bw}ߑ\}ۛD:x ׄ@UjU߈,A^7x\\\ugN+UmΚh{fUYq^\]]z>0v^öAq'tEw ]Q[l2`۷.u'9d^ڵDlmS8kD~ dx U]J8Mj9gh wbA"& EHXwىI7 MV1 EҶƔPgyj1fң&朚k6PE[sOK;/y@Sv% 4% x{{o\jSk)Gl4_ 1PՎU<[J ?lM^B!B!'qa*#.K*3|fG eFǾ YBЬdNFm+J6ZXub9Ʋ'mS9a8luVPx+ImY/hno꽊~sCmªTbMϿg%- mv?rs߶V0A\3\2屎y|`l&B!Bȫ R/0ƽB51:fҪxQmqԙtiͽ~nlX[\g L@ϸf jؒC[Կz~hzӖRj•Vur 5"EZRuC{Py 3lUHƜqP%U_R7 Nbm%B!ByD9g<47`]xZJ7U޺Lٶ6?x[u1.sV13`%WaRlo؈!) ԪMkӱV'>nżjL[zObS*lS_5ǹ{0FSEu5.Cݜx'f8}Y1wđ338tNYG+P_̖LX)z邨5]]1#o hN@k?"-xzB!B!䅈IiI̡ lI\jӸhfo}x.j:^fbB$c]{ U1tŹ(@J8 O$I hLe "6Cǎ-Ja67~IȖn.TJhC7](Ns>VS9ݴ? KrU0AQ`8=݅60vq?|0B!B!*VbRdLW3]vsۖQ( %l[.1uќ`xsIÌDHӸ@RzGEsy1G ڭ/]tlDϹ$@'֩c˴uZ(vIs1|Xt8`Z Dzߛex ,).@B!B!*Q`IC(5nZQ-޻M)=Ջ7|`' uzJsCԓPK::$@luT |m{8[-q]JTUFV$Bu'f(mF;۱1bqj(.Rd,ڀH{:.(`M5Iw5ǔ.rV~!B!Zq'㸔2ڀL%n 2p\;Ks眃.$ۘU{25fe @{s. QU)d&~~I"rJ8{79,ϟ(`S5Ψ@P[PIMk !8g(6yMY]b|u9FbX^?ik1{,BsRK!B!JX \6MSAS:'I `W]%npz0n4$~ "~5s#~GߣV6dCcj}H*V܊8+x{UcכJڍI)Mt!f4ᬔvS|o kjddɱכm.C 9CiM4yRJly<9Dvv<].B!By9yJXkE)5h/yˌV1jʬ`z?;#;~.yJ6Vֵ GyjUbt}?VZ31sY;G!~}o mu c`o5jeޚ+],g@RB~AM1:\ĺ~BRx(.G1^Y .%!4T [|2.Z2h: !B!򊼽ϟv|>T)@RhYX0g_?~fjgbZh~}18&~'VB!B!Xui]VQ1i:!v3dY8O#_f5")B]m%c̜O]UE[U]ios?>>s@ M61NjKQwtW! u$ݢ9ffPU_{((-m6TcyxbB!B!"9<."WAnyR,['u.4MFSuk<&ӔR$xcȬȤKKJ v@mJSZo#l@]ߩЖ5,R|b[ͼ/肫7ݦFM%׿ث}7RDY aivO!uI.Z&>B!B!Dg9qγ1o합UDܐ;}㱷~ǽhM[wT 1UZ1,`hMɴ|&Ѱ[O7m E)95@zyc^S`SAFpk o-Vij M|o\y"]5c(ㅙ~g'Y<|ݖB!B!8K<<: v@z 00\s66iA9ghx>P(yMFg$V|Hwի4)Ԝ~ZGɬVR>5<7##>\Ã[=/&IJ')y.ffaɮD>ыg*Mkͼv!M,*i[ X3wQdFs14%V_G)gZ|;&B!B{s[Ƒ&wEYJ_vs 7N=~ ۶B.-v4AZc5IA'↷Dך|U/Ex.+?| a)ESN! j_ РY~}F`T?[[u8BqO}-􍨶lp'"5%2S ;͇(HII(ALhgXmht8lA(`šěMDPbmjaB!B!I2Jx.q_Q,@݉څ8TPx3a: IDATǏ'mmoz8 u\pzjT6ץ !B!<_/ hM&aק8׾37IhM%V1D7mT$Vb|yvs+H(Co[L߫}WhB!B!*$lyÙ]ѧV=v;zLVU) IF æuR=g7l=WG߬ynטr0Uyw#.Cc猔 眰!hyz6O&~}cmh,)D n:.Fw]m۫bp_gr(vn]7.<́oDל=j,ׂB!B!W֊R[qT6_ pFĴ%;f6K@j,,M!B`x;G3j{pcF5SQ-՛z yB{ Ͳ.Z1.Ė%st. }-~PcxxЪ([d)1~-+}ڝ I ۅݏf>Z+}GP`6c6 fSc墊7lvl[7?;1}{LaoQzvnMچ57N6`D,ը96^q|_s˨[ZP>wSl&֙%>Px^̾݅. ƙOщ$|KLP \J(?qHw2p'$W\jm.fʝB!B! #R;C[?s#&i} k9^1Ȯk>cIT֞?"7R,CskM9p]E Pely=6x J@4`qP6k&zW:ƌ&I6J>oE~$~ %$܎Jh2r_SmMu~7B!B!╺&2-];[VEC+k-\&$2*?B!B!.xV̚ϪL沪S?!nl(l툨DvzϪA}']捾uB_-:X$~ o~^ӟRc)%˿ ǎm Wjj?b({:)Kq7ec H{?0["c;#ҮW3&\>ED-*C|k b݃B!B!?]Q1m1 ja]NiXBToҊhAiadYlAG^"͠7;SjFVBۏgj?kض 9V" PB^NbddжR kV섒 .m&eAmxQJ'qT!Pv ͗q].Y"ZrشV_z:Ǘ @!B!C$u1$XkDe6u!Җw"y&63,d}eRJl65SA2 s!9PI;~ U&]E+7]]j&Ek3͓Fƭcr R՝}&wl`Uwc*&%U$TzsjmZJiMB!B!#p. .I%#Grx<m VR+,FT[ qnF0"ލẙivԖlێR >>>œ[Ea Ǜ*9qsnj$]tF[>:j͉"+(59EZ{qJE<+x{{eWa-?k ޶= B!BKSk8PҙL.y^tm84N9XVYKZ[Y_ok8YO9|;g)aJ_"͢gVG4,Wk]/EmKGa\97gʸn~[[*WA2-1U\[o\o]$B!BKSF55= =L2Uι_+"ԙ&;G;/r%8M7e[Խb4ؼfuo}֟5HjM9yvu֪x-E?_c+h'=;Ҥ+ޚ{sa1VUl{ng0 (Sձ &$߀Ji%|mP"==㉯/|||Uf!B!B^u(vq5$ V"r%]۶y ϟzcHB3aæuR5X헡1j3Am! ~ mEzۡ dmR׫-2R98QM Ⱦ uc0eML oZYk.ѝd(YޅQ"5Om"gABJ"T۰62#F, !B!B^qx>8Ӆh9 Yw*93t)ÈN;fz*JQZ<kRs3э!|!+(D4Q?ss"ȗ8xlks}̟a9-쮾U:]~r9{CH[7ezP\ C.nO- ~ !4.Ŝ/^c̀G[j+,|Bv)AJExяt&xK< /mo/z.-5S8SpR >?>jv)XO`)"[fcl߭ w[gĚc. "/+ڜxk*@&Fj(gl?s.a,ul֘+Ӛ\$]i:bPlFL} u^XTC[o4m입'`5~a}Ț )c*Jmfc>qmc]1^;JWA0~4kk0zUrּ2+ͮv.N!B!wO|}}\jFf-6T>/u^&"@.34tDZj\kRp]\}/!I-~Tjwy5hL۶|[[ dY:ϬSq@x7^q!5e[`89ttNh r8(fI>B>ZQ~S=M!B!8O&tU aD\S}uuD-*C|!u$ ݸh #"&nsVKƶuX9ֶ\"ئk\V6_# ws@G: 6T1I2 V"kblptj=subz.mKŽ6;Jm۰orY{۶]B!B!?~vZWLJA,UK]w*jZQ(׬LӨ6 pN)9'7 Q &PJkUdB•Zkǁ(嚻:4DJDb2q]sy O;]j;44Dz\9X.mKHLa&*P9^F,B!ByU>O|~~z ؋zɚ,5 IZ+FRPJ\@Gz[ur8.a)nZYac$%l9c)CW!pe֢jfo }c%VlyJ`ل岃ެ)@pmFa(EOFOè9|[̻m֩\M(A{U'E1nzn"=ZNt bDjͮ ?-qr\&}8bnNɷmC5i牯/|||ׯ_x.B0!B!BȫtYWswn8nM$1o*܁gnuz1w'A$\kMV7LJa|V@EVTb2)'bgױ-B!B!J4񭔂rvZO|uh4A+2=%&53rjB4ƽu] =c܆kn_ڙbϛPrB/XFiF1xWV6S=mc, x.6ї$.J70d I(BgN^sY-5B}(c7{cJr?pz۶siTB!B!?~??ZUsaGŒZ;x5sD-DHgMY{<&KfXФbՊ9'jXORrZ;%Zsސ0ΝZ"_x:3d>B:O/a*\7rrEk\{PrZ>PV<F !B!B^/|'>>>Z.wە4%M=y$ut:]g*<{Юbą0^uh:kmrC۲+@J;Ym;ǿlQe^s(Z۠B&kr"vPaaL lݜq$.j94$ABms٦NeR 츹6| !B!B^3c;l֊*xi)ܙTmB4mŜQkn:>2{Mf#4=B c\٠2]Ul5"yg? h2b*z;ؿ37%ͻ]V9}j2q>d!m1a߬,w1l7>⿱xr V׀B!B!/;rJˡ?"HۆZ DZ1:łOYI+l=h~ f7ވn("Df̚ڬuEs* )9238c8m*km@H-bݝ[\\c#bZ z%Nv.Υ% HH${-Mȷb_s ha.TWB.&H8Ϊ־{n̢֊}߱B!B!o y i3JjXQmUf^v^酳T&0z9u^h4s9-9ӈknA۰4c~ᴖ.ÕD:й*R8ILH9a #~B:n"i8mO;MY}k">,&T}tlhm^Z!PƋo nV^ |:42q/>VRI!B!J9+'Кg@wH=m#hLcRSOH[s lN ߧK1iOCy]mGߗ`hO)śK9 @M a_T_u)0AN\aj;Kr`/pQn>oƊe`Fw(cMvM qP䜠UҰn~c 0WA1s2L!B!gՂZ pܾMsڶfí&h&I]Kpu;Rq0]+ oH4!B!Bߛ88,5B:,&4ڬu^^^f$H/|p$GT/gV*[+{7h7#|he4_{E/A9 ];0ޔxеU#67UY^=*#[JJƵZ$xی3|>y !B!B^wʅ9e_8[$'q `G"HH !!> D+As>O  BD"I!Nގm|پ֚|UF>yu{]]]gλ̷" @unW^y^qRY|8K`N498d ܘS;g5܌I]h0v g(a}t%vv @6MѣG=zѣG=zӂѨ\b +PwmUrQ7MǚgtaR 8n>W[Vi |#&3;sgycGŗ^ĸ2#Ji.R-qǨe-#3wqVlj5Ǝ$ }'Dl){O=zѣG=zѣǝ{{v;vW{/R0d2Ln|(|,UV j:`a,2^@Ӽf9 FTP=| F&N|9fV:/MZ7o.`1w]v3M6$8lma~1m<;st2&FZG_ku6 թul6E`sGGGLG=zѣG=zN vF9irr؂,o[03!ޗz?"s(e5s*>aa>}:/pL˧LlG)~s-Gg\|IWl΀UoK,Zk3-B}6"Lt7u5$(I @no=cRfݣG=zѣG=zqn.q~~~0al#̄eh 8YD̒?MS32M!69Qs^"q`Ƙ4d 0apt=;XXAm;ۣ#$X4rŘ8`R{x[,Y).s|aY 7Of _a|(;^,g0 wGGGy =zѣG=zѣǝ'''|rF qՔUtIRE&J9*奲L/50^K ]'b9}'Vc5pPA;Os,Fi`F{l7l6{\r#Ӫ  gڙ`,At}"5](#\j >ִ ,f Z2wadmsMƻh ;!ѣG=zѣG=z)qqqs\\\`&v2Km`yFXg친5؄|f2vQ `a,`/Bp:½BN% LG ac{iܼy#zd d [܄=Pq`*[̷ Y"sLE- # xpin$9{{\\\Xˏʅ p=zѣG=zNq#Zfl?93p\$=Iq;EMb0DzȈ k50Ҕ{X ,EXg"9mbq.O_YcA vZiq|f!;Np%W?3iJ/nC=zѣG=zѣG;2af`?a4g3 YIZ>I98@vvQyQ^RΎ"'얆Sob f#D6 Z>c2W_:1  [`౻aga܌cv'q,.Lj啴Fz5tă]RLḲX NnTfndz0Cҹf=p|rfԚ3QF61{ѣG=zѣG=z\} FU`ayf Iq_ D5bv..v#|2?+qoCTFA'+naacmC Y9  :oEJP9e'hx2uG ܓL:Q:}G]dn.Zgyp"ѣG=zѣG= T)fx`9λaJH%%8bb`gڀu4 AL24ˆi Lrnyc?WMy~?ac@?cQCd!p;OF&~ *ay)mɘT,o(n$ V;ÿ`m1 bafM` ѣG=zѣG=ziqzz+WDyvÏ SxƢaVg ¢6 snˤD<%mп7Qܴf8ܐ<(/! iv;b0vnK`L%@%l@5Gr;9ysF4J w~l? U8 FB# Oӄsk8o1FqE{ѣG=zѣG= #&3$ IK_aBF"cnѳ!48Ï"Lco|?'Le!tP5 a\dR(F*;L9zWj:&#4.JjpnG)U#Ͳ&K13 QC5ff8n`X\K=zѣG=zѣǝD8?ƊNð"w>js;b,ń5cv҈R%!Y1&‡h 0v9*Sj.XLԆ.#9֢ .dwϾvN֎LQiuH mXi-Z)[Kj=zѣG=zѣǝ !`!](˥K V8㸁1d&X|$fe ZjĴHfcsp'.7 ^c&`:yq @Zv[77IX3>Xo|S+ X}+4t9n}fլcBðg @>$F5{ѣG=zѣGwbH5 ԓK馲&+%@91.+|`:P0s0 ~/,GUǒ^fy,&AJ0Tʒ8؜ff!/&Crlєv$LlARΪ>1 U.,@;yXuK@:(} gggxpvvNNnѣG=zѣG=z)whF8<ρ!Ӥ1Ȑ^#*s\t#d 1K%A~ΘhP4#n< nͥKz<`0.;uio#ءU2? 8 IDAT_dyn[sNn3kj;G=zѣG=zq<ϘCI*i9B ULa oIa(E<<}r~.:VF GF AN sbEiCwuc1\=ȿML@cc%F3X+?| 0? j ZKޅs6O5݌njtEqmW1!ߠ.͗CyS=dCs7-Y&3!%?.k/>N=zѣG=zѣG;38 _#: ٸE7 }vl& gg\v'''F]8tLk2&eï VD~b:1@2Uٌu.\$16tLyZt%i∯<7o r uә-tx-bĚo\2$R^N@=zѣG=zѣǝ5I3ՓQA8 $(81j:1S oz򓾯$spPz| k'-yd >0h2s5\/4Ҩj{%Wv>]1ցC1 D v9vn4Bgw=ia=zѣG=zѣǝ9JdoXjy,OriM:6&]SHo W%prx.j.X; +Qhn1BF/5BШ fWlxdYDKx\QWQXF[;n899QpV}܌SѣG=zѣG=ziRK8>sU^e$&=0 82q1#]2_޹ Z-hĶEH"~*X  輇I;PHڅ@00.]:]@YCYLG ijss4}Bɒ#&p i&m?/M ~w~sO~Rs@ї$@:2'!)l6l6!YO[kaMaأG=zѣG=z"I sj(0@NL6i%\Ws(7&"s`pg߉Pq[nU,N庂³$aؓ<$Durrѻ9FGBGk *藕 W?!^y׾5|[9^|Epɀ$܊`[`:n 4av`=zѣG=zѣOjVB`4~GIc<o KX_bӔUp֤HO,OO=³1n6/ǯ1EjYıϼXfxjuLCsc3gó>1_}U\\\{sv;vxs^]l_|Eu]q+W_]: Q&-pƒ +csXV f8ofvSΥG=zѣG=zΊ$~xmf%πIbWpn#l[uuR) 璑Ȍ /gt&t<Z+?OznN %wLv7`$XjO G$q/n{?7n͛g-h 2nݺqqw[/҂nxߏ|#vzdekfSnʒd7-t~V,@ =zѣG=zѣ o~o#8g] Z XL8Vc-ލHܚ93O/@{|O='<{,8::xw͎k3@żDHp*sl6|2Fn\k8)~fc&|swLqh&'ff<'Pmg ֌E;O7=zѣG=zѣǝfQMG q\HYX4i1&3c_ =g=Yn݊x#<ߋ6"Qo+Rh60`lZmm6ƍ <ⓟ$qƍw·PSڵk>'xwsW7n#J0N 0 ÐJf?CkG=zѣG=zqf\ RIHTĚJY9Ykk"75 (#~_Fc ~i|sg}k_O???Ku"2%I=TFT_xcy /0%nʯ ~6Ɛ2Dw&W^y%7dMMu{pDz'??< 'nG[kaUKy{J~ףG=zѣG=zq'~[3/#G8f D"kG ƊK%c }뽑ſHm ?óGqqq`2𾋋 nW^˿?Tξ][xGϯ6VvRw}xV=WyKGRk?@%ףG=zѣG=zq'6Ԇc3be`MAٹhBK.D۷oϢcp} ' /_xqqQBdc/窈իWqw?!r0jo߾ϭ[Jp3W\?As=8==۷F{饗o/\5ivi҄$ocn/=zѣG=zѣǝ4\B;^I)eǓ>GAc^O<#KA,S_^\%UӢ*g}Vy7x/?T c ..."`vqq/| l63n0 G>l6whAMIu 1y9"ssVdL0mf4b"AXm$gwt> Ҕ_&Ac{kCI^<wEo=Ktߤ&khd4fY[%rgxkjyߗ9#KXts=,]BHt^|ɯ614kwO?64E1˹3]wv$Vzj3m\+\xִ&qmog3X_m߿83l ֮{뗖Ǔ稍Z_η2~i) ǾFԞHΩSD{4֞eq\^(khͥe "4I5KkJk+Y9w$5>W~j>B]G,Xܫ-?w9k`$sv^r %!D.vrOgxeh]>1ZSE|Zs|N1+|ܖcnͳ|f)VK567-.6ίkYpoK^¨&埭d~qm_e}Ǧy%Zky3䞢/*&h}|j\Yֿ*Ck-._=8??JT6ok|~βv}ߌx K2]%FƳZD9+DLs-Zp;H>(Y$l|TZh۷w 'I|9YNzmI-.]u4!5pvc5]ynT'<ҽ#Z`9[d'qV~"J8C7Oxpo ('ZP8[΃9_D0͇sq3g/;d4 m\Ep-= դd6r.Cŧ\tYiIC(OBDʾ',tMCP i]kImag5po-x$\߲RE[ġ2'rcx'q-r>qT7|c/ASk8 \m~{# @ژ/EZlӆ?4 `Z'@?(\^ꢍrސ}A[ym/9x-c5[o5jsPkœY@ez'kgc`f0E4i9?O@K/wacDla \{#gzI6<a ?ZuuK,C ޓsp\Sɹ 8~+7%1# "5V"___t\cp3$ğGmS-F$2V: 30;Ϫd @a m}J?m>"]=庛g/A}0JqqN@ds$Z`[5eha(XGv7eƌhðbɵjIZN&/ܨ.֦hkDFk-^xE87G =~Fk*avowuw$}-Ap gvއy;7u҆3Qpg\r#X.V_־K}/wŅ6w*_ƍ7>mo{[y${M7:X\K>b43iS-ol<,l\E<ϸ~ϜpZlQ^G !M @V汖UvL_$=Z ]K`-fe1|XhQ˄M_vvlc6{yI qTP9|0 2Ay6+2ez5QYmeӽFE[XTZlv^ ߍ WpZ'NmJ-M6hX, ;9is,{-n1;Wc)쩁$.)7Hd r妈o6$ψhIE]uچ<ڽ*FZTh.fИUxpW"6jC܈jvN!&Z$[ |=B| ҿF<.?bN"eKc=ee(cg>OKZzݙ'R2bk1 IV_sNmū>4Y6nI`UW{1CR D>-K&DÒ1yDml^15.ei4cZ2?:C\.`UWĹzRHU ac֪ԏ50Tއ!gl|U|U_庍9r!>:y^Υ0d9>>VY/K;ӳ+h7E[ӼGs!&P-L1vcaBTqU tbյVCRv`= dm/bIb zLr_lNg7%sDNv`BrgysZ+*Jl~ x vS$$ke\l"cFr7_rEMJxC=%nq0sb8Y~wwpM x[ނ>"2e͇aI4 S\z0駟Ƴ>s>U/=4)Zi8LnƗ:t`p0-frvaRFk⮕d WI]RD EOeE_;lQE؜@>zmY"#8}.2L hM3D7<)0w|lFЩd$T+Ҙ sa/3^5!4hIeg |&Z TdZL(6_f>E dFokG5/9rS q4@Hދ}|QF8}@Y.l:oj#ǀsVuW\ddX~\[pY4g@T9FP FOy&obd iW|F0_ΕCc%h K,>BzcҢsßyJ9dti!^ΩMKNm7g'LoZϯoMu.ʒL!17޳aĚe[ I?-\"@Y%Ip`r4:ͯz8q\|w}w4[5@р .vЀښx%j[Ͽ J[e%0#SM[c)(.3J mhZuV22قI)nX Ĥsc?3Ć/ Қ>ɒ\zJsZkZ4EFS +׉rW&v f?r}|TWYlcdd=kcOJ|[S//x@I&CZb#T#n6#Ft,0(4\À__??ksܾ}0FӨlpttcu IDATG?*HjZm68S2sk&h2"Ae="yK)i 2 ֨ȬWmӗ"k.c A"s-{b`Y^qrWX^epҪseړ4+pW'fq&Z(( ovJtKǁWڄj,T"g3 9WDƗQJ;8(mrM@h\3\,C=E, r446ɲ\jtJK9cRp+Ygb `)KiXx-(J5crQL6k ꥼ{Y\m1ۈOͱՎO fmqRt%S2y*˭5=mnժ 47i b%~ x S56މ 3pK\* ^ޛ<_X^msUmP6b;M4,rCLhHS{uᝯqK}u M̮ųk6v[kfm]5W:$-8քZ2CKN܃&:X6IOr0 {q]w4Jeom_Q.3E\r-? H'vFGkl ؐiW.ci XU?Tk8tx5 f;a`!JmdϟHXִeLJ&~v 3/ˍXo[ik0"`4&v)Jm|%ִJ'E{E7 a4|DB IlSm*r 2qH<[!u,HEYtcULNkU_?F~iy_l6Ї>/~o8<듟d]|n<X~!OOpD$KBІ 2&'VPP utv| c @ 7Fid`8,*N5,v͜4qͦh`ɲsU LR&쫁攉1NcQ&J$ύ$RYV\kEp6c9,p`3Xe~=Ҿe3Z:Esܤ@O2{؆,2VQo5\q?g67,jnbY6toC) }k/HiW{E|Y\cJ}_,ǭօ䏦5!)PƠTt_K-bW(&Yȓ9~TSI`H&Tf@Ӵ m2dLS7F0CY@77!& Ji,d/YiY}q# 54`j/7 M:hx`,W\<xﱟEq3d DrB-&O%<+ bsp$ R=V@q\_sҤʄbϜBgCڮJ X(Z}fT*^X4C.%;I4ƽ֞sk-[[ó~ mk@n/0BX7O?S<لK/K_>ҥKnv9NNNp|| Às|~~ԧ>Oi@ؚZ*zwrY+5r!{93HgN }4|,2ɰl9ХdnbIu.dbLbX{ 妼;D%U y@ nRehtNQ (a2?eq즰QJCid9 _fõCwt2wb/rp?$)m⌁)4|(h'!ZnVQ[,1%gY7Z R?ILL{NkZ.JZpv\|FDtԨ/y91MxM|n} >ϴF#7λ L)=& Mfd+PG`o#(iϕnQX?>;#ֱ6Y2nrS ׀MS+{3b1sm)JqVșe1icq'YC c`6p|sQXkں$<|H20~^|r/eng"~˾2v'Ϣ/'b,kb`VmM']je[c`ԒN>Z ؍mnJH }'5؍UJI{ڨIr!iΪ򼭵l6s~~xJ[~}+\%@rV%7GUr})X18L¡@<1]hXqh'qY&QC"qmHoW{ȕDi5ciB/BH *1}yEft:]Y|)6j,׵CޗUX؜m.[Z~#e%2C" ODc84>೟~ $}p/r ߒ¿֭[=/xߏqlӄoa˗ccGc=~xdzzu|>ZR5PZ8eJ*_nҍqKe/4J/C㥾~f,;Vs$Aw0{I lGҧw)ޟ%;Usʘ]3 nrդ,jmaT:HzK,"@L~hzPuY&6&JIlvΘTb uaC5SnC.MymQ7?u|Vn3+bherM's4BMcoSI%k|m3OsѿAP6Kg[M>k]1U|'e|Kʜ"t*yX.CQse<BX0f7M#kkrtZ@VHʒ[Twmyp jqFAlʵF2)cnWwLҐ!oj x D4V _kR\0&`i\DQ[؞p?KL5}f IIiq$,ƀ ]3CU>Y#J66,s0 Ee Dd:rt R HfdJ777|۷7 \|> .]iwjA Fz4|/"> Àk׮g~g裏V&qSqZ5Sj3fvS8(ILɅki_)K-J<][&|b4+eV j rT ֆ5*757t]&˲trmͭ gH v2XPִ22GgeўlBfe貄-s Mԣ1A=wQ͸Iڦۢ4nƜV٪!~ xL`!&11w-\|] 3*,ci,kS1 CzL fVHCbZrіc6ZAE ^MZ0 Ɨ`-bR[-{58)+k-,J ķQfX [c$PL|ؔX[z^Y+fl?[>$Hh#M|pL˕9k#Ӽ^ e r)KF;1Y<1W IhZ7KKk@ NI:eP ΰ*U&R: CLڠL6s*W a %y1Gvzަ6!/Ӝ +7.В5j %Z+rx?gk~eJ 3'Y!{1V^Msݱr: k,Z_3N<@ %dv,y,aL/vP;W\z]\]hu*GqrP^0PgY,^fP\aU:Pi~~1Qk0o(Y}6{u+Yj^xJcooկ{03o_ų^z%w}ի>ٟH),Oɦe\|24]6iд0(bcG?ZtwַFPT899{ܺu _~}L#_t5 %uLj"KEҨd[銬qL̙F+ +Zc{.-d1S Mb-ؼsLFKKJ-Z%S/1`efGN.hD.yrCݧ4M01Z^Cl4$xkRyPe 6 :Xðb8#t *S髶E1J (Plg# q/ rb HX&V i|bÁDJspW91&h%2MAβIm)!I6PcLF,zdMsΓ,yx h8lg&1|&]inp24ZIB:4 dsƏ,2{m0 sW,EKxiƬmBeՔ`;h8זsCQ/q;$Üf|d-Ԫ4>|6|Zf[hkd&¸)OZ{c>p .Ӏ%MS|D9ul/v5/YyX/C1q698XdY؏mk G昬mZes'ISpakvw3KrLWvښ),'YwQkFHmkN?d@h$w 5&Ѩ~E]O&Я}9с=7j|rܪIr?aP*kKYJ27c! X kjgD^]O,Ԏ=Tߐ3sjciƓ+Ed>>0%b!&g!&a6G?~E>#H|LJEC667?_ܾ}rMk$o), ,iA⩧C=k׮??o۸y&wggnq-ܸq=^G~.&V*soLn0&56w5l[k湠Ζ$湰Byszm5|D`Ѣ147Yiu #3 i'2sΊ EdʽML|MnZ{'pw%xdyșݼ9ge cV6y@gNӟdIV7H|ZOj܌=X?@L}(4 De#Pf.F@!3ZKaXXuR1Kg[ِhB-ZfkcͤbL61˥1831j5n,T1&V˘J0¥S]15&`csR9y|*:4bAm* -xun@*vW~FOӡ-st`4Mv7w$잕f Q|.K4R^4#cDgW1n2Ɨyg_cjhNt9Cf0PZ8Xr<ycF&jU$hrl&YɔsӴ9K,?seC}eLJ$psևR")̌]Β[4M@*$ߢ=CҜ'#Hh @-uPBv=֮[.Ob1I%^+{J $zDS- 8)Hj2)9lUsNf*_1O%I|8A5hu6o<ԥcnu(!׬մ3ڋ$[;| + z۴AߚJkUr3k^X]Zh3 0!oOY׮]ruu&82gvmd/aOCz7oބ?[ }6 2cpvv<6 nܸW_}o{p57{9x;9Qտ˖x%ZP.-sx'3DtxHgRIqզ @ړCeG{4o-Ei\* 2y)§Bjkhؙdl5X51HYBH, biۚCh1eci`rC$i$1kgfI4·ZI3fʘ@YhjZGe[U7+N"/ C[E530RP-k%e%Bb5 +J>Uv¶S_W4rφk:3s8lb%aaIf5;o*r5GiLߊ$cskmxY,K_{_1G93i*1b$Q]Iā> em1*d92HQ}݇[$7tZۚSt˛|+96hf=F¸YK˹UWF/Lx\"˭"JŐl JBV]r,:9AZbmUphRB'jRXG'S-Hqu hI&Y6GT<XZ3{63X-CJ9hzY^/ͧi?U<ҥKx׻ޅl]+G~IKMl-?[6^ly'LF{K/8wu]&iԵ2V>G S2*M,e}=sׯ_.Ų%X g0,!c@rMVoҩMCJ WrMPr*,ɴkkhZVaZ DxjzzZPv QD{,i6!Mȼ&RZ|5JӘ!uf64ٯtq4bI5aoJĶz$|.Ǐ/@JZ0XtH \[>bRf'ج$"eftիrx+ٔٿwU"BH͋48n7m HSۄhZ CY*ݹ˩%%r UL|8w<ϘB+0:bFˬMy WlZ\R=mZ6,E ߙN X,WNalA5Aw#a5`ZС]*ic7+1_m3+1 W 7X&>@9#P1X%~&O2aρ`vJJ)K8PYr2䂾^&5wd:o~-^Ni"<ġUm.T} ;;c3u* Z67ʹU{k+ dr@>C `F'Ŋ {IdB+y+40 L_EpQ5+@ƨH@L+KvZZFYkY^Ԏ2f:Olc.~]xh2xZ(ZcıL!%SVcJԘ< tq-'*5õ ttLkr5B>CcJ9^g%k2J5721IHSg&x?[yt}jXRZ>=9qn7)˽Abd7f]J `G-0Fc0isMJYp>ט775HCfty9rWJlKHLLڳ\[6Zeʦ5)ھ&3by7^sɻ(#!1wA74#+(p 3(uʛ׌A-zeQmk1ȈL"ŗ*r͛uK^Z[[5ӳ$I-Ӫȫ%!B> 1fl NOpXvL>‚YkX *-D(+i[c`DGqą TcS;q~ͱ9nĸb0ڬ3Nm9WKFȬ!\V:,)ˆ jRǣvO{qm-(89~'֫-( t_4nxRZD0x9Doɜ dFNz)sfRu\+JğEv$pe7ߌ3= E֑g4Ji{ cڅ( 69K YeyiXpye\5>ch TӜkcdi0eS7g[a5 BEЕZgv,m|I<;30Dq-cD9Ư3#H$TjI՚ލ9griڅl4o" .q9orҝ?CaLr2zI$YiT9]'B9\J -3 Ct]'WC[}qTj}[2ôMMnK0B'ɲ63%O98Rn  1XWc՘|Uy_[L)3`*dRp+MؒY fWRë=~heUB=IFub4a:aQ&ʯ tO7cdI9;OM%Zټ,;ut]c[{`҆RwY‌ĹI&`\vJ*1x/v]'k26nӓSrg-I_"_ۼizSxɫ~^Zր*ihzrN$p}0&Ώ9[rҚ SYV#֘k".Uk)ԪY]^1}Ҟ׵5P *$ 3sj4y5U[sY2ST!װC%3z?*eѮu^m/D(e:z5k '9nqʕiHdȮBQ\s:22^h^;ל0q Wl 7$Yɢ[TCsB0@M HʴSQƓdCF ^efk˯8ekJi4M:5qs ୕dK˜")Z6@) d.b}f(gu]!Ӵشh͌74U<=ͱ̉*ҩTsw6k *n-)sQ3WhQS{0ZL. Y2 (L9e€g Xt:&/sZҠcp"u+4)Ģa(!"V}w-ᨱm¸a`L2 |Z+}V^zZҡvkJxE ВC/zL:P׌ZI -&-McZ I+lB\M FC4=| !ي17uUdȁ6cVATXAqQ߸ki+~Ȥ%b-evŵkװn#¾c[s:V77֢oӖȾ6fXMn HܜJZL6ϭy9R6hnYV:Z(q&4!X iIcmQ\Ѕ%~Z JƎV 0յku&$/40~k4L13j{э\ZB[3) Xэ&KEi~bcdF6%&C]Ck6kM% Xθlx| DjZt\ LB4cMe^gE!@y4_bM?ĦֲĴ᩵H}NsXՄl4MՅ5ZY[r]j:X*L2}E2v=caP*5,ΡA q%'~&<[R)@BZJ>2>f6fd"1Ւ & I>Syi #*7p㐅ѡYH-S,-$9``b>6 wMob5|uH3H\ RNcKCh@@2@KfjF3kG@ܨkn\EQ m iXD_ԗet5P. PЀ m•}X#m,@@ޯeRCJdI!ϝ)ʹ3@kkJ^hoyV+Y{e[lu!Ϭ45&ҀVrU>/Z65=/i Y]٠{)Yes.V2#ύBm}A(Zs'qyFXJp|YX8%v .iz8hm66y!B]CW_0 najސHke\=[%5=Gk$!rN^Kk-=LtT'mOΫ מ5NcVa~!u(U\D|T#t|hį-^;f }w45,CeXK(מآFo1[cؚjk״VeQN)t- ÚPim I!ZFk EHff@x۫Febh,$ -lia#'ڦGf״  ρ^il(9Kf@KB[GyJL(y! J{^(yFM+ g]3éyA EZ-+$OnE6nJj}3jQk 'Zeɭ̺t6$?QE7m(+iLcάmsp> F5fZ~MӦH rIӶY RQ`mm~jk*MX]ܒf$P=42@K1j zi뽖u;k 7mӀ=mV̐szطRv5F,Ohmm%sJk74\kZkIV_ V\ۈkkC0~&_hjZs0f^jkWk p0Z@f$jVҩ\<έreIuf.UCilxDIb8I5g}_Rw̕ZBZuMRԇZ!lµ=0 899I%r¯5Fij Cjk U_5@u`ᛅ eKԻiΧrQ+ǔ4 Fj]iTZ X-~*uǥ#hJDߥr[Y C%Yn TMH Hgh 5C#L^ -sSӒ,J à M jRt R+I-?g:M$9dߛF ր 69R;i}se,y Fm~Z^l:m~"ҧR"_Q[뷾ooo?Z&2)RSSBK= "65bvRϵ x?! ߪ(hLJ!B1$UNKm-> zeNXK6̇س^$^;uJ{ڝ30)"=M]L}tq"Ԋ7LM?&)(l|BٓD$n5I XjY3n M!ą=&65KiTVЦd7p~s@y_JT0*:yt%("ٿg}/R[H}+|hI^)weO,ԫ]”#OЯ|>VM'N?IIi<)Y8XLX;T$-&0:ͩo5G+S=7iSIxMڗS~QRkύ* !_WIOkGɻ/Y@Do< ""{[H SSMRm׼˷mK-L$J1n=y'lL1#ķyL̜=Ջq*)O'P#= H,wgO-cXS>yN(-(ܗ$Z)!xs^R 0ׁIݔ6` B^Q* D!b!%KmPzEtHmSң :ѣ?-!S`Cп67rZRhL]lJnI~ɣr= 2[*7R[:T4 3ݏIJ ~n"SM owT5+^{CSdv_Od u|5&s{:u v&M`#"c繺ǚ7A{/)* u u4\( PkO,WW~@1)^\%oכN&󾲖^6*MqbvR=5YNM:bO Ib<56hS-q޹idwY к7\ऑ xe ʻ[Nt7i;} #ZC=;%DV蹱b^WAct)#PxxAc@yEIlt8\i撧}0A)U0zVFZ TQ6g嬜FA>/Z?-䅦"?0|G Z۵A9Ǟz `d<߂Z8J;!7#k'v0mM%.ADQHPNPRC)nIENDB`openMSX-RELEASE_0_12_0/share/skins/fancy/led-on.png000066400000000000000000000012501257557151200216520ustar00rootroot00000000000000PNG  IHDR2E PLTE߯uuu}}}uuu}}}TTTUUU\\\TTTTTT\\\TTTTTT,,---- :JKMNLaKtRNS -..../KLLLLM[[[[\_``````aabbb{{{|||wbKGD_sQ-IDAT(N1 DN R@Q8!مK/z=p&apնtR9V@ǤZ>K'|82DU  &(1x*krPx~4ݷ%!}! 6rkUP5kZ }-7*[%I:v[~2:s}a5UeIENDB`openMSX-RELEASE_0_12_0/share/skins/fancy/skin.tcl000066400000000000000000000002721257557151200214410ustar00rootroot00000000000000set xbase 0 set ybase 32 set xwidth 25 set yheight 30 set xspacing 32 set yspacing 32 set scale 4 set fade_delay 0 ;#no fading set position "left" ;#don't allow to override the position openMSX-RELEASE_0_12_0/share/skins/handheld/000077500000000000000000000000001257557151200204375ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/skins/handheld/breaked.png000066400000000000000000000003211257557151200225360ustar00rootroot00000000000000PNG  IHDR(-StEXtSoftwareAdobe ImageReadyqe< PLTE3333'V\tRNS@*KIDATxtQ й-XA#|X$+;W]X-@ &P'=Qqe l.?Ȍi-^IENDB`openMSX-RELEASE_0_12_0/share/skins/handheld/caps-off.png000066400000000000000000000003351257557151200226440ustar00rootroot00000000000000PNG  IHDR(-StEXtSoftwareAdobe ImageReadyqe<PLTE3333)ctRNSSSIDATx\Q!P5Ɋz$ԍBըd&VMb~¢ ҳ;c%ûܻ'6HQ>IENDB`openMSX-RELEASE_0_12_0/share/skins/handheld/caps-on.png000066400000000000000000000003121257557151200225010ustar00rootroot00000000000000PNG  IHDR(-StEXtSoftwareAdobe ImageReadyqe< PLTE33jtRNS@*DIDATxڤQ Cw Jmip E(~,Un6}(fB[:ic'=IENDB`openMSX-RELEASE_0_12_0/share/skins/handheld/code-on.png000066400000000000000000000014011257557151200224650ustar00rootroot00000000000000PNG  IHDR1%PLTE][ItRNS Ad&C#:b0P].nymab?%(FWYTe38<1R F5W>( !UIDATxgo0@m @adwӺvVU>,t6gq,ɴ T ȕƢ'Xd,jع Fc>g7RuHb7PYzAN? ~} e%$'&IENDB`openMSX-RELEASE_0_12_0/share/skins/handheld/fdd-on.png000066400000000000000000000003161257557151200223140ustar00rootroot00000000000000PNG  IHDR(-StEXtSoftwareAdobe ImageReadyqe<PLTEfff33ff33tRNS@IDATxڤ1 Y@ĥ!SL 8S+a$?<ОOU,9@IENDB`openMSX-RELEASE_0_12_0/share/skins/handheld/kana-off.png000066400000000000000000000003321257557151200226250ustar00rootroot00000000000000PNG  IHDR(-StEXtSoftwareAdobe ImageReadyqe< PLTE3333'V\tRNS@*TIDATx\Q!5Ik7/+""U1`8`9z:}dr/ Tӑ\ڞF]]`}q*!sIENDB`openMSX-RELEASE_0_12_0/share/skins/handheld/kana-on.png000066400000000000000000000003151257557151200224700ustar00rootroot00000000000000PNG  IHDR(-StEXtSoftwareAdobe ImageReadyqe< PLTE33zVtRNS AKIDATxl[05-VC&,^*@ m薚!xZ0AtoGr#BjIENDB`openMSX-RELEASE_0_12_0/share/skins/handheld/mute.png000066400000000000000000000003311257557151200221140ustar00rootroot00000000000000PNG  IHDR(-StEXtSoftwareAdobe ImageReadyqe< PLTE3333'V\tRNS@*SIDATx\ 0ϋnv[*w+<j"u1B](vrA(IgYjg͘Y%9`IENDB`openMSX-RELEASE_0_12_0/share/skins/handheld/pause-on.png000066400000000000000000000002661257557151200227000ustar00rootroot00000000000000PNG  IHDR(-StEXtSoftwareAdobe ImageReadyqe< PLTE33zVtRNS A4IDATxb`bDL HB$ ]L`*Q 0`Cu:н`\pxIENDB`openMSX-RELEASE_0_12_0/share/skins/handheld/pause.png000066400000000000000000000002661257557151200222660ustar00rootroot00000000000000PNG  IHDR(-StEXtSoftwareAdobe ImageReadyqe< PLTE33zVtRNS A4IDATxb`bDL HB$ ]L`*Q 0`Cu:н`\pxIENDB`openMSX-RELEASE_0_12_0/share/skins/handheld/power-off.png000066400000000000000000000003421257557151200230500ustar00rootroot00000000000000PNG  IHDR(-StEXtSoftwareAdobe ImageReadyqe<PLTE++++O"tRNSSXIDATx\I!K.j` N1LI19VT@I;;'BPIENDB`openMSX-RELEASE_0_12_0/share/skins/handheld/power-on.png000066400000000000000000000003251257557151200227130ustar00rootroot00000000000000PNG  IHDR(-StEXtSoftwareAdobe ImageReadyqe< PLTE33jtRNS@*OIDATxtQ B)Σ.DT}?> HDDFN7`_q6/Ֆj`r#դNIENDB`openMSX-RELEASE_0_12_0/share/skins/handheld/skin.tcl000066400000000000000000000033131257557151200221070ustar00rootroot00000000000000# global settings # # name default description # ------------------------------------------------------------------------------ # xbase 0 x-coord of bounding box for LEDs # ybase 0 y-coord # xwidth 16 width of images (used to calculate default positions) # yheight 16 height # xspacing 32 space between horizontally placed LEDs # yspacing 35 vertically # horizontal 1 1 -> horizontal 0 -> vertical # fade_delay 5 time before LEDs start fading # fade_duration 5 time it takes to fade from opaque to transparent # position default chosen position, can be left/right/top/bottom/default # Scripts *should* overrule the value 'default', but # they *may* also overrule other values. # # per LED settings # default values are calcluated from the global settings # exact LED names are: power caps kana pause turbo FDD # # name() description # ------------------------------------------------------------------------------ # xcoord x-coord of LED in bounding box # ycoord y-coord # fade_delay_active see global fade_delay setting # fade_delay_non_active " # fade_duration_active see global fade_delay setting # fade_duration_non_active " # active_image filename for LED image, default is -on.png # non_active_image " -off.png set scale 1 set xwidth 16 set yheight 16 set xspacing 20 set yspacing 16 if {$position == "default"} { set position "bottom" } openMSX-RELEASE_0_12_0/share/skins/handheld/throttle.png000066400000000000000000000003211257557151200230060ustar00rootroot00000000000000PNG  IHDR(-StEXtSoftwareAdobe ImageReadyqe<PLTEfff334?tRNSSGIDATxtI b7KX0$dHjBR%JOSo fr~`"\#YIENDB`openMSX-RELEASE_0_12_0/share/skins/handheld/turbo-off.png000066400000000000000000000003121257557151200230440ustar00rootroot00000000000000PNG  IHDR(-StEXtSoftwareAdobe ImageReadyqe<PLTEf33MtRNSS@IDATxڬOA oNmmFqPP'$ c+2 VJbssy+WC\q<IENDB`openMSX-RELEASE_0_12_0/share/skins/handheld/turbo-on.png000066400000000000000000000003271257557151200227140ustar00rootroot00000000000000PNG  IHDR(-StEXtSoftwareAdobe ImageReadyqe<PLTEf333 atRNSSMIDATxl @1B-\8migO/>~\aqԁ_G`t4߃|EvΎp57N$:y={c; ' !裙/<-~!N|s#N:Hc4PʋG*U,IWk=ѕmc2L 4Ni&#El}Cm W)Nשz an9Y|9%EQRT1${.;h`'lH&Rֶ&ZgL>N*qZ?!8.O^9 xFl͗R Oz>~,/e"m_]W}n.| Nݯ+cgM<ֵ A7f,]7]/:Eu^v(6 W ."(G<NU)LDzYn/\d[apZ+߂Ăbr%X~=!SI9<.Į'u|*A 6cw8Ù><.xyw1q$(b&̳ߒpt ZǸQ IENDB`openMSX-RELEASE_0_12_0/share/skins/none/000077500000000000000000000000001257557151200176275ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/skins/none/skin.tcl000066400000000000000000000001241257557151200212740ustar00rootroot00000000000000foreach icon $icons { set active_image($icon) "" set non_active_image($icon) "" } openMSX-RELEASE_0_12_0/share/skins/pause.png000066400000000000000000000005051257557151200205130ustar00rootroot00000000000000PNG  IHDR DgAMA a pHYs  tIME"%jtEXtCommentCreated with GIMPW6PLTE! $ !"pp N tRNS 3DFGGf| 9bKGD=GIDAT8O! %5̪߮C7G/m8Z~=x'!IENDB`openMSX-RELEASE_0_12_0/share/skins/set1/000077500000000000000000000000001257557151200175445ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/skins/set1/breaked.png000066400000000000000000000020771257557151200216550ustar00rootroot00000000000000PNG  IHDR1%gAMA a pHYs  tIME$71%tEXtCommentCreated with GIMPW_PLTE    #%+$+*,+'     "$*3:BBBIRUVW^_rxy{|SSppEDtRNS 56ACDEFGMMOORTTTUUVWXXY`c~>bKGDtmmIDAT8S@Ai]{^Q,kwvJ.\nS3y˲rE*XeHH-9NɁ_-C h(.A0߂`!@^ d8NG|M&@i"1WD[&<$m 8dA2UsV HEK~#_C]"3ah-_݆C^'v>8*Jå6'9GRE; ls)Dos\+B,v7P0!f(iICbYJ> @`t=FZ+Kݚ`z:G,4{#B ϲ0AU0yỎ n7\U[[17L=胣tze3ZN&*.&\.1&+T+_Z1 r=>*$%fSf1)_2ʩ2]= IENDB`openMSX-RELEASE_0_12_0/share/skins/set1/caps-off.png000066400000000000000000000003121257557151200217440ustar00rootroot00000000000000PNG  IHDR1% PLTEfl^tRNS)kIDAT(S͎ EqO/N In'( 5Ջx=ơ+hY,dds}Dn1rӞ$Q㛨7P-IY e&WIENDB`openMSX-RELEASE_0_12_0/share/skins/set1/caps-on.png000066400000000000000000000014151257557151200216130ustar00rootroot00000000000000PNG  IHDR1%PLTEEZTEItRNSA d&C#:b0P]ab.?nym%(FWYTe38<1R F5W> (!#IDATxŔR@)AP{O<}17z8r C`hPg}N 4͈RFh%O<]T1Ue5d$?┱<-{:Ӟ/j4ͦOx:"#r՝gtʥSܭʑbheFPk.ȭBf QZk=#XI`DgZY^Y+8"oc{#:^Uv(d GC$878"݌wG؄]-ĉ;9Mg܉8y+ _s}Ew$?AM '#/>vO{r5Q(3G@Q,2 18i`lUϊn]%CRMs`SIENDB`openMSX-RELEASE_0_12_0/share/skins/set1/code-off.png000066400000000000000000000002661257557151200217400ustar00rootroot00000000000000PNG  IHDR1% PLTEfl^tRNS)WIDAT(c` 켥@5y\V~`Z?V-վ__@x_?U >,Un6}(fB[:ic'=IENDB`openMSX-RELEASE_0_12_0/share/skins/set1/code-on.png000066400000000000000000000014011257557151200215720ustar00rootroot00000000000000PNG  IHDR1%PLTE][ItRNS Ad&C#:b0P].nymab?%(FWYTe38<1R F5W>( !UIDATxgo0@m @adwӺvVU>,t6gq,ɴ T ȕƢ'Xd,jع Fc>g7Ru1R FWe38<(!R7IDATxgw0 SܳѺGMNI4'ʱs͉. r2W痒0 Szy=/s"`VjF[qlhJ˨ Ybdrp-R6gƓEقK63F;u~n(kcT4 [TIARMITE!ɣ،Qs\ɒ`dm+!rnRKJM+}?_jjBl1hR*]ߌ`?c[Bk,`궟ap^ߟU~߫n=/L8| 9@ s-"BOH "{CQIJD^l7b, `Ug0 c1!{0J+nh3H@ qRԵ+IENDB`openMSX-RELEASE_0_12_0/share/skins/set1/kana-off.png000066400000000000000000000003021257557151200217270ustar00rootroot00000000000000PNG  IHDR1% PLTEfl^tRNS)cIDATxQ 0 Cv;KA= 6tc[b"DI$lHmTg g Ι٤:3oF[={%ג-IENDB`openMSX-RELEASE_0_12_0/share/skins/set1/kana-on.png000066400000000000000000000013771257557151200216060ustar00rootroot00000000000000PNG  IHDR1%PLTEʢHtRNSA d&C:#b0P]ab.?nym (FWYTe381 (3`IDATxv0JQvPP@}ߗjc.I. IӜʏg4,(w:9˜W4ME3j\7݆ff]6P-NvgW/Hb^xS„fb2X^*+s`7EZ:nQ,tԵmagJ1 z!%&N<ᘱs8"ggJ7|7 /ZG % eu#J^ yVAd"nG$% cP$3Š 9r 3B&č@-' kQ4*paIENDB`openMSX-RELEASE_0_12_0/share/skins/set1/mute.png000066400000000000000000000034331257557151200212270ustar00rootroot00000000000000PNG  IHDR1%gAMA abKGD pHYs  tIME%;!/OtEXtCommentCreated with GIMPWsIDATX]eߜ9nimvjHP*鶸AV4\ 7x\ke!!1^#4bEc_sf9p]m|I}ޯ|3bix6z(2./5lqq+g>y:Z [,78kn6>2 ]xbwRYOؕm'UX?)(WS!v2xlb\Y\^OV`({!޽ tN_' )8p]N_] qCA^́a.@ 8Y◈wnD< c̯ }ɡS4@XN8 ⧉WP/v'xGb!j0G;yl / s1`h'wPCuO.`Ez'r0"~C7KBNܕtah,hsrdp{3>:F?Lyo{cق,*(Yy21aY{)4Dt\C=-`X bգ1b$M8J8^ 2CqgU]F3mQfhYTjx3 6R&2%8Lc]fy'%SԤnpIj ҐX})NqGmJ?T?" $^ߋNPc-iUV wjs8aq?cq2 CK}2 Ɯ15rF&9O<恐jTy1<'/Qo(D(ٌ6oLOx<F 5*Ť(l9ڼ /c1pM9MbœEH-MQ)5 0XUgɎ&di ~t3ja }WXjNShWDtK+l!W)ePI|b y瓐[-(0NJTƹWݛg  .y >&4ʕQX|nDr4wZ0өg]O ܳCuc-z3#Բ܋c,R֚&D-,᧍¯Ac\ovq= *W)|{9A&c'E/&&~Mڵ$DZ AP(q:8{u(m uN\o3 rktqOhg(s{;-LXnlۼyy&AԲ|˃k1u. fko]]aVYA4T*mk7h5*R>.OVr0 m:{\($z8W? (FSIDATx͔璂0dET.ЂYF☝_{{/܄0tVgqfTT)pILS>|u:Aef!i $ ~j-v,W, AjM[5~: /mix$Yv=ܶX;6J\8P Nyoy%;sثQNG $DBz膉#LC(b^ȀN9Wp#ʲB5Gy$Z*ލ>b<ăXPaE=|}=Ja4,x@L~:M/<,5b7CBVՃ k؇HPzQEc.IENDB`openMSX-RELEASE_0_12_0/share/skins/set1/pause.png000066400000000000000000000015011257557151200213640ustar00rootroot00000000000000PNG  IHDR1%gAMA a pHYs  tIME%/;X2tEXtCommentCreated with GIMPWPLTE ! $ !"BBB^^))_OONNSSccooppyn7tRNS '3356ACCCDEFGGMMOOPQSSUfgpuw*'bKGDPnL\IDAT8V0EZJV+-(Ry8CƄpMX !HJEf,Dj}iõjd6efBMeG/#:*Sj!PN)1%CVjs>Gp ,cZ2Sg-V"!oޓBZ{`< `+X/{BqZݮ Ⱥ| /!!wbV^Z< M}LuēAOϽSy\l-.G*\sVgW9vm r9f[fQpG!U&TTe߃Z3sIENDB`openMSX-RELEASE_0_12_0/share/skins/set1/power-off.png000066400000000000000000000003231257557151200221540ustar00rootroot00000000000000PNG  IHDR1% PLTEfl^tRNS)tIDATx Eos]l,x貋p>"2]谍m|'8^z25%`p{pQF|&4$j pg{kgq*U9uIENDB`openMSX-RELEASE_0_12_0/share/skins/set1/power-on.png000066400000000000000000000014051257557151200220200ustar00rootroot00000000000000PNG  IHDR1%PLTEGtRNSA d&C#:b0P]ab.?nym(FWYTe38<1R F5W%>( H%cIDATx͔璂0Q.HQ@P{5adev= 9)TZOge0uz J$93BCnf܇N9%۔B7.B v)ZtV\d<#ET˰ڪOӃmMZrJ)dX Zham6vTqD^ It8ʦ AhP2Np.a,'TH@T/l^#|z>.(=-zJNQ1sd)vaywl:^+ssvĿK'P Aqq#KaCR# 'u77*\MD'02ص"~zEB.JQ>Y#}g horizontal 0 -> vertical # fade_delay 5 time before LEDs start fading # fade_duration 5 time it takes to fade from opaque to transparent # position default chosen position, can be left/right/top/bottom/default # Scripts *should* overrule the value 'default', but # they *may* also overrule other values. # # per LED settings # default values are calcluated from the global settings # exact LED names are: power caps kana pause turbo FDD # # name() description # ------------------------------------------------------------------------------ # xcoord x-coord of LED in bounding box # ycoord y-coord # fade_delay_active see global fade_delay setting # fade_delay_non_active " # fade_duration_active see global fade_delay setting # fade_duration_non_active " # active_image filename for LED image, default is -on.png # non_active_image " -off.png set xwidth 49 set yheight 36 set xspacing 60 set yspacing 45 if {$position == "default"} { set position "bottom" } openMSX-RELEASE_0_12_0/share/skins/set1/throttle.png000066400000000000000000000020241257557151200221150ustar00rootroot00000000000000PNG  IHDR1%gAMA a pHYs  tIME#KvtEXtCommentCreated with GIMPW/PLTE .,"""!"!! $"";FHRT J L$' *%'=>BBB^_{|ppzRtRNS %&)256ACDEFGGGJKLMOOPPQRRSTTUUWXZ_``aabcptEbKGDdڸ IDAT8˥ys0պ -(X‘pg)r^™6&g`גlK#aXef,L D@._21x2 kH+OSHJ'kzxO6ƳFB{#F"9R!s#CH`;W`'o,6fbo˵i3'>cHf+ov#N:*Rxz,viG=h<QRt'b w8Цvw)UA<#G;R3۲CBp&*.5f [~AaJlz ߠM_K)*ѣ䥮Ci{[_*cB6yJrƗmfnNwu] =Kv,3gKiֺ|t惹RU2]\1^Ŗ1t:K&IENDB`openMSX-RELEASE_0_12_0/share/skins/set1/turbo-off.png000066400000000000000000000003101257557151200221470ustar00rootroot00000000000000PNG  IHDR1% PLTEfl^tRNS)iIDATx Em{wuȥ (:gÏGٍ%DE'4y盻FXi%<]@H퐼!Ykl q1;t%^IENDB`openMSX-RELEASE_0_12_0/share/skins/set1/turbo-on.png000066400000000000000000000014121257557151200220150ustar00rootroot00000000000000PNG  IHDR1%PLTE"єHtRNS Ad&C:#b0P]ab.?nym(FWYTe38<1R F5W%> (!{IDATxr0E1%l(E2k&s vufg%Xl7ĩTEICq>hirQ,iTl.k2MʆaJ2=ߡz=d̕E" %msA*T.m٪xS˒ AAG4N(G`:$1CN)U/̱ZR8gU;B0vڎK"\Ǿ^jIB~D٢$> !IX $qWHDE7xA_ǟ׊~?\џ] " sO9"@s]]Rh m@1eBq!CE0"Yt U@'H<5JIENDB`openMSX-RELEASE_0_12_0/share/skins/set2/000077500000000000000000000000001257557151200175455ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/skins/set2/caps-off.png000066400000000000000000000003131257557151200217460ustar00rootroot00000000000000PNG  IHDRM9l PLTEfl^tRNS)lIDAT(; Pj>Œý(â&Z/5X,Op*-w^$2JT8@R#Dh%Fn&ۙ=5+;&< g3Z?nIENDB`openMSX-RELEASE_0_12_0/share/skins/set2/caps-on.png000066400000000000000000000010761257557151200216170ustar00rootroot00000000000000PNG  IHDRM\!PLTE<(V)tRNS ;g {e(#Lan2s-)q#JXPC-[%ci H% IYA027nڎstҵM]D,K:yHR^П*rLhY+ } Q9w^fن&_fa ֢*umh]z8fjCsJݶD=p1+Gڔkԏf t-IѽYH`0?rM)9r`0>< Pns bfT>6Gw/†},{E\ 2.C!IENDB`openMSX-RELEASE_0_12_0/share/skins/set2/fdd-off.png000066400000000000000000000002731257557151200215620ustar00rootroot00000000000000PNG  IHDRM9l PLTEfl^tRNS)\IDAT(c`Z  ZV55@w_ׯ~Bl|> XwEN(xV7%_Xs IENDB`openMSX-RELEASE_0_12_0/share/skins/set2/fdd-on.png000066400000000000000000000010531257557151200214210ustar00rootroot00000000000000PNG  IHDRM\!PLTE<(V)tRNS ;g xp!)))0&Xc9;lG8Ve_桞4%m ^7?3u\m:b1 ]lf#nt,XC6׷/^@_NO>M '?IENDB`openMSX-RELEASE_0_12_0/share/skins/set2/pause-off.png000066400000000000000000000003231257557151200221360ustar00rootroot00000000000000PNG  IHDRM\! PLTEfl^tRNS)tIDATxA ϥn6Q {P5!Ov 8 p6SD) mе&.lp!{k!U6[[]s7{:F[rɦC s:F~,IENDB`openMSX-RELEASE_0_12_0/share/skins/set2/pause-on.png000066400000000000000000000011061257557151200220000ustar00rootroot00000000000000PNG  IHDRM\!PLTE<(V)tRNS ;g Z[8!8q:(i`P2>QPJU$q#qJ(c{@egINsU_;_K]Sro6aV ,b~zV;M_VēIlٚ29p6-ϨqPlF ֶ߶\, JHl?ŬKc)Raj[u: Bd_/p۴,a-kaxN_jx*ߠ}Nm{wA*2zO>=Hx}0jIENDB`openMSX-RELEASE_0_12_0/share/skins/set2/skin.tcl000066400000000000000000000001611257557151200212130ustar00rootroot00000000000000set xwidth 77 set yheight 25 set xspacing 85 set yspacing 30 if {$position == "default"} { set position "left" } openMSX-RELEASE_0_12_0/share/skins/set2/turbo-off.png000066400000000000000000000003141257557151200221540ustar00rootroot00000000000000PNG  IHDRM9l PLTEfl^tRNS)mIDAT(Sc`Z  ܂L+? YkFׯ_j _~/ PAׯ"_p׏fBG!\VZ ΁1mŌw.IENDB`openMSX-RELEASE_0_12_0/share/skins/set2/turbo-on.png000066400000000000000000000010671257557151200220240ustar00rootroot00000000000000PNG  IHDRM\!PLTE<(V)tRNS g ;E! \xCoX{g,~zl1E~<ݬQIENDB`openMSX-RELEASE_0_12_0/share/skins/set4/000077500000000000000000000000001257557151200175475ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/skins/set4/caps-off.png000066400000000000000000000002641257557151200217550ustar00rootroot00000000000000PNG  IHDR1Y= PLTEqgtRNS旜9SWIDATW0Ϥ "I_%"B1nۏ$墒K%Ѽu[ils`${L%<ͤ,Ɵ667'Q.IENDB`openMSX-RELEASE_0_12_0/share/skins/set4/caps-on.png000066400000000000000000000012051257557151200216130ustar00rootroot00000000000000PNG  IHDR1#%PLTE`NWDtRNSE?t3!AP >:F*70qg.?mͪvJTs< -IDATxڽSv mfyĎ{/ՔoձXp%Im3!b׳zl!Ǵ*3"vi4 c226Nʒ%IW}ʎced42MDr3J[3\-P(r`b<~;] ˘zЦc:艱51<(tW;F3nO;4`s,v:K8x6wkqbeBzv~&]v^qQIENDB`openMSX-RELEASE_0_12_0/share/skins/set4/fdd-off.png000066400000000000000000000002441257557151200215620ustar00rootroot00000000000000PNG  IHDR1Y= PLTEqgtRNS旜9SGIDATc` :@yRVㅺMt)d"x.DDr-n H:" T >`QIENDB`openMSX-RELEASE_0_12_0/share/skins/set4/fdd-on.png000066400000000000000000000013511257557151200214240ustar00rootroot00000000000000PNG  IHDR1#%PLTE|BXXtRNSE ?F3.5g,>!v0H|Uka<8hwTо:?Eh_jyzKtڛnOj(2IDATxڭN08qմ],Z_,KI#$Hǜ+^Hp!HXAش6%M;=}Y$ٮT*Q#bNS;/݅Œ]h4w[B«ֽ֘-$&Z?P^)q(Hx#qGAk5;BCFI\/t. bqVu 0 SmD_b==N3:OG_$YJ86R’88 !Idi,!i~!Xr~!&? Cz<\kIENDB`openMSX-RELEASE_0_12_0/share/skins/set4/kana-off.png000066400000000000000000000002561257557151200217420ustar00rootroot00000000000000PNG  IHDR1Y= PLTEqgtRNS旜9SQIDATWˡ0 C,ai: e- ܵWxp FICSC)ܢ$M|L3a.h3eIENDB`openMSX-RELEASE_0_12_0/share/skins/set4/kana-on.png000066400000000000000000000011631257557151200216020ustar00rootroot00000000000000PNG  IHDR1#%PLTE[gA:t!h370PpT Dx,oc( S7yY_w[@0p}ʳYWPE: )VU~ۺBt/)FP)l9#O.V_ VEW ֡a`%R~1S/N wY˟w=&Ĕ lTՐ$ 2~ U32IENDB`openMSX-RELEASE_0_12_0/share/skins/set4/pause-off.png000066400000000000000000000002741257557151200221450ustar00rootroot00000000000000PNG  IHDR1Y= PLTEqgtRNS旜9S_IDATc` e   2VFEE-Z ͊r2̋ ] Z @P> ' /3lP-L}݂E!mZ3IENDB`openMSX-RELEASE_0_12_0/share/skins/set4/pause-on.png000066400000000000000000000011501257557151200220010ustar00rootroot00000000000000PNG  IHDR1#%PLTEG:tRNSEF? Ht70>3.A:!gP<*hmvq u)IDATxڽSis 9ۦmzWa[ވ\XFBȗà,u ʫ&8Lh2 @W4mI2jݬW;JjnlUlb1̷ `e*^ѰG!'=L:)3q|k+ZS0'ljR1є%be@IfME Pto? Rw'xygFCeO\ M:Ka ƲJc_&Z N<9}۶#A_fiIENDB`openMSX-RELEASE_0_12_0/share/skins/set4/power-off.png000066400000000000000000000003021257557151200221540ustar00rootroot00000000000000PNG  IHDR1#% PLTEqgtRNS旜9SeIDATxQ 0 C֦!cAxM<$ A?.<:,AgH570th3ke|hw EmbsJPvq $$ˁtIDATxڽSr0t=@-޻NF"$<' ͝^J+W$fkJK|( h dU)VNvrǟ.-7UcamIENDB`openMSX-RELEASE_0_12_0/share/skins/set4/skin.tcl000066400000000000000000000001631257557151200212170ustar00rootroot00000000000000set xwidth 50 set yheight 20 set xspacing 60 set yspacing 30 if {$position == "default"} { set position "bottom" } openMSX-RELEASE_0_12_0/share/skins/set4/turbo-off.png000066400000000000000000000002651257557151200221630ustar00rootroot00000000000000PNG  IHDR1Y= PLTEqgtRNS旜9SXIDATc` 0X@O  KBiӦ\65k D.lڴ+.(r6mRxaMlD0k)PUIENDB`openMSX-RELEASE_0_12_0/share/skins/set4/turbo-on.png000066400000000000000000000015401257557151200220220ustar00rootroot00000000000000PNG  IHDR1#%&PLTE( TU`tRNSE? !Ft3P0JH.:AgT7*85 yqUmsapj,eǮ(ل>`]IDATxڥr E@V{Elh!g  $(àt:?YBH1j!ħ\f`y"j&ǙfKYnNas/K%x/\xcE{<7( j b,/ؾZV `Yۻ{x8 D) zyfr`7""Q$4AvTM +d "Ox=+%;is3ߝc^GoRl,{a81Bvp~tuZ-B=BRGZ2AG1@HGQ0Y *{EJVR ]Iy!gCV}xɕTYIENDB`openMSX-RELEASE_0_12_0/share/skins/set5/000077500000000000000000000000001257557151200175505ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/skins/set5/breaked.png000066400000000000000000000005541257557151200216570ustar00rootroot00000000000000PNG  IHDR DtEXtSoftwareAdobe ImageReadyqe<$PLTE))))9))))9ZZ9Z޽{{))ζ%V tRNSIDATxڄQ Ф UGBKt R)7aDpQ5wm# d/D +n%EX˄fPnĻEX -@FT * l4#[PAEtE|0f0BJ_΁6^Joy`JIENDB`openMSX-RELEASE_0_12_0/share/skins/set5/caps-off.png000066400000000000000000000007031257557151200217540ustar00rootroot00000000000000PNG  IHDR DtEXtSoftwareAdobe ImageReadyqe<WPLTE))))9ZZ9Z))sss{{))sscccBB֜k)))JJ9))JJZZ UOtRNSYjIDATxڌ *.`[; bjr_|&Hf$T7ALfḮմ:313,)A6}z` sj^2 @%9c9<XMA8"՘#a3 D:(p-C3 wQX*}(mgqɝv~^`I׾ahe N [z+)v/8_HMۓIENDB`openMSX-RELEASE_0_12_0/share/skins/set5/caps-on.png000066400000000000000000000006041257557151200216160ustar00rootroot00000000000000PNG  IHDR DtEXtSoftwareAdobe ImageReadyqe<HPLTE))9ZZsss9Z{9JJcccBBRZZ99)JJ99kTtRNS.IDATx̓ EoXb  0 ͔%B蘕 ~2)=y*KJO;o{\ ]nyބO1 dqIظ0O%΍(EY"óЅBd!~xل_FM;l F Ҧ{ <IENDB`openMSX-RELEASE_0_12_0/share/skins/set5/fdd-off.png000066400000000000000000000004501257557151200215620ustar00rootroot00000000000000PNG  IHDR DtEXtSoftwareAdobe ImageReadyqe<PLTE))BB9Z9ZZ!B{Ǥ/ tRNS,IDATx D8Ls(zm_Yn$9 ER^X&aùr ,Š~v:-x`EHD-R&k@viJ?)@ ՀKtLTcu | ?AsIENDB`openMSX-RELEASE_0_12_0/share/skins/set5/fdd-on.png000066400000000000000000000005471257557151200214330ustar00rootroot00000000000000PNG  IHDR DtEXtSoftwareAdobe ImageReadyqe<9PLTER))))ssssJ9ZBBB199ZZ{!){QZ6tRNS}IDATx l%i)DȎ^Y/ >b0O'3; .4sA o&+!++sM%Ue8L(AwX~(_%<]{+nqH.%rڀ! (Qi*{zTGp}2T 0:J@^pl5/uѲ$.iiv{N<==&? xIENDB`openMSX-RELEASE_0_12_0/share/skins/set5/kana-on.png000066400000000000000000000005411257557151200216020ustar00rootroot00000000000000PNG  IHDR DtEXtSoftwareAdobe ImageReadyqe<PLTE))9ZZ{9ZBBۦ tRNSSOxIDATxڔI E4ĭ1V_R\sYHpcB1NH{A4 D:2';]j@0z r,|Hyɦ"-V@I<Cz43)J3PPUj 5hHމX]|'6p^Кdhsݚd.uT}~ |WIENDB`openMSX-RELEASE_0_12_0/share/skins/set5/mute.png000066400000000000000000000007211257557151200212300ustar00rootroot00000000000000PNG  IHDR DtEXtSoftwareAdobe ImageReadyqe<`PLTE))))sss9ZZss{9Z9JJ)){))RZZ))999))))ZcccZZƄcc`mk tRNS\\IDATxڄ m"ii'm(G{Nb" B%\/L 9'-q?4'PDLX f@yLQ(Hǘ0F9Zc Eth h= ۅ%hVbdNWmԘсo@Z}h i>{p2G~'n\ 0w˿IENDB`openMSX-RELEASE_0_12_0/share/skins/set5/mute_off.png000066400000000000000000000006371257557151200220700ustar00rootroot00000000000000PNG  IHDR DtEXtSoftwareAdobe ImageReadyqe<BPLTE))sss9ZZν{9Z9JJZssk99RZZcccVtRNSIDATxڄ P%.YnB/NL7RF/)VHtMVDugZVF$Ngoה0"N]T ` 3qܽ; <{ @ B ɥ} !,3 a(+ aHqc;q\ݨK۽݇yz)]=;#e1 4IENDB`openMSX-RELEASE_0_12_0/share/skins/set5/pause-off.png000066400000000000000000000004321257557151200221420ustar00rootroot00000000000000PNG  IHDR DtEXtSoftwareAdobe ImageReadyqe<PLTE)){9ZZ9ZtRNSރYIDATxڤK DzWк&mg+cH饀K ֐]~!vx0q^r4.wi%B$CdMLO}diBU͢>Qth!7/}sĻ~ 0 ^'IENDB`openMSX-RELEASE_0_12_0/share/skins/set5/pause-on.png000066400000000000000000000004041257557151200220030ustar00rootroot00000000000000PNG  IHDR DtEXtSoftwareAdobe ImageReadyqe<PLTE))9ZZ޽9Z{MtRNSރYnIDATxQ 0Cz.ѡ8`G T:%R9M,lp\XW5`9`(~MHDmj}~@6\r)No545,DWIENDB`openMSX-RELEASE_0_12_0/share/skins/set5/pause.png000066400000000000000000000017561257557151200214040ustar00rootroot00000000000000PNG  IHDR DtEXtSoftwareAdobe ImageReadyqe<PLTE))9ZZ޽9Z{  !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~ tRNSރYpIDATxQ 0CEl{gPkDȣ5)b-QG+Pq&$O1[}O?MwU.mJŚ]ĵoL IENDB`openMSX-RELEASE_0_12_0/share/skins/set5/power-off.png000066400000000000000000000007161257557151200221660ustar00rootroot00000000000000PNG  IHDR DtEXtSoftwareAdobe ImageReadyqe<TPLTE)))){))9ZZν{)JJ))1RR9ZBBZss9))))ΔJJ,<9tRNSIDATxڌ а(խ{?Vyq3C"E`,Qaj\^s_3C3yL#P/ " DG%`oI ōNh! ~I@=Z @4 nF OV1n>G=POPBX; .eYb^ ӽ/ʧz8-`;O ? dʃIENDB`openMSX-RELEASE_0_12_0/share/skins/set5/power-on.png000066400000000000000000000005751257557151200220330ustar00rootroot00000000000000PNG  IHDR DtEXtSoftwareAdobe ImageReadyqe<3PLTE))9ZZν{)JJ9ZZss1RR/itRNS%bIDATx̓ DMAKCTٷD ms0VLmF853y5g(3N"P&mI7;ԑYpD:;^<k@ 8,}+TiiSZL{ SB>uUd!:˦ĐE,e,Z6R`gqIENDB`openMSX-RELEASE_0_12_0/share/skins/set5/reverse.png000066400000000000000000000004411257557151200217300ustar00rootroot00000000000000PNG  IHDR DtEXtSoftwareAdobe ImageReadyqe<PLTE)){9ZZ9ZtRNSރYIDATxڤK DA?q;cx* EPp8@nd5*J׺LQjcLڍ 5uk-0< SuG horizontal 0 -> vertical # fade_delay 5 time before LEDs start fading # fade_duration 5 time it takes to fade from opaque to transparent # position default chosen position, can be left/right/top/bottom/default # Scripts *should* overrule the value 'default', but # they *may* also overrule other values. # # per LED settings # default values are calcluated from the global settings # exact LED names are: power caps kana pause turbo FDD # # name() description # ------------------------------------------------------------------------------ # xcoord x-coord of LED in bounding box # ycoord y-coord # fade_delay_active see global fade_delay setting # fade_delay_non_active " # fade_duration_active see global fade_delay setting # fade_duration_non_active " # active_image filename for LED image, default is -on.png # non_active_image " -off.png set xwidth 32 set yheight 32 set xspacing 40 set yspacing 40 if {$position == "default"} { set position "bottom" } openMSX-RELEASE_0_12_0/share/skins/set5/throttle.png000066400000000000000000000005371257557151200221300ustar00rootroot00000000000000PNG  IHDR DtEXtSoftwareAdobe ImageReadyqe<3PLTE))sss{Zss9JJ9ZZƌRZZ99)JJcccޥn"5tRNS%bIDATxڤI0 P; mi x,?m(B`U;%ugʵ!|%&8m5hm4O*452L˪vƺ(IENDB`openMSX-RELEASE_0_12_0/share/skins/set5/turbo-off.png000066400000000000000000000005571257557151200221700ustar00rootroot00000000000000PNG  IHDR DtEXtSoftwareAdobe ImageReadyqe<6PLTE))sBB{9ZZƥ9ZZJc9Z~6tRNS⿿IDATx  D r~dS~Ih҆BSу!A.;kbHI|9jb Z3_ހԩ;ܲD"3Y 9Nm;ˊ.7} p2/9~8 my2c`AQIENDB`openMSX-RELEASE_0_12_0/share/skins/set5/turbo-on.png000066400000000000000000000006721257557151200220300ustar00rootroot00000000000000PNG  IHDR DtEXtSoftwareAdobe ImageReadyqe<EPLTE))s)BB{J)9ZZ{99c)99)9ZZ)B1)k)JJb5HtRNS@^IDATxڜ E[Q:!&kbZ?;4bwIh 2K3^ WgH ˉ)#P- H"@*"\">[&[j;ȽyےFs&"WJ2REE0 bK;߯%KtgWu-(I8 V=k{ 0 vxIENDB`openMSX-RELEASE_0_12_0/share/skins/throttle.png000066400000000000000000000010321257557151200212370ustar00rootroot00000000000000PNG  IHDR DgAMA a pHYs  tIME" ?tEXtCommentCreated with GIMPWrPLTE .,""#""!! "";=FHRTJL$'=>pp?筷!tRNS %&35DEFGKLRSTUWX`abcbKGD%IDAT8 D6_4\nΙ ]wlLc1r[5, /Mae[~7 Zd3c9pDkH,֐JhA! q$! 8)T1}8OW!p dOfJ}ÈW7uq;Ng ( }XH7IENDB`openMSX-RELEASE_0_12_0/share/software/000077500000000000000000000000001257557151200173735ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/software/README000066400000000000000000000004761257557151200202620ustar00rootroot00000000000000This directory is the default file pool for software (i.e. not system ROMs). If you put your software here (e.g. ROM images), openMSX will find them if they are used in a replay or savestate you are trying to load, if they have the right sha1 sum, i.e. if they are exactly the right ROMs. The file name is irrelevant. openMSX-RELEASE_0_12_0/share/softwaredb.xml000066400000000000000000022443011257557151200204310ustar00rootroot00000000000000 1 2 3 1903 MSX Al Alamiah 1986 KW 150b249b6402ed89ed03cc7125f9472c931dcb2a a7c116e987f561aaa6199c3018433c431268ca86 10 Yard Fight 801 MSX IREM 1986 JP fd15698518172bcde3318f08ee032531cbbb9f5d GoodMSX33536dac686b375ba13faf76a3baf2d6978904e0 1942 - MSX1 Version 706 MSX Capcom 1987 JP GoodMSXASCII80733cd627467a866846e15caf1770a5594eaf4cc ASCII8da397e783d677d1a78fff222d9d6cb48b915dada Konamiba07b1b585386f887d4c7e457210b3fce819709a Konamidd1e87a16e5fb38d9d729ef7edc6da21146a99fe[a] Konami9c544426a02312b7148249ef20ae6e2021b45db4 1942 - MSX2 Version 707 MSX Capcom 1987 JP GoodMSXASCII8c9135b1e451193bd4c06d8143eaca934cc89fe05 ASCII8d9826cb2044ed0224124f2f31d0bdc1fb840d81c Konami2fc15ef2c728a2472cfa3a34053e07f821dc18d4 2010 - The Graphic Action Game MSX Coleco 1984 US KonamiSCCbe56425b2eb0a26edad1b5fd26e876cd726869cfGDX Conversion KonamiSCC7cae05a9416e26a7649f7c83d6444eb46f46f6ecGDX Conversion ASCII852cae4dae557fb7d80d9736147e9388f103db98bGDX Conversion KonamiSCC073c469f82053c1e5283691cd61c5ccae37d909fGDX Conversion 3D Golf Simulation - High-speed 196 MSX T&ESOFT 1984 JP GoodMSX0x8000Normal9a63d48c4fc05e76c9de25e5c5f2a0c99b5d1be4 3D Golf Simulation - Normal 44 MSX T&ESOFT 1983 JP GoodMSX0x8000Normalf8164d3872b5d54eaeafc6de3bb98a472f0c828d GoodMSX0x8000Normal6fe8c1725c35d85a5e489c0c8d21134def09ecfb[a1] 3D Tennis 45 MSX ASCII 1983 JP GoodMSX0x8000Normal8f71dddd429173df7ce52c5245af1a76b482a901 A B C 1907 MSX Al Alamiah 1987 KW 5bd09e5a9669c58365c87f32a0e9a8e59680d5c9 A Life M36 Planet 963 MSX Pixel 1987 JP GoodMSXASCII86bde4e6761286a2909858ecef04155e17072996e A-Class Mahjong 1063 MSX Pony Canyon 1988 JP GoodMSXASCII1666508bbdc3b133b9a69ea96789bc134eea46796a A-Train 1329 MSX Pony Canyon 1989 JP GoodMSXASCII16SRAM85fd07ed5b7c0492d42ed91a54a0fe80ce6e4e40d A.E. 105 MSX Programmers 3 1983 JP GoodMSX6b8a684ddbadd798a8e599449b823bceca9cdb58 e14970387aa8927256397cd35fd8c356a0502143 A1 Spirit - The Way To Formula 1 1846 MSX Konami 1987 JP GoodMSXKonamiSCC937464eb371c68add2236bcef91d24a8ce7c4ed1[RC-752] Aacko Presto 3010 MSX The Bytebusters 1986 NL e9c35caa8544f81d927cb610e850085e5d81ceca Acrojet 1050 MSX System Soft 1988 JP GoodMSXASCII161f17e46190635a3214057d33ad4a768939d5a071 MSXDOS293e60e7976fccfbcf2d2b9d754d44ab4d778eec0 Activision Gamecase MSX JAM 2002 JP ASCII8d76c7a5c2d28684f0d67114cc7b9707b296e68a2 ASCII83460ef57fecae3e65d41dee28cdd8f25bdab540e Actman 368 MSX Mass Tael 1984 HK 3ca4a1ab35b3cbe3ddb238ce88ca9df258ae37d6[a] GoodMSXcb48969f27eaebf24c61f651399ec2433d85bcbc 2707dcf377066b3c1755c56cc4c2f953d82f6524[h by Prosoft] 022e88a3cc0e56ec88167703d801f1ce27e37940 Add and Subtract 1904 MSX Al Alamiah 1986 KW 8f5e7717a506e3314d48ae9ff5e6c6c8a3df72eb Adven'chuta! 5 MSX MIA 1983 JP GoodMSXae0c0b0c9468e137eba5b52f96241acd55d9746a Adventure Kid MSX Open 1993 KR 16kbc7c1814aec465d3a27a638697f04a351604011df 16kb0f984330ed5dc929e1207cf6afcc9a35837408d7 Agigongnyong Dooly (Baby Dinosaur Dooly) 3280 MSX Daou 1991 KR doolydbac346f03950fbef81a003b4a42b2898a4e54c1 6867a6f616c457101ee30df3d3d57ea7f8d0a14dnormal rom crack Akumajyo Drakyula - Vampire Killer 696 MSX Konami 1986 JP ASCII8cb544661b6da6a35c20bfe503974f2b79a3c4550[RC-744] GoodMSXKonami5ef7d03b138a2023f6def241b671c666f97ed83b[RC-744] Konamieb58b57f468e12c4300d3a88337e6c45e532c7d3[RC-744] GoodMSXKonami5460a88c25386b1b950b57fc1325fd75587cc825[RC-744] / [a1] GoodMSXKonamib58e77f819dfbde72845801cff4b4e0a2f20ef5f[RC-744] / [a2] GoodMSXKonamie78511a2b8cca37a96aa396c938863c92a20c797[RC-744] / [a3] Konami43084db72dd13112450a7af4b513e13e9266defb[RC-744] / cheat version Konami90b57b582675360916ad68ba65e9284926d93f4a[RC-744] / Ulver crack Konamiaa13b346e11644f8426fc546b359bc6fd7c6b357[RC-744] Konamibf0e576c84efc27941a378b6c77d3a999bac9bb1[RC-744] / ProSoft rip Konami971241cc529d06281f26c3b7438ed59a3d63aa4f[RC-744] Albatros 700 MSX Telenet Japan 1986 JP 3a9e1020b693b244f8437e938cdc7e0917eb441b 13d38cc80112baa9bcbf03569988c163015d434c b31f223dd6304244c49c026e26503b586ebc1885[b] GoodMSXec5111e3b06d64ef51196ff87448e1c62be4cdd8 Alcazar - The Forgotten Fortress 374 MSX Activision 1985 US f3815211c6183b4f9c26b9e7d0a12c3b3870bdda GoodMSX807676038cbba043b8099eba9c5840a4811a7e59 83ed71c0e245a35cc4c4f622a6c88c026140748f 647733c674138fc6a295a0ad3e4d627668a04cdc Alef Ba Ta MSX Al Alamiah 1986 KW 148c9a4f316ff73c4e802baadc34bf852492821d Aleste 1055 MSX Compile 1988 JP translatedASCII1611b712633bd0e44e8d59ceba5a2d6b1c20a43ef9iPhone version (English Translation) GoodMSXASCII1612f6f31f495bfb384c9ca9067bfbf8f98af6adf9 translatedASCII16be2d023f5f1ee4cfbdc712c8ef9eda9d07278d88English Translation Konami4b506234dfd3b6aa1c315b40cd8ddca024dd15c9 translatedKonamif4f7c789dd320192fd544c666ceab720c7f3a747Portugese Translation Konami54b05b9c064232d75299fd31b53335f530c20aea[a] Konami937f8d412e9810c8aeb9d92182cd3b007ace30d2cheat version ASCII16fda4422b86daa233fa4e6104e45305e7cb383053 ASCII162761df1ff6bf826e00dbd4f9a346aa145e91c6c6 Konami030557706d72199acf17df4480f6b1a8ff798d1e translatedASCII164d0f5ad0f29db7f5e188fe9e84d30079159c1614Spanish Translation translatedASCII16366d38aac137899bb1ed3d44c28f1bd27af3af71Spanish Translation translatedASCII1619022db5f9d229e9b5f38ba02d7478ad3310d4c8French Translation ASCII16ac2210f984a0054c112d56676d22b5fac8f916cd Konami72b00f0a63ad6233557d681d60cd002a9b395344 Aleste 2 Theme MSX WYZ 2007 ES 0025565bf89b8dd698fcf49f10345ca29adbc2f6 Alibaba 1905 MSX Al Alamiah 1989 KW ASCII8f9e8cc6e47d5632e3b28500197f8e9187295c52b Alibaba And 40 Thieves 95 MSX Sony 1984 JP GoodMSX639cb280544b060a618af23daf234a44baaf88de acc14e9886a3281b658dc458c1ae93fa9f24db04[a] 41b3b1830084ef00b61339c46c96bd0ef62cd6d3 f19712f0da98b5423ebec3150f5741dfa25acf85 cc93d295a7a5d6b669328cd49b1825c77fe39705[o] 49fd05804c78c791732d2f73b83b3af56999f270[a3] 1643d2d998df2b37e944072417ecb259934b9302 Alien 8 901 MSX Ultimate Play The Game 1986 UK GoodMSX5fb90be078e432db92dd39e621d12f612dbea4a8 3de0cea6a4575e631fee0db985e0f0f584761980[a] df1ee5a8b5f2b203cbf73a553f0bcdab3d7fee89 825fbcc771a698967873018999c7e8750c6e734b Alien 8 Remake MSX Manuel Pazos & LordFred 2009 ES Author4a3069426214aeecac1a57632a9978cbfb2acc21 Aliens 2 900 MSX Square 1987 JP GoodMSXASCII165380e913d8dac23470446844cab21f6921101af8 ASCII160d9c472cf7687b86f3fe2e5af6545a26a0efd5fc[a2] ASCII162639792df6f7c7cfaffc2616b0e1849f18897ace ASCII161e7ac21796642c3ccb66b0ef0f00ba4752428acaUlver crack ASCII169b04d06fa77c4f0757be28eb758b9710769e9759 MSXDOS292a0ca682a367cbc1c6c5f96f8b84619a43e9f12 Aliens Sound Demo MSX DVIK & Joyrex productions 2008 US AuthorKonamiSCC91d3f1463dac4d5fed8d4ff34bed0d388c61f164Please run on msx2 for better graphics Alpha Squadron 375 MSX AG Corp. 1984 JP GoodMSX1d81e6cc165b3330b9b3c8b4f4da0417b1ab901e Alpharoid 704 MSX Pony Canyon 1986 JP f294becf726a704ced1d7214f1c2b1edc77b6bf0 GoodMSX97e7d0ee40c7d6ce1a8edf7b06e5014e6745cfda 627e4d311dfe8a43a629e63f444a6f41eebad835[a] 73e3a43cabbe2a4ea504a06b88ccf7f0b066c726cheat version 7e5264d38c9d83c62f60a74d2c51476a7ca2856f 092ad12c308dfc6d940ba8b1369b4931a4fd45e1Korean Dump Alpine Ski 2860 MSX Double Brain!/Methodic Solutions 1987 NL 6e15b3d92191bf2c5ff07435c9849e31dad3ed1e 96e89ec0a2b4b35e03436a8bcbdf7de5cc4b6aed Alter Ego MSX The New Image 2012 NL Author0cf251004caf6d874a66b05e100b599e5eaf9d32 Amazing Kid MSX Husnisoft 1990 KW 55cc50740d68d2558351cd8d125821dd1be70a1c American Soccer 1052 MSX Nippon Columbia / Colpax / Universal 1987 JP ASCII807793d57b00ad8927a3c1f0b1ed61c606441dbbb[b] GoodMSXASCII81545566bd7decb1612009daa2daa71758fa6e4a1 ASCII84a8eed643993b86e71ea46098a580ec15038cbba[b] American Truck 372 MSX Telenet Japan 1985 JP GoodMSX88fa967c420f7090a11e5de1d3674f4527f53d52[a1] GoodMSX076abc988d51faef30143750dd41318a8a48c5c4 fd6cc08d4842a85a3c741cf9c30883ddfc378895[a] translateda6cd42c6cdebcd9b20330e64a38bb7abde5bbe30[tr Sp] e1c0aaf53501aff407c49b5332c7b5e3977317c3 Anaza - Kaleidoscope Special 885 MSX GA-Yume / HOT-B 1987 JP f67ea4e2dd0356e7747b24633dedae8c414e7e32[h Edgard] 2f2383a8f264172e88465a4cab66e04fbfce1d2e[h Sammi][b] GoodMSX27c40ec7013c1ad1f9a7e1ac1e0c8be99f1e703f 876c09450bc2b36a873e1733cfa31c6b9d080ba5Ulver crack Andorogynus 888 MSX Telenet Japan 1988 JP GoodMSXASCII1655183b7b7df66950dbb65ef1065d7d987e0fdf5a ASCII161064aa5c8a884dddc0a27147ca70881e04988725 ASCII16ae3c3c2c022f566d2e790aba193470d5826f36cf ASCII16875a262be44a27a8357f170080fb8466c9599b62 ASCII16a8186cd3ef78662c87665df52026ecbae4866bfdUlver crack Angelo 376 MSX Mass Tael 1984 HK 3a4a65dc6d540297a7aae0a9914897aacf740807 GoodMSXa1abfc53a36d387b9f0687e3f5897c1ebdf21517 Animal Land 886 MSX ENIX 1987 JP GoodMSXASCII8495876c504bdc4de24860ea15b25dda8f3b06c49 KonamiSCCdb33011d006201b3bd3bbc4c7c952da2990f36e4 Antarctic Adventure 25 MSX Konami 1984 JP 2185be579cb51729f66bf02bdf3a7b6a3cbd7b40[RC-701] / European version 44deaef747c572a4550681fcccc1bbbdc46a1ffc[RC-701] / [a] European version 292215c95c82948823eb0fbe1b081f14d6b6290d[RC-701] / Japanese version GoodMSXe894a38f3e2ad435cc5d17a66600e6016b5e47fb[RC-701] / Japanese version 8999a51c94460c8db1ca033b64b568bf919300c2[RC-701] / [o] Japanese version e8af1da8478ebfd796b1f04a2429073259761ebb[RC-701] / [h by Prosoft] Japanese version 63d3755c81b2c294ff3bd96cfb9f5a2f3d83b86c[RC-701] / [h by Prosoft] Japanese version 46d1c97b97a837387445e6039669fd9d04df8873[RC-701] 0ede48e253729833963f1239932d8d26a5e618a1[RC-701] KonamiSCC6bd5b97baceb3bc8da3571590e2a6a761bb90b48[RC-701] / [SCC] scc+e1c7169bd44a3c67f2941a14699b954d7e337d29[RC-701] / [SD Snatcher RAM SCC+] c7af3306dbae898fcc077492cc4574298951df34[RC-701] / Konami Antiques MSX Collection 1 KonamiSCCc40d24a31e36f786688a556c1b63c1dda40325b6GDX SCC Version Anty 96 MSX Bothtec 1984 JP 1a00eef3df449d71abf4be5a4c8d01ba9dcd6b79[a] GoodMSXf236345f43828597739f4a326318b6a3876ff73f 4b6147172c12f74fe5b2386e6cc78ea0d763316a 4175abdf1383f90a08ebf8a2ae3b065ec9f929a9 Aquapolis SOS 4 MSX General 1983 JP 0400bf9fcebf76ead52a702a00b51eef991f730aonly for MSX1 [h Sammy] GoodMSX633cb458eeab0fea59a70a9a620b7783a44eb0a6only for MSX1 585afa3e19d3c7dfa8218cb14a2fca828f040917 cf6d6b78f38a680e67251b3a56a7b590407441ba Aquattack 90 MSX Interphase 1984 JP GoodMSX93462ac9a322c2e5e0d996fddbe9eba2f3436931 Arab History 1908 MSX Al Alamiah 1985 KW 3ce544aaefdb8a916a1639afea774eb75ec4702f 80a2c10a28307efc765260ff4d422c58b65921fe Aramo 699 MSX XAIN / Sein 1986 JP GoodMSX0x4000Normalec53d72e41e114574f4c116cab19e1633f3da207 Normalc6683bcf9934d695d5ef828e11cb36eb4d86a7b5 ARC 2865 MSX Parallax 1990 NL Arcac43445c9e0a097b6d8116017645fe96b054a111Original, confirmed by dumps from 2 different sources Arctic 1342 MSX Pony Canyon 1989 JP GoodMSXASCII16e8e416977f5dae3635362c513bba004ea89a0bf4 ASCII169c89215dd36eded769691708eb8c82dec1ce5109 Arkanoid 887 MSX TAITO 1986 JP 5002ba598469b34af25bff5011ccc70ed3723b0d 45ce64a455a2b07f3c8a6d541cda3b0e058cc732[a] e1527ae16d33a09d9cf3d4ea52e63337d2273910[a2] GoodMSX2183f07fa3ba87360100b2fa21fda0f55c0f8814 c30d7363fcfb38272ae927c684df1c52c7de1a72 Arkanoid 2 1053 MSX TAITO 1987 JP GoodMSXASCII81642891b693df616f27431299352e1d7750ac94b KonamiSCC9e790499af6916f82809e35d68cc159efe580f99 Konamic843b8b0e5ed98d3bae68e701fb709ea178ed968 GenericKonamib210047af5ccc352cc044bf6c1419552223bf8dccheat version ASCII8452deac5b3341437e70c5b2726aa26cc1d074fba Around the Clock 1912 MSX Al Alamiah 1986 KW fd9eef4ea54e31e9ffb121c079e4b76c990d755c b49a41e57ddde304c9706e9b65bbf63733c14c88 Arsene Lupin 3rd - Babiron no Ougon Densetsu 1235 MSX Toho 1988 JP GoodMSXASCII8c4a919f31d20008b5d6540aa5d2be59aa4cef5cf ASCII81aee947b1ad6d478c0def550868a3851c2910809 GenericKonamia3fbf7c2392c1f97d3a0946a5d1654bf4f7d9b0a ASCII833b66cb2d808e0fbd85be09165934663395675db ASCII8339937d990cccac286e7626ae776da48a1f9f7d5 Arsene Lupin 3rd - Cariostoro no Siro 1038 MSX Toho 1987 JP GoodMSXASCII8e89fc7437d8f9aed1640689a32ebbf047699b147 ASCII8ae8172bcf76adfa3d401467cbc89cbec2db86ee2 ASCII85d82713f36e9395092f16663084fbc85f05c664aUlver crack Arugiisu no Yoku 1054 MSX Kogado 1988 JP GoodMSXASCII8a8059dc1820b461418713acf2af14f3a25734bd3 ASCII8e839f3ac7d52c427ecc011d01e6e241f88ca850c Ashguine 1 978 MSX BIT2 1987 JP GoodMSXASCII8f57b46154cf0711e3f5218e00f67c375d95d1d5d Konamia8eeac428a5992a2390b1827db1358f4c733e200 GenericKonamibd1435a19ba895149fa5b88425316171d165c323Ulver crack Ashguine 2 883 MSX T&ESOFT 1987 JP GoodMSXASCII8efdf63f01293251dc593163a42f31556fd042282 GenericKonami4edb5ed12c1b9bc7efa571ca67ffc732f53d3a4a GenericKonami8a65786d9c6b2fe7c1643c27316d6cbb35fbfd24 GenericKonamie58c1dd46067e2abaaf3ee6a70a7e5541a32017eUlver crack ASCII828354d2ac06789d6b2cc88b40e79e378a9d97f16 Ashguine 3 884 MSX Microcabin 1987 JP GoodMSXASCII86fd90e7573abdb5a7bd6e4e8dc44db80fc163e30 ASCII8bf96a360083c5c26430940546827e91a69ade63e GenericKonami32210a9cbbb51e30ff59ed44bf32e628fdf6c42c GenericKonami2e388978f765bc5fe1fa9a42e1f059de5053721dUlver crack ASCII87c7701eee4c5904cb325453b387e3674ee8db95a ASM Cocar MSX MSX Informatica 1987 BR 194ff03c3063a852c831b034fe215208802b64cf ASMSX Demo 1 - Fixed Point Arithmetic MSX Karoshi Corporation 2004 ES Author66acd2f8ef08dd4cb0dc7ef5176650fbf5828acf ASMSX Demo 2 - TV MSX Karoshi Corporation 2004 ES Author87eb1620474039740bfe0e7e3bea09085d85f247 Astro Marine Corps 2112 MSX Dinamic 1989 ES ASCII1672815a7213f899a454bcf76733834ba499d35cd8 ASCII1648b335806b30ec99609a5b9e82ac896b6cd01b75 Athletic Ball - Ghost Flipper 92 MSX ASCII 1984 JP GoodMSXfbe43d67a5bc0aedb2e194a4e5f9497b7488362b Athletic Land 362 MSX Konami 1984 JP c0476fab5ca3c69b381bb8c43efea1277d7a1b43[RC-700] GoodMSX866cb3ab10f17e4fb356fbd7277bbf17c4e27ebe[RC-700] / [a1] GoodMSX90bdd12d632d2e909ac5d69235ad0366f430b734[RC-700] 29e2f2daed13cef12c20c9c0d383b99d18e489de[RC-700] / [b][o] de9eb0e9ef183f49e9d9b631f8818f9cac5df409[RC-700] / [b] KonamiSCC 6a955432a641d87954763ef58735fa90495b65c8GDX SCC Version Attack 4 Women Volleyball 697 MSX Pax Softonica 1986 JP GoodMSXc5f1897c416f62cd58cd6257282e3428155cd698 c6c64d803d65ce439d00034a86a570f8c77d18d5[Topia] Azzurro 8bit Jam MSX RELEVO Videogames 2011 ES Authore7ac2fb16598e3e446a7d1c9708f10cf4c332a77 B.C's Quest 1798 MSX Sydney/Interphase 1985 JP 0d504c359661ac731de07a7319afd0e31f01a5d7[a] GoodMSX4c8a97669017296c2360ff9d53694a292197991e cd2159b9c60f3ca7bf9f4b3f3163203b9ce2851fcheat version KonamiSCC1f277566ca64519596be173f2e2e34865b6f1fb7 ASCII87a155d6302a522372ba7a13962f053592fd49996 Back To The Future 827 MSX Pony Canyon 1985 JP GoodMSX0x4000Normal469b07eef08348c9acc14e63ba0cd35aef236983 0x4000Normal1a6a751334c663fbfdf93f617a7d779f76f488ce 0x4000Normala7cf4cbe12c8956c0a51a5c2336fe70aee7f4a65Ulver crack Backgammon 593 MSX Tecno Soft 1984 JP GoodMSXc345357e71d1b8f2c9663bf28abb5a3a2957d49f[a1] GoodMSX2b8878609bcc0d1555ae250d621ba6e9df4afc52 Backgammon 2950 MSX Electric Software 1984 UK 363b5a8c94cdb63a7ee72b9d25f7b5053cab319a Baduk MSX Zemina 19xx KR 8301e32565cc81023c71da10f2f3e2f8befb7e3f Bag Man MSX AG Software 2007 IT Author0x8000Normale984bfb4251e7b9f9b6b95d5e1005e67c223ae67MSX-DEV07 Bakerman MSX AG Software 2008 IT Author0x8000Normal619a2d42263da74a723cceead5a19e0e0dd86c6fMSX-DEV08 Balance 598 MSX HAL Laboratory 1984 JP 134a6faf1592978608b55269aea101d5fad68760[a] b103d4248729aeda9ebf5766c1791b1950246968[a4] 28323e06a402747a1e131dc30ce031a339f6c947 GoodMSX6d73ee99ac367a2558fd9366af75ad39ed0d4e69[a1] 4e78689addca0c49befaeec29d096e331d4600b2 GoodMSXcd452442338366411f6b24739fe59e20f3ac24dc Balloon City MSX AG Software 2007 IT Author0x8000Normalc46f85f21f780f126a0d61848071fcdcf84f15b9MSX-DEV07 Banana 273 MSX Studio GEN / ASCII 1984 JP GoodMSX7a40237867f62611d0e976666451648edb80753c Bank MSX German Gomez Herrera 2008 ES AuthorKonamiSCCf0fee046198a076e664811f4e3a788d5f0b47183 Bank Panic 832 MSX Sega 1985 JP GoodMSXb0956b97c9087aabab333c271c7afd9754ae84cd b337ea56278523f984d1b000cd00f339d82017a0 5ed42deb02556f93024b7911b4ac17c6469a8340[h Prosoft] ccf79dc89000d2c2ed45112a76a7cfce86fb1affUlver crack Bank Street Writer 3162 MSX Toshiba 1983 JP 223661138586a1b024d6dba5e5fa67d6b7685884 Barn Stormer 3172 MSX Electric Software 1986 UK 84c5cce8e8e74ab51c3c043075165679270bc5e7 f3dbd4bca83cb9edc364ca49d440f1642e1e2f20 BASIC Nyuumon 1 - Basic Lessons 306 MSX Casio 1984 JP GoodMSX9f0cbb1191235ecca4985de976a0993744b57930 BASIC Nyuumon 2 - Basic Lessons 2 638 MSX Casio 1985 JP GoodMSX01cf858888f5a0d3aee08f42a66c515ab0d06400 Basic Tutor MSX Idea Logic 1986 ES eebfb48d0fd8fe5a7f370195a2b4a76b51b89dc8 Batman - Rescue The Rovin 997 MSX Ocean 1987 UK GoodMSXASCII8709fb35338f21897e275237cc4c5615d0a5c2753 ASCII83739d841abfef971db76ba10915b19b9df833476 ASCII8c522d40644544d78c9fb1f124a8313db1d45d89e ASCII16a3f3fb0f78b1f1e953a8fd57e108eb0d90f480c9 Batten Tanuki no Daibouken - Raccoon Dog 829 MSX Tecno Soft 1985 JP GoodMSXee7dbd36a2b0bbae11c68ac4de75077f283bed05[a1] GoodMSXf86a7945b34c97cc58d5ee27964968870bfcef93 efe44e5f4593036a6c00358638462a933d16b979[a2] 9b58c70b976ca1d841185de862e7d663ec9db56b Battle Colonies MSX Karoshi Corporation 2011 ES Author8379dfd36f2b3b686f9aa5c2c800a47a347a0f46unfinished game Battle Cross 271 MSX Sony 1984 JP 16f29e9553419865c1160617232614a72f88f3d4[o] GoodMSX8b63f36be31d7d021c19103ebe8c68b69aae699c Battle Ship Clapton 2 58 MSX T&ESOFT 1983 JP GoodMSX59bc608b1cb5bde2230bca0eb045cd6c58650774 8e2a2837c48bc8e2d2478043c728a4630982ba87 532037fd92f7f1d34a47767416ce5c979a0694ad 0f214ea9e8d673ee0c66cb6615032e57920d588b[a] Beach Head 2779 MSX Access Software 1985 UK 1bdb1898796e2b48b62a356035192b163113962c Beamrider 281 MSX Activision 1984 US f51f936887498d21f6ee9fe8a7701633be67e79d GoodMSX1231984ae24bf35f9f38596b864420d8ccd3f30b 6da1c6693b67b0415be6de178bc5d319c0bef59d 201d2d99247356ce0e847fb169225481d6adb7c0 Becky 13 MSX MIA 1983 JP GoodMSXd69d4a6f2834b20df3a2afbd7a927220e96e31e1 01ebdd5981c4841abddf6f76eb1f73e237e7688f 702d8f1762d4efe994f520e21e8867adae13b817[a] translated4eb5c8e5bc0b0c1bcfc55c53a819d5d592eb0551Translation by Hap and Rieks Bee & Flower 59 MSX REIZON 1983 JP GoodMSX3197d73a5400a72420f91def6d3aa8bdcb392754 250d4c7c6565b0c4935f6301e8de2c182e659fd0[a] a2de457567e87c794fba8e35c992eac352da78be Beepertron MSX Dioniso 2006 ES Author65961a1926b24c8efa71f2ab68cbafa2d0122acfMSX-DEV06 BeeZ MSX Darkstone 2006 NL Authorbd44cc152851d9f3827fb304f3e4780ee607f896MSX-DEV06 Bengan MSX Daniel Vik 2007 US KonamiSCC908bb09ba2883c3ecdec96ed7e0211ccb05c5018 Best of Hamaraja Night MSX Sunrise/Pastel Hope 2010 NL HamarajaNightb30b17f28e3a3f981cc48102e5c58151ea3fb05c BeTiled! MSX CEZ Games Studio 2007 ES Authorafe11b840b7bf0243b1c4f98309e274a3ddde35fSCC supported / MSX-DEV07 Author8a3749af3d6ff88404df59da9d9ac7990bbc318dSCC supported / MSX-DEV07 Author9267ea63e1d32095868eec1347a4eafbdfb5ef7eFixed - SCC supported / MSX-DEV07 Normal3e219e8205a3a4867c307601683d96bc53ac4c8eSCC supported / Retail Bifamu 146 MSX BANDAI 1984 JP GoodMSXebff5d2b4253f62d3a00f1a6d6b4d81053c7b8a6 4806e6bc1fd24d5a62c6df57ea8fd1b51d6e4b80[a] Binary Land 260 MSX Hudson Soft / Japanese Softbank 1984 JP ef476e25fb6b7221ca778a379117885450dd36f0 013fe787d071c64d7d2692b984734c5812a483c2 Black Onyx 1, The 625 MSX BPS 1985 JP GoodMSX5400c0c929b31a70e76aac676b91d40445f1b427 49f0e168020fddd254843c95b5e6bcf65bdc84ac[h Yagshi] 008e9b1447b7f321aa1f1b4ddf8e0ac0fd62c771[h Prosoft] 42975e598eeafcb02bac82e6976d3cb2647e4a8e Black Onyx 2, The 841 MSX BPS 1986 JP GoodMSXASCII162588b9ade775b93f03fd4b17fd3f78ba70b556d6 Blade Lords 2564 MSX Parallax 1994 NL ASCII8022a53d79bb72c736a5fdad6d82434a98f33e6e7 ASCII8599c824ec92be0369accaaaf31340df6f471f202 Blagger 840 MSX Alligata 1984 UK 0x4000Normalb4dd886d4a44cc42fc9e95d2db0943d422e4bda8[MSX1 only] GoodMSX65af16d35b8d8454ec07718e9f8948335ca73bad 6bc6acb11e684406a5d939337b29f33dc9816ad1[Converted from tape by Alligata] Block Hole 2371 MSX Zemina 1990 KR 8e3e05e8928b5b77fa61f3b833e164bf5222a5fb Block Tower MSX MSX-FAN 1992 JP 0x4000Normald2ce76cddb6cc12dc68e9683b072fb57947ec172 Blockade Runner 631 MSX Interphase 1984 JP GoodMSXbd4b8c48f1d8107f11ae680a787d7b3825669b7c a4f8627b060e521c0c8537e45912d39b8ded5991 Blur MSX XL2S Entertainment 2008 NL Authorf9122717f50a7a5a195d7ade2dd9bd3ea353cc1c Blusy Shop MSX Jordi Sala Clara 2005 ES Normalae3bd2320892fb02ab9facce986af07807513600 Normalf5551b5c88e645090cd9297aa1b3ca921526a6ba AuthorNormal3e44a4e5608e01793877e62f82c12c2b82908461MSX-DEV05 Boggy'84 319 MSX Nippon Columbia / Colpax / Universal 1984 JP e2ef32f2fc312f96b8424569e9e85a6ea3b21ce4 04283ae02107917da6f54a0f38d3a28254f7ab69[a2] GoodMSX5c96169207e197237aa728d8ce4e24c0635d5904 b52b68e6aaa3be876b3349a32b388853ea32fbcc Boing Boing 2191 MSX Idealogic 1985 ES d7e2a4f78b3e240309c6367ac9dd9c9aeb95e3ee e794e2ab8a44582f55c07ee63b87c462d5d48d56 Bokosuka Wars 320 MSX ASCII 1984 JP 5503d23faf94f076410d252f69f6bf5ece4da292[a] GoodMSXe5171c368883de03fc8d75e61bf23d954ec1bba4 d83bb9d7f71de899a726af1180e2e766d39fe5a2 ceb7807a78bcc46285afbf6e607d2f9a5bdffbcc Bomb Jack 3446 MSX Sega 1983 JP cef76580a01cc8e16704590d0d884d7eeadaa3a8 AuthorNormal5c904e7d202d9df2ff2ef9c8300f00d207670e82Kralizec MSX2 Version Bomb Man MSX AG Software 2005 IT AuthorNormal46ecaf85f1ff1b4821670bf44ff1249fe1b13a35MSX-DEV05 Bomber King 1215 MSX Hudson Soft 1988 JP GoodMSXASCII8482cd650220e6931f85ee8532c61dac561365e30 Bomber Man 325 MSX Hudson Soft / Japanese Softbank 1983 JP GoodMSX5324e053709ff8da6c18ae4afba6a2e0c3a722ba 8bcefc250be1c3a677de78e6af4d01d32fe4e5b8 6714e939f0592d3a97f995bfc6d7047242ebd123 64781236e70cf5dd530a420305533ff55db0e4ad 8976f8b8ab2a82f626a73c781d0a21899ae68dae[a] 2c0e3d130a1655617f9f80836f4b8b606f0774f2[o] b0d384ec10f59d201f346a3cd7eb31b9a80c28caEric And The Floaters version 1c6d69fbc7a8fea434b871bdd1aa7d816c48ac44 40217fbafa2af3bcf34f80dd215eee22d86d83ac f54243e461cd8852fbe5e612bbe0ab71cf6ee524 Bomber Man Special 854 MSX Hudson Soft 1986 JP 99cc271b5cffa38b8ec6e6d8770adf2119055fff GoodMSXe700493e1c4c14f711e9c1c262fe3e8d76931ca3 abe625e853cde9908a302f63da407d3e67c6e59eHudson - EVA - Deluxe Edition 7452a98d77e41f72e375f65ad562e33da9be3229 Booga-Boo 2287 MSX Indescomp 1986 ES fd5dd1d16c46eba134910b5e9550656ba9a1fd82 5ceffd3dc9887833e7b5b717650dd956e89803a3 Boogie Woogi Jungle 66 MSX Ample Software 1983 JP GoodMSX0b3241202f0e2be470e5f2e6139c01983a895bc8 05b7a1db1ff15f712e619fd1c79fd7710e93cb3b 86f8e3250cdcbadccf131255a1ef59ed291be8e9[a2] 70aeaa2872e39da681ae12e57b8cd972c6ff2a33 2054770806dc16fd98e5793221110dcdfc5c0b69 Boomerang 292 MSX ASCII 1984 JP da7cfd16dd1a76ae6b551ceabfd7053ca8e3412f[a] GoodMSXef535ab6013d9b6e38227272f2ebe6a0f0b33cb5 Booming Boy MSX MSxNAKE 2015 ES 120f5a2bbba9e7628ae58122f3cb0307648ea22dDemo Version Borfesu 1015 MSX XtalSoft 1987 JP GoodMSXASCII1616c3ced0fb2e360bc7c43d372a0a30eb6bd3963d translatedASCII16855cabd1ad1abae3a9d26999d48c66de8096d7c5English Translation by HAP Bosconian 321 MSX NAMCO 1984 JP 0bf7b432dc132f9a7bc74106261bca07d5f99690 33ad6d1bf1fb816b232ea706d149327e363c7b21[a] GoodMSX451cbb072011b26a7e0bc9931e67995a71f5fd6f 30161382e2410a72ece072719a561ec550256c7c KonamiSCC6f50ab2e4b4f40aa78e3d52ea3a1d6bace3ad361GDX SCC Version Bouken Roman - Dota 1014 MSX System Soft 1986 JP GoodMSX0x4000Normalb15e73a5ff1ae17ac67f2cebb5862d5e5bd80bc0A bug in this game will prevent is from running an several MSX computers.To solve this bug change the byte in the ROM images at address 0x003A from 0x03 to 0x0C,or try it in slot 3 0x4000Normal1ffef57e6c06068b330027294aae173103c8f141 Boulder Dash 2697 MSX First Star Software 1986 UK GoodMSX14fae375b81c86f71e872adca792c23aafac66a0 9b4b4d93bae38876ce095c35993553f962d26fda a294f54da2be26f79020674616af95a25428bcd3 9b3be70492994320e185aae302cdb3841d7f518a 6097fb7194b64148287e654870c98eb0e2b33850 28b25f82e6c35161b4955efa107b472acbb84f70 Boulder Dash 2 2698 MSX First Star Software 1987 UK d28201ca9b7d062d37c62bc4d6212f2a0d3e1cb4 bd13e3d81aa075365feecbee7c52b1baae98ea33 Boullata 1913 MSX Al Alamiah 1984 KW 254208d7ea2f7cc1035cfe7685238ac3ad17e438 Bouncing Block 2192 MSX Idealogic 1988 ES b76ffb94a08457df0a51897cd87cd8a91610224c[a] f1f571c1d9360839450c26d73c6aef89e1e052eb Bousou Tokkyuu Sos - Stop The Train 313 MSX Hudson Soft / Japanese Softbank 1985 JP f2d1a057940b44c04ba08f4ff2767eef8b515833 78b1bb63975d2b51390898539c075bf4bdd72343[a] Brain, The 32 MSX ASCII 1983 JP GoodMSX0x8000Normal208ce6cbff7402bfe52b1062e6b10ae09c0a3179 Break In 1009 MSX The Bytebusters 1987 NL GoodMSXbcfdb43b275c62a76856aab94fb1b53bd92b2db2 Break Out 299 MSX ASCII 1983 JP GoodMSX0x8000Normal3d065377f11b23410282b6b76f26bd93b9d05184 Bridge 2469 MSX Filosoft 1991 NL 0x4000Normal0d9a37ff931e5db2cdee2e0f312fd4bbef5cfd29 Brik MSX German Gomez Herrera 2008 ES AuthorKonamiSCCc1219e0735d31f1c41995795e58b04b97bf1b71a British Bob MSX RELEVO Videogames 2009 ES Authore45af2f15327dfdc1b1250ddab97b7f28801d5caMSXDev09 Entry f2dd846532d2244d67bb1959cf9ac5642a6381fa Brother Adventure 3027 MSX Zemina 1987 KR c64f2fad6417c64595bc204d843527e17a58776f 00a8aac8535a3387ae9bed5d8f4e2a21183666e0h Stefano Motta 6f9b4d791f4a43d7182650f21a7bfe88d1c5c2b4[a] Bruce Lee 629 MSX Comptiq 1985 JP GoodMSX621ea075a534584361c9e9d640e39007a79dfd78 Bubble Bobble 998 MSX TAITO 1987 JP GoodMSXASCII8e73946f1f26589d77926276ca4f9dbee60c53fde ASCII8c5edd1c74e72ddaeba1027da1e848e2c4fda94f5 Konamia40674b5e0ba449078cbb07406edfd2627306122 Konamid66068f5a8b6cad09ed57893ceb0f3dd37ce8715 ASCII8f6f08f7793a703507cba56b5779150b259607cd5 Bubble Utilize MSX Pastel Hope 1992 JP KonamiSCC27731d0f0900ad900cd229d69e9bc4eab6ea4135 Burger Time MSX Coleco 1982 US KonamiSCCccf2bdb5cbdfae61d810a096ba48569e41873940 Burger Time 822 MSX Radio Wave Newspaper Publisher Company 1986 JP GoodMSXce1376941359471cae41887fe11f10d3b696a926 Buru To Marty Kikiippatsu - Inspecteur Z 843 MSX HAL Laboratory 1986 JP translated01b506cfecb366f7e00bcabb717e86e8974c02a2[tr En] GoodMSX613c741535c92dfb01afec4135ef8ebddb49f999 Bust a Move MSX Karoshi Corporation 2011 ES Author59ee15e914910314781c69cd58c91e6c45625733unfinished game Butamaru Pants 67 MSX HAL Laboratory 1983 JP 2f7ff0438f1a8d80292c9e1cb8d77a92c600b505[a] e6a1d90a7a8b4e4b8a8e1afb3d971866931b986d GoodMSXb755d7db109cd0a9392accd1c83e6d995a8a04d6 7d53991b3d63ab97e2788769f1078eb557973500 C-So! 495 MSX Compile 1985 JP GoodMSX8b4853737f7f4006ca4291f481cfdfcf66e2ce34 695ac043eaf66268922ad97bf4ed52753c93b2d0[b] b9ac3ac15b49eaabcb640cc6b48f4706e0f18246 cd69ce70747bc0deebba9fd307a07f5dfb926cc3 22183520d99924a4185c5572a3cf948f4f0f04b5 a84e89769160ea86bcd0e9a0ad2421e17eb1deaa 49a49a932754b6d59f1840b8b274c103aca10d11 Cabbage Patch Kids 140 MSX Konami 1984 JP GoodMSXc06de0eda82e1daa3ea3cbc5eba5d884fd0ce8e8[RC-716] 7b6e64511a55eed58ba04f34cf2547f7df631c33[RC-716] 286094c7127bf98d53d8862427487b49132b16da[RC-716] c6e0b0bc71059583e7e9da22fe13be9e937ad7dc[RC-716] / [h] 2a0f9899a4d8dba76db501d6e3435546cc7cd9aa[RC-716] ed547caf3e45f621b588cb1fa91008c9c1bc9ed7 55819eb8d38095c77d45b834cf15e6753044fc87 KonamiSCC363e898c52f47ad01493490a7ae62bb5e1f8d1a4GDX SCC Version Candoo Ninja 144 MSX Mass Tael 1983 HK 77f3f6758bd4cf5d2b44c51de958aa967c41a9d7[a] GoodMSX69710f4b9cc3440e58c54bd4c562b3e90ff323e0 Cannon Ball 137 MSX Hudson Soft / Japanese Softbank 1983 JP d32279e01fb5762778d696acc2053b9503d5a715[a2] GoodMSXb36c925005f1e530cab2b5f6e2a5ddfd182c0140 aeca6948208b14d112b4733adedea02656b706a0 8aba4281a7ae3e00b13cfc2fd6d9a7aab218d046 Cannon Fighter 2922 MSX Policy 1984 JP 31d80470c8d23ec8b14256aa32f3d54dbc532d76 Cannon Turbo 928 MSX Brother Industries 1987 JP e997d8f02e413d2df5aed2bf7e79986c4c851d34 b9fceafb4d33b7ec5637974f9aff69fbc18157fa Caos Begins MSX Hikaru Games 2007 ES Author317a7cc0ab90882d25d1185eeb9aca6b884c8879MSX-DEV07 Author37f302291c75847f4b87a82e3acbbf62eab625f8Fixed / MSX-DEV07 Captain Chef 139 MSX Nippon Columbia / Colpax / Universal 1984 JP GoodMSXf025c9c2db97dce608fcc1050426beff0dea43eb 5685d9ad04d96c1b8da11bb66bac921d6a29c389 Captain Cosmo 138 MSX NEXA 1984 JP GoodMSX5ad0ba434407a7eeecb597b3d20bcfc6fe05c319 bb7468dd32a160851b39121d24713b0b4e065c19[a] b272f73fe2cb27de212d6753af0e09812a707f0e 0a0220380220dd4d2e2ada3344946e418965ec41 Car Fighter 412 MSX Casio 1985 JP GoodMSXe93c396649bf6328103e8da0247952ebef2a04e9 Car Jamboree 265 MSX Omori Electric Company (OEC) 1984 JP 8a158cdec0923b81ce13af2dffa5d1b7e096a469 GoodMSX11e2f95d1b58e1244efda761603a2ae6d0090d18 4428e31b8dd620138e4db654070fe1c37f564096 Car Race 15 MSX Ample Software 1983 JP GoodMSX97af4f422a283ac8ba387eb0b1b29395c2c966eb e7396ad700f7edf98cc2b0ec2e6127a68d3cd9ba 4fd41171b6b43d51678590a59f004b0b9121751e ab7354c9ced9782d572c4dd69249b76a1244e5ce[a2] Casio Gamecase MSX JAM 2002 JP ASCII8e32d7334ace26a570a08fe7c01ad01a2c08b1991 Casio World Open 422 MSX Casio 1985 JP GoodMSX0a2209e0a71684493998f6618af032ba580d4ff9 5ec989d082566819d2a88664634084634f1a2c11 Castle Excellent 738 MSX ASCII 1986 JP d55e9e237504bc6444c6b62894bf88292150042a GoodMSX811dca8fc14a2b00f503fbef74fdeae4f2873d4c 42af8975ebad6fb3ed5c00c11a236fc49c184875cheat version cb35e8c3a0a3e92281810d5b0eb886fbb1e461cc 698afd758f7ae15ca4a9a81be197320206b1d2b5 Castle Of Nasa MSX Unknown 19xx cf6a43181a15cfde3091d182ceaddf3d9bddd57b Castle Tomb MSX AG Software 2009 IT AuthorNormal902d109e4ff452471b11936039a472cbee81a902MSXDev09 Entry Castle, The 752 MSX ASCII 1986 JP f53bdd847d0097fd4ca47dab031570366c882e65[h Static Soft Corp] GoodMSX615c08820b926e28b143cd7812852e011d3eeffb b5b24db1a01ba6778b6768df15efb097fad5856fcheat version 7f5a95689b81da98387d812e0f6280466f51b832cheat version Caverns of Titan MSX José Luis Tur 2005 ES Author6aa9f75720bb38e410d751225d18e8985ab4d61f Author0x4000Normala5802d5fe8bcda79354519d7e7a7b710bb6f9db8MSX-DEV05 Author0x4000Normal983f97a0d8aedad52f5a2ade5fed6f2812218b2eMSX-DEV05 Authorc5568fc07bfb193b43ad7c840787f274d9c1e42fMSX-DEV05 Chack'n Pop 548 MSX TAITO 1984 JP GoodMSX0b9a870d1e6cc712a3efce2349fb1c1cf6902d22 de3035a159dbd910d4f390cc814e9e99fba94ee0 GoodMSXb6f10b6bedc26d9415335997493139ce7a2c7916[a1] 6c06b3f026161d987e00e6f021c975325030e319 Challenge Derby 549 MSX Pony Canyon 1985 JP GoodMSX3abae41cb742b76ff45dd178bd7db346137fe0fa Champion Boxing 553 MSX Sega 1985 JP GoodMSX39cb046a9b6b6ef4e769cdc6fdf36b274cd44039 e8b9690af00be94513c417d7cb78016339f6dc60 Champion Ice Hockey 789 MSX Sega 1986 JP GoodMSXebb46dd4cf36407ad82d9f50d9bef3e938dc144e b0b2445bd49730d4c1cffdb6cd3f98fe2829dd4f e2aa5bcb66da4f49d3d5727b42cd806bc5c84ba9 Champion Pro Wrestling 552 MSX Sega 1985 JP GoodMSXaf1a6f4dd12d726a7a4377753ce3db82bf24eba4 d1e3afecde1656099dadff99c6ed2aa8fa6dd834 0c364c3499df76f2e15d2aeb744a0de3730ff47b[a2] Champion Soccer 550 MSX Sega 1985 JP GoodMSX821c611b851ddd4cf8cfb4e77cecbc882f12eb72 Championship Kendo 790 MSX Sega 1986 JP GoodMSXf9e9a6e517ee0b9b3147abb6144b31ed2ca8dbda a502e37f580bd287ed8338fcb1d809b8dc90bbe0 Championship Lode Runner 791 MSX Doug Smith 1985 GB GoodMSX650381b96f36e74a4eaf5ae3f924f6f8deac7100 f5526481e64def129bd4109fa2074609ee2e92a0[h Prosoft][b] b5afee9c379017354fbefce797701efeeb30caea[h Prosoft][b2] dd90851b982550fa10d7e2e5c99dc7c5e5277ee7 Cheat Master 2552 MSX MSX-Engine 19xx NL 80ada0241ded833e249c498d01b0bb54179e6643 Cheating Wifes MSX Crappysoft 2005 ES AuthorNormalca770384382bd487dd7330552403f85d7edbb8ceMSX-DEV05 Checkers In Tantan Tanuki 546 MSX Pony Canyon 1985 JP GoodMSXa4be5763cdf2dcd647b87e3aecbab28ecc69776e 26e78436911b5ae09ad6d2e8bde033ca5926dcbc Cheese 3597 MSX Nipon Electronics 1984 JP GoodMSXdd5c2d2d5f0947159b7615f647fb8121f3b037a2 Cheese 2 - Sanno Cg 545 MSX Nipon Electronics 1985 JP GoodMSX930b4896c1590ebbb74d330447ec36e3feb5cab6 96897697afe3a6354f2753f22a8b15d6d977b064 Chess 479 MSX B.U.G. Inc. 1984 JP GoodMSX2c0c71c549738d668518db317dd70aecefd4aa72 c83ee585a2b5643fefa61d77cd4455d6aad7cb52 Chess Master MSX Philips 1985 NL 0x4000Normal4075ef9f51b4e010bd2481a648e05b2c29b3329d adc65666db35cd2ce694a8e2f32b95bd98d72291 Chocobo Racing MSX Sousouke 2004 ES Author2544de44386cf49dad2eb1035ea0e8553dd7598b Choplifter 555 MSX Dan Gorlin 1985 US b3ca70c8c8120f8261bb865baf5f57b6506ca02e 30673d173f96fe1830e579f18daf8ac9fc3366cb[a] GoodMSXa2a7c93c9c8b55a5cce4c9005f3c194e3b762e9c fe71908e340aa5dd241d56a3e8381263e5eb5aeb ChoroQ 556 MSX TAITO 1984 JP ee3ca231328534bba92f9a908c4faed865bcea68[a] 615d685fef332a7742324f9608eb69e6336faaa1 GoodMSX99f9e9753edbaee820e36a1a58f1cd7ae2b26265 Chuckie Egg 2660 MSX A&F Software 1984 UK 53ed25cf66a6ed4b432fa7e4f10a6730ce6923be 7eeeaaed7c69a5245f45b937183a203ade3c11a4 Circus Charlie 160 MSX Konami 1984 JP GoodMSXae6d0efd55def94274f4cec459df8927d132675a[RC-712] 83dc4599fb6706059424695c58e7dd7b3041a846[RC-712] aaa57e97a5400f329048ee80cd346d960bba681b 5402bc4dae0446b8864216114dac1b3cfb0712e9 4263edf02fc2c71cfd91d53012d87eb5e0021dd5 City Connection 760 MSX Nippon Dexter 1986 JP GoodMSX0071b2eb214ff002dbbb7532653585b35b56f243 488a9aaed538c2a24088cbd6815d47414cff999f[h Sammi] 07af8b1ec0786c44778f68ef3bb0b3d4793f42e3[a] Classic Minesweeper MSX Karoshi Corporation 2004 ES Authord632a1f06a8018422fc9bc83ab26c501d34d447d Classic Pong MSX Karoshi Corporation 2004 ES Author92727e6ecc52dfeca071673f0d58e964746899d21st Version Author59a124ed38869892496fc12f14af404a422e162a2nd Version fee6b1579f08ca6a7376cb6db44e7aaead237464 CMJN MSX Paulo Silva EU Author01135411960ef84cc93a56d15060c5edf4ed00bdMSXDev2014 Entry Coaster Race 748 MSX Sony 1986 JP 47ea8d512d9fec15df8770f3b5017aea9bbba9fd 308c73fc04c3b390b72ea8d4980c94c74ba38a5c[a] GoodMSX17f24b9339291dea40d2ed36517f223b9e4f49e1 Cockpit, The 1120 MSX Nidecom 1987 JP GoodMSXASCII16e36e16acfdfa76fa72b218da2bebc668db39d21e Cold Bood MSX Paxanga Soft 2010 ES Author50881d29421b9431b31f8b09fe26ee8b298ba76aDemo Version 0x4000Normal8cde922a3c189889096970fb6e8a1670fcc4e8edRetail Color Ball 127 MSX Hudson Soft 1983 JP GoodMSXac391fe51f69e0a55fe3ab66c8ec3f2792dd5d08 83fdaa429ad8b155ab2328a72fcb8de642ff5615[a] 95d8a590bad595f9441aea4bf68667de05b1b34b[a2] 9df754734b6d0cc77b23eccfd32b1b41d87aa268 Color Midway 18 MSX Magic software 1983 JP GoodMSX3a8c815f9f97c067828e22f50386a9383f8e1a3d Color Tochika - Pillbox 17 MSX Magic software 1983 JP GoodMSX76a79dd0554b8f7e84468fcd3bf73459ce4aeb2b 3450f93fc2b08a052a709930c97edead0099e447[a] Come On! Picot 730 MSX Pony Canyon 1986 JP GoodMSX1cf39e12313ff3064353c136262f176a56ab20a6 f3434cae8631f2f9068b3624bfd1ed8ba4292370[h Sammi] fc7f05f7659f7ef3fd2c51ef0571972a960a68b3 Comecocos 2094 MSX Idealogic 19xx ES 546aaf36ee78717fe06125185f0d420fdff0d34c 62a4853598e663046a926ddeb269cbf520ab75ab Comet Tail 27 MSX ASCII 1983 JP GoodMSX0x8000Normala054ddeaf471212764cc596bcbd30e94b10e6c28 Comic Bakery 326 MSX Konami 1984 JP GoodMSXfc327d3946366ca75de0d0619eb0917a27e1bd61[RC-714] d2604a1f76cadd36ede570e23ad49b0885187e25[RC-714] / [a] 8cf06f93d4e8bcf7be04ae8532f72e6eec8ba2f1[RC-714] 553ed3a8577f25b6ee884ba78fba5454dd5cac36[RC-714] / Ulver crack 9aab2b5dd7ae963d2f1eb7b35122221996b96ffe[RC-714] / Ulver crack c9a50b7649ac241899dbeb6cb80854c392a76b91[RC-714] 2f9698819b1c5cf28ced374be71afd720e650bbb[RC-714] 22e944b9453ffb305642f583e4ea0f2fc1fa29d1[RC-714] KonamiSCCfa4f2f0bfbef1bf744c1722406c45850045225e4[RC-714] / GDX SCC Version Compile Gamecase MSX JAM 2002 JP ASCII810907d8e9845d8f9d8b5283affd7bb824963194f Computer Billiards 480 MSX Konami 1983 JP GoodMSX630aec6f93df8d1ef83c52a0bb6ec571b9c6eed4[RC-706] 0d8b7fffbaccddcae324e38f7f85722ece8d5976[RC-706] / [a] fa9ae3205ac5fe8304d53d94bb8b1cb394183c3a[RC-706] / [a2] 76461e053178371aeb457588233cbfccd0d6c02a[RC-706] fe31a9a3dab733f3d0edb24fea3cff93dc9df6ae[RC-706] f9c85b434839f844072dc73abc8d16c930013f3f[RC-706] fc6660a202dbd228c9e0d7c404340d1dcc1e2844[RC-706] / Konami Antiques MSX Collection 2 Computer Music Composer 266 MSX Rittor Music / MCS 1983 JP GoodMSX0x8000Normala6b2fdae9551ef3633926f6177c771d794d1e91b[Boot with Left Shift pressed down] Computer Nyuumon - Computer Lessons 481 MSX Casio 1985 JP GoodMSX1fc9d3b54a617e57fd6ed10f2ce3c01456710e37 Computer Othello 158 MSX Sony 1984 JP GoodMSXbd8f335fc45e9920f0691a525a0fc853b2e4b595 ca7934ab18f52917b55e1b13b1f71ffc0093f298 Computer Pachinko 1796 MSX JWD Corp. 1984 JP 99d3ef107f2ac4d452b05eb96b922924ef4c5434 Computer Wars MSX Crappysoft 2004 ES Author0x8000Normale73fb56555a7232758d4b5bf2dd6736c9b3d5358 Normal8c19e0176b87f2436057922328dd1945bb3175ba Normald44e9a419f5bba1bccecc90771f066fd6a8093db Con-Dori 30 MSX Cross talk 1983 JP 8592b14ee701eaaf433113a26b199084c124992f 868d84f8a6ae0434bf86adfbb86996ceddefba84[a] GoodMSX09cc29b413dc7b8747420615bf798169ae68ad14 Confused ? 2833 MSX The Bytebusters 1986 NL 9865f0bc4d54137809f0d8b48d5461ddf0c5bf4c db3bf1586c4f9fb5c0db518e8b582d2bc7fbb4eb Contra 1255 MSX Konami 1989 JP ASCII81302d258c952e93666ecec12429d6d2c2f841f43[RC-762] / Needs SCC in slot 2 GoodMSXKonamiSCC90003c78975d00b1e5612fd00dffabb70d616ecd[RC-762] KonamiSCC7964ba4c3c27b6a32d397157fd38dd1dc2f1e543[RC-762] / cheat version KonamiSCC424320762bcbbb6081b1e186e21accf758aeb935[RC-762] / Ulver crack KonamiSCCcc46a737acd729b2839ecd237d38b4a63cfb16cb[RC-762] KonamiSCCbc06bd3d6f138da8f5d38b47e459b4d1942e49e4[RC-762] Cosmic Avenger MSX Coleco 1982 US KonamiSCC2efe8e59c1880b7bc47b608f8938c789875eac20GDX Conversion KonamiSCCffa4ad3dbef4278a900dda417fd8ef0dff0c118fGDX Conversion Cosmic Battle MSX Vendetta 2005 ES Author0x4000Normal30fa1447f8ef8e8ff9653dce6d7d672104f6e6abunfinished / MSX-DEV05 Cosmo Explorer 467 MSX ZAP/Sony 1985 JP GoodMSX0ac6c061c3897a3989dcd75b4878c850c71e4bb8 b6f8e1431bda1296c2959b10b021b80e0ff2a8a2 Courageous Perseus 424 MSX Cosmos computer 1985 JP GoodMSX613cc52fbd5822ade47daa3ec21479641a009c2c 78818059d126e80c33ab654e9ec749cf336f8f96 6cf3f098d592d7dcb767a0e24b5caf327b5f22db[a] Cow Abductors MSX Paxanga Soft 2009 ES Author9d87ba5ac3b9a865f2dd93d239e9cdaf6e48dff5MSXDev09 Entry Author4528654ce1a4d286b19ab5e3730d70598db63d11MSXDev09 Entry Craze 933 MSX Heart Soft 1988 JP GoodMSXASCII169e0312e72f30a20f556b64fe37dbbfe0d4471823 GenericKonamia731d3d3b5badf33c7602febd32cc4e6ec98c646 Crazy Buggy MSX Crappysoft 2005 ES Author5586085d3be79378f9623e52fd04cad25febc34bMSX-DEV05 KonamiSCC9d716d8bb0cf925d19dff40e0e404b38c89adff9GDX SCC Version Crazy Bullet 24 MSX ASCII 1983 JP 0x8000Normalff5b12275950edae9ece3f49ed59402d47f23b1b GoodMSX0x8000Normal5d5dfe6cec605de421c3aed3a743d6840fec1457 Crazy Cars 2907 MSX Titus 1988 UK 76c191bf7dcc0888d65e543c47f615ae45b75b92 70a8afee598dd2e2095353e5086b9f737cf89be3[h by VIP Soft] Crazy MSX Frenchies MSX Jipe of the MSX Café 2007 FR Author0x8000Normal484a21c62bde3eab537f394bc270ac7c1c557f7cMSX-DEV07 Crazy Train 23 MSX Konami 1983 JP 31b452ff42624c6ac1e0141d65869df1eabe5bb3[a] 8e6445376d1fd15cb5524786117a09ac5a832169 f0d74d951a3781c7a2680e1d7ab809d2afe1fd18[o] GoodMSXd29414f61bc1c4f8c1fe4a243a8a8afc2d4f6925[a1] 2f4fa99f80313358e43084490ec44f673268bc52 GoodMSXbbe8df034869a06cb263255ac04374de469c00ba 8aca0ee5845b0b39af624e8a998bb04f02d0fda4 Crimson 1102 MSX XtalSoft 1987 JP GoodMSXASCII8a21adba681956ec35816af673a9d4f6c7743253a Cross Blaim 745 MSX dB-SOFT 1985 JP ASCII1625b28cfe8d6d51f619d182c774f6ceb05b577eebcr GoodMSXCrossBlaimbb902e82a2bdda61101a9b3646462adecdd18c8d Crossword 1914 MSX Al Alamiah 1985 KW 7538fe6f0dfbf72ba7a89d349258afdff547b4c1 Crusader 456 MSX Compile 1985 JP GoodMSX0x4000Normald0e8bc3f5fd93dcd07cbb945b00018a491842242 GoodMSX0x4000Normal3d2d0a2eb3d12fa67ecbbb90b7ac1bdc76ad8412[a1] 0x4000Normalc6f090882886dcf783e5b65bff75c5e034fbb2eb[a] Normalb1e3d7aed6fa07b231f09bbae048b5797eeb5a81 Cubic Duel MSX Concrete Digital Designs 2010 ES AuthorKonamiSCC5d8897b91f02ec8d56a51fd768b307f3a224348f Custar 440 MSX HAL Laboratory 1984 JP f864f825b2270c3b98b3c04671a3ac5f2d95affaonly works on MSX1 69200f928d9aa748aeeb83bef1abe6c4ccf700ddGDX fix 4d6413264d93b53697f4d189fb36500de00db95f 1c11750c14631729f7039f49396e62a10564815b Cyborg Z MSX Zemina 1991 KR Konamic2e661e9b68a78e5a5fae5ae297b5d964d82491e D-Day 220 MSX Toshiba/Jaleco 1984 JP ba325451fb5a80c2872742c5bd61e7bb75c5763b[a] 0ba8ae697f3045b3b3175b27d00ac0e522288ff1[a3] c62e058c2a54f3c9adba2e6a3006b8e749c43bd7 GoodMSX18e87e15b09ec2fddc52fad10edc57918fadec34 0f5e4590d526f5a15d0442fd9194f76bce9bc081 9476ad42c2487ba4fbe0cfd7c50c1fb355bdb068 6eeead7af376794ddb549998ecac9668215eaf54 Daedalian Opus MSX Karoshi Corporation 2006 ES Author3574a6dc954579341934452feb141859b7cfa3bcMSX-DEV06 Daidasso - Great Escape 3288 MSX Carry Lab 1985 JP GoodMSX27a487e56721e947c1f46644f2f8a50156dda82d Daikoukai Jidai 1399 MSX KOEI 1990 JP GoodMSXKoeiSRAM322a4aef32db28c059c30f5f972f1c09708ef6ce62 GoodMSXKoeiSRAM324fafd0c3a38bae0a3adb7e48ca0f062f699d61ec Daisenryaku - Great Strategy 966 MSX Microcabin/System Soft 1986 JP GoodMSXASCII16SRAM210c38f91d8f5db2438414ea58f26dcac78352088 Daiva Story 4 971 MSX T&ESOFT 1987 JP GoodMSXASCII82418c0302abbce8b0f8556b63169c60a849f60ee GenericKonami1833ffc252d43d3d8239e57d5ac2c015b4367988 translatedASCII86159cdfc79c68bf7dfe2653fc0b5893ae913c72bEnglish Translation Dam Busters, The 543 MSX Sydney 1985 JP GoodMSXd8c074267e0b2ec225d5acba688ea7304233da13 0738fefa62be2acbd0f324f28538bba9551406ac Damspel MSX GP Versluis 19xx NL 8c5cf2df5f0b9df67239161aa843d9804af46f0e Danger Tower MSX Danger Team 2009 NL Author595ed23f45a8382739a3b865e153583f41f4ead7MSX-DEV08 Danger X4 223 MSX ASCII 1984 JP 0x8000Normal7c618058809302bc3553d6189bcf310f2e5b0e08 0x8000Normal50eafa5d735af5b34d14f9f6b8c2d9cd2f0441db[a2] GoodMSX0x8000Normal6a4e8325d2174747d56cbe4c6803cbf40eb40a62 DAQ Lord of Idar MSX Darkstone 2010 NL Author9be7612db1f968455dcd0b115239efcb91474e91MSXDev10 Entry Author053c7ff78dce7c3b24250f64ca6b139182e14844Flashrom Version / MSXDev10 Entry Darwin 4078 965 MSX Hudson Soft 1987 JP ASCII83f072673752badfbd234d6005ddad60a494a9438 GoodMSXASCII8d4da7236bd09d735dcb92127c73e43b3ca94dd7f ASCII83bb2f9f4be38b5b08da03ee190c39caec30e2f08 David II 210 MSX ASCII 1984 JP GoodMSX4b19979e01bab2289abb6034cb3c2d02c2e890d1 c5cd97eebe8ac90e57197f0df847f24e14779e2f f53fd0b550c437d683668376829fbd7f569f0ee5[o] Dawn Patrol 1170 MSX The Bytebusters 1987 NL GoodMSX6856a13b67750d36c4c466d30472dc5465879bbf Decathlon 2911 MSX Activision 1984 US 5d43cb6ca89f31d5f543e4dcd3fa9987b9769602 GoodMSXa1656f612360a126e09ef2baaa8002d92054125d 41bfc01065fe4e14886a0f3047acb6b849f94805 Deep Dungeon MSX Trilobyte 2009 NL Author4233ab2fb0d956e0efb43e2a489ca46b65aeda8fMSX-DEV08 Deep Dungeon 1 1160 MSX Scaptrust 1988 JP GoodMSXASCII8SRAM2e6419519c2d3247ea395e4feaa494a2e23e469ce Deep Dungeon 2 1161 MSX Scaptrust 1988 JP GoodMSXASCII8SRAM2d82135a5e28b750c44995af116db890a15f6428a GoodMSXASCII8SRAM82a209a0ead2feb463d211dc428aa847b0b9ed606 Deep Forest 973 MSX XAIN / Sein 1987 JP GoodMSXASCII8fc9adbd816109f7705f342c01f168bb09ca4b2ff ASCII835c02d8d590d92053f083b0c0c897277a5375af6 ASCII8772ad4eabbf0941a9b4655f5615f2a5cf22246b1Ulver crack Demon Crystal, The 798 MSX Radio Wave Newspaper Publisher Company 1986 JP GoodMSX6bad9af91907c2a97fa2f4b9dd2a699ce0d70fba Demonia I 2846 MSX Microids 1986 UK ASCII8d5b164797bc969b55c1a6f4006a4535c3fb03cf0 Designer's Pencil, The MSX Activision 1984 US 4b4a58a310a1138b95192d7fe0881bbdc45601d4 Destoryer MSX CEZ Games Studio 2014 ES Authorfd646f058d1d194a42daad034c17bb1a117555a4 AuthorKonamiSCC8e835c159e737e7fa7967ac4e8b5c4795fb3a5e0SCC music only Devil Zone 3421 MSX Uttum 1989 KR ASCII827a2a026f8d3ac3e00b2fc28f6a5702b25748d0cROM Version by Manuel Pazos and GDX ASCII8fab24aa4ef16cfd7e767726809d208cdd95a195aROM Version by Manuel Pazos and GDX (cheat version) ASCII1648e9bb58e61b55d9b196db82cf51a9913f1a53bbROM Version by Manuel Pazos and GDX MSXDOS2b5323c533dfddbd34836a67f915d3e448c6c43b4 Devil's Heaven 51 MSX General 1984 JP GoodMSX91ba5c063d0ed383165b26e4609d7abda02a5242 Dig Dug 221 MSX NAMCO 1984 JP e3391daab17710b3f34c70f60ac6c8bfea15b6cc GoodMSX6e2e5cff4ee4dd935fbe6684f2c54b39eca06092 c1376facb3db0124f0557a95684d3059a3043fec 4dea8a5b2bbf77222f302addbe4fb6278793356a KonamiSCC0496b9a6e3f48577eae0ec1a110a88359a8a0be4GDX SCC Version Digital Devil Story - Monogatari Megami Tensei 974 MSX Telenet Japan 1987 JP GoodMSXASCII87dc7f7e3966943280f34836656a7d1bd3ace67cd Konami03b42b77b1a7412f1d7bd0998cf8f2f003f77d0a[Kanji] Dinj Belmonte's revenge MSX Retroworks 2012 ES Author3302b30e24374a668c85744849ad9af3657f47adMSXDev12 Entry Dip-Dip 2201 MSX Indescomp 1985 ES 65f89c813a1d57a8f8a04e81682202f759ee6980 Dires 968 MSX Bothtec 1987 JP GoodMSXASCII8SRAM21ba6b699b4e34c23ee60db4e58e1265dc03f0b50 Discover the Computer 1915 MSX Al Alamiah 1986 KW 39a40c1265cc663eef0d21dea01a9bf52c8c140e Dodgin Raven MSX Karoshi Corporation 2009 ES Author03cbca9e6e9a8ef5742ce0ae173a3884f74dbd54Only works on 60Hz Doki Doki Penguin Land 564 MSX Sega 1985 JP GoodMSX6b12bb291e1a98a43ccff8ceb94d23585848febc 23531b4e8a2b3e975483318846cf89e65f32f7d1[a] 8f27317f5eaa3ec7d5b63184808a6202cf036c0f[b] 9a743e997179bafc51f04d422f0bf1e8b22fb1d5 3e98965975166c9c97349427e97447a8817cec44 Donkey Kong MSX Coleco 1981 US AuthorKonamiSCCf90c58b78d46faac333e0560676473d48c03b5afGDX Conversion Donkey Kong 2918 MSX Nintendo 1986 JP GenericKonami86fdfe9e26d6e77f41cbcb47e10e2f32e5518549[cr Dr. Jekyl and Mr. Hyde] KonamiSCCf503c092f96a26ca31620d79d3e4ac81e5ca69bd Donkey Kong Jr. MSX Coleco 1983 US 61752708b2bb3556352799f211a513f1a82c67bcGDX Conversion Donpan 237 MSX Nippon Columbia / Colpax / Universal 1983 JP 291108a63e8aaaf06997bee7e988f89b0a7bb187 8f545627ba0467858c0310ac2d52d16c5bd7c56b Door Door mkII 563 MSX ENIX 1985 JP GoodMSXf8feeac4384bc4f109c70f962e15ce925bf70c13 12c5edb41e696943c16f39c7b45b2aedc421411f 8bb260181c404222e67f37ce84a252c5777472bc 83fce8c3e4843c4b1782b9ef83413c98f081d12e[cr Unicorn] Dorodon 236 MSX UPL 1984 JP GoodMSX19c610339c0480c3d2d0d1c21e3ccbf2179bb191 Double Dragon 2372 MSX Zemina 1989 KR 6724b2abce8145609333c8b2964de0f1a90d9cd5 Double Face 1916 MSX Al Alamiah 1985 KW ac2475e955e98c5d5816ec60118b424b46c30670 Double Face 2 1917 MSX Al Alamiah 1987 KW 08cb8d0a42e1c2a0961aff3ab70262525964b434v1.7 3627431db990b6913606328ae2fb3340452b2797v1.8 Double Jaw 1918 MSX Al Alamiah 1985 KW 7b89c2d62c3106a87c3375bbc0284d2b5a364cc9 afcc2f649e4001a2a8bb701874ef4a26e224f689 Double Vision 969 MSX HARD 1988 JP GoodMSXASCII887a7fa43602f74c47151e79f1aaf7f641331ba51 Dr. Hello MSX Sis Co. 1991 KR KonamiSCCf5bb3c6d41245271faa3c3f54e3948787e181a5eSlotman/GDX conversion Dr. Pill MSX Infinite 2009 NL AuthorKonamiSCCfe192b900c12b5dfaf59f022e57e17a4d2738623MSXDev09 Entry Dr.Copy MSX Emiiru 1987 JP GoodMSX6e6b8cdc55713eabc724f3ec9624e9884254ab33 Dragon Attack 233 MSX Takara 1983 JP GoodMSX761ab0aa7f528bf0f51ea9de75c847ea949478d7 533dc6d28ca60c090740879dcb3cfa386544ee45 0a04143b9357c6f2f0dc177c2acc5b27562e2a76 KonamiSCC03a496f08946a344fff343509f596f7030bf5a50GDX SCC Version Dragon Buster 980 MSX NAMCO 1987 JP GoodMSXASCII8ecbf33a376a7a108176f8d1009d042e01519241c ASCII8e0a8aff980ed84ec12acbf63b40cf9895997faf3Ulver crack Konami81befaa45ee3f1196b0e1ea657b0161495601a87 Konami6030005c4401b3cbcb09f802cd48f5c1c585303b Dragon Quest 1 - MSX1 Version 804 MSX ENIX 1986 JP GoodMSXASCII87b94a728a5945a53d518c18994e1e09a09ec3c1b Dragon Quest 1 - MSX2 Version 805 MSX ENIX 1986 JP GoodMSXASCII85577e483caca553cfbfc0cba26f3ab2444569f81 Konami7e9a9ce7c18206b325830e9cdcbb27179118de96 KonamiSCC27f9144fb24b434f7a75737aa694283084782766[a] Konami7c3aa9cca2f1f6e81ae9d24b3a3c675f08a203ea Dragon Quest 2 - MSX1 Version 1171 MSX ENIX 1987 JP GoodMSXASCII83df7c19f739d74d6efdfd8151343e5a55d4ac842 ASCII8d7b46aece68c924e09f07e3df45711f337d35d6a[a] GenericKonami68691348a29ce59046f993e9abaf3c8651bdda3c Dragon Quest 2 - MSX2 Version 1172 MSX ENIX 1987 JP GoodMSXASCII805280f0a26ac5c42ab63b80a487bb909683e7461 ASCII877fcce7b7c9c915fb0adff059141d4d4307f5a22 ASCII8028eb2de64ee129f9403d114b3059e8b62fef6c2 Dragon Slayer 1 566 MSX Square 1985 JP GoodMSX397716d26a075c6d731f0b04f3fa24aa39521798 9103862cb815ddf10d1ae4bbc238c4e7b1754004 Dragon Slayer 2 - Xanadu 939 MSX Falcom 1987 JP GoodMSXASCII8SRAM86c1814c70d69a50ec60e39ef281f0b8cd7bf8598 ASCII8SRAM888cd90595fbb7dbabd73ac9c0985c0a013992dc6 GenericKonamifcdbc5e15dd6b973e0f1112f4599dad985f48042 Dragon Slayer 3 - Romancia - MSX1 Version 880 MSX Falcom 1986 JP GoodMSXASCII162f0db48fbcf3444f52b9c7c76ba9c4bd38bc2a15[aka Dragon Slayer Jr] ASCII16a52c37c1f16ba13d3f39bb5403c82c0187cbff51[b][aka Dragon Slayer Jr] translatedASCII163795737471fa09f4bee252bb19ea16013cd22809Spanish Translation - Speed Boost / MSX Translations ASCII16f913ba02cd07e7d6a18d79d0fc2f4628841fc5c6 Dragon Slayer 3 - Romancia - MSX2 Version 881 MSX Falcom 1986 JP GoodMSXASCII16b63fc17c1d004ca4d7f3b1ef8962210a779ca665[aka Dragon Slayer Jr] Konami23f3e1b597bee95375f380638340995b55232e7c[aka Dragon Slayer Jr] translatedASCII16cef3021e3f909f1db4f8342f60716d01a1daa10fEnglish Translation - Trained / MSX Translations translatedASCII1600614a2dde5d87eba79e6a787100e3d6c9ff03c7English Translation - Speed Boost / MSX Translations translatedASCII167d00824f71ef375be22a3f271262e2f3937a7a56English Translation - Alternative A / MSX Translations translatedASCII16cca3f7cf3c2a0993d80d75d1af065e8f3d95a132English Translation - Alternative B / MSX Translations translatedASCII16790c782264dd795d119fcefecea5c922ee293e4aEnglish Translation - Speed boost - Alternative B / MSX Translations translatedASCII16592bdccc958d253ef4fa2cf33c965a2d5948f5d2English Translation - Speed boost - Alternative B / MSX Translations ASCII16daa7e84447605aa9d90c1ece2a3052acc74b82bd ASCII16a6bea1c50ff214d9eb614253c22cf1802e5664b4 ASCII16aa50156f9ed36d1c490d8f6b8b2e00bdb0bad44d ASCII1643d400f31cbc9b907fb408235adb903ed00c2ce8 ASCII16220367fc9c62e9176c5e4477db2fe7112131b6cb ASCII160a1415e34775725fbf698cdb7cdb75d51cd451fa ASCII165371d62e7f735bf0e64a604b175b16ae6b36a136 ASCII16d4b2a8f726271c3d771933820a311463c71873e2 Dragon Slayer 4 - MSX1 1377 MSX Falcom 1987 JP GoodMSXASCII8f100a76117e95ab0335e89a901e47d844bbc0ab6 ASCII8a3584e9554bb3e9b384a3b2b80636709d9e842f2Kralizec's Dragon Slayer 4 improved password readability patch Dragon Slayer 4 - MSX2 Version 979 MSX Falcom 1987 JP ASCII83f87c3413c8cc2a25e1f64cfdcd01155a13711f0 GoodMSXASCII86d7a8ea1b283ecc1cc94c359e5f053a6e82ab3f5 Konamifa2d5f56273a0faa5b39096448f82998655de60d ASCII873ad5a90a963f7963a404816e8d6616bbe386ce2Kralizec's Dragon Slayer 4 improved password readability patch translatedASCII89bdd6b4212468e07be25bd43e94b50d2ea1fe9dcTranslated by MSX Translations Drainer 981 MSX Fun Project 1987 JP GoodMSXe34161034364ef03df907d62976c2ac8f551404f Driller Tanks 98 MSX Hudson Soft / Japanese Softbank 1983 JP 95d968baa9f4996926371b8486ab55835b7d9d52[a] d22817c519f5316f61c963580a620b0493a6232d Drink It MSX Crappysoft 2006 ES AuthorNormalc55dd15a27b69b61e839f8500f56badb4ee58ad4 Drive MSX German Gomez Herrera 2008 ES AuthorKonamiSCC52a9173b22c8a72376003764d73e07579dc2015d Drop MSX MSXosaure 2010 FR Authorb918206abcf3c570f636d3f4b9d70bcbc25ed079MSX Basic Challenge 2010 Druid 1173 MSX Nippon Dexter 1988 JP GoodMSXASCII8b11c0ded529af18c29ec46b9e0a795ed0405e7ec ASCII8af3524caccd33ccb7a5b42b1ce4314d055e01672 GenericKonamid0bbf730725806384756f5306ceccd91dc01d975 Konami4d0af1bbba3a3a15bae3beaadbdb46c24fefaed1 Duck Hunt MSX Karoshi Corporation 2004 ES Authora6145a658ac51d8f455ac60629ed2d118d50f1ad Dungeon Hunter 1385 MSX ASCII 1989 JP GoodMSXASCII16d1fbdbdf2e830139584d7dc796806aa3327720dd Dungeon Master 787 MSX ASCII 1986 JP GoodMSX353e2bd901e5502375dc92a04e1686c1192fcf42 c0dca32ae18315ca589aa5f1dcde58916c537ed6 Dunk Shot 785 MSX HAL Laboratory 1986 JP GoodMSXf63d274a8a7ad8e285dd8800264ab9c14aa9f870 GoodMSXe7d9d1e4dcf48779dab41eb6c15478f934bd8bc1[a1] a4dcb6b5c4a770171975c93eabb2868c7e38f914 Dustin 2125 MSX Dinamic 1987 ES ASCII162799d221d5486d48226bbbd3941207e1fc7c985e ASCII1643ea1152883b57316fb0ebdd06472035fa753a7f DX7 Voicing Program 219 MSX YAMAHA 1984 JP 20c24130b7a9d50b5cede80f1638b686c2778800YRM-103 / (no disk support) GoodMSXc20def84efe71efdf91df3100919d151b2e7bdc1YRM-103 / Needs FM Sound Synthesizer Unit / no disk support DX7 Voicing Program II YRM-304 795 MSX YAMAHA 1984 JP c79626eddf8bc390d64ada99ad6da32dfd52d086 265261c8fc05741437f53244699d0dded09f27f7 Dynamite Bowl - MSX1 3211 MSX Softvision 1988 JP GoodMSXASCII16d7109cf20a22558f923c833ff0b4e2311340acb1 Dynamite Bowl - MSX2 967 MSX Softvision 1987 JP GoodMSXASCII16bd70b00ccb8fafca883c7160585e9c5920cca58f ASCII16a6d285e515ad1e2c15b4352d9b699ebe060b1dba E.I. - Exa Innova 97 MSX Programmers 3 1984 JP a14b357a1b16163f5ffa96b0b617d352713ac047 5b33d3f832acd6aa44a48232a9e4ff66fe63c8d5[o] GoodMSX7f6906ff7017a558952197198ac7a1c1e222ed9d 7744ba0294c877daff2dcc3e1dd10f4357bfd53a ceff1e13041a43b4a3ef629b69708770f36bab54 194c7fd1fb3a2a52f4669e98e777b44df647a983 Eagle Fighter 379 MSX Casio 1985 JP GoodMSXd916f0e4bd3c829a0472c6ec18d08907a5c991f3 83fed4090d0942c8e0054437745c1c0ebefc6222[a] Eagles 5 3413 MSX Zemina 1990 KR 6365ee1bfd4764546681d860c90b59f27b25f213 Eat Blue! 2004 MSX Paxanga Soft 2004 ES Author0x8000Normald31eb4ff4d6e4b3af645f24388c734a1d00a0c471st version Author0x8000Normal373319d9dbadc959dd953894be166637889186552nd version Normala02db35c2bdfd9d6838b6cc92a3e3d30a0ced5bf Author0x8000Normal30c6f72928b81c7d37d192cf6d6d6d3d1232469b1st version Eddy 2 109 MSX HAL Laboratory 1984 JP GoodMSX28bf2354cdd70e30b682f40b734029ece44d70bd 20f1883c45fc3c39ddf48a7de69b6ef588fac8fb Eggerland Mystery 391 MSX HAL Laboratory 1985 JP 4b34bf7b950d9467bf701add259a32686f283d24[b] GoodMSXe49f66e8ba45d0647c93a57bbc1fb7aba4665a6e Eggy 388 MSX Bothtec 1985 JP Author0dca3736badad8dbab3b8d552a581065d9977b43iPhone version Ehagahi MSX Hitachi 1985 JP f625afc51ac52b27a7345d2cf6ff1b4aa39c8e2b Eidolon 694 MSX Activision / Pony Canyon 1986 JP GoodMSXASCII8cd8ee800822ff0cd295621faafafc73be4bacad0 Electromatic 1919 MSX Al Alamiah 1985 KW 94584ca2e89ab0b249cd52f3a356176a03f9e35f 1dbe5a8e05fd8ed6a2181271e7423610a259b5b7 Elevator Action 404 MSX TAITO 1985 JP GoodMSX6cbddd118afe63e79cb8a40f8aa879df1c7590e1 e9ca3d6a21092ec8d6933279dc6c96f3927f3b15Ulver crack translatedacab2c6147df101536efde0d57ca4ccde8ba3379[tr Sp Msx Informatica] Equations 1920 MSX Al Alamiah 1985 KW f2ac1ce7584297bb646caec3fe4f0384ed5b072f Equivocal MSX The New Image 2009 NL Authora1fb6a9876601b343e1766935cedf61dab1a8f7d Author21e43a08613fe97436e9e1bdf1f4d0dc7a0c058b Erika 912 MSX Jast 1987 JP GoodMSX7de755f98b6a68886e61473d524d0aa6ab6577d7 Erusurid - Elslid 1080 MSX NCS 1988 JP GoodMSXASCII8SRAM2caeca37113c56e696d7053d90f5f9f3e02de8ad1 Konami98f6769741a1c9df4fd203ed19d70b283c94e41a Está en el Pantano MSX RELEVO Videogames 2010 ES Authorf1550e958a0664f5966568e2677c3a041ba940d5RLV905 Está en la Caja MSX RELEVO Videogames 2009 ES Author76b7fd1aed062d3ee5dbad6a94fb81c3e753e9f6RLV902 / Spanish Version Europe War 1576 MSX KOEI 1992 JP GoodMSXKoeiSRAM32475c3af7495922759a5f424d2e22d7e4073d5992 Exchanger 107 MSX ASCII 1983 JP 0x8000Normalb5b28f53b16d43742d9d7daf5ee7dcf74b80e6c8[a] GoodMSX0x8000Normalded8d969542b7be02524037fa244e6648c3f4e03 0x8000Normala059f6fb5ffd3762010b9743518833499fe2205f ExChess MSX GarlicSoft 2012 ES Author5d6861cfa10dd57315956e25a3c302a711605e44MSXDev11 Entry Exciting Jockey 207 MSX Casio 1984 JP GoodMSX5b17cb69697cf55dff1f0b8777e19d5abc1771fc d4480639d6468e1919c43f4cd0a08e9ac60073dc Exerion 1 108 MSX Jaleco 1984 JP c221c5d90c3c6eabf1a651072cd9841ad1b25d7d 09b0ea7c5595f4e9c9a15219f30e3423bbc25f66[a] 8ecc34ebff9d37c0fe9560cfdc53a052cff3d1ab[a2] translated5be82c297566b3e08fa1fe2534be06ca2f95d565[tr Br Uriano] GoodMSX975fdf521c08030896eb50af4f39a23500eaf7acJaleco def5b6cd56bec213f2f160ef602dbadcf4a96533 Exerion 2 - Zorni 204 MSX Jaleco 1986 JP fc63b9cd7d3fbf5a8309ef09be46ed041321e029[b] 4516a0e0b915cb093e82a19b4570d26c28e213fd[b][u] aa1621a6b61f7cfb2df8dd2cf8647a943e13dfa7 GoodMSX2b36fe2b0d98d65ab31f13fcb858db6858a51d46 Exoide-Z 712 MSX Casio 1986 JP 3987c45a35e5c3fe494a00ef0690a329790ae34d GoodMSX3137e676c37195d46fe6acc4395d0dc4d711d919 91bee8af612f69c5413a1d23102cc5ea896c2caa 06bfdd6d0f0d84b1d534c81b27150193969168b3[a] be431182a97155c09adb9f95a4c23fc8ec8044f0 6cd5308c13c08dac6979b4535d7dbc314e6850a6 Exoide-Z Area 5 713 MSX Casio 1986 JP GoodMSX22c7abe6c8f4b7897ecb583d4913d35b5f62c5e1 0e0ca65312adae0829e6abab554ae9454e52ec0a Express 1924 MSX Al Alamiah 1985 KW a5dc0aa283072f772ee4b9eb93b0966500dd306b 7758f57e91dd62ddb5c88cdd73dc0fb5ccd4de7f F&M Direct Assembler System MSX F&M 198x SE 39e858422a865af991f5dae97fbfbb3d7e1ca69a F1 Spirit - The Way To Formula 1 905 MSX Konami 1987 JP ASCII83880b064dcb851ca221ff67e435137a7cf1141f8[RC-752] / Needs SCC in slot 2 KonamiSCC6e5acdfb1c1610257a7aabf3d5aa858866dbcf2e[RC-752] GoodMSXKonamiSCCff72a1788d47f9876d9fabd720f6a289fb409090[RC-752] GoodMSXKonamiSCC42fbb18722df3e34e5b0f935a2dc0ce0d85099e9[RC-752] / [a1] KonamiSCC8b252fe784692fc15290a04fa59848f609216f16[RC-752] / [b] KonamiSCCf0be97ceca47b65b46c9518e6157b295f8c99c38[RC-752] / Ulver crack KonamiSCC3dc1574c77227beb1e66253c317a794fb70bbdb2 F15 Strike Eagle 1071 MSX System Soft 1987 JP GoodMSXASCII839bc111c953bdf25683db7ff4c0bad79b5c49958 F16 Fighting Falcon 392 MSX NEXA 1984 JP GoodMSX5c7a13b64b48065231f05c35b4c8b209534f3a8b b11b93add30086ce00d2126252893700ca6fd82b Fa Tetris 3232 MSX Fa Soft 1989 KR e8d4acdc4141fb9edaaa5c6aeb4fcf19594e3512 a469935dd39a856c06676817f51b403a3678ac1a 32b9ae8409abcca5e8536e890b21cc310cf45822 Faces 1925 MSX Al Alamiah 1987 KW a420f8d7d3631b1c795d182162221b21aaf48f1b Factory Infection MSX Karoshi Corporation 2004 ES Author1c90a57cc1237bdadd70d90d74e69c33d7a89293 Fairy 620 MSX ZAP 1985 JP GoodMSXedd61208e11149e226cd2eddf5c9eca8ee1ba33e 442047058fe0f290f8022ac88d18e5407ea6068c 768faca2bf9cdde7190904d8c31ce800b00b85d2 Fairy Land Story, The 1005 MSX GA-Yume / HOT-B 1987 JP GoodMSXASCII8e11afc03db4e1d03976d02796b29da9c65d4ff3d ASCII82bb837a9051277ba574d8351a9f91f9e57033074 Konami84566b5f37ab4f04a2e5b950c5beecbd27b88ea0 Konami21380bee0748625e9951a7a924016cc6cec46fb8 Falc 290 MSX Sord 1983 JP GoodMSX216243b4bcb9070f9e86c3f04edaaa493e4b459c Famicle Parodic 1196 MSX BIT2 1988 JP GoodMSXASCII8f12ad34dc3b4279da0fc7094f3d50d6b8bb517dc Konamif04f5c6936301eed31a79c23571f8fc497864dbb Konami18eada2178fc1a89895087aa513305d1ba73c926 Konamib45172e4628f68c32cd13c73cf17c4dd1bed1bb2Ulver crack ASCII806132ead4caadb2395302381bf72c60831500dce ASCII853e0a20b69457d2d97f8420a6f478f7425a06f33 Family Billiards 1004 MSX Pack-In-Video 1987 JP GoodMSXASCII8924fcf87b3747dae06e621975903ee73c42016f2 ASCII86d72a04ef018fa1fa3708c8b5ed3333b2c5e8ead Konamic74c0a9b973f4dbfc132b60fe4d666600ae12552 Konamib1c44be4e6045de929f5ad2b477a34c4b55d2f6a Family Boxing 1197 MSX Sony 1988 JP GoodMSXASCII8d78d763e72e23418f31dc3ce91deea1e8ee10490 Konami66620207ca6c21bcbae8f868bb55532e72e521a6[a] GenericKonami31dbd1c08659c649c49da8d2e6b80328cb6b59e8 Family Stadium 1247 MSX NAMCO 1989 JP GoodMSXASCII16970e26f3c2c085e39267115991add6ccc6e9ff80 Fantasm Soldier Valis, The 860 MSX Telenet Japan 1986 JP GoodMSXASCII82b10234debd2a6a9a02e0750ba6563768bc4a2f3 Konamidccdb2d18c70a94e48b3ec5e0cb986c5d708bbc9 Fantasy Zone 1 1828 MSX Sega 1987 JP GoodMSXASCII8048737f995eecb1dd8dd341d750efd005267796f ASCII86868e7050d989d1ecbd13393ed360addefa4bcd3Ulver crack Konami442a39b196f19a22fc7fb8f14bf17386a292b60e Fantasy Zone 2 - The Tears Of Opa-Opa 1259 MSX Sega 1987 JP GoodMSXASCII16a58ca651f4c12bf07cf3840d5e37b534f5fc075e ASCII161f39c472f1e02511fa7eaaf8ed9fcce4d45861b9 ASCII16df10b3c8470acebbc445f7c24a50b4f24db0187dUlver crack Farm Kit 3135 MSX Joyce Hakkanson Associates 1985 GB e6b71484d5ab578fd3695917fce3c1e805a5b536 Field Of View MSX Artrag 2009 IT ASCII86d800787f5f5d63827b841aa29fe26ed89ea2cef Fifth MSX Daniel Vik 2007 US KonamiSCC75eb441c8298b7fd3982999f772e4c8b983ef17f KonamiSCC59e35d24ab5f8e3b812ba4a1a4feb5be1ab269fe Final Justice 617 MSX Compile 1985 JP d833c66dd541239908555762e04bcbb22bca748a GoodMSX9bca89c71c033bb9a85ee30cf75960ec839c0462 068e5cd232a794f7e203e5316a3ad2bcc7f3c939 GoodMSX1e70296a07a5ff62d01e5115b8c8b87f891585c9[a1] Final Mahjong 65 MSX MIA 1983 JP GoodMSXd086280dea7fb10fabaae26ed9d404bbe69c70c4 Final Race MSX Karoshi Corporation 2005 ES Author6ca349d1ba2fc90c17a57fa01a337fdea7acfd9dunfinished game Final Zone Wolf 838 MSX Telenet Japan 1986 JP GoodMSXASCII8a553c3f204c105c23b227a1e5aeb290671ccdbeb Konami90ea059c57f011a4fb33a558e868ee639882fe5e Fire Ball 1195 MSX Humming Bird Soft 1988 JP ASCII1650422ba24fd158ca5f0b9728b8e41829048cfdfd GoodMSXASCII8cdeadd6cf7bf5b53aa7d901446b0ce586542c580 Fire Rescue 289 MSX Hudson Soft 1984 JP ec0320eda10bf6bbee8081f6672a7bf9d858c1bb[a] GoodMSX0889ca383bcf24198db1fb2dd23dea1bf8c18be8 4b5cb07c6d0129bbccf1d9c5a98887ee36b94c03 KonamiSCC9dcd8d6a89618401da9f1df566a98914a6be4deaGDX SCC Version Flappy Limited 626 MSX dB-SOFT 1985 JP GoodMSX40c626d8c53e2e2d4e7e25d55aa2c11c2645780f 6f391d0408223138b7927ed719d7ac1b6c38ca0d Flappy Limited 85 1809 MSX dB-SOFT 1985 JP GoodMSX46f3954d7f92f5d00f45b82fdde543c28a4b53c9 Flash Point 2622 MSX Stark Texel 19xx NL Konamia3fc0a55a91ed0041221f8da788d824d9d795913[a] Konamide95b99dffeab5c2b453778dbdc8723dc15cbfd7 Flash Splash 295 MSX Toshiba-EMI 1984 JP GoodMSX60f685670de6d2d8ed5f1b1ddd40fac851ff1982 Fleet Commander 2 1842 MSX ASCII 1990 JP GoodMSXASCII88cbfd94324f297a418e7eec6920dfead7b9bed08 Flicky 842 MSX Micronet 1986 JP 5d2bb062d5d783d4d08fe04c2f5c13d618b096f2[b] GoodMSX5999cc1407afe5ba8a760f16178c4cac9c3fa192 2a77bfd0fb9b6d9e60aa9ae243299ebd236d3373 Flics, Les 681 MSX PSS 1985 UK 7bf28813fb11e9db3bf78cc73ee5fb2d76b547dePersonal Soft Services KonamiSCC1565529fed9c59a3c24b2bee153ea679ff1e1027 KonamiSCCd745bc68f6f28abedbaea99d8819952731a8d9b0 KonamiSCCce2dfc37f4ec587c7cb6eb055bea1b064f074053 KonamiSCC8f9594954724753c91615497b58641fea25fd223 KonamiSCC3d549ac05347997c2e2db9c8dbf264103de4c6f4 KonamiSCCf6f0c4f3d847a9cd4a92b29f095a3cf33854164d KonamiSCC448b56548d8aaa67978b5d066545f34af85b5345 KonamiSCC5bc448cbc3163062ad18dfb5b1f269adc077ec34 KonamiSCC37b08a5f01e70cd27ee502850adfe7bbb172b4ab KonamiSCCdf60f831c6b56ed38d9697ed4d993a82a8523f48 KonamiSCCdd4ab0425e94c2dfa376a98718573e3f18dc55c2 KonamiSCC2f3e79b6ff156f2055f549676274addf8ab52102 Flight Deck 2 3159 MSX Aackosoft 1986 NL 488f01314da5644fbf2ca938c6f9720ef2b0d0f9 GoodMSX3eee7c72362522c27866d4367a8cf1e22932db32 deaf7245b990dce6010235159ce226c939dc2d7d Flight Simulator 1202 MSX subLOGIC 1988 JP GoodMSXASCII8453eac7568d5d04c8cf7da86f2e8dc79777343da Konami2bd311a4baf59cb85b839cca1f55b7462aa96952 Flipper Slipper 297 MSX Spectravideo / ASCII 1983 JP GoodMSX3377d2017cc8433fa20b432c9d2328c53bbc10e6 49bfa9871bb0ad3b47a7c63b14a10e0e8ae48a52 097cae3bf23e1f387b2c9708fbfc324ddd88837c 6663e696b35f82b60c18752b58072ae56b2c3657 5ff0ca4d961c5e674d0a10f808515136751513f6 FM Auto Arranger CMP-01 393 MSX YAMAHA 1985 JP 971da55e92167674628a12c443efc43bafcfb6b4 1f0cb04f1dad8943684423c69a11433279b76def FM Music Composer 110 MSX YAMAHA 1984 JP 337087199d58666029f00e08ca82b1917ab58c80YRM-101 GoodMSX39c12004adb3442f2c43aade8a0be420d59eefb1YRM-101 / Needs FM Sound Synthesizer Unit / No disk support GoodMSX7c2743df543c0d8a4ba7e42bf7db580b996d102eYRM-104 / Needs FM Sound Synthesizer Unit / No disk support FM Music Composer II 111 MSX YAMAHA 1985 JP GoodMSXe60e2abf907b66f5b275c7b7b710439adae58d37YRM-505 / Needs FM Sound Synthesizer Unit 24287956e5bfafc49335eadd16b457293e3e35e5YRM-501 FM Music Macro 112 MSX YAMAHA 1986 JP GoodMSXe7c800fa576b3a9554836aa9c5eaa3ae893805d4YRM-303 / YRM-303 Needs FMSound Synthesizer Unit 4a5cf6fffaa793eca8583ac8b2ba71bf993cccb4YRM-303 FM Music Macro II 339 MSX Arrow Soft 1985 JP f5e2e4aacd44739d3a0c1ea4ecb87da385ce89dcYRM-504 FM Voicing Program 396 MSX YAMAHA 1985 JP GoodMSX0x4000Normale4c8f3b87d755c21572040c51cec1431ae59a0beYRM-102 / Needs FM Sound Synthesizer Unit / No disk support Normal057238307d93a8c2cbb2a296b9dc2f4585315af1YRM-102 GoodMSX43dbcc9536fe61c8172eceeec1481778f7a2b9bdYRM-506 / Needs FM Sound Synthesizer Unit / FB-01 FM Voicing Program II YRM-502 397 MSX YAMAHA 1985 JP Normal2a4b4a4657e3077df8a88f98210b76883d3702b1 Normal7b1798561ee1844a7d6432924fbee9b4fc591c19 Normal30747a56f45389be76362f7fc55d673f1bff8312 Formation Z 623 MSX Nippon Dexter / JALECO 1985 JP GoodMSXc0d33b513704adc56718b1f56c1062718683c77c ce01afe30da38dc58994c8264070195e1cd921b8 Formula Dice MSX robylu 2009 IT Author0x8000Normal5c3ccb1681fbf63ebe318d05616eebace91b5ed9 Four battle Mah-jong 172 MSX MIA 1984 JP GoodMSXebde4d61184438175db424fdf25cec238d91a15f Fractions 1 1926 MSX Al Alamiah 1986 KW e930f53bf480aba5d50d97ee7c60aad96c000d33 Fractions 2 1927 MSX Al Alamiah 1987 KW bb3df0a7656249e486accc60f583d3dd41db590b b8b277064fe2427f586a6ad32504ec5ce756a574 Frenzy MSX Stern 1982 US KonamiSCCd92af75f2f4c1a9cb338be6ffc3cd378b1928c60GDX Conversion Frogger 70 MSX Konami 1983 JP GoodMSXa194b845857c8b33ef4fd5e53d7f38150b4fa4cf[RC-704] c84487e485b8739ec75e09a031f84260fb0e319c[RC-704] 0a272147999e5075987e864935c84ab3def2c47d[RC-704] d8e9fd3e9420bcbb1dd530fda1e9c718aa08d8e4[RC-704] / [a] 57dd2cec85ba0e1afc02a9ae291e9a07eba0a65c 58b20a4bde5477094c6065c368c994bb42551754 2ddd61546a471016cac614d3b1db05aab262d034 Front Line 304 MSX TAITO 1984 JP GoodMSXec54e05a6f784f9c616c94ab8d57e5d2647abc66 64ae3acdce06d1b41e23ee2730797fd7e472bddf 883537f12814ee63c48a0ab744383f8d8e6a5ca2[a] bfff77faa5a16a1df08c36ac5025be9585a11c91 Fruit Panic 298 MSX Pony Canyon 1984 JP 50ae2f5ea4f03e71ca49ca02b0a0cfaf4464286f 3d56286713aa7fd54648cd8f59a7e2ac2becc9fc Fruit Search 69 MSX Takara 1983 JP GoodMSXa27eb9cd77837c1e443e275d0e10888e0fd248f4 a41e43fa3c64d776831451c35fe8bdee58625041 Funky Mouse 291 MSX ZAP 1984 JP GoodMSXe7657d8070399d8e4d65308b356e110d83d23151 Futbol 2202 MSX Indescomp 1985 ES 138a2d56237aea915baaef85e69425a658506cfb G-Monkey MSX Karoshi Corporation 2008 ES Author69757f6ff1da2235fe78ffb120bae4e9a16783c2 Authoraffc4dcc0b2c8846202cd99a2f0ef27fc705d96d Gakuen Monogatari - High School Story 1086 MSX Great 1988 JP GoodMSXASCII1632dd4df2b39461c0694f6bb84de0709478a69d31 Galaga 141 MSX NAMCO 1984 JP c81a30eca8cc5226855ea752a9d83181247329ea 25308ebb33fd87a7f6a8e61868d3dcde3bd622c5[a] 95f36cf00cee3db55940eb60cd39412226a99e94 GoodMSXfaa5aef23febc61a875e28eebe3aadf83e1f5ba7 817019ca70a0f72d294eaa87ed3cb194b99ceb58 GoodMSX2a34f0221193712c43f336f08d2a6e58a1a8974a[a1] 08e9bf1974ec5140a9dcd5c91d20abff49d90ae0 4ec477eaff4cb23a2a82810a0031ed7b647ac670 b90aa345904890dd449bbd35c9648d22e01313fd c650de3523eba6d220445cb727db4ef2248c3a98 KonamiSCC90f3696fe96e720a9309cab8b981b6f84744b60eGDX SCC Version Galaxian MSX Atari 1983 US KonamiSCC356c2ae4fc06a73449283146332b3a3d7f5fb2fcConversion by GDX Galaxian 142 MSX NAMCO 1984 JP 692fd8a85978adafbb530510fff71848f39d4fbd[a] 8f95a83328b06e359e02fc625c3d9c54a6576bcd[o] 15b0ac540b31d83569c275cd3970571b637cc8f2[o2] GoodMSXaa2ba89e0b1c91a4405f516ef003ad14a401096c[a1] GoodMSXe2abb08abacce57703fc008881279890328f6277 d357b635ae007609718d21f0e22ea486f4eac2bd 307f5680290a88400126010ab26cbc87fb85913c KonamiSCCa2816649eac034d3b068678860ace2b94d4c1defGDX SCC Version Gall Force - Defence Of Chaos 733 MSX Sony 1986 JP GoodMSXASCII16e8ab46785e75e19dac497c4a82ac2f5b37ac0580 ASCII163425eea336140da03d7d7f09a94fd928d70e2212[a] ASCII16abe52920e895c148851649ecb9c029fdc41a275f ASCII16c3c67787e2a69e2fdc739d8d1542726ec4ee7c47cheat version ASCII162117a3375684fa3c5bedebced4d7c1019a1ccdcaUlver crack ASCII163179c5ecf2aeba38bb33fb72585d770d6cba2c02 Gambler Jikichushinpa 1096 MSX Game Arts 1988 JP GoodMSXASCII889073c052b0fe29b6de077c8bdf5373474081edfMSX 1Mode Gambler Jikichushinpa 2 1369 MSX Game Arts 1988 JP GoodMSXASCII81f3f586255dd49b0f912bf41e07e88fe3eb3fb61 Game Land 460 MSX Casio 1984 JP GoodMSX39ffaacd0c2875018e7673a887a921fe9d31d6dc Game World - 126 Games MSX Zemina 19xx KR ASCII16b2cca92b7a51a43c310e58e011f3ede1e7125a96 Zemina126in13f0a7b0cbaa3d4c3c6ce744708c68c89eff82289 Game World - 30 Games MSX Zemina 19xx KR Zemina80in12de7891988a868f89f0c02fc84f762597d542d6f Game World - 64 Games MSX Zemina 19xx KR Zemina80in15f912637565932c2bd7c0d7b9be6ad3dc971f96f Game World - 80 Games MSX Zemina 19xx KR ASCII830b41d0e9b3c59e8826112c618206e12e187fb5d Zemina80in1a4a75ed32c2af3c5f009661bb76be4f65e2e9a1f Game World - 90 Games MSX Zemina 19xx KR Zemina90in1edd109e4b518c41eb30a2b6f2a492598c9a08008 Zemina90in1517e2771e65fb6f2aebb3beb97bf501b61dbd1fabug fixed Ganbare Goemon - Samurai 923 MSX Konami 1987 JP ASCII855bad6c00e6a676efc7ff349fa1b5422d624f5ef[RC-748] GoodMSXKonamifd71df03b0e9f3caf701ecfc6d5232060cab3b13[RC-748] Konami273a3dab994b5b9c975fd12711dd19e0c6367caa[RC-748] / Ulver crack Konamif924980511d2ea191481c1e4ff5ab7adc3f082ab[RC-748] / Ulver full crack Konami02cfc41132d518e2b9fc632ec6ad00656bd9ec92[RC-748] / [a] Konamif5009c44ba1e72b64e29fc734741d518b4167aea[RC-748] translatedKonami00c555895b47c435d7965f7c488858c35e2944f4[RC-748] [English] [Trained] / Translated by Tsunami & Max Iwamoto translatedKonamib99cb4a334665798042ac4d0c88da0d22cc32bd6[RC-748] [English] / Translated by Tsunami & Max Iwamoto translatedKonamie6ebf12b86ab0c280aab128d8faf1c513ea6f7e7[RC-748] [English] [Trained] translatedKonami993a662586b2c27c9a8c76b628d8273e7577d1b4[RC-748] [English] Konamia1db5d1cce3388cda539bcb94c996cc18d09b5b9 Konami57d08844f631fe0d732c182fa70cd8fbb9106328 Konami19612132dbf0655e8c05a9c5517ed2d2c3668479 Konami04a3d73c71b0a8623a4952fba7c9bcc965c9f28d Konami3948f96c9c9e619a83a20b5eb3066f494b80a74e Konami45e4a266348c108b162e10d4fca71a98938ec0f4 Gang Master 21 MSX ASCII 1983 JP GoodMSX0x8000Normal1ce1ebbb0f62224aa776c4523e2ef708154e7b52 Garakuta 1091 MSX Wachi Electronics 1988 JP GoodMSXASCII16f4da2be2ee712ba2e7cccf56fcd15483fe0023ed Konami4b872dac11a720e2b9e86f22df1e2214c1059281 Garyuuou - Dragon King 917 MSX XAIN / Sein 1987 JP GoodMSXASCII81f26dfa026b116c80c951a995633b4c2f7fa033d ASCII82a1c91bcc9b1b621773232061d74b7098420f1d4 Gekitotsu Pennant Race 1126 MSX Konami 1988 JP ASCII84690b2f6ce28e55d56ab185c573fc27a9b414468[RC-757] / Needs SCC in slot 2 GoodMSXKonamiSCCb8f4ee146c3cdb8f506371d001e6398ecda85251[a1][RC-757] GoodMSXKonamiSCCe22a6317747903475095008d0d5ad9427ddb34af[RC-757] KonamiSCCad995ede603a28c72dad06c245fc7d4dad85d230[RC-757] KonamiSCCd89922ed43ea4fc0b87d7e5baef3f072bb566849[RC-757] Gekitotsu Pennant Race 2 1245 MSX Konami 1989 JP GoodMSXKonamiSCC93b07e3cd5ae2f00b36824a74f83a3ef0d65d0d5[RC-766] translatedKonamiSCCdbed87a53c604f7140577d6707ca0fc8490463a6[a2][RC-766] / tr Eng translatedKonamiSCCfaa5f0bf79fde9ce74b91a7091d203771cae8499[RC-766] / tr Eng Genchohisi 1575 MSX KOEI 1992 JP GoodMSXKoeiSRAM3274d1c7f66242335c736da29413037bd23384efaa Genesis: Dawn of a new day MSX Retroworks 2012 ES AuthorKonamiff79f5f9c29041083c2359f85dd75b02fe07bfc6 AuthorKonami025ba6d1021eadf0c4ff1e189389b31adb08f6fe Genghis Khan - MSX1 Version 695 MSX KOEI 1988 JP GoodMSXKoeiSRAM3212b3c31f0fd10ff5823dcc8bf6dfeb785a8af2f7Unsure about amount of SRAM Genghis Khan - MSX2 Version 1048 MSX KOEI 1988 JP GoodMSXKoeiSRAM328a4bcf0f38c9f243443a4d4865278f695bd83dc8Unsure about amount of SRAM Ghostbusters 462 MSX Activision 1984 US ea62733ce2a8cd17ff6652251d6879c13fe6377a GoodMSXba30ad98a8488bec6d0c661f7b859beb2fe219d1 7f80ce9132cd870caccdf9ebed58980818b5a036 Ghosts'n'Goblins Demo MSX Daniel Vik 2007 US ASCII88a25afaed4bf87468ad7a0c5cf12375554d67741 Girly Block 1084 MSX Telenet Japan 1988 JP GoodMSXASCII16bb99fe7702733ab0f9e8856e91f5ddcb55d30b5f Gitahei 736 MSX Microcabin 1986 JP GoodMSXASCII8b74f759ba3837968a04e9b64321b47cf087e76ff Glider 447 MSX ZAP 1985 JP 310ab424edf15d5399e31fdbc1230ddcde307d1a GoodMSX3a46902d265d98b27bff3a34160de1c7ef4f116c Gniffel MSX Dioniso 2004 ES Authorb987a7858c9fbf2a996cc200f52fb2a840766eb21st version Author071e07f110bf1b2574c63f81042fc80c894e3f732nd version Go Othello 1376 MSX Konami 1989 JP KonamiSCC6da3d46fa71415962004b3b13a2bac2dd513958a[SCC] scc+2c4f9305d160b9a0fbc3ee4226983750ee5f1952[SD Snatcher RAM SCC+] scc+461c581ebc43b47c54a6d706a03c462bf7d1ef75[The Snatcher RAM SCC+] Gofer no Yabou Episode 2 - Nemesis 3 The Eve Of Destruction 1254 MSX Konami 1988 JP ASCII8f416b424fb913ca067fe75279c924a20fac5c6a1[RC-764] / Needs SCC in slot 2 KonamiSCCb4788d85e67950177882ea4cf6a5fa3219f3fbd0[RC-764] GoodMSXKonamiSCC7393f677e0fae5fc83071c6b74756117b7d75e2d[RC-764] / MSX1 Mode GoodMSXKonamiSCC0413bb3aeacb0c28429b8c85b42796dbe48bef6d[RC-764] / MSX1 Mode [a1] KonamiSCC40a0f35dccc7572ae53bcd4be70abfe477d49bc9[RC-764] / [b][o] KonamiSCC5692e41b3a4c5e767cf290fd6c24942d0fd7b2e3[RC-764] KonamiSCC520739caa1e0aac1b8eabc4305556aa75f3f5a3b[RC-764] / Cheat version KonamiSCCc84ea9e7a02609c71ff75893cbb948fc1f1cf21c[RC-764] / Ulver crack KonamiSCCedc238c1eb254cbf90f1dfdc276ff7ccc93a940e[RC-764] / Konami Antiques MSX Collection 1 KonamiSCCd605458c96bbb538c4624deeeed3294ea489a61e[RC-764] / Zemina Version Gokiburi Daisakusen - Bug Bomb 26 MSX Magic software 1983 JP GoodMSX30e3681be376a76cf9e0f89e54555a7631759e15 Golf Game 29 MSX ASCII 1983 JP GoodMSX0x8000Normal69bda8b4a96187597b95cd5c43e58da0716d0fc8 Golf Kyou - Golf Crazy 157 MSX Hudson Soft / Japanese Softbank 1984 JP 27847b02d2acbebab61f2f05bd2fff0c2291a09d GoodMSXe904efabc5a75840c50ea7edb07052683468a64a Golgo 13 Okami no Su 478 MSX Pony Canyon 1986 JP GoodMSX031a611ba1f75b7d5fe3ed088a61c24598b4b314 Golvellius 1016 MSX Compile 1987 JP GoodMSXASCII167a4126934f9e68c34bf00dd3d9a9e753c05ee73f ASCII16930df58762e7b5bcf2d362462f03335bad732398[b] ASCII16d2ccc4a8de5da21745209d0731671e8f3cbea515Ulver crack Konami91505fccdcc43230550d101967010bed27f9b573[o] ASCII16959a2ddefc9dc1a751cf30779b1aaac775f1612f translatedASCII168b7e4bc408a8c715943bfa00069f2d517b39a748English Translation translatedASCII16678efe599b8085bd27f9d3d42cfbbd7ca74ceb89English Translation translatedASCII1605767fec34beaaee390a86be496d98d2b27de641English Translation Gommy - Medieval Defender MSX Retroworks/Dimension Z 2013 ES Authorda3555f2f6fd6341ef4c5bc878b45581101a1126MSXDev13 Entry - English Version Authored73332e7053aab6471f9eb3fea00be25923641fMSXDev13 Entry - Spanish Version Gomok Narabe - Omo Go 156 MSX Comtec 1984 JP 6f0f61285be1158ecf2e5393619187f26a7395be[a] edff8461c6b8802831a59f82cdf0fa3735921c00[o] GoodMSXaacaf2e694a1c05f2ef20a8ce6892242c0f88919 Goonies, The 741 MSX Konami 1986 JP GoodMSXb1248f7b9d4e16a894555c51c533d3fd9a90802e[RC-734] e0fd133eb7f645238b3ec546624c2d12b8a913f9[RC-734] 250d0d6e646eae4817802c49e367ad8c1c35a462[a][RC-734] b7b4d5c83d8c336dbfe32833bba974cea7f0cb8d[RC-734] 4624af3dea3996f70d860dfdbd1a8844b8cf43ec[RC-734] 3ceb18ccdd8153aa260f287f5de4460601040ab6[RC-734] / Ulver crack Gorf MSX Coleco 1983 US GenericKonamiafb235979ed7d91596b7adddffe7c295080e4669GDX Conversion Gorgeous Gemma in Escape from the Space Disposal Planet MSX Impulse9 2014 ES Authord85298a9cdd9bcb4a546cb020ebaccd3c4281f07MSXDev14 Entry Gozilla Kun 463 MSX Toho 1985 JP GoodMSX8bd874aa854cc26d7fc48e7d2de60dbbee681721[a1] GoodMSXd6b6cb3d85b991f8adf2e07a0e8e74b586741d12 GP World 496 MSX Sega 1985 JP GoodMSXeadf124b91b996b890b1ae84b75b21ee6568d1f3 dc49e2ac9985a417078412cc09fe7270033ffb17 e25401f0423cf11e62c955b47887cc3e036f087d GraCo MSX BiFi Productions/Metal Soft (Vampier) 2009 NL AuthorNormal3e0360a4f235522498682a714f523039a5ab6c7cGraphic Coder Gradius - Nemesis 742 MSX Konami 1986 JP ASCII850efb7040339632cf8bddbc1d3eaae1fb2e2188f[RC-742] GoodMSXKonamie31ac6520e912c27ce96431a1dfb112bf71cb7b9[RC-742] GoodMSXKonamif0e4168ea18188fca2581526c2503223b9a28581[RC-742] / [a1] Konami98748c364e7bff50cf073c1a421ebe5b5d8b7025[RC-742] / cheat version Konami910b156b562880ace789fb3f9b11848163c2df20[RC-742] / Ulver crack KonamiSCC6b42315eddefa0a33ea0c4510d6a4bc1086a5163[RC-742] ASCII8e96888c96c3044ca7ce57dbdf1744df7469f7465[RC-742] sccf2854b7198f325691a5ad3129bd05b44d6120771[b][SCC][RC-742] Konami1434386454c776800cba3805c4041b0105c2f71f[SD Snatcher RAM SCC+][RC-742] Konamif4a1e82c533677ed9923b11a0b970264d9ecf8ca[RC-742] / Konami Antiques MSX Collection 1 Konami30a7f3ea1612f20c0f62b1a121bc7d79ae515e89[RC-742] / Konami Antiques MSX Collection 1 Konami0ac97f403e89cf0a5646e1ad49ff2c19223d0a74 KonamiSCC9a6031c20b49afe6624789613eaa2c62c1bd70a8 Gradius 2 - Nemesis 2 932 MSX Konami 1987 JP ASCII8fd7b23a4f1c2058b966b5ddd52cf7ae44a0eebb0[RC-751] / Needs SCC in slot 2 KonamiSCC2ae97f3c8152f56695c3d3a2dedaa781bf0a15f1[RC-751] KonamiSCC076c45e152598777cdb1ace82b11994018bfed83[RC-751] GoodMSXKonamiSCCab30cdeaacbdf14e6366d43d881338178fc665cb[RC-751] / [a1] GoodMSXKonamiSCCd63e20369f98487767810a0c57603bef6a2a07e5[RC-751] KonamiSCCc66483cd0d83292e4f2b54a3e89bd96b8bf9abb2[RC-751] / Penguin cheat version KonamiSCC4127844955388f812e33437f618936dc98944c0a[RC-751] / cheat version KonamiSCC4c15375910461a7a34a2fd54c7f51e712f06bc69[RC-751] / Ulver crack KonamiSCC06aa7bdc1d8f01e67d1a1736084384393074e613[RC-751] / Beta Release KonamiSCC4c2f015685a17db7a8c3893e868e0a84a8dbf1e5[RC-751] / Beta Release[a] GoodMSXKonamiSCC6844758ff5c2c410115d4d7cf12498c42e931732[RC-751] / Beta Release KonamiSCC76666da5870d1901026e3dded3f22ce7a706689d[RC-751] / Konami Antiques MSX Collection 2 KonamiSCC87b7c80069a15fdb90b40dfed736fb8492104cd9[RC-751] / Demo KonamiSCCd441ccfede2c089fbef54e20b90c0d4b513c5196[RC-751] KonamiSCC7561f3c70c221df40f1b28209c59882ca065603d[RC-751] / Demo Version KonamiSCCc6fa7a891b491d2bcf31b81e33d43b9418a0f269[RC-751] / Smooth scroll / vsync patch and cool colors KonamiSCC278e7072dc72990b9180a8f5c9235a53c2a5c3ec KonamiSCCe90856547e130bf745717f4a11cbde69d3fccc54 KonamiSCC1277fa1cef8d13a0e4b651d9b9d07135edd7e57b KonamiSCC124b77059693e719c8e09c23a58cde0537f2a1ae KonamiSCC14cd5da17bc9a4f7c83ffc75cc1d6e826c98cc76 KonamiSCC04617e65a7debc16b59659ea0008fcb4815b01a2 KonamiSCC52dc06b9492e5b03cfd567114281ba590dc030dd KonamiSCC7330a3bc6b629c73e3c4484666d8ebb0243baf51 KonamiSCC90997276b3afdba40ac25dc2a603076220f98958 KonamiSCC663a9e1281fbc1f9197e26a9f4111a8c228c28b6 KonamiSCC97c5bce806337f1bac0a90074e0c986a4f278555 KonamiSCC2100fc1d0ad5a23d8580142bb6daed1f3e27e66b KonamiSCCbf678073f1260b90650cd293a0c8fd0716a98fea KonamiSCCfa40389630ba567e899d8ba59ede823e074e21d8 KonamiSCCdcd26715d7a99c3434d7dd30b862d9a6ae662822 KonamiSCC1d63002549455885b1e0ed18d36b0deb92f987c1 KonamiSCCa5c62e653376eb12e47f4304b6bccac60a80fe5a KonamiSCC99d070056d1f8a71709ae997527c3ed81088332e KonamiSCC0954b9d48076993cd0b7e245700e347ef7cb34d0 KonamiSCCb459a1456a9a99f955c1939b4fd42e1856f69ec0 KonamiSCCbc481f724bfbc10151e077222f2cbe930f29288d KonamiSCCd3390f3a20695e82a54d1b31f9abd5d94f1acb01 KonamiSCC4bd45de36cee439cbbd4e3b866bf2c0a01533c88 KonamiSCCaba49422b6ca5e8ac3a1cfd11257ee8349e6f058 KonamiSCC456a6e94c7fbcb1b8f6b91b0a4560b32145c020a KonamiSCCcdc0b6fe335271c8e340fd5d84c44264c8d89e2a KonamiSCCa4d4ba925f55f34290a31630703ed77f400dc25c KonamiSCCc046f10c0422756f17a3f480abeefde81123a1ba KonamiSCC4717edb49fb5d47c93071e3c74e32263cf04f7b6 KonamiSCC68d66abfb49a0591319171b384718c0b9a12617c KonamiSCC3b355fe5879089e9199a0805e18a0fb06f625ae9 KonamiSCC8611162631970c5d1a8c0e86eaa7d2b89eb3dd9b KonamiSCC8acc071c33dd5d860cda89ffcae381c4b88f508a KonamiSCC329454f7147b48110ff398ea290036102073063c KonamiSCC8f112c22463ef731c45aba353497e4ac8915bef5 KonamiSCC41e7e225fd4151b5a766faa6f9de02af7cc8a6d4 KonamiSCC856c11ab106d93e8f01eb144a356fc8e430e26a8 KonamiSCC0b0805f52b75a10f79903a997a7dc3e5ed07a049 KonamiSCC4a2f0e97b3dc40bdd15b76606b86789db5b0612b KonamiSCC6310aa4d7d01adc8ce34c0d20fd370e738cded30 KonamiSCC1a9a2646f140bd48982c099cfe68deaa87d795ee KonamiSCC65f6d7faf3eb521c2deba18b4091f405e889954f KonamiSCC24c5bac01ea9c1dc54c59a11ac52fd32f7e89587 Graphics Editor 2976 MSX Electric Software 1985 UK 711e03c6620ba574f98f8954f1750cdcafd60d14 Green Beret 3091 MSX Konami 1986 JP 0x4000Normal3ea788262557f578b25e4e36e61c1cf9fb1155d1 0x4000Normaldcf79362f68853733326a22eb8fdafa17bca281e 0x4000Normalacd46021a228b2b612c82d04c124b78e7d4c25e3[a] 0x4000Normala6b77c5873f220c00578339761552e5ee82fab62 Gremlins 2 2325 MSX Topo Soft 1989 ES ASCII16d09d75c7f242a696931407458e717c9bdaadb7d9 Grid Wars MSX Emma Six 2006 GB Author0e6b6644021dbfad3c486c378818ea5e432bb9fdMSX-DEV06 Griel's Quest For The Sangraal MSX Karoshi Corporation 2005 ES 4785fafdef83b62103c9d3feeed582785b2e5189 Author0x4000Normal989d9ba7c0f29694fa3b737d09cf102606b2fd5bMSX-DEV05 Author0x4000Normalacb6e93227bc89da119443e03890b58a1befc0b0MSX-DEV05 Author0x4000Normal4dd2ec21474720eb56f012bb761117b4d507f331MSX-DEV05 Author5a9364271ddca5208b625c891f01a89fd2367a8dMSX-DEV05 Grog's Revenge 2601 MSX Comptiq 1985 JP translated3b7a7cdc05b45518d2757ab82ed6415da1309486Ruote E Cavernicoli [tr It] GoodMSX32615267e8497db3f5e6f5348ee309e73c80a1a7 04325bfe5bc20a8f6d086933d138fb92ca200111 cf4c1a4df0a55c4a0cf33436e649f32e5857b5cc[Topia] ea151b443c27b77ac43fdae99db3adb0abf43fbb[h Prosoft] KonamiSCC0e8b85a181047bd87f251400dce581b8af7325a8 ASCII89795cebc7eb3cf602acb6196043adc5955999e1d GrooveSX Live! MSX aorante/303bcn/WYZ 2012 ES Authorf318d55ef854ae27c94d549cce61b49a679f3ce8v0.04 Guardic 726 MSX Compile 1986 JP GoodMSX67baaaa870ad4a08f55ec1c67dbeabc97b6f18a0 5e28fa0cd90bcfb7995acb025a22f6f556ccac08 32a62d20456e0654a973774f0134a7278ac430e0 Gulkave 732 MSX Sega 1986 JP GoodMSXca5f14925a7d33d83dc581bbe2ff5258389bfba7 49ab343df35bf2df206cdffdcad63c64ca6e7284[a] e90b579ac64798343abb635fcbf929e83714312a 0b474ac53701ff0d53d30e0990faa76fcca90f39 50f61894c2f519fa369d7b925de7d52b708dde29[cr Crack Fm] 11306e4184da793050ca6c0f59ad3b2b9f084ef6Ulver crack ed798b8ee852611a0e4d054bf31652ef3f73e67f Gun Fright 735 MSX Ultimate Play The Game 1986 UK c4bd9f0d34061b4025f8d5c5866d51c763612e92 GoodMSX445963fddfbd0487addaf4b0ad620520e426a1d2 f76e7e4ccca2a77a13d9a2d5198667ba810491f9 974d6ef7601dc590a35644e2af7743067a867ac3 Gunjin Shogi Mars 746 MSX COSMO MIDA 1985 JP GoodMSXf2ec3714138175212b6b6e28b5da4361cc4b5471 Gunsmoke MSX Prosoft 1990 KR b27aef37ab7327e12a8d4460b89af391d3789961 Guru Logic MSX Karoshi Corporation 2003 ES Authorbe6087399419e2539751faf3aa079c45fcd590c71st version Authora355e49523edce5f15ebf92b8320ffa6523c13822nd version Author3f52c04e64688e8a5cbfc95b21ef03698e1de518 Guttblaster 2450 MSX Eurosoft 1988 NL KonamiSCCb26b117ba689775c14bdffb4cf04f8de045f70c0 GoodMSXASCII169bdd5886c87b86043de4864798092794bb0eb4cb KonamiSCC1b8bc66aaecdc92dc7ab1434a3434cd4b7b9cab9 Gyro Adventure 500 MSX Nippon Columbia / Colpax / Universal 1984 JP dd92634209338632401e2b6f118cbaaa5f8add86 ac412b4f746deb653002e30ed9ef874a4d5d61f8 Gyrodine 762 MSX TAITO 1986 JP GoodMSX99a8631cdc37f0dd9d8793ea8eadd3f34e0bba51 6e17ee44d62f57be20177a692319e4da14be572f Gyruss MSX Parker Brothers 1983 US 4f820cb362346a1580ab41cbf2f8f8fde35725a0GDX Conversion H.E.R.O. 282 MSX Activision 1984 US 6fbb385147a939a7e6b47f5945d8e3b671a8c065[b][u] GoodMSXebb70722f75279911cce79e6bd78b8f514561b0f 39b67dd1ae799f2e151c229ac2e32954abb1bd72 ed97f51eef9192ec7f905f2ba4aabf6acc513587 f560760d206099d98d5c148c9847247dc630dd8f b3cfb4a6dbc2aee91f7b23035cf6711030773856 Habilit MSX Iber Soft 1988 ES e05a55586c70106ed7f2f442c77848824c5609bcGDX Fix Hacker 1184 MSX Activision / Pony Canyon 1988 JP GoodMSXASCII16920f141db0d42952130855ac1dce446c327e7d9c Hadesu no Monsho - The Seal Of Hades 869 MSX Casio 1986 JP GoodMSXfcc6b6b9f05de409492a1fb988fa671e46d54196 1804cfbc4f175136b7b934b575d49cbc0e726ffe e2655c64b1a068cd5b021d5ad62106e794d217b5 Hai no Majutsushi - Mahjong 2 1370 MSX Konami 1989 JP ASCII814403ca71d287084569e6c2f4124d2712c665219[RC-765] GoodMSXMajutsushi25022c4f5cfc805e93026fd5fda781e9a1807474[RC-765] Majutsushi3ff54468a5acf1ad44e85bdc993044aaa3165a63[RC-765] / No PCM Majutsushid08d4e2a8d92c01551ff012a71a1f3e57fe2d09c[RC-765] / No PCM Konami90722d413913ab18462aa741f85b820e751bb50f[RC-765][b2] Konamic34a1c225d1a5fc41a998e23d1209b59b3b759bb Konamif5e199a5b39bad10256d8e963f0da272ad359517 Konami4376342bf42334d98dbd3e35ef35460974269c58 Konami751571d21457d89e5c52c45657bfe9a617ae8e14 Haja no Fuin 994 MSX Kogado 1987 JP GoodMSXASCII844b23a175d04ca21236a5fef18600b01b12aaf4d ASCII85683e6e89a06ccab35516951ba6a284e83985d2c Hal Gamecase MSX JAM 2002 JP ASCII810758fb5d42b97a05b537db9b009bd644c153c93 Halnote 999 MSX HAL Laboratory 1987 JP GoodMSXHalnote1af617bfd11b10a71936c606174a80019762ea71Needs System Disk Hanafuda 597 MSX Comtec 1984 JP GoodMSX5370b7746112621169d503bbec2e7fc526279f53 Hanafuda Koiko 275 MSX RAM soft 1984 JP a0e815c2d224d05fb2746e4d327ec22bfe5b349a[o] GoodMSX301ef3c728041eb1dfd89227a901c32eb000d348 9b2df344baa306589af86bddf2ce373b6c66e60e Hang-On 831 MSX Sega 1985 JP GoodMSX26fc48ddaca0fedc90fc151d4ecd5ca6d1ccc77b 7f086cc2ca0c35bf599f48c2d5e901e89411239f a67e699edb471c35a217a5c3e6831c6cdb0eabe5[a] f701e29e07507e78d39365db38ea7f531996cda8 Hans' Adventure MSX The Pets Mode 2010 ES Author9c6c43af0516e7232a7aaafee5a47235232cf7f9Version 1.0 / MSXDev10 Entry Author6835f0aba6c95a106309129f90f58adf04994f14Version 1.1 / MSXDev10 Entry Happy Fret 594 MSX Microcabin 1985 JP translated146332dd0a190c3e6eb4abe1c4306a8f6faca9f0[tr Arrowsoft] Happy Square Christmas MSX aorante/303bcn/The Pets Mode 2010 ES Author63ddb6adb4a3b95a596f9aa876cb658929392d132010 Christmas Demo Harapeko Pakkun 278 MSX ASCII 1984 JP GoodMSX8f35c41a5235dec22c3b99cd6dd63cfda0a7a821 978aa035cc57a6b63d8bbcafcb310ebb2436dd87 Hardball 989 MSX Sony 1987 JP GoodMSXASCII832151ddc4303290c09889bf8107493dd34296043 Konami4ee3e785e6949bd535804a32c6a1a31eeef30031 Harry Fox - MSX Special 824 MSX Microcabin 1986 JP GoodMSXASCII16SRAM2a3de07612da7986387a4f5c41bbbc7e3b244e077 Harry Fox - Yuki no Maou Hen 575 MSX Microcabin 1985 JP GoodMSXHarryFox3626b5dd3188ec2a16e102d05c79f8f242fbd892 Hayabusa - Moonsweeper 386 MSX Imagic/Interphase 1985 JP GoodMSXf93e23d958d2075bf476cb05a3c4f48d0c41aabb 0d7370794507150d78825e5bc20553bea034c309[h Prosoft] Head & Tail 1928 MSX Al Alamiah 1986 KW f4d4d132866707e7f8a97408b093acabb67ef5aa Head Over Heels 2747 MSX Ocean 1987 UK ASCII1626a7b0118d158e9bd3ea947fbe68f57b54e0b847 ASCII166e1e133d75ad56f1309f5e2b8c31a8e915323b73 Heavy Boxing 72 MSX Takara 1983 JP GoodMSX69bc7cb436ea2ea3a6b49c3b050bae24271a2f9c 3b697fd0424848e23d9d21ef97d47d2716bb08f6 0fa2bb1effefde0861fe53a38ff5b56a6a320c80 Heist, The 576 MSX Livesay Computer Games 1985 UK GoodMSX20191b437958e990d14660f476c340e140236c70 70eaf556b2f9f077895d2b72ebe880e2f9213b81 14fa455b51b848bcc15f104eb5fe290f458e409f Heli Tank 308 MSX NABU / ASCII 1983 JP GoodMSX6844ddf610e1a98b55e77fcc364b38a037751a2b Heroes Arena MSX Imanok 2011 ES Authorfa6172ba3a78c03ed6194dee6e26a1dc3adb939f Heroes of The Lance 1643 MSX Pony Canyon 1991 JP GoodMSXASCII8SRAM8ead699582641d103a94775a08121ecc336b99eae ASCII8SRAM86e1d2d5b0688860b0a04d53fe9e5e3939dfba547 High School Kimengumi 990 MSX Sega 1987 JP GoodMSXASCII813fbb273a95481330d33c9edc4263e30ccf1774d High Way Star 258 MSX Way Limit 1983 JP GoodMSXf72681f09b52d530ec905e8df21c4a15e30201ab 0672e025b47c9447398d69fbed5c56afc157acc1 c0fa7d53b3e66bd1e1680bb135db8f74abbac52e[dist Qnix Corp] 650273f96ec4cfa4800049524e38aa9c28c6cafd 8a2b89e20c4d26d274ace6e14ec106e25099768b Hinotori - Firebird 1002 MSX Konami 1987 JP ASCII8fed52c0455ce1e7cef80cd144c3d0ef3636134e5[RC-747] GoodMSXKonamid723f2eb1c887db5448fd56be5e7c57e60d88e63[RC-747] / [a1] GoodMSXKonami612c18616bc800d9eb69b9da3cd29eca31f50c1c[RC-747] Konami300f20adde3941ab10eb158db48250e8e856a911[RC-747] Konami875bbb27f6a54e5d123dc8a6df4bf46f97c23a57[RC-747] ASCII845760316a4ebc74ddd55ee163c5aeaaafe965d2c[RC-747] / Vampier cheat Konami7e5024e6e6aecd05ec6c885786a4aac287ed110b[RC-747] / Ulver crack Konami357c0c2e9b3b2970c587c96f86a6b7272160f54a[RC-747] Konami45c108f04c1abd12bfa5d2222e578bda6ef54c39[RC-747] Konami0329a79f82f67f3799124f3539eccaaa37adca08[RC-747] Konamia0a49baa3755dfeddb993f978efd2fdb7d1e2c79[RC-747] Konamid37d24f73b1f819b676bcbf97217cdb39c9d8916[RC-747] Konamied67e8266c152f3020543fb5b3061c9a57a3ca1f[RC-747] translatedKonami7b0b379c6f46552a6f5a0408b8182e8e79540f7f[RC-747] translatedKonami581eac8e476e2c95aae88befddef1ff3dc81c872[RC-747] / fully patched (Turbo and speed fix) Hisha 605 MSX Microcabin 1985 JP GoodMSX3c2295fb9fc9f1b6a607a74f4538e24d170522e5 eade20a0145afd47ec3551825e9994fa29e17e22 Hit-Bit Demo 3139 MSX Sony 1984 JP fc2a86cd2c24e2acce77a6d3e491821a03dccdfa Hit-Bit Personal Databank MSX Sony 1984 JP 4b0c1dedc44cd1813b274162093f713e895973c1hacked 661d0d86bfb31adf58aa4d766e3bc69cdea55738 Hitsuji Yai - Pretty Sheep 283 MSX Hudson Soft 1983 JP translated3beb85d30e3e7319c719e0a290096fb8aa9ff3bc[tr En] GoodMSXbbca0b00374ba5366ba53b380285d38857450a7d Hole In One 318 MSX HAL Laboratory 1984 JP c344ed78465203e12143bd9889281ba981a195e4 GoodMSX2f8416d9cce6587aba4b71b1d3e3b626609ce7ed 030b6ea534f2285b57b8740c0dcb4c8bf92ed7c7 GoodMSX4f73d4b731b8309145b2a9bf9c37620e4f4a391c[a1] GoodMSXf3952b790ae2a8a1434b52bf816c56c9e680f73e[a2] 874c6bc9bfce17429868c1f5cbde0e734d09370a Hole In One Professional 645 MSX HAL Laboratory 1985 JP 8d3f5cb0d2d23b4a3079f4f139e72173aee18105 GoodMSXbb75492c6ebbbf1827b3695f70e481d65487da44 84fb13460412203ce8b35d3f83c1b41f15ca42c8[cr Hillsoft] 13f55fcb993dabbef792f2a3696c4f545f31f44b a55617157c5d89db5900654b641e757a37fecd1a Hole In One Special 852 MSX HAL Laboratory 1987 JP GoodMSXASCII163837d3a72498caa070866d947956a2e30fb94fbe[a1] GoodMSXASCII16facb967717c21fed9b375481908aa337b3361c45 ASCII1614537ad9a551245261776bf9b64068fe4921ad47[h Thevma] Holy Quaran 1929 MSX Al Alamiah 1987 KW AlQurana18b1d49df954a3316ed215b6facfc1ec020ce13Only for Arabic MSX computers AlQuranDecodedb40b178c6876cf3dedf4ae48262a5914c5f674fdOnly for Arabic MSX computers AlQuranDecoded0dbc7defd96b71c1fe2d63cbc725f5e58aea2db0Only for Arabic MSX computers Home Calc 314 MSX Matsushita Electric Industrial 1984 JP GoodMSXf29cac37c5001ddc752ff4a0b9cc51a965906da8 Hopper 2390 MSX Aackosoft 1986 NL dd9a41e4c8802cd4bbb87e30281711f4e7ae017f Hose Diogo Martinez: The Bussas Quest MSX Muffie 2009 BR Author0x8000Normal21e44cc5957b65b24c34d2c42b39879cacd82ff9MSX-DEV08 AuthorNormalece128e71c14cacde1c58f61c51a86d986789c3e Hot-Assembler MSX Epcom 1985 BR 7f189caa1f0c30760707e05c13bc50c3fb3ac441 Hot-Logo MSX Epcom 1986 BR abb5cd4c8bdde234cadb0e10b794be3249dc51a5 a99796b45c92ca6b18e685130ae3eea43946cf2b Hot-Plan MSX Epcom 1986 BR ac1096e7b725830e61213b2b26310ff322df2946 House MSX German Gomez Herrera 2008 ES AuthorKonamiSCCb5bcbe2dfeaf7cc156c0d8bbf4fdcbe97b2007d4 Hunchback 600 MSX Ocean 1984 UK e6ba49603707c929291c39fbdf866474bf096316 Hustle MSX Maz Soft 1992 JP d3bc33d56b93e19fd32cc473de3e5b9935b3ba28 Hustle Chummy 57 MSX General 1983 JP 86e688cea0f0c8bcd31b6bcd19fd72ac108646bb GoodMSXe06ed9b0b3e1fbb9f27be65c37b4d9b548889b4f Hustler 2431 MSX Bubble Bus 1984 UK 9ae54a8a5badd3edbcb6b693df1821d15efbd9c9 Hydlide 1 577 MSX T&ESOFT 1985 JP GoodMSX9dcb43b654a08de2c52147f4a2e628b5fe3761c8 d80b53ea8796b2483c25bdea5b77b0231d1d4880 Hydlide 2 - Shine Of Darkness 825 MSX T&ESOFT 1986 JP GoodMSXASCII16SRAM2da4b44c734029f60388b7cea4ab97c3d5c6a09e9 ASCII16SRAM2a3537934a4d9dfbf27aca5aaf42e0f18e4975366[a] ASCII16SRAM2b18d36cc60d0e3b325138bb98472b685cca89f90[b] Konami552a758f77a7c35969523dbaca74587f0b59eb2b translatedASCII8SRAM80fe07b5f62e179a1294253a505a296ffefca8c95Woomb Retranslation translatedASCII16SRAM21eae08241c0681d6b6d87c68ebdaaff1baa40069Translated into English Hydlide 3 - The Space Memories MSX1 Version 991 MSX T&ESOFT 1987 JP GoodMSXASCII874e9ea381e2fed07d989d1056002de5737125aaf Hydlide 3 - The Space Memories MSX2 Version 992 MSX T&ESOFT 1987 JP GoodMSXASCII8f182d4785e3eed00292f7ff4795c9dcc6c990a0a GenericKonami5d2062ecc7176ca2b100724c2a17f877878d721d translatedASCII8e10f5d2e622dd7dfe7df2e15d209edd3025adba7Woomb Retranslation Hype 2872 MSX The Bytebusters 1987 NL 0x4000Normal6ad2f111227d6d4659f0089b9397896d16f867a0 a9bf062f695475e84be4ab78afb9715fc3f6f40e[b] 0x4000Normal1d881446dd40769bb3d97793e23a4ab093f333f4 a192bb6bc6726d09d32051dc9f2a885ab7b0b813 Hyper Olympic 1 261 MSX Konami 1984 JP GoodMSX56c4813ac549af8711b2e2e42d7db1b960c4c040[RC-710] 82a15beb8b31d250c12666e5e773a539ad5e47c5 b6973b07a574fb0077ce39c408b47b5ecfcc3788 2b84d0b790bd6b0022923adb4a43fbdf00bc230d f557652ac889c45aa4d1543e2700a9b1c61f2c76 90b64dc7ccadf696cb6f72666f5d1e2c4e2ae79a Hyper Olympic 2 262 MSX Konami 1984 JP GoodMSXcc9a82898da39e5cfeb0400006d093d3b869f28c[RC-711] 4b215e407608508e59be33973212e81a1bd25bc4 7e823020cbdde1e370243dc72e82cc8a7573e34c c426cb17ab82579875a8e0638e2a434b3d25d5b3 Hyper Rally 580 MSX Konami 1985 JP GoodMSX7dfe091e02f6c1b21bd65e44eb5052d564d0ef92[RC-718] 708eef06a0b5aca90dda120877b1cc3a5172a642[RC-718] 2033ea831fd887c82fd8f8a1c9491f7a5dbe0175[RC-718] d1a7cbaff354b59003f0e297723b912cb6ad0b36[a2][RC-718] 14393568469c74bd6f8be7770149db19f0553ab6[a3][RC-718] 3b13e164130cab9b36c1a95e06f8d9fd707fc97f[RC-718] eeb810dd36b512e8cc842be6a22ddf3380088a56[RC-718] / Ulver crack scc+958c2a686fe52324c0adb85b3dda4471910f7000[SD Snatcher RAM SCC+][RC-718] scc+ff4c92cc84676c62b9c08b197effc6eda741db01[The Snatcher RAM SCC+][RC-718] 40010445696c63670de32907251b771f229523ac 638d256616bbd8d97dc09385a17548c09140947c Hyper Sports 1 263 MSX Konami 1984 JP GoodMSX2c85b993671de612e9338d094565a4eb32a97ea0[RC-715] 9cab6a793e39076e3f598aa54bffd5585ddc7e3e[RC-715] 4f6c94cee6550b1619813eaacd88af02ebde51dc[h][RC-715] 50f932b50ba7d67a7182f35314e9ace5ea7b82ff 97d75faf5dda6deb464b7a2be7c48747c3049a46 Hyper Sports 2 264 MSX Konami 1984 JP GoodMSX9002aa0b2d49a9fc323180487ff591781867a99c[RC-717] 1c6b2b81552590e82d3205afbde93653fa449429[RC-717] KonamiSCCd394e2f71cdb955d03cbf8173950e82fc69646bd[SCC][RC-717] scc+1e92d20a5815ad0dffd50d8324fce389ca033e05[SD Snatcher RAM SCC+][RC-717] scc+fcd563fe7ccb293be1f1adb0da7d6f0da8fc7e2e[The Snatcher RAM SCC+][RC-717] cf7260443f814c57619a862f82162fe3e12369e1Konami Antiques MSX Collection 1 d74463824c3c15cb391ef7a13054472b5e7abe73 ae972d988953c09e976f41cd7f4e6cc19a63f7fe a525c50535907ed7092deff7ddf68b650a612668 Hyper Sports 3 579 MSX Konami 1985 JP 1e429b73af3d9e9ae0718d88405a7b7edd0a24f2[RC-733] GoodMSXaf4e0490c178a18a47b34831fb5e3cf8b61d34d9[RC-733] ec76d4d9b61bdbde9a4b5be082dbdae96321a591[a][RC-733] 6842fb21abb2531c17fe0687d77e012348d3dc2c[RC-733] 3d45014accbee7c0df798a7c77771666f863dfd5[RC-733] 00f6e7efbb5a50b66723a3130009a96d8a588363Konami Antiques MSX Collection 2 I need speed MSX CEZ Games Studio 2009 ES Author0293bc55df4f9b7fb97ae9cded8ab2c5c9ed0fd4After MSX-DEV08 version Authorbd46981d1b580ec9a2d1d909f5c1a8dfd9a71ed7MSX-DEV08 Authorc6c079b105492f7665a8d6ccb81e337b864582faRetail Version I.N.E.R.T.I.A. MSX Andrea Rossetti 2006 ES Author0x4000Normala53610adcf84bdb1b84d94d4c6d1f0a7d45e9dddMSX-DEV06 Ibn Maleck 1930 MSX Al Alamiah 1985 KW 2d174a58c81eda0490e4606afa167bbe4082d845 Ibn Sina 1 1931 MSX Al Alamiah 1985 KW f332df804849c7a12f50dbbdbbb37949716b3671 c17b78ae95a17a3d3eb4cd00546f15cbbf4c846e Ibn Sina 2 1932 MSX Al Alamiah 1985 KW 630db2516b6f6da16882f507e74fb35455552ea4 136506ec295af5b3aa0012cf9cf2405be1cbd712 Ice World 365 MSX Casio 1985 JP GoodMSX167b226b54cc5b681a69d2c1509116ffaba868e4 Ide Yousuke no Jissen Mahjong 1062 MSX Pack-In-Video 1988 JP GoodMSXASCII8219e9acc2c023c25a8bbe9fb7d1764ffcff66034 Ideabase MSX Idea Logic 1986 ES a649e98ede62e7cae982779d60674eea9eb7180e Ideatext MSX Idea Logic 1986 ES dc39adf9f825e2f55fa7e6c1bb69483cbbec2695 Iga Ninpouten 1 380 MSX Casio 1985 JP GoodMSXf629b8f7133c3442d0371a4d84275fb2e0974854 9f0bba20e594b58b620eaf656daabbe2ddb365e2 Iga Ninpouten 2 - The Mooncastle 705 MSX Casio 1986 JP GoodMSXf52113b45062adc75cf7a0ed0538b3a07696cc24 3bb48bedf53a781f0470e0bb5ad2e7bba785310e 1f8b9ac28dbadaf5316770cd6a421c998e33a8cbUlver crack 3b24504189722babdcef68b82a5ff6bb7d3ec608 Ikari 891 MSX SNK 1987 JP GoodMSXASCII16e065238580b9e118b6c9e789d797cdd99e0a1c69 ASCII1625b80aa670e7fdc84b51628b6dbe930eb234beb7 ASCII16fe8aae62fdec28631d8f7b6659dd95135c1bcbb7 Illigks Episode 1 - Theseus 225 MSX ASCII 1984 JP ed1f7342b9b26ec02ca2d584570d75f925167ea1[a] 17e39a9a38c70608984b4d14f4948f35d131c816 7d1c7b99c73ff9d89cced66f50ee2f8c83809881[o] GoodMSX8aa6d0aadf00de9112696da5b7dc6e3b62f2d9be f9201b7b99a6cb4d8c847e30dc2617d4243be8e7 Indian No Bouken 101 MSX Hudson Soft 1983 JP 3e7b4374cd6c08182f968328888d1cb3fd1c7dd7[b] GoodMSXaa68633ff1afd385535b2abd7c8756b6cf79256a 71139e82cf55450d0b48f124bc95fbe1ce5dabaf Information Rivalry 1974 MSX Al Alamiah 1990 KW ASCII8217b21d10d80fe2d5d04636ee8c2f97f6edfe34a Inindo Tado Nobunaga 1545 MSX KOEI 1991 JP GoodMSXKoeiSRAM320cc6c30c6918fcf780e57d40f58a4cf2a82225cd KoeiSRAM3297f2cc1cd4c51dc20b86cd190016b8f0c006a968 INK - Exxon Surfing 3605 MSX Matra 2005 ES AuthorMatraInkdf505aecddb20171cafd06351e0b7d208365e708 Intruder + SCC PCM Replayer MSX Artrag 2008 IT AuthorKonamiSCC77f2d9d27724a7c71c688a598d34bd0c8a8e56d0 AuthorKonamiSCCc654b842cff7227d9e85fda25279259e837cee9e AuthorKonamiSCC093d5ab39878ed4cdd42fb7c2a1502b7e65b17c0 Invaders 3323 MSX German Gomez Herrera 2008 ES AuthorKonamiSCC46f995308d736da235ba7f17841d8afeff403c99 Invasion Of The Big Pixels MSX Andreas Gustafsson 2008 SE AuthorKonamiSCC32cdc5c98915ba035865fb134e08ce5b75084099 Invasion of the Zombie Monsters MSX RELEVO Videogames 2010 ES Authorbf2452f9932cbce5986f72eb04d4c1ecec045b7f Iriegas - Illegus 99 MSX ASCII 1983 JP GoodMSX0x8000Normal95ae5b3e24ad0bea21902cb7dfd3fd9581166a76 1090b058d39dfd7bd7ea1a1f6f8fe9947a54805c 138a62b2383905e76386015d5dae67b96f9fe8ac Isin no Arashi 1242 MSX KOEI 1989 JP GoodMSXKoeiSRAM326420d61002da325403fcb6f41afb1d348eeba3b4 Issunhoushi No Donnamondai - Little Samurai 892 MSX Casio 1987 JP GoodMSX02a2fafc7b9249db76803ece347ba73be25f71b5 14451b2931a014e949a4b3d2973d424d910e0d9d[a] 88533246b479840de98c824484dceeb5f2f99366[h Angel] 617933101a195d1f52aba6444930cfe453e6e50a[h Sammi] 1dd7fe1d71354a8b2c2df315ac442b9ce3583b3cUlver crack Italian Stallion MSX Impulse9 2007 ES Author64f921c9c57aedb47663a269046d4c51388f41fbUnsafe PSG write / MSX-DEV07 Author5fe519d6beb8039813d10cd1c08e6e6a08813d2d J.E.T.P.A.C MSX Imanok 2011 ES Authorcc13bac2f50f58bc466d605839187cf09dbe269c Author0x4000Normal8bda6f24ff3818542ea11428d58eb79282a509e1Retail J.P. Winkle 759 MSX MSX Magazine 1986 JP GoodMSX2391ae8c961b4a38b3beaee9ed3362fb2c71851f 6b9f1e8ea91731570ab7e2ca6bdb9fba7937afa5 4e4989c8f6336663690217f32aca22a319b90091 9e6a78cda66fcaa9fe13aedbea7f69f2a8a97433Korean Dump Jack Exploring MSX Jordi Sala Clara 1987 ES Normal912433d680fd52bf91e9b926a98bb081ffdeb1dc Jagur 945 MSX Hudson Soft 1987 JP ASCII161aeae1180471e9a6e8e866993031b881a341f921[a3][Kanji] GoodMSXASCII166fefbe448674b6ea846d0c6b9c8a0d57a11aa410 Konami3b6200f88561a59ae5d2b3e94515b89cdb96044b[a][Kanji] Konami842009e0f7d0e977e47be7a56fe60707f477ed93[Kanji] Konami8e5b563d6cc4ad1a05f6344f1cddf1780897843c Jailbreak MSX Infinite 2010 NL AuthorKonami3e18ead4dcbeef4985a43bb5d082969e2ea424acPassion MSX2 Contest Entry AuthorKonami35183cc25784436973ab4f2667a64e21ab63b019Passion MSX2 Contest Entry Konami157426bb6935678a2c5568f5ec40691520e52d20 Janka 501 MSX UCHUDO / ASCII 1985 JP GoodMSXecc68b4a042794424df771a3336361fdb50d0875 Janyu Mah-Jong 947 MSX Tecno Soft 1987 JP GoodMSXc6227431bfbc3aa921120ec3da7f6429a1a799a0MSX1 Mode Japanese Historical Chronology 253 MSX Stratford Computer Center 1987 JP GoodMSX7013fcf8c76bc610cec9f55fbb99374182641251 Japanese MSX-Write 813 MSX ASCII 1986 JP GoodMSXASCII164180544158a57c99162269e33e4f2c77c9fce84eMSX1 Mode/Not Playable/Needs KanjiROM Japanese MSX-Write II 1175 MSX ASCII 1988 JP GoodMSXASCII8SRAM89afdee40e7b243f99cc50dd1ad710ab0928dd820Kanji ROM Needed Jawbreaker 2 MSX Magoo 2014 EU Authorae3eb3af124b36878ab4f189abfc3737bfd3d56aMSXDev14 Entry Jet Bomber 2392 MSX Aackosoft/The Bytebusters 1985 NL 0x4000Normal53c6655b0ea7c07101018879f3bf60de071c247b Jet Set Willy 497 MSX Hudson Soft 1985 JP cb352e97e9416f4c3032118758fc77d6d0f3ad73[a] GoodMSX45b5b0e9dd8d11d034e09803f92ec9720cda1e3c 1b4f56a1d6d17324e4145fb77f3291a1a4e6f42d[h] Jigsaw Set 34 MSX MIA 1983 JP GoodMSXf99919c8f42addb7f65ab4fb21b603ee7e716c90 Joe Blade 2754 MSX Players 1989 UK d358e08c3df089fb97acf94b27519b2d2d2ab44a d0711b34b407297419572929e6a04200e690f9ba Joytelop 506 MSX Victor Co. of Japan (JVC) 1984 JP GoodMSX5ceedd2ae401febb79c1f6a64ddef4b2e1790674 Jump 502 MSX Mass Tael 1985 HK GoodMSX39ef139d8d66fb18b04dff8b9a5386011f497479 633700d11fc36ccdab118dafd4f7e71e3eca2f46 Jump Coaster 176 MSX Nippon Columbia / Colpax / Universal 1984 JP 0bc97a87b42b0e9d5855dde9b88392921a9b3277[o] ab924f3b59a5aee15be02584442a5eec8ff4e908[o2] GoodMSXa53d1ea775a6c90a84f1f36349540b16cf8307e4 60a938d41398970a99a14c847521fca36d588009 c7ad9ce3ac4f1ff2d6e31fda8fa96a74eca2c8ba ea72d7339d0ad9a81fb75e66e1f5b3d9d86cdad8 Jump Land 503 MSX Nippon Columbia / Colpax / Universal 1985 JP 0b3a217d3278a43d7460136f955171c762a1f8e5 JumpinG - The tutorial game MSX Dimension Z 2011 ES Authora0643d70c853d667eb680c82b9b5f0a2322a7809Demo Version / MSXDev11 Entry Authordbea257c0f3cc4d46a94d2db30370df64eb02221English Version / MSXDev11 Entry Author74f7fba8a016e4ead892c2db5970028ed1aa4fa2Spanish Version / MSXDev11 Entry Jumping Rabbit 175 MSX MIA 1984 JP b5cdef44b22c7e234895b053e487233c117d1a4f Jungle Hunt MSX Atari / Taito 2008 JP Authorcfa30c1d6741e1b56b54622bbc3d89003f739a17Muffie conversion Author0x4000Normalc9a89cceb355e5daf47ed5e8ee3e211ebd2494d8Slotman conversion Author4e09d61162c3dd957d59ece0aa5d7222294b5c5amuffie conversion (beta) junior high school english grammar 217 MSX Stratford Computer Center 1986 JP GoodMSX20ce189d670c0fe5f2a945d73107df7d83070ff7 Juno First 177 MSX Konami 1983 JP a70c6b0e90e7b8ba3e5bc06c9ae7d6af344e491f 14de1be5de19a27919d80cdad2ceb0554a48ff02[o] c5b4fdca8887a8aba203b311c5519f8e4863d910[o2] 4ad170c5dfadc6a811cd5b8dc4c99be656ceff56[o3] 6ebe5e2ed0fd84091ef1a32e44c1353f049ac085 GoodMSX846bd4d89bd034ac5346cbc559752635ad2bda6b 4a92ec1be15c45ee98c08ac96191a369d562181b a54c4244526051d887f9d74a767c254c08d47ad4 Jyan Friend 504 MSX TAITO 1984 JP GoodMSX4991dc097e248436506ed5c8bc3f367a65267ee7 Jyansei 763 MSX Sony 1986 JP GoodMSXASCII160eb3a48a8ab90826726f0c06daadf4bf13390758 GoodMSXASCII16d78293133b075f48c9dc5c4cf0c4a985ae964b08[a1] Kageno Densetsu - The Legend Of Kage 729 MSX TAITO 1986 JP 666e82d8a8e5d2672a9b7159adcdc861a31c8fa1[a] 3d78b0345fc88d7600192f2c268c6646fda6ae1a[b] GoodMSXffc8c57b26f2f495bd77efac5b815efe9c25336c 76a67138d445557e0c079424acdff3c60423d8ec[h Prosoft] Kakikukekon 413 MSX Casio 1985 JP GoodMSX632ef250c76b222158ba90dfc68b9e004dbafe3a Karamaru 423 MSX HAL Laboratory 1984 JP GoodMSX0x8000Normal223337db25c942ece732214aba5dd624b574e9c4 Karoshi Demo MSX Karoshi Corporation 2002 ES 2d3e86ab553c3ac7c97b8f210207f2c8578656b5 Karoshi Game Collection MSX Karoshi Corporation 2004 ES Author5c61a12d490d5e1d97da5e74ac40311e9725ca20 Keiba, The 753 MSX Champion Soft 1986 JP GoodMSX09779f0e9ec0c36ee3700690e2b6e282a4b40a0b Kenpelen Chess 1106 MSX Pony Canyon 1988 JP GoodMSXASCII16a0c486d0f25a3dc586717d5d3e1f5a46d36735fc Keyboard Master 2874 MSX Konami 1984 JP Keyboardmaster2308dd0f15fb5395a1d8a08489daff65ead8e0f2the _voice ROM should always be with the ROM image 0x4000Normal4f36d139ee4baa7d5980f765de9895570ee05f40the _voice ROM should always be with the ROM image Keystone Kapers 133 MSX Activision 1984 US 3d5160331beb1c5cc54ba6ecef6b3ce2ff4660b6 GoodMSXfb4724b8159beae132f89f394f71ce3934a61ae2 05f0880c34b505d095142423d59ef1ffdac35b5e Kick It 926 MSX Aackosoft/The Bytebusters 1986 NL f033ab8a33e2d7a8ac1313b862bc138138b4346e GoodMSX65334123c7d00ca623bf5f7a55756cc75958debb 6a16b2f4efd5ccc4479ba20d9480edbbe64fc741 Kikikaikai - Mystery 924 MSX TAITO 1987 JP GoodMSXASCII86dd28797f9d1fe7cc041077ebe77d08f2ccc0937 ASCII8dcc1ecdd0e3a2a33b994e472ea367a1637cc3799cheat version ASCII838a103e540eb5fbe2627fab2991bda08092635e0[b] GenericKonami6d3008b97b48d99522e45056f52ed944e3d8e208 GenericKonami11f1b9fda9a44f59a6de479aebc18fbc6347f5a7Ulver crack KillMice MSX AG Software 2011 IT AuthorKonamiSCC5bf2b21a9b97e744cef71f2f6e9cacbbf695f3f6MSXDev11 Entry Kinasai MSX Unknown 1984 f772396becdbd15ba0648bf1aef8ccb155e1adad 4732da69df21dabf958ae181ef21f38c6f173881 King & Balloon 147 MSX NAMCO 1984 JP GoodMSXed15174dec31a29f9d41a06de3008db04af11a25 GoodMSXbec70fbf9b1384b6a8adc45226689f02d996b6a7[a1] d2c5497a52f5b8ababfc24fff1573622f86a69c6 15c82849dfc691a9cc39f874ab48243523ea6b9c c1cd04c3ff667d12764fff9e150b863e94b9bb20 KonamiSCC3aaa762a16a942bc45e884a428123eecd1fa6a85GDX SCC Version King Kong 2 929 MSX Konami 1986 JP ASCII899330820cf8aef8bb34d56a666accb4b1ca90e30[RC-745] translatedASCII8ecb538238fc7c5e374d723f4d6120d1b2ef5d80e[RC-745] / English Translation GoodMSXKonamife4bd35e87dd50fddd255172129b9bec38691188[RC-745] Konamic6d2aa3e7303e0e2244256bfcfc113bafe2d6b59[RC-745] translatedKonami2b4c386a56dddd2a3354bdf1a0041d3faa0ccc72[RC-745] / English Translation translatedKonamie178917d1974ac85fd58cb93b936eb9a6f44ba0d[RC-745] / English Translation translatedKonami502d7cf7bc2595af6f83bb2b95baa4db5b0f6a4d[RC-745] / English Translation \ cheat version translatedKonamide2349227d765db73cdc44efc6338f6b530eb133[RC-745] / English Translation \ Ulver crack Konamia5963fa6c95fbe34482862b0f7221b7766e3a194[RC-745] translatedKonamibd9852ea114e8b8ff781115d1d6e6da6f8dfafff[RC-745] / English Translation Konami65a5bda34d1e1b22cfa697e4e7fdf1829cf2238d[RC-745] translatedKonamiec9136f01292ca230dd10029301fe6f8df68eac6[RC-745] / English Translation translatedKonami206a9a6dce2938d149b30cefb661c938419260ed[RC-745] / Espaniol Ultimate Translation translatedKonami78788073af4a1c2921fb18df7175e44c16d513d6[RC-745] / English Ultimate Translation Konami7a4c46676fed273da663b7882f53faa4669e5f8f Konamie21af5c17e5bd871f8b31f4e668304ffbd4b133a Konamiad00ab5a9480a5effde5caa966c0ea8d1b539f73 Konami3be98fdd3c459d53d2f611315e7743288e9f8072 Konami3adc94a0bdec7d9166983c4bb44ecf136de2ecda King's Knight 740 MSX Square 1986 JP GoodMSXASCII16122f659250a0ae10ce0be0dde626dd3e384affa7 ASCII16a2ca7e6e216f8b450eb8db10a4120f0353275b6b[a3] ASCII1646062d3393c49884f84c2dc437ff27854e9d2e49 Konami438bbb3367db4938b3d90fa9d2cfb1f08c072bb7[a] MSXDOS23af846020be32c20d705c4fbfb6f51d24a094be3 King's Valley 1 406 MSX Konami 1985 JP 43dc3911b9a336bb7b17bdbe6e48464e01f2c425[a2][RC-727] 8c9a9a9afe13a58e4cfb72489af2d6b080dffaf6[a4][RC-727] a4383f789482a9e868a9f136aa722129673c2a81[RC-727] GoodMSX0cf26bf02c5b94df118129a681a7f14add708177[a1][RC-727] GoodMSX07448ffb20f260df159a19df2cae1ee5545a0aef[RC-727] de4e210e6f757efc45e66be66b14cd6ec21b2ba7[RC-727] 0f74234adda250d72d4147872cb421a3b6f26efa[RC-727] / Ulver crack 3c27d2873203c6b634543737c1b5f27d0e0334a1 ef93082ea458824b8b2c7b967aedc529c3eb3d39 f063027d9e85c4b324d36c7716a72e4a7f68e7cc King's Valley 2 - The Seal Of El Giza - MSX1 Version 1078 MSX Konami 1988 JP ASCII8f3306e6f25d111da21ce66db3404f5f48acb25a1[RC-760] / Needs SCC in slot 2 GoodMSXKonamiSCCcfd872d005b7bd4cdd6e06c4c0162191f0b0415d[RC-760] KonamiSCCcb612cbfdf79dabe55164b386ca956a3e187bef7[RC-760] / FRS patch King's Valley 2 - The Seal Of El Giza - MSX2 Edit Contest Version 1848 MSX Konami 1988 JP GoodMSXKonamiSCC2e0436303648097e8e936c5467574aff5e3f1517[RC-761] translatedKonamiSCC376b4785cab7d60cc083f0c03bd5e8ea2fee8205[RC-761] / FRS patch King's Valley 2 - The Seal Of El Giza - MSX2 Version 1079 MSX Konami 1988 JP ASCII8f25628e474035e6bf0440b274c5df04f94c49fb8[RC-761] / Needs SCC in slot 2 GoodMSXKonamiSCC5ec8811254dc762c6852289a08acf22954404f0d[RC-761] KonamiSCCb5acea41aeabb5daa4183953f800d47db0c39fb5[RC-761] / Ulver crack KonamiSCC89d96d4b92d9a1c220cae0240cf0c00f310d14aa[RC-761] / FRS patch KonamiSCC22704b82d159d963f1192d7a81810aa2be8b6042 Kinnikuman - Muscle Man 443 MSX BANDAI 1985 JP dd76ee1071e427cbfedeeedcb3a4b8bcad56ba71[a][Kanji] GoodMSXbf7e27b3456f1a96d94a8dbf06688c8f833bd818 9da5878205d58fbcc641c5f10da1aa2dc450ec97 7f16f43b2197f9bcc65c564c1485034797abacb6[a] Kisei 925 MSX Sony 1987 JP GoodMSXASCII8SRAM22e2543ac99193f76b4f35463142294c016f0cf3d Knight Lore 810 MSX Ultimate Play The Game 1986 UK GoodMSXe98e4351119d78b6402c4499ed16faeb3364f6c3 2d8a35b7c5142d33bb54c066e35fee4983330dd9 314dfcde3708984a4bcbee21c8f767952bcfda80 Knight Tyme 2732 MSX Mastertronic 1986 UK ASCII1664321138e03337d0e6178ac30bb98e6623c640ba Knight'08 MSX Jos'b 2008 ES AuthorKonamiSCC0f298581b85e838c1fbaa24273d292217f81662c Knightlore Remake MSX Manuel Pazos & LordFred 2009 ES Authord4a188a7ee514c18dc65f3c6dea7668b6cedc374 Knightmare III - Shalom 946 MSX Konami 1987 JP ASCII8240e15fb5d918aa821f226002772dc800d9f20a4[RC-754] translatedASCII81ff0afe393f89b6517025dc39a282c11c7da87ca[RC-754] / English Translation [B4] GoodMSXKonami25f5adeca8a2ddb754d25eb18cff0d84e5b003bc[RC-754] translatedKonami3b6f140c99cba5bfa510200df49d1e573d687e4d[RC-754] / English Translation translatedKonamid0dfc1927461d9473d0ed471375dcedddba42279[RC-754] / English translation [B4] translatedKonami4323eb37b9a795be32a2b64824fc4acd7afe4e9d[RC-754] / Translation into English ASCII8f999e0187023413d839a67337d0595e150b2398f[RC-754] Konami8c609b8bee4245a1bb81e37d888ac5efb66533cf[RC-754] ASCII8d33508c89656b18063893cadb74573e0b656792d[RC-754] translatedKonami828a38535cb6246469a65f429834db8ab469d914[RC-754] / English Translation [B4] translatedASCII887a6681c98a933d54d043bf603ba137f866696a8[RC-754] / English Translation [B4][disk fix] Konamie4e5c2202f8503dca4f2fa7e8eb4eccac3e38d94 Knither Special 982 MSX Radio Wave Newspaper Publisher Company 1987 JP GoodMSXASCII1663d4e39c59f24f880809caa534d7a46ae83f4c9f Knuckle Joe 3568 MSX Prosoft 1989 KR 0x4000Normal6147f63a319b44b60259b35a75b1c083280b92ea Kobashi MSX Desire In Envy 2004 NL e0854e9322c113ecbccf61f9de1c0a59bda03d23 0d2ba1f2033ae0069236d9ebc5411130f159d5cd Koedoli 3544 MSX Mickey Soft 1988 KR 076f4b6d3f6d825b8b8d3fb2bc87213421a42210 6895a062e10b790e19a55952e634662f3135fa55Ulver crack Konami Game Collection - Volume #1 1107 MSX Konami 1988 JP KonamiSCC728340e95ad58ab989a59f61317a45ab1f6b0bdc[h Thevma] KonamiSCCd2d5c5a0731278d9e0a35997eaa64317636ab832 Konami Game Collection - Volume #2 1108 MSX Konami 1988 JP GenericKonamiee533b8e4bdd6cdf2c273f7c80753cf8db60f545[h Thevma] KonamiSCC1674e0d766d47579a8c07d5bc98cabd7b5093043 Konami Game Collection - Volume #3 1109 MSX Konami 1988 JP 51b24ab4846758a8b6dc6b15e21b0baa70c8a650 58132c6ff44095b1598f267b2bdca432d3178ec9 KonamiSCCb4c475c91e82390a1d21480a0d70fc4b0c48f49d Konami Game Collection - Volume #4 1110 MSX Konami 1988 JP KonamiSCCf2eef7402d2e6e7f7c15a1f3d8bd251603eabf12[h Thevma] KonamiSCC287d196973d5c5f53562e6573c20c66304c0e83f Konami Game Collection Special 1376 MSX Konami 1989 JP KonamiSCCf90064fe4ffed170f9e9c79196aa0b17e0bbd86b[h Thevma] Konami's Baseball 476 MSX Konami 1984 JP GoodMSX076d6f5b9f8427f6959f7a255abb4acac0dd9d0c[RC-724] 9bdfe44a7a0d5151346ff14ece3216b5108cc775[RC-724] da6a332dc236073286aa78582c0fccf728c86ba4 ddfac0f89d4e0337f4c47d680422aaca458d6a45 Konami's Boxing 477 MSX Konami 1985 JP GoodMSX7adb6812f3702b93aa2b2a872722bbd40915670d[RC-736] e3ac6a3ab223f5440bca5ea696e99669dd1f626d[RC-736] 9e190cf3ca52d85fd34eb5df2c0dd8490448b9a2[a2][RC-736] 5e349166fce12d45f3ce3f69ecd6b2a70ef4d68b[RC-736] 20efab45afd6be509ab4f8a012e67b094c196c73[RC-736] / Ulver crack aa95e2c196a59b67bede35366275a48557b1ae06[RC-736] / Ulver crack KonamiSCC4e3a48a1acddff212807f80a676910851e4aca02[SCC][RC-736] scc+7768195d49ad389d388468c4f24a53a5f53e3c5d[SD Snatcher RAM SCC+][RC-736] scc+ae24f03a969bfc7394b9620c8f227ae97627a7ce[The Snatcher RAM SCC+][RC-736] 4569c02e59fb9d7a42d6a0296d821bc7ae7e338dKonami Antiques MSX Collection 1 Konami's Game Master 470 MSX Konami 1985 JP GoodMSX9648d43f3c25d67b9e4972f777d7b02470080d1c[RC-735] / Japanese version GoodMSXabce0b855d67ac10bc667e0f23ceab4a12ffeee6[a1][RC-735] / Japanese version 70ba2331844bd5c14906e9f2284eba119b3e41bf[a][RC-735] / European version 4fe90868376704336fbf45619a47ce18736ff1b2[RC-735] / European version e974e35ace29f6500aa3231c966a7b5959fb5fba Konami's Game Master 2 937 MSX Konami 1987 JP ASCII8SRAM8f02281aa13cb34d2f10add301529b95adff7f768[RC-755] GameMaster2fe74b4df9698a61dffd3ac88f47619675514ba1c[RC-755] / [a] GoodMSXGameMaster25c00f9e3a48411e08fac89b1dfdd2a6e95853919[RC-755] / MSX1 Mode ASCII80099492bd11dfa3ddf87af3aeb4c92b29f78bd82[RC-755] Konami's Golf 471 MSX Konami 1985 JP GoodMSX6a3a68836a468cc761a268175c64d53c4c1c48bd[RC-723] ebf474cf35a56e03771d0b17548b59feb2df0656[RC-723] GoodMSXbcf72a4673c7982361db4efaf8ebc776b6539938[a1][RC-723] 5c7971aa8688ffc429a3e315dd3d45c38f72ddeb[RC-723] d9634296511c599852aff0c6b9993509bcbc3bef[RC-723] 1fb821d2007b28fd6a05974454f5bf8767b106acKonami Antiques MSX Collection 2 8db04faa22e5366d1903abb0728e41adc981f99a 612dc0c710ff07586237ebc4d9244f785f32677b Konami's Mahjong Dojo 155 MSX Konami 1984 JP GoodMSXf415e4096ff3f6a9f17bfbf02f146e44f0d5fb66[RC-707] c23c342ff5274356a2d17d18272253d14fd8ffdf[RC-707] Konami's Ping-Pong 474 MSX Konami 1985 JP GoodMSXae5b8e69732a23aee102bcd996ad06225f1b3eba[RC-731] 3cce9cdf016792e531017b873e13eeb1e7c85013[RC-731] GoodMSX9f4ac1287b8caa286a847c86ac8601a711be2d82[a1][RC-731] 6c68f7be1eb46cca6dcdbb9192cbff9d59604357[a][RC-731] 5e1a50affe1fa973f69e423a8827751b93fc53ee[RC-731] 0ff688433d7ad075fb22ac92ac8f1b883c7a9ae4Konami Antiques MSX Collection 1 90592a09228b3e3300eed51ebd7358b5ed2afa09 376e8b54a560fbd0fbb4807c6262cf7171377d9c 78276a590e98c571fc1dafd782ca90b1462636c0 987ff2c5d3f125cd7ec6fe2ffbf84e006562c9d2 Konami's Soccer 472 MSX Konami 1985 JP eaa6b787784bae1c7f648b57daa1c88cf2b1b095[a][RC-732] 10698ea6b0423f2e858a4c28c10f39646ca5495f[a2][RC-732] 8e9fb34b356b02a839dce5139c12030593be13a2[RC-732] GoodMSXfd7518a6aedb51630364082c27bdfe4411adfd4c[RC-732] GoodMSX89ccb1a7ffaaa75cee9bdc9b4a9ec9b063af7199[a1][RC-732] a0ed2c50e55241d23bb5d12cb6528a2a0c3bc156[a2][RC-732] 79c1bf77c08b733ae04e069deaf25fd24b28beb7[RC-732] 34019d5bc3687ec1255fd7ad7571830a67241ddd[RC-732] / Konami Antiques MSX Collection 3 5bef9572d71848966222819c3a1dd954994a4028[RC-732] / enhancement patch Konami's Synthesizer 768 MSX Konami 1986 JP GoodMSXSynthesizer2feff37d593683ce1c7dfac33ed3207895e01a03[RC-741] Synthesizer6a7faa122454ba34cbdb9f1a75a111a1a4763a56[RC-741][b] Synthesizerf25c416869372ac07abefad131fc732dfbe33a45[RC-741] / Sample Version Synthesizer73a40e7d6da2bc8489297fd57b7b93b2aa5a6440[RC-741] Konami's Tennis 473 MSX Konami 1984 JP GoodMSX5f6972e9e374753faf092409f1ee10bba5231b66[RC-720] 27dc3ae8a5d6bef8532c9c210a44bbcbaad436d5[RC-720] GoodMSXd5f5d2850b0bfda45ba4dfff70d3fd9986399a94[a1][RC-720] 54008fe7cd7f1a47fca70462a0a7b5682146099b[h Prosoft][RC-720] 608a3000428ccfd4c53d7c71b5514de721169fa6 11e69bb4bad230382688a38e76733c665019435c fbaebd9281a0fc97aead473f66fa72b905a60b42 543d1a20f92e78758d76eba2a2fd21748b290c4e 0916f5616008e21fd646bf601c21365003ae4167 Koneko no Daibouken - Catboy 751 MSX Casio 1986 JP befa36ce537fefc09a71de4bb7fc69ce0d6575fe[b] GoodMSX0039cff292fe740d8c87cc2131a473cbb4f8c4f4 000fbc401c7558d9c54a401c7875daa72ab76920Ulver crack Koronis Rift 938 MSX Activision / Pony Canyon 1987 JP GoodMSXASCII16523330d609ca0e527f3e28a078635b3d28489ec9 Koufi 1934 MSX Al Alamiah 1984 KW 7a0ddc5f086a6bc7e8e7f7b1840988623a2cc1b5 Kralizec Battle Tetris MSX Kralizec 2004 ES Author9776da8b475d81400e027e37d949ccffbb33fe30 Kralizec Tetris 3448 MSX Kralizec 2004 ES Author7056a2bea5013894930279f8c09cc77633964778 Author864824c2ef01945b7fccb3ec3399347b714d6aac Kralizec Tetris 2 MSX Kralizec 2005 ES 1fbf5640df41fe48c7d64ffc51e33b57b41fd7e1 Kung-Fu Master 151 MSX Mass Tael 1983 HK 05da84093d973d3bfbd3143ffd155a16a9ec5f82 1ff13030d171fb5f653664e5d11d0777884b2463 602c4d0d12fd59725c0c4e40f51d6bdeb679ea36[a] GoodMSXc1dfc86c441e1128194f3ab490c94e29275b5df9 Kung-Fu Taigun 434 MSX Seibu Kaihatsu 1984 JP 304ea29469461f1cfa801f62af0bf5045a6882fc 538f4c8567bf5caff65dc034da6cb89f8beeab62[cr Claus] GoodMSXe817ab7b50674c414b1c1b72530024eb03396a41 2dd728d7de1f83c1b60cdf480f1fc6b497bd3cd9 8d5fe498a25420de9f428265ebbb1fc2a6c0f2ca[h Boram] 4ac6a17c9c17bf6e9b149b727663496bf1bf35b0 7fe4ad9bdda1575a4a56ff0a0417e5a75d8473fa 36aa652c5d1fb937c5d346cf872baa78198c2f60 Normalf80e7585ca1e8f018cce69803f55af4da01056b7 Kyotoryu no Tera Satsujin Jiken 1097 MSX TAITO 1988 JP GoodMSXASCII8f9486dd810d6321bad4bc9d7703a67d630167f98 L'Empereur 1398 MSX KOEI 1990 JP GoodMSXKoeiSRAM3222f5998b4de47bf2ccc077d5881ac448e6b0484d La Corona Encatada MSX Karoshi Corporation 2009 ES Authorb5bb76d666dda2c3ad93f2ece44be72f5d0f61e3Final Version Author97b8ef65d86ec425d5901fce80d33a3d731e50e1English Version / MSX-DEV08 Authord76a904dd88f7360fe79000c374f40a69b801325English Version / MSX-DEV08 (v2) Author03ae0eebbbf80f8e51c5892a74aa2b4f287a9319Spanish Version / MSX-DEV08 (v2) Author761071622b60384ec38e964fa2b2ee12d3d0b2c9Spanish Version / MSX-DEV08 75a59677275602e9a2b314fc077411b23754308fExtended Version Labyrinth 1033 MSX Pack-In-Video 1987 JP GoodMSXASCII8e003d57d2102e8fdf5d9eea7bd78a3cf67d75778 ASCII851e4231e245ffd733590e3205fa3ffea92ad509f Konami13c59dd8f7320856b15c834038af3c846db33e4e ASCII868158290666f4d7ac1ada40d674a1160c162b193 ASCII8d57fc4121d716182a091bb53c666b46c10142668 Ladder Building 354 MSX ASCII 1984 JP GoodMSX0x8000Normal15ca962994fd25a56bee2f543a83f133f9d2265f Lady Bug MSX Coleco 1982 US KonamiSCCee29322b695f27ec528f99811e069227635d0816GDX Conversion KonamiSCC4e218a6f2aca60d82a22f0a81e81f1619c16c91bGDX Conversion Lander MSX German Gomez Herrera 2008 ES AuthorKonamiSCC720b629e2e22e0af15b482b154a1473e8ab6627c Landmarks MSX Al Alamiah 1990 KW ASCII80a7bed652592ed6325acd037f1f5bbbd1d31b239 Laptick 2 875 MSX dB-SOFT 1986 JP GoodMSXc4f952258020de9e23a718a78c18d22e2cccfe3c 25db27ebb67eec8fe15e3e51947c840d23944533[cr Flash] 6584c84307e5dbb7366068283c3c332aaf52b7a4 Le Mans 2 2064 MSX Electric Software 1985 UK f69541b86c2b396cdce765c2dec498e11232b3c9 Learn Sakhr Basic 1935 MSX Al Alamiah 1986 KW bf3d9433bf32d3af7a7adc0ba77b9690ac754daa Legendly Knight 3157 MSX Topia 1988 KR ASCII82cea9cdd501d77f2b6bd78ae2ae9a63aba64cfed[a] Konami6a98d5787dd0e76f04283ce0aec55d45ba81565d Konami598eb258cbe532e98b4a3c8d87ecd9536d09ce56Ulver crack Leila K MSX DVIK & Joyrex productions 2008 US AuthorKonamiSCCe4401a78869ffa879a55f239e5da4990cb6797a6Sound Demo - Please run on msx2 for good graphics Leonard 3224 MSX Mind Games España 1986 ES 3103f0170bae627bdeb77108e0e782284e4581c9Mindgames Espana S.A. Let's Count 1 1936 MSX Al Alamiah 1989 KW 08eb5001bd95ffb7232cdbdffbec7634eb110ad8 Let's Count 3 1937 MSX Al Alamiah 1990 KW 73938f51fcb26349aa17e1ae1f1bd43b20ef227b Libreway MSX Paulo Silva 2014 EU Authorbe95c417ac1500e6127c57d0adedce6a99117b0eMSXDev14 Entry Limaribu MSX Arnold Metselaar 2009 NL Author6baf75aa6024cb1311fc1171cce4560e16de6d9aMSX-DEV08 Author2b9ab44526a19706de76ff374459cccae3e65c84 Livingstone Supongo 2 2255 MSX Opera Soft 1989 ES ASCII16db1513dff6dd456fc973286816129ace81bcdfdd Lode Run MSX AG Software 2005 IT AuthorNormalea774f43f9c02064fcc07d5131691f5bd4e9c4f5MSX-DEV05 Lode Runner 359 MSX Doug Smith 1983 GB 0421a25aff3ae300b2a4536dd0aa5fa924e7dd72[a] GoodMSX818a6cedbfdf3a5af0c844f32a18e3930178c798 a7c36fe19bb4559e8cc38ca1fae1625ee14b5625 Lode Runner 2 685 MSX Doug Smith 1984 GB GoodMSX4b04574b3b71958664ac730987c4c8ebedeba890 d92c07dd1fa817cf07a36bccd03b3bce5affd325 Looping MSX Coleco 1982 US KonamiSCC1a1c993617ee78333a63d9d695b8469ba3b7e894GDX Conversion KonamiSCC1bfdc271b5fd267c1b7c4af623bbc16990689700GDX Conversion Lord Over 358 MSX ASCII 1983 JP GoodMSX0x8000Normal4025045e93f51afd58545104cc2c897226febaf7 Los Jardines de Zee Wang Zu MSX Hability game 2006 ES Author0x4000Normalba6bff43e5773c6777f73ebc63b4f38466dc2630MSX-DEV06 AuthorKonamiSCC46611b1ab3b8ed272d989fe3b209ebe29a866718MSX-DEV06 Lot Lot 879 MSX Technopolis Soft 1986 JP GoodMSX3c2db6321ee84e3690757c08ade9dc096bd209b3 Lotus F3 MSX DVIK & Joyrex productions 2007 US Authormanbow20f2c5a7c6cc9ff591c911e4f3f204e425bd3cf50MSX-DEV07 Authormanbow2079bacc9708f711af90310f3e08f2abafa8fc919Fixed / MSX-DEV07 Authormanbow2e348e62e3c4c02d87ff2aa4d55ddebb60c3ea507Final Submission for MSXDEV07 / MSX-DEV07 Authormanbow2145552b3f62885efcfe7484008fa4cebe2feae84After MSXDEV07 Version / MSX-DEV07 Lunar Ball 679 MSX Compile 1985 JP ASCII86ce808d69e9198a0343e9fd06744deb6f9e59419[b] GoodMSX95f75fae9b3e961149e352cef3d7e478e9887d5a M-Tanks MSX ASMSoft 2012 ES AuthorKonami93f1783d04aa0511130afed8cfd2b4417fe86b3eMSXDev11 Entry M.A.Z.E MSX DVIK & Joyrex productions 2008 US Authore738a8e4e09b508167b552e9e4350c041ca68c202kbos entry Author763a0c9e1f2f847d79e60da4c6251ce0ad80cb70 Mac Attack 2394 MSX The Bytebusters 1986 NL 0949bd31cdbdfacb3224506c72a2210c50432157 Machinegun Joe Vs The Mafia 148 MSX Hudson Soft / Japanese Softbank 1983 JP 97dd31b2f0dd056abd808062e306b0c2536975bc Macross 653 MSX Bothtec 1985 JP GoodMSX09af3b68be4eea5f5594d5d2d124d92b127dd390 81ddaf3a42c1e1a3d3722e0cd046316a202acd33 cee2b3520404f1eb6eea6bc14ae9971385920514 Mad Rider 1021 MSX Carry Lab 1987 JP GoodMSXASCII84c44939a95a20d701f60830499254e5dabedeeaa ASCII8a53feafa4c70d2378998f0dcb837905e5070c541 Magic Touch 1938 MSX Al Alamiah 1985 KW b9d5ec448d5de19c62dc09ffe3b257e0cef610d2 951a5310534c16a6923eedf28278c13102b6a19f Magic Tune 1939 MSX Al Alamiah 1985 KW 44ece0064495f7c90344e42fc14e29341d4e08bb abf5521759369518dd66390491ac0601c28e25bc Magic Writer 1940 MSX Al Alamiah 1985 KW 48518d95d06a7761c4afe5235ef995bdc53c1952 Magical Kid Wiz 856 MSX Sony 1986 JP 3e2ef016f255168e2df1906e678ca22f880917b8 a3d1212eb4c58958f063aa8577ddfa07903b28b3[a] GoodMSX087afebb16bfd96f465fb0d004326d55452bdd81 71765b3095f4b9f2f04e553402e7406191506a8bUlver crack d8561d9943b21c0298b80f9baef30872b479c2a7 Magical Stones MSX Dioniso 2005 ES AuthorNormalcb0d355627b8c036931a67666e425d8f9225a18016Kb version / MSX-DEV05 AuthorNormal38a3c6659080b38d9a2655e869e9fc0ce3e26c5e8Kb Version / MSX-DEV05 Magical Tree 655 MSX Konami 1984 JP GoodMSXb82dc590aadc930b43db303bb3e3458e7695e3b5[RC-713] f4b58fecbcbc8cd307c34806ecece2a204c26c65[RC-713] 4ef4cd14a70e5eef213f8d70877dd394cb1999b1[RC-713] / [h Magicalsoft] bf5678f3c065a4507f71ed6206a63ac922ad93dd[RC-713] dceffda922852fd22e1a566b9cb3106d82db5847[RC-713] / Ulver crack 539bcdafc48a34704b17f04682b6284dc3da257c[RC-713] KonamiSCCb7d8bdfb3dea0d595e35c593b80104f834af7dae[RC-713] / GDX SCC Version Magnetics 1941 MSX Al Alamiah 1986 KW 4998d34b559f4a531843cebd2302212658d953f7 eb374a5e1d3209fa5da24a8fbbe9c4839dd02679 Magunam - Kiki Ippatsu - Magnum Prohibition 1931 1218 MSX Toshiba-EMI 1988 JP ASCII169c5c1c40ec30c34b1b436cf8cc494c0b509e81fc[a] GoodMSXASCII163243a5cc562451de527917e9ca85657286b2852f MSXDOS2510e1fa5cdd845082c1881d83ed8cf7c4ae70a3c Mah-Jong Crazy 174 MSX Hudson Soft / Japanese Softbank 1984 JP GoodMSX17e1111b1d6aa80e8bc525be345c25e3b00ed504 Maison Ikkoku 1027 MSX Microcabin 1987 JP GoodMSXASCII8ee501fde62b25ddd1c82798ee1b233b8813e717b Konami4fe1d66e8f3cbd5ecd9b22b6aea2b37a604f2492 Maison Ikkoku Final 1225 MSX Microcabin 1988 JP GoodMSXASCII855fb4e3d3a762b613b635ee9745d92f0f3711272 Majikazo 3449 MSX Lemonize 2006 ES Author06cc2eca2930d67f23568ccf77a894f475f27efe AuthorNormal0c52d589f0c8e7e778a61d7f4561f37316a5fa63 Author061aa9a046ba457a3743482a9a1af314c8019913MultiROM version Majyo Densetsu - Knightmare 855 MSX Konami 1986 JP GoodMSXc8ff858d239c62a859f15c2f1bf44e1d657cec13[RC-739] da8249452da86c0ba44f0ae279ccff82a1ddd756[RC-739] / [a] e568e3455c14cab8992b58c2849a745ef5b19b21[RC-739] / [b] 1d6a355a9ce84c78e3bafc27a4f1151e240f68f0[RC-739] c484f44af0ab1bcaf46ae5f14a3b38a53ee77f31[RC-739] 8a6e0f932ce12318a1c5b8dbbfe44b213acb03ae[RC-739] / Ulver crack KonamiSCCde7b6525f9aafe8ef77d25a799f0f8290bbc8f2e[RC-739] / SCC enhanced version KonamiSCC19a95d8e0fd5cb61ed02e0641ed15454f0b9de42[RC-739] / [SCC] scc+f795668afe66c7d88e51f5a22471e2c0a42c7ffb[RC-739] / [SD Snatcher RAM SCC+] 6448aa090d95ab42e8a63f52cd12e40c39521aa0[RC-739] / Konami Antiques MSX Collection 2 7435a9caec4cdefe398299ba9b6adf89c302fda5[RC-739] e234060800fdcca41eb3f7019edd21c28c9ce223[RC-739] KonamiSCCdf203139ad0cd2357f3125417a4157e701dbacf6[RC-739] / SCC Enhanced f2a4b562aa40eb09bfac436af20a31d04a4d1823 Makaijima - Higemaru 1017 MSX Capcom 1987 JP GoodMSXASCII16de0eb1c46c47924879e2a49f9eafba0ab344fe5f translatedASCII16a828bbe7386ed9184334ed645b6c46b3501b4872Django translation MSXDOS2c9d6dea684b0bdb03e5f0762c3e92296a3e38023 Malaika MSX RELEVO Videogames 2013 ES Author5d379dd826d0a8870d72ce5bef0afe2b04ba212aRLV914 / MSXDev13 Entry Author526ae4d65717df1ec2440f1d38149d325cbb6bccRLV914 R1 / MSXDev13 Entry Malaika - Prehistoric Quest MSX Karoshi Corporation 2006 ES Author480fe052ff41f6f6aaa745c413012d1610f02c90Version 1.1 / MSX-DEV06 Authore94d34d164f5379ea85464b51743461e3f6c92abMSX-DEV06 Authore6a0d6c40419647e6b17d30a000331a904c8f5dfupdated version / MSX-DEV06 Authord7bd6e84edcaf6d99abaf6597813501926bda398MSX-DEV06 Malaya no Hihou 1261 MSX Pony Canyon 1988 JP GoodMSXASCII16e6c01bd90cf0b2793282fc53e9a3150a6d7804c5 ASCII16c5e3845c74da6aea2cb68ccd765e4f606f10b5b6 translatedASCII16e037d30f2ee96375a35885d3f9ac09cc8bab60f1Translation (Trained) / MSX Translations translatedASCII1668dad7093530ba2b78b9c059e469831416e44576Translation / MSX Translations Manbow 2 3607 MSX Manbow 2 Team 2007 NL Authormanbow2e3b9241291b5f19ab239726360258a31e02565a3SCC flash cart AuthorKonamiSCC3bfbf89dfe4f896a581b1d130afb57ce722bdf0d Authormanbow200a81872d548a7278b8901608bf12e8f79009beeSCC flash cart Authormanbow276682263b35e846abca6facf6473ce33f1463794SCC flash cart AuthorManbow2_2c2a2663302c845468dddfe7f4eee8823b7d4bdd6SCC flash cart Manbow2_20139bf0c3c9d31b9954587d55d927b7d0955cdd0 Manes 349 MSX ZAP 1984 JP GoodMSX47129fe3791a7171f8fa792f953d7b5d58ba5c24[a1] GoodMSX2729bb0e68a64af525df4c5d9687bae07245980a 820174b4dd3f3df87e5e32fb6f7ed457542d96c1 Manicomio MSX Crappysoft 2005 ES Author386cf567b7b73105d7f603463e1fe2bd919c3d0dMSX-DEV05 Mappy 329 MSX NAMCO 1984 JP 20ce82112ba0c50063667d61e43979ac464a3bb4 GoodMSXe7d06c0a5c7f256c061e5b8173fdcc145d2fc4d6 GoodMSXa8313b0dce35faa80b399a220f19b04333fdec1d[a1] 43ec23bbdb3af0f5dbc6d17ea0b8d745e8833c86 028eb65719ea49cc97ceb6bc3143df87c10078e4 KonamiSCC7df9d6786925a7218fd6808b1cad5e21f3a5a2afGDX SCC Version Marchen Veil 1029 MSX System Sacom 1986 JP GoodMSXASCII8f7dd6841d280cbffa9f0f2da7af3549f23270ddb Marine Battle 74 MSX ASCII 1983 JP GoodMSX0x8000Normala66e19e58ac5b4da7db071ca7ead40c28a332198 Mars II MSX Nagi-p Soft 1990 JP a3772599cdb84df71ef2bb38b1e2cb2f66593969 Mars Lander MSX Crappysoft 2006 ES AuthorNormal2644b85251b03f64fc5c56613d5cd07d9bcd345d Mashou no Yakata Gabalin - Goblin 1020 MSX Pony Canyon 1987 JP GoodMSXe4d61ee74867066b04b0f6aba35d98d72665e6e0 8f92ea214a7a11394c512080d3b65a7afa9a92ed 57a39f6947d1f87107961cd57ee6a52027d5c612 translateda0396786630e4a56206531a2ac9ddc2456ab71d3Translated into English by hap and Zan Maxixdados MSX Cibertron 19xx BR 4c7dd4423ef40d5b929b360260fc86750abbe159[h] Mayhem 2803 MSX Mr. Micro 19xx UK fafd58ec55b2e1e84255036a3cdf2de87d348942[cr Magic] c68ec8dde21a2e3b45a52b4b3b572b1a253b8b28 Maze X MSX B4rret 2008 ES AuthorKonamiSCCdb7e4c8dc56a0e8e0e3df08b14a65030738d5ac4 Mazemax 2892 MSX Loriciels 1985 FR 4d507f0eff2586fe4893e6cced1d743fb3c626f8 Maziacs 3222 MSX DK´Tronics 1985 UK 7f26383fc61af7beb0176a64eb936bccee0d7a3b 1ce6eb24004089dd4122c1effde0f97f371085ed Mecha 8 MSX Oscar Toledo Gutiérrez 2012 ES Author42d4cd136018252bffa0af09115fe9613c94118bMSXDev11 Entry Mecha Taisen on planet Oldskool MSX Damage X 2006 NL Authord8e34fabf8584bb566c5056f8b42b50b6f497f48MSX-DEV06 5d3520687fce4c585d11d36bf7e526d6c8d346d4 Mega Assembler MSX Cibertron 1987 BR ab29429f7d2df7ce4bab46d1d0f2b782df7f908eVersion 1.1 Mega Assembler MSX Orion 1989 BR 5628514e2488ccb15ae464e7f210dcabbb782b91Version 1.0 778472f9113eb6a80144cc717151eb48474b9d34Version 1.0 Mega Twince MSX Karoshi Corporation 2005 ES Author1ef558c7e8e96f7c649110901ab8c86b3f7c68b6unfinished game Megalopolis SOS 78 MSX General 1983 JP 3167ffc2f2411138e4545cbdf4466c3237a04874 2af58d6b3a8c77270fa5bb82f129fc97f999b7ec[a2] GoodMSX8c6e1e21cc45b2ca72ceea93df48eda9cef1dbad KonamiSCC1ebb2e5c3f6dd650871f829be8fa749bde2a7b16GDX SCC Version Meikyu no Tobira - Gate of Labyrinth 1026 MSX Radio Wave Newspaper Publisher Company 1987 JP GoodMSXASCII160cb11c766bd357d203879bd6bee041a4690cc3df MSXDOS2c335e8382fee4ba1bfbd2f4cdd99b8222f3e910a Meikyushinwa - Eggerland Mystery 2 861 MSX HAL Laboratory 1986 JP ASCII16e92baa5fdfb2715e68700024d964098ef35704d9 GoodMSXASCII1698b7a6ac44b82ccfc45eb51595e2905adabac1c7[a] ASCII16cf344e0f58fd918c089c4d4575caec8843944ff6[h Thevma] Konamicbe6c0db6201f8ac09310c788adc634873251164 7f0037069d75a36f1a73b8f7dd286d33f80be6dd Memory 1942 MSX Al Alamiah 1984 KW 6523381fa5012b45a69af0e351f239c97afd4be7 Menace MSX The New Image 2009 NL Authorc6429707d07eaaa68887d31f3813324871b64e10MSXDev09 Entry - bug fix Konami96da54fd442f03b22dd75d0cffebd1a131173032 Metal Gear 1028 MSX Konami 1987 JP ASCII8845b9263d75d89ef02c23fd05cccc13db5d512ea[RC-750] translatedASCII8495eb918ced94ef47885df40ceceef8b56e96691[RC-750] / [tr En] GoodMSXKonamia52021f1b257c7c35d626d5d642134091c45e4f4[RC-750] / Does not work on Non Japanese systems Konami7d685547c2a4b92fa62e00854e4a9689d495f93d[RC-750] / Does not work on Non Japanese systems translatedKonamib656bd19df58fc1ba4628342b87beeec5948e0b3[RC-750] / [tr En] translatedKonamiaa881a2936f368ab887c296b837a8f7f553a3fb2[RC-750] / [tr Sp] Konami65040fb1e9bc4b2d027d7d81e44be30e7620e368[RC-750] / Ulver crack / Does not work on Non Japanese systems translatedKonami8449cdf161babec85508c35c8ca64a5c1fd7671d[RC-750] / French translation by Django translatedKonami7b959c609deaf67d29e3f79f55a03f53961f129e[RC-750] / [tr En][JP/EU patch] ASCII831b8b69de88d84aabdb762af1adeac815723562c[RC-750] / Save to disk Konami5ff1254dccd5dde58d3475a468178738d6260b05[RC-750] Konamic461efa942e1a3a1e580f9d04bf8d15c5e834f8b[RC-750] Konami847841ad88ff2eff6602f03b7231cd1e03f89ffc[RC-750] Konami804be21493a151a0be2075e86c94759dd96f0bf7[RC-750] Konami8dfe236a6a5734824035725a4f513b8fd31b310a[RC-750] Konami1417f9e8ca99deb3b422c5968fdfa03cfa633ec6[RC-750] Konamibd819202a90aae4d5ef0c590757db34e47a74234[RC-750] / Turbo Fix V2 Konami238cddaf5f8566cf042530333ccbbdaef84a8799[RC-750] / Turbo Fix V1 translatedKonamicd115914f7e9515eabaaeed1c8c480a09bd82887[RC-750] / Turbo Fix V2 Konami6228aef9c26276b9ed4157d1c5b3912d27ee6ce5Wii VC Version Metal Gear 2 - Solid Snake 1248 MSX Konami 1990 JP ASCII8356a936987e62967597ec64a0d35a1e8ad1aa20b[RC-767] / Needs SCC in slot 2 translatedASCII84ad4898db3bdcf87b2acd5755e06d45c73d338cd[RC-767] / [tr En] Needs SCC in slot 2 translatedASCII88262ac069e395ba020025caf0519e8eb5a63a0b8[RC-767] / [tr Sp] Needs SCC in slot 2 GoodMSXKonamiSCCaf567ea6e27912d6d5bf1c8e9bc18e9b393fd1ab[RC-767] KonamiSCC983eb5deeb61af5ce82ad598692192b8ae8a2a08[RC-767] KonamiSCCad621247f95f80a1a32e67def5e978c845d19208[RC-767] / [b] translatedKonamiSCCa7b196f7e934faa76602c8c00bd2f6e6d2d70787[RC-767] / [tr En][a] translatedKonamiSCC7be2f14fd0df5718cdb69b972c5f7c1f284b03e6[RC-767] / [tr En] translatedKonamiSCC9de8d411f1ed021014708ada45899beb4fc80c29[RC-767] / [tr En][turbo-R patch] translatedKonamiSCC187b469e348f71b7376f063a5fd0a4fd971fa0e3[RC-767] / [tr En][turbo-R patch] translatedKonamiSCC4b78514753008cabda2ff3d3cb537b2429efd7d2[RC-767] / [tr En] Ulver full crack translatedKonamiSCC53e1fb8ee77757cd20bf4c7aa4a5c6c786c664d6[RC-767] / [tr En] KonamiSCCe7b8574a42b1029539632ce0f5161b1c38d5e95f[RC-767] / Needs SCC in slot 2 ASCII8ac71117eb20ec8a84035acbbd380df8235a5c665[RC-767] KonamiSCCc18adae0ca1243e13a15d381a431cc0dfc68223b[RC-767] KonamiSCCea0824ea0692d2cdff816a2b3c3eb9a587a5f536[RC-767] KonamiSCCe9e778c3d103cc8c32093cd6880fa8d7238bbd9e[RC-767] GoodMSXKonamiSCC89b1f403fac335344a54f23be5b30815449f3173[RC-767] / FRS patch translatedKonamiSCC78c16176b9836880a984e0d86bb22714a917aede[RC-767] / Retranslation by BifiMSX translatedKonamiSCC54c9da6ef45c3086c92414dfb95bf82832820857[RC-767] / FRS patch KonamiSCC8cc81bb1e5d9477e08b2a4728eb997c5890f405d[RC-767] / FRS patch KonamiSCCd8cd8c87d720cf0bf52a7f432999979de5438bd5[RC-767] / Wii Virtual Console Version translatedKonamiSCC7681b0dde4d2c40512e33afe9abc72b7ef591294[RC-767] / Retranslation 1.4 Metal Gear 2 - Solid Snake (Demo) 1849 MSX Konami 1990 JP GoodMSXKonamiSCC21ac0ae7da47abf30986b479489134c79405f95d[RC-767] translatedKonamiSCC3a0b0bdd77a1f3a11533f5ef030370bcc90c6334[RC-767] / [tr En] translatedKonamiSCCefc3814c035ab472736e9473590c628eee5dba38[RC-767] / Retranslation by BifiMSX translatedKonamiSCC5844f14e297b2c93c59cc5e52c005d3296e7a949[RC-767] / Retranslation 1.4 Meteoritooo !! MSX UnAz 2008 ES AuthorKonamiSCCbf0014ca7dd0815b2385a778c5328ae63c692229 Micromind MSX Xgipe 2008 ES AuthorKonamiSCCabeccfe49a6874702d65eb4d06a970d8fcf55123 70b5bcd6c3fb0eab8b500d93a16dfa3f7e6694e8 MIDI Recorder YRM-301 662 MSX YAMAHA 1986 JP 0e24c5c4f16d5f9564d1bcbf29e4187cb6ad9701 6ad2c394f8e3ace4c30c7b751fc2cf2c78f47bb4 Midnight Brothers 858 MSX ZAP 1986 JP f1d68b477f3f54c0b6080985954208b0d95c5973[a] GoodMSX97adcb652bc4baf02d29a9abe5e6547065dec298 Midnight Building 340 MSX Way Limit 1983 JP 5359c905ad063a0faf1c46a8c03b1b9c9a04b3b3[b] GoodMSX5460aa3be896ad949bea75444bc6e7e072a859b8 Mikkie MSX Konami/Sega 2008 JP Authoraeeb8ddbd385bc3aba5009676e5192d32beecf15 31eb67d5f319062154ed911887fe1319476893e4 Mil Caras 3146 MSX Idealogic 1985 ES 7c50d00a0d7b8c3474ed492465a92622ca160151 Mini Golf 663 MSX NAMCO 1985 JP GoodMSXd2bf27695023a3e7daaeab1cb76572f2c412de1e 5a709255f73eb8b601bf9d81210260488f8ac511 Mirai - Future - MSX1 version 1025 MSX XAIN / Sein 1987 JP GoodMSXASCII87abf89652396a648a84ae06e6dabc09735a75798 Mirai - Future - MSX2 Version 1772 MSX XAIN / Sein 1987 JP GoodMSXASCII81c17e58f1a602e07f185bce1aa8589fae76e8b44 Mission to MIR MSX Ray2day 2010 NL Author0x8000Normal812c3fcf4f5e0d99cbf44e0bcb33241625cafd00Version 1.0 / MSXDev2010 Entry Author0x8000Normal0834b48ccd64668292d1319fe70781bb8cd15ccdVersion 1.2 / MSXDev2010 Entry Missisippi Satsujin Jiken 1023 MSX Jaleco 1987 JP GoodMSXASCII8d53a8efefe3714269538a91e5256c2938fe13239 Mitsumega Toohru - The Three-Eyed One Comes Here 1258 MSX NATSUME 1989 JP GoodMSXASCII8176ec8e65a9fdbf59edc245b9e8388cc94195db9 Konami7341efc039394ec159feebcfaa9d4a61ebf08a18 Konami0ec71916791e05d207d7fe0a461a79a76eab52c5[a] Moai no Hibou - Pascua 865 MSX Casio 1986 JP GoodMSXf924d89a28966a1d08c10d5ddbc9e0e182be4669 428b5bdd5cbb3e4ff637cfa5db8c41d8d0070813[a] de05494fae17c0a8c411cf0f782f00aa9209c34e[h Static Soft] Mobile Planet Suthirus 927 MSX HAL Laboratory 1986 JP GoodMSX336d714e3bc2dfe3626e55d01ee4ef74a48cf652 Mobile Suit Gundam 1228 MSX Family Soft 1984 JP GoodMSX917270dd44c63dc883289b4e0902fe6c812f5ea3 c0717a9831d66e29eb907be598ba6606e17ea044[a] 4b565ee44647923b349e6abb150a9c52f00722e4 Moero!! Nettou Yakyuu '88 1227 MSX Jaleco 1988 JP GoodMSXNettouYakyuu1cbd5c7849f97dea1e3375a842f784ff9283310cUses a sample player chip with built-in ROM Mokarimakka 667 MSX Leben Pro 1986 JP GoodMSX17ac538dd63bc93ec0b88b6dd92079e807c22908 aee97fa5e5ead1ff4f0bd4a72872a9293e3fb536[cr Canal Cracker] Mole 79 MSX ASCII 1983 JP GoodMSX0x8000Normal7d0bb1dac966ceee2e4c53f77e6e057d65f15d4d Mole Mole 668 MSX Cross Media Soft 1985 JP 5720bf88ea080ac58e73ad11ffa116cf29733d76 translatedca24614be75c14394629976cbaa42e4e077b2554Translated to English Mole Mole 2 1578 MSX Cross Media Soft 1987 JP GoodMSXf364a44d0b4ceaaa8706830237c0b824dcf9ce0c Mon Mon Monster 1263 MSX GA-Yume / HOT-B 1989 JP GoodMSXASCII8ab087d04eb5c2c8894758c7cee32ccafd80989a7 Konami4c2a8cde9b40c4936f512365a7cd6c8cef92095e Konamib1cf453206beef8ab2ede2b061ed95a3cd91eae3 Konami04a135910588ef8753b04a49c66d138c081284d2 Konami6f51a7d2988d01c94c673525a421ffa6e5a53136GDX fix 521a839f8fb725b15631bd3d400bd941cb545ff8 Monkey Academy 81 MSX Konami 1984 JP GoodMSX8b2626f8dbbb3e97a5087b340a3f3d56c8ee84d1[RC-702] 3d840685daac4960ce3a6d0850a49044bf44c607[RC-702] GoodMSXe61f407e51ea4411145e236272842bdb3e0743e6[RC-702] / [a1] GoodMSX6fefe304cc8cd099456bbbbbcb23ff776cb113c9[RC-702] / [a2] e0d84b3b2ac7675c3ed104797840d43bc2347a5b[RC-702] KonamiSCC3e37d6b312d9100ddc9329748e8dbc11274534e6[RC-702] / GDX SCC Version Monster Bash MSX Karoshi Corporation 2005 ES Author76bc839b786d67081471790268ecacbb3e33e3f3unfinished game Monster Hunter MSX Nerlaska Studio 2006 ES AuthorKonamiSCCaa6055ba0e433aa9c93df4444783cacb9628a7e2English Version - Push 0 to use SCC / MSX-DEV06 AuthorKonamiSCC7b8015f919c8dd95db683f3f3d0799cf7ef1bc22Spanish Version - Push 0 to use SCC / MSX-DEV06 AuthorKonamiSCC1c238009e77c5536d0059631954c2c4a52527283English Version - Push 0 to use SCC / MSX-DEV06 AuthorKonamiSCCca45da2f9cd8edf4ab04ffd542b9c60ef6a9a254Push 0 to use SCC / MSX-DEV06 (Spanish Version) AuthorASCII88e125216f162012731b1c2bad835d9c0dda185faExtended edition Monster's Fair 868 MSX Toho 1986 JP GoodMSXb6d39bbaedb89f8c908cc2f44a0ab17e4b80ca4f 1f7b822b47430a3d817a21af2ab95c3414b328a6 Montana John and the Templar's Treasure MSX Infinite 2008 NL AuthorKonamiSCC9f2fb5f31c2bcff949e08449f197b0c31bdebe50MRC MEGA-Challenge entry AuthorKonamiSCCb493dd7d082e25fc40ad1cccc364e1945590a2f9 AuthorKonamiSCC3ff276af654c7dc712abed79d9123317ce79b4e4 AuthorKonamiSCC65160f0d59068dbbf76b11bc79e17a9369c8dcee Montezuma's Revenge MSX Parker Brothers 2008 US Authorb03786528689b2014c90493f3e02d941c73122bcMuffie conversion Author0x4000Normal17e78fd405e65afa3763c2f7271c82ab155c004bSlotman conversion Author1dc5dfa6cd8d554844f484a1b4e680710c1b9515Muffie conversion Authorbb422bc272c72979b50e2c2311c649b551c0fdfaMuffie conversion Author80669b2172dbaa2d4f9d5485d41db6995ec4e123Muffie conversion Moon Landing 77 MSX ASCII 1983 JP GoodMSX0x8000Normalb2d9dcee787ace26f8f4d2d18e24a3d645a6f853 fbf25f2d26b17e186bfda59d82f9ea49c1231750 Moon Patrol 348 MSX IREM / Dempa 1984 JP 4a7dc53f122dbe0cc6a299902b0642c96141e071[h Prosoft] aadbd54edd09d44eeee77fd040039fabd3f9775b[h Prosoft][o] GoodMSXd1cf5410ed71f77670f65eb08ee944436bc0eb10 bc544df123484fd6400c7f83fd6b23b8e4212f27 a7c41128d062d94f7c88744e40e81f3abb766220 7ec346866d18d6bf3348f90b128d96ab141aa5f4 Mopiranger 669 MSX Konami 1985 JP GoodMSX33ec3136dfb6fd44014c8bbffc8ac79c3cafe75c[RC-728] 9255b2ac6d03fddf3df8880ec247dd3446e3209f[RC-728] GoodMSX4a185a3db81c2ce20d176d13fa5f619c6b7a170c[RC-728] / [a1] 14e7ef125cc902001995bbd8acca0eae2f213dc1[RC-728] / [a] ddc458bb3c70e0708841e516d9b89220c0e098a1[RC-728] / [a3] 1b3059c7873765a476fb7791a4d6fc846ab05ade[RC-728] 9d8029aafc4125af7f151f377978389963432d49[RC-728] / Konami Antiques MSX Collection 1 dd9d6ed442122d142987fd691001efcc2bc18c7c[RC-728] Morita Kazurou no Othello 867 MSX Toshiba-EMI 1986 JP GoodMSX4dd934902907db50ba76053f59ae7737d55824f4 Moron van der Slut MSX The New Image 2010 NL AuthorASCII874ace233682d2e4355a02e52bbd078358811a35bPassion MSX2 Contest Entry Mouser 73 MSX Sony 1983 JP GoodMSX1fb861152a9f3f9c2b2dace26d5f1d93ab9b4ad8 Movit 2 MSX EK 1985 JP 7b6029bfd7cb4b9d4c5ec5b9b3e3c93d00558b5e Mr. Chef And The Sausages MSX Paxanga Soft 2004 ES Author0x8000Normalb76f2e8df68968e3af8a10f7e5966448d6665cb6 Author0x8000Normal40b26d9c745d81c1c33711c1e2747afe3ba2a779 1e2806287e0b307eac1f1bfd06e00c34781016d8 8d2061132ec073d43cf1f9d45a8c751a36fe328d Mr. Chin 333 MSX HAL Laboratory 1984 JP GoodMSXb5d130a50f6939028702c1645426a29ec8ead6e5 bd8227af9833b7c02fa8ecff6416b7cb9e3e1de4 15ba1ffa2f55730592258c9ff4b2d6f91c9ed0ba[o] 9e766243e681338bbb7d947e3ffc5f373678ff4b[o2] a4665ce90e7efaddb88203bf5675ef009c9e7ee3 0ab2bfd449bb97ea77ac35392812c045931e2cd5 Mr. Cracksman MSX RELEVO Videogames 2013 ES Author32e006436b49ed518f9594dc69d5afe123e701f4MSXDev2014 entry Mr. Do Vs Unicorns 335 MSX Nippon Columbia / Colpax / Universal 1984 JP GoodMSX01544042b810c0b401fce5d180adb6fea9b5ed20 4cd78e26776b955eb27ac819c7eaf1f2645810ca 5d8ee4d16e388bb8647c671fbcf30f37cf8c30b1[b] 7a8e82c108b06855e39518b0f21269cbd041edf2[o] 543a356979688cb4a3cdc7c21563cb32971faf5f 6ec4579394c0828eb40c5904df361370bb6cab4c c25729f05b00d72bd7200a9a49eb4e85b6c2014a 246a91ccbac5cc58f57840203dde65c6fce0be12 Mr. Do! 334 MSX Nippon Columbia / Colpax / Universal 1983 JP 6af5044ccf48840ddd23fd07cbd3e230fa38fc8c GoodMSXdc69c7a9ea93cbb0093300955935ef156af5c194 4ab3c16e32a3a0f4373e24cd47cd2805013f2f65 Mr. Do's Wild Ride 660 MSX Nippon Columbia / Colpax / Universal 1985 JP GoodMSXe9b3652482a5b09ae662203f8758a30aa23af62f 4b168d751cedf73f09ad6b7a7e4ce809880b10dd[h Angel] 660db5d4b39de8308aac13be817a408fe3b52477[o] Mr. Mole MSX Nerlaska Studio 2007 ES AuthorASCII872783ba5a0b9f33b4573952a1c721fc5d5025fd5MSX-DEV07 Authorscc168d59fca3170f56858f39d0b53067c0c0d414a8SCC Version / MSX-DEV07 MSX Baseball 1 272 MSX Panasoft 1984 JP b00fde7473740966c0713ed2e6043b2cd6d9262b 158cc62b46b51016194176fd805e767c2ad3dc6c[b] GoodMSX76d488497d9ba54b302a4326f45909f643bdcd7a GoodMSX85a3842eabac50c4e1332666c161541be3508249 MSX Baseball 2 830 MSX Panasoft 1986 JP GoodMSXac150939558ec4e56446be9502177f28a29881eb MSX Derby 11 MSX ASCII 1983 JP GoodMSX0x8000Normalc1c350c719236af5eef8798c4532351befcfd348 MSX Eiwa Jiten MSX Eng-Jap Dictionary 1988 JP GoodMSXASCII8473bc0ea85ed1b218974796d819416e711ac193fHi-Score MSX Home Writer 267 MSX Sony 1985 JP cc06d65a9aba634e3ad75675b960140932506ffe MSX Rugby 596 MSX Panasoft 1985 JP GoodMSX35abd4a14033710730da72edf278aad42d641621 3d107526c16ebbd43bb0e9e5be030531d5466ab0 MSX Shogi Game 403 MSX Alpha Denshi 1984 JP GoodMSX299e52dfbbb94ace4fd1ef59ece45364ce0bb450 910acb8a6cc7fc4857fcbaf6a5fb9561cd6b5097 166f4a8d0f8f6faf23e55120dc8b238b9a1b134a MSX Soccer 595 MSX Panasoft 1985 JP GoodMSXd2aa1605a6b56c118e18b385ab879536d74cc355 GoodMSXc76ca96ed23621a6267d9906cc4a1ee428677f98[a1] 556071f03bfddef1bc84ae311ba286ad849868ad MSX Swimming Game MSX Unknown 19xx a3a502afd3e65b1605260b3739b196f6fd0931e2 MSX Unleashed 3462 MSX DVIK & Joyrex productions 2006 US AuthorKonami5d55fc272184d160af6318855c9d2f728b3f25c0Demo MSX Wars: Scorched Battles MSX The Pets Mode 2010 ES Author0x8000Normal1a3b400513ac85a8f0dffb2a602a9fc7cdec10c1Konamito 2010 Basic Contest Entry MSX-Aid 718 MSX ASCII 1987 JP GoodMSXd4edb2af4170fd4069960ad61d3f664462bef568 MSX-FAN Fandom Library 1 1780 MSX Tokuma Shoten Publishing Co., Ltd. 1988 JP GoodMSXASCII88557dcdb4cf1e15180d17acc8e6d515bf7bf7e7c MSX-FAN Fandom Library 2 1995 MSX Tokuma Shoten Publishing Co., Ltd. 1988 JP GoodMSXASCII87752b6624fb80d9499aaad28dd5776ec74565576MSX1 Mode MSX-FAN Fandom Library 3 1996 MSX Tokuma Shoten Publishing Co., Ltd. 1988 JP GoodMSXASCII8c2f4df1bd91f7dee1536774b9d7089086b9bf835MSX1 Mode e45ddc5bf1a1e63756d11fb43fc50276ca35cab0 c07f47c59f83038c16d6d5052aaf6c271bff4334 4141eb7540003f4abd31dee14fa1ee8be1b01a97 MSX-Logo 724 MSX Promotic International 1985 JP 47663f883f98ab9ddaa0bf0c2831c567919f56f1Logo Computer Systems Inc. MSX-Modem TM-2 + Videotexto MSX Gradiente 1989 BR 2b80f46d1c40aede85a60e53ddcfcc0cbb6d6d5b 6de80e863cdd7856ab7aac4c238224a5352bda3b MSX-PLAN 719 MSX Microsoft 1986 US 61d99816055858b3bcde3416eed30fd86390393d MSX-VTX Datagame MSX Unknown 19xx 08524fbea56885120124fbeeb6e47bc07aea4c30 MSX21 12 MSX ASCII 1983 JP GoodMSX0x8000Normalca05a183f4e7e56102706d1b9b852ad4350264ba MT-Base 2074 MSX Micro Technology 1987 NL 4c921e31137d167976fb654e03d36ef133701936[cr Speedy-Hacking No] 7439f276169555293c367ba692fb644d9ff58058 MT-Debug 2079 MSX Micro Technology 1985 NL 5dacde48fed402b1ce9d0a41b2c126417bf5436b MT-Display 2519 MSX Micro Technology 19xx NL 32c06b6247a40863458481df60a8111551dbaefb Multi Cartridge MSX Marcos Daniel 2005 BR 0626bb3b5b230adc1faab9c80cf4f33a539a82a7 860f5180c9028cbf675d9b1e59b7e7487c59ea0c Multiply and Divide 1943 MSX Al Alamiah 1986 KW 20effe093026e9209bd3327114e9ad51676cf807 Multitext 3471 MSX Victor 19xx JP 9e1633ec4492a01ad654d0a08651daa5ababd940 be42c55ffebc1aea524475128cb687e2afe8e2e4 Music Editor 343 MSX Toshiba-EMI 1984 JP translated0x8000Normal9f6103568e4c11224932a223bfc86fa0c3eef021[tr Pg] GoodMSX20a2e3d7966916f46eb26cc1aa226ec2d11646f9 Music Editor MUE 342 MSX HAL Laboratory 1984 JP 984966dc32808afad67c846024aa2889df20d6e2 Music Harmonizer 3 344 MSX Rittor Music / MCS 1983 JP GoodMSX0x8000Normal85e3386d30dd9a64f4cc1bca03cff35b440f70b5 Music Studio G7 665 MSX Sony 1985 JP GoodMSX8c06d9e1e188431c13721af965faa7391140af2e My Small Dictionary 1975 MSX Al Alamiah 1989 KW acc93f818a9f2925bfcc61df7d9fe2e0cbc0a9ae 11927d62332a3310ad462f2f532ac30533c1fc17 My Watch 1945 MSX Al Alamiah 1987 KW d9195b9a24f6e7c5b99eca046908915bfe369d46 Namake`s Bridgedrome MSX Buresto Faiya 2005 ES Author0x4000Normala39568be17b6ee5d10a61d82d8480cc859be778aMSX-DEV05 Author0x4000Normal05b8109f4f0a4d9c1bcb837982abb573236be936MSX-DEV05 Nausicaa 240 MSX Technopolis Soft 1984 JP GoodMSX0x8000Normalfb968a2407eee00c683af3ac19e88015341564adBoot with Left Shift pressed down Nekketsu Judo 1260 MSX Pony Canyon 1989 JP GoodMSXASCII8721a2ded692d34d864d0440ac347c60880942d19 ASCII81d97922a5ab63e73611d0edbe952f041af3385b3 Konami6075cd96bc85864f22c643b023302f006b565049 ASCII8688b70e60dc272654ac6b0a72237fefdce2ef9ff Neopong 512 MSX Z80ST 2010 ES Author328bf269bc38a706f1b6dec688d4f47e4a830199v1.0 / MSXDev10 entry Authore110e7165e571c64d268efa1f811d62f1a8da769v1.1 / MSXDev10 entry Nessen Koushiyen - Casio Baseball 256 MSX Casio 1984 JP GoodMSXb7fd07b7825c6beeb19b1f38631d2869f60939fb f8c6974260b4da94345295eee27d97a389abfe96 c6d4cd1a2736c9ee63641c75727c2c254a5c1f87 530b3aacbf4e6dcf6e833ceb33f98c7050cc6ee9 Nestor Cartridge MSX Néstor Soriano 19xx ES f6314590061a6b44c5ed24f60ded13d8f2f7d95b New Bubble Bobble 3238 MSX Zemina 1988 KR 223c1f5d38b54d4d5a58988223bb95df1998a620 d1ece8b5ffeb9b99230e640af32cb488fee51e35 58551545e73c838a2e995e6951ab644ed2590786[a] New Horizon English Course 1 570 MSX Sony 1987 JP GoodMSXASCII8ec1bb5c36952b12e6c0805e9a8d5bf31c20a68c4 NGA MSX The Links 1985 JP GoodMSXbc76a04970c547b7c080e747d393d13c87e82e7d NGA II MSX The Links 1985 JP GoodMSX88f3a72513b04459212289797942c4d76f495a05 5dd2ac08fd05a1e091c7c6ecb6c10b49992bdd46[Kanji] 5b20c3e0b9f1f29149039d5246be881a6b0dfdb1[Kanji] Nice Soccer World Cup MSX German Gomez Herrera 2010 ES Author50c8a1f1298b48cd3e325a859ea68d5739d87a01MSX Basic Challenge 2010 Night City MSX Yermani Soft 2005 ES AuthorASCII84b138e041947990309c228890914689908d46685Spanish Version / MSX-DEV06 AuthorASCII822f9b0238ff48840fb7d45ac93a7f15495393920English Version / MSX-DEV06 AuthorASCII8ce3f9356eaa5fd6c1b5ff1ade244ebd9130f1cebMSX-DEV06 AuthorASCII876b7a5e75d5091f0482746e5a0c0a71de07ddf83MSX-DEV06 Night Driver MSX Karoshi Corporation 2007 ES Authorfcc1cf816657e90bb1e0a93898fd17a8325c2cd3MSX-DEV07 Authord8b935757e77f0717559291e2abc7f7ad2e397ad Night Escape MSX AG Software 2011 IT Author1ebbc94d106c995ad9499a4e1ff5848ffe3b509aMSXDev11 Entry Night Pursuit MSX German Gomez Herrera 2008 ES Author873bebca9668f0ebde02742af9ebdcb993c6791b Authorcab996c6abc011b8c2abf3f3a7628437dd40deff a49a7ce290d83aa80a4e2664cf776193aecb6380 6aa2758d972b0ba06e58769a4f96d5e94a3efd51 Night Shade 809 MSX Ultimate Play The Game 1985 UK c3f849c342839fef2ef718b5115a90a31e7db1f6 GoodMSX50e27c5b83c8afe00b077406081d063263e98b62 83fa6a7847faf14b6ebfae4637ac8868c869c7f5 Ningyo Densetsu - Fathom 571 MSX Imagic 1985 JP GoodMSXae52922a197a2f47c7fc4adf9dbfde13c136ed14 b7e279cd68faa5aee4434480feebdf189fb54e73[h Prosoft] Ninja Jajamaru Kun 818 MSX Nippon Dexter 1986 JP GoodMSX371de6f12d834da07c8e67b13e75fc22618ba5e9 ac5639b8ca66600e61452697ed44b48607256065[h Prosoft] Ninja Kun 54 MSX Microcabin 1983 JP GoodMSXec2f1e747756ba1fdae606658aeeafa07e6e7fd4 Konami4d904fb50b82c9ead8432e14ae1cdd23a0e2fbfe Ninja Kun - Ashura no Sho 985 MSX HAL Laboratory 1987 JP GoodMSXASCII85c278deb1390eb45c79b7667b1ea1c54183123ac Ninja Kun - Majyo no Bouken 572 MSX Nippon Dexter / JALECO 1984 JP GoodMSXc9d10b1df3bcadb3d917cf2a6877ae6e71ca3a57 ddc1824826d78e29261b30a675bff9862f308392[b] translated7d566809ea870ab681680ba3daa8946cfcb060e5[tr Br Pauli Soft] c84507bc4f5ea132197f3ede885a67327fe1b68d Ninja Princess 820 MSX Pony Canyon 1986 JP GoodMSX9dfc71887ffc6d94b8780b5d6f207e24c1f58b54 62bfff23e2beae00db14b6c3e933043218b33ded 152e4edc0cadeab783f4724c6539875d9c6ca018Ulver crack Ninjya Kage 255 MSX Hudson Soft / ASCII 1984 JP GoodMSX0125bce373c95ee78df074d19ad032158edc74a2 0ebd63dd4405bc141ba3d17010d6d928979f19d5 049892d1633bcb3f1d5067ba710adeb89b38fcac[a] Nobunaga no Yabou - Bushouhuunroku 1548 MSX KOEI 1991 JP GoodMSXKoeiSRAM32cb6bd26961387def757c7faeec7563d77d404d01 Nobunaga no Yabou - Senkokugunyuden 1321 MSX KOEI 1989 JP GoodMSXKoeiSRAM3289943ae938fb308f4b5149ebc0cbea597b9b0223 Nobunaga no Yabou - Zenkokuhan - MSX1 Version 573 MSX KOEI 1987 JP GoodMSXKoeiSRAM32e6de8bbd60123444de2a90928853985ceb0b4cbf Nobunaga no Yabou - Zenkokuhan - MSX2 Version 987 MSX KOEI 1987 JP GoodMSXKoeiSRAM32fbfdd89ec183ceae63d3d66b0ab45faa951b525a Norakomi's demo Level MSX Norakomi 2009 NL AuthorKonamiSCCdf2d0a6ce5069b53f3de7a518d0c1cb948077948 a5128e37fad7eaa50d0f30bdd126d3a5f0475282 Norseman 2949 MSX Electric Software 1984 UK be7cfd5ca5d78b99c05dbda1e26f4afbddb50c48[b] Number and Target 1946 MSX Al Alamiah 1986 KW ce493944b6b57ffa56e7eb09df76e73b65dcb800 99e56e458a41784fb33909d02945d7a619096369 Nuts And Milk 241 MSX Hudson Soft / Japanese Softbank 1983 JP aab3416aa54fa9b49e98c8286ba6a8f5093545d9 Nyannyan Pro Wrestling 857 MSX Cross Media Soft 1986 JP GoodMSXaeecb99ed7bfe2ff39a5f42637b6b33ecc79c443 Nyorols 53 MSX MIA 1983 JP GoodMSX98e33dca445850cdf24f09050756c3b75202ac13 O'Mac Farmer 288 MSX Mass Tael 1984 HK 6511a7a1bf620f0b2d5e38e954a014828dfcda03[b] GoodMSX5dcecc7c790e398a1c652db568c1e66c6f25e5e4 Odyssey-K 409 MSX Rittor Music / MCS 19xx JP 23b9dfdd7789fcca1fd2d395ee9e377807b76c9a MSXDOS21af4561a62c2e6258ec63bfe4613ce7616496a7a Ogre 914 MSX System Soft 1986 JP GoodMSXASCII16c9c272ffa69d3e0af5be42e7636b02dc2af6ea8c ASCII1682397096d5ef436b99b943c2a52d6eff87694183 ASCII168e66731c99f4d041ddcd33088d0ab0d4cae8bf75Uses Mouse Oh Shit! 2400 MSX Aackosoft 1986 NL a417d9e3fc22f40932d85ee07fd7c3656b90894e Oil's Well 405 MSX Sierra On Line 1985 UK 244eae705df950f60b7d4b523d57b9cfded96b8e[a2] fa4f31ba6d0d9ac821592a8bb224dad390fae0c4[b] c4c49d01eca572ce2e35d91453270f4d3e27152e[b][u] GoodMSX5b53794d3d73df29cbcec411c0f65e9e9898977f Operation Crossbow MSX Karoshi Corporation 2011 ES Author4ac0b569ddd4ea74d5eef4f4574e5ef9759f66c3unfinished game Operation Wolf MSX Toybox 2006 ES Author0x4000Normal366cc928a5dd5948bf6cf2ab1e00efcb2b6a8fd8MSX-DEV06 V1 Author0x4000Normal016c63161d5112ef637d48920a85001c9eb6a10fMSX-DEV06 V2 Author5971dd9906de38ff3668e9e0330008e204f60f23 AuthorNormal6287c1f05ea958e1abe86b87de073bef50977478MSX-DEV06 V3 Authord2de46235b396fe84b6331f3fa4ddfef6a6e88faMultiROM version Optics 1947 MSX Al Alamiah 1985 KW 3c9301eea142857ee5e9e04d0caebbc5a4245715 Othello 725 MSX Pony Canyon 1985 JP 81c50af8ef09e0b7a8a69fd4715592b5e803cdb5 GoodMSX6238f9c5f4c0012b3425be203c23092f6b624fe8 Othello Competicao MSX A&L Soft 2003 BR Authorbb8f2d98925c0d407c78b88f77a9c0e3a759c021 Our Arab World 1948 MSX Al Alamiah 1987 KW 6918679b2be2d014b60b4025a3877dee9ad5f407 Outrun 1047 MSX Sega 1988 JP GoodMSXASCII80f10d77d1536993b8455b3b47278ca5782ef9275 Konami6933de9a128e90a34383f3a9b2660d5bae5d9fe3 Oyoide Tango 411 MSX HAL Laboratory 1984 JP GoodMSX41ba54c945104915ccc8d465930445c905d839e4 3f4dd4b459f12306fe36864344af0b35f8c5d3b4 04084dbc6cbb9f674ebbd9f07900deab995a1d97 bfc23e42039b70b53584bfdbdc90d5afbfc0090f 30a58ac784f56a7aa0d23125e89b8a3d6c129580 Pac-Man 269 MSX NAMCO 1984 JP 56febd50be4accb3eb8c73ff4ff641ea767108b3[a] GoodMSXa72724a79c2c50ec66046c0828934b5cad11b7c9[a1] GoodMSX61695eb1dc6242d8a70c7ef2172cf86a26e3c2dd KonamiSCCfdb9d995ecdd2eacf369c11b8833d08559957695GDX SCC Version Pac-Man Collection DX Demo MSX Opcode 2006 BR Authorb1a97ef65f2bc3e79b628a51fde8dc98f37d9db6 Author5118d6f1c102370691903d07350d5090633589cd Pac-Mania 1256 MSX NAMCO 1989 JP GoodMSXASCII8d5902afbd8dfb1ea96fc8cf4d62cf6e4fa40d4f7 KonamiSCC1d697326d085b008db16fc13e15e4f7af2841e67 Pac-Mania - MSX1 Version 2826 MSX NAMCO 1988 JP ASCII1666802dd98370da809250367365df234f9a6422b7 Pachicom 592 MSX Toshiba-EMI 1985 JP 22e8e217e96cd8229462c58ca7ead1ae33088fc4[h Boram Soft] GoodMSXa2e144546a191da8829b9ca9fa019884fe95156c Pachinko U.F.O 268 MSX Casio 1984 JP GoodMSXcad653766b597a4aa130a9a1956ece77ba4d52bc Pachipro Densetsu 1373 MSX HAL Laboratory 1988 JP GoodMSXASCII878507f3f8b8601d99bbedb6517bec6cddf0bd5b4 Pai Panic 55 MSX ASCII 1983 JP GoodMSX5558ba634018b22dcc687a01f9422fe60ea57adc Pair Logic MSX AG Software 2005 IT AuthorNormal9ab584be0a054e76d345ca937917f1ea195f7967MSX-DEV05 Pairs 71 MSX ASCII 1983 JP 0x4000Normal80c82bb4010476a679cb616ac31fc691b8e8a526 0x8000Normaldf13ed647ed4f5a6b04f1710fc1140ea65dc966f[a2] GoodMSX0x8000Normal27bc809518fbb6c0d52c949e7cbe81980a801d32 Palette Editor MSX BiFi Productions 2005 NL Author0x4000Normaldc6437a5d52f77f524198b9d539b34dec6122854http://bifi.msxnet.org/paledit/ / Version 1.0 Author0x4000Normalb5414e11b227c3c828ee1de206bf2d8f14486bd4http://bifi.msxnet.org/paledit/ / Version 1.1 Author0x4000Normal58747cf76b7c2785dd70409be5d912eafa328616http://bifi.msxnet.org/paledit/ / Version 1.1 Author0x4000Normal98df3e94d3bbef5d18095b62e65274ebfa8d527chttp://bifi.msxnet.org/paledit/ / Version 1.2 updated Author0x4000Normal7e64658b7a4fd451b2f2aaef409953e3e4f1b033http://bifi.msxnet.org/paledit/ / Version 1.3 Panther 1813 MSX IREM 1986 JP GoodMSX479608b5a86d7c77bd6c4dd00605b8efb2e5b43e Parachuteless Joe MSX Paxanga Soft 2005 ES Author0x4000Normalcda2df54006d98890feb1aac1dd955d0458132a4MSX-DEV05 Author0x4000Normalf0b65b71c505c68019e2285d3b8afcf068a74b9f2nd release / MSX-DEV05 Parodius - Tako Saves Earth 1188 MSX Konami 1988 JP ASCII80b02dda5316a318a7f75a811aa54200ddd7abc30[RC-759] / Needs SCC in slot 2 GoodMSXKonamiSCC2220363ae56ef707ab2471fcdb36f4816ad1d32c[RC-759] KonamiSCC75d3b72d9ceeaa55c76223d935629a30ae4124d6[a][RC-759] KonamiSCC2111cec4b5ea698d772bb80664f6be690b47391c[RC-759] / cheat version translatedKonamiSCC04e4eb1cae7cf379977d238b74d727bb929c8d23[RC-759] / English Translation KonamiSCC240a23b63e901b988951dda1ded561c281ccbbc1[RC-759] / Ulver crack translatedKonamiSCCf1de76dfcd8c4e354a8a8058a76191167fd112e3[RC-759] / English Translation translatedASCII87c066cb763f7a4fec0474b5a09e3ef43bbf9248b[RC-759] / English Translation KonamiSCCee66334fbac1c926ed05c1c3f38df3920f501fe6[RC-759] Pass Ball 56 MSX ASCII 1983 JP GoodMSX0x8000Normalaeb705ac42f8d9d62ecdb25899535d1f14f1c49c 5646329786dba6b6cfb1fbd733e921c3a3729091 639da2e949642d36202ff80dc458f32836dcdd87 Pastfinder 582 MSX Activision 1984 US GoodMSX8117ec66c0645a54422841a632cfd6602f35c4f9 Payload 432 MSX ZAP 1985 JP 0486938b52fbcf30a4fa206e07960a5b866fca61[Kanji] GoodMSX5f965bbba026134818759d25119d4114599132a8 536e6ca8baaa19250b1272a70b68dd11ffc97047[t Du - F2] Peek-a-boo MSX DVIK & Joyrex productions 2008 US Authord92c82e155cb34f65587d472d4eb5ed1b9e68018MSX-DEV08 Authora0b45f208df3091e461e15f6182896d2716c2a11MSX-DEV08 (v2) ae1bdab2d9ca17318681afc8e69d0fe861431a02 Peetan 280 MSX Nippon Columbia / Colpax / Universal 1984 JP GoodMSX190bcab0325ded99f42f32976bf965f0164ad2a4 Pegasus 850 MSX Cross Media Soft 1986 JP GoodMSX37e6fe7d07b3f8b2c585c77b231207ac8baa1ea3 295578c06ff3f451f5203fc467a191c9ea335952[h Prosoft] KonamiSCC9bcce15785d5977a8611ddc83981bef299dd6a0b Pengo MSX Paxanga Soft 2010 ES AuthorKonamiSCC6e2597e3dd2558043550a4307d01426998136722Demo / Demo Version AuthorKonamiSCCefd0e4d8df8951be7fa1c129beb50e55999ce099Freeware Version manbow20618eb4059edd8282190889e3b0255113df354c3Retail Penguin Cafe MSX The MSX Café 2006 FR Author0x4000Normal058af0079b868387ad39c6b8a390b18b936d0e48MSX-DEV06 Authorbfa533c539035345cee719b4bbc7b30989e97372 Penguin Mind MSX The MSX Café 2007 FR AuthorASCII88c218c317e1e213e1c5e9439870929009d645a47MSX-DEV07 Penguin Race MSX Dioniso 2003 ES Authora2d2e98f68042b32f3225b58c9989529d5efacdc Penguin-Kun Wars 1 642 MSX ASCII 1985 JP translated688564bedfd713432350d7aee3fbd7a7fc8691d1[tr Br] 86231b604338a9b4c63be995adb2fd9860bab20b[h Pro Soft] GoodMSX5aab681334f9e97fe8d7e0eb008b2afd5e0fc3ac 6c0d3a55f9e8e9c274413d06f4729bc517f77ff5 086915645d703f20274aac7307f61f73cc56545cUlver crack Penguin-Kun Wars 2 1211 MSX ASCII 1988 JP ASCII164607ed284bd4602affa00600c749ce43b2b89b6d GoodMSXASCII16b7104477c8801fe54d1ba91f3a77ac6e4f399f52 ASCII16788c53657d9ea180d04f29ccaf1f1c29a9b75519 ASCII16ce4c5ce07afd297156899eb6c1056eef3f83b803[b] GenericKonamicbec713b32517a503ae6c02497357a8361eccb76 translatedASCII169f476f544d1cd5f87d4ca19665ccdbf6ac0f68a0English translation by Tsunami and Max ASCII16cd2bea197ad97a5a1e90d6b998595c9b5802dd80Bug Fix Pepper 2 MSX Exidy's 2008 JP 479100e82d3f25bbeb9a291163945d2f5bd62f74[b1] / Muffie Version bf041bc8ce3b5dab7b1f85f623b371b263639aabMuffie Version 58530a5080aa54aed05e3b98ed104a1b4c9278eaMuffie Version 5f805a670e31b22723aa7738aa3edc3fdbd8f870Muffie Version KonamiSCC70e947dfa1d08fe100b5975aa145c3d8a4180f68GDX SCC version Perfect Fit MSX Paxanga Soft 2008 ES Author7ec25356abf3720806b2b97cbabdc1f19ed698f8 Normal691b8adc93ec86a78dbccfafe7da1b33358d4cc1Retail 823b638419dc81767a8511db190212607908cece Normal3c3707ba58fd287bc7ac4b485ea65cd7132f2464 Periodic Table 1890 MSX Methali 1985 KW 7033c47a3b20fd04054ef37d209e1545ac053865 Phantomas Saga Infinity MSX Karoshi Corporation/CEZ Games Studio 2006 ES Authore59d739ecd56a11ecf4020430585ea4cc61b02a6 Photon MSX Ola Andersson 2009 SE Authorc10d2a3dc77816cfb5b5d4b983323284f4a4aef5 Pickup MSX German Gomez Herrera 2008 ES AuthorKonamiSCC390fb09a9d944d1d17c328ae138325751d1b4203 Pico Pico 61 MSX Microcabin 1983 JP 683478868f6c6423cc3e9c6aabe1868b30b1e47d[Kanji] b04421e507eeff3b18108d799c95debb1a881a31 Picture Puzzle MSX Karoshi Corporation 2004 ES Author9c9da64f7b0ad20b0d699bbb254ae206668671761st version Author27e56fbec7fa39ce19d045b0dbc4217d290f92e22nd version Picture Puzzle 60 MSX HAL Laboratory 1983 JP 663d12f6fed3ce5017b783db3032eb69d8554fed GoodMSX336a7c451a03c9a55726d171603b54628ad832c8 Pictures and Words 1978 MSX Al Alamiah 1990 KW ASCII85b25d93259a358f2064adc44f6382c91c9394861 PIHKAL MSX Hackers 2009 EU Author65fa1c37e541d66398a2786a8baadecb363a1565Wild Demo Entry eb164a0a65077687c6e71f3ba2f3956f3aea6bb3 Pillars of Islam 1933 MSX Al Alamiah 1986 KW 4a28e8bc5ff7b57c63ce810972c3ffc2683ee3d4 Pinball Blaster 3021 MSX Eurosoft 1988 NL ASCII16c95f8edb24edca9f5d38c26d0e8a34c6b61efb0c Pine Applin 259 MSX ZAP 1984 JP 211e1f7e86495178fb2b05e163b04ed4b7455034 GoodMSX878554d3b579e72b2d3c22e6330208ee0d1ee8a6 c69c5b26eb3fb5e103a887c96e702ea4acafb75a Pingball Maker 615 MSX Nippon Columbia / Colpax / Universal 1985 JP GoodMSX91bd6c21530654fab12cd8a797044d494834c223 Pinky Chase 614 MSX Nippon Columbia / Colpax / Universal 1984 JP 1fbd7af23177ab1de799764d8a26bc2bbf85b042 Pipi 611 MSX Nippon Dexter / JALECO 1984 JP 0x4000Normala4f15647a4e50f1ca7777604df07267dbaa8e12c[b] GoodMSXbe8bb7f5e812cd31a65aec3782a2d526bef9b3d6 f4519d8174f2f5cdbeb8bcf54ea0cafcf8feb66e e05cd2d39e95d8d318d0ff92653e58ebd83b9384 ff2aca98b7bc07d052ecc0d60208ce70a1c8904b Pippols 612 MSX Konami 1985 JP GoodMSX10e63eba7d2b10d946822f21e7fd106f5dfab632[RC-729] 462bbe535702c52a7905ad9c6bcf93032214eae2[RC-729] / cheat version b084a94923ce948cb39dc186d83015f7487cfbe4[RC-729] / Ulver crack 0a9c576eabaf2651ddca35bf17f96e509d6c6ba0[a][RC-729] d621e2a2f0f924a17673f5d47f2182095074acf4[a2][RC-729] cf8e40315f34e30e1be4776bbebc08c0a8403523[RC-729] b1e6299c4711d2f571b72f11d3b1aeb23eb99b64[RC-729] 416d97741622ff3ffab2da5021a3bde3bbc07061[RC-729] scc+8927b9aa770195f255e4e0e143f89bfe93353022[SD Snatcher RAM SCC+][RC-729] scc+9293403c3811b667d21408f4d670b21373ea333e[The Snatcher RAM SCC+][RC-729] Pitfall II - Lost Caverns 607 MSX Activision 1984 US GoodMSX78079266711e60420480e4d95a39f0d7d974ad32 f9a8731b7b978b6eb5310aa9bf644fae5a9015b3 ae8c7355c829248305384243f78a870453367e77 02943609157b14cb036c13388093af7de0033b48 aee34601bbd3878f580642300a7094529d3f89c7 Pitfall! 284 MSX Activision 1984 US GoodMSXb88e9c548873dcfd190e0e38f7b279344eea41ec 2fa9c0f016efc2d1752a272c632393f5063ea06c 5fb4b6c3735e4d9415565a856bb69f9fb4857161[a] Play 38 MSX WYZ 2007 ES 744c92e3e861a9f641da2710302b96b0a26c8dc0 Play Card System 630 MSX YAMAHA 1985 JP GoodMSX8161d9b325c708af463c5622bd89ca94754cfdefUPA-01 / Includes I/O-accessed Device/Needs FMSound Synthesizer Unit Playball3a6aae59ec0d1f542ad3c1c59e0bfc89440be88a Playball 844 MSX Sony 1986 JP GoodMSXPlayballe6fbfb4b10dc393953211dda4b114080a08302a1Uses a sample player chip with built-in ROM Plumber MSX AG Software 2009 IT AuthorNormal23a05191102459579aee8af4527d6998912f0f80MSXDev09 Entry AuthorNormal788ce4ba8d7e881dd2d3cc697c2dd88f44e27798MSXDev09 Entry Poetry 1910 MSX Al Alamiah 1989 KW ASCII8b20e02cbffef692c7aee6281eb36729ee1e928ef Pointless Platform MSX The New Image 2006 NL Author08d5a55b80726fb9c793be20e36f6d88beb643a6MSX-DEV06 Author31706b3fc6cf6ab1d2e3670ddead564564e96c45Release 2 Author2d09b2ea90ad0cb0da104293d99623569c33b86a Pointless Shooting MSX The New Image 2006 NL Author85d305e3e5d065427b8eda986736b19f930abfd8MSX-DEV06 Poiny X Senryosakusen 851 MSX Victor 1986 JP GoodMSX6f247c8af6fc1881242281f19912068b03ca4126[Boot with Left Shift pressed down] Police Force 2 MSX German Gomez Herrera 2008 ES Authore243cb1cb7d7a8c44d68f1a2e2be34a2e6ab220b Author8cfcf72662b67bb77d4dce81949b4b6814a314f7 Police Story, The 853 MSX Pony Canyon 1985 JP GoodMSXcdaf507dba4a48b5c98850a96f61f6d2e8464e00 835b3e2bae1d680d7050ce188c617fbcfb326eb3 35da794345d9a424719c92f53b4e5806b8a94dcc 0f07a40e7965de298bec6b8447190437e0e9f0b3 Pooyan 3180 MSX Gerardin H. 1985 FR GoodMSX692a55393e8eef0800a612c01456e9cecd9c1418 5c572daa07f588e35bfa8be1a8d64caa73bfab5a 8d8336941fac7332279b4dc482500c7394f98393 7f6a1ba8232d6a5a7fcb5024071f305398d52dc5 Poppaq The Fish 322 MSX Mass Tael 1984 HK GoodMSXc713ecd0e2ef89b2443e516d5ffa53d3a3422e22 b8f9248a6f2bc02ed2dcae3f21d6fe0e572e23fe[h Boram Soft] 3871a4b6b8e6358f86d964110abaf263d32a6f8d Poyopoyo Life 1 MSX Pastel Hope 1991 JP KonamiSCCf3cf0e4d88ccfd6720fff8feed8e6a3caada83a0 Poyopoyo Life 2 MSX Pastel Hope 1992 JP KonamiSCC6630158b46f96756d1a90d2df909c4dcc4bca6c7 Praying Hands MSX WYZ 2009 ES 583b223c0b2ca91ae6da16ced34ea3cd500ad54d Predator 1204 MSX Pack-In-Video 1988 JP GoodMSXASCII8ae6ad52d5ebcad7722ea070109a286fe13b06cb7 ASCII8d7f8c34e3ae9dea51d0c87857e0ff78028d402b7cheat version Pretty kingdom MSX Nerlaska Studio 2014 ES Authorcb07324ae94c2546156badbcb5c1ff3c5ff4d041v1 / MSXDev14 Entry - NLK006 Authorbb23232a5d3d4c1d78647cce520a9accecb3cb4fv2 / MSXDev14 Entry - NLK006 Price Of Magik, The 3106 MSX Level 9 Computing 1986 UK ASCII16ac109dac24df4dae813ca5d9f0938cc48a162bb1 Pro Baseball Fan 1013 MSX Telenet Japan 1987 JP GoodMSXASCII8f52690157a51c662ffdf4ae3592ada93d7be4032 Professional Baseball 1812 MSX Technopolis Soft 1986 JP GoodMSX24c5cbf1ee4caa6d83a5aa7a5f3818692542fda8 Professional Mahjong 846 MSX Chatnoir 1985 JP GoodMSXda5fd6ce2f92a40b1ccaf6e3c425d720febc3543 Professional Mahjong Gokuh 1208 MSX Chatnoir 1988 JP GoodMSXASCII16SRAM281143d2a8ef7cc593a604f3cc890312b42bdff01 ASCII16SRAM28afe57c287adc0f93f234e8e8c136ae3c00947c1 ASCII16SRAM26589782c0aa43f6c7da6d92e9b2f8ba4632e1d1d Project A2 1012 MSX Pony Canyon 1987 JP GoodMSXASCII8418f6248221c79039fe6c44fc5a9514842e3ec71 ASCII807c4c39c6e6c03241d2353300f630b367322e78aUlver crack Konami91afe2ee3a5b8e80d67f6c7a30bafbc2d63b2421 Projectiles 1950 MSX Al Alamiah 1985 KW 17bbed846045f3325b1d34c686d35889f9efe157 1b1153675a334be6919468433f24060b8afffc9e bb445f8533ecb5d3d2d2bfe57577b32dbc568e8b Protector, The 632 MSX Pony Canyon 1985 JP GoodMSXd03fdca57b55bdb7e6b5481615b8ad0c8a71f77d 5795c5f9bc8af39ea5b947bf25bda0192dcca89d[t] Proverbs 1951 MSX Al Alamiah 1986 KW b186d547c0b79612bffb63dd49c3230f156c2bbe PSG Music Writer 602 MSX Rittor Music / MCS 1984 JP GoodMSXfc4c05ca47c94df877bed2e02307bd6307f738a6 c2a1ea76f3e3517f9952e495395b8fcc820c7420 PSG Sampler 2517 MSX Karoshi Corporation 2004 ES Author9c26a5a89f543050db2d614f8405c659f5baae081st version Authorc80228bd14f149dbccf57bf9c00f34a4df953d852nd version Authorf6eb344408fbf69332fdf865a8c213455f0a024c[NEW] PSG Sound Test MSX Manuel Pazos 2004 ES f36af9ea08780ebfb194337ccc869aebab65ac66[NEW] 22702ff548fea5a02532d567f89e807cadf67386 Psychedelia 2807 MSX Llamasoft 19xx UK fae437072234c65bca174db9aefd2107a53c358a Psychic War - Cosmic Soldier 2 1117 MSX Kogado 1987 JP GoodMSXASCII816c692a2c9babfdadd8408d2f0f8fae3a8d96fd5 PT3 + AYFX Replayer Demo MSX Shiru and AlCo 2007 RU Authoreb5ed1fd10bbdd6853f595f748f92841de3f55f9 16b7c35288744b94fa06ccc404e7f119ddefcc54 Punchy 2808 MSX Mr. Micro 19xx UK dcfd673926a1c10cf4bada731d072d3f6ce29c44[b] Puznic 2374 MSX Zemina 1990 KR 3b9df9bf57ac9d8f155c623c8ed6057a79491738 Puzzle MSX German Gomez Herrera 2008 ES AuthorKonamiSCC4e864cc0045c85c4f5c5c0efcdf26340a178686a Puzzle 1967 MSX Al Alamiah 1987 KW 0dfe92b48e99bed4cd879e4026d036f94c826ce8 Puzzle Panic 826 MSX System Soft 1986 JP GoodMSXa36c00f32cad6b603bc5525a741cb09064670f34 3767aa91950e1ecc02b2d768dd2d5cf637fccb92GDX fix PWND - of je worst lust (special edition) MSX Metal Soft (Vampier) 2009 US Author0x8000Normal681c30c7539ec738be2e36ddca408eed392f3b6dAfter MSX-DEV08 Version Author0x8000Normal2164c6ee1819f7fe763dbcc9ef366b62bf04aec6MSX-DEV08 AuthorNormal1eb9f2bd558dd8ecafcde3ce61881bfa27f70f1f 2edbf1b6207884f04fca9e561743d84380b01801 PWND 2 - Of Je Assembly Lust MSX Metal Soft (Vampier) 2011 US Author6ac076f0c5dd09186fef2ebbee2b74221882538f Author478a75ad54417b1abf95be7a706d971a0d6cff0fMSX.org Donation Pyramid Warp 64 MSX T&ESOFT 1983 JP GoodMSX1ad2178c27874704b395ab250200d9acb7d3de9c e35c48aae9aed93a114a28ee90f8f20fced3587b[o] a5db007e548e9d320f0f4a9820dd3a2337392963 Pyro-Man 160 MSX Konami 1987 JP translated34809426195e9e75eab850d0ec126bb8ae5d3a6dEnglish Translation by GDX translatedd67d93d2089835e4dc9393ad609c3c7fdbaa56d9French Translation by GDX Q-Bert 739 MSX Konami 1986 JP 2e16a588246743c9b901afdbf1ba094a0b1c8b5d[a][RC-746] GoodMSX531a7f736d86095d791d6d0f267be66eef07949c[RC-746] d5674f0217031d4bfab9c53393c18036a9707b72[RC-746] 6f1774195b8db0ba34af5e16ef94700d1714e011[RC-746] QBIQS MSX Z80ST 2010 ES Authora7a2a867f7e90458f3e8614e8c227da944a6092cv1.0 / MSXDev2010 Entry Author4921e9ed9455e56371fb14481f1963957b1caeb1v1.1 / MSXDev2010 Entry Author680be3a41a605ff95a4f5a8d6d1b79714439e1aev1.2 / Post MSXDev2010 Entry Quarth 1383 MSX Konami 1990 JP ASCII829a42c2b679bac55a0318ce8c97708e402e2779b[RC-769] / Needs SCC in slot 2 GoodMSXKonamiSCC0c62591033aa4e5e6b9eeb21eb2e5e60dcd8ee8e[RC-769] KonamiSCC2312bff70c83e60d1ca0fb9ca78a50637d02771cKorean version by Daou / JP-KR Quarth Demo Version 1850 MSX Konami 1990 JP GoodMSXKonamiSCC1ccac7caeaaa1c5a89503221b0a5eec2f34b1c91[RC-769] Queen's Golf 444 MSX ASCII 1984 JP be9d337dd412056f5fe78fd19467a63e80e3864c[a] GoodMSX1f72e0cd58afe22075a129ac854bbdce0704b0c8 GoodMSX97c39dad3e065f9083fefae157f278844b208058[a1] d1150f7f446e9c7e0fcd834593c291cfa60193cf Questprobe2: Spiderman MSX Impulse9 2010 ES Authorc32a003285634af1dbe515832446110fb4df603b Quinpl 1098 MSX BIT2 1988 JP GoodMSXASCII8c82c6e054313f4c8864e64897baea8b63776020d R-Type77caeccd08eb558b727350fdd278ab0d7315e968 R-Type1af18369c1237f96b8a54c515f5c204b37f54749 R-Type 1046 MSX IREM 1988 JP GoodMSXR-Type37bd4680a36c3a1a078e2bc47b631d858d9296b8MSX1 Mode R-Type9b6cb224f2a7a4af69221f1280d3344a5c88450e R-Type9c886fff02779267041efe45dadefc5fd7f4b9a2Version 2 R-Type0b379610cb7085005a56b24a0890b79dd5a7e817Version 2 R-Type Enhancement Patch (ROM add-on) MSX BiFi Productions 2010 NL Author5d1a8d29debb38ced76d9944c5b939c9edd6dd72Bifi's R-Type Rom add on Raid On Bungeling Bay 599 MSX Brøderbund Software 1984 US GoodMSX636eb837c5d80fb6b0692a72954ca91291ba831b Rainbow Stories 1952 MSX Al Alamiah 1986 KW 108abde71e59839fb4d1e59c3a0cb2366220030c 8f3ec8301e9d21a937cc4f3cbc15b07ce5491d2f Normal492b186fd0d36fd81dae64e4863262e666c58b5e Rally-X 355 MSX NAMCO 1984 JP 4077079ddebb6b78142715c2840cfe15c7e9f539[b] GoodMSXfc7212c6813574224114c3bb5bfa99206c0816a5 GoodMSX3dcecd610e832e7769547d3b6ce2b767f65f3563[a1] GoodMSXc5af6c4255bab55d68e82fa016cb35e3213ba244[a2] e5de6e75ab06822aa10263e971b2473a3b573124 Normal9e06939700ebfebc28d5fb8837a84da5c89fcf2a c0a20a9484d0758eb93c31f570f1f83b3bb3b792 KonamiSCC5ad581075397ea7de010b1cd8475d45fd7738195GDX SCC Version Rambo 676 MSX Pack-In-Video 1985 JP GoodMSX770805bd0be8b09a3be10030060d6ad8dc07d1c3 GoodMSX4334acc2bd5c921bc4dcb6de9a1fcc79d843ee15[a1] ac381dfb4d0b52dedb18a8df7a4651ed2978d12f[a] fe1d02284b0b051308e3c796e8e9eee2f86521b8[a2] 1efcf9e9a8ea38bad319a70a5f7319e2589d4a8bhacked by RicBit / hacked by RicBit 1a90b7c7f24bb7fa93c84bde2d1b655e30c4a7cbhacked by RicBit / h RicBit KonamiSCC441dacb74a47ca4b7d1ce47300d182bc7424ffacAlternative Intro Rapid Burst MSX Karoshi Corporation 2004 ES Author57cdb8c1a9ba733cf62dbe00333aa0343297c76funfinished game Author2bea430e0a74ea88c7d49fe1dd1fc5a8bb0dc7b0unfinished game (PSG Music) AuthorKonamiSCC2e4c9e5a8fbdc747122ff2991a557042fc5d2ad8unfinished game (SCC Music) Rastan Saga 1231 MSX TAITO 1988 JP GoodMSXASCII8f787271597abbcb1297e4e8a4a9561959a195649 ASCII8c08a3c1478e51db62624814acbf36c198d6561e6Ulver crack GenericKonamib81dd702afaaa5946e4ec065042c891ad2f76c2b RatBox MSX AG Software 2010 IT Author0x8000Normalc3937d6f0b4f1ab6f4aaa227402d49eb37babfbe c08d49f2b30dd035ae8cc2a93c03fc3d2e953809 Real Tennis 86 MSX Takara 1983 JP GoodMSX54a49a3c8b42b8b246e62ed45346cf52836ddcac c964e2a3b63e1261c1daec4b4d8bb8e74018e0be Real Time Sequencer DMS-01 MSX Digital Music Systems 1985 GB 5e67916f671ba197066048630e3b9117e8534432 e0abd248bc6f775ba5fc373ba56a6f9d59459f25 Red Alert ! MSX WYZ 2008 ES Authorc7df1bc6e115a39c850d0d59cfba8c17407c476f Red Zone 682 MSX Yellow Horn 1985 JP GoodMSX61e26157f0701cce2ddb860a12f187912398b1b4 91e17aba249925c7e0e5c280469bef09d5534da8[a] adb5a4c5e80d1eb2ab5035c4e229ab1bf81691aa Relics - MSX1 Version 1041 MSX Bothtec 1986 JP GoodMSXASCII874ae85d44cb8ef1bae428c90200cb74be6d56d3a Relics - MSX2 Version 877 MSX Bothtec 1986 JP GoodMSXASCII802c3ec94190c6fce6fa4984d92be48b2697e7706 ASCII84bd533cd7859489a3b26787fb5c7e4d8662cc420 GenericKonamib5749b4cac530e261da9acc0d494a81766d2acc3[a2] Renju & Ojama Dogs 683 MSX Pony Canyon 1985 JP GoodMSX0x8000Normalc72af5118835e49ac6d181cc832336ab22ec5dcb Replicart 1040 MSX Sony 1987 JP GoodMSXASCII8afb693fd0a138cfc59a2bf81eabd7ca2abd4e162 ASCII881bbbd4ea86350ebba03e8d8cb29f050a3d93a83 Retaliot MSX Video Hazard 2009 ES AuthorKonamie27b938a60081c8aba7ad5741aea79f8e33fa8e2MSXDev09 Entry Return of Ishtar, The 1061 MSX NAMCO 1988 JP GoodMSXASCII16d17acc97bf2f853a5fff58da733e4cd53cdaa3ed Return of Jelda 1035 MSX Softmen 1987 KR GoodMSXASCII859f679c5ebf75de98b5e909da9182dbd80413364 Konamia80b5426fd0f4ab19eba1c06620f5efb8fbade3a Revenge Of The Boing Ball MSX R.Bittencourt 2004 BR 0f1c600fc3c3f7ef390214ee8028ce118d2cf5b8[NEW] Rice 1376 MSX Konami 1989 JP KonamiSCC2f6e0a02fcc3d368db142ac837304c18c5226e2f[SCC] scc+4524768e575298cee3b7eee09b3caab784ca3bb7[SD Snatcher RAM SCC+] scc+e9b5b79f434b1c5bdb47f3ffece3ad42e489e20b[The Snatcher RAM SCC+] Rice 2 1376 MSX Konami 1989 JP KonamiSCC3f20b96149f58b86d1f0e79483d5ddf402191ec9[SCC] scc+0a0203d69b68e1c636fdb78934ae7fb5b4d88179[SD Snatcher RAM SCC+] scc+04a564958ea71653f87a8aa51b84aa567c73a8bd[The Snatcher RAM SCC+] Rick & Mick's Adventure 1036 MSX Humming Bird Soft 1987 JP GoodMSXASCII8a57f8ec4f89ab84936f46821969de6bca9f46d8e Konamid3a936e5ef24fdbc7a02a81644ab6cd4b88119d3 Rise Out From Dungeons 353 MSX ASCII 1983 JP f02ca47be665e83d812979d8074a985e5637b979[a] GoodMSX5fbc5d6a01e66a1c456b82ac1190ccde61bc7832 3242e7b1320aa1c69c58faded826ab0fae5ad970Ulver crack da55d2b5de5d6b04a55d901911c9fa7776fd0159 a1d4461adf060d685c4a7e4201a38b5e24a03deeUlver crack 8891e3e3148bb0fe4e91e322ba709d2f95285d1e River MSX Darkstone 2008 NL Author94d7a756e199457ea9958c8f5d6860c9bf334126 AuthorKonamiSCC9a611007b58a001639676728614ffea61cd5ff85 9c371ad2c7b13f20e41d555914982aef660f75e9 River Raid 356 MSX Activision 1984 US GoodMSXa1e14912d45944b9a6baef1d4d3a04c1ae8df923 6d3d2cad90f3e0389b11780e1b43a728216d3c7b 33be9017faf173eae04d0c91ca8d42d1c20596c0 67f68c72c3ac65e2c56f9fc6da28f2fe0803eeb5 6f03ce3a51047ea3edc8e731da9e175096023bd6 c0eea1f6a19deebc3d53da8f8cd4d0184d236d18 Road Fighter 684 MSX Konami 1985 JP GoodMSXbf4a0bab1e8eaa70a8b12cd9a81d7a90a74c9e26[RC-730] bd865b52e80ae713f34e1d2137553011d0983e36[RC-730] / Ulver crack 7abdf08e9c0a511b8182c502ed8c5f42778437e9[a][RC-730] scc+9a5e3433c5d82f2166969162bb822a965c741ce5[SD Snatcher RAM SCC+][RC-730] scc+cc15f65d607a573e0bcd23abe9d0d825e05deb10[The Snatcher RAM SCC+][RC-730] 5c947b82cf81a03674671b663697c9b79546c1eeKonami Antiques MSX Collection 1 Robofrog 2969 MSX Mass Tael 1985 HK acce81bf851fa5869cd75181df4fcf7d4489a9db a2f9e1250c67d2e98be70900d4370545321cdbfe[a] Robots MSX German Gomez Herrera 2008 ES AuthorKonamiSCCb7f36450d1ac76834046fd582d6e77a6d3881a8c AuthorKonamiSCCc960c1b895557ef1b336035029d5c6d553c64085 Robots MSX Michel d'Alger 2011 EU Authorc1577863c6ac7aab87cc0c2d64cc0577e2e5e360MSXDev11 Entry Robowres 2001 1043 MSX Micronet 1987 JP GoodMSXASCII1646c98a3143f5a80b2090311a770f9b73000881c0 Rock'n Roller MSX Kaeru Soft 1988 JP Author25cc9638db16587a182534122d1c6b98ac321e2b Rockn' Bolt 687 MSX Activision 1985 US GoodMSX1edabc3226648b54ae98d524b31f37ca47c8c88b Roger Rubbish 2070 MSX Spectravideo 1984 UK 8607b92458584d5aa770369d94d50361b1d64bd3 1500a07693a7c06189f4cd6764d5ee62f0edd85d[a] Rogo RobotArm SVI-2000 MSX SVI 1986 US 7e830246e183665d02ac04478fe6f590d330be4d b60dea0a4256869a1cd0d1044cde42697852552e Roller Ball 360 MSX HAL Laboratory 1984 JP GenericKonamif6e0dff9f8674383a866902fddb72a280d331d9d[a2] ff9877666d983759d41398c5e3e29801ad95a589 3325a55dc5cb88f79d3a25dc21521d6124a6a90b[b] GoodMSXNormal44baa180f6c9e0f140ac1f0afae75c412cb06b9e Rotating MSX MSX WYZ 2004 ES 495ae7af3d299b9625cdd79a2ee41cde5b328362 Rotors 357 MSX ASCII 1984 JP GoodMSX0x8000Normal34544106fbd29b2a72e1e8c2e0f260b79a0b0883 Royal Blood 1543 MSX KOEI 1991 JP GoodMSXKoeiSRAM32522a53034cf40448d441683f3308fcb4b65e6d7c RSC Ensemblador 3338 MSX Manhattan Transfer 19xx ES 993ba63b06e855ba7294bea25b4250bf2a63fab5 877e8a60ee1658634124db023b50bf5dfa93624b Running Naked in a field of flowers MSX Infinite 2006 NL Authorcb325fafed4b6604a7146bca14109a29443b0c7fMSX-DEV06 v1 Author90acaacc7c64d6148d723f0f46c40ca06e87ca4aMSX-Dev06 v2 d98ab2e25b738f956b4240a96360b88ab6f72093 RX Editor YRM-302 363 MSX YAMAHA 1986 JP e03ec3a6bb6d8b1bc6eab349c7675cb72aa47d57 Saimazoom MSX Karoshi Corporation 2005 ES Author0x4000Normalf448289db0d5c8fe6c4b3ca863af73db3c4a5e45Spanish / MSX-DEV05 Author0x4000Normalfa29ca4486a089e32790a69a9598b051d6b7dac4English / MSX-DEV05 Sakhr Dictionary 1953 MSX Al Alamiah 1987 KW ASCII864c49fdabbdd5e13c4310b03a183ea1a018154a4 e99855e38e3568d6657063c02fe7b64f3b9dae52 Sakhr Logo 1955 MSX Al Alamiah 1987 KW f2b5b9029575b85f0730914a44e57f94c9313df5 a2901318f8216ccd2f4d08b0f272e1bf90ab197a KonamiSCC1e75e859ab01af1b01560520c22bacba86d202d7 Salamander - Operation X 941 MSX Konami 1987 JP ASCII80d76a069726fec7326541f75b809b8b72148ed3a[RC-758] / Needs SCC in slot 2 GoodMSXKonamiSCC0d459788b6c464b50cbc2436e67a2cef248e0c4a[RC-758] KonamiSCC655b15e8fd81866492bcf2b1d6609211b30efce1[RC-758] / cheat version KonamiSCCd30c17109fa1c4a81e39dca57b79464b0aa0b7c2[RC-758] KonamiSCCdf15daf1e68e9c8cd95c4431803d86fe6c2f985e[RC-758] / Ulver crack KonamiSCC0b2538fa04604b0f55b49207ab41e29413830403[RC-758] / Ripple Fix+Trainer KonamiSCC2530570394b17c895f1860c3a87a6cf1c6e93a72[RC-758] / Ripple Fix KonamiSCC764e74c1cc48cc96f00c501994b8e881370fdb59[RC-758] / Trainer KonamiSCC7ca40f36821dbc99ca326fbfda200a1d42dabce6[RC-758] / FRS dynamic Vsync+trainer KonamiSCC96509c8824cbf95843418f3b55f4abf8125e7e57[RC-758] / FRS dynamic Vsync+Ripple Fix+Trainer KonamiSCC41e456910399d4c31894b533c0b2c6ebeac3a47e[RC-758] / FRS dynamic Vsync+Ripple Fix KonamiSCC24f88556ec603e258e6047dd166d9350f53608b2[RC-758] / FRS dynamic Vsync ASCII83b1fc45b04bd03f21d5b88330a0c5d16a9bd2d65[RC-758] KonamiSCCa676173780dc3ef4523ff3123fdf179279d22932[RC-758] KonamiSCCa1830ad66feb55f471d52210c6f07937f38238fe[RC-758] KonamiSCCc19f67882b2586c4ba577ffc5d04197e757e19a5[RC-758] KonamiSCC5261006ca4245653f5153074dfcc8ca27315bb59[RC-758] ASCII85dbb0bc6b9baac9873034c342c5717d2b7fb1ba0 Sangokushi 1 - Romance Of Three Kingdoms - MSX1 Version 757 MSX KOEI 1986 JP GoodMSXASCII814ff6fe464362c6b7dbb47b2ecda3a8c5f05ef79 GoodMSXASCII83f741ba2ab08c5e9fb658882b36b8e3d01682f58[a1] Konami96e2a7163c755fbea77abfd9d09a798687a5a993[Kanji] translatedKonami7ad40ae512bbf4ba688467ba23e16354b8421d0a[tr En] Sangokushi 1 - Romance Of Three Kingdoms - MSX2 Version 942 MSX KOEI 1988 JP GoodMSXKoeiSRAM32dab5a9f93a35fcb92c01e4250b01928fe8bd294b Sangokushi 2 1400 MSX KOEI 1991 JP GoodMSXKoeiSRAM3225ffc7e8ca90274566c31e10b5998cca6522673a 8d787805785a8a259429d5c5573b768e01374d05 f00cddcf86e4d3a612434260f695ac33469bde44 Sasa 163 MSX Mass Tael 1983 HK 395c5f40692c45b891abe07c89d1e87829665fc7 GoodMSXe767c0239b79821463fa42f2c1989d3fd1b4b868 Satujinkurakubu - Murder Club 1125 MSX Microcabin 1988 JP GoodMSXASCII8c1923ab5c5dcaf67af3e23fd1ac42e2d4c61aca7 d0d419359c20ab1d901b75bf4714b0701ca755f5 Saurus Land 161 MSX Nippon Columbia / Colpax / Universal 1982 JP 1a6de1cff831429394dc2b9528993a1dfcae14c6 94913881fb025235eb5a27ca95a5a36f7dde4577 7335e5defd4d4f149f648bc13012109937fba21c Scarlet 7 773 MSX Soft Pro 1986 JP GoodMSXbc3be8f829211dd8a8eb854f86ff014c47ed9254 73f2ddb61f15fcbd143e20e3520df8d73e98dedb 1709541ac30634323392e5098d47d1f644dbfc09[b] 817bebb1be373e9c1bae4850a2bc1daec5586a91Crack FM SCC fake cartridge MSX Konami 2003 JP KonamiSCCfd0e46e4aef2d9713d20b51d2ba57d55bde7dcee SCC PCM Demo 1 MSX Manuel Pazos 2007 ES AuthorKonamiSCC84831671935bf80c250aff223a99684669a0ce72 SCC PCM Demo 2 MSX Manuel Pazos 2007 ES AuthorKonamiSCC1ec94d049fcbf59c5493d5de76bdfb891d24249e SCC PCM Replayer 1 MSX Artrag 2007 IT AuthorKonamiSCC54a1d356fd7bc6098f5c3d94f9d8bfcc2c6d5935 SCC PCM Replayer 2 MSX Artrag 2008 IT AuthorKonamiSCC0b3f5dbdbf5636e22b3a6bc12eeb4ac64b0501e5 SCC+ fake cartridge MSX Konami 2003 JP KonamiSCC3b4143e5769d6235bca8977b41241295493eaf5e 33dd59c9bbd2aa19affdab3bc2dd1ff662cbc8b0 Scion 484 MSX Seibu Denshi 1985 JP 67a0ba97e4dbec6b53694548fdadb1a34c15dfc9[b][o] GoodMSX09fbbd923e0deb82f5e20fa8ce2d842adbf4a646 ca0f156e66016c72681754d7d9a6da5685e48247 Normalee56f26d638a8f0fbdfa1ade83b470d997b96f3f Scope On 189 MSX ASCII 1983 JP GoodMSX0x8000Normalc73d4c27aa2b0fb946e398323bd67d9e53c4f9ef 752bc4b53600d5706269ad02b1b462c3e9c2bd3a Scramble Eggs 40 MSX Ample Software 1983 JP GoodMSX05182561e05fa8e7c8a8184eda885dc4fd12cc5b 5e9cd697ae2c85aa29eb5eceae7298174c4b3c9d[b][o] aa9f3ffcc977fbcc04a0326d5a7371d372d099f5[o] 5baac8cd6d119ef5dc7ddacddc3103a4eda2943bKorean Dump Scramble Formation 956 MSX TAITO 1987 JP GoodMSXASCII824384e63bfe450bce6abd327eae3e27969025379 Konami3f9d72bdafecfa640639cf66721e7380e41c6e6d[a] Konamic5b4cd4b49144e8f77455e9ff9077ff840ab278cUlver crack Screen 5 MSX Yukio 2009 BR 3535be7d6d2a5d3c8048bca752e6adcd52a994d6 Screen Copy MSX Epson 1985 JP 033f5c35c5ddce6cef8b53faac2edd85047e5805 Sea Horse 1956 MSX Al Alamiah 1984 KW 1b5b269bc0c905aff94ebea598a936e34a74fb07 Sea Hunter 2072 MSX Spectravideo 1984 UK c1d272645a249757b8be5bd913ba2d3447892387 Sebaweh 1 1957 MSX Al Alamiah 1985 KW d0cd48404eec5975d0f284c46b3cd710236b87a5 Sebaweh 2 1958 MSX Al Alamiah 1985 KW bbabc8ce1e39d4405dee587737e4dea24477c24d Secret Word 1959 MSX Al Alamiah 1985 KW fdc3cc10b4821ea5169c817fcbd6e99fac464feb Seiken Acho - Kung-Fu Master Taekwondo 531 MSX IREM / ASCII 1985 JP GoodMSX530ecff542a537c320b17bd5d64c86d5d65619f3 da95bcd1f0c28683cfb4a7a2c10599261dca3622 b660a4e2f45477a6b62201ca2ac9cd36748c18d9[b] fc22cec076c0a0a1ee7e410772af3d7d31de3149 translatedd927dce3ca188da733889e4141aa526b9a10e635[tr Sp EngeSoft] ASCII8bfbd5cddc38b5f65543f41562a6be749beae9516 Seikima 2 Special 3398 MSX Tetsuji 1987 JP GoodMSXASCII8c74a5a6164536750d6b3ea90e19798ab6fad79d7 ASCII8510f6d854f813269f67ee15a4cc2ed77d96d2903 Konamia5f3ada0610ad32f59d2f114167612d4f62bec4e GenericKonamid076d7185078b8adddbacfee7e46c61e3e02fe7eUlver crack Seleniak MSX Mark II 2004 ES c48f395060d919a13b3dfdb0d89cea9e32fc8f10MSX-DEV06 Author1eea2cae2bb3401d6734b2001f6b551628a99101MSX-DEV04 Senjyo 202 MSX Tehkan 1984 JP GoodMSXe373e3e9be4f6d30d5db84b699082c00b2021ba3 4936916bf914d99c5b9e3f4c7385f44b6b97d4c5[a] Senjyo no Ookami - Commando 778 MSX Capcom 1985 JP GoodMSXASCII82b255c3e5615b2f5e2365419a35e4115a060e93c 7c811f87fc2c61b5622b75ffa37fa43b9f2a1503 Sets 1960 MSX Al Alamiah 1985 KW 98f3eec607cae92e01c1310f25d980f2859b88cd 899af4c7958de89f2e542e2e459475e69db57054 Sewer Sam 183 MSX Interphase 1984 JP GoodMSXdb0ed31c779fffd2084946e3ba69512b372d845f Shanghai 1134 MSX System Soft 1987 JP GoodMSXASCII1677d95640c80bc4868d04a913f9dbf4b5f6d877d2 6be1ee2b07dbb3e08fccfe6f1b37adfeb742bd23 Shater Hassan 1867 MSX Barq 1987 KW a4d4a6fc06739c543ff0767a1006ae2860d9b635 Shift MSX Infinite 2009 NL AuthorKonamidbcfa55870a267112c9a71bcf85b481b11d98e04Passion MSX2 Contest #1 AuthorKonamiee095ee6361f0a767250fe19c9bc805bba8da4e9 Shigun 950 MSX Nippon Dexter 1987 JP GoodMSXASCII8SRAM816351e6a7d7fc38c23b54ee15ac6f0275621ba96 SHM 3490 MSX NAMCO 1989 JP ASCII8cadb57400c45135f15b2709ddcc897aedfb7aec3 GenericKonamic74325fbeaa7293b46766c99a2dd8bd57db3a7e9 SHMUP! MSX Imanok 2013 ES Author87694fc3e9be68a46c7e32ae643634e8c7b1000bMSXDev13 Entry Shouganai MSX Paxanga Soft 2013 ES Author1d798b8bba3beca92949170741ea34f4b166d067MSXDev13 Entry Shougi Meijin 510 MSX Soft Pro 1985 JP GoodMSXf9ad0cdb227c588b1a5c75220ed53d0314550d7f Shougi Sinan 1 508 MSX Microcabin 1985 JP GoodMSX0e06519e68c78a8f018152511f4f13374c5856aa GoodMSX83df4081529fe5b8bb0a3601614d36c8384c9202 GoodMSX1c4de8d4a1cbf5aa2bcf7816a1e55ee4584f59fb Shougi Sinan 2 1831 MSX Pony Canyon 1988 JP GoodMSXASCII8SRAM839d500fbf22168be2a12ac89ff5a50131bb1c30b 2d1f8f790cbddd060855ae9a3a94a17aa309eaa4 Shout Match 944 MSX Fun Project 1987 JP GoodMSXd844c4c5ae9c7ac671c4be71ac1190a1e5db4d36 2a9430fc0ca601cca12f860cecf9fee32e2a9ff4 Sideview Attackers MSX N.Komabune 1994 JP a034d59a9f66f996af90e37245de5a475b6db0bd Simple ASM 1.0 178 MSX Coral Sea 1984 JP GoodMSX4937a8e35072012a980f85ecfc60604d9d900244Tip : CALL START or CALL EDT 938602b65bb04b941d67c453a948e1f18bbbf5acTip : CALL START or CALL EDT Simple ASM 3.0 MSX Unknown 1988 91e522473a8470511584df3ee5b325ea5e2b81ef f40e43e321c9c0f6c17835c587c62981efbe0392 Sinbad 769 MSX Casio 1986 JP 0x4000Normal3c0263c152e0feaa93cb15bfa04eea61acb5236d[b] GoodMSXd120ca303c3e1dd56adee68f10e9e1ab51f50ab3 Sink King MSX Unknown 2004 fc6292d49736e4f986d2f44eae19a2e0e3185f45Guzuta Raster Leisure Sirius' Squish'em 186 MSX Sirius / ASCII 1983 JP GoodMSX7bf7bd134ddf9ed5b489f7f2eb0aa26bb2428fc9 364ae6695509f5cf16985cc2946a45af5ca4cf56 6e0226e3a5aced55e7aa18b0454b4a282ac4a92d Sirius' Turmoil 206 MSX Sirius / ASCII 1983 JP GoodMSX6b496a50626c3e67dfe49c15a3847448dbeb05a8 8e413a3be17e3f0e4304219a413a5eb04f2b3a04 ASCII8SRAM88c810cc7e5772ab6b780a9493817186ca57117b0 Siryousensen - War Of The Dead 951 MSX Victor 1987 JP GoodMSXASCII8SRAM83194153a9c93aa24b37804a764f112bdef091bf8 Konamieca36fe80a916eb6b196ec56edfc1296d793e350 Konamid452a79b25f18e6a6eadc5c592d5101f115ef0f0[a2] GenericKonamiebed19269b7625c93eccefc2bd758555e2027431 Skate Air MSX Yermani Soft 2006 ES AuthorASCII8e6ba7a6ecda80a1c844542be0b2a44a4a33dc113(Espaniol) / MSX-DEV06 AuthorASCII87a0a1c5570a3361ad75406eea07c93fdad9fde02(English) / MSX-DEV06 AuthorASCII8e7351ef9606bd6d5f38acb7c17aed8a00170b85dMSX-DEV06 AuthorASCII86715aea31525869220d7acb638b6a432875e85a1MSX-DEV06 2987a8f303bec41e4e9cf60f8f40a0b9227eba58 bc1cc3c8f8acf845099b6f6f02c0f6f2d9174b00 Ski Command 523 MSX Casio 1984 JP fe53d85adb758103097fd56132e71c7f4cb32313 616c3b9a0c185c1aa683afa53989f0bb39cd768c[t] GoodMSX17d6db13d7d76fbd110f53b168ab0001c7e65817 3828bf9e72aeaf78ff0281a6b1f7fa440a370cdd 8c317e50e0aa75dfaa374412ce9866d16c0214fd 18419523962b57f1ff49d62182f50882d6c42228 d11c9887399055b1b189b4cd00e8141ee0307335 Skooter 1141 MSX The Bytebusters 1987 NL 4b986d80fc0d4ad3b2e2d0fa77c743bb671bc5bb[b] d264152d4974b78959465acfe0d5678e72930073 GoodMSX64f1ce4691056047f404460031acb0b82a76968d Sky Galdo 774 MSX MagicalZoo 1986 JP GoodMSX0x4000Normal136d838cc1297940d96cd8e27aea9f9e3c7088a6 0x4000Normal72845ccc50f714562ddb87f978c99b4076aa120a 0x4000Normal12f7eafa2e41135173dafed907c8643cf5298d31[h Sammy] 42b0405f6f986010ce16f33a33c3455fa7dd4db7 784a0a260ebf2e285f908fe7336fbf33646deca7 b737fa701906119eb61a567c54dd87af09e8b34c 69ea6b15eeb313e3baa58bece31adff1e3211842 Sky Jaguar 522 MSX Konami 1984 JP GoodMSX1f8334dc7459cfcf2ad132e94976015c02e51390[RC-721] e8959529a71ce9d580766dd2074b2c17cde34c2d[RC-721] 5398bfcc6bf60d393755a0edc15f3b9c14180b47[RC-721] 0d53c456603a56b39392b203ad75323357895381[RC-721] / Ulver crack scc+ac4facb517e2383b4cb22504cda961b15af0a83e[RC-721] / [SD Snatcher RAM SCC+] c55b0daf698d93b8278a9d89f38272064d1594bd[RC-721] / Konami Antiques MSX Collection 1 e8d2f130b0937d1baced5a3996b78f163d4fa70a[RC-721] Slapshot 2203 MSX Indescomp 1985 ES e3a52d448e69d6d14f67d35f24d9924158b1cce1 e8dd2edf5b0db48eee729988731d11b167fe1dd3[h Piratage] Slender - The Camping MSX Pentacour 2013 ES Author1e5f04e72c092140d69c29a9e2826d7326f8ea54MSXDev13 Entry Smurf Rescue In Gargamel’s Castle MSX Coleco 1982 US KonamiSCC2b6a97ff2d0c177093a90ac93d794b671dfaecb8GDX Conversion KonamiSCCcef24c1999a66608c44f3f647e57bad8dcfed6c2GDX Conversion KonamiSCC0dcf1d0c41da3a85d2053825139546697aaf6d80GDX Conversion Snail Maze MSX Karoshi Corporation 2004 ES Authord81a4fee5bff14eda613fb72bc78429874bbb18fBeta 1 Version Authorcf93e1f790c4bdf50b41ec0537dfb97ed0d11b21Beta 2 Version Author5db0acc008672f298d01686da4d56428b690b77fFinal Version Snake-It 960 MSX The Bytebusters 1986 NL GoodMSX7300d44a753dacf582d3079ed09eafc9dd629530 Snoopy Town MSX AG Software 2005 IT AuthorNormale168d460c2acc0f25523e2b99861d187d1aaac00MSX-DEV05 Author0x8000Normalb9aad68e2338ab1814f2dac78b2c4b5877d29ceeMSX-DEV05 Snow Climber MSX DVIK & Joyrex productions 2008 US Author5dbff5d70978482fd5b79c02715bec1ed463e6bf2kbos entry Snowclimber MSX Joyrex 2003 NL ae62d7cd44e6a85ee6d30a319ef5e3476f784148 Sofia 1151 MSX Radio Wave Newspaper Publisher Company 1987 JP GoodMSXASCII85a12439f74ca5d1c2664f01ff6a8302d3ce907a8 ASCII83caeec19423a960d98e4719b301a3d276339e5ae[o] Sokoban 203 MSX ASCII/Thinking Rabbit 1988 JP Normalf35a3f1f09b018957696e015b20dc1d12d3ccc2e Solitario MSX Nitrofurano 2002 PT Normal45e72004565ea1af837f4f181d7da215ca81a965 Sony Test Cartridge MSX Sony 19xx JP dee167d86c9d9d435b1410804055b561ffa2c6e3 c7e34e418b9e852019264527bb7e8de07e76a7cc Soukoban MSX Qnix 1984 KR 6ec24f574a8e40d8785b3e721b1605e1984c5834 Soukoban 203 MSX ASCII/Thinking Rabbit 1984 JP cf55b0d12e550f71cef709358bc7e799eced0083[a][Kanji] 2338946a671d31d12360ace4eeb33f7bc7db56f1[Kanji] GoodMSX04bd78730b100dd879e2fbd3fe646d2bc9bbc46b 5cacfb40c99a9e66e068a15b5d22cbc7ab90cb65Korean Dump Soukoban Pocket Edition MSX Karoshi Corporation 2004 ES Author0306181bb8babbc215e8c5066c1f490719541665 Sp8 Invaders MSX Dioniso 2009 ES Author78e833c0a94478b9c07e974f311dbfef1e014f1cMSX-DEV08 Space Arithmetic 1964 MSX Al Alamiah 1985 KW a25f9b813c4488c73c5f54055748b4dbb057c2e9 Space Buster 2812 MSX Aackosoft/The Bytebusters 1985 NL ASCII8d554710317482127f2e1597e91e72be5ee0726ce[b] c3629b8c32e661e21abeee19825e65ee4da1eb01 Space Camp 776 MSX Pack-In-Video 1986 JP GoodMSXde10aa2a2abfdcafe0064e1e76b676f54c7aafc6 d1ad3beb29aecaa7c3bc1fd8a4be3b10d38cc47a Space Crussader MSX Yermani Soft 2005 ES Author0x4000Normala21d354ddda95975d737746d27fd59cb908ddf0aEnglish / MSX-DEV05 Author0x4000Normal0856cba14ff2396ea257ff927da0767ae74084c5Spanish / MSX-DEV05 Author0x4000Normal5bff56d5671d1131056451025b20c1ce192e279bEnglish Version / MSX-DEV05 Author0x4000Normal96e1f8fa1f4f3fdfa999f6a999d60d75b590f070Spanish Version / MSX-DEV05 Space Invader 530 MSX TAITO 1985 JP GoodMSX4971bdd3db63d394fbc2186182c901ca4b32535b Space Manbow 1238 MSX Konami 1989 JP ASCII8e388e8d8546e329d2c58269d4fa026624a640f1b[RC-768] / Needs SCC in slot 2 GoodMSXKonamiSCCf6199f48ff994fc9a8e33a8581bb3bb16dd301ab[RC-768] KonamiSCC9447e50d1266b7cb96a414eaf9ee16c0f99e55f3[RC-768] KonamiSCCad1081f398ac69c0c92e4afadde482574fe875bb[RC-768] / [t] KonamiSCC17583adb63d16d64ae51820055cd32c0e935425a[RC-768] / Ulver crack KonamiSCC92edf9bd66c229758420cbc6ffc4d635e0c620d0[RC-768] / FRS Turbo Fix v1.0 KonamiSCC1d995cf4521da949072344e8185c44744711f472[RC-768] / FRS Turbo Fix v1.1 Space Maze Attack 43 MSX HAL Laboratory 1983 JP fbbd9a77004f9bdbcfc292f85c32106faa18cfa6 a00f9e66f1fa3de15b028e0fbfed636e194bb967[a] 6580f0c72bac8d2e2ca14b9676452184bddd952e[o] GoodMSX93bf6725a3c1b7071064a01884e1b6e8c6f8b579 Space Scape ! MSX FarAwayCorporation 2008 ES AuthorKonamiSCCfe39af5eb457bba8c5b96b5b500120cfc05c265e 71b90922b6a7e8d1f01ad883de9eb9d1db9630d2 Space Trouble 193 MSX HAL Laboratory 1984 JP fb837d3aa087a75c7064c0808adf98329387cbc6[o] GoodMSX7c0f720c7601ca6dc86c81ab6a76e0587a052f75 2b33dc1268204188d53045b5e10845960b291b25 Space Walk 2737 MSX Mastertronic 1987 UK fff31b8c7549493d8ec220f85e3e8536f150d82a Space Way MSX German Gomez Herrera 2008 ES AuthorKonamiSCC0809b47cf385328c3faa6a80f8378fa5c6690829 Sparkie 192 MSX Konami 1983 JP 4b996bddf88605c432f73564922106a63d31f62c e5480a5a41ce4b8b0706632ea92d29018416dd73[o] e0e8ed637f5d220f881cc9b52bc52e9dd3373489[o2] GoodMSXb9c98b80216983f97e28293b576ca9be3602c1a1 5cc22b5cea25b1b00f6e32bcb58fbd49e7aeb641 Special Screen Mode MSX Daniel Vik 2006 US Author0303ca82776894ec8c6597e5c29afc4a605b2a59 17c5feed40350527ad5d80b4c8683dd75a514f05 Spelunker 777 MSX IREM 1986 JP 4d80fae5133d55503968ee246c1346cddb020ffc[a] GoodMSX49e14b0248c649e7b5754357597695abef70c17d Sphere Redux MSX Impulse9 2007 ES AuthorASCII822a927a7bc70a36379404743a3fde5df8ca543ceMSX-DEV07 (Canceled) f998ac478c3cc0be4e8b375541c2376e804a021f Spider, The 165 MSX Hudson Soft / Japanese Softbank 1983 JP 97f1790146116198133c083de113ed1e5ca6ff21 Sport Racer MSX German Gomez Herrera 2006 ES Authord07af8022481bed2523e0e9eec67bf8ce64f9142 Spread Sheet 3469 MSX Kuma Computers 1984 UK 33c62e6f7478be369580a2d985ff57667f13cf84 Spy Vs Spy II 2483 MSX First Star Software 1986 UK Konami1d129bde09c9db2433b334d36762704de94983ba Square Dancer 188 MSX T&ESOFT 1984 JP GoodMSX18b90aaa2284eb46233e9203af1e8cf1922c417c Stan, the dreamer MSX The Pets Mode 2013 ES Author933bc34f20d5740cac949cd7257b44974199d9ebMSXDev13 Entry Star Battle MSX German Gomez Herrera 2008 ES AuthorKonamiSCC72afbebb12098ade1ec26dcd65890b60b2c705d0 Star Blazer 526 MSX Starcraft 1985 JP b07a6ae619fcc6291838959dc1a899de832f8d97[a] a1e1bffe4b4ea6d67c973fa483c4d6273f68de3c 94ae056895c6882704bddade105cc3433110656d[b] GoodMSXeda9580a1ac75707f621c251a7424df3846e4ec0 Star Command 41 MSX ASCII 1983 JP GoodMSX0x8000Normal225894da7e3afabf33abf05e519e1f6bcac77a21 c0b9231c883d91feb6a76171a6faab26176080ec Star Force 525 MSX Tehkan 1985 JP GoodMSXc1746efeb4b44dcc80dfc4f429009feac91d9c48 15193ca779ed9daf6950da70c155d7ca94767383Ulver crack 5fe86465119343804a722ce4768eb33de806a39f Star Soldier 775 MSX Hudson Soft 1986 JP b58caf47c953b6f00935a201bfccf7ee968dff32 GoodMSX6a56b46b8bf014b25863433d6d96e64641a93eef 7ae260d769a1e12b8f7af09a17876027a423e926[cr Turbo Soft] Star Trap 957 MSX Jast 1987 JP GoodMSXb2bcc1c0d64b6ee9f0e9bbbcf0ef7b64d80d3b90 Star Virgin 1143 MSX Pony Canyon 1988 JP GoodMSXASCII82fc050dbc44ba9dfb72299129d48e5e5e1c892c3 ASCII82bfa08db95502ee9bde61c5fe7b6557c0e4aef5a Starship Simulator 190 MSX NEXA 1984 JP e3ce88e95115765e31193777e57fcb9b40e10d3a GoodMSX0e5062bb18b4a6fb93a89318772928874dd4d6ea Step Up 42 MSX Marvel Soft/Takara 1983 JP GoodMSXfc5d252a26c93dc4cc30984b954a6a5124dee653[a1] GoodMSX27ce427eb1c86ba22707a6ee9086db9d39962dc6 480d85e5325c8902966fdf226868eafc62de4663 2ebd7b784094097d9f0fbebe4e69be8ba9a08a5c[o] b3370cdf4707d9c38afc0f42c11cba5e745ead6a 601da2148d3422da717cc385659fa0a5ab075618 Stepper 527 MSX ASCII 1985 JP GoodMSX1fa7398005fb0bd7075141035e8d888197162030 Stone of Wisdom, The 747 MSX Casio 1986 JP GoodMSX3950f22265139a84cf57f3c0936e7927173f3d7a translated214cedb8f4689da150878ca6f4fcac31359c9766[tr Kr] d56a4752e3e1b2f05dbe982632cecd15a6f8a8f8 Strange Loop 959 MSX Nippon Dexter 1987 JP GoodMSX3063bc3ac5af6d990d8d002acbc9c3a18bab71f6 MSXDOS258178e52855ce5ea365d8ce68c2020918e7513d4 Strategic Mars 1833 MSX dB-SOFT 1989 JP GoodMSXASCII16dc56cff1211bacd94f59e188e44a58d6f40a0c26 Stratos MSX CEZ Games Studio 2004 ES Author9613d00d89ce90334d9776db32b984ceeefdeeff1st version Authorf0bacc798b381e6f7c6b27e0fcbe3065a2c80d652nd version Authorde505f32041800ecae382772a8f5adf2e397124b3rd version Stratos 2 MSX CEZ Games Studio 2005 ES e37e2468faf0e6bf3e38b3cf7e611c846e45c5e9 StrayCat MSX Imanok 2009 ES Authorc939da3791a6772b48be24553689ba4998244fe4MSXDev09 Entry Author0x4000Normalcc39392b47f34dee1358d30fbdc4d613dc412f7cRetail Street Master 3242 MSX Zemina 1991 KR ASCII865da0e5c5de057a1ed836ec19b1f07e10f4b738fZemina Konami49f9d62f121d3b7e35d38436c920a53b7070ecd4Zemina Subacuatic MSX RELEVO Videogames 2012 ES Authoree2d0c6806e0be5ed82e01fd91eb5711271cb5a4 Sudoku MSX DVIK & Joyrex productions 2006 US Author0x4000Normalc44cccb53e2f0cc8560c288354744f29d2488850MSX-DEV06 Author120d477cd28b48b8a63efb34920ac331b54e66e5MSX-DEV06 Authorf993369a54244b63a11fb4359f52af19859eb38b Suikoden 1835 MSX KOEI 1989 JP GoodMSXKoeiSRAM32de251a0af1df3be18469578dc00d414e572ee33e Suparobo 3212 MSX Mass Tael 1984 HK ccf6e244d27ec61195280d0915a3b43d291e3fad Super ASM MSX Unknown 19xx 89e71e7446589f4fa320f94b771cc1a0756ce196José 'Alvaro Toledo Jr. Super Billiards 39 MSX HAL Laboratory 1984 JP GoodMSX0x8000Normal05d7016f39e94bf9fc33d0e8482ff3038d6e01bd 0x8000Normal75794b50c0ea8ca33d6ffa8ec3b68eb21c35d0d2 Super Bio Man 4 3036 MSX Segye-ro Electronics 19xx KR Konamidacc9ed6a80fb15c3e82c4081c07211e033ba820[b] Super Boy I 3034 MSX Zemina 1989 KR d86b5f30154b472d6f61a3784303d5ac328f4435 8b65d999ba187c98ba97a038ea46655fd9d62deb Super Boy II 3035 MSX Zemina 1989 KR cd466c04209a8815a95d3723653e263a868de9e0 85153a8f25882cadde8295d040ddb7586c93cefe Super Boy III 3156 MSX Zemina 1991 KR ASCII89bafce699964f4aabc6b60c71a71c9ff5b0cc82d ASCII814faf5da6ab76de0b62f1ede91e27d46b9db77a2Ulver crack Konami5d65a308bb9ec8c4b4ce334b18d699b1f53227ef Super Bros. World 1 3279 MSX Clover 1991 KR bada3e8e076b9c8d4060082233a2f16184cf4911a85 Super Bubble Bobble 3231 MSX Zemina 1988 KR 03f112b38fb1d05a9b0b50d3c6a54aa2ce9dd2b0 e4a0bf7e002026f2aff0936cbfb13e14fcfdbbb9 Super Cobra 36 MSX Konami 1983 JP GoodMSXa84608f1c2fe1aea1a8a586f2e335cb64bb951fc[RC-705] 9fce4dde148a4a2e9cbb7731baf099d1d16e7c96[RC-705] 61383444e658bdcc4df61e1e393b2516bed18106[RC-705] 7ad00597ceb5a02379055230d907c4421982329a[RC-705] / [a] 31472edf134c8ba532e6a1eded0dcc1aa088e8ba[RC-705] / [o] 87a9f78c0bba4e38d98890e34033a7b14f75f5e2[RC-705] / [o2] de2dd71730922168428dbc11a82fe91b84c23192[RC-705] / [o3] KonamiSCC32972530da19cac5956f83c2253c7cbf41e14dc3[RC-705] / SCC Version scc+93ab050bc4965667ee863773b8ecd36dd4f3f86c[RC-705] / SD Snatcher RAM SCC+ KonamiSCC9539999ed728aa6dbe4bef8b4be9876c7d1392c7[RC-705] / GDX SCC Version Super Columns MSX Hi-Com Co Ltd 1990 GB 16553c30fcdd0e1d00a48fe1cb2def537c4a7c3f Super Cross Force 2071 MSX Spectravideo 1983 UK 54f288590f0c882f6e3a9b7c3f7d15b407190975 Super Daisenryaku 1139 MSX Microcabin 1988 JP GoodMSXASCII16SRAM2d1cc2a77fb17608d542c50e2a1404cf5b4231521 Super Drinker 38 MSX Ample Software 1983 JP GoodMSX82c68a93d01f103af4fe9f9205184ffd1a36a90f 34716d19c216436edb8d68c69f5b779a70250168 056801516eb672b4e06de5ece88121734bdd4385 Super Golf 516 MSX Comtec 1984 JP GoodMSX27c2cdee4be0ca166c2ebd2dac61115a56bb9856 Konami75569bb341466f4a3637f02da949139a9c437eab Super Laydock - Mission Striker 954 MSX T&ESOFT 1987 JP GoodMSXASCII82dfbca8f5cc3a9e9d151382ebe0da410f5393eaf Konamic69165e84e8350a6726901f2ab6e4688f38f03fc[b][u] Konami244399d67d7851f3daa9bb87a14f5b8ef6d8c160 GenericKonami81b85b1152a59bb10d0e0118f80f0a4ed363c785 GenericKonamicf6fe49b28fcf81511f39fdf68f37af82bd724c0Ulver crack Super Lode Runner 955 MSX IREM 1987 JP ASCII168666f3109ad01093b0afab11986cfd60ba37d2a0cr GoodMSXSuperLodeRunner4164dcdfe0613d0f4c53b6f3581548bbbad5eb43 ASCII16fbb22cea9227f6f40080a42a1a169c0747129c95 Konamie171d0cec1d32aaf084e3c8150705c177522cbe6 SuperLodeRunneref6c6cde14eb551b74640ed6b38660572f121b86 SuperLodeRunnerb9d46267d081882dfd89342d3e09b82d7e8f618d Super Monitor MSX Emiiru 1985 JP GoodMSX55f30c8b7118870802d00db860bc1945f18a42c0Version 1.1 GoodMSX35f13b5a32c64b21517f0c4009f264067338912eVersion 1.2 Super Pachinko 520 MSX Nippon Columbia / Colpax / Universal 1985 JP GoodMSX991ef9890dc3e0c0d29d6d249675eae182aca30c Super Pierrot 1140 MSX Nippon Columbia / Colpax / Universal 1987 JP GoodMSXASCII160d2f86dbb70f4b4a4e4dc1bc95df232f48856037MSX1 Mode MSXDOS24cd1c5cadfd96ec96d6ef9f8825b67a3f24e9602 ASCII169c029a7c304ab84fe87708bdc11ba7fd885100d9 Super Rambo Special 772 MSX Pack-In-Video 1986 JP GoodMSXASCII162a3c7ad70e34989fa5170028bad760e65ebe9d57 translatedASCII16cac8e2cbbfa583b12b3a0ddd5e7884d2df808287GDX fix and English Translation translatedASCII16b528755fee312b833fdc19e75090994a95b960d6GDX fix and Frech Translation Super Runner 953 MSX Pony Canyon 1987 JP GoodMSXASCII83202af113265e67e130a0b948dc167a9f9e9cc79 Konami1ab38b014480745eaae143c9e6f67b542e62442f Super Snake 37 MSX HAL Laboratory 1983 JP 0348f1758daa118ebaf2bc593ff9a0a622fc89e8[a] 8dcd9618f92fb014d16e8f4ec9a813e65424ad7d GoodMSX58b3b3a678364d0649c1af0c27da144cfee85146 Super Soccer 517 MSX Sony/Takara 1985 JP GoodMSX996a1c0d92400287c15545116b952f84c7dc03b1 Super Swangi - Super Altered Beast MSX Clover 1990 KR SuperSwangi3d330e3c97ee3e2e362c8a0fc2cfac69af5ace81 KonamiSCC022d6cb56de5d48b22d0216a4da6345c7d2a9b64GDX Conversion to work on any emulator ASCII16c6ec8bb9fe1aa2d25028b268b537c323263668f5GDX Conversion to work on any emulator SuperSwangi8a2858dc9da3b1d192d9d594328295afe5c597b1GDX PSG fix Super Synth 518 MSX Cross Media Soft 1984 JP a2f8f8f9f46262342eee0a1df7c6871008a3a189 GoodMSX830efa0b25e1cf34d5f55d08cb80709d0a2c82ec Super Tennis 519 MSX Takara 1984 JP GoodMSX4a1282901221207da79432fb0bcb4f1e722c8278 58fc860eb4e5c33b22d29a4a39650aea0f327b1e Super Tripper 2206 MSX Indescomp 1985 ES 1a5295a337e5b969c939644cb5ad55b84a6ad31d Super Tritorn 771 MSX XAIN / Sein 1986 JP GoodMSXASCII8795d47c6aae378d0d5bedc9f104a597bcc1ee16b Suru'ba MSX Thinking Rabbit 1983 JP translated1449f9b1f889137831089ddc73e04b8c6b5a56ee[tr Pt] Sweet Acorn 1694 MSX TAITO 1984 JP c8015d4453efb9f78005f6c8481cbce9d99f6cc5[a] GoodMSX221c76d6ac483fb1f11c87d37b9617f1e1d7bc6d Swing 658 MSX Compile 1985 JP GoodMSX2d8e39f210da6d633f773cda03e9970712ea84cf 719f457d8fcbf6c21a88e88f34fcd54f5e96ad68[a] Synthe Saurus 1.1 1137 MSX BIT2 1988 JP GoodMSXASCII8cf088b4eefe7f426e438dd2fe7a3433140abba52 T-Virus:Brain Dead! MSX Dioniso 2004 ES Authorb98242b08c8779d86b516cca6d5d6e35ec3600e01st version Authorff999ca3e78c347bdbd23cb4c7adbfc02c20a3972nd version Taiyou no Shinden - Asteka II 1155 MSX Tokyo Shoseki 1987 JP GoodMSXASCII8SRAM8414fd2a22076f45502c2329b45bda9f8730dcaae 2bd101ac6eb91bd23415dd633eb385e283895ef9 11a81c6f4e0dca40a4e63e2afd02bc98bde94485 af04a2fd8c8403da98476970cd20988b63fac5d0 Takahasi Meijin no Boukenjima - Wonder Boy 782 MSX Hudson Soft 1986 JP 0x4000Normalb5cf03684c03dd74a9d5def34a4c2fc621c8b20d[cr Star Frontiers] GoodMSXd74c381ea17acb94527ef3abaa280793a824d322 2c86e339bb486a84baffee81e9eac06244363779 1672af5bc1740480e8302f7690589ea5b5391bcb 2d1983a385b4c09d317b72cdc96659262882a412[h Magosoft] 352d69066885fc2c7c75f0e3acd885f953916d1c Takeru Densetsu - Fuun Takeshijyou 1825 MSX Brother Industries 1987 JP GoodMSX53337b61d7b86e6d18501967fcfc0908a00cc55c 278e4d5702b2b3198d4ac1d41fb39463f5843a3b[h Sammy] 8beb313d38472a0065919a7ff3b814422879aee7 4dfcd4b25ee9bc50a6221783e393bd914be61cb6 1d5c58d3b76d1d4e4b689346ce454b04072919b1 Tank Battalion 786 MSX NAMCO 1984 JP 117d3b783da9c693e3fcb71fd113f7d90895c8d6[o] e679da4af05aa32843d2f4518b9ceb0df63df990[o2] GoodMSX5bbcbd47f05a2742cfb0cad88856eb2df6e469fe 72ddee4416abba094c0c6ff109248feecf648946 ASCII86ad15c64eed81fceca9a8f3d882cb536e1d0d17b KonamiSCC48ebe2e2436a0bd484500a226adabd6a326538adGDX SCC Version Tape Gamecase MSX JAM 2002 JP ASCII8a8ac24a702d2c850c7a15b5196893817b0c93123 Tapper MSX Bally Midway 1984 US KonamiSCC9bae0bd973bf8be95dcd6b037b85989be0901f6fGDX Conversion Tatica 542 MSX Mass Tael 1984 HK GoodMSX7fef9411858b1ff3ff019646e9e7aa0c61c7e87d 175488d7ef82b5ee94eebce3b96ef67530f7bad9 Tawara 211 MSX ASCII 1984 JP 8f74c91e6598d8de188ae1b506c44229c768b11f 8dd49e12677443aede319f41bf3f82a08d0e5b5d[a] GoodMSX14f2d64d05e2e53e876e3d6af0a5711e557e3c96 Tear of Nile 811 MSX Victor 1986 JP GoodMSXa8eff8c532da894c86198b04b0a5d7707feb28c5 Teenage Mutant Hero Turtles 3203 MSX Image Works 1990 UK ASCII16ebceb3de22e4c0444a969d83ee487f8ee34204e4h JAM Teitoku no Ketsudan 1551 MSX KOEI 1991 JP GoodMSXKoeiSRAM32b2ce0bc86f82f727db956dfbbe2a3fd0cb72f5fc Teki Paki MSX WYZ 2003 ES Authorf373723b18cb7534ba1e8de61d484800478ee604 Telebunnie 228 MSX Mass Tael 1983 HK 50439f12ddff3ed449da3cc1442385aaba609e7d GoodMSX4e17efbe87adc232c055d0dc26eb4476c661aafe Teletron MSX MSX Roelof Horst 1986 NL b3b319311243e20a25712fbe861f4e6ea7f36915 Tengoku Yoitoko - Heaven 976 MSX TAITO 1987 JP GoodMSXASCII800c70cf37c71cb8f99567ae407ff6dd52c4774c2 Konami19e79a79be13ac62bb002ed3323502be97886e17 Konamidb22cd03db61a495d55c460cfaa266271c8a5926 Konami17dcbe8e9ab792483a542b0230897baf0240dcc9Ulver crack translatedASCII8aba839d554733850902f3073f7974e2e87c488baFrench translation by Django ASCII8c3ab3edb7f9ba31124092dc0f1dd2f13d3b52f8f translatedASCII872c3010cdcd6994ce7d15e77e366036c2ece2f5bEnglish Translation - Speed Boost / MSX Translations translatedASCII81fc36b8cc50f3a3b4be10421a39a9f5874f135edEnglish Translation - Speed Boost / MSX Translations translatedASCII84c054d02b73bc2931705985312dcc49b72798558English Translation - Speed Boost / MSX Translations translatedASCII8936f9ba82c7cdb703a4b1fffce75b03508008e12English Translation - Speed Boost / MSX Translations (Trainer Only) translatedASCII8807ccf770ea07edd4f9d0bc2ab68db607650f741English Translation - Speed Boost / MSX Translations translatedASCII8f194d9d7c632acfcc37b8438854551bfd88c2490English Translation - Speed Boost / MSX Translations [Trained] translatedASCII8b1469b65b102a31fad91fc60960f243b626f4fe0English Translation - Speed Boost / MSX Translations - Trained Tensai Rabbian Daifunsen 800 MSX Soft Pro 1986 JP GoodMSX249da12e2711c643c5e18cf96184d33dbf7e2305 45bd813039977750c9a347952d2e527a4e1674c5cr Crack Fm Tensidachino Gogo 977 MSX Jast 1987 JP GoodMSX010d5f325fc38c5b590841d15ce4f07d59f2a4cb Tension 2293 MSX System 4 1988 ES a0d85c5fc74547f46025369da687569fb5aa3991 Teodoro no sabe volar MSX Retroworks/Dimension Z 2012 ES Authorae04c82e2ecde28bfcd19dc39b6c4978410aa908English version / MSXDev12 Entry Author6e44688cd709dc551fc51b6322f49333c4673dfdSpanish version / MSXDev12 Entry Tertis MSX MSXosaure 2010 FR Author3d809fe411d44920af15d41edad7dd60e80e65e2MSX Basic Challenge 2010 Test your Intelligence 1962 MSX Al Alamiah 1985 KW 2f1aacc5d18973e59f9eafa20d7ffddca34d0ef9 Test your Intelligence 2 1963 MSX Al Alamiah 1986 KW c3680636efd8d2f3a7c1c6774dd36399c754b71d Test your Knowledge 1921 MSX Al Alamiah 1985 KW 33352b5e33057bebe0c9b989afe90f178aba7243 4a7827c5f6b73a0f607381dd85353ed3a69d8694 Test your Knowledge 2 1922 MSX Al Alamiah 1986 KW 82bf80166e77f99a6f1e091f97f8ab167373dc60 ASCII81257d2004e625e1ca60ef78a43cd59c7fa2df29c Test your Knowledge 3 1923 MSX Al Alamiah 1988 KW ASCII816f06109c57de5a39fbe150de9c25754483ee9fb Tetra Horror 227 MSX Mass Tael 1984 HK 4ff12fc3adf389a1f76a7a9bab6b2fd20044f5b4[a] 53814fdfeacce237bb68fb9056d005eb614b708a d73ebc1502737ff950d63e93763c8715b369e200 Tetris MSX German Gomez Herrera 2008 ES AuthorKonamiSCCfd1eecd9c89f47741276433b99b75eca2930b58d Tetris 1167 MSX BPS 1988 JP GoodMSXASCII8af310ee21b5534f32ea92c0ae34a44d828d157a7 Tetris 3236 MSX Uttum 1987 KR e9398aceb9f8a648e4bcebe02e5d7b14a3744c7d 90df63292fc2f210b88cc8ec284391423d21c917 Tetsuman 562 MSX HAL Laboratory 1985 JP GoodMSX3c29eef381d77f8c1f425a3e29919013c72f875c GoodMSXf6711e1e2bac04d45b8988f1d03529ad777abf4e[a1] The Chess Game 2442 MSX The Bytebusters 1985 NL 20aeb411ab5adb9bd3db412a7837e496969f8ce3 The Cure MSX XL2S Entertainment 2005 NL AuthorNormal882a63a9655e53fddb1cd48761a1a09b28f7e5a3MSX-DEV05 (Winner) AuthorNormale82bbd2d9dbbb30a2b4a259954c5484fa236733aMSX-DEV05 The Energy 1966 MSX Al Alamiah 1986 KW ad2e1e957ee469d51faa07a99a20dbf00003dacf The Godzilla VS 3 Major Monsters 153 MSX BANDAI 1984 JP GoodMSX2dacf7a08857cbdc706d24b4a46665113d16ff99 fa7178686ddf5dd2898c9453d8bb2c0b83984c73[h] 83fba43371d8d3c50a97c96923e52b40df2a7816Dino Sourcers - Jaleco The Goonies r Good Enough MSX Kralizec 2010 ES KonamiSCCd54c4820cf311d9f456bf61873e76a201ef0ff68Muffie Hack KonamiSCCf2592e91c9682570401a0bbcac63464e4221c81bUnhacked (needs mapper implementation) AuthorKonamiSCC440030109b3a0af8fa0c86dd216b5089761bd4f1Kralizec farewall gift KonamiSCC74bb8727365a78e3d63b7f0fd159f759a7a5e219 KonamiSCC8e017cb41612a1a6cee87aac48cb55e78d3971b3 KonamiSCC00d7e55053f990110086bb9481d19a5576e1169b KonamiSCCa73f674ef23c86d8a6ad11c56a3935856b6c5c0d KonamiSCC7f912a38001fd880dc58c7d3da5e603c1de59c08 The Karuizawa Kidnapping Guidance 731 MSX ENIX 1986 JP GoodMSXASCII81c462c3629d43297a006ba9055b39a2dccba9f6c The Light Corridor 3217 MSX New Frontier 1990 ES ASCII160442dd29ea0f7b78c9cd5849ec7ef5bb21ec0bf5 The Mansion MSX Yermani Soft 2005 ES AuthorNormalb433357683fb1154d75c677ba01893665880f483MSX-DEV05 AuthorNormal6600fab90ca395517d0845f8f380b0e67541b392English / MSX-DEV05 The Maze of Galious - Knightmare II 916 MSX Konami 1987 JP ASCII8ee60e88ae409ddd93d4259b79586dacb2e5ee372[RC-749] GoodMSXKonami4d51d3c5036311392b173a576bc7d91dc9fed6cb[RC-749] Konamid94327745bbb865a394e31e150a6c77263ee95da[RC-749] Konami7a786959d8fc0b518b35341422f096dd6019468d[RC-749] Konamifb053becf87f12e196e00b3e153ec8029ebf1ff8[RC-749] / Ulver full crack Konamie7273feaa30b676941b25061ef0631ec09ccdb7c[RC-749] Konami7d07fdf58c7a86d9f93af35a35acf2d7ded02ff1[RC-749] Konami0c425d8fc1ad36c9b8e248e477b1796cd62d9bed[RC-749] Konamie87e5431ea2ba6cd367ad8bfaa859dae5b297ad4[RC-749] / FRS patch Konamibb100a4bf1dbcae82ddecaeb7ca519fc2dd8a5e7 The Painter MSX YAMAHA 1986 JP Normal7fd2a28c4fdaeb140f3c8c8fb90271b1472c97b9 The Three Dragon Story 3241 MSX Zemina 1989 KR 5ea3d992db53de3a198531736d8926de8401b8c8Muffie Version 6a7ab69c47cc168dd872d32f395541be4939255e cd2557fe39ba4dced3219152c6a6d02c2219b360Muffie Version 49e2331f97ea9352329dafbe0627a238063ce6b7Muffie Version 972ccf1b2672f4f6fbb015c288056c3997fa62dfMSX2 Palette fix The Zoo 1968 MSX Al Alamiah 1989 KW ASCII8ad3d8fc2201a245e352875918e421b42764e4148 Thexder 799 MSX Game Arts 1986 JP 049e7dccd3977049b5f68aad7754105b014ba771[a2] 3888c923f82314489405c38bd6110ab2fdbc1a61[cr Magicrack] 6420473ec647e4d750bc2247020b6722435b5211 GoodMSXa2109da08b1921c4b3b47c6847598d424581b508 7428159df6bf4ecc84a3ed9fb302b2da059475d7Ulver crack Thunder Ball 494 MSX ASCII 1985 JP GoodMSX4f73ba24163946f6247523fc3f2aa5de00c3d7a6 de5da311941056a90cab28fd91522c054871513f Thunder Bolt 758 MSX Pixel 1986 JP d1fcd47f6b68fcee9914b32fd48b2634cf60c994[a][Kanji] GoodMSXb3d57f968fdfebe77f78c05f4a075d139f219ae0[a1] GoodMSX136f2dedd45f4419837eb1e4e623cb1dc317ef39 Ti Ti Pang Pang 3237 MSX Aproman 1989 KR 00ffd7adfc0a7a342912344ad64902e226b3ad41 Tiles of Shalom MSX Jipe of the MSX Café 2007 FR Author0x8000Normal43c6a757ce5ec5a221e6c0e922638f688de8fd8bMSX-DEV07 Time Out - Demo MSX Pepe Vila 2010 ES Author71fd800ffbf05191a92ec72525628edbebb0fb08Demo Author7a8167ef162873b2481d888655411e371f98887eDemo ce45e159d52fd4dee7f508be9d08a7c719ff9839 Time Pilot 46 MSX Konami 1983 JP GoodMSXb3c2dc4d1c940ddb4b9e742b8f5a47ab1e2d385f[RC-703] / [a1] GoodMSXc1c5ec4042760880216f781ebd5e7174a3b0f9ca[RC-703] KonamiSCCb87a0363687d2c3563520d24fbcd148b309a10c9[RC-703] / GDX SCC Version Title Memory 1376 MSX Konami 1989 JP KonamiSCCee267ee21f5753130af665435d4e1ce84a6256f1[SCC] scc+8ac11431e32e23919a2ea37d5b2ef472d634921a[SD Snatcher RAM SCC+] scc+a031ff5aeafb7d470e810bd6cd08c92394f66295[The Snatcher RAM SCC+] Tomb of Genghis Khan MSX Impulse9 2009 ES Authorda80f0bc07cb0ed36c7c106564b5f04353eb176aMSX-DEV08 Toobin 3062 MSX Domark 1989 UK ASCII16d147f2cac0600527ce49bfffc865c54eb783e5e5 Top Roller 231 MSX Jaleco 1984 JP GoodMSX174437bfb20e2895f0f552602da823ca17b2fe93 37c85041128ee21300327726b7300a20ef6b84dd Topple Zip - MSX1 Version 803 MSX Bothtec 1986 JP GoodMSXea8d86ac74dcdca1c72daae517722431767c5ad2 Topple Zip - MSX2 Version 1827 MSX Bothtec 1987 JP GoodMSXASCII8e3036a7bc6ed3ece1c85d5e37fb2ff39ba98ba47 Konami2300aa82bfcbcf95329eb04e58bcd7e4ca0a7889[a] Toque ! 3403 MSX Gradiente 19xx BR 0x8000Normalb1a154ed8f52c2a74457e1c03c99608181dfb8ee Tower of Druaga 808 MSX NAMCO 1986 JP GoodMSX9b815efaa927c11827acfdb0f9b9197b5ac97572 Track & Field 1 261 MSX Konami 1984 JP 9b55970341aaab59a73ae921c6ccf29009c1a8f0[RC-710][aka Hyper Olympic I] Track & Field 2 262 MSX Konami 1984 JP be16501255617bade264dbf63dc59008e1d84ac8[RC-711][aka Hyper Olympic II] b7b63a9b333a0cb1ca0453bffac358fde5430578 c007d2e21f61ee10aa81ebd94267c02f27ac2961 Traffic 764 MSX Sony 1985 JP 503cbad7516c6fde89761388ab32845f35284cc6 GoodMSX7dbeaf25e3a1a66f9c6b331332dc36e73b511d4a Traffic Jam MSX Imanok 2006 ES Author0x4000Normal0f7f55fe448d20fc65dc03c2a4f1717761671e43MSX-DEV06 Author0x4000Normale6af71c99290b6ff2da9d3667e2046651e2c9fcaMSX-DEV06 Traga Perras MSX Jordi Sala Clara 2005 ES Normal23b1b2258b36997c97c02478fd17d0ffa22f9cc9 Tragaperras MSX MSxNAKE 2005 ES AuthorNormal1074acd37ee42661d86b75a8491365ed8ceaa6e0MSX-DEV05 Konami6e79675d20dba15755107c5760b7b4d3dedd6612 Konami1066dd678bb21436eb2adb1158bbe76048879af4 Konami8bef0cb43dc3c1bb1bf2c4027fe38a67fb715d71 Konamic731b1a5b9c93d91d6fdbf202071fdcc315fa2cd Konami724b44dfe5624a2ac0e5dea3d95439056d72f733 Konamib91ccfd63310a415f1952d82fcea8f77eeeb4822 Konamic47b95a97eef8e26d6c520be9152ffa780e7053b Konamibe15281488cdc83b6d302ca80b75088023bad36b Konami0ae3f889318aba6e33bf925ce734ad04970ccc4d Konami4d985a970fe70c79d4ff885caec17355cf3e7b6a Konami0b4956d7044b9f28cc5b6377faa47803b3d8ee34 Konamie0276e9d898b8e78a81986dc1a7bc34ca381d61e Treasure Of Usas, The 895 MSX Konami 1987 JP ASCII87fe5f17ad7e6b70dd56cad2c8bdc516a86f03207[RC-753] GoodMSXKonami632cb0834441626d20cc113cfad2e3a7ec0e0dc7[RC-753] / [a1] Konamid7fb2ca6004c5fdf77c42a5eab102b95a9a862d7[RC-753] / Alternative version GoodMSXKonami4ff2aad8371e382e203c7f29b665612a8c9d937c[RC-753] / [a2] Konami7c7d0bff4a51bb0f1f0cdb4132fdb11f8a71c623[RC-753] / cheat version Konami216b2c181c9ee0fb48e2d9efdb88ea918c8ff04f[RC-753] Konami5b545569a2d0452b9317490636c08a3221208961[RC-753] / Icon Patch Trial Ski 232 MSX ASCII 1984 JP GoodMSX0x8000Normaldfd7ef6fd86efd5c8019b59d8fa5c5a47cf90cd0 Trick Boy 235 MSX T&ESOFT 1984 JP e93da3d1910d513dbb78ad599adbb05eb92b5930 Tritorn 807 MSX XAIN / Sein 1986 JP GoodMSX0x4000Normal4a35065d97d81a94a7649d19b3f9abd34f834eab 0x4000Normalb1d515978727888ffe3eaf31caf296235893e01d[b] Tronkotron MSX UnAz 2008 ES AuthorKonamiSCC500d7a1d81621adf8c6914c6a42d65aa44ad4588 Trump Aid 806 MSX Softvision 1986 JP GoodMSX8547ae0fa25b9f3e26b90d4f3e1a4325b9248549 Tumego 120 970 MSX Champion Soft 1987 JP GoodMSXASCII16bfe8046f8ccc6d6016d7752c04f0654420ef81e7 Turbo 5000 3459 MSX Arcksoft 1987 NL e16ad038563724ffbba980a296ef624c0826bbb3 0ba265a6ec88f1b2e25d7a81a3e83e8be576444c Turboat 205 MSX Mass Tael 1983 HK GoodMSX08be92145a99dd6e0539ff79dca62b2f4cc59b0b 8309d3241e03b4a83b3d9fe54224178ea4e60e31 Turikichi Sampei Burumarine-Hen 1157 MSX Victor 1988 JP GoodMSXASCII8338460029d65ebce0d9119d811187a35c866a958 Turikichi Sampei Turisennin-Hen 1334 MSX Cross Media Soft 1989 JP GoodMSXASCII8108e9164ef6cf1ffe6e3c5303bfbbc3bd807b6df Tutankham MSX Konami 2008 JP Authord77a0208790f9563985d236d79a80d737b47f070 Authorca52d26e847b23c7caeb7d516b5fca5cbd686ef3 Authord59ad9f16932cc3dadb22c25e64c10a73a055056[b1] Author5925e88df896104e25fc6941fcb7925ac7a277b3 Authord0ed63a8bd0302313fbdb7609d35f47ad096be3e d553729af562f9aed3669990407121ebd0e28132 Twin Hammer MSX Best 1989 GB 4fa5382df5a8ff7d631be22bb8445e7bc8018a11 Twinbee 794 MSX Konami 1986 JP GoodMSXd33fe18ede20da4dd3d6991a9c2995e149c8fc3f[RC-740] 751d722a4f36a03e88ac62118ae8d880e40c1c84[a][RC-740] adb3977309d373ad1dc995ae7d2f8280f1673d66[a2][RC-740] 8b828d33dcdfd324df1827ef79f5ca9635968c6e[RC-740] d71b0de8ae4e1d57ace2d1f66cac1c1965102019[RC-740] / Ulver crack scc+b813123219446f16cd10b916cc18ec4a8d7c69de[SD Snatcher RAM SCC+][RC-740] scc+39a91558bba92b7310455cd7513276d487de0f9d[The Snatcher RAM SCC+][RC-740] c551d8cbe7a2e1e34d6662f5861336dffad05397 KonamiSCCffb79f90f39c6cda8b2123169571e70fb1cf2c1e[RC-740] / SCC Enhanced KonamiSCC7a8f50f5f74276905c9ee8e312f4d4f9271dbe0c[RC-740] / SCC+ Enhanced Twinbee Theme MSX WYZ 2007 ES b3942217a49f9aed9497886b45c03cf0798dc7e3 Txupinazo! MSX Imanok 2007 ES Author73ec25854aae297c9c3da0a2d4b4f1eae14fe8c6MSX-DEV07 Tyrian MSX WYZ 2007 ES f01f75f814447316ffe25ce9fc0928de8622c850 TZR Grand Prix Rider 796 MSX ASCII 1986 JP GoodMSXf3c0c1f0ec3e10cd4a5eaae26d593198b1a5ab9a U-Do MSX MSX-FAN 1990 JP 0x4000Normalfa5badc3d9486d9789b05b909125bf74059d26e5 U.F.0. MSX MSXosaure 2008 FR AuthorKonamiSCC22d8ac99ef692675210f101eb4e688cde9b2d7f5 Ultima III - Exodus 1067 MSX Origin Systems 1987 US GoodMSXASCII8SRAM818be429f8b5c7bc4775c99292110aa09341cd644 7ab6c2716fc6238ee6c6b44f5a29ae7e1793e065 Ultraman 103 MSX BANDAI 1984 JP GoodMSXe73a449e34652c18af0a72ab5c3637afa57d5842Tsubaraya Pro Universe Unknown MSX Infinite 2005 NL AuthorNormal8ab1b72ce852a778a645f566a70fa18ee32fd021Competition Version AuthorNormal83dee5f1fd4bdad33b095ed15870c52c96becc63Final Version Author059ab9b167c0e71bda811145a6ea04f0a41a0bdfMSX-DEV05 Uridium MSX Trilobyte 2014 NL Author2451a8027bff9d88edb61651a95d69d54cf06d7afor real CRTs / MSXDev14 Entry Author7c732955507df1eb292184d9648814786819cc0aMSXDev14 Entry Urusei Yatsura 897 MSX Microcabin 1987 JP GoodMSXASCII892bada2d019fcd6eee6d332a8068a9ddfa30d518 Utopia 3463 MSX DVIK & Joyrex productions/Joyrex 2008 US AuthorKonamib550757c8f5b55dbde16b4a7888f10999f01acfdDemo Vampire MSX Coleco 1982 US KonamiSCCa83f21c1e6ebaf134e40116e4a23e8afb4bae58cGDX Conversion KonamiSCCcd3d237f8c6430e6912baaa17c099397431d9f49GDX Conversion Vaxol - Heavy Armed Storm Vehicle 993 MSX Heart Soft 1987 JP GoodMSXASCII164340a2580c949d498d2c8e71699fff860214e9ea VDP Pirates Demo MSX WYZ 2006 ES Author6a5e4d81ddc779e6289987da4a4ad4c20461cef0 Authord198fda8238f80607818c41c86455e1ae29f9a3b Author3cffef3cadd60fa354da6a349bf970bc1db15915 VeejingSX MSX aorante/303bcn 2010 ES Authoreafc1ad38a8ac7e0a54917b802377aa5443ac460v0.1b Author54290e402561c6871e17076dad41f3c8ed939688RU Edition / v0.7b Author3530ab300f841ec7e92e8d38d50ebc09c5794387v0.9b Authora9721f4a8c846621b77c2283e1eee4d34433581ev0.99b Authoraac7b283b1c4453384d55625c57321fde48b0a7dv0.996b Venture MSX Coleco 1982 US KonamiSCCb06c8300ca720ee41b8825e295f01d4c4542b5a1GDX Conversion KonamiSCC546440fb2a8a5b7d14b5749dd918dbd59cb1d16fGDX Conversion Venus Fire 1000 MSX Cross Media Soft 1987 JP GoodMSXdf797593395346e94aa1cf13a9524e30d631888d 889954ae4772156aeb22fea0bdf8c9cf01bec51d Verizon Guy - Can You Hear Me Now? MSX Metal Soft (Vampier) 2009 US AuthorKonamiSCCc7377eaeb7f91639296c3df095ba403dbf4f8e79 MSXDOS2fb7396e0ab8e4c8016375e938213f9bc936eaa7e Victorious Nine 2 1191 MSX TAITO 1987 JP ASCII162f2c7559a5ba7fb938aba152bd84879c0bd3cae2[b][Kanji] GoodMSXASCII167ed1308179c41ae62cfb46124ab0ce22d8b0513b ASCII166a95b7f3a4cff9c9b3d7b8b30b0b0f752dadaa37[b2][Kanji] fa422a101808a2010f4da6bb1dac64baa217451c 40cf1b29de91d25530d67b6d889ba271c0474e9c Video Hustler 62 MSX Konami 1984 JP GoodMSX82868a81d805fb391fce588623fc888296cbf62a[RC-706][aka Konami's Billiards] 7cd94e0051e1899ae4a5d6ebf3da4c32aa1f6911[RC-706][aka Konami's Billiards] 3b6fd6a78807e700a38f9ecb145ab2847c862f52[o][RC-706][aka Konami's Billiards] a2a9d0303cb0b36a73d58e42b7c2c976c540597f[o2][RC-706][aka Konami's Billiards] 01788f6238dc934fbc843aab85fdc0581ed96989[o3][RC-706][aka Konami's Billiards] eed68d254a17d76c36e094dd8a50a362a964e1b7[RC-706][aka Konami's Billiards] KonamiSCCd131d718f57aef3a5469c6785e342a63128bf6d1[SCC][RC-706] scc+82a5b00496eb1b9741a9540a89085e86443e76c5[SD Snatcher RAM SCC+][RC-706] scc+22e095eefb53adf47648520f117afd8bfafa63d0[The Snatcher RAM SCC+][RC-706] 4f893002dbb9d456a16c72206a2eedab2644936b Videotexto MSX Gradiente 19xx BR e7af21424b22233188335fa82e212438054ae377 Videotexto + Cirandão - Sharp HB3000 MSX Epcom 1986 BR fa6a5fdacf29ef40519f69b8704dbbfb250789e7 Videotexto DDX-MODEM MSX Gradiente 19xx BR 208f802929913bd2683bf86b587f639811c3419e Vigilante 3153 MSX Clover 19xx KR eafd8cf86facdeb038e87900de4f1233dd69347c Viking MSX José Luis Tur 2005 ES AuthorNormal91186f361c8323605c79dc4551a768a4b9c5a320MSX-DEV05 Author4db63ea453df9d17cf84a0f977f4768438ec9c45MSX-DEV05 Author17315e6b40f89c2cd0d8929eefcb9394b04e4cc6MSX-DEV05 Normalc5de32b837d16a066542e0ce36d5e85bd4931d49 Volguard 647 MSX dB-SOFT 1985 JP GoodMSX6dfc622e350d29859f05fddb3fa1f974cd4c8bf2 Voyage to Mecca 1969 MSX Al Alamiah 1985 KW 09d2563fd5d9df095aa48be5ead7771c23c50ad2 VTSCC Demo MSX WYZ 2007 ES 8bd0de57be23b8d38a7901196f06050bfaf48980 aa471caed495eb52396903af4371177cb3c86236 Warp & Warp 361 MSX NAMCO 1984 JP b9d2e0b6bb7f2b027c9beb0c762d127efcfff127[o] GoodMSX52f95f6f4261be00ae1b9caf88a05c021c229e28 1f47ccfd979e653b96c9d7a36f62daf3a186e186 KonamiSCCc338405946f83c15307b888046c88651bc56005aGDX SCC Version Warrior 102 MSX ASCII 1983 JP GoodMSX0x8000Normal9b4da7636809d5690d6bd5d782ffa3cf9f4c63ec Warroid 385 MSX Yellow Horn 1985 JP 6ce0632acc2351ae968d7d3ce9c9a0af05c713cf[a] GoodMSXde6db54cef2cc4cbcc09c57c8ed0a1ff01347b6e 2eb544b197b3d89939cc6eeae4c4c04b978ba1e7 c6ca552ebadbbfd76ef6b2990bf3365b4c18cc82 Water Driver 195 MSX Apollo Technica 1984 JP 28aaf59dadb425c1ef6fca860439f4e42e1737b8 Waves 3461 MSX DVIK & Joyrex productions 2005 US AuthorASCII8f50d0c66b08c47b59925952276243e2008668185Version 1.3 Demo AuthorASCII847de7524c32e72e849daee4d7f3e6cf393e3b7dcVersion 1.0 Demo AuthorASCII8b188980c998d1dfb5dfbc21837ffbbb5affdbb3cVersion 1.1 Demo AuthorASCII8badcb43e490e3713bf291e83cff204e1c57ecdfeVersion 1.2 Demo Wedding Bells 383 MSX Nippon Columbia / Colpax / Universal 1984 JP 9dddbce94fb98776ad01870b98f44687b8b068c9 aa2aaa193cddd15c230684499d68bf73f715ed9c[a] 3b1a450960b65b752f1bd3098fecc371dfa23a36 Who Dares Wins 2 2414 MSX Alligata 1986 UK 25a51cd9367c4614291013b8a816fa5e8a2a564c d2c5c19ad327149f029a72e1834b97761e570f02 Who? 1970 MSX Al Alamiah 1987 KW b815cb222a6314c1adad0945af7be759a14c200a Window On The World 1971 MSX Al Alamiah 1985 KW 6b4b099fa7a87c5059ebb40da3089d0d61967644 e7324782c7bec087105312275043984907cbfe1f Wing Man 2 894 MSX ENIX 1987 JP GoodMSXASCII8818d91505ad39bba2eaf7f4857c7d41e95fcb233 Wizardry 893 MSX Sir-Tech Software 1988 US GoodMSXASCII8SRAM834635bf3518c7b33164f3d96da769c7a01102d28 Won-Si-In 3243 MSX Zemina 1991 KR Konamif98f9c0fd32b2392b57b14105a9b730c8957d9b9Very Rare Korean ROM ASCII16c7035f1e915d29eb6e3e44456570911881430d89 Woody Poco 896 MSX dB-SOFT 1987 JP GoodMSXASCII16ebe20c85c5314957fafc223c26bf393847e3bb03 ASCII16f2361230358bd945690ca1c2a9dcc1cbce83941b translatedASCII16a04ccf24f4164282ddc2574378f0d9ad8e180a53English Translation ASCII1607e2f85da94ae1c086a4f6358e5e77025489347cTrained translatedASCII164e34de62f72075831fc9a8c8fa1350a2e3dd6712English Translation (Trained) translatedASCII16c8a0b22bcbf070f73904bf4d584256d7c66772f9English Translation translatedASCII169539eec2adee5bc7c2e9adf7ad84454185895fb6English Translation (Trained) a6816548911bb1f7fafd53e982f67de3fdab19cd Word Invaders MSX SapphiRe Soft 2006 NL Authorda1f207466a88b347e995a6ed7d960c7cec5aa8dMSX-DEV06 Wrangler 2198 MSX Indescomp 1986 ES 76209c36bec7ca971c6db2e3bbce267147e4a361 Wreck, The 3142 MSX Electric Software 1984 UK 58e335c943d2647c3ff1e689069d9fa5e1c7ff6e X0rz MSX Metal Soft (Vampier) 2009 US Author0x8000Normal607059ef3247771f1bbee5c38d4f43cef8856712MSXDev09 Entry Author58d510e572130db643b6115da40edcdd33280017MSXDev09 Entry - bug fix ASCII16bba3af72eaaf31e786fff3e9b9c016f4ec526790 Xevious - Fardraut Saga 1148 MSX NAMCO 1989 JP ASCII16ab3f375b6890b2a9bb1b6124f0fa690950c749f2[a] GoodMSXASCII16f84d7fc089b31440cac1f4fbee3d45b2f6c90e37 ASCII1628b54294007ec345aef7322a2fcaa18d3363a73eUlver crack Xevious Micro 3245 MSX Zemina 1990 KR 1c4e9d38a8f57212dbf12f7aaba6549683b93ed4 Xphere MSX Karoshi Corporation 2007 ES Author810ea313fffaefacde167c0c75e82494245d29dcunfinished game Xyxolog 485 MSX TAITO 1984 JP 1e2abac01eb37e43a7a61da25dc147bbdc30ad32 GoodMSX15cc2f7412373253e908689a778738d52656ad74 505542691f3d40cacf7779e4224771728c426059 b0c29364953c369a136d84301721bab2fecd5b92 Yabyum MSX Tom Gerritsen 1985 NL 1a6c5ba03e0cc76f7e001784e1a205375f5602b8 Yahtzee MSX DVIK & Joyrex productions 2007 US Author0x8000Normal720054d7ab4da46db8c9fb47d17fb7e0aa79d25bMSX-DEV07 Yaksa 1031 MSX Wolfteam 1987 JP GoodMSXASCII83e57fad130be79223068ab29f1e77ecb7f2ad237 Yakyukyo - Baseball Craze 671 MSX Hudson Soft 1984 JP GoodMSXf27721cf9ec316ea3b18233f699febf739b25678 58ba380175cd196f9587701f46b373d6c2b3c314English version Yellow Submarine 890 MSX Brother Industries 1987 JP GoodMSX939ed6053363112c34766335b93d0ddf25059468 e62f5f668ea615b6ec24ab5a593aad5b3bf61558 b9e62db4c7c008daf2d2fb32420662e85bd10fb4 f845c4628fe3180f5360e4e8a329b4d2aff102ad 478c14d3e84a85af14306debc76c31c3a5f51421 Yie Ar Kung-Fu 377 MSX Konami 1985 JP 8b3994ab39768f15617fb2cd9199000226b935bd[RC-725] / [a] GoodMSXa3090a15c95334da3e0f94030eadee02b5032c0c[RC-725] 4ac9fc27a4a7b87e2c2347f5a420a6e846555263[RC-725] / Ulver crack KonamiSCC003d58988d120823c84f6617326f5df83c7e5131[RC-725] / [SCC] scc+0f81ebbd3d02796500b3f862fe1a8d584fb228d3[RC-725] / [SD Snatcher RAM SCC+] 176c82d969a8bef171bdc2b7915201957f43b260[RC-725] / Konami Antiques MSX Collection 1 b0406a38453c3bffcc990576bb725bbf7acaf132[RC-725] a68733d22706aa04d261147d67f2a0a9c17c386f[RC-725] KonamiSCCd0018f192ca6a1b78fed9acef58db389848add3e[RC-725] KonamiSCC65e427d3282fb47192e4a0f4a09245cfe1b00834[RC-725] ff13c3221ba78fafe842ef7d0742a271b2644146 Yie Ar Kung-Fu II - The Emperor Yie-Gah 378 MSX Konami 1985 JP GoodMSX609d0bac4933d1bcc877746a3e14d82c204f40e7[RC-737] / [a1] GoodMSX57827f8d49369e4dbf20a968e535374d35ff1648[RC-737] 5017abeca8fbed43ff2fd1a936843acdc7192dcc[RC-737] / [a] 78d3dc3e48724c1f570d1ee1671100cdd539ad2b[RC-737] / [t] d65f0010dacbd2528eaa25467ef007a87f17ae7a[RC-737] / Ulver crack KonamiSCC181c62bf30a66ca3c74b4165c230fb44674bdb5d[RC-737] / [SCC] scc+b27a3e67215aadc9bafd80f184c6be0b26731076[RC-737] / [SD Snatcher RAM SCC+] 3136bee4c7d4d20c1a5ba49516fc9499f405a5bf[RC-737] / Konami Antiques MSX Collection 2 c45a710fccac84de0d3bffed8095344ac1838f1d[RC-737] 4108d6dfce393ba267993a9e51f8ad9b3ee7d3f9[RC-737] KonamiSCCb0e20c6e5eb91aeaa7fc354eb0a054f480e5197e[RC-737] / SCC Enhanced KonamiSCC128e94fb7968291496761b6b949c612d2190a569[RC-737] / SCC+ Enhanced 3acba00fa8fd1a9f794adc0850a867fb51a48e64 Yokai Tanken Chima Chima 674 MSX Bothtec 1985 JP GoodMSXc60a28a4048710ad3c63ef5bcf4ce03db54c00bb Yokai Yasiki - Boynight - Haunted House 874 MSX Casio 1986 JP GoodMSX15345c8745028c96fc79036b2e3e180ad2f62dab 7c87b7ded0c7813217e5b305036e57849d0b93e9[b] cec8ffb88b3c047f5dbd95d3ed1cd9660803fa7bcheat version 372cdeff033701c86d3e01efe73c172af7492dd1Ulver crack Young Artist 1973 MSX Al Alamiah 1988 KW ASCII8e19cfcbe92ec3682bee869dfad5f6b4058b1f2ce Young Researcher 1977 MSX Al Alamiah 1989 KW e4f7a94d0cb43f56f73d54c22364e91434363945 Young Sherlock - The Legacy Of Doyle 1032 MSX Pack-In-Video 1985 JP GoodMSXASCII897e173dac64dbde7d6a60de7606cba0c860813db Konamib6a5552effcee708b665fa74e5ce7b0fa2541c03 translatedKonamid7e85888117188a6d6f7914e9649fc4821c594a2[tr Pg] Konamic9f9e1a3bccc5ed230c8c6b894f706cd231b1a81 Konami7119f1f62d0416dcd24858b3b63efbfe7d6e7043 Konamie3a3b2d4ade53136771c419119dab9b18cea0dad Konamic0a3afabaff431b0cb3774ecdbd6833040d934b0 Yumetairiku Adventure - Penguin Adventure 873 MSX Konami 1986 JP ASCII8fa6c059e14092d023b1f9f2df28e482f966287db[RC-743] GoodMSXKonamid53e0c8bcd98820afe820f756af35cc97911bfe4[RC-743] Konamid0706fd10e418eba2929d515cc0994f49376a63f[RC-743] / [t] Konamid3d411c8b7891aef9c59cbc20bb4fa3ff0ca03ea[RC-743] Konami898bda19f882c6d1dffdb2173db84a97dc21f7d6[RC-743] / [h Screen] Konami4f35f676d2f382078078343921374c2889d06e11[RC-743] / Ulver full crack Konami05a531043d13ba261b0db37e4ef8b257986ce05f[RC-743] / Konami Antiques MSX Collection 3 Konami632bdb164acab52ee715c7eefed60e0b1ae629e0[RC-743] Konamibae8548faded657cca06c2b02a59e00cf1e55484[RC-743] Yuureikun - Mr.Ghost 1229 MSX System Sacom 1989 JP GoodMSXASCII8efed48854beb2803d9f2b5644edff2aacf7c6af9 Konami6fe7a6bb86897e579393e70961a3a3204adfa48b ASCII89347890b025cc8c521a4abbff309ffc0466f22b3 Konamic570eccb1bf0c993e375e094d803ef112d0763ad Konami2724871629b46e05a0c8b5554a962211e700de0e Zaider - Battle of Peguss 793 MSX Cosmos computer 1986 JP GoodMSXde765e699af4d76d8fa80fb10d1e41581bec11bf f784350af0c5c7407d83da2bb499c59a08496aac 645ec2f28f08b3ca20f0dfe65fc7ca993da6bfbb 468a1e2231d790c782c0daa050083c1f44960e43[b] Zambeze MSX Degora 2006 ES Author7ae2a1ecccd18b1c1877a2a31e870ced5b7861ce Author359c8171cdfbb965854ff4942a740d4045eb5ff5 Zanac A.I. 754 MSX Compile 1986 JP GoodMSX46e9ed7b7f6dfda8eee266476c9ebc4dd9d8fcc2Doesn't work correctly in open MSX yet with 16 kB RAM. 372623401599c57968daac0312dd9a0abbc6bd2f ef42e667558979b44785a0f0d87d9d6aefc96b4d[t] 66d18c9714112d2bd0063a1010ce03304b0a5038 4cede7637930829474e0bd2b640b328a41b51a5c e5f138be98b4ff18e8bc3bafbdbb65f4fdbc378b 3d36aaa58909b813bff34b4b1b738dbcf807b86a Zanac A.I. - 2nd Version 2914 MSX Compile 1987 JP b030b3c22baadd0d419657dcfa4b974ae1cd7508 ffa8fc5756f7e1d1ef168618f5606aca10f8b2c7[a] a9d371c0eec519c48af880df540e03da572f263d[h Krull Software] 65ef88e469848c38c4595cbca82992fcf8dc7ed2Starts at stage 00 Zanac-Ex 755 MSX Compile 1986 JP ASCII169200fd6f1e92f78e3d9c6d082837f719ad0500d4 GoodMSXASCII1623e702084e3031158a50a0560746767bbc1b7bee ASCII16289ac36381905685dda39946127ab2af3317a010 ASCII1636eda820b734ac7a42653af370a475f36bd36b1bcheat version 76cf87f9fb8f70918df1aefb470d82d0caa41175 KonamiSCC8618e2ac27fce4a904dfb9b2cc96610bf8d38d65 Zaxxon 487 MSX Sega 1985 JP 3da301b1082c158e505523fa22c19f1b3f4d851c GoodMSXd88f33ee40839f87cdce6cc4820e1dd192a01776 57034994710a9d744c768493679b78ca06b9dc8d b507937d7ca9cd79af1f410b11a8e6b88f8ecf5cElectric Software Version Zen Assembler 2424 MSX Avalon Software 1986 NL 59679b1ed40416f4c8d65085e5473c621bea6ac4 Zenji 201 MSX Activision / Pony Canyon 1984 JP GoodMSX2f4404d141acc40e48af0b12c70cd44b066ece10 c9440172802818cc5b9ae559fbd3f346a263605c af61fac3f158abe5fd00d4166130942e4852db8c ZERO and the Castle of Infinite Sadness MSX Dioniso 2014 ES Authora44934434a761721f70873b1851de2dfca19bd99MSXDev2014 Entry - with bug Authora1fe1f9489ab188561beb2816a83a7a9fc347623MSXDev2014 Entry Zexas Limited 533 MSX dB-SOFT 1985 JP 9d5f6098b71712e9d9e386739b82fc484380f114 71442ac703c2966142b04071fedf180e81daab9a[a2] GoodMSXa87971b9df0de44644c2188f5f475aef7ae85304 Zippy Race (aka Traverse USA) MSX IREM/Sega/hap 2007 JP Author55ed66ada5d8203937554538b485fbe027a2e2bbMSX conversion 23-01-07 by hap / http://home.planet.nl/~haps/ MSXDOS242f528877218a1bbfae76b878818d2c0867764dd Zoids 964 MSX Toshiba-EMI 1988 JP GoodMSXASCII16d526249c1a35cf1174300df75bc28662f579290a Zombie Hunter 1153 MSX Hi-Score 1989 JP GoodMSXASCII8a322d4e572467be5e1b452214143f88db526b49f Konami67fa6e9005d23d9c49f77884a856cfb376194083 ASCII84cc5f197d2bd3f0306c07a880590b4b4a5574e12 Zombie Incident MSX Nenefranz 2012 NL Authordaf6574ea74d44a98a02e3ee25994f9779a9e65cVersion 1.1 / MSXDev11 Entry Authorbfc22015dcedf9100647869dbaf36f0d6165bf3aVersion 1,0 / MSXDev11 Entry Author612995a27340c4d3adbd7e402a2c3b0cf2b42e34Version 1.2 / MSXDev11 Entry Zombie Near MSX Oscar Toledo Gutiérrez 2011 ES AuthorKonamiecfc5f25368c3eb9ba827f920822c047d9e1128aVersion 1.0 / MSXDev10 Entry AuthorKonami32c84735c30e0679733d5d1918ccec9a625c4d3bVersion 1.1 / MSXDev10 Entry Zone TNT MSX AG Software IT AuthorKonamiSCC6414563c0dd0e9b3cf093d37cfb4ab5eca06f322MSXDev11 Entry Zoom 909 521 MSX Sega 1985 JP 626e4fc5f12f6f354baf6d607beadacbbafd74ce[h Prosoft] GoodMSX07db3e3ffb16c138f9da12cacde48bf7522a188c GoodMSXASCII86d090b0f72ce5263956688e8c415bf882d968f5d Zukkoke Yajikita Onmitsudoutyuu 958 MSX HAL Laboratory 1987 JP GoodMSXASCII806c527db4fbe929b4da005e7463078c05944f477[a1] GoodMSXASCII8094d11ebf8a20b6bfc07902c7f136bdd18b1e7ca openMSX-RELEASE_0_12_0/share/softwaredb1.dtd000066400000000000000000000004551257557151200204630ustar00rootroot00000000000000 openMSX-RELEASE_0_12_0/share/systemroms/000077500000000000000000000000001257557151200177665ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/systemroms/.gitignore000066400000000000000000000000321257557151200217510ustar00rootroot00000000000000/.filecache /*.rom /*.ROM openMSX-RELEASE_0_12_0/share/systemroms/README000066400000000000000000000003311257557151200206430ustar00rootroot00000000000000This directory is the default file pool for system ROMs. If you put your system ROMs here, openMSX will find them if they have the right sha1 sum, i.e. if they are exactly the right ROMs. The file name is irrelevant. openMSX-RELEASE_0_12_0/share/unicodemaps/000077500000000000000000000000001257557151200200505ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/share/unicodemaps/unicodemap.br_gradiente_1_0000066400000000000000000000201051257557151200252000ustar00rootroot00000000000000#region: Brazil, gradient MSX basic 1.0 #format: , , # : hexadecimal unicode number or DEADKEY # : row in keyboard matrix (hexadecimal: 0-B) # : column in keyboard matrix (0-7) # : space separated list of modifiers: # SHIFT CTRL GRAPH CODE # # Example characters in comments are encoded in UTF-8 # 0008, 75, # Backspace 0009, 73, # Tab 000b, 81, # Home (is Home a unicode character?) 000d, 77, # Enter/CR 0012, 82, # Insert (is Insert a unicode character?) 0018, 76, # Select (is Select a unicode character?) 001b, 72, # Escape(SDL maps ESC and ^[ to this code) 001c, 87, # Right (SDL maps ^\ to this code) 001d, 84, # Left (SDL maps ^] to this code) 001e, 85, # Up (SDL maps ^6 to this code) 001f, 86, # Down (SDL maps ^/ to this code) 0020, 80, # Space 007f, 83, # Delete 00a0, 80, # No-break space ( ) 0030, 00, # 0 0031, 01, # 1 0032, 02, # 2 0033, 03, # 3 0034, 04, # 4 0035, 05, # 5 0036, 06, # 6 0037, 07, # 7 0038, 10, # 8 0039, 11, # 9 002d, 12, # - 003d, 13, # = 005c, 14, # \ 005b, 15, # [ 005d, 16, # ] 003b, 17, # ; 0027, 20, # ' 002c, 22, # , 002e, 23, # . 002f, 24, # / DEADKEY1, 25, 0061, 26, # a 0062, 27, # b 0063, 30, # c 0064, 31, # d 0065, 32, # e 0066, 33, # f 0067, 34, # g 0068, 35, # h 0069, 36, # i 006a, 37, # j 006b, 40, # k 006c, 41, # l 006d, 42, # m 006e, 43, # n 006f, 44, # o 0070, 45, # p 0071, 46, # q 0072, 47, # r 0073, 50, # s 0074, 51, # t 0075, 52, # u 0076, 53, # v 0077, 54, # w 0078, 55, # x 0079, 56, # y 007a, 57, # z 0029, 00, SHIFT # ) 0021, 01, SHIFT # ! 0040, 02, SHIFT # @ 0023, 03, SHIFT # # 0024, 04, SHIFT # $ 0025, 05, SHIFT # % 005e, 06, SHIFT # ^ 0026, 07, SHIFT # & 002a, 10, SHIFT # * 0028, 11, SHIFT # ( 005f, 12, SHIFT # _ 002b, 13, SHIFT # + 007c, 14, SHIFT # | 007b, 15, SHIFT # { 007d, 16, SHIFT # } 003a, 17, SHIFT # : 0022, 20, SHIFT # " 007e, 21, GRAPH # ~ 003c, 22, SHIFT # < 003e, 23, SHIFT # > 003f, 24, SHIFT # ? 0041, 26, SHIFT # A 0042, 27, SHIFT # B 0043, 30, SHIFT # C 0044, 31, SHIFT # D 0045, 32, SHIFT # E 0046, 33, SHIFT # F 0047, 34, SHIFT # G 0048, 35, SHIFT # H 0049, 36, SHIFT # I 004a, 37, SHIFT # J 004b, 40, SHIFT # K 004c, 41, SHIFT # L 004d, 42, SHIFT # M 004e, 43, SHIFT # N 004f, 44, SHIFT # O 0050, 45, SHIFT # P 0051, 46, SHIFT # Q 0052, 47, SHIFT # R 0053, 50, SHIFT # S 0054, 51, SHIFT # T 0055, 52, SHIFT # U 0056, 53, SHIFT # V 0057, 54, SHIFT # W 0058, 55, SHIFT # X 0059, 56, SHIFT # Y 005a, 57, SHIFT # Z 25cb, 00, GRAPH # ○ 00bc, 01, GRAPH # ¼ 00bd, 02, GRAPH # ½ 00be, 03, GRAPH # ¾ 2229, 04, GRAPH # ∩ 2030, 05, GRAPH # ‰ 2320, 06, GRAPH # ⌠ 221a, 07, GRAPH # √ 221e, 10, GRAPH # ∞ 2027, 11, GRAPH # ‧ 2500, 12, GRAPH # ─ 00b1, 13, GRAPH # ± 2572, 14, GRAPH # ╲ 263a, 15, GRAPH # ☺ 266a, 16, GRAPH # ♪ 2660, 17, GRAPH # ♠ 2663, 20, GRAPH # ♣ 2264, 22, GRAPH # ≤ 2265, 23, GRAPH # ≥ 2571, 24, GRAPH # ╱ 25fe, 26, GRAPH # ◾ 2534, 27, GRAPH # ┴ 25c7, 30, GRAPH # ◇ 259e, 31, GRAPH # ▞ 25bc, 32, GRAPH # ▼ 251c, 33, GRAPH # ├ 253c, 34, GRAPH # ┼ 2524, 35, GRAPH # ┤ 2584, 36, GRAPH # ▄ 258e, 37, GRAPH # ▎ 258c, 40, GRAPH # ▌ 258a, 41, GRAPH # ▊ 2642, 42, GRAPH # ♂ 2518, 43, GRAPH # ┘ 2586, 44, GRAPH # ▆ 2588, 45, GRAPH # █ 25a7, 46, GRAPH # ▧ 250c, 47, GRAPH # ┌ 29d3, 50, GRAPH # ⧓ 252c, 51, GRAPH # ┬ 2582, 52, GRAPH # ▂ 2514, 53, GRAPH # └ 25b6, 54, GRAPH # ▶ 2573, 55, GRAPH # ╳ 2510, 56, GRAPH # ┐ 263c, 57, GRAPH # ☼ 25d9, 00, SHIFT GRAPH # ◙ 00b2, 02, SHIFT GRAPH # ² 207f, 03, SHIFT GRAPH # ⁿ 2321, 06, SHIFT GRAPH # ⌡ 25d8, 11, SHIFT GRAPH # ◘ 2261, 13, SHIFT GRAPH # ≡ 2502, 14, SHIFT GRAPH # │ 263b, 15, SHIFT GRAPH # ☻ 266b, 16, SHIFT GRAPH # ♫ 2666, 17, SHIFT GRAPH # ♦ 2665, 20, SHIFT GRAPH # ♥ 2248, 21, SHIFT GRAPH # ≈ 00ab, 22, SHIFT GRAPH # « 00bb, 23, SHIFT GRAPH # » 00f7, 24, SHIFT GRAPH # ÷ 25fc, 26, SHIFT GRAPH # ◼ 00b7, 30, SHIFT GRAPH # · 259a, 31, SHIFT GRAPH # ▚ 25b2, 32, SHIFT GRAPH # ▲ 2597, 33, SHIFT GRAPH # ▗ 2596, 35, SHIFT GRAPH # ▖ 2580, 36, SHIFT GRAPH # ▀ 2590, 40, SHIFT GRAPH # ▐ 2595, 41, SHIFT GRAPH # ▕ 2640, 42, SHIFT GRAPH # ♀ 2598, 43, SHIFT GRAPH # ▘ 2594, 44, SHIFT GRAPH # ▔ 25a9, 45, SHIFT GRAPH # ▩ 25a8, 46, SHIFT GRAPH # ▨ 2310, 47, SHIFT GRAPH # ⌐ 259d, 53, SHIFT GRAPH # ▝ 25c0, 54, SHIFT GRAPH # ◀ 2022, 55, SHIFT GRAPH # • 00ac, 56, SHIFT GRAPH # ¬ 00b0, 57, SHIFT GRAPH # ° 03b4, 00, CODE # δ 0192, 01, CODE # ƒ 2260, 02, CODE # ≠ 00a7, 03, CODE # § 00a2, 04, CODE # ¢ 00ff, 05, CODE # ÿ 03b1, 06, CODE # α 00df, 07, CODE # ß 03c4, 10, CODE # τ 0060, 11, CODE # ` 00e7, 21, # ç 03b5, 12, CODE # ε 03b8, 13, CODE # θ 03c6, 15, CODE # φ 03c9, 16, CODE # ω 0169, 17, CODE # ũ 0133, 20, CODE # ij 03c3, 21, CODE # σ 00e5, 22, CODE # å 00aa, 23, CODE # ª 00ba, 24, CODE # º 00e4, 26, CODE # ä 00f9, 27, CODE # ù 00ec, 30, CODE # ì 00ef, 31, CODE # ï 00ee, 32, CODE # î 00f6, 33, CODE # ö 00fc, 34, CODE # ü 00e3, 35, CODE # ã 00ed, 36, CODE # í 00e6, 37, CODE # æ 0129, 40, CODE # ĩ 00f5, 41, CODE # õ 00b5, 42, CODE # µ 00f1, 43, CODE # ñ 00f3, 44, CODE # ó 00fa, 45, CODE # ú 00e2, 46, CODE # â 00f4, 47, CODE # ô 00eb, 50, CODE # ë 00fb, 51, CODE # û 00e9, 52, CODE # é 00f2, 53, CODE # ò 00ea, 54, CODE # ê 00e8, 55, CODE # è 00e1, 56, CODE # á 00e0, 57, CODE # à 0394, 00, SHIFT CODE # Δ 00a1, 01, SHIFT CODE # ¡ 20a7, 02, SHIFT CODE # ₧ 00b6, 03, SHIFT CODE # ¶ 00a3, 04, SHIFT CODE # £ 00a5, 05, SHIFT CODE # ¥ 0393, 10, SHIFT CODE # Γ 00c7, 11, SHIFT CODE # Ç 03a6, 15, SHIFT CODE # Φ 03a9, 16, SHIFT CODE # Ω 0168, 17, SHIFT CODE # Ũ 0132, 20, SHIFT CODE # IJ 03a3, 21, SHIFT CODE # Σ 00c5, 22, SHIFT CODE # Å 00bf, 24, SHIFT CODE # ¿ 00c4, 26, SHIFT CODE # Ä 00d6, 33, SHIFT CODE # Ö 00dc, 34, SHIFT CODE # Ü 00c3, 35, SHIFT CODE # Ã 00c6, 37, SHIFT CODE # Æ 0128, 40, SHIFT CODE # Ĩ 00d5, 41, SHIFT CODE # Õ 00d1, 43, SHIFT CODE # Ñ 03c0, 45, SHIFT CODE # π 00c9, 52, SHIFT CODE # É 0001, 26, CTRL # ^A 0002, 27, CTRL # ^B 0003, 30, CTRL # ^C 0004, 31, CTRL # ^D 0005, 32, CTRL # ^E 0006, 33, CTRL # ^F 0007, 34, CTRL # ^G 000a, 37, CTRL # ^J 000c, 41, CTRL # ^L 000e, 43, CTRL # ^N 000f, 44, CTRL # ^O 0010, 45, CTRL # ^P 0011, 46, CTRL # ^Q 0013, 50, CTRL # ^S 0014, 51, CTRL # ^T 0015, 52, CTRL # ^U 0016, 53, CTRL # ^V 0017, 54, CTRL # ^W 0019, 56, CTRL # ^Y 001a, 57, CTRL # ^Z 0000, 02, CTRL SHIFT # ^@ openMSX-RELEASE_0_12_0/share/unicodemaps/unicodemap.br_gradiente_1_1000066400000000000000000000201411257557151200252010ustar00rootroot00000000000000#region: Brazil, gradient MSX basic 1.1 #format: , , # : hexadecimal unicode number or DEADKEY # : row in keyboard matrix (hexadecimal: 0-B) # : column in keyboard matrix (0-7) # : space separated list of modifiers: # SHIFT CTRL GRAPH CODE # # Example characters in comments are encoded in UTF-8 # 0008, 75, # Backspace 0009, 73, # Tab 000b, 81, # Home (is Home a unicode character?) 000d, 77, # Enter/CR 0012, 82, # Insert (is Insert a unicode character?) 0018, 76, # Select (is Select a unicode character?) 001b, 72, # Escape(SDL maps ESC and ^[ to this code) 001c, 87, # Right (SDL maps ^\ to this code) 001d, 84, # Left (SDL maps ^] to this code) 001e, 85, # Up (SDL maps ^6 to this code) 001f, 86, # Down (SDL maps ^/ to this code) 0020, 80, # Space 007f, 83, # Delete 00a0, 80, # No-break space ( ) 0030, 00, # 0 0031, 01, # 1 0032, 02, # 2 0033, 03, # 3 0034, 04, # 4 0035, 05, # 5 0036, 06, # 6 0037, 07, # 7 0038, 10, # 8 0039, 11, # 9 002d, 12, # - 003d, 13, # = 007b, 14, # { DEADKEY1, 15, # ´ -> á, é, ú, í, ó DEADKEY2, 17, # ~ -> ã, ũ, ĩ, õ 005b, 16, # [ 002a, 20, # * 00e7, 21, # ç 002c, 22, # , 002e, 23, # . 003b, 24, # ; 002f, 25, # / 0061, 26, # a 0062, 27, # b 0063, 30, # c 0064, 31, # d 0065, 32, # e 0066, 33, # f 0067, 34, # g 0068, 35, # h 0069, 36, # i 006a, 37, # j 006b, 40, # k 006c, 41, # l 006d, 42, # m 006e, 43, # n 006f, 44, # o 0070, 45, # p 0071, 46, # q 0072, 47, # r 0073, 50, # s 0074, 51, # t 0075, 52, # u 0076, 53, # v 0077, 54, # w 0078, 55, # x 0079, 56, # y 007a, 57, # z 0029, 00, SHIFT # ) 0021, 01, SHIFT # ! 0022, 02, SHIFT # " 0023, 03, SHIFT # # 0024, 04, SHIFT # $ 0025, 05, SHIFT # % 005e, 06, SHIFT # ^ 0026, 07, SHIFT # & 0027, 10, SHIFT # ' 0028, 11, SHIFT # ( 005f, 12, SHIFT # _ 002b, 13, SHIFT # + 007d, 14, SHIFT # } 005d, 16, SHIFT # ] 0040, 20, SHIFT # @ 00c7, 21, SHIFT # Ç 003c, 22, SHIFT # < 003e, 23, SHIFT # > 003a, 24, SHIFT # : 003f, 25, SHIFT # ? 0041, 26, SHIFT # A 0042, 27, SHIFT # B 0043, 30, SHIFT # C 0044, 31, SHIFT # D 0045, 32, SHIFT # E 0046, 33, SHIFT # F 0047, 34, SHIFT # G 0048, 35, SHIFT # H 0049, 36, SHIFT # I 004a, 37, SHIFT # J 004b, 40, SHIFT # K 004c, 41, SHIFT # L 004d, 42, SHIFT # M 004e, 43, SHIFT # N 004f, 44, SHIFT # O 0050, 45, SHIFT # P 0051, 46, SHIFT # Q 0052, 47, SHIFT # R 0053, 50, SHIFT # S 0054, 51, SHIFT # T 0055, 52, SHIFT # U 0056, 53, SHIFT # V 0057, 54, SHIFT # W 0058, 55, SHIFT # X 0059, 56, SHIFT # Y 005a, 57, SHIFT # Z 25cb, 00, GRAPH # ○ 00bc, 01, GRAPH # ¼ 00bd, 02, GRAPH # ½ 00be, 03, GRAPH # ¾ 2229, 04, GRAPH # ∩ 2030, 05, GRAPH # ‰ 2320, 06, GRAPH # ⌠ 221a, 07, GRAPH # √ 221e, 10, GRAPH # ∞ 2027, 11, GRAPH # ‧ 2500, 12, GRAPH # ─ 00b1, 13, GRAPH # ± 2572, 14, GRAPH # ╲ 263a, 15, GRAPH # ☺ 266a, 16, GRAPH # ♪ 2660, 17, GRAPH # ♠ 2663, 20, GRAPH # ♣ 2264, 22, GRAPH # ≤ 2265, 23, GRAPH # ≥ 2571, 24, GRAPH # ╱ 005c, 25, GRAPH # \ 25fe, 26, GRAPH # ◾ 2534, 27, GRAPH # ┴ 25c7, 30, GRAPH # ◇ 259e, 31, GRAPH # ▞ 25bc, 32, GRAPH # ▼ 251c, 33, GRAPH # ├ 253c, 34, GRAPH # ┼ 2524, 35, GRAPH # ┤ 2584, 36, GRAPH # ▄ 258e, 37, GRAPH # ▎ 258c, 40, GRAPH # ▌ 258a, 41, GRAPH # ▊ 2642, 42, GRAPH # ♂ 2518, 43, GRAPH # ┘ 2586, 44, GRAPH # ▆ 2588, 45, GRAPH # █ 25a7, 46, GRAPH # ▧ 250c, 47, GRAPH # ┌ 29d3, 50, GRAPH # ⧓ 252c, 51, GRAPH # ┬ 2582, 52, GRAPH # ▂ 2514, 53, GRAPH # └ 25b6, 54, GRAPH # ▶ 2573, 55, GRAPH # ╳ 2510, 56, GRAPH # ┐ 263c, 57, GRAPH # ☼ 25d9, 00, SHIFT GRAPH # ◙ 00b2, 02, SHIFT GRAPH # ² 207f, 03, SHIFT GRAPH # ⁿ 2321, 06, SHIFT GRAPH # ⌡ 25d8, 11, SHIFT GRAPH # ◘ 2261, 13, SHIFT GRAPH # ≡ 2502, 14, SHIFT GRAPH # │ 263b, 15, SHIFT GRAPH # ☻ 266b, 16, SHIFT GRAPH # ♫ 2666, 17, SHIFT GRAPH # ♦ 2665, 20, SHIFT GRAPH # ♥ 2248, 21, SHIFT GRAPH # ≈ 00ab, 22, SHIFT GRAPH # « 00bb, 23, SHIFT GRAPH # » 00f7, 24, SHIFT GRAPH # ÷ 007c, 25, SHIFT GRAPH # | 25fc, 26, SHIFT GRAPH # ◼ 00b7, 30, SHIFT GRAPH # · 259a, 31, SHIFT GRAPH # ▚ 25b2, 32, SHIFT GRAPH # ▲ 2597, 33, SHIFT GRAPH # ▗ 2596, 35, SHIFT GRAPH # ▖ 2580, 36, SHIFT GRAPH # ▀ 2590, 40, SHIFT GRAPH # ▐ 2595, 41, SHIFT GRAPH # ▕ 2640, 42, SHIFT GRAPH # ♀ 2598, 43, SHIFT GRAPH # ▘ 2594, 44, SHIFT GRAPH # ▔ 25a9, 45, SHIFT GRAPH # ▩ 25a8, 46, SHIFT GRAPH # ▨ 2310, 47, SHIFT GRAPH # ⌐ 259d, 53, SHIFT GRAPH # ▝ 25c0, 54, SHIFT GRAPH # ◀ 2022, 55, SHIFT GRAPH # • 00ac, 56, SHIFT GRAPH # ¬ 00b0, 57, SHIFT GRAPH # ° 03b4, 00, CODE # δ 0192, 01, CODE # ƒ 2260, 02, CODE # ≠ 00a7, 03, CODE # § 00a2, 04, CODE # ¢ 00ff, 05, CODE # ÿ 03b1, 06, CODE # α 00df, 07, CODE # ß 03c4, 10, CODE # τ 03b5, 12, CODE # ε 03b8, 13, CODE # θ 03c6, 15, CODE # φ 03c9, 16, CODE # ω 0169, 17, CODE # ũ 0133, 20, CODE # ij 03c3, 21, CODE # σ 00e5, 22, CODE # å 00aa, 23, CODE # ª 00ba, 24, CODE # º 00e4, 26, CODE # ä 00f9, 27, CODE # ù 00ec, 30, CODE # ì 00ef, 31, CODE # ï 00ee, 32, CODE # î 00f6, 33, CODE # ö 00fc, 34, CODE # ü 00e3, 35, CODE # ã 00ed, 36, CODE # í 00e6, 37, CODE # æ 0129, 40, CODE # ĩ 00f5, 41, CODE # õ 00b5, 42, CODE # µ 00f1, 43, CODE # ñ 00f3, 44, CODE # ó 00fa, 45, CODE # ú 00e2, 46, CODE # â 00f4, 47, CODE # ô 00eb, 50, CODE # ë 00fb, 51, CODE # û 00e9, 52, CODE # é 00f2, 53, CODE # ò 00ea, 54, CODE # ê 223d, 21, GRAPH # ∽ 00e1, 56, CODE # á 00e0, 57, CODE # à 0394, 00, SHIFT CODE # Δ 00a1, 01, SHIFT CODE # ¡ 20a7, 02, SHIFT CODE # ₧ 00b6, 03, SHIFT CODE # ¶ 00a3, 04, SHIFT CODE # £ 00a5, 05, SHIFT CODE # ¥ 0393, 10, SHIFT CODE # Γ 03a6, 15, SHIFT CODE # Φ 03a9, 16, SHIFT CODE # Ω 0168, 17, SHIFT CODE # Ũ 0132, 20, SHIFT CODE # IJ 03a3, 21, SHIFT CODE # Σ 00c5, 22, SHIFT CODE # Å 00bf, 24, SHIFT CODE # ¿ 00c4, 26, SHIFT CODE # Ä 00d6, 33, SHIFT CODE # Ö 00dc, 34, SHIFT CODE # Ü 00c3, 35, SHIFT CODE # Ã 00c6, 37, SHIFT CODE # Æ 0128, 40, SHIFT CODE # Ĩ 00d5, 41, SHIFT CODE # Õ 00d1, 43, SHIFT CODE # Ñ 03c0, 45, SHIFT CODE # π 00c9, 52, SHIFT CODE # É 0001, 26, CTRL # ^A 0002, 27, CTRL # ^B 0003, 30, CTRL # ^C 0004, 31, CTRL # ^D 0005, 32, CTRL # ^E 0006, 33, CTRL # ^F 0007, 34, CTRL # ^G 000a, 37, CTRL # ^J 000c, 41, CTRL # ^L 000e, 43, CTRL # ^N 000f, 44, CTRL # ^O 0010, 45, CTRL # ^P 0011, 46, CTRL # ^Q 0013, 50, CTRL # ^S 0014, 51, CTRL # ^T 0015, 52, CTRL # ^U 0016, 53, CTRL # ^V 0017, 54, CTRL # ^W 0019, 56, CTRL # ^Y 001a, 57, CTRL # ^Z 0000, 20, CTRL SHIFT # ^@ openMSX-RELEASE_0_12_0/share/unicodemaps/unicodemap.br_hotbit000066400000000000000000000164011257557151200240740ustar00rootroot00000000000000#region: Brazil, sharp hotbit MSX basic 1.1 #format: , , # : hexadecimal unicode number or DEADKEY # : row in keyboard matrix (hexadecimal: 0-B) # : column in keyboard matrix (0-7) # : space separated list of modifiers: # SHIFT CTRL GRAPH CODE # # Example characters in comments are encoded in UTF-8 # 0008, 75, # Backspace 0009, 73, # Tab 000b, 81, # Home (is Home a unicode character?) 000d, 77, # Enter/CR 0012, 82, # Insert (is Insert a unicode character?) 0018, 76, # Select (is Select a unicode character?) 001b, 72, # Escape(SDL maps ESC and ^[ to this code) 001c, 87, # Right (SDL maps ^\ to this code) 001d, 84, # Left (SDL maps ^] to this code) 001e, 85, # Up (SDL maps ^6 to this code) 001f, 86, # Down (SDL maps ^/ to this code) 0020, 80, # Space 007f, 83, # Delete 00a0, 80, # No-break space ( ) 0030, 00, # 0 0031, 01, # 1 0032, 02, # 2 0033, 03, # 3 0034, 04, # 4 0035, 05, # 5 0036, 06, # 6 0037, 07, # 7 0038, 10, # 8 0039, 11, # 9 002d, 12, # - 003d, 13, # = 005c, 14, # \ DEADKEY1, 15, # ´ -> á, é, ú, í, ó DEADKEY2, 16, # ¨ -> ü DEADKEY3, 20, # ~ -> ã, õ 00e7, 17, # ç 005b, 21, # [ 002c, 22, # , 002e, 23, # . 002f, 24, # / 003c, 25, # < 0061, 26, # a 0062, 27, # b 0063, 30, # c 0064, 31, # d 0065, 32, # e 0066, 33, # f 0067, 34, # g 0068, 35, # h 0069, 36, # i 006a, 37, # j 006b, 40, # k 006c, 41, # l 006d, 42, # m 006e, 43, # n 006f, 44, # o 0070, 45, # p 0071, 46, # q 0072, 47, # r 0073, 50, # s 0074, 51, # t 0075, 52, # u 0076, 53, # v 0077, 54, # w 0078, 55, # x 0079, 56, # y 007a, 57, # z 0029, 00, SHIFT # ) 0021, 01, SHIFT # ! 0040, 02, SHIFT # @ 0023, 03, SHIFT # # 0024, 04, SHIFT # $ 0025, 05, SHIFT # % 0022, 06, SHIFT # " 0026, 07, SHIFT # & 002a, 10, SHIFT # * 0028, 11, SHIFT # ( 005f, 12, SHIFT # _ 002b, 13, SHIFT # + 005e, 14, SHIFT # ^ 0027, 16, SHIFT # ' 00c7, 17, SHIFT # Ç 005d, 21, SHIFT # ] 003b, 22, SHIFT # ; 003a, 23, SHIFT # : 003f, 24, SHIFT # ? 003e, 25, SHIFT # > 0041, 26, SHIFT # A 0042, 27, SHIFT # B 0043, 30, SHIFT # C 0044, 31, SHIFT # D 0045, 32, SHIFT # E 0046, 33, SHIFT # F 0047, 34, SHIFT # G 0048, 35, SHIFT # H 0049, 36, SHIFT # I 004a, 37, SHIFT # J 004b, 40, SHIFT # K 004c, 41, SHIFT # L 004d, 42, SHIFT # M 004e, 43, SHIFT # N 004f, 44, SHIFT # O 0050, 45, SHIFT # P 0051, 46, SHIFT # Q 0052, 47, SHIFT # R 0053, 50, SHIFT # S 0054, 51, SHIFT # T 0055, 52, SHIFT # U 0056, 53, SHIFT # V 0057, 54, SHIFT # W 0058, 55, SHIFT # X 0059, 56, SHIFT # Y 005a, 57, SHIFT # Z 25cb, 00, GRAPH # ○ 00bc, 01, GRAPH # ¼ 00bd, 02, GRAPH # ½ 00be, 03, GRAPH # ¾ 2229, 04, GRAPH # ∩ 2030, 05, GRAPH # ‰ 2320, 06, GRAPH # ⌠ 221a, 07, GRAPH # √ 221e, 10, GRAPH # ∞ 2027, 11, GRAPH # ‧ 2500, 12, GRAPH # ─ 00b1, 13, GRAPH # ± 2572, 14, GRAPH # ╲ 263a, 15, GRAPH # ☺ 266a, 16, GRAPH # ♪ 2660, 17, GRAPH # ♠ 2663, 20, GRAPH # ♣ 2264, 22, GRAPH # ≤ 2265, 23, GRAPH # ≥ 2571, 24, GRAPH # ╱ 007c, 25, GRAPH # | 25fe, 26, GRAPH # ◾ 2534, 27, GRAPH # ┴ 25c7, 30, GRAPH # ◇ 259e, 31, GRAPH # ▞ 25bc, 32, GRAPH # ▼ 251c, 33, GRAPH # ├ 253c, 34, GRAPH # ┼ 2524, 35, GRAPH # ┤ 2584, 36, GRAPH # ▄ 258e, 37, GRAPH # ▎ 258c, 40, GRAPH # ▌ 258a, 41, GRAPH # ▊ 2642, 42, GRAPH # ♂ 2518, 43, GRAPH # ┘ 2586, 44, GRAPH # ▆ 2588, 45, GRAPH # █ 25a7, 46, GRAPH # ▧ 250c, 47, GRAPH # ┌ 29d3, 50, GRAPH # ⧓ 252c, 51, GRAPH # ┬ 2582, 52, GRAPH # ▂ 2514, 53, GRAPH # └ 25b6, 54, GRAPH # ▶ 2573, 55, GRAPH # ╳ 2510, 56, GRAPH # ┐ 263c, 57, GRAPH # ☼ 25d9, 00, SHIFT GRAPH # ◙ 00b2, 02, SHIFT GRAPH # ² 207f, 03, SHIFT GRAPH # ⁿ 2321, 06, SHIFT GRAPH # ⌡ 25d8, 11, SHIFT GRAPH # ◘ 2261, 13, SHIFT GRAPH # ≡ 2502, 14, SHIFT GRAPH # │ 263b, 15, SHIFT GRAPH # ☻ 266b, 16, SHIFT GRAPH # ♫ 2666, 17, SHIFT GRAPH # ♦ 2665, 20, SHIFT GRAPH # ♥ 2248, 21, SHIFT GRAPH # ≈ 00ab, 22, SHIFT GRAPH # « 00bb, 23, SHIFT GRAPH # » 00f7, 24, SHIFT GRAPH # ÷ 25fc, 26, SHIFT GRAPH # ◼ 00b7, 30, SHIFT GRAPH # · 259a, 31, SHIFT GRAPH # ▚ 25b2, 32, SHIFT GRAPH # ▲ 2597, 33, SHIFT GRAPH # ▗ 2596, 35, SHIFT GRAPH # ▖ 2580, 36, SHIFT GRAPH # ▀ 2590, 40, SHIFT GRAPH # ▐ 2595, 41, SHIFT GRAPH # ▕ 2640, 42, SHIFT GRAPH # ♀ 2598, 43, SHIFT GRAPH # ▘ 2594, 44, SHIFT GRAPH # ▔ 25a9, 45, SHIFT GRAPH # ▩ 25a8, 46, SHIFT GRAPH # ▨ 2310, 47, SHIFT GRAPH # ⌐ 259d, 53, SHIFT GRAPH # ▝ 25c0, 54, SHIFT GRAPH # ◀ 2022, 55, SHIFT GRAPH # • 00ac, 56, SHIFT GRAPH # ¬ 00b0, 57, SHIFT GRAPH # ° 03b4, 00, CODE # δ 0192, 01, CODE # ƒ 2260, 02, CODE # ≠ 00a7, 03, CODE # § 00a2, 04, CODE # ¢ 00ff, 05, CODE # ÿ 03b1, 06, CODE # α 00df, 07, CODE # ß 03c4, 10, CODE # τ 03b5, 12, CODE # ε 03b8, 13, CODE # θ 03c6, 15, CODE # φ 03c9, 16, CODE # ω 00aa, 20, CODE # ª 03c3, 21, CODE # σ 03a3, 22, CODE # Σ 00ba, 25, CODE # º 00e6, 26, CODE # æ 0133, 27, CODE # ij 007d, 30, CODE # } 00f1, 31, CODE # ñ 00b6, 32, CODE # ¶ 00d1, 33, CODE # Ñ 00bf, 34, CODE # ¿ 03c0, 37, CODE # π 03a6, 40, CODE # Φ 03a9, 41, CODE # Ω 0394, 44, CODE # Δ 00b5, 45, CODE # µ 00a1, 46, CODE # ¡ 00a3, 47, CODE # £ 00c6, 50, CODE # Æ 00a5, 51, CODE # ¥ 0393, 52, CODE # Γ 0132, 53, CODE # IJ 20a7, 54, CODE # ₧ 007b, 55, CODE # { 007e, 21, GRAPH # ~ 0001, 26, CTRL # ^A 0002, 27, CTRL # ^B 0003, 30, CTRL # ^C 0004, 31, CTRL # ^D 0005, 32, CTRL # ^E 0006, 33, CTRL # ^F 0007, 34, CTRL # ^G 000a, 37, CTRL # ^J 000c, 41, CTRL # ^L 000e, 43, CTRL # ^N 000f, 44, CTRL # ^O 0010, 45, CTRL # ^P 0011, 46, CTRL # ^Q 0013, 50, CTRL # ^S 0014, 51, CTRL # ^T 0015, 52, CTRL # ^U 0016, 53, CTRL # ^V 0017, 54, CTRL # ^W 0019, 56, CTRL # ^Y 001a, 57, CTRL # ^Z 0000, 02, CTRL SHIFT # ^@ openMSX-RELEASE_0_12_0/share/unicodemaps/unicodemap.de000066400000000000000000000201111257557151200225010ustar00rootroot00000000000000#region: German #format: , , # : hexadecimal unicode number or DEADKEY # : row in keyboard matrix (hexadecimal: 0-B) # : column in keyboard matrix (0-7) # : space separated list of modifiers: # SHIFT CTRL GRAPH CODE # # Example characters in comments are encoded in UTF-8 # DEADKEY1, 13, 0000, 02, CTRL CODE # ^@ 0001, 26, CTRL # ^A 0002, 27, CTRL # ^B 0003, 30, CTRL # ^C 0004, 31, CTRL # ^D 0005, 32, CTRL # ^E 0006, 33, CTRL # ^F 0007, 34, CTRL # ^G 0008, 75, # Backspace 0009, 73, # Tab 000a, 37, CTRL # ^J 000b, 81, # Home (is Home a unicode character?) 000c, 41, CTRL # ^L 000d, 77, # Enter/CR 000e, 43, CTRL # ^N 000f, 44, CTRL # ^O 0010, 45, CTRL # ^P 0011, 46, CTRL # ^Q 0012, 82, # Insert (is Insert a unicode character?) 0013, 50, CTRL # ^S 0014, 51, CTRL # ^T 0015, 52, CTRL # ^U 0016, 53, CTRL # ^V 0017, 54, CTRL # ^W 0018, 76, # Select (is Select a unicode character?) 0019, 57, CTRL # ^Y 001a, 56, CTRL # ^Z 001b, 72, # Escape(SDL maps ESC and ^[ to this code) 001c, 87, # Right (SDL maps ^\ to this code) 001d, 84, # Left (SDL maps ^] to this code) 001e, 85, # Up (SDL maps ^6 to this code) 001f, 86, # Down (SDL maps ^/ to this code) 0020, 80, # Space 0021, 01, SHIFT # ! 0022, 02, SHIFT # " 0023, 21, # # 0024, 04, SHIFT # $ 0025, 05, SHIFT # % 0026, 06, SHIFT # & 0027, 13, GRAPH # ' 0028, 10, SHIFT # ( 0029, 11, SHIFT # ) 002a, 16, SHIFT # * 002b, 16, # + 002c, 22, # , 002d, 24, # - 002e, 23, # . 002f, 07, SHIFT # / 0030, 00, # 0 0031, 01, # 1 0032, 02, # 2 0033, 03, # 3 0034, 04, # 4 0035, 05, # 5 0036, 06, # 6 0037, 07, # 7 0038, 10, # 8 0039, 11, # 9 003a, 23, SHIFT # : 003b, 22, SHIFT # ; 003c, 14, # < 003d, 00, SHIFT # = 003e, 14, SHIFT # > 003f, 12, SHIFT # ? 0040, 02, CODE # @ 0041, 26, SHIFT # A 0042, 27, SHIFT # B 0043, 30, SHIFT # C 0044, 31, SHIFT # D 0045, 32, SHIFT # E 0046, 33, SHIFT # F 0047, 34, SHIFT # G 0048, 35, SHIFT # H 0049, 36, SHIFT # I 004a, 37, SHIFT # J 004b, 40, SHIFT # K 004c, 41, SHIFT # L 004d, 42, SHIFT # M 004e, 43, SHIFT # N 004f, 44, SHIFT # O 0050, 45, SHIFT # P 0051, 46, SHIFT # Q 0052, 47, SHIFT # R 0053, 50, SHIFT # S 0054, 51, SHIFT # T 0055, 52, SHIFT # U 0056, 53, SHIFT # V 0057, 54, SHIFT # W 0058, 55, SHIFT # X 0059, 57, SHIFT # Y 005a, 56, SHIFT # Z 005b, 10, CODE # [ 005c, 07, CODE # \ 005d, 11, CODE # ] 005e, 21, SHIFT # ^ 005f, 24, SHIFT # _ 0060, 13, SHIFT GRAPH # ` 0061, 26, # a 0062, 27, # b 0063, 30, # c 0064, 31, # d 0065, 32, # e 0066, 33, # f 0067, 34, # g 0068, 35, # h 0069, 36, # i 006a, 37, # j 006b, 40, # k 006c, 41, # l 006d, 42, # m 006e, 43, # n 006f, 44, # o 0070, 45, # p 0071, 46, # q 0072, 47, # r 0073, 50, # s 0074, 51, # t 0075, 52, # u 0076, 53, # v 0077, 54, # w 0078, 55, # x 0079, 57, # y 007a, 56, # z 007b, 10, SHIFT CODE # { 007c, 01, CODE # | 007d, 11, SHIFT CODE # } 007e, 21, GRAPH # ~ 007f, 83, # Delete 00a0, 80, # No-break space ( ) 00a1, 01, SHIFT CODE # ¡ 00a2, 05, CODE # ¢ 00a3, 05, SHIFT CODE # £ 00a5, 57, SHIFT CODE # ¥ 00a7, 03, SHIFT # § 00aa, 23, CODE # ª 00ab, 14, GRAPH # « 00ac, 56, SHIFT GRAPH # ¬ 00b0, 57, SHIFT GRAPH # ° 00b1, 16, GRAPH # ± 00b2, 02, SHIFT GRAPH # ² 00b5, 42, CODE # µ 00b6, 03, SHIFT CODE # ¶ 00b7, 30, SHIFT GRAPH # · 00ba, 24, CODE # º 00bb, 14, SHIFT GRAPH # » 00bc, 01, GRAPH # ¼ 00bd, 02, GRAPH # ½ 00be, 03, GRAPH # ¾ 00bf, 12, SHIFT CODE # ¿ 00c3, 35, SHIFT CODE # Ã 00c4, 20, SHIFT # Ä 00c5, 22, SHIFT CODE # Å 00c6, 37, SHIFT CODE # Æ 00c7, 04, SHIFT CODE # Ç 00c9, 52, SHIFT CODE # É 00d1, 43, SHIFT CODE # Ñ 00d5, 41, SHIFT CODE # Õ 00d6, 17, SHIFT # Ö 00dc, 15, SHIFT # Ü 00df, 12, # ß 00e0, 57, CODE # à 00e1, 56, CODE # á 00e2, 46, CODE # â 00e3, 35, CODE # ã 00e4, 20, # ä 00e5, 22, CODE # å 00e6, 37, CODE # æ 00e7, 04, CODE # ç 00e8, 55, CODE # è 00e9, 52, CODE # é 00ea, 54, CODE # ê 00eb, 50, CODE # ë 00ec, 30, CODE # ì 00ed, 36, CODE # í 00ee, 32, CODE # î 00ef, 31, CODE # ï 00f1, 43, CODE # ñ 00f2, 53, CODE # ò 00f3, 44, CODE # ó 00f4, 47, CODE # ô 00f5, 41, CODE # õ 00f6, 17, # ö 00f7, 05, SHIFT GRAPH # ÷ 00f9, 27, CODE # ù 00fa, 45, CODE # ú 00fb, 51, CODE # û 00fc, 15, # ü 00ff, 34, CODE # ÿ 0128, 40, SHIFT CODE # Ĩ 0129, 40, CODE # ĩ 0132, 20, SHIFT CODE # IJ 0133, 20, CODE # ij 0168, 17, SHIFT CODE # Ũ 0169, 17, CODE # ũ 0192, 33, CODE # ƒ 0393, 06, SHIFT CODE # Γ 0394, 00, SHIFT CODE # Δ 03a3, 21, SHIFT CODE # Σ 03a6, 15, SHIFT CODE # Φ 03a9, 16, SHIFT CODE # Ω 03b1, 26, CODE # α 03b4, 00, CODE # δ 03b5, 03, CODE # ε 03b8, 12, CODE # θ 03c0, 45, SHIFT CODE # π 03c3, 21, CODE # σ 03c4, 06, CODE # τ 03c6, 15, CODE # φ 03c9, 16, CODE # ω 2022, 55, SHIFT GRAPH # • 2027, 11, GRAPH # ‧ 2030, 05, GRAPH # ‰ 207f, 03, SHIFT GRAPH # ⁿ 20a7, 02, SHIFT CODE # ₧ 221a, 22, GRAPH # √ 221e, 10, GRAPH # ∞ 2229, 04, GRAPH # ∩ 223d, 21, SHIFT GRAPH # ∽ 2248, 22, SHIFT GRAPH # ≈ 2260, 51, SHIFT GRAPH # ≠ 2261, 24, SHIFT GRAPH # ≡ 2264, 14, CODE # ≤ 2265, 14, SHIFT CODE # ≥ 2310, 47, SHIFT GRAPH # ⌐ 2320, 06, GRAPH # ⌠ 2321, 06, SHIFT GRAPH # ⌡ 2500, 24, GRAPH # ─ 2502, 23, GRAPH # │ 250c, 47, GRAPH # ┌ 2510, 56, GRAPH # ┐ 2514, 53, GRAPH # └ 2518, 43, GRAPH # ┘ 251c, 33, GRAPH # ├ 2524, 35, GRAPH # ┤ 252c, 51, GRAPH # ┬ 2534, 27, GRAPH # ┴ 253c, 34, GRAPH # ┼ 2571, 07, GRAPH # ╱ 2572, 07, SHIFT GRAPH # ╲ 2573, 55, GRAPH # ╳ 2580, 36, SHIFT GRAPH # ▀ 2582, 52, GRAPH # ▂ 2584, 36, GRAPH # ▄ 2586, 44, GRAPH # ▆ 2588, 45, GRAPH # █ 258a, 41, GRAPH # ▊ 258c, 40, GRAPH # ▌ 258e, 37, GRAPH # ▎ 2590, 40, SHIFT GRAPH # ▐ 2594, 44, SHIFT GRAPH # ▔ 2595, 41, SHIFT GRAPH # ▕ 2596, 35, SHIFT GRAPH # ▖ 2597, 33, SHIFT GRAPH # ▗ 2598, 43, SHIFT GRAPH # ▘ 259a, 31, SHIFT GRAPH # ▚ 259d, 53, SHIFT GRAPH # ▝ 259e, 31, GRAPH # ▞ 25a7, 46, GRAPH # ▧ 25a8, 46, SHIFT GRAPH # ▨ 25a9, 45, SHIFT GRAPH # ▩ 25b2, 32, SHIFT GRAPH # ▲ 25b6, 54, GRAPH # ▶ 25bc, 32, GRAPH # ▼ 25c0, 54, SHIFT GRAPH # ◀ 25c7, 30, GRAPH # ◇ 25cb, 00, GRAPH # ○ 25d8, 11, SHIFT GRAPH # ◘ 25d9, 00, SHIFT GRAPH # ◙ 25fc, 26, SHIFT GRAPH # ◼ 25fe, 26, GRAPH # ◾ 263a, 15, GRAPH # ☺ 263b, 15, SHIFT GRAPH # ☻ 263c, 57, GRAPH # ☼ 2640, 42, SHIFT GRAPH # ♀ 2642, 42, GRAPH # ♂ 2660, 17, GRAPH # ♠ 2663, 20, GRAPH # ♣ 2665, 20, SHIFT GRAPH # ♥ 2666, 17, SHIFT GRAPH # ♦ 266a, 12, GRAPH # ♪ 266b, 12, SHIFT GRAPH # ♫ 29d3, 50, GRAPH # ⧓ openMSX-RELEASE_0_12_0/share/unicodemaps/unicodemap.es000066400000000000000000000130631257557151200225300ustar00rootroot00000000000000#region: Spain (Espagna) #format: , , # : hexadecimal unicode number or DEADKEY # : row in keyboard matrix (hexadecimal: 0-B) # : column in keyboard matrix (0-7) # : space separated list of modifiers: # SHIFT CTRL GRAPH CODE CAPSLOCK DEADKEY1, 25, 0000, 02, CTRL SHIFT # ^@ 0001, 26, CTRL # ^A 0002, 27, CTRL # ^B 0003, 30, CTRL # ^C 0004, 31, CTRL # ^D 0005, 32, CTRL # ^E 0006, 33, CTRL # ^F 0007, 34, CTRL # ^G 0008, 75, # Backspace 0009, 73, # Tab 000a, 37, CTRL # ^J 000b, 81, # Home (is Home a unicode character?) 000c, 41, CTRL # ^L 000d, 77, # Enter/CR 000e, 43, CTRL # ^N 000f, 44, CTRL # ^O 0010, 45, CTRL # ^P 0011, 46, CTRL # ^Q 0012, 82, # Insert (is Insert a unicode character?) 0013, 50, CTRL # ^S 0014, 51, CTRL # ^T 0015, 52, CTRL # ^U 0016, 53, CTRL # ^V 0017, 54, CTRL # ^W 0018, 76, # Select (is Select a unicode character?) 0019, 56, CTRL # ^Y 001a, 57, CTRL # ^Z 001b, 72, # Escape(SDL maps ESC and ^[ to this code) 001c, 87, # Right (SDL maps ^\ to this code) 001d, 84, # Left (SDL maps ^] to this code) 001e, 85, # Up (SDL maps ^6 to this code) 001f, 86, # Down (SDL maps ^/ to this code) 0020, 80, 0021, 01, SHIFT 0022, 20, SHIFT 0023, 03, SHIFT 0024, 04, SHIFT 0025, 05, SHIFT 0026, 07, SHIFT 0027, 20, 0028, 11, SHIFT 0029, 00, SHIFT 002a, 10, SHIFT 002b, 13, SHIFT 002c, 22, 002d, 12, 002e, 23, 002f, 24, 0030, 00, 0031, 01, 0032, 02, 0033, 03, 0034, 04, 0035, 05, 0036, 06, 0037, 07, 0038, 10, 0039, 11, 003A, 21, SHIFT 003B, 21, 003c, 22, SHIFT 003d, 13, 003e, 23, SHIFT 003f, 24, SHIFT 0040, 02, SHIFT 0041, 26, SHIFT 0042, 27, SHIFT 0043, 30, SHIFT 0044, 31, SHIFT 0045, 32, SHIFT 0046, 33, SHIFT 0047, 34, SHIFT 0048, 35, SHIFT 0049, 36, SHIFT 004a, 37, SHIFT 004b, 40, SHIFT 004c, 41, SHIFT 004d, 42, SHIFT 004e, 43, SHIFT 004f, 44, SHIFT 0050, 45, SHIFT 0051, 46, SHIFT 0052, 47, SHIFT 0053, 50, SHIFT 0054, 51, SHIFT 0055, 52, SHIFT 0056, 53, SHIFT 0057, 54, SHIFT 0058, 55, SHIFT 0059, 56, SHIFT 005a, 57, SHIFT 005b, 15, 005c, 14, 005d, 16, 005e, 06, SHIFT 005f, 12, SHIFT 0061, 26, 0062, 27, 0063, 30, 0064, 31, 0065, 32, 0066, 33, 0067, 34, 0068, 35, 0069, 36, 006a, 37, 006b, 40, 006c, 41, 006d, 42, 006e, 43, 006f, 44, 0070, 45, 0071, 46, 0072, 47, 0073, 50, 0074, 51, 0075, 52, 0076, 53, 0077, 54, 0078, 55, 0079, 56, 007a, 57, 007b, 15, SHIFT 007d, 16, SHIFT 007f, 83, # Delete 00a0, 80, 00a1, 01, SHIFT CODE 00a2, 04, CODE 00a3, 04, SHIFT CODE 00a5, 05, SHIFT CODE 00A6, 14, SHIFT 00a7, 03, CODE 00aa, 23, CODE 00ab, 22, SHIFT GRAPH 00ac, 56, SHIFT GRAPH 00b0, 57, SHIFT GRAPH 00b1, 13, GRAPH 00b2, 02, SHIFT GRAPH 00b5, 42, CODE 00b6, 33, GRAPH 00b7, 30, SHIFT GRAPH 00ba, 24, CODE 00bb, 23, SHIFT GRAPH 00bc, 01, GRAPH 00bd, 02, GRAPH 00bf, 24, SHIFT CODE 00c3, 35, SHIFT CODE 00c4, 26, SHIFT CODE 00c5, 22, SHIFT CODE 00c6, 37, SHIFT CODE 00c7, 11, SHIFT CODE 00c9, 52, SHIFT CODE 00D1, 17, SHIFT 00d5, 41, SHIFT CODE 00d6, 33, SHIFT CODE 00dc, 34, SHIFT CODE 00df, 07, CODE 00e0, 57, CODE 00e1, 56, CODE 00e2, 46, CODE 00e3, 35, CODE 00e4, 26, CODE 00e5, 22, CODE 00e6, 37, CODE 00e7, 11, CODE 00e8, 55, CODE 00e9, 52, CODE 00ea, 54, CODE 00eb, 50, CODE 00ec, 30, CODE 00ed, 36, CODE 00ee, 32, CODE 00ef, 31, CODE 00F1, 17, 00f2, 53, CODE 00f3, 44, CODE 00f4, 47, CODE 00f5, 41, CODE 00f6, 33, CODE 00f7, 24, SHIFT GRAPH 00f9, 27, CODE 00fa, 45, CODE 00fb, 51, CODE 00fc, 34, CODE 00ff, 05, CODE 0128, 40, SHIFT CODE 0129, 40, CODE 0168, 17, SHIFT CODE 0169, 17, CODE 0192, 01, CODE 0393, 10, SHIFT CODE 0398, 13, CODE 03a3, 21, SHIFT CODE 03a6, 15, SHIFT CODE 03a9, 16, SHIFT CODE 03b1, 06, CODE 03b4, 00, CODE 03b5, 12, CODE 03c0, 45, SHIFT CODE 03c3, 21, CODE 03c4, 10, CODE 03c6, 15, CODE 2022, 11, GRAPH 203c, 35, GRAPH 207f, 03, SHIFT GRAPH 20a7, 02, SHIFT CODE 2190, 43, GRAPH 2191, 47, GRAPH 2192, 53, GRAPH 2193, 56, GRAPH 2194, 24, GRAPH 2195, 51, GRAPH 21a8, 12, GRAPH 2219, 55, SHIFT GRAPH 221a, 07, GRAPH 221e, 10, GRAPH 221f, 55, GRAPH 2229, 04, GRAPH 2248, 21, SHIFT GRAPH 2261, 13, SHIFT GRAPH 2264, 22, GRAPH 2265, 23, GRAPH 2302, 00, SHIFT CODE 2310, 47, SHIFT GRAPH 2320, 06, GRAPH 2321, 06, SHIFT GRAPH 2500, 26, GRAPH 2502, 40, CODE 250c, 16, CODE 2510, 03, CODE 2514, 52, GRAPH 2518, 02, CODE 251c, 44, SHIFT GRAPH 2524, 41, SHIFT CODE 252c, 44, GRAPH 2534, 31, SHIFT GRAPH 253c, 52, SHIFT GRAPH 2550, 32, GRAPH 2551, 03, GRAPH 2552, 53, SHIFT GRAPH 2553, 35, SHIFT GRAPH 2554, 41, SHIFT GRAPH 2555, 20, SHIFT CODE 2556, 17, CODE 2557, 21, GRAPH 2558, 33, SHIFT GRAPH 2559, 43, SHIFT GRAPH 255a, 41, GRAPH 255b, 03, SHIFT CODE 255c, 05, GRAPH 255d, 30, GRAPH 255e, 37, GRAPH 255f, 31, GRAPH 2560, 46, GRAPH 2561, 41, CODE 2562, 17, SHIFT CODE 2563, 20, CODE 2564, 50, SHIFT GRAPH 2565, 50, GRAPH 2566, 46, SHIFT GRAPH 2567, 54, GRAPH 2568, 54, SHIFT GRAPH 2569, 37, SHIFT GRAPH 256a, 00, SHIFT CODE 256b, 45, SHIFT GRAPH 256c, 32, SHIFT GRAPH 2580, 36, SHIFT GRAPH 2584, 36, GRAPH 2588, 45, GRAPH 258c, 40, GRAPH 2590, 40, SHIFT GRAPH 2591, 35, SHIFT CODE 2592, 35, CODE 2593, 40, SHIFT CODE 25a0, 26, SHIFT GRAPH 25ac, 14, SHIFT GRAPH 25b2, 14, GRAPH 25ba, 34, SHIFT GRAPH 25bc, 12, SHIFT GRAPH 25c4, 27, GRAPH 25cb, 00, GRAPH 25d8, 11, SHIFT GRAPH 25d9, 00, SHIFT GRAPH 263a, 15, GRAPH 263b, 15, SHIFT GRAPH 263c, 57, GRAPH 2640, 42, SHIFT GRAPH 2642, 42, GRAPH 2660, 17, GRAPH 2663, 20, GRAPH 2665, 20, SHIFT GRAPH 2666, 17, SHIFT GRAPH 266a, 16, GRAPH 266b, 16, SHIFT GRAPH openMSX-RELEASE_0_12_0/share/unicodemaps/unicodemap.fr000066400000000000000000000201111257557151200225200ustar00rootroot00000000000000#region: French #format: , , # : hexadecimal unicode number or DEADKEY # : row in keyboard matrix (hexadecimal: 0-B) # : column in keyboard matrix (0-7) # : space separated list of modifiers: # SHIFT CTRL GRAPH CODE # # Example characters in comments are encoded in UTF-8 # DEADKEY1, 15, 0000, 02, CTRL CODE # ^@ 0001, 46, CTRL # ^A 0002, 27, CTRL # ^B 0003, 30, CTRL # ^C 0004, 31, CTRL # ^D 0005, 32, CTRL # ^E 0006, 33, CTRL # ^F 0007, 34, CTRL # ^G 0008, 75, # Backspace 0009, 73, # Tab 000a, 37, CTRL # ^J 000b, 81, # Home (is Home a unicode character?) 000c, 41, CTRL # ^L 000d, 77, # Enter/CR 000e, 43, CTRL # ^N 000f, 44, CTRL # ^O 0010, 45, CTRL # ^P 0011, 26, CTRL # ^Q 0012, 82, # Insert (is Insert a unicode character?) 0013, 50, CTRL # ^S 0014, 51, CTRL # ^T 0015, 52, CTRL # ^U 0016, 53, CTRL # ^V 0017, 57, CTRL # ^W 0018, 76, # Select (is Select a unicode character?) 0019, 56, CTRL # ^Y 001a, 54, CTRL # ^Z 001b, 72, # Escape(SDL maps ESC and ^[ to this code) 001c, 87, # Right (SDL maps ^\ to this code) 001d, 84, # Left (SDL maps ^] to this code) 001e, 85, # Up (SDL maps ^6 to this code) 001f, 86, # Down (SDL maps ^/ to this code) 0020, 80, # Space 0021, 10, # ! 0022, 03, # " 0023, 21, # # 0024, 16, # $ 0025, 20, SHIFT # % 0026, 01, # & 0027, 04, # ' 0028, 05, # ( 0029, 12, # ) 002a, 16, SHIFT # * 002b, 24, SHIFT # + 002c, 42, # , 002d, 13, # - 002e, 22, SHIFT # . 002f, 23, SHIFT # / 0030, 00, SHIFT # 0 0031, 01, SHIFT # 1 0032, 02, SHIFT # 2 0033, 03, SHIFT # 3 0034, 04, SHIFT # 4 0035, 05, SHIFT # 5 0036, 06, SHIFT # 6 0037, 07, SHIFT # 7 0038, 10, SHIFT # 8 0039, 11, SHIFT # 9 003a, 23, # : 003b, 22, # ; 003c, 14, # < 003d, 24, # = 003e, 14, SHIFT # > 003f, 42, SHIFT # ? 0040, 02, CODE # @ 0041, 46, SHIFT # A 0042, 27, SHIFT # B 0043, 30, SHIFT # C 0044, 31, SHIFT # D 0045, 32, SHIFT # E 0046, 33, SHIFT # F 0047, 34, SHIFT # G 0048, 35, SHIFT # H 0049, 36, SHIFT # I 004a, 37, SHIFT # J 004b, 40, SHIFT # K 004c, 41, SHIFT # L 004d, 17, SHIFT # M 004e, 43, SHIFT # N 004f, 44, SHIFT # O 0050, 45, SHIFT # P 0051, 26, SHIFT # Q 0052, 47, SHIFT # R 0053, 50, SHIFT # S 0054, 51, SHIFT # T 0055, 52, SHIFT # U 0056, 53, SHIFT # V 0057, 57, SHIFT # W 0058, 55, SHIFT # X 0059, 56, SHIFT # Y 005a, 54, SHIFT # Z 005b, 05, SHIFT CODE # [ 005c, 23, SHIFT CODE # \ 005d, 12, SHIFT CODE # ] 005e, 06, CODE # ^ 005f, 13, SHIFT # _ 0060, 04, CODE # ` 0061, 46, # a 0062, 27, # b 0063, 30, # c 0064, 31, # d 0065, 32, # e 0066, 33, # f 0067, 34, # g 0068, 35, # h 0069, 36, # i 006a, 37, # j 006b, 40, # k 006c, 41, # l 006d, 17, # m 006e, 43, # n 006f, 44, # o 0070, 45, # p 0071, 26, # q 0072, 47, # r 0073, 50, # s 0074, 51, # t 0075, 52, # u 0076, 53, # v 0077, 57, # w 0078, 55, # x 0079, 56, # y 007a, 54, # z 007b, 05, CODE # { 007c, 01, CODE # | 007d, 12, CODE # } 007e, 07, SHIFT CODE # ~ 007f, 83, # Delete 00a0, 80, # No-break space ( ) 00a1, 01, SHIFT CODE # ¡ 00a2, 16, CODE # ¢ 00a3, 21, SHIFT # £ 00a5, 56, SHIFT CODE # ¥ 00a7, 06, # § 00aa, 23, CODE # ª 00ab, 14, GRAPH # « 00ac, 56, SHIFT GRAPH # ¬ 00b0, 12, SHIFT # ° 00b1, 24, GRAPH # ± 00b2, 02, SHIFT GRAPH # ² 00b5, 42, CODE # µ 00b6, 06, SHIFT CODE # ¶ 00b7, 30, SHIFT GRAPH # · 00ba, 24, CODE # º 00bb, 14, SHIFT GRAPH # » 00bc, 01, GRAPH # ¼ 00bd, 02, GRAPH # ½ 00be, 03, GRAPH # ¾ 00bf, 42, SHIFT CODE # ¿ 00c3, 35, SHIFT CODE # Ã 00c4, 26, SHIFT CODE # Ä 00c5, 22, SHIFT CODE # Å 00c6, 37, SHIFT CODE # Æ 00c7, 11, SHIFT CODE # Ç 00c9, 02, SHIFT CODE # É 00d1, 43, SHIFT CODE # Ñ 00d5, 41, SHIFT CODE # Õ 00d6, 33, SHIFT CODE # Ö 00dc, 34, SHIFT CODE # Ü 00df, 27, CODE # ß 00e0, 00, # à 00e1, 56, CODE # á 00e2, 46, CODE # â 00e3, 35, CODE # ã 00e4, 26, CODE # ä 00e5, 22, CODE # å 00e6, 37, CODE # æ 00e7, 11, # ç 00e8, 07, # è 00e9, 02, # é 00ea, 54, CODE # ê 00eb, 50, CODE # ë 00ec, 30, CODE # ì 00ed, 36, CODE # í 00ee, 32, CODE # î 00ef, 31, CODE # ï 00f1, 43, CODE # ñ 00f2, 53, CODE # ò 00f3, 44, CODE # ó 00f4, 47, CODE # ô 00f5, 41, CODE # õ 00f6, 33, CODE # ö 00f7, 22, GRAPH # ÷ 00f9, 20, # ù 00fa, 45, CODE # ú 00fb, 51, CODE # û 00fc, 34, CODE # ü 00ff, 52, CODE # ÿ 0128, 40, SHIFT CODE # Ĩ 0129, 40, CODE # ĩ 0132, 20, SHIFT CODE # IJ 0133, 20, CODE # ij 0168, 17, SHIFT CODE # Ũ 0169, 17, CODE # ũ 0192, 55, CODE # ƒ 0393, 10, SHIFT CODE # Γ 0394, 00, SHIFT CODE # Δ 03a3, 21, SHIFT CODE # Σ 03a6, 13, SHIFT CODE # Φ 03a9, 57, SHIFT CODE # Ω 03b1, 03, CODE # α 03b4, 00, CODE # δ 03b5, 07, CODE # ε 03b8, 11, CODE # θ 03c0, 45, SHIFT CODE # π 03c3, 21, CODE # σ 03c4, 10, CODE # τ 03c6, 13, CODE # φ 03c9, 57, CODE # ω 2022, 55, SHIFT GRAPH # • 2027, 11, GRAPH # ‧ 2030, 21, GRAPH # ‰ 207f, 03, SHIFT GRAPH # ⁿ 20a7, 03, SHIFT CODE # ₧ 221a, 07, GRAPH # √ 221e, 10, GRAPH # ∞ 2229, 05, GRAPH # ∩ 223d, 04, GRAPH # ∽ 2248, 04, SHIFT GRAPH # ≈ 2260, 51, SHIFT GRAPH # ≠ 2261, 24, SHIFT GRAPH # ≡ 2264, 14, CODE # ≤ 2265, 14, SHIFT CODE # ≥ 2310, 47, SHIFT GRAPH # ⌐ 2320, 06, GRAPH # ⌠ 2321, 06, SHIFT GRAPH # ⌡ 2500, 13, GRAPH # ─ 2502, 01, SHIFT GRAPH # │ 250c, 47, GRAPH # ┌ 2510, 56, GRAPH # ┐ 2514, 53, GRAPH # └ 2518, 43, GRAPH # ┘ 251c, 33, GRAPH # ├ 2524, 35, GRAPH # ┤ 252c, 51, GRAPH # ┬ 2534, 27, GRAPH # ┴ 253c, 34, GRAPH # ┼ 2571, 23, SHIFT GRAPH # ╱ 2572, 23, GRAPH # ╲ 2573, 55, GRAPH # ╳ 2580, 36, SHIFT GRAPH # ▀ 2582, 52, GRAPH # ▂ 2584, 36, GRAPH # ▄ 2586, 44, GRAPH # ▆ 2588, 45, GRAPH # █ 258a, 41, GRAPH # ▊ 258c, 40, GRAPH # ▌ 258e, 37, GRAPH # ▎ 2590, 40, SHIFT GRAPH # ▐ 2594, 44, SHIFT GRAPH # ▔ 2595, 41, SHIFT GRAPH # ▕ 2596, 35, SHIFT GRAPH # ▖ 2597, 33, SHIFT GRAPH # ▗ 2598, 43, SHIFT GRAPH # ▘ 259a, 31, SHIFT GRAPH # ▚ 259d, 53, SHIFT GRAPH # ▝ 259e, 31, GRAPH # ▞ 25a7, 46, GRAPH # ▧ 25a8, 46, SHIFT GRAPH # ▨ 25a9, 45, SHIFT GRAPH # ▩ 25b2, 32, SHIFT GRAPH # ▲ 25b6, 54, GRAPH # ▶ 25bc, 32, GRAPH # ▼ 25c0, 54, SHIFT GRAPH # ◀ 25c7, 30, GRAPH # ◇ 25cb, 00, GRAPH # ○ 25d8, 11, SHIFT GRAPH # ◘ 25d9, 00, SHIFT GRAPH # ◙ 25fc, 26, SHIFT GRAPH # ◼ 25fe, 26, GRAPH # ◾ 263a, 12, GRAPH # ☺ 263b, 12, SHIFT GRAPH # ☻ 263c, 57, GRAPH # ☼ 2640, 42, SHIFT GRAPH # ♀ 2642, 42, GRAPH # ♂ 2660, 17, GRAPH # ♠ 2663, 20, GRAPH # ♣ 2665, 20, SHIFT GRAPH # ♥ 2666, 17, SHIFT GRAPH # ♦ 266a, 16, GRAPH # ♪ 266b, 16, SHIFT GRAPH # ♫ 29d3, 50, GRAPH # ⧓ openMSX-RELEASE_0_12_0/share/unicodemaps/unicodemap.gb000066400000000000000000000201541257557151200225100ustar00rootroot00000000000000#region: United Kingdom #format: , , # : hexadecimal unicode number or DEADKEY # : row in keyboard matrix (hexadecimal: 0-B) # : column in keyboard matrix (0-7) # : space separated list of modifiers: # SHIFT CTRL GRAPH CODE # # Example characters in comments are encoded in UTF-8 # DEADKEY1, 25, 0000, 02, CTRL SHIFT # ^@ 0001, 26, CTRL # ^A 0002, 27, CTRL # ^B 0003, 30, CTRL # ^C 0004, 31, CTRL # ^D 0005, 32, CTRL # ^E 0006, 33, CTRL # ^F 0007, 34, CTRL # ^G 0008, 75, # Backspace 0009, 73, # Tab 000a, 37, CTRL # ^J 000b, 81, # Home (is Home a unicode character?) 000c, 41, CTRL # ^L 000d, 77, # Enter/CR 000e, 43, CTRL # ^N 000f, 44, CTRL # ^O 0010, 45, CTRL # ^P 0011, 46, CTRL # ^Q 0012, 82, # Insert (is Insert a unicode character?) 0013, 50, CTRL # ^S 0014, 51, CTRL # ^T 0015, 52, CTRL # ^U 0016, 53, CTRL # ^V 0017, 54, CTRL # ^W 0018, 76, # Select (is Select a unicode character?) 0019, 56, CTRL # ^Y 001a, 57, CTRL # ^Z 001b, 72, # Escape(SDL maps ESC and ^[ to this code) 001c, 87, # Right (SDL maps ^\ to this code) 001d, 84, # Left (SDL maps ^] to this code) 001e, 85, # Up (SDL maps ^6 to this code) 001f, 86, # Down (SDL maps ^/ to this code) 0020, 80, # Space 0021, 01, SHIFT # ! 0022, 20, SHIFT # " 0023, 03, SHIFT # # 0024, 04, SHIFT # $ 0025, 05, SHIFT # % 0026, 07, SHIFT # & 0027, 20, # ' 0028, 11, SHIFT # ( 0029, 00, SHIFT # ) 002a, 10, SHIFT # * 002b, 13, SHIFT # + 002c, 22, # , 002d, 12, # - 002e, 23, # . 002f, 24, # / 0030, 00, # 0 0031, 01, # 1 0032, 02, # 2 0033, 03, # 3 0034, 04, # 4 0035, 05, # 5 0036, 06, # 6 0037, 07, # 7 0038, 10, # 8 0039, 11, # 9 003a, 17, SHIFT # : 003b, 17, # ; 003c, 22, SHIFT # < 003d, 13, # = 003e, 23, SHIFT # > 003f, 24, SHIFT # ? 0040, 02, SHIFT # @ 0041, 26, SHIFT # A 0042, 27, SHIFT # B 0043, 30, SHIFT # C 0044, 31, SHIFT # D 0045, 32, SHIFT # E 0046, 33, SHIFT # F 0047, 34, SHIFT # G 0048, 35, SHIFT # H 0049, 36, SHIFT # I 004a, 37, SHIFT # J 004b, 40, SHIFT # K 004c, 41, SHIFT # L 004d, 42, SHIFT # M 004e, 43, SHIFT # N 004f, 44, SHIFT # O 0050, 45, SHIFT # P 0051, 46, SHIFT # Q 0052, 47, SHIFT # R 0053, 50, SHIFT # S 0054, 51, SHIFT # T 0055, 52, SHIFT # U 0056, 53, SHIFT # V 0057, 54, SHIFT # W 0058, 55, SHIFT # X 0059, 56, SHIFT # Y 005a, 57, SHIFT # Z 005b, 15, # [ 005c, 14, # \ 005d, 16, # ] 005e, 06, SHIFT # ^ 005f, 12, SHIFT # _ 0060, 14, CODE # ` 0061, 26, # a 0062, 27, # b 0063, 30, # c 0064, 31, # d 0065, 32, # e 0066, 33, # f 0067, 34, # g 0068, 35, # h 0069, 36, # i 006a, 37, # j 006b, 40, # k 006c, 41, # l 006d, 42, # m 006e, 43, # n 006f, 44, # o 0070, 45, # p 0071, 46, # q 0072, 47, # r 0073, 50, # s 0074, 51, # t 0075, 52, # u 0076, 53, # v 0077, 54, # w 0078, 55, # x 0079, 56, # y 007a, 57, # z 007b, 15, SHIFT # { 007c, 14, SHIFT # | 007d, 16, SHIFT # } 007e, 21, SHIFT # ~ 007f, 83, # Delete 00a0, 80, # No-break space ( ) 00a1, 01, SHIFT CODE # ¡ 00a2, 04, CODE # ¢ 00a3, 04, SHIFT CODE # £ 00a3, 21, # £ 00a5, 05, SHIFT CODE # ¥ 00a7, 03, CODE # § 00aa, 23, CODE # ª 00ab, 22, SHIFT GRAPH # « 00ac, 56, SHIFT GRAPH # ¬ 00b0, 57, SHIFT GRAPH # ° 00b1, 13, GRAPH # ± 00b2, 02, SHIFT GRAPH # ² 00b5, 42, CODE # µ 00b6, 03, SHIFT CODE # ¶ 00b7, 30, SHIFT GRAPH # · 00ba, 24, CODE # º 00bb, 23, SHIFT GRAPH # » 00bc, 01, GRAPH # ¼ 00bd, 02, GRAPH # ½ 00be, 03, GRAPH # ¾ 00bf, 24, SHIFT CODE # ¿ 00c3, 35, SHIFT CODE # Ã 00c4, 26, SHIFT CODE # Ä 00c5, 22, SHIFT CODE # Å 00c6, 37, SHIFT CODE # Æ 00c7, 11, SHIFT CODE # Ç 00c9, 52, SHIFT CODE # É 00d1, 43, SHIFT CODE # Ñ 00d5, 41, SHIFT CODE # Õ 00d6, 33, SHIFT CODE # Ö 00dc, 34, SHIFT CODE # Ü 00df, 07, CODE # ß 00e0, 57, CODE # à 00e1, 56, CODE # á 00e2, 46, CODE # â 00e3, 35, CODE # ã 00e4, 26, CODE # ä 00e5, 22, CODE # å 00e6, 37, CODE # æ 00e7, 11, CODE # ç 00e8, 55, CODE # è 00e9, 52, CODE # é 00ea, 54, CODE # ê 00eb, 50, CODE # ë 00ec, 30, CODE # ì 00ed, 36, CODE # í 00ee, 32, CODE # î 00ef, 31, CODE # ï 00f1, 43, CODE # ñ 00f2, 53, CODE # ò 00f3, 44, CODE # ó 00f4, 47, CODE # ô 00f5, 41, CODE # õ 00f6, 33, CODE # ö 00f7, 24, SHIFT GRAPH # ÷ 00f9, 27, CODE # ù 00fa, 45, CODE # ú 00fb, 51, CODE # û 00fc, 34, CODE # ü 00ff, 05, CODE # ÿ 0128, 40, SHIFT CODE # Ĩ 0129, 40, CODE # ĩ 0132, 20, SHIFT CODE # IJ 0133, 20, CODE # ij 0168, 17, SHIFT CODE # Ũ 0169, 17, CODE # ũ 0192, 01, CODE # ƒ 0393, 10, SHIFT CODE # Γ 0394, 00, SHIFT CODE # Δ 03a3, 21, SHIFT CODE # Σ 03a6, 15, SHIFT CODE # Φ 03a9, 16, SHIFT CODE # Ω 03b1, 06, CODE # α 03b4, 00, CODE # δ 03b5, 12, CODE # ε 03b8, 13, CODE # θ 03c0, 45, SHIFT CODE # π 03c3, 21, CODE # σ 03c4, 10, CODE # τ 03c6, 15, CODE # φ 03c9, 16, CODE # ω 2022, 55, SHIFT GRAPH # • 2027, 11, GRAPH # ‧ 2030, 05, GRAPH # ‰ 207f, 03, SHIFT GRAPH # ⁿ 20a7, 02, SHIFT CODE # ₧ 221a, 07, GRAPH # √ 221e, 10, GRAPH # ∞ 2229, 04, GRAPH # ∩ 223d, 21, GRAPH # ∽ 2248, 21, SHIFT GRAPH # ≈ 2260, 02, CODE # ≠ 2261, 13, SHIFT GRAPH # ≡ 2264, 22, GRAPH # ≤ 2265, 23, GRAPH # ≥ 2310, 47, SHIFT GRAPH # ⌐ 2320, 06, GRAPH # ⌠ 2321, 06, SHIFT GRAPH # ⌡ 2500, 12, GRAPH # ─ 2502, 14, SHIFT GRAPH # │ 250c, 47, GRAPH # ┌ 2510, 56, GRAPH # ┐ 2514, 53, GRAPH # └ 2518, 43, GRAPH # ┘ 251c, 33, GRAPH # ├ 2524, 35, GRAPH # ┤ 252c, 51, GRAPH # ┬ 2534, 27, GRAPH # ┴ 253c, 34, GRAPH # ┼ 2571, 24, GRAPH # ╱ 2572, 14, GRAPH # ╲ 2573, 55, GRAPH # ╳ 2580, 36, SHIFT GRAPH # ▀ 2582, 52, GRAPH # ▂ 2584, 36, GRAPH # ▄ 2586, 44, GRAPH # ▆ 2588, 45, GRAPH # █ 258a, 41, GRAPH # ▊ 258c, 40, GRAPH # ▌ 258e, 37, GRAPH # ▎ 2590, 40, SHIFT GRAPH # ▐ 2594, 44, SHIFT GRAPH # ▔ 2595, 41, SHIFT GRAPH # ▕ 2596, 35, SHIFT GRAPH # ▖ 2597, 33, SHIFT GRAPH # ▗ 2598, 43, SHIFT GRAPH # ▘ 259a, 31, SHIFT GRAPH # ▚ 259d, 53, SHIFT GRAPH # ▝ 259e, 31, GRAPH # ▞ 25a7, 46, GRAPH # ▧ 25a8, 46, SHIFT GRAPH # ▨ 25a9, 45, SHIFT GRAPH # ▩ 25b2, 32, SHIFT GRAPH # ▲ 25b6, 54, GRAPH # ▶ 25bc, 32, GRAPH # ▼ 25c0, 54, SHIFT GRAPH # ◀ 25c7, 30, GRAPH # ◇ 25cb, 00, GRAPH # ○ 25d8, 11, SHIFT GRAPH # ◘ 25d9, 00, SHIFT GRAPH # ◙ 25fc, 26, SHIFT GRAPH # ◼ 25fe, 26, GRAPH # ◾ 263a, 15, GRAPH # ☺ 263b, 15, SHIFT GRAPH # ☻ 263c, 57, GRAPH # ☼ 2640, 42, SHIFT GRAPH # ♀ 2642, 42, GRAPH # ♂ 2660, 17, GRAPH # ♠ 2663, 20, GRAPH # ♣ 2665, 20, SHIFT GRAPH # ♥ 2666, 17, SHIFT GRAPH # ♦ 266a, 16, GRAPH # ♪ 266b, 16, SHIFT GRAPH # ♫ 29d3, 50, GRAPH # ⧓ openMSX-RELEASE_0_12_0/share/unicodemaps/unicodemap.int000066400000000000000000000201511257557151200227070ustar00rootroot00000000000000#region: International #format: , , # : hexadecimal unicode number or DEADKEY # : row in keyboard matrix (hexadecimal: 0-B) # : column in keyboard matrix (0-7) # : space separated list of modifiers: # SHIFT CTRL GRAPH CODE # # Example characters in comments are encoded in UTF-8 # DEADKEY1, 25, 0000, 02, CTRL SHIFT # ^@ 0001, 26, CTRL # ^A 0002, 27, CTRL # ^B 0003, 30, CTRL # ^C 0004, 31, CTRL # ^D 0005, 32, CTRL # ^E 0006, 33, CTRL # ^F 0007, 34, CTRL # ^G 0008, 75, # Backspace 0009, 73, # Tab 000a, 37, CTRL # ^J 000b, 81, # Home (is Home a unicode character?) 000c, 41, CTRL # ^L 000d, 77, # Enter/CR 000e, 43, CTRL # ^N 000f, 44, CTRL # ^O 0010, 45, CTRL # ^P 0011, 46, CTRL # ^Q 0012, 82, # Insert (is Insert a unicode character?) 0013, 50, CTRL # ^S 0014, 51, CTRL # ^T 0015, 52, CTRL # ^U 0016, 53, CTRL # ^V 0017, 54, CTRL # ^W 0018, 76, # Select (is Select a unicode character?) 0019, 56, CTRL # ^Y 001a, 57, CTRL # ^Z 001b, 72, # Escape(SDL maps ESC and ^[ to this code) 001c, 87, # Right (SDL maps ^\ to this code) 001d, 84, # Left (SDL maps ^] to this code) 001e, 85, # Up (SDL maps ^6 to this code) 001f, 86, # Down (SDL maps ^/ to this code) 0020, 80, # Space 0021, 01, SHIFT # ! 0022, 20, SHIFT # " 0023, 03, SHIFT # # 0024, 04, SHIFT # $ 0025, 05, SHIFT # % 0026, 07, SHIFT # & 0027, 20, # ' 0028, 11, SHIFT # ( 0029, 00, SHIFT # ) 002a, 10, SHIFT # * 002b, 13, SHIFT # + 002c, 22, # , 002d, 12, # - 002e, 23, # . 002f, 24, # / 0030, 00, # 0 0031, 01, # 1 0032, 02, # 2 0033, 03, # 3 0034, 04, # 4 0035, 05, # 5 0036, 06, # 6 0037, 07, # 7 0038, 10, # 8 0039, 11, # 9 003a, 17, SHIFT # : 003b, 17, # ; 003c, 22, SHIFT # < 003d, 13, # = 003e, 23, SHIFT # > 003f, 24, SHIFT # ? 0040, 02, SHIFT # @ 0041, 26, SHIFT # A 0042, 27, SHIFT # B 0043, 30, SHIFT # C 0044, 31, SHIFT # D 0045, 32, SHIFT # E 0046, 33, SHIFT # F 0047, 34, SHIFT # G 0048, 35, SHIFT # H 0049, 36, SHIFT # I 004a, 37, SHIFT # J 004b, 40, SHIFT # K 004c, 41, SHIFT # L 004d, 42, SHIFT # M 004e, 43, SHIFT # N 004f, 44, SHIFT # O 0050, 45, SHIFT # P 0051, 46, SHIFT # Q 0052, 47, SHIFT # R 0053, 50, SHIFT # S 0054, 51, SHIFT # T 0055, 52, SHIFT # U 0056, 53, SHIFT # V 0057, 54, SHIFT # W 0058, 55, SHIFT # X 0059, 56, SHIFT # Y 005a, 57, SHIFT # Z 005b, 15, # [ 005c, 14, # \ 005d, 16, # ] 005e, 06, SHIFT # ^ 005f, 12, SHIFT # _ 0060, 21, # ` 0061, 26, # a 0062, 27, # b 0063, 30, # c 0064, 31, # d 0065, 32, # e 0066, 33, # f 0067, 34, # g 0068, 35, # h 0069, 36, # i 006a, 37, # j 006b, 40, # k 006c, 41, # l 006d, 42, # m 006e, 43, # n 006f, 44, # o 0070, 45, # p 0071, 46, # q 0072, 47, # r 0073, 50, # s 0074, 51, # t 0075, 52, # u 0076, 53, # v 0077, 54, # w 0078, 55, # x 0079, 56, # y 007a, 57, # z 007b, 15, SHIFT # { 007c, 14, SHIFT # | 007d, 16, SHIFT # } 007e, 21, SHIFT # ~ 007f, 83, # Delete 00a0, 80, # No-break space ( ) 00a1, 01, SHIFT CODE # ¡ 00a2, 04, CODE # ¢ 00a3, 04, SHIFT CODE # £ 00a5, 05, SHIFT CODE # ¥ 00a7, 03, CODE # § 00aa, 23, CODE # ª 00ab, 22, SHIFT GRAPH # « 00ac, 56, SHIFT GRAPH # ¬ 00b0, 57, SHIFT GRAPH # ° 00b1, 13, GRAPH # ± 00b2, 02, SHIFT GRAPH # ² 00b5, 42, CODE # µ 00b6, 03, SHIFT CODE # ¶ 00b7, 30, SHIFT GRAPH # · 00ba, 24, CODE # º 00bb, 23, SHIFT GRAPH # » 00bc, 01, GRAPH # ¼ 00bd, 02, GRAPH # ½ 00be, 03, GRAPH # ¾ 00bf, 24, SHIFT CODE # ¿ 00c3, 35, SHIFT CODE # Ã 00c4, 26, SHIFT CODE # Ä 00c5, 22, SHIFT CODE # Å 00c6, 37, SHIFT CODE # Æ 00c7, 11, SHIFT CODE # Ç 00c9, 52, SHIFT CODE # É 00d1, 43, SHIFT CODE # Ñ 00d5, 41, SHIFT CODE # Õ 00d6, 33, SHIFT CODE # Ö 00dc, 34, SHIFT CODE # Ü 00df, 07, CODE # ß 00e0, 57, CODE # à 00e1, 56, CODE # á 00e2, 46, CODE # â 00e3, 35, CODE # ã 00e4, 26, CODE # ä 00e5, 22, CODE # å 00e6, 37, CODE # æ 00e7, 11, CODE # ç 00e8, 55, CODE # è 00e9, 52, CODE # é 00ea, 54, CODE # ê 00eb, 50, CODE # ë 00ec, 30, CODE # ì 00ed, 36, CODE # í 00ee, 32, CODE # î 00ef, 31, CODE # ï 00f1, 43, CODE # ñ 00f2, 53, CODE # ò 00f3, 44, CODE # ó 00f4, 47, CODE # ô 00f5, 41, CODE # õ 00f6, 33, CODE # ö 00f7, 24, SHIFT GRAPH # ÷ 00f9, 27, CODE # ù 00fa, 45, CODE # ú 00fb, 51, CODE # û 00fc, 34, CODE # ü 00ff, 05, CODE # ÿ 0128, 40, SHIFT CODE # Ĩ 0129, 40, CODE # ĩ 0132, 20, SHIFT CODE # IJ 0133, 20, CODE # ij 0168, 17, SHIFT CODE # Ũ 0169, 17, CODE # ũ 0192, 01, CODE # ƒ 0393, 10, SHIFT CODE # Γ 0394, 00, SHIFT CODE # Δ 03a3, 21, SHIFT CODE # Σ 03a6, 15, SHIFT CODE # Φ 03a9, 16, SHIFT CODE # Ω 03b1, 06, CODE # α 03b4, 00, CODE # δ 03b5, 12, CODE # ε 03b8, 13, CODE # θ 03c0, 45, SHIFT CODE # π 03c3, 21, CODE # σ 03c4, 10, CODE # τ 03c6, 15, CODE # φ 03c9, 16, CODE # ω 2022, 55, SHIFT GRAPH # • 2027, 11, GRAPH # ‧ 2030, 05, GRAPH # ‰ 207f, 03, SHIFT GRAPH # ⁿ 20a7, 02, SHIFT CODE # ₧ 221a, 07, GRAPH # √ 221e, 10, GRAPH # ∞ 2229, 04, GRAPH # ∩ 223d, 21, GRAPH # ∽ 2248, 21, SHIFT GRAPH # ≈ 2260, 02, CODE # ≠ 2261, 13, SHIFT GRAPH # ≡ 2264, 22, GRAPH # ≤ 2265, 23, GRAPH # ≥ 2310, 47, SHIFT GRAPH # ⌐ 2320, 06, GRAPH # ⌠ 2321, 06, SHIFT GRAPH # ⌡ 2500, 12, GRAPH # ─ 2502, 14, SHIFT GRAPH # │ 250c, 47, GRAPH # ┌ 2510, 56, GRAPH # ┐ 2514, 53, GRAPH # └ 2518, 43, GRAPH # ┘ 251c, 33, GRAPH # ├ 2524, 35, GRAPH # ┤ 252c, 51, GRAPH # ┬ 2534, 27, GRAPH # ┴ 253c, 34, GRAPH # ┼ 2571, 24, GRAPH # ╱ 2572, 14, GRAPH # ╲ 2573, 55, GRAPH # ╳ 2580, 36, SHIFT GRAPH # ▀ 2582, 52, GRAPH # ▂ 2584, 36, GRAPH # ▄ 2586, 44, GRAPH # ▆ 2588, 45, GRAPH # █ 258a, 41, GRAPH # ▊ 258c, 40, GRAPH # ▌ 258e, 37, GRAPH # ▎ 2590, 40, SHIFT GRAPH # ▐ 2594, 44, SHIFT GRAPH # ▔ 2595, 41, SHIFT GRAPH # ▕ 2596, 35, SHIFT GRAPH # ▖ 2597, 33, SHIFT GRAPH # ▗ 2598, 43, SHIFT GRAPH # ▘ 259a, 31, SHIFT GRAPH # ▚ 259d, 53, SHIFT GRAPH # ▝ 259e, 31, GRAPH # ▞ 25a7, 46, GRAPH # ▧ 25a8, 46, SHIFT GRAPH # ▨ 25a9, 45, SHIFT GRAPH # ▩ 25b2, 32, SHIFT GRAPH # ▲ 25b6, 54, GRAPH # ▶ 25bc, 32, GRAPH # ▼ 25c0, 54, SHIFT GRAPH # ◀ 25c7, 30, GRAPH # ◇ 25cb, 00, GRAPH # ○ 25d8, 11, SHIFT GRAPH # ◘ 25d9, 00, SHIFT GRAPH # ◙ 25fc, 26, SHIFT GRAPH # ◼ 25fe, 26, GRAPH # ◾ 263a, 15, GRAPH # ☺ 263b, 15, SHIFT GRAPH # ☻ 263c, 57, GRAPH # ☼ 2640, 42, SHIFT GRAPH # ♀ 2642, 42, GRAPH # ♂ 2660, 17, GRAPH # ♠ 2663, 20, GRAPH # ♣ 2665, 20, SHIFT GRAPH # ♥ 2666, 17, SHIFT GRAPH # ♦ 266a, 16, GRAPH # ♪ 266b, 16, SHIFT GRAPH # ♫ 29d3, 50, GRAPH # ⧓ 29d7, 50, SHIFT GRAPH # openMSX-RELEASE_0_12_0/share/unicodemaps/unicodemap.jp_ansi000066400000000000000000000146551257557151200235540ustar00rootroot00000000000000#region: Japanese ANSI #format: , , # : hexadecimal unicode number or DEADKEY # : row in keyboard matrix (hexadecimal: 0-B) # : column in keyboard matrix (0-7) # : space separated list of modifiers: # SHIFT CTRL GRAPH CODE CAPSLOCK 0000, 02, CTRL SHIFT # ^@ 0001, 26, CTRL # ^A 0002, 27, CTRL # ^B 0003, 30, CTRL # ^C 0004, 31, CTRL # ^D 0005, 32, CTRL # ^E 0006, 33, CTRL # ^F 0007, 34, CTRL # ^G 0008, 75, # Backspace 0009, 73, # Tab 000a, 37, CTRL # ^J 000b, 81, # Home (is Home a unicode character?) 000c, 41, CTRL # ^L 000d, 77, # Enter/CR 000e, 43, CTRL # ^N 000f, 44, CTRL # ^O 0010, 45, CTRL # ^P 0011, 46, CTRL # ^Q 0012, 82, # Insert (is Insert a unicode character?) 0013, 50, CTRL # ^S 0014, 51, CTRL # ^T 0015, 52, CTRL # ^U 0016, 53, CTRL # ^V 0017, 54, CTRL # ^W 0018, 76, # Select (is Select a unicode character?) 0019, 56, CTRL # ^Y 001a, 57, CTRL # ^Z 001b, 72, # Escape(SDL maps ESC and ^[ to this code) 001c, 87, # Right (SDL maps ^\ to this code) 001d, 84, # Left (SDL maps ^] to this code) 001e, 85, # Up (SDL maps ^6 to this code) 001f, 86, # Down (SDL maps ^/ to this code) 0020, 80, 0021, 01, SHIFT 0022, 02, SHIFT 0023, 03, SHIFT 0024, 04, SHIFT 0025, 05, SHIFT 0026, 06, SHIFT 0027, 07, SHIFT 0028, 10, SHIFT 0029, 11, SHIFT 002a, 20, SHIFT 002b, 17, SHIFT 002c, 22, 002d, 12, 002e, 23, 002f, 24, 0030, 00, 0031, 01, 0032, 02, 0033, 03, 0034, 04, 0035, 05, 0036, 06, 0037, 07, 0038, 10, 0039, 11, 003a, 20, 003b, 17, 003c, 22, SHIFT 003d, 12, SHIFT 003e, 23, SHIFT 003f, 24, SHIFT 0040, 15, 0041, 26, SHIFT 0042, 27, SHIFT 0043, 30, SHIFT 0044, 31, SHIFT 0045, 32, SHIFT 0046, 33, SHIFT 0047, 34, SHIFT 0048, 35, SHIFT 0049, 36, SHIFT 004a, 37, SHIFT 004b, 40, SHIFT 004c, 41, SHIFT 004d, 42, SHIFT 004e, 43, SHIFT 004f, 44, SHIFT 0050, 45, SHIFT 0051, 46, SHIFT 0052, 47, SHIFT 0053, 50, SHIFT 0054, 51, SHIFT 0055, 52, SHIFT 0056, 53, SHIFT 0057, 54, SHIFT 0058, 55, SHIFT 0059, 56, SHIFT 005a, 57, SHIFT 005b, 16, 005c, 14, # backslash 005d, 21, 005e, 13, 005f, 25, SHIFT 0060, 15, SHIFT 0061, 26, 0062, 27, 0063, 30, 0064, 31, 0065, 32, 0066, 33, 0067, 34, 0068, 35, 0069, 36, 006a, 37, 006b, 40, 006c, 41, 006d, 42, 006e, 43, 006f, 44, 0070, 45, 0071, 46, 0072, 47, 0073, 50, 0074, 51, 0075, 52, 0076, 53, 0077, 54, 0078, 55, 0079, 56, 007a, 57, 007b, 16, SHIFT 007c, 14, SHIFT 007d, 21, SHIFT 007e, 13, SHIFT 007f, 83, # Delete 00a0, 80, 00a1, 42, SHIFT CAPSLOCK CODE 00a5, 14, # yen 00aa, 24, CAPSLOCK CODE 00ab, 22, SHIFT CAPSLOCK CODE 00ac, 04, SHIFT CAPSLOCK CODE 00b2, 25, CODE 00ba, 01, SHIFT CAPSLOCK CODE 00bb, 30, SHIFT CAPSLOCK CODE 00bc, 43, SHIFT CAPSLOCK CODE 00bd, 05, SHIFT CAPSLOCK CODE 00bf, 02, SHIFT CAPSLOCK CODE 00c9, 80, 00d1, 25, SHIFT CODE 00e1, 80, 03C0, 45, GRAPH 2310, 03, SHIFT CAPSLOCK CODE 2500, 12, GRAPH 2502, 36, GRAPH 250C, 32, GRAPH 2510, 51, GRAPH 2514, 30, GRAPH 2518, 27, GRAPH 251C, 31, GRAPH 2524, 34, GRAPH 252C, 47, GRAPH 2534, 53, GRAPH 253C, 33, GRAPH 2550, 44, CAPSLOCK CODE 2551, 51, CAPSLOCK CODE 2552, 42, CAPSLOCK CODE 2553, 22, CAPSLOCK CODE 2554, 00, CAPSLOCK CODE 2555, 32, CAPSLOCK CODE 2556, 54, CAPSLOCK CODE 2557, 26, CAPSLOCK CODE 2558, 43, CAPSLOCK CODE 2559, 17, CAPSLOCK CODE 255a, 11, CAPSLOCK CODE 255b, 33, CAPSLOCK CODE 255c, 31, CAPSLOCK CODE 255d, 50, CAPSLOCK CODE 255e, 07, CAPSLOCK CODE 255f, 10, CAPSLOCK CODE 2560, 36, CAPSLOCK CODE 2561, 05, CAPSLOCK CODE 2562, 46, CAPSLOCK CODE 2563, 47, CAPSLOCK CODE 2564, 40, CAPSLOCK CODE 2565, 41, CAPSLOCK CODE 2566, 52, CAPSLOCK CODE 2567, 35, CAPSLOCK CODE 2568, 37, CAPSLOCK CODE 2569, 56, CAPSLOCK CODE 256a, 13, CAPSLOCK CODE 256b, 12, CAPSLOCK CODE 256c, 45, CAPSLOCK CODE 2573, 55, GRAPH 2584, 23, CAPSLOCK CODE 2588, 16, CAPSLOCK CODE 258c, 25, CAPSLOCK CODE 2592, 01, CAPSLOCK CODE 2593, 02, CAPSLOCK CODE 25a0, 80, 25CB, 16, GRAPH 25CF, 21, GRAPH 2660, 24, GRAPH 2663, 17, GRAPH 2665, 20, GRAPH 2666, 25, GRAPH 3001, 22, SHIFT CODE 3002, 23, SHIFT CODE 3008, 16, SHIFT CODE 3009, 21, SHIFT CODE 3041, 01, SHIFT CODE 3042, 01, CODE 3043, 02, SHIFT CODE 3044, 02, CODE 3045, 03, SHIFT CODE 3046, 03, CODE 3047, 04, SHIFT CODE 3048, 04, CODE 3049, 05, SHIFT CODE 304A, 05, CODE 304B, 46, CODE 304D, 54, CODE 304F, 32, CODE 3051, 47, CODE 3053, 51, CODE 3055, 26, CODE 3057, 50, CODE 3059, 31, CODE 305B, 33, CODE 305D, 34, CODE 305F, 57, CODE 3061, 55, CODE 3063, 30, SHIFT CODE 3064, 30, CODE 3066, 53, CODE 3068, 27, CODE 306A, 06, CODE 306B, 07, CODE 306C, 10, CODE 306D, 11, CODE 306E, 00, CODE 306F, 56, CODE 3072, 52, CODE 3075, 36, CODE 3078, 44, CODE 307B, 45, CODE 307E, 35, CODE 307F, 37, CODE 3080, 40, CODE 3081, 41, CODE 3082, 17, CODE 3083, 43, SHIFT CODE 3084, 43, CODE 3085, 42, SHIFT CODE 3086, 42, CODE 3087, 22, SHIFT CODE 3088, 22, CODE 3089, 12, CODE 308A, 13, CODE 308B, 14, CODE 308C, 15, CODE 308D, 16, CODE 308F, 23, CODE 3092, 24, CODE 3093, 25, CODE 309b, 15, CODE 309B, 20, CODE 309c, 16, CODE 309C, 21, CODE 30a1, 03, SHIFT CODE 30a2, 03, CODE 30a3, 32, SHIFT CODE 30a4, 32, CODE 30a5, 04, SHIFT CODE 30a6, 04, CODE 30a7, 05, SHIFT CODE 30a8, 05, CODE 30a9, 06, SHIFT CODE 30aa, 06, CODE 30ab, 51, CODE 30ad, 34, CODE 30af, 35, CODE 30b1, 20, CODE 30b3, 27, CODE 30b5, 55, CODE 30b7, 31, CODE 30b9, 47, CODE 30bb, 45, CODE 30bd, 30, CODE 30bf, 46, CODE 30c1, 26, CODE 30c3, 57, SHIFT CODE 30c4, 57, CODE 30c6, 54, CODE 30c8, 50, CODE 30ca, 52, CODE 30cb, 36, CODE 30cc, 01, CODE 30cd, 22, CODE 30ce, 40, CODE 30cf, 33, CODE 30d2, 53, CODE 30d5, 02, CODE 30d8, 13, CODE 30db, 12, CODE 30de, 37, CODE 30df, 43, CODE 30e0, 21, CODE 30e1, 24, CODE 30e2, 42, CODE 30e3, 07, SHIFT CODE 30e4, 07, CODE 30e5, 10, SHIFT CODE 30e6, 10, CODE 30e7, 11, SHIFT CODE 30e8, 11, CODE 30e9, 44, CODE 30ea, 41, CODE 30eb, 23, CODE 30ec, 17, CODE 30ed, 25, CODE 30ed, 25, CODE 30ef, 00, CODE 30f2, 00, SHIFT CODE 30f3, 56, CODE 30fb, 24, SHIFT CODE 30fc, 14, CODE 30FC, 20, SHIFT CODE 4E07, 00, GRAPH 4E2D, 41, GRAPH 5186, 14, GRAPH 5206, 42, GRAPH 5343, 11, GRAPH 571F, 07, GRAPH 5927, 23, GRAPH 5C0F, 22, GRAPH 5E74, 56, GRAPH 65E5, 01, GRAPH 6642, 35, GRAPH 6708, 02, GRAPH 6728, 05, GRAPH 6C34, 04, GRAPH 706B, 03, GRAPH 767E, 10, GRAPH 79D2, 50, GRAPH 91D1, 06, GRAPH FF61, 24, SHIFT CODE FF62, 16, SHIFT CODE FF63, 21, SHIFT CODE FF64, 23, SHIFT CODE FF65, 25, SHIFT CODE openMSX-RELEASE_0_12_0/share/unicodemaps/unicodemap.jp_jis000066400000000000000000000163101257557151200233750ustar00rootroot00000000000000#region: Japanese JIS #format: , , # : hexadecimal unicode number or DEADKEY # : row in keyboard matrix (hexadecimal: 0-B) # : column in keyboard matrix (0-7) # : space separated list of modifiers: # SHIFT CTRL GRAPH CODE CAPSLOCK 0000, 02, CTRL SHIFT # ^@ 0001, 26, CTRL # ^A 0002, 27, CTRL # ^B 0003, 30, CTRL # ^C 0004, 31, CTRL # ^D 0005, 32, CTRL # ^E 0006, 33, CTRL # ^F 0007, 34, CTRL # ^G 0008, 75, # Backspace 0009, 73, # Tab 000a, 37, CTRL # ^J 000b, 81, # Home (is Home a unicode character?) 000c, 41, CTRL # ^L 000d, 77, # Enter/CR 000e, 43, CTRL # ^N 000f, 44, CTRL # ^O 0010, 45, CTRL # ^P 0011, 46, CTRL # ^Q 0012, 82, # Insert (is Insert a unicode character?) 0013, 50, CTRL # ^S 0014, 51, CTRL # ^T 0015, 52, CTRL # ^U 0016, 53, CTRL # ^V 0017, 54, CTRL # ^W 0018, 76, # Select (is Select a unicode character?) 0019, 56, CTRL # ^Y 001a, 57, CTRL # ^Z 001b, 72, # Escape(SDL maps ESC and ^[ to this code) 001c, 87, # Right (SDL maps ^\ to this code) 001d, 84, # Left (SDL maps ^] to this code) 001e, 85, # Up (SDL maps ^6 to this code) 001f, 86, # Down (SDL maps ^/ to this code) 0020, 80, 0021, 01, SHIFT 0022, 02, SHIFT 0023, 03, SHIFT 0024, 04, SHIFT 0025, 05, SHIFT 0026, 06, SHIFT 0027, 07, SHIFT 0028, 10, SHIFT 0029, 11, SHIFT 002a, 20, SHIFT 002b, 17, SHIFT 002c, 22, 002d, 12, 002e, 23, 002f, 24, 0030, 00, # 0 0031, 01, # 1 0032, 02, # 2 0033, 03, # 3 0034, 04, # 4 0035, 05, # 5 0036, 06, # 6 0037, 07, # 7 0038, 10, # 8 0039, 11, # 9 003a, 20, # : 003b, 17, # ; 003c, 22, SHIFT 003d, 12, SHIFT 003e, 23, SHIFT 003f, 24, SHIFT 0040, 15, 0041, 26, SHIFT 0042, 27, SHIFT 0043, 30, SHIFT 0044, 31, SHIFT 0045, 32, SHIFT 0046, 33, SHIFT 0047, 34, SHIFT 0048, 35, SHIFT 0049, 36, SHIFT 004a, 37, SHIFT 004b, 40, SHIFT 004c, 41, SHIFT 004d, 42, SHIFT 004e, 43, SHIFT 004f, 44, SHIFT 0050, 45, SHIFT 0051, 46, SHIFT 0052, 47, SHIFT 0053, 50, SHIFT 0054, 51, SHIFT 0055, 52, SHIFT 0056, 53, SHIFT 0057, 54, SHIFT 0058, 55, SHIFT 0059, 56, SHIFT 005a, 57, SHIFT 005b, 16, 005c, 14, # backslash 005d, 21, 005e, 13, 005f, 25, SHIFT 0060, 15, SHIFT 0061, 26, 0062, 27, 0063, 30, 0064, 31, 0065, 32, 0066, 33, 0067, 34, 0068, 35, 0069, 36, 006a, 37, 006b, 40, 006c, 41, 006d, 42, 006e, 43, 006f, 44, 0070, 45, 0071, 46, 0072, 47, 0073, 50, 0074, 51, 0075, 52, 0076, 53, 0077, 54, 0078, 55, 0079, 56, 007a, 57, 007b, 16, SHIFT 007c, 14, SHIFT 007d, 21, SHIFT #007e, 00, SHIFT # ~ 007e, 13, SHIFT 007f, 83, # Delete 00a0, 80, 00a1, 42, SHIFT CAPSLOCK CODE 00A5, 14, # yen 00aa, 24, CAPSLOCK CODE 00ab, 22, SHIFT CAPSLOCK CODE 00ac, 04, SHIFT CAPSLOCK CODE 00b2, 25, CODE 00ba, 01, SHIFT CAPSLOCK CODE 00bb, 30, SHIFT CAPSLOCK CODE 00bc, 43, SHIFT CAPSLOCK CODE 00bd, 05, SHIFT CAPSLOCK CODE 00bf, 02, SHIFT CAPSLOCK CODE 00c5, 30, SHIFT CODE 00c9, 80, 00d1, 25, SHIFT CODE 00e1, 80, 00e7, 01, SHIFT CODE 00ea, 02, SHIFT CODE 00ec, 42, SHIFT CODE 00ee, 43, SHIFT CODE 03C0, 45, GRAPH 2310, 03, SHIFT CAPSLOCK CODE 2500, 12, GRAPH 2502, 36, GRAPH 250C, 32, GRAPH 2510, 51, GRAPH 2514, 30, GRAPH 2518, 27, GRAPH 251C, 31, GRAPH 2524, 34, GRAPH 252C, 47, GRAPH 2534, 53, GRAPH 253C, 33, GRAPH 2550, 44, CAPSLOCK CODE 2551, 51, CAPSLOCK CODE 2552, 42, CAPSLOCK CODE 2553, 22, CAPSLOCK CODE 2554, 00, CAPSLOCK CODE 2555, 32, CAPSLOCK CODE 2556, 54, CAPSLOCK CODE 2557, 26, CAPSLOCK CODE 2558, 43, CAPSLOCK CODE 2559, 17, CAPSLOCK CODE 255a, 11, CAPSLOCK CODE 255b, 33, CAPSLOCK CODE 255c, 31, CAPSLOCK CODE 255d, 50, CAPSLOCK CODE 255e, 07, CAPSLOCK CODE 255f, 10, CAPSLOCK CODE 2560, 36, CAPSLOCK CODE 2561, 05, CAPSLOCK CODE 2562, 46, CAPSLOCK CODE 2563, 47, CAPSLOCK CODE 2564, 40, CAPSLOCK CODE 2565, 41, CAPSLOCK CODE 2566, 52, CAPSLOCK CODE 2567, 35, CAPSLOCK CODE 2568, 37, CAPSLOCK CODE 2569, 56, CAPSLOCK CODE 256a, 13, CAPSLOCK CODE 256b, 12, CAPSLOCK CODE 256c, 45, CAPSLOCK CODE 2573, 55, GRAPH 2584, 23, CAPSLOCK CODE 2588, 16, CAPSLOCK CODE 258c, 25, CAPSLOCK CODE 2591, 20, SHIFT CODE 2592, 01, CAPSLOCK CODE 2593, 02, CAPSLOCK CODE 25a0, 80, 25CB, 16, GRAPH 25CF, 21, GRAPH 2660, 24, GRAPH 2663, 17, GRAPH 2665, 20, GRAPH 2666, 25, GRAPH 3001, 22, SHIFT CODE 3002, 23, SHIFT CODE 3008, 16, SHIFT CODE 3009, 21, SHIFT CODE 3041, 03, SHIFT CODE 3042, 03, CODE 3043, 32, SHIFT CODE 3044, 32, CODE 3045, 04, SHIFT CODE 3046, 04, CODE 3047, 05, SHIFT CODE 3048, 05, CODE 3049, 06, SHIFT CODE 304A, 06, CODE 304B, 51, CODE 304D, 34, CODE 304F, 35, CODE 3051, 20, CODE 3053, 27, CODE 3055, 55, CODE 3057, 31, CODE 3059, 47, CODE 305B, 45, CODE 305D, 30, CODE 305F, 46, CODE 3061, 26, CODE 3063, 57, SHIFT CODE 3064, 57, CODE 3066, 54, CODE 3068, 50, CODE 306A, 52, CODE 306B, 36, CODE 306D, 22, CODE 306E, 40, CODE 306F, 33, CODE 3072, 53, CODE 3075, 02, CODE 3078, 13, CODE 307B, 12, CODE 307E, 37, CODE 307F, 43, CODE 3080, 21, CODE 3081, 24, CODE 3082, 42, CODE 3083, 07, SHIFT CODE 3084, 07, CODE 3085, 10, SHIFT CODE 3086, 10, CODE 3087, 11, SHIFT CODE 3088, 11, CODE 3089, 44, CODE 308A, 41, CODE 308B, 23, CODE 308C, 17, CODE 308D, 25, CODE 308F, 00, CODE 3092, 00, SHIFT CODE 3093, 56, CODE 309b, 15, CODE 309B, 15, CODE 309c, 16, CODE 30a1, 03, SHIFT CODE CAPSLOCK 30a2, 03, CODE CAPSLOCK 30a3, 32, SHIFT CODE CAPSLOCK 30a4, 32, CODE CAPSLOCK 30a5, 04, SHIFT CODE CAPSLOCK 30a6, 04, CODE CAPSLOCK 30a7, 05, SHIFT CODE CAPSLOCK 30a8, 05, CODE CAPSLOCK 30a9, 06, SHIFT CODE CAPSLOCK 30aa, 06, CODE CAPSLOCK 30ab, 51, CODE CAPSLOCK 30ad, 34, CODE CAPSLOCK 30af, 35, CODE CAPSLOCK 30b1, 20, CODE CAPSLOCK 30b3, 27, CODE CAPSLOCK 30b5, 55, CODE CAPSLOCK 30b7, 31, CODE CAPSLOCK 30b9, 47, CODE CAPSLOCK 30bb, 45, CODE CAPSLOCK 30bd, 30, CODE CAPSLOCK 30bf, 46, CODE CAPSLOCK 30c1, 26, CODE CAPSLOCK 30c3, 57, SHIFT CODE CAPSLOCK 30c4, 57, CODE CAPSLOCK 30c6, 54, CODE CAPSLOCK 30c8, 50, CODE CAPSLOCK 30ca, 52, CODE CAPSLOCK 30cb, 36, CODE CAPSLOCK 30cc, 01, CODE CAPSLOCK 30cd, 22, CODE CAPSLOCK 30ce, 40, CODE CAPSLOCK 30cf, 33, CODE CAPSLOCK 30d2, 53, CODE CAPSLOCK 30d5, 02, CODE CAPSLOCK 30d8, 13, CODE CAPSLOCK 30db, 12, CODE CAPSLOCK 30de, 37, CODE CAPSLOCK 30df, 43, CODE CAPSLOCK 30e0, 21, CODE CAPSLOCK 30e1, 24, CODE CAPSLOCK 30e2, 42, CODE CAPSLOCK 30e3, 07, SHIFT CODE CAPSLOCK 30e4, 07, CODE CAPSLOCK 30e5, 10, SHIFT CODE CAPSLOCK 30e6, 10, CODE CAPSLOCK 30e7, 11, SHIFT CODE CAPSLOCK 30e8, 11, CODE CAPSLOCK 30e9, 44, CODE CAPSLOCK 30ea, 41, CODE CAPSLOCK 30eb, 23, CODE CAPSLOCK 30ec, 17, CODE CAPSLOCK 30ed, 25, CODE CAPSLOCK 30ed, 25, CODE CAPSLOCK 30ef, 00, CODE CAPSLOCK 30f2, 00, SHIFT CODE CAPSLOCK 30f3, 56, CODE CAPSLOCK 30fb, 24, SHIFT CODE CAPSLOCK 30fc, 14, CODE CAPSLOCK 4E07, 00, GRAPH 4E2D, 41, GRAPH 5186, 14, GRAPH 5206, 42, GRAPH 5343, 11, GRAPH 571F, 07, GRAPH 5927, 23, GRAPH 5C0F, 22, GRAPH 5E74, 56, GRAPH 65E5, 01, GRAPH 6642, 35, GRAPH 6708, 02, GRAPH 6728, 05, GRAPH 6C34, 04, GRAPH 706B, 03, GRAPH 767E, 10, GRAPH 79D2, 50, GRAPH 91D1, 06, GRAPH FF61, 23, SHIFT CODE FF62, 16, SHIFT CODE FF63, 21, SHIFT CODE FF64, 22, SHIFT CODE FF65, 24, SHIFT CODE openMSX-RELEASE_0_12_0/share/unicodemaps/unicodemap.kr000066400000000000000000000104661257557151200225410ustar00rootroot00000000000000#region: Korean #format: , , # : hexadecimal unicode number or DEADKEY # : row in keyboard matrix (hexadecimal: 0-B) # : column in keyboard matrix (0-7) # : space separated list of modifiers: # SHIFT CTRL GRAPH CODE CAPSLOCK 0000, 02, CTRL SHIFT # ^@ 0001, 26, CTRL # ^A 0002, 27, CTRL # ^B 0003, 30, CTRL # ^C 0004, 31, CTRL # ^D 0005, 32, CTRL # ^E 0006, 33, CTRL # ^F 0007, 34, CTRL # ^G 0008, 75, # Backspace 0009, 73, # Tab 000a, 37, CTRL # ^J 000b, 81, # Home (is Home a unicode character?) 000c, 41, CTRL # ^L 000d, 77, # Enter/CR 000e, 43, CTRL # ^N 000f, 44, CTRL # ^O 0010, 45, CTRL # ^P 0011, 46, CTRL # ^Q 0012, 82, # Insert (is Insert a unicode character?) 0013, 50, CTRL # ^S 0014, 51, CTRL # ^T 0015, 52, CTRL # ^U 0016, 53, CTRL # ^V 0017, 54, CTRL # ^W 0018, 76, # Select (is Select a unicode character?) 0019, 56, CTRL # ^Y 001a, 57, CTRL # ^Z 001b, 72, # Escape(SDL maps ESC and ^[ to this code) 001c, 87, # Right (SDL maps ^\ to this code) 001d, 84, # Left (SDL maps ^] to this code) 001e, 85, # Up (SDL maps ^6 to this code) 001f, 86, # Down (SDL maps ^/ to this code) 007f, 83, # Delete 0020, 80, 0021, 01, SHIFT 0022, 02, SHIFT 0023, 03, SHIFT 0024, 04, SHIFT 0025, 05, SHIFT 0026, 06, SHIFT 0027, 07, SHIFT 0028, 10, SHIFT 0029, 11, SHIFT 002a, 20, SHIFT 002b, 17, SHIFT 002c, 22, 002d, 12, 002e, 23, 002f, 24, 0030, 00, 0031, 01, 0032, 02, 0033, 03, 0034, 04, 0035, 05, 0036, 06, 0037, 07, 0038, 10, 0039, 11, 003a, 20, 003b, 17, 003c, 22, SHIFT 003d, 12, SHIFT 003e, 23, SHIFT 003f, 24, SHIFT 0040, 15, 0041, 26, SHIFT 0042, 27, SHIFT 0043, 30, SHIFT 0044, 31, SHIFT 0045, 32, SHIFT 0046, 33, SHIFT 0047, 34, SHIFT 0048, 35, SHIFT 0049, 36, SHIFT 004a, 37, SHIFT 004b, 40, SHIFT 004c, 41, SHIFT 004d, 42, SHIFT 004e, 43, SHIFT 004f, 44, SHIFT 0050, 45, SHIFT 0051, 46, SHIFT 0052, 47, SHIFT 0053, 50, SHIFT 0054, 51, SHIFT 0055, 52, SHIFT 0056, 53, SHIFT 0057, 54, SHIFT 0058, 55, SHIFT 0059, 56, SHIFT 005a, 57, SHIFT 005b, 16, 005c, 14, 005d, 21, 005e, 13, 005f, 25, SHIFT 0060, 15, SHIFT 0061, 26, 0062, 27, 0063, 30, 0064, 31, 0065, 32, 0066, 33, 0067, 34, 0068, 35, 0069, 36, 006a, 37, 006b, 40, 006c, 41, 006d, 42, 006e, 43, 006f, 44, 0070, 45, 0071, 46, 0072, 47, 0073, 50, 0074, 51, 0075, 52, 0076, 53, 0077, 54, 0078, 55, 0079, 56, 007a, 57, 007b, 16, SHIFT 007c, 14, SHIFT 007d, 21, SHIFT 007e, 13, SHIFT 00a0, 80, 00a1, 80, 00a2, 36, CODE 00a3, 44, SHIFT CODE 00a5, 37, CODE 00a7, 33, GRAPH 00aa, 41, CODE 00ab, 80, 00ac, 80, 00b0, 80, 00b1, 80, 00b2, 80, 00b5, 80, 00b6, 31, GRAPH 00b7, 80, 00ba, 80, 00bb, 80, 00bc, 80, 00bd, 80, 00bf, 80, 00c4, 46, SHIFT CODE 00c5, 51, CODE 00c6, 54, CODE 00c7, 24, GRAPH 00c9, 51, SHIFT CODE 00d1, 42, CODE 00d6, 40, CODE 00dc, 44, CODE 00df, 80, 00e0, 21, GRAPH 00e1, 45, SHIFT CODE 00e2, 25, GRAPH 00e4, 16, GRAPH 00e5, 47, CODE 00e6, 31, CODE 00e7, 47, SHIFT CODE 00e8, 32, SHIFT CODE 00e9, 17, GRAPH 00ea, 50, CODE 00eb, 32, CODE 00ec, 46, CODE 00ed, 35, CODE 00ee, 26, CODE 00ef, 33, CODE 00f1, 27, CODE 00f2, 57, CODE 00f3, 56, CODE 00f4, 54, SHIFT CODE 00f6, 30, CODE 00f7, 80, 00f9, 53, CODE 00fa, 43, CODE 00fb, 55, CODE 00fc, 20, GRAPH 00ff, 34, CODE 0192, 52, CODE 0393, 80, 0398, 80, 03a3, 80, 03a6, 80, 03a9, 80, 03b1, 80, 03b4, 80, 03b5, 80, 03c0, 80, 03c3, 80, 03c4, 80, 03c6, 80, 203c, 34, GRAPH 207f, 80, 20a7, 45, CODE 2190, 27, GRAPH 2191, 32, GRAPH 2192, 30, GRAPH 2193, 51, GRAPH 2195, 47, GRAPH 21a8, 12, GRAPH 2219, 80, 221a, 80, 221e, 80, 221f, 55, GRAPH 2229, 80, 2248, 80, 2261, 80, 2264, 80, 2265, 80, 2310, 80, 2320, 80, 2321, 80, 2500, 80, 2502, 80, 250c, 80, 2510, 80, 2514, 80, 2518, 80, 251c, 80, 2524, 80, 252c, 80, 2534, 80, 253c, 80, 2550, 80, 2551, 80, 2552, 80, 2553, 80, 2554, 80, 2555, 80, 2556, 80, 2557, 80, 2558, 80, 2559, 80, 255a, 80, 255b, 80, 255c, 80, 255d, 80, 255e, 80, 255f, 80, 2560, 80, 2561, 80, 2562, 80, 2563, 80, 2564, 80, 2565, 80, 2566, 80, 2567, 80, 2568, 80, 2569, 80, 256a, 80, 256b, 80, 256c, 80, 2580, 80, 2584, 80, 2588, 80, 258c, 80, 2590, 80, 2591, 80, 2592, 80, 2593, 80, 25a0, 80, 25ac, 36, GRAPH 25ba, 45, GRAPH 25c4, 53, GRAPH openMSX-RELEASE_0_12_0/share/unicodemaps/unicodemap.proto_fr000066400000000000000000000202471257557151200237550ustar00rootroot00000000000000#region: Philips VG8000/VG8010 French version # Seems to be some kind of prototype of the French # key matrix #format: , , # : hexadecimal unicode number or DEADKEY # : row in keyboard matrix (hexadecimal: 0-B) # : column in keyboard matrix (0-7) # : space separated list of modifiers: # SHIFT CTRL GRAPH CODE # # Example characters in comments are encoded in UTF-8 # DEADKEY1, 54, 0000, 33, CTRL CODE # ^@ 0001, 13, CTRL # ^A 0002, 07, CTRL # ^B 0003, 11, CTRL # ^C 0004, 32, CTRL # ^D 0005, 52, CTRL # ^E 0006, 22, CTRL # ^F 0007, 27, CTRL # ^G 0008, 75, # Backspace 0009, 73, # Tab 000a, 16, CTRL # ^J 000b, 81, # Home (is Home a unicode character?) 000c, 06, CTRL # ^L 000d, 77, # Enter/CR 000e, 10, CTRL # ^N 000f, 56, CTRL # ^O 0010, 45, CTRL # ^P 0011, 12, CTRL # ^Q 0012, 82, # Insert (is Insert a unicode character?) 0013, 02, CTRL # ^S 0014, 37, CTRL # ^T 0015, 31, CTRL # ^U 0016, 17, CTRL # ^V 0017, 21, CTRL # ^W 0018, 76, # Select (is Select a unicode character?) 0019, 51, CTRL # ^Y 001a, 23, CTRL # ^Z 001b, 72, # Escape(SDL maps ESC and ^[ to this code) 001c, 87, # Right (SDL maps ^\ to this code) 001d, 84, # Left (SDL maps ^] to this code) 001e, 85, # Up (SDL maps ^6 to this code) 001f, 86, # Down (SDL maps ^/ to this code) 0020, 80, # Space 0021, 26, # ! 0022, 43, # " 0023, 14, # # 0024, 24, # $ 0025, 44, SHIFT # % 0026, 03, # & 0027, 53, # ' 0028, 57, # ( 0029, 35, # ) 002a, 24, SHIFT # * 002b, 30, SHIFT # + 002c, 20, # , 002d, 15, # - 002e, 00, SHIFT # . 002f, 50, SHIFT # / 0030, 25, SHIFT # 0 0031, 03, SHIFT # 1 0032, 33, SHIFT # 2 0033, 43, SHIFT # 3 0034, 53, SHIFT # 4 0035, 57, SHIFT # 5 0036, 47, SHIFT # 6 0037, 41, SHIFT # 7 0038, 26, SHIFT # 8 0039, 55, SHIFT # 9 003a, 50, # : 003b, 00, # ; 003c, 04, # < 003d, 30, # = 003e, 04, SHIFT # > 003f, 20, SHIFT # ? 0040, 33, CODE # @ 0041, 13, SHIFT # A 0042, 07, SHIFT # B 0043, 11, SHIFT # C 0044, 32, SHIFT # D 0045, 52, SHIFT # E 0046, 22, SHIFT # F 0047, 27, SHIFT # G 0048, 40, SHIFT # H 0049, 36, SHIFT # I 004a, 16, SHIFT # J 004b, 46, SHIFT # K 004c, 06, SHIFT # L 004d, 34, SHIFT # M 004e, 10, SHIFT # N 004f, 56, SHIFT # O 0050, 45, SHIFT # P 0051, 12, SHIFT # Q 0052, 42, SHIFT # R 0053, 02, SHIFT # S 0054, 37, SHIFT # T 0055, 31, SHIFT # U 0056, 17, SHIFT # V 0057, 21, SHIFT # W 0058, 01, SHIFT # X 0059, 51, SHIFT # Y 005a, 23, SHIFT # Z 005b, 57, SHIFT CODE # [ 005c, 50, SHIFT CODE # \ 005d, 35, SHIFT CODE # ] 005e, 47, CODE # ^ 005f, 15, SHIFT # _ 0060, 53, CODE # ` 0061, 13, # a 0062, 07, # b 0063, 11, # c 0064, 32, # d 0065, 52, # e 0066, 22, # f 0067, 27, # g 0068, 40, # h 0069, 36, # i 006a, 16, # j 006b, 46, # k 006c, 06, # l 006d, 34, # m 006e, 10, # n 006f, 56, # o 0070, 45, # p 0071, 12, # q 0072, 42, # r 0073, 02, # s 0074, 37, # t 0075, 31, # u 0076, 17, # v 0077, 21, # w 0078, 01, # x 0079, 51, # y 007a, 23, # z 007b, 57, CODE # { 007c, 03, CODE # | 007d, 35, CODE # } 007e, 41, SHIFT CODE # ~ 007f, 83, # Delete 00a0, 80, # No-break space ( ) 00a1, 03, SHIFT CODE # ¡ 00a2, 24, CODE # ¢ 00a3, 14, SHIFT # £ 00a5, 51, SHIFT CODE # ¥ 00a7, 47, # § 00aa, 50, CODE # ª 00ab, 04, GRAPH # « 00ac, 51, SHIFT GRAPH # ¬ 00b0, 35, SHIFT # ° 00b1, 30, GRAPH # ± 00b2, 33, SHIFT GRAPH # ² 00b5, 20, CODE # µ 00b6, 47, SHIFT CODE # ¶ 00b7, 11, SHIFT GRAPH # · 00ba, 30, CODE # º 00bb, 04, SHIFT GRAPH # » 00bc, 03, GRAPH # ¼ 00bd, 33, GRAPH # ½ 00be, 43, GRAPH # ¾ 00bf, 20, SHIFT CODE # ¿ 00c3, 40, SHIFT CODE # Ã 00c4, 12, SHIFT CODE # Ä 00c5, 00, SHIFT CODE # Å 00c6, 16, SHIFT CODE # Æ 00c7, 55, SHIFT CODE # Ç 00c9, 33, SHIFT CODE # É 00d1, 10, SHIFT CODE # Ñ 00d5, 06, SHIFT CODE # Õ 00d6, 22, SHIFT CODE # Ö 00dc, 27, SHIFT CODE # Ü 00df, 07, CODE # ß 00e0, 25, # à 00e1, 51, CODE # á 00e2, 13, CODE # â 00e3, 40, CODE # ã 00e4, 12, CODE # ä 00e5, 00, CODE # å 00e6, 16, CODE # æ 00e7, 55, # ç 00e8, 41, # è 00e9, 33, # é 00ea, 23, CODE # ê 00eb, 02, CODE # ë 00ec, 11, CODE # ì 00ed, 36, CODE # í 00ee, 52, CODE # î 00ef, 32, CODE # ï 00f1, 10, CODE # ñ 00f2, 17, CODE # ò 00f3, 56, CODE # ó 00f4, 42, CODE # ô 00f5, 06, CODE # õ 00f6, 22, CODE # ö 00f7, 00, GRAPH # ÷ 00f9, 44, # ù 00fa, 45, CODE # ú 00fb, 37, CODE # û 00fc, 27, CODE # ü 00ff, 31, CODE # ÿ 0128, 46, SHIFT CODE # Ĩ 0129, 46, CODE # ĩ 0132, 44, SHIFT CODE # IJ 0133, 44, CODE # ij 0168, 34, SHIFT CODE # Ũ 0169, 34, CODE # ũ 0192, 01, CODE # ƒ 0393, 26, SHIFT CODE # Γ 0394, 25, SHIFT CODE # Δ 03a3, 14, SHIFT CODE # Σ 03a6, 15, SHIFT CODE # Φ 03a9, 21, SHIFT CODE # Ω 03b1, 43, CODE # α 03b4, 25, CODE # δ 03b5, 41, CODE # ε 03b8, 55, CODE # θ 03c0, 45, SHIFT CODE # π 03c3, 14, CODE # σ 03c4, 26, CODE # τ 03c6, 15, CODE # φ 03c9, 21, CODE # ω 2022, 01, SHIFT GRAPH # • 2027, 55, GRAPH # ‧ 2030, 14, GRAPH # ‰ 207f, 43, SHIFT GRAPH # ⁿ 20a7, 43, SHIFT CODE # ₧ 221a, 41, GRAPH # √ 221e, 26, GRAPH # ∞ 2229, 57, GRAPH # ∩ 223d, 53, GRAPH # ∽ 2248, 53, SHIFT GRAPH # ≈ 2260, 37, SHIFT GRAPH # ≠ 2261, 30, SHIFT GRAPH # ≡ 2264, 04, CODE # ≤ 2265, 04, SHIFT CODE # ≥ 2310, 42, SHIFT GRAPH # ⌐ 2320, 47, GRAPH # ⌠ 2321, 47, SHIFT GRAPH # ⌡ 2500, 15, GRAPH # ─ 2502, 03, SHIFT GRAPH # │ 250c, 42, GRAPH # ┌ 2510, 51, GRAPH # ┐ 2514, 17, GRAPH # └ 2518, 10, GRAPH # ┘ 251c, 22, GRAPH # ├ 2524, 40, GRAPH # ┤ 252c, 37, GRAPH # ┬ 2534, 07, GRAPH # ┴ 253c, 27, GRAPH # ┼ 2571, 50, SHIFT GRAPH # ╱ 2572, 50, GRAPH # ╲ 2573, 01, GRAPH # ╳ 2580, 36, SHIFT GRAPH # ▀ 2582, 31, GRAPH # ▂ 2584, 36, GRAPH # ▄ 2586, 56, GRAPH # ▆ 2588, 45, GRAPH # █ 258a, 06, GRAPH # ▊ 258c, 46, GRAPH # ▌ 258e, 16, GRAPH # ▎ 2590, 46, SHIFT GRAPH # ▐ 2594, 56, SHIFT GRAPH # ▔ 2595, 06, SHIFT GRAPH # ▕ 2596, 40, SHIFT GRAPH # ▖ 2597, 22, SHIFT GRAPH # ▗ 2598, 10, SHIFT GRAPH # ▘ 259a, 32, SHIFT GRAPH # ▚ 259d, 17, SHIFT GRAPH # ▝ 259e, 32, GRAPH # ▞ 25a7, 13, GRAPH # ▧ 25a8, 13, SHIFT GRAPH # ▨ 25a9, 45, SHIFT GRAPH # ▩ 25b2, 52, SHIFT GRAPH # ▲ 25b6, 23, GRAPH # ▶ 25bc, 52, GRAPH # ▼ 25c0, 23, SHIFT GRAPH # ◀ 25c7, 11, GRAPH # ◇ 25cb, 25, GRAPH # ○ 25d8, 55, SHIFT GRAPH # ◘ 25d9, 25, SHIFT GRAPH # ◙ 25fc, 12, SHIFT GRAPH # ◼ 25fe, 12, GRAPH # ◾ 263a, 35, GRAPH # ☺ 263b, 35, SHIFT GRAPH # ☻ 263c, 21, GRAPH # ☼ 2640, 20, SHIFT GRAPH # ♀ 2642, 20, GRAPH # ♂ 2660, 34, GRAPH # ♠ 2663, 44, GRAPH # ♣ 2665, 44, SHIFT GRAPH # ♥ 2666, 34, SHIFT GRAPH # ♦ 266a, 24, GRAPH # ♪ 266b, 24, SHIFT GRAPH # ♫ 29d3, 02, GRAPH # ⧓ openMSX-RELEASE_0_12_0/share/unicodemaps/unicodemap.proto_int000066400000000000000000000202471257557151200241400ustar00rootroot00000000000000#region: Philips VG8000/VG8010 international version # Seems to be some kind of prototype of the international # key matrix #format: , , # : hexadecimal unicode number or DEADKEY # : row in keyboard matrix (hexadecimal: 0-B) # : column in keyboard matrix (0-7) # : space separated list of modifiers: # SHIFT CTRL GRAPH CODE # # Example characters in comments are encoded in UTF-8 # 0000, 33, CTRL SHIFT # ^@ 0001, 12, CTRL # ^A 0002, 07, CTRL # ^B 0003, 11, CTRL # ^C 0004, 32, CTRL # ^D 0005, 52, CTRL # ^E 0006, 22, CTRL # ^F 0007, 27, CTRL # ^G 0008, 75, # Backspace 0009, 73, # Tab 000a, 16, CTRL # ^J 000b, 81, # Home (is Home a unicode character?) 000c, 06, CTRL # ^L 000d, 77, # Enter/CR 000e, 10, CTRL # ^N 000f, 56, CTRL # ^O 0010, 45, CTRL # ^P 0011, 13, CTRL # ^Q 0012, 82, # Insert (is Insert a unicode character?) 0013, 02, CTRL # ^S 0014, 37, CTRL # ^T 0015, 31, CTRL # ^U 0016, 17, CTRL # ^V 0017, 23, CTRL # ^W 0018, 76, # Select (is Select a unicode character?) 0019, 51, CTRL # ^Y 001a, 21, CTRL # ^Z 001b, 72, # Escape(SDL maps ESC and ^[ to this code) 001c, 87, # Right (SDL maps ^\ to this code) 001d, 84, # Left (SDL maps ^] to this code) 001e, 85, # Up (SDL maps ^6 to this code) 001f, 86, # Down (SDL maps ^/ to this code) 0020, 80, # Space 0021, 03, SHIFT # ! 0022, 44, SHIFT # " 0023, 43, SHIFT # # 0024, 53, SHIFT # $ 0025, 57, SHIFT # % 0026, 41, SHIFT # & 0027, 44, # ' 0028, 55, SHIFT # ( 0029, 25, SHIFT # ) 002a, 26, SHIFT # * 002b, 15, SHIFT # + 002c, 00, # , 002d, 35, # - 002e, 50, # . 002f, 30, # / 0030, 25, # 0 0031, 03, # 1 0032, 33, # 2 0033, 43, # 3 0034, 53, # 4 0035, 57, # 5 0036, 47, # 6 0037, 41, # 7 0038, 26, # 8 0039, 55, # 9 003a, 34, SHIFT # : 003b, 34, # ; 003c, 00, SHIFT # < 003d, 15, # = 003e, 50, SHIFT # > 003f, 30, SHIFT # ? 0040, 33, SHIFT # @ 0041, 12, SHIFT # A 0042, 07, SHIFT # B 0043, 11, SHIFT # C 0044, 32, SHIFT # D 0045, 52, SHIFT # E 0046, 22, SHIFT # F 0047, 27, SHIFT # G 0048, 40, SHIFT # H 0049, 36, SHIFT # I 004a, 16, SHIFT # J 004b, 46, SHIFT # K 004c, 06, SHIFT # L 004d, 20, SHIFT # M 004e, 10, SHIFT # N 004f, 56, SHIFT # O 0050, 45, SHIFT # P 0051, 13, SHIFT # Q 0052, 42, SHIFT # R 0053, 02, SHIFT # S 0054, 37, SHIFT # T 0055, 31, SHIFT # U 0056, 17, SHIFT # V 0057, 23, SHIFT # W 0058, 01, SHIFT # X 0059, 51, SHIFT # Y 005a, 21, SHIFT # Z 005b, 54, # [ 005c, 04, # \ 005d, 24, # ] 005e, 47, SHIFT # ^ 005f, 35, SHIFT # _ 0060, 14, # ` 0061, 12, # a 0062, 07, # b 0063, 11, # c 0064, 32, # d 0065, 52, # e 0066, 22, # f 0067, 27, # g 0068, 40, # h 0069, 36, # i 006a, 16, # j 006b, 46, # k 006c, 06, # l 006d, 20, # m 006e, 10, # n 006f, 56, # o 0070, 45, # p 0071, 13, # q 0072, 42, # r 0073, 02, # s 0074, 37, # t 0075, 31, # u 0076, 17, # v 0077, 23, # w 0078, 01, # x 0079, 51, # y 007a, 21, # z 007b, 54, SHIFT # { 007c, 04, SHIFT # | 007d, 24, SHIFT # } 007e, 14, SHIFT # ~ 007f, 83, # Delete 00a0, 80, # No-break space ( ) 00a1, 03, SHIFT CODE # ¡ 00a2, 53, CODE # ¢ 00a3, 53, SHIFT CODE # £ 00a5, 57, SHIFT CODE # ¥ 00a7, 43, CODE # § 00aa, 50, CODE # ª 00ab, 00, SHIFT GRAPH # « 00ac, 51, SHIFT GRAPH # ¬ 00b0, 21, SHIFT GRAPH # ° 00b1, 15, GRAPH # ± 00b2, 33, SHIFT GRAPH # ² 00b5, 20, CODE # µ 00b6, 43, SHIFT CODE # ¶ 00b7, 11, SHIFT GRAPH # · 00ba, 30, CODE # º 00bb, 50, SHIFT GRAPH # » 00bc, 03, GRAPH # ¼ 00bd, 33, GRAPH # ½ 00be, 43, GRAPH # ¾ 00bf, 30, SHIFT CODE # ¿ 00c3, 40, SHIFT CODE # Ã 00c4, 12, SHIFT CODE # Ä 00c5, 00, SHIFT CODE # Å 00c6, 16, SHIFT CODE # Æ 00c7, 55, SHIFT CODE # Ç 00c9, 31, SHIFT CODE # É 00d1, 10, SHIFT CODE # Ñ 00d5, 06, SHIFT CODE # Õ 00d6, 22, SHIFT CODE # Ö 00dc, 27, SHIFT CODE # Ü 00df, 41, CODE # ß 00e0, 21, CODE # à 00e1, 51, CODE # á 00e2, 13, CODE # â 00e3, 40, CODE # ã 00e4, 12, CODE # ä 00e5, 00, CODE # å 00e6, 16, CODE # æ 00e7, 55, CODE # ç 00e8, 01, CODE # è 00e9, 31, CODE # é 00ea, 23, CODE # ê 00eb, 02, CODE # ë 00ec, 11, CODE # ì 00ed, 36, CODE # í 00ee, 52, CODE # î 00ef, 32, CODE # ï 00f1, 10, CODE # ñ 00f2, 17, CODE # ò 00f3, 56, CODE # ó 00f4, 42, CODE # ô 00f5, 06, CODE # õ 00f6, 22, CODE # ö 00f7, 30, SHIFT GRAPH # ÷ 00f9, 07, CODE # ù 00fa, 45, CODE # ú 00fb, 37, CODE # û 00fc, 27, CODE # ü 00ff, 57, CODE # ÿ 0128, 46, SHIFT CODE # Ĩ 0129, 46, CODE # ĩ 0132, 44, SHIFT CODE # IJ 0133, 44, CODE # ij 0168, 34, SHIFT CODE # Ũ 0169, 34, CODE # ũ 0192, 03, CODE # ƒ 0393, 26, SHIFT CODE # Γ 0394, 25, SHIFT CODE # Δ 03a3, 14, SHIFT CODE # Σ 03a6, 54, SHIFT CODE # Φ 03a9, 24, SHIFT CODE # Ω 03b1, 47, CODE # α 03b4, 25, CODE # δ 03b5, 35, CODE # ε 03b8, 15, CODE # θ 03c0, 45, SHIFT CODE # π 03c3, 14, CODE # σ 03c4, 26, CODE # τ 03c6, 54, CODE # φ 03c9, 24, CODE # ω 2022, 01, SHIFT GRAPH # • 2027, 55, GRAPH # ‧ 2030, 57, GRAPH # ‰ 207f, 43, SHIFT GRAPH # ⁿ 20a7, 33, SHIFT CODE # ₧ 221a, 41, GRAPH # √ 221e, 26, GRAPH # ∞ 2229, 53, GRAPH # ∩ 223d, 14, GRAPH # ∽ 2248, 14, SHIFT GRAPH # ≈ 2260, 33, CODE # ≠ 2261, 15, SHIFT GRAPH # ≡ 2264, 00, GRAPH # ≤ 2265, 50, GRAPH # ≥ 2310, 42, SHIFT GRAPH # ⌐ 2320, 47, GRAPH # ⌠ 2321, 47, SHIFT GRAPH # ⌡ 2500, 35, GRAPH # ─ 2502, 04, SHIFT GRAPH # │ 250c, 42, GRAPH # ┌ 2510, 51, GRAPH # ┐ 2514, 17, GRAPH # └ 2518, 10, GRAPH # ┘ 251c, 22, GRAPH # ├ 2524, 40, GRAPH # ┤ 252c, 37, GRAPH # ┬ 2534, 07, GRAPH # ┴ 253c, 27, GRAPH # ┼ 2571, 30, GRAPH # ╱ 2572, 04, GRAPH # ╲ 2573, 01, GRAPH # ╳ 2580, 36, SHIFT GRAPH # ▀ 2582, 31, GRAPH # ▂ 2584, 36, GRAPH # ▄ 2586, 56, GRAPH # ▆ 2588, 45, GRAPH # █ 258a, 06, GRAPH # ▊ 258c, 46, GRAPH # ▌ 258e, 16, GRAPH # ▎ 2590, 46, SHIFT GRAPH # ▐ 2594, 56, SHIFT GRAPH # ▔ 2595, 06, SHIFT GRAPH # ▕ 2596, 40, SHIFT GRAPH # ▖ 2597, 22, SHIFT GRAPH # ▗ 2598, 10, SHIFT GRAPH # ▘ 259a, 32, SHIFT GRAPH # ▚ 259d, 17, SHIFT GRAPH # ▝ 259e, 32, GRAPH # ▞ 25a7, 13, GRAPH # ▧ 25a8, 13, SHIFT GRAPH # ▨ 25a9, 45, SHIFT GRAPH # ▩ 25b2, 52, SHIFT GRAPH # ▲ 25b6, 23, GRAPH # ▶ 25bc, 52, GRAPH # ▼ 25c0, 23, SHIFT GRAPH # ◀ 25c7, 11, GRAPH # ◇ 25cb, 25, GRAPH # ○ 25d8, 55, SHIFT GRAPH # ◘ 25d9, 25, SHIFT GRAPH # ◙ 25fc, 12, SHIFT GRAPH # ◼ 25fe, 12, GRAPH # ◾ 263a, 54, GRAPH # ☺ 263b, 54, SHIFT GRAPH # ☻ 263c, 21, GRAPH # ☼ 2640, 20, SHIFT GRAPH # ♀ 2642, 20, GRAPH # ♂ 2660, 34, GRAPH # ♠ 2663, 44, GRAPH # ♣ 2665, 44, SHIFT GRAPH # ♥ 2666, 34, SHIFT GRAPH # ♦ 266a, 24, GRAPH # ♪ 266b, 24, SHIFT GRAPH # ♫ 29d3, 02, GRAPH # ⧓ openMSX-RELEASE_0_12_0/share/unicodemaps/unicodemap.ru000066400000000000000000000125771257557151200225600ustar00rootroot00000000000000#region: Russian #format: , , # : hexadecimal unicode number or DEADKEY # : row in keyboard matrix (hexadecimal: 0-B) # : column in keyboard matrix (0-7) # : space separated list of modifiers: # SHIFT CTRL GRAPH CODE CAPSLOCK 0000, 02, CTRL SHIFT # ^@ 0001, 26, CTRL # ^A 0002, 27, CTRL # ^B 0003, 30, CTRL # ^C 0004, 31, CTRL # ^D 0005, 32, CTRL # ^E 0006, 33, CTRL # ^F 0007, 34, CTRL # ^G 0008, 75, # Backspace 0009, 73, # Tab 000a, 37, CTRL # ^J 000b, 81, # Home (is Home a unicode character?) 000c, 41, CTRL # ^L 000d, 77, # Enter/CR 000e, 43, CTRL # ^N 000f, 44, CTRL # ^O 0010, 45, CTRL # ^P 0011, 46, CTRL # ^Q 0012, 82, # Insert (is Insert a unicode character?) 0013, 50, CTRL # ^S 0014, 51, CTRL # ^T 0015, 52, CTRL # ^U 0016, 53, CTRL # ^V 0017, 54, CTRL # ^W 0018, 76, # Select (is Select a unicode character?) 0019, 56, CTRL # ^Y 001a, 57, CTRL # ^Z 001b, 72, # Escape(SDL maps ESC and ^[ to this code) 001c, 87, # Right (SDL maps ^\ to this code) 001d, 84, # Left (SDL maps ^] to this code) 001e, 85, # Up (SDL maps ^6 to this code) 001f, 86, # Down (SDL maps ^/ to this code) 0020, 80, 0021, 02, 0022, 03, 0023, 04, 0024, 12, 0025, 06, 0026, 07, 0027, 10, 0028, 11, 0029, 00, 002A, 16, 002B, 01, 002C, 24, SHIFT 002D, 14, 002E, 21, SHIFT 002F, 25, SHIFT 0030, 12, SHIFT 0031, 02, SHIFT 0032, 03, SHIFT 0033, 04, SHIFT 0034, 05, SHIFT 0035, 06, SHIFT 0036, 07, SHIFT 0037, 10, SHIFT 0038, 11, SHIFT 0039, 00, SHIFT 003A, 16, SHIFT 003B, 01, SHIFT 003C, 24, 003D, 13, 003E, 21, 003F, 25, 0040, 23, 0041, 33, SHIFT 0042, 22, SHIFT 0043, 54, SHIFT 0044, 41, SHIFT 0045, 51, SHIFT 0046, 26, SHIFT 0047, 52, SHIFT 0048, 15, SHIFT 0049, 27, SHIFT 004A, 46, SHIFT 004B, 47, SHIFT 004C, 40, SHIFT 004D, 53, SHIFT 004E, 56, SHIFT 004F, 37, SHIFT 0050, 34, SHIFT 0051, 57, SHIFT 0052, 35, SHIFT 0053, 30, SHIFT 0054, 43, SHIFT 0055, 32, SHIFT 0056, 17, SHIFT 0057, 31, SHIFT 0058, 42, SHIFT 0059, 50, SHIFT 005A, 45, SHIFT 005B, 36, 005C, 20, 005D, 44, 005E, 14, SHIFT 005F, 13, SHIFT 0061, 33, 0062, 22, 0063, 54, 0064, 41, 0065, 51, 0066, 26, 0067, 52, 0068, 15, 0069, 27, 006A, 46, 006B, 47, 006C, 40, 006D, 53, 006E, 56, 006F, 37, 0070, 34, 0071, 57, 0072, 35, 0073, 30, 0074, 43, 0075, 32, 0076, 17, 0077, 31, 0078, 42, 0079, 50, 007A, 45, 007B, 36, SHIFT 007C, 55, 007D, 44, SHIFT 007E, 55, SHIFT 007f, 83, # Delete 00a0, 80, 00A4, 05, 0395, 22, SHIFT GRAPH 0394, 56, SHIFT GRAPH 00b0, 57, SHIFT GRAPH 00b1, 13, GRAPH 00b2, 02, SHIFT GRAPH 251c, 33, GRAPH 00b7, 30, SHIFT GRAPH 03c9, 23, SHIFT GRAPH 03b1, 01, GRAPH 03b4, 02, GRAPH 00f7, 24, SHIFT GRAPH 0410, 33, SHIFT CODE 0411, 22, SHIFT CODE 0412, 31, SHIFT CODE 0413, 52, SHIFT CODE 0414, 41, SHIFT CODE 0415, 51, SHIFT CODE 0416, 17, SHIFT CODE 0417, 45, SHIFT CODE 0418, 27, SHIFT CODE 0419, 46, SHIFT CODE 041A, 47, SHIFT CODE 041B, 40, SHIFT CODE 041C, 53, SHIFT CODE 041D, 56, SHIFT CODE 041E, 37, SHIFT CODE 041F, 34, SHIFT CODE 0420, 35, SHIFT CODE 0421, 30, SHIFT CODE 0422, 43, SHIFT CODE 0423, 32, SHIFT CODE 0424, 26, SHIFT CODE 0425, 15, SHIFT CODE 0426, 54, SHIFT CODE 0427, 55, SHIFT CODE 0428, 36, SHIFT CODE 0429, 44, SHIFT CODE 042A, 14, CODE 042B, 50, SHIFT CODE 042C, 42, SHIFT CODE 042D, 20, SHIFT CODE 042E, 23, SHIFT CODE 042F, 57, SHIFT CODE 0430, 33, CODE 0431, 22, CODE 0432, 31, CODE 0433, 52, CODE 0434, 41, CODE 0435, 51, CODE 0436, 17, CODE 0437, 45, CODE 0438, 27, CODE 0439, 46, CODE 043A, 47, CODE 043B, 40, CODE 043C, 53, CODE 043D, 56, CODE 043E, 37, CODE 043F, 34, CODE 0440, 35, CODE 0441, 30, CODE 0442, 43, CODE 0443, 32, CODE 0444, 26, CODE 0445, 15, CODE 0446, 54, CODE 0447, 55, CODE 0448, 36, CODE 0449, 44, CODE 044B, 50, CODE 044C, 42, CODE 044D, 20, CODE 044E, 23, CODE 044F, 57, CODE 2022, 11, GRAPH 2524, 35, GRAPH 253c, 34, GRAPH 207f, 03, SHIFT GRAPH 2518, 43, GRAPH 250c, 47, GRAPH 2514, 53, GRAPH 2510, 56, GRAPH 2571, 24, GRAPH 252c, 51, GRAPH 2500, 12, GRAPH 2022, 55, SHIFT GRAPH 221a, 07, GRAPH 221e, 10, GRAPH 2573, 55, GRAPH 2229, 04, GRAPH 2248, 21, SHIFT GRAPH 2261, 13, SHIFT GRAPH 2264, 22, GRAPH 2265, 23, GRAPH 03a9, 47, SHIFT GRAPH 2320, 06, GRAPH 2321, 06, SHIFT GRAPH 03a6, 07, SHIFT GRAPH 03b3, 05, SHIFT GRAPH 03bc, 04, SHIFT GRAPH 03c3, 01, SHIFT GRAPH 0060, 25, GRAPH 25fe, 26, GRAPH 2582, 52, GRAPH 2594, 44, SHIFT GRAPH 2586, 44, GRAPH 259a, 31, SHIFT GRAPH 25bc, 32, GRAPH 00df, 03, GRAPH 259d, 53, SHIFT GRAPH 2596, 35, SHIFT GRAPH 2595, 41, SHIFT GRAPH 03c0, 21, GRAPH 2597, 33, SHIFT GRAPH 2589, 43, SHIFT GRAPH 258a, 41, GRAPH 0393, 05, GRAPH 03a3, 30, GRAPH 258e, 37, GRAPH 259e, 31, GRAPH 25a7, 46, GRAPH 03c6, 51, SHIFT GRAPH 29d3, 50, GRAPH 25a8, 46, SHIFT GRAPH 25b6, 54, GRAPH 25c0, 54, SHIFT GRAPH 25a9, 45, SHIFT GRAPH 25b2, 32, SHIFT GRAPH 2580, 36, SHIFT GRAPH 2584, 36, GRAPH 2588, 45, GRAPH 258c, 40, GRAPH 2590, 40, SHIFT GRAPH 25a0, 26, SHIFT GRAPH 2260, 27, SHIFT GRAPH 2502, 14, SHIFT GRAPH 2572, 14, GRAPH 2534, 27, GRAPH 25cb, 00, GRAPH 25d8, 11, SHIFT GRAPH 03b8, 10, SHIFT GRAPH 25d9, 00, SHIFT GRAPH 263a, 15, GRAPH 263b, 15, SHIFT GRAPH 263c, 57, GRAPH 2640, 42, SHIFT GRAPH 2642, 42, GRAPH 2660, 17, GRAPH 2663, 20, GRAPH 2665, 20, SHIFT GRAPH 2666, 17, SHIFT GRAPH 266a, 16, GRAPH 266b, 16, SHIFT GRAPH openMSX-RELEASE_0_12_0/src/000077500000000000000000000000001257557151200152265ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/src/.gitignore000066400000000000000000000001761257557151200172220ustar00rootroot00000000000000/*.swp /*.rpo /*.rom /*.ROM /.xvpics /*.dsk /*.prof /gmon.out /*.vim /.kdbgrc.openmsx /.debug /*.bb /*.bbg /*.da /GNUmakefile openMSX-RELEASE_0_12_0/src/AndroidApiWrapper.cc000066400000000000000000000047531257557151200211210ustar00rootroot00000000000000/* * Android API wrapper * * It makes a few Android functions available for the android flavour of openMSX * */ #include "build-info.hh" #include "AndroidApiWrapper.hh" #if PLATFORM_ANDROID #include "openmsx.hh" #include #include #include // The jniVM parameter gets set by the JNI mechanism directly after loading the // shared lib containing openMSX by invoking a method with following // signature if it exists in the shared lib: // JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) // This method is defined at the end of this source file and must be able to // initialize this jniVM parameter, hence it must exist outside the openmsx // namespace static JavaVM *jniVM = NULL; namespace openmsx { std::string AndroidApiWrapper::getStorageDirectory() { JNIEnv * jniEnv = NULL; jclass cls; jmethodID mid; jobject storageDirectory; jstring storageDirectoryName; jniVM->AttachCurrentThread(&jniEnv, NULL); if( !jniEnv ) { throw JniException("Java VM AttachCurrentThread() failed"); } cls = jniEnv->FindClass("android/os/Environment"); if (cls == 0) { throw JniException("Cant find class android/os/Environment"); } mid = jniEnv->GetStaticMethodID(cls, "getExternalStorageDirectory", "()Ljava/io/File;"); if (mid == 0) { throw JniException("Cant find getExternalStorageDirectory method"); } storageDirectory = jniEnv->CallStaticObjectMethod(cls, mid); if (storageDirectory == 0) { throw JniException("Cant get storageDirectory"); } cls = jniEnv->GetObjectClass(storageDirectory); if (cls == 0) { throw JniException("Cant find class for storageDirectory object"); } mid = jniEnv->GetMethodID(cls, "getAbsolutePath", "()Ljava/lang/String;"); if (mid == 0) { throw JniException("Cant find getAbsolutePath method"); } storageDirectoryName = (jstring)jniEnv->CallObjectMethod(storageDirectory, mid); if (storageDirectoryName == 0) { throw JniException("Cant get storageDirectoryName"); } const char *str = jniEnv->GetStringUTFChars(storageDirectoryName, NULL); if (str == NULL) { throw JniException("Cant convert storageDirectoryName to C format"); } std::string rslt(str); jniEnv->ReleaseStringUTFChars(storageDirectoryName, str); return rslt; } } // namespace openmsx JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { // Store the reference to the JVM so that the JNI calls can use it jniVM = vm; return JNI_VERSION_1_2; }; JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) { // Nothing to do }; #endif openMSX-RELEASE_0_12_0/src/AndroidApiWrapper.hh000066400000000000000000000006211257557151200211210ustar00rootroot00000000000000#ifndef ANDROIDAPIWRAPPER_HH #include "build-info.hh" #if PLATFORM_ANDROID #include "MSXException.hh" namespace openmsx { class AndroidApiWrapper { public: static std::string getStorageDirectory(); }; class JniException final : public MSXException { public: explicit JniException(string_ref message) : MSXException(message) {} }; } // namespace openmsx #endif // #if PLATFORM_ANDROID #endif openMSX-RELEASE_0_12_0/src/Autofire.cc000066400000000000000000000016761257557151200173250ustar00rootroot00000000000000#include "Autofire.hh" #include #include namespace openmsx { Autofire::Autofire(CommandController& commandController, unsigned newMinInts, unsigned newMaxInts, string_ref name) : min_ints(std::max(newMinInts, 1u)) , max_ints(std::max(newMaxInts, min_ints + 1)) , speedSetting(commandController, name, "controls the speed of this autofire circuit", 0, 0, 100) , clock(EmuTime::zero) { setClock(); speedSetting.attach(*this); } Autofire::~Autofire() { speedSetting.detach(*this); } void Autofire::setClock() { int speed = speedSetting.getInt(); clock.setFreq( (2 * 50 * 60) / (max_ints - (speed * (max_ints - min_ints)) / 100)); } void Autofire::update(const Setting& setting) { (void)setting; assert(&setting == &speedSetting); setClock(); } bool Autofire::getSignal(EmuTime::param time) { return speedSetting.getInt() == 0 ? false : clock.getTicksTill(time) & 1; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/Autofire.hh000066400000000000000000000035741257557151200173360ustar00rootroot00000000000000#ifndef AUTOFIRE_HH #define AUTOFIRE_HH #include "Observer.hh" #include "DynamicClock.hh" #include "EmuTime.hh" #include "IntegerSetting.hh" #include "noncopyable.hh" #include "string_ref.hh" namespace openmsx { class CommandController; /** * Autofire is a device that is between two other devices and outside * the bus. For example, between the keyboard and the PPI * or between a joyport connecter and the PSG. * * There can be multiple autofire circuits. For example, one used * by the Ren-Sha Turbo and another one built into a joystick. */ class Autofire final : private Observer, private noncopyable { public: Autofire(CommandController& commandController, unsigned newMinInts, unsigned newMaxInts, string_ref name); ~Autofire(); /** Get the output signal in negative logic. * @result When auto-fire is on, result will alternate between true * and false. When auto-fire if off result is false. */ bool getSignal(EmuTime::param time); private: /** Sets the clock frequency according to the current value of the speed * settings. */ void setClock(); void update(const Setting& setting) override; // Following two values specify the range of the autofire // as measured by the test program: /** Number of interrupts at fastest setting (>=1). * The number of interrupts for 50 periods, measured * in ntsc mode (which gives 60 interrupts per second). */ const unsigned min_ints; /** Number of interrupts at slowest setting (>=min_ints+1). * The number of interrupts for 50 periods, measured * in ntsc mode (which gives 60 interrupts per second). */ const unsigned max_ints; /** The currently selected speed. */ IntegerSetting speedSetting; /** Each tick of this clock, the signal changes. * Frequency is derived from speed, min_ints and max_ints. */ DynamicClock clock; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/CLIOption.cc000066400000000000000000000010541257557151200173350ustar00rootroot00000000000000#include "CLIOption.hh" #include "MSXException.hh" #include using std::string; namespace openmsx { // class CLIOption string CLIOption::getArgument(const string& option, array_ref& cmdLine) const { if (cmdLine.empty()) { throw FatalError("Missing argument for option \"" + option + '\"'); } string argument = std::move(cmdLine.front()); cmdLine.pop_front(); return argument; } string CLIOption::peekArgument(const array_ref& cmdLine) const { return cmdLine.empty() ? "" : cmdLine.front(); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/CLIOption.hh000066400000000000000000000014221257557151200173460ustar00rootroot00000000000000#ifndef CLIOPTION_HH #define CLIOPTION_HH #include "array_ref.hh" #include "string_ref.hh" namespace openmsx { class CLIOption { public: virtual void parseOption(const std::string& option, array_ref& cmdLine) = 0; virtual string_ref optionHelp() const = 0; protected: ~CLIOption() {} std::string getArgument(const std::string& option, array_ref& cmdLine) const; std::string peekArgument(const array_ref& cmdLine) const; }; class CLIFileType { public: virtual void parseFileType(const std::string& filename, array_ref& cmdLine) = 0; virtual string_ref fileTypeHelp() const = 0; protected: ~CLIFileType() {} }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/CartridgeSlotManager.cc000066400000000000000000000275261257557151200216120ustar00rootroot00000000000000#include "CartridgeSlotManager.hh" #include "MSXMotherBoard.hh" #include "HardwareConfig.hh" #include "CommandException.hh" #include "FileContext.hh" #include "TclObject.hh" #include "MSXException.hh" #include "StringOp.hh" #include "CliComm.hh" #include "unreachable.hh" #include "memory.hh" #include "outer.hh" #include "xrange.hh" #include using std::string; using std::vector; namespace openmsx { // CartridgeSlotManager::Slot CartridgeSlotManager::Slot::Slot() : config(nullptr), useCount(0), ps(0), ss(0) { } CartridgeSlotManager::Slot::~Slot() { assert(!config); assert(useCount == 0); } bool CartridgeSlotManager::Slot::exists() const { return cartCommand != nullptr; } bool CartridgeSlotManager::Slot::used(const HardwareConfig* allowed) const { assert((useCount == 0) == (config == nullptr)); return config && (config != allowed); } // CartridgeSlotManager CartridgeSlotManager::CartridgeSlotManager(MSXMotherBoard& motherBoard_) : motherBoard(motherBoard_) , cartCmd(*this, motherBoard, "cart") , extSlotInfo(motherBoard.getMachineInfoCommand()) { } CartridgeSlotManager::~CartridgeSlotManager() { for (auto slot : xrange(MAX_SLOTS)) { (void)slot; assert(!slots[slot].exists()); assert(!slots[slot].used()); } } int CartridgeSlotManager::getSlotNum(string_ref slot) { if (slot.size() == 1) { if (('0' <= slot[0]) && (slot[0] <= '3')) { return slot[0] - '0'; } else if (('a' <= slot[0]) && (slot[0] <= 'p')) { return -(1 + slot[0] - 'a'); } else if (slot[0] == 'X') { return -256; } } else if (slot.size() == 2) { if ((slot[0] == '?') && ('0' <= slot[1]) && (slot[1] <= '3')) { return slot[1] - '0' - 128; } } else if (slot == "any") { return -256; } throw MSXException("Invalid slot specification: " + slot); } void CartridgeSlotManager::createExternalSlot(int ps) { createExternalSlot(ps, -1); } void CartridgeSlotManager::createExternalSlot(int ps, int ss) { if (isExternalSlot(ps, ss, false)) { throw MSXException("Slot is already an external slot."); } for (auto slot : xrange(MAX_SLOTS)) { if (!slots[slot].exists()) { slots[slot].ps = ps; slots[slot].ss = ss; char slotName[] = "carta"; slotName[4] += slot; char extName[] = "exta"; extName[3] += slot; motherBoard.getMSXCliComm().update( CliComm::HARDWARE, slotName, "add"); slots[slot].cartCommand = make_unique( *this, motherBoard, slotName); slots[slot].extCommand = make_unique( motherBoard, extName); return; } } UNREACHABLE; } int CartridgeSlotManager::getSlot(int ps, int ss) const { for (auto slot : xrange(MAX_SLOTS)) { if (slots[slot].exists() && (slots[slot].ps == ps) && (slots[slot].ss == ss)) { return slot; } } UNREACHABLE; // was not an external slot return 0; // avoid warning } void CartridgeSlotManager::testRemoveExternalSlot( int ps, const HardwareConfig& allowed) const { testRemoveExternalSlot(ps, -1, allowed); } void CartridgeSlotManager::testRemoveExternalSlot( int ps, int ss, const HardwareConfig& allowed) const { int slot = getSlot(ps, ss); if (slots[slot].used(&allowed)) { throw MSXException("Slot still in use."); } } void CartridgeSlotManager::removeExternalSlot(int ps) { removeExternalSlot(ps, -1); } void CartridgeSlotManager::removeExternalSlot(int ps, int ss) { int slot = getSlot(ps, ss); assert(!slots[slot].used()); const auto& slotName = slots[slot].cartCommand->getName(); motherBoard.getMSXCliComm().update( CliComm::HARDWARE, slotName, "remove"); slots[slot].cartCommand.reset(); slots[slot].extCommand.reset(); } void CartridgeSlotManager::getSpecificSlot(unsigned slot, int& ps, int& ss) const { assert(slot < MAX_SLOTS); if (!slots[slot].exists()) { throw MSXException(StringOp::Builder() << "slot-" << char('a' + slot) << " not defined."); } if (slots[slot].used()) { throw MSXException(StringOp::Builder() << "slot-" << char('a' + slot) << " already in use."); } ps = slots[slot].ps; ss = slots[slot].ss; } void CartridgeSlotManager::getAnyFreeSlot(int& ps, int& ss) const { // search for the lowest free slot ps = 4; // mark no free slot for (auto slot : xrange(MAX_SLOTS)) { if (slots[slot].exists() && !slots[slot].used()) { int p = slots[slot].ps; int s = slots[slot].ss; if ((p < ps) || ((p == ps) && (s < ss))) { ps = p; ss = s; } } } if (ps == 4) { throw MSXException("Not enough free cartridge slots"); } } void CartridgeSlotManager::allocatePrimarySlot( int& ps, const HardwareConfig& hwConfig) { for (auto slot : xrange(MAX_SLOTS)) { ps = slots[slot].ps; if (slots[slot].exists() && (slots[slot].ss == -1) && !slots[slot].used()) { assert(slots[slot].useCount == 0); slots[slot].config = &hwConfig; slots[slot].useCount = 1; return; } } throw MSXException("No free primary slot"); } void CartridgeSlotManager::freePrimarySlot( int ps, const HardwareConfig& hwConfig) { int slot = getSlot(ps, -1); assert(slots[slot].config == &hwConfig); (void)hwConfig; assert(slots[slot].useCount == 1); slots[slot].config = nullptr; slots[slot].useCount = 0; } void CartridgeSlotManager::allocateSlot( int ps, int ss, const HardwareConfig& hwConfig) { for (auto slot : xrange(MAX_SLOTS)) { if (!slots[slot].exists()) continue; if ((slots[slot].ps == ps) && (slots[slot].ss == ss)) { if (slots[slot].useCount == 0) { slots[slot].config = &hwConfig; } else { if (slots[slot].config != &hwConfig) { throw MSXException(StringOp::Builder() << "Slot " << ps << '-' << ss << " already in use by " << slots[slot].config->getName()); } } ++slots[slot].useCount; } } // Slot not found, was not an external slot. No problem. } void CartridgeSlotManager::freeSlot( int ps, int ss, const HardwareConfig& hwConfig) { for (auto slot : xrange(MAX_SLOTS)) { if (!slots[slot].exists()) continue; if ((slots[slot].ps == ps) && (slots[slot].ss == ss)) { assert(slots[slot].config == &hwConfig); (void)hwConfig; assert(slots[slot].useCount > 0); --slots[slot].useCount; if (slots[slot].useCount == 0) { slots[slot].config = nullptr; } return; } } // Slot not found, was not an external slot. No problem. } bool CartridgeSlotManager::isExternalSlot(int ps, int ss, bool convert) const { for (auto slot : xrange(MAX_SLOTS)) { int tmp = (convert && (slots[slot].ss == -1)) ? 0 : slots[slot].ss; if (slots[slot].exists() && (slots[slot].ps == ps) && (tmp == ss)) { return true; } } return false; } // CartCmd CartridgeSlotManager::CartCmd::CartCmd( CartridgeSlotManager& manager_, MSXMotherBoard& motherBoard, string_ref commandName) : RecordedCommand(motherBoard.getCommandController(), motherBoard.getStateChangeDistributor(), motherBoard.getScheduler(), commandName) , manager(manager_) , cliComm(motherBoard.getMSXCliComm()) { } const HardwareConfig* CartridgeSlotManager::CartCmd::getExtensionConfig( string_ref cartname) { if (cartname.size() != 5) { throw SyntaxError(); } int slot = cartname[4] - 'a'; return manager.slots[slot].config; } void CartridgeSlotManager::CartCmd::execute( array_ref tokens, TclObject& result, EmuTime::param /*time*/) { string_ref cartname = tokens[0].getString(); // strip namespace qualification // TODO investigate whether it's a good idea to strip namespace at a // higher level for all commands. How does that interact with // the event recording feature? auto pos = cartname.rfind("::"); if (pos != string_ref::npos) { cartname = cartname.substr(pos + 2); } if (tokens.size() == 1) { // query name of cartridge auto* extConf = getExtensionConfig(cartname); result.addListElement(cartname + ':'); result.addListElement(extConf ? extConf->getName() : ""); if (!extConf) { TclObject options; options.addListElement("empty"); result.addListElement(options); } } else if ((tokens[1] == "eject") || (tokens[1] == "-eject")) { // remove cartridge (or extension) if (tokens[1] == "-eject") { result.setString( "Warning: use of '-eject' is deprecated, " "instead use the 'eject' subcommand"); } if (auto* extConf = getExtensionConfig(cartname)) { try { manager.motherBoard.removeExtension(*extConf); cliComm.update(CliComm::MEDIA, cartname, ""); } catch (MSXException& e) { throw CommandException("Can't remove cartridge: " + e.getMessage()); } } } else { // insert cartridge auto slotname = (cartname.size() == 5) ? string(1, cartname[4]) : "any"; size_t extensionNameToken = 1; if (tokens[1] == "insert") { if (tokens.size() > 2) { extensionNameToken = 2; } else { throw CommandException("Missing argument to insert subcommand"); } } array_ref options(std::begin(tokens) + extensionNameToken + 1, std::end(tokens)); try { string_ref romname = tokens[extensionNameToken].getString(); auto extension = HardwareConfig::createRomConfig( manager.motherBoard, romname, slotname, options); if (slotname != "any") { if (auto* extConf = getExtensionConfig(cartname)) { // still a cartridge inserted, (try to) remove it now manager.motherBoard.removeExtension(*extConf); } } result.setString(manager.motherBoard.insertExtension( "ROM", std::move(extension))); cliComm.update(CliComm::MEDIA, cartname, romname); } catch (MSXException& e) { throw CommandException(e.getMessage()); } } } string CartridgeSlotManager::CartCmd::help(const vector& tokens) const { return tokens[0] + " eject : remove the ROM cartridge from this slot\n" + tokens[0] + " insert : insert ROM cartridge with \n" + tokens[0] + " : insert ROM cartridge with \n" + tokens[0] + " : show which ROM cartridge is in this slot"; } void CartridgeSlotManager::CartCmd::tabCompletion(vector& tokens) const { vector extra; if (tokens.size() < 3) { extra = { "eject", "insert" }; } completeFileName(tokens, userFileContext(), extra); } bool CartridgeSlotManager::CartCmd::needRecord(array_ref tokens) const { return tokens.size() > 1; } // class CartridgeSlotInfo CartridgeSlotManager::CartridgeSlotInfo::CartridgeSlotInfo( InfoCommand& machineInfoCommand) : InfoTopic(machineInfoCommand, "external_slot") { } void CartridgeSlotManager::CartridgeSlotInfo::execute( array_ref tokens, TclObject& result) const { auto& manager = OUTER(CartridgeSlotManager, extSlotInfo); switch (tokens.size()) { case 2: { // return list of slots string slot = "slotX"; for (auto i : xrange(CartridgeSlotManager::MAX_SLOTS)) { if (!manager.slots[i].exists()) continue; slot[4] = char('a' + i); result.addListElement(slot); } break; } case 3: { // return info on a particular slot const auto& name = tokens[2].getString(); if ((name.size() != 5) || (!name.starts_with("slot"))) { throw CommandException("Invalid slot name: " + name); } unsigned num = name[4] - 'a'; if (num >= CartridgeSlotManager::MAX_SLOTS) { throw CommandException("Invalid slot name: " + name); } auto& slot = manager.slots[num]; if (!slot.exists()) { throw CommandException("Slot '" + name + "' doesn't currently exist in this msx machine."); } result.addListElement(slot.ps); if (slot.ss == -1) { result.addListElement("X"); } else { result.addListElement(slot.ss); } if (slot.config) { result.addListElement(slot.config->getName()); } else { result.addListElement(""); } break; } default: throw SyntaxError(); } } string CartridgeSlotManager::CartridgeSlotInfo::help( const vector& /*tokens*/) const { return "Without argument: show list of available external slots.\n" "With argument: show primary and secondary slot number for " "given external slot.\n"; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/CartridgeSlotManager.hh000066400000000000000000000050751257557151200216170ustar00rootroot00000000000000#ifndef CARTRIDGESLOTMANAGER_HH #define CARTRIDGESLOTMANAGER_HH #include "RecordedCommand.hh" #include "InfoTopic.hh" #include "noncopyable.hh" #include "string_ref.hh" #include namespace openmsx { class MSXMotherBoard; class ExtCmd; class HardwareConfig; class CartridgeSlotManager : private noncopyable { public: explicit CartridgeSlotManager(MSXMotherBoard& motherBoard); ~CartridgeSlotManager(); static int getSlotNum(string_ref slot); void createExternalSlot(int ps); void createExternalSlot(int ps, int ss); void removeExternalSlot(int ps); void removeExternalSlot(int ps, int ss); void testRemoveExternalSlot(int ps, const HardwareConfig& allowed) const; void testRemoveExternalSlot(int ps, int ss, const HardwareConfig& allowed) const; // Query/allocate/free external slots void getSpecificSlot(unsigned slot, int& ps, int& ss) const; void getAnyFreeSlot(int& ps, int& ss) const; void allocateSlot(int ps, int ss, const HardwareConfig& hwConfig); void freeSlot(int ps, int ss, const HardwareConfig& hwConfig); // Allocate/free external primary slots void allocatePrimarySlot(int& ps, const HardwareConfig& hwConfig); void freePrimarySlot(int ps, const HardwareConfig& hwConfig); bool isExternalSlot(int ps, int ss, bool convert) const; private: int getSlot(int ps, int ss) const; MSXMotherBoard& motherBoard; class CartCmd final : public RecordedCommand { public: CartCmd(CartridgeSlotManager& manager, MSXMotherBoard& motherBoard, string_ref commandName); void execute(array_ref tokens, TclObject& result, EmuTime::param time) override; std::string help(const std::vector& tokens) const override; void tabCompletion(std::vector& tokens) const override; bool needRecord(array_ref tokens) const override; private: const HardwareConfig* getExtensionConfig(string_ref cartname); CartridgeSlotManager& manager; CliComm& cliComm; } cartCmd; struct CartridgeSlotInfo final : InfoTopic { CartridgeSlotInfo(InfoCommand& machineInfoCommand); void execute(array_ref tokens, TclObject& result) const override; std::string help(const std::vector& tokens) const override; } extSlotInfo; struct Slot { Slot(); ~Slot(); bool exists() const; bool used(const HardwareConfig* allowed = nullptr) const; std::unique_ptr cartCommand; std::unique_ptr extCommand; const HardwareConfig* config; unsigned useCount; int ps; int ss; }; static const unsigned MAX_SLOTS = 16 + 4; Slot slots[MAX_SLOTS]; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/CliExtension.cc000066400000000000000000000021161257557151200201410ustar00rootroot00000000000000#include "CliExtension.hh" #include "CommandLineParser.hh" #include "MSXMotherBoard.hh" #include "MSXException.hh" #include using std::string; namespace openmsx { CliExtension::CliExtension(CommandLineParser& cmdLineParser_) : cmdLineParser(cmdLineParser_) { cmdLineParser.registerOption("-ext", *this); cmdLineParser.registerOption("-exta", *this); cmdLineParser.registerOption("-extb", *this); cmdLineParser.registerOption("-extc", *this); cmdLineParser.registerOption("-extd", *this); } void CliExtension::parseOption(const string& option, array_ref& cmdLine) { try { string extensionName = getArgument(option, cmdLine); MSXMotherBoard* motherboard = cmdLineParser.getMotherBoard(); assert(motherboard); string slotname; if (option.size() == 5) { slotname = option[4]; } else { slotname = "any"; } motherboard->loadExtension(extensionName, slotname); } catch (MSXException& e) { throw FatalError(e.getMessage()); } } string_ref CliExtension::optionHelp() const { return "Insert the extension specified in argument"; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/CliExtension.hh000066400000000000000000000007151257557151200201560ustar00rootroot00000000000000#ifndef CLIEXTENSION_HH #define CLIEXTENSION_HH #include "CLIOption.hh" namespace openmsx { class CommandLineParser; class CliExtension final : public CLIOption { public: explicit CliExtension(CommandLineParser& cmdLineParser); void parseOption(const std::string& option, array_ref& cmdLine) override; string_ref optionHelp() const override; private: CommandLineParser& cmdLineParser; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/Clock.hh000066400000000000000000000110311257557151200165760ustar00rootroot00000000000000#ifndef CLOCK_HH #define CLOCK_HH #include "EmuDuration.hh" #include "EmuTime.hh" #include "DivModByConst.hh" #include "serialize.hh" #include namespace openmsx { /** Represents a clock with a fixed frequency. * The frequency is in Hertz, so every tick is 1/frequency second. * A clock has a current time, which can be increased by * an integer number of ticks. */ template class Clock { private: // stuff below calculates: // MASTER_TICKS = MAIN_FREQ / (FREQ_NUM / FREQ_DENOM) + 0.5 static_assert(MAIN_FREQ < (1ull << 32), "must fit in 32 bit"); static const uint64_t P = MAIN_FREQ * FREQ_DENOM + (FREQ_NUM / 2); static const uint64_t MASTER_TICKS = P / FREQ_NUM; static_assert(MASTER_TICKS < (1ull << 32), "must fit in 32 bit"); static const unsigned MASTER_TICKS32 = MASTER_TICKS; public: // Note: default copy constructor and assigment operator are ok. /** Calculates the duration of the given number of ticks at this * clock's frequency. */ static const EmuDuration duration(unsigned ticks) { return EmuDuration(ticks * MASTER_TICKS); } /** Create a new clock, which starts ticking at the given time. */ explicit Clock(EmuTime::param e) : lastTick(e) { } /** Gets the time at which the last clock tick occurred. */ EmuTime::param getTime() const { return lastTick; } /** Checks whether this clock's last tick is or is not before the * given time stamp. */ bool before(EmuTime::param e) const { return lastTick.time < e.time; } /** Calculate the number of ticks for this clock until the given time. * It is not allowed to call this method for a time in the past. */ unsigned getTicksTill(EmuTime::param e) const { assert(e.time >= lastTick.time); uint64_t result = (e.time - lastTick.time) / MASTER_TICKS; #ifdef DEBUG // we don't even want this overhead in devel builds assert(result == unsigned(result)); #endif return unsigned(result); } /** Same as above, only faster, Though the time interval may not * be too large. */ unsigned getTicksTill_fast(EmuTime::param e) const { assert(e.time >= lastTick.time); DivModByConst dm; return dm.div(e.time - lastTick.time); } /** Calculate the number of ticks this clock has to tick to reach * or go past the given time. * It is not allowed to call this method for a time in the past. */ uint64_t getTicksTillUp(EmuTime::param e) const { assert(e.time >= lastTick.time); return (e.time - lastTick.time + MASTER_TICKS - 1) / MASTER_TICKS; } /** Calculate the time at which this clock will have ticked the given * number of times (counted from its last tick). */ const EmuTime operator+(uint64_t n) const { return EmuTime(lastTick.time + n * MASTER_TICKS); } /** Like operator+() but faster, though the step can't be too big (max * a little over 1 second). */ EmuTime getFastAdd(unsigned n) const { #ifdef DEBUG assert((uint64_t(n) * MASTER_TICKS) < (1ull << 32)); #endif return EmuTime(lastTick.time + n * MASTER_TICKS); } /** Reset the clock to start ticking at the given time. */ void reset(EmuTime::param e) { lastTick.time = e.time; } /** Advance this clock in time until the last tick which is not past * the given time. * It is not allowed to advance a clock to a time in the past. */ void advance(EmuTime::param e) { assert(lastTick.time <= e.time); lastTick.time = e.time - ((e.time - lastTick.time) % MASTER_TICKS); } /** Same as above, only faster, Though the time interval may not * be too large. */ void advance_fast(EmuTime::param e) { assert(lastTick.time <= e.time); DivModByConst dm; lastTick.time = e.time - dm.mod(e.time - lastTick.time); } /** Advance this clock by the given number of ticks. */ void operator+=(unsigned n) { lastTick.time += n * MASTER_TICKS; } /** Advance this clock by the given number of ticks. * This method is similar to operator+=, but it's optimized for * speed. OTOH the amount of ticks should not be too large, * otherwise an overflow occurs. Use operator+() when the duration * of the ticks approaches 1 second. */ void fastAdd(unsigned n) { #ifdef DEBUG // we don't even want this overhead in development versions assert((n * MASTER_TICKS) < (1ull << 32)); #endif lastTick.time += n * MASTER_TICKS32; } template void serialize(Archive& ar, unsigned /*version*/) { ar.serialize("lastTick", lastTick); } private: /** Time of this clock's last tick. */ EmuTime lastTick; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/CommandLineParser.cc000066400000000000000000000414531257557151200211070ustar00rootroot00000000000000#include "CommandLineParser.hh" #include "GlobalCommandController.hh" #include "Interpreter.hh" #include "SettingsConfig.hh" #include "File.hh" #include "FileContext.hh" #include "FileOperations.hh" #include "GlobalCliComm.hh" #include "StdioMessages.hh" #include "Version.hh" #include "CliConnection.hh" #include "ConfigException.hh" #include "FileException.hh" #include "EnumSetting.hh" #include "XMLException.hh" #include "StringOp.hh" #include "xrange.hh" #include "GLUtil.hh" #include "Reactor.hh" #include "RomInfo.hh" #include "hash_map.hh" #include "memory.hh" #include "outer.hh" #include "stl.hh" #include "xxhash.hh" #include "build-info.hh" #include #include using std::cout; using std::endl; using std::string; using std::vector; namespace openmsx { // class CommandLineParser using CmpOptions = LessTupleElement<0>; using CmpFileTypes = CmpTupleElement<0, StringOp::caseless>; CommandLineParser::CommandLineParser(Reactor& reactor_) : reactor(reactor_) , msxRomCLI(*this) , cliExtension(*this) , replayCLI(*this) , saveStateCLI(*this) , cassettePlayerCLI(*this) #if COMPONENT_LASERDISC , laserdiscPlayerCLI(*this) #endif , diskImageCLI(*this) , hdImageCLI(*this) , cdImageCLI(*this) , parseStatus(UNPARSED) { haveConfig = false; haveSettings = false; registerOption("-h", helpOption, PHASE_BEFORE_INIT, 1); registerOption("--help", helpOption, PHASE_BEFORE_INIT, 1); registerOption("-v", versionOption, PHASE_BEFORE_INIT, 1); registerOption("--version", versionOption, PHASE_BEFORE_INIT, 1); registerOption("-bash", bashOption, PHASE_BEFORE_INIT, 1); registerOption("-setting", settingOption, PHASE_BEFORE_SETTINGS); registerOption("-control", controlOption, PHASE_BEFORE_SETTINGS, 1); registerOption("-script", scriptOption, PHASE_BEFORE_SETTINGS, 1); // correct phase? #if COMPONENT_GL registerOption("-nopbo", noPBOOption, PHASE_BEFORE_SETTINGS, 1); #endif registerOption("-testconfig", testConfigOption, PHASE_BEFORE_SETTINGS, 1); registerOption("-machine", machineOption, PHASE_BEFORE_MACHINE); registerFileType("tcl", scriptOption); // At this point all options and file-types must be registered sort(begin(options), end(options), CmpOptions()); sort(begin(fileTypes), end(fileTypes), CmpFileTypes()); } CommandLineParser::~CommandLineParser() { } void CommandLineParser::registerOption( const char* str, CLIOption& cliOption, ParsePhase phase, unsigned length) { options.emplace_back(str, OptionData{&cliOption, phase, length}); } void CommandLineParser::registerFileType( string_ref extensions, CLIFileType& cliFileType) { for (auto& ext: StringOp::split(extensions, ',')) { fileTypes.emplace_back(ext, &cliFileType); } } bool CommandLineParser::parseOption( const string& arg, array_ref& cmdLine, ParsePhase phase) { auto it = lower_bound(begin(options), end(options), arg, CmpOptions()); if ((it != end(options)) && (it->first == arg)) { // parse option if (it->second.phase <= phase) { try { it->second.option->parseOption(arg, cmdLine); return true; } catch (MSXException& e) { throw FatalError(e.getMessage()); } } } return false; // unknown } bool CommandLineParser::parseFileName(const string& arg, array_ref& cmdLine) { // First try the fileName as we get it from the commandline. This may // be more interesting than the original fileName of a (g)zipped file: // in case of an OMR file for instance, we want to select on the // original extension, and not on the extension inside the gzipped // file. bool processed = parseFileNameInner(arg, arg, cmdLine); if (!processed) { try { File file(userFileContext().resolve(arg)); string originalName = file.getOriginalName(); processed = parseFileNameInner(originalName, arg, cmdLine); } catch (FileException&) { // ignore } } return processed; } bool CommandLineParser::parseFileNameInner(const string& name, const string& originalPath, array_ref& cmdLine) { string_ref extension = FileOperations::getExtension(name); if (extension.empty()) { return false; // no extension } auto it = lower_bound(begin(fileTypes), end(fileTypes), extension, CmpFileTypes()); StringOp::casecmp cmp; if ((it == end(fileTypes)) || !cmp(it->first, extension)) { return false; // unknown extension } try { // parse filetype it->second->parseFileType(originalPath, cmdLine); return true; // file processed } catch (MSXException& e) { throw FatalError(e.getMessage()); } } void CommandLineParser::parse(int argc, char** argv) { parseStatus = RUN; vector cmdLineBuf; for (auto i : xrange(1, argc)) { cmdLineBuf.push_back(FileOperations::getConventionalPath(argv[i])); } array_ref cmdLine(cmdLineBuf); vector backupCmdLine; for (ParsePhase phase = PHASE_BEFORE_INIT; (phase <= PHASE_LAST) && (parseStatus != EXIT); phase = static_cast(phase + 1)) { switch (phase) { case PHASE_INIT: reactor.init(); getInterpreter().init(argv[0]); break; case PHASE_LOAD_SETTINGS: // after -control and -setting has been parsed if (parseStatus != CONTROL) { // if there already is a XML-StdioConnection, we // can't also show plain messages on stdout auto& cliComm = reactor.getGlobalCliComm(); cliComm.addListener(make_unique()); } if (!haveSettings) { auto& settingsConfig = reactor.getGlobalCommandController().getSettingsConfig(); // Load default settings file in case the user // didn't specify one. auto context = systemFileContext(); string filename = "settings.xml"; try { settingsConfig.loadSetting(context, filename); } catch (XMLException& e) { reactor.getCliComm().printWarning( "Loading of settings failed: " + e.getMessage() + "\n" "Reverting to default settings."); } catch (FileException&) { // settings.xml not found } catch (ConfigException& e) { throw FatalError("Error in default settings: " + e.getMessage()); } // Consider an attempt to load the settings good enough. haveSettings = true; // Even if parsing failed, use this file for saving, // this forces overwriting a non-setting file. settingsConfig.setSaveFilename(context, filename); } break; case PHASE_LOAD_MACHINE: { if (!haveConfig) { // load default config file in case the user didn't specify one const auto& machine = reactor.getMachineSetting().getString(); try { reactor.switchMachine(machine.str()); } catch (MSXException& e) { reactor.getCliComm().printInfo( "Failed to initialize default machine: " + e.getMessage()); // Default machine is broken; fall back to C-BIOS config. const auto& fallbackMachine = reactor.getMachineSetting().getRestoreValue().getString(); reactor.getCliComm().printInfo("Using fallback machine: " + fallbackMachine); try { reactor.switchMachine(fallbackMachine.str()); } catch (MSXException& e2) { // Fallback machine failed as well; we're out of options. throw FatalError(e2.getMessage()); } } haveConfig = true; } break; } default: // iterate over all arguments while (!cmdLine.empty()) { string arg = std::move(cmdLine.front()); cmdLine.pop_front(); // first try options if (!parseOption(arg, cmdLine, phase)) { // next try the registered filetypes (xml) if ((phase != PHASE_LAST) || !parseFileName(arg, cmdLine)) { // no option or known file backupCmdLine.push_back(arg); auto it = lower_bound(begin(options), end(options), arg, CmpOptions()); if ((it != end(options)) && (it->first == arg)) { for (unsigned i = 0; i < it->second.length - 1; ++i) { if (!cmdLine.empty()) { backupCmdLine.push_back(std::move(cmdLine.front())); cmdLine.pop_front(); } } } } } } std::swap(backupCmdLine, cmdLineBuf); backupCmdLine.clear(); cmdLine = cmdLineBuf; break; } } if (!cmdLine.empty() && (parseStatus != EXIT)) { throw FatalError( "Error parsing command line: " + cmdLine.front() + "\n" + "Use \"openmsx -h\" to see a list of available options" ); } } bool CommandLineParser::isHiddenStartup() const { return (parseStatus == CONTROL) || (parseStatus == TEST); } CommandLineParser::ParseStatus CommandLineParser::getParseStatus() const { assert(parseStatus != UNPARSED); return parseStatus; } const CommandLineParser::Scripts& CommandLineParser::getStartupScripts() const { return scriptOption.scripts; } MSXMotherBoard* CommandLineParser::getMotherBoard() const { return reactor.getMotherBoard(); } GlobalCommandController& CommandLineParser::getGlobalCommandController() const { return reactor.getGlobalCommandController(); } Interpreter& CommandLineParser::getInterpreter() const { return reactor.getInterpreter(); } // Control option void CommandLineParser::ControlOption::parseOption( const string& option, array_ref& cmdLine) { const auto& fullType = getArgument(option, cmdLine); string_ref type, arguments; StringOp::splitOnFirst(fullType, ':', type, arguments); auto& parser = OUTER(CommandLineParser, controlOption); auto& controller = parser.getGlobalCommandController(); auto& distributor = parser.reactor.getEventDistributor(); auto& cliComm = parser.reactor.getGlobalCliComm(); std::unique_ptr connection; if (type == "stdio") { connection = make_unique( controller, distributor); #ifdef _WIN32 } else if (type == "pipe") { OSVERSIONINFO info; info.dwOSVersionInfoSize = sizeof(info); GetVersionEx(&info); if (info.dwPlatformId == VER_PLATFORM_WIN32_NT) { connection = make_unique( controller, distributor, arguments); } else { throw FatalError("Pipes are not supported on this " "version of Windows"); } #endif } else { throw FatalError("Unknown control type: '" + type + '\''); } cliComm.addListener(std::move(connection)); parser.parseStatus = CommandLineParser::CONTROL; } string_ref CommandLineParser::ControlOption::optionHelp() const { return "Enable external control of openMSX process"; } // Script option void CommandLineParser::ScriptOption::parseOption( const string& option, array_ref& cmdLine) { parseFileType(getArgument(option, cmdLine), cmdLine); } string_ref CommandLineParser::ScriptOption::optionHelp() const { return "Run extra startup script"; } void CommandLineParser::ScriptOption::parseFileType( const string& filename, array_ref& /*cmdLine*/) { scripts.push_back(filename); } string_ref CommandLineParser::ScriptOption::fileTypeHelp() const { return "Extra Tcl script to run at startup"; } // Help option static string formatSet(const vector& inputSet, string::size_type columns) { StringOp::Builder outString; string::size_type totalLength = 0; // ignore the starting spaces for now for (auto& temp : inputSet) { if (totalLength == 0) { // first element ? outString << " " << temp; totalLength = temp.size(); } else { outString << ", "; if ((totalLength + temp.size()) > columns) { outString << "\n " << temp; totalLength = temp.size(); } else { outString << temp; totalLength += 2 + temp.size(); } } } if (totalLength < columns) { outString << string(columns - totalLength, ' '); } return outString; } static string formatHelptext(string_ref helpText, unsigned maxLength, unsigned indent) { string outText; string_ref::size_type index = 0; while (helpText.substr(index).size() > maxLength) { auto pos = helpText.substr(index, maxLength).rfind(' '); if (pos == string_ref::npos) { pos = helpText.substr(maxLength).find(' '); if (pos == string_ref::npos) { pos = helpText.substr(index).size(); } } outText += helpText.substr(index, index + pos) + '\n' + string(indent, ' '); index = pos + 1; } string_ref t = helpText.substr(index); outText.append(t.data(), t.size()); return outText; } // items grouped per common help-text using GroupedItems = hash_map, XXHasher>; static void printItemMap(const GroupedItems& itemMap) { vector printSet; for (auto& p : itemMap) { printSet.push_back(formatSet(p.second, 15) + ' ' + formatHelptext(p.first, 50, 20)); } sort(begin(printSet), end(printSet)); for (auto& s : printSet) { cout << s << endl; } } // class HelpOption void CommandLineParser::HelpOption::parseOption( const string& /*option*/, array_ref& /*cmdLine*/) { auto& parser = OUTER(CommandLineParser, helpOption); const auto& fullVersion = Version::full(); cout << fullVersion << endl; cout << string(fullVersion.size(), '=') << endl; cout << endl; cout << "usage: openmsx [arguments]" << endl; cout << " an argument is either an option or a filename" << endl; cout << endl; cout << " this is the list of supported options:" << endl; GroupedItems itemMap; for (auto& p : parser.options) { const auto& helpText = p.second.option->optionHelp(); if (!helpText.empty()) { itemMap[helpText].push_back(p.first); } } printItemMap(itemMap); cout << endl; cout << " this is the list of supported file types:" << endl; itemMap.clear(); for (auto& p : parser.fileTypes) { itemMap[p.second->fileTypeHelp()].push_back(p.first); } printItemMap(itemMap); parser.parseStatus = CommandLineParser::EXIT; } string_ref CommandLineParser::HelpOption::optionHelp() const { return "Shows this text"; } // class VersionOption void CommandLineParser::VersionOption::parseOption( const string& /*option*/, array_ref& /*cmdLine*/) { cout << Version::full() << endl; cout << "flavour: " << BUILD_FLAVOUR << endl; cout << "components: " << BUILD_COMPONENTS << endl; auto& parser = OUTER(CommandLineParser, versionOption); parser.parseStatus = CommandLineParser::EXIT; } string_ref CommandLineParser::VersionOption::optionHelp() const { return "Prints openMSX version and exits"; } // Machine option void CommandLineParser::MachineOption::parseOption( const string& option, array_ref& cmdLine) { auto& parser = OUTER(CommandLineParser, machineOption); if (parser.haveConfig) { throw FatalError("Only one machine option allowed"); } try { parser.reactor.switchMachine(getArgument(option, cmdLine)); } catch (MSXException& e) { throw FatalError(e.getMessage()); } parser.haveConfig = true; } string_ref CommandLineParser::MachineOption::optionHelp() const { return "Use machine specified in argument"; } // class SettingOption void CommandLineParser::SettingOption::parseOption( const string& option, array_ref& cmdLine) { auto& parser = OUTER(CommandLineParser, settingOption); if (parser.haveSettings) { throw FatalError("Only one setting option allowed"); } try { auto& settingsConfig = parser.reactor.getGlobalCommandController().getSettingsConfig(); settingsConfig.loadSetting( currentDirFileContext(), getArgument(option, cmdLine)); parser.haveSettings = true; } catch (FileException& e) { throw FatalError(e.getMessage()); } catch (ConfigException& e) { throw FatalError(e.getMessage()); } } string_ref CommandLineParser::SettingOption::optionHelp() const { return "Load an alternative settings file"; } // class NoPBOOption void CommandLineParser::NoPBOOption::parseOption( const string& /*option*/, array_ref& /*cmdLine*/) { #if COMPONENT_GL cout << "Disabling PBO" << endl; gl::PixelBuffers::enabled = false; #endif } string_ref CommandLineParser::NoPBOOption::optionHelp() const { return "Disables usage of openGL PBO (for debugging)"; } // class TestConfigOption void CommandLineParser::TestConfigOption::parseOption( const string& /*option*/, array_ref& /*cmdLine*/) { auto& parser = OUTER(CommandLineParser, testConfigOption); parser.parseStatus = CommandLineParser::TEST; } string_ref CommandLineParser::TestConfigOption::optionHelp() const { return "Test if the specified config works and exit"; } // class BashOption void CommandLineParser::BashOption::parseOption( const string& /*option*/, array_ref& cmdLine) { auto& parser = OUTER(CommandLineParser, bashOption); string last = cmdLine.empty() ? "" : cmdLine.front(); cmdLine.clear(); // eat all remaining parameters if (last == "-machine") { for (auto& s : Reactor::getHwConfigs("machines")) { cout << s << '\n'; } } else if (StringOp::startsWith(last, "-ext")) { for (auto& s : Reactor::getHwConfigs("extensions")) { cout << s << '\n'; } } else if (last == "-romtype") { for (auto& s : RomInfo::getAllRomTypes()) { cout << s << '\n'; } } else { for (auto& p : parser.options) { cout << p.first << '\n'; } } parser.parseStatus = CommandLineParser::EXIT; } string_ref CommandLineParser::BashOption::optionHelp() const { return ""; // don't include this option in --help } } // namespace openmsx openMSX-RELEASE_0_12_0/src/CommandLineParser.hh000066400000000000000000000111271257557151200211140ustar00rootroot00000000000000#ifndef COMMANDLINEPARSER_HH #define COMMANDLINEPARSER_HH #include "CLIOption.hh" #include "MSXRomCLI.hh" #include "CliExtension.hh" #include "ReplayCLI.hh" #include "SaveStateCLI.hh" #include "CassettePlayerCLI.hh" #include "DiskImageCLI.hh" #include "HDImageCLI.hh" #include "CDImageCLI.hh" #include "array_ref.hh" #include "string_ref.hh" #include "noncopyable.hh" #include "components.hh" #include #include #include #if COMPONENT_LASERDISC #include "LaserdiscPlayerCLI.hh" #endif namespace openmsx { class Reactor; class MSXMotherBoard; class GlobalCommandController; class Interpreter; class CommandLineParser : private noncopyable { public: enum ParseStatus { UNPARSED, RUN, CONTROL, TEST, EXIT }; enum ParsePhase { PHASE_BEFORE_INIT, // --help, --version, -bash PHASE_INIT, // calls Reactor::init() PHASE_BEFORE_SETTINGS, // -setting, -nommx, ... PHASE_LOAD_SETTINGS, // loads settings.xml PHASE_BEFORE_MACHINE, // -machine PHASE_LOAD_MACHINE, // loads machine hardwareconfig.xml PHASE_LAST, // all the rest }; explicit CommandLineParser(Reactor& reactor); ~CommandLineParser(); void registerOption(const char* str, CLIOption& cliOption, ParsePhase phase = PHASE_LAST, unsigned length = 2); void registerFileType(string_ref extensions, CLIFileType& cliFileType); void parse(int argc, char** argv); ParseStatus getParseStatus() const; using Scripts = std::vector; const Scripts& getStartupScripts() const; MSXMotherBoard* getMotherBoard() const; GlobalCommandController& getGlobalCommandController() const; Interpreter& getInterpreter() const; /** Need to suppress renderer window on startup? */ bool isHiddenStartup() const; private: struct OptionData { CLIOption* option; ParsePhase phase; unsigned length; // length in parameters }; bool parseFileName(const std::string& arg, array_ref& cmdLine); bool parseFileNameInner(const std::string& arg, const std::string& originalPath, array_ref& cmdLine); bool parseOption(const std::string& arg, array_ref& cmdLine, ParsePhase phase); void createMachineSetting(); std::vector> options; std::vector> fileTypes; Reactor& reactor; struct HelpOption final : CLIOption { void parseOption(const std::string& option, array_ref& cmdLine) override; string_ref optionHelp() const override; } helpOption; struct VersionOption final : CLIOption { void parseOption(const std::string& option, array_ref& cmdLine) override; string_ref optionHelp() const override; } versionOption; struct ControlOption final : CLIOption { void parseOption(const std::string& option, array_ref& cmdLine) override; string_ref optionHelp() const override; } controlOption; struct ScriptOption final : CLIOption, CLIFileType { void parseOption(const std::string& option, array_ref& cmdLine) override; string_ref optionHelp() const override; void parseFileType(const std::string& filename, array_ref& cmdLine) override; string_ref fileTypeHelp() const override; CommandLineParser::Scripts scripts; } scriptOption; struct MachineOption final : CLIOption { void parseOption(const std::string& option, array_ref& cmdLine) override; string_ref optionHelp() const override; } machineOption; struct SettingOption final : CLIOption { void parseOption(const std::string& option, array_ref& cmdLine) override; string_ref optionHelp() const override; } settingOption; struct NoPBOOption final : CLIOption { void parseOption(const std::string& option, array_ref& cmdLine) override; string_ref optionHelp() const override; } noPBOOption; struct TestConfigOption final : CLIOption { void parseOption(const std::string& option, array_ref& cmdLine) override; string_ref optionHelp() const override; } testConfigOption; struct BashOption final : CLIOption { void parseOption(const std::string& option, array_ref& cmdLine) override; string_ref optionHelp() const override; } bashOption; MSXRomCLI msxRomCLI; CliExtension cliExtension; ReplayCLI replayCLI; SaveStateCLI saveStateCLI; CassettePlayerCLI cassettePlayerCLI; #if COMPONENT_LASERDISC LaserdiscPlayerCLI laserdiscPlayerCLI; #endif DiskImageCLI diskImageCLI; HDImageCLI hdImageCLI; CDImageCLI cdImageCLI; ParseStatus parseStatus; bool haveConfig; bool haveSettings; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/Connector.cc000066400000000000000000000036521257557151200174750ustar00rootroot00000000000000#include "Connector.hh" #include "Pluggable.hh" #include "PluggingController.hh" #include "serialize.hh" #include "CliComm.hh" namespace openmsx { Connector::Connector(PluggingController& pluggingController_, string_ref name_, std::unique_ptr dummy_) : pluggingController(pluggingController_) , name(name_.str()) , dummy(std::move(dummy_)) { plugged = dummy.get(); pluggingController.registerConnector(*this); } Connector::~Connector() { pluggingController.unregisterConnector(*this); } void Connector::plug(Pluggable& device, EmuTime::param time) { device.plug(*this, time); plugged = &device; // not executed if plug fails } void Connector::unplug(EmuTime::param time) { plugged->unplug(time); plugged = dummy.get(); } template void Connector::serialize(Archive& ar, unsigned /*version*/) { std::string plugName; if (!ar.isLoader() && (plugged != dummy.get())) { plugName = plugged->getName(); } ar.serialize("plugName", plugName); if (!ar.isLoader()) { if (!plugName.empty()) { ar.beginSection(); ar.serializePolymorphic("pluggable", *plugged); ar.endSection(); } } else { if (plugName.empty()) { // was not plugged in plugged = dummy.get(); } else if (Pluggable* pluggable = pluggingController.findPluggable(plugName)) { plugged = pluggable; // set connector before loading the pluggable so that // the pluggable can test whether it was connected pluggable->setConnector(this); ar.skipSection(false); ar.serializePolymorphic("pluggable", *plugged); } else { // was plugged, but we don't have that pluggable anymore pluggingController.getCliComm().printWarning( "Pluggable \"" + plugName + "\" was plugged in, " "but is not available anymore on this system, " "so it will be ignored."); ar.skipSection(true); plugged = dummy.get(); } } } INSTANTIATE_SERIALIZE_METHODS(Connector); } // namespace openmsx openMSX-RELEASE_0_12_0/src/Connector.hh000066400000000000000000000040661257557151200175070ustar00rootroot00000000000000#ifndef CONNECTOR_HH #define CONNECTOR_HH #include "EmuTime.hh" #include "noncopyable.hh" #include "serialize_meta.hh" #include "string_ref.hh" #include namespace openmsx { class Pluggable; class PluggingController; /** * Represents something you can plug devices into. * Examples are a joystick port, a printer port, a MIDI port etc. * When there is not an actual Pluggable plugged in, a dummy Pluggable * is used. */ class Connector : private noncopyable { public: /** * Name that identifies this connector. */ const std::string& getName() const { return name; } /** * Get a description for this connector */ virtual const std::string getDescription() const = 0; /** * A Connector belong to a certain class. * Only Pluggables of this class can be plugged in this Connector. */ virtual string_ref getClass() const = 0; /** * This plugs a Pluggable in this Connector. * The default implementation is ok. * @throw PlugException */ virtual void plug(Pluggable& device, EmuTime::param time); /** * This unplugs the currently inserted Pluggable from this Connector. * It is replaced by the dummy Pluggable provided by the concrete * Connector subclass. */ virtual void unplug(EmuTime::param time); /** * Returns the Pluggable currently plugged in. */ Pluggable& getPlugged() const { return *plugged; } PluggingController& getPluggingController() const { return pluggingController; } template void serialize(Archive& ar, unsigned version); protected: /** * Creates a new Connector. * @param pluggingController PluggingController. * @param name Name that identifies this connector. * @param dummy Dummy Pluggable whose class matches this Connector. */ Connector(PluggingController& pluggingController, string_ref name, std::unique_ptr dummy); ~Connector(); private: PluggingController& pluggingController; const std::string name; const std::unique_ptr dummy; Pluggable* plugged; }; REGISTER_BASE_CLASS(Connector, "Connector"); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/DebugDevice.cc000066400000000000000000000074471257557151200177170ustar00rootroot00000000000000#include "DebugDevice.hh" #include "Clock.hh" #include "FileOperations.hh" #include "serialize.hh" #include #include using std::string; namespace openmsx { DebugDevice::DebugDevice(const DeviceConfig& config) : MSXDevice(config) , fileNameSetting( getCommandController(), "debugoutput", "name of the file the debugdevice outputs to", config.getChildData("filename", "stdout")) { openOutput(fileNameSetting.getString()); reset(EmuTime::dummy()); } void DebugDevice::reset(EmuTime::param /*time*/) { mode = OFF; modeParameter = 0; } void DebugDevice::writeIO(word port, byte value, EmuTime::param time) { const auto& newName = fileNameSetting.getString(); if (newName != fileNameString) { openOutput(newName); } switch (port & 0x01) { case 0: switch ((value & 0x30) >> 4) { case 0: mode = OFF; break; case 1: mode = SINGLEBYTE; modeParameter = value & 0x0F; break; case 2: mode = MULTIBYTE; modeParameter = value & 0x03; break; case 3: break; } if (!(value & 0x40)){ (*outputstrm) << std::endl; } break; case 1: switch (mode) { case OFF: break; case SINGLEBYTE: outputSingleByte(value, time); break; case MULTIBYTE: outputMultiByte(value); default: break; } break; } } void DebugDevice::outputSingleByte(byte value, EmuTime::param time) { if (modeParameter & 0x01) { displayByte(value, HEX); } if (modeParameter & 0x02) { displayByte(value, BIN); } if (modeParameter & 0x04) { displayByte(value, DEC); } if (modeParameter & 0x08) { (*outputstrm) << '\''; byte tmp = ((value >= ' ') && (value != 127)) ? value : '.'; displayByte(tmp, ASC); (*outputstrm) << "' "; } Clock<3579545> zero(EmuTime::zero); (*outputstrm) << "emutime: " << std::dec << zero.getTicksTill(time); if ((modeParameter & 0x08) && ((value < ' ') || (value == 127))) { displayByte(value, ASC); // do special effects } (*outputstrm) << std::endl; } void DebugDevice::outputMultiByte(byte value) { DisplayType dispType; switch (modeParameter) { case 0: dispType = HEX; break; case 1: dispType = BIN; break; case 2: dispType = DEC; break; case 3: default: dispType = ASC; break; } displayByte(value, dispType); } void DebugDevice::displayByte(byte value, DisplayType type) { switch (type) { case HEX: (*outputstrm) << std::hex << std::setw(2) << std::setfill('0') << int(value) << "h " << std::flush; break; case BIN: { for (byte mask = 0x80; mask; mask >>= 1) { (*outputstrm) << ((value & mask) ? '1' : '0'); } (*outputstrm) << "b " << std::flush; break; } case DEC: (*outputstrm) << std::dec << std::setw(3) << std::setfill('0') << int(value) << ' ' << std::flush; break; case ASC: (*outputstrm).put(value); (*outputstrm) << std::flush; break; } } void DebugDevice::openOutput(string_ref name) { fileNameString = name.str(); debugOut.close(); if (name == "stdout") { outputstrm = &std::cout; } else if (name == "stderr") { outputstrm = &std::cerr; } else { string realName = FileOperations::expandTilde(name); FileOperations::openofstream(debugOut, realName, std::ios::app); outputstrm = &debugOut; } } static enum_string debugModeInfo[] = { { "OFF", DebugDevice::OFF }, { "SINGLEBYTE", DebugDevice::SINGLEBYTE }, { "MULTIBYTE", DebugDevice::MULTIBYTE }, { "ASCII", DebugDevice::ASCII } }; SERIALIZE_ENUM(DebugDevice::DebugMode, debugModeInfo); template void DebugDevice::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("mode", mode); ar.serialize("modeParameter", modeParameter); } INSTANTIATE_SERIALIZE_METHODS(DebugDevice); REGISTER_MSXDEVICE(DebugDevice, "DebugDevice"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/DebugDevice.hh000066400000000000000000000016461257557151200177240ustar00rootroot00000000000000#ifndef DEBUGDEVICE_HH #define DEBUGDEVICE_HH #include "MSXDevice.hh" #include "FilenameSetting.hh" #include namespace openmsx { class DebugDevice final : public MSXDevice { public: explicit DebugDevice(const DeviceConfig& config); void reset(EmuTime::param time) override; void writeIO(word port, byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); // public for serialization enum DebugMode {OFF, SINGLEBYTE, MULTIBYTE, ASCII}; private: enum DisplayType {HEX, BIN, DEC, ASC}; void outputSingleByte(byte value, EmuTime::param time); void outputMultiByte(byte value); void displayByte(byte value, DisplayType type); void openOutput(string_ref name); FilenameSetting fileNameSetting; std::ostream* outputstrm; std::ofstream debugOut; std::string fileNameString; DebugMode mode; byte modeParameter; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/DeviceFactory.cc000066400000000000000000000213651257557151200202730ustar00rootroot00000000000000#include "DeviceFactory.hh" #include "XMLElement.hh" #include "DeviceConfig.hh" #include "MSXRam.hh" #include "MSXPPI.hh" #include "VDP.hh" #include "MSXE6Timer.hh" #include "MSXFacMidiInterface.hh" #include "MSXResetStatusRegister.hh" #include "MSXTurboRPause.hh" #include "MSXTurboRPCM.hh" #include "MSXS1985.hh" #include "MSXS1990.hh" #include "MSXPSG.hh" #include "MSXMusic.hh" #include "MSXFmPac.hh" #include "MSXAudio.hh" #include "MSXMoonSound.hh" #include "MSXOPL3Cartridge.hh" #include "MSXYamahaSFG.hh" #include "MC6850.hh" #include "MSXKanji.hh" #include "MSXBunsetsu.hh" #include "MSXMemoryMapper.hh" #include "PanasonicRam.hh" #include "MSXRTC.hh" #include "PasswordCart.hh" #include "RomFactory.hh" #include "MSXPrinterPort.hh" #include "MSXSCCPlusCart.hh" #include "PhilipsFDC.hh" #include "MicrosolFDC.hh" #include "AVTFDC.hh" #include "NationalFDC.hh" #include "VictorFDC.hh" #include "SanyoFDC.hh" #include "TurboRFDC.hh" #include "SunriseIDE.hh" #include "BeerIDE.hh" #include "GoudaSCSI.hh" #include "MegaSCSI.hh" #include "ESE_RAM.hh" #include "ESE_SCC.hh" #include "MSXMatsushita.hh" #include "MSXVictorHC9xSystemControl.hh" #include "MSXCielTurbo.hh" #include "MSXKanji12.hh" #include "MSXMidi.hh" #include "MSXRS232.hh" #include "MSXMegaRam.hh" #include "MSXPac.hh" #include "MSXHBI55.hh" #include "DebugDevice.hh" #include "V9990.hh" #include "Video9000.hh" #include "ADVram.hh" #include "NowindInterface.hh" #include "MSXMirrorDevice.hh" #include "DummyDevice.hh" #include "MSXDeviceSwitch.hh" #include "MSXMapperIO.hh" #include "VDPIODelay.hh" #include "CliComm.hh" #include "MSXException.hh" #include "memory.hh" #include "components.hh" #include "SensorKid.hh" #if COMPONENT_LASERDISC #include "PioneerLDControl.hh" #endif using std::unique_ptr; namespace openmsx { static unique_ptr createWD2793BasedFDC(const DeviceConfig& conf) { const XMLElement* styleEl = conf.findChild("connectionstyle"); std::string type; if (!styleEl) { conf.getCliComm().printWarning( "WD2793 as FDC type without a connectionstyle is " "deprecated, please update your config file to use " "WD2793 with connectionstyle Philips!"); type = "Philips"; } else { type = styleEl->getData(); } if ((type == "Philips") || (type == "Sony")) { return make_unique(conf); } else if (type == "Microsol") { return make_unique(conf); } else if (type == "AVT") { return make_unique(conf); } else if (type == "National") { return make_unique(conf); } else if (type == "Sanyo") { return make_unique(conf); } else if (type == "Victor") { return make_unique(conf); } throw MSXException("Unknown WD2793 FDC connection style " + type); } unique_ptr DeviceFactory::create(const DeviceConfig& conf) { unique_ptr result; const std::string& type = conf.getXML()->getName(); if (type == "PPI") { result = make_unique(conf); } else if (type == "RAM") { result = make_unique(conf); } else if (type == "VDP") { result = make_unique(conf); } else if (type == "E6Timer") { result = make_unique(conf); } else if (type == "ResetStatusRegister" || type == "F4Device") { result = make_unique(conf); } else if (type == "TurboRPause") { result = make_unique(conf); } else if (type == "TurboRPCM") { result = make_unique(conf); } else if (type == "S1985") { result = make_unique(conf); } else if (type == "S1990") { result = make_unique(conf); } else if (type == "PSG") { result = make_unique(conf); } else if (type == "MSX-MUSIC") { result = make_unique(conf); } else if (type == "MSX-MUSIC-WX") { result = make_unique(conf); } else if (type == "FMPAC") { result = make_unique(conf); } else if (type == "MSX-AUDIO") { result = make_unique(conf); } else if (type == "MusicModuleMIDI") { result = make_unique(conf); } else if (type == "FACMIDIInterface") { result = make_unique(conf); } else if (type == "YamahaSFG") { result = make_unique(conf); } else if (type == "MoonSound") { result = make_unique(conf); } else if (type == "OPL3Cartridge") { result = make_unique(conf); } else if (type == "Kanji") { result = make_unique(conf); } else if (type == "Bunsetsu") { result = make_unique(conf); } else if (type == "MemoryMapper") { result = make_unique(conf); } else if (type == "PanasonicRAM") { result = make_unique(conf); } else if (type == "RTC") { result = make_unique(conf); } else if (type == "PasswordCart") { result = make_unique(conf); } else if (type == "ROM") { result = RomFactory::create(conf); } else if (type == "PrinterPort") { result = make_unique(conf); } else if (type == "SCCplus") { // Note: it's actually called SCC-I result = make_unique(conf); } else if ((type == "WD2793") || (type == "WD1770")) { result = createWD2793BasedFDC(conf); } else if (type == "Microsol") { conf.getCliComm().printWarning( "Microsol as FDC type is deprecated, please update " "your config file to use WD2793 with connectionstyle " "Microsol!"); result = make_unique(conf); } else if (type == "MB8877A") { conf.getCliComm().printWarning( "MB8877A as FDC type is deprecated, please update your " "config file to use WD2793 with connectionstyle National!"); result = make_unique(conf); } else if (type == "TC8566AF") { result = make_unique(conf); } else if (type == "BeerIDE") { result = make_unique(conf); } else if (type == "SunriseIDE") { result = make_unique(conf); } else if (type == "GoudaSCSI") { result = make_unique(conf); } else if (type == "MegaSCSI") { result = make_unique(conf); } else if (type == "ESERAM") { result = make_unique(conf); } else if (type == "WaveSCSI") { result = make_unique(conf, true); } else if (type == "ESESCC") { result = make_unique(conf, false); } else if (type == "Matsushita") { result = make_unique(conf); } else if (type == "VictorHC9xSystemControl") { result = make_unique(conf); } else if (type == "CielTurbo") { result = make_unique(conf); } else if (type == "Kanji12") { result = make_unique(conf); } else if (type == "MSX-MIDI") { result = make_unique(conf); } else if (type == "MSX-RS232") { result = make_unique(conf); } else if (type == "MegaRam") { result = make_unique(conf); } else if (type == "PAC") { result = make_unique(conf); } else if (type == "HBI55") { result = make_unique(conf); } else if (type == "DebugDevice") { result = make_unique(conf); } else if (type == "V9990") { result = make_unique(conf); } else if (type == "Video9000") { result = make_unique(conf); } else if (type == "ADVram") { result = make_unique(conf); } else if (type == "PioneerLDControl") { #if COMPONENT_LASERDISC result = make_unique(conf); #else throw MSXException("Laserdisc component not compiled in"); #endif } else if (type == "Nowind") { result = make_unique(conf); } else if (type == "Mirror") { result = make_unique(conf); } else if (type == "SensorKid") { result = make_unique(conf); } else { throw MSXException("Unknown device \"" + type + "\" specified in configuration"); } if (result) result->init(); return result; } static XMLElement createConfig(string_ref name, string_ref id) { XMLElement config(name); config.addAttribute("id", id); return config; } unique_ptr DeviceFactory::createDummyDevice( const HardwareConfig& hwConf) { static XMLElement xml(createConfig("Dummy", "")); return make_unique(DeviceConfig(hwConf, xml)); } unique_ptr DeviceFactory::createDeviceSwitch( const HardwareConfig& hwConf) { static XMLElement xml(createConfig("DeviceSwitch", "DeviceSwitch")); return make_unique(DeviceConfig(hwConf, xml)); } unique_ptr DeviceFactory::createMapperIO( const HardwareConfig& hwConf) { static XMLElement xml(createConfig("MapperIO", "MapperIO")); return make_unique(DeviceConfig(hwConf, xml)); } unique_ptr DeviceFactory::createVDPIODelay( const HardwareConfig& hwConf, MSXCPUInterface& cpuInterface) { static XMLElement xml(createConfig("VDPIODelay", "VDPIODelay")); return make_unique(DeviceConfig(hwConf, xml), cpuInterface); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/DeviceFactory.hh000066400000000000000000000014061257557151200202770ustar00rootroot00000000000000#ifndef DEVICEFACTORY_HH #define DEVICEFACTORY_HH #include namespace openmsx { class MSXDevice; class DeviceConfig; class HardwareConfig; class DummyDevice; class MSXDeviceSwitch; class MSXMapperIO; class VDPIODelay; class MSXCPUInterface; class DeviceFactory { public: static std::unique_ptr create(const DeviceConfig& conf); static std::unique_ptr createDummyDevice( const HardwareConfig& hcConf); static std::unique_ptr createDeviceSwitch( const HardwareConfig& hcConf); static std::unique_ptr createMapperIO( const HardwareConfig& hcConf); static std::unique_ptr createVDPIODelay( const HardwareConfig& hcConf, MSXCPUInterface& cpuInterface); }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/Doxyfile000066400000000000000000003054671257557151200167530ustar00rootroot00000000000000# Doxyfile 1.8.7 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all text # before the first occurrence of this tag. Doxygen uses libiconv (or the iconv # built into libc) for the transcoding. See http://www.gnu.org/software/libiconv # for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = openMSX # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = # With the PROJECT_LOGO tag one can specify an logo or icon that is included in # the documentation. The maximum height of the logo should not exceed 55 pixels # and the maximum width should not exceed 200 pixels. Doxygen will copy the logo # to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = doxy # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and # will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes # performance problems for the file system. # The default value is: NO. CREATE_SUBDIRS = NO # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, # Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), # Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, # Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, # Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, # Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, # Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = NO # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the # first line (until the first dot) of a Javadoc-style comment as the brief # description. If set to NO, the Javadoc-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = YES # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a # new page for each member. If set to NO, the documentation of a member will be # part of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:\n" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines. ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding "class=itcl::class" # will allow you to use the command class in the itcl::class meaning. TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, Javascript, # C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: # FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: # Fortran. In the later case the parser tries to guess whether the code is fixed # or free formatted code, this is the default for Fortran type files), VHDL. For # instance to make doxygen treat .inc files as Fortran files (default is PHP), # and .f files as C (default is Fortran), use: inc=Fortran f=C. # # Note For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See http://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by by putting a % sign in front of the word # or globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = YES # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small # doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PACKAGE tag is set to YES all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined # locally in source files will be included in the documentation. If set to NO # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO these classes will be included in the various overviews. This option has # no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # (class|struct|union) declarations. If set to NO these declarations will be # included in the documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any # documentation blocks found inside the body of a function. If set to NO these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file # names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. # The default value is: system dependent. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the # todo list. This list is created by putting \todo commands in the # documentation. # The default value is: YES. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the # test list. This list is created by putting \test commands in the # documentation. # The default value is: YES. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES the list # will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. Do not use file names with spaces, bibtex cannot handle them. See # also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = NO # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some parameters # in a documented function, or documenting parameters that don't exist or using # markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO doxygen will only warn about wrong or incomplete parameter # documentation, but not about the absence of documentation. # The default value is: NO. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. # Note: If this tag is empty the current directory is searched. INPUT = ./ \ ../derived/x86_64-linux-devel/config/ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: http://www.gnu.org/software/libiconv) for the list of # possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank the # following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, # *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, # *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, # *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, # *.qsf, *.as and *.js. FILE_PATTERNS = *.cc \ *.hh # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = ./ufo # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER ) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = YES # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # function all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = YES # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = YES # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES, then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see http://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the config file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = YES # The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in # which the alphabetical index list will be split. # Minimum value: 1, maximum value: 20, default value: 5. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- # defined cascading style sheet that is included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefor more robust against future updates. # Doxygen will copy the style sheet file to the output directory. For an example # see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the stylesheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see # http://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this # to NO can help when comparing the output of multiple runs. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: http://developer.apple.com/tools/xcode/), introduced with # OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a # Makefile in the HTML output directory. Running make will produce the docset in # that directory and running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on # Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler ( hhc.exe). If non-empty # doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated ( # YES) or that it should be included in the master .chm file ( NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated ( # YES) or a normal table of contents ( NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- # folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location of Qt's # qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the # generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has # the same information as the tab index, you could consider setting # DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # # Note that when changing this option you need to delete any form_*.png files in # the HTML output directory before the changes have effect. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # http://www.mathjax.org) which uses client side Javascript for the rendering # instead of using prerendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: # http://docs.mathjax.org/en/latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from http://www.mathjax.org before deployment. # The default value is: http://cdn.mathjax.org/mathjax/latest. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://www.mathjax.org/mathjax # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the javascript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /(); NEXT; } CASE(46) { int c = ld_R_xhl(); NEXT; } CASE(4E) { int c = ld_R_xhl(); NEXT; } CASE(56) { int c = ld_R_xhl(); NEXT; } CASE(5E) { int c = ld_R_xhl(); NEXT; } CASE(66) { int c = ld_R_xhl(); NEXT; } CASE(6E) { int c = ld_R_xhl(); NEXT; } CASE(7E) { int c = ld_R_xhl(); NEXT; } CASE(76) { int c = halt(); NEXT_STOP; } CASE(80) { int c = add_a_R(); NEXT; } CASE(81) { int c = add_a_R(); NEXT; } CASE(82) { int c = add_a_R(); NEXT; } CASE(83) { int c = add_a_R(); NEXT; } CASE(84) { int c = add_a_R(); NEXT; } CASE(85) { int c = add_a_R(); NEXT; } CASE(86) { int c = add_a_xhl(); NEXT; } CASE(87) { int c = add_a_a(); NEXT; } CASE(88) { int c = adc_a_R(); NEXT; } CASE(89) { int c = adc_a_R(); NEXT; } CASE(8A) { int c = adc_a_R(); NEXT; } CASE(8B) { int c = adc_a_R(); NEXT; } CASE(8C) { int c = adc_a_R(); NEXT; } CASE(8D) { int c = adc_a_R(); NEXT; } CASE(8E) { int c = adc_a_xhl(); NEXT; } CASE(8F) { int c = adc_a_a(); NEXT; } CASE(90) { int c = sub_R(); NEXT; } CASE(91) { int c = sub_R(); NEXT; } CASE(92) { int c = sub_R(); NEXT; } CASE(93) { int c = sub_R(); NEXT; } CASE(94) { int c = sub_R(); NEXT; } CASE(95) { int c = sub_R(); NEXT; } CASE(96) { int c = sub_xhl(); NEXT; } CASE(97) { int c = sub_a(); NEXT; } CASE(98) { int c = sbc_a_R(); NEXT; } CASE(99) { int c = sbc_a_R(); NEXT; } CASE(9A) { int c = sbc_a_R(); NEXT; } CASE(9B) { int c = sbc_a_R(); NEXT; } CASE(9C) { int c = sbc_a_R(); NEXT; } CASE(9D) { int c = sbc_a_R(); NEXT; } CASE(9E) { int c = sbc_a_xhl(); NEXT; } CASE(9F) { int c = sbc_a_a(); NEXT; } CASE(A0) { int c = and_R(); NEXT; } CASE(A1) { int c = and_R(); NEXT; } CASE(A2) { int c = and_R(); NEXT; } CASE(A3) { int c = and_R(); NEXT; } CASE(A4) { int c = and_R(); NEXT; } CASE(A5) { int c = and_R(); NEXT; } CASE(A6) { int c = and_xhl(); NEXT; } CASE(A7) { int c = and_a(); NEXT; } CASE(A8) { int c = xor_R(); NEXT; } CASE(A9) { int c = xor_R(); NEXT; } CASE(AA) { int c = xor_R(); NEXT; } CASE(AB) { int c = xor_R(); NEXT; } CASE(AC) { int c = xor_R(); NEXT; } CASE(AD) { int c = xor_R(); NEXT; } CASE(AE) { int c = xor_xhl(); NEXT; } CASE(AF) { int c = xor_a(); NEXT; } CASE(B0) { int c = or_R(); NEXT; } CASE(B1) { int c = or_R(); NEXT; } CASE(B2) { int c = or_R(); NEXT; } CASE(B3) { int c = or_R(); NEXT; } CASE(B4) { int c = or_R(); NEXT; } CASE(B5) { int c = or_R(); NEXT; } CASE(B6) { int c = or_xhl(); NEXT; } CASE(B7) { int c = or_a(); NEXT; } CASE(B8) { int c = cp_R(); NEXT; } CASE(B9) { int c = cp_R(); NEXT; } CASE(BA) { int c = cp_R(); NEXT; } CASE(BB) { int c = cp_R(); NEXT; } CASE(BC) { int c = cp_R(); NEXT; } CASE(BD) { int c = cp_R(); NEXT; } CASE(BE) { int c = cp_xhl(); NEXT; } CASE(BF) { int c = cp_a(); NEXT; } CASE(D3) { int c = out_byte_a(); NEXT; } CASE(DB) { int c = in_a_byte(); NEXT; } CASE(D9) { int c = exx(); NEXT; } CASE(E3) { int c = ex_xsp_SS(); NEXT; } CASE(EB) { int c = ex_de_hl(); NEXT; } CASE(E9) { int c = jp_SS(); NEXT; } CASE(F9) { int c = ld_sp_SS(); NEXT; } CASE(F3) { int c = di(); NEXT; } CASE(FB) { int c = ei(); NEXT_EI; } CASE(C6) { int c = add_a_byte(); NEXT; } CASE(CE) { int c = adc_a_byte(); NEXT; } CASE(D6) { int c = sub_byte(); NEXT; } CASE(DE) { int c = sbc_a_byte(); NEXT; } CASE(E6) { int c = and_byte(); NEXT; } CASE(EE) { int c = xor_byte(); NEXT; } CASE(F6) { int c = or_byte(); NEXT; } CASE(FE) { int c = cp_byte(); NEXT; } CASE(C0) { int c = ret(CondNZ()); NEXT; } CASE(C8) { int c = ret(CondZ ()); NEXT; } CASE(D0) { int c = ret(CondNC()); NEXT; } CASE(D8) { int c = ret(CondC ()); NEXT; } CASE(E0) { int c = ret(CondPO()); NEXT; } CASE(E8) { int c = ret(CondPE()); NEXT; } CASE(F0) { int c = ret(CondP ()); NEXT; } CASE(F8) { int c = ret(CondM ()); NEXT; } CASE(C9) { int c = ret(); NEXT; } CASE(C2) { int c = jp(CondNZ()); NEXT; } CASE(CA) { int c = jp(CondZ ()); NEXT; } CASE(D2) { int c = jp(CondNC()); NEXT; } CASE(DA) { int c = jp(CondC ()); NEXT; } CASE(E2) { int c = jp(CondPO()); NEXT; } CASE(EA) { int c = jp(CondPE()); NEXT; } CASE(F2) { int c = jp(CondP ()); NEXT; } CASE(FA) { int c = jp(CondM ()); NEXT; } CASE(C3) { int c = jp(CondTrue()); NEXT; } CASE(C4) { int c = call(CondNZ()); NEXT; } CASE(CC) { int c = call(CondZ ()); NEXT; } CASE(D4) { int c = call(CondNC()); NEXT; } CASE(DC) { int c = call(CondC ()); NEXT; } CASE(E4) { int c = call(CondPO()); NEXT; } CASE(EC) { int c = call(CondPE()); NEXT; } CASE(F4) { int c = call(CondP ()); NEXT; } CASE(FC) { int c = call(CondM ()); NEXT; } CASE(CD) { int c = call(CondTrue()); NEXT; } CASE(C1) { int c = pop_SS (); NEXT; } CASE(D1) { int c = pop_SS (); NEXT; } CASE(E1) { int c = pop_SS (); NEXT; } CASE(F1) { int c = pop_SS (); NEXT; } CASE(C5) { int c = push_SS(); NEXT; } CASE(D5) { int c = push_SS(); NEXT; } CASE(E5) { int c = push_SS(); NEXT; } CASE(F5) { int c = push_SS(); NEXT; } CASE(C7) { int c = rst<0x00>(); NEXT; } CASE(CF) { int c = rst<0x08>(); NEXT; } CASE(D7) { int c = rst<0x10>(); NEXT; } CASE(DF) { int c = rst<0x18>(); NEXT; } CASE(E7) { int c = rst<0x20>(); NEXT; } CASE(EF) { int c = rst<0x28>(); NEXT; } CASE(F7) { int c = rst<0x30>(); NEXT; } CASE(FF) { int c = rst<0x38>(); NEXT; } CASE(CB) { byte cb_opcode = RDMEM_OPCODE(T::CC_PREFIX); incR(1); switch (cb_opcode) { case 0x00: { int c = rlc_R(); NEXT; } case 0x01: { int c = rlc_R(); NEXT; } case 0x02: { int c = rlc_R(); NEXT; } case 0x03: { int c = rlc_R(); NEXT; } case 0x04: { int c = rlc_R(); NEXT; } case 0x05: { int c = rlc_R(); NEXT; } case 0x07: { int c = rlc_R(); NEXT; } case 0x06: { int c = rlc_xhl(); NEXT; } case 0x08: { int c = rrc_R(); NEXT; } case 0x09: { int c = rrc_R(); NEXT; } case 0x0a: { int c = rrc_R(); NEXT; } case 0x0b: { int c = rrc_R(); NEXT; } case 0x0c: { int c = rrc_R(); NEXT; } case 0x0d: { int c = rrc_R(); NEXT; } case 0x0f: { int c = rrc_R(); NEXT; } case 0x0e: { int c = rrc_xhl(); NEXT; } case 0x10: { int c = rl_R(); NEXT; } case 0x11: { int c = rl_R(); NEXT; } case 0x12: { int c = rl_R(); NEXT; } case 0x13: { int c = rl_R(); NEXT; } case 0x14: { int c = rl_R(); NEXT; } case 0x15: { int c = rl_R(); NEXT; } case 0x17: { int c = rl_R(); NEXT; } case 0x16: { int c = rl_xhl(); NEXT; } case 0x18: { int c = rr_R(); NEXT; } case 0x19: { int c = rr_R(); NEXT; } case 0x1a: { int c = rr_R(); NEXT; } case 0x1b: { int c = rr_R(); NEXT; } case 0x1c: { int c = rr_R(); NEXT; } case 0x1d: { int c = rr_R(); NEXT; } case 0x1f: { int c = rr_R(); NEXT; } case 0x1e: { int c = rr_xhl(); NEXT; } case 0x20: { int c = sla_R(); NEXT; } case 0x21: { int c = sla_R(); NEXT; } case 0x22: { int c = sla_R(); NEXT; } case 0x23: { int c = sla_R(); NEXT; } case 0x24: { int c = sla_R(); NEXT; } case 0x25: { int c = sla_R(); NEXT; } case 0x27: { int c = sla_R(); NEXT; } case 0x26: { int c = sla_xhl(); NEXT; } case 0x28: { int c = sra_R(); NEXT; } case 0x29: { int c = sra_R(); NEXT; } case 0x2a: { int c = sra_R(); NEXT; } case 0x2b: { int c = sra_R(); NEXT; } case 0x2c: { int c = sra_R(); NEXT; } case 0x2d: { int c = sra_R(); NEXT; } case 0x2f: { int c = sra_R(); NEXT; } case 0x2e: { int c = sra_xhl(); NEXT; } case 0x30: { int c = T::isR800() ? sla_R() : sll_R(); NEXT; } case 0x31: { int c = T::isR800() ? sla_R() : sll_R(); NEXT; } case 0x32: { int c = T::isR800() ? sla_R() : sll_R(); NEXT; } case 0x33: { int c = T::isR800() ? sla_R() : sll_R(); NEXT; } case 0x34: { int c = T::isR800() ? sla_R() : sll_R(); NEXT; } case 0x35: { int c = T::isR800() ? sla_R() : sll_R(); NEXT; } case 0x37: { int c = T::isR800() ? sla_R() : sll_R(); NEXT; } case 0x36: { int c = T::isR800() ? sla_xhl() : sll_xhl(); NEXT; } case 0x38: { int c = srl_R(); NEXT; } case 0x39: { int c = srl_R(); NEXT; } case 0x3a: { int c = srl_R(); NEXT; } case 0x3b: { int c = srl_R(); NEXT; } case 0x3c: { int c = srl_R(); NEXT; } case 0x3d: { int c = srl_R(); NEXT; } case 0x3f: { int c = srl_R(); NEXT; } case 0x3e: { int c = srl_xhl(); NEXT; } case 0x40: { int c = bit_N_R<0,B>(); NEXT; } case 0x41: { int c = bit_N_R<0,C>(); NEXT; } case 0x42: { int c = bit_N_R<0,D>(); NEXT; } case 0x43: { int c = bit_N_R<0,E>(); NEXT; } case 0x44: { int c = bit_N_R<0,H>(); NEXT; } case 0x45: { int c = bit_N_R<0,L>(); NEXT; } case 0x47: { int c = bit_N_R<0,A>(); NEXT; } case 0x48: { int c = bit_N_R<1,B>(); NEXT; } case 0x49: { int c = bit_N_R<1,C>(); NEXT; } case 0x4a: { int c = bit_N_R<1,D>(); NEXT; } case 0x4b: { int c = bit_N_R<1,E>(); NEXT; } case 0x4c: { int c = bit_N_R<1,H>(); NEXT; } case 0x4d: { int c = bit_N_R<1,L>(); NEXT; } case 0x4f: { int c = bit_N_R<1,A>(); NEXT; } case 0x50: { int c = bit_N_R<2,B>(); NEXT; } case 0x51: { int c = bit_N_R<2,C>(); NEXT; } case 0x52: { int c = bit_N_R<2,D>(); NEXT; } case 0x53: { int c = bit_N_R<2,E>(); NEXT; } case 0x54: { int c = bit_N_R<2,H>(); NEXT; } case 0x55: { int c = bit_N_R<2,L>(); NEXT; } case 0x57: { int c = bit_N_R<2,A>(); NEXT; } case 0x58: { int c = bit_N_R<3,B>(); NEXT; } case 0x59: { int c = bit_N_R<3,C>(); NEXT; } case 0x5a: { int c = bit_N_R<3,D>(); NEXT; } case 0x5b: { int c = bit_N_R<3,E>(); NEXT; } case 0x5c: { int c = bit_N_R<3,H>(); NEXT; } case 0x5d: { int c = bit_N_R<3,L>(); NEXT; } case 0x5f: { int c = bit_N_R<3,A>(); NEXT; } case 0x60: { int c = bit_N_R<4,B>(); NEXT; } case 0x61: { int c = bit_N_R<4,C>(); NEXT; } case 0x62: { int c = bit_N_R<4,D>(); NEXT; } case 0x63: { int c = bit_N_R<4,E>(); NEXT; } case 0x64: { int c = bit_N_R<4,H>(); NEXT; } case 0x65: { int c = bit_N_R<4,L>(); NEXT; } case 0x67: { int c = bit_N_R<4,A>(); NEXT; } case 0x68: { int c = bit_N_R<5,B>(); NEXT; } case 0x69: { int c = bit_N_R<5,C>(); NEXT; } case 0x6a: { int c = bit_N_R<5,D>(); NEXT; } case 0x6b: { int c = bit_N_R<5,E>(); NEXT; } case 0x6c: { int c = bit_N_R<5,H>(); NEXT; } case 0x6d: { int c = bit_N_R<5,L>(); NEXT; } case 0x6f: { int c = bit_N_R<5,A>(); NEXT; } case 0x70: { int c = bit_N_R<6,B>(); NEXT; } case 0x71: { int c = bit_N_R<6,C>(); NEXT; } case 0x72: { int c = bit_N_R<6,D>(); NEXT; } case 0x73: { int c = bit_N_R<6,E>(); NEXT; } case 0x74: { int c = bit_N_R<6,H>(); NEXT; } case 0x75: { int c = bit_N_R<6,L>(); NEXT; } case 0x77: { int c = bit_N_R<6,A>(); NEXT; } case 0x78: { int c = bit_N_R<7,B>(); NEXT; } case 0x79: { int c = bit_N_R<7,C>(); NEXT; } case 0x7a: { int c = bit_N_R<7,D>(); NEXT; } case 0x7b: { int c = bit_N_R<7,E>(); NEXT; } case 0x7c: { int c = bit_N_R<7,H>(); NEXT; } case 0x7d: { int c = bit_N_R<7,L>(); NEXT; } case 0x7f: { int c = bit_N_R<7,A>(); NEXT; } case 0x46: { int c = bit_N_xhl<0>(); NEXT; } case 0x4e: { int c = bit_N_xhl<1>(); NEXT; } case 0x56: { int c = bit_N_xhl<2>(); NEXT; } case 0x5e: { int c = bit_N_xhl<3>(); NEXT; } case 0x66: { int c = bit_N_xhl<4>(); NEXT; } case 0x6e: { int c = bit_N_xhl<5>(); NEXT; } case 0x76: { int c = bit_N_xhl<6>(); NEXT; } case 0x7e: { int c = bit_N_xhl<7>(); NEXT; } case 0x80: { int c = res_N_R<0,B>(); NEXT; } case 0x81: { int c = res_N_R<0,C>(); NEXT; } case 0x82: { int c = res_N_R<0,D>(); NEXT; } case 0x83: { int c = res_N_R<0,E>(); NEXT; } case 0x84: { int c = res_N_R<0,H>(); NEXT; } case 0x85: { int c = res_N_R<0,L>(); NEXT; } case 0x87: { int c = res_N_R<0,A>(); NEXT; } case 0x88: { int c = res_N_R<1,B>(); NEXT; } case 0x89: { int c = res_N_R<1,C>(); NEXT; } case 0x8a: { int c = res_N_R<1,D>(); NEXT; } case 0x8b: { int c = res_N_R<1,E>(); NEXT; } case 0x8c: { int c = res_N_R<1,H>(); NEXT; } case 0x8d: { int c = res_N_R<1,L>(); NEXT; } case 0x8f: { int c = res_N_R<1,A>(); NEXT; } case 0x90: { int c = res_N_R<2,B>(); NEXT; } case 0x91: { int c = res_N_R<2,C>(); NEXT; } case 0x92: { int c = res_N_R<2,D>(); NEXT; } case 0x93: { int c = res_N_R<2,E>(); NEXT; } case 0x94: { int c = res_N_R<2,H>(); NEXT; } case 0x95: { int c = res_N_R<2,L>(); NEXT; } case 0x97: { int c = res_N_R<2,A>(); NEXT; } case 0x98: { int c = res_N_R<3,B>(); NEXT; } case 0x99: { int c = res_N_R<3,C>(); NEXT; } case 0x9a: { int c = res_N_R<3,D>(); NEXT; } case 0x9b: { int c = res_N_R<3,E>(); NEXT; } case 0x9c: { int c = res_N_R<3,H>(); NEXT; } case 0x9d: { int c = res_N_R<3,L>(); NEXT; } case 0x9f: { int c = res_N_R<3,A>(); NEXT; } case 0xa0: { int c = res_N_R<4,B>(); NEXT; } case 0xa1: { int c = res_N_R<4,C>(); NEXT; } case 0xa2: { int c = res_N_R<4,D>(); NEXT; } case 0xa3: { int c = res_N_R<4,E>(); NEXT; } case 0xa4: { int c = res_N_R<4,H>(); NEXT; } case 0xa5: { int c = res_N_R<4,L>(); NEXT; } case 0xa7: { int c = res_N_R<4,A>(); NEXT; } case 0xa8: { int c = res_N_R<5,B>(); NEXT; } case 0xa9: { int c = res_N_R<5,C>(); NEXT; } case 0xaa: { int c = res_N_R<5,D>(); NEXT; } case 0xab: { int c = res_N_R<5,E>(); NEXT; } case 0xac: { int c = res_N_R<5,H>(); NEXT; } case 0xad: { int c = res_N_R<5,L>(); NEXT; } case 0xaf: { int c = res_N_R<5,A>(); NEXT; } case 0xb0: { int c = res_N_R<6,B>(); NEXT; } case 0xb1: { int c = res_N_R<6,C>(); NEXT; } case 0xb2: { int c = res_N_R<6,D>(); NEXT; } case 0xb3: { int c = res_N_R<6,E>(); NEXT; } case 0xb4: { int c = res_N_R<6,H>(); NEXT; } case 0xb5: { int c = res_N_R<6,L>(); NEXT; } case 0xb7: { int c = res_N_R<6,A>(); NEXT; } case 0xb8: { int c = res_N_R<7,B>(); NEXT; } case 0xb9: { int c = res_N_R<7,C>(); NEXT; } case 0xba: { int c = res_N_R<7,D>(); NEXT; } case 0xbb: { int c = res_N_R<7,E>(); NEXT; } case 0xbc: { int c = res_N_R<7,H>(); NEXT; } case 0xbd: { int c = res_N_R<7,L>(); NEXT; } case 0xbf: { int c = res_N_R<7,A>(); NEXT; } case 0x86: { int c = res_N_xhl<0>(); NEXT; } case 0x8e: { int c = res_N_xhl<1>(); NEXT; } case 0x96: { int c = res_N_xhl<2>(); NEXT; } case 0x9e: { int c = res_N_xhl<3>(); NEXT; } case 0xa6: { int c = res_N_xhl<4>(); NEXT; } case 0xae: { int c = res_N_xhl<5>(); NEXT; } case 0xb6: { int c = res_N_xhl<6>(); NEXT; } case 0xbe: { int c = res_N_xhl<7>(); NEXT; } case 0xc0: { int c = set_N_R<0,B>(); NEXT; } case 0xc1: { int c = set_N_R<0,C>(); NEXT; } case 0xc2: { int c = set_N_R<0,D>(); NEXT; } case 0xc3: { int c = set_N_R<0,E>(); NEXT; } case 0xc4: { int c = set_N_R<0,H>(); NEXT; } case 0xc5: { int c = set_N_R<0,L>(); NEXT; } case 0xc7: { int c = set_N_R<0,A>(); NEXT; } case 0xc8: { int c = set_N_R<1,B>(); NEXT; } case 0xc9: { int c = set_N_R<1,C>(); NEXT; } case 0xca: { int c = set_N_R<1,D>(); NEXT; } case 0xcb: { int c = set_N_R<1,E>(); NEXT; } case 0xcc: { int c = set_N_R<1,H>(); NEXT; } case 0xcd: { int c = set_N_R<1,L>(); NEXT; } case 0xcf: { int c = set_N_R<1,A>(); NEXT; } case 0xd0: { int c = set_N_R<2,B>(); NEXT; } case 0xd1: { int c = set_N_R<2,C>(); NEXT; } case 0xd2: { int c = set_N_R<2,D>(); NEXT; } case 0xd3: { int c = set_N_R<2,E>(); NEXT; } case 0xd4: { int c = set_N_R<2,H>(); NEXT; } case 0xd5: { int c = set_N_R<2,L>(); NEXT; } case 0xd7: { int c = set_N_R<2,A>(); NEXT; } case 0xd8: { int c = set_N_R<3,B>(); NEXT; } case 0xd9: { int c = set_N_R<3,C>(); NEXT; } case 0xda: { int c = set_N_R<3,D>(); NEXT; } case 0xdb: { int c = set_N_R<3,E>(); NEXT; } case 0xdc: { int c = set_N_R<3,H>(); NEXT; } case 0xdd: { int c = set_N_R<3,L>(); NEXT; } case 0xdf: { int c = set_N_R<3,A>(); NEXT; } case 0xe0: { int c = set_N_R<4,B>(); NEXT; } case 0xe1: { int c = set_N_R<4,C>(); NEXT; } case 0xe2: { int c = set_N_R<4,D>(); NEXT; } case 0xe3: { int c = set_N_R<4,E>(); NEXT; } case 0xe4: { int c = set_N_R<4,H>(); NEXT; } case 0xe5: { int c = set_N_R<4,L>(); NEXT; } case 0xe7: { int c = set_N_R<4,A>(); NEXT; } case 0xe8: { int c = set_N_R<5,B>(); NEXT; } case 0xe9: { int c = set_N_R<5,C>(); NEXT; } case 0xea: { int c = set_N_R<5,D>(); NEXT; } case 0xeb: { int c = set_N_R<5,E>(); NEXT; } case 0xec: { int c = set_N_R<5,H>(); NEXT; } case 0xed: { int c = set_N_R<5,L>(); NEXT; } case 0xef: { int c = set_N_R<5,A>(); NEXT; } case 0xf0: { int c = set_N_R<6,B>(); NEXT; } case 0xf1: { int c = set_N_R<6,C>(); NEXT; } case 0xf2: { int c = set_N_R<6,D>(); NEXT; } case 0xf3: { int c = set_N_R<6,E>(); NEXT; } case 0xf4: { int c = set_N_R<6,H>(); NEXT; } case 0xf5: { int c = set_N_R<6,L>(); NEXT; } case 0xf7: { int c = set_N_R<6,A>(); NEXT; } case 0xf8: { int c = set_N_R<7,B>(); NEXT; } case 0xf9: { int c = set_N_R<7,C>(); NEXT; } case 0xfa: { int c = set_N_R<7,D>(); NEXT; } case 0xfb: { int c = set_N_R<7,E>(); NEXT; } case 0xfc: { int c = set_N_R<7,H>(); NEXT; } case 0xfd: { int c = set_N_R<7,L>(); NEXT; } case 0xff: { int c = set_N_R<7,A>(); NEXT; } case 0xc6: { int c = set_N_xhl<0>(); NEXT; } case 0xce: { int c = set_N_xhl<1>(); NEXT; } case 0xd6: { int c = set_N_xhl<2>(); NEXT; } case 0xde: { int c = set_N_xhl<3>(); NEXT; } case 0xe6: { int c = set_N_xhl<4>(); NEXT; } case 0xee: { int c = set_N_xhl<5>(); NEXT; } case 0xf6: { int c = set_N_xhl<6>(); NEXT; } case 0xfe: { int c = set_N_xhl<7>(); NEXT; } default: UNREACHABLE; return; } } CASE(ED) { byte ed_opcode = RDMEM_OPCODE(T::CC_PREFIX); incR(1); switch (ed_opcode) { case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f: case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f: case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29: case 0x2a: case 0x2b: case 0x2c: case 0x2d: case 0x2e: case 0x2f: case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: case 0x3a: case 0x3b: case 0x3c: case 0x3d: case 0x3e: case 0x3f: case 0x77: case 0x7f: case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f: case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: case 0x98: case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d: case 0x9e: case 0x9f: case 0xa4: case 0xa5: case 0xa6: case 0xa7: case 0xac: case 0xad: case 0xae: case 0xaf: case 0xb4: case 0xb5: case 0xb6: case 0xb7: case 0xbc: case 0xbd: case 0xbe: case 0xbf: case 0xc0: case 0xc2: case 0xc4: case 0xc5: case 0xc6: case 0xc7: case 0xc8: case 0xca: case 0xcb: case 0xcc: case 0xcd: case 0xce: case 0xcf: case 0xd0: case 0xd2: case 0xd3: case 0xd4: case 0xd5: case 0xd6: case 0xd7: case 0xd8: case 0xda: case 0xdb: case 0xdc: case 0xdd: case 0xde: case 0xdf: case 0xe0: case 0xe1: case 0xe2: case 0xe3: case 0xe4: case 0xe5: case 0xe6: case 0xe7: case 0xe8: case 0xe9: case 0xea: case 0xeb: case 0xec: case 0xed: case 0xee: case 0xef: case 0xf0: case 0xf1: case 0xf2: case 0xf4: case 0xf5: case 0xf6: case 0xf7: case 0xf8: case 0xf9: case 0xfa: case 0xfb: case 0xfc: case 0xfd: case 0xfe: case 0xff: { int c = nop(); NEXT; } case 0x40: { int c = in_R_c(); NEXT; } case 0x48: { int c = in_R_c(); NEXT; } case 0x50: { int c = in_R_c(); NEXT; } case 0x58: { int c = in_R_c(); NEXT; } case 0x60: { int c = in_R_c(); NEXT; } case 0x68: { int c = in_R_c(); NEXT; } case 0x70: { int c = in_R_c(); NEXT; } case 0x78: { int c = in_R_c(); NEXT; } case 0x41: { int c = out_c_R(); NEXT; } case 0x49: { int c = out_c_R(); NEXT; } case 0x51: { int c = out_c_R(); NEXT; } case 0x59: { int c = out_c_R(); NEXT; } case 0x61: { int c = out_c_R(); NEXT; } case 0x69: { int c = out_c_R(); NEXT; } case 0x71: { int c = out_c_0(); NEXT; } case 0x79: { int c = out_c_R(); NEXT; } case 0x42: { int c = sbc_hl_SS(); NEXT; } case 0x52: { int c = sbc_hl_SS(); NEXT; } case 0x62: { int c = sbc_hl_hl (); NEXT; } case 0x72: { int c = sbc_hl_SS(); NEXT; } case 0x4a: { int c = adc_hl_SS(); NEXT; } case 0x5a: { int c = adc_hl_SS(); NEXT; } case 0x6a: { int c = adc_hl_hl (); NEXT; } case 0x7a: { int c = adc_hl_SS(); NEXT; } case 0x43: { int c = ld_xword_SS_ED(); NEXT; } case 0x53: { int c = ld_xword_SS_ED(); NEXT; } case 0x63: { int c = ld_xword_SS_ED(); NEXT; } case 0x73: { int c = ld_xword_SS_ED(); NEXT; } case 0x4b: { int c = ld_SS_xword_ED(); NEXT; } case 0x5b: { int c = ld_SS_xword_ED(); NEXT; } case 0x6b: { int c = ld_SS_xword_ED(); NEXT; } case 0x7b: { int c = ld_SS_xword_ED(); NEXT; } case 0x47: { int c = ld_i_a(); NEXT; } case 0x4f: { int c = ld_r_a(); NEXT; } case 0x57: { int c = ld_a_IR(); if (T::isR800()) { NEXT; } else { NEXT_STOP; }} case 0x5f: { int c = ld_a_IR(); if (T::isR800()) { NEXT; } else { NEXT_STOP; }} case 0x67: { int c = rrd(); NEXT; } case 0x6f: { int c = rld(); NEXT; } case 0x45: case 0x4d: case 0x55: case 0x5d: case 0x65: case 0x6d: case 0x75: case 0x7d: { int c = retn(); NEXT_STOP; } case 0x46: case 0x4e: case 0x66: case 0x6e: { int c = im_N<0>(); NEXT; } case 0x56: case 0x76: { int c = im_N<1>(); NEXT; } case 0x5e: case 0x7e: { int c = im_N<2>(); NEXT; } case 0x44: case 0x4c: case 0x54: case 0x5c: case 0x64: case 0x6c: case 0x74: case 0x7c: { int c = neg(); NEXT; } case 0xa0: { int c = ldi(); NEXT; } case 0xa1: { int c = cpi(); NEXT; } case 0xa2: { int c = ini(); NEXT; } case 0xa3: { int c = outi(); NEXT; } case 0xa8: { int c = ldd(); NEXT; } case 0xa9: { int c = cpd(); NEXT; } case 0xaa: { int c = ind(); NEXT; } case 0xab: { int c = outd(); NEXT; } case 0xb0: { int c = ldir(); NEXT; } case 0xb1: { int c = cpir(); NEXT; } case 0xb2: { int c = inir(); NEXT; } case 0xb3: { int c = otir(); NEXT; } case 0xb8: { int c = lddr(); NEXT; } case 0xb9: { int c = cpdr(); NEXT; } case 0xba: { int c = indr(); NEXT; } case 0xbb: { int c = otdr(); NEXT; } case 0xc1: { int c = T::isR800() ? mulub_a_R() : nop(); NEXT; } case 0xc9: { int c = T::isR800() ? mulub_a_R() : nop(); NEXT; } case 0xd1: { int c = T::isR800() ? mulub_a_R() : nop(); NEXT; } case 0xd9: { int c = T::isR800() ? mulub_a_R() : nop(); NEXT; } case 0xc3: { int c = T::isR800() ? muluw_hl_SS() : nop(); NEXT; } case 0xf3: { int c = T::isR800() ? muluw_hl_SS() : nop(); NEXT; } default: UNREACHABLE; return; } } opDD_2: CASE(DD) { byte opcodeDD = RDMEM_OPCODE(T::CC_DD + T::CC_MAIN); incR(1); switch (opcodeDD) { case 0x00: // nop(); case 0x01: // ld_bc_word(); case 0x02: // ld_xbc_a(); case 0x03: // inc_bc(); case 0x04: // inc_b(); case 0x05: // dec_b(); case 0x06: // ld_b_byte(); case 0x07: // rlca(); case 0x08: // ex_af_af(); case 0x0a: // ld_a_xbc(); case 0x0b: // dec_bc(); case 0x0c: // inc_c(); case 0x0d: // dec_c(); case 0x0e: // ld_c_byte(); case 0x0f: // rrca(); case 0x10: // djnz(); case 0x11: // ld_de_word(); case 0x12: // ld_xde_a(); case 0x13: // inc_de(); case 0x14: // inc_d(); case 0x15: // dec_d(); case 0x16: // ld_d_byte(); case 0x17: // rla(); case 0x18: // jr(); case 0x1a: // ld_a_xde(); case 0x1b: // dec_de(); case 0x1c: // inc_e(); case 0x1d: // dec_e(); case 0x1e: // ld_e_byte(); case 0x1f: // rra(); case 0x20: // jr_nz(); case 0x27: // daa(); case 0x28: // jr_z(); case 0x2f: // cpl(); case 0x30: // jr_nc(); case 0x31: // ld_sp_word(); case 0x32: // ld_xbyte_a(); case 0x33: // inc_sp(); case 0x37: // scf(); case 0x38: // jr_c(); case 0x3a: // ld_a_xbyte(); case 0x3b: // dec_sp(); case 0x3c: // inc_a(); case 0x3d: // dec_a(); case 0x3e: // ld_a_byte(); case 0x3f: // ccf(); case 0x40: // ld_b_b(); case 0x41: // ld_b_c(); case 0x42: // ld_b_d(); case 0x43: // ld_b_e(); case 0x47: // ld_b_a(); case 0x48: // ld_c_b(); case 0x49: // ld_c_c(); case 0x4a: // ld_c_d(); case 0x4b: // ld_c_e(); case 0x4f: // ld_c_a(); case 0x50: // ld_d_b(); case 0x51: // ld_d_c(); case 0x52: // ld_d_d(); case 0x53: // ld_d_e(); case 0x57: // ld_d_a(); case 0x58: // ld_e_b(); case 0x59: // ld_e_c(); case 0x5a: // ld_e_d(); case 0x5b: // ld_e_e(); case 0x5f: // ld_e_a(); case 0x64: // ld_ixh_ixh(); == nop case 0x6d: // ld_ixl_ixl(); == nop case 0x76: // halt(); case 0x78: // ld_a_b(); case 0x79: // ld_a_c(); case 0x7a: // ld_a_d(); case 0x7b: // ld_a_e(); case 0x7f: // ld_a_a(); case 0x80: // add_a_b(); case 0x81: // add_a_c(); case 0x82: // add_a_d(); case 0x83: // add_a_e(); case 0x87: // add_a_a(); case 0x88: // adc_a_b(); case 0x89: // adc_a_c(); case 0x8a: // adc_a_d(); case 0x8b: // adc_a_e(); case 0x8f: // adc_a_a(); case 0x90: // sub_b(); case 0x91: // sub_c(); case 0x92: // sub_d(); case 0x93: // sub_e(); case 0x97: // sub_a(); case 0x98: // sbc_a_b(); case 0x99: // sbc_a_c(); case 0x9a: // sbc_a_d(); case 0x9b: // sbc_a_e(); case 0x9f: // sbc_a_a(); case 0xa0: // and_b(); case 0xa1: // and_c(); case 0xa2: // and_d(); case 0xa3: // and_e(); case 0xa7: // and_a(); case 0xa8: // xor_b(); case 0xa9: // xor_c(); case 0xaa: // xor_d(); case 0xab: // xor_e(); case 0xaf: // xor_a(); case 0xb0: // or_b(); case 0xb1: // or_c(); case 0xb2: // or_d(); case 0xb3: // or_e(); case 0xb7: // or_a(); case 0xb8: // cp_b(); case 0xb9: // cp_c(); case 0xba: // cp_d(); case 0xbb: // cp_e(); case 0xbf: // cp_a(); case 0xc0: // ret_nz(); case 0xc1: // pop_bc(); case 0xc2: // jp_nz(); case 0xc3: // jp(); case 0xc4: // call_nz(); case 0xc5: // push_bc(); case 0xc6: // add_a_byte(); case 0xc7: // rst_00(); case 0xc8: // ret_z(); case 0xc9: // ret(); case 0xca: // jp_z(); case 0xcc: // call_z(); case 0xcd: // call(); case 0xce: // adc_a_byte(); case 0xcf: // rst_08(); case 0xd0: // ret_nc(); case 0xd1: // pop_de(); case 0xd2: // jp_nc(); case 0xd3: // out_byte_a(); case 0xd4: // call_nc(); case 0xd5: // push_de(); case 0xd6: // sub_byte(); case 0xd7: // rst_10(); case 0xd8: // ret_c(); case 0xd9: // exx(); case 0xda: // jp_c(); case 0xdb: // in_a_byte(); case 0xdc: // call_c(); case 0xde: // sbc_a_byte(); case 0xdf: // rst_18(); case 0xe0: // ret_po(); case 0xe2: // jp_po(); case 0xe4: // call_po(); case 0xe6: // and_byte(); case 0xe7: // rst_20(); case 0xe8: // ret_pe(); case 0xea: // jp_pe(); case 0xeb: // ex_de_hl(); case 0xec: // call_pe(); case 0xed: // ed(); case 0xee: // xor_byte(); case 0xef: // rst_28(); case 0xf0: // ret_p(); case 0xf1: // pop_af(); case 0xf2: // jp_p(); case 0xf3: // di(); case 0xf4: // call_p(); case 0xf5: // push_af(); case 0xf6: // or_byte(); case 0xf7: // rst_30(); case 0xf8: // ret_m(); case 0xfa: // jp_m(); case 0xfb: // ei(); case 0xfc: // call_m(); case 0xfe: // cp_byte(); case 0xff: // rst_38(); if (T::isR800()) { int c = T::CC_DD + nop(); NEXT; } else { T::add(T::CC_DD); #ifdef USE_COMPUTED_GOTO goto *(opcodeTable[opcodeDD]); #else opcodeMain = opcodeDD; goto switchopcode; #endif } case 0x09: { int c = add_SS_TT(); NEXT; } case 0x19: { int c = add_SS_TT(); NEXT; } case 0x29: { int c = add_SS_SS(); NEXT; } case 0x39: { int c = add_SS_TT(); NEXT; } case 0x21: { int c = ld_SS_word(); NEXT; } case 0x22: { int c = ld_xword_SS(); NEXT; } case 0x2a: { int c = ld_SS_xword(); NEXT; } case 0x23: { int c = inc_SS(); NEXT; } case 0x2b: { int c = dec_SS(); NEXT; } case 0x24: { int c = inc_R(); NEXT; } case 0x2c: { int c = inc_R(); NEXT; } case 0x25: { int c = dec_R(); NEXT; } case 0x2d: { int c = dec_R(); NEXT; } case 0x26: { int c = ld_R_byte(); NEXT; } case 0x2e: { int c = ld_R_byte(); NEXT; } case 0x34: { int c = inc_xix(); NEXT; } case 0x35: { int c = dec_xix(); NEXT; } case 0x36: { int c = ld_xix_byte(); NEXT; } case 0x44: { int c = ld_R_R(); NEXT; } case 0x45: { int c = ld_R_R(); NEXT; } case 0x4c: { int c = ld_R_R(); NEXT; } case 0x4d: { int c = ld_R_R(); NEXT; } case 0x54: { int c = ld_R_R(); NEXT; } case 0x55: { int c = ld_R_R(); NEXT; } case 0x5c: { int c = ld_R_R(); NEXT; } case 0x5d: { int c = ld_R_R(); NEXT; } case 0x7c: { int c = ld_R_R(); NEXT; } case 0x7d: { int c = ld_R_R(); NEXT; } case 0x60: { int c = ld_R_R(); NEXT; } case 0x61: { int c = ld_R_R(); NEXT; } case 0x62: { int c = ld_R_R(); NEXT; } case 0x63: { int c = ld_R_R(); NEXT; } case 0x65: { int c = ld_R_R(); NEXT; } case 0x67: { int c = ld_R_R(); NEXT; } case 0x68: { int c = ld_R_R(); NEXT; } case 0x69: { int c = ld_R_R(); NEXT; } case 0x6a: { int c = ld_R_R(); NEXT; } case 0x6b: { int c = ld_R_R(); NEXT; } case 0x6c: { int c = ld_R_R(); NEXT; } case 0x6f: { int c = ld_R_R(); NEXT; } case 0x70: { int c = ld_xix_R(); NEXT; } case 0x71: { int c = ld_xix_R(); NEXT; } case 0x72: { int c = ld_xix_R(); NEXT; } case 0x73: { int c = ld_xix_R(); NEXT; } case 0x74: { int c = ld_xix_R(); NEXT; } case 0x75: { int c = ld_xix_R(); NEXT; } case 0x77: { int c = ld_xix_R(); NEXT; } case 0x46: { int c = ld_R_xix(); NEXT; } case 0x4e: { int c = ld_R_xix(); NEXT; } case 0x56: { int c = ld_R_xix(); NEXT; } case 0x5e: { int c = ld_R_xix(); NEXT; } case 0x66: { int c = ld_R_xix(); NEXT; } case 0x6e: { int c = ld_R_xix(); NEXT; } case 0x7e: { int c = ld_R_xix(); NEXT; } case 0x84: { int c = add_a_R(); NEXT; } case 0x85: { int c = add_a_R(); NEXT; } case 0x86: { int c = add_a_xix(); NEXT; } case 0x8c: { int c = adc_a_R(); NEXT; } case 0x8d: { int c = adc_a_R(); NEXT; } case 0x8e: { int c = adc_a_xix(); NEXT; } case 0x94: { int c = sub_R(); NEXT; } case 0x95: { int c = sub_R(); NEXT; } case 0x96: { int c = sub_xix(); NEXT; } case 0x9c: { int c = sbc_a_R(); NEXT; } case 0x9d: { int c = sbc_a_R(); NEXT; } case 0x9e: { int c = sbc_a_xix(); NEXT; } case 0xa4: { int c = and_R(); NEXT; } case 0xa5: { int c = and_R(); NEXT; } case 0xa6: { int c = and_xix(); NEXT; } case 0xac: { int c = xor_R(); NEXT; } case 0xad: { int c = xor_R(); NEXT; } case 0xae: { int c = xor_xix(); NEXT; } case 0xb4: { int c = or_R(); NEXT; } case 0xb5: { int c = or_R(); NEXT; } case 0xb6: { int c = or_xix(); NEXT; } case 0xbc: { int c = cp_R(); NEXT; } case 0xbd: { int c = cp_R(); NEXT; } case 0xbe: { int c = cp_xix(); NEXT; } case 0xe1: { int c = pop_SS (); NEXT; } case 0xe5: { int c = push_SS(); NEXT; } case 0xe3: { int c = ex_xsp_SS(); NEXT; } case 0xe9: { int c = jp_SS(); NEXT; } case 0xf9: { int c = ld_sp_SS(); NEXT; } case 0xcb: ixy = getIX(); goto xx_cb; case 0xdd: T::add(T::CC_DD); goto opDD_2; case 0xfd: T::add(T::CC_DD); goto opFD_2; default: UNREACHABLE; return; } } opFD_2: CASE(FD) { byte opcodeFD = RDMEM_OPCODE(T::CC_DD + T::CC_MAIN); incR(1); switch (opcodeFD) { case 0x00: // nop(); case 0x01: // ld_bc_word(); case 0x02: // ld_xbc_a(); case 0x03: // inc_bc(); case 0x04: // inc_b(); case 0x05: // dec_b(); case 0x06: // ld_b_byte(); case 0x07: // rlca(); case 0x08: // ex_af_af(); case 0x0a: // ld_a_xbc(); case 0x0b: // dec_bc(); case 0x0c: // inc_c(); case 0x0d: // dec_c(); case 0x0e: // ld_c_byte(); case 0x0f: // rrca(); case 0x10: // djnz(); case 0x11: // ld_de_word(); case 0x12: // ld_xde_a(); case 0x13: // inc_de(); case 0x14: // inc_d(); case 0x15: // dec_d(); case 0x16: // ld_d_byte(); case 0x17: // rla(); case 0x18: // jr(); case 0x1a: // ld_a_xde(); case 0x1b: // dec_de(); case 0x1c: // inc_e(); case 0x1d: // dec_e(); case 0x1e: // ld_e_byte(); case 0x1f: // rra(); case 0x20: // jr_nz(); case 0x27: // daa(); case 0x28: // jr_z(); case 0x2f: // cpl(); case 0x30: // jr_nc(); case 0x31: // ld_sp_word(); case 0x32: // ld_xbyte_a(); case 0x33: // inc_sp(); case 0x37: // scf(); case 0x38: // jr_c(); case 0x3a: // ld_a_xbyte(); case 0x3b: // dec_sp(); case 0x3c: // inc_a(); case 0x3d: // dec_a(); case 0x3e: // ld_a_byte(); case 0x3f: // ccf(); case 0x40: // ld_b_b(); case 0x41: // ld_b_c(); case 0x42: // ld_b_d(); case 0x43: // ld_b_e(); case 0x47: // ld_b_a(); case 0x48: // ld_c_b(); case 0x49: // ld_c_c(); case 0x4a: // ld_c_d(); case 0x4b: // ld_c_e(); case 0x4f: // ld_c_a(); case 0x50: // ld_d_b(); case 0x51: // ld_d_c(); case 0x52: // ld_d_d(); case 0x53: // ld_d_e(); case 0x57: // ld_d_a(); case 0x58: // ld_e_b(); case 0x59: // ld_e_c(); case 0x5a: // ld_e_d(); case 0x5b: // ld_e_e(); case 0x5f: // ld_e_a(); case 0x64: // ld_ixh_ixh(); == nop case 0x6d: // ld_ixl_ixl(); == nop case 0x76: // halt(); case 0x78: // ld_a_b(); case 0x79: // ld_a_c(); case 0x7a: // ld_a_d(); case 0x7b: // ld_a_e(); case 0x7f: // ld_a_a(); case 0x80: // add_a_b(); case 0x81: // add_a_c(); case 0x82: // add_a_d(); case 0x83: // add_a_e(); case 0x87: // add_a_a(); case 0x88: // adc_a_b(); case 0x89: // adc_a_c(); case 0x8a: // adc_a_d(); case 0x8b: // adc_a_e(); case 0x8f: // adc_a_a(); case 0x90: // sub_b(); case 0x91: // sub_c(); case 0x92: // sub_d(); case 0x93: // sub_e(); case 0x97: // sub_a(); case 0x98: // sbc_a_b(); case 0x99: // sbc_a_c(); case 0x9a: // sbc_a_d(); case 0x9b: // sbc_a_e(); case 0x9f: // sbc_a_a(); case 0xa0: // and_b(); case 0xa1: // and_c(); case 0xa2: // and_d(); case 0xa3: // and_e(); case 0xa7: // and_a(); case 0xa8: // xor_b(); case 0xa9: // xor_c(); case 0xaa: // xor_d(); case 0xab: // xor_e(); case 0xaf: // xor_a(); case 0xb0: // or_b(); case 0xb1: // or_c(); case 0xb2: // or_d(); case 0xb3: // or_e(); case 0xb7: // or_a(); case 0xb8: // cp_b(); case 0xb9: // cp_c(); case 0xba: // cp_d(); case 0xbb: // cp_e(); case 0xbf: // cp_a(); case 0xc0: // ret_nz(); case 0xc1: // pop_bc(); case 0xc2: // jp_nz(); case 0xc3: // jp(); case 0xc4: // call_nz(); case 0xc5: // push_bc(); case 0xc6: // add_a_byte(); case 0xc7: // rst_00(); case 0xc8: // ret_z(); case 0xc9: // ret(); case 0xca: // jp_z(); case 0xcc: // call_z(); case 0xcd: // call(); case 0xce: // adc_a_byte(); case 0xcf: // rst_08(); case 0xd0: // ret_nc(); case 0xd1: // pop_de(); case 0xd2: // jp_nc(); case 0xd3: // out_byte_a(); case 0xd4: // call_nc(); case 0xd5: // push_de(); case 0xd6: // sub_byte(); case 0xd7: // rst_10(); case 0xd8: // ret_c(); case 0xd9: // exx(); case 0xda: // jp_c(); case 0xdb: // in_a_byte(); case 0xdc: // call_c(); case 0xde: // sbc_a_byte(); case 0xdf: // rst_18(); case 0xe0: // ret_po(); case 0xe2: // jp_po(); case 0xe4: // call_po(); case 0xe6: // and_byte(); case 0xe7: // rst_20(); case 0xe8: // ret_pe(); case 0xea: // jp_pe(); case 0xeb: // ex_de_hl(); case 0xec: // call_pe(); case 0xed: // ed(); case 0xee: // xor_byte(); case 0xef: // rst_28(); case 0xf0: // ret_p(); case 0xf1: // pop_af(); case 0xf2: // jp_p(); case 0xf3: // di(); case 0xf4: // call_p(); case 0xf5: // push_af(); case 0xf6: // or_byte(); case 0xf7: // rst_30(); case 0xf8: // ret_m(); case 0xfa: // jp_m(); case 0xfb: // ei(); case 0xfc: // call_m(); case 0xfe: // cp_byte(); case 0xff: // rst_38(); if (T::isR800()) { int c = T::CC_DD + nop(); NEXT; } else { T::add(T::CC_DD); #ifdef USE_COMPUTED_GOTO goto *(opcodeTable[opcodeFD]); #else opcodeMain = opcodeFD; goto switchopcode; #endif } case 0x09: { int c = add_SS_TT(); NEXT; } case 0x19: { int c = add_SS_TT(); NEXT; } case 0x29: { int c = add_SS_SS(); NEXT; } case 0x39: { int c = add_SS_TT(); NEXT; } case 0x21: { int c = ld_SS_word(); NEXT; } case 0x22: { int c = ld_xword_SS(); NEXT; } case 0x2a: { int c = ld_SS_xword(); NEXT; } case 0x23: { int c = inc_SS(); NEXT; } case 0x2b: { int c = dec_SS(); NEXT; } case 0x24: { int c = inc_R(); NEXT; } case 0x2c: { int c = inc_R(); NEXT; } case 0x25: { int c = dec_R(); NEXT; } case 0x2d: { int c = dec_R(); NEXT; } case 0x26: { int c = ld_R_byte(); NEXT; } case 0x2e: { int c = ld_R_byte(); NEXT; } case 0x34: { int c = inc_xix(); NEXT; } case 0x35: { int c = dec_xix(); NEXT; } case 0x36: { int c = ld_xix_byte(); NEXT; } case 0x44: { int c = ld_R_R(); NEXT; } case 0x45: { int c = ld_R_R(); NEXT; } case 0x4c: { int c = ld_R_R(); NEXT; } case 0x4d: { int c = ld_R_R(); NEXT; } case 0x54: { int c = ld_R_R(); NEXT; } case 0x55: { int c = ld_R_R(); NEXT; } case 0x5c: { int c = ld_R_R(); NEXT; } case 0x5d: { int c = ld_R_R(); NEXT; } case 0x7c: { int c = ld_R_R(); NEXT; } case 0x7d: { int c = ld_R_R(); NEXT; } case 0x60: { int c = ld_R_R(); NEXT; } case 0x61: { int c = ld_R_R(); NEXT; } case 0x62: { int c = ld_R_R(); NEXT; } case 0x63: { int c = ld_R_R(); NEXT; } case 0x65: { int c = ld_R_R(); NEXT; } case 0x67: { int c = ld_R_R(); NEXT; } case 0x68: { int c = ld_R_R(); NEXT; } case 0x69: { int c = ld_R_R(); NEXT; } case 0x6a: { int c = ld_R_R(); NEXT; } case 0x6b: { int c = ld_R_R(); NEXT; } case 0x6c: { int c = ld_R_R(); NEXT; } case 0x6f: { int c = ld_R_R(); NEXT; } case 0x70: { int c = ld_xix_R(); NEXT; } case 0x71: { int c = ld_xix_R(); NEXT; } case 0x72: { int c = ld_xix_R(); NEXT; } case 0x73: { int c = ld_xix_R(); NEXT; } case 0x74: { int c = ld_xix_R(); NEXT; } case 0x75: { int c = ld_xix_R(); NEXT; } case 0x77: { int c = ld_xix_R(); NEXT; } case 0x46: { int c = ld_R_xix(); NEXT; } case 0x4e: { int c = ld_R_xix(); NEXT; } case 0x56: { int c = ld_R_xix(); NEXT; } case 0x5e: { int c = ld_R_xix(); NEXT; } case 0x66: { int c = ld_R_xix(); NEXT; } case 0x6e: { int c = ld_R_xix(); NEXT; } case 0x7e: { int c = ld_R_xix(); NEXT; } case 0x84: { int c = add_a_R(); NEXT; } case 0x85: { int c = add_a_R(); NEXT; } case 0x86: { int c = add_a_xix(); NEXT; } case 0x8c: { int c = adc_a_R(); NEXT; } case 0x8d: { int c = adc_a_R(); NEXT; } case 0x8e: { int c = adc_a_xix(); NEXT; } case 0x94: { int c = sub_R(); NEXT; } case 0x95: { int c = sub_R(); NEXT; } case 0x96: { int c = sub_xix(); NEXT; } case 0x9c: { int c = sbc_a_R(); NEXT; } case 0x9d: { int c = sbc_a_R(); NEXT; } case 0x9e: { int c = sbc_a_xix(); NEXT; } case 0xa4: { int c = and_R(); NEXT; } case 0xa5: { int c = and_R(); NEXT; } case 0xa6: { int c = and_xix(); NEXT; } case 0xac: { int c = xor_R(); NEXT; } case 0xad: { int c = xor_R(); NEXT; } case 0xae: { int c = xor_xix(); NEXT; } case 0xb4: { int c = or_R(); NEXT; } case 0xb5: { int c = or_R(); NEXT; } case 0xb6: { int c = or_xix(); NEXT; } case 0xbc: { int c = cp_R(); NEXT; } case 0xbd: { int c = cp_R(); NEXT; } case 0xbe: { int c = cp_xix(); NEXT; } case 0xe1: { int c = pop_SS (); NEXT; } case 0xe5: { int c = push_SS(); NEXT; } case 0xe3: { int c = ex_xsp_SS(); NEXT; } case 0xe9: { int c = jp_SS(); NEXT; } case 0xf9: { int c = ld_sp_SS(); NEXT; } case 0xcb: ixy = getIY(); goto xx_cb; case 0xdd: T::add(T::CC_DD); goto opDD_2; case 0xfd: T::add(T::CC_DD); goto opFD_2; default: UNREACHABLE; return; } } #ifndef USE_COMPUTED_GOTO default: UNREACHABLE; return; } #endif xx_cb: { unsigned tmp = RD_WORD_PC(T::CC_DD + T::CC_DD_CB); int8_t ofst = tmp & 0xFF; unsigned addr = (ixy + ofst) & 0xFFFF; byte xxcb_opcode = tmp >> 8; switch (xxcb_opcode) { case 0x00: { int c = rlc_xix_R(addr); NEXT; } case 0x01: { int c = rlc_xix_R(addr); NEXT; } case 0x02: { int c = rlc_xix_R(addr); NEXT; } case 0x03: { int c = rlc_xix_R(addr); NEXT; } case 0x04: { int c = rlc_xix_R(addr); NEXT; } case 0x05: { int c = rlc_xix_R(addr); NEXT; } case 0x06: { int c = rlc_xix_R(addr); NEXT; } case 0x07: { int c = rlc_xix_R(addr); NEXT; } case 0x08: { int c = rrc_xix_R(addr); NEXT; } case 0x09: { int c = rrc_xix_R(addr); NEXT; } case 0x0a: { int c = rrc_xix_R(addr); NEXT; } case 0x0b: { int c = rrc_xix_R(addr); NEXT; } case 0x0c: { int c = rrc_xix_R(addr); NEXT; } case 0x0d: { int c = rrc_xix_R(addr); NEXT; } case 0x0e: { int c = rrc_xix_R(addr); NEXT; } case 0x0f: { int c = rrc_xix_R(addr); NEXT; } case 0x10: { int c = rl_xix_R(addr); NEXT; } case 0x11: { int c = rl_xix_R(addr); NEXT; } case 0x12: { int c = rl_xix_R(addr); NEXT; } case 0x13: { int c = rl_xix_R(addr); NEXT; } case 0x14: { int c = rl_xix_R(addr); NEXT; } case 0x15: { int c = rl_xix_R(addr); NEXT; } case 0x16: { int c = rl_xix_R(addr); NEXT; } case 0x17: { int c = rl_xix_R(addr); NEXT; } case 0x18: { int c = rr_xix_R(addr); NEXT; } case 0x19: { int c = rr_xix_R(addr); NEXT; } case 0x1a: { int c = rr_xix_R(addr); NEXT; } case 0x1b: { int c = rr_xix_R(addr); NEXT; } case 0x1c: { int c = rr_xix_R(addr); NEXT; } case 0x1d: { int c = rr_xix_R(addr); NEXT; } case 0x1e: { int c = rr_xix_R(addr); NEXT; } case 0x1f: { int c = rr_xix_R(addr); NEXT; } case 0x20: { int c = sla_xix_R(addr); NEXT; } case 0x21: { int c = sla_xix_R(addr); NEXT; } case 0x22: { int c = sla_xix_R(addr); NEXT; } case 0x23: { int c = sla_xix_R(addr); NEXT; } case 0x24: { int c = sla_xix_R(addr); NEXT; } case 0x25: { int c = sla_xix_R(addr); NEXT; } case 0x26: { int c = sla_xix_R(addr); NEXT; } case 0x27: { int c = sla_xix_R(addr); NEXT; } case 0x28: { int c = sra_xix_R(addr); NEXT; } case 0x29: { int c = sra_xix_R(addr); NEXT; } case 0x2a: { int c = sra_xix_R(addr); NEXT; } case 0x2b: { int c = sra_xix_R(addr); NEXT; } case 0x2c: { int c = sra_xix_R(addr); NEXT; } case 0x2d: { int c = sra_xix_R(addr); NEXT; } case 0x2e: { int c = sra_xix_R(addr); NEXT; } case 0x2f: { int c = sra_xix_R(addr); NEXT; } case 0x30: { int c = T::isR800() ? sll2() : sll_xix_R(addr); NEXT; } case 0x31: { int c = T::isR800() ? sll2() : sll_xix_R(addr); NEXT; } case 0x32: { int c = T::isR800() ? sll2() : sll_xix_R(addr); NEXT; } case 0x33: { int c = T::isR800() ? sll2() : sll_xix_R(addr); NEXT; } case 0x34: { int c = T::isR800() ? sll2() : sll_xix_R(addr); NEXT; } case 0x35: { int c = T::isR800() ? sll2() : sll_xix_R(addr); NEXT; } case 0x36: { int c = T::isR800() ? sll2() : sll_xix_R(addr); NEXT; } case 0x37: { int c = T::isR800() ? sll2() : sll_xix_R(addr); NEXT; } case 0x38: { int c = srl_xix_R(addr); NEXT; } case 0x39: { int c = srl_xix_R(addr); NEXT; } case 0x3a: { int c = srl_xix_R(addr); NEXT; } case 0x3b: { int c = srl_xix_R(addr); NEXT; } case 0x3c: { int c = srl_xix_R(addr); NEXT; } case 0x3d: { int c = srl_xix_R(addr); NEXT; } case 0x3e: { int c = srl_xix_R(addr); NEXT; } case 0x3f: { int c = srl_xix_R(addr); NEXT; } case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47: { int c = bit_N_xix<0>(addr); NEXT; } case 0x48: case 0x49: case 0x4a: case 0x4b: case 0x4c: case 0x4d: case 0x4e: case 0x4f: { int c = bit_N_xix<1>(addr); NEXT; } case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57: { int c = bit_N_xix<2>(addr); NEXT; } case 0x58: case 0x59: case 0x5a: case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f: { int c = bit_N_xix<3>(addr); NEXT; } case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: { int c = bit_N_xix<4>(addr); NEXT; } case 0x68: case 0x69: case 0x6a: case 0x6b: case 0x6c: case 0x6d: case 0x6e: case 0x6f: { int c = bit_N_xix<5>(addr); NEXT; } case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: { int c = bit_N_xix<6>(addr); NEXT; } case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f: { int c = bit_N_xix<7>(addr); NEXT; } case 0x80: { int c = res_N_xix_R<0,B>(addr); NEXT; } case 0x81: { int c = res_N_xix_R<0,C>(addr); NEXT; } case 0x82: { int c = res_N_xix_R<0,D>(addr); NEXT; } case 0x83: { int c = res_N_xix_R<0,E>(addr); NEXT; } case 0x84: { int c = res_N_xix_R<0,H>(addr); NEXT; } case 0x85: { int c = res_N_xix_R<0,L>(addr); NEXT; } case 0x87: { int c = res_N_xix_R<0,A>(addr); NEXT; } case 0x88: { int c = res_N_xix_R<1,B>(addr); NEXT; } case 0x89: { int c = res_N_xix_R<1,C>(addr); NEXT; } case 0x8a: { int c = res_N_xix_R<1,D>(addr); NEXT; } case 0x8b: { int c = res_N_xix_R<1,E>(addr); NEXT; } case 0x8c: { int c = res_N_xix_R<1,H>(addr); NEXT; } case 0x8d: { int c = res_N_xix_R<1,L>(addr); NEXT; } case 0x8f: { int c = res_N_xix_R<1,A>(addr); NEXT; } case 0x90: { int c = res_N_xix_R<2,B>(addr); NEXT; } case 0x91: { int c = res_N_xix_R<2,C>(addr); NEXT; } case 0x92: { int c = res_N_xix_R<2,D>(addr); NEXT; } case 0x93: { int c = res_N_xix_R<2,E>(addr); NEXT; } case 0x94: { int c = res_N_xix_R<2,H>(addr); NEXT; } case 0x95: { int c = res_N_xix_R<2,L>(addr); NEXT; } case 0x97: { int c = res_N_xix_R<2,A>(addr); NEXT; } case 0x98: { int c = res_N_xix_R<3,B>(addr); NEXT; } case 0x99: { int c = res_N_xix_R<3,C>(addr); NEXT; } case 0x9a: { int c = res_N_xix_R<3,D>(addr); NEXT; } case 0x9b: { int c = res_N_xix_R<3,E>(addr); NEXT; } case 0x9c: { int c = res_N_xix_R<3,H>(addr); NEXT; } case 0x9d: { int c = res_N_xix_R<3,L>(addr); NEXT; } case 0x9f: { int c = res_N_xix_R<3,A>(addr); NEXT; } case 0xa0: { int c = res_N_xix_R<4,B>(addr); NEXT; } case 0xa1: { int c = res_N_xix_R<4,C>(addr); NEXT; } case 0xa2: { int c = res_N_xix_R<4,D>(addr); NEXT; } case 0xa3: { int c = res_N_xix_R<4,E>(addr); NEXT; } case 0xa4: { int c = res_N_xix_R<4,H>(addr); NEXT; } case 0xa5: { int c = res_N_xix_R<4,L>(addr); NEXT; } case 0xa7: { int c = res_N_xix_R<4,A>(addr); NEXT; } case 0xa8: { int c = res_N_xix_R<5,B>(addr); NEXT; } case 0xa9: { int c = res_N_xix_R<5,C>(addr); NEXT; } case 0xaa: { int c = res_N_xix_R<5,D>(addr); NEXT; } case 0xab: { int c = res_N_xix_R<5,E>(addr); NEXT; } case 0xac: { int c = res_N_xix_R<5,H>(addr); NEXT; } case 0xad: { int c = res_N_xix_R<5,L>(addr); NEXT; } case 0xaf: { int c = res_N_xix_R<5,A>(addr); NEXT; } case 0xb0: { int c = res_N_xix_R<6,B>(addr); NEXT; } case 0xb1: { int c = res_N_xix_R<6,C>(addr); NEXT; } case 0xb2: { int c = res_N_xix_R<6,D>(addr); NEXT; } case 0xb3: { int c = res_N_xix_R<6,E>(addr); NEXT; } case 0xb4: { int c = res_N_xix_R<6,H>(addr); NEXT; } case 0xb5: { int c = res_N_xix_R<6,L>(addr); NEXT; } case 0xb7: { int c = res_N_xix_R<6,A>(addr); NEXT; } case 0xb8: { int c = res_N_xix_R<7,B>(addr); NEXT; } case 0xb9: { int c = res_N_xix_R<7,C>(addr); NEXT; } case 0xba: { int c = res_N_xix_R<7,D>(addr); NEXT; } case 0xbb: { int c = res_N_xix_R<7,E>(addr); NEXT; } case 0xbc: { int c = res_N_xix_R<7,H>(addr); NEXT; } case 0xbd: { int c = res_N_xix_R<7,L>(addr); NEXT; } case 0xbf: { int c = res_N_xix_R<7,A>(addr); NEXT; } case 0x86: { int c = res_N_xix_R<0,DUMMY>(addr); NEXT; } case 0x8e: { int c = res_N_xix_R<1,DUMMY>(addr); NEXT; } case 0x96: { int c = res_N_xix_R<2,DUMMY>(addr); NEXT; } case 0x9e: { int c = res_N_xix_R<3,DUMMY>(addr); NEXT; } case 0xa6: { int c = res_N_xix_R<4,DUMMY>(addr); NEXT; } case 0xae: { int c = res_N_xix_R<5,DUMMY>(addr); NEXT; } case 0xb6: { int c = res_N_xix_R<6,DUMMY>(addr); NEXT; } case 0xbe: { int c = res_N_xix_R<7,DUMMY>(addr); NEXT; } case 0xc0: { int c = set_N_xix_R<0,B>(addr); NEXT; } case 0xc1: { int c = set_N_xix_R<0,C>(addr); NEXT; } case 0xc2: { int c = set_N_xix_R<0,D>(addr); NEXT; } case 0xc3: { int c = set_N_xix_R<0,E>(addr); NEXT; } case 0xc4: { int c = set_N_xix_R<0,H>(addr); NEXT; } case 0xc5: { int c = set_N_xix_R<0,L>(addr); NEXT; } case 0xc7: { int c = set_N_xix_R<0,A>(addr); NEXT; } case 0xc8: { int c = set_N_xix_R<1,B>(addr); NEXT; } case 0xc9: { int c = set_N_xix_R<1,C>(addr); NEXT; } case 0xca: { int c = set_N_xix_R<1,D>(addr); NEXT; } case 0xcb: { int c = set_N_xix_R<1,E>(addr); NEXT; } case 0xcc: { int c = set_N_xix_R<1,H>(addr); NEXT; } case 0xcd: { int c = set_N_xix_R<1,L>(addr); NEXT; } case 0xcf: { int c = set_N_xix_R<1,A>(addr); NEXT; } case 0xd0: { int c = set_N_xix_R<2,B>(addr); NEXT; } case 0xd1: { int c = set_N_xix_R<2,C>(addr); NEXT; } case 0xd2: { int c = set_N_xix_R<2,D>(addr); NEXT; } case 0xd3: { int c = set_N_xix_R<2,E>(addr); NEXT; } case 0xd4: { int c = set_N_xix_R<2,H>(addr); NEXT; } case 0xd5: { int c = set_N_xix_R<2,L>(addr); NEXT; } case 0xd7: { int c = set_N_xix_R<2,A>(addr); NEXT; } case 0xd8: { int c = set_N_xix_R<3,B>(addr); NEXT; } case 0xd9: { int c = set_N_xix_R<3,C>(addr); NEXT; } case 0xda: { int c = set_N_xix_R<3,D>(addr); NEXT; } case 0xdb: { int c = set_N_xix_R<3,E>(addr); NEXT; } case 0xdc: { int c = set_N_xix_R<3,H>(addr); NEXT; } case 0xdd: { int c = set_N_xix_R<3,L>(addr); NEXT; } case 0xdf: { int c = set_N_xix_R<3,A>(addr); NEXT; } case 0xe0: { int c = set_N_xix_R<4,B>(addr); NEXT; } case 0xe1: { int c = set_N_xix_R<4,C>(addr); NEXT; } case 0xe2: { int c = set_N_xix_R<4,D>(addr); NEXT; } case 0xe3: { int c = set_N_xix_R<4,E>(addr); NEXT; } case 0xe4: { int c = set_N_xix_R<4,H>(addr); NEXT; } case 0xe5: { int c = set_N_xix_R<4,L>(addr); NEXT; } case 0xe7: { int c = set_N_xix_R<4,A>(addr); NEXT; } case 0xe8: { int c = set_N_xix_R<5,B>(addr); NEXT; } case 0xe9: { int c = set_N_xix_R<5,C>(addr); NEXT; } case 0xea: { int c = set_N_xix_R<5,D>(addr); NEXT; } case 0xeb: { int c = set_N_xix_R<5,E>(addr); NEXT; } case 0xec: { int c = set_N_xix_R<5,H>(addr); NEXT; } case 0xed: { int c = set_N_xix_R<5,L>(addr); NEXT; } case 0xef: { int c = set_N_xix_R<5,A>(addr); NEXT; } case 0xf0: { int c = set_N_xix_R<6,B>(addr); NEXT; } case 0xf1: { int c = set_N_xix_R<6,C>(addr); NEXT; } case 0xf2: { int c = set_N_xix_R<6,D>(addr); NEXT; } case 0xf3: { int c = set_N_xix_R<6,E>(addr); NEXT; } case 0xf4: { int c = set_N_xix_R<6,H>(addr); NEXT; } case 0xf5: { int c = set_N_xix_R<6,L>(addr); NEXT; } case 0xf7: { int c = set_N_xix_R<6,A>(addr); NEXT; } case 0xf8: { int c = set_N_xix_R<7,B>(addr); NEXT; } case 0xf9: { int c = set_N_xix_R<7,C>(addr); NEXT; } case 0xfa: { int c = set_N_xix_R<7,D>(addr); NEXT; } case 0xfb: { int c = set_N_xix_R<7,E>(addr); NEXT; } case 0xfc: { int c = set_N_xix_R<7,H>(addr); NEXT; } case 0xfd: { int c = set_N_xix_R<7,L>(addr); NEXT; } case 0xff: { int c = set_N_xix_R<7,A>(addr); NEXT; } case 0xc6: { int c = set_N_xix_R<0,DUMMY>(addr); NEXT; } case 0xce: { int c = set_N_xix_R<1,DUMMY>(addr); NEXT; } case 0xd6: { int c = set_N_xix_R<2,DUMMY>(addr); NEXT; } case 0xde: { int c = set_N_xix_R<3,DUMMY>(addr); NEXT; } case 0xe6: { int c = set_N_xix_R<4,DUMMY>(addr); NEXT; } case 0xee: { int c = set_N_xix_R<5,DUMMY>(addr); NEXT; } case 0xf6: { int c = set_N_xix_R<6,DUMMY>(addr); NEXT; } case 0xfe: { int c = set_N_xix_R<7,DUMMY>(addr); NEXT; } default: UNREACHABLE; } } } template inline void CPUCore::cpuTracePre() { start_pc = getPC(); } template inline void CPUCore::cpuTracePost() { if (unlikely(tracingEnabled)) { cpuTracePost_slow(); } } template void CPUCore::cpuTracePost_slow() { byte opbuf[4]; string dasmOutput; dasm(*interface, start_pc, opbuf, dasmOutput, T::getTimeFast()); std::cout << std::setfill('0') << std::hex << std::setw(4) << start_pc << " : " << dasmOutput << " AF=" << std::setw(4) << getAF() << " BC=" << std::setw(4) << getBC() << " DE=" << std::setw(4) << getDE() << " HL=" << std::setw(4) << getHL() << " IX=" << std::setw(4) << getIX() << " IY=" << std::setw(4) << getIY() << " SP=" << std::setw(4) << getSP() << std::endl << std::dec; } template void CPUCore::executeSlow() { if (unlikely(false && nmiEdge)) { // Note: NMIs are disabled, see also raiseNMI() nmiEdge = false; nmi(); // NMI occured } else if (unlikely(IRQStatus && getIFF1() && !getAfterEI())) { // normal interrupt if (unlikely(getAfterLDAI())) { // HACK!!! // The 'ld a,i' or 'ld a,r' instruction copies the IFF2 // bit to the V flag. Though when the Z80 accepts an // IRQ directly after this instruction, the V flag is 0 // (instead of the expected value 1). This can probably // be explained if you look at the pipeline of the Z80. // But for speed reasons we implement it here as a // fix-up (a hack) in the IRQ routine. This behaviour // is actually a bug in the Z80. // Thanks to n_n for reporting this behaviour. I think // this was discovered by GuyveR800. Also thanks to // n_n for writing a test program that demonstrates // this quirk. // I also wrote a test program that demonstrates this // behaviour is the same whether 'ld a,i' is preceded // by a 'ei' instruction or not (so it's not caused by // the 'delayed IRQ acceptance of ei'). assert(getF() & V_FLAG); setF(getF() & ~V_FLAG); } IRQAccept.signal(); switch (getIM()) { case 0: irq0(); break; case 1: irq1(); break; case 2: irq2(); break; default: UNREACHABLE; } } else if (unlikely(getHALT())) { // in halt mode incR(T::advanceHalt(T::haltStates(), scheduler.getNext())); setSlowInstructions(); } else { assert(isSameAfter()); clearNextAfter(); cpuTracePre(); assert(T::limitReached()); // we want only one instruction executeInstructions(); cpuTracePost(); copyNextAfter(); } } template void CPUCore::execute(bool fastForward) { // In fast-forward mode, breakpoints, watchpoints or debug condtions // won't trigger. It is possible we already are in break mode, but // break is ignored in fast-forward mode. assert(fastForward || !interface->isBreaked()); if (fastForward) { interface->setFastForward(true); } execute2(fastForward); interface->setFastForward(false); } template void CPUCore::execute2(bool fastForward) { // note: Don't use getTimeFast() here, because 'once in a while' we // need to CPUClock::sync() to avoid overflow. // Should be done at least once per second (approx). So only // once in this method is enough. scheduler.schedule(T::getTime()); setSlowInstructions(); if (!fastForward && (interface->isContinue() || interface->isStep())) { // at least one instruction interface->setContinue(false); executeSlow(); scheduler.schedule(T::getTimeFast()); --slowInstructions; if (interface->isStep()) { interface->setStep(false); interface->doBreak(); return; } } // Note: we call scheduler _after_ executing the instruction and before // deciding between executeFast() and executeSlow() (because a // SyncPoint could set an IRQ and then we must choose executeSlow()) if (fastForward || (!interface->anyBreakPoints() && !tracingEnabled)) { // fast path, no breakpoints, no tracing while (!needExitCPULoop()) { if (slowInstructions) { --slowInstructions; executeSlow(); scheduler.schedule(T::getTimeFast()); } else { while (slowInstructions == 0) { T::enableLimit(); // does CPUClock::sync() if (likely(!T::limitReached())) { // multiple instructions assert(isSameAfter()); executeInstructions(); assert(isSameAfter()); } scheduler.schedule(T::getTimeFast()); if (needExitCPULoop()) return; } } } } else { while (!needExitCPULoop()) { if (interface->checkBreakPoints(getPC(), motherboard)) { assert(interface->isBreaked()); break; } if (slowInstructions == 0) { cpuTracePre(); assert(T::limitReached()); // only one instruction assert(isSameAfter()); executeInstructions(); assert(isSameAfter()); cpuTracePost(); } else { --slowInstructions; executeSlow(); } // Don't use getTimeFast() here, we need a call to // CPUClock::sync() 'once in a while'. (During a // reverse fast-forward this wasn't always the case). scheduler.schedule(T::getTime()); } } } template template ALWAYS_INLINE byte CPUCore::get8() const { if (R8 == A) { return getA(); } else if (R8 == F) { return getF(); } else if (R8 == B) { return getB(); } else if (R8 == C) { return getC(); } else if (R8 == D) { return getD(); } else if (R8 == E) { return getE(); } else if (R8 == H) { return getH(); } else if (R8 == L) { return getL(); } else if (R8 == IXH) { return getIXh(); } else if (R8 == IXL) { return getIXl(); } else if (R8 == IYH) { return getIYh(); } else if (R8 == IYL) { return getIYl(); } else if (R8 == REG_I) { return getI(); } else if (R8 == REG_R) { return getR(); } else if (R8 == DUMMY) { return 0; } else { UNREACHABLE; return 0; } } template template ALWAYS_INLINE unsigned CPUCore::get16() const { if (R16 == AF) { return getAF(); } else if (R16 == BC) { return getBC(); } else if (R16 == DE) { return getDE(); } else if (R16 == HL) { return getHL(); } else if (R16 == IX) { return getIX(); } else if (R16 == IY) { return getIY(); } else if (R16 == SP) { return getSP(); } else { UNREACHABLE; return 0; } } template template ALWAYS_INLINE void CPUCore::set8(byte x) { if (R8 == A) { setA(x); } else if (R8 == F) { setF(x); } else if (R8 == B) { setB(x); } else if (R8 == C) { setC(x); } else if (R8 == D) { setD(x); } else if (R8 == E) { setE(x); } else if (R8 == H) { setH(x); } else if (R8 == L) { setL(x); } else if (R8 == IXH) { setIXh(x); } else if (R8 == IXL) { setIXl(x); } else if (R8 == IYH) { setIYh(x); } else if (R8 == IYL) { setIYl(x); } else if (R8 == REG_I) { setI(x); } else if (R8 == REG_R) { setR(x); } else if (R8 == DUMMY) { /* nothing */ } else { UNREACHABLE; } } template template ALWAYS_INLINE void CPUCore::set16(unsigned x) { if (R16 == AF) { setAF(x); } else if (R16 == BC) { setBC(x); } else if (R16 == DE) { setDE(x); } else if (R16 == HL) { setHL(x); } else if (R16 == IX) { setIX(x); } else if (R16 == IY) { setIY(x); } else if (R16 == SP) { setSP(x); } else { UNREACHABLE; } } // LD r,r template template int CPUCore::ld_R_R() { set8(get8()); return T::CC_LD_R_R + EE; } // LD SP,ss template template int CPUCore::ld_sp_SS() { setSP(get16()); return T::CC_LD_SP_HL + EE; } // LD (ss),a template template int CPUCore::ld_SS_a() { T::setMemPtr((getA() << 8) | ((get16() + 1) & 0xFF)); WRMEM(get16(), getA(), T::CC_LD_SS_A_1); return T::CC_LD_SS_A; } // LD (HL),r template template int CPUCore::ld_xhl_R() { WRMEM(getHL(), get8(), T::CC_LD_HL_R_1); return T::CC_LD_HL_R; } // LD (IXY+e),r template template int CPUCore::ld_xix_R() { int8_t ofst = RDMEM_OPCODE(T::CC_DD + T::CC_LD_XIX_R_1); unsigned addr = (get16() + ofst) & 0xFFFF; T::setMemPtr(addr); WRMEM(addr, get8(), T::CC_DD + T::CC_LD_XIX_R_2); return T::CC_DD + T::CC_LD_XIX_R; } // LD (HL),n template int CPUCore::ld_xhl_byte() { byte val = RDMEM_OPCODE(T::CC_LD_HL_N_1); WRMEM(getHL(), val, T::CC_LD_HL_N_2); return T::CC_LD_HL_N; } // LD (IXY+e),n template template int CPUCore::ld_xix_byte() { unsigned tmp = RD_WORD_PC(T::CC_DD + T::CC_LD_XIX_N_1); int8_t ofst = tmp & 0xFF; byte val = tmp >> 8; unsigned addr = (get16() + ofst) & 0xFFFF; T::setMemPtr(addr); WRMEM(addr, val, T::CC_DD + T::CC_LD_XIX_N_2); return T::CC_DD + T::CC_LD_XIX_N; } // LD (nn),A template int CPUCore::ld_xbyte_a() { unsigned x = RD_WORD_PC(T::CC_LD_NN_A_1); T::setMemPtr((getA() << 8) | ((x + 1) & 0xFF)); WRMEM(x, getA(), T::CC_LD_NN_A_2); return T::CC_LD_NN_A; } // LD (nn),ss template template inline int CPUCore::WR_NN_Y(unsigned reg) { unsigned addr = RD_WORD_PC(T::CC_LD_XX_HL_1 + EE); T::setMemPtr(addr + 1); WR_WORD(addr, reg, T::CC_LD_XX_HL_2 + EE); return T::CC_LD_XX_HL + EE; } template template int CPUCore::ld_xword_SS() { return WR_NN_Y(get16()); } template template int CPUCore::ld_xword_SS_ED() { return WR_NN_Y(get16()); } // LD A,(ss) template template int CPUCore::ld_a_SS() { T::setMemPtr(get16() + 1); setA(RDMEM(get16(), T::CC_LD_A_SS_1)); return T::CC_LD_A_SS; } // LD A,(nn) template int CPUCore::ld_a_xbyte() { unsigned addr = RD_WORD_PC(T::CC_LD_A_NN_1); T::setMemPtr(addr + 1); setA(RDMEM(addr, T::CC_LD_A_NN_2)); return T::CC_LD_A_NN; } // LD r,n template template int CPUCore::ld_R_byte() { set8(RDMEM_OPCODE(T::CC_LD_R_N_1 + EE)); return T::CC_LD_R_N + EE; } // LD r,(hl) template template int CPUCore::ld_R_xhl() { set8(RDMEM(getHL(), T::CC_LD_R_HL_1)); return T::CC_LD_R_HL; } // LD r,(IXY+e) template template int CPUCore::ld_R_xix() { int8_t ofst = RDMEM_OPCODE(T::CC_DD + T::CC_LD_R_XIX_1); unsigned addr = (get16() + ofst) & 0xFFFF; T::setMemPtr(addr); set8(RDMEM(addr, T::CC_DD + T::CC_LD_R_XIX_2)); return T::CC_DD + T::CC_LD_R_XIX; } // LD ss,(nn) template template inline unsigned CPUCore::RD_P_XX() { unsigned addr = RD_WORD_PC(T::CC_LD_HL_XX_1 + EE); T::setMemPtr(addr + 1); unsigned result = RD_WORD(addr, T::CC_LD_HL_XX_2 + EE); return result; } template template int CPUCore::ld_SS_xword() { set16(RD_P_XX()); return T::CC_LD_HL_XX + EE; } template template int CPUCore::ld_SS_xword_ED() { set16(RD_P_XX()); return T::CC_LD_HL_XX + T::EE_ED; } // LD ss,nn template template int CPUCore::ld_SS_word() { set16(RD_WORD_PC(T::CC_LD_SS_NN_1 + EE)); return T::CC_LD_SS_NN + EE; } // ADC A,r template inline void CPUCore::ADC(byte reg) { unsigned res = getA() + reg + ((getF() & C_FLAG) ? 1 : 0); byte f = ((res & 0x100) ? C_FLAG : 0) | ((getA() ^ res ^ reg) & H_FLAG) | (((getA() ^ res) & (reg ^ res) & 0x80) >> 5) | // V_FLAG 0; // N_FLAG if (T::isR800()) { f |= ZSTable[res & 0xFF]; f |= getF() & (X_FLAG | Y_FLAG); } else { f |= ZSXYTable[res & 0xFF]; } setF(f); setA(res); } template inline int CPUCore::adc_a_a() { unsigned res = 2 * getA() + ((getF() & C_FLAG) ? 1 : 0); byte f = ((res & 0x100) ? C_FLAG : 0) | (res & H_FLAG) | (((getA() ^ res) & 0x80) >> 5) | // V_FLAG 0; // N_FLAG if (T::isR800()) { f |= ZSTable[res & 0xFF]; f |= getF() & (X_FLAG | Y_FLAG); } else { f |= ZSXYTable[res & 0xFF]; } setF(f); setA(res); return T::CC_CP_R; } template template int CPUCore::adc_a_R() { ADC(get8()); return T::CC_CP_R + EE; } template int CPUCore::adc_a_byte() { ADC(RDMEM_OPCODE(T::CC_CP_N_1)); return T::CC_CP_N; } template int CPUCore::adc_a_xhl() { ADC(RDMEM(getHL(), T::CC_CP_XHL_1)); return T::CC_CP_XHL; } template template int CPUCore::adc_a_xix() { int8_t ofst = RDMEM_OPCODE(T::CC_DD + T::CC_CP_XIX_1); unsigned addr = (get16() + ofst) & 0xFFFF; T::setMemPtr(addr); ADC(RDMEM(addr, T::CC_DD + T::CC_CP_XIX_2)); return T::CC_DD + T::CC_CP_XIX; } // ADD A,r template inline void CPUCore::ADD(byte reg) { unsigned res = getA() + reg; byte f = ((res & 0x100) ? C_FLAG : 0) | ((getA() ^ res ^ reg) & H_FLAG) | (((getA() ^ res) & (reg ^ res) & 0x80) >> 5) | // V_FLAG 0; // N_FLAG if (T::isR800()) { f |= ZSTable[res & 0xFF]; f |= getF() & (X_FLAG | Y_FLAG); } else { f |= ZSXYTable[res & 0xFF]; } setF(f); setA(res); } template inline int CPUCore::add_a_a() { unsigned res = 2 * getA(); byte f = ((res & 0x100) ? C_FLAG : 0) | (res & H_FLAG) | (((getA() ^ res) & 0x80) >> 5) | // V_FLAG 0; // N_FLAG if (T::isR800()) { f |= ZSTable[res & 0xFF]; f |= getF() & (X_FLAG | Y_FLAG); } else { f |= ZSXYTable[res & 0xFF]; } setF(f); setA(res); return T::CC_CP_R; } template template int CPUCore::add_a_R() { ADD(get8()); return T::CC_CP_R + EE; } template int CPUCore::add_a_byte() { ADD(RDMEM_OPCODE(T::CC_CP_N_1)); return T::CC_CP_N; } template int CPUCore::add_a_xhl() { ADD(RDMEM(getHL(), T::CC_CP_XHL_1)); return T::CC_CP_XHL; } template template int CPUCore::add_a_xix() { int8_t ofst = RDMEM_OPCODE(T::CC_DD + T::CC_CP_XIX_1); unsigned addr = (get16() + ofst) & 0xFFFF; T::setMemPtr(addr); ADD(RDMEM(addr, T::CC_DD + T::CC_CP_XIX_2)); return T::CC_DD + T::CC_CP_XIX; } // AND r template inline void CPUCore::AND(byte reg) { setA(getA() & reg); byte f = 0; if (T::isR800()) { f |= ZSPHTable[getA()]; f |= getF() & (X_FLAG | Y_FLAG); } else { f |= ZSPXYTable[getA()] | H_FLAG; } setF(f); } template int CPUCore::and_a() { byte f = 0; if (T::isR800()) { f |= ZSPHTable[getA()]; f |= getF() & (X_FLAG | Y_FLAG); } else { f |= ZSPXYTable[getA()] | H_FLAG; } setF(f); return T::CC_CP_R; } template template int CPUCore::and_R() { AND(get8()); return T::CC_CP_R + EE; } template int CPUCore::and_byte() { AND(RDMEM_OPCODE(T::CC_CP_N_1)); return T::CC_CP_N; } template int CPUCore::and_xhl() { AND(RDMEM(getHL(), T::CC_CP_XHL_1)); return T::CC_CP_XHL; } template template int CPUCore::and_xix() { int8_t ofst = RDMEM_OPCODE(T::CC_DD + T::CC_CP_XIX_1); unsigned addr = (get16() + ofst) & 0xFFFF; T::setMemPtr(addr); AND(RDMEM(addr, T::CC_DD + T::CC_CP_XIX_2)); return T::CC_DD + T::CC_CP_XIX; } // CP r template inline void CPUCore::CP(byte reg) { unsigned q = getA() - reg; byte f = ZSTable[q & 0xFF] | ((q & 0x100) ? C_FLAG : 0) | N_FLAG | ((getA() ^ q ^ reg) & H_FLAG) | (((reg ^ getA()) & (getA() ^ q) & 0x80) >> 5); // V_FLAG if (T::isR800()) { f |= getF() & (X_FLAG | Y_FLAG); } else { f |= reg & (X_FLAG | Y_FLAG); // XY from operand, not from result } setF(f); } template int CPUCore::cp_a() { byte f = ZS0 | N_FLAG; if (T::isR800()) { f |= getF() & (X_FLAG | Y_FLAG); } else { f |= getA() & (X_FLAG | Y_FLAG); // XY from operand, not from result } setF(f); return T::CC_CP_R; } template template int CPUCore::cp_R() { CP(get8()); return T::CC_CP_R + EE; } template int CPUCore::cp_byte() { CP(RDMEM_OPCODE(T::CC_CP_N_1)); return T::CC_CP_N; } template int CPUCore::cp_xhl() { CP(RDMEM(getHL(), T::CC_CP_XHL_1)); return T::CC_CP_XHL; } template template int CPUCore::cp_xix() { int8_t ofst = RDMEM_OPCODE(T::CC_DD + T::CC_CP_XIX_1); unsigned addr = (get16() + ofst) & 0xFFFF; T::setMemPtr(addr); CP(RDMEM(addr, T::CC_DD + T::CC_CP_XIX_2)); return T::CC_DD + T::CC_CP_XIX; } // OR r template inline void CPUCore::OR(byte reg) { setA(getA() | reg); byte f = 0; if (T::isR800()) { f |= ZSPTable[getA()]; f |= getF() & (X_FLAG | Y_FLAG); } else { f |= ZSPXYTable[getA()]; } setF(f); } template int CPUCore::or_a() { byte f = 0; if (T::isR800()) { f |= ZSPTable[getA()]; f |= getF() & (X_FLAG | Y_FLAG); } else { f |= ZSPXYTable[getA()]; } setF(f); return T::CC_CP_R; } template template int CPUCore::or_R() { OR(get8()); return T::CC_CP_R + EE; } template int CPUCore::or_byte() { OR(RDMEM_OPCODE(T::CC_CP_N_1)); return T::CC_CP_N; } template int CPUCore::or_xhl() { OR(RDMEM(getHL(), T::CC_CP_XHL_1)); return T::CC_CP_XHL; } template template int CPUCore::or_xix() { int8_t ofst = RDMEM_OPCODE(T::CC_DD + T::CC_CP_XIX_1); unsigned addr = (get16() + ofst) & 0xFFFF; T::setMemPtr(addr); OR(RDMEM(addr, T::CC_DD + T::CC_CP_XIX_2)); return T::CC_DD + T::CC_CP_XIX; } // SBC A,r template inline void CPUCore::SBC(byte reg) { unsigned res = getA() - reg - ((getF() & C_FLAG) ? 1 : 0); byte f = ((res & 0x100) ? C_FLAG : 0) | N_FLAG | ((getA() ^ res ^ reg) & H_FLAG) | (((reg ^ getA()) & (getA() ^ res) & 0x80) >> 5); // V_FLAG if (T::isR800()) { f |= ZSTable[res & 0xFF]; f |= getF() & (X_FLAG | Y_FLAG); } else { f |= ZSXYTable[res & 0xFF]; } setF(f); setA(res); } template int CPUCore::sbc_a_a() { if (T::isR800()) { word t = (getF() & C_FLAG) ? (255 * 256 | ZS255 | C_FLAG | H_FLAG | N_FLAG) : ( 0 * 256 | ZS0 | N_FLAG); setAF(t | (getF() & (X_FLAG | Y_FLAG))); } else { setAF((getF() & C_FLAG) ? (255 * 256 | ZSXY255 | C_FLAG | H_FLAG | N_FLAG) : ( 0 * 256 | ZSXY0 | N_FLAG)); } return T::CC_CP_R; } template template int CPUCore::sbc_a_R() { SBC(get8()); return T::CC_CP_R + EE; } template int CPUCore::sbc_a_byte() { SBC(RDMEM_OPCODE(T::CC_CP_N_1)); return T::CC_CP_N; } template int CPUCore::sbc_a_xhl() { SBC(RDMEM(getHL(), T::CC_CP_XHL_1)); return T::CC_CP_XHL; } template template int CPUCore::sbc_a_xix() { int8_t ofst = RDMEM_OPCODE(T::CC_DD + T::CC_CP_XIX_1); unsigned addr = (get16() + ofst) & 0xFFFF; T::setMemPtr(addr); SBC(RDMEM(addr, T::CC_DD + T::CC_CP_XIX_2)); return T::CC_DD + T::CC_CP_XIX; } // SUB r template inline void CPUCore::SUB(byte reg) { unsigned res = getA() - reg; byte f = ((res & 0x100) ? C_FLAG : 0) | N_FLAG | ((getA() ^ res ^ reg) & H_FLAG) | (((reg ^ getA()) & (getA() ^ res) & 0x80) >> 5); // V_FLAG if (T::isR800()) { f |= ZSTable[res & 0xFF]; f |= getF() & (X_FLAG | Y_FLAG); } else { f |= ZSXYTable[res & 0xFF]; } setF(f); setA(res); } template int CPUCore::sub_a() { if (T::isR800()) { word t = 0 * 256 | ZS0 | N_FLAG; setAF(t | (getF() & (X_FLAG | Y_FLAG))); } else { setAF(0 * 256 | ZSXY0 | N_FLAG); } return T::CC_CP_R; } template template int CPUCore::sub_R() { SUB(get8()); return T::CC_CP_R + EE; } template int CPUCore::sub_byte() { SUB(RDMEM_OPCODE(T::CC_CP_N_1)); return T::CC_CP_N; } template int CPUCore::sub_xhl() { SUB(RDMEM(getHL(), T::CC_CP_XHL_1)); return T::CC_CP_XHL; } template template int CPUCore::sub_xix() { int8_t ofst = RDMEM_OPCODE(T::CC_DD + T::CC_CP_XIX_1); unsigned addr = (get16() + ofst) & 0xFFFF; T::setMemPtr(addr); SUB(RDMEM(addr, T::CC_DD + T::CC_CP_XIX_2)); return T::CC_DD + T::CC_CP_XIX; } // XOR r template inline void CPUCore::XOR(byte reg) { setA(getA() ^ reg); byte f = 0; if (T::isR800()) { f |= ZSPTable[getA()]; f |= getF() & (X_FLAG | Y_FLAG); } else { f |= ZSPXYTable[getA()]; } setF(f); } template int CPUCore::xor_a() { if (T::isR800()) { word t = 0 * 256 + ZSP0; setAF(t | (getF() & (X_FLAG | Y_FLAG))); } else { setAF(0 * 256 + ZSPXY0); } return T::CC_CP_R; } template template int CPUCore::xor_R() { XOR(get8()); return T::CC_CP_R + EE; } template int CPUCore::xor_byte() { XOR(RDMEM_OPCODE(T::CC_CP_N_1)); return T::CC_CP_N; } template int CPUCore::xor_xhl() { XOR(RDMEM(getHL(), T::CC_CP_XHL_1)); return T::CC_CP_XHL; } template template int CPUCore::xor_xix() { int8_t ofst = RDMEM_OPCODE(T::CC_DD + T::CC_CP_XIX_1); unsigned addr = (get16() + ofst) & 0xFFFF; T::setMemPtr(addr); XOR(RDMEM(addr, T::CC_DD + T::CC_CP_XIX_2)); return T::CC_DD + T::CC_CP_XIX; } // DEC r template inline byte CPUCore::DEC(byte reg) { byte res = reg - 1; byte f = ((reg & ~res & 0x80) >> 5) | // V_FLAG (((res & 0x0F) + 1) & H_FLAG) | N_FLAG; if (T::isR800()) { f |= getF() & (C_FLAG | X_FLAG | Y_FLAG); f |= ZSTable[res]; } else { f |= getF() & C_FLAG; f |= ZSXYTable[res]; } setF(f); return res; } template template int CPUCore::dec_R() { set8(DEC(get8())); return T::CC_INC_R + EE; } template template inline int CPUCore::DEC_X(unsigned x) { byte val = DEC(RDMEM(x, T::CC_INC_XHL_1 + EE)); WRMEM(x, val, T::CC_INC_XHL_2 + EE); return T::CC_INC_XHL + EE; } template int CPUCore::dec_xhl() { return DEC_X<0>(getHL()); } template template int CPUCore::dec_xix() { int8_t ofst = RDMEM_OPCODE(T::CC_DD + T::CC_INC_XIX_1); unsigned addr = (get16() + ofst) & 0xFFFF; T::setMemPtr(addr); return DEC_X(addr); } // INC r template inline byte CPUCore::INC(byte reg) { reg++; byte f = ((reg & -reg & 0x80) >> 5) | // V_FLAG (((reg & 0x0F) - 1) & H_FLAG) | 0; // N_FLAG if (T::isR800()) { f |= getF() & (C_FLAG | X_FLAG | Y_FLAG); f |= ZSTable[reg]; } else { f |= getF() & C_FLAG; f |= ZSXYTable[reg]; } setF(f); return reg; } template template int CPUCore::inc_R() { set8(INC(get8())); return T::CC_INC_R + EE; } template template inline int CPUCore::INC_X(unsigned x) { byte val = INC(RDMEM(x, T::CC_INC_XHL_1 + EE)); WRMEM(x, val, T::CC_INC_XHL_2 + EE); return T::CC_INC_XHL + EE; } template int CPUCore::inc_xhl() { return INC_X<0>(getHL()); } template template int CPUCore::inc_xix() { int8_t ofst = RDMEM_OPCODE(T::CC_DD + T::CC_INC_XIX_1); unsigned addr = (get16() + ofst) & 0xFFFF; T::setMemPtr(addr); return INC_X(addr); } // ADC HL,ss template template inline int CPUCore::adc_hl_SS() { unsigned reg = get16(); T::setMemPtr(getHL() + 1); unsigned res = getHL() + reg + ((getF() & C_FLAG) ? 1 : 0); byte f = (res >> 16) | // C_FLAG 0; // N_FLAG if (T::isR800()) { f |= getF() & (X_FLAG | Y_FLAG); } if (res & 0xFFFF) { f |= ((getHL() ^ res ^ reg) >> 8) & H_FLAG; f |= 0; // Z_FLAG f |= ((getHL() ^ res) & (reg ^ res) & 0x8000) >> 13; // V_FLAG if (T::isR800()) { f |= (res >> 8) & S_FLAG; } else { f |= (res >> 8) & (S_FLAG | X_FLAG | Y_FLAG); } } else { f |= ((getHL() ^ reg) >> 8) & H_FLAG; f |= Z_FLAG; f |= (getHL() & reg & 0x8000) >> 13; // V_FLAG f |= 0; // S_FLAG (X_FLAG Y_FLAG) } setF(f); setHL(res); return T::CC_ADC_HL_SS; } template int CPUCore::adc_hl_hl() { T::setMemPtr(getHL() + 1); unsigned res = 2 * getHL() + ((getF() & C_FLAG) ? 1 : 0); byte f = (res >> 16) | // C_FLAG 0; // N_FLAG if (T::isR800()) { f |= getF() & (X_FLAG | Y_FLAG); } if (res & 0xFFFF) { f |= 0; // Z_FLAG f |= ((getHL() ^ res) & 0x8000) >> 13; // V_FLAG if (T::isR800()) { f |= (res >> 8) & (H_FLAG | S_FLAG); } else { f |= (res >> 8) & (H_FLAG | S_FLAG | X_FLAG | Y_FLAG); } } else { f |= Z_FLAG; f |= (getHL() & 0x8000) >> 13; // V_FLAG f |= 0; // H_FLAG S_FLAG (X_FLAG Y_FLAG) } setF(f); setHL(res); return T::CC_ADC_HL_SS; } // ADD HL/IX/IY,ss template template int CPUCore::add_SS_TT() { unsigned reg1 = get16(); unsigned reg2 = get16(); T::setMemPtr(reg1 + 1); unsigned res = reg1 + reg2; byte f = (((reg1 ^ res ^ reg2) >> 8) & H_FLAG) | (res >> 16) | // C_FLAG 0; // N_FLAG if (T::isR800()) { f |= getF() & (S_FLAG | Z_FLAG | V_FLAG | X_FLAG | Y_FLAG); } else { f |= getF() & (S_FLAG | Z_FLAG | V_FLAG); f |= (res >> 8) & (X_FLAG | Y_FLAG); } setF(f); set16(res & 0xFFFF); return T::CC_ADD_HL_SS + EE; } template template int CPUCore::add_SS_SS() { unsigned reg = get16(); T::setMemPtr(reg + 1); unsigned res = 2 * reg; byte f = (res >> 16) | // C_FLAG 0; // N_FLAG if (T::isR800()) { f |= getF() & (S_FLAG | Z_FLAG | V_FLAG | X_FLAG | Y_FLAG); f |= (res >> 8) & H_FLAG; } else { f |= getF() & (S_FLAG | Z_FLAG | V_FLAG); f |= (res >> 8) & (H_FLAG | X_FLAG | Y_FLAG); } setF(f); set16(res & 0xFFFF); return T::CC_ADD_HL_SS + EE; } // SBC HL,ss template template inline int CPUCore::sbc_hl_SS() { unsigned reg = get16(); T::setMemPtr(getHL() + 1); unsigned res = getHL() - reg - ((getF() & C_FLAG) ? 1 : 0); byte f = ((res & 0x10000) ? C_FLAG : 0) | N_FLAG; if (T::isR800()) { f |= getF() & (X_FLAG | Y_FLAG); } if (res & 0xFFFF) { f |= ((getHL() ^ res ^ reg) >> 8) & H_FLAG; f |= 0; // Z_FLAG f |= ((reg ^ getHL()) & (getHL() ^ res) & 0x8000) >> 13; // V_FLAG if (T::isR800()) { f |= (res >> 8) & S_FLAG; } else { f |= (res >> 8) & (S_FLAG | X_FLAG | Y_FLAG); } } else { f |= ((getHL() ^ reg) >> 8) & H_FLAG; f |= Z_FLAG; f |= ((reg ^ getHL()) & getHL() & 0x8000) >> 13; // V_FLAG f |= 0; // S_FLAG (X_FLAG Y_FLAG) } setF(f); setHL(res); return T::CC_ADC_HL_SS; } template int CPUCore::sbc_hl_hl() { T::setMemPtr(getHL() + 1); byte f = T::isR800() ? (getF() & (X_FLAG | Y_FLAG)) : 0; if (getF() & C_FLAG) { f |= C_FLAG | H_FLAG | S_FLAG | N_FLAG; if (!T::isR800()) { f |= X_FLAG | Y_FLAG; } setHL(0xFFFF); } else { f |= Z_FLAG | N_FLAG; setHL(0); } setF(f); return T::CC_ADC_HL_SS; } // DEC ss template template int CPUCore::dec_SS() { set16(get16() - 1); return T::CC_INC_SS + EE; } // INC ss template template int CPUCore::inc_SS() { set16(get16() + 1); return T::CC_INC_SS + EE; } // BIT n,r template template int CPUCore::bit_N_R() { byte reg = get8(); byte f = 0; // N_FLAG if (T::isR800()) { // this is very different from Z80 (not only XY flags) f |= getF() & (S_FLAG | V_FLAG | C_FLAG | X_FLAG | Y_FLAG); f |= H_FLAG; f |= (reg & (1 << N)) ? 0 : Z_FLAG; } else { f |= ZSPHTable[reg & (1 << N)]; f |= getF() & C_FLAG; f |= reg & (X_FLAG | Y_FLAG); } setF(f); return T::CC_BIT_R; } template template inline int CPUCore::bit_N_xhl() { byte m = RDMEM(getHL(), T::CC_BIT_XHL_1) & (1 << N); byte f = 0; // N_FLAG if (T::isR800()) { f |= getF() & (S_FLAG | V_FLAG | C_FLAG | X_FLAG | Y_FLAG); f |= H_FLAG; f |= m ? 0 : Z_FLAG; } else { f |= ZSPHTable[m]; f |= getF() & C_FLAG; f |= (T::getMemPtr() >> 8) & (X_FLAG | Y_FLAG); } setF(f); return T::CC_BIT_XHL; } template template inline int CPUCore::bit_N_xix(unsigned addr) { T::setMemPtr(addr); byte m = RDMEM(addr, T::CC_DD + T::CC_BIT_XIX_1) & (1 << N); byte f = 0; // N_FLAG if (T::isR800()) { f |= getF() & (S_FLAG | V_FLAG | C_FLAG | X_FLAG | Y_FLAG); f |= H_FLAG; f |= m ? 0 : Z_FLAG; } else { f |= ZSPHTable[m]; f |= getF() & C_FLAG; f |= (addr >> 8) & (X_FLAG | Y_FLAG); } setF(f); return T::CC_DD + T::CC_BIT_XIX; } // RES n,r static inline byte RES(unsigned b, byte reg) { return reg & ~(1 << b); } template template int CPUCore::res_N_R() { set8(RES(N, get8())); return T::CC_SET_R; } template template inline byte CPUCore::RES_X(unsigned bit, unsigned addr) { byte res = RES(bit, RDMEM(addr, T::CC_SET_XHL_1 + EE)); WRMEM(addr, res, T::CC_SET_XHL_2 + EE); return res; } template template int CPUCore::res_N_xhl() { RES_X<0>(N, getHL()); return T::CC_SET_XHL; } template template int CPUCore::res_N_xix_R(unsigned a) { T::setMemPtr(a); set8(RES_X(N, a)); return T::CC_DD + T::CC_SET_XIX; } // SET n,r static inline byte SET(unsigned b, byte reg) { return reg | (1 << b); } template template int CPUCore::set_N_R() { set8(SET(N, get8())); return T::CC_SET_R; } template template inline byte CPUCore::SET_X(unsigned bit, unsigned addr) { byte res = SET(bit, RDMEM(addr, T::CC_SET_XHL_1 + EE)); WRMEM(addr, res, T::CC_SET_XHL_2 + EE); return res; } template template int CPUCore::set_N_xhl() { SET_X<0>(N, getHL()); return T::CC_SET_XHL; } template template int CPUCore::set_N_xix_R(unsigned a) { T::setMemPtr(a); set8(SET_X(N, a)); return T::CC_DD + T::CC_SET_XIX; } // RL r template inline byte CPUCore::RL(byte reg) { byte c = reg >> 7; reg = (reg << 1) | ((getF() & C_FLAG) ? 0x01 : 0); byte f = c ? C_FLAG : 0; if (T::isR800()) { f |= ZSPTable[reg]; f |= getF() & (X_FLAG | Y_FLAG); } else { f |= ZSPXYTable[reg]; } setF(f); return reg; } template template inline byte CPUCore::RL_X(unsigned x) { byte res = RL(RDMEM(x, T::CC_SET_XHL_1 + EE)); WRMEM(x, res, T::CC_SET_XHL_2 + EE); return res; } template template int CPUCore::rl_R() { set8(RL(get8())); return T::CC_SET_R; } template int CPUCore::rl_xhl() { RL_X<0>(getHL()); return T::CC_SET_XHL; } template template int CPUCore::rl_xix_R(unsigned a) { T::setMemPtr(a); set8(RL_X(a)); return T::CC_DD + T::CC_SET_XIX; } // RLC r template inline byte CPUCore::RLC(byte reg) { byte c = reg >> 7; reg = (reg << 1) | c; byte f = c ? C_FLAG : 0; if (T::isR800()) { f |= ZSPTable[reg]; f |= getF() & (X_FLAG | Y_FLAG); } else { f |= ZSPXYTable[reg]; } setF(f); return reg; } template template inline byte CPUCore::RLC_X(unsigned x) { byte res = RLC(RDMEM(x, T::CC_SET_XHL_1 + EE)); WRMEM(x, res, T::CC_SET_XHL_2 + EE); return res; } template template int CPUCore::rlc_R() { set8(RLC(get8())); return T::CC_SET_R; } template int CPUCore::rlc_xhl() { RLC_X<0>(getHL()); return T::CC_SET_XHL; } template template int CPUCore::rlc_xix_R(unsigned a) { T::setMemPtr(a); set8(RLC_X(a)); return T::CC_DD + T::CC_SET_XIX; } // RR r template inline byte CPUCore::RR(byte reg) { byte c = reg & 1; reg = (reg >> 1) | ((getF() & C_FLAG) << 7); byte f = c ? C_FLAG : 0; if (T::isR800()) { f |= ZSPTable[reg]; f |= getF() & (X_FLAG | Y_FLAG); } else { f |= ZSPXYTable[reg]; } setF(f); return reg; } template template inline byte CPUCore::RR_X(unsigned x) { byte res = RR(RDMEM(x, T::CC_SET_XHL_1 + EE)); WRMEM(x, res, T::CC_SET_XHL_2 + EE); return res; } template template int CPUCore::rr_R() { set8(RR(get8())); return T::CC_SET_R; } template int CPUCore::rr_xhl() { RR_X<0>(getHL()); return T::CC_SET_XHL; } template template int CPUCore::rr_xix_R(unsigned a) { T::setMemPtr(a); set8(RR_X(a)); return T::CC_DD + T::CC_SET_XIX; } // RRC r template inline byte CPUCore::RRC(byte reg) { byte c = reg & 1; reg = (reg >> 1) | (c << 7); byte f = c ? C_FLAG : 0; if (T::isR800()) { f |= ZSPTable[reg]; f |= getF() & (X_FLAG | Y_FLAG); } else { f |= ZSPXYTable[reg]; } setF(f); return reg; } template template inline byte CPUCore::RRC_X(unsigned x) { byte res = RRC(RDMEM(x, T::CC_SET_XHL_1 + EE)); WRMEM(x, res, T::CC_SET_XHL_2 + EE); return res; } template template int CPUCore::rrc_R() { set8(RRC(get8())); return T::CC_SET_R; } template int CPUCore::rrc_xhl() { RRC_X<0>(getHL()); return T::CC_SET_XHL; } template template int CPUCore::rrc_xix_R(unsigned a) { T::setMemPtr(a); set8(RRC_X(a)); return T::CC_DD + T::CC_SET_XIX; } // SLA r template inline byte CPUCore::SLA(byte reg) { byte c = reg >> 7; reg <<= 1; byte f = c ? C_FLAG : 0; if (T::isR800()) { f |= ZSPTable[reg]; f |= getF() & (X_FLAG | Y_FLAG); } else { f |= ZSPXYTable[reg]; } setF(f); return reg; } template template inline byte CPUCore::SLA_X(unsigned x) { byte res = SLA(RDMEM(x, T::CC_SET_XHL_1 + EE)); WRMEM(x, res, T::CC_SET_XHL_2 + EE); return res; } template template int CPUCore::sla_R() { set8(SLA(get8())); return T::CC_SET_R; } template int CPUCore::sla_xhl() { SLA_X<0>(getHL()); return T::CC_SET_XHL; } template template int CPUCore::sla_xix_R(unsigned a) { T::setMemPtr(a); set8(SLA_X(a)); return T::CC_DD + T::CC_SET_XIX; } // SLL r template inline byte CPUCore::SLL(byte reg) { assert(!T::isR800()); // this instruction is Z80-only byte c = reg >> 7; reg = (reg << 1) | 1; byte f = c ? C_FLAG : 0; f |= ZSPXYTable[reg]; setF(f); return reg; } template template inline byte CPUCore::SLL_X(unsigned x) { byte res = SLL(RDMEM(x, T::CC_SET_XHL_1 + EE)); WRMEM(x, res, T::CC_SET_XHL_2 + EE); return res; } template template int CPUCore::sll_R() { set8(SLL(get8())); return T::CC_SET_R; } template int CPUCore::sll_xhl() { SLL_X<0>(getHL()); return T::CC_SET_XHL; } template template int CPUCore::sll_xix_R(unsigned a) { T::setMemPtr(a); set8(SLL_X(a)); return T::CC_DD + T::CC_SET_XIX; } template int CPUCore::sll2() { assert(T::isR800()); // this instruction is R800-only byte f = (getF() & (X_FLAG | Y_FLAG)) | (getA() >> 7) | // C_FLAG 0; // all other flags zero setF(f); return T::CC_DD + T::CC_SET_XIX; // TODO } // SRA r template inline byte CPUCore::SRA(byte reg) { byte c = reg & 1; reg = (reg >> 1) | (reg & 0x80); byte f = c ? C_FLAG : 0; if (T::isR800()) { f |= ZSPTable[reg]; f |= getF() & (X_FLAG | Y_FLAG); } else { f |= ZSPXYTable[reg]; } setF(f); return reg; } template template inline byte CPUCore::SRA_X(unsigned x) { byte res = SRA(RDMEM(x, T::CC_SET_XHL_1 + EE)); WRMEM(x, res, T::CC_SET_XHL_2 + EE); return res; } template template int CPUCore::sra_R() { set8(SRA(get8())); return T::CC_SET_R; } template int CPUCore::sra_xhl() { SRA_X<0>(getHL()); return T::CC_SET_XHL; } template template int CPUCore::sra_xix_R(unsigned a) { T::setMemPtr(a); set8(SRA_X(a)); return T::CC_DD + T::CC_SET_XIX; } // SRL R template inline byte CPUCore::SRL(byte reg) { byte c = reg & 1; reg >>= 1; byte f = c ? C_FLAG : 0; if (T::isR800()) { f |= ZSPTable[reg]; f |= getF() & (X_FLAG | Y_FLAG); } else { f |= ZSPXYTable[reg]; } setF(f); return reg; } template template inline byte CPUCore::SRL_X(unsigned x) { byte res = SRL(RDMEM(x, T::CC_SET_XHL_1 + EE)); WRMEM(x, res, T::CC_SET_XHL_2 + EE); return res; } template template int CPUCore::srl_R() { set8(SRL(get8())); return T::CC_SET_R; } template int CPUCore::srl_xhl() { SRL_X<0>(getHL()); return T::CC_SET_XHL; } template template int CPUCore::srl_xix_R(unsigned a) { T::setMemPtr(a); set8(SRL_X(a)); return T::CC_DD + T::CC_SET_XIX; } // RLA RLCA RRA RRCA template int CPUCore::rla() { byte c = getF() & C_FLAG; byte f = (getA() & 0x80) ? C_FLAG : 0; if (T::isR800()) { f |= getF() & (S_FLAG | Z_FLAG | P_FLAG | X_FLAG | Y_FLAG); } else { f |= getF() & (S_FLAG | Z_FLAG | P_FLAG); } setA((getA() << 1) | (c ? 1 : 0)); if (!T::isR800()) { f |= getA() & (X_FLAG | Y_FLAG); } setF(f); return T::CC_RLA; } template int CPUCore::rlca() { setA((getA() << 1) | (getA() >> 7)); byte f = 0; if (T::isR800()) { f |= getF() & (S_FLAG | Z_FLAG | P_FLAG | X_FLAG | Y_FLAG); f |= getA() & C_FLAG; } else { f |= getF() & (S_FLAG | Z_FLAG | P_FLAG); f |= getA() & (Y_FLAG | X_FLAG | C_FLAG); } setF(f); return T::CC_RLA; } template int CPUCore::rra() { byte c = (getF() & C_FLAG) << 7; byte f = (getA() & 0x01) ? C_FLAG : 0; if (T::isR800()) { f |= getF() & (S_FLAG | Z_FLAG | P_FLAG | X_FLAG | Y_FLAG); } else { f |= getF() & (S_FLAG | Z_FLAG | P_FLAG); } setA((getA() >> 1) | c); if (!T::isR800()) { f |= getA() & (X_FLAG | Y_FLAG); } setF(f); return T::CC_RLA; } template int CPUCore::rrca() { byte f = getA() & C_FLAG; if (T::isR800()) { f |= getF() & (S_FLAG | Z_FLAG | P_FLAG | X_FLAG | Y_FLAG); } else { f |= getF() & (S_FLAG | Z_FLAG | P_FLAG); } setA((getA() >> 1) | (getA() << 7)); if (!T::isR800()) { f |= getA() & (X_FLAG | Y_FLAG); } setF(f); return T::CC_RLA; } // RLD template int CPUCore::rld() { byte val = RDMEM(getHL(), T::CC_RLD_1); T::setMemPtr(getHL() + 1); WRMEM(getHL(), (val << 4) | (getA() & 0x0F), T::CC_RLD_2); setA((getA() & 0xF0) | (val >> 4)); byte f = 0; if (T::isR800()) { f |= getF() & (C_FLAG | X_FLAG | Y_FLAG); f |= ZSPTable[getA()]; } else { f |= getF() & C_FLAG; f |= ZSPXYTable[getA()]; } setF(f); return T::CC_RLD; } // RRD template int CPUCore::rrd() { byte val = RDMEM(getHL(), T::CC_RLD_1); T::setMemPtr(getHL() + 1); WRMEM(getHL(), (val >> 4) | (getA() << 4), T::CC_RLD_2); setA((getA() & 0xF0) | (val & 0x0F)); byte f = 0; if (T::isR800()) { f |= getF() & (C_FLAG | X_FLAG | Y_FLAG); f |= ZSPTable[getA()]; } else { f |= getF() & C_FLAG; f |= ZSPXYTable[getA()]; } setF(f); return T::CC_RLD; } // PUSH ss template template inline void CPUCore::PUSH(unsigned reg) { setSP(getSP() - 2); WR_WORD_rev(getSP(), reg, T::CC_PUSH_1 + EE); } template template int CPUCore::push_SS() { PUSH(get16()); return T::CC_PUSH + EE; } // POP ss template template inline unsigned CPUCore::POP() { unsigned addr = getSP(); setSP(addr + 2); return RD_WORD(addr, T::CC_POP_1 + EE); } template template int CPUCore::pop_SS() { set16(POP()); return T::CC_POP + EE; } // CALL nn / CALL cc,nn template template int CPUCore::call(COND cond) { unsigned addr = RD_WORD_PC(T::CC_CALL_1); T::setMemPtr(addr); if (cond(getF())) { PUSH(getPC()); setPC(addr); return T::CC_CALL_A; } else { return T::CC_CALL_B; } } // RST n template template int CPUCore::rst() { PUSH<0>(getPC()); T::setMemPtr(ADDR); setPC(ADDR); return T::CC_RST; } // RET template template inline int CPUCore::RET(COND cond) { if (cond(getF())) { unsigned addr = POP(); T::setMemPtr(addr); setPC(addr); return T::CC_RET_A + EE; } else { return T::CC_RET_B + EE; } } template template int CPUCore::ret(COND cond) { return RET(cond); } template int CPUCore::ret() { return RET<0>(CondTrue()); } template int CPUCore::retn() { // also reti setIFF1(getIFF2()); setSlowInstructions(); return RET(CondTrue()); } // JP ss template template int CPUCore::jp_SS() { setPC(get16()); T::R800ForcePageBreak(); return T::CC_JP_HL + EE; } // JP nn / JP cc,nn template template int CPUCore::jp(COND cond) { unsigned addr = RD_WORD_PC(T::CC_JP_1); T::setMemPtr(addr); if (cond(getF())) { setPC(addr); T::R800ForcePageBreak(); return T::CC_JP_A; } else { return T::CC_JP_B; } } // JR e template template int CPUCore::jr(COND cond) { int8_t ofst = RDMEM_OPCODE(T::CC_JR_1); if (cond(getF())) { if ((getPC() & 0xFF) == 0) { // On R800, when this instruction is located in the // last two byte of a page (a page is a 256-byte // (aligned) memory block) and even if we jump back, // thus fetching the next opcode byte does not cause a // page-break, there still is one cycle overhead. It's // as-if there is a page-break. // // This could be explained by some (very limited) // pipeline behaviour in R800: it seems that the // decision to cause a page-break on the next // instruction is already made before the jump // destination address for the current instruction is // calculated (though a destination address in another // page is also a reason for a page-break). // // It's likely all instructions behave like this, but I // think we can get away with only explicitly emulating // this behaviour in the djnz and the jr (conditional // or not) instructions: all other instructions that // cause the PC to change in a non-incremental way do // already force a pagebreak for another reason, so // this effect is masked. Examples of such instructions // are: JP, RET, CALL, RST, all repeated block // instructions, accepting an IRQ, (are there more // instructions are events that change PC?) // // See doc/r800-djnz.txt for more details. T::R800ForcePageBreak(); } setPC((getPC() + ofst) & 0xFFFF); T::setMemPtr(getPC()); return T::CC_JR_A; } else { return T::CC_JR_B; } } // DJNZ e template int CPUCore::djnz() { byte b = getB() - 1; setB(b); int8_t ofst = RDMEM_OPCODE(T::CC_JR_1 + T::EE_DJNZ); if (b) { if ((getPC() & 0xFF) == 0) { // See comment in jr() T::R800ForcePageBreak(); } setPC((getPC() + ofst) & 0xFFFF); T::setMemPtr(getPC()); return T::CC_JR_A + T::EE_DJNZ; } else { return T::CC_JR_B + T::EE_DJNZ; } } // EX (SP),ss template template int CPUCore::ex_xsp_SS() { unsigned res = RD_WORD_impl(getSP(), T::CC_EX_SP_HL_1 + EE); T::setMemPtr(res); WR_WORD_rev(getSP(), get16(), T::CC_EX_SP_HL_2 + EE); set16(res); return T::CC_EX_SP_HL + EE; } // IN r,(c) template template int CPUCore::in_R_c() { if (T::isR800()) T::waitForEvenCycle(T::CC_IN_R_C_1); T::setMemPtr(getBC() + 1); byte res = READ_PORT(getBC(), T::CC_IN_R_C_1); byte f = 0; if (T::isR800()) { f |= getF() & (C_FLAG | X_FLAG | Y_FLAG); f |= ZSPTable[res]; } else { f |= getF() & C_FLAG; f |= ZSPXYTable[res]; } setF(f); set8(res); return T::CC_IN_R_C; } // IN a,(n) template int CPUCore::in_a_byte() { unsigned y = RDMEM_OPCODE(T::CC_IN_A_N_1) + 256 * getA(); T::setMemPtr(y + 1); if (T::isR800()) T::waitForEvenCycle(T::CC_IN_A_N_2); setA(READ_PORT(y, T::CC_IN_A_N_2)); return T::CC_IN_A_N; } // OUT (c),r template template int CPUCore::out_c_R() { if (T::isR800()) T::waitForEvenCycle(T::CC_OUT_C_R_1); T::setMemPtr(getBC() + 1); WRITE_PORT(getBC(), get8(), T::CC_OUT_C_R_1); return T::CC_OUT_C_R; } template int CPUCore::out_c_0() { // TODO not on R800 if (T::isR800()) T::waitForEvenCycle(T::CC_OUT_C_R_1); T::setMemPtr(getBC() + 1); byte out_c_x = isTurboR ? 255 : 0; WRITE_PORT(getBC(), out_c_x, T::CC_OUT_C_R_1); return T::CC_OUT_C_R; } // OUT (n),a template int CPUCore::out_byte_a() { byte port = RDMEM_OPCODE(T::CC_OUT_N_A_1); unsigned y = (getA() << 8) | port; T::setMemPtr((getA() << 8) | ((port + 1) & 255)); if (T::isR800()) T::waitForEvenCycle(T::CC_OUT_N_A_2); WRITE_PORT(y, getA(), T::CC_OUT_N_A_2); return T::CC_OUT_N_A; } // block CP template inline int CPUCore::BLOCK_CP(int increase, bool repeat) { T::setMemPtr(T::getMemPtr() + increase); byte val = RDMEM(getHL(), T::CC_CPI_1); byte res = getA() - val; setHL(getHL() + increase); setBC(getBC() - 1); byte f = ((getA() ^ val ^ res) & H_FLAG) | ZSTable[res] | N_FLAG | (getBC() ? V_FLAG : 0); if (T::isR800()) { f |= getF() & (C_FLAG | X_FLAG | Y_FLAG); } else { f |= getF() & C_FLAG; unsigned k = res - ((f & H_FLAG) >> 4); f |= (k << 4) & Y_FLAG; // bit 1 -> flag 5 f |= k & X_FLAG; // bit 3 -> flag 3 } setF(f); if (repeat && getBC() && res) { setPC(getPC() - 2); T::setMemPtr(getPC() + 1); return T::CC_CPIR; } else { return T::CC_CPI; } } template int CPUCore::cpd() { return BLOCK_CP(-1, false); } template int CPUCore::cpi() { return BLOCK_CP( 1, false); } template int CPUCore::cpdr() { return BLOCK_CP(-1, true ); } template int CPUCore::cpir() { return BLOCK_CP( 1, true ); } // block LD template inline int CPUCore::BLOCK_LD(int increase, bool repeat) { byte val = RDMEM(getHL(), T::CC_LDI_1); WRMEM(getDE(), val, T::CC_LDI_2); setHL(getHL() + increase); setDE(getDE() + increase); setBC(getBC() - 1); byte f = getBC() ? V_FLAG : 0; if (T::isR800()) { f |= getF() & (S_FLAG | Z_FLAG | C_FLAG | X_FLAG | Y_FLAG); } else { f |= getF() & (S_FLAG | Z_FLAG | C_FLAG); f |= ((getA() + val) << 4) & Y_FLAG; // bit 1 -> flag 5 f |= (getA() + val) & X_FLAG; // bit 3 -> flag 3 } setF(f); if (repeat && getBC()) { setPC(getPC() - 2); T::setMemPtr(getPC() + 1); return T::CC_LDIR; } else { return T::CC_LDI; } } template int CPUCore::ldd() { return BLOCK_LD(-1, false); } template int CPUCore::ldi() { return BLOCK_LD( 1, false); } template int CPUCore::lddr() { return BLOCK_LD(-1, true ); } template int CPUCore::ldir() { return BLOCK_LD( 1, true ); } // block IN template inline int CPUCore::BLOCK_IN(int increase, bool repeat) { // TODO R800 flags if (T::isR800()) T::waitForEvenCycle(T::CC_INI_1); T::setMemPtr(getBC() + increase); setBC(getBC() - 0x100); // decr before use byte val = READ_PORT(getBC(), T::CC_INI_1); WRMEM(getHL(), val, T::CC_INI_2); setHL(getHL() + increase); unsigned k = val + ((getC() + increase) & 0xFF); byte b = getB(); setF(((val & S_FLAG) >> 6) | // N_FLAG ((k & 0x100) ? (H_FLAG | C_FLAG) : 0) | ZSXYTable[b] | (ZSPXYTable[(k & 0x07) ^ b] & P_FLAG)); if (repeat && b) { setPC(getPC() - 2); return T::CC_INIR; } else { return T::CC_INI; } } template int CPUCore::ind() { return BLOCK_IN(-1, false); } template int CPUCore::ini() { return BLOCK_IN( 1, false); } template int CPUCore::indr() { return BLOCK_IN(-1, true ); } template int CPUCore::inir() { return BLOCK_IN( 1, true ); } // block OUT template inline int CPUCore::BLOCK_OUT(int increase, bool repeat) { // TODO R800 flags byte val = RDMEM(getHL(), T::CC_OUTI_1); setHL(getHL() + increase); if (T::isR800()) T::waitForEvenCycle(T::CC_OUTI_2); WRITE_PORT(getBC(), val, T::CC_OUTI_2); setBC(getBC() - 0x100); // decr after use T::setMemPtr(getBC() + increase); unsigned k = val + getL(); byte b = getB(); setF(((val & S_FLAG) >> 6) | // N_FLAG ((k & 0x100) ? (H_FLAG | C_FLAG) : 0) | ZSXYTable[b] | (ZSPXYTable[(k & 0x07) ^ b] & P_FLAG)); if (repeat && b) { setPC(getPC() - 2); return T::CC_OTIR; } else { return T::CC_OUTI; } } template int CPUCore::outd() { return BLOCK_OUT(-1, false); } template int CPUCore::outi() { return BLOCK_OUT( 1, false); } template int CPUCore::otdr() { return BLOCK_OUT(-1, true ); } template int CPUCore::otir() { return BLOCK_OUT( 1, true ); } // various template int CPUCore::nop() { return T::CC_NOP; } template int CPUCore::ccf() { byte f = 0; if (T::isR800()) { // H flag is different from Z80 (and as always XY flags as well) f |= getF() & (S_FLAG | Z_FLAG | P_FLAG | C_FLAG | X_FLAG | Y_FLAG | H_FLAG); } else { f |= (getF() & C_FLAG) << 4; // H_FLAG // only set X(Y) flag (don't reset if already set) if (isTurboR) { // Y flag is not changed on a turboR-Z80 f |= getF() & (S_FLAG | Z_FLAG | P_FLAG | C_FLAG | Y_FLAG); f |= (getF() | getA()) & X_FLAG; } else { f |= getF() & (S_FLAG | Z_FLAG | P_FLAG | C_FLAG); f |= (getF() | getA()) & (X_FLAG | Y_FLAG); } } f ^= C_FLAG; setF(f); return T::CC_CCF; } template int CPUCore::cpl() { setA(getA() ^ 0xFF); byte f = H_FLAG | N_FLAG; if (T::isR800()) { f |= getF(); } else { f |= getF() & (S_FLAG | Z_FLAG | P_FLAG | C_FLAG); f |= getA() & (X_FLAG | Y_FLAG); } setF(f); return T::CC_CPL; } template int CPUCore::daa() { byte a = getA(); byte f = getF(); byte adjust = 0; if ((f & H_FLAG) || ((getA() & 0xf) > 9)) adjust += 6; if ((f & C_FLAG) || (getA() > 0x99)) adjust += 0x60; if (f & N_FLAG) a -= adjust; else a += adjust; if (T::isR800()) { f &= C_FLAG | N_FLAG | X_FLAG | Y_FLAG; f |= ZSPTable[a]; } else { f &= C_FLAG | N_FLAG; f |= ZSPXYTable[a]; } f |= (getA() > 0x99) | ((getA() ^ a) & H_FLAG); setA(a); setF(f); return T::CC_DAA; } template int CPUCore::neg() { // alternative: LUT word negTable[256] unsigned a = getA(); unsigned res = -signed(a); byte f = ((res & 0x100) ? C_FLAG : 0) | N_FLAG | ((res ^ a) & H_FLAG) | ((a & res & 0x80) >> 5); // V_FLAG if (T::isR800()) { f |= ZSTable[res & 0xFF]; f |= getF() & (X_FLAG | Y_FLAG); } else { f |= ZSXYTable[res & 0xFF]; } setF(f); setA(res); return T::CC_NEG; } template int CPUCore::scf() { byte f = C_FLAG; if (T::isR800()) { f |= getF() & (S_FLAG | Z_FLAG | P_FLAG | X_FLAG | Y_FLAG); } else { // only set X(Y) flag (don't reset if already set) if (isTurboR) { // Y flag is not changed on a turboR-Z80 f |= getF() & (S_FLAG | Z_FLAG | P_FLAG | Y_FLAG); f |= (getF() | getA()) & X_FLAG; } else { f |= getF() & (S_FLAG | Z_FLAG | P_FLAG); f |= (getF() | getA()) & (X_FLAG | Y_FLAG); } } setF(f); return T::CC_SCF; } template int CPUCore::ex_af_af() { unsigned t = getAF2(); setAF2(getAF()); setAF(t); return T::CC_EX; } template int CPUCore::ex_de_hl() { unsigned t = getDE(); setDE(getHL()); setHL(t); return T::CC_EX; } template int CPUCore::exx() { unsigned t1 = getBC2(); setBC2(getBC()); setBC(t1); unsigned t2 = getDE2(); setDE2(getDE()); setDE(t2); unsigned t3 = getHL2(); setHL2(getHL()); setHL(t3); return T::CC_EX; } template int CPUCore::di() { setIFF1(false); setIFF2(false); return T::CC_DI; } template int CPUCore::ei() { setIFF1(true); setIFF2(true); setAfterEI(); // no ints directly after this instr setSlowInstructions(); return T::CC_EI; } template int CPUCore::halt() { setHALT(true); setSlowInstructions(); if (!(getIFF1() || getIFF2())) { diHaltCallback.execute(); } return T::CC_HALT; } template template int CPUCore::im_N() { setIM(N); return T::CC_IM; } // LD A,I/R template template int CPUCore::ld_a_IR() { setA(get8()); byte f = getIFF2() ? V_FLAG : 0; if (T::isR800()) { f |= getF() & (C_FLAG | X_FLAG | Y_FLAG); f |= ZSTable[getA()]; } else { f |= getF() & C_FLAG; f |= ZSXYTable[getA()]; // see comment in the IRQ acceptance part of executeSlow(). setAfterLDAI(); // only Z80 (not R800) has this quirk setSlowInstructions(); } setF(f); return T::CC_LD_A_I; } // LD I/R,A template int CPUCore::ld_r_a() { // This code sequence: // XOR A / LD R,A / LD A,R // gives A=2 for Z80, but A=1 for R800. The difference can possibly be // explained by a difference in the relative time between writing the // new value to the R register and increasing the R register per M1 // cycle. Here we implemented the R800 behaviour by storing 'A-1' into // R, that's good enough for now. byte val = getA(); if (T::isR800()) val -= 1; setR(val); return T::CC_LD_A_I; } template int CPUCore::ld_i_a() { setI(getA()); return T::CC_LD_A_I; } // MULUB A,r template template int CPUCore::mulub_a_R() { assert(T::isR800()); // this instruction is R800-only // Verified on real R800: // YHXN flags are unchanged // SV flags are reset // Z flag is set when result is zero // C flag is set when result doesn't fit in 8-bit setHL(unsigned(getA()) * get8()); setF((getF() & (N_FLAG | H_FLAG | X_FLAG | Y_FLAG)) | 0 | // S_FLAG V_FLAG (getHL() ? 0 : Z_FLAG) | ((getHL() & 0xFF00) ? C_FLAG : 0)); return T::CC_MULUB; } // MULUW HL,ss template template int CPUCore::muluw_hl_SS() { assert(T::isR800()); // this instruction is R800-only // Verified on real R800: // YHXN flags are unchanged // SV flags are reset // Z flag is set when result is zero // C flag is set when result doesn't fit in 16-bit unsigned res = unsigned(getHL()) * get16(); setDE(res >> 16); setHL(res & 0xffff); setF((getF() & (N_FLAG | H_FLAG | X_FLAG | Y_FLAG)) | 0 | // S_FLAG V_FLAG (res ? 0 : Z_FLAG) | ((res & 0xFFFF0000) ? C_FLAG : 0)); return T::CC_MULUW; } // versions: // 1 -> initial version // 2 -> moved memptr from here to Z80TYPE (and not to R800TYPE) // 3 -> timing of the emulation changed (no changes in serialization) template template void CPUCore::serialize(Archive& ar, unsigned version) { T::serialize(ar, version); ar.serialize("regs", static_cast(*this)); if (ar.versionBelow(version, 2)) { unsigned memptr = 0; // dummy value (avoid warning) ar.serialize("memptr", memptr); T::setMemPtr(memptr); } if (ar.isLoader()) { invalidateMemCache(0x0000, 0x10000); } // don't serialize // IRQStatus // NMIStatus, nmiEdge // slowInstructions // exitLoop if (T::isR800() && ar.versionBelow(version, 3)) { motherboard.getMSXCliComm().printWarning( "Loading an old savestate: the timing of the R800 " "emulation has changed. This may cause synchronization " "problems in replay."); } } // Force template instantiation template class CPUCore; template class CPUCore; INSTANTIATE_SERIALIZE_METHODS(CPUCore); INSTANTIATE_SERIALIZE_METHODS(CPUCore); } // namespace openmsx openMSX-RELEASE_0_12_0/src/cpu/CPUCore.hh000066400000000000000000000321331257557151200176000ustar00rootroot00000000000000#ifndef CPUCORE_HH #define CPUCORE_HH #include "CPURegs.hh" #include "CacheLine.hh" #include "Probe.hh" #include "EmuTime.hh" #include "BooleanSetting.hh" #include "IntegerSetting.hh" #include "serialize_meta.hh" #include "openmsx.hh" #include "array_ref.hh" #include #include #include namespace openmsx { class MSXCPUInterface; class Scheduler; class MSXMotherBoard; class TclCallback; class TclObject; class Interpreter; enum Reg8 : int; enum Reg16 : int; class CPUBase {}; // only for bw-compat savestates template class CPUCore final : public CPUBase, public CPURegs, public CPU_POLICY { public: CPUCore(MSXMotherBoard& motherboard, const std::string& name, const BooleanSetting& traceSetting, TclCallback& diHaltCallback, EmuTime::param time); void setInterface(MSXCPUInterface* interf) { interface = interf; } /** * Reset the CPU. */ void doReset(EmuTime::param time); void execute(bool fastForward); /** Request to exit the main CPU emulation loop. * This method may only be called from the main thread. The CPU loop * will immediately be exited (current instruction will be finished, * but no new instruction will be executed). */ void exitCPULoopSync(); /** Similar to exitCPULoopSync(), but this method may be called from * any thread. Although now the loop will only be exited 'soon'. */ void exitCPULoopAsync(); void warp(EmuTime::param time); EmuTime::param getCurrentTime() const; void wait(EmuTime::param time); void waitCycles(unsigned cycles); void setNextSyncPoint(EmuTime::param time); void invalidateMemCache(unsigned start, unsigned size); bool isM1Cycle(unsigned address) const; void disasmCommand(Interpreter& interp, array_ref tokens, TclObject& result) const; /** * Raises the maskable interrupt count. * Devices should call MSXCPU::raiseIRQ instead, or use the IRQHelper class. */ void raiseIRQ(); /** * Lowers the maskable interrupt count. * Devices should call MSXCPU::lowerIRQ instead, or use the IRQHelper class. */ void lowerIRQ(); /** * Raises the non-maskable interrupt count. * Devices should call MSXCPU::raiseNMI instead, or use the IRQHelper class. */ void raiseNMI(); /** * Lowers the non-maskable interrupt count. * Devices should call MSXCPU::lowerNMI instead, or use the IRQHelper class. */ void lowerNMI(); /** * Change the clock freq. */ void setFreq(unsigned freq); template void serialize(Archive& ar, unsigned version); private: void execute2(bool fastForward); bool needExitCPULoop(); void setSlowInstructions(); void doSetFreq(); // Observer !! non-virtual !! void update(const Setting& setting); // memory cache const byte* readCacheLine[CacheLine::NUM]; byte* writeCacheLine[CacheLine::NUM]; bool readCacheTried [CacheLine::NUM]; bool writeCacheTried[CacheLine::NUM]; MSXMotherBoard& motherboard; Scheduler& scheduler; MSXCPUInterface* interface; const BooleanSetting& traceSetting; TclCallback& diHaltCallback; Probe IRQStatus; Probe IRQAccept; // dynamic freq BooleanSetting freqLocked; IntegerSetting freqValue; unsigned freq; // state machine variables int slowInstructions; int NMIStatus; /** * Set to true when there was a rising edge on the NMI line * (rising = non-active -> active). * Set to false when the CPU jumps to the NMI handler address. */ bool nmiEdge; std::atomic exitLoop; /** In sync with traceSetting.getBoolean(). */ bool tracingEnabled; /** 'normal' Z80 and Z80 in a turboR behave slightly different */ const bool isTurboR; inline void cpuTracePre(); inline void cpuTracePost(); void cpuTracePost_slow(); inline byte READ_PORT(unsigned port, unsigned cc); inline void WRITE_PORT(unsigned port, byte value, unsigned cc); template byte RDMEMslow(unsigned address, unsigned cc); template inline byte RDMEM_impl2(unsigned address, unsigned cc); template inline byte RDMEM_impl (unsigned address, unsigned cc); inline byte RDMEM_OPCODE(unsigned cc); inline byte RDMEM(unsigned address, unsigned cc); template unsigned RD_WORD_slow(unsigned address, unsigned cc); template inline unsigned RD_WORD_impl2(unsigned address, unsigned cc); template inline unsigned RD_WORD_impl (unsigned address, unsigned cc); inline unsigned RD_WORD_PC(unsigned cc); inline unsigned RD_WORD(unsigned address, unsigned cc); template void WRMEMslow(unsigned address, byte value, unsigned cc); template inline void WRMEM_impl2(unsigned address, byte value, unsigned cc); template inline void WRMEM_impl (unsigned address, byte value, unsigned cc); inline void WRMEM(unsigned address, byte value, unsigned cc); void WR_WORD_slow(unsigned address, unsigned value, unsigned cc); inline void WR_WORD(unsigned address, unsigned value, unsigned cc); template void WR_WORD_rev_slow(unsigned address, unsigned value, unsigned cc); template inline void WR_WORD_rev2(unsigned address, unsigned value, unsigned cc); template inline void WR_WORD_rev (unsigned address, unsigned value, unsigned cc); void executeInstructions(); inline void nmi(); inline void irq0(); inline void irq1(); inline void irq2(); void executeSlow(); template inline byte get8() const; template inline unsigned get16() const; template inline void set8 (byte x); template inline void set16(unsigned x); template inline int ld_R_R(); template inline int ld_sp_SS(); template inline int ld_SS_a(); template inline int ld_xhl_R(); template inline int ld_xix_R(); inline int ld_xhl_byte(); template inline int ld_xix_byte(); template inline int WR_NN_Y(unsigned reg); template inline int ld_xword_SS(); template inline int ld_xword_SS_ED(); template inline int ld_a_SS(); inline int ld_xbyte_a(); inline int ld_a_xbyte(); template inline int ld_R_byte(); template inline int ld_R_xhl(); template inline int ld_R_xix(); template inline unsigned RD_P_XX(); template inline int ld_SS_xword(); template inline int ld_SS_xword_ED(); template inline int ld_SS_word(); inline void ADC(byte reg); inline int adc_a_a(); template inline int adc_a_R(); inline int adc_a_byte(); inline int adc_a_xhl(); template inline int adc_a_xix(); inline void ADD(byte reg); inline int add_a_a(); template inline int add_a_R(); inline int add_a_byte(); inline int add_a_xhl(); template inline int add_a_xix(); inline void AND(byte reg); inline int and_a(); template inline int and_R(); inline int and_byte(); inline int and_xhl(); template inline int and_xix(); inline void CP(byte reg); inline int cp_a(); template inline int cp_R(); inline int cp_byte(); inline int cp_xhl(); template inline int cp_xix(); inline void OR(byte reg); inline int or_a(); template inline int or_R(); inline int or_byte(); inline int or_xhl(); template inline int or_xix(); inline void SBC(byte reg); inline int sbc_a_a(); template inline int sbc_a_R(); inline int sbc_a_byte(); inline int sbc_a_xhl(); template inline int sbc_a_xix(); inline void SUB(byte reg); inline int sub_a(); template inline int sub_R(); inline int sub_byte(); inline int sub_xhl(); template inline int sub_xix(); inline void XOR(byte reg); inline int xor_a(); template inline int xor_R(); inline int xor_byte(); inline int xor_xhl(); template inline int xor_xix(); inline byte DEC(byte reg); template inline int dec_R(); template inline int DEC_X(unsigned x); inline int dec_xhl(); template inline int dec_xix(); inline byte INC(byte reg); template inline int inc_R(); template inline int INC_X(unsigned x); inline int inc_xhl(); template inline int inc_xix(); template inline int adc_hl_SS(); inline int adc_hl_hl(); template inline int add_SS_TT(); template inline int add_SS_SS(); template inline int sbc_hl_SS(); inline int sbc_hl_hl(); template inline int dec_SS(); template inline int inc_SS(); template inline int bit_N_R(); template inline int bit_N_xhl(); template inline int bit_N_xix(unsigned a); template inline int res_N_R(); template inline byte RES_X(unsigned bit, unsigned addr); template inline int res_N_xhl(); template inline int res_N_xix_R(unsigned a); template inline int set_N_R(); template inline byte SET_X(unsigned bit, unsigned addr); template inline int set_N_xhl(); template inline int set_N_xix_R(unsigned a); inline byte RL(byte reg); template inline byte RL_X(unsigned x); template inline int rl_R(); inline int rl_xhl(); template inline int rl_xix_R(unsigned a); inline byte RLC(byte reg); template inline byte RLC_X(unsigned x); template inline int rlc_R(); inline int rlc_xhl(); template inline int rlc_xix_R(unsigned a); inline byte RR(byte reg); template inline byte RR_X(unsigned x); template inline int rr_R(); inline int rr_xhl(); template inline int rr_xix_R(unsigned a); inline byte RRC(byte reg); template inline byte RRC_X(unsigned x); template inline int rrc_R(); inline int rrc_xhl(); template inline int rrc_xix_R(unsigned a); inline byte SLA(byte reg); template inline byte SLA_X(unsigned x); template inline int sla_R(); inline int sla_xhl(); template inline int sla_xix_R(unsigned a); inline byte SLL(byte reg); template inline byte SLL_X(unsigned x); template inline int sll_R(); inline int sll_xhl(); template inline int sll_xix_R(unsigned a); inline int sll2(); inline byte SRA(byte reg); template inline byte SRA_X(unsigned x); template inline int sra_R(); inline int sra_xhl(); template inline int sra_xix_R(unsigned a); inline byte SRL(byte reg); template inline byte SRL_X(unsigned x); template inline int srl_R(); inline int srl_xhl(); template inline int srl_xix_R(unsigned a); inline int rla(); inline int rlca(); inline int rra(); inline int rrca(); inline int rld(); inline int rrd(); template inline void PUSH(unsigned reg); template inline int push_SS(); template inline unsigned POP(); template inline int pop_SS(); template inline int call(COND cond); template inline int rst(); template inline int RET(COND cond); template inline int ret(COND cond); inline int ret(); inline int retn(); template inline int jp_SS(); template inline int jp(COND cond); template inline int jr(COND cond); inline int djnz(); template inline int ex_xsp_SS(); template inline int in_R_c(); inline int in_a_byte(); template inline int out_c_R(); inline int out_c_0(); inline int out_byte_a(); inline int BLOCK_CP(int increase, bool repeat); inline int cpd(); inline int cpi(); inline int cpdr(); inline int cpir(); inline int BLOCK_LD(int increase, bool repeat); inline int ldd(); inline int ldi(); inline int lddr(); inline int ldir(); inline int BLOCK_IN(int increase, bool repeat); inline int ind(); inline int ini(); inline int indr(); inline int inir(); inline int BLOCK_OUT(int increase, bool repeat); inline int outd(); inline int outi(); inline int otdr(); inline int otir(); inline int nop(); inline int ccf(); inline int cpl(); inline int daa(); inline int neg(); inline int scf(); inline int ex_af_af(); inline int ex_de_hl(); inline int exx(); inline int di(); inline int ei(); inline int halt(); template inline int im_N(); template inline int ld_a_IR(); inline int ld_r_a(); inline int ld_i_a(); template int mulub_a_R(); template int muluw_hl_SS(); friend class MSXCPU; friend class Z80TYPE; friend class R800TYPE; }; class Z80TYPE; class R800TYPE; SERIALIZE_CLASS_VERSION(CPUCore, 3); SERIALIZE_CLASS_VERSION(CPUCore, 3); // keep these two the same } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/cpu/CPURegs.cc000066400000000000000000000023171257557151200175770ustar00rootroot00000000000000#include "CPURegs.hh" #include "serialize.hh" namespace openmsx { // version 1: Initial version. // version 2: Replaced 'afterEI' boolean with 'after' byte // (holds both afterEI and afterLDAI information). template void CPURegs::serialize(Archive& ar, unsigned version) { ar.serialize("af", AF_.w); ar.serialize("bc", BC_.w); ar.serialize("de", DE_.w); ar.serialize("hl", HL_.w); ar.serialize("af2", AF2_.w); ar.serialize("bc2", BC2_.w); ar.serialize("de2", DE2_.w); ar.serialize("hl2", HL2_.w); ar.serialize("ix", IX_.w); ar.serialize("iy", IY_.w); ar.serialize("pc", PC_.w); ar.serialize("sp", SP_.w); ar.serialize("i", I_); byte r = getR(); ar.serialize("r", r); // combined R_ and R2_ if (ar.isLoader()) setR(r); ar.serialize("im", IM_); ar.serialize("iff1", IFF1_); ar.serialize("iff2", IFF2_); assert(isSameAfter()); if (ar.versionBelow(version, 2)) { bool afterEI = false; // initialize to avoid warning ar.serialize("afterEI", afterEI); clearNextAfter(); if (afterEI) setAfterEI(); } else { ar.serialize("after", afterNext_); } copyNextAfter(); ar.serialize("halt", HALT_); } INSTANTIATE_SERIALIZE_METHODS(CPURegs); } // namespace openmsx openMSX-RELEASE_0_12_0/src/cpu/CPURegs.hh000066400000000000000000000275401257557151200176160ustar00rootroot00000000000000#ifndef CPUREGS_HH #define CPUREGS_HH #include "serialize_meta.hh" #include "openmsx.hh" #include "build-info.hh" #include namespace openmsx { template struct z80regpair_8bit; template<> struct z80regpair_8bit { byte l, h; }; template<> struct z80regpair_8bit { byte h, l; }; union z80regpair { z80regpair_8bit b; word w; }; class CPURegs { public: CPURegs(bool r800) : HALT_(0), Rmask(r800 ? 0xff : 0x7f) {} inline byte getA() const { return AF_.b.h; } inline byte getF() const { return AF_.b.l; } inline byte getB() const { return BC_.b.h; } inline byte getC() const { return BC_.b.l; } inline byte getD() const { return DE_.b.h; } inline byte getE() const { return DE_.b.l; } inline byte getH() const { return HL_.b.h; } inline byte getL() const { return HL_.b.l; } inline byte getA2() const { return AF2_.b.h; } inline byte getF2() const { return AF2_.b.l; } inline byte getB2() const { return BC2_.b.h; } inline byte getC2() const { return BC2_.b.l; } inline byte getD2() const { return DE2_.b.h; } inline byte getE2() const { return DE2_.b.l; } inline byte getH2() const { return HL2_.b.h; } inline byte getL2() const { return HL2_.b.l; } inline byte getIXh() const { return IX_.b.h; } inline byte getIXl() const { return IX_.b.l; } inline byte getIYh() const { return IY_.b.h; } inline byte getIYl() const { return IY_.b.l; } inline byte getPCh() const { return PC_.b.h; } inline byte getPCl() const { return PC_.b.l; } inline byte getSPh() const { return SP_.b.h; } inline byte getSPl() const { return SP_.b.l; } inline unsigned getAF() const { return AF_.w; } inline unsigned getBC() const { return BC_.w; } inline unsigned getDE() const { return DE_.w; } inline unsigned getHL() const { return HL_.w; } inline unsigned getAF2() const { return AF2_.w; } inline unsigned getBC2() const { return BC2_.w; } inline unsigned getDE2() const { return DE2_.w; } inline unsigned getHL2() const { return HL2_.w; } inline unsigned getIX() const { return IX_.w; } inline unsigned getIY() const { return IY_.w; } inline unsigned getPC() const { return PC_.w; } inline unsigned getSP() const { return SP_.w; } inline byte getIM() const { return IM_; } inline byte getI() const { return I_; } inline byte getR() const { return (R_ & Rmask) | (R2_ & ~Rmask); } inline bool getIFF1() const { return IFF1_; } inline bool getIFF2() const { return IFF2_; } inline byte getHALT() const { return HALT_; } inline void setA(byte x) { AF_.b.h = x; } inline void setF(byte x) { AF_.b.l = x; } inline void setB(byte x) { BC_.b.h = x; } inline void setC(byte x) { BC_.b.l = x; } inline void setD(byte x) { DE_.b.h = x; } inline void setE(byte x) { DE_.b.l = x; } inline void setH(byte x) { HL_.b.h = x; } inline void setL(byte x) { HL_.b.l = x; } inline void setA2(byte x) { AF2_.b.h = x; } inline void setF2(byte x) { AF2_.b.l = x; } inline void setB2(byte x) { BC2_.b.h = x; } inline void setC2(byte x) { BC2_.b.l = x; } inline void setD2(byte x) { DE2_.b.h = x; } inline void setE2(byte x) { DE2_.b.l = x; } inline void setH2(byte x) { HL2_.b.h = x; } inline void setL2(byte x) { HL2_.b.l = x; } inline void setIXh(byte x) { IX_.b.h = x; } inline void setIXl(byte x) { IX_.b.l = x; } inline void setIYh(byte x) { IY_.b.h = x; } inline void setIYl(byte x) { IY_.b.l = x; } inline void setPCh(byte x) { PC_.b.h = x; } inline void setPCl(byte x) { PC_.b.l = x; } inline void setSPh(byte x) { SP_.b.h = x; } inline void setSPl(byte x) { SP_.b.l = x; } inline void setAF(unsigned x) { AF_.w = x; } inline void setBC(unsigned x) { BC_.w = x; } inline void setDE(unsigned x) { DE_.w = x; } inline void setHL(unsigned x) { HL_.w = x; } inline void setAF2(unsigned x) { AF2_.w = x; } inline void setBC2(unsigned x) { BC2_.w = x; } inline void setDE2(unsigned x) { DE2_.w = x; } inline void setHL2(unsigned x) { HL2_.w = x; } inline void setIX(unsigned x) { IX_.w = x; } inline void setIY(unsigned x) { IY_.w = x; } inline void setPC(unsigned x) { PC_.w = x; } inline void setSP(unsigned x) { SP_.w = x; } inline void setIM(byte x) { IM_ = x; } inline void setI(byte x) { I_ = x; } inline void setR(byte x) { R_ = x; R2_ = x; } inline void setIFF1(bool x) { IFF1_ = x; } inline void setIFF2(bool x) { IFF2_ = x; } inline void setHALT(bool x) { HALT_ = (HALT_ & ~1) | (x ? 1 : 0); } inline void setExtHALT(bool x) { HALT_ = (HALT_ & ~2) | (x ? 2 : 0); } inline void incR(byte x) { R_ += x; } // These methods are used to set/query whether the previously // executed instruction was a 'EI' or 'LD A,{I,R}' instruction. // Initially this could only be queried between two // instructions, so e.g. after the EI instruction was executed // but before the next one has started, for emulation this is // good enough. But for debugging we still want to be able to // query this info during the execution of the next // instruction: e.g. a IO-watchpoint is triggered during the // execution of some OUT instruction, at the time we evaluate // the condition for that watchpoint, we still want to be able // to query whether the previous instruction was a EI // instruction. inline bool isSameAfter() const { // Between two instructions these two should be the same return after_ == afterNext_; } inline bool getAfterEI() const { assert(isSameAfter()); return (after_ & 0x01) != 0; } inline bool getAfterLDAI() const { assert(isSameAfter()); return (after_ & 0x02) != 0; } inline bool debugGetAfterEI() const { // Can be called during execution of an instruction return (after_ & 0x01) != 0; } inline void clearNextAfter() { // Right before executing an instruction this should be // cleared afterNext_ = 0x00; } inline bool isNextAfterClear() const { // In the fast code path we avoid calling clearNextAfter() // before every instruction. But in debug mode we want // to verify that this optimzation is valid. return afterNext_ == 0; } inline void setAfterEI() { // Set both after_ and afterNext_. Can only be called // at the end of an instruction (status of prev // instruction can't be queried anymore) assert(isNextAfterClear()); afterNext_ = after_ = 0x01; } inline void setAfterLDAI() { // Set both, see above. assert(isNextAfterClear()); afterNext_ = after_ = 0x02; } inline void copyNextAfter() { // At the end of an instruction, the next flags become // the current flags. setAfterEI/LDAI() already sets // both after_ and afterNext_, thus calling this method // is only required to clear the flags. Instructions // right after a EI or LD A,I/R instruction are always // executed in the slow code path. So this means that // in the fast code path we don't need to call // copyNextAfter(). after_ = afterNext_; } template void serialize(Archive& ar, unsigned version); private: z80regpair PC_; z80regpair AF_, BC_, DE_, HL_; z80regpair AF2_, BC2_, DE2_, HL2_; z80regpair IX_, IY_, SP_; bool IFF1_, IFF2_; byte after_, afterNext_; byte HALT_; byte IM_, I_; byte R_, R2_; // refresh = R & Rmask | R2 & ~Rmask const byte Rmask; // 0x7F for Z80, 0xFF for R800 }; SERIALIZE_CLASS_VERSION(CPURegs, 2); /* The above implementation uses a union to access the upper/lower 8 bits in a * 16 bit value. At some point in the past I replaced this with the * implementation below because it generated faster code. Though when I measure * it now, the original version is faster again. * TODO need to investigate this further. */ #if 0 class CPURegs { public: inline byte getA() const { return AF >> 8; } inline byte getF() const { return AF & 255; } inline byte getB() const { return BC >> 8; } inline byte getC() const { return BC & 255; } inline byte getD() const { return DE >> 8; } inline byte getE() const { return DE & 255; } inline byte getH() const { return HL >> 8; } inline byte getL() const { return HL & 255; } inline byte getA2() const { return AF2 >> 8; } inline byte getF2() const { return AF2 & 255; } inline byte getB2() const { return BC2 >> 8; } inline byte getC2() const { return BC2 & 255; } inline byte getD2() const { return DE2 >> 8; } inline byte getE2() const { return DE2 & 255; } inline byte getH2() const { return HL2 >> 8; } inline byte getL2() const { return HL2 & 255; } inline byte getIXh() const { return IX >> 8; } inline byte getIXl() const { return IX & 255; } inline byte getIYh() const { return IY >> 8; } inline byte getIYl() const { return IY & 255; } inline byte getPCh() const { return PC >> 8; } inline byte getPCl() const { return PC & 255; } inline byte getSPh() const { return SP >> 8; } inline byte getSPl() const { return SP & 255; } inline word getAF() const { return AF; } inline word getBC() const { return BC; } inline word getDE() const { return DE; } inline word getHL() const { return HL; } inline word getAF2() const { return AF2; } inline word getBC2() const { return BC2; } inline word getDE2() const { return DE2; } inline word getHL2() const { return HL2; } inline word getIX() const { return IX; } inline word getIY() const { return IY; } inline word getPC() const { return PC; } inline word getSP() const { return SP; } inline byte getIM() const { return IM; } inline byte getI() const { return I; } inline byte getR() const { return (R & 0x7F) | (R2 & 0x80); } inline bool getIFF1() const { return IFF1; } inline bool getIFF2() const { return IFF2; } inline bool getHALT() const { return HALT; } inline void setA(byte x) { AF = (AF & 0x00FF) | (x << 8); } inline void setF(byte x) { AF = (AF & 0xFF00) | x; } inline void setB(byte x) { BC = (BC & 0x00FF) | (x << 8); } inline void setC(byte x) { BC = (BC & 0xFF00) | x; } inline void setD(byte x) { DE = (DE & 0x00FF) | (x << 8); } inline void setE(byte x) { DE = (DE & 0xFF00) | x; } inline void setH(byte x) { HL = (HL & 0x00FF) | (x << 8); } inline void setL(byte x) { HL = (HL & 0xFF00) | x; } inline void setA2(byte x) { AF2 = (AF2 & 0x00FF) | (x << 8); } inline void setF2(byte x) { AF2 = (AF2 & 0xFF00) | x; } inline void setB2(byte x) { BC2 = (BC2 & 0x00FF) | (x << 8); } inline void setC2(byte x) { BC2 = (BC2 & 0xFF00) | x; } inline void setD2(byte x) { DE2 = (DE2 & 0x00FF) | (x << 8); } inline void setE2(byte x) { DE2 = (DE2 & 0xFF00) | x; } inline void setH2(byte x) { HL2 = (HL2 & 0x00FF) | (x << 8); } inline void setL2(byte x) { HL2 = (HL2 & 0xFF00) | x; } inline void setIXh(byte x) { IX = (IX & 0x00FF) | (x << 8); } inline void setIXl(byte x) { IX = (IX & 0xFF00) | x; } inline void setIYh(byte x) { IY = (IY & 0x00FF) | (x << 8); } inline void setIYl(byte x) { IY = (IY & 0xFF00) | x; } inline void setPCh(byte x) { PC = (PC & 0x00FF) | (x << 8); } inline void setPCl(byte x) { PC = (PC & 0xFF00) | x; } inline void setSPh(byte x) { SP = (SP & 0x00FF) | (x << 8); } inline void setSPl(byte x) { SP = (SP & 0xFF00) | x; } inline void setAF(word x) { AF = x; } inline void setBC(word x) { BC = x; } inline void setDE(word x) { DE = x; } inline void setHL(word x) { HL = x; } inline void setAF2(word x) { AF2 = x; } inline void setBC2(word x) { BC2 = x; } inline void setDE2(word x) { DE2 = x; } inline void setHL2(word x) { HL2 = x; } inline void setIX(word x) { IX = x; } inline void setIY(word x) { IY = x; } inline void setPC(word x) { PC = x; } inline void setSP(word x) { SP = x; } inline void setIM(byte x) { IM = x; } inline void setI(byte x) { I = x; } inline void setR(byte x) { R = x; R2 = x; } inline void setIFF1(bool x) { IFF1 = x; } inline void setIFF2(bool x) { IFF2 = x; } inline void setHALT(bool x) { HALT = x; } inline void incR(byte x) { R += x; } private: word AF, BC, DE, HL; word AF2, BC2, DE2, HL2; word IX, IY, PC, SP; bool IFF1, IFF2, HALT; byte IM, I; byte R, R2; // refresh = R&127 | R2&128 }; #endif } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/cpu/CacheLine.hh000066400000000000000000000005431257557151200201530ustar00rootroot00000000000000#ifndef CACHELINE_HH #define CACHELINE_HH namespace openmsx { namespace CacheLine { static const unsigned BITS = 8; // 256 bytes static const unsigned SIZE = 1 << BITS; static const unsigned NUM = 0x10000 / SIZE; static const unsigned LOW = SIZE - 1; static const unsigned HIGH = 0xFFFF - LOW; } // namespace CacheLine } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/cpu/Dasm.cc000066400000000000000000000047441257557151200172210ustar00rootroot00000000000000#include "Dasm.hh" #include "DasmTables.hh" #include "MSXCPUInterface.hh" #include "StringOp.hh" namespace openmsx { static char sign(unsigned char a) { return (a & 128) ? '-' : '+'; } static int abs(unsigned char a) { return (a & 128) ? (256 - a) : a; } unsigned dasm(const MSXCPUInterface& interf, word pc, byte buf[4], std::string& dest, EmuTime::param time) { const char* s; unsigned i = 0; const char* r = nullptr; buf[0] = interf.peekMem(pc, time); switch (buf[0]) { case 0xCB: buf[1] = interf.peekMem(pc + 1, time); s = mnemonic_cb[buf[1]]; i = 2; break; case 0xED: buf[1] = interf.peekMem(pc + 1, time); s = mnemonic_ed[buf[1]]; i = 2; break; case 0xDD: case 0xFD: r = (buf[0] == 0xDD) ? "ix" : "iy"; buf[1] = interf.peekMem(pc + 1, time); if (buf[1] != 0xcb) { s = mnemonic_xx[buf[1]]; i = 2; } else { buf[2] = interf.peekMem(pc + 2, time); buf[3] = interf.peekMem(pc + 3, time); s = mnemonic_xx_cb[buf[3]]; i = 4; } break; default: s = mnemonic_main[buf[0]]; i = 1; } for (int j = 0; s[j]; ++j) { switch (s[j]) { case 'B': buf[i] = interf.peekMem(pc + i, time); dest += '#' + StringOp::toHexString( static_cast(buf[i]), 2); i += 1; break; case 'R': buf[i] = interf.peekMem(pc + i, time); dest += '#' + StringOp::toHexString( (pc + 2 + static_cast(buf[i])) & 0xFFFF, 4); i += 1; break; case 'W': buf[i + 0] = interf.peekMem(pc + i + 0, time); buf[i + 1] = interf.peekMem(pc + i + 1, time); dest += '#' + StringOp::toHexString(buf[i] + buf[i + 1] * 256, 4); i += 2; break; case 'X': buf[i] = interf.peekMem(pc + i, time); dest += '(' + std::string(r) + sign(buf[i]) + '#' + StringOp::toHexString(abs(buf[i]), 2) + ')'; i += 1; break; case 'Y': dest += std::string(r) + sign(buf[2]) + '#' + StringOp::toHexString(abs(buf[2]), 2); break; case 'I': dest += r; break; case '!': dest = "db #ED,#" + StringOp::toHexString(buf[1], 2) + " "; return 2; case '@': dest = "db #" + StringOp::toHexString(buf[0], 2) + " "; return 1; case '#': dest = "db #" + StringOp::toHexString(buf[0], 2) + ",#CB,#" + StringOp::toHexString(buf[2], 2) + ' '; return 2; case ' ': { dest.resize(7, ' '); break; } default: dest += s[j]; break; } } dest.resize(19, ' '); return i; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/cpu/Dasm.hh000066400000000000000000000013471257557151200172270ustar00rootroot00000000000000#ifndef DASM_HH #define DASM_HH #include "EmuTime.hh" #include "openmsx.hh" #include namespace openmsx { class MSXCPUInterface; /** Disassemble * @param interf The CPU interface, used to peek bytes from memory * @param pc The position (program counter) where to start disassembling * @param buf The bytes that form this opcode (max 4). The buffer is only * filled with the min required bytes (see return value) * @param dest String representation of the disassembled opcode * @param time TODO * @return Length of the disassembled opcode in bytes */ unsigned dasm(const MSXCPUInterface& interf, word pc, byte buf[4], std::string& dest, EmuTime::param time); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/cpu/DebugCondition.cc000066400000000000000000000003621257557151200212220ustar00rootroot00000000000000#include "DebugCondition.hh" namespace openmsx { unsigned DebugCondition::lastId = 0; DebugCondition::DebugCondition(TclObject command, TclObject condition) : BreakPointBase(command, condition) , id(++lastId) { } } // namespace openmsx openMSX-RELEASE_0_12_0/src/cpu/DebugCondition.hh000066400000000000000000000006661257557151200212430ustar00rootroot00000000000000#ifndef DEBUGCONDITION_HH #define DEBUGCONDITION_HH #include "BreakPointBase.hh" namespace openmsx { /** General debugger condition * Like breakpoints, but not tied to a specifc address. */ class DebugCondition final : public BreakPointBase { public: DebugCondition(TclObject command, TclObject condition); unsigned getId() const { return id; } private: unsigned id; static unsigned lastId; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/cpu/IRQHelper.cc000066400000000000000000000010661257557151200201220ustar00rootroot00000000000000#include "IRQHelper.hh" #include "MSXCPU.hh" #include "DeviceConfig.hh" namespace openmsx { // class IRQSource IRQSource::IRQSource(MSXCPU& cpu_) : cpu(cpu_) { } void IRQSource::raise() { cpu.raiseIRQ(); } void IRQSource::lower() { cpu.lowerIRQ(); } // class OptionalIRQ OptionalIRQ::OptionalIRQ(MSXCPU& cpu, const DeviceConfig& config) : cpu(config.getChildDataAsBool("irq_connected", true) ? &cpu : nullptr) { } void OptionalIRQ::raise() { if (cpu) cpu->raiseIRQ(); } void OptionalIRQ::lower() { if (cpu) cpu->lowerIRQ(); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/cpu/IRQHelper.hh000066400000000000000000000050031257557151200201270ustar00rootroot00000000000000#ifndef IRQHELPER_HH #define IRQHELPER_HH #include "Probe.hh" #include "MSXMotherBoard.hh" #include "noncopyable.hh" #include "serialize.hh" #include namespace openmsx { class MSXMotherBoard; class MSXCPU; class DeviceConfig; /** Helper class for doing interrupt request (IRQ) administration. * IRQ is either enabled or disabled; when enabled it contributes * one to the CPU IRQ count, when disabled zero. * Calling set() in enabled state does nothing; * neither does calling reset() in disabled state. */ // policy class for IRQ source class IRQSource { protected: explicit IRQSource(MSXCPU& cpu); void raise(); void lower(); private: MSXCPU& cpu; }; // supports tag in hardware config class OptionalIRQ { protected: OptionalIRQ(MSXCPU& cpu, const DeviceConfig& config); void raise(); void lower(); private: MSXCPU* const cpu; }; // generic implementation template class IntHelper : public SOURCE, private noncopyable { public: /** Create a new IntHelper. * Initially there is no interrupt request on the bus. */ IntHelper(MSXMotherBoard& motherboard, const std::string& name) : SOURCE(motherboard.getCPU()) , request(motherboard.getDebugger(), name, "Outgoing IRQ signal.", false) { } IntHelper(MSXMotherBoard& motherboard, const std::string& name, const DeviceConfig& config) : SOURCE(motherboard.getCPU(), config) , request(motherboard.getDebugger(), name, "Outgoing IRQ signal.", false) { } /** Destroy this IntHelper. * Resets interrupt request if it is active. */ ~IntHelper() { reset(); } /** Set the interrupt request on the bus. */ inline void set() { if (!request) { request = true; SOURCE::raise(); } } /** Reset the interrupt request on the bus. */ inline void reset() { if (request) { request = false; SOURCE::lower(); } } /** Convenience function: calls set() or reset(). */ inline void set(bool s) { if (s) { set(); } else { reset(); } } /** Get the interrupt state. * @return true iff interrupt request is active. */ inline bool getState() const { return request; } template void serialize(Archive& ar, unsigned /*version*/) { bool pending = request; ar.serialize("pending", pending); if (ar.isLoader()) { set(pending); } } private: Probe request; }; // convenience types using IRQHelper = IntHelper; using OptionalIRQHelper = IntHelper; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/cpu/MSXCPU.cc000066400000000000000000000251071257557151200173500ustar00rootroot00000000000000#include "MSXCPU.hh" #include "MSXMotherBoard.hh" #include "Debugger.hh" #include "Scheduler.hh" #include "IntegerSetting.hh" #include "CPUCore.hh" #include "Z80.hh" #include "R800.hh" #include "TclObject.hh" #include "memory.hh" #include "outer.hh" #include "serialize.hh" #include "unreachable.hh" #include using std::string; using std::vector; namespace openmsx { MSXCPU::MSXCPU(MSXMotherBoard& motherboard_) : motherboard(motherboard_) , traceSetting( motherboard.getCommandController(), "cputrace", "CPU tracing on/off", false, Setting::DONT_SAVE) , diHaltCallback( motherboard.getCommandController(), "di_halt_callback", "Tcl proc called when the CPU executed a DI/HALT sequence") , z80(make_unique>( motherboard, "z80", traceSetting, diHaltCallback, EmuTime::zero)) , r800(motherboard.isTurboR() ? make_unique>( motherboard, "r800", traceSetting, diHaltCallback, EmuTime::zero) : nullptr) , timeInfo(motherboard.getMachineInfoCommand()) , z80FreqInfo(motherboard.getMachineInfoCommand(), "z80_freq", *z80) , r800FreqInfo(r800 ? make_unique( motherboard.getMachineInfoCommand(), "r800_freq", *r800) : nullptr) , debuggable(motherboard_) , reference(EmuTime::zero) { z80Active = true; // setActiveCPU(CPU_Z80); newZ80Active = z80Active; motherboard.getDebugger().setCPU(this); motherboard.getScheduler().setCPU(this); traceSetting.attach(*this); z80->freqLocked.attach(*this); z80->freqValue.attach(*this); if (r800) { r800->freqLocked.attach(*this); r800->freqValue.attach(*this); } } MSXCPU::~MSXCPU() { traceSetting.detach(*this); z80->freqLocked.detach(*this); z80->freqValue.detach(*this); if (r800) { r800->freqLocked.detach(*this); r800->freqValue.detach(*this); } motherboard.getScheduler().setCPU(nullptr); motherboard.getDebugger() .setCPU(nullptr); } void MSXCPU::setInterface(MSXCPUInterface* interface) { z80 ->setInterface(interface); if (r800) r800->setInterface(interface); } void MSXCPU::doReset(EmuTime::param time) { z80 ->doReset(time); if (r800) r800->doReset(time); reference = time; } void MSXCPU::setActiveCPU(CPUType cpu) { if (cpu == CPU_R800) assert(r800); bool tmp = cpu == CPU_Z80; if (tmp != z80Active) { exitCPULoopSync(); newZ80Active = tmp; } } void MSXCPU::setDRAMmode(bool dram) { assert(r800); r800->setDRAMmode(dram); } void MSXCPU::execute(bool fastForward) { if (z80Active != newZ80Active) { EmuTime time = getCurrentTime(); z80Active = newZ80Active; z80Active ? z80 ->warp(time) : r800->warp(time); invalidateMemCache(0x0000, 0x10000); } z80Active ? z80 ->execute(fastForward) : r800->execute(fastForward); } void MSXCPU::exitCPULoopSync() { z80Active ? z80 ->exitCPULoopSync() : r800->exitCPULoopSync(); } void MSXCPU::exitCPULoopAsync() { z80Active ? z80 ->exitCPULoopAsync() : r800->exitCPULoopAsync(); } EmuTime::param MSXCPU::getCurrentTime() const { return z80Active ? z80 ->getCurrentTime() : r800->getCurrentTime(); } void MSXCPU::setNextSyncPoint(EmuTime::param time) { z80Active ? z80 ->setNextSyncPoint(time) : r800->setNextSyncPoint(time); } void MSXCPU::updateVisiblePage(byte page, byte primarySlot, byte secondarySlot) { invalidateMemCache(page * 0x4000, 0x4000); if (r800) r800->updateVisiblePage(page, primarySlot, secondarySlot); } void MSXCPU::invalidateMemCache(word start, unsigned size) { z80Active ? z80 ->invalidateMemCache(start, size) : r800->invalidateMemCache(start, size); } void MSXCPU::raiseIRQ() { z80 ->raiseIRQ(); if (r800) r800->raiseIRQ(); } void MSXCPU::lowerIRQ() { z80 ->lowerIRQ(); if (r800) r800->lowerIRQ(); } void MSXCPU::raiseNMI() { z80 ->raiseNMI(); if (r800) r800->raiseNMI(); } void MSXCPU::lowerNMI() { z80 ->lowerNMI(); if (r800) r800->lowerNMI(); } bool MSXCPU::isM1Cycle(unsigned address) const { return z80Active ? z80 ->isM1Cycle(address) : r800->isM1Cycle(address); } void MSXCPU::setZ80Freq(unsigned freq) { z80->setFreq(freq); } void MSXCPU::wait(EmuTime::param time) { z80Active ? z80 ->wait(time) : r800->wait(time); } void MSXCPU::waitCycles(unsigned cycles) { z80Active ? z80 ->waitCycles(cycles) : r800->waitCycles(cycles); } void MSXCPU::waitCyclesR800(unsigned cycles) { if (isR800Active()) { r800->waitCycles(cycles); } } CPURegs& MSXCPU::getRegisters() { if (z80Active) { return *z80; } else { return *r800; } } void MSXCPU::update(const Setting& setting) { z80 ->update(setting); if (r800) r800->update(setting); exitCPULoopSync(); } // Command void MSXCPU::disasmCommand( Interpreter& interp, array_ref tokens, TclObject& result) const { z80Active ? z80 ->disasmCommand(interp, tokens, result) : r800->disasmCommand(interp, tokens, result); } void MSXCPU::setPaused(bool paused) { if (z80Active) { z80 ->setExtHALT(paused); z80 ->exitCPULoopSync(); } else { r800->setExtHALT(paused); r800->exitCPULoopSync(); } } // class TimeInfoTopic MSXCPU::TimeInfoTopic::TimeInfoTopic(InfoCommand& machineInfoCommand) : InfoTopic(machineInfoCommand, "time") { } void MSXCPU::TimeInfoTopic::execute( array_ref /*tokens*/, TclObject& result) const { auto& cpu = OUTER(MSXCPU, timeInfo); EmuDuration dur = cpu.getCurrentTime() - cpu.reference; result.setDouble(dur.toDouble()); } string MSXCPU::TimeInfoTopic::help(const vector& /*tokens*/) const { return "Prints the time in seconds that the MSX is powered on\n"; } // class CPUFreqInfoTopic MSXCPU::CPUFreqInfoTopic::CPUFreqInfoTopic( InfoCommand& machineInfoCommand, const string& name, CPUClock& clock_) : InfoTopic(machineInfoCommand, name) , clock(clock_) { } void MSXCPU::CPUFreqInfoTopic::execute( array_ref /*tokens*/, TclObject& result) const { result.setInt(clock.getFreq()); } string MSXCPU::CPUFreqInfoTopic::help(const vector& /*tokens*/) const { return "Returns the actual frequency of this CPU.\n" "This frequency can vary because:\n" " - the user has overridden the freq via the '{z80,r800}_freq' setting\n" " - (only on some MSX machines) the MSX software can switch the Z80 between 2 frequencies\n" "See also the '{z80,r800}_freq_locked' setting.\n"; } // class Debuggable static const char* const CPU_REGS_DESC = "Registers of the active CPU (Z80 or R800).\n" "Each byte in this debuggable represents one 8 bit register:\n" " 0 -> A 1 -> F 2 -> B 3 -> C\n" " 4 -> D 5 -> E 6 -> H 7 -> L\n" " 8 -> A' 9 -> F' 10 -> B' 11 -> C'\n" " 12 -> D' 13 -> E' 14 -> H' 15 -> L'\n" " 16 -> IXH 17 -> IXL 18 -> IYH 19 -> IYL\n" " 20 -> PCH 21 -> PCL 22 -> SPH 23 -> SPL\n" " 24 -> I 25 -> R 26 -> IM 27 -> IFF1/2\n" "The last position (27) contains the IFF1 and IFF2 flags in respectively\n" "bit 0 and 1. Bit 2 contains 'IFF1 AND last-instruction-was-not-EI', so\n" "this effectively indicates that the CPU could accept an interrupt at\n" "the start of the current instruction.\n"; MSXCPU::Debuggable::Debuggable(MSXMotherBoard& motherboard) : SimpleDebuggable(motherboard, "CPU regs", CPU_REGS_DESC, 28) { } byte MSXCPU::Debuggable::read(unsigned address) { auto& cpu = OUTER(MSXCPU, debuggable); const CPURegs& regs = cpu.getRegisters(); switch (address) { case 0: return regs.getA(); case 1: return regs.getF(); case 2: return regs.getB(); case 3: return regs.getC(); case 4: return regs.getD(); case 5: return regs.getE(); case 6: return regs.getH(); case 7: return regs.getL(); case 8: return regs.getA2(); case 9: return regs.getF2(); case 10: return regs.getB2(); case 11: return regs.getC2(); case 12: return regs.getD2(); case 13: return regs.getE2(); case 14: return regs.getH2(); case 15: return regs.getL2(); case 16: return regs.getIXh(); case 17: return regs.getIXl(); case 18: return regs.getIYh(); case 19: return regs.getIYl(); case 20: return regs.getPCh(); case 21: return regs.getPCl(); case 22: return regs.getSPh(); case 23: return regs.getSPl(); case 24: return regs.getI(); case 25: return regs.getR(); case 26: return regs.getIM(); case 27: return 1 * regs.getIFF1() + 2 * regs.getIFF2() + 4 * (regs.getIFF1() && !regs.debugGetAfterEI()); default: UNREACHABLE; return 0; } } void MSXCPU::Debuggable::write(unsigned address, byte value) { auto& cpu = OUTER(MSXCPU, debuggable); CPURegs& regs = cpu.getRegisters(); switch (address) { case 0: regs.setA(value); break; case 1: regs.setF(value); break; case 2: regs.setB(value); break; case 3: regs.setC(value); break; case 4: regs.setD(value); break; case 5: regs.setE(value); break; case 6: regs.setH(value); break; case 7: regs.setL(value); break; case 8: regs.setA2(value); break; case 9: regs.setF2(value); break; case 10: regs.setB2(value); break; case 11: regs.setC2(value); break; case 12: regs.setD2(value); break; case 13: regs.setE2(value); break; case 14: regs.setH2(value); break; case 15: regs.setL2(value); break; case 16: regs.setIXh(value); break; case 17: regs.setIXl(value); break; case 18: regs.setIYh(value); break; case 19: regs.setIYl(value); break; case 20: regs.setPCh(value); break; case 21: regs.setPCl(value); break; case 22: regs.setSPh(value); break; case 23: regs.setSPl(value); break; case 24: regs.setI(value); break; case 25: regs.setR(value); break; case 26: if (value < 3) regs.setIM(value); break; case 27: regs.setIFF1((value & 0x01) != 0); regs.setIFF2((value & 0x02) != 0); // can't change afterEI break; default: UNREACHABLE; } } // version 1: initial version // version 2: activeCPU,newCPU -> z80Active,newZ80Active template void MSXCPU::serialize(Archive& ar, unsigned version) { if (ar.versionAtLeast(version, 2)) { ar.serialize("z80", *z80); if (r800) ar.serialize("r800", *r800); ar.serialize("z80Active", z80Active); ar.serialize("newZ80Active", newZ80Active); } else { // backwards-compatibility assert(ar.isLoader()); ar.serializeWithID("z80", *z80); if (r800) ar.serializeWithID("r800", *r800); CPUBase* activeCPU = nullptr; CPUBase* newCPU = nullptr; ar.serializePointerID("activeCPU", activeCPU); ar.serializePointerID("newCPU", newCPU); z80Active = activeCPU == z80.get(); if (newCPU) { newZ80Active = newCPU == z80.get(); } else { newZ80Active = z80Active; } } ar.serialize("resetTime", reference); } INSTANTIATE_SERIALIZE_METHODS(MSXCPU); } // namespace openmsx openMSX-RELEASE_0_12_0/src/cpu/MSXCPU.hh000066400000000000000000000125701257557151200173620ustar00rootroot00000000000000#ifndef MSXCPU_HH #define MSXCPU_HH #include "InfoTopic.hh" #include "SimpleDebuggable.hh" #include "Observer.hh" #include "BooleanSetting.hh" #include "EmuTime.hh" #include "TclCallback.hh" #include "noncopyable.hh" #include "serialize_meta.hh" #include "openmsx.hh" #include "array_ref.hh" #include namespace openmsx { class MSXMotherBoard; class MSXCPUInterface; class CPUClock; class CPURegs; class Z80TYPE; class R800TYPE; template class CPUCore; class TclObject; class Interpreter; class MSXCPU final : private Observer, private noncopyable { public: enum CPUType { CPU_Z80, CPU_R800 }; explicit MSXCPU(MSXMotherBoard& motherboard); ~MSXCPU(); /** Reset CPU. * Requires CPU is not in the middle of an instruction, * so exitCPULoop was called and execute() method returned. */ void doReset(EmuTime::param time); /** Switch between Z80/R800. */ void setActiveCPU(CPUType cpu); /** Sets DRAM or ROM mode (influences memory access speed for R800). */ void setDRAMmode(bool dram); /** Inform CPU of bank switch. This will invalidate memory cache and * update memory timings on R800. */ void updateVisiblePage(byte page, byte primarySlot, byte secondarySlot); /** Invalidate the CPU its cache for the interval [start, start + size) * For example MSXMemoryMapper and MSXGameCartrigde need to call this * method when a 'memory switch' occurs. */ void invalidateMemCache(word start, unsigned size); /** This method raises a maskable interrupt. A device may call this * method more than once. If the device wants to lower the * interrupt again it must call the lowerIRQ() method exactly as * many times. * Before using this method take a look at IRQHelper. */ void raiseIRQ(); /** This methods lowers the maskable interrupt again. A device may never * call this method more often than it called the method * raiseIRQ(). * Before using this method take a look at IRQHelper. */ void lowerIRQ(); /** This method raises a non-maskable interrupt. A device may call this * method more than once. If the device wants to lower the * interrupt again it must call the lowerNMI() method exactly as * many times. * Before using this method take a look at IRQHelper. */ void raiseNMI(); /** This methods lowers the non-maskable interrupt again. A device may never * call this method more often than it called the method * raiseNMI(). * Before using this method take a look at IRQHelper. */ void lowerNMI(); /** Should only be used from within a MSXDevice::readMem() method. * Returns true if that read was the first byte of an instruction * (the Z80 M1 pin is active). This implementation is not 100% * accurate, but good enough for now. */ bool isM1Cycle(unsigned address) const; /** See CPUCore::exitCPULoopsync() */ void exitCPULoopSync(); /** See CPUCore::exitCPULoopAsync() */ void exitCPULoopAsync(); /** Is the R800 currently active? */ bool isR800Active() const { return !z80Active; } /** Switch the Z80 clock freq. */ void setZ80Freq(unsigned freq); void setInterface(MSXCPUInterface* interf); void disasmCommand(Interpreter& interp, array_ref tokens, TclObject& result) const; /** (un)pause CPU. During pause the CPU executes NOP instructions * continuously (just like during HALT). Used by turbor hw pause. */ void setPaused(bool paused); void setNextSyncPoint(EmuTime::param time); void wait(EmuTime::param time); void waitCycles(unsigned cycles); void waitCyclesR800(unsigned cycles); CPURegs& getRegisters(); template void serialize(Archive& ar, unsigned version); private: // only for MSXMotherBoard void execute(bool fastForward); friend class MSXMotherBoard; /** The time returned by this method is not safe to use for Scheduler * or IO related stuff (because it can sometimes already be higher then * still to be scheduled sync points). Use Scheduler::getCurrentTime() * instead. * TODO is this comment still true? */ EmuTime::param getCurrentTime() const; // Observer void update(const Setting& setting) override; MSXMotherBoard& motherboard; BooleanSetting traceSetting; TclCallback diHaltCallback; const std::unique_ptr> z80; const std::unique_ptr> r800; // can be nullptr struct TimeInfoTopic final : InfoTopic { TimeInfoTopic(InfoCommand& machineInfoCommand); void execute(array_ref tokens, TclObject& result) const override; std::string help (const std::vector& tokens) const override; } timeInfo; class CPUFreqInfoTopic final : public InfoTopic { public: CPUFreqInfoTopic(InfoCommand& machineInfoCommand, const std::string& name, CPUClock& clock); void execute(array_ref tokens, TclObject& result) const override; std::string help (const std::vector& tokens) const override; private: CPUClock& clock; }; CPUFreqInfoTopic z80FreqInfo; // always present const std::unique_ptr r800FreqInfo; // can be nullptr struct Debuggable final : SimpleDebuggable { Debuggable(MSXMotherBoard& motherboard); byte read(unsigned address) override; void write(unsigned address, byte value) override; } debuggable; EmuTime reference; bool z80Active; bool newZ80Active; }; SERIALIZE_CLASS_VERSION(MSXCPU, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/cpu/MSXCPUInterface.cc000066400000000000000000000777311257557151200212030ustar00rootroot00000000000000#include "MSXCPUInterface.hh" #include "DebugCondition.hh" #include "DummyDevice.hh" #include "CommandException.hh" #include "TclObject.hh" #include "Interpreter.hh" #include "Reactor.hh" #include "MSXMotherBoard.hh" #include "MSXCPU.hh" #include "VDPIODelay.hh" #include "CliComm.hh" #include "MSXMultiIODevice.hh" #include "MSXMultiMemDevice.hh" #include "MSXWatchIODevice.hh" #include "MSXException.hh" #include "CartridgeSlotManager.hh" #include "EventDistributor.hh" #include "Event.hh" #include "DeviceFactory.hh" #include "ReadOnlySetting.hh" #include "serialize.hh" #include "StringOp.hh" #include "checked_cast.hh" #include "memory.hh" #include "outer.hh" #include "stl.hh" #include "unreachable.hh" #include #include #include #include #include using std::string; using std::vector; using std::min; using std::shared_ptr; namespace openmsx { // Global variables bool MSXCPUInterface::breaked = false; bool MSXCPUInterface::continued = false; bool MSXCPUInterface::step = false; MSXCPUInterface::BreakPoints MSXCPUInterface::breakPoints; //TODO watchpoints MSXCPUInterface::Conditions MSXCPUInterface::conditions; static std::unique_ptr breakedSetting; static unsigned breakedSettingCount = 0; // Bitfields used in the disallowReadCache and disallowWriteCache arrays static const byte SECUNDARY_SLOT_BIT = 0x01; static const byte MEMORY_WATCH_BIT = 0x02; static const byte GLOBAL_WRITE_BIT = 0x04; MSXCPUInterface::MSXCPUInterface(MSXMotherBoard& motherBoard_) : memoryDebug (motherBoard_) , slottedMemoryDebug(motherBoard_) , ioDebug (motherBoard_) , slotInfo(motherBoard_.getMachineInfoCommand()) , subSlottedInfo(motherBoard_.getMachineInfoCommand()) , externalSlotInfo(motherBoard_.getMachineInfoCommand()) , inputPortInfo (motherBoard_.getMachineInfoCommand()) , outputPortInfo(motherBoard_.getMachineInfoCommand()) , dummyDevice(DeviceFactory::createDummyDevice( *motherBoard_.getMachineConfig())) , msxcpu(motherBoard_.getCPU()) , cliComm(motherBoard_.getMSXCliComm()) , motherBoard(motherBoard_) , fastForward(false) { for (int port = 0; port < 256; ++port) { IO_In [port] = dummyDevice.get(); IO_Out[port] = dummyDevice.get(); } for (int primSlot = 0; primSlot < 4; ++primSlot) { primarySlotState[primSlot] = 0; secondarySlotState[primSlot] = 0; expanded[primSlot] = 0; subSlotRegister[primSlot] = 0; for (int secSlot = 0; secSlot < 4; ++secSlot) { for (int page = 0; page < 4; ++page) { slotLayout[primSlot][secSlot][page] = dummyDevice.get(); } } } for (auto& dev : visibleDevices) { dev = dummyDevice.get(); } // initially allow all regions to be cached memset(disallowReadCache, 0, sizeof(disallowReadCache)); memset(disallowWriteCache, 0, sizeof(disallowWriteCache)); // Note: SlotState is initialised at reset msxcpu.setInterface(this); if (motherBoard.isTurboR()) { // TODO also MSX2+ needs (slightly different) VDPIODelay delayDevice = DeviceFactory::createVDPIODelay( *motherBoard.getMachineConfig(), *this); for (int port = 0x98; port <= 0x9B; ++port) { assert(IO_In [port] == dummyDevice.get()); assert(IO_Out[port] == dummyDevice.get()); IO_In [port] = delayDevice.get(); IO_Out[port] = delayDevice.get(); } } if (breakedSettingCount++ == 0) { assert(!breakedSetting); breakedSetting = make_unique( motherBoard.getReactor().getCommandController(), "breaked", "Similar to 'debug breaked'", TclObject("false")); } } MSXCPUInterface::~MSXCPUInterface() { if (--breakedSettingCount == 0) { assert(breakedSetting); breakedSetting = nullptr; } removeAllWatchPoints(); if (delayDevice) { for (int port = 0x98; port <= 0x9B; ++port) { assert(IO_In [port] == delayDevice.get()); assert(IO_Out[port] == delayDevice.get()); IO_In [port] = dummyDevice.get(); IO_Out[port] = dummyDevice.get(); } } msxcpu.setInterface(nullptr); #ifndef NDEBUG for (int port = 0; port < 256; ++port) { if (IO_In[port] != dummyDevice.get()) { std::cout << "In-port " << port << " still registered " << IO_In[port]->getName() << std::endl; UNREACHABLE; } if (IO_Out[port] != dummyDevice.get()) { std::cout << "Out-port " << port << " still registered " << IO_Out[port]->getName() << std::endl; UNREACHABLE; } } for (int primSlot = 0; primSlot < 4; ++primSlot) { assert(!isExpanded(primSlot)); for (int secSlot = 0; secSlot < 4; ++secSlot) { for (int page = 0; page < 4; ++page) { assert(slotLayout[primSlot][secSlot][page] == dummyDevice.get()); } } } #endif } void MSXCPUInterface::removeAllWatchPoints() { while (!watchPoints.empty()) { removeWatchPoint(watchPoints.back()); } } byte MSXCPUInterface::readMemSlow(word address, EmuTime::param time) { // something special in this region? if (unlikely(disallowReadCache[address >> CacheLine::BITS])) { // execute read watches before actual read if (readWatchSet[address >> CacheLine::BITS] [address & CacheLine::LOW]) { executeMemWatch(WatchPoint::READ_MEM, address); } } if (unlikely((address == 0xFFFF) && isExpanded(primarySlotState[3]))) { return 0xFF ^ subSlotRegister[primarySlotState[3]]; } else { return visibleDevices[address >> 14]->readMem(address, time); } } void MSXCPUInterface::writeMemSlow(word address, byte value, EmuTime::param time) { if (unlikely((address == 0xFFFF) && isExpanded(primarySlotState[3]))) { setSubSlot(primarySlotState[3], value); // Confirmed on turboR GT machine: write does _not_ also go to // the underlying (hidden) device. But it's theoretically // possible other slotexpanders behave different. } else { visibleDevices[address>>14]->writeMem(address, value, time); } // something special in this region? if (unlikely(disallowWriteCache[address >> CacheLine::BITS])) { // slot-select-ignore writes (Super Lode Runner) for (auto& g : globalWrites) { // very primitive address selection mechanism, // but more than enough for now if (unlikely(g.addr == address)) { g.device->globalWrite(address, value, time); } } // execute write watches after actual write if (writeWatchSet[address >> CacheLine::BITS] [address & CacheLine::LOW]) { executeMemWatch(WatchPoint::WRITE_MEM, address, value); } } } void MSXCPUInterface::setExpanded(int ps) { if (expanded[ps] == 0) { for (int page = 0; page < 4; ++page) { if (slotLayout[ps][0][page] != dummyDevice.get()) { throw MSXException("Can't expand slot because " "it's already in use."); } } } expanded[ps]++; changeExpanded(isExpanded(primarySlotState[3])); } void MSXCPUInterface::testUnsetExpanded( int ps, vector allowed) const { // TODO handle multi-devices allowed.push_back(dummyDevice.get()); sort(begin(allowed), end(allowed)); // for set_difference() assert(isExpanded(ps)); if (expanded[ps] != 1) return; // ok, still expanded after this std::vector inUse; for (int ss = 0; ss < 4; ++ss) { for (int page = 0; page < 4; ++page) { MSXDevice* device = slotLayout[ps][ss][page]; std::vector devices; if (auto memDev = dynamic_cast(device)) { devices = memDev->getDevices(); sort(begin(devices), end(devices)); // for set_difference() } else { devices.push_back(device); } std::set_difference(begin(devices), end(devices), begin(allowed), end(allowed), std::inserter(inUse, end(inUse))); } } if (inUse.empty()) return; // ok, no more devices in use StringOp::Builder msg; msg << "Can't remove slot expander from slot " << ps << " because the following devices are still inserted:"; for (auto& d : inUse) { msg << " " << d->getName(); } msg << '.'; throw MSXException(msg); } void MSXCPUInterface::unsetExpanded(int ps) { #ifndef NDEBUG try { vector dummy; testUnsetExpanded(ps, dummy); } catch (...) { UNREACHABLE; } #endif expanded[ps]--; changeExpanded(isExpanded(primarySlotState[3])); } void MSXCPUInterface::changeExpanded(bool isExpanded) { if (isExpanded) { disallowReadCache [0xFF] |= SECUNDARY_SLOT_BIT; disallowWriteCache[0xFF] |= SECUNDARY_SLOT_BIT; } else { disallowReadCache [0xFF] &= ~SECUNDARY_SLOT_BIT; disallowWriteCache[0xFF] &= ~SECUNDARY_SLOT_BIT; } msxcpu.invalidateMemCache(0xFFFF & CacheLine::HIGH, 0x100); } MSXDevice*& MSXCPUInterface::getDevicePtr(byte port, bool isIn) { MSXDevice** devicePtr = isIn ? &IO_In[port] : &IO_Out[port]; while (auto watch = dynamic_cast(*devicePtr)) { devicePtr = &watch->getDevicePtr(); } if (*devicePtr == delayDevice.get()) { devicePtr = isIn ? &delayDevice->getInDevicePtr (port) : &delayDevice->getOutDevicePtr(port); } return *devicePtr; } void MSXCPUInterface::register_IO_In(byte port, MSXDevice* device) { MSXDevice*& devicePtr = getDevicePtr(port, true); // in register_IO(port, true, devicePtr, device); // in } void MSXCPUInterface::unregister_IO_In(byte port, MSXDevice* device) { MSXDevice*& devicePtr = getDevicePtr(port, true); // in unregister_IO(devicePtr, device); } void MSXCPUInterface::register_IO_Out(byte port, MSXDevice* device) { MSXDevice*& devicePtr = getDevicePtr(port, false); // out register_IO(port, false, devicePtr, device); // out } void MSXCPUInterface::unregister_IO_Out(byte port, MSXDevice* device) { MSXDevice*& devicePtr = getDevicePtr(port, false); // out unregister_IO(devicePtr, device); } void MSXCPUInterface::register_IO(int port, bool isIn, MSXDevice*& devicePtr, MSXDevice* device) { if (devicePtr == dummyDevice.get()) { // first, replace DummyDevice devicePtr = device; } else { if (auto multi = dynamic_cast(devicePtr)) { // third or more, add to existing MultiIO device multi->addDevice(device); } else { // second, create a MultiIO device multi = new MSXMultiIODevice(device->getHardwareConfig()); multi->addDevice(devicePtr); multi->addDevice(device); devicePtr = multi; } if (isIn) { cliComm.printWarning( "Conflicting input port 0x" + StringOp::toHexString(port, 2) + " for devices " + devicePtr->getName()); } } } void MSXCPUInterface::unregister_IO(MSXDevice*& devicePtr, MSXDevice* device) { if (auto multi = dynamic_cast(devicePtr)) { // remove from MultiIO device multi->removeDevice(device); auto& devices = multi->getDevices(); if (devices.size() == 1) { // only one remaining, remove MultiIO device devicePtr = devices.front(); devices.pop_back(); delete multi; } } else { // remove last, put back DummyDevice assert(devicePtr == device); devicePtr = dummyDevice.get(); } } static void reportMemOverlap(int ps, int ss, MSXDevice& dev1, MSXDevice& dev2) { throw MSXException(StringOp::Builder() << "Overlapping memory devices in slot " << ps << '.' << ss << ": " << dev1.getName() << " and " << dev2.getName() << '.'); } void MSXCPUInterface::testRegisterSlot( MSXDevice& device, int ps, int ss, int base, int size) { int page = base >> 14; MSXDevice*& slot = slotLayout[ps][ss][page]; if (size == 0x4000) { // full 16kb, directly register device (no multiplexer) if (slot != dummyDevice.get()) { reportMemOverlap(ps, ss, *slot, device); } } else { // partial page if (slot == dummyDevice.get()) { // first, ok } else if (auto multi = dynamic_cast(slot)) { // second (or more), check for overlap if (!multi->canAdd(base, size)) { reportMemOverlap(ps, ss, *slot, device); } } else { // conflict with 'full ranged' device reportMemOverlap(ps, ss, *slot, device); } } } void MSXCPUInterface::registerSlot( MSXDevice& device, int ps, int ss, int base, int size) { int page = base >> 14; MSXDevice*& slot = slotLayout[ps][ss][page]; if (size == 0x4000) { // full 16kb, directly register device (no multiplexer) assert(slot == dummyDevice.get()); slot = &device; } else { // partial page if (slot == dummyDevice.get()) { // first auto* multi = new MSXMultiMemDevice(device.getHardwareConfig()); multi->add(device, base, size); slot = multi; } else if (auto* multi = dynamic_cast(slot)) { // second or more assert(multi->canAdd(base, size)); multi->add(device, base, size); } else { // conflict with 'full ranged' device assert(false); } } updateVisible(page); } void MSXCPUInterface::unregisterSlot( MSXDevice& device, int ps, int ss, int base, int size) { int page = base >> 14; MSXDevice*& slot = slotLayout[ps][ss][page]; if (auto* multi = dynamic_cast(slot)) { // partial range multi->remove(device, base, size); if (multi->empty()) { delete multi; slot = dummyDevice.get(); } } else { // full 16kb range assert(slot == &device); slot = dummyDevice.get(); } updateVisible(page); } void MSXCPUInterface::registerMemDevice( MSXDevice& device, int ps, int ss, int base_, int size_) { if (!isExpanded(ps) && (ss != 0)) { throw MSXException(StringOp::Builder() << "Slot " << ps << '.' << ss << " does not exist because slot is not expanded."); } // split range on 16kb borders // first check if registration is possible int base = base_; int size = size_; while (size > 0) { int partialSize = min(size, ((base + 0x4000) & ~0x3FFF) - base); testRegisterSlot(device, ps, ss, base, partialSize); base += partialSize; size -= partialSize; } // if all checks are successful, only then actually register base = base_; size = size_; while (size > 0) { int partialSize = min(size, ((base + 0x4000) & ~0x3FFF) - base); registerSlot(device, ps, ss, base, partialSize); base += partialSize; size -= partialSize; } } void MSXCPUInterface::unregisterMemDevice( MSXDevice& device, int ps, int ss, int base, int size) { // split range on 16kb borders while (size > 0) { int partialSize = min(size, ((base + 0x4000) & ~0x3FFF) - base); unregisterSlot(device, ps, ss, base, partialSize); base += partialSize; size -= partialSize; } } void MSXCPUInterface::registerGlobalWrite(MSXDevice& device, word address) { globalWrites.push_back({&device, address}); disallowWriteCache[address >> CacheLine::BITS] |= GLOBAL_WRITE_BIT; msxcpu.invalidateMemCache(address & CacheLine::HIGH, 0x100); } void MSXCPUInterface::unregisterGlobalWrite(MSXDevice& device, word address) { GlobalWriteInfo info = { &device, address }; globalWrites.erase(find_unguarded(globalWrites, info)); for (auto& g : globalWrites) { if ((g.addr >> CacheLine::BITS) == (address >> CacheLine::BITS)) { // there is still a global write in this region return; } } disallowWriteCache[address >> CacheLine::BITS] &= ~GLOBAL_WRITE_BIT; msxcpu.invalidateMemCache(address & CacheLine::HIGH, 0x100); } ALWAYS_INLINE void MSXCPUInterface::updateVisible(int page, int ps, int ss) { MSXDevice* newDevice = slotLayout[ps][ss][page]; if (visibleDevices[page] != newDevice) { visibleDevices[page] = newDevice; msxcpu.updateVisiblePage(page, ps, ss); } } void MSXCPUInterface::updateVisible(int page) { updateVisible(page, primarySlotState[page], secondarySlotState[page]); } void MSXCPUInterface::reset() { for (auto& reg : subSlotRegister) { reg = 0; } setPrimarySlots(0); } byte MSXCPUInterface::readIRQVector() { return motherBoard.readIRQVector(); } void MSXCPUInterface::setPrimarySlots(byte value) { // Change the slot structure. // Originally the code below was a loop over the 4 pages, and the check // for (un)expanded-slot was done unconditionally at the end. I've // completely unrolled the loop and only check for (un)expanded slot // when the slot in page 3 has changed. I've also added checks for slot // changes for the other 3 pages. Usually when this register is written // only one of the 4 pages actually changes, so these extra checks do // pay off. This does make the code a bit more complex (and the // generated code slightly bigger), but it does make a measurable speed // difference. Changing the slots several hundreds of times per // (EmuTime) is not unusual. So this routine ended up quite high // (top-10) in some profile results. int ps0 = (value >> 0) & 3; if (unlikely(primarySlotState[0] != ps0)) { primarySlotState[0] = ps0; int ss0 = (subSlotRegister[ps0] >> 0) & 3; secondarySlotState[0] = ss0; updateVisible(0, ps0, ss0); } int ps1 = (value >> 2) & 3; if (unlikely(primarySlotState[1] != ps1)) { primarySlotState[1] = ps1; int ss1 = (subSlotRegister[ps1] >> 2) & 3; secondarySlotState[1] = ss1; updateVisible(1, ps1, ss1); } int ps2 = (value >> 4) & 3; if (unlikely(primarySlotState[2] != ps2)) { primarySlotState[2] = ps2; int ss2 = (subSlotRegister[ps2] >> 4) & 3; secondarySlotState[2] = ss2; updateVisible(2, ps2, ss2); } int ps3 = (value >> 6) & 3; if (unlikely(primarySlotState[3] != ps3)) { bool oldExpanded = isExpanded(primarySlotState[3]); bool newExpanded = isExpanded(ps3); primarySlotState[3] = ps3; int ss3 = (subSlotRegister[ps3] >> 6) & 3; secondarySlotState[3] = ss3; updateVisible(3, ps3, ss3); if (unlikely(oldExpanded != newExpanded)) { changeExpanded(newExpanded); } } } void MSXCPUInterface::setSubSlot(byte primSlot, byte value) { subSlotRegister[primSlot] = value; for (int page = 0; page < 4; ++page, value >>= 2) { if (primSlot == primarySlotState[page]) { secondarySlotState[page] = value & 3; // Change the visible devices updateVisible(page); } } } byte MSXCPUInterface::peekMem(word address, EmuTime::param time) const { if ((address == 0xFFFF) && isExpanded(primarySlotState[3])) { return 0xFF ^ subSlotRegister[primarySlotState[3]]; } else { return visibleDevices[address >> 14]->peekMem(address, time); } } byte MSXCPUInterface::peekSlottedMem(unsigned address, EmuTime::param time) const { byte primSlot = (address & 0xC0000) >> 18; byte subSlot = (address & 0x30000) >> 16; byte page = (address & 0x0C000) >> 14; word offset = (address & 0xFFFF); // includes page if (!isExpanded(primSlot)) { subSlot = 0; } if ((offset == 0xFFFF) && isExpanded(primSlot)) { return 0xFF ^ subSlotRegister[primSlot]; } else { return slotLayout[primSlot][subSlot][page]->peekMem(offset, time); } } byte MSXCPUInterface::readSlottedMem(unsigned address, EmuTime::param time) { byte primSlot = (address & 0xC0000) >> 18; byte subSlot = (address & 0x30000) >> 16; byte page = (address & 0x0C000) >> 14; word offset = (address & 0xFFFF); // includes page if (!isExpanded(primSlot)) { subSlot = 0; } if ((offset == 0xFFFF) && isExpanded(primSlot)) { return 0xFF ^ subSlotRegister[primSlot]; } else { return slotLayout[primSlot][subSlot][page]->peekMem(offset, time); } } void MSXCPUInterface::writeSlottedMem(unsigned address, byte value, EmuTime::param time) { byte primSlot = (address & 0xC0000) >> 18; byte subSlot = (address & 0x30000) >> 16; byte page = (address & 0x0C000) >> 14; word offset = (address & 0xFFFF); // includes page if (!isExpanded(primSlot)) { subSlot = 0; } if ((offset == 0xFFFF) && isExpanded(primSlot)) { setSubSlot(primSlot, value); } else { slotLayout[primSlot][subSlot][page]->writeMem(offset, value, time); } } void MSXCPUInterface::insertBreakPoint(const BreakPoint& bp) { auto it = upper_bound(begin(breakPoints), end(breakPoints), bp, CompareBreakpoints()); breakPoints.insert(it, bp); } void MSXCPUInterface::removeBreakPoint(const BreakPoint& bp) { auto range = equal_range(begin(breakPoints), end(breakPoints), bp.getAddress(), CompareBreakpoints()); breakPoints.erase(find_if_unguarded(range.first, range.second, [&](const BreakPoint& i) { return &i == &bp; })); } void MSXCPUInterface::checkBreakPoints( std::pair range, MSXMotherBoard& motherBoard) { // create copy for the case that breakpoint/condition removes itself // - keeps object alive by holding a shared_ptr to it // - avoids iterating over a changing collection BreakPoints bpCopy(range.first, range.second); auto& cliComm = motherBoard.getReactor().getGlobalCliComm(); auto& interp = motherBoard.getReactor().getInterpreter(); for (auto& p : bpCopy) { p.checkAndExecute(cliComm, interp); } auto condCopy = conditions; for (auto& c : condCopy) { c.checkAndExecute(cliComm, interp); } } void MSXCPUInterface::setWatchPoint(const shared_ptr& watchPoint) { watchPoints.push_back(watchPoint); WatchPoint::Type type = watchPoint->getType(); switch (type) { case WatchPoint::READ_IO: registerIOWatch(*watchPoint, IO_In); break; case WatchPoint::WRITE_IO: registerIOWatch(*watchPoint, IO_Out); break; case WatchPoint::READ_MEM: case WatchPoint::WRITE_MEM: updateMemWatch(type); break; default: UNREACHABLE; break; } } void MSXCPUInterface::removeWatchPoint(shared_ptr watchPoint) { // Pass shared_ptr by value to keep the object alive for the duration // of this function, otherwise it gets deleted as soon as it's removed // from the watchPoints collection. for (auto it = begin(watchPoints); it != end(watchPoints); ++it) { if (*it == watchPoint) { // remove before calling updateMemWatch() watchPoints.erase(it); WatchPoint::Type type = watchPoint->getType(); switch (type) { case WatchPoint::READ_IO: unregisterIOWatch(*watchPoint, IO_In); break; case WatchPoint::WRITE_IO: unregisterIOWatch(*watchPoint, IO_Out); break; case WatchPoint::READ_MEM: case WatchPoint::WRITE_MEM: updateMemWatch(type); break; default: UNREACHABLE; break; } break; } } } void MSXCPUInterface::setCondition(const DebugCondition& cond) { conditions.push_back(cond); } void MSXCPUInterface::removeCondition(const DebugCondition& cond) { conditions.erase(find_if_unguarded(conditions, [&](DebugCondition& e) { return &e == &cond; })); } void MSXCPUInterface::registerIOWatch(WatchPoint& watchPoint, MSXDevice** devices) { assert(dynamic_cast(&watchPoint)); auto& ioWatch = static_cast(watchPoint); unsigned beginPort = ioWatch.getBeginAddress(); unsigned endPort = ioWatch.getEndAddress(); assert(beginPort <= endPort); assert(endPort < 0x100); for (unsigned port = beginPort; port <= endPort; ++port) { ioWatch.getDevice(port).getDevicePtr() = devices[port]; devices[port] = &ioWatch.getDevice(port); } } void MSXCPUInterface::unregisterIOWatch(WatchPoint& watchPoint, MSXDevice** devices) { assert(dynamic_cast(&watchPoint)); auto& ioWatch = static_cast(watchPoint); unsigned beginPort = ioWatch.getBeginAddress(); unsigned endPort = ioWatch.getEndAddress(); assert(beginPort <= endPort); assert(endPort < 0x100); for (unsigned port = beginPort; port <= endPort; ++port) { // find pointer to watchpoint MSXDevice** prev = &devices[port]; while (*prev != &ioWatch.getDevice(port)) { prev = &checked_cast(*prev)->getDevicePtr(); } // remove watchpoint from chain *prev = checked_cast(*prev)->getDevicePtr(); } } void MSXCPUInterface::updateMemWatch(WatchPoint::Type type) { std::bitset* watchSet = (type == WatchPoint::READ_MEM) ? readWatchSet : writeWatchSet; for (unsigned i = 0; i < CacheLine::NUM; ++i) { watchSet[i].reset(); } for (auto& w : watchPoints) { if (w->getType() == type) { unsigned beginAddr = w->getBeginAddress(); unsigned endAddr = w->getEndAddress(); assert(beginAddr <= endAddr); assert(endAddr < 0x10000); for (unsigned addr = beginAddr; addr <= endAddr; ++addr) { watchSet[addr >> CacheLine::BITS].set( addr & CacheLine::LOW); } } } for (unsigned i = 0; i < CacheLine::NUM; ++i) { if (readWatchSet [i].any()) { disallowReadCache [i] |= MEMORY_WATCH_BIT; } else { disallowReadCache [i] &= ~MEMORY_WATCH_BIT; } if (writeWatchSet[i].any()) { disallowWriteCache[i] |= MEMORY_WATCH_BIT; } else { disallowWriteCache[i] &= ~MEMORY_WATCH_BIT; } } msxcpu.invalidateMemCache(0x0000, 0x10000); } void MSXCPUInterface::executeMemWatch(WatchPoint::Type type, unsigned address, unsigned value) { assert(!watchPoints.empty()); if (isFastForward()) return; auto& cliComm = motherBoard.getReactor().getGlobalCliComm(); auto& interp = motherBoard.getReactor().getInterpreter(); interp.setVariable("wp_last_address", TclObject(int(address))); if (value != ~0u) { interp.setVariable("wp_last_value", TclObject(int(value))); } auto wpCopy = watchPoints; for (auto& w : wpCopy) { if ((w->getBeginAddress() <= address) && (w->getEndAddress() >= address) && (w->getType() == type)) { w->checkAndExecute(cliComm, interp); } } interp.unsetVariable("wp_last_address"); interp.unsetVariable("wp_last_value"); } void MSXCPUInterface::doBreak() { assert(!isFastForward()); if (breaked) return; breaked = true; msxcpu.exitCPULoopSync(); Reactor& reactor = motherBoard.getReactor(); reactor.block(); breakedSetting->setReadOnlyValue(TclObject("true")); reactor.getCliComm().update(CliComm::STATUS, "cpu", "suspended"); reactor.getEventDistributor().distributeEvent( std::make_shared(OPENMSX_BREAK_EVENT)); } void MSXCPUInterface::doStep() { assert(!isFastForward()); if (breaked) { step = true; doContinue2(); } } void MSXCPUInterface::doContinue() { assert(!isFastForward()); if (breaked) { continued = true; doContinue2(); } } void MSXCPUInterface::doContinue2() { breaked = false; Reactor& reactor = motherBoard.getReactor(); breakedSetting->setReadOnlyValue(TclObject("false")); reactor.getCliComm().update(CliComm::STATUS, "cpu", "running"); reactor.unblock(); } void MSXCPUInterface::cleanup() { // before the Tcl interpreter is destroyed, we must delete all // TclObjects. Breakpoints and conditions contain such objects // for the condition and action. // TODO it would be nicer if breakpoints and conditions were not // global objects. breakPoints.clear(); conditions.clear(); } // class MemoryDebug MSXCPUInterface::MemoryDebug::MemoryDebug(MSXMotherBoard& motherBoard) : SimpleDebuggable(motherBoard, "memory", "The memory currently visible for the CPU.", 0x10000) { } byte MSXCPUInterface::MemoryDebug::read(unsigned address, EmuTime::param time) { auto& interface = OUTER(MSXCPUInterface, memoryDebug); return interface.peekMem(address, time); } void MSXCPUInterface::MemoryDebug::write(unsigned address, byte value, EmuTime::param time) { auto& interface = OUTER(MSXCPUInterface, memoryDebug); return interface.writeMem(address, value, time); } // class SlottedMemoryDebug MSXCPUInterface::SlottedMemoryDebug::SlottedMemoryDebug( MSXMotherBoard& motherBoard) : SimpleDebuggable(motherBoard, "slotted memory", "The memory in slots and subslots.", 0x10000 * 4 * 4) { } byte MSXCPUInterface::SlottedMemoryDebug::read(unsigned address, EmuTime::param time) { auto& interface = OUTER(MSXCPUInterface, slottedMemoryDebug); return interface.peekSlottedMem(address, time); } void MSXCPUInterface::SlottedMemoryDebug::write(unsigned address, byte value, EmuTime::param time) { auto& interface = OUTER(MSXCPUInterface, slottedMemoryDebug); return interface.writeSlottedMem(address, value, time); } // class SlotInfo static unsigned getSlot( Interpreter& interp, const TclObject& token, const string& itemName) { unsigned slot = token.getInt(interp); if (slot >= 4) { throw CommandException(itemName + " must be in range 0..3"); } return slot; } MSXCPUInterface::SlotInfo::SlotInfo( InfoCommand& machineInfoCommand) : InfoTopic(machineInfoCommand, "slot") { } void MSXCPUInterface::SlotInfo::execute(array_ref tokens, TclObject& result) const { if (tokens.size() != 5) { throw SyntaxError(); } auto& interp = getInterpreter(); unsigned ps = getSlot(interp, tokens[2], "Primary slot"); unsigned ss = getSlot(interp, tokens[3], "Secondary slot"); unsigned page = getSlot(interp, tokens[4], "Page"); auto& interface = OUTER(MSXCPUInterface, slotInfo); if (!interface.isExpanded(ps)) { ss = 0; } interface.slotLayout[ps][ss][page]->getNameList(result); } string MSXCPUInterface::SlotInfo::help(const vector& /*tokens*/) const { return "Retrieve name of the device inserted in given " "primary slot / secondary slot / page."; } // class SubSlottedInfo MSXCPUInterface::SubSlottedInfo::SubSlottedInfo( InfoCommand& machineInfoCommand) : InfoTopic(machineInfoCommand, "issubslotted") { } void MSXCPUInterface::SubSlottedInfo::execute(array_ref tokens, TclObject& result) const { if (tokens.size() != 3) { throw SyntaxError(); } auto& interface = OUTER(MSXCPUInterface, subSlottedInfo); result.setInt(interface.isExpanded( getSlot(getInterpreter(), tokens[2], "Slot"))); } string MSXCPUInterface::SubSlottedInfo::help( const vector& /*tokens*/) const { return "Indicates whether a certain primary slot is expanded."; } // class ExternalSlotInfo MSXCPUInterface::ExternalSlotInfo::ExternalSlotInfo( InfoCommand& machineInfoCommand) : InfoTopic(machineInfoCommand, "isexternalslot") { } void MSXCPUInterface::ExternalSlotInfo::execute( array_ref tokens, TclObject& result) const { int ps = 0; int ss = 0; auto& interp = getInterpreter(); switch (tokens.size()) { case 4: ss = getSlot(interp, tokens[3], "Secondary slot"); // Fall-through case 3: ps = getSlot(interp, tokens[2], "Primary slot"); break; default: throw SyntaxError(); } auto& interface = OUTER(MSXCPUInterface, externalSlotInfo); auto& manager = interface.motherBoard.getSlotManager(); result.setInt(manager.isExternalSlot(ps, ss, true)); } string MSXCPUInterface::ExternalSlotInfo::help( const vector& /*tokens*/) const { return "Indicates whether a certain slot is external or internal."; } // class IODebug MSXCPUInterface::IODebug::IODebug(MSXMotherBoard& motherBoard) : SimpleDebuggable(motherBoard, "ioports", "IO ports.", 0x100) { } byte MSXCPUInterface::IODebug::read(unsigned address, EmuTime::param time) { auto& interface = OUTER(MSXCPUInterface, ioDebug); return interface.IO_In[address & 0xFF]->peekIO(address, time); } void MSXCPUInterface::IODebug::write(unsigned address, byte value, EmuTime::param time) { auto& interface = OUTER(MSXCPUInterface, ioDebug); interface.writeIO(word(address), value, time); } // class IOInfo MSXCPUInterface::IOInfo::IOInfo(InfoCommand& machineInfoCommand, const char* name) : InfoTopic(machineInfoCommand, name) { } void MSXCPUInterface::IOInfo::helper( array_ref tokens, TclObject& result, MSXDevice** devices) const { if (tokens.size() != 3) { throw SyntaxError(); } unsigned port = tokens[2].getInt(getInterpreter()); if (port >= 256) { throw CommandException("Port must be in range 0..255"); } result.setString(devices[port]->getName()); } void MSXCPUInterface::IInfo::execute( array_ref tokens, TclObject& result) const { auto& interface = OUTER(MSXCPUInterface, inputPortInfo); helper(tokens, result, interface.IO_In); } void MSXCPUInterface::OInfo::execute( array_ref tokens, TclObject& result) const { auto& interface = OUTER(MSXCPUInterface, outputPortInfo); helper(tokens, result, interface.IO_Out); } string MSXCPUInterface::IOInfo::help(const vector& /*tokens*/) const { return "Return the name of the device connected to the given IO port."; } template void MSXCPUInterface::serialize(Archive& ar, unsigned /*version*/) { // TODO watchPoints ??? // primary and 4 secundary slot select registers byte prim = 0; if (!ar.isLoader()) { for (int i = 0; i < 4; ++i) { prim |= primarySlotState[i] << (2 * i); } } ar.serialize("primarySlots", prim); ar.serialize("subSlotRegs", subSlotRegister); if (ar.isLoader()) { setPrimarySlots(prim); for (int i = 0; i < 4; ++i) { setSubSlot(i, subSlotRegister[i]); } } if (delayDevice) { ar.serialize("vdpDelay", *delayDevice); } } INSTANTIATE_SERIALIZE_METHODS(MSXCPUInterface); } // namespace openmsx openMSX-RELEASE_0_12_0/src/cpu/MSXCPUInterface.hh000066400000000000000000000306121257557151200212000ustar00rootroot00000000000000#ifndef MSXCPUINTERFACE_HH #define MSXCPUINTERFACE_HH #include "SimpleDebuggable.hh" #include "InfoTopic.hh" #include "CacheLine.hh" #include "MSXDevice.hh" #include "BreakPoint.hh" #include "WatchPoint.hh" #include "openmsx.hh" #include "noncopyable.hh" #include "likely.hh" #include #include #include #include namespace openmsx { class VDPIODelay; class DummyDevice; class MSXMotherBoard; class MSXCPU; class CliComm; class BreakPoint; class DebugCondition; class CartridgeSlotManager; struct CompareBreakpoints { bool operator()(const BreakPoint& x, const BreakPoint& y) const { return x.getAddress() < y.getAddress(); } bool operator()(const BreakPoint& x, word y) const { return x.getAddress() < y; } bool operator()(word x, const BreakPoint& y) const { return x < y.getAddress(); } }; class MSXCPUInterface : private noncopyable { public: explicit MSXCPUInterface(MSXMotherBoard& motherBoard); ~MSXCPUInterface(); /** * Devices can register their In ports. This is normally done * in their constructor. Once device are registered, their * readIO() method can get called. */ void register_IO_In(byte port, MSXDevice* device); void unregister_IO_In(byte port, MSXDevice* device); /** * Devices can register their Out ports. This is normally done * in their constructor. Once device are registered, their * writeIO() method can get called. */ void register_IO_Out(byte port, MSXDevice* device); void unregister_IO_Out(byte port, MSXDevice* device); /** * Devices can register themself in the MSX slotstructure. * This is normally done in their constructor. Once devices * are registered their readMem() / writeMem() methods can * get called. */ void registerMemDevice(MSXDevice& device, int primSl, int secSL, int base, int size); void unregisterMemDevice(MSXDevice& device, int primSl, int secSL, int base, int size); /** (Un)register global writes. * @see MSXDevice::globalWrite() */ void registerGlobalWrite(MSXDevice& device, word address); void unregisterGlobalWrite(MSXDevice& device, word address); /** * Reset (the slot state) */ void reset(); /** * This reads a byte from the currently selected device */ inline byte readMem(word address, EmuTime::param time) { if (unlikely(disallowReadCache[address >> CacheLine::BITS])) { return readMemSlow(address, time); } return visibleDevices[address >> 14]->readMem(address, time); } /** * This writes a byte to the currently selected device */ inline void writeMem(word address, byte value, EmuTime::param time) { if (unlikely(disallowWriteCache[address >> CacheLine::BITS])) { writeMemSlow(address, value, time); return; } visibleDevices[address>>14]->writeMem(address, value, time); } /** * This read a byte from the given IO-port * @see MSXDevice::readIO() */ inline byte readIO(word port, EmuTime::param time) { return IO_In[port & 0xFF]->readIO(port, time); } /** * This writes a byte to the given IO-port * @see MSXDevice::writeIO() */ inline void writeIO(word port, byte value, EmuTime::param time) { IO_Out[port & 0xFF]->writeIO(port, value, time); } /** * Test that the memory in the interval [start, start + * CacheLine::SIZE) is cacheable for reading. If it is, a pointer to a * buffer containing this interval must be returned. If not, a null * pointer must be returned. * Cacheable for reading means the data may be read directly * from the buffer, thus bypassing the readMem() method, and * thus also ignoring EmuTime. * The default implementation always returns a null pointer. * An interval will never cross a 16KB border. * An interval will never contain the address 0xffff. */ inline const byte* getReadCacheLine(word start) const { if (unlikely(disallowReadCache[start >> CacheLine::BITS])) { return nullptr; } return visibleDevices[start >> 14]->getReadCacheLine(start); } /** * Test that the memory in the interval [start, start + * CacheLine::SIZE) is cacheable for writing. If it is, a pointer to a * buffer containing this interval must be returned. If not, a null * pointer must be returned. * Cacheable for writing means the data may be written directly * to the buffer, thus bypassing the writeMem() method, and * thus also ignoring EmuTime. * The default implementation always returns a null pointer. * An interval will never cross a 16KB border. * An interval will never contain the address 0xffff. */ inline byte* getWriteCacheLine(word start) const { if (unlikely(disallowWriteCache[start >> CacheLine::BITS])) { return nullptr; } return visibleDevices[start >> 14]->getWriteCacheLine(start); } /** * CPU uses this method to read 'extra' data from the databus * used in interrupt routines. In MSX this returns always 255. */ byte readIRQVector(); /* * Should only be used by PPI * TODO: make private / friend */ void setPrimarySlots(byte value); /** * Peek memory location * @see MSXDevice::peekMem() */ byte peekMem(word address, EmuTime::param time) const; byte peekSlottedMem(unsigned address, EmuTime::param time) const; byte readSlottedMem(unsigned address, EmuTime::param time); void writeSlottedMem(unsigned address, byte value, EmuTime::param time); void setExpanded(int ps); void unsetExpanded(int ps); void testUnsetExpanded(int ps, std::vector allowed) const; inline bool isExpanded(int ps) const { return expanded[ps] != 0; } void changeExpanded(bool isExpanded); DummyDevice& getDummyDevice() { return *dummyDevice; } static void insertBreakPoint(const BreakPoint& bp); static void removeBreakPoint(const BreakPoint& bp); using BreakPoints = std::vector; static const BreakPoints& getBreakPoints() { return breakPoints; } void setWatchPoint(const std::shared_ptr& watchPoint); void removeWatchPoint(std::shared_ptr watchPoint); // note: must be shared_ptr (not unique_ptr), see WatchIO::doReadCallback() using WatchPoints = std::vector>; const WatchPoints& getWatchPoints() const { return watchPoints; } static void setCondition(const DebugCondition& cond); static void removeCondition(const DebugCondition& cond); using Conditions = std::vector; static const Conditions& getConditions() { return conditions; } static bool isBreaked() { return breaked; } void doBreak(); void doStep(); void doContinue(); // should only be used by CPUCore static bool isStep() { return step; } static void setStep (bool x) { step = x; } static bool isContinue() { return continued; } static void setContinue(bool x) { continued = x; } // breakpoint methods used by CPUCore static bool anyBreakPoints() { return !breakPoints.empty() || !conditions.empty(); } static bool checkBreakPoints(unsigned pc, MSXMotherBoard& motherBoard) { auto range = equal_range(begin(breakPoints), end(breakPoints), pc, CompareBreakpoints()); if (conditions.empty() && (range.first == range.second)) { return false; } // slow path non-inlined checkBreakPoints(range, motherBoard); return isBreaked(); } // cleanup global variables static void cleanup(); // In fast-forward mode, breakpoints, watchpoints and conditions should // not trigger. void setFastForward(bool fastForward_) { fastForward = fastForward_; } bool isFastForward() const { return fastForward; } template void serialize(Archive& ar, unsigned version); private: byte readMemSlow(word address, EmuTime::param time); void writeMemSlow(word address, byte value, EmuTime::param time); MSXDevice*& getDevicePtr(byte port, bool isIn); void register_IO (int port, bool isIn, MSXDevice*& devicePtr, MSXDevice* device); void unregister_IO(MSXDevice*& devicePtr, MSXDevice* device); void testRegisterSlot(MSXDevice& device, int ps, int ss, int base, int size); void registerSlot(MSXDevice& device, int ps, int ss, int base, int size); void unregisterSlot(MSXDevice& device, int ps, int ss, int base, int size); static void checkBreakPoints(std::pair range, MSXMotherBoard& motherBoard); void removeAllWatchPoints(); void registerIOWatch (WatchPoint& watchPoint, MSXDevice** devices); void unregisterIOWatch(WatchPoint& watchPoint, MSXDevice** devices); void updateMemWatch(WatchPoint::Type type); void executeMemWatch(WatchPoint::Type type, unsigned address, unsigned value = ~0u); void doContinue2(); struct MemoryDebug final : SimpleDebuggable { MemoryDebug(MSXMotherBoard& motherBoard); byte read(unsigned address, EmuTime::param time) override; void write(unsigned address, byte value, EmuTime::param time) override; } memoryDebug; struct SlottedMemoryDebug final : SimpleDebuggable { SlottedMemoryDebug(MSXMotherBoard& motherBoard); byte read(unsigned address, EmuTime::param time) override; void write(unsigned address, byte value, EmuTime::param time) override; } slottedMemoryDebug; struct IODebug final : SimpleDebuggable { IODebug(MSXMotherBoard& motherBoard); byte read(unsigned address, EmuTime::param time) override; void write(unsigned address, byte value, EmuTime::param time) override; } ioDebug; struct SlotInfo final : InfoTopic { SlotInfo(InfoCommand& machineInfoCommand); void execute(array_ref tokens, TclObject& result) const override; std::string help(const std::vector& tokens) const override; } slotInfo; struct SubSlottedInfo final : InfoTopic { SubSlottedInfo(InfoCommand& machineInfoCommand); void execute(array_ref tokens, TclObject& result) const override; std::string help(const std::vector& tokens) const override; } subSlottedInfo; struct ExternalSlotInfo final : InfoTopic { ExternalSlotInfo(InfoCommand& machineInfoCommand); void execute(array_ref tokens, TclObject& result) const override; std::string help(const std::vector& tokens) const override; } externalSlotInfo; struct IOInfo : InfoTopic { IOInfo(InfoCommand& machineInfoCommand, const char* name); void helper(array_ref tokens, TclObject& result, MSXDevice** devices) const; std::string help(const std::vector& tokens) const override; }; struct IInfo final : IOInfo { IInfo(InfoCommand& machineInfoCommand) : IOInfo(machineInfoCommand, "input_port") {} void execute(array_ref tokens, TclObject& result) const override; } inputPortInfo; struct OInfo final : IOInfo { OInfo(InfoCommand& machineInfoCommand) : IOInfo(machineInfoCommand, "output_port") {} void execute(array_ref tokens, TclObject& result) const override; } outputPortInfo; /** Updated visibleDevices for a given page and clears the cache * on changes. * Should be called whenever PrimarySlotState or SecondarySlotState * was modified. * @param page page [0..3] to update visibleDevices for. */ void updateVisible(int page); inline void updateVisible(int page, int ps, int ss); void setSubSlot(byte primSlot, byte value); std::unique_ptr dummyDevice; MSXCPU& msxcpu; CliComm& cliComm; MSXMotherBoard& motherBoard; std::unique_ptr delayDevice; // can be nullptr byte disallowReadCache [CacheLine::NUM]; byte disallowWriteCache[CacheLine::NUM]; std::bitset readWatchSet [CacheLine::NUM]; std::bitset writeWatchSet[CacheLine::NUM]; struct GlobalWriteInfo { MSXDevice* device; word addr; bool operator==(const GlobalWriteInfo& rhs) const { return (device == rhs.device) && (addr == rhs.addr); } }; std::vector globalWrites; MSXDevice* IO_In [256]; MSXDevice* IO_Out[256]; MSXDevice* slotLayout[4][4][4]; MSXDevice* visibleDevices[4]; byte subSlotRegister[4]; byte primarySlotState[4]; byte secondarySlotState[4]; unsigned expanded[4]; bool fastForward; // no need to serialize // All CPUs (Z80 and R800) of all MSX machines share this state. static BreakPoints breakPoints; WatchPoints watchPoints; // TODO must also be static static Conditions conditions; static bool breaked; static bool continued; static bool step; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/cpu/MSXMultiDevice.cc000066400000000000000000000011551257557151200211300ustar00rootroot00000000000000#include "MSXMultiDevice.hh" #include "DeviceConfig.hh" #include "XMLElement.hh" #include "unreachable.hh" namespace openmsx { static DeviceConfig getMultiConfig(const HardwareConfig& hwConf) { static XMLElement xml("Multi"); return DeviceConfig(hwConf, xml); } MSXMultiDevice::MSXMultiDevice(const HardwareConfig& hwConf) : MSXDevice(getMultiConfig(hwConf), "Multi") { } void MSXMultiDevice::reset(EmuTime::param /*time*/) { UNREACHABLE; } void MSXMultiDevice::powerUp(EmuTime::param /*time*/) { UNREACHABLE; } void MSXMultiDevice::powerDown(EmuTime::param /*time*/) { UNREACHABLE; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/cpu/MSXMultiDevice.hh000066400000000000000000000005701257557151200211420ustar00rootroot00000000000000#ifndef MSXMULTIDEVICE_HH #define MSXMULTIDEVICE_HH #include "MSXDevice.hh" namespace openmsx { class MSXMultiDevice : public MSXDevice { public: explicit MSXMultiDevice(const HardwareConfig& hwConf); void reset(EmuTime::param time) override; void powerUp(EmuTime::param time) override; void powerDown(EmuTime::param time) override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/cpu/MSXMultiIODevice.cc000066400000000000000000000030431257557151200213560ustar00rootroot00000000000000#include "MSXMultiIODevice.hh" #include "TclObject.hh" #include "stl.hh" #include #include namespace openmsx { MSXMultiIODevice::MSXMultiIODevice(const HardwareConfig& hwConf) : MSXMultiDevice(hwConf) { } MSXMultiIODevice::~MSXMultiIODevice() { assert(devices.empty()); } void MSXMultiIODevice::addDevice(MSXDevice* device) { assert(!contains(devices, device)); devices.push_back(device); } void MSXMultiIODevice::removeDevice(MSXDevice* device) { devices.erase(find_unguarded(devices, device)); } std::string MSXMultiIODevice::getName() const { TclObject list; getNameList(list); return list.getString().str(); } void MSXMultiIODevice::getNameList(TclObject& result) const { for (auto* dev : devices) { const auto& name = dev->getName(); if (!name.empty()) { result.addListElement(name); } } } byte MSXMultiIODevice::readIO(word port, EmuTime::param time) { // conflict: return the result from the first device, call readIO() // also on all other devices, but discard result assert(!devices.empty()); auto it = begin(devices); byte result = (*it)->readIO(port, time); for (++it; it != end(devices); ++it) { (*it)->readIO(port, time); } return result; } byte MSXMultiIODevice::peekIO(word port, EmuTime::param time) const { // conflict: just peek first device assert(!devices.empty()); return devices.front()->peekIO(port, time); } void MSXMultiIODevice::writeIO(word port, byte value, EmuTime::param time) { for (auto& d : devices) { d->writeIO(port, value, time); } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/cpu/MSXMultiIODevice.hh000066400000000000000000000014211257557151200213660ustar00rootroot00000000000000#ifndef MSXMULTIIODEVICE_HH #define MSXMULTIIODEVICE_HH #include "MSXMultiDevice.hh" #include namespace openmsx { class MSXMultiIODevice final : public MSXMultiDevice { public: using Devices = std::vector; explicit MSXMultiIODevice(const HardwareConfig& hwConf); ~MSXMultiIODevice(); void addDevice(MSXDevice* device); void removeDevice(MSXDevice* device); Devices& getDevices() { return devices; } // MSXDevice std::string getName() const override; void getNameList(TclObject& result) const override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; private: Devices devices; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/cpu/MSXMultiMemDevice.cc000066400000000000000000000073571257557151200216010ustar00rootroot00000000000000#include "MSXMultiMemDevice.hh" #include "DummyDevice.hh" #include "MSXCPUInterface.hh" #include "TclObject.hh" #include "likely.hh" #include "stl.hh" #include "unreachable.hh" #include "xrange.hh" #include #include namespace openmsx { MSXMultiMemDevice::Range::Range( unsigned base_, unsigned size_, MSXDevice& device_) : base(base_), size(size_), device(&device_) { } bool MSXMultiMemDevice::Range::operator==(const Range& other) const { return (base == other.base) && (size == other.size) && (device == other.device); } MSXMultiMemDevice::MSXMultiMemDevice(const HardwareConfig& hwConf) : MSXMultiDevice(hwConf) { // add sentinel at the end ranges.emplace_back(0x0000, 0x10000, getCPUInterface().getDummyDevice()); } MSXMultiMemDevice::~MSXMultiMemDevice() { assert(empty()); } static bool isInside(unsigned x, unsigned start, unsigned size) { return (x - start) < size; } static bool overlap(unsigned start1, unsigned size1, unsigned start2, unsigned size2) { return (isInside(start1, start2, size2)) || (isInside(start1 + size1 - 1, start2, size2)); } bool MSXMultiMemDevice::canAdd(int base, int size) { for (auto i : xrange(ranges.size() - 1)) { if (overlap(base, size, ranges[i].base, ranges[i].size)) { return false; } } return true; } void MSXMultiMemDevice::add(MSXDevice& device, int base, int size) { assert(canAdd(base, size)); ranges.insert(begin(ranges), Range(base, size, device)); } void MSXMultiMemDevice::remove(MSXDevice& device, int base, int size) { ranges.erase(find_unguarded(ranges, Range(base, size, device))); } std::vector MSXMultiMemDevice::getDevices() const { std::vector result; for (auto i : xrange(ranges.size() - 1)) { result.push_back(ranges[i].device); } return result; } std::string MSXMultiMemDevice::getName() const { TclObject list; getNameList(list); return list.getString().str(); } void MSXMultiMemDevice::getNameList(TclObject& result) const { for (auto& r : ranges) { const auto& name = r.device->getName(); if (!name.empty()) { result.addListElement(name); } } } const MSXMultiMemDevice::Range& MSXMultiMemDevice::searchRange(unsigned address) const { for (auto& r : ranges) { if (isInside(address, r.base, r.size)) { return r; } } UNREACHABLE; return ranges.back(); } MSXDevice* MSXMultiMemDevice::searchDevice(unsigned address) const { return searchRange(address).device; } byte MSXMultiMemDevice::readMem(word address, EmuTime::param time) { return searchDevice(address)->readMem(address, time); } byte MSXMultiMemDevice::peekMem(word address, EmuTime::param time) const { return searchDevice(address)->peekMem(address, time); } void MSXMultiMemDevice::writeMem(word address, byte value, EmuTime::param time) { searchDevice(address)->writeMem(address, value, time); } const byte* MSXMultiMemDevice::getReadCacheLine(word start) const { assert((start & CacheLine::HIGH) == start); // start is aligned // Because start is aligned we don't need to wory about the begin // address of the range. But we must make sure the end of the range // doesn't only fill a partial cacheline. const auto& range = searchRange(start); if (unlikely(((range.base + range.size) & CacheLine::HIGH) == start)) { // The end of this memory device only fills a partial // cacheline. This can't be cached. return nullptr; } return searchDevice(start)->getReadCacheLine(start); } byte* MSXMultiMemDevice::getWriteCacheLine(word start) const { assert((start & CacheLine::HIGH) == start); const auto& range = searchRange(start); if (unlikely(((range.base + range.size) & CacheLine::HIGH) == start)) { return nullptr; } return searchDevice(start)->getWriteCacheLine(start); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/cpu/MSXMultiMemDevice.hh000066400000000000000000000023671257557151200216070ustar00rootroot00000000000000#ifndef MSXMULTIMEMDEVICE_HH #define MSXMULTIMEMDEVICE_HH #include "MSXMultiDevice.hh" #include namespace openmsx { class MSXMultiMemDevice final : public MSXMultiDevice { public: MSXMultiMemDevice(const HardwareConfig& hwConf); ~MSXMultiMemDevice(); bool canAdd(int base, int size); void add(MSXDevice& device, int base, int size); void remove(MSXDevice& device, int base, int size); bool empty() const { return ranges.size() == 1; } std::vector getDevices() const; // MSXDevice std::string getName() const override; void getNameList(TclObject& result) const override; byte readMem(word address, EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte* getWriteCacheLine(word start) const override; private: struct Range { Range(unsigned base_, unsigned size_, MSXDevice& device_); bool operator==(const Range& other) const; unsigned base; unsigned size; MSXDevice* device; }; const Range& searchRange(unsigned address) const; MSXDevice* searchDevice(unsigned address) const; std::vector ranges; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/cpu/MSXWatchIODevice.cc000066400000000000000000000055261257557151200213420ustar00rootroot00000000000000#include "MSXWatchIODevice.hh" #include "MSXMotherBoard.hh" #include "Reactor.hh" #include "MSXCPUInterface.hh" #include "TclObject.hh" #include "Interpreter.hh" #include namespace openmsx { // class WatchIO WatchIO::WatchIO(MSXMotherBoard& motherboard_, WatchPoint::Type type, unsigned beginAddr, unsigned endAddr, TclObject command, TclObject condition, unsigned newId /*= -1*/) : WatchPoint(command, condition, type, beginAddr, endAddr, newId) , motherboard(motherboard_) { for (unsigned i = byte(beginAddr); i <= byte(endAddr); ++i) { ios.push_back(make_unique( *motherboard.getMachineConfig(), *this)); } } WatchIO::~WatchIO() { } MSXWatchIODevice& WatchIO::getDevice(byte port) { byte begin = getBeginAddress(); return *ios[port - begin]; } void WatchIO::doReadCallback(unsigned port) { auto& cpuInterface = motherboard.getCPUInterface(); if (cpuInterface.isFastForward()) return; auto& cliComm = motherboard.getReactor().getGlobalCliComm(); auto& interp = motherboard.getReactor().getInterpreter(); interp.setVariable("wp_last_address", TclObject(int(port))); // keep this object alive by holding a shared_ptr to it, for the case // this watchpoint deletes itself in checkAndExecute() auto keepAlive = shared_from_this(); checkAndExecute(cliComm, interp); interp.unsetVariable("wp_last_address"); } void WatchIO::doWriteCallback(unsigned port, unsigned value) { auto& cpuInterface = motherboard.getCPUInterface(); if (cpuInterface.isFastForward()) return; auto& cliComm = motherboard.getReactor().getGlobalCliComm(); auto& interp = motherboard.getReactor().getInterpreter(); interp.setVariable("wp_last_address", TclObject(int(port))); interp.setVariable("wp_last_value", TclObject(int(value))); // see comment in doReadCallback() above auto keepAlive = shared_from_this(); checkAndExecute(cliComm, interp); interp.unsetVariable("wp_last_address"); interp.unsetVariable("wp_last_value"); } // class MSXWatchIODevice MSXWatchIODevice::MSXWatchIODevice( const HardwareConfig& hwConf, WatchIO& watchIO_) : MSXMultiDevice(hwConf) , watchIO(watchIO_) , device(nullptr) { } std::string MSXWatchIODevice::getName() const { assert(device); return device->getName(); } byte MSXWatchIODevice::peekIO(word port, EmuTime::param time) const { assert(device); return device->peekIO(port, time); } byte MSXWatchIODevice::readIO(word port, EmuTime::param time) { assert(device); // first trigger watchpoint, then read from device watchIO.doReadCallback(port); return device->readIO(port, time); } void MSXWatchIODevice::writeIO(word port, byte value, EmuTime::param time) { assert(device); // first write to device, then trigger watchpoint device->writeIO(port, value, time); watchIO.doWriteCallback(port, value); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/cpu/MSXWatchIODevice.hh000066400000000000000000000023631257557151200213500ustar00rootroot00000000000000#ifndef MSXWATCHIODEVICE_HH #define MSXWATCHIODEVICE_HH #include "MSXMultiDevice.hh" #include "WatchPoint.hh" #include namespace openmsx { class MSXWatchIODevice; class WatchIO final : public WatchPoint , public std::enable_shared_from_this { public: WatchIO(MSXMotherBoard& motherboard, WatchPoint::Type type, unsigned beginAddr, unsigned endAddr, TclObject command, TclObject condition, unsigned newId = -1); ~WatchIO(); MSXWatchIODevice& getDevice(byte port); private: void doReadCallback(unsigned port); void doWriteCallback(unsigned port, unsigned value); MSXMotherBoard& motherboard; std::vector> ios; friend class MSXWatchIODevice; }; class MSXWatchIODevice final : public MSXMultiDevice { public: MSXWatchIODevice(const HardwareConfig& hwConf, WatchIO& watchIO); MSXDevice*& getDevicePtr() { return device; } private: // MSXDevice std::string getName() const override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; WatchIO& watchIO; MSXDevice* device; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/cpu/R800.hh000066400000000000000000000206151257557151200167730ustar00rootroot00000000000000#ifndef R800_HH #define R800_HH #include "CPUClock.hh" #include "CPURegs.hh" #include "Clock.hh" #include "likely.hh" #include "inline.hh" namespace openmsx { class R800TYPE : public CPUClock { public: void updateVisiblePage(byte page, byte primarySlot, byte secondarySlot) { extraMemoryDelay[page] = extraMemoryDelays[page][primarySlot][secondarySlot]; } void setDRAMmode(bool dram) { // TODO currently hardcoded, move to config file? unsigned val = dram ? 0 : 1; extraMemoryDelays[0][0][0] = val; // BIOS extraMemoryDelays[1][0][0] = val; // BASIC extraMemoryDelays[0][3][1] = val; // SUB-ROM extraMemoryDelays[1][3][1] = val; // KANJI-DRIVER } protected: template struct Normalize { static const bool value = B; }; static const int CLOCK_FREQ = 7159090; ALWAYS_INLINE unsigned haltStates() const { return 1; } // TODO check this ALWAYS_INLINE bool isR800() const { return true; } R800TYPE(EmuTime::param time, Scheduler& scheduler) : CPUClock(time, scheduler) , lastRefreshTime(time) { R800ForcePageBreak(); // TODO currently hardcoded, move to config file? for (int page = 0; page < 4; ++page) { for (int prim = 0; prim < 4; ++prim) { for (int sec = 0; sec < 4; ++sec) { unsigned val; if ((prim == 1) || (prim == 2)) { // external slot val = 2; } else if ((prim == 3) && (sec == 0)) { // internal RAM val = 0; } else { // internal ROM val = 1; } extraMemoryDelays[page][prim][sec] = val; } } } for (int page = 0; page < 4; ++page) { extraMemoryDelay[page] = extraMemoryDelays[page][0][0]; } } ALWAYS_INLINE void R800ForcePageBreak() { lastPage = -1; } template ALWAYS_INLINE void PRE_MEM(unsigned address) { int newPage = address >> 8; if (PRE_PB) { // there is a statically predictable page break at this // point -> 'add(1)' moved to static cost table } else { if (unlikely(newPage != lastPage) || unlikely(extraMemoryDelay[address >> 14])) { add(1); } } if (!POST_PB) { lastPage = newPage; } } template ALWAYS_INLINE void POST_MEM(unsigned address) { add(extraMemoryDelay[address >> 14]); if (POST_PB) { R800ForcePageBreak(); } } template ALWAYS_INLINE void PRE_WORD(unsigned address) { int newPage = address >> 8; if (PRE_PB) { // there is a statically predictable page break at this // point -> 'add(1)' moved to static cost table if (unlikely(extraMemoryDelay[address >> 14])) { add(1); } } else { if (unlikely(extraMemoryDelay[address >> 14])) { add(2); } else if (unlikely(newPage != lastPage)) { add(1); } } if (!POST_PB) { lastPage = newPage; } } template ALWAYS_INLINE void POST_WORD(unsigned address) { add(2 * extraMemoryDelay[address >> 14]); if (POST_PB) { R800ForcePageBreak(); } } ALWAYS_INLINE void R800Refresh(CPURegs& R) { // atoc documentation says refresh every 222 clocks // duration: 256/1024KB 13.5 clocks // 512KB 21.5 clocks // But 26/210 matches measurements much better // (loosly based on old measurements by Jon on his analogue scope) EmuTime time = getTimeFast(); if (unlikely(lastRefreshTime.getTicksTill_fast(time) >= 210)) { R800RefreshSlow(time, R); // slow-path not inline } } NEVER_INLINE void R800RefreshSlow(EmuTime::param time, CPURegs& R) { do { lastRefreshTime += 210; } while (unlikely(lastRefreshTime.getTicksTill_fast(time) >= 210)); waitForEvenCycle(0); add(25); R800ForcePageBreak(); // TODO check this R.incR(1); } void setTime(EmuTime::param time) { // Base class implementation. CPUClock::setTime(time); // Otherwise advance_fast() in R800Refresh() above, gets a too // large time interval. lastRefreshTime.reset(time); } ALWAYS_INLINE void setMemPtr(unsigned) { /* nothing*/ } ALWAYS_INLINE unsigned getMemPtr() const { return 0; } // dummy value static const int I = 6; // cycles for an I/O operation static const int O = 1; // wait for one cycle and wait for next even // clock cycle (to sync with slower IO bus) // the latter part must be implemented // dynamically (not here in static tables) static const int P = 1; // cycles for a (statically known) page-break static const int CC_LD_A_SS = 1+P+1, CC_LD_A_SS_1 = 1+P, CC_LD_A_NN = 3+P+1, CC_LD_A_NN_1 = 1, CC_LD_A_NN_2 = 3+P, CC_LD_A_I = 2, CC_LD_R_R = 1, CC_LD_R_N = 2, CC_LD_R_N_1 = 1, CC_LD_R_HL = 1+P+1, CC_LD_R_HL_1 = 1+P, CC_LD_R_XIX = 3+P+1, CC_LD_R_XIX_1 = 1, CC_LD_R_XIX_2 = 3+P, // +1 CC_LD_HL_R = 1+P+1, CC_LD_HL_R_1 = 1+P, CC_LD_HL_N = 2+P+1, CC_LD_HL_N_1 = 1, CC_LD_HL_N_2 = 2+P, CC_LD_SS_A = 1+P+1, CC_LD_SS_A_1 = 1+P, CC_LD_NN_A = 3+P+1, CC_LD_NN_A_1 = 1, CC_LD_NN_A_2 = 3+P, CC_LD_XIX_R = 3+P+1, CC_LD_XIX_R_1 = 1, CC_LD_XIX_R_2 = 3+P, // +1 CC_LD_XIX_N = 3+P+1, CC_LD_XIX_N_1 = 1, CC_LD_XIX_N_2 = 3+P, // +1 CC_LD_HL_XX = 3+P+2, CC_LD_HL_XX_1 = 1, CC_LD_HL_XX_2 = 3+P, CC_LD_SP_HL = 1, CC_LD_SS_NN = 3, CC_LD_SS_NN_1 = 1, CC_LD_XX_HL = 3+P+2, CC_LD_XX_HL_1 = 1, CC_LD_XX_HL_2 = 3+P, CC_CP_R = 1, CC_CP_N = 2, CC_CP_N_1 = 1, CC_CP_XHL = 1+P+1, CC_CP_XHL_1 = 1+P, CC_CP_XIX = 3+P+1, CC_CP_XIX_1 = 1, CC_CP_XIX_2 = 3+P, // +1 CC_INC_R = 1, CC_INC_XHL = 1+P+2+P+1, CC_INC_XHL_1 = 1, CC_INC_XHL_2 = 1+P+2+P, CC_INC_XIX = 3+P+2+P+1, CC_INC_XIX_1 = 1, EE_INC_XIX = 2, // +1 CC_INC_SS = 1, CC_ADD_HL_SS = 1, CC_ADC_HL_SS = 2, CC_LDI = 2+P+1+P+1, CC_LDI_1 = 2+P, CC_LDI_2 = 2+P+1+P, CC_LDIR = 2+P+1+P+1, CC_CPI = 2+P+2, CC_CPI_1 = 2+P, CC_CPIR = 2+P+3, // TODO check CC_PUSH = 2+P+2, CC_PUSH_1 = 2+P, CC_POP = 1+P+2, CC_POP_1 = 1+P, CC_CALL = 3+P+2, CC_CALL_1 = 1, EE_CALL = 1, CC_CALL_A = 3+P+2, CC_CALL_B = 3, CC_RST = 2+P+2, // TODO check CC_RET_A = 1+P+2, CC_RET_B = 1, EE_RET_C = 0, CC_RETN = 2+P+2, EE_RETN = 1, // TODO check CC_JP_A = 4, CC_JP_B = 3, CC_JP_1 = 1, CC_JP_HL = 2, CC_JR_A = 3, CC_JR_B = 2, CC_JR_1 = 1, CC_DJNZ = 3, EE_DJNZ = 0, CC_EX_SP_HL = 1+P+4, CC_EX_SP_HL_1 = 1+P, CC_EX_SP_HL_2 = 1+P+2, CC_BIT_R = 2, CC_BIT_XHL = 2+P+1, CC_BIT_XHL_1 = 2+P, CC_BIT_XIX = 3+P+1, CC_BIT_XIX_1 = 3+P, // +1 CC_SET_R = 2, CC_SET_XHL = 2+P+2+P+1, CC_SET_XHL_1 = 2+P, CC_SET_XHL_2 = 2+P+2+P, CC_SET_XIX = 3+P+2+P+1, EE_SET_XIX = 1, // +1 CC_RLA = 1, CC_RLD = 2+P+2+P+1, CC_RLD_1 = 2+P, CC_RLD_2 = 2+P+2+P, CC_IN_A_N = 2+O+I, CC_IN_A_N_1 = 1, CC_IN_A_N_2 = 2+O, CC_IN_R_C = 2+O+I, CC_IN_R_C_1 = 2+O, CC_INI = 2+O+I+P+1, CC_INI_1 = 2+O, CC_INI_2 = 2+O+I+P, CC_INIR = 2+O+I+P+1, // TODO check CC_OUT_N_A = 2+O+I, CC_OUT_N_A_1 = 1, CC_OUT_N_A_2 = 2+O, CC_OUT_C_R = 2+O+I, CC_OUT_C_R_1 = 2+O, CC_OUTI = 2+P+1+O+I, CC_OUTI_1 = 2+P, CC_OUTI_2 = 2+P+1+O, CC_OTIR = 2+P+1+O+I, // TODO check CC_EX = 1, CC_NOP = 1, CC_CCF = 1, CC_SCF = 1, CC_DAA = 1, CC_NEG = 2, CC_CPL = 1, CC_DI = 2, CC_EI = 1, CC_HALT = 1, // TODO check CC_IM = 3, CC_MULUB = 14, CC_MULUW = 36, CC_NMI = 1+P+2, EE_NMI_1 = -1, // TODO check CC_IRQ0 = 1+P+2, EE_IRQ0_1 = -1, // TODO check CC_IRQ1 = 1+P+2, EE_IRQ1_1 = -1, // TODO check CC_IRQ2 = 1+P+2+P+2, EE_IRQ2_1 = -1, CC_IRQ2_2 = 1+P+2+P, // TODO check CC_MAIN = 0, CC_DD = 1, CC_PREFIX = 1, CC_DD_CB = 1, // +1 EE_ED = 1, CC_RDMEM = 1, CC_WRMEM = 2; template void serialize(Archive& ar, unsigned version) { CPUClock::serialize(ar, version); ar.serialize("lastRefreshTime", lastRefreshTime); ar.serialize("lastPage", lastPage); ar.serialize("extraMemoryDelay", extraMemoryDelay); // don't serialize 'extraMemoryDelays', is initialized in // constructor and setDRAMmode() } private: Clock lastRefreshTime; int lastPage; unsigned extraMemoryDelays[4][4][4]; unsigned extraMemoryDelay[4]; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/cpu/VDPIODelay.cc000066400000000000000000000035511257557151200201700ustar00rootroot00000000000000#include "VDPIODelay.hh" #include "MSXCPU.hh" #include "MSXCPUInterface.hh" #include "DummyDevice.hh" #include "serialize.hh" #include namespace openmsx { VDPIODelay::VDPIODelay(const DeviceConfig& config, MSXCPUInterface& cpuInterface) : MSXDevice(config) , cpu(getCPU()) // used frequently, so cache it , lastTime(EmuTime::zero) { for (int port = 0x098; port <= 0x9B; ++port) { getInDevicePtr (port) = &cpuInterface.getDummyDevice(); getOutDevicePtr(port) = &cpuInterface.getDummyDevice(); } } const MSXDevice& VDPIODelay::getInDevice(byte port) const { assert((0x98 <= port) && (port <= 0x9B)); return *inDevices[port - 0x98]; } MSXDevice*& VDPIODelay::getInDevicePtr(byte port) { assert((0x98 <= port) && (port <= 0x9B)); return inDevices[port - 0x98]; } MSXDevice*& VDPIODelay::getOutDevicePtr(byte port) { assert((0x98 <= port) && (port <= 0x9B)); return outDevices[port - 0x98]; } byte VDPIODelay::readIO(word port, EmuTime::param time) { delay(time); return getInDevicePtr(byte(port))->readIO(byte(port), lastTime.getTime()); } byte VDPIODelay::peekIO(word port, EmuTime::param time) const { return getInDevice(byte(port)).peekIO(byte(port), time); } void VDPIODelay::writeIO(word port, byte value, EmuTime::param time) { delay(time); getOutDevicePtr(byte(port))->writeIO(byte(port), value, lastTime.getTime()); } void VDPIODelay::delay(EmuTime::param time) { cpu.waitCycles(1); if (cpu.isR800Active()) { // Number of cycles based on measurements on real HW. // See doc/turbor-vdp-io-timing.ods for details. lastTime += 62; // 8us if (time < lastTime.getTime()) { cpu.wait(lastTime.getTime()); return; } } lastTime.advance(time); } template void VDPIODelay::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("lastTime", lastTime); } INSTANTIATE_SERIALIZE_METHODS(VDPIODelay); } // namespace openmsx openMSX-RELEASE_0_12_0/src/cpu/VDPIODelay.hh000066400000000000000000000016131257557151200201770ustar00rootroot00000000000000#ifndef VDPIODELAY_HH #define VDPIODELAY_HH #include "MSXDevice.hh" #include "Clock.hh" namespace openmsx { class MSXCPU; class MSXCPUInterface; class VDPIODelay final : public MSXDevice { public: VDPIODelay(const DeviceConfig& config, MSXCPUInterface& cpuInterface); byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; const MSXDevice& getInDevice(byte port) const; MSXDevice*& getInDevicePtr (byte port); MSXDevice*& getOutDevicePtr(byte port); template void serialize(Archive& ar, unsigned version); private: void delay(EmuTime::param time); MSXCPU& cpu; MSXDevice* inDevices[4]; MSXDevice* outDevices[4]; /** Remembers the time at which last VDP I/O action took place. */ Clock<7159090> lastTime; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/cpu/WatchPoint.cc000066400000000000000000000010111257557151200203750ustar00rootroot00000000000000#include "WatchPoint.hh" #include namespace openmsx { unsigned WatchPoint::lastId = 0; WatchPoint::WatchPoint(TclObject command, TclObject condition, Type type_, unsigned beginAddr_, unsigned endAddr_, unsigned newId /*= -1*/) : BreakPointBase(command, condition) , id((newId == unsigned(-1)) ? ++lastId : newId) , beginAddr(beginAddr_), endAddr(endAddr_), type(type_) { assert(beginAddr <= endAddr); } WatchPoint::~WatchPoint() { } } // namespace openmsx openMSX-RELEASE_0_12_0/src/cpu/WatchPoint.hh000066400000000000000000000016731257557151200204250ustar00rootroot00000000000000#ifndef WATCHPOINT_HH #define WATCHPOINT_HH #include "BreakPointBase.hh" namespace openmsx { /** Base class for CPU breakpoints. * For performance reasons every bp is associated with exactly one * (immutable) address. */ class WatchPoint : public BreakPointBase { public: enum Type { READ_IO, WRITE_IO, READ_MEM, WRITE_MEM }; /** Begin and end address are inclusive (IOW range = [begin, end]) */ WatchPoint(TclObject command, TclObject condition, Type type, unsigned beginAddr, unsigned endAddr, unsigned newId = -1); virtual ~WatchPoint(); // needed for dynamic_cast unsigned getId() const { return id; } Type getType() const { return type; } unsigned getBeginAddress() const { return beginAddr; } unsigned getEndAddress() const { return endAddr; } private: unsigned id; unsigned beginAddr; unsigned endAddr; Type type; static unsigned lastId; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/cpu/Z80.hh000066400000000000000000000120731257557151200167220ustar00rootroot00000000000000#ifndef Z80_HH #define Z80_HH #include "CPUClock.hh" #include "inline.hh" #include namespace openmsx { class CPURegs; class Z80TYPE : public CPUClock { protected: template struct Normalize { static const bool value = false; }; static const int CLOCK_FREQ = 3579545; static const int WAIT_CYCLES = 1; Z80TYPE(EmuTime::param time, Scheduler& scheduler) : CPUClock(time, scheduler) { } ALWAYS_INLINE unsigned haltStates() const { return 4 + WAIT_CYCLES; } // HALT + M1 ALWAYS_INLINE bool isR800() const { return false; } template ALWAYS_INLINE void PRE_MEM (unsigned /*address*/) { } template < bool> ALWAYS_INLINE void POST_MEM (unsigned /*address*/) { } template ALWAYS_INLINE void PRE_WORD (unsigned /*address*/) { } template < bool> ALWAYS_INLINE void POST_WORD(unsigned /*address*/) { } ALWAYS_INLINE void R800Refresh(CPURegs& /*R*/) { } ALWAYS_INLINE void R800ForcePageBreak() { } ALWAYS_INLINE void setMemPtr(unsigned x) { memptr = x; } ALWAYS_INLINE unsigned getMemPtr() const { return memptr; } static const int CC_LD_A_SS = 5+3, CC_LD_A_SS_1 = 5+1, CC_LD_A_NN = 5+3+3+3, CC_LD_A_NN_1 = 5+1, CC_LD_A_NN_2 = 5+3+3+1, CC_LD_A_I = 5+6, CC_LD_R_R = 5, CC_LD_R_N = 5+3, CC_LD_R_N_1 = 5+1, CC_LD_R_HL = 5+3, CC_LD_R_HL_1 = 5+1, CC_LD_R_XIX = 5+3+5+3, CC_LD_R_XIX_1 = 5+1, CC_LD_R_XIX_2 = 5+3+5+1, // +5 CC_LD_HL_R = 5+3, CC_LD_HL_R_1 = 5+1, CC_LD_HL_N = 5+3+3, CC_LD_HL_N_1 = 5+1, CC_LD_HL_N_2 = 5+3+1, CC_LD_SS_A = 5+3, CC_LD_SS_A_1 = 5+1, CC_LD_NN_A = 5+3+3+3, CC_LD_NN_A_1 = 5+1, CC_LD_NN_A_2 = 5+3+3+1, CC_LD_XIX_R = 5+3+5+3, CC_LD_XIX_R_1 = 5+1, CC_LD_XIX_R_2 = 5+3+5+1, // +5 CC_LD_XIX_N = 5+3+5+3, CC_LD_XIX_N_1 = 5+1, CC_LD_XIX_N_2 = 5+3+5+1, // +5 CC_LD_HL_XX = 5+3+3+3+3, CC_LD_HL_XX_1 = 5+1, CC_LD_HL_XX_2 = 5+3+3+1, CC_LD_SP_HL = 7, CC_LD_SS_NN = 5+3+3, CC_LD_SS_NN_1 = 5+1, CC_LD_XX_HL = 5+3+3+3+3, CC_LD_XX_HL_1 = 5+1, CC_LD_XX_HL_2 = 5+3+3+1, CC_CP_R = 5, CC_CP_N = 5+3, CC_CP_N_1 = 5+1, CC_CP_XHL = 5+3, CC_CP_XHL_1 = 5+1, CC_CP_XIX = 5+3+5+3, CC_CP_XIX_1 = 5+1, CC_CP_XIX_2 = 5+3+5+1, // +5 CC_INC_R = 5, CC_INC_XHL = 5+4+3, CC_INC_XHL_1 = 5+1, CC_INC_XHL_2 = 5+4+1, CC_INC_XIX = 5+3+5+4+3, CC_INC_XIX_1 = 5+1, EE_INC_XIX = 8, // +5 CC_INC_SS = 7, CC_ADD_HL_SS = 5+4+3, CC_ADC_HL_SS = 5+5+4+3, CC_LDI = 5+5+3+5, CC_LDI_1 = 5+5+1, CC_LDI_2 = 5+5+3+1, CC_LDIR = 5+5+3+5+5, CC_CPI = 5+5+3+5, CC_CPI_1 = 5+5+1, CC_CPIR = 5+5+3+5+5, CC_PUSH = 6+3+3, CC_PUSH_1 = 6+1, CC_POP = 5+3+3, CC_POP_1 = 5+1, CC_CALL = 5+3+4+3+3, CC_CALL_1 = 5+1, EE_CALL = 6, CC_CALL_A = 5+3+4+3+3, CC_CALL_B = 5+3+3, CC_RST = 6+3+3, CC_RET_A = 5+3+3, CC_RET_B = 5, EE_RET_C = 1, CC_RETN = 5+5+3+3, EE_RETN = 5, CC_JP_A = 5+3+3, CC_JP_B = 5+3+3, CC_JP_1 = 5+1, CC_JP_HL = 5, CC_JR_A = 5+3+5, CC_JR_B = 5+3, CC_JR_1 = 5+1, CC_DJNZ = 6+3+5, EE_DJNZ = 1, CC_EX_SP_HL = 5+3+4+3+5, CC_EX_SP_HL_1 = 5+1, CC_EX_SP_HL_2 = 5+3+4+1, CC_BIT_R = 5+5, CC_BIT_XHL = 5+5+4, CC_BIT_XHL_1 = 5+5+1, CC_BIT_XIX = 5+3+5+4, CC_BIT_XIX_1 = 5+3+5+1, // +5 CC_SET_R = 5+5, CC_SET_XHL = 5+5+4+3, CC_SET_XHL_1 = 5+5+1, CC_SET_XHL_2 = 5+5+4+1, CC_SET_XIX = 5+3+5+4+3, EE_SET_XIX = 3, // +5 CC_RLA = 5, CC_RLD = 5+5+3+4+3, CC_RLD_1 = 5+5+1, CC_RLD_2 = 5+5+3+4+1, CC_IN_A_N = 5+3+4, CC_IN_A_N_1 = 5+1, CC_IN_A_N_2 = 5+3+1, CC_IN_R_C = 5+5+4, CC_IN_R_C_1 = 5+5+1, CC_INI = 5+6+4+3, CC_INI_1 = 5+6+1, CC_INI_2 = 5+6+4+1, CC_INIR = 5+6+4+3+5, CC_OUT_N_A = 5+3+4, CC_OUT_N_A_1 = 5+1, CC_OUT_N_A_2 = 5+3+1, CC_OUT_C_R = 5+5+4, CC_OUT_C_R_1 = 5+5+1, CC_OUTI = 5+6+3+4, CC_OUTI_1 = 5+6+1, CC_OUTI_2 = 5+6+3+1, CC_OTIR = 5+6+3+4+5, CC_EX = 5, CC_NOP = 5, CC_CCF = 5, CC_SCF = 5, CC_DAA = 5, CC_NEG = 5+5, CC_CPL = 5, CC_DI = 5, CC_EI = 5, CC_HALT = 5, CC_IM = 5+5, CC_MULUB = 0, CC_MULUW = 0, CC_NMI = 5+3+3, EE_NMI_1 = -1, CC_IRQ0 = 7+3+3, EE_IRQ0_1 = 1, CC_IRQ1 = 7+3+3, EE_IRQ1_1 = 1, CC_IRQ2 = 7+3+3+3+3, EE_IRQ2_1 = 1, CC_IRQ2_2 = 7+3+3+1, CC_MAIN = 1, CC_DD = 5, CC_PREFIX = 5+1, CC_DD_CB = 5+1, // +5 EE_ED = 5, CC_RDMEM = 3, CC_WRMEM = 3; // versions (version number shared with CPUCore<> class) // 1 -> initial version // 2 -> moved memptr from CPUCore to here template void serialize(Archive& ar, unsigned version) { CPUClock::serialize(ar, version); if (version >= 2) { ar.serialize("memptr", memptr); } } private: unsigned memptr; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/debugger/000077500000000000000000000000001257557151200170125ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/src/debugger/.gitignore000066400000000000000000000001761257557151200210060ustar00rootroot00000000000000/*.ROM /*.bb /*.bbg /*.da /*.dsk /*.prof /*.rom /*.rpo /*.swp /*.vim /.debug /.kdbgrc.openmsx /.xvpics /gmon.out /GNUmakefile openMSX-RELEASE_0_12_0/src/debugger/DasmTables.cc000066400000000000000000000307221257557151200213440ustar00rootroot00000000000000#include "DasmTables.hh" namespace openmsx { const char* mnemonic_xx_cb[256] = { "#","#","#","#","#","#","rlc Y" ,"#", "#","#","#","#","#","#","rrc Y" ,"#", "#","#","#","#","#","#","rl Y" ,"#", "#","#","#","#","#","#","rr Y" ,"#", "#","#","#","#","#","#","sla Y" ,"#", "#","#","#","#","#","#","sra Y" ,"#", "#","#","#","#","#","#","sll Y" ,"#", "#","#","#","#","#","#","srl Y" ,"#", "#","#","#","#","#","#","bit 0,Y","#", "#","#","#","#","#","#","bit 1,Y","#", "#","#","#","#","#","#","bit 2,Y","#", "#","#","#","#","#","#","bit 3,Y","#", "#","#","#","#","#","#","bit 4,Y","#", "#","#","#","#","#","#","bit 5,Y","#", "#","#","#","#","#","#","bit 6,Y","#", "#","#","#","#","#","#","bit 7,Y","#", "#","#","#","#","#","#","res 0,Y","#", "#","#","#","#","#","#","res 1,Y","#", "#","#","#","#","#","#","res 2,Y","#", "#","#","#","#","#","#","res 3,Y","#", "#","#","#","#","#","#","res 4,Y","#", "#","#","#","#","#","#","res 5,Y","#", "#","#","#","#","#","#","res 6,Y","#", "#","#","#","#","#","#","res 7,Y","#", "#","#","#","#","#","#","set 0,Y","#", "#","#","#","#","#","#","set 1,Y","#", "#","#","#","#","#","#","set 2,Y","#", "#","#","#","#","#","#","set 3,Y","#", "#","#","#","#","#","#","set 4,Y","#", "#","#","#","#","#","#","set 5,Y","#", "#","#","#","#","#","#","set 6,Y","#", "#","#","#","#","#","#","set 7,Y","#" }; const char* mnemonic_cb[256] = { "rlc b" ,"rlc c" ,"rlc d" ,"rlc e" ,"rlc h" ,"rlc l" ,"rlc (hl)" ,"rlc a" , "rrc b" ,"rrc c" ,"rrc d" ,"rrc e" ,"rrc h" ,"rrc l" ,"rrc (hl)" ,"rrc a" , "rl b" ,"rl c" ,"rl d" ,"rl e" ,"rl h" ,"rl l" ,"rl (hl)" ,"rl a" , "rr b" ,"rr c" ,"rr d" ,"rr e" ,"rr h" ,"rr l" ,"rr (hl)" ,"rr a" , "sla b" ,"sla c" ,"sla d" ,"sla e" ,"sla h" ,"sla l" ,"sla (hl)" ,"sla a" , "sra b" ,"sra c" ,"sra d" ,"sra e" ,"sra h" ,"sra l" ,"sra (hl)" ,"sra a" , "sll b" ,"sll c" ,"sll d" ,"sll e" ,"sll h" ,"sll l" ,"sll (hl)" ,"sll a" , "srl b" ,"srl c" ,"srl d" ,"srl e" ,"srl h" ,"srl l" ,"srl (hl)" ,"srl a" , "bit 0,b","bit 0,c","bit 0,d","bit 0,e","bit 0,h","bit 0,l","bit 0,(hl)","bit 0,a", "bit 1,b","bit 1,c","bit 1,d","bit 1,e","bit 1,h","bit 1,l","bit 1,(hl)","bit 1,a", "bit 2,b","bit 2,c","bit 2,d","bit 2,e","bit 2,h","bit 2,l","bit 2,(hl)","bit 2,a", "bit 3,b","bit 3,c","bit 3,d","bit 3,e","bit 3,h","bit 3,l","bit 3,(hl)","bit 3,a", "bit 4,b","bit 4,c","bit 4,d","bit 4,e","bit 4,h","bit 4,l","bit 4,(hl)","bit 4,a", "bit 5,b","bit 5,c","bit 5,d","bit 5,e","bit 5,h","bit 5,l","bit 5,(hl)","bit 5,a", "bit 6,b","bit 6,c","bit 6,d","bit 6,e","bit 6,h","bit 6,l","bit 6,(hl)","bit 6,a", "bit 7,b","bit 7,c","bit 7,d","bit 7,e","bit 7,h","bit 7,l","bit 7,(hl)","bit 7,a", "res 0,b","res 0,c","res 0,d","res 0,e","res 0,h","res 0,l","res 0,(hl)","res 0,a", "res 1,b","res 1,c","res 1,d","res 1,e","res 1,h","res 1,l","res 1,(hl)","res 1,a", "res 2,b","res 2,c","res 2,d","res 2,e","res 2,h","res 2,l","res 2,(hl)","res 2,a", "res 3,b","res 3,c","res 3,d","res 3,e","res 3,h","res 3,l","res 3,(hl)","res 3,a", "res 4,b","res 4,c","res 4,d","res 4,e","res 4,h","res 4,l","res 4,(hl)","res 4,a", "res 5,b","res 5,c","res 5,d","res 5,e","res 5,h","res 5,l","res 5,(hl)","res 5,a", "res 6,b","res 6,c","res 6,d","res 6,e","res 6,h","res 6,l","res 6,(hl)","res 6,a", "res 7,b","res 7,c","res 7,d","res 7,e","res 7,h","res 7,l","res 7,(hl)","res 7,a", "set 0,b","set 0,c","set 0,d","set 0,e","set 0,h","set 0,l","set 0,(hl)","set 0,a", "set 1,b","set 1,c","set 1,d","set 1,e","set 1,h","set 1,l","set 1,(hl)","set 1,a", "set 2,b","set 2,c","set 2,d","set 2,e","set 2,h","set 2,l","set 2,(hl)","set 2,a", "set 3,b","set 3,c","set 3,d","set 3,e","set 3,h","set 3,l","set 3,(hl)","set 3,a", "set 4,b","set 4,c","set 4,d","set 4,e","set 4,h","set 4,l","set 4,(hl)","set 4,a", "set 5,b","set 5,c","set 5,d","set 5,e","set 5,h","set 5,l","set 5,(hl)","set 5,a", "set 6,b","set 6,c","set 6,d","set 6,e","set 6,h","set 6,l","set 6,(hl)","set 6,a", "set 7,b","set 7,c","set 7,d","set 7,e","set 7,h","set 7,l","set 7,(hl)","set 7,a" }; const char* mnemonic_ed[256] = { "!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" , "!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" , "!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" , "!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" , "!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" , "!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" , "!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" , "!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" , "in b,(c)","out (c),b","sbc hl,bc","ld (W),bc","neg","retn","im 0","ld i,a", "in c,(c)","out (c),c","adc hl,bc","ld bc,(W)","!" ,"reti","!" ,"ld r,a", "in d,(c)","out (c),d","sbc hl,de","ld (W),de","!" ,"!" ,"im 1","ld a,i", "in e,(c)","out (c),e","adc hl,de","ld de,(W)","!" ,"!" ,"im 2","ld a,r", "in h,(c)","out (c),h","sbc hl,hl","ld (W),hl","!" ,"!" ,"!" ,"rrd" , "in l,(c)","out (c),l","adc hl,hl","ld hl,(W)","!" ,"!" ,"!" ,"rld" , "in f,(c)","out (c),0","sbc hl,sp","ld (W),sp","!" ,"!" ,"!" ,"!" , "in a,(c)","out (c),a","adc hl,sp","ld sp,(W)","!" ,"!" ,"!" ,"!" , "!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" , "!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" , "!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" , "!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" ,"!" , "ldi" ,"cpi" ,"ini" ,"outi" ,"!" ,"!" ,"!" ,"!" , "ldd" ,"cpd" ,"ind" ,"outd" ,"!" ,"!" ,"!" ,"!" , "ldir" ,"cpir" ,"inir" ,"otir" ,"!" ,"!" ,"!" ,"!" , "lddr" ,"cpdr" ,"indr" ,"otdr" ,"!" ,"!" ,"!" ,"!" , "!" ,"mulub a,b","!" ,"muluw hl,bc","!","!" ,"!" ,"!" , "!" ,"mulub a,c","!" ,"!" ,"!", "!" ,"!" ,"!" , "!" ,"mulub a,d","!" ,"muluw hl,de","!","!" ,"!" ,"!" , "!" ,"mulub a,e","!" ,"!" ,"!", "!" ,"!" ,"!" , "!" ,"mulub a,h","!" ,"muluw hl,hl","!","!" ,"!" ,"!" , "!" ,"mulub a,l","!" ,"!" ,"!", "!" ,"!" ,"!" , "!" ,"mulub a,(hl)","!" ,"muluw hl,sp","!","!" ,"!" ,"!" , "!" ,"mulub a,a","!" ,"!" ,"!", "!" ,"!" ,"!" }; const char* mnemonic_xx[256] = { "@" ,"@" ,"@" ,"@" ,"@" ,"@" ,"@" ,"@" , "@" ,"add I,bc","@" ,"@" ,"@" ,"@" ,"@" ,"@" , "@" ,"@" ,"@" ,"@" ,"@" ,"@" ,"@" ,"@" , "@" ,"add I,de","@" ,"@" ,"@" ,"@" ,"@" ,"@" , "@" ,"ld I,W" ,"ld (W),I","inc I" ,"inc Ih" ,"dec Ih" ,"ld Ih,B","@" , "@" ,"add I,I" ,"ld I,(W)","dec I" ,"inc Il" ,"dec Il" ,"ld Il,B","@" , "@" ,"@" ,"@" ,"@" ,"inc X" ,"dec X" ,"ld X,B" ,"@" , "@" ,"add I,sp","@" ,"@" ,"@" ,"@" ,"@" ,"@" , "@" ,"@" ,"@" ,"@" ,"ld b,Ih" ,"ld b,Il" ,"ld b,X" ,"@" , "@" ,"@" ,"@" ,"@" ,"ld c,Ih" ,"ld c,Il" ,"ld c,X" ,"@" , "@" ,"@" ,"@" ,"@" ,"ld d,Ih" ,"ld d,Il" ,"ld d,X" ,"@" , "@" ,"@" ,"@" ,"@" ,"ld e,Ih" ,"ld e,Il" ,"ld e,X" ,"@" , "ld Ih,b","ld Ih,c" ,"ld Ih,d" ,"ld Ih,e" ,"ld Ih,h" ,"ld Ih,l" ,"ld h,X" ,"ld Ih,a", "ld Il,b","ld Il,c" ,"ld Il,d" ,"ld Il,e" ,"ld Il,h" ,"ld Il,l" ,"ld l,X" ,"ld Il,a", "ld X,b" ,"ld X,c" ,"ld X,d" ,"ld X,e" ,"ld X,h" ,"ld X,l" ,"@" ,"ld X,a" , "@" ,"@" ,"@" ,"@" ,"ld a,Ih" ,"ld a,Il" ,"ld a,X" ,"@" , "@" ,"@" ,"@" ,"@" ,"add a,Ih","add a,Il","add a,X","@" , "@" ,"@" ,"@" ,"@" ,"adc a,Ih","adc a,Il","adc a,X","@" , "@" ,"@" ,"@" ,"@" ,"sub Ih" ,"sub Il" ,"sub X" ,"@" , "@" ,"@" ,"@" ,"@" ,"sbc a,Ih","sbc a,Il","sbc a,X","@" , "@" ,"@" ,"@" ,"@" ,"and Ih" ,"and Il" ,"and X" ,"@" , "@" ,"@" ,"@" ,"@" ,"xor Ih" ,"xor Il" ,"xor X" ,"@" , "@" ,"@" ,"@" ,"@" ,"or Ih" ,"or Il" ,"or X" ,"@" , "@" ,"@" ,"@" ,"@" ,"cp Ih" ,"cp Il" ,"cp X" ,"@" , "@" ,"@" ,"@" ,"@" ,"@" ,"@" ,"@" ,"@" , "@" ,"@" ,"@" ,"fd cb" ,"@" ,"@" ,"@" ,"@" , "@" ,"@" ,"@" ,"@" ,"@" ,"@" ,"@" ,"@" , "@" ,"@" ,"@" ,"@" ,"@" ,"@" ,"@" ,"@" , "@" ,"pop I" ,"@" ,"ex (sp),I","@" ,"push I" ,"@" ,"@" , "@" ,"jp (I)" ,"@" ,"@" ,"@" ,"@" ,"@" ,"@" , "@" ,"@" ,"@" ,"@" ,"@" ,"@" ,"@" ,"@" , "@" ,"ld sp,I" ,"@" ,"@" ,"@" ,"@" ,"@" ,"@" }; const char* mnemonic_main[256] = { "nop" ,"ld bc,W" ,"ld (bc),a","inc bc" ,"inc b" ,"dec b" ,"ld b,B" ,"rlca" , "ex af,af'","add hl,bc","ld a,(bc)","dec bc" ,"inc c" ,"dec c" ,"ld c,B" ,"rrca" , "djnz R" ,"ld de,W" ,"ld (de),a","inc de" ,"inc d" ,"dec d" ,"ld d,B" ,"rla" , "jr R" ,"add hl,de","ld a,(de)","dec de" ,"inc e" ,"dec e" ,"ld e,B" ,"rra" , "jr nz,R" ,"ld hl,W" ,"ld (W),hl","inc hl" ,"inc h" ,"dec h" ,"ld h,B" ,"daa" , "jr z,R" ,"add hl,hl","ld hl,(W)","dec hl" ,"inc l" ,"dec l" ,"ld l,B" ,"cpl" , "jr nc,R" ,"ld sp,W" ,"ld (W),a" ,"inc sp" ,"inc (hl)" ,"dec (hl)" ,"ld (hl),B" ,"scf" , "jr c,R" ,"add hl,sp","ld a,(W)" ,"dec sp" ,"inc a" ,"dec a" ,"ld a,B" ,"ccf" , "ld b,b" ,"ld b,c" ,"ld b,d" ,"ld b,e" ,"ld b,h" ,"ld b,l" ,"ld b,(hl)" ,"ld b,a" , "ld c,b" ,"ld c,c" ,"ld c,d" ,"ld c,e" ,"ld c,h" ,"ld c,l" ,"ld c,(hl)" ,"ld c,a" , "ld d,b" ,"ld d,c" ,"ld d,d" ,"ld d,e" ,"ld d,h" ,"ld d,l" ,"ld d,(hl)" ,"ld d,a" , "ld e,b" ,"ld e,c" ,"ld e,d" ,"ld e,e" ,"ld e,h" ,"ld e,l" ,"ld e,(hl)" ,"ld e,a" , "ld h,b" ,"ld h,c" ,"ld h,d" ,"ld h,e" ,"ld h,h" ,"ld h,l" ,"ld h,(hl)" ,"ld h,a" , "ld l,b" ,"ld l,c" ,"ld l,d" ,"ld l,e" ,"ld l,h" ,"ld l,l" ,"ld l,(hl)" ,"ld l,a" , "ld (hl),b","ld (hl),c","ld (hl),d","ld (hl),e" ,"ld (hl),h","ld (hl),l","halt" ,"ld (hl),a", "ld a,b" ,"ld a,c" ,"ld a,d" ,"ld a,e" ,"ld a,h" ,"ld a,l" ,"ld a,(hl)" ,"ld a,a" , "add a,b" ,"add a,c" ,"add a,d" ,"add a,e" ,"add a,h" ,"add a,l" ,"add a,(hl)","add a,a" , "adc a,b" ,"adc a,c" ,"adc a,d" ,"adc a,e" ,"adc a,h" ,"adc a,l" ,"adc a,(hl)","adc a,a" , "sub b" ,"sub c" ,"sub d" ,"sub e" ,"sub h" ,"sub l" ,"sub (hl)" ,"sub a" , "sbc a,b" ,"sbc a,c" ,"sbc a,d" ,"sbc a,e" ,"sbc a,h" ,"sbc a,l" ,"sbc a,(hl)","sbc a,a" , "and b" ,"and c" ,"and d" ,"and e" ,"and h" ,"and l" ,"and (hl)" ,"and a" , "xor b" ,"xor c" ,"xor d" ,"xor e" ,"xor h" ,"xor l" ,"xor (hl)" ,"xor a" , "or b" ,"or c" ,"or d" ,"or e" ,"or h" ,"or l" ,"or (hl)" ,"or a" , "cp b" ,"cp c" ,"cp d" ,"cp e" ,"cp h" ,"cp l" ,"cp (hl)" ,"cp a" , "ret nz" ,"pop bc" ,"jp nz,W" ,"jp W" ,"call nz,W","push bc" ,"add a,B" ,"rst 00h" , "ret z" ,"ret" ,"jp z,W" ,"cb" ,"call z,W" ,"call W" ,"adc a,B" ,"rst 08h" , "ret nc" ,"pop de" ,"jp nc,W" ,"out (B),a" ,"call nc,W","push de" ,"sub B" ,"rst 10h" , "ret c" ,"exx" ,"jp c,W" ,"in a,(B)" ,"call c,W" ,"dd" ,"sbc a,B" ,"rst 18h" , "ret po" ,"pop hl" ,"jp po,W" ,"ex (sp),hl","call po,W","push hl" ,"and B" ,"rst 20h" , "ret pe" ,"jp (hl)" ,"jp pe,W" ,"ex de,hl" ,"call pe,W","ed" ,"xor B" ,"rst 28h" , "ret p" ,"pop af" ,"jp p,W" ,"di" ,"call p,W" ,"push af" ,"or B" ,"rst 30h" , "ret m" ,"ld sp,hl" ,"jp m,W" ,"ei" ,"call m,W" ,"fd" ,"cp B" ,"rst 38h" }; } // namespace openmsx openMSX-RELEASE_0_12_0/src/debugger/DasmTables.hh000066400000000000000000000004401257557151200213500ustar00rootroot00000000000000#ifndef DASMTABLES_HH #define DASMTABLES_HH namespace openmsx { extern const char* mnemonic_xx_cb[256]; extern const char* mnemonic_cb[256]; extern const char* mnemonic_ed[256]; extern const char* mnemonic_xx[256]; extern const char* mnemonic_main[256]; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/debugger/Debuggable.hh000066400000000000000000000006301257557151200213530ustar00rootroot00000000000000#ifndef DEBUGGABLE_HH #define DEBUGGABLE_HH #include "openmsx.hh" #include namespace openmsx { class Debuggable { public: virtual unsigned getSize() const = 0; virtual const std::string& getDescription() const = 0; virtual byte read(unsigned address) = 0; virtual void write(unsigned address, byte value) = 0; protected: Debuggable() {} ~Debuggable() {} }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/debugger/Debugger.cc000066400000000000000000001045541257557151200210560ustar00rootroot00000000000000#include "Debugger.hh" #include "Debuggable.hh" #include "ProbeBreakPoint.hh" #include "MSXMotherBoard.hh" #include "Reactor.hh" #include "MSXCPU.hh" #include "MSXCPUInterface.hh" #include "BreakPoint.hh" #include "DebugCondition.hh" #include "MSXWatchIODevice.hh" #include "TclObject.hh" #include "CommandException.hh" #include "MemBuffer.hh" #include "StringOp.hh" #include "KeyRange.hh" #include "stl.hh" #include "unreachable.hh" #include "memory.hh" #include #include using std::shared_ptr; using std::make_shared; using std::string; using std::vector; using std::begin; using std::end; namespace openmsx { Debugger::Debugger(MSXMotherBoard& motherBoard_) : motherBoard(motherBoard_) , cmd(motherBoard.getCommandController(), motherBoard.getStateChangeDistributor(), motherBoard.getScheduler()) , cpu(nullptr) { } Debugger::~Debugger() { assert(!cpu); assert(debuggables.empty()); } void Debugger::registerDebuggable(string name, Debuggable& debuggable) { assert(!debuggables.contains(name)); debuggables.emplace_noDuplicateCheck(std::move(name), &debuggable); } void Debugger::unregisterDebuggable(string_ref name, Debuggable& debuggable) { assert(debuggables.contains(name)); assert(debuggables[name.str()] == &debuggable); (void)debuggable; debuggables.erase(name); } Debuggable* Debugger::findDebuggable(string_ref name) { auto it = debuggables.find(name); return (it != end(debuggables)) ? it->second : nullptr; } Debuggable& Debugger::getDebuggable(string_ref name) { Debuggable* result = findDebuggable(name); if (!result) { throw CommandException("No such debuggable: " + name); } return *result; } void Debugger::registerProbe(ProbeBase& probe) { assert(!probes.contains(probe.getName())); probes.insert_noDuplicateCheck(&probe); } void Debugger::unregisterProbe(ProbeBase& probe) { assert(probes.contains(probe.getName())); probes.erase(probe.getName()); } ProbeBase* Debugger::findProbe(string_ref name) { auto it = probes.find(name); return (it != end(probes)) ? *it : nullptr; } ProbeBase& Debugger::getProbe(string_ref name) { auto* result = findProbe(name); if (!result) { throw CommandException("No such probe: " + name); } return *result; } unsigned Debugger::insertProbeBreakPoint( TclObject command, TclObject condition, ProbeBase& probe, unsigned newId /*= -1*/) { auto bp = make_unique( command, condition, *this, probe, newId); unsigned result = bp->getId(); probeBreakPoints.push_back(std::move(bp)); return result; } void Debugger::removeProbeBreakPoint(string_ref name) { if (name.starts_with("pp#")) { // remove by id try { unsigned id = fast_stou(name.substr(3)); auto it = find_if(begin(probeBreakPoints), end(probeBreakPoints), [&](std::unique_ptr& e) { return e->getId() == id; }); if (it == end(probeBreakPoints)) { throw CommandException("No such breakpoint: " + name); } probeBreakPoints.erase(it); } catch (std::invalid_argument&) { // parse error in fast_stou() throw CommandException("No such breakpoint: " + name); } } else { // remove by probe, only works for unconditional bp auto it = find_if(begin(probeBreakPoints), end(probeBreakPoints), [&](std::unique_ptr& e) { return e->getProbe().getName() == name; }); if (it == end(probeBreakPoints)) { throw CommandException( "No (unconditional) breakpoint for probe: " + name); } probeBreakPoints.erase(it); } } void Debugger::removeProbeBreakPoint(ProbeBreakPoint& bp) { probeBreakPoints.erase(find_if_unguarded(probeBreakPoints, [&](ProbeBreakPoints::value_type& v) { return v.get() == &bp; })); } unsigned Debugger::setWatchPoint(TclObject command, TclObject condition, WatchPoint::Type type, unsigned beginAddr, unsigned endAddr, unsigned newId /*= -1*/) { shared_ptr wp; if ((type == WatchPoint::READ_IO) || (type == WatchPoint::WRITE_IO)) { wp = make_shared( motherBoard, type, beginAddr, endAddr, command, condition, newId); } else { wp = make_shared( command, condition, type, beginAddr, endAddr, newId); } motherBoard.getCPUInterface().setWatchPoint(wp); return wp->getId(); } void Debugger::transfer(Debugger& other) { // Copy watchpoints to new machine. assert(motherBoard.getCPUInterface().getWatchPoints().empty()); for (auto& wp : other.motherBoard.getCPUInterface().getWatchPoints()) { setWatchPoint(wp->getCommandObj(), wp->getConditionObj(), wp->getType(), wp->getBeginAddress(), wp->getEndAddress(), wp->getId()); } // Copy probes to new machine. assert(probeBreakPoints.empty()); for (auto& bp : other.probeBreakPoints) { if (ProbeBase* probe = findProbe(bp->getProbe().getName())) { insertProbeBreakPoint(bp->getCommandObj(), bp->getConditionObj(), *probe, bp->getId()); } } // Breakpoints and conditions are (currently) global, so no need to // copy those. } // class Debugger::Cmd static word getAddress(Interpreter& interp, array_ref tokens) { if (tokens.size() < 3) { throw CommandException("Missing argument"); } unsigned addr = tokens[2].getInt(interp); if (addr >= 0x10000) { throw CommandException("Invalid address"); } return addr; } Debugger::Cmd::Cmd(CommandController& commandController, StateChangeDistributor& stateChangeDistributor, Scheduler& scheduler) : RecordedCommand(commandController, stateChangeDistributor, scheduler, "debug") { } bool Debugger::Cmd::needRecord(array_ref tokens) const { // Note: it's crucial for security that only the write and write_block // subcommands are recorded and replayed. The 'set_bp' command for // example would allow to set a callback that can execute arbitrary Tcl // code. See comments in RecordedCommand for more details. if (tokens.size() < 2) return false; string_ref subCmd = tokens[1].getString(); return (subCmd == "write") || (subCmd == "write_block"); } void Debugger::Cmd::execute( array_ref tokens, TclObject& result, EmuTime::param /*time*/) { if (tokens.size() < 2) { throw CommandException("Missing argument"); } string_ref subCmd = tokens[1].getString(); if (subCmd == "read") { read(tokens, result); } else if (subCmd == "read_block") { readBlock(tokens, result); } else if (subCmd == "write") { write(tokens, result); } else if (subCmd == "write_block") { writeBlock(tokens, result); } else if (subCmd == "size") { size(tokens, result); } else if (subCmd == "desc") { desc(tokens, result); } else if (subCmd == "list") { list(result); } else if (subCmd == "step") { debugger().motherBoard.getCPUInterface().doStep(); } else if (subCmd == "cont") { debugger().motherBoard.getCPUInterface().doContinue(); } else if (subCmd == "disasm") { debugger().cpu->disasmCommand(getInterpreter(), tokens, result); } else if (subCmd == "break") { debugger().motherBoard.getCPUInterface().doBreak(); } else if (subCmd == "breaked") { result.setInt(debugger().motherBoard.getCPUInterface().isBreaked()); } else if (subCmd == "set_bp") { setBreakPoint(tokens, result); } else if (subCmd == "remove_bp") { removeBreakPoint(tokens, result); } else if (subCmd == "list_bp") { listBreakPoints(tokens, result); } else if (subCmd == "set_watchpoint") { setWatchPoint(tokens, result); } else if (subCmd == "remove_watchpoint") { removeWatchPoint(tokens, result); } else if (subCmd == "list_watchpoints") { listWatchPoints(tokens, result); } else if (subCmd == "set_condition") { setCondition(tokens, result); } else if (subCmd == "remove_condition") { removeCondition(tokens, result); } else if (subCmd == "list_conditions") { listConditions(tokens, result); } else if (subCmd == "probe") { probe(tokens, result); } else { throw SyntaxError(); } } void Debugger::Cmd::list(TclObject& result) { result.addListElements(keys(debugger().debuggables)); } void Debugger::Cmd::desc(array_ref tokens, TclObject& result) { if (tokens.size() != 3) { throw SyntaxError(); } Debuggable& device = debugger().getDebuggable(tokens[2].getString()); result.setString(device.getDescription()); } void Debugger::Cmd::size(array_ref tokens, TclObject& result) { if (tokens.size() != 3) { throw SyntaxError(); } Debuggable& device = debugger().getDebuggable(tokens[2].getString()); result.setInt(device.getSize()); } void Debugger::Cmd::read(array_ref tokens, TclObject& result) { if (tokens.size() != 4) { throw SyntaxError(); } Debuggable& device = debugger().getDebuggable(tokens[2].getString()); unsigned addr = tokens[3].getInt(getInterpreter()); if (addr >= device.getSize()) { throw CommandException("Invalid address"); } result.setInt(device.read(addr)); } void Debugger::Cmd::readBlock(array_ref tokens, TclObject& result) { if (tokens.size() != 5) { throw SyntaxError(); } auto& interp = getInterpreter(); Debuggable& device = debugger().getDebuggable(tokens[2].getString()); unsigned size = device.getSize(); unsigned addr = tokens[3].getInt(interp); if (addr >= size) { throw CommandException("Invalid address"); } unsigned num = tokens[4].getInt(interp); if (num > (size - addr)) { throw CommandException("Invalid size"); } MemBuffer buf(num); for (unsigned i = 0; i < num; ++i) { buf[i] = device.read(addr + i); } result.setBinary(buf.data(), num); } void Debugger::Cmd::write(array_ref tokens, TclObject& /*result*/) { if (tokens.size() != 5) { throw SyntaxError(); } auto& interp = getInterpreter(); Debuggable& device = debugger().getDebuggable(tokens[2].getString()); unsigned addr = tokens[3].getInt(interp); if (addr >= device.getSize()) { throw CommandException("Invalid address"); } unsigned value = tokens[4].getInt(interp); if (value >= 256) { throw CommandException("Invalid value"); } device.write(addr, value); } void Debugger::Cmd::writeBlock(array_ref tokens, TclObject& /*result*/) { if (tokens.size() != 5) { throw SyntaxError(); } Debuggable& device = debugger().getDebuggable(tokens[2].getString()); unsigned size = device.getSize(); unsigned addr = tokens[3].getInt(getInterpreter()); if (addr >= size) { throw CommandException("Invalid address"); } unsigned num; const byte* buf = tokens[4].getBinary(num); if ((num + addr) > size) { throw CommandException("Invalid size"); } for (unsigned i = 0; i < num; ++i) { device.write(addr + i, static_cast(buf[i])); } } void Debugger::Cmd::setBreakPoint(array_ref tokens, TclObject& result) { TclObject command("debug break"); TclObject condition; switch (tokens.size()) { case 5: // command command = tokens[4]; // fall-through case 4: // condition condition = tokens[3]; // fall-through case 3: { // address word addr = getAddress(getInterpreter(), tokens); BreakPoint bp(addr, command, condition); result.setString(StringOp::Builder() << "bp#" << bp.getId()); debugger().motherBoard.getCPUInterface().insertBreakPoint(bp); break; } default: if (tokens.size() < 3) { throw CommandException("Too few arguments."); } else { throw CommandException("Too many arguments."); } } } void Debugger::Cmd::removeBreakPoint( array_ref tokens, TclObject& /*result*/) { if (tokens.size() != 3) { throw SyntaxError(); } auto& interface = debugger().motherBoard.getCPUInterface(); auto& breakPoints = interface.getBreakPoints(); string_ref tmp = tokens[2].getString(); if (tmp.starts_with("bp#")) { // remove by id try { unsigned id = fast_stou(tmp.substr(3)); auto it = find_if(begin(breakPoints), end(breakPoints), [&](const BreakPoint& bp) { return bp.getId() == id; }); if (it == end(breakPoints)) { throw CommandException("No such breakpoint: " + tmp); } interface.removeBreakPoint(*it); } catch (std::invalid_argument&) { // parse error in fast_stou() throw CommandException("No such breakpoint: " + tmp); } } else { // remove by addr, only works for unconditional bp word addr = getAddress(getInterpreter(), tokens); auto range = equal_range(begin(breakPoints), end(breakPoints), addr, CompareBreakpoints()); auto it = find_if(range.first, range.second, [&](const BreakPoint& bp) { return bp.getCondition().empty(); }); if (it == range.second) { throw CommandException( "No (unconditional) breakpoint at address: " + tmp); } interface.removeBreakPoint(*it); } } void Debugger::Cmd::listBreakPoints( array_ref /*tokens*/, TclObject& result) { string res; auto& interface = debugger().motherBoard.getCPUInterface(); for (auto& bp : interface.getBreakPoints()) { TclObject line; line.addListElement(StringOp::Builder() << "bp#" << bp.getId()); line.addListElement("0x" + StringOp::toHexString(bp.getAddress(), 4)); line.addListElement(bp.getCondition()); line.addListElement(bp.getCommand()); res += line.getString() + '\n'; } result.setString(res); } void Debugger::Cmd::setWatchPoint(array_ref tokens, TclObject& result) { TclObject command("debug break"); TclObject condition; unsigned beginAddr, endAddr; WatchPoint::Type type; switch (tokens.size()) { case 6: // command command = tokens[5]; // fall-through case 5: // condition condition = tokens[4]; // fall-through case 4: { // address + type string_ref typeStr = tokens[2].getString(); unsigned max; if (typeStr == "read_io") { type = WatchPoint::READ_IO; max = 0x100; } else if (typeStr == "write_io") { type = WatchPoint::WRITE_IO; max = 0x100; } else if (typeStr == "read_mem") { type = WatchPoint::READ_MEM; max = 0x10000; } else if (typeStr == "write_mem") { type = WatchPoint::WRITE_MEM; max = 0x10000; } else { throw CommandException("Invalid type: " + typeStr); } auto& interp = getInterpreter(); if (tokens[3].getListLength(interp) == 2) { beginAddr = tokens[3].getListIndex(interp, 0).getInt(interp); endAddr = tokens[3].getListIndex(interp, 1).getInt(interp); if (endAddr < beginAddr) { throw CommandException( "Not a valid range: end address may " "not be smaller than begin address."); } } else { beginAddr = endAddr = tokens[3].getInt(interp); } if (endAddr >= max) { throw CommandException("Invalid address: out of range"); } break; } default: if (tokens.size() < 4) { throw CommandException("Too few arguments."); } else { throw CommandException("Too many arguments."); } } unsigned id = debugger().setWatchPoint( command, condition, type, beginAddr, endAddr); result.setString(StringOp::Builder() << "wp#" << id); } void Debugger::Cmd::removeWatchPoint( array_ref tokens, TclObject& /*result*/) { if (tokens.size() != 3) { throw SyntaxError(); } string_ref tmp = tokens[2].getString(); try { if (tmp.starts_with("wp#")) { // remove by id unsigned id = fast_stou(tmp.substr(3)); auto& interface = debugger().motherBoard.getCPUInterface(); for (auto& wp : interface.getWatchPoints()) { if (wp->getId() == id) { interface.removeWatchPoint(wp); return; } } } } catch (std::invalid_argument&) { // parse error in fast_stou() } throw CommandException("No such watchpoint: " + tmp); } void Debugger::Cmd::listWatchPoints( array_ref /*tokens*/, TclObject& result) { string res; auto& interface = debugger().motherBoard.getCPUInterface(); for (auto& wp : interface.getWatchPoints()) { TclObject line; line.addListElement(StringOp::Builder() << "wp#" << wp->getId()); string type; switch (wp->getType()) { case WatchPoint::READ_IO: type = "read_io"; break; case WatchPoint::WRITE_IO: type = "write_io"; break; case WatchPoint::READ_MEM: type = "read_mem"; break; case WatchPoint::WRITE_MEM: type = "write_mem"; break; default: UNREACHABLE; break; } line.addListElement(type); unsigned beginAddr = wp->getBeginAddress(); unsigned endAddr = wp->getEndAddress(); if (beginAddr == endAddr) { line.addListElement("0x" + StringOp::toHexString(beginAddr, 4)); } else { TclObject range; range.addListElement("0x" + StringOp::toHexString(beginAddr, 4)); range.addListElement("0x" + StringOp::toHexString(endAddr, 4)); line.addListElement(range); } line.addListElement(wp->getCondition()); line.addListElement(wp->getCommand()); res += line.getString() + '\n'; } result.setString(res); } void Debugger::Cmd::setCondition(array_ref tokens, TclObject& result) { TclObject command("debug break"); TclObject condition; switch (tokens.size()) { case 4: // command command = tokens[3]; // fall-through case 3: { // condition condition = tokens[2]; DebugCondition dc(command, condition); result.setString(StringOp::Builder() << "cond#" << dc.getId()); debugger().motherBoard.getCPUInterface().setCondition(dc); break; } default: if (tokens.size() < 3) { throw CommandException("Too few arguments."); } else { throw CommandException("Too many arguments."); } } } void Debugger::Cmd::removeCondition( array_ref tokens, TclObject& /*result*/) { if (tokens.size() != 3) { throw SyntaxError(); } string_ref tmp = tokens[2].getString(); try { if (tmp.starts_with("cond#")) { // remove by id unsigned id = fast_stou(tmp.substr(5)); auto& interface = debugger().motherBoard.getCPUInterface(); for (auto& c : interface.getConditions()) { if (c.getId() == id) { interface.removeCondition(c); return; } } } } catch (std::invalid_argument&) { // parse error in fast_stou() } throw CommandException("No such condition: " + tmp); } void Debugger::Cmd::listConditions( array_ref /*tokens*/, TclObject& result) { string res; auto& interface = debugger().motherBoard.getCPUInterface(); for (auto& c : interface.getConditions()) { TclObject line; line.addListElement(StringOp::Builder() << "cond#" << c.getId()); line.addListElement(c.getCondition()); line.addListElement(c.getCommand()); res += line.getString() + '\n'; } result.setString(res); } void Debugger::Cmd::probe(array_ref tokens, TclObject& result) { if (tokens.size() < 3) { throw CommandException("Missing argument"); } string_ref subCmd = tokens[2].getString(); if (subCmd == "list") { probeList(tokens, result); } else if (subCmd == "desc") { probeDesc(tokens, result); } else if (subCmd == "read") { probeRead(tokens, result); } else if (subCmd == "set_bp") { probeSetBreakPoint(tokens, result); } else if (subCmd == "remove_bp") { probeRemoveBreakPoint(tokens, result); } else if (subCmd == "list_bp") { probeListBreakPoints(tokens, result); } else { throw SyntaxError(); } } void Debugger::Cmd::probeList(array_ref /*tokens*/, TclObject& result) { // TODO use transform_iterator or transform_view for (auto* p : debugger().probes) { result.addListElement(p->getName()); } } void Debugger::Cmd::probeDesc(array_ref tokens, TclObject& result) { if (tokens.size() != 4) { throw SyntaxError(); } ProbeBase& probe = debugger().getProbe(tokens[3].getString()); result.setString(probe.getDescription()); } void Debugger::Cmd::probeRead(array_ref tokens, TclObject& result) { if (tokens.size() != 4) { throw SyntaxError(); } ProbeBase& probe = debugger().getProbe(tokens[3].getString()); result.setString(probe.getValue()); } void Debugger::Cmd::probeSetBreakPoint( array_ref tokens, TclObject& result) { TclObject command("debug break"); TclObject condition; ProbeBase* probe; switch (tokens.size()) { case 6: // command command = tokens[5]; // fall-through case 5: // condition condition = tokens[4]; // fall-through case 4: { // probe probe = &debugger().getProbe(tokens[3].getString()); break; } default: if (tokens.size() < 4) { throw CommandException("Too few arguments."); } else { throw CommandException("Too many arguments."); } } unsigned id = debugger().insertProbeBreakPoint(command, condition, *probe); result.setString(StringOp::Builder() << "pp#" << id); } void Debugger::Cmd::probeRemoveBreakPoint( array_ref tokens, TclObject& /*result*/) { if (tokens.size() != 4) { throw SyntaxError(); } debugger().removeProbeBreakPoint(tokens[3].getString()); } void Debugger::Cmd::probeListBreakPoints( array_ref /*tokens*/, TclObject& result) { string res; for (auto& p : debugger().probeBreakPoints) { TclObject line; line.addListElement(StringOp::Builder() << "pp#" << p->getId()); line.addListElement(p->getProbe().getName()); line.addListElement(p->getCondition()); line.addListElement(p->getCommand()); res += line.getString() + '\n'; } result.setString(res); } string Debugger::Cmd::help(const vector& tokens) const { static const string generalHelp = "debug []\n" " Possible subcommands are:\n" " list returns a list of all debuggables\n" " desc returns a description of this debuggable\n" " size returns the size of this debuggable\n" " read read a byte from a debuggable\n" " write write a byte to a debuggable\n" " read_block read a whole block at once\n" " write_block write a whole block at once\n" " set_bp insert a new breakpoint\n" " remove_bp remove a certain breakpoint\n" " list_bp list the active breakpoints\n" " set_watchpoint insert a new watchpoint\n" " remove_watchpoint remove a certain watchpoint\n" " list_watchpoints list the active watchpoints\n" " set_condition insert a new condition\n" " remove_condition remove a certain condition\n" " list_conditions list the active conditions\n" " probe probe related subcommands\n" " cont continue execution after break\n" " step execute one instruction\n" " break break CPU at current position\n" " breaked query CPU breaked status\n" " disasm disassemble instructions\n" " The arguments are specific for each subcommand.\n" " Type 'help debug ' for help about a specific subcommand.\n"; static const string listHelp = "debug list\n" " Returns a list with the names of all 'debuggables'.\n" " These names are used in other debug subcommands.\n"; static const string descHelp = "debug desc \n" " Returns a description for the debuggable with given name.\n"; static const string sizeHelp = "debug size \n" " Returns the size (in bytes) of the debuggable with given name.\n"; static const string readHelp = "debug read \n" " Read a byte at offset from the given debuggable.\n" " The offset must be smaller than the value returned from the " "'size' subcommand\n" " Note that openMSX comes with a bunch of Tcl scripts that make " "some of the debug reads much more convenient (e.g. reading from " "Z80 or VDP registers). See the Console Command Reference for more " "details about these.\n"; static const string writeHelp = "debug write \n" " Write a byte to the given debuggable at a certain offset.\n" " The offset must be smaller than the value returned from the " "'size' subcommand\n"; static const string readBlockHelp = "debug read_block \n" " Read a whole block at once. This is equivalent with repeated " "invocations of the 'read' subcommand, but using this subcommand " "may be faster. The result is a Tcl binary string (see Tcl manual).\n" " The block is specified as size/offset in the debuggable. The " "complete block must fit in the debuggable (see the 'size' " "subcommand).\n"; static const string writeBlockHelp = "debug write_block \n" " Write a whole block at once. This is equivalent with repeated " "invocations of the 'write' subcommand, but using this subcommand " "may be faster. The argument must be a Tcl binary string " "(see Tcl manual).\n" " The block has a size and an offset in the debuggable. The " "complete block must fit in the debuggable (see the 'size' " "subcommand).\n"; static const string setBpHelp = "debug set_bp [] []\n" " Insert a new breakpoint at given address. When the CPU is about " "to execute the instruction at this address, execution will be " "breaked. At least this is the default behaviour, see next " "paragraphs.\n" " Optionally you can specify a condition. When the CPU reaches " "the breakpoint this condition is evaluated, only when the condition " "evaluated to true execution will be breaked.\n" " A condition must be specified as a Tcl expression. For example\n" " debug set_bp 0xf37d {[reg C] == 0x2F}\n" " This breaks on address 0xf37d but only when Z80 register C has the " "value 0x2F.\n" " Also optionally you can specify a command that should be " "executed when the breakpoint is reached (and condition is true). " "By default this command is 'debug break'.\n" " The result of this command is a breakpoint ID. This ID can " "later be used to remove this breakpoint again.\n"; static const string removeBpHelp = "debug remove_bp \n" " Remove the breakpoint with given ID again. You can use the " "'list_bp' subcommand to see all valid IDs.\n"; static const string listBpHelp = "debug list_bp\n" " Lists all active breakpoints. The result is printed in 4 " "columns. The first column contains the breakpoint ID. The " "second one has the address. The third has the condition " "(default condition is empty). And the last column contains " "the command that will be executed (default is 'debug break').\n"; static const string setWatchPointHelp = "debug set_watchpoint [] []\n" " Insert a new watchpoint of given type on the given region, " "there can be an optional condition and alternative command. See " "the 'set_bp' subcommand for details about these last two.\n" " Type must be one of the following:\n" " read_io break when CPU reads from given IO port(s)\n" " write_io break when CPU writes to given IO port(s)\n" " read_mem break when CPU reads from given memory location(s)\n" " write_mem break when CPU writes to given memory location(s)\n" " Region is either a single value, this corresponds to a single " "memory location or IO port. Otherwise region must be a list of " "two values (enclosed in braces) that specify a begin and end " "point of a whole memory region or a range of IO ports.\n" "During the execution of , the following global Tcl " "variables are set:\n" " ::wp_last_address this is the actual address of the mem/io " "read/write that triggered the watchpoint\n" " ::wp_last_value this is the actual value that was written " "by the mem/io write that triggered the watchpoint\n" "Examples:\n" " debug set_watchpoint write_io 0x99 {[reg A] == 0x81}\n" " debug set_watchpoint read_mem {0xfbe5 0xfbef}\n"; static const string removeWatchPointHelp = "debug remove_watchpoint \n" " Remove the watchpoint with given ID again. You can use the " "'list_watchpoints' subcommand to see all valid IDs.\n"; static const string listWatchPointsHelp = "debug list_watchpoints\n" " Lists all active watchpoints. The result is similar to the " "'list_bp' subcommand, but there is an extra column (2nd column) " "that contains the type of the watchpoint.\n"; static const string setCondHelp = "debug set_condition []\n" " Insert a new condition. These are much like breakpoints, " "except that they are checked before every instruction " "(breakpoints are tied to a specific address).\n" " Conditions will slow down simulation MUCH more than " "breakpoints. So only use them when you don't care about " "simulation speed (when you're debugging this is usually not " "a problem).\n" " See 'help debug set_bp' for more details.\n"; static const string removeCondHelp = "debug remove_condition \n" " Remove the condition with given ID again. You can use the " "'list_conditions' subcommand to see all valid IDs.\n"; static const string listCondHelp = "debug list_conditions\n" " Lists all active conditions. The result is similar to the " "'list_bp' subcommand, but without the 2nd column that would " "show the address.\n"; static const string probeHelp = "debug probe []\n" " Possible subcommands are:\n" " list returns a list of all probes\n" " desc returns a description of this probe\n" " read returns the current value of this probe\n" " set_bp [] [] set a breakpoint on the given probe\n" " remove_bp remove the given breakpoint\n" " list_bp returns a list of breakpoints that are set on probes\n"; static const string contHelp = "debug cont\n" " Continue execution after CPU was breaked.\n"; static const string stepHelp = "debug step\n" " Execute one instruction. This command is only meaningful in " "break mode.\n"; static const string breakHelp = "debug break\n" " Immediately break CPU execution. When CPU was already breaked " "this command has no effect.\n"; static const string breakedHelp = "debug breaked\n" " Query the CPU breaked status. Returns '1' when CPU was " "breaked, '0' otherwise.\n"; static const string disasmHelp = "debug disasm \n" " Disassemble the instruction at the given address. The result " "is a Tcl list. The first element in the list contains a textual " "representation of the instruction, the next elements contain the " "bytes that make up this instruction (thus the length of the " "resulting list can be used to derive the number of bytes in the " "instruction).\n" " Note that openMSX comes with a 'disasm' Tcl script that is much " "more convenient to use than this subcommand."; static const string unknownHelp = "Unknown subcommand, use 'help debug' to see a list of valid " "subcommands.\n"; if (tokens.size() == 1) { return generalHelp; } else if (tokens[1] == "list") { return listHelp; } else if (tokens[1] == "desc") { return descHelp; } else if (tokens[1] == "size") { return sizeHelp; } else if (tokens[1] == "read") { return readHelp; } else if (tokens[1] == "write") { return writeHelp; } else if (tokens[1] == "read_block") { return readBlockHelp; } else if (tokens[1] == "write_block") { return writeBlockHelp; } else if (tokens[1] == "set_bp") { return setBpHelp; } else if (tokens[1] == "remove_bp") { return removeBpHelp; } else if (tokens[1] == "list_bp") { return listBpHelp; } else if (tokens[1] == "set_watchpoint") { return setWatchPointHelp; } else if (tokens[1] == "remove_watchpoint") { return removeWatchPointHelp; } else if (tokens[1] == "list_watchpoints") { return listWatchPointsHelp; } else if (tokens[1] == "set_condition") { return setCondHelp; } else if (tokens[1] == "remove_condition") { return removeCondHelp; } else if (tokens[1] == "list_conditions") { return listCondHelp; } else if (tokens[1] == "probe") { return probeHelp; } else if (tokens[1] == "cont") { return contHelp; } else if (tokens[1] == "step") { return stepHelp; } else if (tokens[1] == "break") { return breakHelp; } else if (tokens[1] == "breaked") { return breakedHelp; } else if (tokens[1] == "disasm") { return disasmHelp; } else { return unknownHelp; } } vector Debugger::Cmd::getBreakPointIds() const { vector bpids; auto& interface = debugger().motherBoard.getCPUInterface(); for (auto& bp : interface.getBreakPoints()) { bpids.push_back(StringOp::Builder() << "bp#" << bp.getId()); } return bpids; } vector Debugger::Cmd::getWatchPointIds() const { vector wpids; auto& interface = debugger().motherBoard.getCPUInterface(); for (auto& w : interface.getWatchPoints()) { wpids.push_back(StringOp::Builder() << "wp#" << w->getId()); } return wpids; } vector Debugger::Cmd::getConditionIds() const { vector condids; auto& interface = debugger().motherBoard.getCPUInterface(); for (auto& c : interface.getConditions()) { condids.push_back(StringOp::Builder() << "cond#" << c.getId()); } return condids; } void Debugger::Cmd::tabCompletion(vector& tokens) const { static const char* const singleArgCmds[] = { "list", "step", "cont", "break", "breaked", "list_bp", "list_watchpoints", "list_conditions", }; static const char* const debuggableArgCmds[] = { "desc", "size", "read", "read_block", "write", "write_block", }; static const char* const otherCmds[] = { "disasm", "set_bp", "remove_bp", "set_watchpoint", "remove_watchpoint", "set_condition", "remove_condition", "probe", }; switch (tokens.size()) { case 2: { vector cmds; cmds.insert(end(cmds), begin(singleArgCmds), end(singleArgCmds)); cmds.insert(end(cmds), begin(debuggableArgCmds), end(debuggableArgCmds)); cmds.insert(end(cmds), begin(otherCmds), end(otherCmds)); completeString(tokens, cmds); break; } case 3: if (!contains(singleArgCmds, tokens[1])) { // this command takes (an) argument(s) if (contains(debuggableArgCmds, tokens[1])) { // it takes a debuggable here completeString(tokens, keys(debugger().debuggables)); } else if (tokens[1] == "remove_bp") { // this one takes a bp id completeString(tokens, getBreakPointIds()); } else if (tokens[1] == "remove_watchpoint") { // this one takes a wp id completeString(tokens, getWatchPointIds()); } else if (tokens[1] == "remove_condition") { // this one takes a cond id completeString(tokens, getConditionIds()); } else if (tokens[1] == "set_watchpoint") { static const char* const types[] = { "write_io", "write_mem", "read_io", "read_mem", }; completeString(tokens, types); } else if (tokens[1] == "probe") { static const char* const subCmds[] = { "list", "desc", "read", "set_bp", "remove_bp", "list_bp", }; completeString(tokens, subCmds); } } break; case 4: if ((tokens[1] == "probe") && ((tokens[2] == "desc") || (tokens[2] == "read") || (tokens[2] == "set_bp"))) { std::vector probes; for (auto* p : debugger().probes) { probes.push_back(p->getName()); } completeString(tokens, probes); } break; } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/debugger/Debugger.hh000066400000000000000000000102151257557151200210560ustar00rootroot00000000000000#ifndef DEBUGGER_HH #define DEBUGGER_HH #include "Probe.hh" #include "RecordedCommand.hh" #include "WatchPoint.hh" #include "hash_map.hh" #include "string_ref.hh" #include "noncopyable.hh" #include "outer.hh" #include "xxhash.hh" #include #include namespace openmsx { class MSXMotherBoard; class Debuggable; class ProbeBase; class ProbeBreakPoint; class MSXCPU; class Debugger : private noncopyable { public: explicit Debugger(MSXMotherBoard& motherBoard); ~Debugger(); void registerDebuggable (std::string name, Debuggable& interface); void unregisterDebuggable (string_ref name, Debuggable& interface); Debuggable* findDebuggable(string_ref name); void registerProbe (ProbeBase& probe); void unregisterProbe(ProbeBase& probe); ProbeBase* findProbe(string_ref name); void removeProbeBreakPoint(ProbeBreakPoint& bp); void setCPU(MSXCPU* cpu_) { cpu = cpu_; } void transfer(Debugger& other); MSXMotherBoard& getMotherBoard() { return motherBoard; } private: Debuggable& getDebuggable(string_ref name); ProbeBase& getProbe(string_ref name); unsigned insertProbeBreakPoint( TclObject command, TclObject condition, ProbeBase& probe, unsigned newId = -1); void removeProbeBreakPoint(string_ref name); unsigned setWatchPoint(TclObject command, TclObject condition, WatchPoint::Type type, unsigned beginAddr, unsigned endAddr, unsigned newId = -1); MSXMotherBoard& motherBoard; class Cmd final : public RecordedCommand { public: Cmd(CommandController& commandController, StateChangeDistributor& stateChangeDistributor, Scheduler& scheduler); bool needRecord(array_ref tokens) const override; void execute(array_ref tokens, TclObject& result, EmuTime::param time) override; std::string help(const std::vector& tokens) const override; void tabCompletion(std::vector& tokens) const override; private: Debugger& debugger() { return OUTER(Debugger, cmd); } const Debugger& debugger() const { return OUTER(Debugger, cmd); } void list(TclObject& result); void desc(array_ref tokens, TclObject& result); void size(array_ref tokens, TclObject& result); void read(array_ref tokens, TclObject& result); void readBlock(array_ref tokens, TclObject& result); void write(array_ref tokens, TclObject& result); void writeBlock(array_ref tokens, TclObject& result); void setBreakPoint(array_ref tokens, TclObject& result); void removeBreakPoint(array_ref tokens, TclObject& result); void listBreakPoints(array_ref tokens, TclObject& result); std::vector getBreakPointIds() const; std::vector getWatchPointIds() const; std::vector getConditionIds() const; void setWatchPoint(array_ref tokens, TclObject& result); void removeWatchPoint(array_ref tokens, TclObject& result); void listWatchPoints(array_ref tokens, TclObject& result); void setCondition(array_ref tokens, TclObject& result); void removeCondition(array_ref tokens, TclObject& result); void listConditions(array_ref tokens, TclObject& result); void probe(array_ref tokens, TclObject& result); void probeList(array_ref tokens, TclObject& result); void probeDesc(array_ref tokens, TclObject& result); void probeRead(array_ref tokens, TclObject& result); void probeSetBreakPoint(array_ref tokens, TclObject& result); void probeRemoveBreakPoint(array_ref tokens, TclObject& result); void probeListBreakPoints(array_ref tokens, TclObject& result); } cmd; struct NameFromProbe { const std::string& operator()(const ProbeBase* p) const { return p->getName(); } }; hash_map debuggables; hash_set probes; using ProbeBreakPoints = std::vector>; ProbeBreakPoints probeBreakPoints; MSXCPU* cpu; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/debugger/Probe.cc000066400000000000000000000012021257557151200203630ustar00rootroot00000000000000#include "Probe.hh" #include "Debugger.hh" namespace openmsx { ProbeBase::ProbeBase(Debugger& debugger_, const std::string& name_, const std::string& description_) : debugger(debugger_) , name(name_) , description(description_) { debugger.registerProbe(*this); } ProbeBase::~ProbeBase() { debugger.unregisterProbe(*this); } Probe::Probe(Debugger& debugger, const std::string& name, const std::string& description) : ProbeBase(debugger, name, description) { } void Probe::signal() { notify(); } std::string Probe::getValue() const { return ""; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/debugger/Probe.hh000066400000000000000000000030431257557151200204020ustar00rootroot00000000000000#ifndef PROBE_HH #define PROBE_HH #include "Subject.hh" #include "StringOp.hh" #include namespace openmsx { class Debugger; class ProbeBase : public Subject { public: const std::string& getName() const { return name; } const std::string& getDescription() const { return description; } virtual std::string getValue() const = 0; protected: ProbeBase(Debugger& debugger, const std::string& name, const std::string& description); ~ProbeBase(); private: Debugger& debugger; const std::string name; const std::string description; }; template class Probe final : public ProbeBase { public: Probe(Debugger& debugger, const std::string& name, const std::string& description, const T& t); const T& operator=(const T& newValue) { if (value != newValue) { value = newValue; notify(); } return value; } operator const T&() const { return value; } private: std::string getValue() const override; T value; }; template Probe::Probe(Debugger& debugger, const std::string& name, const std::string& description, const T& t) : ProbeBase(debugger, name, description) , value(t) { } template std::string Probe::getValue() const { return StringOp::toString(value); } // specialization for void template<> class Probe final : public ProbeBase { public: Probe(Debugger& debugger, const std::string& name, const std::string& description); void signal(); private: std::string getValue() const override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/debugger/ProbeBreakPoint.cc000066400000000000000000000016731257557151200223560ustar00rootroot00000000000000#include "ProbeBreakPoint.hh" #include "Probe.hh" #include "Debugger.hh" #include "MSXMotherBoard.hh" #include "Reactor.hh" #include "TclObject.hh" namespace openmsx { unsigned ProbeBreakPoint::lastId = 0; ProbeBreakPoint::ProbeBreakPoint( TclObject command, TclObject condition, Debugger& debugger_, ProbeBase& probe_, unsigned newId /*= -1*/) : BreakPointBase(command, condition) , debugger(debugger_) , probe(probe_) , id((newId == unsigned(-1)) ? ++lastId : newId) { probe.attach(*this); } ProbeBreakPoint::~ProbeBreakPoint() { probe.detach(*this); } void ProbeBreakPoint::update(const ProbeBase& /*subject*/) { auto& reactor = debugger.getMotherBoard().getReactor(); auto& cliComm = reactor.getGlobalCliComm(); auto& interp = reactor.getInterpreter(); checkAndExecute(cliComm, interp); } void ProbeBreakPoint::subjectDeleted(const ProbeBase& /*subject*/) { debugger.removeProbeBreakPoint(*this); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/debugger/ProbeBreakPoint.hh000066400000000000000000000015141257557151200223620ustar00rootroot00000000000000#ifndef PROBEBREAKPOINT_HH #define PROBEBREAKPOINT_HH #include "BreakPointBase.hh" #include "Observer.hh" namespace openmsx { class Debugger; class ProbeBase; class ProbeBreakPoint final : public BreakPointBase , private Observer { public: ProbeBreakPoint(TclObject command, TclObject condition, Debugger& debugger, ProbeBase& probe, unsigned newId = -1); ~ProbeBreakPoint(); unsigned getId() const { return id; } const ProbeBase& getProbe() const { return probe; } private: // Observer void update(const ProbeBase& subject) override; void subjectDeleted(const ProbeBase& subject) override; Debugger& debugger; ProbeBase& probe; const unsigned id; static unsigned lastId; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/debugger/SimpleDebuggable.cc000066400000000000000000000022261257557151200225160ustar00rootroot00000000000000#include "SimpleDebuggable.hh" #include "MSXMotherBoard.hh" #include "Debugger.hh" #include "unreachable.hh" namespace openmsx { SimpleDebuggable::SimpleDebuggable( MSXMotherBoard& motherBoard_, const std::string& name_, const std::string& description_, unsigned size_) : motherBoard(motherBoard_) , name(name_) , description(description_) , size(size_) { motherBoard.getDebugger().registerDebuggable(name, *this); } SimpleDebuggable::~SimpleDebuggable() { motherBoard.getDebugger().unregisterDebuggable(name, *this); } unsigned SimpleDebuggable::getSize() const { return size; } const std::string& SimpleDebuggable::getDescription() const { return description; } byte SimpleDebuggable::read(unsigned address) { return read(address, motherBoard.getCurrentTime()); } byte SimpleDebuggable::read(unsigned /*address*/, EmuTime::param /*time*/) { UNREACHABLE; return 0; } void SimpleDebuggable::write(unsigned address, byte value) { write(address, value, motherBoard.getCurrentTime()); } void SimpleDebuggable::write(unsigned /*address*/, byte /*value*/, EmuTime::param /*time*/) { // does nothing } } // namespace openmsx openMSX-RELEASE_0_12_0/src/debugger/SimpleDebuggable.hh000066400000000000000000000017121257557151200225270ustar00rootroot00000000000000#ifndef SIMPLEDEBUGGABLE_HH #define SIMPLEDEBUGGABLE_HH #include "Debuggable.hh" #include "EmuTime.hh" namespace openmsx { class MSXMotherBoard; class SimpleDebuggable : public Debuggable { public: unsigned getSize() const final override; const std::string& getDescription() const final override; byte read(unsigned address) override; virtual byte read(unsigned address, EmuTime::param time); void write(unsigned address, byte value) override; virtual void write(unsigned address, byte value, EmuTime::param time); const std::string& getName() const { return name; } MSXMotherBoard& getMotherBoard() const { return motherBoard; } protected: SimpleDebuggable(MSXMotherBoard& motherBoard, const std::string& name, const std::string& description, unsigned size); ~SimpleDebuggable(); private: MSXMotherBoard& motherBoard; const std::string name; const std::string description; const unsigned size; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/events/000077500000000000000000000000001257557151200165325ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/src/events/.gitignore000066400000000000000000000001761257557151200205260ustar00rootroot00000000000000/*.ROM /*.bb /*.bbg /*.da /*.dsk /*.prof /*.rom /*.rpo /*.swp /*.vim /.debug /.kdbgrc.openmsx /.xvpics /gmon.out /GNUmakefile openMSX-RELEASE_0_12_0/src/events/AdhocCliCommParser.cc000066400000000000000000000136251257557151200225070ustar00rootroot00000000000000#include "AdhocCliCommParser.hh" #include "utf8_unchecked.hh" AdhocCliCommParser::AdhocCliCommParser(std::function callback_) : callback(std::move(callback_)) , state(O0) { } void AdhocCliCommParser::parse(const char* buf, size_t n) { for (size_t i = 0; i < n; ++i) parse(buf[i]); } void AdhocCliCommParser::parse(char c) { // Whenever there is a parse error we return to the initial state switch (state) { case O0: // looking for opening tag state = (c == '<') ? O1 : O0; break; case O1: // matched < state = (c == 'c') ? O2 : O0; break; case O2: // matched ') { state = C0; command.clear(); } else { state = O0; } break; case C0: // matched , now parsing xml entities and if (c == '<') state = C1; else if (c == '&') state = A1; else command += c; break; case C1: // matched < state = (c == '/') ? C2 : O0; break; case C2: // matched ') callback(command); state = O0; break; case A1: // matched & if (c == 'l') state = L2; else if (c == 'a') state = A2; else if (c == 'g') state = G2; else if (c == 'q') state = Q2; else if (c == '#') { state = H2; unicode = 0; } else state = O0; // error break; case A2: // matched &a if (c == 'm') state = A3; else if (c == 'p') state = P3; else state = O0; // error break; case A3: // matched &am state = (c == 'p') ? A4 : O0; break; case A4: // matched & if (c == ';') { command += '&'; state = C0; } else { state = O0; // error } break; case P3: // matched &ap state = (c == 'o') ? P4 : O0; break; case P4: // matched &apo state = (c == 's') ? P5 : O0; break; case P5: // matched &apos if (c == ';') { command += '\''; state = C0; } else { state = O0; // error } break; case Q2: // matched &q state = (c == 'u') ? Q3 : O0; break; case Q3: // matched &qu state = (c == 'o') ? Q4 : O0; break; case Q4: // matched &quo state = (c == 't') ? Q5 : O0; break; case Q5: // matched " if (c == ';') { command += '"'; state = C0; } else { state = O0; // error } break; case G2: // matched &g state = (c == 't') ? G3 : O0; break; case G3: // matched > if (c == ';') { command += '>'; state = C0; } else { state = O0; // error } break; case L2: // matched &l state = (c == 't') ? L3 : O0; break; case L3: // matched < if (c == ';') { command += '<'; state = C0; } else { state = O0; // error } break; case H2: // matched &# if (c == ';') { utf8::unchecked::append(unicode, back_inserter(command)); state = C0; } else { unicode *= 16; if (('0' <= c) && (c <= '9')) unicode += c - '0'; else if (('a' <= c) && (c <= 'f')) unicode += c - 'a' + 10; else if (('A' <= c) && (c <= 'F')) unicode += c - 'A' + 10; else state = O0; } break; } } #if 0 #include #include using namespace std; template void print(const T& t) { for (auto& i : t) cout << i << std::endl; } void test(const string& stream, const vector& expectedCommands) { vector result; AdhocCliCommParser parser([&](const string& cmd) { result.push_back(cmd); }); parser.parse(stream.data(), stream.size()); if (result != expectedCommands) { cout << "ERROR while parsing " << stream << endl; cout << "Expected to see:" << endl; print(expectedCommands); cout << "but got:" << endl; print(result); } else { cout << "ok" << endl; } } int main() { test("foo", {"foo"}); test(" foo", {"foo"}); test("foo ", {"foo"}); test(" foo ", {"foo"}); test("foobar", {"foo", "bar"}); test("foo bar", {"foo", "bar"}); test("&", {"&"}); test("'", {"'"}); test(""", {"\""}); test("<", {"<"}); test(">", {">"}); test(")", {"A"}); test("<command>", {""}); test(" foo ", {"foo"}); test(" foo foo", {"foo"}); // errors, but we do recover test("&unknown;foo", {"foo"}); test("&#fffffff;foo", {"foo"}); test("<<<<foo", {"foo"}); test("foo", {"foo"}); test("foo", {"foo"}); test("foofoo", {"foo"}); // This was not accepted before, but now is test("foo", {"foo"}); // This was accepted before but now isn't test("foo", {}); // should be "foo" test("foo", {}); // should be "foo" test("", {}); // sort-of OK, was before an empty command, now no command } #endif openMSX-RELEASE_0_12_0/src/events/AdhocCliCommParser.hh000066400000000000000000000023421257557151200225130ustar00rootroot00000000000000#ifndef ADHOCCLICOMMPARSER_HH #define ADHOCCLICOMMPARSER_HH #include #include #include class AdhocCliCommParser { public: AdhocCliCommParser(std::function callback); void parse(const char* buf, size_t n); private: void parse(char c); std::function callback; std::string command; uint32_t unicode; enum State { O0, // no tag char matched yet O1, // matched < O2, // , now parsing xml entities and C1, // matched < C2, // #include #include using std::ostringstream; using std::string; using std::vector; using std::unique_ptr; using std::move; namespace openmsx { class AfterCmd { public: virtual ~AfterCmd() {} string_ref getCommand() const; const string& getId() const; virtual string getType() const = 0; void execute(); protected: AfterCmd(AfterCommand& afterCommand, const TclObject& command); unique_ptr removeSelf(); AfterCommand& afterCommand; TclObject command; string id; static unsigned lastAfterId; }; class AfterTimedCmd : public AfterCmd, private Schedulable { public: double getTime() const; void reschedule(); protected: AfterTimedCmd(Scheduler& scheduler, AfterCommand& afterCommand, const TclObject& command, double time); private: void executeUntil(EmuTime::param time) override; void schedulerDeleted() override; double time; // Zero when expired, otherwise the original duration (to // be able to reschedule for 'after idle'). }; class AfterTimeCmd final : public AfterTimedCmd { public: AfterTimeCmd(Scheduler& scheduler, AfterCommand& afterCommand, const TclObject& command, double time); string getType() const override; }; class AfterIdleCmd final : public AfterTimedCmd { public: AfterIdleCmd(Scheduler& scheduler, AfterCommand& afterCommand, const TclObject& command, double time); string getType() const override; }; template class AfterEventCmd final : public AfterCmd { public: AfterEventCmd(AfterCommand& afterCommand, const TclObject& type, const TclObject& command); string getType() const override; private: const string type; }; class AfterInputEventCmd final : public AfterCmd { public: AfterInputEventCmd(AfterCommand& afterCommand, AfterCommand::EventPtr event, const TclObject& command); string getType() const override; AfterCommand::EventPtr getEvent() const { return event; } private: AfterCommand::EventPtr event; }; class AfterRealTimeCmd final : public AfterCmd, private RTSchedulable { public: AfterRealTimeCmd(RTScheduler& rtScheduler, AfterCommand& afterCommand, const TclObject& command, double time); string getType() const override; private: void executeRT() override; }; AfterCommand::AfterCommand(Reactor& reactor_, EventDistributor& eventDistributor_, CommandController& commandController) : Command(commandController, "after") , reactor(reactor_) , eventDistributor(eventDistributor_) { // TODO DETACHED <-> EMU types should be cleaned up // (moved to event iso listener?) eventDistributor.registerEventListener( OPENMSX_KEY_UP_EVENT, *this); eventDistributor.registerEventListener( OPENMSX_KEY_DOWN_EVENT, *this); eventDistributor.registerEventListener( OPENMSX_MOUSE_MOTION_EVENT, *this); eventDistributor.registerEventListener( OPENMSX_MOUSE_BUTTON_UP_EVENT, *this); eventDistributor.registerEventListener( OPENMSX_MOUSE_BUTTON_DOWN_EVENT, *this); eventDistributor.registerEventListener( OPENMSX_JOY_AXIS_MOTION_EVENT, *this); eventDistributor.registerEventListener( OPENMSX_JOY_HAT_EVENT, *this); eventDistributor.registerEventListener( OPENMSX_JOY_BUTTON_UP_EVENT, *this); eventDistributor.registerEventListener( OPENMSX_JOY_BUTTON_DOWN_EVENT, *this); eventDistributor.registerEventListener( OPENMSX_FINISH_FRAME_EVENT, *this); eventDistributor.registerEventListener( OPENMSX_BREAK_EVENT, *this); eventDistributor.registerEventListener( OPENMSX_QUIT_EVENT, *this); eventDistributor.registerEventListener( OPENMSX_BOOT_EVENT, *this); eventDistributor.registerEventListener( OPENMSX_MACHINE_LOADED_EVENT, *this); eventDistributor.registerEventListener( OPENMSX_AFTER_TIMED_EVENT, *this); } AfterCommand::~AfterCommand() { eventDistributor.unregisterEventListener( OPENMSX_AFTER_TIMED_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_MACHINE_LOADED_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_BOOT_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_QUIT_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_BREAK_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_FINISH_FRAME_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_JOY_BUTTON_DOWN_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_JOY_BUTTON_UP_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_JOY_HAT_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_JOY_AXIS_MOTION_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_MOUSE_BUTTON_DOWN_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_MOUSE_BUTTON_UP_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_MOUSE_MOTION_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_KEY_DOWN_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_KEY_UP_EVENT, *this); } void AfterCommand::execute(array_ref tokens, TclObject& result) { if (tokens.size() < 2) { throw CommandException("Missing argument"); } string_ref subCmd = tokens[1].getString(); if (subCmd == "time") { afterTime(tokens, result); } else if (subCmd == "realtime") { afterRealTime(tokens, result); } else if (subCmd == "idle") { afterIdle(tokens, result); } else if (subCmd == "frame") { afterEvent(tokens, result); } else if (subCmd == "break") { afterEvent(tokens, result); } else if (subCmd == "quit") { afterEvent(tokens, result); } else if (subCmd == "boot") { afterEvent(tokens, result); } else if (subCmd == "machine_switch") { afterEvent(tokens, result); } else if (subCmd == "info") { afterInfo(tokens, result); } else if (subCmd == "cancel") { afterCancel(tokens, result); } else { try { // A valid integer? int time = tokens[1].getInt(getInterpreter()); afterTclTime(time, tokens, result); } catch (CommandException&) { try { // A valid event name? afterInputEvent( InputEventFactory::createInputEvent( tokens[1], getInterpreter()), tokens, result); } catch (MSXException&) { throw SyntaxError(); } } } } static double getTime(Interpreter& interp, const TclObject& obj) { double time = obj.getDouble(interp); if (time < 0) { throw CommandException("Not a valid time specification"); } return time; } void AfterCommand::afterTime(array_ref tokens, TclObject& result) { if (tokens.size() != 4) { throw SyntaxError(); } MSXMotherBoard* motherBoard = reactor.getMotherBoard(); if (!motherBoard) return; double time = getTime(getInterpreter(), tokens[2]); auto cmd = make_unique( motherBoard->getScheduler(), *this, tokens[3], time); result.setString(cmd->getId()); afterCmds.push_back(move(cmd)); } void AfterCommand::afterRealTime(array_ref tokens, TclObject& result) { if (tokens.size() != 4) { throw SyntaxError(); } double time = getTime(getInterpreter(), tokens[2]); auto cmd = make_unique( reactor.getRTScheduler(), *this, tokens[3], time); result.setString(cmd->getId()); afterCmds.push_back(move(cmd)); } void AfterCommand::afterTclTime( int ms, array_ref tokens, TclObject& result) { TclObject command; command.addListElements(std::begin(tokens) + 2, std::end(tokens)); auto cmd = make_unique( reactor.getRTScheduler(), *this, command, ms / 1000.0); result.setString(cmd->getId()); afterCmds.push_back(move(cmd)); } template void AfterCommand::afterEvent(array_ref tokens, TclObject& result) { if (tokens.size() != 3) { throw SyntaxError(); } auto cmd = make_unique>( *this, tokens[1], tokens[2]); result.setString(cmd->getId()); afterCmds.push_back(move(cmd)); } void AfterCommand::afterInputEvent( const EventPtr& event, array_ref tokens, TclObject& result) { if (tokens.size() != 3) { throw SyntaxError(); } auto cmd = make_unique( *this, event, tokens[2]); result.setString(cmd->getId()); afterCmds.push_back(move(cmd)); } void AfterCommand::afterIdle(array_ref tokens, TclObject& result) { if (tokens.size() != 4) { throw SyntaxError(); } MSXMotherBoard* motherBoard = reactor.getMotherBoard(); if (!motherBoard) return; double time = getTime(getInterpreter(), tokens[2]); auto cmd = make_unique( motherBoard->getScheduler(), *this, tokens[3], time); result.setString(cmd->getId()); afterCmds.push_back(move(cmd)); } void AfterCommand::afterInfo(array_ref /*tokens*/, TclObject& result) { ostringstream str; for (auto& cmd : afterCmds) { str << cmd->getId() << ": "; str << cmd->getType() << ' '; if (auto cmd2 = dynamic_cast(cmd.get())) { str.precision(3); str << std::fixed << std::showpoint << cmd2->getTime() << ' '; } str << cmd->getCommand() << '\n'; } result.setString(str.str()); } void AfterCommand::afterCancel(array_ref tokens, TclObject& /*result*/) { if (tokens.size() < 3) { throw SyntaxError(); } if (tokens.size() == 3) { auto id = tokens[2].getString(); auto it = find_if(begin(afterCmds), end(afterCmds), [&](std::unique_ptr& e) { return e->getId() == id; }); if (it != end(afterCmds)) { afterCmds.erase(it); return; } } TclObject command; command.addListElements(std::begin(tokens) + 2, std::end(tokens)); string_ref cmdStr = command.getString(); auto it = find_if(begin(afterCmds), end(afterCmds), [&](std::unique_ptr& e) { return e->getCommand() == cmdStr; }); if (it != end(afterCmds)) { afterCmds.erase(it); // Tcl manual is not clear about this, but it seems // there's only occurence of this command canceled. // It's also not clear which of the (possibly) several // matches is canceled. return; } // It's not an error if no match is found } string AfterCommand::help(const vector& /*tokens*/) const { return "after time execute a command after some time (MSX time)\n" "after realtime execute a command after some time (realtime)\n" "after idle execute a command after some time being idle\n" "after frame execute a command after a new frame is drawn\n" "after break execute a command after a breakpoint is reached\n" "after boot execute a command after a (re)boot\n" "after machine_switch execute a command after a switch to a new machine\n" "after info list all postponed commands\n" "after cancel cancel the postponed command with given id\n"; } void AfterCommand::tabCompletion(vector& tokens) const { if (tokens.size() == 2) { static const char* const cmds[] = { "time", "realtime", "idle", "frame", "break", "boot", "machine_switch", "info", "cancel", }; completeString(tokens, cmds); } // TODO : make more complete } template void AfterCommand::executeMatches(PRED pred) { // predicate should return false on matches auto it = partition(begin(afterCmds), end(afterCmds), pred); AfterCmds tmp(std::make_move_iterator(it), std::make_move_iterator(end(afterCmds))); afterCmds.erase(it, end(afterCmds)); for (auto& c : tmp) { c->execute(); } } template struct AfterEventPred { bool operator()(const unique_ptr& x) const { return !dynamic_cast*>(x.get()); } }; template void AfterCommand::executeEvents() { executeMatches(AfterEventPred()); } struct AfterEmuTimePred { bool operator()(const unique_ptr& x) const { if (auto* cmd = dynamic_cast(x.get())) { if (cmd->getTime() == 0.0) { return false; } } return true; } }; struct AfterInputEventPred { AfterInputEventPred(AfterCommand::EventPtr event_) : event(std::move(event_)) {} bool operator()(const unique_ptr& x) const { if (auto* cmd = dynamic_cast(x.get())) { if (cmd->getEvent()->matches(*event)) return false; } return true; } AfterCommand::EventPtr event; }; int AfterCommand::signalEvent(const std::shared_ptr& event) { if (event->getType() == OPENMSX_FINISH_FRAME_EVENT) { executeEvents(); } else if (event->getType() == OPENMSX_BREAK_EVENT) { executeEvents(); } else if (event->getType() == OPENMSX_BOOT_EVENT) { executeEvents(); } else if (event->getType() == OPENMSX_QUIT_EVENT) { executeEvents(); } else if (event->getType() == OPENMSX_MACHINE_LOADED_EVENT) { executeEvents(); } else if (event->getType() == OPENMSX_AFTER_TIMED_EVENT) { executeMatches(AfterEmuTimePred()); } else { executeMatches(AfterInputEventPred(event)); for (auto& c : afterCmds) { if (auto* cmd = dynamic_cast(c.get())) { cmd->reschedule(); } } } return 0; } // class AfterCmd unsigned AfterCmd::lastAfterId = 0; AfterCmd::AfterCmd(AfterCommand& afterCommand_, const TclObject& command_) : afterCommand(afterCommand_), command(command_) { ostringstream str; str << "after#" << ++lastAfterId; id = str.str(); } string_ref AfterCmd::getCommand() const { return command.getString(); } const string& AfterCmd::getId() const { return id; } void AfterCmd::execute() { try { command.executeCommand(afterCommand.getInterpreter()); } catch (CommandException& e) { afterCommand.getCommandController().getCliComm().printWarning( "Error executing delayed command: " + e.getMessage()); } } unique_ptr AfterCmd::removeSelf() { auto it = find_if_unguarded(afterCommand.afterCmds, [&](std::unique_ptr& e) { return e.get() == this; }); auto result = move(*it); afterCommand.afterCmds.erase(it); return result; } // class AfterTimedCmd AfterTimedCmd::AfterTimedCmd( Scheduler& scheduler, AfterCommand& afterCommand, const TclObject& command, double time_) : AfterCmd(afterCommand, command) , Schedulable(scheduler) , time(time_) { reschedule(); } double AfterTimedCmd::getTime() const { return time; } void AfterTimedCmd::reschedule() { removeSyncPoint(); setSyncPoint(getCurrentTime() + EmuDuration(time)); } void AfterTimedCmd::executeUntil(EmuTime::param /*time*/) { time = 0.0; // execute on next event afterCommand.eventDistributor.distributeEvent( std::make_shared(OPENMSX_AFTER_TIMED_EVENT)); } void AfterTimedCmd::schedulerDeleted() { removeSelf(); } // class AfterTimeCmd AfterTimeCmd::AfterTimeCmd( Scheduler& scheduler, AfterCommand& afterCommand, const TclObject& command, double time) : AfterTimedCmd(scheduler, afterCommand, command, time) { } string AfterTimeCmd::getType() const { return "time"; } // class AfterIdleCmd AfterIdleCmd::AfterIdleCmd( Scheduler& scheduler, AfterCommand& afterCommand, const TclObject& command, double time) : AfterTimedCmd(scheduler, afterCommand, command, time) { } string AfterIdleCmd::getType() const { return "idle"; } // class AfterEventCmd template AfterEventCmd::AfterEventCmd( AfterCommand& afterCommand, const TclObject& type_, const TclObject& command) : AfterCmd(afterCommand, command), type(type_.getString().str()) { } template string AfterEventCmd::getType() const { return type; } // AfterInputEventCmd AfterInputEventCmd::AfterInputEventCmd( AfterCommand& afterCommand, AfterCommand::EventPtr event_, const TclObject& command) : AfterCmd(afterCommand, command) , event(std::move(event_)) { } string AfterInputEventCmd::getType() const { return event->toString(); } // class AfterRealTimeCmd AfterRealTimeCmd::AfterRealTimeCmd( RTScheduler& rtScheduler, AfterCommand& afterCommand, const TclObject& command, double time) : AfterCmd(afterCommand, command) , RTSchedulable(rtScheduler) { scheduleRT(uint64_t(time * 1e6)); // micro seconds } string AfterRealTimeCmd::getType() const { return "realtime"; } void AfterRealTimeCmd::executeRT() { // Remove self before executing, but keep self alive till the end of // this method. Otherwise execute could execute 'after cancel ..' and // removeSelf() asserts that it can't find itself anymore. auto self = removeSelf(); execute(); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/events/AfterCommand.hh000066400000000000000000000035251257557151200214200ustar00rootroot00000000000000#ifndef AFTERCOMMAND_HH #define AFTERCOMMAND_HH #include "Command.hh" #include "EventListener.hh" #include "Event.hh" #include #include namespace openmsx { class Reactor; class EventDistributor; class CommandController; class AfterCmd; class AfterCommand final : public Command, private EventListener { public: using EventPtr = std::shared_ptr; AfterCommand(Reactor& reactor, EventDistributor& eventDistributor, CommandController& commandController); ~AfterCommand(); void execute(array_ref tokens, TclObject& result) override; std::string help(const std::vector& tokens) const override; void tabCompletion(std::vector& tokens) const override; private: template void executeMatches(PRED pred); template void executeEvents(); template void afterEvent( array_ref tokens, TclObject& result); void afterInputEvent(const EventPtr& event, array_ref tokens, TclObject& result); void afterTclTime (int ms, array_ref tokens, TclObject& result); void afterTime (array_ref tokens, TclObject& result); void afterRealTime(array_ref tokens, TclObject& result); void afterIdle (array_ref tokens, TclObject& result); void afterInfo (array_ref tokens, TclObject& result); void afterCancel (array_ref tokens, TclObject& result); // EventListener int signalEvent(const std::shared_ptr& event) override; using AfterCmds = std::vector>; AfterCmds afterCmds; Reactor& reactor; EventDistributor& eventDistributor; friend class AfterCmd; friend class AfterTimedCmd; friend class AfterRealTimeCmd; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/events/CliComm.cc000066400000000000000000000013171257557151200203660ustar00rootroot00000000000000#include "CliComm.hh" namespace openmsx { const char* const CliComm::levelStr[CliComm::NUM_LEVELS] = { "info", "warning", "error", "progress" }; const char* const CliComm::updateStr[CliComm::NUM_UPDATES] = { "led", "setting", "setting-info", "hardware", "plug", "unplug", "media", "status", "extension", "sounddevice", "connector" }; CliComm::CliComm() { } CliComm::~CliComm() { } void CliComm::printInfo(string_ref message) { log(INFO, message); } void CliComm::printWarning(string_ref message) { log(WARNING, message); } void CliComm::printError(string_ref message) { log(LOGLEVEL_ERROR, message); } void CliComm::printProgress(string_ref message) { log(PROGRESS, message); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/events/CliComm.hh000066400000000000000000000023421257557151200203770ustar00rootroot00000000000000#ifndef CLICOMM_HH #define CLICOMM_HH #include "array_ref.hh" #include "string_ref.hh" namespace openmsx { class CliComm { public: enum LogLevel { INFO, WARNING, LOGLEVEL_ERROR, // ERROR may give preprocessor name clashes PROGRESS, NUM_LEVELS // must be last }; enum UpdateType { LED, SETTING, SETTINGINFO, HARDWARE, PLUG, UNPLUG, MEDIA, STATUS, EXTENSION, SOUNDDEVICE, CONNECTOR, NUM_UPDATES // must be last }; virtual void log(LogLevel level, string_ref message) = 0; virtual void update(UpdateType type, string_ref name, string_ref value) = 0; // convenience methods (shortcuts for log()) void printInfo (string_ref message); void printWarning (string_ref message); void printError (string_ref message); void printProgress(string_ref message); // string representations of the LogLevel and UpdateType enums static array_ref getLevelStrings() { return make_array_ref(levelStr); } static array_ref getUpdateStrings() { return make_array_ref(updateStr); } protected: CliComm(); ~CliComm(); private: static const char* const levelStr [NUM_LEVELS]; static const char* const updateStr[NUM_UPDATES]; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/events/CliConnection.cc000066400000000000000000000223141257557151200215720ustar00rootroot00000000000000// TODO: // - To avoid any possible conflicts, anything called from "run" should be // locked. // - Maybe document for each method whether it is called from the listener // thread or from the main thread? // - Unsubscribe at CliComm after stream is closed. #include "CliConnection.hh" #include "EventDistributor.hh" #include "Event.hh" #include "CommandController.hh" #include "CommandException.hh" #include "TclObject.hh" #include "XMLElement.hh" #include "checked_cast.hh" #include "cstdiop.hh" #include "unistdp.hh" #include "openmsx.hh" #include "StringOp.hh" #include #include #ifdef _WIN32 #include "SocketStreamWrapper.hh" #include "SspiNegotiateServer.hh" #endif using std::string; namespace openmsx { // class CliCommandEvent class CliCommandEvent : public Event { public: CliCommandEvent(string command_, const CliConnection* id_) : Event(OPENMSX_CLICOMMAND_EVENT) , command(std::move(command_)), id(id_) { } const string& getCommand() const { return command; } const CliConnection* getId() const { return id; } void toStringImpl(TclObject& result) const override { result.addListElement("CliCmd"); result.addListElement(getCommand()); } bool lessImpl(const Event& other) const override { auto& otherCmdEvent = checked_cast(other); return getCommand() < otherCmdEvent.getCommand(); } private: const string command; const CliConnection* id; }; // class CliConnection CliConnection::CliConnection(CommandController& commandController_, EventDistributor& eventDistributor_) : parser([this](const std::string& cmd) { execute(cmd); }) , thread(this) , commandController(commandController_) , eventDistributor(eventDistributor_) { for (auto& en : updateEnabled) { en = false; } eventDistributor.registerEventListener(OPENMSX_CLICOMMAND_EVENT, *this); } CliConnection::~CliConnection() { eventDistributor.unregisterEventListener(OPENMSX_CLICOMMAND_EVENT, *this); } void CliConnection::log(CliComm::LogLevel level, string_ref message) { auto levelStr = CliComm::getLevelStrings(); output(StringOp::Builder() << "" << XMLElement::XMLEscape(message.str()) << "\n"); } void CliConnection::update(CliComm::UpdateType type, string_ref machine, string_ref name, string_ref value) { if (!getUpdateEnable(type)) return; auto updateStr = CliComm::getUpdateStrings(); StringOp::Builder tmp; tmp << "' << XMLElement::XMLEscape(value.str()) << "\n"; output(tmp); } void CliConnection::startOutput() { output("\n"); } void CliConnection::start() { thread.start(); } void CliConnection::end() { output("\n"); close(); } void CliConnection::execute(const string& command) { eventDistributor.distributeEvent( std::make_shared(command, this)); } static string reply(const string& message, bool status) { return StringOp::Builder() << "" << XMLElement::XMLEscape(message) << "\n"; } int CliConnection::signalEvent(const std::shared_ptr& event) { auto& commandEvent = checked_cast(*event); if (commandEvent.getId() == this) { try { string result = commandController.executeCommand( commandEvent.getCommand(), this).getString().str(); output(reply(result, true)); } catch (CommandException& e) { string result = e.getMessage() + '\n'; output(reply(result, false)); } } return 0; } // class StdioConnection static const int BUF_SIZE = 4096; StdioConnection::StdioConnection(CommandController& commandController, EventDistributor& eventDistributor) : CliConnection(commandController, eventDistributor) , ok(true) { startOutput(); } StdioConnection::~StdioConnection() { end(); } void StdioConnection::run() { // runs in helper thread while (ok) { char buf[BUF_SIZE]; int n = read(STDIN_FILENO, buf, sizeof(buf)); if (n > 0) { parser.parse(buf, n); } else if (n < 0) { close(); break; } } } void StdioConnection::output(string_ref message) { if (ok) { std::cout << message << std::flush; } } void StdioConnection::close() { // don't close stdin/out/err ok = false; } #ifdef _WIN32 // class PipeConnection // INVALID_HANDLE_VALUE is #defined as (HANDLE)(-1) // but that gives a old-style-cast warning static const HANDLE OPENMSX_INVALID_HANDLE_VALUE = reinterpret_cast(-1); PipeConnection::PipeConnection(CommandController& commandController, EventDistributor& eventDistributor, string_ref name) : CliConnection(commandController, eventDistributor) { string pipeName = "\\\\.\\pipe\\" + name; pipeHandle = CreateFileA(pipeName.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr); if (pipeHandle == OPENMSX_INVALID_HANDLE_VALUE) { char msg[256]; snprintf(msg, 255, "Error reopening pipefile '%s': error %u", pipeName.c_str(), unsigned(GetLastError())); throw FatalError(msg); } shutdownEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); if (!shutdownEvent) { throw FatalError(StringOp::Builder() << "Error creating shutdown event: " << GetLastError()); } startOutput(); } PipeConnection::~PipeConnection() { end(); CloseHandle(shutdownEvent); } static void InitOverlapped(LPOVERLAPPED overlapped) { ZeroMemory(overlapped, sizeof(*overlapped)); overlapped->hEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); if (!overlapped->hEvent) { throw FatalError(StringOp::Builder() << "Error creating overlapped event: " << GetLastError()); } } static void ClearOverlapped(LPOVERLAPPED overlapped) { if (overlapped->hEvent) { CloseHandle(overlapped->hEvent); overlapped->hEvent = nullptr; } } void PipeConnection::run() { // runs in helper thread OVERLAPPED overlapped; InitOverlapped(&overlapped); HANDLE waitHandles[2] = { shutdownEvent, overlapped.hEvent }; while (pipeHandle != OPENMSX_INVALID_HANDLE_VALUE) { char buf[BUF_SIZE]; if (!ReadFile(pipeHandle, buf, BUF_SIZE, nullptr, &overlapped) && GetLastError() != ERROR_IO_PENDING) { break; // Pipe broke } DWORD wait = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE); if (wait == WAIT_OBJECT_0 + 1) { DWORD bytesRead; if (!GetOverlappedResult(pipeHandle, &overlapped, &bytesRead, TRUE)) { break; // Pipe broke } parser.parse(buf, bytesRead); } else if (wait == WAIT_OBJECT_0) { break; // Shutdown } else { throw FatalError(StringOp::Builder() << "WaitForMultipleObjects returned unexpectedly: " << wait); } } ClearOverlapped(&overlapped); // We own the pipe handle, so close it here CloseHandle(pipeHandle); pipeHandle = OPENMSX_INVALID_HANDLE_VALUE; } void PipeConnection::output(string_ref message) { if (pipeHandle != OPENMSX_INVALID_HANDLE_VALUE) { std::cout << message << std::flush; } } void PipeConnection::close() { SetEvent(shutdownEvent); thread.join(); assert(pipeHandle == OPENMSX_INVALID_HANDLE_VALUE); } #endif // _WIN32 // class SocketConnection SocketConnection::SocketConnection(CommandController& commandController, EventDistributor& eventDistributor, SOCKET sd_) : CliConnection(commandController, eventDistributor) , sd(sd_), established(false) { } SocketConnection::~SocketConnection() { end(); } void SocketConnection::run() { // runs in helper thread #ifdef _WIN32 bool ok; { std::lock_guard lock(mutex); // Authenticate and authorize the caller SocketStreamWrapper stream(sd); SspiNegotiateServer server(stream); ok = server.Authenticate() && server.Authorize(); } if (!ok) { close(); return; } #endif // Start output element established = true; // TODO needs locking? startOutput(); // TODO is locking correct? // No need to lock in this thread because we don't write to 'sd' // and 'sd' only gets written to in this thread. while (true) { if (sd == OPENMSX_INVALID_SOCKET) return; char buf[BUF_SIZE]; int n = sock_recv(sd, buf, BUF_SIZE); if (n > 0) { parser.parse(buf, n); } else if (n < 0) { close(); break; } } } void SocketConnection::output(string_ref message) { if (!established) { // TODO needs locking? // Connection isn't authorized yet (and opening tag is not // yet send). Ignore log and update messages for now. return; } const char* data = message.data(); unsigned pos = 0; size_t bytesLeft = message.size(); while (bytesLeft) { int bytesSend; { std::lock_guard lock(mutex); if (sd == OPENMSX_INVALID_SOCKET) return; bytesSend = sock_send(sd, &data[pos], bytesLeft); } if (bytesSend > 0) { bytesLeft -= bytesSend; pos += bytesSend; } else { close(); break; } } } void SocketConnection::close() { std::lock_guard lock(mutex); if (sd != OPENMSX_INVALID_SOCKET) { SOCKET _sd = sd; sd = OPENMSX_INVALID_SOCKET; sock_close(_sd); } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/events/CliConnection.hh000066400000000000000000000062731257557151200216120ustar00rootroot00000000000000#ifndef CLICONNECTION_HH #define CLICONNECTION_HH #include "CliListener.hh" #include "Thread.hh" #include "EventListener.hh" #include "Socket.hh" #include "CliComm.hh" #include "AdhocCliCommParser.hh" #include #include namespace openmsx { class CommandController; class EventDistributor; class CliConnection : public CliListener, private EventListener , protected Runnable { public: void setUpdateEnable(CliComm::UpdateType type, bool value) { updateEnabled[type] = value; } bool getUpdateEnable(CliComm::UpdateType type) const { return updateEnabled[type]; } /** Starts the helper thread. * Called when this CliConnection is added to GlobalCliComm (and * after it's allowed to respond to external commands). * Subclasses should themself send the opening tag (startOutput()). */ void start(); protected: CliConnection(CommandController& commandController, EventDistributor& eventDistributor); ~CliConnection(); virtual void output(string_ref message) = 0; /** End this connection by sending the closing tag * and then closing the stream. * Subclasses should call this method at the start of their destructor. */ void end(); /** Close the connection. After this method is called, calls to * output() should be ignored. */ virtual void close() = 0; /** Send opening XML tag, should be called exactly once by a subclass * shortly after opening a connection. Cannot be implemented in the * base class because some subclasses (want to send data before this * tag). */ void startOutput(); AdhocCliCommParser parser; Thread thread; // TODO: Possible to make this private? private: void execute(const std::string& command); // CliListener void log(CliComm::LogLevel level, string_ref message) override; void update(CliComm::UpdateType type, string_ref machine, string_ref name, string_ref value) override; // EventListener int signalEvent(const std::shared_ptr& event) override; CommandController& commandController; EventDistributor& eventDistributor; bool updateEnabled[CliComm::NUM_UPDATES]; }; class StdioConnection final : public CliConnection { public: StdioConnection(CommandController& commandController, EventDistributor& eventDistributor); ~StdioConnection(); void output(string_ref message) override; private: void close() override; void run() override; bool ok; }; #ifdef _WIN32 class PipeConnection final : public CliConnection { public: PipeConnection(CommandController& commandController, EventDistributor& eventDistributor, string_ref name); ~PipeConnection(); void output(string_ref message) override; private: void close() override; void run() override; HANDLE pipeHandle; HANDLE shutdownEvent; }; #endif class SocketConnection final : public CliConnection { public: SocketConnection(CommandController& commandController, EventDistributor& eventDistributor, SOCKET sd); ~SocketConnection(); void output(string_ref message) override; private: void close() override; void run() override; std::mutex mutex; SOCKET sd; bool established; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/events/CliListener.hh000066400000000000000000000006311257557151200212700ustar00rootroot00000000000000#ifndef CLILISTENER_HH #define CLILISTENER_HH #include "CliComm.hh" namespace openmsx { class CliListener { public: virtual ~CliListener() {} virtual void log(CliComm::LogLevel level, string_ref message) = 0; virtual void update(CliComm::UpdateType type, string_ref machine, string_ref name, string_ref value) = 0; protected: CliListener() {} }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/events/CliServer.cc000066400000000000000000000140271257557151200207430ustar00rootroot00000000000000#include "CliServer.hh" #include "GlobalCliComm.hh" #include "CliConnection.hh" #include "StringOp.hh" #include "FileOperations.hh" #include "MSXException.hh" #include "memory.hh" #include "random.hh" #include "statp.hh" #include #ifdef _WIN32 #include #include #else #include #endif using std::string; namespace openmsx { static string getUserName() { #if defined(_WIN32) return "default"; #else struct passwd* pw = getpwuid(getuid()); return pw->pw_name ? pw->pw_name : ""; #endif } static bool checkSocketDir(const string& dir) { struct stat st; if (stat(dir.c_str(), &st)) { // cannot stat return false; } if (!S_ISDIR(st.st_mode)) { // not a directory return false; } #ifndef _WIN32 // only do permission and owner checks on *nix if ((st.st_mode & 0777) != 0700) { // wrong permissions return false; } if (st.st_uid != getuid()) { // wrong uid return false; } #endif return true; } static bool checkSocket(const string& socket) { string_ref name = FileOperations::getFilename(socket); if (!name.starts_with("socket.")) { return false; // wrong name } struct stat st; if (stat(socket.c_str(), &st)) { // cannot stat return false; } #ifdef _WIN32 if (!S_ISREG(st.st_mode)) { // not a regular file return false; } #else if (!S_ISSOCK(st.st_mode)) { // not a socket return false; } #endif #ifndef _WIN32 // only do permission and owner checks on *nix if ((st.st_mode & 0777) != 0600) { // check will be different on win32 (!= 777) thus actually useless // wrong permissions return false; } if (st.st_uid != getuid()) { // does this work on win32? is this check meaningful? // wrong uid return false; } #endif return true; } #ifdef _WIN32 static int openPort(SOCKET listenSock) { const int BASE = 9938; const int RANGE = 64; int first = random_int(0, RANGE - 1); // [0, RANGE) for (int n = 0; n < RANGE; ++n) { int port = BASE + ((first + n) % RANGE); sockaddr_in server_address; memset(&server_address, 0, sizeof(server_address)); server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); server_address.sin_port = htons(port); if (bind(listenSock, reinterpret_cast(&server_address), sizeof(server_address)) != -1) { return port; } } throw MSXException("Couldn't open socket."); } #endif void CliServer::createSocket() { string dir = FileOperations::getTempDir() + "/openmsx-" + getUserName(); FileOperations::mkdir(dir, 0700); if (!checkSocketDir(dir)) { throw MSXException("Couldn't create socket directory."); } socketName = StringOp::Builder() << dir << "/socket." << int(getpid()); #ifdef _WIN32 listenSock = socket(AF_INET, SOCK_STREAM, 0); if (listenSock == OPENMSX_INVALID_SOCKET) { throw MSXException(sock_error()); } int portNumber = openPort(listenSock); // write port number to file FileOperations::unlink(socketName); // ignore error std::ofstream out; FileOperations::openofstream(out, socketName); out << portNumber << std::endl; if (!out.good()) { throw MSXException("Couldn't open socket."); } #else listenSock = socket(AF_UNIX, SOCK_STREAM, 0); if (listenSock == OPENMSX_INVALID_SOCKET) { throw MSXException(sock_error()); } FileOperations::unlink(socketName); // ignore error sockaddr_un addr; strcpy(addr.sun_path, socketName.c_str()); addr.sun_family = AF_UNIX; if (bind(listenSock, reinterpret_cast(&addr), sizeof(addr)) == -1) { throw MSXException("Couldn't open socket."); } if (chmod(socketName.c_str(), 0600) == -1) { throw MSXException("Couldn't open socket."); } #endif if (!checkSocket(socketName)) { throw MSXException("Couldn't open socket."); } listen(listenSock, SOMAXCONN); } // The BSD socket API does not contain a simple way to cancel a call to // accept(). As a workaround, we connect to the server socket ourselves. bool CliServer::exitAcceptLoop() { #ifdef _WIN32 // Windows application-level firewalls pop up a warning about openMSX // connecting to itself. Since closing the socket is sufficient to make // Windows exit the accept() call, this code is disabled on Windows. return true; /* SOCKET sd = socket(AF_INET, SOCK_STREAM, 0); sockaddr_in addr; memset((char*)&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); addr.sin_port = htons(portNumber); */ #else SOCKET sd = socket(AF_UNIX, SOCK_STREAM, 0); sockaddr_un addr; addr.sun_family = AF_UNIX; strcpy(addr.sun_path, socketName.c_str()); // Code below is OS-independent, but unreachable on Windows: if (sd == OPENMSX_INVALID_SOCKET) { return false; } int r = connect(sd, reinterpret_cast(&addr), sizeof(addr)); close(sd); return r != SOCKET_ERROR; #endif } static void deleteSocket(const string& socket) { FileOperations::unlink(socket); // ignore errors string dir = socket.substr(0, socket.find_last_of('/')); FileOperations::rmdir(dir); // ignore errors } CliServer::CliServer(CommandController& commandController_, EventDistributor& eventDistributor_, GlobalCliComm& cliComm_) : commandController(commandController_) , eventDistributor(eventDistributor_) , cliComm(cliComm_) , thread(this) , listenSock(OPENMSX_INVALID_SOCKET) { exitLoop = false; sock_startup(); try { createSocket(); thread.start(); } catch (MSXException& e) { cliComm.printWarning(e.getMessage()); } } CliServer::~CliServer() { exitLoop = true; if (listenSock != OPENMSX_INVALID_SOCKET) { sock_close(listenSock); if (!exitAcceptLoop()) { // clean exit failed, try emergency exit thread.stop(); } } thread.join(); deleteSocket(socketName); sock_cleanup(); } void CliServer::run() { mainLoop(); } void CliServer::mainLoop() { while (!exitLoop) { // wait for incomming connection SOCKET sd = accept(listenSock, nullptr, nullptr); if (sd == OPENMSX_INVALID_SOCKET) { // sock_close(listenSock); // hangs on win32 return; } cliComm.addListener(make_unique( commandController, eventDistributor, sd)); } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/events/CliServer.hh000066400000000000000000000013171257557151200207530ustar00rootroot00000000000000#ifndef CLISERVER_HH #define CLISERVER_HH #include "Thread.hh" #include "Socket.hh" #include namespace openmsx { class CommandController; class EventDistributor; class GlobalCliComm; class CliServer final : private Runnable { public: CliServer(CommandController& commandController, EventDistributor& eventDistributor, GlobalCliComm& cliComm); ~CliServer(); private: // Runnable void run() override; void mainLoop(); void createSocket(); bool exitAcceptLoop(); CommandController& commandController; EventDistributor& eventDistributor; GlobalCliComm& cliComm; Thread thread; std::string socketName; SOCKET listenSock; bool exitLoop; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/events/Event.cc000066400000000000000000000014021257557151200201170ustar00rootroot00000000000000#include "Event.hh" #include "TclObject.hh" namespace openmsx { // class Event std::string Event::toString() const { TclObject result; toStringImpl(result); return result.getString().str(); } bool Event::operator<(const Event& other) const { return (getType() != other.getType()) ? (getType() < other.getType()) : lessImpl(other); } bool Event::operator==(const Event& other) const { return !(*this < other) && !(other < *this); } bool Event::operator!=(const Event& other) const { return !(*this == other); } void SimpleEvent::toStringImpl(TclObject& result) const { result.addListElement("simple"); result.addListElement(int(getType())); } bool SimpleEvent::lessImpl(const Event& /*other*/) const { return false; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/events/Event.hh000066400000000000000000000062521257557151200201410ustar00rootroot00000000000000#ifndef EVENT_HH #define EVENT_HH #include "noncopyable.hh" #include namespace openmsx { class TclObject; enum EventType { OPENMSX_KEY_UP_EVENT, OPENMSX_KEY_DOWN_EVENT, OPENMSX_MOUSE_MOTION_EVENT, OPENMSX_MOUSE_MOTION_GROUP_EVENT, OPENMSX_MOUSE_BUTTON_UP_EVENT, OPENMSX_MOUSE_BUTTON_DOWN_EVENT, OPENMSX_JOY_AXIS_MOTION_EVENT, OPENMSX_JOY_HAT_EVENT, OPENMSX_JOY_BUTTON_UP_EVENT, OPENMSX_JOY_BUTTON_DOWN_EVENT, OPENMSX_FOCUS_EVENT, OPENMSX_RESIZE_EVENT, OPENMSX_QUIT_EVENT, OPENMSX_OSD_CONTROL_RELEASE_EVENT, OPENMSX_OSD_CONTROL_PRESS_EVENT, OPENMSX_BOOT_EVENT, // sent when the MSX resets or power ups /** Sent when VDP (V99x8 or V9990) reaches the end of a frame */ OPENMSX_FINISH_FRAME_EVENT, /** Sent when a OPENMSX_FINISH_FRAME_EVENT caused a redraw of the screen. * So in other words send when a OPENMSX_FINISH_FRAME_EVENT event was send * and the frame was not skipped and the event came from the active video * source. */ OPENMSX_FRAME_DRAWN_EVENT, OPENMSX_BREAK_EVENT, OPENMSX_SWITCH_RENDERER_EVENT, /** Used to schedule 'taking reverse snapshots' between Z80 instructions. */ OPENMSX_TAKE_REVERSE_SNAPSHOT, /** Command received on CliComm connection */ OPENMSX_CLICOMMAND_EVENT, /** Send when an after-emutime command should be executed. */ OPENMSX_AFTER_TIMED_EVENT, /** Send when a (new) machine configuration is loaded */ OPENMSX_MACHINE_LOADED_EVENT, /** Send when a machine is (de)activated. * This events is specific per machine. */ OPENMSX_MACHINE_ACTIVATED, OPENMSX_MACHINE_DEACTIVATED, /** Send when (part of) the openMSX window gets exposed, and thus * should be repainted. */ OPENMSX_EXPOSE_EVENT, /** Delete old MSXMotherboards */ OPENMSX_DELETE_BOARDS, OPENMSX_MIDI_IN_READER_EVENT, OPENMSX_MIDI_IN_WINDOWS_EVENT, OPENMSX_MIDI_IN_COREMIDI_EVENT, OPENMSX_MIDI_IN_COREMIDI_VIRTUAL_EVENT, OPENMSX_RS232_TESTER_EVENT, NUM_EVENT_TYPES // must be last }; class Event : private noncopyable { public: EventType getType() const { return type; } std::string toString() const; bool operator< (const Event& other) const; bool operator==(const Event& other) const; bool operator!=(const Event& other) const; /** Should 'bind -repeat' be stopped by 'other' event. * Normally all events should stop auto-repeat of the previous * event. But see OsdControlEvent for some exceptions. */ virtual bool isRepeatStopper(const Event& /*other*/) const { return true; } /** Does this event 'match' the given event. Normally an event * only matches itself (as defined by operator==). But e.g. * MouseMotionGroupEvent matches any MouseMotionEvent. */ virtual bool matches(const Event& other) const { return *this == other; } protected: explicit Event(EventType type_) : type(type_) {} ~Event() {} private: virtual void toStringImpl(TclObject& result) const = 0; virtual bool lessImpl(const Event& other) const = 0; const EventType type; }; // implementation for events that don't need additional data class SimpleEvent : public Event { public: SimpleEvent(EventType type) : Event(type) {} void toStringImpl(TclObject& result) const override; bool lessImpl(const Event& other) const override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/events/EventDistributor.cc000066400000000000000000000075571257557151200223730ustar00rootroot00000000000000#include "EventDistributor.hh" #include "EventListener.hh" #include "Reactor.hh" #include "RTScheduler.hh" #include "Interpreter.hh" #include "InputEventGenerator.hh" #include "Thread.hh" #include "KeyRange.hh" #include "stl.hh" #include #include #include using std::pair; using std::string; namespace openmsx { EventDistributor::EventDistributor(Reactor& reactor_) : reactor(reactor_) { } void EventDistributor::registerEventListener( EventType type, EventListener& listener, Priority priority) { std::lock_guard lock(mutex); auto& priorityMap = listeners[type]; for (auto* l : values(priorityMap)) { // a listener may only be registered once for each type assert(l != &listener); (void)l; } // insert at highest position that keeps listeners sorted on priority auto it = upper_bound(begin(priorityMap), end(priorityMap), priority, LessTupleElement<0>()); priorityMap.insert(it, {priority, &listener}); } void EventDistributor::unregisterEventListener( EventType type, EventListener& listener) { std::lock_guard lock(mutex); auto& priorityMap = listeners[type]; priorityMap.erase(find_if_unguarded(priorityMap, [&](PriorityMap::value_type v) { return v.second == &listener; })); } void EventDistributor::distributeEvent(const EventPtr& event) { // TODO: Implement a real solution against modifying data structure while // iterating through it. // For example, assign nullptr first and then iterate again after // delivering events to remove the nullptr values. // TODO: Is it useful to test for 0 listeners or should we just always // queue the event? assert(event); std::unique_lock lock(mutex); if (!listeners[event->getType()].empty()) { scheduledEvents.push_back(event); // must release lock, otherwise there's a deadlock: // thread 1: Reactor::deleteMotherBoard() // EventDistributor::unregisterEventListener() // thread 2: EventDistributor::distributeEvent() // Reactor::enterMainLoop() condition.notify_all(); lock.unlock(); reactor.enterMainLoop(); } } bool EventDistributor::isRegistered(EventType type, EventListener* listener) const { for (auto* l : values(listeners[type])) { if (l == listener) return true; } return false; } void EventDistributor::deliverEvents() { assert(Thread::isMainThread()); reactor.getInputEventGenerator().poll(); reactor.getInterpreter().poll(); reactor.getRTScheduler().execute(); std::unique_lock lock(mutex); // It's possible that executing an event triggers scheduling of another // event. We also want to execute those secondary events. That's why // we have this while loop here. // For example the 'loadstate' command event, triggers a machine switch // event and as reaction to the latter event, AfterCommand will // unsubscribe from the ols MSXEventDistributor. This really should be // done before we exit this method. while (!scheduledEvents.empty()) { EventQueue eventsCopy; swap(eventsCopy, scheduledEvents); for (auto& event : eventsCopy) { auto type = event->getType(); auto priorityMapCopy = listeners[type]; lock.unlock(); unsigned blockPriority = unsigned(-1); // allow all for (auto& p : priorityMapCopy) { // It's possible delivery to one of the previous // Listeners unregistered the current Listener. if (!isRegistered(type, p.second)) continue; unsigned currentPriority = p.first; if (currentPriority >= blockPriority) break; if (unsigned block = p.second->signalEvent(event)) { assert(block > currentPriority); blockPriority = block; } } lock.lock(); } } } bool EventDistributor::sleep(unsigned us) { std::chrono::microseconds duration(us); std::unique_lock lock(cvMutex); return condition.wait_for(lock, duration) == std::cv_status::timeout; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/events/EventDistributor.hh000066400000000000000000000047261257557151200224000ustar00rootroot00000000000000#ifndef EVENTDISTRIBUTOR_HH #define EVENTDISTRIBUTOR_HH #include "Event.hh" #include "noncopyable.hh" #include #include #include #include #include namespace openmsx { class Reactor; class EventListener; class EventDistributor : private noncopyable { public: using EventPtr = std::shared_ptr; /** Priorities from high to low, higher priority listeners can block * events for lower priority listeners. */ enum Priority { OTHER, CONSOLE, HOTKEY, MSX, }; explicit EventDistributor(Reactor& reactor); /** * Registers a given object to receive certain events. * @param type The type of the events you want to receive. * @param listener Listener that will be notified when an event arrives. * @param priority Listeners have a priority, higher priority liseners * can block events for lower priority listeners. */ void registerEventListener(EventType type, EventListener& listener, Priority priority = OTHER); /** * Unregisters a previously registered event listener. * @param type The type of the events the listener should no longer receive. * @param listener Listener to unregister. */ void unregisterEventListener(EventType type, EventListener& listener); /** Schedule the given event for delivery. Actual delivery happens * when the deliverEvents() method is called. Events are always * in the main thread. */ void distributeEvent(const EventPtr& event); /** This actually delivers the events. It may only be called from the * main loop in Reactor (and only from the main thread). Also see * the distributeEvent() method. */ void deliverEvents(); /** Sleep for the specified amount of time, but return early when * (another thread) called the distributeEvent() method. * @param us Amount of time to sleep, in micro seconds. * @result true if we return because time has passed * false if we return because distributeEvent() was called */ bool sleep(unsigned us); private: bool isRegistered(EventType type, EventListener* listener) const; Reactor& reactor; using PriorityMap = std::vector>; PriorityMap listeners[NUM_EVENT_TYPES]; using EventQueue = std::vector; EventQueue scheduledEvents; std::mutex mutex; // lock datastructures std::mutex cvMutex; // lock condition_variable std::condition_variable condition; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/events/EventListener.hh000066400000000000000000000014041257557151200216410ustar00rootroot00000000000000#ifndef EVENTLISTENER_HH #define EVENTLISTENER_HH #include namespace openmsx { class Event; class EventListener { public: /** * This method gets called when an event you are subscribed to occurs. * @result Must return a bitmask of EventListener priorities. When a * bit is set, this event won't be delivered to listeners with * that priority. It's only allowed/possible to block an event * for listeners with a strictly lower priority than this * listener. Returning 0 means don't block the event for any * listeners. */ virtual int signalEvent(const std::shared_ptr& event) = 0; protected: EventListener() {} ~EventListener() {} }; } // namespace openmsx #endif // EVENTLISTENER_HH openMSX-RELEASE_0_12_0/src/events/FinishFrameEvent.hh000066400000000000000000000035341257557151200222550ustar00rootroot00000000000000#ifndef FINISHFRAMEEVENT_HH #define FINISHFRAMEEVENT_HH #include "Event.hh" #include "TclObject.hh" #include "checked_cast.hh" #include namespace openmsx { /** * This event is send when a device (v99x8, v9990, video9000, laserdisc) * reaches the end of a frame. This event has info on: * - which device generated the event * - which video layer was active * - was the frame actually rendered or not (frameskip) * Note that even if a frame was rendered (not skipped) it may not (need to) be * displayed because the corresponding video layer is not active. Or also even * if the corresponding video layer for a device is not active, the rendered * frame may still be displayed as part of a superimposed video layer. */ class FinishFrameEvent final : public Event { public: FinishFrameEvent(int thisSource_, int selectedSource_, bool skipped_) : Event(OPENMSX_FINISH_FRAME_EVENT) , thisSource(thisSource_), selectedSource(selectedSource_) , skipped(skipped_) { } int getSource() const { return thisSource; } int getSelectedSource() const { return selectedSource; } bool isSkipped() const { return skipped; } bool needRender() const { return !skipped && (thisSource == selectedSource); } void toStringImpl(TclObject& result) const override { result.addListElement("finishframe"); result.addListElement(int(thisSource)); result.addListElement(int(selectedSource)); result.addListElement(skipped); } bool lessImpl(const Event& other) const override { auto& e = checked_cast(other); auto t1 = std::make_tuple( getSource(), getSelectedSource(), isSkipped()); auto t2 = std::make_tuple( e.getSource(), e.getSelectedSource(), e.isSkipped()); return t1 < t2; } private: const int thisSource; const int selectedSource; const bool skipped; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/events/GlobalCliComm.cc000066400000000000000000000055551257557151200215170ustar00rootroot00000000000000#include "GlobalCliComm.hh" #include "CliListener.hh" #include "CliConnection.hh" #include "Thread.hh" #include "ScopedAssign.hh" #include "stl.hh" #include #include #include namespace openmsx { GlobalCliComm::GlobalCliComm() : delivering(false) , allowExternalCommands(false) { } GlobalCliComm::~GlobalCliComm() { assert(Thread::isMainThread()); assert(!delivering); } void GlobalCliComm::addListener(std::unique_ptr listener) { // can be called from any thread std::lock_guard lock(mutex); auto* p = listener.get(); listeners.push_back(std::move(listener)); if (allowExternalCommands) { if (auto* conn = dynamic_cast(p)) { conn->start(); } } } std::unique_ptr GlobalCliComm::removeListener(CliListener& listener) { // can be called from any thread std::lock_guard lock(mutex); auto it = find_if_unguarded(listeners, [&](const std::unique_ptr& ptr) { return ptr.get() == &listener; }); auto result = std::move(*it); listeners.erase(it); return result; } void GlobalCliComm::setAllowExternalCommands() { assert(!allowExternalCommands); // should only be called once allowExternalCommands = true; for (auto& listener : listeners) { if (auto* conn = dynamic_cast(listener.get())) { conn->start(); } } } void GlobalCliComm::log(LogLevel level, string_ref message) { assert(Thread::isMainThread()); if (delivering) { // Don't allow recursive calls, this would hang while trying to // acquire the mutex below. But also when we would change // this to a recursive-mutex, this could result in an infinite // loop. // One example of a recursive invocation is when something goes // wrong in the Tcl proc attached to message_callback (e.g. the // font used to display the message could not be loaded). std::cerr << "Recursive cliComm message: " << message << std::endl; return; } ScopedAssign sa(delivering, true); std::lock_guard lock(mutex); if (!listeners.empty()) { for (auto& l : listeners) { l->log(level, message); } } else { // don't let the message get lost std::cerr << message << std::endl; } } void GlobalCliComm::update(UpdateType type, string_ref name, string_ref value) { assert(type < NUM_UPDATES); auto it = prevValues[type].find(name); if (it != end(prevValues[type])) { if (it->second == value) { return; } it->second = value.str(); } else { prevValues[type].emplace_noDuplicateCheck(name.str(), value.str()); } updateHelper(type, "", name, value); } void GlobalCliComm::updateHelper(UpdateType type, string_ref machine, string_ref name, string_ref value) { assert(Thread::isMainThread()); std::lock_guard lock(mutex); for (auto& l : listeners) { l->update(type, machine, name, value); } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/events/GlobalCliComm.hh000066400000000000000000000022721257557151200215220ustar00rootroot00000000000000#ifndef GLOBALCLICOMM_HH #define GLOBALCLICOMM_HH #include "CliComm.hh" #include "hash_map.hh" #include "noncopyable.hh" #include "xxhash.hh" #include #include #include namespace openmsx { class CliListener; class GlobalCliComm final : public CliComm, private noncopyable { public: GlobalCliComm(); ~GlobalCliComm(); void addListener(std::unique_ptr listener); std::unique_ptr removeListener(CliListener& listener); // Before this method has been called commands send over external // connections are not yet processed (but they keep pending). void setAllowExternalCommands(); // CliComm void log(LogLevel level, string_ref message) override; void update(UpdateType type, string_ref name, string_ref value) override; private: void updateHelper(UpdateType type, string_ref machine, string_ref name, string_ref value); hash_map prevValues[NUM_UPDATES]; std::vector> listeners; std::mutex mutex; // lock access to listeners member bool delivering; bool allowExternalCommands; friend class MSXCliComm; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/events/HotKey.cc000066400000000000000000000512021257557151200202440ustar00rootroot00000000000000#include "HotKey.hh" #include "InputEventFactory.hh" #include "GlobalCommandController.hh" #include "CommandException.hh" #include "EventDistributor.hh" #include "CliComm.hh" #include "InputEvents.hh" #include "XMLElement.hh" #include "TclObject.hh" #include "SettingsConfig.hh" #include "memory.hh" #include "outer.hh" #include "unreachable.hh" #include "build-info.hh" #include #include using std::string; using std::vector; using std::make_shared; // This file implements all Tcl key bindings. These are the 'classical' hotkeys // (e.g. F11 to (un)mute sound) and the more recent input layers. The idea // behind an input layer is something like an OSD widget that (temporarily) // takes semi-exclusive access to the input. So while the widget is active // keyboard (and joystick) input is no longer passed to the emulated MSX. // However the classical hotkeys or the openMSX console still receive input. namespace openmsx { const bool META_HOT_KEYS = #ifdef __APPLE__ true; #else false; #endif HotKey::HotKey(RTScheduler& rtScheduler, GlobalCommandController& commandController_, EventDistributor& eventDistributor_) : RTSchedulable(rtScheduler) , bindCmd (commandController_, *this, false) , bindDefaultCmd (commandController_, *this, true) , unbindCmd (commandController_, *this, false) , unbindDefaultCmd(commandController_, *this, true) , activateCmd (commandController_) , deactivateCmd (commandController_) , commandController(commandController_) , eventDistributor(eventDistributor_) { initDefaultBindings(); eventDistributor.registerEventListener( OPENMSX_KEY_DOWN_EVENT, *this, EventDistributor::HOTKEY); eventDistributor.registerEventListener( OPENMSX_KEY_UP_EVENT, *this, EventDistributor::HOTKEY); eventDistributor.registerEventListener( OPENMSX_MOUSE_MOTION_EVENT, *this, EventDistributor::HOTKEY); eventDistributor.registerEventListener( OPENMSX_MOUSE_BUTTON_DOWN_EVENT, *this, EventDistributor::HOTKEY); eventDistributor.registerEventListener( OPENMSX_MOUSE_BUTTON_UP_EVENT, *this, EventDistributor::HOTKEY); eventDistributor.registerEventListener( OPENMSX_JOY_BUTTON_DOWN_EVENT, *this, EventDistributor::HOTKEY); eventDistributor.registerEventListener( OPENMSX_JOY_BUTTON_UP_EVENT, *this, EventDistributor::HOTKEY); eventDistributor.registerEventListener( OPENMSX_JOY_AXIS_MOTION_EVENT, *this, EventDistributor::HOTKEY); eventDistributor.registerEventListener( OPENMSX_JOY_HAT_EVENT, *this, EventDistributor::HOTKEY); eventDistributor.registerEventListener( OPENMSX_FOCUS_EVENT, *this, EventDistributor::HOTKEY); eventDistributor.registerEventListener( OPENMSX_OSD_CONTROL_RELEASE_EVENT, *this, EventDistributor::HOTKEY); eventDistributor.registerEventListener( OPENMSX_OSD_CONTROL_PRESS_EVENT, *this, EventDistributor::HOTKEY); } HotKey::~HotKey() { eventDistributor.unregisterEventListener(OPENMSX_OSD_CONTROL_PRESS_EVENT, *this); eventDistributor.unregisterEventListener(OPENMSX_OSD_CONTROL_RELEASE_EVENT, *this); eventDistributor.unregisterEventListener(OPENMSX_FOCUS_EVENT, *this); eventDistributor.unregisterEventListener(OPENMSX_JOY_BUTTON_UP_EVENT, *this); eventDistributor.unregisterEventListener(OPENMSX_JOY_BUTTON_DOWN_EVENT, *this); eventDistributor.unregisterEventListener(OPENMSX_JOY_AXIS_MOTION_EVENT, *this); eventDistributor.unregisterEventListener(OPENMSX_JOY_HAT_EVENT, *this); eventDistributor.unregisterEventListener(OPENMSX_MOUSE_BUTTON_UP_EVENT, *this); eventDistributor.unregisterEventListener(OPENMSX_MOUSE_BUTTON_DOWN_EVENT, *this); eventDistributor.unregisterEventListener(OPENMSX_MOUSE_MOTION_EVENT, *this); eventDistributor.unregisterEventListener(OPENMSX_KEY_UP_EVENT, *this); eventDistributor.unregisterEventListener(OPENMSX_KEY_DOWN_EVENT, *this); } void HotKey::initDefaultBindings() { // TODO move to Tcl script? if (META_HOT_KEYS) { // Hot key combos using Mac's Command key. bindDefault(make_shared( Keys::combine(Keys::K_D, Keys::KM_META)), HotKeyInfo("screenshot -guess-name")); bindDefault(make_shared( Keys::combine(Keys::K_P, Keys::KM_META)), HotKeyInfo("toggle pause")); bindDefault(make_shared( Keys::combine(Keys::K_T, Keys::KM_META)), HotKeyInfo("toggle throttle")); bindDefault(make_shared( Keys::combine(Keys::K_L, Keys::KM_META)), HotKeyInfo("toggle console")); bindDefault(make_shared( Keys::combine(Keys::K_U, Keys::KM_META)), HotKeyInfo("toggle mute")); bindDefault(make_shared( Keys::combine(Keys::K_F, Keys::KM_META)), HotKeyInfo("toggle fullscreen")); bindDefault(make_shared( Keys::combine(Keys::K_Q, Keys::KM_META)), HotKeyInfo("exit")); } else { // Hot key combos for typical PC keyboards. bindDefault(make_shared(Keys::K_PRINT), HotKeyInfo("screenshot -guess-name")); bindDefault(make_shared(Keys::K_PAUSE), HotKeyInfo("toggle pause")); bindDefault(make_shared(Keys::K_F9), HotKeyInfo("toggle throttle")); bindDefault(make_shared(Keys::K_F10), HotKeyInfo("toggle console")); bindDefault(make_shared(Keys::K_F11), HotKeyInfo("toggle mute")); bindDefault(make_shared(Keys::K_F12), HotKeyInfo("toggle fullscreen")); bindDefault(make_shared( Keys::combine(Keys::K_F4, Keys::KM_ALT)), HotKeyInfo("exit")); bindDefault(make_shared( Keys::combine(Keys::K_PAUSE, Keys::KM_CTRL)), HotKeyInfo("exit")); bindDefault(make_shared( Keys::combine(Keys::K_RETURN, Keys::KM_ALT)), HotKeyInfo("toggle fullscreen")); #if PLATFORM_ANDROID // The follwing binding is specific for Android, in order // to remap the android back button to an SDL KEY event. // I could have put all Android key bindings in a separate // else(...) clause. However, an Android user might actually // be using a PC keyboard (through USB or Bluetooth) and in such // case expect all default PC keybindings to exist as well bindDefault(make_shared(Keys::K_WORLD_92), HotKeyInfo("quitmenu::quit_menu")); #endif } } static HotKey::EventPtr createEvent(const TclObject& obj, Interpreter& interp) { auto event = InputEventFactory::createInputEvent(obj, interp); if (!dynamic_cast (event.get()) && !dynamic_cast (event.get()) && !dynamic_cast(event.get()) && !dynamic_cast (event.get()) && !dynamic_cast (event.get()) && !dynamic_cast (event.get())) { throw CommandException("Unsupported event type"); } return event; } static HotKey::EventPtr createEvent(const string& str, Interpreter& interp) { return createEvent(TclObject(str), interp); } void HotKey::loadBindings(const XMLElement& config) { // restore default bindings unboundKeys.clear(); boundKeys.clear(); cmdMap = defaultMap; // load bindings auto* bindingsElement = config.findChild("bindings"); if (!bindingsElement) return; auto copy = *bindingsElement; // dont iterate over changing container for (auto& elem : copy.getChildren()) { try { auto& interp = commandController.getInterpreter(); if (elem.getName() == "bind") { bind(createEvent(elem.getAttribute("key"), interp), HotKeyInfo(elem.getData(), elem.getAttributeAsBool("repeat", false))); } else if (elem.getName() == "unbind") { unbind(createEvent(elem.getAttribute("key"), interp)); } } catch (MSXException& e) { commandController.getCliComm().printWarning( "Error while loading key bindings: " + e.getMessage()); } } } void HotKey::saveBindings(XMLElement& config) const { auto& bindingsElement = config.getCreateChild("bindings"); bindingsElement.removeAllChildren(); // add explicit bind's for (auto& k : boundKeys) { auto it2 = cmdMap.find(k); assert(it2 != end(cmdMap)); auto& info = it2->second; auto& elem = bindingsElement.addChild("bind", info.command); elem.addAttribute("key", k->toString()); if (info.repeat) { elem.addAttribute("repeat", "true"); } } // add explicit unbind's for (auto& k : unboundKeys) { auto& elem = bindingsElement.addChild("unbind"); elem.addAttribute("key", k->toString()); } } void HotKey::bind(const EventPtr& event, const HotKeyInfo& info) { unboundKeys.erase(event); boundKeys.insert(event); defaultMap.erase(event); cmdMap[event] = info; saveBindings(commandController.getSettingsConfig().getXMLElement()); } void HotKey::unbind(const EventPtr& event) { if (boundKeys.find(event) == end(boundKeys)) { // only when not a regular bound event unboundKeys.insert(event); } boundKeys.erase(event); defaultMap.erase(event); cmdMap.erase(event); saveBindings(commandController.getSettingsConfig().getXMLElement()); } void HotKey::bindDefault(const EventPtr& event, const HotKeyInfo& info) { if ((unboundKeys.find(event) == end(unboundKeys)) && (boundKeys.find(event) == end(boundKeys))) { // not explicity bound or unbound cmdMap[event] = info; } defaultMap[event] = info; } void HotKey::unbindDefault(const EventPtr& event) { if ((unboundKeys.find(event) == end(unboundKeys)) && (boundKeys.find(event) == end(boundKeys))) { // not explicity bound or unbound cmdMap.erase(event); } defaultMap.erase(event); } void HotKey::bindLayer(const EventPtr& event, const HotKeyInfo& info, const string& layer) { layerMap[layer][event] = info; } void HotKey::unbindLayer(const EventPtr& event, const string& layer) { layerMap[layer].erase(event); } void HotKey::unbindFullLayer(const string& layer) { layerMap.erase(layer); } void HotKey::activateLayer(std::string layer, bool blocking) { // Insert new activattion record at the end of the list. // (it's not an error if the same layer was already active, in such // as case it will now appear twice in the list of active layer, // and it must also be deactivated twice). activeLayers.push_back({std::move(layer), blocking}); } void HotKey::deactivateLayer(string_ref layer) { // remove the first matching activation record from the end // (it's not an error if there is no match at all) auto it = std::find_if(activeLayers.rbegin(), activeLayers.rend(), [&](const LayerInfo& info) { return info.layer == layer; }); if (it != activeLayers.rend()) { // 'reverse_iterator' -> 'iterator' conversion is a bit tricky activeLayers.erase((it + 1).base()); } } static HotKey::BindMap::const_iterator findMatch( const HotKey::BindMap& map, const Event& event) { return find_if(begin(map), end(map), [&](const HotKey::BindMap::value_type& p) { return p.first->matches(event); }); } void HotKey::executeRT() { if (lastEvent) executeEvent(lastEvent); } int HotKey::signalEvent(const EventPtr& event) { if (lastEvent != event) { // If the newly received event is different from the repeating // event, we stop the repeat process. // Except when we're repeating a OsdControlEvent and the // received event was actually the 'generating' event for the // Osd event. E.g. a cursor-keyboard-down event will generate // a corresponding osd event (the osd event is send before the // original event). Without this hack, key-repeat will not work // for osd key bindings. if (lastEvent && lastEvent->isRepeatStopper(*event)) { stopRepeat(); } } return executeEvent(event); } int HotKey::executeEvent(const EventPtr& event) { // First search in active layers (from back to front) bool blocking = false; for (auto it = activeLayers.rbegin(); it != activeLayers.rend(); ++it) { auto& cmap = layerMap[it->layer]; // ok, if this entry doesn't exist yet auto it2 = findMatch(cmap, *event); if (it2 != end(cmap)) { executeBinding(event, it2->second); // Deny event to MSX listeners, also don't pass event // to other layers (including the default layer). return EventDistributor::MSX; } blocking = it->blocking; if (blocking) break; // don't try lower layers } // If the event was not yet handled, try the default layer. auto it = findMatch(cmdMap, *event); if (it != end(cmdMap)) { executeBinding(event, it->second); return EventDistributor::MSX; // deny event to MSX listeners } // Event is not handled, only let it pass to the MSX if there was no // blocking layer active. return blocking ? EventDistributor::MSX : 0; } void HotKey::executeBinding(const EventPtr& event, const HotKeyInfo& info) { if (info.repeat) { startRepeat(event); } try { // Make a copy of the command string because executing the // command could potentially execute (un)bind commands so // that the original string becomes invalid. // Valgrind complained about this in the following scenario: // - open the OSD menu // - activate the 'Exit openMSX' item // The latter is triggered from e.g. a 'OSDControl A PRESS' // event. The Tcl script bound to that event closes the main // menu and reopens a new quit_menu. This will re-bind the // action for the 'OSDControl A PRESS' event. string copy = info.command; // ignore return value commandController.executeCommand(copy); } catch (CommandException& e) { commandController.getCliComm().printWarning( "Error executing hot key command: " + e.getMessage()); } } void HotKey::startRepeat(const EventPtr& event) { // I initially thought about using the builtin SDL key-repeat feature, // but that won't work for example on joystick buttons. So we have to // code it ourselves. // On android, because of the sensitivity of the touch screen it's // very hard to have touches of short durations. So half a second is // too short for the key-repeat-delay. A full second should be fine. static const unsigned DELAY = PLATFORM_ANDROID ? 1000 : 500; // Repeat period. static const unsigned PERIOD = 30; unsigned delay = (lastEvent ? PERIOD : DELAY) * 1000; lastEvent = event; scheduleRT(delay); } void HotKey::stopRepeat() { lastEvent.reset(); cancelRT(); } // class BindCmd static string getBindCmdName(bool defaultCmd) { return defaultCmd ? "bind_default" : "bind"; } HotKey::BindCmd::BindCmd(CommandController& commandController, HotKey& hotKey_, bool defaultCmd_) : Command(commandController, getBindCmdName(defaultCmd_)) , hotKey(hotKey_) , defaultCmd(defaultCmd_) { } string HotKey::BindCmd::formatBinding(const HotKey::BindMap::value_type& p) { auto& info = p.second; return p.first->toString() + (info.repeat ? " [repeat]" : "") + ": " + info.command + '\n'; } static vector parse(bool defaultCmd, array_ref tokens_, string& layer, bool& layers) { layers = false; vector tokens(std::begin(tokens_) + 1, std::end(tokens_)); for (size_t i = 0; i < tokens.size(); /**/) { if (tokens[i] == "-layer") { if (i == (tokens.size() - 1)) { throw CommandException("Missing layer name"); } if (defaultCmd) { throw CommandException( "Layers are not supported for default bindings"); } layer = tokens[i + 1].getString().str(); auto it = begin(tokens) + i; tokens.erase(it, it + 2); } else if (tokens[i] == "-layers") { layers = true; tokens.erase(begin(tokens) + i); } else { ++i; } } return tokens; } void HotKey::BindCmd::execute(array_ref tokens_, TclObject& result) { string layer; bool layers; auto tokens = parse(defaultCmd, tokens_, layer, layers); auto& cmdMap = defaultCmd ? hotKey.defaultMap : layer.empty() ? hotKey.cmdMap : hotKey.layerMap[layer]; if (layers) { for (auto& p : hotKey.layerMap) { // An alternative for this test is to always properly // prune layerMap. ATM this approach seems simpler. if (!p.second.empty()) { result.addListElement(p.first); } } return; } switch (tokens.size()) { case 0: { // show all bounded keys (for this layer) string r; for (auto& p : cmdMap) { r += formatBinding(p); } result.setString(r); break; } case 1: { // show bindings for this key (in this layer) auto it = cmdMap.find(createEvent(tokens[0], getInterpreter())); if (it == end(cmdMap)) { throw CommandException("Key not bound"); } result.setString(formatBinding(*it)); break; } default: { // make a new binding string command; bool repeat = false; unsigned start = 1; if (tokens[1] == "-repeat") { repeat = true; ++start; } for (unsigned i = start; i < tokens.size(); ++i) { if (i != start) command += ' '; string_ref t = tokens[i].getString(); command.append(t.data(), t.size()); } HotKey::HotKeyInfo info(command, repeat); auto event = createEvent(tokens[0], getInterpreter()); if (defaultCmd) { hotKey.bindDefault(event, info); } else if (layer.empty()) { hotKey.bind(event, info); } else { hotKey.bindLayer(event, info, layer); } break; } } } string HotKey::BindCmd::help(const vector& /*tokens*/) const { string cmd = getBindCmdName(defaultCmd); return cmd + " : show all bounded keys\n" + cmd + " : show binding for this key\n" + cmd + " [-repeat] : bind key to command, optionally " "repeat command while key remains pressed\n" "These 3 take an optional '-layer ' option, " "see activate_input_layer." + cmd + " -layers : show a list of layers with bound keys\n"; } // class UnbindCmd static string getUnbindCmdName(bool defaultCmd) { return defaultCmd ? "unbind_default" : "unbind"; } HotKey::UnbindCmd::UnbindCmd(CommandController& commandController, HotKey& hotKey_, bool defaultCmd_) : Command(commandController, getUnbindCmdName(defaultCmd_)) , hotKey(hotKey_) , defaultCmd(defaultCmd_) { } void HotKey::UnbindCmd::execute(array_ref tokens_, TclObject& /*result*/) { string layer; bool layers; auto tokens = parse(defaultCmd, tokens_, layer, layers); if (layers) { throw SyntaxError(); } if ((tokens.size() > 1) || (layer.empty() && (tokens.size() != 1))) { throw SyntaxError(); } HotKey::EventPtr event; if (tokens.size() == 1) { event = createEvent(tokens[0], getInterpreter()); } if (defaultCmd) { assert(event); hotKey.unbindDefault(event); } else if (layer.empty()) { assert(event); hotKey.unbind(event); } else { if (event) { hotKey.unbindLayer(event, layer); } else { hotKey.unbindFullLayer(layer); } } } string HotKey::UnbindCmd::help(const vector& /*tokens*/) const { string cmd = getUnbindCmdName(defaultCmd); return cmd + " : unbind this key\n" + cmd + " -layer : unbind key in a specific layer\n" + cmd + " -layer : unbind all keys in this layer\n"; } // class ActivateCmd HotKey::ActivateCmd::ActivateCmd(CommandController& commandController) : Command(commandController, "activate_input_layer") { } void HotKey::ActivateCmd::execute(array_ref tokens, TclObject& result) { string_ref layer; bool blocking = false; for (size_t i = 1; i < tokens.size(); ++i) { if (tokens[i] == "-blocking") { blocking = true; } else { if (!layer.empty()) { throw SyntaxError(); } layer = tokens[i].getString(); } } string r; auto& hotKey = OUTER(HotKey, activateCmd); if (layer.empty()) { for (auto it = hotKey.activeLayers.rbegin(); it != hotKey.activeLayers.rend(); ++it) { r += it->layer; if (it->blocking) { r += " -blocking"; } r += '\n'; } } else { hotKey.activateLayer(layer.str(), blocking); } result.setString(r); } string HotKey::ActivateCmd::help(const vector& /*tokens*/) const { return "activate_input_layer " ": show list of active layers (most recent on top)\n" "activate_input_layer [-blocking] " ": activate new layer, optionally in blocking mode\n"; } // class DeactivateCmd HotKey::DeactivateCmd::DeactivateCmd(CommandController& commandController) : Command(commandController, "deactivate_input_layer") { } void HotKey::DeactivateCmd::execute(array_ref tokens, TclObject& /*result*/) { if (tokens.size() != 2) { throw SyntaxError(); } auto& hotKey = OUTER(HotKey, deactivateCmd); hotKey.deactivateLayer(tokens[1].getString()); } string HotKey::DeactivateCmd::help(const vector& /*tokens*/) const { return "deactivate_input_layer : deactive the given input layer"; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/events/HotKey.hh000066400000000000000000000071351257557151200202640ustar00rootroot00000000000000#ifndef HOTKEY_HH #define HOTKEY_HH #include "RTSchedulable.hh" #include "EventListener.hh" #include "Command.hh" #include "stl.hh" #include "string_ref.hh" #include #include #include #include #include namespace openmsx { class Event; class RTScheduler; class GlobalCommandController; class EventDistributor; class XMLElement; class HotKey final : public RTSchedulable, public EventListener { public: struct HotKeyInfo { HotKeyInfo() {} // for map::operator[] HotKeyInfo(std::string command_, bool repeat_ = false) : command(std::move(command_)), repeat(repeat_) {} std::string command; bool repeat; }; using EventPtr = std::shared_ptr; using BindMap = std::map; using KeySet = std::set; HotKey(RTScheduler& rtScheduler, GlobalCommandController& commandController, EventDistributor& eventDistributor); ~HotKey(); void loadBindings(const XMLElement& config); void saveBindings(XMLElement& config) const; private: struct LayerInfo { std::string layer; bool blocking; }; void initDefaultBindings(); void bind (const EventPtr& event, const HotKeyInfo& info); void unbind (const EventPtr& event); void bindDefault (const EventPtr& event, const HotKeyInfo& info); void unbindDefault(const EventPtr& event); void bindLayer (const EventPtr& event, const HotKeyInfo& info, const std::string& layer); void unbindLayer (const EventPtr& event, const std::string& layer); void unbindFullLayer(const std::string& layer); void activateLayer (std::string layer, bool blocking); void deactivateLayer(string_ref layer); int executeEvent(const EventPtr& event); void executeBinding(const EventPtr& event, const HotKeyInfo& info); void startRepeat (const EventPtr& event); void stopRepeat(); // EventListener int signalEvent(const EventPtr& event) override; // RTSchedulable void executeRT() override; class BindCmd final : public Command { public: BindCmd(CommandController& commandController, HotKey& hotKey, bool defaultCmd); void execute(array_ref tokens, TclObject& result) override; std::string help(const std::vector& tokens) const override; private: std::string formatBinding(const HotKey::BindMap::value_type& p); HotKey& hotKey; const bool defaultCmd; }; BindCmd bindCmd; BindCmd bindDefaultCmd; class UnbindCmd final : public Command { public: UnbindCmd(CommandController& commandController, HotKey& hotKey, bool defaultCmd); void execute(array_ref tokens, TclObject& result) override; std::string help(const std::vector& tokens) const override; private: HotKey& hotKey; const bool defaultCmd; }; UnbindCmd unbindCmd; UnbindCmd unbindDefaultCmd; struct ActivateCmd final : Command { ActivateCmd(CommandController& commandController); void execute(array_ref tokens, TclObject& result) override; std::string help(const std::vector& tokens) const override; } activateCmd; struct DeactivateCmd final : Command { DeactivateCmd(CommandController& commandController); void execute(array_ref tokens, TclObject& result) override; std::string help(const std::vector& tokens) const override; } deactivateCmd; BindMap cmdMap; BindMap defaultMap; std::map layerMap; std::vector activeLayers; KeySet boundKeys; KeySet unboundKeys; GlobalCommandController& commandController; EventDistributor& eventDistributor; EventPtr lastEvent; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/events/InputEventFactory.cc000066400000000000000000000163341257557151200225010ustar00rootroot00000000000000#include "InputEventFactory.hh" #include "InputEvents.hh" #include "CommandException.hh" #include "Interpreter.hh" #include "TclObject.hh" #include #include using std::make_shared; namespace openmsx { namespace InputEventFactory { static EventPtr parseKeyEvent(string_ref str, unsigned unicode) { auto keyCode = Keys::getCode(str); if (keyCode == Keys::K_NONE) { throw CommandException("Invalid keycode: " + str); } if (keyCode & Keys::KD_RELEASE) { return make_shared(keyCode, unicode); } else { return make_shared(keyCode, unicode); } } static EventPtr parseKeyEvent(const TclObject& str, Interpreter& interp) { auto len = str.getListLength(interp); if (len == 2) { auto comp1 = str.getListIndex(interp, 1).getString(); return parseKeyEvent(comp1, 0); } else if (len == 3) { auto comp1 = str.getListIndex(interp, 1).getString(); auto comp2 = str.getListIndex(interp, 2).getString(); if (comp2.starts_with("unicode")) { try { return parseKeyEvent( comp1, fast_stou(comp2.substr(7))); } catch (std::invalid_argument&) { // parse error in fast_stou() } } } throw CommandException("Invalid keyboard event: " + str.getString()); } static bool upDown(string_ref str) { if (str == "up") { return true; } else if (str == "down") { return false; } throw CommandException( "Invalid direction (expected 'up' or 'down'): " + str); } static EventPtr parseMouseEvent(const TclObject& str, Interpreter& interp) { auto len = str.getListLength(interp); if (len >= 2) { auto comp1 = str.getListIndex(interp, 1).getString(); if (comp1 == "motion") { if (len == 2) { return make_shared(); } else if ((len == 4) || (len == 6)) { int absX = 0, absY = 0; if (len == 6) { absX = str.getListIndex(interp, 4).getInt(interp); absY = str.getListIndex(interp, 5).getInt(interp); } else { // for bw-compat also allow events without absX,absY } return make_shared( str.getListIndex(interp, 2).getInt(interp), str.getListIndex(interp, 3).getInt(interp), absX, absY); } } else if (comp1.starts_with("button") && (len == 3)) { try { unsigned button = fast_stou(comp1.substr(6)); if (upDown(str.getListIndex(interp, 2).getString())) { return make_shared (button); } else { return make_shared(button); } } catch (std::invalid_argument&) { // parse error in fast_stou() } } } throw CommandException("Invalid mouse event: " + str.getString()); } static EventPtr parseOsdControlEvent(const TclObject& str, Interpreter& interp) { if (str.getListLength(interp) == 3) { auto buttonName = str.getListIndex(interp, 1).getString(); unsigned button; if (buttonName == "LEFT") { button = OsdControlEvent::LEFT_BUTTON; } else if (buttonName == "RIGHT") { button = OsdControlEvent::RIGHT_BUTTON; } else if (buttonName == "UP") { button = OsdControlEvent::UP_BUTTON; } else if (buttonName == "DOWN") { button = OsdControlEvent::DOWN_BUTTON; } else if (buttonName == "A") { button = OsdControlEvent::A_BUTTON; } else if (buttonName == "B") { button = OsdControlEvent::B_BUTTON; } else { goto error; } auto buttonAction = str.getListIndex(interp, 2).getString(); if (buttonAction == "RELEASE") { return make_shared(button, nullptr); } else if (buttonAction == "PRESS") { return make_shared (button, nullptr); } } error: throw CommandException("Invalid OSDcontrol event: " + str.getString()); } static EventPtr parseJoystickEvent(const TclObject& str, Interpreter& interp) { try { if (str.getListLength(interp) != 3) goto error; auto comp0 = str.getListIndex(interp, 0).getString(); // joyN auto comp1 = str.getListIndex(interp, 1).getString(); auto comp2 = str.getListIndex(interp, 2); unsigned joystick = fast_stou(comp0.substr(3)) - 1; if (comp1.starts_with("button")) { unsigned button = fast_stou(comp1.substr(6)); if (upDown(comp2.getString())) { return make_shared (joystick, button); } else { return make_shared(joystick, button); } } else if (comp1.starts_with("axis")) { unsigned axis = fast_stou(comp1.substr(4)); int value = str.getListIndex(interp, 2).getInt(interp); return make_shared(joystick, axis, value); } else if (comp1.starts_with("hat")) { unsigned hat = fast_stou(comp1.substr(3)); auto valueStr = str.getListIndex(interp, 2).getString(); int value; if (valueStr == "up") value = SDL_HAT_UP; else if (valueStr == "right") value = SDL_HAT_RIGHT; else if (valueStr == "down") value = SDL_HAT_DOWN; else if (valueStr == "left") value = SDL_HAT_LEFT; else if (valueStr == "rightup") value = SDL_HAT_RIGHTUP; else if (valueStr == "rightdown") value = SDL_HAT_RIGHTDOWN; else if (valueStr == "leftup") value = SDL_HAT_LEFTUP; else if (valueStr == "leftdown") value = SDL_HAT_LEFTDOWN; else if (valueStr == "center") value = SDL_HAT_CENTERED; else { throw CommandException("Invalid hat value: " + valueStr); } return make_shared(joystick, hat, value); } } catch (std::invalid_argument&) { // parse error in fast_stou() } error: throw CommandException("Invalid joystick event: " + str.getString()); } static EventPtr parseFocusEvent(const TclObject& str, Interpreter& interp) { if (str.getListLength(interp) != 2) { throw CommandException("Invalid focus event: " + str.getString()); } return make_shared(str.getListIndex(interp, 1).getBoolean(interp)); } static EventPtr parseResizeEvent(const TclObject& str, Interpreter& interp) { if (str.getListLength(interp) != 3) { throw CommandException("Invalid resize event: " + str.getString()); } return make_shared( str.getListIndex(interp, 1).getInt(interp), str.getListIndex(interp, 2).getInt(interp)); } static EventPtr parseQuitEvent(const TclObject& str, Interpreter& interp) { if (str.getListLength(interp) != 1) { throw CommandException("Invalid quit event: " + str.getString()); } return make_shared(); } EventPtr createInputEvent(const TclObject& str, Interpreter& interp) { if (str.getListLength(interp) == 0) { throw CommandException("Invalid event: " + str.getString()); } auto type = str.getListIndex(interp, 0).getString(); if (type == "keyb") { return parseKeyEvent(str, interp); } else if (type == "mouse") { return parseMouseEvent(str, interp); } else if (type.starts_with("joy")) { return parseJoystickEvent(str, interp); } else if (type == "focus") { return parseFocusEvent(str, interp); } else if (type == "resize") { return parseResizeEvent(str, interp); } else if (type == "quit") { return parseQuitEvent(str, interp); } else if (type == "command") { return EventPtr(); //return parseCommandEvent(str); } else if (type == "OSDcontrol") { return parseOsdControlEvent(str, interp); } else { // fall back return parseKeyEvent(type, 0); } } EventPtr createInputEvent(string_ref str, Interpreter& interp) { return createInputEvent(TclObject(str), interp); } } // namespace InputEventFactory } // namespace openmsx openMSX-RELEASE_0_12_0/src/events/InputEventFactory.hh000066400000000000000000000006551257557151200225120ustar00rootroot00000000000000#ifndef INPUTEVENTFACTORY_HH #define INPUTEVENTFACTORY_HH #include "string_ref.hh" #include namespace openmsx { class Event; class TclObject; class Interpreter; namespace InputEventFactory { using EventPtr = std::shared_ptr; EventPtr createInputEvent(string_ref str, Interpreter& interp); EventPtr createInputEvent(const TclObject& str, Interpreter& interp); } } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/events/InputEventGenerator.cc000066400000000000000000000330011257557151200230060ustar00rootroot00000000000000#include "InputEventGenerator.hh" #include "EventDistributor.hh" #include "InputEvents.hh" #include "IntegerSetting.hh" #include "GlobalSettings.hh" #include "Keys.hh" #include "checked_cast.hh" #include "memory.hh" #include "outer.hh" #include "unreachable.hh" #include "build-info.hh" #include #include using std::string; using std::vector; using std::make_shared; namespace openmsx { bool InputEventGenerator::androidButtonA = false; bool InputEventGenerator::androidButtonB = false; InputEventGenerator::InputEventGenerator(CommandController& commandController, EventDistributor& eventDistributor_, GlobalSettings& globalSettings_) : eventDistributor(eventDistributor_) , globalSettings(globalSettings_) , grabInput( commandController, "grabinput", "This setting controls if openMSX takes over mouse and keyboard input", false, Setting::DONT_SAVE) , escapeGrabCmd(commandController) , escapeGrabState(ESCAPE_GRAB_WAIT_CMD) , keyRepeat(false) { setGrabInput(grabInput.getBoolean()); grabInput.attach(*this); eventDistributor.registerEventListener(OPENMSX_FOCUS_EVENT, *this); reinit(); osdControlButtonsState = unsigned(~0); // 0 is pressed, 1 is released #ifndef SDL_JOYSTICK_DISABLED SDL_JoystickEventState(SDL_ENABLE); // joysticks generate events #endif } InputEventGenerator::~InputEventGenerator() { eventDistributor.unregisterEventListener(OPENMSX_FOCUS_EVENT, *this); grabInput.detach(*this); } void InputEventGenerator::reinit() { SDL_EnableUNICODE(1); setKeyRepeat(keyRepeat); } void InputEventGenerator::wait() { // SDL bug workaround if (!SDL_WasInit(SDL_INIT_VIDEO)) { SDL_Delay(100); } if (SDL_WaitEvent(nullptr)) { poll(); } } void InputEventGenerator::poll() { SDL_Event event; while (SDL_PollEvent(&event) == 1) { #if 0 string t; switch (event.type) { case SDL_ACTIVEEVENT: t = "SDL_ACTIVEEVENT"; break; case SDL_KEYDOWN: t = "SDL_KEYDOWN"; break; case SDL_KEYUP: t = "SDL_KEYUP"; break; case SDL_MOUSEMOTION: t = "SDL_MOUSEMOTION"; break; case SDL_MOUSEBUTTONDOWN: t = "SDL_MOUSEBUTTONDOWN"; break; case SDL_MOUSEBUTTONUP: t = "SDL_MOUSEBUTTONUP"; break; case SDL_JOYAXISMOTION: t = "SDL_JOYAXISMOTION"; break; case SDL_JOYBALLMOTION: t = "SDL_JOYBALLMOTION"; break; case SDL_JOYHATMOTION: t = "SDL_JOYHATMOTION"; break; case SDL_JOYBUTTONDOWN: t = "SDL_JOYBUTTONDOWN"; break; case SDL_JOYBUTTONUP: t = "SDL_JOYBUTTONUP"; break; case SDL_QUIT: t = "SDL_QUIT"; break; case SDL_SYSWMEVENT: t = "SDL_SYSWMEVENT"; break; case SDL_VIDEORESIZE: t = "SDL_VIDEORESIZE"; break; case SDL_VIDEOEXPOSE: t = "SDL_VIDEOEXPOSE"; break; case SDL_USEREVENT: t = "SDL_USEREVENT"; break; default: t = "UNKNOWN"; break; } std::cerr << "SDL event received, type: " << t << std::endl; #endif handle(event); } } void InputEventGenerator::setKeyRepeat(bool enable) { keyRepeat = enable; if (keyRepeat) { SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); } else { SDL_EnableKeyRepeat(0, 0); } } void InputEventGenerator::setNewOsdControlButtonState( unsigned newState, const EventPtr& origEvent) { unsigned deltaState = osdControlButtonsState ^ newState; for (unsigned i = OsdControlEvent::LEFT_BUTTON; i <= OsdControlEvent::B_BUTTON; ++i) { if (deltaState & (1 << i)) { if (newState & (1 << i)) { eventDistributor.distributeEvent( make_shared( i, origEvent)); } else { eventDistributor.distributeEvent( make_shared( i, origEvent)); } } } osdControlButtonsState = newState; } void InputEventGenerator::triggerOsdControlEventsFromJoystickAxisMotion( unsigned axis, short value, const EventPtr& origEvent) { unsigned neg_button, pos_button; switch (axis) { case 0: neg_button = 1 << OsdControlEvent::LEFT_BUTTON; pos_button = 1 << OsdControlEvent::RIGHT_BUTTON; break; // axis 0 case 1: neg_button = 1 << OsdControlEvent::UP_BUTTON; pos_button = 1 << OsdControlEvent::DOWN_BUTTON; break; default: // Ignore all other axis (3D joysticks and flight joysticks may // have more than 2 axis) return; } if (value > 0) { // release negative button, press positive button setNewOsdControlButtonState( (osdControlButtonsState | neg_button) & ~pos_button, origEvent); } else if (value < 0) { // press negative button, release positive button setNewOsdControlButtonState( (osdControlButtonsState | pos_button) & ~neg_button, origEvent); } else { // release both buttons setNewOsdControlButtonState( osdControlButtonsState | neg_button | pos_button, origEvent); } } void InputEventGenerator::triggerOsdControlEventsFromJoystickHat( int value, const EventPtr& origEvent) { unsigned dir = 0; if (!(value & SDL_HAT_UP )) dir |= 1 << OsdControlEvent::UP_BUTTON; if (!(value & SDL_HAT_DOWN )) dir |= 1 << OsdControlEvent::DOWN_BUTTON; if (!(value & SDL_HAT_LEFT )) dir |= 1 << OsdControlEvent::LEFT_BUTTON; if (!(value & SDL_HAT_RIGHT)) dir |= 1 << OsdControlEvent::RIGHT_BUTTON; unsigned ab = osdControlButtonsState & ((1 << OsdControlEvent::A_BUTTON) | (1 << OsdControlEvent::B_BUTTON)); setNewOsdControlButtonState(ab | dir, origEvent); } void InputEventGenerator::osdControlChangeButton( bool up, unsigned changedButtonMask, const EventPtr& origEvent) { auto newButtonState = up ? osdControlButtonsState | changedButtonMask : osdControlButtonsState & ~changedButtonMask; setNewOsdControlButtonState(newButtonState, origEvent); } void InputEventGenerator::triggerOsdControlEventsFromJoystickButtonEvent( unsigned button, bool up, const EventPtr& origEvent) { osdControlChangeButton( up, ((button & 1) ? (1 << OsdControlEvent::B_BUTTON) : (1 << OsdControlEvent::A_BUTTON)), origEvent); } void InputEventGenerator::triggerOsdControlEventsFromKeyEvent( Keys::KeyCode keyCode, bool up, const EventPtr& origEvent) { keyCode = static_cast(keyCode & Keys::K_MASK); if (keyCode == Keys::K_LEFT) { osdControlChangeButton(up, 1 << OsdControlEvent::LEFT_BUTTON, origEvent); } else if (keyCode == Keys::K_RIGHT) { osdControlChangeButton(up, 1 << OsdControlEvent::RIGHT_BUTTON, origEvent); } else if (keyCode == Keys::K_UP) { osdControlChangeButton(up, 1 << OsdControlEvent::UP_BUTTON, origEvent); } else if (keyCode == Keys::K_DOWN) { osdControlChangeButton(up, 1 << OsdControlEvent::DOWN_BUTTON, origEvent); } else if (keyCode == Keys::K_SPACE || keyCode == Keys::K_RETURN) { osdControlChangeButton(up, 1 << OsdControlEvent::A_BUTTON, origEvent); } else if (keyCode == Keys::K_ESCAPE) { osdControlChangeButton(up, 1 << OsdControlEvent::B_BUTTON, origEvent); } } void InputEventGenerator::handle(const SDL_Event& evt) { EventPtr event; switch (evt.type) { case SDL_KEYUP: // Virtual joystick of SDL Android port does not have joystick // buttons. It has however up to 6 virtual buttons that can be // mapped to SDL keyboard events. Two of these virtual buttons // will be mapped to keys SDLK_WORLD_93 and 94 and are // interpeted here as joystick buttons (respectively button 0 // and 1). if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_93) { event = make_shared(0, 0); triggerOsdControlEventsFromJoystickButtonEvent( 0, true, event); androidButtonA = false; } else if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_94) { event = make_shared(0, 1); triggerOsdControlEventsFromJoystickButtonEvent( 1, true, event); androidButtonB = false; } else { auto keyCode = Keys::getCode( evt.key.keysym.sym, evt.key.keysym.mod, evt.key.keysym.scancode, true); event = make_shared( keyCode, evt.key.keysym.unicode); triggerOsdControlEventsFromKeyEvent(keyCode, true, event); } break; case SDL_KEYDOWN: if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_93) { event = make_shared(0, 0); triggerOsdControlEventsFromJoystickButtonEvent( 0, false, event); androidButtonA = true; } else if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_94) { event = make_shared(0, 1); triggerOsdControlEventsFromJoystickButtonEvent( 1, false, event); androidButtonB = true; } else { auto keyCode = Keys::getCode( evt.key.keysym.sym, evt.key.keysym.mod, evt.key.keysym.scancode, false); event = make_shared( keyCode, evt.key.keysym.unicode); triggerOsdControlEventsFromKeyEvent(keyCode, false, event); } break; case SDL_MOUSEBUTTONUP: event = make_shared(evt.button.button); break; case SDL_MOUSEBUTTONDOWN: event = make_shared(evt.button.button); break; case SDL_MOUSEMOTION: event = make_shared( evt.motion.xrel, evt.motion.yrel, evt.motion.x, evt.motion.y); break; case SDL_JOYBUTTONUP: event = make_shared( evt.jbutton.which, evt.jbutton.button); triggerOsdControlEventsFromJoystickButtonEvent( evt.jbutton.button, true, event); break; case SDL_JOYBUTTONDOWN: event = make_shared( evt.jbutton.which, evt.jbutton.button); triggerOsdControlEventsFromJoystickButtonEvent( evt.jbutton.button, false, event); break; case SDL_JOYAXISMOTION: { auto& setting = globalSettings.getJoyDeadzoneSetting(evt.jaxis.which); int threshold = (setting.getInt() * 32768) / 100; auto value = (evt.jaxis.value < -threshold) ? evt.jaxis.value : (evt.jaxis.value > threshold) ? evt.jaxis.value : 0; event = make_shared( evt.jaxis.which, evt.jaxis.axis, value); triggerOsdControlEventsFromJoystickAxisMotion( evt.jaxis.axis, value, event); break; } case SDL_JOYHATMOTION: event = make_shared( evt.jhat.which, evt.jhat.hat, evt.jhat.value); triggerOsdControlEventsFromJoystickHat(evt.jhat.value, event); break; case SDL_ACTIVEEVENT: event = make_shared(evt.active.gain != 0); break; case SDL_VIDEORESIZE: event = make_shared(evt.resize.w, evt.resize.h); break; case SDL_VIDEOEXPOSE: event = make_shared(OPENMSX_EXPOSE_EVENT); break; case SDL_QUIT: event = make_shared(); break; default: break; } #if 0 if (event) { std::cerr << "SDL event converted to: " + event->toString() << std::endl; } else { std::cerr << "SDL event was of unknown type, not converted to an openMSX event" << std::endl; } #endif if (event) eventDistributor.distributeEvent(event); } void InputEventGenerator::update(const Setting& setting) { assert(&setting == &grabInput); (void)setting; escapeGrabState = ESCAPE_GRAB_WAIT_CMD; setGrabInput(grabInput.getBoolean()); } int InputEventGenerator::signalEvent(const std::shared_ptr& event) { auto& focusEvent = checked_cast(*event); switch (escapeGrabState) { case ESCAPE_GRAB_WAIT_CMD: // nothing break; case ESCAPE_GRAB_WAIT_LOST: if (focusEvent.getGain() == false) { escapeGrabState = ESCAPE_GRAB_WAIT_GAIN; } break; case ESCAPE_GRAB_WAIT_GAIN: if (focusEvent.getGain() == true) { escapeGrabState = ESCAPE_GRAB_WAIT_CMD; } setGrabInput(true); break; default: UNREACHABLE; } return 0; } void InputEventGenerator::setGrabInput(bool grab) { // Note that this setting is also changed in VisibleSurface constructor // because for Mac we want to enable it in fullscreen. // It's not worth it to get that exactly right here, because here // we don't have easy access to renderer settings and it may only // go wrong if you explicitly change grab input at full screen (on Mac) SDL_WM_GrabInput(grab ? SDL_GRAB_ON : SDL_GRAB_OFF); } // Wrap SDL joystick button functions to handle the 'fake' android joystick // buttons. The method InputEventGenerator::handle() already takes care of fake // events for the andoid joystick buttons, these two wrappers handle the direct // joystick button state queries. int InputEventGenerator::joystickNumButtons(SDL_Joystick* joystick) { if (PLATFORM_ANDROID) { return 2; } else { return SDL_JoystickNumButtons(joystick); } } bool InputEventGenerator::joystickGetButton(SDL_Joystick* joystick, int button) { if (PLATFORM_ANDROID) { switch (button) { case 0: return androidButtonA; case 1: return androidButtonB; default: UNREACHABLE; return false; } } else { return SDL_JoystickGetButton(joystick, button) != 0; } } // class EscapeGrabCmd InputEventGenerator::EscapeGrabCmd::EscapeGrabCmd( CommandController& commandController) : Command(commandController, "escape_grab") { } void InputEventGenerator::EscapeGrabCmd::execute( array_ref /*tokens*/, TclObject& /*result*/) { auto& inputEventGenerator = OUTER(InputEventGenerator, escapeGrabCmd); if (inputEventGenerator.grabInput.getBoolean()) { inputEventGenerator.escapeGrabState = InputEventGenerator::ESCAPE_GRAB_WAIT_LOST; inputEventGenerator.setGrabInput(false); } } string InputEventGenerator::EscapeGrabCmd::help( const vector& /*tokens*/) const { return "Temporarily release input grab."; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/events/InputEventGenerator.hh000066400000000000000000000061021257557151200230220ustar00rootroot00000000000000#ifndef INPUTEVENTGENERATOR_HH #define INPUTEVENTGENERATOR_HH #include "Observer.hh" #include "BooleanSetting.hh" #include "EventListener.hh" #include "Command.hh" #include "noncopyable.hh" #include "Keys.hh" #include #include namespace openmsx { class CommandController; class EventDistributor; class GlobalSettings; class InputEventGenerator final : private Observer , private EventListener , private noncopyable { public: InputEventGenerator(CommandController& commandController, EventDistributor& eventDistributor, GlobalSettings& globalSettings); ~InputEventGenerator(); /** Wait for event(s) and handle it. * This method should be called from the main thread. */ void wait(); /** * Enable or disable keyboard event repeats */ void setKeyRepeat(bool enable); /** * This functions shouldn't be needed, but in the SDL library input * and video or closely coupled (sigh). For example when the video mode * is changed we need to reset the keyrepeat and unicode settings. */ void reinit(); /** Input Grab on or off */ BooleanSetting& getGrabInput() { return grabInput; } /** Normally the following two functions simply delegate to * SDL_JoystickNumButtons() and SDL_JoystickGetButton(). Except on * Android, see comments in .cc for more details. */ static int joystickNumButtons(SDL_Joystick* joystick); static bool joystickGetButton(SDL_Joystick* joystick, int button); void poll(); private: using EventPtr = std::shared_ptr; void handle(const SDL_Event& event); void setGrabInput(bool grab); // Observer void update(const Setting& setting) override; // EventListener int signalEvent(const std::shared_ptr& event) override; EventDistributor& eventDistributor; GlobalSettings& globalSettings; BooleanSetting grabInput; struct EscapeGrabCmd final : Command { EscapeGrabCmd(CommandController& commandController); void execute(array_ref tokens, TclObject& result) override; std::string help(const std::vector& tokens) const override; } escapeGrabCmd; enum EscapeGrabState { ESCAPE_GRAB_WAIT_CMD, ESCAPE_GRAB_WAIT_LOST, ESCAPE_GRAB_WAIT_GAIN } escapeGrabState; // OsdControl void setNewOsdControlButtonState( unsigned newState, const EventPtr& origEvent); void triggerOsdControlEventsFromJoystickAxisMotion( unsigned axis, short value, const EventPtr& origEvent); void triggerOsdControlEventsFromJoystickHat( int value, const EventPtr& origEvent); void osdControlChangeButton( bool up, unsigned changedButtonMask, const EventPtr& origEvent); void triggerOsdControlEventsFromJoystickButtonEvent( unsigned button, bool up, const EventPtr& origEvent); void triggerOsdControlEventsFromKeyEvent( Keys::KeyCode keyCode, bool up, const EventPtr& origEvent); bool keyRepeat; unsigned osdControlButtonsState; // 0 is pressed, 1 is released // only for Android static bool androidButtonA; static bool androidButtonB; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/events/InputEvents.cc000066400000000000000000000331411257557151200213270ustar00rootroot00000000000000#include "InputEvents.hh" #include "Keys.hh" #include "TclObject.hh" #include "StringOp.hh" #include "Timer.hh" #include "checked_cast.hh" #include #include #include using std::make_tuple; using std::string; using std::vector; namespace openmsx { // class TimedEvent TimedEvent::TimedEvent(EventType type) : Event(type) , realtime(Timer::getTime()) { } // class KeyEvent #if PLATFORM_ANDROID // The unicode support in the SDL Android port is currently broken. // It always sets the unicode value to 0 while on other platforms, // unicode is set to non-zero for character keys on keypress. // As a workaround, set unicode to keycode for character keys on keypress // until SDL Android port has been fixed. // Furthermore, try to set unicode value to correct character, taking into consideration // the modifier keys. The assumption is that Android has a qwerty keyboard, which is // true for the standard virtual keyboard of android 4.0, 4.1 and 4.2 and also true // for the more convenient "hackers keyboard" app. // However, some Android devices with a physical keyboard might have an Azerty keyboard // I don't know what the SDL layer does with the key events received from such Azerty // keyboard. Probably it won't work well with this work-around code. Must eventually fix // the unicode support in the SDL Android port, together with the main developer of the port. // Note that in an older version od SDL Android port, the unicode was always set to the // keycode (for all key presses and releases so even for non character keys) // Simulate this old broken behaviour and then "fix" the unicode static uint16_t fixUnicode(Keys::KeyCode keyCode, uint16_t brokenUnicode) { brokenUnicode = keyCode; Keys::KeyCode maskedKeyCode = (Keys::KeyCode)(int(brokenUnicode) & int(Keys::K_MASK)); if (brokenUnicode & Keys::KD_RELEASE) { return 0; } if (maskedKeyCode >= Keys::K_UP) { return 0; } if (maskedKeyCode >= Keys::K_WORLD_90 && maskedKeyCode <= Keys::K_WORLD_95) { return 0; } if ((keyCode & Keys::KM_SHIFT) == Keys::KM_SHIFT) { if (maskedKeyCode >= Keys::K_A && maskedKeyCode <= Keys::K_Z) { // Convert lowercase character into uppercase return brokenUnicode - 32; } // Convert several characters, assuming user has a qwerty keyboard on the Android or that Android has translated everything // to qwerty keyboard combinations before passing the events to the SDL layer. // Note that the 'rows' mentioned in below mapping table are based on the "hackers keyboard" app. Though // this mapping turns out to work fine with the standard Android 4.x keyboard app as well. switch (maskedKeyCode) { // row 1 case Keys::K_1: return uint16_t('!'); case Keys::K_2: return uint16_t('@'); case Keys::K_3: return uint16_t('#'); case Keys::K_4: return uint16_t('$'); case Keys::K_5: return uint16_t('%'); case Keys::K_6: return uint16_t('^'); case Keys::K_7: return uint16_t('&'); case Keys::K_8: return uint16_t('*'); case Keys::K_9: return uint16_t('('); case Keys::K_0: return uint16_t(')'); case Keys::K_MINUS: return uint16_t('_'); case Keys::K_EQUALS: return uint16_t('+'); // row 2 case Keys::K_LEFTBRACKET: return uint16_t('{'); case Keys::K_RIGHTBRACKET: return uint16_t('}'); case Keys::K_BACKSLASH: return uint16_t('|'); // row 3 case Keys::K_SEMICOLON: return uint16_t(':'); case Keys::K_QUOTE: return uint16_t('"'); // row 4 case Keys::K_COMMA: return uint16_t('<'); case Keys::K_PERIOD: return uint16_t('>'); case Keys::K_SLASH: return uint16_t('?'); } } return brokenUnicode; } KeyEvent::KeyEvent(EventType type, Keys::KeyCode keyCode_, uint16_t unicode_) : TimedEvent(type), keyCode(keyCode_), unicode(fixUnicode(keyCode_, unicode_)) { } #else KeyEvent::KeyEvent(EventType type, Keys::KeyCode keyCode_, uint16_t unicode_) : TimedEvent(type), keyCode(keyCode_), unicode(unicode_) { } #endif void KeyEvent::toStringImpl(TclObject& result) const { result.addListElement("keyb"); result.addListElement(Keys::getName(getKeyCode())); if (getUnicode() != 0) { result.addListElement(StringOp::Builder() << "unicode" << getUnicode()); } } bool KeyEvent::lessImpl(const Event& other) const { // note: don't compare unicode auto& o = checked_cast(other); return getKeyCode() < o.getKeyCode(); } // class KeyUpEvent KeyUpEvent::KeyUpEvent(Keys::KeyCode keyCode) : KeyEvent(OPENMSX_KEY_UP_EVENT, keyCode, uint16_t(0)) { } KeyUpEvent::KeyUpEvent(Keys::KeyCode keyCode, uint16_t unicode) : KeyEvent(OPENMSX_KEY_UP_EVENT, keyCode, unicode) { } // class KeyDownEvent KeyDownEvent::KeyDownEvent(Keys::KeyCode keyCode) : KeyEvent(OPENMSX_KEY_DOWN_EVENT, keyCode, uint16_t(0)) { } KeyDownEvent::KeyDownEvent(Keys::KeyCode keyCode, uint16_t unicode) : KeyEvent(OPENMSX_KEY_DOWN_EVENT, keyCode, unicode) { } // class MouseButtonEvent MouseButtonEvent::MouseButtonEvent(EventType type, unsigned button_) : TimedEvent(type), button(button_) { } void MouseButtonEvent::toStringHelper(TclObject& result) const { result.addListElement("mouse"); result.addListElement(StringOp::Builder() << "button" << getButton()); } bool MouseButtonEvent::lessImpl(const Event& other) const { auto& o = checked_cast(other); return getButton() < o.getButton(); } // class MouseButtonUpEvent MouseButtonUpEvent::MouseButtonUpEvent(unsigned button) : MouseButtonEvent(OPENMSX_MOUSE_BUTTON_UP_EVENT, button) { } void MouseButtonUpEvent::toStringImpl(TclObject& result) const { toStringHelper(result); result.addListElement("up"); } // class MouseButtonDownEvent MouseButtonDownEvent::MouseButtonDownEvent(unsigned button) : MouseButtonEvent(OPENMSX_MOUSE_BUTTON_DOWN_EVENT, button) { } void MouseButtonDownEvent::toStringImpl(TclObject& result) const { toStringHelper(result); result.addListElement("down"); } // class MouseMotionEvent MouseMotionEvent::MouseMotionEvent(int xrel_, int yrel_, int xabs_, int yabs_) : TimedEvent(OPENMSX_MOUSE_MOTION_EVENT) , xrel(xrel_), yrel(yrel_) , xabs(xabs_), yabs(yabs_) { } void MouseMotionEvent::toStringImpl(TclObject& result) const { result.addListElement("mouse"); result.addListElement("motion"); result.addListElement(getX()); result.addListElement(getY()); result.addListElement(getAbsX()); result.addListElement(getAbsY()); } bool MouseMotionEvent::lessImpl(const Event& other) const { auto& o = checked_cast(other); return make_tuple( getX(), getY(), getAbsX(), getAbsY()) < make_tuple(o.getX(), o.getY(), o.getAbsX(), o.getAbsY()); } // class MouseMotionGroupEvent MouseMotionGroupEvent::MouseMotionGroupEvent() : Event(OPENMSX_MOUSE_MOTION_GROUP_EVENT) { } void MouseMotionGroupEvent::toStringImpl(TclObject& result) const { result.addListElement("mouse"); result.addListElement("motion"); } bool MouseMotionGroupEvent::lessImpl(const Event& /*other*/) const { // All MouseMotionGroup events are equivalent return false; } bool MouseMotionGroupEvent::matches(const Event& other) const { return other.getType() == OPENMSX_MOUSE_MOTION_EVENT; } // class JoystickEvent JoystickEvent::JoystickEvent(EventType type, unsigned joystick_) : TimedEvent(type), joystick(joystick_) { } void JoystickEvent::toStringHelper(TclObject& result) const { result.addListElement(StringOp::Builder() << "joy" << getJoystick() + 1); } bool JoystickEvent::lessImpl(const Event& other) const { auto& o = checked_cast(other); return (getJoystick() != o.getJoystick()) ? (getJoystick() < o.getJoystick()) : lessImpl(o); } // class JoystickButtonEvent JoystickButtonEvent::JoystickButtonEvent( EventType type, unsigned joystick, unsigned button_) : JoystickEvent(type, joystick), button(button_) { } void JoystickButtonEvent::toStringHelper(TclObject& result) const { JoystickEvent::toStringHelper(result); result.addListElement(StringOp::Builder() << "button" << getButton()); } bool JoystickButtonEvent::lessImpl(const JoystickEvent& other) const { auto& o = checked_cast(other); return getButton() < o.getButton(); } // class JoystickButtonUpEvent JoystickButtonUpEvent::JoystickButtonUpEvent(unsigned joystick, unsigned button) : JoystickButtonEvent(OPENMSX_JOY_BUTTON_UP_EVENT, joystick, button) { } void JoystickButtonUpEvent::toStringImpl(TclObject& result) const { toStringHelper(result); result.addListElement("up"); } // class JoystickButtonDownEvent JoystickButtonDownEvent::JoystickButtonDownEvent(unsigned joystick, unsigned button) : JoystickButtonEvent(OPENMSX_JOY_BUTTON_DOWN_EVENT, joystick, button) { } void JoystickButtonDownEvent::toStringImpl(TclObject& result) const { toStringHelper(result); result.addListElement("down"); } // class JoystickAxisMotionEvent JoystickAxisMotionEvent::JoystickAxisMotionEvent( unsigned joystick, unsigned axis_, short value_) : JoystickEvent(OPENMSX_JOY_AXIS_MOTION_EVENT, joystick) , axis(axis_), value(value_) { } void JoystickAxisMotionEvent::toStringImpl(TclObject& result) const { toStringHelper(result); result.addListElement(StringOp::Builder() << "axis" << getAxis()); result.addListElement(getValue()); } bool JoystickAxisMotionEvent::lessImpl(const JoystickEvent& other) const { auto& o = checked_cast(other); return make_tuple( getAxis(), getValue()) < make_tuple(o.getAxis(), o.getValue()); } // class JoystickHatEvent JoystickHatEvent::JoystickHatEvent(unsigned joystick, unsigned hat_, unsigned value_) : JoystickEvent(OPENMSX_JOY_HAT_EVENT, joystick) , hat(hat_), value(value_) { } void JoystickHatEvent::toStringImpl(TclObject& result) const { toStringHelper(result); result.addListElement(StringOp::Builder() << "hat" << getHat()); const char* str; switch (getValue()) { case SDL_HAT_UP: str = "up"; break; case SDL_HAT_RIGHT: str = "right"; break; case SDL_HAT_DOWN: str = "down"; break; case SDL_HAT_LEFT: str = "left"; break; case SDL_HAT_RIGHTUP: str = "rightup"; break; case SDL_HAT_RIGHTDOWN: str = "rightdown"; break; case SDL_HAT_LEFTUP: str = "leftup"; break; case SDL_HAT_LEFTDOWN: str = "leftdown"; break; default: str = "center"; break; } result.addListElement(str); } bool JoystickHatEvent::lessImpl(const JoystickEvent& other) const { auto& o = checked_cast(other); return make_tuple( getHat(), getValue()) < make_tuple(o.getHat(), o.getValue()); } // class FocusEvent FocusEvent::FocusEvent(bool gain_) : Event(OPENMSX_FOCUS_EVENT), gain(gain_) { } void FocusEvent::toStringImpl(TclObject& result) const { result.addListElement("focus"); result.addListElement(getGain()); } bool FocusEvent::lessImpl(const Event& other) const { auto& o = checked_cast(other); return getGain() < o.getGain(); } // class ResizeEvent ResizeEvent::ResizeEvent(unsigned x_, unsigned y_) : Event(OPENMSX_RESIZE_EVENT), x(x_), y(y_) { } void ResizeEvent::toStringImpl(TclObject& result) const { result.addListElement("resize"); result.addListElement(int(getX())); result.addListElement(int(getY())); } bool ResizeEvent::lessImpl(const Event& other) const { auto& o = checked_cast(other); return make_tuple( getX(), getY()) < make_tuple(o.getX(), o.getY()); } // class QuitEvent QuitEvent::QuitEvent() : Event(OPENMSX_QUIT_EVENT) { } void QuitEvent::toStringImpl(TclObject& result) const { result.addListElement("quit"); } bool QuitEvent::lessImpl(const Event& /*other*/) const { return false; } // class OsdControlEvent OsdControlEvent::OsdControlEvent( EventType type, unsigned button_, const std::shared_ptr& origEvent_) : TimedEvent(type), origEvent(origEvent_), button(button_) { } bool OsdControlEvent::isRepeatStopper(const Event& other) const { // If this OsdControlEvent was geneated by the other event, then // repeat should not be stopped. if (origEvent.get() == &other) return false; // If this OsdControlEvent event was generated by a joystick motion // event and the new event is also a joystick motion event then don't // stop repeat. We don't need to check the actual values of the events // (it also isn't trivial), because when the values differ by enough, // a new OsdControlEvent will be generated and that one will stop // repeat. return !dynamic_cast(origEvent.get()) || !dynamic_cast(&other); } void OsdControlEvent::toStringHelper(TclObject& result) const { result.addListElement("OSDcontrol"); static const char* const names[] = { "LEFT", "RIGHT", "UP", "DOWN", "A", "B" }; result.addListElement(names[getButton()]); } bool OsdControlEvent::lessImpl(const Event& other) const { auto& o = checked_cast(other); return getButton() < o.getButton(); } // class OsdControlReleaseEvent OsdControlReleaseEvent::OsdControlReleaseEvent( unsigned button, const std::shared_ptr& origEvent) : OsdControlEvent(OPENMSX_OSD_CONTROL_RELEASE_EVENT, button, origEvent) { } void OsdControlReleaseEvent::toStringImpl(TclObject& result) const { toStringHelper(result); result.addListElement("RELEASE"); } // class OsdControlPressEvent OsdControlPressEvent::OsdControlPressEvent( unsigned button, const std::shared_ptr& origEvent) : OsdControlEvent(OPENMSX_OSD_CONTROL_PRESS_EVENT, button, origEvent) { } void OsdControlPressEvent::toStringImpl(TclObject& result) const { toStringHelper(result); result.addListElement("PRESS"); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/events/InputEvents.hh000066400000000000000000000160151257557151200213420ustar00rootroot00000000000000#ifndef INPUTEVENTS_HH #define INPUTEVENTS_HH #include "Event.hh" #include "Keys.hh" #include #include namespace openmsx { class TimedEvent : public Event { public: /** Query creation time. */ uint64_t getRealTime() const { return realtime; } protected: explicit TimedEvent(EventType type); private: const uint64_t realtime; }; class KeyEvent : public TimedEvent { public: Keys::KeyCode getKeyCode() const { return keyCode; } uint16_t getUnicode() const { return unicode; } protected: KeyEvent(EventType type, Keys::KeyCode keyCode, uint16_t unicode); private: void toStringImpl(TclObject& result) const override; bool lessImpl(const Event& other) const override; const Keys::KeyCode keyCode; const uint16_t unicode; }; class KeyUpEvent : public KeyEvent { public: explicit KeyUpEvent(Keys::KeyCode keyCode); KeyUpEvent(Keys::KeyCode keyCode, uint16_t unicode); }; class KeyDownEvent : public KeyEvent { public: explicit KeyDownEvent(Keys::KeyCode keyCode); KeyDownEvent(Keys::KeyCode keyCode, uint16_t unicode); }; class MouseButtonEvent : public TimedEvent { public: static const unsigned LEFT = 1; static const unsigned MIDDLE = 2; static const unsigned RIGHT = 3; static const unsigned WHEELUP = 4; static const unsigned WHEELDOWN = 5; unsigned getButton() const { return button; } protected: MouseButtonEvent(EventType type, unsigned button_); void toStringHelper(TclObject& result) const; private: bool lessImpl(const Event& other) const override; const unsigned button; }; class MouseButtonUpEvent : public MouseButtonEvent { public: explicit MouseButtonUpEvent(unsigned button); private: void toStringImpl(TclObject& result) const override; }; class MouseButtonDownEvent : public MouseButtonEvent { public: explicit MouseButtonDownEvent(unsigned button); private: void toStringImpl(TclObject& result) const override; }; class MouseMotionEvent : public TimedEvent { public: MouseMotionEvent(int xrel, int yrel, int xabs, int yabs); int getX() const { return xrel; } int getY() const { return yrel; } int getAbsX() const { return xabs; } int getAbsY() const { return yabs; } private: void toStringImpl(TclObject& result) const override; bool lessImpl(const Event& other) const override; const int xrel; const int yrel; const int xabs; const int yabs; }; class MouseMotionGroupEvent : public Event { public: MouseMotionGroupEvent(); private: void toStringImpl(TclObject& result) const override; bool lessImpl(const Event& other) const override; bool matches(const Event& other) const override; }; class JoystickEvent : public TimedEvent { public: unsigned getJoystick() const { return joystick; } protected: JoystickEvent(EventType type, unsigned joystick); void toStringHelper(TclObject& result) const; private: bool lessImpl(const Event& other) const override; virtual bool lessImpl(const JoystickEvent& other) const = 0; const unsigned joystick; }; class JoystickButtonEvent : public JoystickEvent { public: unsigned getButton() const { return button; } protected: JoystickButtonEvent(EventType type, unsigned joystick, unsigned button); void toStringHelper(TclObject& result) const; private: bool lessImpl(const JoystickEvent& other) const override; const unsigned button; }; class JoystickButtonUpEvent : public JoystickButtonEvent { public: JoystickButtonUpEvent(unsigned joystick, unsigned button); private: void toStringImpl(TclObject& result) const override; }; class JoystickButtonDownEvent : public JoystickButtonEvent { public: JoystickButtonDownEvent(unsigned joystick, unsigned button); private: void toStringImpl(TclObject& result) const override; }; class JoystickAxisMotionEvent : public JoystickEvent { public: static const unsigned X_AXIS = 0; static const unsigned Y_AXIS = 1; JoystickAxisMotionEvent(unsigned joystick, unsigned axis, short value); unsigned getAxis() const { return axis; } short getValue() const { return value; } private: void toStringImpl(TclObject& result) const override; bool lessImpl(const JoystickEvent& other) const override; const unsigned axis; const short value; }; class JoystickHatEvent : public JoystickEvent { public: JoystickHatEvent(unsigned joystick, unsigned hat, unsigned value); unsigned getHat() const { return hat; } unsigned getValue() const { return value; } private: void toStringImpl(TclObject& result) const override; bool lessImpl(const JoystickEvent& other) const override; const unsigned hat; const unsigned value; }; class FocusEvent : public Event { public: explicit FocusEvent(bool gain); bool getGain() const { return gain; } private: void toStringImpl(TclObject& result) const override; bool lessImpl(const Event& other) const override; const bool gain; }; class ResizeEvent : public Event { public: ResizeEvent(unsigned x, unsigned y); unsigned getX() const { return x; } unsigned getY() const { return y; } private: void toStringImpl(TclObject& result) const override; bool lessImpl(const Event& other) const override; const unsigned x; const unsigned y; }; class QuitEvent : public Event { public: QuitEvent(); private: void toStringImpl(TclObject& result) const override; bool lessImpl(const Event& other) const override; }; /** OSD events are triggered by other events. They aggregate keyboard and * joystick events into one set of events that can be used to e.g. control * OSD elements. */ class OsdControlEvent : public TimedEvent { public: enum { LEFT_BUTTON, RIGHT_BUTTON, UP_BUTTON, DOWN_BUTTON, A_BUTTON, B_BUTTON }; unsigned getButton() const { return button; } /** Get the event that actually triggered the creation of this event. * Typically this will be a keyboard or joystick event. This could * also return nullptr (after a toString/fromString conversion). * For the current use (key-repeat) this is ok. */ /** Normally all events should stop the repeat process in 'bind -repeat', * but in case of OsdControlEvent there are two exceptions: * - we should not stop because of the original host event that * actually generated this 'artificial' OsdControlEvent. * - if the original host event is a joystick motion event, we * should not stop repeat for 'small' relative new joystick events. */ bool isRepeatStopper(const Event& other) const override; protected: OsdControlEvent(EventType type, unsigned button_, const std::shared_ptr& origEvent); void toStringHelper(TclObject& result) const; private: bool lessImpl(const Event& other) const override; const std::shared_ptr origEvent; const unsigned button; }; class OsdControlReleaseEvent : public OsdControlEvent { public: OsdControlReleaseEvent(unsigned button, const std::shared_ptr& origEvent); private: void toStringImpl(TclObject& result) const override; }; class OsdControlPressEvent : public OsdControlEvent { public: OsdControlPressEvent(unsigned button, const std::shared_ptr& origEvent); private: void toStringImpl(TclObject& result) const override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/events/Keys.cc000066400000000000000000000232351257557151200177610ustar00rootroot00000000000000#include "Keys.hh" #include "StringOp.hh" #include "stl.hh" #include #include #include using std::string; namespace openmsx { namespace Keys { static std::vector> keys; using CmpKeys = CmpTupleElement<0, StringOp::caseless>; static void initialize() { static bool init = false; if (init) return; init = true; keys = { { "BACKSPACE", K_BACKSPACE }, { "TAB", K_TAB }, { "CLEAR", K_CLEAR }, { "RETURN", K_RETURN }, { "PAUSE", K_PAUSE }, { "ESCAPE", K_ESCAPE }, { "SPACE", K_SPACE }, { "EXCLAIM", K_EXCLAIM }, { "QUOTEDBL", K_QUOTEDBL }, { "HASH", K_HASH }, { "DOLLAR", K_DOLLAR }, { "AMPERSAND", K_AMPERSAND }, { "QUOTE", K_QUOTE }, { "LEFTPAREN", K_LEFTPAREN }, { "RIGHTPAREN", K_RIGHTPAREN }, { "ASTERISK", K_ASTERISK }, { "PLUS", K_PLUS }, { "COMMA", K_COMMA }, { "MINUS", K_MINUS }, { "PERIOD", K_PERIOD }, { "SLASH", K_SLASH }, { "0", K_0 }, { "1", K_1 }, { "2", K_2 }, { "3", K_3 }, { "4", K_4 }, { "5", K_5 }, { "6", K_6 }, { "7", K_7 }, { "8", K_8 }, { "9", K_9 }, { "COLON", K_COLON }, { "SEMICOLON", K_SEMICOLON }, { "LESS", K_LESS }, { "EQUALS", K_EQUALS }, { "GREATER", K_GREATER }, { "QUESTION", K_QUESTION }, { "AT", K_AT }, { "LEFTBRACKET",K_LEFTBRACKET }, { "BACKSLASH", K_BACKSLASH }, { "RIGHTBRACKET",K_RIGHTBRACKET }, { "CARET", K_CARET }, { "UNDERSCORE", K_UNDERSCORE }, { "BACKQUOTE", K_BACKQUOTE }, { "A", K_A }, { "B", K_B }, { "C", K_C }, { "D", K_D }, { "E", K_E }, { "F", K_F }, { "G", K_G }, { "H", K_H }, { "I", K_I }, { "J", K_J }, { "K", K_K }, { "L", K_L }, { "M", K_M }, { "N", K_N }, { "O", K_O }, { "P", K_P }, { "Q", K_Q }, { "R", K_R }, { "S", K_S }, { "T", K_T }, { "U", K_U }, { "V", K_V }, { "W", K_W }, { "X", K_X }, { "Y", K_Y }, { "Z", K_Z }, { "DELETE", K_DELETE }, { "WORLD_0", K_WORLD_0 }, { "WORLD_1", K_WORLD_1 }, { "WORLD_2", K_WORLD_2 }, { "WORLD_3", K_WORLD_3 }, { "WORLD_4", K_WORLD_4 }, { "WORLD_5", K_WORLD_5 }, { "WORLD_6", K_WORLD_6 }, { "WORLD_7", K_WORLD_7 }, { "WORLD_8", K_WORLD_8 }, { "WORLD_9", K_WORLD_9 }, { "WORLD_10", K_WORLD_10 }, { "WORLD_11", K_WORLD_11 }, { "WORLD_12", K_WORLD_12 }, { "WORLD_13", K_WORLD_13 }, { "WORLD_14", K_WORLD_14 }, { "WORLD_15", K_WORLD_15 }, { "WORLD_16", K_WORLD_16 }, { "WORLD_17", K_WORLD_17 }, { "WORLD_18", K_WORLD_18 }, { "WORLD_19", K_WORLD_19 }, { "WORLD_20", K_WORLD_20 }, { "WORLD_21", K_WORLD_21 }, { "WORLD_22", K_WORLD_22 }, { "WORLD_23", K_WORLD_23 }, { "WORLD_24", K_WORLD_24 }, { "WORLD_25", K_WORLD_25 }, { "WORLD_26", K_WORLD_26 }, { "WORLD_27", K_WORLD_27 }, { "WORLD_28", K_WORLD_28 }, { "WORLD_29", K_WORLD_29 }, { "WORLD_30", K_WORLD_30 }, { "WORLD_31", K_WORLD_31 }, { "WORLD_32", K_WORLD_32 }, { "WORLD_33", K_WORLD_33 }, { "WORLD_34", K_WORLD_34 }, { "WORLD_35", K_WORLD_35 }, { "WORLD_36", K_WORLD_36 }, { "WORLD_37", K_WORLD_37 }, { "WORLD_38", K_WORLD_38 }, { "WORLD_39", K_WORLD_39 }, { "WORLD_40", K_WORLD_40 }, { "WORLD_41", K_WORLD_41 }, { "WORLD_42", K_WORLD_42 }, { "WORLD_43", K_WORLD_43 }, { "WORLD_44", K_WORLD_44 }, { "WORLD_45", K_WORLD_45 }, { "WORLD_46", K_WORLD_46 }, { "WORLD_47", K_WORLD_47 }, { "WORLD_48", K_WORLD_48 }, { "WORLD_49", K_WORLD_49 }, { "WORLD_50", K_WORLD_50 }, { "WORLD_51", K_WORLD_51 }, { "WORLD_52", K_WORLD_52 }, { "WORLD_53", K_WORLD_53 }, { "WORLD_54", K_WORLD_54 }, { "WORLD_55", K_WORLD_55 }, { "WORLD_56", K_WORLD_56 }, { "WORLD_57", K_WORLD_57 }, { "WORLD_58", K_WORLD_58 }, { "WORLD_59", K_WORLD_59 }, { "WORLD_60", K_WORLD_60 }, { "WORLD_61", K_WORLD_61 }, { "WORLD_62", K_WORLD_62 }, { "WORLD_63", K_WORLD_63 }, { "WORLD_64", K_WORLD_64 }, { "WORLD_65", K_WORLD_65 }, { "WORLD_66", K_WORLD_66 }, { "WORLD_67", K_WORLD_67 }, { "WORLD_68", K_WORLD_68 }, { "WORLD_69", K_WORLD_69 }, { "WORLD_70", K_WORLD_70 }, { "WORLD_71", K_WORLD_71 }, { "WORLD_72", K_WORLD_72 }, { "WORLD_73", K_WORLD_73 }, { "WORLD_74", K_WORLD_74 }, { "WORLD_75", K_WORLD_75 }, { "WORLD_76", K_WORLD_76 }, { "WORLD_77", K_WORLD_77 }, { "WORLD_78", K_WORLD_78 }, { "WORLD_79", K_WORLD_79 }, { "WORLD_80", K_WORLD_80 }, { "WORLD_81", K_WORLD_81 }, { "WORLD_82", K_WORLD_82 }, { "WORLD_83", K_WORLD_83 }, { "WORLD_84", K_WORLD_84 }, { "WORLD_85", K_WORLD_85 }, { "WORLD_86", K_WORLD_86 }, { "WORLD_87", K_WORLD_87 }, { "WORLD_88", K_WORLD_88 }, { "WORLD_89", K_WORLD_89 }, { "WORLD_90", K_WORLD_90 }, { "WORLD_91", K_WORLD_91 }, { "WORLD_92", K_WORLD_92 }, { "WORLD_93", K_WORLD_93 }, { "WORLD_94", K_WORLD_94 }, { "WORLD_95", K_WORLD_95 }, // Numeric keypad { "KP0", K_KP0 }, { "KP1", K_KP1 }, { "KP2", K_KP2 }, { "KP3", K_KP3 }, { "KP4", K_KP4 }, { "KP5", K_KP5 }, { "KP6", K_KP6 }, { "KP7", K_KP7 }, { "KP8", K_KP8 }, { "KP9", K_KP9 }, { "KP_PERIOD", K_KP_PERIOD }, { "KP_DIVIDE", K_KP_DIVIDE }, { "KP_MULTIPLY",K_KP_MULTIPLY }, { "KP_MINUS", K_KP_MINUS }, { "KP_PLUS", K_KP_PLUS }, { "KP_ENTER", K_KP_ENTER }, { "KP_EQUALS", K_KP_EQUALS }, // Arrows + Home/End pad { "UP", K_UP }, { "DOWN", K_DOWN }, { "RIGHT", K_RIGHT }, { "LEFT", K_LEFT }, { "INSERT", K_INSERT }, { "HOME", K_HOME }, { "END", K_END }, { "PAGEUP", K_PAGEUP }, { "PAGEDOWN", K_PAGEDOWN }, // Function keys { "F1", K_F1 }, { "F2", K_F2 }, { "F3", K_F3 }, { "F4", K_F4 }, { "F5", K_F5 }, { "F6", K_F6 }, { "F7", K_F7 }, { "F8", K_F8 }, { "F9", K_F9 }, { "F10", K_F10 }, { "F11", K_F11 }, { "F12", K_F12 }, { "F13", K_F13 }, { "F14", K_F14 }, { "F15", K_F15 }, // Key state modifier keys { "NUMLOCK", K_NUMLOCK }, { "CAPSLOCK", K_CAPSLOCK }, { "SCROLLOCK", K_SCROLLOCK }, { "RSHIFT", K_RSHIFT }, { "LSHIFT", K_LSHIFT }, { "RCTRL", K_RCTRL }, { "LCTRL", K_LCTRL }, { "RALT", K_RALT }, { "LALT", K_LALT }, { "RMETA", K_RMETA }, { "LMETA", K_LMETA }, { "LSUPER", K_LSUPER }, // Left "Windows" key { "RSUPER", K_RSUPER }, // Right "Windows" key { "RMODE", K_MODE }, // "Alt Gr" key { "COMPOSE", K_COMPOSE }, // Multi-key compose key // Miscellaneous function keys { "HELP", K_HELP }, { "PRINT", K_PRINT }, { "SYSREQ", K_SYSREQ }, { "BREAK", K_BREAK }, { "MENU", K_MENU }, { "POWER", K_POWER }, // Power Macintosh power key { "EURO", K_EURO }, // Some european keyboards { "UNDO", K_UNDO }, // Japanese keyboard special keys { "ZENKAKU_HENKAKU", K_ZENKAKU_HENKAKU }, { "MUHENKAN", K_MUHENKAN }, { "HENKAN_MODE", K_HENKAN_MODE }, { "HIRAGANA_KATAKANA", K_HIRAGANA_KATAKANA }, // Modifiers { "SHIFT", KM_SHIFT }, { "CTRL", KM_CTRL }, { "ALT", KM_ALT }, { "META", KM_META }, { "MODE", KM_MODE }, // Direction modifiers { "PRESS", KD_PRESS }, { "RELEASE", KD_RELEASE }, }; sort(begin(keys), end(keys), CmpKeys()); } KeyCode getCode(string_ref name) { initialize(); auto result = static_cast(0); string_ref::size_type lastPos = 0; while (lastPos != string_ref::npos) { auto pos = name.substr(lastPos).find_first_of(",+/"); auto part = (pos != string_ref::npos) ? name.substr(lastPos, pos) : name.substr(lastPos); auto it = lower_bound(begin(keys), end(keys), part, CmpKeys()); StringOp::casecmp cmp; if ((it == end(keys)) || !cmp(it->first, part)) { return K_NONE; } KeyCode partCode = it->second; if ((partCode & K_MASK) && (result & K_MASK)) { // more than one non-modifier component // is not allowed return K_NONE; } result = static_cast(result | partCode); lastPos = (pos != string_ref::npos) ? lastPos + pos + 1 : string_ref::npos; } return result; } KeyCode getCode(SDLKey key, SDLMod mod, Uint8 scancode, bool release) { auto result = static_cast(key); if (result == 0) { // Assume it is a Japanese keyboard and check // scancode to recognize a few japanese // specific keys for which SDL does not have an // SDLKey keysym definition. switch (scancode) { case 49: result = static_cast(K_ZENKAKU_HENKAKU); break; case 129: result = static_cast(K_HENKAN_MODE); break; case 131: result = static_cast(K_MUHENKAN); break; case 208: result = static_cast(K_HIRAGANA_KATAKANA); break; } } if (result == 0) { // Assume it is a Korean keyboard and check // for scancode 56; on Korean keyboard it is used // for R-ALT key but SDL does not seem to recognize it, // as reported by Miso Kim switch (scancode) { case 56: result = static_cast(K_RALT); break; } } if (mod & KMOD_CTRL) { result = static_cast(result | KM_CTRL); } if (mod & KMOD_SHIFT) { result = static_cast(result | KM_SHIFT); } if (mod & KMOD_ALT) { result = static_cast(result | KM_ALT); } if (mod & KMOD_META) { result = static_cast(result | KM_META); } if (mod & KMOD_MODE) { result = static_cast(result | KM_MODE); } if (release) { result = static_cast(result | KD_RELEASE); } return result; } const string getName(KeyCode keyCode) { initialize(); string result; for (auto& p : keys) { if (p.second == (keyCode & K_MASK)) { result = p.first.str(); break; } } if (result.empty()) { return "unknown"; } if (keyCode & KM_CTRL) { result += "+CTRL"; } if (keyCode & KM_SHIFT) { result += "+SHIFT"; } if (keyCode & KM_ALT) { result += "+ALT"; } if (keyCode & KM_META) { result += "+META"; } if (keyCode & KM_MODE) { result += "+MODE"; } if (keyCode & KD_RELEASE) { result += ",RELEASE"; } return result; } } // namespace Keys } // namespace openmsx openMSX-RELEASE_0_12_0/src/events/Keys.hh000066400000000000000000000202701257557151200177670ustar00rootroot00000000000000#ifndef KEYS_HH #define KEYS_HH #include "string_ref.hh" #include #include // TODO #include namespace openmsx { namespace Keys { /** * Constants that identify keys and key modifiers. * * There are two special key codes: * - K_NONE : returned when we do string -> key code * translation and there is no key with given * name. Most likely the keyname was misspelled. * - K_UNKNOWN : this code is returned when a real key was * pressed, but we have no idea which key it is. * Should only happen when the user has some * exotic keyboard. Note that it might be possible * that there are multiple keys that produce this * code. */ enum KeyCode { K_NONE = -1, K_UNKNOWN = SDLK_UNKNOWN, K_BACKSPACE = SDLK_BACKSPACE, K_TAB = SDLK_TAB, K_CLEAR = SDLK_CLEAR, K_RETURN = SDLK_RETURN, K_PAUSE = SDLK_PAUSE, K_ESCAPE = SDLK_ESCAPE, K_SPACE = SDLK_SPACE, K_EXCLAIM = SDLK_EXCLAIM, K_QUOTEDBL = SDLK_QUOTEDBL, K_HASH = SDLK_HASH, K_DOLLAR = SDLK_DOLLAR, K_AMPERSAND = SDLK_AMPERSAND, K_QUOTE = SDLK_QUOTE, K_LEFTPAREN = SDLK_LEFTPAREN, K_RIGHTPAREN = SDLK_RIGHTPAREN, K_ASTERISK = SDLK_ASTERISK, K_PLUS = SDLK_PLUS, K_COMMA = SDLK_COMMA, K_MINUS = SDLK_MINUS, K_PERIOD = SDLK_PERIOD, K_SLASH = SDLK_SLASH, K_0 = SDLK_0, K_1 = SDLK_1, K_2 = SDLK_2, K_3 = SDLK_3, K_4 = SDLK_4, K_5 = SDLK_5, K_6 = SDLK_6, K_7 = SDLK_7, K_8 = SDLK_8, K_9 = SDLK_9, K_COLON = SDLK_COLON, K_SEMICOLON = SDLK_SEMICOLON, K_LESS = SDLK_LESS, K_EQUALS = SDLK_EQUALS, K_GREATER = SDLK_GREATER, K_QUESTION = SDLK_QUESTION, K_AT = SDLK_AT, K_LEFTBRACKET = SDLK_LEFTBRACKET, K_BACKSLASH = SDLK_BACKSLASH, K_RIGHTBRACKET = SDLK_RIGHTBRACKET, K_CARET = SDLK_CARET, K_UNDERSCORE = SDLK_UNDERSCORE, K_BACKQUOTE = SDLK_BACKQUOTE, K_A = SDLK_a, K_B = SDLK_b, K_C = SDLK_c, K_D = SDLK_d, K_E = SDLK_e, K_F = SDLK_f, K_G = SDLK_g, K_H = SDLK_h, K_I = SDLK_i, K_J = SDLK_j, K_K = SDLK_k, K_L = SDLK_l, K_M = SDLK_m, K_N = SDLK_n, K_O = SDLK_o, K_P = SDLK_p, K_Q = SDLK_q, K_R = SDLK_r, K_S = SDLK_s, K_T = SDLK_t, K_U = SDLK_u, K_V = SDLK_v, K_W = SDLK_w, K_X = SDLK_x, K_Y = SDLK_y, K_Z = SDLK_z, K_DELETE = SDLK_DELETE, K_WORLD_0 = SDLK_WORLD_0, K_WORLD_1 = SDLK_WORLD_1, K_WORLD_2 = SDLK_WORLD_2, K_WORLD_3 = SDLK_WORLD_3, K_WORLD_4 = SDLK_WORLD_4, K_WORLD_5 = SDLK_WORLD_5, K_WORLD_6 = SDLK_WORLD_6, K_WORLD_7 = SDLK_WORLD_7, K_WORLD_8 = SDLK_WORLD_8, K_WORLD_9 = SDLK_WORLD_9, K_WORLD_10 = SDLK_WORLD_10, K_WORLD_11 = SDLK_WORLD_11, K_WORLD_12 = SDLK_WORLD_12, K_WORLD_13 = SDLK_WORLD_13, K_WORLD_14 = SDLK_WORLD_14, K_WORLD_15 = SDLK_WORLD_15, K_WORLD_16 = SDLK_WORLD_16, K_WORLD_17 = SDLK_WORLD_17, K_WORLD_18 = SDLK_WORLD_18, K_WORLD_19 = SDLK_WORLD_19, K_WORLD_20 = SDLK_WORLD_20, K_WORLD_21 = SDLK_WORLD_21, K_WORLD_22 = SDLK_WORLD_22, K_WORLD_23 = SDLK_WORLD_23, K_WORLD_24 = SDLK_WORLD_24, K_WORLD_25 = SDLK_WORLD_25, K_WORLD_26 = SDLK_WORLD_26, K_WORLD_27 = SDLK_WORLD_27, K_WORLD_28 = SDLK_WORLD_28, K_WORLD_29 = SDLK_WORLD_29, K_WORLD_30 = SDLK_WORLD_30, K_WORLD_31 = SDLK_WORLD_31, K_WORLD_32 = SDLK_WORLD_32, K_WORLD_33 = SDLK_WORLD_33, K_WORLD_34 = SDLK_WORLD_34, K_WORLD_35 = SDLK_WORLD_35, K_WORLD_36 = SDLK_WORLD_36, K_WORLD_37 = SDLK_WORLD_37, K_WORLD_38 = SDLK_WORLD_38, K_WORLD_39 = SDLK_WORLD_39, K_WORLD_40 = SDLK_WORLD_40, K_WORLD_41 = SDLK_WORLD_41, K_WORLD_42 = SDLK_WORLD_42, K_WORLD_43 = SDLK_WORLD_43, K_WORLD_44 = SDLK_WORLD_44, K_WORLD_45 = SDLK_WORLD_45, K_WORLD_46 = SDLK_WORLD_46, K_WORLD_47 = SDLK_WORLD_47, K_WORLD_48 = SDLK_WORLD_48, K_WORLD_49 = SDLK_WORLD_49, K_WORLD_50 = SDLK_WORLD_50, K_WORLD_51 = SDLK_WORLD_51, K_WORLD_52 = SDLK_WORLD_52, K_WORLD_53 = SDLK_WORLD_53, K_WORLD_54 = SDLK_WORLD_54, K_WORLD_55 = SDLK_WORLD_55, K_WORLD_56 = SDLK_WORLD_56, K_WORLD_57 = SDLK_WORLD_57, K_WORLD_58 = SDLK_WORLD_58, K_WORLD_59 = SDLK_WORLD_59, K_WORLD_60 = SDLK_WORLD_60, K_WORLD_61 = SDLK_WORLD_61, K_WORLD_62 = SDLK_WORLD_62, K_WORLD_63 = SDLK_WORLD_63, K_WORLD_64 = SDLK_WORLD_64, K_WORLD_65 = SDLK_WORLD_65, K_WORLD_66 = SDLK_WORLD_66, K_WORLD_67 = SDLK_WORLD_67, K_WORLD_68 = SDLK_WORLD_68, K_WORLD_69 = SDLK_WORLD_69, K_WORLD_70 = SDLK_WORLD_70, K_WORLD_71 = SDLK_WORLD_71, K_WORLD_72 = SDLK_WORLD_72, K_WORLD_73 = SDLK_WORLD_73, K_WORLD_74 = SDLK_WORLD_74, K_WORLD_75 = SDLK_WORLD_75, K_WORLD_76 = SDLK_WORLD_76, K_WORLD_77 = SDLK_WORLD_77, K_WORLD_78 = SDLK_WORLD_78, K_WORLD_79 = SDLK_WORLD_79, K_WORLD_80 = SDLK_WORLD_80, K_WORLD_81 = SDLK_WORLD_81, K_WORLD_82 = SDLK_WORLD_82, K_WORLD_83 = SDLK_WORLD_83, K_WORLD_84 = SDLK_WORLD_84, K_WORLD_85 = SDLK_WORLD_85, K_WORLD_86 = SDLK_WORLD_86, K_WORLD_87 = SDLK_WORLD_87, K_WORLD_88 = SDLK_WORLD_88, K_WORLD_89 = SDLK_WORLD_89, K_WORLD_90 = SDLK_WORLD_90, K_WORLD_91 = SDLK_WORLD_91, K_WORLD_92 = SDLK_WORLD_92, K_WORLD_93 = SDLK_WORLD_93, K_WORLD_94 = SDLK_WORLD_94, K_WORLD_95 = SDLK_WORLD_95, // Numeric keypad K_KP0 = SDLK_KP0, K_KP1 = SDLK_KP1, K_KP2 = SDLK_KP2, K_KP3 = SDLK_KP3, K_KP4 = SDLK_KP4, K_KP5 = SDLK_KP5, K_KP6 = SDLK_KP6, K_KP7 = SDLK_KP7, K_KP8 = SDLK_KP8, K_KP9 = SDLK_KP9, K_KP_PERIOD = SDLK_KP_PERIOD, K_KP_DIVIDE = SDLK_KP_DIVIDE, K_KP_MULTIPLY = SDLK_KP_MULTIPLY, K_KP_MINUS = SDLK_KP_MINUS, K_KP_PLUS = SDLK_KP_PLUS, K_KP_ENTER = SDLK_KP_ENTER, K_KP_EQUALS = SDLK_KP_EQUALS, // Arrows + Home/End pad K_UP = SDLK_UP, K_DOWN = SDLK_DOWN, K_RIGHT = SDLK_RIGHT, K_LEFT = SDLK_LEFT, K_INSERT = SDLK_INSERT, K_HOME = SDLK_HOME, K_END = SDLK_END, K_PAGEUP = SDLK_PAGEUP, K_PAGEDOWN = SDLK_PAGEDOWN, // Function keys K_F1 = SDLK_F1, K_F2 = SDLK_F2, K_F3 = SDLK_F3, K_F4 = SDLK_F4, K_F5 = SDLK_F5, K_F6 = SDLK_F6, K_F7 = SDLK_F7, K_F8 = SDLK_F8, K_F9 = SDLK_F9, K_F10 = SDLK_F10, K_F11 = SDLK_F11, K_F12 = SDLK_F12, K_F13 = SDLK_F13, K_F14 = SDLK_F14, K_F15 = SDLK_F15, // Key state modifier keys K_NUMLOCK = SDLK_NUMLOCK, K_CAPSLOCK = SDLK_CAPSLOCK, K_SCROLLOCK = SDLK_SCROLLOCK, K_RSHIFT = SDLK_RSHIFT, K_LSHIFT = SDLK_LSHIFT, K_RCTRL = SDLK_RCTRL, K_LCTRL = SDLK_LCTRL, K_RALT = SDLK_RALT, K_LALT = SDLK_LALT, K_RMETA = SDLK_RMETA, K_LMETA = SDLK_LMETA, K_LSUPER = SDLK_LSUPER, // Left "Windows" key K_RSUPER = SDLK_RSUPER, // Right "Windows" key K_MODE = SDLK_MODE, // "Alt Gr" key K_COMPOSE = SDLK_COMPOSE, // Multi-key compose key // Miscellaneous function keys K_HELP = SDLK_HELP, K_PRINT = SDLK_PRINT, K_SYSREQ = SDLK_SYSREQ, K_BREAK = SDLK_BREAK, K_MENU = SDLK_MENU, K_POWER = SDLK_POWER, // Power Macintosh power key K_EURO = SDLK_EURO, // Some european keyboards K_UNDO = SDLK_UNDO, // Some japanese keyboard keys are unknown to SDL. // That is; they are all mapped to SDLKey=0 // However, they can recognized on their scancode // These keys are usefull for Japanese users who want to map // their host keyboard to the Japanese MSX keyboard // (e.g. the MSX turbo R keyboard) // Define some codes above suspected SDLKey value range, to // avoid clash with SDLKey values K_ZENKAKU_HENKAKU = 0x10000, // Enables EMI mode (MSX does this with CTRL+SPACE) K_MUHENKAN = 0x10001, // ??? K_HENKAN_MODE = 0x10002, // Similar to kanalock on MSX K_HIRAGANA_KATAKANA = 0x10003, // MSX switches between the two sets based on capslock state K_MASK = 0x1FFFF, // Modifiers KM_SHIFT = 0x020000, KM_CTRL = 0x040000, KM_ALT = 0x080000, KM_META = 0x100000, KM_MODE = 0x200000, // Direction modifiers KD_PRESS = 0, // key press KD_RELEASE = 0x400000 // key release }; /** * Translate key name to key code. * Returns K_NONE when the name is unknown. */ KeyCode getCode(string_ref name); KeyCode getCode(SDLKey key, SDLMod mod = KMOD_NONE, Uint8 scancode = 0, bool release = false); /** * Translate key code to key name. * Returns the string "unknown" for unknown key codes. */ const std::string getName(KeyCode keyCode); /** * Convenience method to create key combinations (hides ugly casts). */ inline KeyCode combine(KeyCode key, KeyCode modifier) { return static_cast(int(key) | int(modifier)); } } // namespace Keys } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/events/MSXCliComm.cc000066400000000000000000000013731257557151200207600ustar00rootroot00000000000000#include "MSXCliComm.hh" #include "GlobalCliComm.hh" #include "MSXMotherBoard.hh" namespace openmsx { MSXCliComm::MSXCliComm(MSXMotherBoard& motherBoard_, GlobalCliComm& cliComm_) : motherBoard(motherBoard_) , cliComm(cliComm_) { } void MSXCliComm::log(LogLevel level, string_ref message) { cliComm.log(level, message); } void MSXCliComm::update(UpdateType type, string_ref name, string_ref value) { assert(type < NUM_UPDATES); auto it = prevValues[type].find(name); if (it != end(prevValues[type])) { if (it->second == value) { return; } it->second = value.str(); } else { prevValues[type].emplace_noDuplicateCheck(name.str(), value.str()); } cliComm.updateHelper(type, motherBoard.getMachineID(), name, value); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/events/MSXCliComm.hh000066400000000000000000000012201257557151200207610ustar00rootroot00000000000000#ifndef MSXCLICOMM_HH #define MSXCLICOMM_HH #include "CliComm.hh" #include "hash_map.hh" #include "noncopyable.hh" #include "xxhash.hh" namespace openmsx { class MSXMotherBoard; class GlobalCliComm; class MSXCliComm final : public CliComm, private noncopyable { public: MSXCliComm(MSXMotherBoard& motherBoard, GlobalCliComm& cliComm); void log(LogLevel level, string_ref message) override; void update(UpdateType type, string_ref name, string_ref value) override; private: MSXMotherBoard& motherBoard; GlobalCliComm& cliComm; hash_map prevValues[NUM_UPDATES]; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/events/MessageCommand.cc000066400000000000000000000026241257557151200217300ustar00rootroot00000000000000#include "MessageCommand.hh" #include "CommandException.hh" #include "CliComm.hh" #include "TclObject.hh" #include "xrange.hh" namespace openmsx { MessageCommand::MessageCommand(CommandController& controller) : Command(controller, "message") { } static CliComm::LogLevel getLevel(string_ref level) { auto levels = CliComm::getLevelStrings(); for (auto i : xrange(levels.size())) { if (level == levels[i]) { return static_cast(i); } } throw CommandException("Unknown level string: " + level); } void MessageCommand::execute(array_ref tokens, TclObject& /*result*/) { CliComm& cliComm = getCliComm(); CliComm::LogLevel level = CliComm::INFO; switch (tokens.size()) { case 3: level = getLevel(tokens[2].getString()); // fall-through case 2: cliComm.log(level, tokens[1].getString()); break; default: throw SyntaxError(); } } std::string MessageCommand::help(const std::vector& /*tokens*/) const { return "message []\n" "Print a message. (By default) this message will be shown in " "a colored box at the top of the screen. It's possible to " "specify a level for the message (e.g. 'info', 'warning' or " "'error')."; } void MessageCommand::tabCompletion(std::vector& tokens) const { if (tokens.size() == 3) { completeString(tokens, CliComm::getLevelStrings()); } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/events/MessageCommand.hh000066400000000000000000000007351257557151200217430ustar00rootroot00000000000000#ifndef MESSAGECOMMAND_HH #define MESSAGECOMMAND_HH #include "Command.hh" namespace openmsx { class CommandController; class MessageCommand final : public Command { public: MessageCommand(CommandController& controller); void execute(array_ref tokens, TclObject& result) override; std::string help(const std::vector& tokens) const override; void tabCompletion(std::vector& tokens) const override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/events/Socket.cc000066400000000000000000000041711257557151200202740ustar00rootroot00000000000000#include "Socket.hh" #include "MSXException.hh" #include "utf8_checked.hh" #include #include namespace openmsx { std::string sock_error() { #ifdef _WIN32 wchar_t* s = nullptr; FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, WSAGetLastError(), 0, reinterpret_cast(&s), 0, nullptr); std::string result = utf8::utf16to8(s); LocalFree(s); return result; #else return strerror(errno); #endif } void sock_startup() { #ifdef _WIN32 // MAKEWORD is #define'd as ((WORD)(((BYTE)(a))|(((WORD)((BYTE)(b)))<<8))) // but using that gives old-style-cast warnings WORD w = 1 | (1 << 8); // MAKEWORD(1, 1) WSAData wsaData; if (WSAStartup(w, &wsaData) != 0) { throw FatalError(sock_error()); } #else // nothing needed for unix #endif } void sock_cleanup() { #ifdef _WIN32 WSACleanup(); #else // nothing needed for unix #endif } // close a connection void sock_close(SOCKET sd) { #ifdef _WIN32 closesocket(sd); #else close(sd); #endif } int sock_recv(SOCKET sd, char* buf, size_t count) { int num = recv(sd, buf, int(count), 0); if (num > 0) return num; // normal case if (num == 0) return -1; // socket was closed by client #ifdef _WIN32 // Something bad happened on the socket. It could just be a // "would block" notification, or it could be something more // serious. WSAEWOULDBLOCK can happen after select() says a // socket is readable under Win9x, it doesn't happen on // WinNT/2000 or on Unix. int err; int errlen = sizeof(err); getsockopt(sd, SOL_SOCKET, SO_ERROR, reinterpret_cast(&err), &errlen); if (err == WSAEWOULDBLOCK) return 0; return -1; #else if (errno == EWOULDBLOCK) return 0; return -1; #endif } int sock_send(SOCKET sd, const char* buf, size_t count) { int num = send(sd, buf, int(count), 0); if (num >= 0) return num; // normal case #ifdef _WIN32 int err; int errlen = sizeof(err); getsockopt(sd, SOL_SOCKET, SO_ERROR, reinterpret_cast(&err), &errlen); if (err == WSAEWOULDBLOCK) return 0; return -1; #else if (errno == EWOULDBLOCK) return 0; return -1; #endif } } // namespace openmsx openMSX-RELEASE_0_12_0/src/events/Socket.hh000066400000000000000000000014211257557151200203010ustar00rootroot00000000000000#ifndef SOCKET_HH #define SOCKET_HH #include #ifndef _WIN32 #include #include #include #include #include #include #else #include #endif namespace openmsx { #ifndef _WIN32 static const int OPENMSX_INVALID_SOCKET = -1; static const int SOCKET_ERROR = -1; using SOCKET = int; #else // INVALID_SOCKET is #defined as (SOCKET)(~0) // but that gives a old-style-cast warning static const SOCKET OPENMSX_INVALID_SOCKET = static_cast(~0); #endif std::string sock_error(); void sock_startup(); void sock_cleanup(); void sock_close(SOCKET sd); int sock_recv(SOCKET sd, char* buf, size_t count); int sock_send(SOCKET sd, const char* buf, size_t count); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/events/StdioMessages.cc000066400000000000000000000007721257557151200216210ustar00rootroot00000000000000#include "StdioMessages.hh" #include using std::string; namespace openmsx { void StdioMessages::log(CliComm::LogLevel level, string_ref message) { auto levelStr = CliComm::getLevelStrings(); ((level == CliComm::INFO) ? std::cout : std::cerr) << levelStr[level] << ": " << message << std::endl; } void StdioMessages::update(CliComm::UpdateType /*type*/, string_ref /*machine*/, string_ref /*name*/, string_ref /*value*/) { // ignore } } // namespace openmsx openMSX-RELEASE_0_12_0/src/events/StdioMessages.hh000066400000000000000000000005661257557151200216340ustar00rootroot00000000000000#ifndef STDIOMESSAGES_HH #define STDIOMESSAGES_HH #include "CliListener.hh" namespace openmsx { class StdioMessages final : public CliListener { public: void log(CliComm::LogLevel level, string_ref message) override; void update(CliComm::UpdateType type, string_ref machine, string_ref name, string_ref value) override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/events/TclCallbackMessages.cc000066400000000000000000000017771257557151200227040ustar00rootroot00000000000000#include "TclCallbackMessages.hh" #include "GlobalCliComm.hh" namespace openmsx { TclCallbackMessages::TclCallbackMessages(GlobalCliComm& cliComm_, CommandController& controller) : cliComm(cliComm_) , messageCallback( controller, "message_callback", "Tcl proc called when a new message is available", false, // don't print callback err on cliComm (would cause infinite loop) false) // don't save setting { cliComm.addListener(std::unique_ptr(this)); // wrap in unique_ptr } TclCallbackMessages::~TclCallbackMessages() { std::unique_ptr ptr = cliComm.removeListener(*this); ptr.release(); } void TclCallbackMessages::log(CliComm::LogLevel level, string_ref message) { auto levelStr = CliComm::getLevelStrings(); messageCallback.execute(message, levelStr[level]); } void TclCallbackMessages::update( CliComm::UpdateType /*type*/, string_ref /*machine*/, string_ref /*name*/, string_ref /*value*/) { // ignore } } // namespace openmsx openMSX-RELEASE_0_12_0/src/events/TclCallbackMessages.hh000066400000000000000000000011711257557151200227020ustar00rootroot00000000000000#ifndef TCLCALLBACKMESSAGES_HH #define TCLCALLBACKMESSAGES_HH #include "CliListener.hh" #include "TclCallback.hh" namespace openmsx { class GlobalCliComm; class CommandController; class TclCallbackMessages final : public CliListener { public: TclCallbackMessages(GlobalCliComm& cliComm, CommandController& controller); ~TclCallbackMessages(); void log(CliComm::LogLevel level, string_ref message) override; void update(CliComm::UpdateType type, string_ref machine, string_ref name, string_ref value) override; private: GlobalCliComm& cliComm; TclCallback messageCallback; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/000077500000000000000000000000001257557151200157625ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/src/fdc/.gitignore000066400000000000000000000001761257557151200177560ustar00rootroot00000000000000/*.ROM /*.bb /*.bbg /*.da /*.dsk /*.prof /*.rom /*.rpo /*.swp /*.vim /.debug /.kdbgrc.openmsx /.xvpics /gmon.out /GNUmakefile openMSX-RELEASE_0_12_0/src/fdc/AVTFDC.cc000066400000000000000000000054051257557151200172440ustar00rootroot00000000000000#include "AVTFDC.hh" #include "DriveMultiplexer.hh" #include "WD2793.hh" #include "serialize.hh" namespace openmsx { AVTFDC::AVTFDC(const DeviceConfig& config) : WD2793BasedFDC(config) { } byte AVTFDC::readIO(word port, EmuTime::param time) { byte value; switch (port & 0x07) { case 0: value = controller.getStatusReg(time); break; case 1: value = controller.getTrackReg(time); break; case 2: value = controller.getSectorReg(time); break; case 3: value = controller.getDataReg(time); break; case 4: value = 0x7F; if (controller.getIRQ(time)) value |= 0x80; if (controller.getDTRQ(time)) value &= ~0x40; break; default: value = 255; break; } return value; } byte AVTFDC::peekIO(word port, EmuTime::param time) const { byte value; switch (port & 0x07) { case 0: value = controller.peekStatusReg(time); break; case 1: value = controller.peekTrackReg(time); break; case 2: value = controller.peekSectorReg(time); break; case 3: value = controller.peekDataReg(time); break; case 4: value = 0x7F; if (controller.peekIRQ(time)) value |= 0x80; if (controller.peekDTRQ(time)) value &= ~0x40; break; default: value = 255; break; } return value; } void AVTFDC::writeIO(word port, byte value, EmuTime::param time) { switch (port & 0x07) { case 0: controller.setCommandReg(value, time); break; case 1: controller.setTrackReg(value, time); break; case 2: controller.setSectorReg(value, time); break; case 3: controller.setDataReg(value, time); break; case 4: /* nothing only read... */ break; case 5: // From mohai // bit 0: drive select A (and motor on, as this is a WD1770, // we use this as workaround) // bit 1: drive select B (and motor on, as this is a WD1770, // we use this as workaround) // bit 2: side select // bit 3: density: 1=single 0=double (not supported by openMSX) // // Set correct drive DriveMultiplexer::DriveNum drive; switch (value & 0x03) { case 1: drive = DriveMultiplexer::DRIVE_A; break; case 2: drive = DriveMultiplexer::DRIVE_B; break; default: // No drive selected or two drives at same time // The motor is enabled for all drives at the same time, so // in a real machine you must take care to do not select more // than one drive at the same time (you could get data // collision). drive = DriveMultiplexer::NO_DRIVE; } multiplexer.selectDrive(drive, time); multiplexer.setSide((value & 0x04) != 0); multiplexer.setMotor(drive != DriveMultiplexer::NO_DRIVE, time); break; } } template void AVTFDC::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); } INSTANTIATE_SERIALIZE_METHODS(AVTFDC); REGISTER_MSXDEVICE(AVTFDC, "AVTFDC"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/AVTFDC.hh000066400000000000000000000007441257557151200172570ustar00rootroot00000000000000#ifndef AVTFDC_HH #define AVTFDC_HH #include "WD2793BasedFDC.hh" namespace openmsx { class AVTFDC final : public WD2793BasedFDC { public: explicit AVTFDC(const DeviceConfig& config); byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/BootBlocks.cc000066400000000000000000000125351257557151200203400ustar00rootroot00000000000000#include "BootBlocks.hh" namespace openmsx { // bootblock created with regular nms8250 and '_format' const SectorBuffer BootBlocks::dos1BootBlock = {{ 0xeb,0xfe,0x90,0x4e,0x4d,0x53,0x20,0x32,0x2e,0x30,0x50,0x00,0x02,0x02,0x01,0x00, 0x02,0x70,0x00,0xa0,0x05,0xf9,0x03,0x00,0x09,0x00,0x02,0x00,0x00,0x00,0xd0,0xed, 0x53,0x59,0xc0,0x32,0xd0,0xc0,0x36,0x56,0x23,0x36,0xc0,0x31,0x1f,0xf5,0x11,0xab, 0xc0,0x0e,0x0f,0xcd,0x7d,0xf3,0x3c,0xca,0x63,0xc0,0x11,0x00,0x01,0x0e,0x1a,0xcd, 0x7d,0xf3,0x21,0x01,0x00,0x22,0xb9,0xc0,0x21,0x00,0x3f,0x11,0xab,0xc0,0x0e,0x27, 0xcd,0x7d,0xf3,0xc3,0x00,0x01,0x58,0xc0,0xcd,0x00,0x00,0x79,0xe6,0xfe,0xfe,0x02, 0xc2,0x6a,0xc0,0x3a,0xd0,0xc0,0xa7,0xca,0x22,0x40,0x11,0x85,0xc0,0xcd,0x77,0xc0, 0x0e,0x07,0xcd,0x7d,0xf3,0x18,0xb4,0x1a,0xb7,0xc8,0xd5,0x5f,0x0e,0x06,0xcd,0x7d, 0xf3,0xd1,0x13,0x18,0xf2,0x42,0x6f,0x6f,0x74,0x20,0x65,0x72,0x72,0x6f,0x72,0x0d, 0x0a,0x50,0x72,0x65,0x73,0x73,0x20,0x61,0x6e,0x79,0x20,0x6b,0x65,0x79,0x20,0x66, 0x6f,0x72,0x20,0x72,0x65,0x74,0x72,0x79,0x0d,0x0a,0x00,0x00,0x4d,0x53,0x58,0x44, 0x4f,0x53,0x20,0x20,0x53,0x59,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }}; // bootblock created with nms8250 and MSX-DOS 2.20 const SectorBuffer BootBlocks::dos2BootBlock = {{ 0xeb,0xfe,0x90,0x4e,0x4d,0x53,0x20,0x32,0x2e,0x30,0x50,0x00,0x02,0x02,0x01,0x00, 0x02,0x70,0x00,0xa0,0x05,0xf9,0x03,0x00,0x09,0x00,0x02,0x00,0x00,0x00,0x18,0x10, 0x56,0x4f,0x4c,0x5f,0x49,0x44,0x00,0x71,0x60,0x03,0x19,0x00,0x00,0x00,0x00,0x00, 0xd0,0xed,0x53,0x6a,0xc0,0x32,0x72,0xc0,0x36,0x67,0x23,0x36,0xc0,0x31,0x1f,0xf5, 0x11,0xab,0xc0,0x0e,0x0f,0xcd,0x7d,0xf3,0x3c,0x28,0x26,0x11,0x00,0x01,0x0e,0x1a, 0xcd,0x7d,0xf3,0x21,0x01,0x00,0x22,0xb9,0xc0,0x21,0x00,0x3f,0x11,0xab,0xc0,0x0e, 0x27,0xcd,0x7d,0xf3,0xc3,0x00,0x01,0x69,0xc0,0xcd,0x00,0x00,0x79,0xe6,0xfe,0xd6, 0x02,0xf6,0x00,0xca,0x22,0x40,0x11,0x85,0xc0,0x0e,0x09,0xcd,0x7d,0xf3,0x0e,0x07, 0xcd,0x7d,0xf3,0x18,0xb8,0x42,0x6f,0x6f,0x74,0x20,0x65,0x72,0x72,0x6f,0x72,0x0d, 0x0a,0x50,0x72,0x65,0x73,0x73,0x20,0x61,0x6e,0x79,0x20,0x6b,0x65,0x79,0x20,0x66, 0x6f,0x72,0x20,0x72,0x65,0x74,0x72,0x79,0x0d,0x0a,0x24,0x00,0x4d,0x53,0x58,0x44, 0x4f,0x53,0x20,0x20,0x53,0x59,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }}; } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/BootBlocks.hh000066400000000000000000000005401257557151200203430ustar00rootroot00000000000000#ifndef BOOTBLOCKS_HH #define BOOTBLOCKS_HH #include "DiskImageUtils.hh" namespace openmsx { class BootBlocks { public: // bootblock created with regular nms8250 and '_format' static const SectorBuffer dos1BootBlock; // bootblock created with nms8250 and MSX-DOS 2.20 static const SectorBuffer dos2BootBlock; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/DMKDiskImage.cc000066400000000000000000000133001257557151200204570ustar00rootroot00000000000000#include "DMKDiskImage.hh" #include "RawTrack.hh" #include "DiskExceptions.hh" #include "File.hh" #include "FilePool.hh" #include #include namespace openmsx { struct DmkHeader { byte writeProtected; byte numTracks; byte trackLen[2]; byte flags; byte reserved[7]; byte format[4]; }; static_assert(sizeof(DmkHeader) == 16, "must be size 16"); static const byte FLAG_SINGLE_SIDED = 0x10; static const unsigned IDAM_FLAGS_MASK = 0xC000; static const unsigned FLAG_MFM_SECTOR = 0x8000; static bool isValidDmkHeader(const DmkHeader& header) { if (!((header.writeProtected == 0x00) || (header.writeProtected == 0xff))) { return false; } unsigned trackLen = header.trackLen[0] + 256 * header.trackLen[1]; if (trackLen >= 0x4000) return false; // too large track length if (trackLen <= 128) return false; // too small if (header.flags & ~0xd0) return false; // unknown flag set for (int i = 0; i < 7; ++i) { if (header.reserved[i] != 0) return false; } for (int i = 0; i < 4; ++i) { if (header.format[i] != 0) return false; } return true; } DMKDiskImage::DMKDiskImage(Filename& filename, const std::shared_ptr& file_) : Disk(filename) , file(file_) { DmkHeader header; file->seek(0); file->read(&header, sizeof(header)); if (!isValidDmkHeader(header)) { throw MSXException("Not a DMK image"); } numTracks = header.numTracks; dmkTrackLen = header.trackLen[0] + 256 * header.trackLen[1] - 128; singleSided = (header.flags & FLAG_SINGLE_SIDED) != 0;; writeProtected = header.writeProtected == 0xff; // TODO should we print a warning when dmkTrackLen is too far from the // ideal value RawTrack::SIZE? This might indicate the disk image // was not a 3.5" DD disk image and data will be lost on either // read or write. } void DMKDiskImage::seekTrack(byte track, byte side) { unsigned t = singleSided ? track : (2 * track + side); file->seek(sizeof(DmkHeader) + t * (dmkTrackLen + 128)); } void DMKDiskImage::readTrack(byte track, byte side, RawTrack& output) { assert(side < 2); output.clear(dmkTrackLen); if ((singleSided && side) || (track >= numTracks)) { // no such side/track, only clear output return; } seekTrack(track, side); // Read idam data (still needs to be converted). byte idamBuf[2 * 64]; file->read(idamBuf, sizeof(idamBuf)); // Read raw track data. file->read(output.getRawBuffer(), dmkTrackLen); // Convert idam data into an easier to work with internal format. int lastIdam = -1; for (int i = 0; i < 64; ++i) { unsigned idx = idamBuf[2 * i + 0] + 256 * idamBuf[2 * i + 1]; if (idx == 0) break; // end of table reached if ((idx & IDAM_FLAGS_MASK) != FLAG_MFM_SECTOR) { // single density (FM) sector not yet supported, ignore continue; } idx &= ~IDAM_FLAGS_MASK; // clear flags if (idx < 128) { // Invalid IDAM offset, ignore continue; } idx -= 128; if (idx >= dmkTrackLen) { // Invalid IDAM offset, ignore continue; } if (int(idx) <= lastIdam) { // Invalid IDAM offset: // must be strictly bigger than previous, ignore continue; } output.addIdam(idx); lastIdam = idx; } } void DMKDiskImage::writeTrackImpl(byte track, byte side, const RawTrack& input) { assert(side < 2); if ((singleSided && side) || (track >= numTracks)) { // no such side/track, ignore write // TODO a possible enhancement is to extend the file with // extra tracks (or even convert from single sided to // double sided) return; } seekTrack(track, side); // Write idam table. byte idamOut[2 * 64] = {}; // zero-initialize auto& idamIn = input.getIdamBuffer(); for (int i = 0; i < std::min(64, int(idamIn.size())); ++i) { int t = (idamIn[i] + 128) | FLAG_MFM_SECTOR; idamOut[2 * i + 0] = t & 0xff; idamOut[2 * i + 1] = t >> 8; } file->write(idamOut, sizeof(idamOut)); // Write raw track data. assert(input.getLength() == dmkTrackLen); file->write(input.getRawBuffer(), dmkTrackLen); } void DMKDiskImage::readSectorImpl(size_t logicalSector, SectorBuffer& buf) { byte track, side, sector; logToPhys(logicalSector, track, side, sector); RawTrack rawTrack; readTrack(track, side, rawTrack); RawTrack::Sector sectorInfo; if (!rawTrack.decodeSector(sector, sectorInfo)) { throw NoSuchSectorException("Sector not found"); } // TODO should we check sector size == 512? // crc errors? correct track/head? rawTrack.readBlock(sectorInfo.dataIdx, sizeof(buf), buf.raw); } void DMKDiskImage::writeSectorImpl(size_t logicalSector, const SectorBuffer& buf) { byte track, side, sector; logToPhys(logicalSector, track, side, sector); RawTrack rawTrack; readTrack(track, side, rawTrack); RawTrack::Sector sectorInfo; if (!rawTrack.decodeSector(sector, sectorInfo)) { throw NoSuchSectorException("Sector not found"); } // TODO do checks? see readSectorImpl() rawTrack.writeBlock(sectorInfo.dataIdx, sizeof(buf), buf.raw); writeTrack(track, side, rawTrack); } size_t DMKDiskImage::getNbSectorsImpl() const { unsigned t = singleSided ? numTracks : (2 * numTracks); return t * const_cast(this)->getSectorsPerTrack(); } bool DMKDiskImage::isWriteProtectedImpl() const { return writeProtected || file->isReadOnly(); } Sha1Sum DMKDiskImage::getSha1SumImpl(FilePool& filepool) { return filepool.getSha1Sum(*file); } void DMKDiskImage::detectGeometryFallback() { // The implementation in Disk::detectGeometryFallback() uses // getNbSectors(), but for DMK images that doesn't work before we know // the geometry. // detectGeometryFallback() is for example used when the bootsector // could not be read. For a DMK image this can happen when that sector // has CRC errors or is missing or deleted. setSectorsPerTrack(9); setNbSides(singleSided ? 1 : 2); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/DMKDiskImage.hh000066400000000000000000000022601257557151200204740ustar00rootroot00000000000000#ifndef DMKDISKIMAGE_HH #define DMKDISKIMAGE_HH #include "Disk.hh" #include namespace openmsx { class File; /** DMK disk image. A description of the file format can be found * in doc/DMK-Format-Details.txt or at the oriinal site: * http://www.trs-80.com/wordpress/dsk-and-dmk-image-utilities/ * (at the bottom of the page) */ class DMKDiskImage final : public Disk { public: DMKDiskImage(Filename& filename, const std::shared_ptr& file); void readTrack(byte track, byte side, RawTrack& output) override; void writeTrackImpl(byte track, byte side, const RawTrack& input) override; // logical sector emulation for SectorAccessibleDisk void readSectorImpl (size_t sector, SectorBuffer& buf) override; void writeSectorImpl(size_t sector, const SectorBuffer& buf) override; size_t getNbSectorsImpl() const override; bool isWriteProtectedImpl() const override; Sha1Sum getSha1SumImpl(FilePool& filepool) override; private: void detectGeometryFallback() override; void seekTrack(byte track, byte side); std::shared_ptr file; unsigned numTracks; unsigned dmkTrackLen; bool singleSided; bool writeProtected; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/DSKDiskImage.cc000066400000000000000000000021361257557151200204720ustar00rootroot00000000000000#include "DSKDiskImage.hh" #include "File.hh" #include "FilePool.hh" namespace openmsx { DSKDiskImage::DSKDiskImage(const Filename& fileName) : SectorBasedDisk(fileName) , file(std::make_shared(fileName, File::PRE_CACHE)) { setNbSectors(file->getSize() / sizeof(SectorBuffer)); } DSKDiskImage::DSKDiskImage(const Filename& fileName, const std::shared_ptr& file_) : SectorBasedDisk(fileName) , file(file_) { setNbSectors(file->getSize() / sizeof(SectorBuffer)); } DSKDiskImage::~DSKDiskImage() { } void DSKDiskImage::readSectorImpl(size_t sector, SectorBuffer& buf) { file->seek(sector * sizeof(buf)); file->read(&buf, sizeof(buf)); } void DSKDiskImage::writeSectorImpl(size_t sector, const SectorBuffer& buf) { file->seek(sector * sizeof(buf)); file->write(&buf, sizeof(buf)); } bool DSKDiskImage::isWriteProtectedImpl() const { return file->isReadOnly(); } Sha1Sum DSKDiskImage::getSha1SumImpl(FilePool& filePool) { if (hasPatches()) { return SectorAccessibleDisk::getSha1SumImpl(filePool); } return filePool.getSha1Sum(*file); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/DSKDiskImage.hh000066400000000000000000000012241257557151200205010ustar00rootroot00000000000000#ifndef DSKDISKIMAGE_HH #define DSKDISKIMAGE_HH #include "SectorBasedDisk.hh" #include namespace openmsx { class File; class DSKDiskImage final : public SectorBasedDisk { public: explicit DSKDiskImage(const Filename& filename); DSKDiskImage(const Filename& filename, const std::shared_ptr& file); ~DSKDiskImage(); private: void readSectorImpl (size_t sector, SectorBuffer& buf) override; void writeSectorImpl(size_t sector, const SectorBuffer& buf) override; bool isWriteProtectedImpl() const override; Sha1Sum getSha1SumImpl(FilePool& filepool) override; const std::shared_ptr file; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/DirAsDSK.cc000066400000000000000000001251321257557151200176410ustar00rootroot00000000000000#include "DirAsDSK.hh" #include "DiskChanger.hh" #include "Scheduler.hh" #include "CliComm.hh" #include "BootBlocks.hh" #include "File.hh" #include "FileException.hh" #include "ReadDir.hh" #include "StringOp.hh" #include "stl.hh" #include #include #include #include using std::map; using std::string; using std::vector; namespace openmsx { static const unsigned SECTOR_SIZE = sizeof(SectorBuffer); static const unsigned SECTORS_PER_DIR = 7; static const unsigned NUM_FATS = 2; static const unsigned NUM_TRACKS = 80; static const unsigned SECTORS_PER_CLUSTER = 2; static const unsigned SECTORS_PER_TRACK = 9; static const unsigned FIRST_FAT_SECTOR = 1; static const unsigned DIR_ENTRIES_PER_SECTOR = SECTOR_SIZE / sizeof(MSXDirEntry); // First valid regular cluster number. static const unsigned FIRST_CLUSTER = 2; static const unsigned FREE_FAT = 0x000; static const unsigned BAD_FAT = 0xFF7; static const unsigned EOF_FAT = 0xFFF; // actually 0xFF8-0xFFF // Transform BAD_FAT (0xFF7) and EOF_FAT-range (0xFF8-0xFFF) // to a single value: EOF_FAT (0xFFF). static unsigned normalizeFAT(unsigned cluster) { return (cluster < BAD_FAT) ? cluster : EOF_FAT; } unsigned DirAsDSK::readFATHelper(const SectorBuffer* fat, unsigned cluster) const { assert(FIRST_CLUSTER <= cluster); assert(cluster < maxCluster); auto* buf = fat[0].raw; auto* p = &buf[(cluster * 3) / 2]; unsigned result = (cluster & 1) ? (p[0] >> 4) + (p[1] << 4) : p[0] + ((p[1] & 0x0F) << 8); return normalizeFAT(result); } void DirAsDSK::writeFATHelper(SectorBuffer* fat, unsigned cluster, unsigned val) const { assert(FIRST_CLUSTER <= cluster); assert(cluster < maxCluster); auto* buf = fat[0].raw; auto* p = &buf[(cluster * 3) / 2]; if (cluster & 1) { p[0] = (p[0] & 0x0F) + (val << 4); p[1] = val >> 4; } else { p[0] = val; p[1] = (p[1] & 0xF0) + ((val >> 8) & 0x0F); } } SectorBuffer* DirAsDSK::fat() { return §ors[FIRST_FAT_SECTOR]; } SectorBuffer* DirAsDSK::fat2() { return §ors[firstSector2ndFAT]; } // Read entry from FAT. unsigned DirAsDSK::readFAT(unsigned cluster) { return readFATHelper(fat(), cluster); } // Write an entry to both FAT1 and FAT2. void DirAsDSK::writeFAT12(unsigned cluster, unsigned val) { writeFATHelper(fat (), cluster, val); writeFATHelper(fat2(), cluster, val); // An alternative is to copy FAT1 to FAT2 after changes have been made // to FAT1. This is probably more like what the real disk rom does. } // Returns maxCluster in case of no more free clusters unsigned DirAsDSK::findNextFreeCluster(unsigned cluster) { assert(cluster < maxCluster); do { ++cluster; assert(cluster >= FIRST_CLUSTER); } while ((cluster < maxCluster) && (readFAT(cluster) != FREE_FAT)); return cluster; } unsigned DirAsDSK::findFirstFreeCluster() { return findNextFreeCluster(FIRST_CLUSTER - 1); } // Throws when there are no more free clusters. unsigned DirAsDSK::getFreeCluster() { unsigned cluster = findFirstFreeCluster(); if (cluster == maxCluster) { throw MSXException("disk full"); } return cluster; } unsigned DirAsDSK::clusterToSector(unsigned cluster) const { assert(cluster >= FIRST_CLUSTER); assert(cluster < maxCluster); return firstDataSector + SECTORS_PER_CLUSTER * (cluster - FIRST_CLUSTER); } void DirAsDSK::sectorToCluster(unsigned sector, unsigned& cluster, unsigned& offset) const { assert(sector >= firstDataSector); assert(sector < nofSectors); sector -= firstDataSector; cluster = (sector / SECTORS_PER_CLUSTER) + FIRST_CLUSTER; offset = (sector % SECTORS_PER_CLUSTER) * SECTOR_SIZE; } unsigned DirAsDSK::sectorToCluster(unsigned sector) const { unsigned cluster, offset; sectorToCluster(sector, cluster, offset); return cluster; } MSXDirEntry& DirAsDSK::msxDir(DirIndex dirIndex) { assert(dirIndex.sector < nofSectors); assert(dirIndex.idx < DIR_ENTRIES_PER_SECTOR); return sectors[dirIndex.sector].dirEntry[dirIndex.idx]; } // Returns -1 when there are no more sectors for this directory. unsigned DirAsDSK::nextMsxDirSector(unsigned sector) { if (sector < firstDataSector) { // Root directory. assert(firstDirSector <= sector); ++sector; if (sector == firstDataSector) { // Root directory has a fixed number of sectors. return unsigned(-1); } return sector; } else { // Subdirectory. unsigned cluster, offset; sectorToCluster(sector, cluster, offset); if (offset < ((SECTORS_PER_CLUSTER - 1) * SECTOR_SIZE)) { // Next sector still in same cluster. return sector + 1; } unsigned nextCl = readFAT(cluster); if ((nextCl < FIRST_CLUSTER) || (maxCluster <= nextCl)) { // No next cluster, end of directory reached. return unsigned(-1); } return clusterToSector(nextCl); } } // Check if a msx filename is used in a specific msx (sub)directory. bool DirAsDSK::checkMSXFileExists( const string& msxFilename, unsigned msxDirSector) { do { for (unsigned idx = 0; idx < DIR_ENTRIES_PER_SECTOR; ++idx) { DirIndex dirIndex(msxDirSector, idx); if (memcmp(msxDir(dirIndex).filename, msxFilename.data(), 8 + 3) == 0) { return true; } } msxDirSector = nextMsxDirSector(msxDirSector); } while (msxDirSector != unsigned(-1)); // Searched through all sectors of this (sub)directory. return false; } // Returns msx directory entry for the given host file. Or -1 if the host file // is not mapped in the virtual disk. DirAsDSK::DirIndex DirAsDSK::findHostFileInDSK(const string& hostName) { for (auto& p : mapDirs) { if (p.second.hostName == hostName) { return p.first; } } return DirIndex(unsigned(-1), unsigned(-1)); } // Check if a host file is already mapped in the virtual disk. bool DirAsDSK::checkFileUsedInDSK(const string& hostName) { DirIndex dirIndex = findHostFileInDSK(hostName); return dirIndex.sector != unsigned(-1); } static string hostToMsxName(string hostName) { // Create an MSX filename 8.3 format. TODO use vfat-like abbreviation transform(begin(hostName), end(hostName), begin(hostName), [](char a) { return (a == ' ') ? '_' : ::toupper(a); }); string_ref file, ext; StringOp::splitOnLast(hostName, '.', file, ext); if (file.empty()) std::swap(file, ext); string result(8 + 3, ' '); memcpy(&*begin(result) + 0, file.data(), std::min(8, file.size())); memcpy(&*begin(result) + 8, ext .data(), std::min(3, ext .size())); replace(begin(result), end(result), '.', '_'); return result; } static string msxToHostName(const char* msxName) { string result; for (unsigned i = 0; (i < 8) && (msxName[i] != ' '); ++i) { result += tolower(msxName[i]); } if (msxName[8] != ' ') { result += '.'; for (unsigned i = 8; (i < (8 + 3)) && (msxName[i] != ' '); ++i) { result += tolower(msxName[i]); } } return result; } DirAsDSK::DirAsDSK(DiskChanger& diskChanger_, CliComm& cliComm_, const Filename& hostDir_, SyncMode syncMode_, BootSectorType bootSectorType) : SectorBasedDisk(hostDir_) , diskChanger(diskChanger_) , cliComm(cliComm_) , hostDir(hostDir_.getResolved() + '/') , syncMode(syncMode_) , lastAccess(EmuTime::zero) , nofSectors((diskChanger_.isDoubleSidedDrive() ? 2 : 1) * SECTORS_PER_TRACK * NUM_TRACKS) , nofSectorsPerFat((((3 * nofSectors) / (2 * SECTORS_PER_CLUSTER)) + SECTOR_SIZE - 1) / SECTOR_SIZE) , firstSector2ndFAT(FIRST_FAT_SECTOR + nofSectorsPerFat) , firstDirSector(FIRST_FAT_SECTOR + NUM_FATS * nofSectorsPerFat) , firstDataSector(firstDirSector + SECTORS_PER_DIR) , maxCluster((nofSectors - firstDataSector) / SECTORS_PER_CLUSTER + FIRST_CLUSTER) , sectors(nofSectors) { if (!FileOperations::isDirectory(hostDir)) { throw MSXException("Not a directory"); } // First create structure for the virtual disk. byte numSides = diskChanger_.isDoubleSidedDrive() ? 2 : 1; setNbSectors(nofSectors); setSectorsPerTrack(SECTORS_PER_TRACK); setNbSides(numSides); // Initially the whole disk is filled with 0xE5 (at least on Philips // NMS8250). memset(sectors.data(), 0xE5, sizeof(SectorBuffer) * nofSectors); // Use selected bootsector, fill-in values. byte mediaDescriptor = (numSides == 2) ? 0xF9 : 0xF8; const auto& protoBootSector = bootSectorType == BOOTSECTOR_DOS1 ? BootBlocks::dos1BootBlock : BootBlocks::dos2BootBlock; memcpy(§ors[0], &protoBootSector, sizeof(protoBootSector)); auto& bootSector = sectors[0].bootSector; bootSector.bpSector = SECTOR_SIZE; bootSector.spCluster = SECTORS_PER_CLUSTER; bootSector.nrFats = NUM_FATS; bootSector.dirEntries = SECTORS_PER_DIR * (SECTOR_SIZE / sizeof(MSXDirEntry)); bootSector.nrSectors = nofSectors; bootSector.descriptor = mediaDescriptor; bootSector.sectorsFat = nofSectorsPerFat; bootSector.sectorsTrack = SECTORS_PER_TRACK; bootSector.nrSides = numSides; // Clear FAT1 + FAT2. memset(fat(), 0, SECTOR_SIZE * nofSectorsPerFat * NUM_FATS); // First 3 bytes are initialized specially: // 'cluster 0' contains the media descriptor // 'cluster 1' is marked as EOF_FAT // So cluster 2 is the first usable cluster number fat ()->raw[0] = mediaDescriptor; fat ()->raw[1] = 0xFF; fat ()->raw[2] = 0xFF; fat2()->raw[0] = mediaDescriptor; fat2()->raw[1] = 0xFF; fat2()->raw[2] = 0xFF; // Assign empty directory entries. memset(§ors[firstDirSector], 0, SECTOR_SIZE * SECTORS_PER_DIR); // No host files are mapped to this disk yet. assert(mapDirs.empty()); // Import the host filesystem. syncWithHost(); } bool DirAsDSK::isWriteProtectedImpl() const { return syncMode == SYNC_READONLY; } void DirAsDSK::checkCaches() { bool needSync; if (auto* scheduler = diskChanger.getScheduler()) { auto now = scheduler->getCurrentTime(); auto delta = now - lastAccess; needSync = delta > EmuDuration::sec(1); // Do not update lastAccess because we don't actually call // syncWithHost(). } else { // Happens when dirasdisk is used in virtual_drive. needSync = true; } if (needSync) { flushCaches(); } } void DirAsDSK::readSectorImpl(size_t sector, SectorBuffer& buf) { assert(sector < nofSectors); // 'Peek-mode' is used to periodically calculate a sha1sum for the // whole disk (used by reverse). We don't want this calculation to // interfer with the access time we use for normal read/writes. So in // peek-mode we skip the whole sync-step. if (!isPeekMode()) { bool needSync; if (auto* scheduler = diskChanger.getScheduler()) { auto now = scheduler->getCurrentTime(); auto delta = now - lastAccess; lastAccess = now; needSync = delta > EmuDuration::sec(1); } else { // Happens when dirasdisk is used in virtual_drive. needSync = true; } if (needSync) { syncWithHost(); flushCaches(); // e.g. sha1sum // Let the diskdrive report the disk has been ejected. // E.g. a turbor machine uses this to flush its // internal disk caches. diskChanger.forceDiskChange(); } } // Simply return the sector from our virtual disk image. memcpy(&buf, §ors[sector], sizeof(buf)); } void DirAsDSK::syncWithHost() { // Check for removed host files. This frees up space in the virtual // disk. Do this first because otherwise later actions may fail (run // out of virtual disk space) for no good reason. checkDeletedHostFiles(); // Next update existing files. This may enlarge or shrink virtual // files. In case not all host files fit on the virtual disk it's // better to update the existing files than to (partly) add a too big // new file and have no space left to enlarge the existing files. checkModifiedHostFiles(); // Last add new host files (this can only consume virtual disk space). addNewHostFiles("", firstDirSector); } void DirAsDSK::checkDeletedHostFiles() { // This handles both host files and directories. auto copy = mapDirs; for (auto& p : copy) { if (mapDirs.find(p.first) == end(mapDirs)) { // While iterating over (the copy of) mapDirs we delete // entries of mapDirs (when we delete files only the // current entry is deleted, when we delete // subdirectories possibly many entries are deleted). // At this point in the code we've reached such an // entry that's still in the original (copy of) mapDirs // but has already been deleted from the current // mapDirs. Ignore it. continue; } const DirIndex& dirIndex = p.first; MapDir& mapDir = p.second; string fullHostName = hostDir + mapDir.hostName; bool isMSXDirectory = (msxDir(dirIndex).attrib & MSXDirEntry::ATT_DIRECTORY) != 0; FileOperations::Stat fst; if ((!FileOperations::getStat(fullHostName, fst)) || (FileOperations::isDirectory(fst) != isMSXDirectory)) { // TODO also check access permission // Error stat-ing file, or directory/file type is not // the same on the msx and host side (e.g. a host file // has been removed and a host directory with the same // name has been created). In both cases delete the msx // entry (if needed it will be recreated soon). deleteMSXFile(dirIndex); } } } void DirAsDSK::deleteMSXFile(DirIndex dirIndex) { // Remove mapping between host and msx file (if any). mapDirs.erase(dirIndex); char c = msxDir(dirIndex).filename[0]; if (c == 0 || c == char(0xE5)) { // Directory entry not in use, don't need to do anything. return; } if (msxDir(dirIndex).attrib & MSXDirEntry::ATT_DIRECTORY) { // If we're deleting a directory then also (recursively) // delete the files/directories in this directory. const char* msxName = msxDir(dirIndex).filename; if ((memcmp(msxName, ". ", 11) == 0) || (memcmp(msxName, ".. ", 11) == 0)) { // But skip the "." and ".." entries. return; } // Sanity check on cluster range. unsigned cluster = msxDir(dirIndex).startCluster; if ((FIRST_CLUSTER <= cluster) && (cluster < maxCluster)) { // Recursively delete all files in this subdir. deleteMSXFilesInDir(clusterToSector(cluster)); } } // At this point we have a regular file or an empty subdirectory. // Delete it by marking the first filename char as 0xE5. msxDir(dirIndex).filename[0] = char(0xE5); // Clear the FAT chain to free up space in the virtual disk. freeFATChain(msxDir(dirIndex).startCluster); } void DirAsDSK::deleteMSXFilesInDir(unsigned msxDirSector) { do { for (unsigned idx = 0; idx < DIR_ENTRIES_PER_SECTOR; ++idx) { deleteMSXFile(DirIndex(msxDirSector, idx)); } msxDirSector = nextMsxDirSector(msxDirSector); } while (msxDirSector != unsigned(-1)); } void DirAsDSK::freeFATChain(unsigned cluster) { // Follow a FAT chain and mark all clusters on this chain as free. while ((FIRST_CLUSTER <= cluster) && (cluster < maxCluster)) { unsigned nextCl = readFAT(cluster); writeFAT12(cluster, FREE_FAT); cluster = nextCl; } } void DirAsDSK::checkModifiedHostFiles() { auto copy = mapDirs; for (auto& p : copy) { if (mapDirs.find(p.first) == end(mapDirs)) { // See comment in checkDeletedHostFiles(). continue; } const DirIndex& dirIndex = p.first; MapDir& mapDir = p.second; string fullHostName = hostDir + mapDir.hostName; bool isMSXDirectory = (msxDir(dirIndex).attrib & MSXDirEntry::ATT_DIRECTORY) != 0; FileOperations::Stat fst; if (FileOperations::getStat(fullHostName, fst) && (FileOperations::isDirectory(fst) == isMSXDirectory)) { // Detect changes in host file. // Heuristic: we use filesize and modification time to detect // changes in file content. // TODO do we need both filesize and mtime or is mtime alone // enough? // We ignore time/size changes in directories, // typically such a change indicates one of the files // in that directory is changed/added/removed. But such // changes are handled elsewhere. if (!isMSXDirectory && ((mapDir.mtime != fst.st_mtime) || (mapDir.filesize != size_t(fst.st_size)))) { importHostFile(dirIndex, fst); } } else { // Only very rarely happens (because checkDeletedHostFiles() // checked this just recently). deleteMSXFile(dirIndex); } } } void DirAsDSK::importHostFile(DirIndex dirIndex, FileOperations::Stat& fst) { assert(!(msxDir(dirIndex).attrib & MSXDirEntry::ATT_DIRECTORY)); assert(mapDirs.find(dirIndex) != end(mapDirs)); // Set _msx_ modification time. setMSXTimeStamp(dirIndex, fst); // Set _host_ modification time (and filesize) // Note: this is the _only_ place where we update the mapdir.mtime // field. We do _not_ update it when the msx writes to the file. So in // case the msx does write a data sector, it writes to the correct // offset in the host file, but mtime is not updated. On the next // host->emu sync we will resync the full file content and only then // update mtime. This may seem inefficient, but it makes sure that we // never miss any file changes performed by the host. E.g. the host // changed part of the file short before the msx wrote to the same // file. We can never guarantee that such race-scenarios work // correctly, but at least with this mtime-update convention the file // content will be the same on the host and the msx side after every // sync. size_t hostSize = fst.st_size; auto& mapDir = mapDirs[dirIndex]; mapDir.filesize = hostSize; mapDir.mtime = fst.st_mtime; bool moreClustersInChain = true; unsigned curCl = msxDir(dirIndex).startCluster; // If there is no cluster assigned yet to this file (curCl == 0), then // find a free cluster. Treat invalid cases in the same way (curCl == 1 // or curCl >= maxCluster). if ((curCl < FIRST_CLUSTER) || (curCl >= maxCluster)) { moreClustersInChain = false; curCl = findFirstFreeCluster(); // maxCluster in case of disk-full } auto remainingSize = hostSize; unsigned prevCl = 0; try { string fullHostName = hostDir + mapDir.hostName; File file(fullHostName, "rb"); // don't uncompress while (remainingSize && (curCl < maxCluster)) { unsigned logicalSector = clusterToSector(curCl); for (unsigned i = 0; i < SECTORS_PER_CLUSTER; ++i) { unsigned sector = logicalSector + i; assert(sector < nofSectors); auto* buf = §ors[sector]; memset(buf, 0, SECTOR_SIZE); // in case (end of) file only fills partial sector auto sz = std::min(remainingSize, SECTOR_SIZE); file.read(buf, sz); remainingSize -= sz; if (remainingSize == 0) { // Don't fill next sectors in this cluster // if there is no data left. break; } } if (prevCl) { writeFAT12(prevCl, curCl); } else { msxDir(dirIndex).startCluster = curCl; } prevCl = curCl; // Check if we can follow the existing FAT chain or // need to allocate a free cluster. if (moreClustersInChain) { curCl = readFAT(curCl); if ((curCl == EOF_FAT) || // normal end (curCl < FIRST_CLUSTER) || // invalid (curCl >= maxCluster)) { // invalid // Treat invalid FAT chain the same as // a normal EOF_FAT. moreClustersInChain = false; curCl = findFirstFreeCluster(); } } else { curCl = findNextFreeCluster(curCl); } } if (remainingSize != 0) { cliComm.printWarning("Virtual diskimage full: " + mapDir.hostName + " truncated."); } } catch (FileException& e) { // Error opening or reading host file. cliComm.printWarning("Error reading host file: " + mapDir.hostName + ": " + e.getMessage() + " Truncated file on MSX disk."); } // In all cases (no error / image full / host read error) we need to // properly terminate the FAT chain. if (prevCl) { writeFAT12(prevCl, EOF_FAT); } else { // Filesize zero: don't allocate any cluster, write zero // cluster number (checked on a MSXTurboR, DOS2 mode). msxDir(dirIndex).startCluster = FREE_FAT; } // Clear remains of FAT if needed. if (moreClustersInChain) { freeFATChain(curCl); } // Write (possibly truncated) file size. msxDir(dirIndex).size = uint32_t(hostSize - remainingSize); // TODO in case of an error (disk image full, or host file read error), // wouldn't it be better to remove the (half imported) msx file again? // Sometimes when I'm using DirAsDSK I have one file that is too big // (e.g. a core file, or a vim .swp file) and that one prevents // DirAsDSK from importing the other (small) files in my directory. } void DirAsDSK::setMSXTimeStamp(DirIndex dirIndex, FileOperations::Stat& fst) { // Use intermediate param to prevent compilation error for Android time_t mtime = fst.st_mtime; auto* mtim = localtime(&mtime); int t1 = mtim ? (mtim->tm_sec >> 1) + (mtim->tm_min << 5) + (mtim->tm_hour << 11) : 0; msxDir(dirIndex).time = t1; int t2 = mtim ? mtim->tm_mday + ((mtim->tm_mon + 1) << 5) + ((mtim->tm_year + 1900 - 1980) << 9) : 0; msxDir(dirIndex).date = t2; } // Used to add 'regular' files before 'derived' files. E.g. when editing a file // in a host editor, you often get backup/swap files like this: // myfile.txt myfile.txt~ .myfile.txt.swp // Currently the 1st and 2nd are mapped to the same MSX filename. If more // host files map to the same MSX file then (currently) one of the two is // ignored. Which one is ignored depends on the order in which they are added // to the virtual disk. This routine/heuristic tries to add 'regular' files // before derived files. static size_t weight(const string& hostName) { // TODO this weight function can most likely be improved size_t result = 0; string_ref file, ext; StringOp::splitOnLast(hostName, '.', file, ext); // too many '.' characters result += std::count(begin(file), end(file), '.') * 100; // too long extension result += ext.size() * 10; // too long file result += file.size(); return result; } void DirAsDSK::addNewHostFiles(const string& hostSubDir, unsigned msxDirSector) { assert(!StringOp::startsWith(hostSubDir, '/')); assert(hostSubDir.empty() || StringOp::endsWith(hostSubDir, '/')); vector hostNames; { ReadDir dir(hostDir + hostSubDir); while (auto* d = dir.getEntry()) { hostNames.emplace_back(d->d_name); } } sort(begin(hostNames), end(hostNames), [](const string& l, const string& r) { return weight(l) < weight(r); }); for (auto& hostName : hostNames) { try { string fullHostName = hostDir + hostSubDir + hostName; FileOperations::Stat fst; if (!FileOperations::getStat(fullHostName, fst)) { throw MSXException("Error accessing " + fullHostName); } if (FileOperations::isDirectory(fst)) { if ((hostName == "..") || (hostName == ".")) { continue; } addNewDirectory(hostSubDir, hostName, msxDirSector, fst); } else if (FileOperations::isRegularFile(fst)) { addNewHostFile(hostSubDir, hostName, msxDirSector, fst); } else { throw MSXException("Not a regular file: " + fullHostName); } } catch (MSXException& e) { cliComm.printWarning(e.getMessage()); } } } void DirAsDSK::addNewDirectory(const string& hostSubDir, const string& hostName, unsigned msxDirSector, FileOperations::Stat& fst) { string hostPath = hostSubDir + hostName; DirIndex dirIndex = findHostFileInDSK(hostPath); unsigned newMsxDirSector; if (dirIndex.sector == unsigned(-1)) { // MSX directory doesn't exist yet, create it. // Allocate a cluster to hold the subdirectory entries. unsigned cluster = getFreeCluster(); writeFAT12(cluster, EOF_FAT); // Allocate and fill in directory entry. try { dirIndex = fillMSXDirEntry(hostSubDir, hostName, msxDirSector); } catch (...) { // Rollback allocation of directory cluster. writeFAT12(cluster, FREE_FAT); throw; } setMSXTimeStamp(dirIndex, fst); msxDir(dirIndex).attrib = MSXDirEntry::ATT_DIRECTORY; msxDir(dirIndex).startCluster = cluster; // Initialize the new directory. newMsxDirSector = clusterToSector(cluster); for (unsigned i = 0; i < SECTORS_PER_CLUSTER; ++i) { memset(§ors[newMsxDirSector + i], 0, SECTOR_SIZE); } DirIndex idx0(newMsxDirSector, 0); // entry for "." DirIndex idx1(newMsxDirSector, 1); // ".." memset(msxDir(idx0).filename, ' ', 11); memset(msxDir(idx1).filename, ' ', 11); memset(msxDir(idx0).filename, '.', 1); memset(msxDir(idx1).filename, '.', 2); msxDir(idx0).attrib = MSXDirEntry::ATT_DIRECTORY; msxDir(idx1).attrib = MSXDirEntry::ATT_DIRECTORY; setMSXTimeStamp(idx0, fst); setMSXTimeStamp(idx1, fst); msxDir(idx0).startCluster = cluster; msxDir(idx1).startCluster = msxDirSector == firstDirSector ? 0 : sectorToCluster(msxDirSector); } else { if (!(msxDir(dirIndex).attrib & MSXDirEntry::ATT_DIRECTORY)) { // Should rarely happen because checkDeletedHostFiles() // recently checked this. (It could happen when a host // directory is *just*recently* created with the same // name as an existing msx file). Ignore, it will be // corrected in the next sync. return; } unsigned cluster = msxDir(dirIndex).startCluster; if ((cluster < FIRST_CLUSTER) || (cluster >= maxCluster)) { // Sanity check on cluster range. return; } newMsxDirSector = clusterToSector(cluster); } // Recursively process this directory. addNewHostFiles(hostSubDir + hostName + '/', newMsxDirSector); } void DirAsDSK::addNewHostFile(const string& hostSubDir, const string& hostName, unsigned msxDirSector, FileOperations::Stat& fst) { if (checkFileUsedInDSK(hostSubDir + hostName)) { // File is already present in the virtual disk, do nothing. return; } string hostPath = hostSubDir + hostName; string fullHostName = hostDir + hostPath; // TODO check for available free space on disk instead of max free space static const int DISK_SPACE = (nofSectors - firstDataSector) * SECTOR_SIZE; if (fst.st_size > DISK_SPACE) { cliComm.printWarning("File too large: " + fullHostName); return; } DirIndex dirIndex = fillMSXDirEntry(hostSubDir, hostName, msxDirSector); importHostFile(dirIndex, fst); } DirAsDSK::DirIndex DirAsDSK::fillMSXDirEntry( const string& hostSubDir, const string& hostName, unsigned msxDirSector) { string hostPath = hostSubDir + hostName; try { // Get empty dir entry (possibly extends subdirectory). DirIndex dirIndex = getFreeDirEntry(msxDirSector); // Create correct MSX filename. string msxFilename = hostToMsxName(hostName); if (checkMSXFileExists(msxFilename, msxDirSector)) { // TODO: actually should increase vfat abrev if possible!! throw MSXException( "MSX name " + msxToHostName(msxFilename.c_str()) + " already exists"); } // Fill in hostName / msx filename. assert(!StringOp::endsWith(hostPath, '/')); mapDirs[dirIndex].hostName = hostPath; memset(&msxDir(dirIndex), 0, sizeof(MSXDirEntry)); // clear entry memcpy(msxDir(dirIndex).filename, msxFilename.data(), 8 + 3); return dirIndex; } catch (MSXException& e) { throw MSXException("Couldn't add " + hostPath + ": " + e.getMessage()); } } DirAsDSK::DirIndex DirAsDSK::getFreeDirEntry(unsigned msxDirSector) { while (true) { for (unsigned idx = 0; idx < DIR_ENTRIES_PER_SECTOR; ++idx) { DirIndex dirIndex(msxDirSector, idx); const char* msxName = msxDir(dirIndex).filename; if ((msxName[0] == char(0x00)) || (msxName[0] == char(0xE5))) { // Found an unused msx entry. There shouldn't // be any hostfile mapped to this entry. assert(mapDirs.find(dirIndex) == end(mapDirs)); return dirIndex; } } unsigned sector = nextMsxDirSector(msxDirSector); if (sector == unsigned(-1)) break; msxDirSector = sector; } // No free space in existing directory. if (msxDirSector == (firstDataSector - 1)) { // Can't extend root directory. throw MSXException("root directory full"); } // Extend sub-directory: allocate and clear a new cluster, add this // cluster in the existing FAT chain. unsigned cluster = sectorToCluster(msxDirSector); unsigned newCluster = getFreeCluster(); unsigned sector = clusterToSector(newCluster); memset(§ors[sector], 0, SECTORS_PER_CLUSTER * SECTOR_SIZE); writeFAT12(cluster, newCluster); writeFAT12(newCluster, EOF_FAT); // First entry in this newly allocated cluster is free. Return it. return DirIndex(sector, 0); } void DirAsDSK::writeSectorImpl(size_t sector_, const SectorBuffer& buf) { assert(sector_ < nofSectors); assert(syncMode != SYNC_READONLY); auto sector = unsigned(sector_); // Update last access time. if (auto* scheduler = diskChanger.getScheduler()) { lastAccess = scheduler->getCurrentTime(); } DirIndex dirDirIndex; if (sector == 0) { // Ignore. We don't allow writing to the bootsector. It would // be very bad if the MSX tried to format this disk using other // disk parameters than this code assumes. It's also not useful // to write a different bootprogram to this disk because it // will be lost when this virtual disk is ejected. } else if (sector < firstSector2ndFAT) { writeFATSector(sector, buf); } else if (sector < firstDirSector) { // Write to 2nd FAT, only buffer it. Don't interpret the data // in FAT2 in any way (nor trigger any action on this write). memcpy(§ors[sector], &buf, sizeof(buf)); } else if (isDirSector(sector, dirDirIndex)) { // Either root- or sub-directory. writeDIRSector(sector, dirDirIndex, buf); } else { writeDataSector(sector, buf); } } void DirAsDSK::writeFATSector(unsigned sector, const SectorBuffer& buf) { // Create copy of old FAT (to be able to detect changes). vector oldFAT(nofSectorsPerFat); memcpy(&oldFAT[0], fat(), sizeof(oldFAT)); // Update current FAT with new data. memcpy(§ors[sector], &buf, sizeof(buf)); // Look for changes. for (unsigned i = FIRST_CLUSTER; i < maxCluster; ++i) { if (readFAT(i) != readFATHelper(oldFAT.data(), i)) { exportFileFromFATChange(i, oldFAT.data()); } } // At this point there should be no more differences. // Note: we can't use // assert(memcmp(fat(), oldFAT, sizeof(oldFAT)) == 0); // because exportFileFromFATChange() only updates the part of the FAT // that actually contains FAT info. E.g. not the media ID at the // beginning nor the unsused part at the end. And for example the 'CALL // FORMAT' routine also writes these parts of the FAT. for (unsigned i = FIRST_CLUSTER; i < maxCluster; ++i) { assert(readFAT(i) == readFATHelper(oldFAT.data(), i)); } } void DirAsDSK::exportFileFromFATChange(unsigned cluster, SectorBuffer* oldFAT) { // Get first cluster in the FAT chain that contains 'cluster'. unsigned chainLength; // not used unsigned startCluster = getChainStart(cluster, chainLength); // Copy this whole chain from FAT1 to FAT2 (so that the loop in // writeFATSector() sees this part is already handled). unsigned tmp = startCluster; while ((FIRST_CLUSTER <= tmp) && (tmp < maxCluster)) { unsigned next = readFAT(tmp); writeFATHelper(oldFAT, tmp, next); tmp = next; } // Find the corresponding direntry and (if found) export file based on // new cluster chain. DirIndex dirIndex, dirDirIndex; if (getDirEntryForCluster(startCluster, dirIndex, dirDirIndex)) { exportToHost(dirIndex, dirDirIndex); } } unsigned DirAsDSK::getChainStart(unsigned cluster, unsigned& chainLength) { // Search for the first cluster in the chain that contains 'cluster' // Note: worst case (this implementation of) the search is O(N^2), but // because usually FAT chains are allocated in ascending order, this // search is fast O(N). chainLength = 0; for (unsigned i = FIRST_CLUSTER; i < maxCluster; ++i) { if (readFAT(i) == cluster) { // Found a predecessor. cluster = i; ++chainLength; i = FIRST_CLUSTER - 1; // restart search } } return cluster; } // Generic helper function that walks over the whole MSX directory tree // (possibly it stops early so it doesn't always walk over the whole tree). // The action that is performed while walking depends on the functor parameter. template bool DirAsDSK::scanMsxDirs(FUNC func, unsigned sector) { size_t rdIdx = 0; vector dirs; // TODO make vector of struct instead of vector dirs2; // 2 parallel vectors. while (true) { do { // About to process a new directory sector. if (func.onDirSector(sector)) return true; for (unsigned idx = 0; idx < DIR_ENTRIES_PER_SECTOR; ++idx) { // About to process a new directory entry. DirIndex dirIndex(sector, idx); const MSXDirEntry& entry = msxDir(dirIndex); if (func.onDirEntry(dirIndex, entry)) return true; if ((entry.filename[0] == char(0x00)) || (entry.filename[0] == char(0xE5)) || !(entry.attrib & MSXDirEntry::ATT_DIRECTORY)) { // Not a directory. continue; } unsigned cluster = msxDir(dirIndex).startCluster; if ((cluster < FIRST_CLUSTER) || (cluster >= maxCluster)) { // Cluster=0 happens for ".." entries to // the root directory, also be robust for // bogus data. continue; } unsigned dir = clusterToSector(cluster); if (contains(dirs, dir)) { // Already found this sector. Except // for the special "." and ".." // entries, loops should not occur in // valid disk images, but don't crash // on (intentionally?) invalid images. continue; } // Found a new directory, insert in the set of // yet-to-be-processed directories. dirs.push_back(dir); dirs2.push_back(dirIndex); } sector = nextMsxDirSector(sector); } while (sector != unsigned(-1)); // Scan next subdirectory (if any). if (rdIdx == dirs.size()) { // Visited all directories. return false; } // About to process a new subdirectory. func.onVisitSubDir(dirs2[rdIdx]); sector = dirs[rdIdx++]; } } // Base class for functor objects to be used in scanMsxDirs(). // This implements all required methods with empty implementations. struct NullScanner { // Called right before we enter a new subdirectory. void onVisitSubDir(DirAsDSK::DirIndex /*subdir*/) {} // Called when a new sector of a (sub)directory is being scanned. inline bool onDirSector(unsigned /*dirSector*/) { return false; } // Called for each directory entry (in a sector). inline bool onDirEntry(DirAsDSK::DirIndex /*dirIndex*/, const MSXDirEntry& /*entry*/) { return false; } }; // Base class for the IsDirSector and DirEntryForCluster scanner algorithms // below. This class remembers the directory entry of the last visited subdir. struct DirScanner : NullScanner { DirScanner(DirAsDSK::DirIndex& dirDirIndex_) : dirDirIndex(dirDirIndex_) { dirDirIndex = DirAsDSK::DirIndex(0, 0); // represents entry for root dir } // Called right before we enter a new subdirectory. void onVisitSubDir(DirAsDSK::DirIndex subdir) { dirDirIndex = subdir; } DirAsDSK::DirIndex& dirDirIndex; }; // Figure out whether a given sector is part of the msx directory structure. struct IsDirSector : DirScanner { IsDirSector(unsigned sector_, DirAsDSK::DirIndex& dirDirIndex) : DirScanner(dirDirIndex) , sector(sector_) {} bool onDirSector(unsigned dirSector) { return sector == dirSector; } const unsigned sector; }; bool DirAsDSK::isDirSector(unsigned sector, DirIndex& dirDirIndex) { return scanMsxDirs(IsDirSector(sector, dirDirIndex), firstDirSector); } // Search for the directory entry that has the given startCluster. struct DirEntryForCluster : DirScanner { DirEntryForCluster(unsigned cluster_, DirAsDSK::DirIndex& dirIndex_, DirAsDSK::DirIndex& dirDirIndex) : DirScanner(dirDirIndex) , cluster(cluster_) , result(dirIndex_) {} bool onDirEntry(DirAsDSK::DirIndex dirIndex, const MSXDirEntry& entry) { if (entry.startCluster == cluster) { result = dirIndex; return true; } return false; } const unsigned cluster; DirAsDSK::DirIndex& result; }; bool DirAsDSK::getDirEntryForCluster(unsigned cluster, DirIndex& dirIndex, DirIndex& dirDirIndex) { return scanMsxDirs(DirEntryForCluster(cluster, dirIndex, dirDirIndex), firstDirSector); } DirAsDSK::DirIndex DirAsDSK::getDirEntryForCluster(unsigned cluster) { DirIndex dirIndex, dirDirIndex; if (getDirEntryForCluster(cluster, dirIndex, dirDirIndex)) { return dirIndex; } else { return DirIndex(unsigned(-1), unsigned(-1)); // not found } } // Remove the mapping between the msx and host for all the files/dirs in the // given msx directory (+ subdirectories). struct UnmapHostFiles : NullScanner { UnmapHostFiles(DirAsDSK::MapDirs& mapDirs_) : mapDirs(mapDirs_) {} bool onDirEntry(DirAsDSK::DirIndex dirIndex, const MSXDirEntry& /*entry*/) { mapDirs.erase(dirIndex); return false; } DirAsDSK::MapDirs& mapDirs; }; void DirAsDSK::unmapHostFiles(unsigned msxDirSector) { scanMsxDirs(UnmapHostFiles(mapDirs), msxDirSector); } void DirAsDSK::exportToHost(DirIndex dirIndex, DirIndex dirDirIndex) { // Handle both files and subdirectories. if (msxDir(dirIndex).attrib & MSXDirEntry::ATT_VOLUME) { // But ignore volume ID. return; } const char* msxName = msxDir(dirIndex).filename; string hostName; auto it = mapDirs.find(dirIndex); if (it == end(mapDirs)) { // Host file/dir does not yet exist, create hostname from // msx name. if ((msxName[0] == char(0x00)) || (msxName[0] == char(0xE5))) { // Invalid MSX name, don't do anything. return; } string hostSubDir; if (dirDirIndex.sector != 0) { // Not the msx root directory. auto it2 = mapDirs.find(dirDirIndex); assert(it2 != end(mapDirs)); hostSubDir = it2->second.hostName; assert(!StringOp::endsWith(hostSubDir, '/')); hostSubDir += '/'; } hostName = hostSubDir + msxToHostName(msxName); mapDirs[dirIndex].hostName = hostName; } else { // Hostname is already known. hostName = it->second.hostName; } if (msxDir(dirIndex).attrib & MSXDirEntry::ATT_DIRECTORY) { if ((memcmp(msxName, ". ", 11) == 0) || (memcmp(msxName, ".. ", 11) == 0)) { // Don't export "." or "..". return; } exportToHostDir(dirIndex, hostName); } else { exportToHostFile(dirIndex, hostName); } } void DirAsDSK::exportToHostDir(DirIndex dirIndex, const string& hostName) { try { unsigned cluster = msxDir(dirIndex).startCluster; if ((cluster < FIRST_CLUSTER) || (cluster >= maxCluster)) { // Sanity check on cluster range. return; } unsigned msxDirSector = clusterToSector(cluster); // Create the host directory. string fullHostName = hostDir + hostName; FileOperations::mkdirp(fullHostName); // Export all the components in this directory. do { if (readFAT(sectorToCluster(msxDirSector)) == FREE_FAT) { // This happens e.g. on a TurboR when a directory // is removed: first the FAT is cleared before // the directory entry is updated. return; } for (unsigned idx = 0; idx < DIR_ENTRIES_PER_SECTOR; ++idx) { exportToHost(DirIndex(msxDirSector, idx), dirIndex); } msxDirSector = nextMsxDirSector(msxDirSector); } while (msxDirSector != unsigned(-1)); } catch (FileException& e) { cliComm.printWarning("Error while syncing host directory: " + hostName + ": " + e.getMessage()); } } void DirAsDSK::exportToHostFile(DirIndex dirIndex, const string& hostName) { // We write a host file with length that is the minimum of: // - Length indicated in msx directory entry. // - Length of FAT-chain * cluster-size, this chain can have length=0 // if startCluster is not (yet) filled in. try { unsigned curCl = msxDir(dirIndex).startCluster; unsigned msxSize = msxDir(dirIndex).size; string fullHostName = hostDir + hostName; File file(fullHostName, File::TRUNCATE); unsigned offset = 0; while ((FIRST_CLUSTER <= curCl) && (curCl < maxCluster)) { unsigned logicalSector = clusterToSector(curCl); for (unsigned i = 0; i < SECTORS_PER_CLUSTER; ++i) { if (offset >= msxSize) break; unsigned sector = logicalSector + i; assert(sector < nofSectors); auto writeSize = std::min(msxSize - offset, SECTOR_SIZE); file.write(§ors[sector], writeSize); offset += SECTOR_SIZE; } if (offset >= msxSize) break; curCl = readFAT(curCl); } } catch (FileException& e) { cliComm.printWarning("Error while syncing host file: " + hostName + ": " + e.getMessage()); } } void DirAsDSK::writeDIRSector(unsigned sector, DirIndex dirDirIndex, const SectorBuffer& buf) { // Look for changed directory entries. for (unsigned idx = 0; idx < DIR_ENTRIES_PER_SECTOR; ++idx) { auto& newEntry = buf.dirEntry[idx]; DirIndex dirIndex(sector, idx); if (memcmp(&msxDir(dirIndex), &newEntry, sizeof(newEntry)) != 0) { writeDIREntry(dirIndex, dirDirIndex, newEntry); } } // At this point sector should be updated. assert(memcmp(§ors[sector], &buf, sizeof(buf)) == 0); } void DirAsDSK::writeDIREntry(DirIndex dirIndex, DirIndex dirDirIndex, const MSXDirEntry& newEntry) { if (memcmp(msxDir(dirIndex).filename, newEntry.filename, 8 + 3) || ((msxDir(dirIndex).attrib & MSXDirEntry::ATT_DIRECTORY) != ( newEntry.attrib & MSXDirEntry::ATT_DIRECTORY))) { // Name or file-type in the direntry was changed. auto it = mapDirs.find(dirIndex); if (it != end(mapDirs)) { // If there is an associated hostfile, then delete it // (in case of a rename, the file will be recreated // below). string fullHostName = hostDir + it->second.hostName; FileOperations::deleteRecursive(fullHostName); // ignore return value // Remove mapping between msx and host file/dir. mapDirs.erase(it); if (msxDir(dirIndex).attrib & MSXDirEntry::ATT_DIRECTORY) { // In case of a directory also unmap all // sub-components. unsigned cluster = msxDir(dirIndex).startCluster; if ((FIRST_CLUSTER <= cluster) && (cluster < maxCluster)) { unmapHostFiles(clusterToSector(cluster)); } } } } // Copy the new msx directory entry. memcpy(&msxDir(dirIndex), &newEntry, sizeof(newEntry)); // (Re-)export the full file/directory. exportToHost(dirIndex, dirDirIndex); } void DirAsDSK::writeDataSector(unsigned sector, const SectorBuffer& buf) { assert(sector >= firstDataSector); assert(sector < nofSectors); // Buffer the write, whether the sector is mapped to a file or not. memcpy(§ors[sector], &buf, sizeof(buf)); // Get first cluster in the FAT chain that contains this sector. unsigned cluster, offset, chainLength; sectorToCluster(sector, cluster, offset); unsigned startCluster = getChainStart(cluster, chainLength); offset += (sizeof(buf) * SECTORS_PER_CLUSTER) * chainLength; // Get corresponding directory entry. DirIndex dirIndex = getDirEntryForCluster(startCluster); // no need to check for 'dirIndex.sector == unsigned(-1)' auto it = mapDirs.find(dirIndex); if (it == end(mapDirs)) { // This sector was not mapped to a file, nothing more to do. return; } // Actually write data to host file. string fullHostName = hostDir + it->second.hostName; try { File file(fullHostName, "rb+"); // don't uncompress file.seek(offset); unsigned msxSize = msxDir(dirIndex).size; if (msxSize > offset) { auto writeSize = std::min(msxSize - offset, sizeof(buf)); file.write(&buf, writeSize); } } catch (FileException& e) { cliComm.printWarning("Couldn't write to file " + fullHostName + ": " + e.getMessage()); } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/DirAsDSK.hh000066400000000000000000000126431257557151200176550ustar00rootroot00000000000000#ifndef DIRASDSK_HH #define DIRASDSK_HH #include "SectorBasedDisk.hh" #include "DiskImageUtils.hh" #include "FileOperations.hh" #include "EmuTime.hh" #include namespace openmsx { class DiskChanger; class CliComm; class DirAsDSK final : public SectorBasedDisk { public: enum SyncMode { SYNC_READONLY, SYNC_FULL }; enum BootSectorType { BOOTSECTOR_DOS1, BOOTSECTOR_DOS2 }; public: DirAsDSK(DiskChanger& diskChanger, CliComm& cliComm, const Filename& hostDir, SyncMode syncMode, BootSectorType bootSectorType); // SectorBasedDisk void readSectorImpl (size_t sector, SectorBuffer& buf) override; void writeSectorImpl(size_t sector, const SectorBuffer& buf) override; bool isWriteProtectedImpl() const override; void checkCaches() override; private: struct DirIndex { DirIndex() {} DirIndex(unsigned sector_, unsigned idx_) : sector(sector_), idx(idx_) {} bool operator<(const DirIndex& rhs) const { if (sector != rhs.sector) return sector < rhs.sector; return idx < rhs.idx; } unsigned sector; unsigned idx; }; struct MapDir { std::string hostName; // path relative to 'hostDir' // The following two are used to detect changes in the host // file compared to the last host->virtual-disk sync. time_t mtime; // Modification time of host file at the time of // the last sync. size_t filesize; // Host file size, normally the same as msx // filesize, except when the host file was // truncated. }; SectorBuffer* fat(); SectorBuffer* fat2(); MSXDirEntry& msxDir(DirIndex dirIndex); void writeFATSector (unsigned sector, const SectorBuffer& buf); void writeDIRSector (unsigned sector, DirIndex dirDirIndex, const SectorBuffer& buf); void writeDataSector(unsigned sector, const SectorBuffer& buf); void writeDIREntry(DirIndex dirIndex, DirIndex dirDirIndex, const MSXDirEntry& newEntry); void syncWithHost(); void checkDeletedHostFiles(); void deleteMSXFile(DirIndex dirIndex); void deleteMSXFilesInDir(unsigned msxDirSector); void freeFATChain(unsigned cluster); void addNewHostFiles(const std::string& hostSubDir, unsigned msxDirSector); void addNewDirectory(const std::string& hostSubDir, const std::string& hostName, unsigned msxDirSector, FileOperations::Stat& fst); void addNewHostFile(const std::string& hostSubDir, const std::string& hostName, unsigned msxDirSector, FileOperations::Stat& fst); DirIndex fillMSXDirEntry( const std::string& hostSubDir, const std::string& hostName, unsigned msxDirSector); DirIndex getFreeDirEntry(unsigned msxDirSector); DirIndex findHostFileInDSK(const std::string& hostName); bool checkFileUsedInDSK(const std::string& hostName); unsigned nextMsxDirSector(unsigned sector); bool checkMSXFileExists(const std::string& msxfilename, unsigned msxDirSector); void checkModifiedHostFiles(); void setMSXTimeStamp(DirIndex dirIndex, FileOperations::Stat& fst); void importHostFile(DirIndex dirIndex, FileOperations::Stat& fst); void exportToHost(DirIndex dirIndex, DirIndex dirDirIndex); void exportToHostDir (DirIndex dirIndex, const std::string& hostName); void exportToHostFile(DirIndex dirIndex, const std::string& hostName); unsigned findNextFreeCluster(unsigned cluster); unsigned findFirstFreeCluster(); unsigned getFreeCluster(); unsigned readFAT(unsigned cluster); void writeFAT12(unsigned cluster, unsigned val); void exportFileFromFATChange(unsigned cluster, SectorBuffer* oldFAT); unsigned getChainStart(unsigned cluster, unsigned& chainLength); bool isDirSector(unsigned sector, DirIndex& dirDirIndex); bool getDirEntryForCluster(unsigned cluster, DirIndex& dirIndex, DirIndex& dirDirIndex); DirIndex getDirEntryForCluster(unsigned cluster); void unmapHostFiles(unsigned msxDirSector); template bool scanMsxDirs( FUNC func, unsigned msxDirSector); friend struct NullScanner; friend struct DirScanner; friend struct IsDirSector; friend struct DirEntryForCluster; friend struct UnmapHostFiles; // internal helper functions unsigned readFATHelper(const SectorBuffer* fat, unsigned cluster) const; void writeFATHelper(SectorBuffer* fat, unsigned cluster, unsigned val) const; unsigned clusterToSector(unsigned cluster) const; void sectorToCluster(unsigned sector, unsigned& cluster, unsigned& offset) const; unsigned sectorToCluster(unsigned sector) const; private: DiskChanger& diskChanger; // used to query time / report disk change CliComm& cliComm; // TODO don't use CliComm to report errors/warnings const std::string hostDir; const SyncMode syncMode; EmuTime lastAccess; // last time there was a sector read/write // For each directory entry that has a mapped host file/directory we // store the name, last modification time and size of the corresponding // host file/dir. using MapDirs = std::map; MapDirs mapDirs; // format parameters which depend on single/double sided // varying root parameters const unsigned nofSectors; const unsigned nofSectorsPerFat; // parameters that depend on these and thus also vary const unsigned firstSector2ndFAT; const unsigned firstDirSector; const unsigned firstDataSector; const unsigned maxCluster; // First cluster number that can NOT be used anymore. // Storage for the whole virtual disk. std::vector sectors; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/Disk.cc000066400000000000000000000076501257557151200171730ustar00rootroot00000000000000#include "Disk.hh" #include "DiskExceptions.hh" using std::string; namespace openmsx { Disk::Disk(const DiskName& name_) : name(name_), nbSides(0) { } void Disk::writeTrack(byte track, byte side, const RawTrack& input) { if (isWriteProtected()) { throw WriteProtectedException(""); } writeTrackImpl(track, side, input); flushCaches(); } bool Disk::isDoubleSided() { if (!nbSides) { detectGeometry(); } return nbSides == 2; } // Note: Special case to convert between logical/physical sector numbers // for the boot sector and the 1st FAT sector (logical sector: 0/1, // physical location: track 0, side 0, sector 1/2): perform this // conversion without relying on the detected geometry parameters. // Otherwise the detectGeometry() method (which itself reads these // two sectors) would get in an infinite loop. size_t Disk::physToLog(byte track, byte side, byte sector) { if ((track == 0) && (side == 0)) { return sector - 1; } if (!nbSides) { detectGeometry(); } return sectorsPerTrack * (side + nbSides * track) + (sector - 1); } void Disk::logToPhys(size_t log, byte& track, byte& side, byte& sector) { if (log <= 1) { track = 0; side = 0; sector = byte(log + 1); return; } if (!nbSides) { detectGeometry(); } track = byte(log / (nbSides * sectorsPerTrack)); // TODO check for overflow side = byte((log / sectorsPerTrack) % nbSides); sector = byte((log % sectorsPerTrack) + 1); } unsigned Disk::getSectorsPerTrack() { if (!nbSides) { detectGeometry(); } return sectorsPerTrack; } void Disk::detectGeometryFallback() // if all else fails, use statistics { // TODO maybe also check for 8*80 for 8 sectors per track sectorsPerTrack = 9; // most of the time (sorry 5.25" disk users...) // 360k disks are likely to be single sided: nbSides = (getNbSectors() == 720) ? 1 : 2; } void Disk::detectGeometry() { // From the MSX Red Book (p265): // // How to determine media types // // a) Read the boot sector (track 0, sector 1) of the target drive // // b) Check if the first byte is either 0E9H or 0EBH (the JMP // instruction on 8086) // // c) If step b) fails, the disk is a version prior to MS-DOS 2.0; // therefore, use the first byte of the FAT passed from the caller // and make sure it is between 0F8h and 0FFh. // // If step c) is successful, use this as a media descriptor. // If step c) fails, then this disk cannot be read. // // d) If step b) succeeds, read bytes # 0B to # 1D. This is the // DPB for MS-DOS, Version 2.0 and above. The DPB for MSXDOS can // be obtained as follows. // // .... // +18 +19 Sectors per track // +1A +1B Number of heads // ... // // Media Descriptor 0F8H 0F9H 0FAh 0FBH 0FCH 0FDH 0FEH 0FFH // byte (FATID) // Sectors/track 9 9 8 8 9 9 8 8 // No. of sides 1 2 1 2 1 2 1 2 // Tracks/side 80 80 80 80 40 40 40 40 // ... try { SectorBuffer buf; readSector(0, buf); // bootsector if ((buf.raw[0] == 0xE9) || (buf.raw[0] == 0xEB)) { // use values from bootsector sectorsPerTrack = buf.bootSector.sectorsTrack; nbSides = buf.bootSector.nrSides; if ((sectorsPerTrack == 0) || (sectorsPerTrack > 255) || (nbSides == 0) || (nbSides > 255)) { // seems like bogus values, use defaults detectGeometryFallback(); } } else { readSector(1, buf); // 1st fat sector byte mediaDescriptor = buf.raw[0]; if (mediaDescriptor >= 0xF8) { sectorsPerTrack = (mediaDescriptor & 2) ? 8 : 9; nbSides = (mediaDescriptor & 1) ? 2 : 1; } else { // invalid media descriptor, just assume it's a // normal DS or SS DD disk detectGeometryFallback(); } } } catch (MSXException&) { // read error, assume it's a 3.5" DS or SS DD disk detectGeometryFallback(); } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/Disk.hh000066400000000000000000000021771257557151200172040ustar00rootroot00000000000000#ifndef DISK_HH #define DISK_HH #include "SectorAccessibleDisk.hh" #include "DiskName.hh" #include "openmsx.hh" namespace openmsx { class RawTrack; class Disk : public SectorAccessibleDisk { public: virtual ~Disk() {} const DiskName& getName() const { return name; } /** Replace a full track in this image with the given track. */ void writeTrack(byte track, byte side, const RawTrack& input); /** Read a full track from this disk image. */ virtual void readTrack (byte track, byte side, RawTrack& output) = 0; bool isDoubleSided(); protected: explicit Disk(const DiskName& name); size_t physToLog(byte track, byte side, byte sector); void logToPhys(size_t log, byte& track, byte& side, byte& sector); virtual void detectGeometry(); virtual void detectGeometryFallback(); void setSectorsPerTrack(unsigned num) { sectorsPerTrack = num; } unsigned getSectorsPerTrack(); void setNbSides(unsigned num) { nbSides = num; } virtual void writeTrackImpl(byte track, byte side, const RawTrack& input) = 0; private: const DiskName name; unsigned sectorsPerTrack; unsigned nbSides; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/DiskChanger.cc000066400000000000000000000266531257557151200204670ustar00rootroot00000000000000#include "DiskChanger.hh" #include "DiskFactory.hh" #include "DummyDisk.hh" #include "RamDSKDiskImage.hh" #include "DirAsDSK.hh" #include "CommandController.hh" #include "RecordedCommand.hh" #include "StateChangeDistributor.hh" #include "Scheduler.hh" #include "FilePool.hh" #include "File.hh" #include "MSXMotherBoard.hh" #include "Reactor.hh" #include "DiskManipulator.hh" #include "FileContext.hh" #include "FileOperations.hh" #include "FileException.hh" #include "CommandException.hh" #include "CliComm.hh" #include "TclObject.hh" #include "EmuTime.hh" #include "serialize.hh" #include "serialize_stl.hh" #include "serialize_constr.hh" #include "memory.hh" #include using std::string; using std::vector; namespace openmsx { class DiskCommand final : public Command // TODO RecordedCommand { public: DiskCommand(CommandController& commandController, DiskChanger& diskChanger); void execute(array_ref tokens, TclObject& result) override; string help(const vector& tokens) const override; void tabCompletion(vector& tokens) const override; bool needRecord(array_ref tokens) const /*override*/; private: DiskChanger& diskChanger; }; DiskChanger::DiskChanger(MSXMotherBoard& board, const string& driveName_, bool createCmd, bool isDoubleSidedDrive) : reactor(board.getReactor()) , controller(board.getCommandController()) , stateChangeDistributor(&board.getStateChangeDistributor()) , scheduler(&board.getScheduler()) , driveName(driveName_) , doubleSidedDrive(isDoubleSidedDrive) { init(board.getMachineID() + "::", createCmd); } DiskChanger::DiskChanger(Reactor& reactor_, const string& driveName_) : reactor(reactor_) , controller(reactor.getCommandController()) , stateChangeDistributor(nullptr) , scheduler(nullptr) , driveName(driveName_) , doubleSidedDrive(true) // irrelevant, but needs a value { init("", true); } void DiskChanger::init(const string& prefix, bool createCmd) { if (createCmd) createCommand(); ejectDisk(); auto& manipulator = reactor.getDiskManipulator(); manipulator.registerDrive(*this, prefix); if (stateChangeDistributor) { stateChangeDistributor->registerListener(*this); } } void DiskChanger::createCommand() { if (diskCommand) return; diskCommand = make_unique(controller, *this); } DiskChanger::~DiskChanger() { if (stateChangeDistributor) { stateChangeDistributor->unregisterListener(*this); } auto& manipulator = reactor.getDiskManipulator(); manipulator.unregisterDrive(*this); } const DiskName& DiskChanger::getDiskName() const { return disk->getName(); } bool DiskChanger::diskChanged() { bool ret = diskChangedFlag; diskChangedFlag = false; return ret; } SectorAccessibleDisk* DiskChanger::getSectorAccessibleDisk() { if (dynamic_cast(disk.get())) { return nullptr; } return dynamic_cast(disk.get()); } const std::string& DiskChanger::getContainerName() const { return getDriveName(); } void DiskChanger::sendChangeDiskEvent(array_ref args) { // note: might throw MSXException if (stateChangeDistributor) { stateChangeDistributor->distributeNew( std::make_shared( args, scheduler->getCurrentTime())); } else { signalStateChange(std::make_shared( args, EmuTime::zero)); } } void DiskChanger::signalStateChange(const std::shared_ptr& event) { auto* commandEvent = dynamic_cast(event.get()); if (!commandEvent) return; auto& tokens = commandEvent->getTokens(); if (tokens[0] == getDriveName()) { if (tokens[1] == "eject") { ejectDisk(); } else { insertDisk(tokens); } } } void DiskChanger::stopReplay(EmuTime::param /*time*/) { // nothing } int DiskChanger::insertDisk(string_ref filename) { TclObject args[] = { TclObject("dummy"), TclObject(filename) }; try { insertDisk(args); return 0; } catch (MSXException&) { return -1; } } void DiskChanger::insertDisk(array_ref args) { const string& diskImage = FileOperations::getConventionalPath(args[1].getString()); auto& diskFactory = reactor.getDiskFactory(); std::unique_ptr newDisk(diskFactory.createDisk(diskImage, *this)); for (unsigned i = 2; i < args.size(); ++i) { Filename filename(args[i].getString().str(), userFileContext()); newDisk->applyPatch(filename); } // no errors, only now replace original disk changeDisk(std::move(newDisk)); } void DiskChanger::ejectDisk() { changeDisk(make_unique()); } void DiskChanger::changeDisk(std::unique_ptr newDisk) { disk = std::move(newDisk); diskChangedFlag = true; controller.getCliComm().update(CliComm::MEDIA, getDriveName(), getDiskName().getResolved()); } // class DiskCommand DiskCommand::DiskCommand(CommandController& commandController, DiskChanger& diskChanger_) : Command(commandController, diskChanger_.driveName) , diskChanger(diskChanger_) { } void DiskCommand::execute(array_ref tokens, TclObject& result) { if (tokens.size() == 1) { result.addListElement(diskChanger.getDriveName() + ':'); result.addListElement(diskChanger.getDiskName().getResolved()); TclObject options; if (dynamic_cast(diskChanger.disk.get())) { options.addListElement("empty"); } else if (dynamic_cast(diskChanger.disk.get())) { options.addListElement("dirasdisk"); } else if (dynamic_cast(diskChanger.disk.get())) { options.addListElement("ramdsk"); } if (diskChanger.disk->isWriteProtected()) { options.addListElement("readonly"); } if (options.getListLength(getInterpreter()) != 0) { result.addListElement(options); } } else if (tokens[1] == "ramdsk") { string args[] = { diskChanger.getDriveName(), tokens[1].getString().str() }; diskChanger.sendChangeDiskEvent(args); } else if (tokens[1] == "-ramdsk") { string args[] = {diskChanger.getDriveName(), "ramdsk"}; diskChanger.sendChangeDiskEvent(args); result.setString( "Warning: use of '-ramdsk' is deprecated, instead use the 'ramdsk' subcommand"); } else if (tokens[1] == "-eject") { string args[] = {diskChanger.getDriveName(), "eject"}; diskChanger.sendChangeDiskEvent(args); result.setString( "Warning: use of '-eject' is deprecated, instead use the 'eject' subcommand"); } else if (tokens[1] == "eject") { string args[] = {diskChanger.getDriveName(), "eject"}; diskChanger.sendChangeDiskEvent(args); } else { int firstFileToken = 1; if (tokens[1] == "insert") { if (tokens.size() > 2) { firstFileToken = 2; // skip this subcommand as filearg } else { throw CommandException("Missing argument to insert subcommand"); } } try { vector args = { diskChanger.getDriveName() }; for (unsigned i = firstFileToken; i < tokens.size(); ++i) { string_ref option = tokens[i].getString(); if (option == "-ips") { if (++i == tokens.size()) { throw MSXException( "Missing argument for option \"" + option + '\"'); } args.push_back(tokens[i].getString().str()); } else { // backwards compatibility args.push_back(option.str()); } } diskChanger.sendChangeDiskEvent(args); } catch (FileException& e) { throw CommandException(e.getMessage()); } } } string DiskCommand::help(const vector& /*tokens*/) const { const string& name = diskChanger.getDriveName(); return name + " eject : remove disk from virtual drive\n" + name + " ramdsk : create a virtual disk in RAM\n" + name + " insert : change the disk file\n" + name + " : change the disk file\n" + name + " : show which disk image is in drive"; } void DiskCommand::tabCompletion(vector& tokens) const { if (tokens.size() >= 2) { static const char* const extra[] = { "eject", "ramdsk", "insert", }; completeFileName(tokens, userFileContext(), extra); } } bool DiskCommand::needRecord(array_ref tokens) const { return tokens.size() > 1; } static string calcSha1(SectorAccessibleDisk* disk, FilePool& filePool) { return disk ? disk->getSha1Sum(filePool).toString() : ""; } // version 1: initial version // version 2: replaced Filename with DiskName template void DiskChanger::serialize(Archive& ar, unsigned version) { DiskName diskname = disk->getName(); if (ar.versionBelow(version, 2)) { // there was no DiskName yet, just a plain Filename Filename filename; ar.serialize("disk", filename); if (filename.getOriginal() == "ramdisk") { diskname = DiskName(Filename(), "ramdisk"); } else { diskname = DiskName(filename, ""); } } else { ar.serialize("disk", diskname); } vector patches; if (!ar.isLoader()) { patches = disk->getPatches(); } ar.serialize("patches", patches); auto& filePool = reactor.getFilePool(); string oldChecksum; if (!ar.isLoader()) { oldChecksum = calcSha1(getSectorAccessibleDisk(), filePool); } ar.serialize("checksum", oldChecksum); if (ar.isLoader()) { diskname.updateAfterLoadState(); string name = diskname.getResolved(); // TODO use Filename if (!name.empty()) { // Only when the original file doesn't exist on this // system, try to search by sha1sum. This means we // prefer the original file over a file with a matching // sha1sum (the original file may have changed). An // alternative is to prefer the exact sha1sum match. // I'm not sure which alternative is better. if (!FileOperations::exists(name)) { assert(!oldChecksum.empty()); auto file = filePool.getFile( FilePool::DISK, Sha1Sum(oldChecksum)); if (file.is_open()) { name = file.getURL(); } } vector args = { TclObject("dummy"), TclObject(name) }; for (auto& p : patches) { p.updateAfterLoadState(); args.emplace_back(p.getResolved()); // TODO } try { insertDisk(args); } catch (MSXException& e) { throw MSXException( "Couldn't reinsert disk in drive " + getDriveName() + ": " + e.getMessage()); // Alternative: Print warning and continue // without diskimage. Is this better? } } string newChecksum = calcSha1(getSectorAccessibleDisk(), filePool); if (oldChecksum != newChecksum) { controller.getCliComm().printWarning( "The content of the diskimage " + diskname.getResolved() + " has changed since the time this savestate was " "created. This might result in emulation problems " "or even diskcorruption. To prevent the latter, " "the disk is now write-protected (eject and " "reinsert the disk if you want to override this)."); disk->forceWriteProtect(); } } // This should only be restored after disk is inserted ar.serialize("diskChanged", diskChangedFlag); } // extra (local) constructor arguments for polymorphic de-serialization template<> struct SerializeConstructorArgs { using type = std::tuple; template void save(Archive& ar, const DiskChanger& changer) { ar.serialize("driveName", changer.getDriveName()); } template type load(Archive& ar, unsigned /*version*/) { string driveName; ar.serialize("driveName", driveName); return make_tuple(driveName); } }; INSTANTIATE_SERIALIZE_METHODS(DiskChanger); REGISTER_POLYMORPHIC_CLASS_1(DiskContainer, DiskChanger, "DiskChanger", std::reference_wrapper); } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/DiskChanger.hh000066400000000000000000000044701257557151200204720ustar00rootroot00000000000000#ifndef DISKCHANGER_HH #define DISKCHANGER_HH #include "DiskContainer.hh" #include "StateChangeListener.hh" #include "serialize_meta.hh" #include "array_ref.hh" #include "noncopyable.hh" #include #include namespace openmsx { class CommandController; class StateChangeDistributor; class Scheduler; class MSXMotherBoard; class Reactor; class Disk; class DiskCommand; class TclObject; class DiskName; class DiskChanger final : public DiskContainer, private StateChangeListener , private noncopyable { public: DiskChanger(MSXMotherBoard& board, const std::string& driveName, bool createCommand = true, bool isDoubleSidedDrive = true); DiskChanger(Reactor& reactor, const std::string& driveName); // for virtual_drive ~DiskChanger(); void createCommand(); const std::string& getDriveName() const { return driveName; } const DiskName& getDiskName() const; bool peekDiskChanged() const { return diskChangedFlag; } void forceDiskChange() { diskChangedFlag = true; } Disk& getDisk() { return *disk; } // DiskContainer SectorAccessibleDisk* getSectorAccessibleDisk() override; const std::string& getContainerName() const override; bool diskChanged() override; int insertDisk(string_ref filename) override; // for NowindCommand void changeDisk(std::unique_ptr newDisk); // for DirAsDSK Scheduler* getScheduler() const { return scheduler; } bool isDoubleSidedDrive() const { return doubleSidedDrive; } template void serialize(Archive& ar, unsigned version); private: void init(const std::string& prefix, bool createCmd); void insertDisk(array_ref args); void ejectDisk(); void sendChangeDiskEvent(array_ref args); // StateChangeListener void signalStateChange(const std::shared_ptr& event) override; void stopReplay(EmuTime::param time) override; Reactor& reactor; CommandController& controller; StateChangeDistributor* stateChangeDistributor; Scheduler* scheduler; const std::string driveName; std::unique_ptr disk; friend class DiskCommand; std::unique_ptr diskCommand; // must come after driveName const bool doubleSidedDrive; // for DirAsDSK bool diskChangedFlag; }; SERIALIZE_CLASS_VERSION(DiskChanger, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/DiskContainer.cc000066400000000000000000000003621257557151200210270ustar00rootroot00000000000000#include "DiskContainer.hh" #include "NowindRomDisk.hh" namespace openmsx { DiskContainer::~DiskContainer() { } bool DiskContainer::isRomdisk() const { return dynamic_cast(this) != nullptr; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/DiskContainer.hh000066400000000000000000000016001257557151200210350ustar00rootroot00000000000000#ifndef DISKCONTAINER_HH #define DISKCONTAINER_HH #include "serialize_meta.hh" #include "string_ref.hh" #include #include namespace openmsx { class SectorAccessibleDisk; class MSXMotherBoard; class DiskContainer { public: virtual ~DiskContainer(); virtual SectorAccessibleDisk* getSectorAccessibleDisk() = 0; virtual const std::string& getContainerName() const = 0; virtual bool diskChanged() = 0; // for nowind // - error handling with return values instead of exceptions virtual int insertDisk(string_ref filename) = 0; // for nowind bool isRomdisk() const; template void serialize(Archive& /*ar*/, unsigned /*version*/) {} }; // Subclass 'DiskChanger' needs (global) 'MSXMotherBoard' constructor parameter REGISTER_BASE_CLASS_1(DiskContainer, "DiskContainer", std::reference_wrapper); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/DiskDrive.cc000066400000000000000000000030661257557151200201620ustar00rootroot00000000000000#include "DiskDrive.hh" #include "DiskExceptions.hh" namespace openmsx { // class DiskDrive DiskDrive::~DiskDrive() { } // class DummyDrive bool DummyDrive::isDiskInserted() const { return false; } bool DummyDrive::isWriteProtected() const { return true; } bool DummyDrive::isDoubleSided() const { return false; } bool DummyDrive::isTrack00() const { return false; // National_FS-5500F1 2nd drive detection depends on this } void DummyDrive::setSide(bool /*side*/) { // ignore } void DummyDrive::step(bool /*direction*/, EmuTime::param /*time*/) { // ignore } void DummyDrive::setMotor(bool /*status*/, EmuTime::param /*time*/) { // ignore } bool DummyDrive::indexPulse(EmuTime::param /*time*/) { return false; } EmuTime DummyDrive::getTimeTillIndexPulse(EmuTime::param /*time*/, int /*count*/) { return EmuTime::infinity; } void DummyDrive::setHeadLoaded(bool /*status*/, EmuTime::param /*time*/) { // ignore } bool DummyDrive::headLoaded(EmuTime::param /*time*/) { return false; } void DummyDrive::writeTrack(const RawTrack& /*track*/) { throw DriveEmptyException("No drive selected"); } void DummyDrive::readTrack(RawTrack& /*track*/) { throw DriveEmptyException("No drive selected"); } EmuTime DummyDrive::getNextSector(EmuTime::param /*time*/, RawTrack& /*track*/, RawTrack::Sector& /*sector*/) { return EmuTime::infinity; } bool DummyDrive::diskChanged() { return false; } bool DummyDrive::peekDiskChanged() const { return false; } bool DummyDrive::isDummyDrive() const { return true; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/DiskDrive.hh000066400000000000000000000066151257557151200201770ustar00rootroot00000000000000#ifndef DISKDRIVE_HH #define DISKDRIVE_HH #include "EmuTime.hh" #include "RawTrack.hh" #include "noncopyable.hh" namespace openmsx { /** * This (abstract) class defines the DiskDrive interface */ class DiskDrive : private noncopyable { public: static const unsigned ROTATIONS_PER_SECOND = 5; // 300rpm virtual ~DiskDrive(); /** Is drive ready? */ virtual bool isDiskInserted() const = 0; /** Is disk write protected? */ virtual bool isWriteProtected() const = 0; /** Is disk double sided? */ virtual bool isDoubleSided() const = 0; /** Head above track 0 */ virtual bool isTrack00() const = 0; /** Side select. * @param side false = side 0, * true = side 1. */ virtual void setSide(bool side) = 0; /** Step head * @param direction false = out, * true = in. * @param time The moment in emulated time this action takes place. */ virtual void step(bool direction, EmuTime::param time) = 0; /** Set motor on/off * @param status false = off, * true = on. * @param time The moment in emulated time this action takes place. */ virtual void setMotor(bool status, EmuTime::param time) = 0; /** Gets the state of the index pulse. * @param time The moment in emulated time to get the pulse state for. */ virtual bool indexPulse(EmuTime::param time) = 0; /** Return the time till the start of the next index pulse * When there is no disk in the drive or when the disk is not spinning, * this function returns the current time. * @param time The current time * @param count Number of required index pulses. */ virtual EmuTime getTimeTillIndexPulse(EmuTime::param time, int count = 1) = 0; /** Set head loaded status. * @param status false = not loaded, * true = loaded. * @param time The moment in emulated time this action takes place. */ virtual void setHeadLoaded(bool status, EmuTime::param time) = 0; /** Is head loaded? */ virtual bool headLoaded(EmuTime::param time) = 0; virtual void writeTrack(const RawTrack& track) = 0; virtual void readTrack ( RawTrack& track) = 0; virtual EmuTime getNextSector(EmuTime::param time, RawTrack& track, RawTrack::Sector& sector) = 0; /** Is disk changed? */ virtual bool diskChanged() = 0; virtual bool peekDiskChanged() const = 0; /** Is there a dummy (unconncted) drive? */ virtual bool isDummyDrive() const = 0; }; /** * This class implements a not connected disk drive. */ class DummyDrive final : public DiskDrive { public: bool isDiskInserted() const override; bool isWriteProtected() const override; bool isDoubleSided() const override; bool isTrack00() const override; void setSide(bool side) override; void step(bool direction, EmuTime::param time) override; void setMotor(bool status, EmuTime::param time) override; bool indexPulse(EmuTime::param time) override; EmuTime getTimeTillIndexPulse(EmuTime::param time, int count) override; void setHeadLoaded(bool status, EmuTime::param time) override; bool headLoaded(EmuTime::param time) override; void writeTrack(const RawTrack& track) override; void readTrack ( RawTrack& track) override; EmuTime getNextSector(EmuTime::param time, RawTrack& track, RawTrack::Sector& sector) override; bool diskChanged() override; bool peekDiskChanged() const override; bool isDummyDrive() const override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/DiskExceptions.hh000066400000000000000000000013371257557151200212430ustar00rootroot00000000000000#ifndef DISKEXCEPTIONS_HH #define DISKEXCEPTIONS_HH #include "MSXException.hh" namespace openmsx { class NoSuchSectorException final : public MSXException { public: explicit NoSuchSectorException(string_ref message) : MSXException(message) {} }; class DiskIOErrorException final : public MSXException { public: explicit DiskIOErrorException(string_ref message) : MSXException(message) {} }; class DriveEmptyException final : public MSXException { public: explicit DriveEmptyException(string_ref message) : MSXException(message) {} }; class WriteProtectedException final : public MSXException { public: explicit WriteProtectedException(string_ref message) : MSXException(message) {} }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/DiskFactory.cc000066400000000000000000000057671257557151200205320ustar00rootroot00000000000000#include "DiskFactory.hh" #include "Reactor.hh" #include "File.hh" #include "FileContext.hh" #include "DSKDiskImage.hh" #include "XSADiskImage.hh" #include "DMKDiskImage.hh" #include "RamDSKDiskImage.hh" #include "DirAsDSK.hh" #include "DiskPartition.hh" #include "MSXException.hh" #include "memory.hh" #include using std::string; namespace openmsx { DiskFactory::DiskFactory(Reactor& reactor_) : reactor(reactor_) , syncDirAsDSKSetting( reactor.getCommandController(), "DirAsDSKmode", "type of syncronisation between host directory and dir-as-dsk diskimage", DirAsDSK::SYNC_FULL, EnumSetting::Map{ {"read_only", DirAsDSK::SYNC_READONLY}, {"full", DirAsDSK::SYNC_FULL}}) , bootSectorSetting( reactor.getCommandController(), "bootsector", "boot sector type for dir-as-dsk", DirAsDSK::BOOTSECTOR_DOS2, EnumSetting::Map{ {"DOS1", DirAsDSK::BOOTSECTOR_DOS1}, {"DOS2", DirAsDSK::BOOTSECTOR_DOS2}}) { } std::unique_ptr DiskFactory::createDisk( const string& diskImage, DiskChanger& diskChanger) { if (diskImage == "ramdsk") { return make_unique(); } Filename filename(diskImage, userFileContext()); try { // First try DirAsDSK return make_unique( diskChanger, reactor.getCliComm(), filename, syncDirAsDSKSetting.getEnum(), bootSectorSetting.getEnum()); } catch (MSXException&) { // DirAsDSK didn't work, no problem } try { auto file = std::make_shared(filename, File::PRE_CACHE); try { // first try XSA return make_unique(filename, *file); } catch (MSXException&) { // XSA didn't work, still no problem } try { // next try dmk file->seek(0); return make_unique(filename, file); } catch (MSXException& /*e*/) { // DMK didn't work, still no problem } // next try normal DSK return make_unique(filename, file); } catch (MSXException& e) { // File could not be opened or (very rare) something is wrong // with the DSK image. Try to interpret the filename as // : // Try this last because the ':' character could be // part of the filename itself. So only try this if // the name could not be interpreted as a valid // filename. auto pos = diskImage.find_last_of(':'); if (pos == string::npos) { // does not contain ':', throw previous exception throw; } std::shared_ptr wholeDisk; try { Filename filename2(diskImage.substr(0, pos)); wholeDisk = std::make_shared(filename2); } catch (MSXException&) { // If this fails we still prefer to show the // previous error message, because it's most // likely more descriptive. throw e; } unsigned num; try { num = fast_stou(diskImage.substr(pos + 1)); } catch (std::invalid_argument&) { // not a valid partion number, throw previous exception throw e; } return make_unique(*wholeDisk, num, wholeDisk); } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/DiskFactory.hh000066400000000000000000000010341257557151200205230ustar00rootroot00000000000000#ifndef DISKFACTORY_HH #define DISKFACTORY_HH #include "DirAsDSK.hh" #include "EnumSetting.hh" #include namespace openmsx { class Reactor; class DiskChanger; class Disk; class DiskFactory { public: explicit DiskFactory(Reactor& reactor); std::unique_ptr createDisk( const std::string& diskImage, DiskChanger& diskChanger); private: Reactor& reactor; EnumSetting syncDirAsDSKSetting; EnumSetting bootSectorSetting; }; } // namespace openmsx #endif // DISKFACTORY_HH openMSX-RELEASE_0_12_0/src/fdc/DiskImageCLI.cc000066400000000000000000000027261257557151200204650ustar00rootroot00000000000000#include "DiskImageCLI.hh" #include "CommandLineParser.hh" #include "GlobalCommandController.hh" #include "TclObject.hh" #include "MSXException.hh" using std::string; namespace openmsx { DiskImageCLI::DiskImageCLI(CommandLineParser& parser_) : parser(parser_) { parser.registerOption("-diska", *this); parser.registerOption("-diskb", *this); parser.registerFileType("di1,di2,dmk,dsk,xsa", *this); driveLetter = 'a'; } void DiskImageCLI::parseOption(const string& option, array_ref& cmdLine) { string filename = getArgument(option, cmdLine); parse(string_ref(option).substr(1), filename, cmdLine); } string_ref DiskImageCLI::optionHelp() const { return "Insert the disk image specified in argument"; } void DiskImageCLI::parseFileType(const string& filename, array_ref& cmdLine) { parse(string("disk") + driveLetter, filename, cmdLine); ++driveLetter; } string_ref DiskImageCLI::fileTypeHelp() const { return "Disk image"; } void DiskImageCLI::parse(string_ref drive, string_ref image, array_ref& cmdLine) { if (!parser.getGlobalCommandController().hasCommand(drive)) { // TODO WIP throw MSXException("No drive named '" + drive + "'."); } TclObject command; command.addListElement(drive); command.addListElement(image); while (peekArgument(cmdLine) == "-ips") { cmdLine.pop_front(); command.addListElement(getArgument("-ips", cmdLine)); } command.executeCommand(parser.getInterpreter()); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/DiskImageCLI.hh000066400000000000000000000013561257557151200204750ustar00rootroot00000000000000#ifndef DISKIMAGEMANAGER_HH #define DISKIMAGEMANAGER_HH #include "CLIOption.hh" namespace openmsx { class CommandLineParser; class DiskImageCLI final : public CLIOption, public CLIFileType { public: explicit DiskImageCLI(CommandLineParser& cmdLineParser); void parseOption(const std::string& option, array_ref& cmdLine) override; string_ref optionHelp() const override; void parseFileType(const std::string& filename, array_ref& cmdLine) override; string_ref fileTypeHelp() const override; private: void parse(string_ref drive, string_ref image, array_ref& cmdLine); CommandLineParser& parser; char driveLetter; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/DiskImageUtils.cc000066400000000000000000000164121257557151200211530ustar00rootroot00000000000000#include "DiskImageUtils.hh" #include "DiskPartition.hh" #include "CommandException.hh" #include "StringOp.hh" #include "BootBlocks.hh" #include "endian.hh" #include "random.hh" #include #include #include namespace openmsx { namespace DiskImageUtils { static const char PARTAB_HEADER[11] = { '\353', '\376', '\220', 'M', 'S', 'X', '_', 'I', 'D', 'E', ' ' }; static bool isPartitionTableSector(const PartitionTable& pt) { return memcmp(pt.header, PARTAB_HEADER, sizeof(PARTAB_HEADER)) == 0; } bool hasPartitionTable(SectorAccessibleDisk& disk) { SectorBuffer buf; disk.readSector(0, buf); return isPartitionTableSector(buf.pt); } static Partition& checkImpl(SectorAccessibleDisk& disk, unsigned partition, SectorBuffer& buf) { // check number in range if (partition < 1 || partition > 31) { throw CommandException( "Invalid partition number specified (must be 1-31)."); } // check drive has a partition table disk.readSector(0, buf); if (!isPartitionTableSector(buf.pt)) { throw CommandException( "No (or invalid) partition table."); } // check valid partition number auto& p = buf.pt.part[31 - partition]; if (p.start == 0) { throw CommandException(StringOp::Builder() << "No partition number " << partition); } return p; } void checkValidPartition(SectorAccessibleDisk& disk, unsigned partition) { SectorBuffer buf; checkImpl(disk, partition, buf); } void checkFAT12Partition(SectorAccessibleDisk& disk, unsigned partition) { SectorBuffer buf; Partition& p = checkImpl(disk, partition, buf); // check partition type if (p.sys_ind != 0x01) { throw CommandException("Only FAT12 partitions are supported."); } } // Create a correct bootsector depending on the required size of the filesystem static void setBootSector(MSXBootSector& boot, size_t nbSectors, unsigned& firstDataSector, byte& descriptor, bool dos1) { // start from the default bootblock .. auto& defaultBootBlock = dos1 ? BootBlocks::dos1BootBlock : BootBlocks::dos2BootBlock; memcpy(&boot, &defaultBootBlock, sizeof(boot)); // .. and fill-in image-size dependent parameters .. // these are the same for most formats byte nbReservedSectors = 1; byte nbHiddenSectors = 1; // all these are initialized below (in this order) word nbSides; byte nbFats; byte nbSectorsPerFat; byte nbSectorsPerCluster; word nbDirEntry; // now set correct info according to size of image (in sectors!) // and using the same layout as used by Jon in IDEFDISK v 3.1 if (nbSectors > 32732) { // 32732 < nbSectors // note: this format is only valid for nbSectors <= 65536 nbSides = 32; // copied from a partition from an IDE HD nbFats = 2; nbSectorsPerFat = 12; // copied from a partition from an IDE HD nbSectorsPerCluster = 16; nbDirEntry = 256; descriptor = 0xF0; nbHiddenSectors = 16; // override default from above } else if (nbSectors > 16388) { // 16388 < nbSectors <= 32732 nbSides = 2; // unknown yet nbFats = 2; nbSectorsPerFat = 12; nbSectorsPerCluster = 8; nbDirEntry = 256; descriptor = 0XF0; } else if (nbSectors > 8212) { // 8212 < nbSectors <= 16388 nbSides = 2; // unknown yet nbFats = 2; nbSectorsPerFat = 12; nbSectorsPerCluster = 4; nbDirEntry = 256; descriptor = 0xF0; } else if (nbSectors > 4126) { // 4126 < nbSectors <= 8212 nbSides = 2; // unknown yet nbFats = 2; nbSectorsPerFat = 12; nbSectorsPerCluster = 2; nbDirEntry = 256; descriptor = 0xF0; } else if (nbSectors > 2880) { // 2880 < nbSectors <= 4126 nbSides = 2; // unknown yet nbFats = 2; nbSectorsPerFat = 6; nbSectorsPerCluster = 2; nbDirEntry = 224; descriptor = 0xF0; } else if (nbSectors > 1440) { // 1440 < nbSectors <= 2880 nbSides = 2; // unknown yet nbFats = 2; nbSectorsPerFat = 5; nbSectorsPerCluster = 2; nbDirEntry = 112; descriptor = 0xF0; } else if (nbSectors > 720) { // normal double sided disk // 720 < nbSectors <= 1440 nbSides = 2; nbFats = 2; nbSectorsPerFat = 3; nbSectorsPerCluster = 2; nbDirEntry = 112; descriptor = 0xF9; nbSectors = 1440; // force nbSectors to 1440, why? } else { // normal single sided disk // nbSectors <= 720 nbSides = 1; nbFats = 2; nbSectorsPerFat = 2; nbSectorsPerCluster = 2; nbDirEntry = 112; descriptor = 0xF8; nbSectors = 720; // force nbSectors to 720, why? } boot.nrSectors = uint16_t(nbSectors); // TODO check for overflow boot.nrSides = nbSides; boot.spCluster = nbSectorsPerCluster; boot.nrFats = nbFats; boot.sectorsFat = nbSectorsPerFat; boot.dirEntries = nbDirEntry; boot.descriptor = descriptor; boot.resvSectors = nbReservedSectors; boot.hiddenSectors = nbHiddenSectors; if (!dos1) { // set random volume id boot.vol_id = random_32bit() & 0x7F7F7F7F; // why are bits masked? } unsigned nbRootDirSectors = nbDirEntry / 16; unsigned rootDirStart = 1 + nbFats * nbSectorsPerFat; firstDataSector = rootDirStart + nbRootDirSectors; } void format(SectorAccessibleDisk& disk, bool dos1) { // first create a bootsector for given partition size size_t nbSectors = disk.getNbSectors(); SectorBuffer buf; unsigned firstDataSector; byte descriptor; setBootSector(buf.bootSector, nbSectors, firstDataSector, descriptor, dos1); disk.writeSector(0, buf); // write empty FAT and directory sectors memset(&buf, 0, sizeof(buf)); for (unsigned i = 2; i < firstDataSector; ++i) { disk.writeSector(i, buf); } // first FAT sector is special: // - first byte contains the media descriptor // - first two clusters must be marked as EOF buf.raw[0] = descriptor; buf.raw[1] = 0xFF; buf.raw[2] = 0xFF; disk.writeSector(1, buf); // write 'empty' data sectors memset(&buf, 0xE5, sizeof(buf)); for (size_t i = firstDataSector; i < nbSectors; ++i) { disk.writeSector(i, buf); } } static void logicalToCHS(unsigned logical, unsigned& cylinder, unsigned& head, unsigned& sector) { // This is made to fit the openMSX harddisk configuration: // 32 sectors/track 16 heads unsigned tmp = logical + 1; sector = tmp % 32; if (sector == 0) sector = 32; tmp = (tmp - sector) / 32; head = tmp % 16; cylinder = tmp / 16; } void partition(SectorAccessibleDisk& disk, const std::vector& sizes) { assert(sizes.size() <= 31); SectorBuffer buf; memset(&buf, 0, sizeof(buf)); memcpy(buf.pt.header, PARTAB_HEADER, sizeof(PARTAB_HEADER)); buf.pt.end = 0xAA55; unsigned partitionOffset = 1; for (unsigned i = 0; i < sizes.size(); ++i) { unsigned partitionNbSectors = sizes[i]; auto& p = buf.pt.part[30 - i]; unsigned startCylinder, startHead, startSector; logicalToCHS(partitionOffset, startCylinder, startHead, startSector); unsigned endCylinder, endHead, endSector; logicalToCHS(partitionOffset + partitionNbSectors - 1, endCylinder, endHead, endSector); p.boot_ind = (i == 0) ? 0x80 : 0x00; // bootflag p.head = startHead; p.sector = startSector; p.cyl = startCylinder; p.sys_ind = 0x01; // FAT12 p.end_head = endHead; p.end_sector = endSector; p.end_cyl = endCylinder; p.start = partitionOffset; p.size = sizes[i]; DiskPartition diskPartition(disk, partitionOffset, partitionNbSectors); format(diskPartition); partitionOffset += partitionNbSectors; } disk.writeSector(0, buf); } } // namespace DiskImageUtils } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/DiskImageUtils.hh000066400000000000000000000120201257557151200211540ustar00rootroot00000000000000#ifndef DISK_IMAGE_UTILS_HH #define DISK_IMAGE_UTILS_HH #include "openmsx.hh" #include "AlignedBuffer.hh" #include "endian.hh" #include "alignof.hh" #include namespace openmsx { class SectorAccessibleDisk; struct MSXBootSector { byte jumpCode[3]; // + 0 0xE5 to bootprogram byte name[8]; // + 3 Endian::UA_L16 bpSector; // +11 bytes per sector (always 512) byte spCluster; // +13 sectors per cluster (always 2) Endian::L16 resvSectors; // +14 nb of non-data sectors (ex bootsector) byte nrFats; // +16 nb of fats Endian::UA_L16 dirEntries; // +17 max nb of files in root directory Endian::UA_L16 nrSectors; // +19 nb of sectors on this disk byte descriptor; // +21 media descriptor Endian::L16 sectorsFat; // +22 sectors per FAT Endian::L16 sectorsTrack; // +24 sectors per track Endian::L16 nrSides; // +26 number of side Endian::L16 hiddenSectors; // +28 not used byte pad1[9]; // +30 Endian::UA_L32 vol_id; // +39 byte pad2[512-43]; // +43 }; static_assert(sizeof(MSXBootSector) == 512, "must be size 512"); struct MSXDirEntry { static const byte ATT_REGULAR = 0x00; // Normal file static const byte ATT_READONLY = 0x01; // Read-Only file static const byte ATT_HIDDEN = 0x02; // Hidden file static const byte ATT_SYSTEM = 0x04; // System file static const byte ATT_VOLUME = 0x08; // filename is Volume Label static const byte ATT_DIRECTORY = 0x10; // entry is a subdir static const byte ATT_ARCHIVE = 0x20; // Archive bit union { struct { char base[8];// + 0 char ext [3];// + 8 } name; char filename[8 + 3];// + 0 }; byte attrib; // +11 byte reserved[10]; // +12 unused Endian::L16 time; // +22 Endian::L16 date; // +24 Endian::L16 startCluster; // +26 Endian::L32 size; // +28 }; static_assert(sizeof(MSXDirEntry) == 32, "must be size 32"); // Note: can't use Endian::L32 for 'start' and 'size' because the Partition // struct itself is not 4-bytes aligned. struct Partition { byte boot_ind; // + 0 0x80 - active byte head; // + 1 starting head byte sector; // + 2 tarting sector byte cyl; // + 3 starting cylinder byte sys_ind; // + 4 what partition type byte end_head; // + 5 end head byte end_sector; // + 6 end sector byte end_cyl; // + 7 end cylinder Endian::UA_L32 start; // + 8 starting sector counting from 0 Endian::UA_L32 size; // +12 nr of sectors in partition }; static_assert(sizeof(Partition) == 16, "must be size 16"); static_assert(ALIGNOF(Partition) == 1, "must not have alignment requirements"); struct PartitionTable { char header[11]; // + 0 char pad[3]; // + 3 Partition part[31]; // + 14,+30,..,+494 Not 4-byte aligned!! Endian::L16 end; // +510 }; static_assert(sizeof(PartitionTable) == 512, "must be size 512"); // Buffer that can hold a (512-byte) disk sector. // The main advantages of this type over something like 'byte buf[512]' are: // - No need for reinterpret_cast<> when interpreting the data in a // specific way (this could in theory cause alignment problems). // - This type has a stricter alignment, so memcpy() and memset() can work // faster compared to using a raw byte array. union SectorBuffer { byte raw[512]; // raw byte data MSXBootSector bootSector; // interpreted as bootSector MSXDirEntry dirEntry[16]; // interpreted as 16 dir entries PartitionTable pt; // interpreted as Sunrise-IDE partition table AlignedBuffer aligned; // force big alignment (for faster memcpy) }; static_assert(sizeof(SectorBuffer) == 512, "must be size 512"); namespace DiskImageUtils { /** Checks whether * the disk is partitioned * the specified partition exists * throws a CommandException if one of these conditions is false * @param disk The disk to check. * @param partition Partition number, in range [1..31]. */ void checkValidPartition(SectorAccessibleDisk& disk, unsigned partition); /** Like above, but also check whether partition is of type FAT12. */ void checkFAT12Partition(SectorAccessibleDisk& disk, unsigned partition); /** Check whether the given disk is partitioned. */ bool hasPartitionTable(SectorAccessibleDisk& disk); /** Format the given disk (= a single partition). * The formatting depends on the size of the image. * @param disk the disk/partition image to be formatted * @param dos1 set to true if you want to force dos1 formatting (boot sector) */ void format(SectorAccessibleDisk& disk, bool dos1 = false); /** Write a partition table to the given disk and format each partition * @param disk The disk to partition. * @param sizes The number of sectors for each partition. */ void partition(SectorAccessibleDisk& disk, const std::vector& sizes); }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/DiskManipulator.cc000066400000000000000000000425601257557151200214060ustar00rootroot00000000000000#include "DiskManipulator.hh" #include "DiskContainer.hh" #include "MSXtar.hh" #include "DiskImageUtils.hh" #include "DSKDiskImage.hh" #include "DiskPartition.hh" #include "CommandController.hh" #include "CommandException.hh" #include "Reactor.hh" #include "File.hh" #include "Filename.hh" #include "FileContext.hh" #include "FileException.hh" #include "FileOperations.hh" #include "SectorBasedDisk.hh" #include "StringOp.hh" #include "TclObject.hh" #include "memory.hh" #include "xrange.hh" #include #include #include using std::string; using std::vector; using std::unique_ptr; namespace openmsx { #ifndef _MSC_EXTENSIONS // #ifdef required to avoid link error with vc++, see also // http://www.codeguru.com/forum/showthread.php?t=430949 const unsigned DiskManipulator::MAX_PARTITIONS; #endif DiskManipulator::DiskManipulator(CommandController& commandController, Reactor& reactor_) : Command(commandController, "diskmanipulator") , reactor(reactor_) { } DiskManipulator::~DiskManipulator() { assert(drives.empty()); // all DiskContainers must be unregistered } string DiskManipulator::getMachinePrefix() const { string id = reactor.getMachineID(); return id.empty() ? id : id + "::"; } void DiskManipulator::registerDrive( DiskContainer& drive, const std::string& prefix) { assert(findDriveSettings(drive) == end(drives)); DriveSettings driveSettings; driveSettings.drive = &drive; driveSettings.driveName = prefix + drive.getContainerName(); driveSettings.partition = 0; for (unsigned i = 0; i <= MAX_PARTITIONS; ++i) { driveSettings.workingDir[i] = '/'; } drives.push_back(driveSettings); } void DiskManipulator::unregisterDrive(DiskContainer& drive) { auto it = findDriveSettings(drive); assert(it != end(drives)); drives.erase(it); } DiskManipulator::Drives::iterator DiskManipulator::findDriveSettings( DiskContainer& drive) { return find_if(begin(drives), end(drives), [&](DriveSettings& ds) { return ds.drive == &drive; }); } DiskManipulator::Drives::iterator DiskManipulator::findDriveSettings( string_ref name) { return find_if(begin(drives), end(drives), [&](DriveSettings& ds) { return ds.driveName == name; }); } DiskManipulator::DriveSettings& DiskManipulator::getDriveSettings( string_ref diskname) { // first split-off the end numbers (if present) // these will be used as partition indication auto pos1 = diskname.find("::"); auto tmp1 = (pos1 == string_ref::npos) ? diskname : diskname.substr(pos1); auto pos2 = tmp1.find_first_of("0123456789"); auto pos1b = (pos1 == string_ref::npos) ? 0 : pos1; auto tmp2 = diskname.substr(0, pos2 + pos1b); auto it = findDriveSettings(tmp2); if (it == end(drives)) { it = findDriveSettings(getMachinePrefix() + tmp2); if (it == end(drives)) { throw CommandException("Unknown drive: " + tmp2); } } auto* disk = it->drive->getSectorAccessibleDisk(); if (!disk) { // not a SectorBasedDisk throw CommandException("Unsupported disk type."); } if (pos2 == string_ref::npos) { // whole disk it->partition = 0; } else { try { unsigned partition = fast_stou(diskname.substr(pos2)); DiskImageUtils::checkFAT12Partition(*disk, partition); it->partition = partition; } catch (std::invalid_argument&) { // parse error in fast_stou() throw CommandException("Invalid partition name"); } } return *it; } unique_ptr DiskManipulator::getPartition( const DriveSettings& driveData) { auto* disk = driveData.drive->getSectorAccessibleDisk(); assert(disk); return make_unique(*disk, driveData.partition); } void DiskManipulator::execute(array_ref tokens, TclObject& result) { if (tokens.size() == 1) { throw CommandException("Missing argument"); } string_ref subcmd = tokens[1].getString(); if (((tokens.size() != 4) && (subcmd == "savedsk")) || ((tokens.size() != 4) && (subcmd == "mkdir")) || ((tokens.size() != 3) && (subcmd == "dir")) || ((tokens.size() < 3 || tokens.size() > 4) && (subcmd == "format")) || ((tokens.size() < 3 || tokens.size() > 4) && (subcmd == "chdir")) || ((tokens.size() < 4) && (subcmd == "export")) || ((tokens.size() < 4) && (subcmd == "import")) || ((tokens.size() < 4) && (subcmd == "create"))) { throw CommandException("Incorrect number of parameters"); } if (subcmd == "export") { string_ref dir = tokens[3].getString(); if (!FileOperations::isDirectory(dir)) { throw CommandException(dir + " is not a directory"); } auto& settings = getDriveSettings(tokens[2].getString()); array_ref lists(std::begin(tokens) + 4, std::end(tokens)); exprt(settings, dir, lists); } else if (subcmd == "import") { auto& settings = getDriveSettings(tokens[2].getString()); array_ref lists(std::begin(tokens) + 3, std::end(tokens)); result.setString(import(settings, lists)); } else if (subcmd == "savedsk") { auto& settings = getDriveSettings(tokens[2].getString()); savedsk(settings, tokens[3].getString()); } else if (subcmd == "chdir") { auto& settings = getDriveSettings(tokens[2].getString()); if (tokens.size() == 3) { result.setString("Current directory: " + settings.workingDir[settings.partition]); } else { result.setString(chdir(settings, tokens[3].getString())); } } else if (subcmd == "mkdir") { auto& settings = getDriveSettings(tokens[2].getString()); mkdir(settings, tokens[3].getString()); } else if (subcmd == "create") { create(tokens); } else if (subcmd == "format") { bool dos1 = false; string_ref drive = tokens[2].getString(); if (tokens.size() == 4) { if (drive == "-dos1") { dos1 = true; drive = tokens[3].getString(); } else if (tokens[3] == "-dos1") { dos1 = true; } } auto& settings = getDriveSettings(drive); format(settings, dos1); } else if (subcmd == "dir") { auto& settings = getDriveSettings(tokens[2].getString()); result.setString(dir(settings)); } else { throw CommandException("Unknown subcommand: " + subcmd); } } string DiskManipulator::help(const vector& tokens) const { string helptext; if (tokens.size() >= 2) { if (tokens[1] == "import" ) { helptext= "diskmanipulator import \n" "Import all files and subdirs from the host OS as specified into the\n" " in the current MSX subdirectory as was specified with the\n" "last chdir command.\n"; } else if (tokens[1] == "export" ) { helptext= "diskmanipulator export \n" "Extract all files and subdirs from the MSX subdirectory specified with\n" "the chdir command from to the host OS in .\n"; } else if (tokens[1] == "savedsk") { helptext= "diskmanipulator savedsk \n" "This saves the complete drive content to , it is not possible to\n" "save just one partition. The main purpose of this command is to make it\n" "possible to save a 'ramdsk' into a file and to take 'live backups' of\n" "dsk-files in use.\n"; } else if (tokens[1] == "chdir") { helptext= "diskmanipulator chdir \n" "Change the working directory on . This will be the\n" "directory were the 'import', 'export' and 'dir' commands will\n" "work on.\n" "In case of a partitioned drive, each partition has its own\n" "working directory.\n"; } else if (tokens[1] == "mkdir") { helptext= "diskmanipulator mkdir \n" "This creates the directory on . If needed, all missing\n" "parent directories are created at the same time. Accepts both\n" "absolute and relative pathnames.\n"; } else if (tokens[1] == "create") { helptext= "diskmanipulator create [...]\n" "Creates a formatted dsk file with the given size.\n" "If multiple sizes are given, a partitioned disk image will\n" "be created with each partition having the size as indicated. By\n" "default the sizes are expressed in kilobyte, add the postfix M\n" "for megabyte.\n"; } else if (tokens[1] == "format") { helptext= "diskmanipulator format \n" "formats the current (partition on) with a regular\n" "FAT12 MSX filesystem with an MSX-DOS2 boot sector.\n"; } else if (tokens[1] == "dir") { helptext= "diskmanipulator dir \n" "Shows the content of the current directory on \n"; } else { helptext = "Unknown diskmanipulator subcommand: " + tokens[1]; } } else { helptext= "diskmanipulator create [ ...] : create a formatted dsk file with name \n" " having the given (partition) size(s)\n" "diskmanipulator savedsk : save as dsk file named as \n" "diskmanipulator format : format (a partition) on \n" "diskmanipulator chdir : change directory on \n" "diskmanipulator mkdir : create directory on \n" "diskmanipulator dir : long format file listing of current\n" " directory on \n" "diskmanipulator import ... : import files and subdirs from \n" "diskmanipulator export : export all files on to \n" "For more info use 'help diskmanipulator '.\n"; } return helptext; } void DiskManipulator::tabCompletion(vector& tokens) const { if (tokens.size() == 2) { static const char* const cmds[] = { "import", "export", "savedsk", "dir", "create", "format", "chdir", "mkdir", }; completeString(tokens, cmds); } else if ((tokens.size() == 3) && (tokens[1] == "create")) { completeFileName(tokens, userFileContext()); } else if (tokens.size() == 3) { vector names; if ((tokens[1] == "format") || (tokens[1] == "create")) { names.push_back("-dos1"); } for (auto& d : drives) { const auto& name1 = d.driveName; // with prexix const auto& name2 = d.drive->getContainerName(); // without prefix names.insert(end(names), {name1, name2}); // if it has partitions then we also add the partition // numbers to the autocompletion if (auto* disk = d.drive->getSectorAccessibleDisk()) { for (unsigned i = 1; i <= MAX_PARTITIONS; ++i) { try { DiskImageUtils::checkFAT12Partition(*disk, i); names.insert(end(names), { name1 + StringOp::toString(i), name2 + StringOp::toString(i)}); } catch (MSXException&) { // skip invalid partition } } } } completeString(tokens, names); } else if (tokens.size() >= 4) { if ((tokens[1] == "savedsk") || (tokens[1] == "import") || (tokens[1] == "export")) { completeFileName(tokens, userFileContext()); } else if (tokens[1] == "create") { static const char* const cmds[] = { "360", "720", "32M", "-dos1" }; completeString(tokens, cmds); } else if (tokens[1] == "format") { static const char* const cmds[] = { "-dos1" }; completeString(tokens, cmds); } } } void DiskManipulator::savedsk(const DriveSettings& driveData, string_ref filename) { auto partition = getPartition(driveData); SectorBuffer buf; File file(filename, File::CREATE); for (auto i : xrange(partition->getNbSectors())) { partition->readSector(i, buf); file.write(&buf, sizeof(buf)); } } void DiskManipulator::create(array_ref tokens) { vector sizes; unsigned totalSectors = 0; bool dos1 = false; for (unsigned i = 3; i < tokens.size(); ++i) { if (tokens[i] == "-dos1") { dos1 = true; continue; } if (sizes.size() >= MAX_PARTITIONS) { throw CommandException(StringOp::Builder() << "Maximum number of partitions is " << MAX_PARTITIONS); } string tok = tokens[i].getString().str(); char* q; int sectors = strtol(tok.c_str(), &q, 0); int scale = 1024; // default is kilobytes if (*q) { if ((q == tok.c_str()) || *(q + 1)) { throw CommandException("Invalid size: " + tok); } switch (tolower(*q)) { case 'b': scale = 1; break; case 'k': scale = 1024; break; case 'm': scale = 1024 * 1024; break; case 's': scale = SectorBasedDisk::SECTOR_SIZE; break; default: throw CommandException( string("Invalid postfix: ") + q); } } sectors = (sectors * scale) / SectorBasedDisk::SECTOR_SIZE; // for a 32MB disk or greater the sectors would be >= 65536 // since MSX use 16 bits for this, in case of sectors = 65536 // the truncated word will be 0 -> formatted as 320 Kb disk! if (sectors > 65535) sectors = 65535; // this is the max size for fat12 :-) // TEMP FIX: the smallest bootsector we create in MSXtar is for // a normal single sided disk. // TODO: MSXtar must be altered and this temp fix must be set to // the real smallest dsk possible (= bootsecor + minimal fat + // minial dir + minimal data clusters) if (sectors < 720) sectors = 720; sizes.push_back(sectors); totalSectors += sectors; } if (sizes.empty()) { throw CommandException("No size(s) given."); } if (sizes.size() > 1) { // extra sector for partition table ++totalSectors; } // create file with correct size Filename filename(tokens[2].getString().str()); try { File file(filename, File::CREATE); file.truncate(totalSectors * SectorBasedDisk::SECTOR_SIZE); } catch (FileException& e) { throw CommandException("Couldn't create image: " + e.getMessage()); } // initialize (create partition tables and format partitions) DSKDiskImage image(filename); if (sizes.size() > 1) { DiskImageUtils::partition(image, sizes); } else { // only one partition specified, don't create partition table DiskImageUtils::format(image, dos1); } } void DiskManipulator::format(DriveSettings& driveData, bool dos1) { auto partition = getPartition(driveData); DiskImageUtils::format(*partition, dos1); driveData.workingDir[driveData.partition] = '/'; } unique_ptr DiskManipulator::getMSXtar( SectorAccessibleDisk& disk, DriveSettings& driveData) { if (DiskImageUtils::hasPartitionTable(disk)) { throw CommandException( "Please select partition number."); } auto result = make_unique(disk); try { result->chdir(driveData.workingDir[driveData.partition]); } catch (MSXException&) { driveData.workingDir[driveData.partition] = '/'; throw CommandException( "Directory " + driveData.workingDir[driveData.partition] + " doesn't exist anymore. Went back to root " "directory. Command aborted, please retry."); } return result; } string DiskManipulator::dir(DriveSettings& driveData) { auto partition = getPartition(driveData); unique_ptr workhorse = getMSXtar(*partition, driveData); return workhorse->dir(); } string DiskManipulator::chdir(DriveSettings& driveData, string_ref filename) { auto partition = getPartition(driveData); auto workhorse = getMSXtar(*partition, driveData); try { workhorse->chdir(filename); } catch (MSXException& e) { throw CommandException("chdir failed: " + e.getMessage()); } // TODO clean-up this temp hack, used to enable relative paths string& cwd = driveData.workingDir[driveData.partition]; if (StringOp::startsWith(filename, '/')) { cwd = filename.str(); } else { if (!StringOp::endsWith(cwd, '/')) cwd += '/'; cwd.append(filename.data(), filename.size()); } return "New working directory: " + cwd; } void DiskManipulator::mkdir(DriveSettings& driveData, string_ref filename) { auto partition = getPartition(driveData); auto workhorse = getMSXtar(*partition, driveData); try { workhorse->mkdir(filename); } catch (MSXException& e) { throw CommandException(e.getMessage()); } } string DiskManipulator::import(DriveSettings& driveData, array_ref lists) { auto partition = getPartition(driveData); auto workhorse = getMSXtar(*partition, driveData); string messages; auto& interp = getInterpreter(); for (auto& l : lists) { for (auto i : xrange(l.getListLength(interp))) { auto s = l.getListIndex(interp, i).getString(); try { FileOperations::Stat st; if (!FileOperations::getStat(s, st)) { throw CommandException( "Non-existing file " + s); } if (FileOperations::isDirectory(st)) { messages += workhorse->addDir(s); } else if (FileOperations::isRegularFile(st)) { messages += workhorse->addFile(s.str()); } else { // ignore other stuff (sockets, device nodes, ..) messages += "Ignoring " + s + '\n'; } } catch (MSXException& e) { throw CommandException(e.getMessage()); } } } return messages; } void DiskManipulator::exprt(DriveSettings& driveData, string_ref dirname, array_ref lists) { auto partition = getPartition(driveData); auto workhorse = getMSXtar(*partition, driveData); try { if (lists.empty()) { // export all workhorse->getDir(dirname); } else { for (auto& l : lists) { workhorse->getItemFromDir(dirname, l.getString()); } } } catch (MSXException& e) { throw CommandException(e.getMessage()); } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/DiskManipulator.hh000066400000000000000000000042141257557151200214120ustar00rootroot00000000000000#ifndef FILEMANIPULATOR_HH #define FILEMANIPULATOR_HH #include "Command.hh" #include "string_ref.hh" #include #include namespace openmsx { class CommandController; class DiskContainer; class SectorAccessibleDisk; class DiskPartition; class MSXtar; class Reactor; class DiskManipulator final : public Command { public: explicit DiskManipulator(CommandController& commandController, Reactor& reactor); ~DiskManipulator(); void registerDrive(DiskContainer& drive, const std::string& prefix); void unregisterDrive(DiskContainer& drive); private: static const unsigned MAX_PARTITIONS = 31; struct DriveSettings { DiskContainer* drive; std::string driveName; // includes machine prefix std::string workingDir[MAX_PARTITIONS + 1]; /** 0 = whole disk, 1..MAX_PARTITIONS = partition number */ unsigned partition; }; using Drives = std::vector; Drives drives; // Command interface void execute(array_ref tokens, TclObject& result) override; std::string help (const std::vector& tokens) const override; void tabCompletion(std::vector& tokens) const override; std::string getMachinePrefix() const; Drives::iterator findDriveSettings(DiskContainer& drive); Drives::iterator findDriveSettings(string_ref name); DriveSettings& getDriveSettings(string_ref diskname); std::unique_ptr getPartition( const DriveSettings& driveData); std::unique_ptr getMSXtar(SectorAccessibleDisk& disk, DriveSettings& driveData); void create(array_ref tokens); void savedsk(const DriveSettings& driveData, string_ref filename); void format(DriveSettings& driveData, bool dos1); std::string chdir(DriveSettings& driveData, string_ref filename); void mkdir(DriveSettings& driveData, string_ref filename); std::string dir(DriveSettings& driveData); std::string import(DriveSettings& driveData, array_ref lists); void exprt(DriveSettings& driveData, string_ref dirname, array_ref lists); Reactor& reactor; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/DiskName.cc000066400000000000000000000013101257557151200177570ustar00rootroot00000000000000#include "DiskName.hh" #include "serialize.hh" using std::string; namespace openmsx { DiskName::DiskName(const Filename& name_, const string& extra_) : name(name_) , extra(extra_) { } string DiskName::getOriginal() const { return name.getOriginal() + extra; } string DiskName::getResolved() const { return name.getResolved() + extra; } void DiskName::updateAfterLoadState() { name.updateAfterLoadState(); } bool DiskName::empty() const { return name.empty() && extra.empty(); } template void DiskName::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("filename", name); ar.serialize("extra", extra); } INSTANTIATE_SERIALIZE_METHODS(DiskName); } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/DiskName.hh000066400000000000000000000007731257557151200200050ustar00rootroot00000000000000#ifndef DISKNAME_HH #define DISKNAME_HH #include "Filename.hh" namespace openmsx { class DiskName { public: DiskName(const Filename& name, const std::string& extra = ""); std::string getOriginal() const; std::string getResolved() const; void updateAfterLoadState(); bool empty() const; const Filename& getFilename() const { return name; } template void serialize(Archive& ar, unsigned version); private: Filename name; std::string extra; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/DiskPartition.cc000066400000000000000000000030761257557151200210630ustar00rootroot00000000000000#include "DiskPartition.hh" #include "DiskImageUtils.hh" #include "Filename.hh" #include "StringOp.hh" #include namespace openmsx { using namespace DiskImageUtils; static DiskName getDiskName(SectorAccessibleDisk* disk, unsigned partition) { if (auto* dsk = dynamic_cast(disk)) { return DiskName(dsk->getName().getFilename(), StringOp::Builder() << ':' << partition); } else { return DiskName(Filename("dummy")); } } DiskPartition::DiskPartition(SectorAccessibleDisk& disk, unsigned partition, const std::shared_ptr& owned_) : SectorBasedDisk(getDiskName(&disk, partition)) , parent(disk) , owned(owned_) { assert(!owned || (owned.get() == &disk)); if (partition == 0) { start = 0; setNbSectors(disk.getNbSectors()); } else { checkValidPartition(disk, partition); // throws SectorBuffer buf; disk.readSector(0, buf); auto& p = buf.pt.part[31 - partition]; start = p.start; setNbSectors(p.size); } } DiskPartition::DiskPartition(SectorAccessibleDisk& parent_, size_t start_, size_t length) : SectorBasedDisk(getDiskName(nullptr, 0)) , parent(parent_) , start(start_) { setNbSectors(length); } void DiskPartition::readSectorImpl(size_t sector, SectorBuffer& buf) { parent.readSector(start + sector, buf); } void DiskPartition::writeSectorImpl(size_t sector, const SectorBuffer& buf) { parent.writeSector(start + sector, buf); } bool DiskPartition::isWriteProtectedImpl() const { return parent.isWriteProtected(); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/DiskPartition.hh000066400000000000000000000022541257557151200210720ustar00rootroot00000000000000#ifndef DISKPARTITION_HH #define DISKPARTITION_HH #include "SectorBasedDisk.hh" #include namespace openmsx { class DiskPartition final : public SectorBasedDisk { public: /** Return a partition (as a SectorbasedDisk) from another Disk. * @param disk The whole disk. * @param partition The partition number. * 0 for the whole disk * 1-31 for a specific partition, this must be a valid partition. * @param owned If specified it should be a shared_ptr to the Disk * object passed as first parameter. This DiskPartition * will then take (shared) ownership of that Disk. */ DiskPartition(SectorAccessibleDisk& disk, unsigned partition, const std::shared_ptr& owned = nullptr); DiskPartition(SectorAccessibleDisk& parent, size_t start, size_t length); private: void readSectorImpl (size_t sector, SectorBuffer& buf) override; void writeSectorImpl(size_t sector, const SectorBuffer& buf) override; bool isWriteProtectedImpl() const override; SectorAccessibleDisk& parent; std::shared_ptr owned; size_t start; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/DriveMultiplexer.cc000066400000000000000000000057051257557151200216040ustar00rootroot00000000000000#include "DriveMultiplexer.hh" #include "serialize.hh" namespace openmsx { DriveMultiplexer::DriveMultiplexer(DiskDrive* drv[4]) { motor = false; side = false; selected = NO_DRIVE; drive[DRIVE_A] = drv[0]; drive[DRIVE_B] = drv[1]; drive[DRIVE_C] = drv[2]; drive[DRIVE_D] = drv[3]; drive[NO_DRIVE] = &dummyDrive; } void DriveMultiplexer::selectDrive(DriveNum num, EmuTime::param time) { if (selected != num) { drive[selected]->setMotor(false, time); selected = num; drive[selected]->setSide(side); drive[selected]->setMotor(motor, time); } } bool DriveMultiplexer::isDiskInserted() const { return drive[selected]->isDiskInserted(); } bool DriveMultiplexer::isWriteProtected() const { return drive[selected]->isWriteProtected(); } bool DriveMultiplexer::isDoubleSided() const { return drive[selected]->isDoubleSided(); } void DriveMultiplexer::setSide(bool side_) { side = side_; drive[selected]->setSide(side); } void DriveMultiplexer::step(bool direction, EmuTime::param time) { drive[selected]->step(direction, time); } bool DriveMultiplexer::isTrack00() const { return drive[selected]->isTrack00(); } void DriveMultiplexer::setMotor(bool status, EmuTime::param time) { motor = status; drive[selected]->setMotor(status, time); } bool DriveMultiplexer::indexPulse(EmuTime::param time) { return drive[selected]->indexPulse(time); } EmuTime DriveMultiplexer::getTimeTillIndexPulse(EmuTime::param time, int count) { return drive[selected]->getTimeTillIndexPulse(time, count); } void DriveMultiplexer::setHeadLoaded(bool status, EmuTime::param time) { drive[selected]->setHeadLoaded(status, time); } bool DriveMultiplexer::headLoaded(EmuTime::param time) { return drive[selected]->headLoaded(time); } void DriveMultiplexer::writeTrack(const RawTrack& track) { drive[selected]->writeTrack(track); } void DriveMultiplexer::readTrack(RawTrack& track) { drive[selected]->readTrack(track); } EmuTime DriveMultiplexer::getNextSector(EmuTime::param time, RawTrack& track, RawTrack::Sector& sector) { return drive[selected]->getNextSector(time, track, sector); } bool DriveMultiplexer::diskChanged() { return drive[selected]->diskChanged(); } bool DriveMultiplexer::peekDiskChanged() const { return drive[selected]->peekDiskChanged(); } bool DriveMultiplexer::isDummyDrive() const { return drive[selected]->isDummyDrive(); } static enum_string driveNumInfo[] = { { "A", DriveMultiplexer::DRIVE_A }, { "B", DriveMultiplexer::DRIVE_B }, { "C", DriveMultiplexer::DRIVE_C }, { "D", DriveMultiplexer::DRIVE_D }, { "none", DriveMultiplexer::NO_DRIVE } }; SERIALIZE_ENUM(DriveMultiplexer::DriveNum, driveNumInfo); template void DriveMultiplexer::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("selected", selected); ar.serialize("motor", motor); ar.serialize("side", side); } INSTANTIATE_SERIALIZE_METHODS(DriveMultiplexer); } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/DriveMultiplexer.hh000066400000000000000000000031161257557151200216100ustar00rootroot00000000000000#ifndef DRIVEMULTIPLEXER_HH #define DRIVEMULTIPLEXER_HH #include "DiskDrive.hh" namespace openmsx { /** * This class connects to a FDC as a normal DiskDrive and deligates all * requests to one of four other DiskDrives. */ class DriveMultiplexer final : public DiskDrive { public: enum DriveNum { DRIVE_A = 0, DRIVE_B = 1, DRIVE_C = 2, DRIVE_D = 3, NO_DRIVE = 4 }; // Multiplexer interface explicit DriveMultiplexer(DiskDrive* drive[4]); void selectDrive(DriveNum num, EmuTime::param time); // DiskDrive interface bool isDiskInserted() const override; bool isWriteProtected() const override; bool isDoubleSided() const override; bool isTrack00() const override; void setSide(bool side) override; void step(bool direction, EmuTime::param time) override; void setMotor(bool status, EmuTime::param time) override; bool indexPulse(EmuTime::param time) override; EmuTime getTimeTillIndexPulse(EmuTime::param time, int count) override; void setHeadLoaded(bool status, EmuTime::param time) override; bool headLoaded(EmuTime::param time) override; void writeTrack(const RawTrack& track) override; void readTrack ( RawTrack& track) override; EmuTime getNextSector(EmuTime::param time, RawTrack& track, RawTrack::Sector& sector) override; bool diskChanged() override; bool peekDiskChanged() const override; bool isDummyDrive() const override; template void serialize(Archive& ar, unsigned version); private: DummyDrive dummyDrive; DiskDrive* drive[5]; DriveNum selected; bool motor; bool side; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/DummyDisk.cc000066400000000000000000000010711257557151200201760ustar00rootroot00000000000000#include "DummyDisk.hh" #include "DiskExceptions.hh" namespace openmsx { DummyDisk::DummyDisk() : SectorBasedDisk(Filename("")) { setNbSectors(0); } bool DummyDisk::isDummyDisk() const { return true; } bool DummyDisk::isWriteProtectedImpl() const { return true; // TODO check } void DummyDisk::readSectorImpl(size_t /*sector*/, SectorBuffer& /*buf*/) { throw DriveEmptyException("No disk in drive"); } void DummyDisk::writeSectorImpl(size_t /*sector*/, const SectorBuffer& /*buf*/) { throw DriveEmptyException("No disk in drive"); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/DummyDisk.hh000066400000000000000000000007001257557151200202060ustar00rootroot00000000000000#ifndef FDCDUMMYBACKEND_HH #define FDCDUMMYBACKEND_HH #include "SectorBasedDisk.hh" namespace openmsx { class DummyDisk final : public SectorBasedDisk { public: DummyDisk(); bool isDummyDisk() const override; private: void readSectorImpl (size_t sector, SectorBuffer& buf) override; void writeSectorImpl(size_t sector, const SectorBuffer& buf) override; bool isWriteProtectedImpl() const override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/EmptyDiskPatch.cc000066400000000000000000000013461257557151200211660ustar00rootroot00000000000000#include "EmptyDiskPatch.hh" #include "SectorAccessibleDisk.hh" #include namespace openmsx { EmptyDiskPatch::EmptyDiskPatch(SectorAccessibleDisk& disk_) : disk(disk_) { } void EmptyDiskPatch::copyBlock(size_t src, byte* dst, size_t num) const { (void)num; assert(num == SectorAccessibleDisk::SECTOR_SIZE); assert((src % SectorAccessibleDisk::SECTOR_SIZE) == 0); auto& buf = *aligned_cast(dst); disk.readSectorImpl(src / SectorAccessibleDisk::SECTOR_SIZE, buf); } size_t EmptyDiskPatch::getSize() const { return disk.getNbSectors() * SectorAccessibleDisk::SECTOR_SIZE; } std::vector EmptyDiskPatch::getFilenames() const { // return {}; return std::vector(); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/EmptyDiskPatch.hh000066400000000000000000000010231257557151200211700ustar00rootroot00000000000000#ifndef EMPTYDISKPATCH_HH #define EMPTYDISKPATCH_HH #include "PatchInterface.hh" namespace openmsx { class SectorAccessibleDisk; class EmptyDiskPatch final : public PatchInterface { public: explicit EmptyDiskPatch(SectorAccessibleDisk& disk); void copyBlock(size_t src, byte* dst, size_t num) const override; size_t getSize() const override; std::vector getFilenames() const override; bool isEmptyPatch() const override { return true; } private: SectorAccessibleDisk& disk; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/MSXFDC.cc000066400000000000000000000042011257557151200172520ustar00rootroot00000000000000#include "MSXFDC.hh" #include "RealDrive.hh" #include "XMLElement.hh" #include "StringOp.hh" #include "MSXException.hh" #include "serialize.hh" #include "memory.hh" namespace openmsx { MSXFDC::MSXFDC(const DeviceConfig& config) : MSXDevice(config) , rom(getName() + " ROM", "rom", config) { bool singleSided = config.findChild("singlesided") != nullptr; int numDrives = config.getChildDataAsInt("drives", 1); if ((0 > numDrives) || (numDrives >= 4)) { throw MSXException(StringOp::Builder() << "Invalid number of drives: " << numDrives); } unsigned timeout = config.getChildDataAsInt("motor_off_timeout_ms", 0); const XMLElement* styleEl = config.findChild("connectionstyle"); bool signalsNeedMotorOn = !styleEl || (styleEl->getData() == "Philips"); EmuDuration motorTimeout = EmuDuration::msec(timeout); int i = 0; for ( ; i < numDrives; ++i) { drives[i] = make_unique( getMotherBoard(), motorTimeout, signalsNeedMotorOn, !singleSided); } for ( ; i < 4; ++i) { drives[i] = make_unique(); } } MSXFDC::~MSXFDC() { } void MSXFDC::powerDown(EmuTime::param time) { for (auto& drive : drives) { drive->setMotor(false, time); } } byte MSXFDC::readMem(word address, EmuTime::param /*time*/) { return *MSXFDC::getReadCacheLine(address); } byte MSXFDC::peekMem(word address, EmuTime::param /*time*/) const { return *MSXFDC::getReadCacheLine(address); } const byte* MSXFDC::getReadCacheLine(word start) const { return &rom[start & 0x3FFF]; } template void MSXFDC::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); // Drives are already constructed at this point, so we cannot use the // polymorphic object construction of the serialization framework. // Destroying and reconstructing the drives is not an option because // DriveMultiplexer already has pointers to the drives. char tag[7] = { 'd', 'r', 'i', 'v', 'e', 'X', 0 }; for (int i = 0; i < 4; ++i) { if (auto drive = dynamic_cast(drives[i].get())) { tag[5] = char('a' + i); ar.serialize(tag, *drive); } } } INSTANTIATE_SERIALIZE_METHODS(MSXFDC); } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/MSXFDC.hh000066400000000000000000000012531257557151200172700ustar00rootroot00000000000000#ifndef MSXFDC_HH #define MSXFDC_HH #include "MSXDevice.hh" #include "Rom.hh" #include namespace openmsx { class DiskDrive; class MSXFDC : public MSXDevice { public: void powerDown(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; const byte* getReadCacheLine(word start) const override; template void serialize(Archive& ar, unsigned version); protected: explicit MSXFDC(const DeviceConfig& config); ~MSXFDC(); Rom rom; std::unique_ptr drives[4]; }; REGISTER_BASE_NAME_HELPER(MSXFDC, "FDC"); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/MSXtar.cc000066400000000000000000000617221257557151200174570ustar00rootroot00000000000000// Note: For Mac OS X 10.3 must be included before . #include #ifndef _MSC_VER #include #else #include #endif #include "MSXtar.hh" #include "ReadDir.hh" #include "SectorAccessibleDisk.hh" #include "FileOperations.hh" #include "MSXException.hh" #include "StringOp.hh" #include "File.hh" #include #include #include #include #include using std::string; namespace openmsx { static const unsigned BAD_FAT = 0xFF7; static const unsigned EOF_FAT = 0xFFF; // actually 0xFF8-0xFFF, signals EOF in FAT12 static const unsigned SECTOR_SIZE = SectorAccessibleDisk::SECTOR_SIZE; static const byte T_MSX_REG = 0x00; // Normal file static const byte T_MSX_READ = 0x01; // Read-Only file static const byte T_MSX_HID = 0x02; // Hidden file static const byte T_MSX_SYS = 0x04; // System file static const byte T_MSX_VOL = 0x08; // filename is Volume Label static const byte T_MSX_DIR = 0x10; // entry is a subdir static const byte T_MSX_ARC = 0x20; // Archive bit // This particular combination of flags indicates that this dir entry is used // to store a long Unicode file name. // For details, read http://home.teleport.com/~brainy/lfn.htm static const byte T_MSX_LFN = 0x0F; // LFN entry (long files names) /** Transforms a clusternumber towards the first sector of this cluster * The calculation uses info read fom the bootsector */ unsigned MSXtar::clusterToSector(unsigned cluster) { return 1 + rootDirLast + sectorsPerCluster * (cluster - 2); } /** Transforms a sectornumber towards it containing cluster * The calculation uses info read fom the bootsector */ unsigned MSXtar::sectorToCluster(unsigned sector) { return 2 + ((sector - (1 + rootDirLast)) / sectorsPerCluster); } /** Initialize object variables by reading info from the bootsector */ void MSXtar::parseBootSector(const MSXBootSector& boot) { unsigned nbRootDirSectors = boot.dirEntries / 16; sectorsPerFat = boot.sectorsFat; sectorsPerCluster = boot.spCluster; if (boot.nrSectors == 0) { // TODO: check limits more accurately throw MSXException(StringOp::Builder() << "Illegal number of sectors: " << boot.nrSectors); } if (boot.nrSides == 0) { // TODO: check limits more accurately throw MSXException(StringOp::Builder() << "Illegal number of sides: " << boot.nrSides); } if (boot.nrFats == 0) { // TODO: check limits more accurately throw MSXException(StringOp::Builder() << "Illegal number of FATs: " << boot.nrFats); } if (sectorsPerFat == 0) { // TODO: check limits more accurately throw MSXException(StringOp::Builder() << "Illegal number sectors per FAT: " << sectorsPerFat); } if (nbRootDirSectors == 0) { // TODO: check limits more accurately throw MSXException(StringOp::Builder() << "Illegal number of root dir sectors: " << nbRootDirSectors); } if (sectorsPerCluster == 0) { // TODO: check limits more accurately throw MSXException(StringOp::Builder() << "Illegal number of sectors per cluster: " << sectorsPerCluster); } rootDirStart = 1 + boot.nrFats * sectorsPerFat; chrootSector = rootDirStart; rootDirLast = rootDirStart + nbRootDirSectors - 1; maxCluster = sectorToCluster(boot.nrSectors); // Some (invalid) diskimages have a too small FAT to be able to address // all clusters of the image. OpenMSX SVN revisions pre-11326 even // created such invalid images for some disk sizes!! unsigned maxFatCluster = (2 * SECTOR_SIZE * sectorsPerFat) / 3; maxCluster = std::min(maxCluster, maxFatCluster); } void MSXtar::writeLogicalSector(unsigned sector, const SectorBuffer& buf) { assert(!fatBuffer.empty()); unsigned fatSector = sector - 1; if (fatSector < sectorsPerFat) { // we have a cache and this is a sector of the 1st FAT // --> update cache memcpy(&fatBuffer[fatSector], &buf, sizeof(buf)); fatCacheDirty = true; } else { disk.writeSector(sector, buf); } } void MSXtar::readLogicalSector(unsigned sector, SectorBuffer& buf) { assert(!fatBuffer.empty()); unsigned fatSector = sector - 1; if (fatSector < sectorsPerFat) { // we have a cache and this is a sector of the 1st FAT // --> read from cache memcpy(&buf, &fatBuffer[fatSector], sizeof(buf)); } else { disk.readSector(sector, buf); } } MSXtar::MSXtar(SectorAccessibleDisk& sectordisk) : disk(sectordisk) { if (disk.getNbSectors() == 0) { throw MSXException("No disk inserted."); } try { SectorBuffer buf; disk.readSector(0, buf); parseBootSector(buf.bootSector); } catch (MSXException& e) { throw MSXException("Bad disk image: " + e.getMessage()); } // cache complete FAT fatCacheDirty = false; fatBuffer.resize(sectorsPerFat); for (unsigned i = 0; i < sectorsPerFat; ++i) { disk.readSector(i + 1, fatBuffer[i]); } } MSXtar::~MSXtar() { if (!fatCacheDirty) return; for (unsigned i = 0; i < sectorsPerFat; ++i) { try { disk.writeSector(i + 1, fatBuffer[i]); } catch (MSXException&) { // nothing } } } // transform BAD_FAT (0xFF7) and EOF_FAT-range (0xFF8-0xFFF) // to a single value: EOF_FAT (0xFFF) static unsigned normalizeFAT(unsigned cluster) { return (cluster < BAD_FAT) ? cluster : EOF_FAT; } // Get the next clusternumber from the FAT chain unsigned MSXtar::readFAT(unsigned clnr) const { assert(!fatBuffer.empty()); // FAT must already be cached auto* data = fatBuffer[0].raw; auto* p = &data[(clnr * 3) / 2]; unsigned result = (clnr & 1) ? (p[0] >> 4) + (p[1] << 4) : p[0] + ((p[1] & 0x0F) << 8); return normalizeFAT(result); } // Write an entry to the FAT void MSXtar::writeFAT(unsigned clnr, unsigned val) { assert(!fatBuffer.empty()); // FAT must already be cached assert(val < 4096); // FAT12 auto* data = fatBuffer[0].raw; auto* p = &data[(clnr * 3) / 2]; if (clnr & 1) { p[0] = (p[0] & 0x0F) + (val << 4); p[1] = val >> 4; } else { p[0] = val; p[1] = (p[1] & 0xF0) + ((val >> 8) & 0x0F); } fatCacheDirty = true; } // Find the next clusternumber marked as free in the FAT // @throws When no more free clusters unsigned MSXtar::findFirstFreeCluster() { for (unsigned cluster = 2; cluster < maxCluster; ++cluster) { if (readFAT(cluster) == 0) { return cluster; } } throw MSXException("Disk full."); } // Get the next sector from a file or (root/sub)directory // If no next sector then 0 is returned unsigned MSXtar::getNextSector(unsigned sector) { if (sector <= rootDirLast) { // sector is part of the root directory return (sector == rootDirLast) ? 0 : sector + 1; } unsigned currCluster = sectorToCluster(sector); if (currCluster == sectorToCluster(sector + 1)) { // next sector of cluster return sector + 1; } else { // first sector in next cluster unsigned nextcl = readFAT(currCluster); return (nextcl == EOF_FAT) ? 0 : clusterToSector(nextcl); } } // get start cluster from a directory entry, // also takes care of BAD_FAT and EOF_FAT-range. unsigned MSXtar::getStartCluster(const MSXDirEntry& entry) { return normalizeFAT(entry.startCluster); } // If there are no more free entries in a subdirectory, the subdir is // expanded with an extra cluster. This function gets the free cluster, // clears it and updates the fat for the subdir // returns: the first sector in the newly appended cluster // @throws When disk is full unsigned MSXtar::appendClusterToSubdir(unsigned sector) { unsigned nextCl = findFirstFreeCluster(); unsigned nextSector = clusterToSector(nextCl); // clear this cluster SectorBuffer buf; memset(&buf, 0, sizeof(buf)); for (unsigned i = 0; i < sectorsPerCluster; ++i) { writeLogicalSector(i + nextSector, buf); } unsigned curCl = sectorToCluster(sector); assert(readFAT(curCl) == EOF_FAT); writeFAT(curCl, nextCl); writeFAT(nextCl, EOF_FAT); return nextSector; } // Returns the index of a free (or with deleted file) entry // In: The current dir sector // Out: index number, if no index is found then -1 is returned unsigned MSXtar::findUsableIndexInSector(unsigned sector) { SectorBuffer buf; readLogicalSector(sector, buf); // find a not used (0x00) or delete entry (0xE5) for (unsigned i = 0; i < 16; ++i) { byte tmp = buf.dirEntry[i].filename[0]; if ((tmp == 0x00) || (tmp == 0xE5)) { return i; } } return unsigned(-1); } // This function returns the sector and dirindex for a new directory entry // if needed the involved subdirectroy is expanded by an extra cluster // returns: a DirEntry containing sector and index // @throws When either root dir is full or disk is full MSXtar::DirEntry MSXtar::addEntryToDir(unsigned sector) { // this routine adds the msxname to a directory sector, if needed (and // possible) the directory is extened with an extra cluster DirEntry result; result.sector = sector; if (sector <= rootDirLast) { // add to the root directory for (/* */ ; result.sector <= rootDirLast; result.sector++) { result.index = findUsableIndexInSector(result.sector); if (result.index != unsigned(-1)) { return result; } } throw MSXException("Root directory full."); } else { // add to a subdir while (true) { result.index = findUsableIndexInSector(result.sector); if (result.index != unsigned(-1)) { return result; } unsigned nextSector = getNextSector(result.sector); if (nextSector == 0) { nextSector = appendClusterToSubdir(result.sector); } result.sector = nextSector; } } } // create an MSX filename 8.3 format, if needed in vfat like abreviation static char toMSXChr(char a) { a = toupper(a); if (a == ' ' || a == '.') { a = '_'; } return a; } // Transform a long hostname in a 8.3 uppercase filename as used in the // direntries on an MSX static string makeSimpleMSXFileName(string_ref fullFilename) { string_ref dir, fullFile; StringOp::splitOnLast(fullFilename, '/', dir, fullFile); // handle speciale case '.' and '..' first string result(8 + 3, ' '); if ((fullFile == ".") || (fullFile == "..")) { memcpy(&*begin(result), fullFile.data(), fullFile.size()); return result; } string_ref file, ext; StringOp::splitOnLast(fullFile, '.', file, ext); if (file.empty()) std::swap(file, ext); StringOp::trimRight(file, ' '); StringOp::trimRight(ext, ' '); // put in major case and create '_' if needed string fileS(file.data(), std::min(8, file.size())); string extS (ext .data(), std::min(3, ext .size())); std::transform(begin(fileS), end(fileS), begin(fileS), toMSXChr); std::transform(begin(extS ), end(extS ), begin(extS ), toMSXChr); // add correct number of spaces memcpy(&*begin(result) + 0, fileS.data(), fileS.size()); memcpy(&*begin(result) + 8, extS .data(), extS .size()); return result; } // This function creates a new MSX subdir with given date 'd' and time 't' // in the subdir pointed at by 'sector'. In the newly // created subdir the entries for '.' and '..' are created // returns: the first sector of the new subdir // @throws in case no directory could be created unsigned MSXtar::addSubdir( const string& msxName, unsigned t, unsigned d, unsigned sector) { // returns the sector for the first cluster of this subdir DirEntry result = addEntryToDir(sector); // load the sector SectorBuffer buf; readLogicalSector(result.sector, buf); auto& dirEntry = buf.dirEntry[result.index]; dirEntry.attrib = T_MSX_DIR; dirEntry.time = t; dirEntry.date = d; memcpy(&dirEntry, makeSimpleMSXFileName(msxName).data(), 11); // dirEntry.filesize = fsize; unsigned curCl = findFirstFreeCluster(); dirEntry.startCluster = curCl; writeFAT(curCl, EOF_FAT); // save the sector again writeLogicalSector(result.sector, buf); // clear this cluster unsigned logicalSector = clusterToSector(curCl); memset(&buf, 0, sizeof(buf)); for (unsigned i = 0; i < sectorsPerCluster; ++i) { writeLogicalSector(i + logicalSector, buf); } // now add the '.' and '..' entries!! memset(&buf.dirEntry[0], 0, sizeof(MSXDirEntry)); memset(&buf.dirEntry[0], ' ', 11); memset(&buf.dirEntry[0], '.', 1); buf.dirEntry[0].attrib = T_MSX_DIR; buf.dirEntry[0].time = t; buf.dirEntry[0].date = d; buf.dirEntry[0].startCluster = curCl; memset(&buf.dirEntry[1], 0, sizeof(MSXDirEntry)); memset(&buf.dirEntry[1], ' ', 11); memset(&buf.dirEntry[1], '.', 2); buf.dirEntry[1].attrib = T_MSX_DIR; buf.dirEntry[1].time = t; buf.dirEntry[1].date = d; buf.dirEntry[1].startCluster = sectorToCluster(sector); // and save this in the first sector of the new subdir writeLogicalSector(logicalSector, buf); return logicalSector; } static void getTimeDate(time_t& totalSeconds, unsigned& time, unsigned& date) { tm* mtim = localtime(&totalSeconds); if (!mtim) { time = 0; date = 0; } else { time = (mtim->tm_sec >> 1) + (mtim->tm_min << 5) + (mtim->tm_hour << 11); date = mtim->tm_mday + ((mtim->tm_mon + 1) << 5) + ((mtim->tm_year + 1900 - 1980) << 9); } } // Get the time/date from a host file in MSX format static void getTimeDate(const string& filename, unsigned& time, unsigned& date) { struct stat st; if (stat(filename.c_str(), &st)) { // stat failed time = 0; date = 0; } else { // Some info indicates that st.st_mtime could be useless on win32 with vfat. // On Android 'st_mtime' is 'unsigned long' instead of 'time_t' // (like on linux), so we require a reinterpret_cast. That cast // is fine (but redundant) on linux. getTimeDate(reinterpret_cast(st.st_mtime), time, date); } } // Add an MSXsubdir with the time properties from the HOST-OS subdir // @throws when subdir could not be created unsigned MSXtar::addSubdirToDSK(const string& hostName, const string& msxName, unsigned sector) { unsigned time, date; getTimeDate(hostName, time, date); return addSubdir(msxName, time, date, sector); } // This file alters the filecontent of a given file // It only changes the file content (and the filesize in the msxDirEntry) // It doesn't changes timestamps nor filename, filetype etc. // @throws when something goes wrong void MSXtar::alterFileInDSK(MSXDirEntry& msxDirEntry, const string& hostName) { // get host file size struct stat st; if (stat(hostName.c_str(), &st)) { throw MSXException("Error reading host file: " + hostName); } unsigned hostSize = st.st_size; unsigned remaining = hostSize; // open host file for reading File file(FileOperations::expandTilde(hostName), "rb"); // copy host file to image unsigned prevCl = 0; unsigned curCl = getStartCluster(msxDirEntry); while (remaining) { // allocate new cluster if needed try { if ((curCl == 0) || (curCl == EOF_FAT)) { unsigned newCl = findFirstFreeCluster(); if (prevCl == 0) { msxDirEntry.startCluster = newCl; } else { writeFAT(prevCl, newCl); } writeFAT(newCl, EOF_FAT); curCl = newCl; } } catch (MSXException&) { // no more free clusters break; } // fill cluster unsigned logicalSector = clusterToSector(curCl); for (unsigned j = 0; (j < sectorsPerCluster) && remaining; ++j) { SectorBuffer buf; memset(&buf, 0, sizeof(buf)); unsigned chunkSize = std::min(SECTOR_SIZE, remaining); file.read(&buf, chunkSize); writeLogicalSector(logicalSector + j, buf); remaining -= chunkSize; } // advance to next cluster prevCl = curCl; curCl = readFAT(curCl); } // terminate FAT chain if (prevCl == 0) { msxDirEntry.startCluster = 0; } else { writeFAT(prevCl, EOF_FAT); } // free rest of FAT chain while ((curCl != EOF_FAT) && (curCl != 0)) { unsigned nextCl = readFAT(curCl); writeFAT(curCl, 0); curCl = nextCl; } // write (possibly truncated) file size msxDirEntry.size = hostSize - remaining; if (remaining) { throw MSXException("Disk full, " + hostName + " truncated."); } } // Find the dir entry for 'name' in subdir starting at the given 'sector' // with given 'index' // returns: a DirEntry with sector and index filled in // sector is 0 if no match was found MSXtar::DirEntry MSXtar::findEntryInDir( const string& name, unsigned sector, SectorBuffer& buf) { DirEntry result; result.sector = sector; result.index = 0; // avoid warning (only some gcc versions complain) while (result.sector) { // read sector and scan 16 entries readLogicalSector(result.sector, buf); for (result.index = 0; result.index < 16; ++result.index) { if (string(buf.dirEntry[result.index].filename, 11) == name) { return result; } } // try next sector result.sector = getNextSector(result.sector); } return result; } // Add file to the MSX disk in the subdir pointed to by 'sector' // @throws when file could not be added string MSXtar::addFileToDSK(const string& fullname, unsigned rootSector) { string_ref dir, hostName; StringOp::splitOnLast(fullname, "/\\", dir, hostName); string msxName = makeSimpleMSXFileName(hostName); // first find out if the filename already exists in current dir SectorBuffer dummy; DirEntry fullMsxDirEntry = findEntryInDir(msxName, rootSector, dummy); if (fullMsxDirEntry.sector != 0) { // TODO implement overwrite option return "Warning: preserving entry " + hostName + '\n'; } SectorBuffer buf; DirEntry entry = addEntryToDir(rootSector); readLogicalSector(entry.sector, buf); auto& dirEntry = buf.dirEntry[entry.index]; memset(&dirEntry, 0, sizeof(dirEntry)); memcpy(&dirEntry, msxName.data(), 11); dirEntry.attrib = T_MSX_REG; // compute time/date stamps unsigned time, date; getTimeDate(fullname, time, date); dirEntry.time = time; dirEntry.date = date; try { alterFileInDSK(dirEntry, fullname); } catch (MSXException&) { // still write directory entry writeLogicalSector(entry.sector, buf); throw; } writeLogicalSector(entry.sector, buf); return ""; } // Transfer directory and all its subdirectories to the MSX diskimage // @throws when an error occurs string MSXtar::recurseDirFill(string_ref dirName, unsigned sector) { string messages; ReadDir dir(dirName.str()); while (dirent* d = dir.getEntry()) { string name(d->d_name); string fullName = dirName + '/' + name; FileOperations::Stat st; if (!FileOperations::getStat(fullName, st)) { // ignore, normally this should not happen continue; } if (FileOperations::isRegularFile(st)) { // add new file messages += addFileToDSK(fullName, sector); } else if (FileOperations::isDirectory(st) && (name != ".") && (name != "..")) { string msxFileName = makeSimpleMSXFileName(name); SectorBuffer buf; DirEntry entry = findEntryInDir(msxFileName, sector, buf); if (entry.sector != 0) { // entry already exists .. auto& msxDirEntry = buf.dirEntry[entry.index]; if (msxDirEntry.attrib & T_MSX_DIR) { // .. and is a directory unsigned nextSector = clusterToSector( getStartCluster(msxDirEntry)); messages += recurseDirFill(fullName, nextSector); } else { // .. but is NOT a directory messages += "MSX file " + msxFileName + " is not a directory.\n"; } } else { // add new directory unsigned nextSector = addSubdirToDSK(fullName, name, sector); messages += recurseDirFill(fullName, nextSector); } } } return messages; } string MSXtar::condensName(const MSXDirEntry& dirEntry) { string result; for (unsigned i = 0; (i < 8) && (dirEntry.name.base[i] != ' '); ++i) { result += tolower(dirEntry.name.base[i]); } if (dirEntry.name.ext[0] != ' ') { result += '.'; for (unsigned i = 0; (i < 3) && (dirEntry.name.ext[i] != ' '); ++i) { result += tolower(dirEntry.name.ext[i]); } } return result; } // Set the entries from dirEntry to the timestamp of resultFile void MSXtar::changeTime(const string& resultFile, const MSXDirEntry& dirEntry) { unsigned t = dirEntry.time; unsigned d = dirEntry.date; struct tm mtim; struct utimbuf utim; mtim.tm_sec = (t & 0x001f) << 1; mtim.tm_min = (t & 0x07e0) >> 5; mtim.tm_hour = (t & 0xf800) >> 11; mtim.tm_mday = (d & 0x001f); mtim.tm_mon = ((d & 0x01e0) >> 5) - 1; mtim.tm_year = ((d & 0xfe00) >> 9) + 80; mtim.tm_isdst = -1; utim.actime = mktime(&mtim); utim.modtime = mktime(&mtim); utime(resultFile.c_str(), &utim); } string MSXtar::dir() { StringOp::Builder result; for (unsigned sector = chrootSector; sector != 0; sector = getNextSector(sector)) { SectorBuffer buf; readLogicalSector(sector, buf); for (unsigned i = 0; i < 16; ++i) { auto& dirEntry = buf.dirEntry[i]; if ((dirEntry.filename[0] == char(0xe5)) || (dirEntry.filename[0] == char(0x00)) || (dirEntry.attrib == T_MSX_LFN)) continue; // filename first (in condensed form for human readablitly) string tmp = condensName(dirEntry); tmp.resize(13, ' '); result << tmp; // attributes result << (dirEntry.attrib & T_MSX_DIR ? 'd' : '-') << (dirEntry.attrib & T_MSX_READ ? 'r' : '-') << (dirEntry.attrib & T_MSX_HID ? 'h' : '-') << (dirEntry.attrib & T_MSX_VOL ? 'v' : '-') // TODO check if this is the output of files,l << (dirEntry.attrib & T_MSX_ARC ? 'a' : '-') // TODO check if this is the output of files,l << " "; // filesize result << dirEntry.size << '\n'; } } return result; } // routines to update the global vars: chrootSector void MSXtar::chdir(string_ref newRootDir) { chroot(newRootDir, false); } void MSXtar::mkdir(string_ref newRootDir) { unsigned tmpMSXchrootSector = chrootSector; chroot(newRootDir, true); chrootSector = tmpMSXchrootSector; } void MSXtar::chroot(string_ref newRootDir, bool createDir) { if (newRootDir.starts_with("/") || newRootDir.starts_with("\\")) { // absolute path, reset chrootSector chrootSector = rootDirStart; StringOp::trimLeft(newRootDir, "/\\"); } while (!newRootDir.empty()) { string_ref firstPart, lastPart; StringOp::splitOnFirst(newRootDir, "/\\", firstPart, lastPart); newRootDir = lastPart; StringOp::trimLeft(newRootDir, "/\\"); // find 'firstPart' directory or create it if requested SectorBuffer buf; string simple = makeSimpleMSXFileName(firstPart); DirEntry entry = findEntryInDir(simple, chrootSector, buf); if (entry.sector == 0) { if (!createDir) { throw MSXException("Subdirectory " + firstPart + " not found."); } // creat new subdir time_t now; time(&now); unsigned t, d; getTimeDate(now, t, d); chrootSector = addSubdir(simple, t, d, chrootSector); } else { auto& dirEntry = buf.dirEntry[entry.index]; if (!(dirEntry.attrib & T_MSX_DIR)) { throw MSXException(firstPart + " is not a directory."); } chrootSector = clusterToSector(getStartCluster(dirEntry)); } } } void MSXtar::fileExtract(const string& resultFile, const MSXDirEntry& dirEntry) { unsigned size = dirEntry.size; unsigned sector = clusterToSector(getStartCluster(dirEntry)); File file(FileOperations::expandTilde(resultFile), "wb"); while (size && sector) { SectorBuffer buf; readLogicalSector(sector, buf); unsigned savesize = std::min(size, SECTOR_SIZE); file.write(&buf, savesize); size -= savesize; sector = getNextSector(sector); } // now change the access time changeTime(resultFile, dirEntry); } // extracts a single item (file or directory) from the msximage to the host OS string MSXtar::singleItemExtract(string_ref dirName, string_ref itemName, unsigned sector) { // first find out if the filename exists in current dir SectorBuffer buf; string msxName = makeSimpleMSXFileName(itemName); DirEntry entry = findEntryInDir(msxName, sector, buf); if (entry.sector == 0) { return itemName + " not found!\n"; } auto& msxDirEntry = buf.dirEntry[entry.index]; // create full name for loacl filesystem string fullName = dirName + '/' + condensName(msxDirEntry); // ...and extract if (msxDirEntry.attrib & T_MSX_DIR) { // recursive extract this subdir FileOperations::mkdirp(fullName); recurseDirExtract( fullName, clusterToSector(getStartCluster(msxDirEntry))); } else { // it is a file fileExtract(fullName, msxDirEntry); } return ""; } // extracts the contents of the directory (at sector) and all its subdirs to the host OS void MSXtar::recurseDirExtract(string_ref dirName, unsigned sector) { for (/* */ ; sector != 0; sector = getNextSector(sector)) { SectorBuffer buf; readLogicalSector(sector, buf); for (unsigned i = 0; i < 16; ++i) { auto& dirEntry = buf.dirEntry[i]; if ((dirEntry.filename[0] == char(0xe5)) || (dirEntry.filename[0] == char(0x00)) || (dirEntry.filename[0] == '.')) continue; string filename = condensName(dirEntry); string fullName = filename; if (!dirName.empty()) { fullName = dirName + '/' + filename; } if (dirEntry.attrib != T_MSX_DIR) { // TODO fileExtract(fullName, dirEntry); } if (dirEntry.attrib == T_MSX_DIR) { FileOperations::mkdirp(fullName); // now change the access time changeTime(fullName, dirEntry); recurseDirExtract( fullName, clusterToSector(getStartCluster(dirEntry))); } } } } string MSXtar::addDir(string_ref rootDirName) { return recurseDirFill(rootDirName, chrootSector); } string MSXtar::addFile(const string& filename) { return addFileToDSK(filename, chrootSector); } string MSXtar::getItemFromDir(string_ref rootDirName, string_ref itemName) { return singleItemExtract(rootDirName, itemName, chrootSector); } void MSXtar::getDir(string_ref rootDirName) { recurseDirExtract(rootDirName, chrootSector); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/MSXtar.hh000066400000000000000000000057021257557151200174650ustar00rootroot00000000000000// This code implements the functionality of my older msxtar program // that could manipulate files and directories on dsk and ide-hd images // Integrating it is seen as temporary bypassing of the need for a // DirAsDisk2 that supports subdirs, partitions etc. since this class will // of those functionalities although not on a dynamic base #ifndef MSXTAR_HH #define MSXTAR_HH #include "MemBuffer.hh" #include "DiskImageUtils.hh" #include "noncopyable.hh" #include "string_ref.hh" namespace openmsx { class SectorAccessibleDisk; class MSXtar : private noncopyable { public: explicit MSXtar(SectorAccessibleDisk& disk); ~MSXtar(); void chdir(string_ref newRootDir); void mkdir(string_ref newRootDir); std::string dir(); std::string addFile(const std::string& filename); std::string addDir(string_ref rootDirName); std::string getItemFromDir(string_ref rootDirName, string_ref itemName); void getDir(string_ref rootDirName); private: struct DirEntry { unsigned sector; unsigned index; }; void writeLogicalSector(unsigned sector, const SectorBuffer& buf); void readLogicalSector (unsigned sector, SectorBuffer& buf); unsigned clusterToSector(unsigned cluster); unsigned sectorToCluster(unsigned sector); void parseBootSector(const MSXBootSector& boot); unsigned readFAT(unsigned clnr) const; void writeFAT(unsigned clnr, unsigned val); unsigned findFirstFreeCluster(); unsigned findUsableIndexInSector(unsigned sector); unsigned getNextSector(unsigned sector); unsigned getStartCluster(const MSXDirEntry& entry); unsigned appendClusterToSubdir(unsigned sector); DirEntry addEntryToDir(unsigned sector); unsigned addSubdir(const std::string& msxName, unsigned t, unsigned d, unsigned sector); void alterFileInDSK(MSXDirEntry& msxDirEntry, const std::string& hostName); unsigned addSubdirToDSK(const std::string& hostName, const std::string& msxName, unsigned sector); DirEntry findEntryInDir(const std::string& name, unsigned sector, SectorBuffer& sectorBuf); std::string addFileToDSK(const std::string& hostName, unsigned sector); std::string recurseDirFill(string_ref dirName, unsigned sector); std::string condensName(const MSXDirEntry& dirEntry); void changeTime (const std::string& resultFile, const MSXDirEntry& dirEntry); void fileExtract(const std::string& resultFile, const MSXDirEntry& dirEntry); void recurseDirExtract(string_ref dirName, unsigned sector); std::string singleItemExtract(string_ref dirName, string_ref itemName, unsigned sector); void chroot(string_ref newRootDir, bool createDir); SectorAccessibleDisk& disk; MemBuffer fatBuffer; unsigned maxCluster; unsigned sectorsPerCluster; unsigned sectorsPerFat; unsigned rootDirStart; // first sector from the root directory unsigned rootDirLast; // last sector from the root directory unsigned chrootSector; bool fatCacheDirty; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/MicrosolFDC.cc000066400000000000000000000060651257557151200204040ustar00rootroot00000000000000#include "MicrosolFDC.hh" #include "DriveMultiplexer.hh" #include "WD2793.hh" #include "serialize.hh" namespace openmsx { MicrosolFDC::MicrosolFDC(const DeviceConfig& config) : WD2793BasedFDC(config) { } byte MicrosolFDC::readIO(word port, EmuTime::param time) { byte value; switch (port & 0x07) { case 0: value = controller.getStatusReg(time); break; case 1: value = controller.getTrackReg(time); break; case 2: value = controller.getSectorReg(time); break; case 3: value = controller.getDataReg(time); break; case 4: value = 0x7F; if (controller.getIRQ(time)) value |= 0x80; if (controller.getDTRQ(time)) value &= ~0x40; break; default: value = 255; break; } return value; } byte MicrosolFDC::peekIO(word port, EmuTime::param time) const { byte value; switch (port & 0x07) { case 0: value = controller.peekStatusReg(time); break; case 1: value = controller.peekTrackReg(time); break; case 2: value = controller.peekSectorReg(time); break; case 3: value = controller.peekDataReg(time); break; case 4: value = 0x7F; if (controller.peekIRQ(time)) value |= 0x80; if (controller.peekDTRQ(time)) value &= ~0x40; break; default: value = 255; break; } return value; } void MicrosolFDC::writeIO(word port, byte value, EmuTime::param time) { switch (port & 0x07) { case 0: controller.setCommandReg(value, time); break; case 1: controller.setTrackReg(value, time); break; case 2: controller.setSectorReg(value, time); break; case 3: controller.setDataReg(value, time); break; case 4: // From Ricardo Bittencourt // bit 0: drive select A // bit 1: drive select B // bit 2: drive select C // bit 3: drive select D // bit 4: side select // bit 5: turn on motor // bit 6: enable waitstates // bit 7: density: 0=single 1=double // // When you enable a drive select bit, the led on the // disk-drive turns on. Since this was used as user feedback, // in things such as "replace disk 1 when the led turns off" // we need to connect this to the OSD later on. // Set correct drive DriveMultiplexer::DriveNum drive; switch (value & 0x0F) { case 1: drive = DriveMultiplexer::DRIVE_A; break; case 2: drive = DriveMultiplexer::DRIVE_B; break; case 4: drive = DriveMultiplexer::DRIVE_C; break; case 8: drive = DriveMultiplexer::DRIVE_D; break; default: // No drive selected or two drives at same time // The motor is enabled for all drives at the same time, so // in a real machine you must take care to do not select more // than one drive at the same time (you could get data // collision). drive = DriveMultiplexer::NO_DRIVE; } multiplexer.selectDrive(drive, time); multiplexer.setSide((value & 0x10) != 0); multiplexer.setMotor((value & 0x20) != 0, time); break; } } template void MicrosolFDC::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); } INSTANTIATE_SERIALIZE_METHODS(MicrosolFDC); REGISTER_MSXDEVICE(MicrosolFDC, "MicrosolFDC"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/MicrosolFDC.hh000066400000000000000000000007701257557151200204130ustar00rootroot00000000000000#ifndef MICROSOLFDC_HH #define MICROSOLFDC_HH #include "WD2793BasedFDC.hh" namespace openmsx { class MicrosolFDC final : public WD2793BasedFDC { public: explicit MicrosolFDC(const DeviceConfig& config); byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/NationalFDC.cc000066400000000000000000000067361257557151200203670ustar00rootroot00000000000000#include "NationalFDC.hh" #include "CacheLine.hh" #include "DriveMultiplexer.hh" #include "WD2793.hh" #include "serialize.hh" namespace openmsx { NationalFDC::NationalFDC(const DeviceConfig& config) : WD2793BasedFDC(config) { } byte NationalFDC::readMem(word address, EmuTime::param time) { byte value; switch (address & 0x3FC7) { case 0x3F80: value = controller.getStatusReg(time); break; case 0x3F81: value = controller.getTrackReg(time); break; case 0x3F82: value = controller.getSectorReg(time); break; case 0x3F83: value = controller.getDataReg(time); break; case 0x3F84: case 0x3F85: case 0x3F86: case 0x3F87: value = 0x7F; if (controller.getIRQ(time)) value |= 0x80; if (controller.getDTRQ(time)) value &= ~0x40; break; default: value = NationalFDC::peekMem(address, time); break; } return value; } byte NationalFDC::peekMem(word address, EmuTime::param time) const { byte value; // According to atarulum: // 7FBC is mirrored in 7FBC - 7FBF // 7FB8 - 7FBF is mirrored in 7F80 - 7FBF switch (address & 0x3FC7) { case 0x3F80: value = controller.peekStatusReg(time); break; case 0x3F81: value = controller.peekTrackReg(time); break; case 0x3F82: value = controller.peekSectorReg(time); break; case 0x3F83: value = controller.peekDataReg(time); break; case 0x3F84: case 0x3F85: case 0x3F86: case 0x3F87: // Drive control IRQ and DRQ lines are not connected to Z80 interrupt request // bit 7: intrq // bit 6: !dtrq // other bits read 1 value = 0x7F; if (controller.peekIRQ(time)) value |= 0x80; if (controller.peekDTRQ(time)) value &= ~0x40; break; default: if (address < 0x8000) { // ROM only visible in 0x0000-0x7FFF value = MSXFDC::peekMem(address, time); } else { value = 255; } break; } return value; } const byte* NationalFDC::getReadCacheLine(word start) const { if ((start & 0x3FC0 & CacheLine::HIGH) == (0x3F80 & CacheLine::HIGH)) { // FDC at 0x7FB8-0x7FBC (also mirrored) return nullptr; } else if (start < 0x8000) { // ROM at 0x0000-0x7FFF return MSXFDC::getReadCacheLine(start); } else { return unmappedRead; } } void NationalFDC::writeMem(word address, byte value, EmuTime::param time) { switch (address & 0x3FC7) { case 0x3F80: controller.setCommandReg(value, time); break; case 0x3F81: controller.setTrackReg(value, time); break; case 0x3F82: controller.setSectorReg(value, time); break; case 0x3F83: controller.setDataReg(value, time); break; case 0x3F84: case 0x3F85: case 0x3F86: case 0x3F87: // bit 0 -> select drive 0 // bit 1 -> select drive 1 // bit 2 -> side select // bit 3 -> motor on DriveMultiplexer::DriveNum drive; switch (value & 3) { case 1: drive = DriveMultiplexer::DRIVE_A; break; case 2: drive = DriveMultiplexer::DRIVE_B; break; default: drive = DriveMultiplexer::NO_DRIVE; } multiplexer.selectDrive(drive, time); multiplexer.setSide((value & 0x04) != 0); multiplexer.setMotor((value & 0x08) != 0, time); break; } } byte* NationalFDC::getWriteCacheLine(word address) const { if ((address & 0x3FC0) == (0x3F80 & CacheLine::HIGH)) { // FDC at 0x7FB8-0x7FBC (also mirrored) return nullptr; } else { return unmappedWrite; } } template void NationalFDC::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); } INSTANTIATE_SERIALIZE_METHODS(NationalFDC); REGISTER_MSXDEVICE(NationalFDC, "NationalFDC"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/NationalFDC.hh000066400000000000000000000011651257557151200203700ustar00rootroot00000000000000#ifndef NATIONALFDC_HH #define NATIONALFDC_HH #include "WD2793BasedFDC.hh" namespace openmsx { class NationalFDC final : public WD2793BasedFDC { public: explicit NationalFDC(const DeviceConfig& config); byte readMem(word address, EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/NowindCommand.cc000066400000000000000000000246471257557151200210430ustar00rootroot00000000000000#include "NowindCommand.hh" #include "NowindRomDisk.hh" #include "NowindInterface.hh" #include "DiskChanger.hh" #include "DSKDiskImage.hh" #include "DiskPartition.hh" #include "FileContext.hh" #include "StringOp.hh" #include "FileOperations.hh" #include "CommandException.hh" #include "TclObject.hh" #include "array_ref.hh" #include "memory.hh" #include "unreachable.hh" #include using std::unique_ptr; using std::set; using std::string; using std::vector; namespace openmsx { NowindCommand::NowindCommand(const string& basename, CommandController& commandController, NowindInterface& interface_) : Command(commandController, basename) , interface(interface_) { } unique_ptr NowindCommand::createDiskChanger( const string& basename, unsigned n, MSXMotherBoard& motherBoard) const { string name = StringOp::Builder() << basename << n + 1; return make_unique(motherBoard, name, false, true); } unsigned NowindCommand::searchRomdisk(const NowindHost::Drives& drives) const { for (unsigned i = 0; i < drives.size(); ++i) { if (drives[i]->isRomdisk()) { return i; } } return 255; } void NowindCommand::processHdimage( string_ref hdimage, NowindHost::Drives& drives) const { MSXMotherBoard& motherboard = interface.getMotherBoard(); // Possible formats are: // or : // Though itself can contain ':' characters. To solve this // disambiguity we will always interpret the string as if // it is an existing filename. set partitions; auto pos = hdimage.find_last_of(':'); if ((pos != string::npos) && !FileOperations::exists(hdimage)) { partitions = StringOp::parseRange( hdimage.substr(pos + 1), 1, 31); } auto wholeDisk = std::make_shared(Filename(hdimage.str())); bool failOnError = true; if (partitions.empty()) { // insert all partitions failOnError = false; for (unsigned i = 1; i <= 31; ++i) { partitions.insert(i); } } for (auto& p : partitions) { try { // Explicit conversion to shared_ptr is // for some reason needed in 32-bit vs2013 build (not in 64-bit // and not in vs2012, nor gcc/clang). Compiler bug??? auto partition = make_unique( *wholeDisk, p, std::shared_ptr(wholeDisk)); auto drive = createDiskChanger( interface.basename, unsigned(drives.size()), motherboard); drive->changeDisk(unique_ptr(std::move(partition))); drives.push_back(std::move(drive)); } catch (MSXException&) { if (failOnError) throw; } } } void NowindCommand::execute(array_ref tokens, TclObject& result) { auto& host = interface.host; auto& drives = interface.drives; unsigned oldRomdisk = searchRomdisk(drives); if (tokens.size() == 1) { // no arguments, show general status assert(!drives.empty()); StringOp::Builder r; for (unsigned i = 0; i < drives.size(); ++i) { r << "nowind" << i + 1 << ": "; if (dynamic_cast(drives[i].get())) { r << "romdisk\n"; } else if (auto changer = dynamic_cast( drives[i].get())) { string filename = changer->getDiskName().getOriginal(); r << (filename.empty() ? "--empty--" : filename) << '\n'; } else { UNREACHABLE; } } r << "phantom drives: " << (host.getEnablePhantomDrives() ? "enabled" : "disabled") << '\n'; r << "allow other diskroms: " << (host.getAllowOtherDiskroms() ? "yes" : "no") << '\n'; result.setString(r); return; } // first parse complete commandline and store state in these local vars bool enablePhantom = false; bool disablePhantom = false; bool allowOther = false; bool disallowOther = false; bool changeDrives = false; unsigned romdisk = 255; NowindHost::Drives tmpDrives; string error; // actually parse the commandline array_ref args(&tokens[1], tokens.size() - 1); while (error.empty() && !args.empty()) { bool createDrive = false; string_ref image; string_ref arg = args.front().getString(); args.pop_front(); if ((arg == "--ctrl") || (arg == "-c")) { enablePhantom = false; disablePhantom = true; } else if ((arg == "--no-ctrl") || (arg == "-C")) { enablePhantom = true; disablePhantom = false; } else if ((arg == "--allow") || (arg == "-a")) { allowOther = true; disallowOther = false; } else if ((arg == "--no-allow") || (arg == "-A")) { allowOther = false; disallowOther = true; } else if ((arg == "--romdisk") || (arg == "-j")) { if (romdisk != 255) { error = "Can only have one romdisk"; } else { romdisk = unsigned(tmpDrives.size()); tmpDrives.push_back(make_unique()); changeDrives = true; } } else if ((arg == "--image") || (arg == "-i")) { if (args.empty()) { error = "Missing argument for option: " + arg; } else { image = args.front().getString(); args.pop_front(); createDrive = true; } } else if ((arg == "--hdimage") || (arg == "-m")) { if (args.empty()) { error = "Missing argument for option: " + arg; } else { try { string_ref hdimage = args.front().getString(); args.pop_front(); processHdimage(hdimage, tmpDrives); changeDrives = true; } catch (MSXException& e) { error = e.getMessage(); } } } else { // everything else is interpreted as an image name image = arg; createDrive = true; } if (createDrive) { auto drive = createDiskChanger( interface.basename, unsigned(tmpDrives.size()), interface.getMotherBoard()); changeDrives = true; if (!image.empty()) { if (drive->insertDisk(image)) { error = "Invalid disk image: " + image; } } tmpDrives.push_back(std::move(drive)); } } if (tmpDrives.size() > 8) { error = "Can't have more than 8 drives"; } // if there was no error, apply the changes bool optionsChanged = false; if (error.empty()) { if (enablePhantom && !host.getEnablePhantomDrives()) { host.setEnablePhantomDrives(true); optionsChanged = true; } if (disablePhantom && host.getEnablePhantomDrives()) { host.setEnablePhantomDrives(false); optionsChanged = true; } if (allowOther && !host.getAllowOtherDiskroms()) { host.setAllowOtherDiskroms(true); optionsChanged = true; } if (disallowOther && host.getAllowOtherDiskroms()) { host.setAllowOtherDiskroms(false); optionsChanged = true; } if (changeDrives) { std::swap(tmpDrives, drives); } } // cleanup tmpDrives, this contains either // - the old drives (when command was successful) // - the new drives (when there was an error) auto prevSize = tmpDrives.size(); tmpDrives.clear(); for (auto& d : drives) { if (auto disk = dynamic_cast(d.get())) { disk->createCommand(); } } if (!error.empty()) { throw CommandException(error); } // calculate result string string r; if (changeDrives && (prevSize != drives.size())) { r += "Number of drives changed. "; } if (changeDrives && (romdisk != oldRomdisk)) { if (oldRomdisk == 255) { r += "Romdisk added. "; } else if (romdisk == 255) { r += "Romdisk removed. "; } else { r += "Romdisk changed position. "; } } if (optionsChanged) { r += "Boot options changed. "; } if (!r.empty()) { r += "You may need to reset the MSX for the changes to take effect."; } result.setString(r); } string NowindCommand::help(const vector& /*tokens*/) const { return "Similar to the disk commands there is a nowind command " "for each nowind interface. This command is modeled after the " "'usbhost' command of the real nowind interface. Though only a " "subset of the options is supported. Here's a short overview.\n" "\n" "Command line options\n" " long short explanation\n" "--image -i specify disk image\n" "--hdimage -m specify harddisk image\n" "--romdisk -j enable romdisk\n" // "--flash -f update firmware\n" "--ctrl -c no phantom disks\n" "--no-ctrl -C enable phantom disks\n" "--allow -a allow other diskroms to initialize\n" "--no-allow -A don't allow other diskroms to initialize\n" //"--dsk2rom -z converts a 360kB disk to romdisk.bin\n" //"--debug -d enable libnowind debug info\n" //"--test -t testmode\n" //"--help -h help message\n" "\n" "If you don't pass any arguments to this command, you'll get " "an overview of the current nowind status.\n" "\n" "This command will create a certain amount of drives on the " "nowind interface and (optionally) insert diskimages in those " "drives. For each of these drives there will also be a " "'nowind<1..8>' command created. Those commands are similar to " "e.g. the diska command. They can be used to access the more " "advanced diskimage insertion options. See 'help nowind<1..8>' " "for details.\n" "\n" "In some cases it is needed to reboot the MSX before the " "changes take effect. In those cases you'll get a message " "that warns about this.\n" "\n" "Examples:\n" "nowinda -a image.dsk -j Image.dsk is inserted into drive A: and the romdisk\n" " will be drive B:. Other diskroms will be able to\n" " install drives as well. For example when the MSX has\n" " an internal diskdrive, drive C: en D: will be\n" " available as well.\n" "nowinda disk1.dsk disk2.dsk The two images will be inserted in A: and B:\n" " respectively.\n" "nowinda -m hdimage.dsk Inserts a harddisk image. All available partitions\n" " will be mounted as drives.\n" "nowinda -m hdimage.dsk:1 Inserts the first partition only.\n" "nowinda -m hdimage.dsk:2-4 Inserts the 2nd, 3th and 4th partition as drive A:\n" " B: and C:.\n"; } void NowindCommand::tabCompletion(vector& tokens) const { static const char* const extra[] = { "-c", "--ctrl", "-C", "--no-ctrl", "-a", "--allow", "-A", "--no-allow", "-j", "--romdisk", "-i", "--image", "-m", "--hdimage", }; completeFileName(tokens, userFileContext(), extra); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/NowindCommand.hh000066400000000000000000000017551257557151200210500ustar00rootroot00000000000000#ifndef NOWINDCOMMAND_HH #define NOWINDCOMMAND_HH #include "NowindHost.hh" #include "Command.hh" #include "string_ref.hh" #include namespace openmsx { class NowindInterface; class DiskChanger; class MSXMotherBoard; class NowindCommand final : public Command { public: NowindCommand(const std::string& basename, CommandController& commandController, NowindInterface& interface); void execute(array_ref tokens, TclObject& result) override; std::string help(const std::vector& tokens) const override; void tabCompletion(std::vector& tokens) const override; std::unique_ptr createDiskChanger( const std::string& basename, unsigned n, MSXMotherBoard& motherBoard) const; private: unsigned searchRomdisk(const NowindHost::Drives& drives) const; void processHdimage(string_ref hdimage, NowindHost::Drives& drives) const; NowindInterface& interface; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/NowindHost.cc000066400000000000000000000451601257557151200203730ustar00rootroot00000000000000#include "NowindHost.hh" #include "DiskContainer.hh" #include "SectorAccessibleDisk.hh" #include "serialize.hh" #include "serialize_stl.hh" #include "unreachable.hh" #include "memory.hh" #include #include #include #include #include #include #include using std::string; using std::vector; using std::fstream; using std::ios; namespace openmsx { static const unsigned SECTOR_SIZE = sizeof(SectorBuffer); static void DBERR(const char* message, ...) { (void)message; #if 0 va_list args; va_start(args, message); printf("nowind: "); vprintf(message, args); va_end(args); #endif } NowindHost::NowindHost(const Drives& drives_) : drives(drives_) , lastTime(0) , state(STATE_SYNC1) , romdisk(255) , allowOtherDiskroms(false) , enablePhantomDrives(true) { } NowindHost::~NowindHost() { } byte NowindHost::peek() const { return isDataAvailable() ? hostToMsxFifo.front() : 0xFF; } // receive: msx <- pc byte NowindHost::read() { return isDataAvailable() ? hostToMsxFifo.pop_front() : 0xFF; } bool NowindHost::isDataAvailable() const { return !hostToMsxFifo.empty(); } // send: msx -> pc void NowindHost::write(byte data, unsigned time) { unsigned duration = time - lastTime; lastTime = time; if (duration >= 500) { // timeout (500ms), start looking for AF05 purge(); state = STATE_SYNC1; } switch (state) { case STATE_SYNC1: if (data == 0xAF) state = STATE_SYNC2; break; case STATE_SYNC2: switch (data) { case 0x05: state = STATE_COMMAND; recvCount = 0; break; case 0xAF: state = STATE_SYNC2; break; case 0xFF: state = STATE_SYNC1; msxReset(); break; default: state = STATE_SYNC1; break; } break; case STATE_COMMAND: assert(recvCount < 9); cmdData[recvCount] = data; if (++recvCount == 9) { executeCommand(); } break; case STATE_DISKREAD: assert(recvCount < 2); extraData[recvCount] = data; if (++recvCount == 2) { doDiskRead2(); } break; case STATE_DISKWRITE: assert(recvCount < (transferSize + 2)); extraData[recvCount] = data; if (++recvCount == (transferSize + 2)) { doDiskWrite2(); } break; case STATE_DEVOPEN: assert(recvCount < 11); extraData[recvCount] = data; if (++recvCount == 11) { deviceOpen(); } break; case STATE_IMAGE: assert(recvCount < 40); extraData[recvCount] = data; if ((data == 0) || (data == ':') || (++recvCount == 40)) { auto data = reinterpret_cast(extraData); callImage(string(data, recvCount)); state = STATE_SYNC1; } break; case STATE_MESSAGE: assert(recvCount < (240 - 1)); extraData[recvCount] = data; if ((data == 0) || (++recvCount == (240 - 1))) { extraData[recvCount] = 0; DBERR("%s\n", reinterpret_cast(extraData)); state = STATE_SYNC1; } break; default: UNREACHABLE; } } void NowindHost::msxReset() { for (auto& dev : devices) { dev.fs.reset(); } DBERR("MSX reset\n"); } SectorAccessibleDisk* NowindHost::getDisk() const { byte num = cmdData[7]; // reg_a if (num >= drives.size()) { return nullptr; } return drives[num]->getSectorAccessibleDisk(); } void NowindHost::executeCommand() { assert(recvCount == 9); byte cmd = cmdData[8]; switch (cmd) { //case 0x0D: BDOS_0DH_DiskReset(); //case 0x0F: BDOS_0FH_OpenFile(); //case 0x10: BDOS_10H_CloseFile(); //case 0x11: BDOS_11H_FindFirst(); //case 0x12: BDOS_12H_FindNext(); //case 0x13: BDOS_13H_DeleteFile(); //case 0x14: BDOS_14H_ReadSeq(); //case 0x15: BDOS_15H_WriteSeq(); //case 0x16: BDOS_16H_CreateFile(); //case 0x17: BDOS_17H_RenameFile(); //case 0x21: BDOS_21H_ReadRandomFile(); //case 0x22: BDOS_22H_WriteRandomFile(); //case 0x23: BDOS_23H_GetFileSize(); //case 0x24: BDOS_24H_SetRandomRecordField(); //case 0x26: BDOS_26H_WriteRandomBlock(); //case 0x27: BDOS_27H_ReadRandomBlock(); //case 0x28: BDOS_28H_WriteRandomFileWithZeros(); //case 0x2A: BDOS_2AH_GetDate(); //case 0x2B: BDOS_2BH_SetDate(); //case 0x2C: BDOS_2CH_GetTime(); //case 0x2D: BDOS_2DH_SetTime(); //case 0x2E: BDOS_2EH_Verify(); //case 0x2F: BDOS_2FH_ReadLogicalSector(); //case 0x30: BDOS_30H_WriteLogicalSector(); case 0x80: { // DSKIO auto* disk = getDisk(); if (!disk) { // no such drive or no disk inserted // (causes a timeout on the MSX side) state = STATE_SYNC1; return; } byte reg_f = cmdData[6]; if (reg_f & 1) { // carry flag diskWriteInit(*disk); } else { diskReadInit(*disk); } break; } case 0x81: DSKCHG(); state = STATE_SYNC1; break; //case 0x82: GETDPB(); //case 0x83: CHOICE(); //case 0x84: DSKFMT(); case 0x85: DRIVES(); state = STATE_SYNC1; break; case 0x86: INIENV(); state = STATE_SYNC1; break; case 0x87: setDateMSX(); state = STATE_SYNC1; break; case 0x88: state = STATE_DEVOPEN; recvCount = 0; break; case 0x89: deviceClose(); state = STATE_SYNC1; break; //case 0x8A: deviceRandomIO(fcb); case 0x8B: deviceWrite(); state = STATE_SYNC1; break; case 0x8C: deviceRead(); state = STATE_SYNC1; break; //case 0x8D: deviceEof(fcb); //case 0x8E: auxIn(); //case 0x8F: auxOut(); case 0x90: state = STATE_MESSAGE; recvCount = 0; break; case 0xA0: state = STATE_IMAGE; recvCount = 0; break; //case 0xFF: vramDump(); default: // Unknown USB command! state = STATE_SYNC1; break; } } // send: pc -> msx void NowindHost::send(byte value) { hostToMsxFifo.push_back(value); } void NowindHost::send16(word value) { hostToMsxFifo.push_back(value & 255); hostToMsxFifo.push_back(value >> 8); } void NowindHost::purge() { hostToMsxFifo.clear(); } void NowindHost::sendHeader() { send(0xFF); // needed because first read might fail! send(0xAF); send(0x05); } void NowindHost::DSKCHG() { auto* disk = getDisk(); if (!disk) { // no such drive or no disk inserted return; } sendHeader(); byte num = cmdData[7]; // reg_a assert(num < drives.size()); if (drives[num]->diskChanged()) { send(255); // changed // read first FAT sector (contains media descriptor) SectorBuffer sectorBuffer; if (disk->readSectors(§orBuffer, 1, 1)) { // TODO read error sectorBuffer.raw[0] = 0; } send(sectorBuffer.raw[0]); // new mediadescriptor } else { send(0); // not changed // TODO shouldn't we send some (dummy) byte here? // nowind-diskrom seems to read it (but doesn't use it) } } void NowindHost::DRIVES() { // at least one drive (MSXDOS1 cannot handle 0 drives) byte numberOfDrives = std::max(1, byte(drives.size())); byte reg_a = cmdData[7]; sendHeader(); send(getEnablePhantomDrives() ? 0x02 : 0); send(reg_a | (getAllowOtherDiskroms() ? 0 : 0x80)); send(numberOfDrives); romdisk = 255; // no romdisk for (unsigned i = 0; i < drives.size(); ++i) { if (drives[i]->isRomdisk()) { romdisk = i; break; } } } void NowindHost::INIENV() { sendHeader(); send(romdisk); // calculated in DRIVES() } void NowindHost::setDateMSX() { auto td = time(nullptr); auto* tm = localtime(&td); sendHeader(); send(tm->tm_mday); // day send(tm->tm_mon + 1); // month send16(tm->tm_year + 1900); // year } unsigned NowindHost::getSectorAmount() const { byte reg_b = cmdData[1]; return reg_b; } unsigned NowindHost::getStartSector() const { byte reg_c = cmdData[0]; byte reg_e = cmdData[2]; byte reg_d = cmdData[3]; unsigned startSector = reg_e + (reg_d * 256); if (reg_c < 0x80) { // FAT16 read/write sector startSector += reg_c << 16; } return startSector; } unsigned NowindHost::getStartAddress() const { byte reg_l = cmdData[4]; byte reg_h = cmdData[5]; return reg_h * 256 + reg_l; } unsigned NowindHost::getCurrentAddress() const { unsigned startAdress = getStartAddress(); return startAdress + transfered; } void NowindHost::diskReadInit(SectorAccessibleDisk& disk) { unsigned sectorAmount = getSectorAmount(); buffer.resize(sectorAmount); unsigned startSector = getStartSector(); if (disk.readSectors(buffer.data(), startSector, sectorAmount)) { // read error state = STATE_SYNC1; return; } transfered = 0; retryCount = 0; doDiskRead1(); } void NowindHost::doDiskRead1() { unsigned bytesLeft = unsigned(buffer.size() * SECTOR_SIZE) - transfered; if (bytesLeft == 0) { sendHeader(); send(0x01); // end of receive-loop send(0x00); // no more data state = STATE_SYNC1; return; } static const unsigned NUMBEROFBLOCKS = 32; // 32 * 64 bytes = 2048 bytes transferSize = std::min(bytesLeft, NUMBEROFBLOCKS * 64); // hardcoded in firmware unsigned address = getCurrentAddress(); if (address >= 0x8000) { if (transferSize & 0x003F) { transferSectors(address, transferSize); } else { transferSectorsBackwards(address, transferSize); } } else { // transfer below 0x8000 // TODO shouldn't we also test for (transferSize & 0x3F)? unsigned endAddress = address + transferSize; if (endAddress <= 0x8000) { transferSectorsBackwards(address, transferSize); } else { transferSize = 0x8000 - address; transferSectors(address, transferSize); } } // wait for 2 bytes state = STATE_DISKREAD; recvCount = 0; } void NowindHost::doDiskRead2() { // diskrom sends back the last two bytes read assert(recvCount == 2); byte tail1 = extraData[0]; byte tail2 = extraData[1]; if ((tail1 == 0xAF) && (tail2 == 0x07)) { transfered += transferSize; retryCount = 0; unsigned address = getCurrentAddress(); size_t bytesLeft = (buffer.size() * SECTOR_SIZE) - transfered; if ((address == 0x8000) && (bytesLeft > 0)) { sendHeader(); send(0x01); // end of receive-loop send(0xff); // more data for page 2/3 } // continue the rest of the disk read doDiskRead1(); } else { purge(); if (++retryCount == 10) { // do nothing, timeout on MSX // too many retries, aborting readDisk() state = STATE_SYNC1; return; } // try again, wait for two bytes state = STATE_DISKREAD; recvCount = 0; } } // sends "02" + "transfer_addr" + "amount" + "data" + "0F 07" void NowindHost::transferSectors(unsigned transferAddress, unsigned amount) { sendHeader(); send(0x00); // don't exit command, (more) data is coming send16(transferAddress); send16(amount); auto* bufferPointer = buffer[0].raw + transfered; for (unsigned i = 0; i < amount; ++i) { send(bufferPointer[i]); } send(0xAF); send(0x07); // used for validation } // sends "02" + "transfer_addr" + "amount" + "data" + "0F 07" void NowindHost::transferSectorsBackwards(unsigned transferAddress, unsigned amount) { sendHeader(); send(0x02); // don't exit command, (more) data is coming send16(transferAddress + amount); send(amount / 64); auto* bufferPointer = buffer[0].raw + transfered; for (int i = amount - 1; i >= 0; --i) { send(bufferPointer[i]); } send(0xAF); send(0x07); // used for validation } void NowindHost::diskWriteInit(SectorAccessibleDisk& disk) { if (disk.isWriteProtected()) { sendHeader(); send(1); send(0); // WRITEPROTECTED state = STATE_SYNC1; return; } unsigned sectorAmount = std::min(128u, getSectorAmount()); buffer.resize(sectorAmount); transfered = 0; doDiskWrite1(); } void NowindHost::doDiskWrite1() { unsigned bytesLeft = unsigned(buffer.size() * SECTOR_SIZE) - transfered; if (bytesLeft == 0) { // All data transferred! unsigned sectorAmount = unsigned(buffer.size()); unsigned startSector = getStartSector(); if (auto* disk = getDisk()) { if (disk->writeSectors(&buffer[0], startSector, sectorAmount)) { // TODO write error } } sendHeader(); send(255); state = STATE_SYNC1; return; } static const unsigned BLOCKSIZE = 240; transferSize = std::min(bytesLeft, BLOCKSIZE); unsigned address = getCurrentAddress(); unsigned endAddress = address + transferSize; if ((address ^ endAddress) & 0x8000) { // would cross page 1-2 boundary -> limit to page 1 transferSize = 0x8000 - address; } sendHeader(); send(0); // data ahead! send16(address); send16(transferSize); send(0xaa); // wait for data state = STATE_DISKWRITE; recvCount = 0; } void NowindHost::doDiskWrite2() { assert(recvCount == (transferSize + 2)); auto* buf = buffer[0].raw + transfered; for (unsigned i = 0; i < transferSize; ++i) { buf[i] = extraData[i + 1]; } byte seq1 = extraData[0]; byte seq2 = extraData[transferSize + 1]; if ((seq1 == 0xaa) && (seq2 == 0xaa)) { // good block received transfered += transferSize; unsigned address = getCurrentAddress(); size_t bytesLeft = (buffer.size() * SECTOR_SIZE) - transfered; if ((address == 0x8000) && (bytesLeft > 0)) { sendHeader(); send(254); // more data for page 2/3 } } else { // ERROR!!! // This situation is still not handled correctly! purge(); } // continue the rest of the disk write doDiskWrite1(); } unsigned NowindHost::getFCB() const { // note: same code as getStartAddress(), merge??? byte reg_l = cmdData[4]; byte reg_h = cmdData[5]; return reg_h * 256 + reg_l; } string NowindHost::extractName(int begin, int end) const { string result; for (int i = begin; i < end; ++i) { char c = extraData[i]; if (c == ' ') break; result += toupper(c); } return result; } int NowindHost::getDeviceNum() const { unsigned fcb = getFCB(); for (unsigned i = 0; i < MAX_DEVICES; ++i) { if (devices[i].fs && devices[i].fcb == fcb) { return i; } } return -1; } int NowindHost::getFreeDeviceNum() { int dev = getDeviceNum(); if (dev != -1) { // There already was a device open with this fcb address, // reuse that device. return dev; } // Search for free device. for (unsigned i = 0; i < MAX_DEVICES; ++i) { if (!devices[i].fs) { return i; } } // All devices are in use. This can't happen when the MSX software // functions correctly. We'll simply reuse the first device. It would // be nicer if we reuse the oldest device, but that's harder to // implement, and actually it doesn't really matter. return 0; } void NowindHost::deviceOpen() { state = STATE_SYNC1; assert(recvCount == 11); string filename = extractName(0, 8); string ext = extractName(8, 11); if (!ext.empty()) { filename += '.'; filename += ext; } unsigned fcb = getFCB(); unsigned dev = getFreeDeviceNum(); devices[dev].fs = make_unique(); // takes care of deleting old fs devices[dev].fcb = fcb; sendHeader(); byte errorCode = 0; byte openMode = cmdData[2]; // reg_e switch (openMode) { case 1: // read-only mode devices[dev].fs->open(filename.c_str(), ios::in | ios::binary); errorCode = 53; // file not found break; case 2: // create new file, write-only devices[dev].fs->open(filename.c_str(), ios::out | ios::binary); errorCode = 56; // bad file name break; case 8: // append to existing file, write-only devices[dev].fs->open(filename.c_str(), ios::out | ios::binary | ios::app); errorCode = 53; // file not found break; case 4: send(58); // sequential I/O only return; default: send(0xFF); // TODO figure out a good error number return; } assert(errorCode != 0); if (devices[dev].fs->fail()) { devices[dev].fs.reset(); send(errorCode); return; } unsigned readLen = 0; bool eof = false; char buffer[256]; if (openMode == 1) { // read-only mode, already buffer first 256 bytes readLen = readHelper1(dev, buffer); assert(readLen <= 256); eof = readLen < 256; } send(0x00); // no error send16(fcb); send16(9 + readLen + (eof ? 1 : 0)); // number of bytes to transfer send(openMode); send(0); send(0); send(0); send(cmdData[3]); // reg_d send(0); send(0); send(0); send(0); if (openMode == 1) { readHelper2(readLen, buffer); } } void NowindHost::deviceClose() { int dev = getDeviceNum(); if (dev == -1) return; devices[dev].fs.reset(); } void NowindHost::deviceWrite() { int dev = getDeviceNum(); if (dev == -1) return; char data = cmdData[0]; // reg_c devices[dev].fs->write(&data, 1); } void NowindHost::deviceRead() { int dev = getDeviceNum(); if (dev == -1) return; char buffer[256]; unsigned readLen = readHelper1(dev, buffer); bool eof = readLen < 256; send(0xAF); send(0x05); send(0x00); // dummy send16(getFCB() + 9); send16(readLen + (eof ? 1 : 0)); readHelper2(readLen, buffer); } unsigned NowindHost::readHelper1(unsigned dev, char* buffer) { assert(dev < MAX_DEVICES); unsigned len = 0; for (/**/; len < 256; ++len) { devices[dev].fs->read(&buffer[len], 1); if (devices[dev].fs->eof()) break; } return len; } void NowindHost::readHelper2(unsigned len, const char* buffer) { for (unsigned i = 0; i < len; ++i) { send(buffer[i]); } if (len < 256) { send(0x1A); // end-of-file } } // strips a string from outer double-quotes and anything outside them // ie: 'pre("foo")bar' will result in 'foo' static string stripquotes(const string& str) { auto first = str.find_first_of('\"'); if (first == string::npos) { // There are no quotes, return the whole string. return str; } auto last = str.find_last_of ('\"'); if (first == last) { // Error, there's only a single double-quote char. return ""; } // Return the part between the quotes. return str.substr(first + 1, last - first - 1); } void NowindHost::callImage(const string& filename) { byte num = cmdData[7]; // reg_a if (num >= drives.size()) { // invalid drive number return; } if (drives[num]->insertDisk(stripquotes(filename))) { // TODO error handling } } static enum_string stateInfo[] = { { "SYNC1", NowindHost::STATE_SYNC1 }, { "SYNC2", NowindHost::STATE_SYNC2 }, { "COMMAND", NowindHost::STATE_COMMAND }, { "DISKREAD", NowindHost::STATE_DISKREAD }, { "DISKWRITE", NowindHost::STATE_DISKWRITE }, { "DEVOPEN", NowindHost::STATE_DEVOPEN }, { "IMAGE", NowindHost::STATE_IMAGE }, }; SERIALIZE_ENUM(NowindHost::State, stateInfo); template void NowindHost::serialize(Archive& ar, unsigned /*version*/) { // drives is serialized elsewhere ar.serialize("hostToMsxFifo", hostToMsxFifo); ar.serialize("lastTime", lastTime); ar.serialize("state", state); ar.serialize("recvCount", recvCount); ar.serialize("cmdData", cmdData); ar.serialize("extraData", extraData); // for backwards compatibility, serialize buffer as a vector size_t bufSize = buffer.size() * sizeof(SectorBuffer); byte* bufRaw = buffer.data()->raw; vector tmp(bufRaw, bufRaw + bufSize); ar.serialize("buffer", tmp); memcpy(bufRaw, tmp.data(), bufSize); ar.serialize("transfered", transfered); ar.serialize("retryCount", retryCount); ar.serialize("transferSize", transferSize); ar.serialize("romdisk", romdisk); ar.serialize("allowOtherDiskroms", allowOtherDiskroms); ar.serialize("enablePhantomDrives", enablePhantomDrives); // Note: We don't serialize 'devices'. So after a loadstate it will be // as-if the devices are closed again. The reason for not serializing // this is that it's very hard to serialize a fstream (and we anyway // can't restore the state of the host filesystem). } INSTANTIATE_SERIALIZE_METHODS(NowindHost); } // namspace openmsx openMSX-RELEASE_0_12_0/src/fdc/NowindHost.hh000066400000000000000000000070561257557151200204070ustar00rootroot00000000000000#ifndef NOWINDHOST_HH #define NOWINDHOST_HH #include "DiskImageUtils.hh" #include "circular_buffer.hh" #include "openmsx.hh" #include #include #include #include namespace openmsx { class SectorAccessibleDisk; class DiskContainer; class NowindHost { public: using Drives = std::vector>; explicit NowindHost(const Drives& drives); ~NowindHost(); // public for usb-host implementation bool isDataAvailable() const; // read one byte of response-data from the host (msx <- pc) byte read(); // like read(), but without side effects (doesn't consume the data) byte peek() const; // Write one byte of command-data to the host (msx -> pc) // Time parameter is in milliseconds. Emulators can pass emulation // time, usbhost can pass real time. void write(byte value, unsigned time); void setAllowOtherDiskroms(bool allow) { allowOtherDiskroms = allow; } bool getAllowOtherDiskroms() const { return allowOtherDiskroms; } void setEnablePhantomDrives(bool enable) { enablePhantomDrives = enable; } bool getEnablePhantomDrives() const { return enablePhantomDrives; } template void serialize(Archive& ar, unsigned version); // public for serialization enum State { STATE_SYNC1, // waiting for AF STATE_SYNC2, // waiting for 05 STATE_COMMAND, // waiting for command (9 bytes) STATE_DISKREAD, // waiting for AF07 STATE_DISKWRITE, // waiting for AAAA STATE_DEVOPEN, // waiting for filename (11 bytes) STATE_IMAGE, // waiting for filename STATE_MESSAGE, // waiting for null-terminated message }; private: void msxReset(); SectorAccessibleDisk* getDisk() const; void executeCommand(); void send(byte value); void send16(word value); void sendHeader(); void purge(); void DRIVES(); void DSKCHG(); void CHOICE(); void INIENV(); void setDateMSX(); unsigned getSectorAmount() const; unsigned getStartSector() const; unsigned getStartAddress() const; unsigned getCurrentAddress() const; void diskReadInit(SectorAccessibleDisk& disk); void doDiskRead1(); void doDiskRead2(); void transferSectors(unsigned transferAddress, unsigned amount); void transferSectorsBackwards(unsigned transferAddress, unsigned amount); void diskWriteInit(SectorAccessibleDisk& disk); void doDiskWrite1(); void doDiskWrite2(); unsigned getFCB() const; std::string extractName(int begin, int end) const; unsigned readHelper1(unsigned dev, char* buffer); void readHelper2(unsigned len, const char* buffer); int getDeviceNum() const; int getFreeDeviceNum(); void deviceOpen(); void deviceClose(); void deviceWrite(); void deviceRead(); void callImage(const std::string& filename); static const unsigned MAX_DEVICES = 16; const Drives& drives; cb_queue hostToMsxFifo; struct { std::unique_ptr fs; // not in use when fs == nullptr unsigned fcb; } devices[MAX_DEVICES]; // state-machine std::vector buffer;// work buffer for diskread/write unsigned lastTime; // last time a byte was received from MSX State state; unsigned recvCount; // how many bytes recv in this state unsigned transfered; // progress within diskread/write unsigned retryCount; // only used for diskread unsigned transferSize; // size of current chunk byte cmdData[9]; // reg_[cbedlhfa] + cmd byte extraData[240 + 2]; // extra data for diskread/write byte romdisk; // index of romdisk (255 = no romdisk) bool allowOtherDiskroms; bool enablePhantomDrives; }; } // namespace openmsx #endif // NOWINDHOST_HH openMSX-RELEASE_0_12_0/src/fdc/NowindInterface.cc000066400000000000000000000077561257557151200213670ustar00rootroot00000000000000#include "NowindInterface.hh" #include "NowindCommand.hh" #include "DiskChanger.hh" #include "Clock.hh" #include "MSXMotherBoard.hh" #include "MSXException.hh" #include "serialize.hh" #include "serialize_stl.hh" #include "memory.hh" #include #include using std::string; namespace openmsx { NowindInterface::NowindInterface(const DeviceConfig& config) : MSXDevice(config) , rom(getName() + " ROM", "rom", config) , flash(rom, std::vector(rom.getSize() / 0x10000, {0x10000, false}), 0x01A4, false, config) , host(drives) , basename("nowindX") { nowindsInUse = getMotherBoard().getSharedStuff("nowindsInUse"); unsigned i = 0; while ((*nowindsInUse)[i]) { if (++i == MAX_NOWINDS) { throw MSXException("Too many nowind interfaces."); } } (*nowindsInUse)[i] = true; basename[6] = char('a' + i); command = make_unique( basename, getCommandController(), *this); // start with one (empty) drive auto drive = command->createDiskChanger(basename, 0, getMotherBoard()); drive->createCommand(); drives.push_back(std::move(drive)); reset(EmuTime::dummy()); } NowindInterface::~NowindInterface() { unsigned i = basename[6] - 'a'; assert((*nowindsInUse)[i]); (*nowindsInUse)[i] = false; } void NowindInterface::reset(EmuTime::param /*time*/) { // version 1 didn't change the bank number // version 2 (produced by Sunrise) does reset the bank number bank = 0; // Flash state is NOT changed on reset //flash.reset(); } byte NowindInterface::peekMem(word address, EmuTime::param /*time*/) const { if (((0x2000 <= address) && (address < 0x4000)) || ((0x8000 <= address) && (address < 0xA000))) { return host.peek(); } else if ((0x4000 <= address) && (address < 0xC000)) { // note: range 0x8000-0xA000 is already handled above return flash.peek(bank * 0x4000 + (address & 0x3FFF)); } else { return 0xFF; } } byte NowindInterface::readMem(word address, EmuTime::param /*time*/) { if (((0x2000 <= address) && (address < 0x4000)) || ((0x8000 <= address) && (address < 0xA000))) { return host.read(); } else if ((0x4000 <= address) && (address < 0xC000)) { // note: range 0x8000-0xA000 is already handled above return flash.read(bank * 0x4000 + (address & 0x3FFF)); } else { return 0xFF; } } const byte* NowindInterface::getReadCacheLine(word address) const { if (((0x2000 <= address) && (address < 0x4000)) || ((0x8000 <= address) && (address < 0xA000))) { // nowind region, not cachable return nullptr; } else if ((0x4000 <= address) && (address < 0xC000)) { // note: range 0x8000-0xA000 is already handled above return flash.getReadCacheLine(bank * 0x4000 + (address & 0x3FFF)); } else { return unmappedRead; } } void NowindInterface::writeMem(word address, byte value, EmuTime::param time) { if (address < 0x4000) { flash.write(bank * 0x4000 + address, value); } else if (((0x4000 <= address) && (address < 0x6000)) || ((0x8000 <= address) && (address < 0xA000))) { static const Clock<1000> clock(EmuTime::zero); host.write(value, clock.getTicksTill(time)); } else if (((0x6000 <= address) && (address < 0x8000)) || ((0xA000 <= address) && (address < 0xC000))) { byte max = rom.getSize() / (16 * 1024); bank = (value < max) ? value : value & (max - 1); invalidateMemCache(0x4000, 0x4000); invalidateMemCache(0xA000, 0x2000); } } byte* NowindInterface::getWriteCacheLine(word address) const { if (address < 0xC000) { // not cachable return nullptr; } else { return unmappedWrite; } } template void NowindInterface::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("flash", flash); ar.serializeWithID("drives", drives, std::ref(getMotherBoard())); ar.serialize("nowindhost", host); ar.serialize("bank", bank); // don't serialize command, rom, basename } INSTANTIATE_SERIALIZE_METHODS(NowindInterface); REGISTER_MSXDEVICE(NowindInterface, "NowindInterface"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/NowindInterface.hh000066400000000000000000000022741257557151200213670ustar00rootroot00000000000000#ifndef NOWINDINTERFACE_HH #define NOWINDINTERFACE_HH #include "MSXDevice.hh" #include "NowindHost.hh" #include "Rom.hh" #include "AmdFlash.hh" #include #include #include #include namespace openmsx { class NowindCommand; class DiskContainer; class NowindInterface final : public MSXDevice { public: explicit NowindInterface(const DeviceConfig& config); ~NowindInterface(); void reset(EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; byte readMem(word address, EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word address) const override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: Rom rom; AmdFlash flash; NowindHost host; std::unique_ptr command; NowindHost::Drives drives; std::string basename; byte bank; static const unsigned MAX_NOWINDS = 8; // a-h using NowindsInUse = std::bitset; std::shared_ptr nowindsInUse; friend class NowindCommand; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/NowindRomDisk.cc000066400000000000000000000013301257557151200210150ustar00rootroot00000000000000#include "NowindRomDisk.hh" #include "serialize.hh" #include "serialize_meta.hh" namespace openmsx { SectorAccessibleDisk* NowindRomDisk::getSectorAccessibleDisk() { return nullptr; } const std::string& NowindRomDisk::getContainerName() const { static const std::string NAME = "NowindRomDisk"; return NAME; } bool NowindRomDisk::diskChanged() { return false; } int NowindRomDisk::insertDisk(string_ref /*filename*/) { return -1; // Can't change NowindRomDisk disk image } template void NowindRomDisk::serialize(Archive& /*ar*/, unsigned /*version*/) { } INSTANTIATE_SERIALIZE_METHODS(NowindRomDisk); REGISTER_POLYMORPHIC_CLASS(DiskContainer, NowindRomDisk, "NowindRomDisk"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/NowindRomDisk.hh000066400000000000000000000007171257557151200210370ustar00rootroot00000000000000#ifndef NOWINDROMDISK_HH #define NOWINDROMDISK_HH #include "DiskContainer.hh" namespace openmsx { class NowindRomDisk final : public DiskContainer { public: SectorAccessibleDisk* getSectorAccessibleDisk() override; const std::string& getContainerName() const override; bool diskChanged() override; int insertDisk(string_ref filename) override; template void serialize(Archive& ar, unsigned version); }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/PhilipsFDC.cc000066400000000000000000000103441257557151200202200ustar00rootroot00000000000000#include "PhilipsFDC.hh" #include "CacheLine.hh" #include "DriveMultiplexer.hh" #include "WD2793.hh" #include "serialize.hh" namespace openmsx { PhilipsFDC::PhilipsFDC(const DeviceConfig& config) : WD2793BasedFDC(config) { reset(getCurrentTime()); } void PhilipsFDC::reset(EmuTime::param time) { WD2793BasedFDC::reset(time); writeMem(0x3FFC, 0x00, time); writeMem(0x3FFD, 0x00, time); } byte PhilipsFDC::readMem(word address, EmuTime::param time) { byte value; switch (address & 0x3FFF) { case 0x3FF8: value = controller.getStatusReg(time); break; case 0x3FF9: value = controller.getTrackReg(time); break; case 0x3FFA: value = controller.getSectorReg(time); break; case 0x3FFB: value = controller.getDataReg(time); break; case 0x3FFF: value = 0xC0; if (controller.getIRQ(time)) value &= ~0x40; if (controller.getDTRQ(time)) value &= ~0x80; break; default: value = PhilipsFDC::peekMem(address, time); break; } return value; } byte PhilipsFDC::peekMem(word address, EmuTime::param time) const { byte value; // FDC registers are mirrored in // 0x3FF8-0x3FFF // 0x7FF8-0x7FFF // 0xBFF8-0xBFFF // 0xFFF8-0xFFFF switch (address & 0x3FFF) { case 0x3FF8: value = controller.peekStatusReg(time); break; case 0x3FF9: value = controller.peekTrackReg(time); break; case 0x3FFA: value = controller.peekSectorReg(time); break; case 0x3FFB: value = controller.peekDataReg(time); break; case 0x3FFC: // bit 0 = side select // TODO check other bits !! value = sideReg; // value = multiplexer.getSideSelect(); break; case 0x3FFD: // bit 1,0 -> drive number // (00 or 10: drive A, 01: drive B, 11: nothing) // bit 7 -> motor on // TODO check other bits !! value = driveReg; // multiplexer.getSelectedDrive(); break; case 0x3FFE: // not used value = 255; break; case 0x3FFF: // Drive control IRQ and DRQ lines are not connected to Z80 // interrupt request // bit 6: !intrq // bit 7: !dtrq // TODO check other bits !! value = 0xC0; if (controller.peekIRQ(time)) value &= ~0x40; if (controller.peekDTRQ(time)) value &= ~0x80; break; default: if ((0x4000 <= address) && (address < 0x8000)) { // ROM only visible in 0x4000-0x7FFF value = MSXFDC::peekMem(address, time); } else { value = 255; } break; } return value; } const byte* PhilipsFDC::getReadCacheLine(word start) const { // if address overlap 0x7ff8-0x7ffb then return nullptr, // else normal ROM behaviour if ((start & 0x3FF8 & CacheLine::HIGH) == (0x3FF8 & CacheLine::HIGH)) { return nullptr; } else if ((0x4000 <= start) && (start < 0x8000)) { // ROM visible in 0x4000-0x7FFF return MSXFDC::getReadCacheLine(start); } else { return unmappedRead; } } void PhilipsFDC::writeMem(word address, byte value, EmuTime::param time) { switch (address & 0x3FFF) { case 0x3FF8: controller.setCommandReg(value, time); break; case 0x3FF9: controller.setTrackReg(value, time); break; case 0x3FFA: controller.setSectorReg(value, time); break; case 0x3FFB: controller.setDataReg(value, time); break; case 0x3FFC: // bit 0 = side select // TODO check other bits !! sideReg = value; multiplexer.setSide(value & 1); break; case 0x3FFD: // bit 1,0 -> drive number // (00 or 10: drive A, 01: drive B, 11: nothing) // TODO bit 6 -> drive LED (0 -> off, 1 -> on) // bit 7 -> motor on // TODO check other bits !! driveReg = value; DriveMultiplexer::DriveNum drive; switch (value & 3) { case 0: case 2: drive = DriveMultiplexer::DRIVE_A; break; case 1: drive = DriveMultiplexer::DRIVE_B; break; case 3: default: drive = DriveMultiplexer::NO_DRIVE; } multiplexer.selectDrive(drive, time); multiplexer.setMotor((value & 128) != 0, time); break; } } byte* PhilipsFDC::getWriteCacheLine(word address) const { if ((address & 0x3FF8) == (0x3FF8 & CacheLine::HIGH)) { return nullptr; } else { return unmappedWrite; } } template void PhilipsFDC::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("sideReg", sideReg); ar.serialize("driveReg", driveReg); } INSTANTIATE_SERIALIZE_METHODS(PhilipsFDC); REGISTER_MSXDEVICE(PhilipsFDC, "PhilipsFDC"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/PhilipsFDC.hh000066400000000000000000000013051257557151200202270ustar00rootroot00000000000000#ifndef PHILIPSFDC_HH #define PHILIPSFDC_HH #include "WD2793BasedFDC.hh" namespace openmsx { class PhilipsFDC final : public WD2793BasedFDC { public: explicit PhilipsFDC(const DeviceConfig& config); void reset(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: byte sideReg; byte driveReg; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/RamDSKDiskImage.cc000066400000000000000000000012701257557151200211300ustar00rootroot00000000000000#include "RamDSKDiskImage.hh" #include "DiskImageUtils.hh" #include namespace openmsx { RamDSKDiskImage::RamDSKDiskImage(size_t size) : SectorBasedDisk(DiskName(Filename(), "ramdsk")) , data(size / sizeof(SectorBuffer)) { setNbSectors(size / sizeof(SectorBuffer)); DiskImageUtils::format(*this); } RamDSKDiskImage::~RamDSKDiskImage() { } void RamDSKDiskImage::readSectorImpl(size_t sector, SectorBuffer& buf) { memcpy(&buf, &data[sector], sizeof(buf)); } void RamDSKDiskImage::writeSectorImpl(size_t sector, const SectorBuffer& buf) { memcpy(&data[sector], &buf, sizeof(buf)); } bool RamDSKDiskImage::isWriteProtectedImpl() const { return false; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/RamDSKDiskImage.hh000066400000000000000000000010521257557151200211400ustar00rootroot00000000000000#ifndef RAMDSKDISKIMAGE_HH #define RAMDSKDISKIMAGE_HH #include "SectorBasedDisk.hh" #include "MemBuffer.hh" namespace openmsx { class RamDSKDiskImage final : public SectorBasedDisk { public: explicit RamDSKDiskImage(size_t size = 720 * 1024); ~RamDSKDiskImage(); private: // SectorBasedDisk void readSectorImpl (size_t sector, SectorBuffer& buf) override; void writeSectorImpl(size_t sector, const SectorBuffer& buf) override; bool isWriteProtectedImpl() const override; MemBuffer data; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/RawTrack.cc000066400000000000000000000123161257557151200200120ustar00rootroot00000000000000#include "RawTrack.hh" #include "CRC16.hh" #include "serialize.hh" #include "serialize_stl.hh" #include #include using std::vector; namespace openmsx { #ifndef _MSC_VER // Workaround vc++ bug??? // I'm reasonably sure the following line is required. If it's left out I get // a link error when compiling with gcc (though only in a debug build). This // page also says it's required: // http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.13 // Though with this line Vampier got a link error in vc++, removing the line // fixed the problem. const unsigned RawTrack::STANDARD_SIZE; #endif RawTrack::RawTrack() { clear(STANDARD_SIZE); } void RawTrack::clear(unsigned size) { idam.clear(); data.assign(size, 0x4e); } void RawTrack::addIdam(unsigned idx) { assert(idx < data.size()); assert(idam.empty() || (idx > idam.back())); idam.push_back(idx); } bool RawTrack::decodeSectorImpl(int idx, Sector& sector) const { // read (and check) address mark // assume addr mark starts with three A1 bytes (should be // located right before the current 'idx' position) if (read(idx) != 0xFE) return false; ++idx; int addrIdx = idx; CRC16 addrCrc; addrCrc.init<0xA1, 0xA1, 0xA1, 0xFE>(); updateCrc(addrCrc, addrIdx, 4); byte trackNum = read(idx++); byte headNum = read(idx++); byte secNum = read(idx++); byte sizeCode = read(idx++); byte addrCrc1 = read(idx++); byte addrCrc2 = read(idx++); bool addrCrcErr = (256 * addrCrc1 + addrCrc2) != addrCrc.getValue(); // Locate data mark, should starts within 43 bytes from current // position (that's what the WD2793 does). for (int i = 0; i < 43; ++i) { int idx2 = idx + i; int j = 0; for (; j < 3; ++j) { if (read(idx2 + j) != 0xA1) break; } if (j != 3) continue; // didn't find 3 x 0xA1 byte type = read(idx2 + 3); if (!((type == 0xfb) || (type == 0xf8))) continue; CRC16 dataCrc; dataCrc.init<0xA1, 0xA1, 0xA1>(); dataCrc.update(type); // OK, found start of data, calculate CRC. int dataIdx = idx2 + 4; unsigned sectorSize = 128 << (sizeCode & 7); updateCrc(dataCrc, dataIdx, sectorSize); byte dataCrc1 = read(dataIdx + sectorSize + 0); byte dataCrc2 = read(dataIdx + sectorSize + 1); bool dataCrcErr = (256 * dataCrc1 + dataCrc2) != dataCrc.getValue(); // store result sector.addrIdx = addrIdx; sector.dataIdx = dataIdx; sector.track = trackNum; sector.head = headNum; sector.sector = secNum; sector.sizeCode = sizeCode; sector.deleted = type == 0xf8; sector.addrCrcErr = addrCrcErr; sector.dataCrcErr = dataCrcErr; return true; } return false; } vector RawTrack::decodeAll() const { vector result; for (auto& i : idam) { Sector sector; if (decodeSectorImpl(i, sector)) { result.push_back(sector); } } return result; } static vector rotateIdam(vector idam, unsigned startIdx) { // find first element that is equal or bigger auto it = lower_bound(begin(idam), end(idam), startIdx); // rotate range so that we start at that element rotate(begin(idam), it, end(idam)); return idam; } bool RawTrack::decodeNextSector(unsigned startIdx, Sector& sector) const { // get first valid sector for (auto& i : rotateIdam(idam, startIdx)) { if (decodeSectorImpl(i, sector)) { return true; } } return false; } bool RawTrack::decodeSector(byte sectorNum, Sector& sector) const { for (auto& i : idam) { if (decodeSectorImpl(i, sector) && (sector.sector == sectorNum)) { return true; } } return false; } void RawTrack::readBlock(int idx, unsigned size, byte* destination) const { for (unsigned i = 0; i < size; ++i) { destination[i] = read(idx + i); } } void RawTrack::writeBlock(int idx, unsigned size, const byte* source) { for (unsigned i = 0; i < size; ++i) { write(idx + i, source[i]); } } void RawTrack::updateCrc(CRC16& crc, int idx, int size) const { unsigned start = wrapIndex(idx); unsigned end = start + size; if (end <= data.size()) { crc.update(&data[start], size); } else { unsigned part = unsigned(data.size()) - start; crc.update(&data[start], part); crc.update(&data[ 0], size - part); } } word RawTrack::calcCrc(int idx, int size) const { CRC16 crc; updateCrc(crc, idx, size); return crc.getValue(); } // version 1: initial version (fixed track length of 6250) // version 2: variable track length template void RawTrack::serialize(Archive& ar, unsigned version) { ar.serialize("idam", idam); unsigned len = unsigned(data.size()); if (ar.versionAtLeast(version, 2)) { ar.serialize("trackLength", len); } else { assert(ar.isLoader()); len = 6250; } if (ar.isLoader()) { data.resize(len); } ar.serialize_blob("data", data.data(), data.size()); } INSTANTIATE_SERIALIZE_METHODS(RawTrack); template void RawTrack::Sector::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("addrIdx", addrIdx); ar.serialize("dataIdx", dataIdx); ar.serialize("track", track); ar.serialize("head", head); ar.serialize("sector", sector); ar.serialize("sizeCode", sizeCode); ar.serialize("deleted", deleted); ar.serialize("addrCrcErr", addrCrcErr); ar.serialize("dataCrcErr", dataCrcErr); } INSTANTIATE_SERIALIZE_METHODS(RawTrack::Sector); } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/RawTrack.hh000066400000000000000000000143511257557151200200250ustar00rootroot00000000000000#ifndef RAWTRACK_HH #define RAWTRACK_HH #include "openmsx.hh" #include "serialize_meta.hh" #include namespace openmsx { class CRC16; // This class represents a raw disk track. It contains the logical sector // content, but also address blocks, CRC checksums, sync blocks and the data // in the gaps between these blocks. // // The internal representation is based on the DMK disk image file format. See: // http://www.trs-80.com/wordpress/dsk-and-dmk-image-utilities/ // (at the bottom of the page) // // Besides the raw track data, this format also stores the positions of the // 'address marks' in the track (this roughly corresponds with the start of a // sector). Of course a real disk doesn't have such a list. In a real disk this // information is stored as 'MFM encodings with missing clock transitions'. // // Normal MFM encoding goes like this: An input bit of '0' is encoded as 'x0' // with x the inverse of the previously encoded bit. A '1' input bit is encoded // as '01'. (More in detail: a '1' encoded bit indicates a magnetic flux // transition, a '0' bit is no flux transition). So for example the input byte // 0xA1 (binary 10100001) is MFM encoded as '01 00 01 00 10 10 10 01'. (Note // that decoding is simply taking every 2nd bit (the data bits), the other bits // (the clock bits) ensure that between encoded '1' bits is always at least 1 // and at most 3 zero bits. So no too dense flux transitions and not too far // apart to keep the stream synchronized). // // Now for the missing clock transitions: besides the encodings for the 256 // possible input bytes, the FDC can write two other encodings, namely: // 01 00 01 00 10 00 10 01 (0xA1 with missing clock between bit 4 and 5) // 01 01 00 10 00 10 01 00 (0xC2 with missing clock between bit 3 and 4) // // So in principle we should store each of these special 0xA1 or 0xC2 bytes. // Instead we only store the locations of '0xA1 0xA1 0xA1 0xFE' sequences (the // 0xA1 bytes have missing clocks, this sequence indicates the start of an // address mark). So we don't store the location of the special 0xC2 bytes, nor // the location of each special 0xA1 byte. These certainly do occur on a real // disk track, but the WD2793 controller only reacts to the full sequence // above, so for us this is good enough. (The FDC also uses these special // encodings to re-synchronize itself with the input stream, e.g. figure out // which bit is the start bit in a byte, but from a functional emulation point // of view we can ignore this). // // Also note that it's possible to create real disks that have still completely // different magnetic flux patterns than the 256+2 possible MFM patterns // described above. Such disks cannot be described by this class. But for // openMSX that's not a problem because the WD2793 or TC8566AF disk controllers // anyway can't handle such disks (they would always interpret the flux pattern // as one of the 256+2 MFM patterns). Other systems (like Amiga) have disk // controllers that allow more direct access to the disk and could for example // encode the data in a more efficient way than MFM (e.g. GCR6). That's why // Amiga can fit more data on the same disk (even more than simply storing 10 // or 11 sectors on a track by making the gaps between the sectors smaller). class RawTrack { public: // Typical track length is 6250 bytes: // 250kbps, 300rpm -> 6250 bytes per rotation. // The IBM Disk Format Specification confirms this number. // Of course this is in ideal circumstances: in reality the rotation // speed can vary and thus the disk can be formatted with slightly more // or slightly less raw bytes per track. This class can also represent // tracks of different lengths. static const unsigned STANDARD_SIZE = 6250; struct Sector { int addrIdx; int dataIdx; byte track; byte head; byte sector; byte sizeCode; bool deleted; bool addrCrcErr; bool dataCrcErr; template void serialize(Archive& ar, unsigned version); }; /* Construct a (cleared) track. */ RawTrack(); /** Clear track data. Also sets the track length. */ void clear(unsigned size); /** Get track length. */ unsigned getLength() const { return unsigned(data.size()); } void addIdam(unsigned idx); // In the methods below, 'index' is allowed to be 'out-of-bounds', // it will wrap like in a circular buffer. byte read(int idx) const { return data[wrapIndex(idx)]; } void write(int idx, byte val) { data[wrapIndex(idx)] = val; } int wrapIndex(int idx) const { // operator% in not a modulo but a remainder operation (makes a // difference for negative inputs). Hence the extra test. int tmp = idx % int(data.size()); return (tmp >= 0) ? tmp : int(tmp + data.size()); } byte* getRawBuffer() { return data.data(); } const byte* getRawBuffer() const { return data.data(); } const std::vector& getIdamBuffer() const { return idam; } /** Get info on all sectors in this track. */ std::vector decodeAll() const; /** Get the next sector (starting from a certain index). */ bool decodeNextSector(unsigned startIdx, Sector& sector) const; /** Get a sector with a specific number. * Note that if a sector with the same number occurs multiple times, * this method will always return the same (the first) sector. So * don't use it in the implementation of FDC / DiskDrive code. */ bool decodeSector(byte sectorNum, Sector& sector) const; /** Like memcpy() but copy from/to circular buffer. */ void readBlock (int idx, unsigned size, byte* destination) const; void writeBlock(int idx, unsigned size, const byte* source); /** Convenience method to calculate CRC for part of this track. */ word calcCrc(int idx, int length) const; void updateCrc(CRC16& crc, int idx, int size) const; template void serialize(Archive& ar, unsigned version); private: bool decodeSectorImpl(int idx, Sector& sector) const; // Index into 'data'-array to positions where an address mark // starts (it points to the 'FE' byte in the 'A1 A1 A1 FE ..' // sequence. std::vector idam; // MFM-decoded raw data, this does NOT include the missing clock // transitions that can occur in the encodings of the 'A1' and // 'C2' bytes. std::vector data; }; SERIALIZE_CLASS_VERSION(RawTrack, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/RealDrive.cc000066400000000000000000000253611257557151200201550ustar00rootroot00000000000000#include "RealDrive.hh" #include "Disk.hh" #include "DiskChanger.hh" #include "MSXMotherBoard.hh" #include "Reactor.hh" #include "LedStatus.hh" #include "CommandController.hh" #include "CliComm.hh" #include "GlobalSettings.hh" #include "MSXException.hh" #include "serialize.hh" #include "memory.hh" using std::string; using std::vector; namespace openmsx { RealDrive::RealDrive(MSXMotherBoard& motherBoard_, EmuDuration::param motorTimeout_, bool signalsNeedMotorOn_, bool doubleSided) : syncLoadingTimeout(motherBoard_.getScheduler()) , syncMotorTimeout (motherBoard_.getScheduler()) , motherBoard(motherBoard_) , loadingIndicator( motherBoard.getReactor().getGlobalSettings().getThrottleManager()) , motorTimeout(motorTimeout_) , motorTimer(getCurrentTime()) , headLoadTimer(getCurrentTime()) , headPos(0), side(0), startAngle(0) , motorStatus(false), headLoadStatus(false) , doubleSizedDrive(doubleSided) , signalsNeedMotorOn(signalsNeedMotorOn_) { drivesInUse = motherBoard.getSharedStuff("drivesInUse"); unsigned i = 0; while ((*drivesInUse)[i]) { if (++i == MAX_DRIVES) { throw MSXException("Too many disk drives."); } } (*drivesInUse)[i] = true; string driveName = "diskX"; driveName[4] = char('a' + i); if (motherBoard.getCommandController().hasCommand(driveName)) { throw MSXException("Duplicated drive name: " + driveName); } motherBoard.getMSXCliComm().update(CliComm::HARDWARE, driveName, "add"); changer = make_unique(motherBoard, driveName, true, doubleSizedDrive); } RealDrive::~RealDrive() { doSetMotor(false, getCurrentTime()); // to send LED event const auto& driveName = changer->getDriveName(); motherBoard.getMSXCliComm().update(CliComm::HARDWARE, driveName, "remove"); unsigned driveNum = driveName[4] - 'a'; assert((*drivesInUse)[driveNum]); (*drivesInUse)[driveNum] = false; } bool RealDrive::isDiskInserted() const { // The game 'Trojka' mentions on the disk label that it works on a // single-sided drive. The 2nd side of the disk actually contains a // copy protection (obviously only checked on machines with a double // sided drive). This copy-protection works fine in openMSX (when using // a proper DMK disk image). The real disk also runs fine on machines // with single sided drives. Though when we initially ran this game in // an emulated machine with a single sided drive, the copy-protection // check didn't pass. Initially we emulated single sided drives by // simply ignoring the side-select signal. Now when the 2nd side is // selected on a single sided drive, we disable the drive-ready signal. // This makes the 'Trojka' copy-protection check pass. // TODO verify that this is indeed how single sided drives behave if (!doubleSizedDrive && (side != 0)) return false; return !changer->getDisk().isDummyDisk(); } bool RealDrive::isWriteProtected() const { // On a NMS8280 the write protected signal is never active when the // drive motor is turned off. See also isTrack00(). if (signalsNeedMotorOn && !motorStatus) return false; return changer->getDisk().isWriteProtected(); } bool RealDrive::isDoubleSided() const { return doubleSizedDrive ? changer->getDisk().isDoubleSided() : false; } void RealDrive::setSide(bool side_) { side = side_ ? 1 : 0; // also for single-sided drives } void RealDrive::step(bool direction, EmuTime::param time) { if (direction) { // step in if (headPos < MAX_TRACK) { headPos++; } } else { // step out if (headPos > 0) { headPos--; } } // ThrottleManager heuristic: // If the motor is turning and there is head movement, assume the // MSX program is (still) loading/saving to disk if (motorStatus) setLoading(time); } bool RealDrive::isTrack00() const { // On a Philips-NMS8280 the track00 signal is never active when the // drive motor is turned off. On a National-FS-5500F2 the motor status // doesn't matter, the single/dual drive detection routine (during // the MSX boot sequence) even depends on this signal with motor off. if (signalsNeedMotorOn && !motorStatus) return false; return headPos == 0; } void RealDrive::setMotor(bool status, EmuTime::param time) { // If status = true, motor is immediately turned on. If status = false, // the motor is only turned off after some (configurable) amount of // time (can be zero). Let's call the last passed status parameter the // 'logical' motor status. // // Loading indicator heuristic: // Loading indicator only reacts to _changes_ in the _logical_ motor // status. So when the motor is turned off, we immediately assume the // MSX program is done loading (or saving) (we don't wait for the motor // timeout). Turning the motor on when it already was logically on has // no effect. But turning it back on while it was logically off but // still in the motor-off-timeout phase does reset the loading // indicator. // if (status) { // (Try to) remove scheduled action to turn motor off. if (syncMotorTimeout.removeSyncPoint()) { // If there actually was such an action scheduled, we // need to turn on the loading indicator. assert(motorStatus); setLoading(time); return; } if (motorStatus) { // Motor was still turning, we're done. // Note: no effect on loading indicator. return; } // Actually turn motor on (it was off before). doSetMotor(true, time); setLoading(time); } else { if (!motorStatus) { // Motor was already off, we're done. return; } if (syncMotorTimeout.pendingSyncPoint()) { // We had already scheduled an action to turn the motor // off, we're done. return; } // Heuristic: // Immediately react to 'logical' motor status, even if the // motor will (possibly) still keep rotating for a few // seconds. syncLoadingTimeout.removeSyncPoint(); loadingIndicator.update(false); // Turn the motor off after some timeout (timeout could be 0) syncMotorTimeout.setSyncPoint(time + motorTimeout); } } unsigned RealDrive::getCurrentAngle(EmuTime::param time) const { if (motorStatus) { // rotating, take passed time into account auto deltaAngle = motorTimer.getTicksTillUp(time); return (startAngle + deltaAngle) % TICKS_PER_ROTATION; } else { // not rotating, angle didn't change return startAngle; } } void RealDrive::doSetMotor(bool status, EmuTime::param time) { startAngle = getCurrentAngle(time); motorStatus = status; motorTimer.advance(time); // TODO The following is a hack to emulate the drive LED behaviour. // This should be moved to the FDC mapping code. // TODO Each drive should get it's own independent LED. motherBoard.getLedStatus().setLed(LedStatus::FDD, status); } void RealDrive::setLoading(EmuTime::param time) { assert(motorStatus); loadingIndicator.update(true); // ThrottleManager heuristic: // We want to avoid getting stuck in 'loading state' when the MSX // program forgets to turn off the motor. syncLoadingTimeout.removeSyncPoint(); syncLoadingTimeout.setSyncPoint(time + EmuDuration::sec(1)); } void RealDrive::execLoadingTimeout() { loadingIndicator.update(false); } void RealDrive::execMotorTimeout(EmuTime::param time) { doSetMotor(false, time); } bool RealDrive::indexPulse(EmuTime::param time) { // Tested on real NMS8250: // Only when there's a disk inserted and when the motor is spinning // there are index pulses generated. if (!(motorStatus && isDiskInserted())) { return false; } return getCurrentAngle(time) < INDEX_DURATION; } EmuTime RealDrive::getTimeTillIndexPulse(EmuTime::param time, int count) { if (!motorStatus || !isDiskInserted()) { // TODO is this correct? return EmuTime::infinity; } unsigned delta = TICKS_PER_ROTATION - getCurrentAngle(time); auto dur1 = MotorClock::duration(delta); auto dur2 = MotorClock::duration(TICKS_PER_ROTATION) * (count - 1); return time + dur1 + dur2; } void RealDrive::setHeadLoaded(bool status, EmuTime::param time) { if (headLoadStatus != status) { headLoadStatus = status; headLoadTimer.advance(time); } } bool RealDrive::headLoaded(EmuTime::param time) { return headLoadStatus && (headLoadTimer.getTicksTill(time) > 10); } void RealDrive::writeTrack(const RawTrack& track) { changer->getDisk().writeTrack(headPos, side, track); } void RealDrive::readTrack(RawTrack& track) { changer->getDisk().readTrack(headPos, side, track); } static inline unsigned divUp(unsigned a, unsigned b) { return (a + b - 1) / b; } EmuTime RealDrive::getNextSector( EmuTime::param time, RawTrack& track, RawTrack::Sector& sector) { int currentAngle = getCurrentAngle(time); changer->getDisk().readTrack(headPos, side, track); unsigned trackLen = track.getLength(); unsigned idx = divUp(currentAngle * trackLen, TICKS_PER_ROTATION); if (!track.decodeNextSector(idx, sector)) { return EmuTime::infinity; } int sectorAngle = divUp(sector.addrIdx * TICKS_PER_ROTATION, trackLen); int delta = sectorAngle - currentAngle; if (delta < 0) delta += TICKS_PER_ROTATION; assert(0 <= delta); assert(unsigned(delta) < TICKS_PER_ROTATION); return time + MotorClock::duration(delta); } bool RealDrive::diskChanged() { return changer->diskChanged(); } bool RealDrive::peekDiskChanged() const { return changer->peekDiskChanged(); } bool RealDrive::isDummyDrive() const { return false; } // version 1: initial version // version 2: removed 'timeOut', added MOTOR_TIMEOUT schedulable // version 3: added 'startAngle' // version 4: removed 'userData' from Schedulable template void RealDrive::serialize(Archive& ar, unsigned version) { if (ar.versionAtLeast(version, 4)) { ar.serialize("syncLoadingTimeout", syncLoadingTimeout); ar.serialize("syncMotorTimeout", syncMotorTimeout); } else { Schedulable::restoreOld(ar, {&syncLoadingTimeout, &syncMotorTimeout}); } ar.serialize("motorTimer", motorTimer); ar.serialize("headLoadTimer", headLoadTimer); ar.serialize("changer", *changer); ar.serialize("headPos", headPos); ar.serialize("side", side); ar.serialize("motorStatus", motorStatus); ar.serialize("headLoadStatus", headLoadStatus); if (ar.versionAtLeast(version, 3)) { ar.serialize("startAngle", startAngle); } else { assert(ar.isLoader()); startAngle = 0; } if (ar.isLoader()) { // Right after a loadstate, the 'loading indicator' state may // be wrong, but that's OK. It's anyway only a heuristic and // it will be correct after at most one second. // This is a workaround for the fact that we can have multiple drives // (and only one is on), in which case the 2nd drive will turn off the // LED again which the first drive just turned on. TODO: fix by modelling // individual drive LEDs properly. See also // http://sourceforge.net/tracker/index.php?func=detail&aid=1540929&group_id=38274&atid=421864 if (motorStatus) { motherBoard.getLedStatus().setLed(LedStatus::FDD, true); } } } INSTANTIATE_SERIALIZE_METHODS(RealDrive); } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/RealDrive.hh000066400000000000000000000062121257557151200201610ustar00rootroot00000000000000#ifndef REALDRIVE_HH #define REALDRIVE_HH #include "DiskDrive.hh" #include "Clock.hh" #include "Schedulable.hh" #include "ThrottleManager.hh" #include "outer.hh" #include "serialize_meta.hh" #include #include namespace openmsx { class MSXMotherBoard; class DiskChanger; /** This class implements a real drive, single or double sided. */ class RealDrive final : public DiskDrive { public: RealDrive(MSXMotherBoard& motherBoard, EmuDuration::param motorTimeout, bool signalsNeedMotorOn, bool doubleSided); ~RealDrive(); // DiskDrive interface bool isDiskInserted() const override; bool isWriteProtected() const override; bool isDoubleSided() const override; bool isTrack00() const override; void setSide(bool side) override; void step(bool direction, EmuTime::param time) override; void setMotor(bool status, EmuTime::param time) override; bool indexPulse(EmuTime::param time) override; EmuTime getTimeTillIndexPulse(EmuTime::param time, int count) override; void setHeadLoaded(bool status, EmuTime::param time) override; bool headLoaded(EmuTime::param time) override; void writeTrack(const RawTrack& track) override; void readTrack ( RawTrack& track) override; EmuTime getNextSector(EmuTime::param time, RawTrack& track, RawTrack::Sector& sector) override; bool diskChanged() override; bool peekDiskChanged() const override; bool isDummyDrive() const override; template void serialize(Archive& ar, unsigned version); private: struct SyncLoadingTimeout : Schedulable { friend class RealDrive; SyncLoadingTimeout(Scheduler& s) : Schedulable(s) {} void executeUntil(EmuTime::param /*time*/) override { auto& drive = OUTER(RealDrive, syncLoadingTimeout); drive.execLoadingTimeout(); } } syncLoadingTimeout; struct SyncMotorTimeout : Schedulable { friend class RealDrive; SyncMotorTimeout(Scheduler& s) : Schedulable(s) {} void executeUntil(EmuTime::param time) override { auto& drive = OUTER(RealDrive, syncMotorTimeout); drive.execMotorTimeout(time); } } syncMotorTimeout; void execLoadingTimeout(); void execMotorTimeout(EmuTime::param time); EmuTime::param getCurrentTime() const { return syncLoadingTimeout.getCurrentTime(); } void doSetMotor(bool status, EmuTime::param time); void setLoading(EmuTime::param time); unsigned getCurrentAngle(EmuTime::param time) const; static const unsigned MAX_TRACK = 85; static const unsigned TICKS_PER_ROTATION = 200000; static const unsigned INDEX_DURATION = TICKS_PER_ROTATION / 50; MSXMotherBoard& motherBoard; LoadingIndicator loadingIndicator; const EmuDuration motorTimeout; using MotorClock = Clock; MotorClock motorTimer; Clock<1000> headLoadTimer; // ms std::unique_ptr changer; unsigned headPos; unsigned side; unsigned startAngle; bool motorStatus; bool headLoadStatus; const bool doubleSizedDrive; const bool signalsNeedMotorOn; static const unsigned MAX_DRIVES = 26; // a-z using DrivesInUse = std::bitset; std::shared_ptr drivesInUse; }; SERIALIZE_CLASS_VERSION(RealDrive, 4); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/SanyoFDC.cc000066400000000000000000000073061257557151200177050ustar00rootroot00000000000000#include "SanyoFDC.hh" #include "CacheLine.hh" #include "DriveMultiplexer.hh" #include "WD2793.hh" #include "serialize.hh" // Note: although this implementation seems to work (e.g. for the Sanyo // MFD-001), it has not been checked on real hardware how the FDC registers are // mirrored across the slot, nor how the ROM is visible in the slot. Currently // FDC registers are implemented to be not mirrored, and ROM is implemented to // be visible in page 0 and 1. namespace openmsx { SanyoFDC::SanyoFDC(const DeviceConfig& config) : WD2793BasedFDC(config) { } byte SanyoFDC::readMem(word address, EmuTime::param time) { byte value; switch (address) { case 0x7FF8: value = controller.getStatusReg(time); break; case 0x7FF9: value = controller.getTrackReg(time); break; case 0x7FFA: value = controller.getSectorReg(time); break; case 0x7FFB: value = controller.getDataReg(time); break; case 0x7FFC: case 0x7FFD: case 0x7FFE: case 0x7FFF: value = 0x3F; if (controller.getIRQ(time)) value |= 0x80; if (controller.getDTRQ(time)) value |= 0x40; break; default: value = SanyoFDC::peekMem(address, time); break; } return value; } byte SanyoFDC::peekMem(word address, EmuTime::param time) const { byte value; switch (address) { case 0x7FF8: value = controller.peekStatusReg(time); break; case 0x7FF9: value = controller.peekTrackReg(time); break; case 0x7FFA: value = controller.peekSectorReg(time); break; case 0x7FFB: value = controller.peekDataReg(time); break; case 0x7FFC: case 0x7FFD: case 0x7FFE: case 0x7FFF: // Drive control IRQ and DRQ lines are not connected to Z80 interrupt request // bit 7: intrq // bit 6: dtrq // other bits read 1 value = 0x3F; if (controller.peekIRQ(time)) value |= 0x80; if (controller.peekDTRQ(time)) value |= 0x40; break; default: if (address < 0x8000) { // ROM only visible in 0x0000-0x7FFF (not verified!) value = MSXFDC::peekMem(address, time); } else { value = 255; } break; } return value; } const byte* SanyoFDC::getReadCacheLine(word start) const { if ((start & CacheLine::HIGH) == (0x7FF8 & CacheLine::HIGH)) { // FDC at 0x7FF8-0x7FFC - mirroring behaviour unknown return nullptr; } else if (start < 0x8000) { // ROM at 0x0000-0x7FFF (this is a guess, not checked!) return MSXFDC::getReadCacheLine(start); } else { return unmappedRead; } } void SanyoFDC::writeMem(word address, byte value, EmuTime::param time) { switch (address) { case 0x7FF8: controller.setCommandReg(value, time); break; case 0x7FF9: controller.setTrackReg(value, time); break; case 0x7FFA: controller.setSectorReg(value, time); break; case 0x7FFB: controller.setDataReg(value, time); break; case 0x7FFC: case 0x7FFD: case 0x7FFE: case 0x7FFF: // bit 0 -> select drive 0 // bit 1 -> select drive 1 // bit 2 -> side select // bit 3 -> motor on DriveMultiplexer::DriveNum drive; switch (value & 3) { case 1: drive = DriveMultiplexer::DRIVE_A; break; case 2: drive = DriveMultiplexer::DRIVE_B; break; default: drive = DriveMultiplexer::NO_DRIVE; } multiplexer.selectDrive(drive, time); multiplexer.setSide((value & 0x04) != 0); multiplexer.setMotor((value & 0x08) != 0, time); break; } } byte* SanyoFDC::getWriteCacheLine(word address) const { if ((address & CacheLine::HIGH) == (0x7FF8 & CacheLine::HIGH)) { // FDC at 0x7FF8-0x7FFC - mirroring behaviour unknown return nullptr; } else { return unmappedWrite; } } template void SanyoFDC::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); } INSTANTIATE_SERIALIZE_METHODS(SanyoFDC); REGISTER_MSXDEVICE(SanyoFDC, "SanyoFDC"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/SanyoFDC.hh000066400000000000000000000011511257557151200177070ustar00rootroot00000000000000#ifndef SANYOFDC_HH #define SANYOFDC_HH #include "WD2793BasedFDC.hh" namespace openmsx { class SanyoFDC final : public WD2793BasedFDC { public: explicit SanyoFDC(const DeviceConfig& config); byte readMem(word address, EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/SectorAccessibleDisk.cc000066400000000000000000000071641257557151200223310ustar00rootroot00000000000000#include "SectorAccessibleDisk.hh" #include "EmptyDiskPatch.hh" #include "IPSPatch.hh" #include "DiskExceptions.hh" #include "sha1.hh" #include "xrange.hh" #include "memory.hh" namespace openmsx { #ifndef _MSC_VER // This line is required according to the c++ standard, but because of a vc++ // extension, we get a link error in vc++ when we add this line. See also: // http://blogs.msdn.com/b/xiangfan/archive/2010/03/03/vc-s-evil-extension-implicit-definition-of-static-constant-member.aspx const size_t SectorAccessibleDisk::SECTOR_SIZE; #endif SectorAccessibleDisk::SectorAccessibleDisk() : patch(make_unique(*this)) , forcedWriteProtect(false) , peekMode(false) { } SectorAccessibleDisk::~SectorAccessibleDisk() { } void SectorAccessibleDisk::readSector(size_t sector, SectorBuffer& buf) { if (!isDummyDisk() && // in that case we want DriveEmptyException (sector > 1) && // allow reading sector 0 and 1 without calling // getNbSectors() because this potentially calls // detectGeometry() and that would cause an // infinite loop (getNbSectors() <= sector)) { throw NoSuchSectorException("No such sector"); } try { // in the end this calls readSectorImpl() patch->copyBlock(sector * sizeof(buf), buf.raw, sizeof(buf)); } catch (MSXException& e) { throw DiskIOErrorException("Disk I/O error: " + e.getMessage()); } } void SectorAccessibleDisk::writeSector(size_t sector, const SectorBuffer& buf) { if (isWriteProtected()) { throw WriteProtectedException(""); } if (!isDummyDisk() && (getNbSectors() <= sector)) { throw NoSuchSectorException("No such sector"); } try { writeSectorImpl(sector, buf); } catch (MSXException& e) { throw DiskIOErrorException("Disk I/O error: " + e.getMessage()); } flushCaches(); } size_t SectorAccessibleDisk::getNbSectors() const { return getNbSectorsImpl(); } void SectorAccessibleDisk::applyPatch(const Filename& patchFile) { patch = make_unique(patchFile, std::move(patch)); } std::vector SectorAccessibleDisk::getPatches() const { return patch->getFilenames(); } bool SectorAccessibleDisk::hasPatches() const { return !patch->isEmptyPatch(); } Sha1Sum SectorAccessibleDisk::getSha1Sum(FilePool& filePool) { checkCaches(); if (sha1cache.empty()) { sha1cache = getSha1SumImpl(filePool); } return sha1cache; } Sha1Sum SectorAccessibleDisk::getSha1SumImpl(FilePool& /*filePool*/) { try { setPeekMode(true); SHA1 sha1; for (auto i : xrange(getNbSectors())) { SectorBuffer buf; readSector(i, buf); sha1.update(buf.raw, sizeof(buf)); } setPeekMode(false); return sha1.digest(); } catch (MSXException&) { setPeekMode(false); throw; } } int SectorAccessibleDisk::readSectors ( SectorBuffer* buffers, size_t startSector, size_t nbSectors) { try { for (auto i : xrange(nbSectors)) { readSector(startSector + i, buffers[i]); } return 0; } catch (MSXException&) { return -1; } } int SectorAccessibleDisk::writeSectors( const SectorBuffer* buffers, size_t startSector, size_t nbSectors) { try { for (auto i : xrange(nbSectors)) { writeSector(startSector + i, buffers[i]); } return 0; } catch (MSXException&) { return -1; } } bool SectorAccessibleDisk::isWriteProtected() const { return forcedWriteProtect || isWriteProtectedImpl(); } void SectorAccessibleDisk::forceWriteProtect() { // can't be undone forcedWriteProtect = true; } bool SectorAccessibleDisk::isDummyDisk() const { return false; } void SectorAccessibleDisk::checkCaches() { // nothing } void SectorAccessibleDisk::flushCaches() { sha1cache.clear(); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/SectorAccessibleDisk.hh000066400000000000000000000041301257557151200223310ustar00rootroot00000000000000#ifndef SECTORACCESSIBLEDISK_HH #define SECTORACCESSIBLEDISK_HH #include "DiskImageUtils.hh" #include "Filename.hh" #include "sha1.hh" #include #include namespace openmsx { class FilePool; class PatchInterface; class SectorAccessibleDisk { public: static const size_t SECTOR_SIZE = sizeof(SectorBuffer); // sector stuff void readSector (size_t sector, SectorBuffer& buf); void writeSector(size_t sector, const SectorBuffer& buf); size_t getNbSectors() const; // write protected stuff bool isWriteProtected() const; void forceWriteProtect(); virtual bool isDummyDisk() const; // patch stuff void applyPatch(const Filename& patchFile); std::vector getPatches() const; bool hasPatches() const; /** Calculate SHA1 of the content of this disk. * This value is cached (and flushed on writes). */ Sha1Sum getSha1Sum(FilePool& filepool); // For compatibility with nowind // - read/write multiple sectors instead of one-per-one // - use error codes instead of exceptions // - different order of parameters int readSectors ( SectorBuffer* buffers, size_t startSector, size_t nbSectors); int writeSectors(const SectorBuffer* buffers, size_t startSector, size_t nbSectors); protected: SectorAccessibleDisk(); ~SectorAccessibleDisk(); // Peek-mode changes the behaviour of readSector(). ATM it only has // an effect on DirAsDSK. See comment in DirAsDSK::readSectorImpl() // for more details. void setPeekMode(bool peek) { peekMode = peek; } bool isPeekMode() const { return peekMode; } virtual void checkCaches(); virtual void flushCaches(); virtual Sha1Sum getSha1SumImpl(FilePool& filepool); private: virtual void readSectorImpl (size_t sector, SectorBuffer& buf) = 0; virtual void writeSectorImpl(size_t sector, const SectorBuffer& buf) = 0; virtual size_t getNbSectorsImpl() const = 0; virtual bool isWriteProtectedImpl() const = 0; std::unique_ptr patch; Sha1Sum sha1cache; bool forcedWriteProtect; bool peekMode; friend class EmptyDiskPatch; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/SectorBasedDisk.cc000066400000000000000000000142211257557151200213020ustar00rootroot00000000000000#include "SectorBasedDisk.hh" #include "MSXException.hh" #include namespace openmsx { SectorBasedDisk::SectorBasedDisk(const DiskName& name) : Disk(name) , nbSectors(size_t(-1)) // to detect misuse , cachedTrackNum(-1) { } void SectorBasedDisk::writeTrackImpl(byte track, byte side, const RawTrack& input) { for (auto& s : input.decodeAll()) { // Ignore 'track' and 'head' information // Always assume sectorsize = 512 (so also ignore sizeCode). // Ignore CRC value/errors of both address and data. // Ignore sector type (deleted or not) // Ignore sectors that are outside the range 1..sectorsPerTrack if ((s.sector < 1) || (s.sector > getSectorsPerTrack())) continue; SectorBuffer buf; input.readBlock(s.dataIdx, 512, buf.raw); auto logicalSector = physToLog(track, side, s.sector); writeSector(logicalSector, buf); // it's important to use writeSector() and not writeSectorImpl() // because only the former flushes SHA1 cache } } void SectorBasedDisk::readTrack(byte track, byte side, RawTrack& output) { // Try to cache the last result of this method (the cache will be // flushed on any write to the disk). This very simple cache mechanism // will typically already have a very high hit-rate. For example during // emulation of a WD2793 read sector, we also emulate the search for // the correct sector. So the disk rotates from sector to sector, and // each time we re-read the track data (because emutime has passed). // Typically the software will also read several sectors from the same // track before moving to the next. checkCaches(); int num = track | (side << 8); if (num == cachedTrackNum) { output = cachedTrackData; return; } cachedTrackNum = num; // This disk image only stores the actual sector data, not all the // extra gap, sync and header information that is in reality stored // in between the sectors. This function transforms the cooked sector // data back into raw track data. It assumes a standard IBM double // density, 9 sectors/track, 512 bytes/sector track layout. // // -- track -- // gap4a 80 x 0x4e // sync 12 x 0x00 // index mark 3 x 0xc2(*) // 1 x 0xfc // gap1 50 x 0x4e // 9 x [sector] 9 x [[658]] // gap4b 182 x 0x4e // // -- sector -- // sync 12 x 0x00 // ID addr mark 3 x 0xa1(*) // 1 x 0xfe // C H R N 4 x [..] // CRC 2 x [..] // gap2 22 x 0x4e // sync 12 x 0x00 // data mark 3 x 0xa1(*) // 1 x 0xfb // data 512 x [..] <-- actual sector data // CRC 2 x [..] // gap3 84 x 0x4e // // (*) Missing clock transitions in MFM encoding try { output.clear(RawTrack::STANDARD_SIZE); // clear idam positions unsigned idx = 0; for (int i = 0; i < 80; ++i) output.write(idx++, 0x4E); // gap4a for (int i = 0; i < 12; ++i) output.write(idx++, 0x00); // sync for (int i = 0; i < 3; ++i) output.write(idx++, 0xC2); // index mark (1) for (int i = 0; i < 1; ++i) output.write(idx++, 0xFC); // (2) for (int i = 0; i < 50; ++i) output.write(idx++, 0x4E); // gap1 for (int j = 0; j < 9; ++j) { for (int i = 0; i < 12; ++i) output.write(idx++, 0x00); // sync for (int i = 0; i < 3; ++i) output.write(idx++, 0xA1); // addr mark (1) output.addIdam(idx); for (int i = 0; i < 1; ++i) output.write(idx++, 0xFE); // (2) output.write(idx++, track); // C: Cylinder number output.write(idx++, side); // H: Head Address output.write(idx++, j + 1); // R: Record output.write(idx++, 0x02); // N: Number (length of sector: 512 = 128 << 2) word addrCrc = output.calcCrc(idx - 8, 8); output.write(idx++, addrCrc >> 8); // CRC (high byte) output.write(idx++, addrCrc & 0xff); // (low byte) for (int i = 0; i < 22; ++i) output.write(idx++, 0x4E); // gap2 for (int i = 0; i < 12; ++i) output.write(idx++, 0x00); // sync for (int i = 0; i < 3; ++i) output.write(idx++, 0xA1); // data mark (1) for (int i = 0; i < 1; ++i) output.write(idx++, 0xFB); // (2) auto logicalSector = physToLog(track, side, j + 1); SectorBuffer buf; readSector(logicalSector, buf); for (int i = 0; i < 512; ++i) output.write(idx++, buf.raw[i]); word dataCrc = output.calcCrc(idx - (512 + 4), 512 + 4); output.write(idx++, dataCrc >> 8); // CRC (high byte) output.write(idx++, dataCrc & 0xff); // (low byte) for (int i = 0; i < 84; ++i) output.write(idx++, 0x4E); // gap3 } for (int i = 0; i < 182; ++i) output.write(idx++, 0x4E); // gap4b assert(idx == RawTrack::STANDARD_SIZE); } catch (MSXException& /*e*/) { // There was an error while reading the actual sector data. // Most likely this is because we're reading the 81th track on // a disk with only 80 tracks (or similar). If you do this on a // real disk, you simply read an 'empty' track. So we do the // same here. output.clear(RawTrack::STANDARD_SIZE); cachedTrackNum = -1; // needed? } cachedTrackData = output; } void SectorBasedDisk::flushCaches() { Disk::flushCaches(); cachedTrackNum = -1; } size_t SectorBasedDisk::getNbSectorsImpl() const { assert(nbSectors != size_t(-1)); // must have been initialized return nbSectors; } void SectorBasedDisk::setNbSectors(size_t num) { assert(nbSectors == size_t(-1)); // can only set this once nbSectors = num; } void SectorBasedDisk::detectGeometry() { // the following are just heuristics... if (getNbSectors() == 1440) { // explicitly check for 720kb filesize // "trojka.dsk" is 720kb, but has bootsector and FAT media ID // for a single sided disk. From an emulator point of view it // must be accessed as a double sided disk. // "SDSNAT2.DSK" has invalid media ID in both FAT and // bootsector, other data in the bootsector is invalid as well. // Altough the first byte of the bootsector is 0xE9 to indicate // valid bootsector data. The only way to detect the format is // to look at the diskimage filesize. setSectorsPerTrack(9); setNbSides(2); } else { // Don't check for "360kb -> single sided disk". The MSXMania // disks are double sided disk but are truncated at 360kb. Disk::detectGeometry(); } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/SectorBasedDisk.hh000066400000000000000000000015631257557151200213210ustar00rootroot00000000000000#ifndef SECTORBASEDDISK_HH #define SECTORBASEDDISK_HH #include "Disk.hh" #include "RawTrack.hh" #include "noncopyable.hh" namespace openmsx { /** Abstract class for disk images that only represent the logical sector * information (so not the raw track data that is sometimes needed for * copy-protected disks). */ class SectorBasedDisk : public Disk, private noncopyable { protected: explicit SectorBasedDisk(const DiskName& name); void detectGeometry() override; void flushCaches() override; void setNbSectors(size_t num); protected: ~SectorBasedDisk() {} private: // Disk size_t getNbSectorsImpl() const override; void readTrack(byte track, byte side, RawTrack& output) override; void writeTrackImpl(byte track, byte side, const RawTrack& input) override; size_t nbSectors; RawTrack cachedTrackData; int cachedTrackNum; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/TC8566AF.cc000066400000000000000000000563001257557151200173430ustar00rootroot00000000000000/* * Based on code from NLMSX written by Frits Hilderink * and blueMSX written by Daniel Vik */ #include "TC8566AF.hh" #include "DiskDrive.hh" #include "Clock.hh" #include "CliComm.hh" #include "MSXException.hh" #include "serialize.hh" namespace openmsx { static const byte STM_DB0 = 0x01; // FDD 0 Busy static const byte STM_DB1 = 0x02; // FDD 1 Busy static const byte STM_DB2 = 0x04; // FDD 2 Busy static const byte STM_DB3 = 0x08; // FDD 3 Busy static const byte STM_CB = 0x10; // FDC Busy static const byte STM_NDM = 0x20; // Non-DMA mode static const byte STM_DIO = 0x40; // Data Input/Output static const byte STM_RQM = 0x80; // Request for Master static const byte ST0_DS0 = 0x01; // Drive Select 0,1 static const byte ST0_DS1 = 0x02; // static const byte ST0_HD = 0x04; // Head Address static const byte ST0_NR = 0x08; // Not Ready static const byte ST0_EC = 0x10; // Equipment Check static const byte ST0_SE = 0x20; // Seek End static const byte ST0_IC0 = 0x40; // Interrupt Code static const byte ST0_IC1 = 0x80; // static const byte ST1_MA = 0x01; // Missing Address Mark static const byte ST1_NW = 0x02; // Not Writable static const byte ST1_ND = 0x04; // No Data // = 0x08; // - static const byte ST1_OR = 0x10; // Over Run static const byte ST1_DE = 0x20; // Data Error // = 0x40; // - static const byte ST1_EN = 0x80; // End of Cylinder static const byte ST2_MD = 0x01; // Missing Address Mark in Data Field static const byte ST2_BC = 0x02; // Bad Cylinder static const byte ST2_SN = 0x04; // Scan Not Satisfied static const byte ST2_SH = 0x08; // Scan Equal Satisfied static const byte ST2_NC = 0x10; // No cylinder static const byte ST2_DD = 0x20; // Data Error in Data Field static const byte ST2_CM = 0x40; // Control Mark // = 0x80; // - static const byte ST3_DS0 = 0x01; // Drive Select 0 static const byte ST3_DS1 = 0x02; // Drive Select 1 static const byte ST3_HD = 0x04; // Head Address static const byte ST3_2S = 0x08; // Two Side static const byte ST3_TK0 = 0x10; // Track 0 static const byte ST3_RDY = 0x20; // Ready static const byte ST3_WP = 0x40; // Write Protect static const byte ST3_FLT = 0x80; // Fault TC8566AF::TC8566AF(Scheduler& scheduler, DiskDrive* drv[4], CliComm& cliComm_, EmuTime::param time) : Schedulable(scheduler) , cliComm(cliComm_) , delayTime(EmuTime::zero) , headUnloadTime(EmuTime::zero) // head not loaded { // avoid UMR (on savestate) dataAvailable = 0; dataCurrent = 0; setDrqRate(); drive[0] = drv[0]; drive[1] = drv[1]; drive[2] = drv[2]; drive[3] = drv[3]; reset(time); } void TC8566AF::reset(EmuTime::param time) { drive[0]->setMotor(false, time); drive[1]->setMotor(false, time); drive[2]->setMotor(false, time); drive[3]->setMotor(false, time); //enableIntDma = 0; //notReset = 1; driveSelect = 0; status0 = 0; status1 = 0; status2 = 0; status3 = 0; commandCode = 0; command = CMD_UNKNOWN; phase = PHASE_IDLE; phaseStep = 0; cylinderNumber = 0; headNumber = 0; sectorNumber = 0; number = 0; currentTrack = 0; sectorsPerCylinder = 0; fillerByte = 0; gapLength = 0; specifyData[0] = 0; // TODO check specifyData[1] = 0; // TODO check seekValue = 0; headUnloadTime = EmuTime::zero; // head not loaded mainStatus = STM_RQM; //interrupt = false; } byte TC8566AF::peekReg(int reg, EmuTime::param time) const { switch (reg) { case 4: // Main Status Register return peekStatus(); case 5: // data port return peekDataPort(time); } return 0xff; } byte TC8566AF::readReg(int reg, EmuTime::param time) { switch (reg) { case 4: // Main Status Register return readStatus(time); case 5: // data port return readDataPort(time); } return 0xff; } byte TC8566AF::peekStatus() const { bool nonDMAMode = specifyData[1] & 1; bool dma = nonDMAMode && (phase == PHASE_DATATRANSFER); return mainStatus | (dma ? STM_NDM : 0); } byte TC8566AF::readStatus(EmuTime::param time) { if (delayTime.before(time)) { mainStatus |= STM_RQM; } return peekStatus(); } void TC8566AF::setDrqRate() { delayTime.setFreq(trackData.getLength() * DiskDrive::ROTATIONS_PER_SECOND); } byte TC8566AF::peekDataPort(EmuTime::param time) const { switch (phase) { case PHASE_DATATRANSFER: return executionPhasePeek(time); case PHASE_RESULT: return resultsPhasePeek(); default: return 0xff; } } byte TC8566AF::readDataPort(EmuTime::param time) { //interrupt = false; switch (phase) { case PHASE_DATATRANSFER: if (delayTime.before(time)) { return executionPhaseRead(time); } else { return 0xff; // TODO check this } case PHASE_RESULT: return resultsPhaseRead(time); default: return 0xff; } } byte TC8566AF::executionPhasePeek(EmuTime::param time) const { switch (command) { case CMD_READ_DATA: if (delayTime.before(time)) { assert(dataAvailable); return trackData.read(dataCurrent); } else { return 0xff; // TODO check this } default: return 0xff; } } byte TC8566AF::executionPhaseRead(EmuTime::param time) { switch (command) { case CMD_READ_DATA: { assert(dataAvailable); byte result = trackData.read(dataCurrent++); crc.update(result); --dataAvailable; delayTime += 1; // time when next byte will be available mainStatus &= ~STM_RQM; if (delayTime.before(time)) { // lost data status0 |= ST0_IC0; status1 |= ST1_OR; resultPhase(); } else if (!dataAvailable) { // check crc error word diskCrc = 256 * trackData.read(dataCurrent++); diskCrc += trackData.read(dataCurrent++); if (diskCrc != crc.getValue()) { status0 |= ST0_IC0; status1 |= ST1_DE; status2 |= ST2_DD; } resultPhase(); } return result; } default: return 0xff; } } byte TC8566AF::resultsPhasePeek() const { switch (command) { case CMD_READ_DATA: case CMD_WRITE_DATA: case CMD_FORMAT: switch (phaseStep) { case 0: return status0; case 1: return status1; case 2: return status2; case 3: return cylinderNumber; case 4: return headNumber; case 5: return sectorNumber; case 6: return number; } break; case CMD_SENSE_INTERRUPT_STATUS: switch (phaseStep) { case 0: return status0; case 1: return currentTrack; } break; case CMD_SENSE_DEVICE_STATUS: switch (phaseStep) { case 0: return status3; } break; default: // nothing break; } return 0xff; } byte TC8566AF::resultsPhaseRead(EmuTime::param time) { byte result = resultsPhasePeek(); switch (command) { case CMD_READ_DATA: case CMD_WRITE_DATA: case CMD_FORMAT: switch (phaseStep++) { case 6: endCommand(time); break; } break; case CMD_SENSE_INTERRUPT_STATUS: switch (phaseStep++) { case 1: endCommand(time); break; } break; case CMD_SENSE_DEVICE_STATUS: switch (phaseStep++) { case 0: endCommand(time); break; } break; default: // nothing break; } return result; } void TC8566AF::writeReg(int reg, byte data, EmuTime::param time) { switch (reg) { case 2: // control register 0 drive[3]->setMotor((data & 0x80) != 0, time); drive[2]->setMotor((data & 0x40) != 0, time); drive[1]->setMotor((data & 0x20) != 0, time); drive[0]->setMotor((data & 0x10) != 0, time); //enableIntDma = data & 0x08; //notReset = data & 0x04; driveSelect = data & 0x03; break; //case 3: // control register 1 // controlReg1 = data; // break; case 5: // data port writeDataPort(data, time); break; } } void TC8566AF::writeDataPort(byte value, EmuTime::param time) { switch (phase) { case PHASE_IDLE: idlePhaseWrite(value, time); break; case PHASE_COMMAND: commandPhaseWrite(value, time); break; case PHASE_DATATRANSFER: executionPhaseWrite(value, time); break; default: // nothing break; } } void TC8566AF::idlePhaseWrite(byte value, EmuTime::param time) { command = CMD_UNKNOWN; commandCode = value; if ((commandCode & 0x1f) == 0x06) command = CMD_READ_DATA; if ((commandCode & 0x3f) == 0x05) command = CMD_WRITE_DATA; if ((commandCode & 0x3f) == 0x09) command = CMD_WRITE_DELETED_DATA; if ((commandCode & 0x1f) == 0x0c) command = CMD_READ_DELETED_DATA; if ((commandCode & 0xbf) == 0x02) command = CMD_READ_DIAGNOSTIC; if ((commandCode & 0xbf) == 0x0a) command = CMD_READ_ID; if ((commandCode & 0xbf) == 0x0d) command = CMD_FORMAT; if ((commandCode & 0x1f) == 0x11) command = CMD_SCAN_EQUAL; if ((commandCode & 0x1f) == 0x19) command = CMD_SCAN_LOW_OR_EQUAL; if ((commandCode & 0x1f) == 0x1d) command = CMD_SCAN_HIGH_OR_EQUAL; if ((commandCode & 0xff) == 0x0f) command = CMD_SEEK; if ((commandCode & 0xff) == 0x07) command = CMD_RECALIBRATE; if ((commandCode & 0xff) == 0x08) command = CMD_SENSE_INTERRUPT_STATUS; if ((commandCode & 0xff) == 0x03) command = CMD_SPECIFY; if ((commandCode & 0xff) == 0x04) command = CMD_SENSE_DEVICE_STATUS; phase = PHASE_COMMAND; phaseStep = 0; mainStatus |= STM_CB; switch (command) { case CMD_READ_DATA: case CMD_WRITE_DATA: case CMD_FORMAT: status0 &= ~(ST0_IC0 | ST0_IC1); status1 = 0; status2 = 0; //MT = value & 0x80; //MFM = value & 0x40; //SK = value & 0x20; break; case CMD_RECALIBRATE: status0 &= ~ST0_SE; break; case CMD_SENSE_INTERRUPT_STATUS: resultPhase(); break; case CMD_SEEK: case CMD_SPECIFY: case CMD_SENSE_DEVICE_STATUS: break; default: endCommand(time); } } void TC8566AF::commandPhase1(byte value) { drive[driveSelect]->setSide((value & 0x04) != 0); status0 &= ~(ST0_DS0 | ST0_DS1 | ST0_IC0 | ST0_IC1); status0 |= //(drive[driveSelect]->isDiskInserted() ? 0 : ST0_DS0) | (value & (ST0_DS0 | ST0_DS1)) | (drive[driveSelect]->isDummyDrive() ? ST0_IC1 : 0); status3 = (value & (ST3_DS0 | ST3_DS1)) | (drive[driveSelect]->isTrack00() ? ST3_TK0 : 0) | (drive[driveSelect]->isDoubleSided() ? ST3_HD : 0) | (drive[driveSelect]->isWriteProtected() ? ST3_WP : 0) | (drive[driveSelect]->isDiskInserted() ? ST3_RDY : 0); } EmuTime TC8566AF::locateSector(EmuTime::param time) { RawTrack::Sector sectorInfo; int lastIdx = -1; EmuTime next = time; while (true) { try { next = drive[driveSelect]->getNextSector( next, trackData, sectorInfo); setDrqRate(); } catch (MSXException& /*e*/) { return EmuTime::infinity; } if ((next == EmuTime::infinity) || (sectorInfo.addrIdx == lastIdx)) { // no sectors on track or sector already seen return EmuTime::infinity; } if (lastIdx == -1) lastIdx = sectorInfo.addrIdx; if (sectorInfo.addrCrcErr) continue; if (sectorInfo.track != cylinderNumber) continue; if (sectorInfo.head != headNumber) continue; if (sectorInfo.sector != sectorNumber) continue; break; } // TODO does TC8566AF look at lower 3 bits? dataAvailable = 128 << (sectorInfo.sizeCode & 7); dataCurrent = sectorInfo.dataIdx; return next; } void TC8566AF::commandPhaseWrite(byte value, EmuTime::param time) { switch (command) { case CMD_READ_DATA: case CMD_WRITE_DATA: switch (phaseStep++) { case 0: commandPhase1(value); break; case 1: cylinderNumber = value; break; case 2: headNumber = value; break; case 3: sectorNumber = value; break; case 4: number = value; break; case 5: // End Of Track break; case 6: // Gap Length break; case 7: // Data length phase = PHASE_DATATRANSFER; phaseStep = 0; //interrupt = true; // load drive head, if not already loaded EmuTime ready = time; if (!isHeadLoaded(time)) { ready += getHeadLoadDelay(); // set 'head is loaded' headUnloadTime = EmuTime::infinity; } // actually read sector: fills in // trackData, dataAvailable and dataCurrent ready = locateSector(ready); if (ready == EmuTime::infinity) { status0 |= ST0_IC0; status1 |= ST1_ND; resultPhase(); return; } if (command == CMD_READ_DATA) { mainStatus |= STM_DIO; } else { mainStatus &= ~STM_DIO; } // Initialize crc // TODO 0xFB vs 0xF8 depends on deleted vs normal data crc.init<0xA1, 0xA1, 0xA1, 0xFB>(); // first byte is available when it's rotated below the // drive-head delayTime.reset(ready); mainStatus &= ~STM_RQM; break; } break; case CMD_FORMAT: switch (phaseStep++) { case 0: commandPhase1(value); break; case 1: number = value; break; case 2: sectorsPerCylinder = value; sectorNumber = value; break; case 3: gapLength = value; break; case 4: fillerByte = value; mainStatus &= ~STM_DIO; phase = PHASE_DATATRANSFER; phaseStep = 0; //interrupt = true; initTrackHeader(time); break; } break; case CMD_SEEK: switch (phaseStep++) { case 0: commandPhase1(value); break; case 1: seekValue = value; // target track doSeek(time); break; } break; case CMD_RECALIBRATE: switch (phaseStep++) { case 0: commandPhase1(value); seekValue = 255; // max try 255 steps doSeek(time); break; } break; case CMD_SPECIFY: specifyData[phaseStep] = value; switch (phaseStep++) { case 1: endCommand(time); break; } break; case CMD_SENSE_DEVICE_STATUS: switch (phaseStep++) { case 0: commandPhase1(value); resultPhase(); break; } break; default: // nothing break; } } void TC8566AF::initTrackHeader(EmuTime::param time) { try { // get track length, see comment in WD2793 for details. drive[driveSelect]->readTrack(trackData); } catch (MSXException& /*e*/) { endCommand(time); } trackData.clear(trackData.getLength()); setDrqRate(); dataCurrent = 0; dataAvailable = trackData.getLength(); for (int i = 0; i < 80; ++i) trackData.write(dataCurrent++, 0x4E); // gap4a for (int i = 0; i < 12; ++i) trackData.write(dataCurrent++, 0x00); // sync for (int i = 0; i < 3; ++i) trackData.write(dataCurrent++, 0xC2); // index mark for (int i = 0; i < 1; ++i) trackData.write(dataCurrent++, 0xFC); // " " for (int i = 0; i < 50; ++i) trackData.write(dataCurrent++, 0x4E); // gap1 } void TC8566AF::formatSector() { for (int i = 0; i < 12; ++i) trackData.write(dataCurrent++, 0x00); // sync for (int i = 0; i < 3; ++i) trackData.write(dataCurrent++, 0xA1); // addr mark trackData.addIdam(dataCurrent); for (int i = 0; i < 1; ++i) trackData.write(dataCurrent++, 0xFE); // " " trackData.write(dataCurrent++, currentTrack); // C: Cylinder number trackData.write(dataCurrent++, headNumber); // H: Head Address trackData.write(dataCurrent++, sectorNumber); // R: Record trackData.write(dataCurrent++, number); // N: Length of sector word addrCrc = trackData.calcCrc(dataCurrent - 8, 8); trackData.write(dataCurrent++, addrCrc >> 8); // CRC (high byte) trackData.write(dataCurrent++, addrCrc & 0xff); // (low byte) for (int i = 0; i < 22; ++i) trackData.write(dataCurrent++, 0x4E); // gap2 for (int i = 0; i < 12; ++i) trackData.write(dataCurrent++, 0x00); // sync for (int i = 0; i < 3; ++i) trackData.write(dataCurrent++, 0xA1); // data mark for (int i = 0; i < 1; ++i) trackData.write(dataCurrent++, 0xFB); // " " int sectorSize = 128 << (number & 7); // 2 -> 512bytes for (int i = 0; i < sectorSize; ++i) trackData.write(dataCurrent++, fillerByte); word dataCrc = trackData.calcCrc(dataCurrent - (sectorSize + 4), sectorSize + 4); trackData.write(dataCurrent++, dataCrc >> 8); // CRC (high byte) trackData.write(dataCurrent++, dataCrc & 0xff); // (low byte) for (int i = 0; i < gapLength; ++i) trackData.write(dataCurrent++, 0x4E); // gap3 } void TC8566AF::doSeek(EmuTime::param time) { DiskDrive& currentDrive = *drive[driveSelect]; bool direction = false; // initialize to avoid warning switch (command) { case CMD_SEEK: if (seekValue > currentTrack) { ++currentTrack; direction = true; } else if (seekValue < currentTrack) { --currentTrack; direction = false; } else { assert(seekValue == currentTrack); status0 |= ST0_SE; endCommand(time); return; } break; case CMD_RECALIBRATE: if (currentDrive.isTrack00() || (seekValue == 0)) { if (seekValue == 0) { status0 |= ST0_EC; } currentTrack = 0; status0 |= ST0_SE; endCommand(time); return; } direction = false; --seekValue; break; default: UNREACHABLE; } currentDrive.step(direction, time); setSyncPoint(time + getSeekDelay()); } void TC8566AF::executeUntil(EmuTime::param time) { if ((command == CMD_SEEK) || (command == CMD_RECALIBRATE)) { doSeek(time); } } void TC8566AF::writeSector() { // write 2 CRC bytes (big endian) trackData.write(dataCurrent++, crc.getValue() >> 8); trackData.write(dataCurrent++, crc.getValue() & 0xFF); drive[driveSelect]->writeTrack(trackData); } void TC8566AF::executionPhaseWrite(byte value, EmuTime::param time) { switch (command) { case CMD_WRITE_DATA: assert(dataAvailable); trackData.write(dataCurrent++, value); crc.update(value); --dataAvailable; delayTime += 1; // time when next byte can be written mainStatus &= ~STM_RQM; if (delayTime.before(time)) { // lost data status0 |= ST0_IC0; status1 |= ST1_OR; resultPhase(); } else if (!dataAvailable) { try { writeSector(); } catch (MSXException&) { status0 |= ST0_IC0; status1 |= ST1_NW; } resultPhase(); } break; case CMD_FORMAT: delayTime += 1; // correct? mainStatus &= ~STM_RQM; switch (phaseStep & 3) { case 0: currentTrack = value; break; case 1: headNumber = value; break; case 2: sectorNumber = value; break; case 3: number = value; formatSector(); break; } ++phaseStep; if (phaseStep == 4 * sectorsPerCylinder) { // data for all sectors was written, now write track try { drive[driveSelect]->writeTrack(trackData); } catch (MSXException&) { status0 |= ST0_IC0; status1 |= ST1_NW; } resultPhase(); } break; default: // nothing break; } } void TC8566AF::resultPhase() { mainStatus |= STM_DIO | STM_RQM; phase = PHASE_RESULT; phaseStep = 0; //interrupt = true; } void TC8566AF::endCommand(EmuTime::param time) { phase = PHASE_IDLE; mainStatus &= ~(STM_CB | STM_DIO); delayTime.reset(time); // set STM_RQM if (headUnloadTime == EmuTime::infinity) { headUnloadTime = time + getHeadUnloadDelay(); } } bool TC8566AF::diskChanged(unsigned driveNum) { assert(driveNum < 4); return drive[driveNum]->diskChanged(); } bool TC8566AF::peekDiskChanged(unsigned driveNum) const { assert(driveNum < 4); return drive[driveNum]->peekDiskChanged(); } bool TC8566AF::isHeadLoaded(EmuTime::param time) const { return time < headUnloadTime; } EmuDuration TC8566AF::getHeadLoadDelay() const { return EmuDuration::msec(2 * (specifyData[1] >> 1)); // 2ms per unit } EmuDuration TC8566AF::getHeadUnloadDelay() const { return EmuDuration::msec(16 * (specifyData[0] & 0x0F)); // 16ms per unit } EmuDuration TC8566AF::getSeekDelay() const { return EmuDuration::msec(16 - (specifyData[0] >> 4)); // 1ms per unit } static enum_string commandInfo[] = { { "UNKNOWN", TC8566AF::CMD_UNKNOWN }, { "READ_DATA", TC8566AF::CMD_READ_DATA }, { "WRITE_DATA", TC8566AF::CMD_WRITE_DATA }, { "WRITE_DELETED_DATA", TC8566AF::CMD_WRITE_DELETED_DATA }, { "READ_DELETED_DATA", TC8566AF::CMD_READ_DELETED_DATA }, { "READ_DIAGNOSTIC", TC8566AF::CMD_READ_DIAGNOSTIC }, { "READ_ID", TC8566AF::CMD_READ_ID }, { "FORMAT", TC8566AF::CMD_FORMAT }, { "SCAN_EQUAL", TC8566AF::CMD_SCAN_EQUAL }, { "SCAN_LOW_OR_EQUAL", TC8566AF::CMD_SCAN_LOW_OR_EQUAL }, { "SCAN_HIGH_OR_EQUAL", TC8566AF::CMD_SCAN_HIGH_OR_EQUAL }, { "SEEK", TC8566AF::CMD_SEEK }, { "RECALIBRATE", TC8566AF::CMD_RECALIBRATE }, { "SENSE_INTERRUPT_STATUS", TC8566AF::CMD_SENSE_INTERRUPT_STATUS }, { "SPECIFY", TC8566AF::CMD_SPECIFY }, { "SENSE_DEVICE_STATUS", TC8566AF::CMD_SENSE_DEVICE_STATUS } }; SERIALIZE_ENUM(TC8566AF::Command, commandInfo); static enum_string phaseInfo[] = { { "IDLE", TC8566AF::PHASE_IDLE }, { "COMMAND", TC8566AF::PHASE_COMMAND }, { "DATATRANSFER", TC8566AF::PHASE_DATATRANSFER }, { "RESULT", TC8566AF::PHASE_RESULT } }; SERIALIZE_ENUM(TC8566AF::Phase, phaseInfo); // version 1: initial version // version 2: added specifyData, headUnloadTime, seekValue // inherit from Schedulable // version 3: Replaced 'sectorSize', 'sectorOffset', 'sectorBuf' // with 'dataAvailable', 'dataCurrent', .trackData'. // Not 100% backwardscompatible, see also comments in WD2793. // Added 'crc' and 'gapLength'. // version 4: changed type of delayTime from Clock to DynamicClock template void TC8566AF::serialize(Archive& ar, unsigned version) { if (ar.versionAtLeast(version, 4)) { ar.serialize("delayTime", delayTime); } else { assert(ar.isLoader()); Clock<6250 * 5> c(EmuTime::dummy()); ar.serialize("delayTime", c); delayTime.reset(c.getTime()); delayTime.setFreq(6250 * 5); } ar.serialize("command", command); ar.serialize("phase", phase); ar.serialize("phaseStep", phaseStep); ar.serialize("driveSelect", driveSelect); ar.serialize("mainStatus", mainStatus); ar.serialize("status0", status0); ar.serialize("status1", status1); ar.serialize("status2", status2); ar.serialize("status3", status3); ar.serialize("commandCode", commandCode); ar.serialize("cylinderNumber", cylinderNumber); ar.serialize("headNumber", headNumber); ar.serialize("sectorNumber", sectorNumber); ar.serialize("number", number); ar.serialize("currentTrack", currentTrack); ar.serialize("sectorsPerCylinder", sectorsPerCylinder); ar.serialize("fillerByte", fillerByte); if (ar.versionAtLeast(version, 2)) { ar.template serializeBase(*this); ar.serialize("specifyData", specifyData); ar.serialize("headUnloadTime", headUnloadTime); ar.serialize("seekValue", seekValue); } else { assert(ar.isLoader()); specifyData[0] = 0xDF; // values normally set by TurboR disk rom specifyData[1] = 0x03; headUnloadTime = EmuTime::zero; seekValue = 0; } if (ar.versionAtLeast(version, 3)) { ar.serialize("dataAvailable", dataAvailable); ar.serialize("dataCurrent", dataCurrent); ar.serialize("trackData", trackData); ar.serialize("gapLength", gapLength); word crcVal = crc.getValue(); ar.serialize("crc", crcVal); crc.init(crcVal); } else { // Compared to previous versions the buffer managment worked // differently (was sector oriented instead of track oriented). // Converting the old state to the new state is not that easy, // we only give a warning when an old savestate that was in // the middle of a read/write operation is loaded. //ar.serialize("sectorSize", sectorSize); //ar.serialize("sectorOffset", sectorOffset); //ar.serialize_blob("sectorBuf", sectorBuf, sizeof(sectorBuf)); //TODO wrning if ((phase == PHASE_DATATRANSFER) && ((command == CMD_READ_DATA) || (command == CMD_WRITE_DATA) || (command == CMD_FORMAT))) { cliComm.printWarning( "Loading an old savestate that had an " "in-progress TC8566AF data-transfer command. " "This is not fully backwards-compatible and " "could cause wrong emulation behavior."); } } }; INSTANTIATE_SERIALIZE_METHODS(TC8566AF); } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/TC8566AF.hh000066400000000000000000000061321257557151200173530ustar00rootroot00000000000000#ifndef TC8566AF_HH #define TC8566AF_HH #include "DynamicClock.hh" #include "RawTrack.hh" #include "CRC16.hh" #include "Schedulable.hh" #include "serialize_meta.hh" #include "openmsx.hh" namespace openmsx { class Scheduler; class DiskDrive; class CliComm; class TC8566AF final : public Schedulable /* private noncopyable */ { public: TC8566AF(Scheduler& scheduler, DiskDrive* drive[4], CliComm& cliComm, EmuTime::param time); void reset(EmuTime::param time); byte readReg(int reg, EmuTime::param time); byte peekReg(int reg, EmuTime::param time) const; void writeReg(int reg, byte data, EmuTime::param time); bool diskChanged(unsigned driveNum); bool peekDiskChanged(unsigned driveNum) const; template void serialize(Archive& ar, unsigned version); // public for serialization enum Command { CMD_UNKNOWN, CMD_READ_DATA, CMD_WRITE_DATA, CMD_WRITE_DELETED_DATA, CMD_READ_DELETED_DATA, CMD_READ_DIAGNOSTIC, CMD_READ_ID, CMD_FORMAT, CMD_SCAN_EQUAL, CMD_SCAN_LOW_OR_EQUAL, CMD_SCAN_HIGH_OR_EQUAL, CMD_SEEK, CMD_RECALIBRATE, CMD_SENSE_INTERRUPT_STATUS, CMD_SPECIFY, CMD_SENSE_DEVICE_STATUS, }; enum Phase { PHASE_IDLE, PHASE_COMMAND, PHASE_DATATRANSFER, PHASE_RESULT, }; private: // Schedulable void executeUntil(EmuTime::param time) override; byte peekDataPort(EmuTime::param time) const; byte readDataPort(EmuTime::param time); byte peekStatus() const; byte readStatus(EmuTime::param time); byte executionPhasePeek(EmuTime::param time) const; byte executionPhaseRead(EmuTime::param time); byte resultsPhasePeek() const; byte resultsPhaseRead(EmuTime::param time); void writeDataPort(byte value, EmuTime::param time); void idlePhaseWrite(byte value, EmuTime::param time); void commandPhase1(byte value); void commandPhaseWrite(byte value, EmuTime::param time); void doSeek(EmuTime::param time); void executionPhaseWrite(byte value, EmuTime::param time); void resultPhase(); void endCommand(EmuTime::param time); bool isHeadLoaded(EmuTime::param time) const; EmuDuration getHeadLoadDelay() const; EmuDuration getHeadUnloadDelay() const; EmuDuration getSeekDelay() const; EmuTime locateSector(EmuTime::param time); void writeSector(); void initTrackHeader(EmuTime::param time); void formatSector(); void setDrqRate(); private: CliComm& cliComm; DiskDrive* drive[4]; DynamicClock delayTime; EmuTime headUnloadTime; // Before this time head is loaded, after // this time it's unloaded. Set to zero/infinity // to force a (un)loaded head. Command command; Phase phase; int phaseStep; //bool interrupt; RawTrack trackData; int dataAvailable; int dataCurrent; CRC16 crc; byte driveSelect; byte mainStatus; byte status0; byte status1; byte status2; byte status3; byte commandCode; byte cylinderNumber; byte headNumber; byte sectorNumber; byte number; byte currentTrack; byte sectorsPerCylinder; byte fillerByte; byte gapLength; byte specifyData[2]; // filled in by SPECIFY command byte seekValue; }; SERIALIZE_CLASS_VERSION(TC8566AF, 4); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/TurboRFDC.cc000066400000000000000000000132241257557151200200250ustar00rootroot00000000000000/* * Based on code from NLMSX written by Frits Hilderink */ #include "TurboRFDC.hh" #include "MSXCPU.hh" #include "CacheLine.hh" #include "MSXException.hh" #include "serialize.hh" namespace openmsx { static TurboRFDC::Type parseType(const DeviceConfig& config) { auto ioregs = config.getChildData("io_regs", ""); if (ioregs == "7FF2") { return TurboRFDC::R7FF2; } else if (ioregs == "7FF8") { return TurboRFDC::R7FF8; } else if (ioregs == "") { // for backwards compatibility return TurboRFDC::BOTH; } else { throw MSXException( "Invalid 'io_regs' specification: expected one of " "'7FF2' or '7FF8', but got: " + ioregs); } } TurboRFDC::TurboRFDC(const DeviceConfig& config) : MSXFDC(config) , controller(getScheduler(), reinterpret_cast(drives), getCliComm(), getCurrentTime()) , romBlockDebug(*this, &bank, 0x4000, 0x4000, 14) , blockMask((rom.getSize() / 0x4000) - 1) , type(parseType(config)) { reset(getCurrentTime()); } void TurboRFDC::reset(EmuTime::param time) { setBank(0); controller.reset(time); } byte TurboRFDC::readMem(word address, EmuTime::param time) { if (0x3FF0 <= (address & 0x3FFF)) { // Reading or writing to this region takes 1 extra clock // cycle. But only in R800 mode. Verified on a real turboR // machine, it happens for all 16 positions in this region // and both for reading and writing. getCPU().waitCyclesR800(1); if (type != R7FF8) { // turboR or BOTH switch (address & 0xF) { case 0x1: { byte result = 0x33; if (controller.diskChanged(0)) result &= ~0x10; if (controller.diskChanged(1)) result &= ~0x20; return result; } case 0x4: return controller.readReg(4, time); case 0x5: return controller.readReg(5, time); } } if (type != R7FF2) { // non-turboR or BOTH switch (address & 0xF) { case 0xA: return controller.readReg(4, time); case 0xB: return controller.readReg(5, time); } } } // all other stuff is handled by peekMem() return TurboRFDC::peekMem(address, time); } byte TurboRFDC::peekMem(word address, EmuTime::param time) const { if (0x3FF0 <= (address & 0x3FFF)) { // note: this implementation requires that the handled // addresses for the MSX2 and TURBOR variants don't overlap if (type != R7FF8) { // turboR or BOTH switch (address & 0xF) { case 0x0: return bank; case 0x1: { // bit 0 FD2HD1 High Density detect drive 1 // bit 1 FD2HD2 High Density detect drive 2 // bit 4 FDCHG1 Disk Change detect on drive 1 // bit 5 FDCHG2 Disk Change detect on drive 2 // active low byte result = 0x33; if (controller.peekDiskChanged(0)) result &= ~0x10; if (controller.peekDiskChanged(1)) result &= ~0x20; return result; } case 0x4: return controller.peekReg(4, time); case 0x5: return controller.peekReg(5, time); } } if (type != R7FF2) { // non-turboR or BOTH switch (address & 0xF) { case 0xA: return controller.peekReg(4, time); case 0xB: return controller.peekReg(5, time); } } switch (address & 0xF) { // TODO Any idea what these 4 are? I've confirmed that on a // real FS-A1GT I get these values, though the ROM dumps // contain 0xFF in these locations. When looking at the ROM // content via the 'RomPanasonic' mapper in slot 3-3, you can // see that the ROM dumps are correct (these 4 values are not // part of the ROM). // This MRC post indicates that also on non-turbor machines // you see these 4 values (bluemsx' post of 10-03-2013, 10:15): // http://www.msx.org/forum/msx-talk/development/finally-have-feature-request-openmsx?page=3 case 0xC: return 0xFC; case 0xD: return 0xFC; case 0xE: return 0xFF; case 0xF: return 0x3F; default: return 0xFF; // other regs in this region } } else if ((0x4000 <= address) && (address < 0x8000)) { return memory[address & 0x3FFF]; } else { return 0xFF; } } const byte* TurboRFDC::getReadCacheLine(word start) const { if ((start & 0x3FF0) == (0x3FF0 & CacheLine::HIGH)) { return nullptr; } else if ((0x4000 <= start) && (start < 0x8000)) { return &memory[start & 0x3FFF]; } else { return unmappedRead; } } void TurboRFDC::writeMem(word address, byte value, EmuTime::param time) { if (0x3FF0 <= (address & 0x3FFF)) { // See comment in readMem(). getCPU().waitCyclesR800(1); } if ((address == 0x6000) || (address == 0x7FF0) || (address == 0x7FFE)) { // TODO Is this correct? Are these 3 switch addresses used in // all variants? setBank(value); } else { if (type != R7FF8) { // turboR or BOTH switch (address & 0x3FFF) { case 0x3FF2: case 0x3FF3: case 0x3FF4: case 0x3FF5: controller.writeReg(address & 0xF, value, time); break; } } if (type != R7FF2) { // non-turboR or BOTH switch (address & 0x3FFF) { case 0x3FF8: case 0x3FF9: case 0x3FFA: case 0x3FFB: controller.writeReg((address & 0xF) - 6, value, time); break; } } } } void TurboRFDC::setBank(byte value) { invalidateMemCache(0x4000, 0x4000); bank = value & blockMask; memory = &rom[0x4000 * bank]; } byte* TurboRFDC::getWriteCacheLine(word address) const { if ((address == (0x6000 & CacheLine::HIGH)) || (address == (0x7FF0 & CacheLine::HIGH)) || (address == (0x7FFE & CacheLine::HIGH)) || ((address & 0x3FF0) == (0x3FF0 & CacheLine::HIGH))) { return nullptr; } else { return unmappedWrite; } } template void TurboRFDC::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("TC8566AF", controller); ar.serialize("bank", bank); if (ar.isLoader()) { setBank(bank); } } INSTANTIATE_SERIALIZE_METHODS(TurboRFDC); REGISTER_MSXDEVICE(TurboRFDC, "TurboRFDC"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/TurboRFDC.hh000066400000000000000000000016151257557151200200400ustar00rootroot00000000000000#ifndef TURBORFDC_HH #define TURBORFDC_HH #include "MSXFDC.hh" #include "RomBlockDebuggable.hh" #include "TC8566AF.hh" namespace openmsx { class TurboRFDC final : public MSXFDC { public: enum Type { BOTH, R7FF2, R7FF8 }; explicit TurboRFDC(const DeviceConfig& config); void reset(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: void setBank(byte value); TC8566AF controller; RomBlockDebuggable romBlockDebug; const byte* memory; const byte blockMask; byte bank; const Type type; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/VictorFDC.cc000066400000000000000000000114141257557151200200550ustar00rootroot00000000000000#include "VictorFDC.hh" #include "CacheLine.hh" #include "DriveMultiplexer.hh" #include "WD2793.hh" #include "serialize.hh" // This implementation is documented in the HC-95 service manual: // // FDD interface: // 7FF8 I/O FDC STATUS/COMMAND // 7FF9 I/O FDC TRACK REGISTER // 7FFA I/O FDC SECTOR REGISTER // 7FFB I/O FDC DATA REGISTER // 7FFC I/O bit 0 A DRIVE MOTOR ON/OFF "1" ON // I/O bit 1 B DRIVE MOTOR ON/OFF "1" ON // I/O bit 2 DRIVE SELECT "0" A DRIVE "1" B DRIVE // O bit 3 SIDE SELECT "0" SIDE 0 "1" SIDE 1 // I SIDE SELECT "1" SIDE 0 "0" SIDE 1 // I/O bit 4 DRIVE ENABLE "0" ENABLE // bit 5 unused // I bit 6 FDC DATA REQUEST "1" REQUEST // I bit 7 FDC INTERRUPT REQUEST "1" REQUEST namespace openmsx { static const int DRIVE_A_MOTOR = 0x01; static const int DRIVE_B_MOTOR = 0x02; static const int DRIVE_SELECT = 0x04; static const int SIDE_SELECT = 0x08; static const int DRIVE_DISABLE = 0x10; // renamed due to inverse logic static const int DATA_REQUEST = 0x40; static const int INTR_REQUEST = 0x80; VictorFDC::VictorFDC(const DeviceConfig& config) : WD2793BasedFDC(config) { reset(getCurrentTime()); } void VictorFDC::reset(EmuTime::param time) { WD2793BasedFDC::reset(time); // initialize in such way that drives are disabled // (and motors off, etc.) // TODO: test on real machine (this is an assumption) writeMem(0x7FFC, DRIVE_DISABLE, time); } byte VictorFDC::readMem(word address, EmuTime::param time) { byte value; switch (address) { case 0x7FF8: value = controller.getStatusReg(time); break; case 0x7FF9: value = controller.getTrackReg(time); break; case 0x7FFA: value = controller.getSectorReg(time); break; case 0x7FFB: value = controller.getDataReg(time); break; case 0x7FFC: value = driveControls; if (controller.getIRQ(time)) value |= INTR_REQUEST; if (controller.getDTRQ(time)) value |= DATA_REQUEST; value ^= SIDE_SELECT; // inverted break; default: value = VictorFDC::peekMem(address, time); break; } return value; } byte VictorFDC::peekMem(word address, EmuTime::param time) const { byte value; switch (address) { case 0x7FF8: value = controller.peekStatusReg(time); break; case 0x7FF9: value = controller.peekTrackReg(time); break; case 0x7FFA: value = controller.peekSectorReg(time); break; case 0x7FFB: value = controller.peekDataReg(time); break; case 0x7FFC: value = driveControls; if (controller.peekIRQ(time)) value |= INTR_REQUEST; if (controller.peekDTRQ(time)) value |= DATA_REQUEST; value ^= SIDE_SELECT; // inverted break; default: if ((0x4000 <= address) && (address < 0x8000)) { // ROM only visible in 0x4000-0x7FFF value = MSXFDC::peekMem(address, time); } else { value = 255; } break; } return value; } const byte* VictorFDC::getReadCacheLine(word start) const { if ((start & CacheLine::HIGH) == (0x7FF8 & CacheLine::HIGH)) { // FDC at 0x7FF8-0x7FFC return nullptr; } else if ((0x4000 <= start) && (start < 0x8000)) { // ROM at 0x4000-0x7FFF return MSXFDC::getReadCacheLine(start); } else { return unmappedRead; } } void VictorFDC::writeMem(word address, byte value, EmuTime::param time) { switch (address) { case 0x7FF8: controller.setCommandReg(value, time); break; case 0x7FF9: controller.setTrackReg(value, time); break; case 0x7FFA: controller.setSectorReg(value, time); break; case 0x7FFB: controller.setDataReg(value, time); break; case 0x7FFC: DriveMultiplexer::DriveNum drive; if ((value & DRIVE_DISABLE) != 0) { drive = DriveMultiplexer::NO_DRIVE; } else { drive = ((value & DRIVE_SELECT) != 0) ? DriveMultiplexer::DRIVE_B : DriveMultiplexer::DRIVE_A; } multiplexer.selectDrive(drive, time); multiplexer.setSide((value & SIDE_SELECT) != 0); multiplexer.setMotor((drive == DriveMultiplexer::DRIVE_A) ? ((value & DRIVE_A_MOTOR) != 0) : ((value & DRIVE_B_MOTOR) != 0), time); // this is not 100% correct: the motors can be controlled independently via bit 0 and 1 // back up for reading: driveControls = value & (DRIVE_A_MOTOR | DRIVE_B_MOTOR | DRIVE_SELECT | SIDE_SELECT | DRIVE_DISABLE); break; } } byte* VictorFDC::getWriteCacheLine(word address) const { if ((address & CacheLine::HIGH) == (0x7FF8 & CacheLine::HIGH)) { // FDC at 0x7FF8-0x7FFC return nullptr; } else { return unmappedWrite; } } template void VictorFDC::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("driveControls", driveControls); } INSTANTIATE_SERIALIZE_METHODS(VictorFDC); REGISTER_MSXDEVICE(VictorFDC, "VictorFDC"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/VictorFDC.hh000066400000000000000000000012671257557151200200740ustar00rootroot00000000000000#ifndef VICTORFDC_HH #define VICTORFDC_HH #include "WD2793BasedFDC.hh" namespace openmsx { class VictorFDC final : public WD2793BasedFDC { public: explicit VictorFDC(const DeviceConfig& config); void reset(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: byte driveControls; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/WD2793.cc000066400000000000000000000674351257557151200171470ustar00rootroot00000000000000#include "WD2793.hh" #include "DiskDrive.hh" #include "CliComm.hh" #include "Clock.hh" #include "MSXException.hh" #include "serialize.hh" #include "unreachable.hh" #include namespace openmsx { // Status register static const int BUSY = 0x01; static const int INDEX = 0x02; static const int S_DRQ = 0x02; static const int TRACK00 = 0x04; static const int LOST_DATA = 0x04; static const int CRC_ERROR = 0x08; static const int SEEK_ERROR = 0x10; static const int RECORD_NOT_FOUND = 0x10; static const int HEAD_LOADED = 0x20; static const int RECORD_TYPE = 0x20; static const int WRITE_PROTECTED = 0x40; static const int NOT_READY = 0x80; // Command register static const int STEP_SPEED = 0x03; static const int V_FLAG = 0x04; static const int E_FLAG = 0x04; static const int H_FLAG = 0x08; static const int T_FLAG = 0x10; static const int M_FLAG = 0x10; static const int N2R_IRQ = 0x01; static const int R2N_IRQ = 0x02; static const int IDX_IRQ = 0x04; static const int IMM_IRQ = 0x08; /** This class has emulation for WD1770, WD1793, WD2793. Though at the moment * the only emulated difference between WD1770 and WD{12}793 is that WD1770 * has no ready input signal. (E.g. we don't emulate the WD1770 motor out * signal yet). */ WD2793::WD2793(Scheduler& scheduler, DiskDrive& drive_, CliComm& cliComm_, EmuTime::param time, bool isWD1770_) : Schedulable(scheduler) , drive(drive_) , cliComm(cliComm_) , drqTime(EmuTime::infinity) , irqTime(EmuTime::infinity) , pulse5(EmuTime::infinity) , isWD1770(isWD1770_) { // avoid (harmless) UMR in serialize() dataCurrent = 0; dataAvailable = 0; lastWasA1 = false; setDrqRate(); reset(time); } void WD2793::reset(EmuTime::param time) { removeSyncPoint(); fsmState = FSM_NONE; statusReg = 0; trackReg = 0; dataReg = 0; directionIn = true; drqTime.reset(EmuTime::infinity); // DRQ = false irqTime = EmuTime::infinity; // INTRQ = false; immediateIRQ = false; // Execute Restore command sectorReg = 0x01; setCommandReg(0x03, time); } bool WD2793::getDTRQ(EmuTime::param time) { return peekDTRQ(time); } bool WD2793::peekDTRQ(EmuTime::param time) const { return time >= drqTime.getTime(); } void WD2793::setDrqRate() { drqTime.setFreq(trackData.getLength() * DiskDrive::ROTATIONS_PER_SECOND); } bool WD2793::getIRQ(EmuTime::param time) { return peekIRQ(time); } bool WD2793::peekIRQ(EmuTime::param time) const { return immediateIRQ || (irqTime <= time); } bool WD2793::isReady() const { // The WD1770 has no ready input signal (instead that pin is replaced // by a motor-on/off output pin). return drive.isDiskInserted() || isWD1770; } void WD2793::setCommandReg(byte value, EmuTime::param time) { removeSyncPoint(); commandReg = value; irqTime = EmuTime::infinity; // INTRQ = false; switch (commandReg & 0xF0) { case 0x00: // restore case 0x10: // seek case 0x20: // step case 0x30: // step (Update trackRegister) case 0x40: // step-in case 0x50: // step-in (Update trackRegister) case 0x60: // step-out case 0x70: // step-out (Update trackRegister) startType1Cmd(time); break; case 0x80: // read sector case 0x90: // read sector (multi) case 0xA0: // write sector case 0xB0: // write sector (multi) startType2Cmd(time); break; case 0xC0: // Read Address case 0xE0: // read track case 0xF0: // write track startType3Cmd(time); break; case 0xD0: // Force interrupt startType4Cmd(time); break; } } byte WD2793::getStatusReg(EmuTime::param time) { if (((commandReg & 0x80) == 0) || ((commandReg & 0xF0) == 0xD0)) { // Type I or type IV command statusReg &= ~(INDEX | TRACK00 | HEAD_LOADED | WRITE_PROTECTED); if (drive.indexPulse(time)) { statusReg |= INDEX; } if (drive.isTrack00()) { statusReg |= TRACK00; } if (drive.headLoaded(time)) { statusReg |= HEAD_LOADED; } if (drive.isWriteProtected()) { statusReg |= WRITE_PROTECTED; } } else { // Not type I command so bit 1 should be DRQ if (getDTRQ(time)) { statusReg |= S_DRQ; } else { statusReg &= ~S_DRQ; } } if (isReady()) { statusReg &= ~NOT_READY; } else { statusReg |= NOT_READY; } // Reset INTRQ only if it's not scheduled to turn on in the future. if (irqTime <= time) { // if (INTRQ == true) irqTime = EmuTime::infinity; // INTRQ = false; } return statusReg; } byte WD2793::peekStatusReg(EmuTime::param time) const { // TODO implement proper peek? return const_cast(this)->getStatusReg(time); } void WD2793::setTrackReg(byte value, EmuTime::param /*time*/) { trackReg = value; } byte WD2793::getTrackReg(EmuTime::param time) { return peekTrackReg(time); } byte WD2793::peekTrackReg(EmuTime::param /*time*/) const { return trackReg; } void WD2793::setSectorReg(byte value, EmuTime::param /*time*/) { sectorReg = value; } byte WD2793::getSectorReg(EmuTime::param time) { return peekSectorReg(time); } byte WD2793::peekSectorReg(EmuTime::param /*time*/) const { return sectorReg; } void WD2793::setDataReg(byte value, EmuTime::param time) { dataReg = value; if (!getDTRQ(time)) return; assert(statusReg & BUSY); if (((commandReg & 0xE0) == 0xA0) || // write sector ((commandReg & 0xF0) == 0xF0)) { // write track if (fsmState == FSM_CHECK_WRITE) { // 1st byte of a write sector command, // don't automatically re-activate DTRQ drqTime.reset(EmuTime::infinity); // DRQ = false } else { // handle lost bytes drqTime += 1; // time when next byte will be accepted while (dataAvailable && unlikely(getDTRQ(time))) { statusReg |= LOST_DATA; drqTime += 1; trackData.write(dataCurrent++, 0); crc.update(0); dataAvailable--; } } byte write = value; // written value not always same as given value if ((commandReg & 0xF0) == 0xF0) { // write track, handle chars with special meaning bool prevA1 = lastWasA1; lastWasA1 = false; if (value == 0xF5) { // write A1 with missing clock transitions write = 0xA1; lastWasA1 = true; // Initialize CRC: the calculated CRC value // includes the 3 A1 bytes. So when starting // from the initial value 0xffff, we should not // re-initialize the CRC value on the 2nd and // 3rd A1 byte. Though what we do instead is on // each A1 byte initialize the value as if // there were already 2 A1 bytes written. crc.init<0xA1, 0xA1>(); } else if (value == 0xF6) { // write C2 with missing clock transitions write = 0xC2; } else if (value == 0xF7) { // write 2 CRC bytes, big endian word crcVal = crc.getValue(); if (dataAvailable) { drqTime += 1; trackData.write(dataCurrent++, crcVal >> 8); dataAvailable--; } write = crcVal & 0xFF; } else if (value == 0xFE) { // Record locations of 0xA1 (with missing clock // transition) followed by 0xFE. The FE byte has // no special meaning for the WD2793 itself, // but it does for the DMK file format. if (prevA1) { trackData.addIdam(dataCurrent); } } } if (dataAvailable) { trackData.write(dataCurrent++, write); crc.update(write); dataAvailable--; } assert(!dataAvailable || !getDTRQ(time)); } } byte WD2793::getDataReg(EmuTime::param time) { if ((((commandReg & 0xE0) == 0x80) || // read sector ((commandReg & 0xF0) == 0xC0) || // read address ((commandReg & 0xF0) == 0xE0)) && // read track getDTRQ(time)) { assert(statusReg & BUSY); dataReg = trackData.read(dataCurrent++); crc.update(dataReg); dataAvailable--; drqTime += 1; // time when the next byte will be available while (dataAvailable && unlikely(getDTRQ(time))) { statusReg |= LOST_DATA; dataReg = trackData.read(dataCurrent++); crc.update(dataReg); dataAvailable--; drqTime += 1; } assert(!dataAvailable || !getDTRQ(time)); if (dataAvailable == 0) { if ((commandReg & 0xE0) == 0x80) { // read sector // update crc status flag word diskCrc = 256 * trackData.read(dataCurrent++); diskCrc += trackData.read(dataCurrent++); if (diskCrc == crc.getValue()) { statusReg &= ~CRC_ERROR; } else { statusReg |= CRC_ERROR; } if (!(commandReg & M_FLAG)) { endCmd(); } else { // TODO multi sector read sectorReg++; endCmd(); } } else { // read track, read address // TODO check CRC error on 'read address' endCmd(); } } } return dataReg; } byte WD2793::peekDataReg(EmuTime::param time) const { if ((((commandReg & 0xE0) == 0x80) || // read sector ((commandReg & 0xF0) == 0xC0) || // read address ((commandReg & 0xF0) == 0xE0)) && // read track peekDTRQ(time)) { return trackData.read(dataCurrent); } else { return dataReg; } } void WD2793::schedule(FSMState state, EmuTime::param time) { assert(!pendingSyncPoint()); fsmState = state; setSyncPoint(time); } void WD2793::executeUntil(EmuTime::param time) { FSMState state = fsmState; fsmState = FSM_NONE; switch (state) { case FSM_SEEK: if ((commandReg & 0x80) == 0x00) { // Type I command seekNext(time); } break; case FSM_TYPE2_WAIT_LOAD: if ((commandReg & 0xC0) == 0x80) { // Type II command type2WaitLoad(time); } break; case FSM_TYPE2_LOADED: if ((commandReg & 0xC0) == 0x80) { // Type II command type2Loaded(time); } break; case FSM_TYPE2_NOT_FOUND: if ((commandReg & 0xC0) == 0x80) { // Type II command type2NotFound(); } break; case FSM_TYPE2_ROTATED: if ((commandReg & 0xC0) == 0x80) { // Type II command type2Rotated(time); } break; case FSM_CHECK_WRITE: if ((commandReg & 0xE0) == 0xA0) { // write sector command checkStartWrite(time); } break; case FSM_WRITE_SECTOR: if ((commandReg & 0xE0) == 0xA0) { // write sector command doneWriteSector(); } break; case FSM_TYPE3_WAIT_LOAD: if (((commandReg & 0xC0) == 0xC0) && ((commandReg & 0xF0) != 0xD0)) { // Type III command type3WaitLoad(time); } break; case FSM_TYPE3_LOADED: if (((commandReg & 0xC0) == 0xC0) && ((commandReg & 0xF0) != 0xD0)) { // Type III command type3Loaded(time); } break; case FSM_TYPE3_ROTATED: if (((commandReg & 0xC0) == 0xC0) && ((commandReg & 0xF0) != 0xD0)) { // Type III command type3Rotated(time); } break; case FSM_WRITE_TRACK: if ((commandReg & 0xF0) == 0xF0) { // write track command doneWriteTrack(); } break; case FSM_READ_TRACK: if ((commandReg & 0xF0) == 0xE0) { // read track command endCmd(); // TODO check this (e.g. DRQ) } break; default: UNREACHABLE; } } void WD2793::startType1Cmd(EmuTime::param time) { statusReg &= ~(SEEK_ERROR | CRC_ERROR); statusReg |= BUSY; drive.setHeadLoaded((commandReg & H_FLAG) != 0, time); switch (commandReg & 0xF0) { case 0x00: // restore trackReg = 0xFF; dataReg = 0x00; seek(time); break; case 0x10: // seek seek(time); break; case 0x20: // step case 0x30: // step (Update trackRegister) step(time); break; case 0x40: // step-in case 0x50: // step-in (Update trackRegister) directionIn = true; step(time); break; case 0x60: // step-out case 0x70: // step-out (Update trackRegister) directionIn = false; step(time); break; } } void WD2793::seek(EmuTime::param time) { if (trackReg == dataReg) { endType1Cmd(); } else { directionIn = (dataReg > trackReg); step(time); } } void WD2793::step(EmuTime::param time) { const int timePerStep[4] = { // in ms, in case a 1MHz clock is used (as in MSX) 6, 12, 20, 30 }; if ((commandReg & T_FLAG) || ((commandReg & 0xE0) == 0x00)) { // Restore or seek or T_FLAG if (directionIn) { trackReg++; } else { trackReg--; } } if (!directionIn && drive.isTrack00()) { trackReg = 0; endType1Cmd(); } else { drive.step(directionIn, time); schedule(FSM_SEEK, time + EmuDuration::msec(timePerStep[commandReg & STEP_SPEED])); } } void WD2793::seekNext(EmuTime::param time) { if ((commandReg & 0xE0) == 0x00) { // Restore or seek seek(time); } else { endType1Cmd(); } } void WD2793::endType1Cmd() { if (commandReg & V_FLAG) { // verify sequence // TODO verify sequence } endCmd(); } void WD2793::startType2Cmd(EmuTime::param time) { statusReg &= ~(LOST_DATA | RECORD_NOT_FOUND | RECORD_TYPE | WRITE_PROTECTED); statusReg |= BUSY; if (!isReady()) { endCmd(); } else { // WD2795/WD2797 would now set SSO output drive.setHeadLoaded(true, time); if (commandReg & E_FLAG) { schedule(FSM_TYPE2_WAIT_LOAD, time + EmuDuration::msec(30)); // when 1MHz clock } else { type2WaitLoad(time); } } } void WD2793::type2WaitLoad(EmuTime::param time) { // TODO wait till head loaded, I arbitrarily took 1ms delay schedule(FSM_TYPE2_LOADED, time + EmuDuration::msec(1)); } void WD2793::type2Loaded(EmuTime::param time) { if (((commandReg & 0xE0) == 0xA0) && (drive.isWriteProtected())) { // write command and write protected statusReg |= WRITE_PROTECTED; endCmd(); return; } pulse5 = drive.getTimeTillIndexPulse(time, 5); type2Search(time); } void WD2793::type2Search(EmuTime::param time) { assert(time < pulse5); // Locate (next) sector on disk. try { EmuTime next = drive.getNextSector(time, trackData, sectorInfo); setDrqRate(); if (next < pulse5) { // Wait till sector is actually rotated under head schedule(FSM_TYPE2_ROTATED, next); return; } } catch (MSXException& /*e*/) { // nothing } // Sector not found in 5 revolutions (or read error), // schedule to give a RECORD_NOT_FOUND error if (pulse5 < EmuTime::infinity) { schedule(FSM_TYPE2_NOT_FOUND, pulse5); } else { // Drive not rotating. How does a real WD293 handle this? type2NotFound(); } } void WD2793::type2Rotated(EmuTime::param time) { // The CRC status bit should only toggle after the disk has rotated if (sectorInfo.addrCrcErr) { statusReg |= CRC_ERROR; } else { statusReg &= ~CRC_ERROR; } if ((sectorInfo.addrCrcErr) || (sectorInfo.track != trackReg) || (sectorInfo.sector != sectorReg)) { // TODO implement (optional) head compare // not the sector we were looking for, continue searching type2Search(time); return; } // Ok, found matching sector. // Get sectorsize from disk: 128, 256, 512 or 1024 bytes // Verified on real WD2793: // sizecode=255 results in a sector size of 1024 bytes, // This suggests the WD2793 only looks at the lower 2 bits. dataAvailable = 128 << (sectorInfo.sizeCode & 3); dataCurrent = sectorInfo.dataIdx; crc.init<0xA1, 0xA1, 0xA1, 0xFB>(); // TODO possibly A1 A1 A1 F8 switch (commandReg & 0xE0) { case 0x80: // read sector or read sector multi startReadSector(time); break; case 0xA0: // write sector or write sector multi startWriteSector(time); break; } } void WD2793::type2NotFound() { statusReg |= RECORD_NOT_FOUND; endCmd(); } void WD2793::startReadSector(EmuTime::param time) { unsigned gapLength = trackData.wrapIndex( sectorInfo.dataIdx - sectorInfo.addrIdx); drqTime.reset(time); drqTime += gapLength + 1 + 1; // (first) byte can be read in a moment } void WD2793::startWriteSector(EmuTime::param time) { // At the current moment in time, the 'FE' byte in the address mark // is located under the drive head (because the DMK format points to // the 'FE' byte in the address header). After this byte there still // follow the C,H,R,N and 2 crc bytes. So the address header ends in // 7 bytes. // - After 2 more bytes the WD2793 will activate DRQ. // - 8 bytes later the WD2793 will check that the CPU has send the // first byte (if not the command will be aborted without any writes // to the disk, not even gap or data mark bytes). // - after a pauze of 12 bytes, the WD2793 will write 12 zero bytes, // followed by the 4 bytes data header (A1 A1 A1 FB). // - Next the WD2793 write the actual data bytes. At this moment it // will also activate DRQ to receive the 2nd byte from the CPU. // // Note that between the 1st and 2nd activation of DRQ is a longer // durtaion than between all later DRQ activations. The write-sector // routine in Microsol_CDX-2 depends on this. // // TODO after the address header, the WD2793 skips 22 bytes and then // starts writing. The current code instead reuses the location of // the existing data block. drqTime.reset(time); drqTime += 7 + 2; // activate DRQ 2 bytes after end of address header // 8 bytes later, the WD2793 will check whether the CPU wrote the // first byte. schedule(FSM_CHECK_WRITE, drqTime + 8); } void WD2793::checkStartWrite(EmuTime::param time) { // By now the CPU should already have written the first byte, otherwise // the write sector command doesn't even start. if (getDTRQ(time)) { statusReg |= LOST_DATA; endCmd(); return; } // Moment in time when the first data byte will be written (and when // DRQ will be re-activated for the 2nd byte). drqTime.reset(time); drqTime += 12 + 12 + 4; // Moment in time when the sector is fully written. At that time we // will write the collected data to the disk image (whether the // CPU wrote all required data or not). assert((dataAvailable & 0x7F) == 0x7F); // already decreased by one. schedule(FSM_WRITE_SECTOR, drqTime + (dataAvailable + 1)); } void WD2793::doneWriteSector() { try { // any lost data? while (dataAvailable) { statusReg |= LOST_DATA; trackData.write(dataCurrent++, 0); crc.update(0); dataAvailable--; } // write 2 CRC bytes (big endian) trackData.write(dataCurrent++, crc.getValue() >> 8); trackData.write(dataCurrent++, crc.getValue() & 0xFF); // write one byte of 0xFE // TODO check this, datasheet is not very clear about this trackData.write(dataCurrent++, 0xFE); // write sector (actually full track) to disk. drive.writeTrack(trackData); if (!(commandReg & M_FLAG)) { endCmd(); } else { // TODO multi sector write sectorReg++; endCmd(); } } catch (MSXException&) { // Backend couldn't write data // TODO which status bit should be set? statusReg |= RECORD_NOT_FOUND; endCmd(); } } void WD2793::startType3Cmd(EmuTime::param time) { statusReg &= ~(LOST_DATA | RECORD_NOT_FOUND | RECORD_TYPE); statusReg |= BUSY; if (!isReady()) { endCmd(); } else { drive.setHeadLoaded(true, time); // WD2795/WD2797 would now set SSO output if (commandReg & E_FLAG) { schedule(FSM_TYPE3_WAIT_LOAD, time + EmuDuration::msec(30)); // when 1MHz clock } else { type3WaitLoad(time); } } } void WD2793::type3WaitLoad(EmuTime::param time) { // TODO wait till head loaded, I arbitrarily took 1ms delay schedule(FSM_TYPE3_LOADED, time + EmuDuration::msec(1)); } void WD2793::type3Loaded(EmuTime::param time) { // TODO TG43 update if (((commandReg & 0xF0) == 0xF0) && (drive.isWriteProtected())) { // write track command and write protected statusReg |= WRITE_PROTECTED; endCmd(); return; } EmuTime next(EmuTime::dummy()); if ((commandReg & 0xF0) == 0xC0) { // read address try { // wait till next sector header RawTrack::Sector sector; next = drive.getNextSector(time, trackData, sector); setDrqRate(); if (next == EmuTime::infinity) { // TODO wait for 5 revolutions statusReg |= RECORD_NOT_FOUND; endCmd(); return; } dataCurrent = sector.addrIdx; dataAvailable = 6; } catch (MSXException&) { // read addr failed statusReg |= RECORD_NOT_FOUND; endCmd(); return; } } else { // read/write track // wait till next index pulse next = drive.getTimeTillIndexPulse(time); } schedule(FSM_TYPE3_ROTATED, next); } void WD2793::type3Rotated(EmuTime::param time) { switch (commandReg & 0xF0) { case 0xC0: // read Address readAddressCmd(time); break; case 0xE0: // read track readTrackCmd(time); break; case 0xF0: // write track writeTrackCmd(time); break; } } void WD2793::readAddressCmd(EmuTime::param time) { drqTime.reset(time); drqTime += 1; // (first) byte can be read in a moment } void WD2793::readTrackCmd(EmuTime::param time) { try { drive.readTrack(trackData); setDrqRate(); dataCurrent = 0; dataAvailable = trackData.getLength(); drqTime.reset(time); // Stop command at next index pulse schedule(FSM_READ_TRACK, drqTime + dataAvailable); drqTime += 1; // (first) byte can be read in a moment } catch (MSXException&) { // read track failed, TODO status bits? endCmd(); } } void WD2793::writeTrackCmd(EmuTime::param time) { // TODO By now the CPU should already have written the first byte, // otherwise the write track command doesn't even start. This is not // yet implemented. try { // The _only_ reason we call readTrack() is to get the track // length of the existing track. Ideally we should just // overwrite the track with another length. But the DMK file // format cannot handle tracks with different lengths. drive.readTrack(trackData); } catch (MSXException& /*e*/) { endCmd(); } trackData.clear(trackData.getLength()); setDrqRate(); dataCurrent = 0; dataAvailable = trackData.getLength(); drqTime.reset(time); // DRQ = true lastWasA1 = false; // Moment in time when the track will be written (whether the CPU wrote // all required data or not). schedule(FSM_WRITE_TRACK, drqTime + dataAvailable); } void WD2793::doneWriteTrack() { try { // any lost data? while (dataAvailable) { statusReg |= LOST_DATA; trackData.write(dataCurrent, 0); dataCurrent++; dataAvailable--; } drive.writeTrack(trackData); } catch (MSXException&) { // Ignore. Should rarely happen, because // write-protected is already checked at the // beginning of write-track command (maybe // when disk is swapped during format) } endCmd(); } void WD2793::startType4Cmd(EmuTime::param time) { // Force interrupt byte flags = commandReg & 0x0F; if (flags & (N2R_IRQ | R2N_IRQ)) { // all flags not yet supported #ifdef DEBUG std::cerr << "WD2793 type 4 cmd, unimplemented bits " << int(flags) << std::endl; #endif } if (flags == 0x00) { immediateIRQ = false; } if ((flags & IDX_IRQ) && isReady()) { irqTime = drive.getTimeTillIndexPulse(time); } else { assert(irqTime == EmuTime::infinity); // INTRQ = false } if (flags & IMM_IRQ) { immediateIRQ = true; } drqTime.reset(EmuTime::infinity); // DRQ = false statusReg &= ~BUSY; // reset status on Busy } void WD2793::endCmd() { drqTime.reset(EmuTime::infinity); // DRQ = false irqTime = EmuTime::zero; // INTRQ = true; statusReg &= ~BUSY; } static enum_string fsmStateInfo[] = { { "NONE", WD2793::FSM_NONE }, { "SEEK", WD2793::FSM_SEEK }, { "TYPE2_WAIT_LOAD", WD2793::FSM_TYPE2_WAIT_LOAD }, { "TYPE2_LOADED", WD2793::FSM_TYPE2_LOADED }, { "TYPE2_NOT_FOUND", WD2793::FSM_TYPE2_NOT_FOUND }, { "TYPE2_ROTATED", WD2793::FSM_TYPE2_ROTATED }, { "CHECK_WRITE", WD2793::FSM_CHECK_WRITE }, { "WRITE_SECTOR", WD2793::FSM_WRITE_SECTOR }, { "TYPE3_WAIT_LOAD", WD2793::FSM_TYPE3_WAIT_LOAD }, { "TYPE3_LOADED", WD2793::FSM_TYPE3_LOADED }, { "TYPE3_ROTATED", WD2793::FSM_TYPE3_ROTATED }, { "WRITE_TRACK", WD2793::FSM_WRITE_TRACK }, { "READ_TRACK", WD2793::FSM_READ_TRACK }, { "IDX_IRQ", WD2793::FSM_IDX_IRQ } }; SERIALIZE_ENUM(WD2793::FSMState, fsmStateInfo); // version 1: initial version // version 2: removed members: commandStart, DRQTimer, DRQ, transferring, formatting // added member: drqTime (has different semantics than DRQTimer) // also the timing of the data-transfer commands (read/write sector // and write track) has changed. So this could result in replay-sync // errors. // (Also the enum FSMState has changed, but that's not a problem.) // version 3: Added members 'crc' and 'lastWasA1'. // Replaced 'dataBuffer' with 'trackData'. We don't attempt to migrate // the old 'dataBuffer' content to 'trackData' (doing so would be // quite difficult). This means that old savestates that were in the // middle of a sector/track read/write command probably won't work // correctly anymore. We do give a warning on this. // version 4: changed type of drqTime from Clock to DynamicClock // version 5: added 'pulse5' and 'sectorInfo' // version 6: no layout changes, only added new enum value 'FSM_CHECK_WRITE' // version 7: replaced 'bool INTRQ' with 'EmuTime irqTime' // version 8: removed 'userData' from Schedulable template void WD2793::serialize(Archive& ar, unsigned version) { EmuTime bw_irqTime = EmuTime::zero; if (ar.versionAtLeast(version, 8)) { ar.template serializeBase(*this); } else { static const int SCHED_FSM = 0; static const int SCHED_IDX_IRQ = 1; assert(ar.isLoader()); removeSyncPoint(); for (auto& old : Schedulable::serializeBW(ar)) { if (old.userData == SCHED_FSM) { setSyncPoint(old.time); } else if (old.userData == SCHED_IDX_IRQ) { bw_irqTime = old.time; } } } ar.serialize("fsmState", fsmState); ar.serialize("statusReg", statusReg); ar.serialize("commandReg", commandReg); ar.serialize("sectorReg", sectorReg); ar.serialize("trackReg", trackReg); ar.serialize("dataReg", dataReg); ar.serialize("directionIn", directionIn); ar.serialize("immediateIRQ", immediateIRQ); ar.serialize("dataCurrent", dataCurrent); ar.serialize("dataAvailable", dataAvailable); if (ar.versionAtLeast(version, 2)) { if (ar.versionAtLeast(version, 4)) { ar.serialize("drqTime", drqTime); } else { assert(ar.isLoader()); Clock<6250 * 5> c(EmuTime::dummy()); ar.serialize("drqTime", c); drqTime.reset(c.getTime()); drqTime.setFreq(6250 * 5); } } else { assert(ar.isLoader()); //ar.serialize("commandStart", commandStart); //ar.serialize("DRQTimer", DRQTimer); //ar.serialize("DRQ", DRQ); //ar.serialize("transferring", transferring); //ar.serialize("formatting", formatting); drqTime.reset(EmuTime::infinity); // Compared to version 1, the datatransfer commands are // implemented very differently. We don't attempt to restore // the correct state from the old savestate. But we do give a // warning. if ((statusReg & BUSY) && (((commandReg & 0xC0) == 0x80) || // read/write sector ((commandReg & 0xF0) == 0xF0))) { // write track cliComm.printWarning( "Loading an old savestate that had an " "in-progress WD2793 data-transfer command. " "This is not fully backwards-compatible and " "could cause wrong emulation behavior."); } } if (ar.versionAtLeast(version, 3)) { ar.serialize("trackData", trackData); ar.serialize("lastWasA1", lastWasA1); word crcVal = crc.getValue(); ar.serialize("crc", crcVal); crc.init(crcVal); } else { assert(ar.isLoader()); //ar.serialize_blob("dataBuffer", dataBuffer, sizeof(dataBuffer)); // Compared to version 1 or 2, the databuffer works different: // before we only stored the data of the logical sector, now // we store the full content of the raw track. We don't attempt // to migrate the old format to the new one (it's not very // easy). We only give a warning. if ((statusReg & BUSY) && (((commandReg & 0xC0) == 0x80) || // read/write sector ((commandReg & 0xF0) == 0xF0))) { // write track cliComm.printWarning( "Loading an old savestate that had an " "in-progress WD2793 data-transfer command. " "This is not fully backwards-compatible and " "could cause wrong emulation behavior."); } } if (ar.versionAtLeast(version, 5)) { ar.serialize("pulse5", pulse5); ar.serialize("sectorInfo", sectorInfo); } else { // leave pulse5 at EmuTime::infinity // leave sectorInfo uninitialized } if (ar.versionAtLeast(version, 7)) { ar.serialize("irqTime", irqTime); } else { assert(ar.isLoader()); bool INTRQ = false; // dummy init to avoid warning ar.serialize("INTRQ", INTRQ); irqTime = INTRQ ? EmuTime::zero : EmuTime::infinity; if (bw_irqTime != EmuTime::zero) { irqTime = bw_irqTime; } } } INSTANTIATE_SERIALIZE_METHODS(WD2793); } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/WD2793.hh000066400000000000000000000066721257557151200171550ustar00rootroot00000000000000#ifndef WD2793_HH #define WD2793_HH #include "RawTrack.hh" #include "DynamicClock.hh" #include "Schedulable.hh" #include "CRC16.hh" #include "serialize_meta.hh" namespace openmsx { class Scheduler; class DiskDrive; class CliComm; class WD2793 final : public Schedulable { public: WD2793(Scheduler& scheduler, DiskDrive& drive, CliComm& cliComm, EmuTime::param time, bool isWD1770); void reset(EmuTime::param time); byte getStatusReg(EmuTime::param time); byte getTrackReg (EmuTime::param time); byte getSectorReg(EmuTime::param time); byte getDataReg (EmuTime::param time); byte peekStatusReg(EmuTime::param time) const; byte peekTrackReg (EmuTime::param time) const; byte peekSectorReg(EmuTime::param time) const; byte peekDataReg (EmuTime::param time) const; void setCommandReg(byte value, EmuTime::param time); void setTrackReg (byte value, EmuTime::param time); void setSectorReg (byte value, EmuTime::param time); void setDataReg (byte value, EmuTime::param time); bool getIRQ (EmuTime::param time); bool getDTRQ(EmuTime::param time); bool peekIRQ (EmuTime::param time) const; bool peekDTRQ(EmuTime::param time) const; template void serialize(Archive& ar, unsigned version); // public for serialize enum FSMState { FSM_NONE, FSM_SEEK, FSM_TYPE2_WAIT_LOAD, FSM_TYPE2_LOADED, FSM_TYPE2_NOT_FOUND, FSM_TYPE2_ROTATED, FSM_CHECK_WRITE, FSM_WRITE_SECTOR, FSM_TYPE3_WAIT_LOAD, FSM_TYPE3_LOADED, FSM_TYPE3_ROTATED, FSM_WRITE_TRACK, FSM_READ_TRACK, FSM_IDX_IRQ }; private: void executeUntil(EmuTime::param time) override; void startType1Cmd(EmuTime::param time); void seek(EmuTime::param time); void step(EmuTime::param time); void seekNext(EmuTime::param time); void endType1Cmd(); void startType2Cmd (EmuTime::param time); void type2WaitLoad (EmuTime::param time); void type2Loaded (EmuTime::param time); void type2Search (EmuTime::param time); void type2NotFound (); void type2Rotated (EmuTime::param time); void startReadSector (EmuTime::param time); void startWriteSector(EmuTime::param time); void checkStartWrite (EmuTime::param time); void doneWriteSector(); void startType3Cmd (EmuTime::param time); void type3WaitLoad (EmuTime::param time); void type3Loaded (EmuTime::param time); void type3Rotated (EmuTime::param time); void readAddressCmd(EmuTime::param time); void readTrackCmd (EmuTime::param time); void writeTrackCmd (EmuTime::param time); void doneWriteTrack(); void startType4Cmd(EmuTime::param time); void endCmd(); void setDrqRate(); bool isReady() const; void schedule(FSMState state, EmuTime::param time); private: DiskDrive& drive; CliComm& cliComm; // DRQ is high iff current time is past this time. // This clock ticks at the 'byte-rate' of the current track, // typically '6250 bytes/rotation * 5 rotations/second'. DynamicClock drqTime; // INTRQ is high iff current time is past this time. EmuTime irqTime; EmuTime pulse5; // time at which the 5th index pulse will be received RawTrack trackData; RawTrack::Sector sectorInfo; int dataCurrent; // which byte in track is next to be read/write int dataAvailable; // how many bytes left to read/write CRC16 crc; FSMState fsmState; byte statusReg; byte commandReg; byte sectorReg; byte trackReg; byte dataReg; bool directionIn; bool immediateIRQ; bool lastWasA1; const bool isWD1770; }; SERIALIZE_CLASS_VERSION(WD2793, 8); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/WD2793BasedFDC.cc000066400000000000000000000013251257557151200204050ustar00rootroot00000000000000#include "WD2793BasedFDC.hh" #include "XMLElement.hh" #include "serialize.hh" namespace openmsx { WD2793BasedFDC::WD2793BasedFDC(const DeviceConfig& config) : MSXFDC(config) , multiplexer(reinterpret_cast(drives)) , controller( getScheduler(), multiplexer, getCliComm(), getCurrentTime(), config.getXML()->getName() == "WD1770") { } void WD2793BasedFDC::reset(EmuTime::param time) { controller.reset(time); } template void WD2793BasedFDC::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("multiplexer", multiplexer); ar.serialize("wd2793", controller); } INSTANTIATE_SERIALIZE_METHODS(WD2793BasedFDC); } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/WD2793BasedFDC.hh000066400000000000000000000010501257557151200204120ustar00rootroot00000000000000#ifndef WD2793BASEDFDC_HH #define WD2793BASEDFDC_HH #include "MSXFDC.hh" #include "DriveMultiplexer.hh" #include "WD2793.hh" namespace openmsx { class WD2793BasedFDC : public MSXFDC { public: void reset(EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); protected: explicit WD2793BasedFDC(const DeviceConfig& config); ~WD2793BasedFDC() {} DriveMultiplexer multiplexer; WD2793 controller; }; REGISTER_BASE_NAME_HELPER(WD2793BasedFDC, "WD2793BasedFDC"); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/fdc/XSADiskImage.cc000066400000000000000000000145411257557151200205070ustar00rootroot00000000000000#include "XSADiskImage.hh" #include "DiskExceptions.hh" #include "File.hh" #include using std::string; using std::vector; namespace openmsx { class XSAExtractor { public: explicit XSAExtractor(File& file); unsigned getData(MemBuffer& data); private: static const int MAXSTRLEN = 254; static const int TBLSIZE = 16; static const int MAXHUFCNT = 127; inline byte charIn(); void chkHeader(); void unLz77(); unsigned rdStrLen(); int rdStrPos(); bool bitIn(); void initHufInfo(); void mkHufTbl(); struct HufNode { HufNode* child1; HufNode* child2; int weight; }; MemBuffer outBuf; // the output buffer const byte* inBufPos; // pos in input buffer const byte* inBufEnd; unsigned sectors; int updHufCnt; int cpDist[TBLSIZE + 1]; int cpdBmask[TBLSIZE]; int tblSizes[TBLSIZE]; HufNode hufTbl[2 * TBLSIZE - 1]; byte bitFlg; // flag with the bits byte bitCnt; // nb bits left static const int cpdExt[TBLSIZE]; // Extra bits for distance codes }; // XSADiskImage XSADiskImage::XSADiskImage(Filename& filename, File& file) : SectorBasedDisk(filename) { XSAExtractor extractor(file); unsigned sectors = extractor.getData(data); setNbSectors(sectors); } void XSADiskImage::readSectorImpl(size_t sector, SectorBuffer& buf) { memcpy(&buf, &data[sector], sizeof(buf)); } void XSADiskImage::writeSectorImpl(size_t /*sector*/, const SectorBuffer& /*buf*/) { throw WriteProtectedException("Write protected"); } bool XSADiskImage::isWriteProtectedImpl() const { return true; } // XSAExtractor const int XSAExtractor::cpdExt[TBLSIZE] = { 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; XSAExtractor::XSAExtractor(File& file) { size_t size; inBufPos = file.mmap(size); inBufEnd = inBufPos + size; if ((charIn() != 'P') || (charIn() != 'C') || (charIn() != 'K') || (charIn() != '\010')) { throw MSXException("Not an XSA image"); } chkHeader(); initHufInfo(); // initialize the cpDist tables unLz77(); } unsigned XSAExtractor::getData(MemBuffer& data) { // destroys internal outBuf, but that's ok data.swap(outBuf); return sectors; } // Get the next character from the input buffer byte XSAExtractor::charIn() { if (inBufPos >= inBufEnd) { throw MSXException("Corrupt XSA image: unexpected end of file"); } return *inBufPos++; } // check fileheader void XSAExtractor::chkHeader() { // read original length (little endian) unsigned outBufLen = 0; for (int i = 0, base = 1; i < 4; ++i, base <<= 8) { outBufLen += base * charIn(); } sectors = (outBufLen + 511) / 512; outBuf.resize(sectors); // skip compressed length inBufPos += 4; // skip original filename while (charIn()) /*empty*/; } // the actual decompression algorithm itself void XSAExtractor::unLz77() { bitCnt = 0; // no bits read yet size_t remaining = sectors * sizeof(SectorBuffer); byte* out = outBuf.data()->raw; size_t outIdx = 0; while (true) { if (bitIn()) { // 1-bit unsigned strLen = rdStrLen(); if (strLen == (MAXSTRLEN + 1)) { return; } unsigned strPos = rdStrPos(); if ((strPos == 0) || (strPos > outIdx)) { throw MSXException( "Corrupt XSA image: invalid offset"); } if (remaining < strLen) { throw MSXException( "Invalid XSA image: too small output buffer"); } remaining -= strLen; while (strLen--) { out[outIdx] = out[outIdx - strPos]; ++outIdx; } } else { // 0-bit if (remaining == 0) { throw MSXException( "Invalid XSA image: too small output buffer"); } --remaining; out[outIdx++] = charIn(); } } } // read string length unsigned XSAExtractor::rdStrLen() { if (!bitIn()) return 2; if (!bitIn()) return 3; if (!bitIn()) return 4; byte nrBits; for (nrBits = 2; (nrBits != 7) && bitIn(); ++nrBits) { // nothing } unsigned len = 1; while (nrBits--) { len = (len << 1) | (bitIn() ? 1 : 0); } return (len + 1); } // read string pos int XSAExtractor::rdStrPos() { HufNode* hufPos = &hufTbl[2 * TBLSIZE - 2]; while (hufPos->child1) { if (bitIn()) { hufPos = hufPos->child2; } else { hufPos = hufPos->child1; } } byte cpdIndex = byte(hufPos - hufTbl); ++tblSizes[cpdIndex]; int strPos; if (cpdBmask[cpdIndex] >= 256) { byte strPosLsb = charIn(); byte strPosMsb = 0; for (byte nrBits = cpdExt[cpdIndex] - 8; nrBits--; strPosMsb |= (bitIn() ? 1 : 0)) { strPosMsb <<= 1; } strPos = strPosLsb + 256 * strPosMsb; } else { strPos = 0; for (byte nrBits = cpdExt[cpdIndex]; nrBits--; strPos |= (bitIn() ? 1 : 0)) { strPos <<= 1; } } if ((updHufCnt--) == 0) { mkHufTbl(); // make the huffman table } return strPos + cpDist[cpdIndex]; } // read a bit from the input file bool XSAExtractor::bitIn() { if (bitCnt == 0) { bitFlg = charIn(); // read bitFlg bitCnt = 8; // 8 bits left } bool temp = bitFlg & 1; --bitCnt; // 1 bit less bitFlg >>= 1; return temp; } // initialize the huffman info tables void XSAExtractor::initHufInfo() { int offs = 1; for (int i = 0; i != TBLSIZE; ++i) { cpDist[i] = offs; cpdBmask[i] = 1 << cpdExt[i]; offs += cpdBmask[i]; } cpDist[TBLSIZE] = offs; for (int i = 0; i != TBLSIZE; ++i) { tblSizes[i] = 0; // reset the table counters hufTbl[i].child1 = nullptr; // mark the leave nodes } mkHufTbl(); // make the huffman table } // Make huffman coding info void XSAExtractor::mkHufTbl() { // Initialize the huffman tree HufNode* hufPos = hufTbl; for (int i = 0; i != TBLSIZE; ++i) { (hufPos++)->weight = 1 + (tblSizes[i] >>= 1); } for (int i = TBLSIZE; i != 2 * TBLSIZE - 1; ++i) { (hufPos++)->weight = -1; } // Place the nodes in the correct manner in the tree while (hufTbl[2 * TBLSIZE - 2].weight == -1) { HufNode* l1Pos; HufNode* l2Pos; for (hufPos = hufTbl; !(hufPos->weight); ++hufPos) { // nothing } l1Pos = hufPos++; while (!(hufPos->weight)) { ++hufPos; } if (hufPos->weight < l1Pos->weight) { l2Pos = l1Pos; l1Pos = hufPos++; } else { l2Pos = hufPos++; } int tempW; while ((tempW = (hufPos)->weight) != -1) { if (tempW) { if (tempW < l1Pos->weight) { l2Pos = l1Pos; l1Pos = hufPos; } else if (tempW < l2Pos->weight) { l2Pos = hufPos; } } ++hufPos; } hufPos->weight = l1Pos->weight + l2Pos->weight; (hufPos->child1 = l1Pos)->weight = 0; (hufPos->child2 = l2Pos)->weight = 0; } updHufCnt = MAXHUFCNT; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/fdc/XSADiskImage.hh000066400000000000000000000014561257557151200205220ustar00rootroot00000000000000/****************************************************************/ /* LZ77 data decompression */ /* Copyright (c) 1994 by XelaSoft */ /* version history: */ /* version 0.9, start date: 11-27-1994 */ /****************************************************************/ #ifndef XSADISKIMAGE_HH #define XSADISKIMAGE_HH #include "SectorBasedDisk.hh" #include "MemBuffer.hh" namespace openmsx { class File; class XSADiskImage final : public SectorBasedDisk { public: XSADiskImage(Filename& filename, File& file); private: // SectorBasedDisk void readSectorImpl (size_t sector, SectorBuffer& buf) override; void writeSectorImpl(size_t sector, const SectorBuffer& buf) override; bool isWriteProtectedImpl() const override; MemBuffer data; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/file/000077500000000000000000000000001257557151200161455ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/src/file/.gitignore000066400000000000000000000001761257557151200201410ustar00rootroot00000000000000/*.ROM /*.bb /*.bbg /*.da /*.dsk /*.prof /*.rom /*.rpo /*.swp /*.vim /.debug /.kdbgrc.openmsx /.xvpics /gmon.out /GNUmakefile openMSX-RELEASE_0_12_0/src/file/CompressedFileAdapter.cc000066400000000000000000000054671257557151200226750ustar00rootroot00000000000000#include "CompressedFileAdapter.hh" #include "FileException.hh" #include "hash_set.hh" #include "xxhash.hh" #include using std::string; namespace openmsx { struct GetURLFromDecompressed { template const string& operator()(const Ptr& p) const { return p->cachedURL; } }; static hash_set, GetURLFromDecompressed, XXHasher> decompressCache; CompressedFileAdapter::CompressedFileAdapter(std::unique_ptr file_) : file(std::move(file_)), pos(0) { } CompressedFileAdapter::~CompressedFileAdapter() { auto it = decompressCache.find(getURL()); decompressed.reset(); if (it != end(decompressCache) && it->unique()) { // delete last user of Decompressed, remove from cache decompressCache.erase(it); } } void CompressedFileAdapter::decompress() { if (decompressed) return; string url = getURL(); auto it = decompressCache.find(url); if (it != end(decompressCache)) { decompressed = *it; } else { decompressed = std::make_shared(); decompress(*file, *decompressed); decompressed->cachedModificationDate = getModificationDate(); decompressed->cachedURL = std::move(url); decompressCache.insert_noDuplicateCheck(decompressed); } // close original file after succesful decompress file.reset(); } void CompressedFileAdapter::read(void* buffer, size_t num) { decompress(); if (decompressed->size < (pos + num)) { throw FileException("Read beyond end of file"); } const auto& buf = decompressed->buf; memcpy(buffer, buf.data() + pos, num); pos += num; } void CompressedFileAdapter::write(const void* /*buffer*/, size_t /*num*/) { throw FileException("Writing to compressed files not yet supported"); } const byte* CompressedFileAdapter::mmap(size_t& size) { decompress(); size = decompressed->size; return reinterpret_cast(decompressed->buf.data()); } void CompressedFileAdapter::munmap() { // nothing } size_t CompressedFileAdapter::getSize() { decompress(); return decompressed->size; } void CompressedFileAdapter::seek(size_t newpos) { pos = newpos; } size_t CompressedFileAdapter::getPos() { return pos; } void CompressedFileAdapter::truncate(size_t /*size*/) { throw FileException("Truncating compressed files not yet supported."); } void CompressedFileAdapter::flush() { // nothing because writing is not supported } const string CompressedFileAdapter::getURL() const { return file ? file->getURL() : decompressed->cachedURL; } const string CompressedFileAdapter::getOriginalName() { decompress(); return decompressed->originalName; } bool CompressedFileAdapter::isReadOnly() const { return true; } time_t CompressedFileAdapter::getModificationDate() { return file ? file->getModificationDate() : decompressed->cachedModificationDate; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/file/CompressedFileAdapter.hh000066400000000000000000000023531257557151200226760ustar00rootroot00000000000000#ifndef COMPRESSEDFILEADAPTER_HH #define COMPRESSEDFILEADAPTER_HH #include "FileBase.hh" #include "MemBuffer.hh" #include namespace openmsx { class CompressedFileAdapter : public FileBase { public: struct Decompressed { MemBuffer buf; size_t size; std::string originalName; std::string cachedURL; time_t cachedModificationDate; }; void read(void* buffer, size_t num) final override; void write(const void* buffer, size_t num) final override; const byte* mmap(size_t& size) final override; void munmap() final override; size_t getSize() final override; void seek(size_t pos) final override; size_t getPos() final override; void truncate(size_t size) final override; void flush() final override; const std::string getURL() const final override; const std::string getOriginalName() final override; bool isReadOnly() const final override; time_t getModificationDate() final override; protected: explicit CompressedFileAdapter(std::unique_ptr file); ~CompressedFileAdapter(); virtual void decompress(FileBase& file, Decompressed& decompressed) = 0; private: void decompress(); std::unique_ptr file; std::shared_ptr decompressed; size_t pos; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/file/File.cc000066400000000000000000000047751257557151200173500ustar00rootroot00000000000000#include "File.hh" #include "Filename.hh" #include "LocalFile.hh" #include "GZFileAdapter.hh" #include "ZipFileAdapter.hh" #include "checked_cast.hh" #include "memory.hh" #include #include using std::string; namespace openmsx { File::File() { } static std::unique_ptr init(string_ref url, File::OpenMode mode) { static const byte GZ_HEADER[3] = { 0x1F, 0x8B, 0x08 }; static const byte ZIP_HEADER[4] = { 0x50, 0x4B, 0x03, 0x04 }; std::unique_ptr file = make_unique(url, mode); if (file->getSize() >= 4) { byte buf[4]; file->read(buf, 4); file->seek(0); if (memcmp(buf, GZ_HEADER, 3) == 0) { file = make_unique(std::move(file)); } else if (memcmp(buf, ZIP_HEADER, 4) == 0) { file = make_unique(std::move(file)); } else { // only pre-cache non-compressed files if (mode == File::PRE_CACHE) { checked_cast(file.get())->preCacheFile(); } } } return file; } File::File(const Filename& filename, OpenMode mode) : file(init(filename.getResolved(), mode)) { } File::File(string_ref url, OpenMode mode) : file(init(url, mode)) { } File::File(string_ref filename, const char* mode) : file(make_unique(filename, mode)) { } File::File(const Filename& filename, const char* mode) : file(make_unique(filename.getResolved(), mode)) { } File::File(File&& other) : file(std::move(other.file)) { } File::~File() { } File& File::operator=(File&& other) { file = std::move(other.file); return *this; } void File::close() { file.reset(); } void File::read(void* buffer, size_t num) { file->read(buffer, num); } void File::write(const void* buffer, size_t num) { file->write(buffer, num); } const byte* File::mmap(size_t& size) { return file->mmap(size); } void File::munmap() { file->munmap(); } size_t File::getSize() { return file->getSize(); } void File::seek(size_t pos) { file->seek(pos); } size_t File::getPos() { return file->getPos(); } void File::truncate(size_t size) { return file->truncate(size); } void File::flush() { file->flush(); } const string File::getURL() const { return file->getURL(); } const string File::getLocalReference() const { return file->getLocalReference(); } const string File::getOriginalName() { string orig = file->getOriginalName(); return !orig.empty() ? orig : getURL(); } bool File::isReadOnly() const { return file->isReadOnly(); } time_t File::getModificationDate() { return file->getModificationDate(); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/file/File.hh000066400000000000000000000075051257557151200173540ustar00rootroot00000000000000#ifndef FILE_HH #define FILE_HH #include "openmsx.hh" #include "string_ref.hh" #include #include namespace openmsx { class Filename; class FileBase; class File { public: enum OpenMode { NORMAL, TRUNCATE, CREATE, LOAD_PERSISTENT, SAVE_PERSISTENT, PRE_CACHE, }; /** Create a closed file handle. * The only valid operations on such an object are is_open() and the * move-assignment operator. */ File(); /** Create file object and open underlying file. * @param filename Name of the file to be opened. * @param mode Mode to open the file in: * @throws FileNotFoundException if file not found * @throws FileException for other errors */ explicit File(string_ref filename, OpenMode mode = NORMAL); explicit File(const Filename& filename, OpenMode mode = NORMAL); /** This constructor maps very closely on the fopen() libc function. * Compared to constructor above, it does not transparantly * uncompress files. * @param filename Name of the file to be opened. * @param mode Open mode, same meaning as in fopen(), but we assert * that it contains a 'b' character. */ File(string_ref filename, const char* mode); File(const Filename& filename, const char* mode); File(File&& other); ~File(); File& operator=(File&& other); /** Return true iff this file handle refers to an open file. */ bool is_open() const { return file.get() != nullptr; } /** Close the current file. * Equivalent to assigning a default constructed value to this object. */ void close(); /** Read from file. * @param buffer Destination address * @param num Number of bytes to read * @throws FileException */ void read(void* buffer, size_t num); /** Write to file. * @param buffer Source address * @param num Number of bytes to write * @throws FileException */ void write(const void* buffer, size_t num); /** Map file in memory. * @param size Filled in with filesize. * @result Pointer to memory block. * @throws FileException */ const byte* mmap(size_t& size); /** Unmap file from memory. */ void munmap(); /** Returns the size of this file * @result The size of this file * @throws FileException */ size_t getSize(); /** Move read/write pointer to the specified position. * @param pos Position in bytes from the beginning of the file. * @throws FileException */ void seek(size_t pos); /** Get the current position of the read/write pointer. * @result Position in bytes from the beginning of the file. * @throws FileException */ size_t getPos(); /** Truncate file size. Enlarging file size always works, but * making file smaller doesn't work on some platforms (windows) * @throws FileException */ void truncate(size_t size); /** Force a write of all buffered data to disk. There is no need to * call this function before destroying a File object. */ void flush(); /** Returns the URL of this file object. * @throws FileException */ const std::string getURL() const; /** Get Original filename for this object. This will usually just * return the filename portion of the URL. However for compressed * files this will be different. * @result Original file name * @throws FileException */ const std::string getOriginalName(); /** Check if this file is readonly * @result true iff file is readonly * @throws FileException */ bool isReadOnly() const; /** Get the date/time of last modification * @throws FileException */ time_t getModificationDate(); private: friend class LocalFileReference; /** This is an internal method used by LocalFileReference. * Returns the path to the (uncompressed) file on the local, * filesystem. Or an empty string in case there is no such path. */ const std::string getLocalReference() const; std::unique_ptr file; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/file/FileBase.cc000066400000000000000000000022411257557151200201250ustar00rootroot00000000000000#include "FileBase.hh" #include "FileOperations.hh" #include #include using std::string; namespace openmsx { FileBase::~FileBase() { munmap(); } const byte* FileBase::mmap(size_t& size) { if (mmapBuf.empty()) { size = getSize(); MemBuffer tmpBuf(size); read(tmpBuf.data(), size); std::swap(mmapBuf, tmpBuf); } return mmapBuf.data(); } void FileBase::munmap() { mmapBuf.clear(); } void FileBase::truncate(size_t newSize) { auto oldSize = getSize(); if (newSize < oldSize) { // default truncate() can't shrink file return; } auto remaining = newSize - oldSize; seek(oldSize); static const size_t BUF_SIZE = 4096; byte buf[BUF_SIZE]; memset(buf, 0, sizeof(buf)); while (remaining) { auto chunkSize = std::min(BUF_SIZE, remaining); write(buf, chunkSize); remaining -= chunkSize; } } const string FileBase::getLocalReference() { // default implementation, file is not backed (uncompressed) on // the local file system return ""; } const string FileBase::getOriginalName() { // default implementation just returns filename portion of URL return FileOperations::getFilename(getURL()).str(); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/file/FileBase.hh000066400000000000000000000016431257557151200201440ustar00rootroot00000000000000#ifndef FILEBASE_HH #define FILEBASE_HH #include "MemBuffer.hh" #include "openmsx.hh" #include "noncopyable.hh" #include namespace openmsx { class FileBase : private noncopyable { public: virtual ~FileBase(); virtual void read(void* buffer, size_t num) = 0; virtual void write(const void* buffer, size_t num) = 0; // If you override mmap(), make sure to call munmap() in // your destructor. virtual const byte* mmap(size_t& size); virtual void munmap(); virtual size_t getSize() = 0; virtual void seek(size_t pos) = 0; virtual size_t getPos() = 0; virtual void truncate(size_t size); virtual void flush() = 0; virtual const std::string getURL() const = 0; virtual const std::string getLocalReference(); virtual const std::string getOriginalName(); virtual bool isReadOnly() const = 0; virtual time_t getModificationDate() = 0; private: MemBuffer mmapBuf; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/file/FileContext.cc000066400000000000000000000111731257557151200207030ustar00rootroot00000000000000#include "FileContext.hh" #include "FileOperations.hh" #include "FileException.hh" #include "StringOp.hh" #include "serialize.hh" #include "serialize_stl.hh" #include "openmsx.hh" #include "stl.hh" #include #include using std::string; using std::vector; namespace openmsx { const string USER_DIRS = "{{USER_DIRS}}"; const string USER_OPENMSX = "{{USER_OPENMSX}}"; const string USER_DATA = "{{USER_DATA}}"; const string SYSTEM_DATA = "{{SYSTEM_DATA}}"; static string subst(string_ref path, string_ref before, string_ref after) { assert(path.starts_with(before)); return after + path.substr(before.size()); } static vector getPathsHelper(const vector& input) { vector result; for (auto& s : input) { if (StringOp::startsWith(s, USER_OPENMSX)) { result.push_back(subst(s, USER_OPENMSX, FileOperations::getUserOpenMSXDir())); } else if (StringOp::startsWith(s, USER_DATA)) { result.push_back(subst(s, USER_DATA, FileOperations::getUserDataDir())); } else if (StringOp::startsWith(s, SYSTEM_DATA)) { result.push_back(subst(s, SYSTEM_DATA, FileOperations::getSystemDataDir())); } else if (s == USER_DIRS) { // Nothing. Keep USER_DIRS for isUserContext() } else { result.push_back(s); } } return result; } static string resolveHelper(const vector& pathList, string_ref filename) { string filepath = FileOperations::expandCurrentDirFromDrive(filename); filepath = FileOperations::expandTilde(filepath); if (FileOperations::isAbsolutePath(filepath)) { // absolute path, don't resolve return filepath; } for (auto& p : pathList) { string name = FileOperations::join(p, filename); name = FileOperations::expandTilde(name); if (FileOperations::exists(name)) { return name; } } // not found in any path throw FileException(filename + " not found in this context"); } FileContext::FileContext(vector&& paths_, vector&& savePaths_) : paths(std::move(paths_)), savePaths(std::move(savePaths_)) { } const string FileContext::resolve(string_ref filename) const { vector pathList = getPathsHelper(paths); string result = resolveHelper(pathList, filename); assert(FileOperations::expandTilde(result) == result); return result; } const string FileContext::resolveCreate(string_ref filename) const { string result; vector pathList = getPathsHelper(savePaths); try { result = resolveHelper(pathList, filename); } catch (FileException&) { string path = pathList.front(); try { FileOperations::mkdirp(path); } catch (FileException&) { // ignore } result = FileOperations::join(path, filename); } assert(FileOperations::expandTilde(result) == result); return result; } vector FileContext::getPaths() const { return getPathsHelper(paths); } bool FileContext::isUserContext() const { return contains(paths, USER_DIRS); } template void FileContext::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("paths", paths); ar.serialize("savePaths", savePaths); } INSTANTIATE_SERIALIZE_METHODS(FileContext); /// static string backSubstSymbols(const string& path) { string systemData = FileOperations::getSystemDataDir(); if (StringOp::startsWith(path, systemData)) { return subst(path, systemData, SYSTEM_DATA); } string userData = FileOperations::getSystemDataDir(); if (StringOp::startsWith(path, userData)) { return subst(path, userData, SYSTEM_DATA); } string userDir = FileOperations::getUserOpenMSXDir(); if (StringOp::startsWith(path, userDir)) { return subst(path, userDir, USER_OPENMSX); } // TODO USER_DIRS (not needed ATM) return path; } FileContext configFileContext(string_ref path, string_ref hwDescr, string_ref userName) { return { { backSubstSymbols(FileOperations::expandTilde(path)) }, { FileOperations::join( USER_OPENMSX, "persistent", hwDescr, userName) } }; } FileContext systemFileContext() { return { { USER_DATA, SYSTEM_DATA }, { USER_DATA } }; } FileContext preferSystemFileContext() { return { { SYSTEM_DATA, USER_DATA }, // first system dir {} }; } FileContext userFileContext(string_ref savePath) { vector savePaths; if (!savePath.empty()) { savePaths = { FileOperations::join( USER_OPENMSX, "persistent", savePath) }; } return { { "", USER_DIRS }, std::move(savePaths) }; } FileContext userDataFileContext(string_ref subDir) { return { { "", USER_OPENMSX + '/' + subDir }, {} }; } FileContext currentDirFileContext() { return { { "" }, {} }; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/file/FileContext.hh000066400000000000000000000016761257557151200207240ustar00rootroot00000000000000#ifndef FILECONTEXT_HH #define FILECONTEXT_HH #include "string_ref.hh" #include namespace openmsx { class FileContext final { public: FileContext() {} FileContext(std::vector&& paths, std::vector&& savePaths); const std::string resolve (string_ref filename) const; const std::string resolveCreate(string_ref filename) const; std::vector getPaths() const; bool isUserContext() const; template void serialize(Archive& ar, unsigned version); private: std::vector paths; std::vector savePaths; }; FileContext configFileContext(string_ref path, string_ref hwDescr, string_ref userName); FileContext systemFileContext(); FileContext preferSystemFileContext(); FileContext userFileContext(string_ref savePath = ""); FileContext userDataFileContext(string_ref subdir); FileContext currentDirFileContext(); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/file/FileException.hh000066400000000000000000000004041257557151200212220ustar00rootroot00000000000000#ifndef FILEEXCEPTION_HH #define FILEEXCEPTION_HH #include "MSXException.hh" namespace openmsx { class FileException : public MSXException { public: explicit FileException(string_ref message) : MSXException(message) {} }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/file/FileNotFoundException.hh000066400000000000000000000004471257557151200227060ustar00rootroot00000000000000#ifndef FILENOTFOUNDEXCEPTION_HH #define FILENOTFOUNDEXCEPTION_HH #include "FileException.hh" namespace openmsx { class FileNotFoundException : public FileException { public: explicit FileNotFoundException(string_ref message) : FileException(message) {} }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/file/FileOperations.cc000066400000000000000000000455241257557151200214110ustar00rootroot00000000000000#ifdef _WIN32 #ifndef _WIN32_IE #define _WIN32_IE 0x0500 // For SHGetSpecialFolderPathW with MinGW #endif #include "utf8_checked.hh" #include "vla.hh" #include #include #include #include #include #include #include #include #include #else // ifdef _WIN32_ ... #include #include #include #include #endif // ifdef _WIN32_ ... else ... #include "openmsx.hh" // for ad_printf #include "systemfuncs.hh" #if HAVE_NFTW #include #endif #if defined(PATH_MAX) #define MAXPATHLEN PATH_MAX #elif defined(MAX_PATH) #define MAXPATHLEN MAX_PATH #else #define MAXPATHLEN 4096 #endif #ifdef __APPLE__ #include #endif #include "ReadDir.hh" #include "FileOperations.hh" #include "FileException.hh" #include "StringOp.hh" #include "statp.hh" #include "unistdp.hh" #include "countof.hh" #include "build-info.hh" #include "AndroidApiWrapper.hh" #include #include #include #include #include #ifndef _MSC_VER #include #endif using std::string; #ifdef _WIN32 using namespace utf8; #endif namespace openmsx { namespace FileOperations { #ifdef __APPLE__ static std::string findShareDir() { // Find bundle location: // for an app folder, this is the outer directory, // for an unbundled executable, it is the executable itself. ProcessSerialNumber psn; if (GetCurrentProcess(&psn) != noErr) { throw FatalError("Failed to get process serial number"); } FSRef location; if (GetProcessBundleLocation(&psn, &location) != noErr) { throw FatalError("Failed to get process bundle location"); } // Get info about the location. FSCatalogInfo catalogInfo; FSRef parentRef; if (FSGetCatalogInfo( &location, kFSCatInfoVolume | kFSCatInfoNodeFlags, &catalogInfo, nullptr, nullptr, &parentRef ) != noErr) { throw FatalError("Failed to get info about bundle path"); } // Get reference to root directory of the volume we are searching. // We will need this later to know when to give up. FSRef root; if (FSGetVolumeInfo( catalogInfo.volume, 0, nullptr, kFSVolInfoNone, nullptr, nullptr, &root ) != noErr) { throw FatalError("Failed to get reference to root directory"); } // Make sure we are looking at a directory. if (~catalogInfo.nodeFlags & kFSNodeIsDirectoryMask) { // Location is not a directory, so it is the path to the executable. location = parentRef; } while (true) { // Iterate through the files in the directory. FSIterator iterator; if (FSOpenIterator(&location, kFSIterateFlat, &iterator) != noErr) { throw FatalError("Failed to open iterator"); } bool filesLeft = true; // iterator has files left for next call while (filesLeft) { // Get info about several files at a time. const int MAX_SCANNED_FILES = 100; ItemCount actualObjects; FSRef refs[MAX_SCANNED_FILES]; FSCatalogInfo catalogInfos[MAX_SCANNED_FILES]; HFSUniStr255 names[MAX_SCANNED_FILES]; OSErr err = FSGetCatalogInfoBulk( iterator, MAX_SCANNED_FILES, &actualObjects, nullptr /*containerChanged*/, kFSCatInfoNodeFlags, catalogInfos, refs, nullptr /*specs*/, names ); if (err == errFSNoMoreItems) { filesLeft = false; } else if (err != noErr) { throw FatalError("Catalog get failed"); } for (ItemCount i = 0; i < actualObjects; i++) { // We're only interested in subdirectories. if (catalogInfos[i].nodeFlags & kFSNodeIsDirectoryMask) { // Convert the name to a CFString. CFStringRef name = CFStringCreateWithCharactersNoCopy( kCFAllocatorDefault, names[i].unicode, names[i].length, kCFAllocatorNull // do not deallocate character buffer ); // Is this the directory we are looking for? static const CFStringRef SHARE = CFSTR("share"); CFComparisonResult cmp = CFStringCompare(SHARE, name, 0); CFRelease(name); if (cmp == kCFCompareEqualTo) { // Clean up. OSErr closeErr = FSCloseIterator(iterator); assert(closeErr == noErr); (void)closeErr; // Get full path of directory. UInt8 path[256]; if (FSRefMakePath( &refs[i], path, sizeof(path)) != noErr ) { throw FatalError("Path too long"); } return std::string(reinterpret_cast(path)); } } } } OSErr closeErr = FSCloseIterator(iterator); assert(closeErr == noErr); (void)closeErr; // Are we in the root yet? if (FSCompareFSRefs(&location, &root) == noErr) { throw FatalError("Could not find \"share\" directory anywhere"); } // Go up one level. if (FSGetCatalogInfo( &location, kFSCatInfoNone, nullptr, nullptr, nullptr, &parentRef ) != noErr ) { throw FatalError("Failed to get parent directory"); } location = parentRef; } } #endif // __APPLE__ string expandTilde(string_ref path) { if (path.empty() || path[0] != '~') { return path.str(); } auto pos = path.find_first_of('/'); string_ref user = ((path.size() == 1) || (pos == 1)) ? "" : path.substr(1, (pos == string_ref::npos) ? pos : pos - 1); string result = getUserHomeDir(user); if (result.empty()) { // failed to find homedir, return the path unchanged return path.str(); } if (pos == string_ref::npos) { return result; } if (result.back() != '/') { result += '/'; } string_ref last = path.substr(pos + 1); result.append(last.data(), last.size()); return result; } void mkdir(const string& path, mode_t mode) { #ifdef _WIN32 (void)&mode; // Suppress C4100 VC++ warning if ((path == "/") || StringOp::endsWith(path, ':') || StringOp::endsWith(path, ":/")) { return; } int result = _wmkdir(utf8to16(getNativePath(path)).c_str()); #else int result = ::mkdir(path.c_str(), mode); #endif if (result && (errno != EEXIST)) { throw FileException("Error creating dir " + path); } } void mkdirp(string_ref path_) { if (path_.empty()) { return; } string path = expandTilde(path_); string::size_type pos = 0; do { pos = path.find_first_of('/', pos + 1); mkdir(path.substr(0, pos), 0755); } while (pos != string::npos); if (!isDirectory(path)) { throw FileException("Error creating dir " + path); } } int unlink(const std::string& path) { #ifdef _WIN32 return _wunlink(utf8to16(path).c_str()); #else return ::unlink(path.c_str()); #endif } int rmdir(const std::string& path) { #ifdef _WIN32 return _wrmdir(utf8to16(path).c_str()); #else return ::rmdir(path.c_str()); #endif } #ifdef _WIN32 int deleteRecursive(const std::string& path) { std::wstring pathW = utf8to16(path); SHFILEOPSTRUCTW rmdirFileop; rmdirFileop.hwnd = nullptr; rmdirFileop.wFunc = FO_DELETE; rmdirFileop.pFrom = pathW.c_str(); rmdirFileop.pTo = nullptr; rmdirFileop.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI; rmdirFileop.fAnyOperationsAborted = FALSE; rmdirFileop.hNameMappings = nullptr; rmdirFileop.lpszProgressTitle = nullptr; return SHFileOperationW(&rmdirFileop); } #elif HAVE_NFTW static int deleteRecursive_cb(const char* fpath, const struct stat* /*sb*/, int /*typeflag*/, struct FTW* /*ftwbuf*/) { return remove(fpath); } int deleteRecursive(const std::string& path) { return nftw(path.c_str(), deleteRecursive_cb, 64, FTW_DEPTH | FTW_PHYS); } #else // This is a platform independent version of deleteRecursive() (it builds on // top of helper routines that _are_ platform specific). Though I still prefer // the two platform specific deleteRecursive() routines above because they are // likely more optimized and likely contain less bugs than this version (e.g. // we're walking over the entries in a directory while simultaneously deleting // entries in that same directory. Although this seems to work fine, I'm not // 100% sure our ReadDir 'emulation code' for windows covers all corner cases. // While the windows version above very likely does handle everything). int deleteRecursive(const std::string& path) { if (isDirectory(path)) { { ReadDir dir(path); while (dirent* d = dir.getEntry()) { int err = deleteRecursive(d->d_name); if (err) return err; } } return rmdir(path); } else { return unlink(path); } } #endif FILE_t openFile(const std::string& filename, const std::string& mode) { // Mode must contain a 'b' character. On unix this doesn't make any // difference. But on windows this is required to open the file // in binary mode. assert(mode.find('b') != std::string::npos); #ifdef _WIN32 return FILE_t(_wfopen(utf8to16(filename).c_str(), utf8to16(mode).c_str())); #else return FILE_t(fopen(filename.c_str(), mode.c_str())); #endif } void openofstream(std::ofstream& stream, const std::string& filename) { #if defined _WIN32 && defined _MSC_VER // MinGW 3.x doesn't support ofstream.open(wchar_t*) // TODO - this means that unicode text may not work right here stream.open(utf8to16(filename).c_str()); #else stream.open(filename.c_str()); #endif } void openofstream(std::ofstream& stream, const std::string& filename, std::ios_base::openmode mode) { #if defined _WIN32 && defined _MSC_VER // MinGW 3.x doesn't support ofstream.open(wchar_t*) // TODO - this means that unicode text may not work right here stream.open(utf8to16(filename).c_str(), mode); #else stream.open(filename.c_str(), mode); #endif } string_ref getFilename(string_ref path) { auto pos = path.rfind('/'); if (pos == string_ref::npos) { return path; } else { return path.substr(pos + 1); } } string_ref getBaseName(string_ref path) { auto pos = path.rfind('/'); if (pos == string_ref::npos) { return ""; } else { return path.substr(0, pos + 1); } } string_ref getExtension(string_ref path) { string_ref filename = getFilename(path); auto pos = filename.rfind('.'); if (pos == string_ref::npos) { return ""; } else { return filename.substr(pos + 1); } } string_ref stripExtension(string_ref path) { auto pos = path.rfind('.'); if (pos == string_ref::npos) { return path; } else { return path.substr(0, pos); } } string join(string_ref part1, string_ref part2) { if (part1.empty() || isAbsolutePath(part2)) { return part2.str(); } if (part1.back() == '/') { return part1 + part2; } return part1 + '/' + part2; } string join(string_ref part1, string_ref part2, string_ref part3) { return join(part1, join(part2, part3)); } string join(string_ref part1, string_ref part2, string_ref part3, string_ref part4) { return join(part1, join(part2, join(part3, part4))); } string getNativePath(string_ref path) { string result = path.str(); #ifdef _WIN32 replace(begin(result), end(result), '/', '\\'); #endif return result; } string getConventionalPath(string_ref path) { string result = path.str(); #ifdef _WIN32 replace(begin(result), end(result), '\\', '/'); #endif return result; } string getCurrentWorkingDirectory() { #ifdef _WIN32 wchar_t bufW[MAXPATHLEN]; wchar_t* result = _wgetcwd(bufW, MAXPATHLEN); string buf; if (result) { buf = utf16to8(result); } #else char buf[MAXPATHLEN]; char* result = getcwd(buf, MAXPATHLEN); #endif if (!result) { throw FileException("Couldn't get current working directory."); } return buf; } string getAbsolutePath(string_ref path) { // In rare cases getCurrentWorkingDirectory() can throw, // so only call it when really necessary. if (isAbsolutePath(path)) { return path.str(); } string currentDir = getCurrentWorkingDirectory(); return join(currentDir, path); } bool isAbsolutePath(string_ref path) { #ifdef _WIN32 if ((path.size() >= 3) && (path[1] == ':') && ((path[2] == '/') || (path[2] == '\\'))) { char drive = tolower(path[0]); if (('a' <= drive) && (drive <= 'z')) { return true; } } #endif return !path.empty() && (path[0] == '/'); } string getUserHomeDir(string_ref username) { #ifdef _WIN32 (void)(&username); // ignore parameter, avoid warning wchar_t bufW[MAXPATHLEN + 1]; if (!SHGetSpecialFolderPathW(nullptr, bufW, CSIDL_PERSONAL, TRUE)) { throw FatalError(StringOp::Builder() << "SHGetSpecialFolderPathW failed: " << GetLastError()); } return getConventionalPath(utf16to8(bufW)); #else const char* dir = nullptr; struct passwd* pw = nullptr; if (username.empty()) { dir = getenv("HOME"); if (!dir) { pw = getpwuid(getuid()); } } else { pw = getpwnam(username.str().c_str()); } if (pw) { dir = pw->pw_dir; } return dir ? dir : ""; #endif } const string& getUserOpenMSXDir() { #ifdef _WIN32 static const string OPENMSX_DIR = expandTilde("~/openMSX"); #elif PLATFORM_ANDROID static const string OPENMSX_DIR = AndroidApiWrapper::getStorageDirectory() + "/openMSX"; #else static const string OPENMSX_DIR = expandTilde("~/.openMSX"); #endif return OPENMSX_DIR; } string getUserDataDir() { const char* const NAME = "OPENMSX_USER_DATA"; char* value = getenv(NAME); return value ? value : getUserOpenMSXDir() + "/share"; } string getSystemDataDir() { const char* const NAME = "OPENMSX_SYSTEM_DATA"; if (char* value = getenv(NAME)) { return value; } string newValue; #ifdef _WIN32 wchar_t bufW[MAXPATHLEN + 1]; int res = GetModuleFileNameW(nullptr, bufW, countof(bufW)); if (!res) { throw FatalError(StringOp::Builder() << "Cannot detect openMSX directory. GetModuleFileNameW failed: " << GetLastError()); } string filename = utf16to8(bufW); auto pos = filename.find_last_of('\\'); if (pos == string::npos) { throw FatalError("openMSX is not in directory!?"); } newValue = getConventionalPath(filename.substr(0, pos)) + "/share"; #elif defined(__APPLE__) newValue = findShareDir(); #elif PLATFORM_ANDROID newValue = getAbsolutePath("openmsx_system"); ad_printf("System data dir: %s", newValue.c_str()); #else // defined in build-info.hh (default /opt/openMSX/share) newValue = DATADIR; #endif return newValue; } #ifdef _WIN32 static bool driveExists(char driveLetter) { char buf[] = { driveLetter, ':', 0 }; return GetFileAttributesA(buf) != INVALID_FILE_ATTRIBUTES; } #endif string expandCurrentDirFromDrive(string_ref path) { string result = path.str(); #ifdef _WIN32 if (((path.size() == 2) && (path[1] == ':')) || ((path.size() >= 3) && (path[1] == ':') && (path[2] != '/'))) { // get current directory for this drive unsigned char drive = tolower(path[0]); if (('a' <= drive) && (drive <= 'z')) { wchar_t bufW[MAXPATHLEN + 1]; if (driveExists(drive) && _wgetdcwd(drive - 'a' + 1, bufW, MAXPATHLEN)) { result = getConventionalPath(utf16to8(bufW)); if (result.back() != '/') { result += '/'; } if (path.size() > 2) { string_ref tmp = path.substr(2); result.append(tmp.data(), tmp.size()); } } } } #endif return result; } bool getStat(string_ref filename_, Stat& st) { string filename = expandTilde(filename_); // workaround for VC++: strip trailing slashes (but keep it if it's the // only character in the path) auto pos = filename.find_last_not_of('/'); if (pos == string::npos) { // string was either empty or a (sequence of) '/' character(s) filename = filename.empty() ? "" : "/"; } else { filename.resize(pos + 1); } #ifdef _WIN32 return _wstat(utf8to16(filename).c_str(), &st) == 0; #else return stat(filename.c_str(), &st) == 0; #endif } bool isRegularFile(const Stat& st) { return S_ISREG(st.st_mode); } bool isRegularFile(string_ref filename) { Stat st; return getStat(filename, st) && isRegularFile(st); } bool isDirectory(const Stat& st) { return S_ISDIR(st.st_mode); } bool isDirectory(string_ref directory) { Stat st; return getStat(directory, st) && isDirectory(st); } bool exists(string_ref filename) { Stat st; // dummy return getStat(filename, st); } time_t getModificationDate(const Stat& st) { return st.st_mtime; } static unsigned getNextNum(dirent* d, string_ref prefix, string_ref extension, unsigned nofdigits) { auto extensionLen = extension.size(); auto prefixLen = prefix.size(); string_ref name(d->d_name); if ((name.size() != (prefixLen + nofdigits + extensionLen)) || (name.substr(0, prefixLen) != prefix) || (name.substr(prefixLen + nofdigits, extensionLen) != extension)) { return 0; } try { return fast_stou(name.substr(prefixLen, nofdigits)); } catch (std::invalid_argument&) { return 0; } } string getNextNumberedFileName( string_ref directory, string_ref prefix, string_ref extension) { const unsigned nofdigits = 4; unsigned max_num = 0; string dirName = getUserOpenMSXDir() + '/' + directory; try { mkdirp(dirName); } catch (FileException&) { // ignore } ReadDir dir(dirName); while (auto* d = dir.getEntry()) { max_num = std::max(max_num, getNextNum(d, prefix, extension, nofdigits)); } std::ostringstream os; os << dirName << '/' << prefix; os.width(nofdigits); os.fill('0'); os << (max_num + 1) << extension; return os.str(); } string parseCommandFileArgument( string_ref argument, string_ref directory, string_ref prefix, string_ref extension) { if (argument.empty()) { // directory is also created when needed return getNextNumberedFileName(directory, prefix, extension); } string filename = argument.str(); if (getBaseName(filename).empty()) { // no dir given, use standard dir (and create it) string dir = getUserOpenMSXDir() + '/' + directory; mkdirp(dir); filename = dir + '/' + filename; } else { filename = expandTilde(filename); } if (!StringOp::endsWith(filename, extension) && !exists(filename)) { // Expected extension not already given, append it. But only // when the filename without extension doesn't already exist. // Without this exception stuff like 'soundlog start /dev/null' // reports an error " ... error opening file /dev/null.wav." filename.append(extension.data(), extension.size()); } return filename; } string getTempDir() { #ifdef _WIN32 DWORD len = GetTempPathW(0, nullptr); if (len) { VLA(wchar_t, bufW, (len+1)); len = GetTempPathW(len, bufW); if (len) { // Strip last backslash if (bufW[len-1] == L'\\') { bufW[len-1] = L'\0'; } return utf16to8(bufW); } } throw FatalError(StringOp::Builder() << "GetTempPathW failed: " << GetLastError()); #elif PLATFORM_ANDROID string result = getSystemDataDir() + "/tmp"; return result; #else const char* result = nullptr; if (!result) result = getenv("TMPDIR"); if (!result) result = getenv("TMP"); if (!result) result = getenv("TEMP"); if (!result) { result = "/tmp"; } return result; #endif } FILE_t openUniqueFile(const std::string& directory, std::string& filename) { #ifdef _WIN32 std::wstring directoryW = utf8to16(directory); wchar_t filenameW[MAX_PATH]; if (!GetTempFileNameW(directoryW.c_str(), L"msx", 0, filenameW)) { throw FileException(StringOp::Builder() << "GetTempFileNameW failed: " << GetLastError()); } filename = utf16to8(filenameW); return FILE_t(_wfopen(filenameW, L"wb")); #else filename = directory + "/XXXXXX"; int fd = mkstemp(const_cast(filename.c_str())); if (fd == -1) { throw FileException("Coundn't get temp file name"); } return FILE_t(fdopen(fd, "wb")); #endif } } // namespace FileOperations } // namespace openmsx openMSX-RELEASE_0_12_0/src/file/FileOperations.hh000066400000000000000000000211531257557151200214130ustar00rootroot00000000000000#ifndef FILEOPERATIONS_HH #define FILEOPERATIONS_HH #include "string_ref.hh" #include "unistdp.hh" // needed for mode_t definition when building with VC++ #include "statp.hh" #include #include #include namespace openmsx { namespace FileOperations { struct FClose { void operator()(FILE* f) { fclose(f); } }; using FILE_t = std::unique_ptr; const char nativePathSeparator = #ifdef _WIN32 '\\'; #else '/'; #endif /** * Expand the '~' character to the users home directory * @param path Pathname, with or without '~' character * @result The expanded pathname */ std::string expandTilde(string_ref path); /** * Create the specified directory. Does some sanity checks so that * it bahaves the same on all platforms. The mode parameter is ignored * on windows. For compatibility with *nix creating the root dir (or a * drivename) is not an error instead the operation is silently * ignored. This function can only create one dircetory at-a-time. You * probably want to use the mkdirp function (see below). * @param path The path of the directory to create * @param mode The permission bits (*nix only) * @throw FileException */ void mkdir(const std::string& path, mode_t mode); /** * Acts like the unix command "mkdir -p". Creates the * specified directory, including the parent directories. * @param path The path of the directory to create * @throw FileException */ void mkdirp(string_ref path); /** * Call unlink() in a platform-independent manner */ int unlink(const std::string& path); /** * Call rmdir() in a platform-independent manner */ int rmdir(const std::string& path); /** Recurively delete a file or directory and (in case of a directory) * all its sub-components. */ int deleteRecursive(const std::string& path); /** Call fopen() in a platform-independent manner * @param filename the file path * @param mode the mode parameter, same as fopen * @result A pointer to the opened file, or nullptr on error * On error the global variable 'errno' is filled in (see * man fopen for details). */ FILE_t openFile(const std::string& filename, const std::string& mode); /** * Open an ofstream in a platform-independent manner * @param stream an ofstream * @param filename the file path */ void openofstream(std::ofstream& stream, const std::string& filename); /** * Open an ofstream in a platform-independent manner * @param stream an ofstream * @param filename the file path * @param mode the open mode */ void openofstream(std::ofstream& stream, const std::string& filename, std::ios_base::openmode mode); /** * Returns the file portion of a path name. * @param path The pathname * @result The file portion */ string_ref getFilename(string_ref path); /** * Returns the directory portion of a path. * @param path The pathname * @result The directory portion. This includes the ending '/'. * If path doesn't have a directory portion the result * is an empty string. */ string_ref getBaseName(string_ref path); /** * Returns the extension portion of a path. * @param path The pathname * @result The extension portion. This excludes the '.'. * If path doesn't have an extension portion the result * is an empty string. */ string_ref getExtension(string_ref path); /** * Returns the path without extension. * @param path The pathname * @result The path without extension. This excludes the '.'. * If path doesn't have an extension portion the result * remains unchanged. */ string_ref stripExtension(string_ref path); /** Join two paths. * Returns the equivalent of 'path1 + '/' + path2'. If 'part2' is an * absolute path, that path is returned ('part1' is ignored). If * 'part1' is empty or if it already ends with '/', there will be no * extra '/' added inbetween 'part1' and 'part2'. */ std::string join(string_ref part1, string_ref part2); std::string join(string_ref part1, string_ref part2, string_ref part3); std::string join(string_ref part1, string_ref part2, string_ref part3, string_ref part4); /** * Returns the path in conventional path-delimiter. * @param path The pathname. * @result The path in conventional path-delimiter. * On UNI*Y systems, it will have no effect indeed. * Just for portability issue. (Especially for Win32) */ std::string getConventionalPath(string_ref path); /** * Returns the path in native path-delimiter. * @param path The pathname. * @result The path in native path-delimiter. * On UNI*Y systems, it will have no effect indeed. * Just for portability issue. (Especially for Win32) */ std::string getNativePath(string_ref path); /** Returns the current working directory. * @throw FileException (for example when directory has been deleted). */ std::string getCurrentWorkingDirectory(); /** Transform given path into an absolute path * @throw FileException */ std::string getAbsolutePath(string_ref path); /** * Checks whether it's a absolute path or not. * @param path The pathname. */ bool isAbsolutePath(string_ref path); /** * Get user's home directory. * @param username The name of the user * @result Home directory of the user or empty string in case of error * UNI*Y: get from env var "HOME" or from /etc/passwd * empty string means current user * Win32: Currently use "My Documents" as home directory. * Not "Documents and Settings". * This is because to support Win9x. * Ignores the username parameter */ std::string getUserHomeDir(string_ref username); /** * Get the openMSX dir in the user's home directory. * Default value is "~/.openMSX" (UNIX) or "~/openMSX" (win) */ const std::string& getUserOpenMSXDir(); /** * Get the openMSX data dir in the user's home directory. * Default value is "~/.openMSX/share" (UNIX) or "~/openMSX/share" (win) */ std::string getUserDataDir(); /** * Get system directory. * UNI*Y: statically defined as "/opt/openMSX/share". * Win32: use "same directory as .exe" + "/share". */ std::string getSystemDataDir(); /** * Get the current directory of the specified drive * Linux: just return an empty string */ std::string expandCurrentDirFromDrive(string_ref path); #ifdef _WIN32 typedef struct _stat Stat; #else using Stat = struct stat; #endif /** * Call stat() and return the stat structure * @param filename the file path (will be tilde expanded) * @param st The stat structute that will be filled in * @result true iff success */ bool getStat(string_ref filename, Stat& st); /** * Is this a regular file (no directory, device, ..)? */ bool isRegularFile(string_ref filename); bool isRegularFile(const Stat& st); /** * Is this a directory? */ bool isDirectory(string_ref directory); bool isDirectory(const Stat& st); /** * Does this file (directory) exists? */ bool exists(string_ref filename); /** Get the date/time of last modification */ time_t getModificationDate(const Stat& st); /** * Gets the next numbered file name with the specified prefix in the * specified directory, with the specified extension. Examples: * automatic numbering of filenames for new screenshots or sound logs. * @param directory Name of the directory in the openMSX user dir in * which should be searched for the next filename * @param prefix Prefix of the filename with numbers * @param extension Extension of the filename with numbers */ std::string getNextNumberedFileName( string_ref directory, string_ref prefix, string_ref extension); /** Helper function for parsing filename arguments in Tcl commands. * - If argument is empty then getNextNumberedFileName() is used * with given directory, prefix and extension. * - If argument doesn't already end with the given extension that * extension is appended. * - If argument doesn't already include a directory, the given * directory is used (and created if required). */ std::string parseCommandFileArgument( string_ref argument, string_ref directory, string_ref prefix, string_ref extension); /** * Get the name of the temp directory on the system. * Typically /tmp on *nix and C:/WINDOWS/TEMP on windows */ std::string getTempDir(); /** * Open a new file with a unique name in the provided directory * @param directory directory in which to open the temp file * @param filename [output param] the name of the resulting file * @result pointer to the opened file */ FILE_t openUniqueFile(const std::string& directory, std::string& filename); } // namespace FileOperations } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/file/FilePool.cc000066400000000000000000000332521257557151200201720ustar00rootroot00000000000000#include "FilePool.hh" #include "File.hh" #include "FileException.hh" #include "FileContext.hh" #include "FileOperations.hh" #include "TclObject.hh" #include "ReadDir.hh" #include "Date.hh" #include "CommandController.hh" #include "CommandException.hh" #include "EventDistributor.hh" #include "CliComm.hh" #include "Timer.hh" #include "StringOp.hh" #include "memory.hh" #include "sha1.hh" #include "stl.hh" #include #include using std::ifstream; using std::get; using std::make_tuple; using std::ofstream; using std::pair; using std::string; using std::vector; using std::unique_ptr; namespace openmsx { const char* const FILE_CACHE = "/.filecache"; static string initialFilePoolSettingValue() { TclObject result; for (auto& p : systemFileContext().getPaths()) { TclObject entry1; entry1.addListElement("-path"); entry1.addListElement(FileOperations::join(p, "systemroms")); entry1.addListElement("-types"); entry1.addListElement("system_rom"); result.addListElement(entry1); TclObject entry2; entry2.addListElement("-path"); entry2.addListElement(FileOperations::join(p, "software")); entry2.addListElement("-types"); entry2.addListElement("rom disk tape"); result.addListElement(entry2); } return result.getString().str(); } FilePool::FilePool(CommandController& controller, EventDistributor& distributor_) : filePoolSetting( controller, "__filepool", "This is an internal setting. Don't change this directly, " "instead use the 'filepool' command.", initialFilePoolSettingValue()) , distributor(distributor_) , cliComm(controller.getCliComm()) , quit(false) { filePoolSetting.attach(*this); distributor.registerEventListener(OPENMSX_QUIT_EVENT, *this); readSha1sums(); needWrite = false; } FilePool::~FilePool() { if (needWrite) { writeSha1sums(); } distributor.unregisterEventListener(OPENMSX_QUIT_EVENT, *this); filePoolSetting.detach(*this); } void FilePool::insert(const Sha1Sum& sum, time_t time, const string& filename) { auto it = upper_bound(begin(pool), end(pool), sum, LessTupleElement<0>()); pool.insert(it, make_tuple(sum, time, filename)); needWrite = true; } void FilePool::remove(Pool::iterator it) { pool.erase(it); needWrite = true; } // Change the sha1sum of the element pointed to by 'it' into 'newSum'. // Also re-arrange the items so that pool remains sorted on sha1sum. Internally // this method doesn't actually sort, it merely rotates the elements. // Returns false if the new position is before (or at) the old position. // Returns true if the new position is after the old position. bool FilePool::adjust(Pool::iterator it, const Sha1Sum& newSum) { needWrite = true; auto newIt = upper_bound(begin(pool), end(pool), newSum, LessTupleElement<0>()); get<0>(*it) = newSum; // update sum if (newIt > it) { // move to back rotate(it, it + 1, newIt); return true; } else { if (newIt < it) { // move to front rotate(newIt, it, it + 1); } else { // (unlikely) sha1sum has changed, but after // resorting item would remain in the same // position } return false; } } static bool parse(const string& line, Sha1Sum& sha1, time_t& time, string& filename) { if (line.size() <= 68) return false; try { sha1.parse40(line.data()); } catch (MSXException& /*e*/) { return false; } time = Date::fromString(line.data() + 42); if (time == time_t(-1)) return false; filename.assign(line, 68, line.size()); return true; } void FilePool::readSha1sums() { assert(pool.empty()); string cacheFile = FileOperations::getUserDataDir() + FILE_CACHE; ifstream file(cacheFile.c_str()); string line; Sha1Sum sum; string filename; time_t time; while (file.good()) { getline(file, line); if (parse(line, sum, time, filename)) { pool.emplace_back(sum, time, filename); } } if (!std::is_sorted(begin(pool), end(pool), LessTupleElement<0>())) { // This should _rarely_ happen. In fact it should only happen // when .filecache was manually edited. Though because it's // very important that pool is indeed sorted I've added this // safety mechanism. sort(begin(pool), end(pool), LessTupleElement<0>()); } } void FilePool::writeSha1sums() { string cacheFile = FileOperations::getUserDataDir() + FILE_CACHE; ofstream file; FileOperations::openofstream(file, cacheFile); if (!file.is_open()) { return; } for (auto& p : pool) { file << get<0>(p).toString() << " " // sum << Date::toString(get<1>(p)) << " " // date << get<2>(p) // filename << '\n'; } } static int parseTypes(Interpreter& interp, const TclObject& list) { int result = 0; unsigned num = list.getListLength(interp); for (unsigned i = 0; i < num; ++i) { string_ref elem = list.getListIndex(interp, i).getString(); if (elem == "system_rom") { result |= FilePool::SYSTEM_ROM; } else if (elem == "rom") { result |= FilePool::ROM; } else if (elem == "disk") { result |= FilePool::DISK; } else if (elem == "tape") { result |= FilePool::TAPE; } else { throw CommandException("Unknown type: " + elem); } } return result; } void FilePool::update(const Setting& setting) { assert(&setting == &filePoolSetting); (void)setting; getDirectories(); // check for syntax errors } FilePool::Directories FilePool::getDirectories() const { Directories result; auto& interp = filePoolSetting.getInterpreter(); const TclObject& all = filePoolSetting.getValue(); unsigned numLines = all.getListLength(interp); for (unsigned i = 0; i < numLines; ++i) { Entry entry; bool hasPath = false; entry.types = 0; TclObject line = all.getListIndex(interp, i); unsigned numItems = line.getListLength(interp); if (numItems & 1) { throw CommandException( "Expected a list with an even number " "of elements, but got " + line.getString()); } for (unsigned j = 0; j < numItems; j += 2) { string_ref name = line.getListIndex(interp, j + 0).getString(); TclObject value = line.getListIndex(interp, j + 1); if (name == "-path") { entry.path = value.getString().str(); hasPath = true; } else if (name == "-types") { entry.types = parseTypes(interp, value); } else { throw CommandException( "Unknown item: " + name); } } if (!hasPath) { throw CommandException( "Missing -path item: " + line.getString()); } if (entry.types == 0) { throw CommandException( "Missing -types item: " + line.getString()); } result.push_back(entry); } return result; } File FilePool::getFile(FileType fileType, const Sha1Sum& sha1sum) { File result = getFromPool(sha1sum); if (result.is_open()) return result; // not found in cache, need to scan directories ScanProgress progress; progress.lastTime = Timer::getTime(); progress.amountScanned = 0; Directories directories; try { directories = getDirectories(); } catch (CommandException& e) { cliComm.printWarning("Error while parsing '__filepool' setting" + e.getMessage()); } for (auto& d : directories) { if (d.types & fileType) { string path = FileOperations::expandTilde(d.path); result = scanDirectory(sha1sum, path, d.path, progress); if (result.is_open()) return result; } } return result; // not found } static void reportProgress(const string& filename, size_t percentage, CliComm& cliComm, EventDistributor& distributor) { cliComm.printProgress( "Calculating SHA1 sum for " + filename + "... " + StringOp::toString(percentage) + "%"); distributor.deliverEvents(); } static Sha1Sum calcSha1sum(File& file, CliComm& cliComm, EventDistributor& distributor) { size_t size; const byte* data = file.mmap(size); if (size < 10*1024*1024) { // for small files, don't show progress return SHA1::calc(data, size); } // Calculate sha1 in several steps so that we can show progress information SHA1 sha1; static const size_t NUMBER_OF_STEPS = 100; // calculate in NUMBER_OF_STEPS steps and report progress every step auto stepSize = size / NUMBER_OF_STEPS; auto remainder = size % NUMBER_OF_STEPS; size_t offset = 0; string filename = file.getOriginalName(); reportProgress(filename, 0, cliComm, distributor); for (size_t i = 0; i < (NUMBER_OF_STEPS - 1); ++i) { sha1.update(&data[offset], stepSize); offset += stepSize; reportProgress(filename, i + 1, cliComm, distributor); } sha1.update(data + offset, stepSize + remainder); reportProgress(filename, 100, cliComm, distributor); return sha1.digest(); } File FilePool::getFromPool(const Sha1Sum& sha1sum) { auto bound = equal_range(begin(pool), end(pool), sha1sum, LessTupleElement<0>()); // use indices instead of iterators auto i = distance(begin(pool), bound.first); auto last = distance(begin(pool), bound.second); while (i != last) { auto it = begin(pool) + i; auto& time = get<1>(*it); const auto& filename = get<2>(*it); try { File file(filename); auto newTime = file.getModificationDate(); if (time == newTime) { // When modification time is unchanged, assume // sha1sum is also unchanged. So avoid // expensive sha1sum calculation. return file; } time = newTime; // update timestamp needWrite = true; auto newSum = calcSha1sum(file, cliComm, distributor); if (newSum == sha1sum) { // Modification time was changed, but // (recalculated) sha1sum is still the same. return file; } // Sha1sum has changed: update sha1sum, move entry to // new position new sum and continue searching. if (adjust(it, newSum)) { // after --last; // no ++i } else { // before (or at) ++i; } } catch (FileException&) { // Error reading file: remove from db and continue // searching. remove(it); --last; } } return File(); // not found } File FilePool::scanDirectory( const Sha1Sum& sha1sum, const string& directory, const string& poolPath, ScanProgress& progress) { ReadDir dir(directory); while (dirent* d = dir.getEntry()) { if (quit) { // Scanning can take a long time. Allow to exit // openmsx when it takes too long. Stop scanning // by pretending we didn't find the file. return File(); } string file = d->d_name; string path = directory + '/' + file; FileOperations::Stat st; if (FileOperations::getStat(path, st)) { File result; if (FileOperations::isRegularFile(st)) { result = scanFile(sha1sum, path, st, poolPath, progress); } else if (FileOperations::isDirectory(st)) { if ((file != ".") && (file != "..")) { result = scanDirectory(sha1sum, path, poolPath, progress); } } if (result.is_open()) return result; } } return File(); // not found } File FilePool::scanFile(const Sha1Sum& sha1sum, const string& filename, const FileOperations::Stat& st, const string& poolPath, ScanProgress& progress) { ++progress.amountScanned; // Periodically send a progress message with the current filename auto now = Timer::getTime(); if (now > (progress.lastTime + 250000)) { // 4Hz progress.lastTime = now; cliComm.printProgress("Searching for file with sha1sum " + sha1sum.toString() + "...\nIndexing filepool " + poolPath + ": [" + StringOp::toString(progress.amountScanned) + "]: " + filename.substr(poolPath.size())); } // deliverEvents() is relatively cheap when there are no events to // deliver, so it's ok to call on each file. distributor.deliverEvents(); auto it = findInDatabase(filename); if (it == end(pool)) { // not in pool try { File file(filename); auto sum = calcSha1sum(file, cliComm, distributor); auto time = FileOperations::getModificationDate(st); insert(sum, time, filename); if (sum == sha1sum) { return file; } } catch (FileException&) { // ignore } } else { // already in pool assert(filename == get<2>(*it)); try { auto time = FileOperations::getModificationDate(st); if (time == get<1>(*it)) { // db is still up to date if (get<0>(*it) == sha1sum) { return File(filename); } } else { // db outdated File file(filename); auto sum = calcSha1sum(file, cliComm, distributor); get<1>(*it) = time; adjust(it, sum); if (sum == sha1sum) { return file; } } } catch (FileException&) { // error reading file, remove from db remove(it); } } return File(); // not found } FilePool::Pool::iterator FilePool::findInDatabase(const string& filename) { // Linear search in pool for filename. // Search from back to front because often, soon after this search, we // will insert/remove an element from the vector. This requires // shifting all elements in the vector starting from a certain // position. Starting the search from the back increases the likelihood // that the to-be-shifted elements are already in the memory cache. for (auto it = pool.rbegin(); it != pool.rend(); ++it) { if (get<2>(*it) == filename) { return it.base() - 1; } } return end(pool); // not found } Sha1Sum FilePool::getSha1Sum(File& file) { auto time = file.getModificationDate(); const auto& filename = file.getURL(); auto it = findInDatabase(filename); if ((it != end(pool)) && (get<1>(*it) == time)) { // in database and modification time matches, // assume sha1sum also matches return get<0>(*it); } // not in database or timestamp mismatch auto sum = calcSha1sum(file, cliComm, distributor); if (it == end(pool)) { // was not yet in database, insert new entry insert(sum, time, filename); } else { // was already in database, but with wrong timestamp (and sha1sum) get<1>(*it) = time; adjust(it, sum); } return sum; } int FilePool::signalEvent(const std::shared_ptr& event) { (void)event; // avoid warning for non-assert compiles assert(event->getType() == OPENMSX_QUIT_EVENT); quit = true; return 0; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/file/FilePool.hh000066400000000000000000000045721257557151200202070ustar00rootroot00000000000000#ifndef FILEPOOL_HH #define FILEPOOL_HH #include "FileOperations.hh" #include "StringSetting.hh" #include "Observer.hh" #include "EventListener.hh" #include "sha1.hh" #include "noncopyable.hh" #include #include #include #include #include namespace openmsx { class CommandController; class EventDistributor; class File; class CliComm; class FilePool final : private Observer, private EventListener , private noncopyable { public: FilePool(CommandController& controler, EventDistributor& distributor); ~FilePool(); enum FileType { SYSTEM_ROM = 1, ROM = 2, DISK = 4, TAPE = 8 }; /** Search file with the given sha1sum. * If found it returns the (already opened) file, * if not found it returns nullptr. */ File getFile(FileType fileType, const Sha1Sum& sha1sum); /** Calculate sha1sum for the given File object. * If possible the result is retrieved from cache, avoiding the * relatively expensive calculation. */ Sha1Sum getSha1Sum(File& file); private: struct ScanProgress { uint64_t lastTime; unsigned amountScanned; }; struct Entry { std::string path; int types; }; using Directories = std::vector; // , sorted on sha1sum using Pool = std::vector>; void insert(const Sha1Sum& sum, time_t time, const std::string& filename); void remove(Pool::iterator it); bool adjust(Pool::iterator it, const Sha1Sum& newSum); void readSha1sums(); void writeSha1sums(); File getFromPool(const Sha1Sum& sha1sum); File scanDirectory(const Sha1Sum& sha1sum, const std::string& directory, const std::string& poolPath, ScanProgress& progress); File scanFile(const Sha1Sum& sha1sum, const std::string& filename, const FileOperations::Stat& st, const std::string& poolPath, ScanProgress& progress); Pool::iterator findInDatabase(const std::string& filename); Directories getDirectories() const; // Observer void update(const Setting& setting) override; // EventListener int signalEvent(const std::shared_ptr& event) override; StringSetting filePoolSetting; EventDistributor& distributor; CliComm& cliComm; Pool pool; bool quit; bool needWrite; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/file/Filename.cc000066400000000000000000000023321257557151200201740ustar00rootroot00000000000000#include "Filename.hh" #include "FileContext.hh" #include "FileOperations.hh" #include "MSXException.hh" #include "serialize.hh" #include using std::string; namespace openmsx { // dummy constructor, to be able to serialize vector Filename::Filename() { } Filename::Filename(string filename) : originalFilename(std::move(filename)) , resolvedFilename(originalFilename) { } Filename::Filename(string filename, const FileContext& context) : originalFilename(std::move(filename)) , resolvedFilename(FileOperations::getAbsolutePath( context.resolve(originalFilename))) { } void Filename::updateAfterLoadState() { if (empty()) return; if (FileOperations::exists(resolvedFilename)) return; try { resolvedFilename = FileOperations::getAbsolutePath( userFileContext().resolve(originalFilename)); } catch (MSXException&) { // nothing } } bool Filename::empty() const { assert(getOriginal().empty() == getResolved().empty()); return getOriginal().empty(); } template void Filename::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("original", originalFilename); ar.serialize("resolved", resolvedFilename); } INSTANTIATE_SERIALIZE_METHODS(Filename); } // namespace openmsx openMSX-RELEASE_0_12_0/src/file/Filename.hh000066400000000000000000000036051257557151200202120ustar00rootroot00000000000000#ifndef FILENAME_HH #define FILENAME_HH #include namespace openmsx { class FileContext; /** This class represents a filename. * A filename is resolved in a certain context. * It is possible to query both the resolved and unresolved filename. * * Most file operations will want the resolved name, but for example * for savestates we (also) want the unresolved name. */ class Filename { public: Filename(); explicit Filename(std::string filename); Filename(std::string filename, const FileContext& context); const std::string& getOriginal() const { return originalFilename; } const std::string& getResolved() const { return resolvedFilename; } /** After a loadstate we prefer to use the exact same file as before * savestate. But if that file is not available (possibly because * snapshot is loaded on a different host machine), we fallback to * the original filename. */ void updateAfterLoadState(); /** Convenience method to test for empty filename. * In any case getOriginal().empty() and getResolved().empty() return * the same result. This method is a shortcut to either of these. */ bool empty() const; /** Change the resolved part of this filename * E.g. on loadstate when we didn't find the original file, but another * file with a matching checksum. */ void setResolved(const std::string& resolved) { resolvedFilename = resolved; } // Do both Filename objects point to the same file? bool operator==(const Filename& other) const { return resolvedFilename == other.resolvedFilename; } bool operator!=(const Filename& other) const { return !(*this == other); } template void serialize(Archive& ar, unsigned version); private: // non-const because we want this class to be assignable // (to be able to store them in std::vector) std::string originalFilename; std::string resolvedFilename; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/file/GZFileAdapter.cc000066400000000000000000000032221257557151200210740ustar00rootroot00000000000000#include "GZFileAdapter.hh" #include "ZlibInflate.hh" #include "FileException.hh" namespace openmsx { const byte ASCII_FLAG = 0x01; // bit 0 set: file probably ascii text const byte HEAD_CRC = 0x02; // bit 1 set: header CRC present const byte EXTRA_FIELD = 0x04; // bit 2 set: extra field present const byte ORIG_NAME = 0x08; // bit 3 set: original file name present const byte COMMENT = 0x10; // bit 4 set: file comment present const byte RESERVED = 0xE0; // bits 5..7: reserved GZFileAdapter::GZFileAdapter(std::unique_ptr file_) : CompressedFileAdapter(std::move(file_)) { } static bool skipHeader(ZlibInflate& zlib, std::string& originalName) { // check magic bytes if (zlib.get16LE() != 0x8B1F) { return false; } byte method = zlib.getByte(); byte flags = zlib.getByte(); if (method != Z_DEFLATED || (flags & RESERVED) != 0) { return false; } // Discard time, xflags and OS code: zlib.skip(6); if ((flags & EXTRA_FIELD) != 0) { // skip the extra field int len = zlib.get16LE(); zlib.skip(len); } if ((flags & ORIG_NAME) != 0) { // get the original file name originalName = zlib.getCString(); } if ((flags & COMMENT) != 0) { // skip the .gz file comment zlib.getCString(); } if ((flags & HEAD_CRC) != 0) { // skip the header crc zlib.skip(2); } return true; } void GZFileAdapter::decompress(FileBase& file, Decompressed& decompressed) { size_t size; const byte* data = file.mmap(size); ZlibInflate zlib(data, size); if (!skipHeader(zlib, decompressed.originalName)) { throw FileException("Not a gzip header"); } decompressed.size = zlib.inflate(decompressed.buf); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/file/GZFileAdapter.hh000066400000000000000000000005351257557151200211120ustar00rootroot00000000000000#ifndef GZFILEADAPTER_HH #define GZFILEADAPTER_HH #include "CompressedFileAdapter.hh" namespace openmsx { class GZFileAdapter final : public CompressedFileAdapter { public: explicit GZFileAdapter(std::unique_ptr file); private: void decompress(FileBase& file, Decompressed& decompressed) override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/file/LocalFile.cc000066400000000000000000000143411257557151200203110ustar00rootroot00000000000000#include "systemfuncs.hh" #include "unistdp.hh" #include #include #if HAVE_MMAP #include #endif #if defined _WIN32 #include #include #endif #include "LocalFile.hh" #include "FileException.hh" #include "FileNotFoundException.hh" #include "PreCacheFile.hh" #include "StringOp.hh" #include "memory.hh" #include // for strchr, strerror #include #include #ifndef EOVERFLOW #define EOVERFLOW 0 #endif using std::string; namespace openmsx { LocalFile::LocalFile(string_ref filename_, File::OpenMode mode) : filename(FileOperations::expandTilde(filename_)) #if HAVE_MMAP || defined _WIN32 , mmem(nullptr) #endif #if defined _WIN32 , hMmap(nullptr) #endif , readOnly(false) { if (mode == File::SAVE_PERSISTENT) { auto pos = filename.find_last_of('/'); if (pos != string::npos) { FileOperations::mkdirp(filename.substr(0, pos)); } } const string name = FileOperations::getNativePath(filename); if ((mode == File::SAVE_PERSISTENT) || (mode == File::TRUNCATE)) { // open file read/write truncated file = FileOperations::openFile(name, "wb+"); } else if (mode == File::CREATE) { // open file read/write file = FileOperations::openFile(name, "rb+"); if (!file) { // create if it didn't exist yet file = FileOperations::openFile(name, "wb+"); } } else { // open file read/write file = FileOperations::openFile(name, "rb+"); if (!file) { // if that fails try read only file = FileOperations::openFile(name, "rb"); readOnly = true; } } if (!file) { int err = errno; if (err == ENOENT) { throw FileNotFoundException( "File \"" + filename + "\" not found"); } else { throw FileException( "Error opening file \"" + filename + "\": " + strerror(err)); } } getSize(); // check filesize } LocalFile::LocalFile(string_ref filename_, const char* mode) : filename(FileOperations::expandTilde(filename_)) #if HAVE_MMAP || defined _WIN32 , mmem(nullptr) #endif #if defined _WIN32 , hMmap(nullptr) #endif , readOnly(false) { assert(strchr(mode, 'b')); const string name = FileOperations::getNativePath(filename); file = FileOperations::openFile(name, mode); if (!file) { throw FileException("Error opening file \"" + filename + "\""); } getSize(); // check filesize } LocalFile::~LocalFile() { munmap(); } void LocalFile::preCacheFile() { string name = FileOperations::getNativePath(filename); cache = make_unique(name); } void LocalFile::read(void* buffer, size_t num) { if (fread(buffer, 1, num, file.get()) != num) { if (ferror(file.get())) { throw FileException("Error reading file"); } if (feof(file.get())) { throw FileException("Read beyond end of file"); } } } void LocalFile::write(const void* buffer, size_t num) { if (fwrite(buffer, 1, num, file.get()) != num) { if (ferror(file.get())) { throw FileException("Error writing file"); } } } #if defined _WIN32 const byte* LocalFile::mmap(size_t& size) { size = getSize(); if (size == 0) return nullptr; if (!mmem) { int fd = _fileno(file.get()); if (fd == -1) { throw FileException("_fileno failed"); } auto hFile = reinterpret_cast(_get_osfhandle(fd)); // No need to close if (hFile == INVALID_HANDLE_VALUE) { throw FileException("_get_osfhandle failed"); } assert(!hMmap); hMmap = CreateFileMapping(hFile, nullptr, PAGE_WRITECOPY, 0, 0, nullptr); if (!hMmap) { throw FileException(StringOp::Builder() << "CreateFileMapping failed: " << GetLastError()); } mmem = static_cast(MapViewOfFile(hMmap, FILE_MAP_COPY, 0, 0, 0)); if (!mmem) { DWORD gle = GetLastError(); CloseHandle(hMmap); hMmap = nullptr; throw FileException(StringOp::Builder() << "MapViewOfFile failed: " << gle); } } return mmem; } void LocalFile::munmap() { if (mmem) { // TODO: make this a valid failure path // When pages are dirty, UnmapViewOfFile is a save operation, // and that can fail. However, mummap is called from // the destructor, for which there is no expectation // that it will fail. So this area needs some work. // It is NOT an option to throw an exception (not even // FatalError). if (!UnmapViewOfFile(mmem)) { std::cerr << "UnmapViewOfFile failed: " << StringOp::toString(GetLastError()) << std::endl; } mmem = nullptr; } if (hMmap) { CloseHandle(hMmap); hMmap = nullptr; } } #elif HAVE_MMAP const byte* LocalFile::mmap(size_t& size) { size = getSize(); if (size == 0) return nullptr; if (!mmem) { mmem = static_cast( ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(file.get()), 0)); // MAP_FAILED is #define'd using an old-style cast, we // have to redefine it ourselves to avoid a warning auto MY_MAP_FAILED = reinterpret_cast(-1); if (mmem == MY_MAP_FAILED) { throw FileException("Error mmapping file"); } } return mmem; } void LocalFile::munmap() { if (mmem) { ::munmap(const_cast(mmem), getSize()); mmem = nullptr; } } #endif size_t LocalFile::getSize() { struct stat st; int ret = fstat(fileno(file.get()), &st); if (ret && (errno == EOVERFLOW)) { // on 32-bit systems, the fstat() call returns a EOVERFLOW // error in case the file is bigger than (1<<31)-1 bytes throw FileException("Files >= 2GB are not supported on " "32-bit platforms: " + getURL()); } if (ret) { throw FileException("Cannot get file size"); } return st.st_size; } void LocalFile::seek(size_t pos) { if (fseek(file.get(), long(pos), SEEK_SET) != 0) { throw FileException("Error seeking file"); } } size_t LocalFile::getPos() { return ftell(file.get()); } #if HAVE_FTRUNCATE void LocalFile::truncate(size_t size) { int fd = fileno(file.get()); if (ftruncate(fd, size)) { throw FileException("Error truncating file"); } } #endif void LocalFile::flush() { fflush(file.get()); } const string LocalFile::getURL() const { return filename; } const string LocalFile::getLocalReference() { return filename; } bool LocalFile::isReadOnly() const { return readOnly; } time_t LocalFile::getModificationDate() { struct stat st; if (fstat(fileno(file.get()), &st)) { throw FileException("Cannot stat file"); } return st.st_mtime; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/file/LocalFile.hh000066400000000000000000000023431257557151200203220ustar00rootroot00000000000000#ifndef LOCALFILE_HH #define LOCALFILE_HH #if defined _WIN32 #include #endif #include "File.hh" #include "FileBase.hh" #include "FileOperations.hh" #include "systemfuncs.hh" #include #include namespace openmsx { class PreCacheFile; class LocalFile final : public FileBase { public: LocalFile(string_ref filename, File::OpenMode mode); LocalFile(string_ref filename, const char* mode); ~LocalFile(); void read (void* buffer, size_t num) override; void write(const void* buffer, size_t num) override; #if HAVE_MMAP || defined _WIN32 const byte* mmap(size_t& size) override; void munmap() override; #endif size_t getSize() override; void seek(size_t pos) override; size_t getPos() override; #if HAVE_FTRUNCATE void truncate(size_t size) override; #endif void flush() override; const std::string getURL() const override; const std::string getLocalReference() override; bool isReadOnly() const override; time_t getModificationDate() override; void preCacheFile(); private: std::string filename; FileOperations::FILE_t file; #if HAVE_MMAP byte* mmem; #endif #if defined _WIN32 byte* mmem; HANDLE hMmap; #endif std::unique_ptr cache; bool readOnly; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/file/LocalFileReference.cc000066400000000000000000000036741257557151200221370ustar00rootroot00000000000000#include "LocalFileReference.hh" #include "File.hh" #include "Filename.hh" #include "FileOperations.hh" #include "FileException.hh" #include "StringOp.hh" #include "build-info.hh" #include #include using std::string; namespace openmsx { LocalFileReference::LocalFileReference(const Filename& filename) { init(filename.getResolved()); } LocalFileReference::LocalFileReference(const string& url) { init(url); } LocalFileReference::LocalFileReference(File& file) { init(file); } void LocalFileReference::init(const string& url) { File file(url); init(file); } void LocalFileReference::init(File& file) { tmpFile = file.getLocalReference(); if (!tmpFile.empty()) { // file is backed on the (local) filesystem, // we can simply use the path to that file assert(tmpDir.empty()); // no need to delete file/dir later return; } // create temp dir #if defined(_WIN32) || PLATFORM_ANDROID tmpDir = FileOperations::getTempDir() + FileOperations::nativePathSeparator + "openmsx"; #else // TODO - why not just use getTempDir()? tmpDir = StringOp::Builder() << "/tmp/openmsx." << int(getpid()); #endif // it's possible this directory already exists, in that case the // following function does nothing FileOperations::mkdirp(tmpDir); // create temp file auto fp = FileOperations::openUniqueFile(tmpDir, tmpFile); if (!fp) { throw FileException("Couldn't create temp file"); } // write temp file size_t size; const byte* buf = file.mmap(size); if (fwrite(buf, 1, size, fp.get()) != size) { throw FileException("Couldn't write temp file"); } } LocalFileReference::~LocalFileReference() { if (!tmpDir.empty()) { FileOperations::unlink(tmpFile); // it's possible the directory is not empty, in that case // the following function will fail, we ignore that error FileOperations::rmdir(tmpDir); } } const string LocalFileReference::getFilename() const { assert(!tmpFile.empty()); return tmpFile; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/file/LocalFileReference.hh000066400000000000000000000046271257557151200221500ustar00rootroot00000000000000#ifndef LOCALFILEREFERENCE_HH #define LOCALFILEREFERENCE_HH #include namespace openmsx { class File; class Filename; /** Helper class to use files in APIs other than openmsx::File. * The openMSX File class has support for (g)zipped files (or maybe in the * future files over http, ftp, ...). Sometimes you need to pass a filename * to an API that doesn't support this (for example SDL_LoadWav()). This * class allows to create a temporary local uncompressed version of such * files. Use it like this: * * LocalFileReference file(filename); // can be any filename supported * // by openmsx::File * my_function(file.getFilename()); // my_function() can now work on * // a regular local file * * Note: In the past this functionality was available in the openmsx::File * class. The current implementation of that class always keep an open * file reference to the corresponding file. This gave problems on * (some versions of) windows if the external function tries to open * the file in read-write mode (for example IMG_Load() does this). The * implementation of this class does not keep a reference to the file. */ class LocalFileReference { public: LocalFileReference() {} explicit LocalFileReference(const Filename& filename); explicit LocalFileReference(const std::string& url); explicit LocalFileReference(File& file); ~LocalFileReference(); // non-copyable, but moveable LocalFileReference(const LocalFileReference&) = delete; LocalFileReference& operator=(const LocalFileReference&) = delete; // =default is not yet supported in VS2013, or not for move-operations(?) //LocalFileReference(LocalFileReference&&) = default; //LocalFileReference& operator=(LocalFileReference&&) = default; LocalFileReference(LocalFileReference&& other) : tmpFile(std::move(other.tmpFile)) , tmpDir (std::move(other.tmpDir)) {} LocalFileReference& operator=(LocalFileReference&& other) { tmpFile = std::move(other.tmpFile); tmpDir = std::move(other.tmpDir); return *this; } /** Returns path to a local uncompressed version of this file. * This path only remains valid as long as this object is in scope. */ const std::string getFilename() const; private: void init(const std::string& url); void init(File& url); std::string tmpFile; std::string tmpDir; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/file/PreCacheFile.cc000066400000000000000000000025751257557151200207370ustar00rootroot00000000000000#include "PreCacheFile.hh" #include "FileOperations.hh" #include "statp.hh" #include #include namespace openmsx { PreCacheFile::PreCacheFile(const std::string& name_) : name(name_), thread(this), exitLoop(false) { thread.start(); } PreCacheFile::~PreCacheFile() { exitLoop = true; thread.join(); } void PreCacheFile::run() { struct stat st; if (stat(name.c_str(), &st)) return; if (!S_ISREG(st.st_mode)) { // don't pre-cache non regular files (e.g. /dev/fd0) return; } auto file = FileOperations::openFile(name, "rb"); if (!file) return; fseek(file.get(), 0, SEEK_END); auto size = ftell(file.get()); if (size < 1024 * 1024) { // only pre-cache small files const unsigned BLOCK_SIZE = 4096; unsigned block = 0; unsigned repeat = 0; while (true) { if (exitLoop) break; char buf[BLOCK_SIZE]; if (fseek(file.get(), block * BLOCK_SIZE, SEEK_SET)) break; size_t read = fread(buf, 1, BLOCK_SIZE, file.get()); if (read != BLOCK_SIZE) { // error or end-of-file reached, // in both cases stop pre-caching break; } // Just reading a file linearly from front to back // makes Linux classify the read as a 'streaming read'. // Linux doesn't cache those. To avoid this we read // some of the blocks twice. if (repeat != 0) { --repeat; ++block; } else { repeat = 5; } } } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/file/PreCacheFile.hh000066400000000000000000000011241257557151200207360ustar00rootroot00000000000000#ifndef PRECACHEFILE_HH #define PRECACHEFILE_HH #include "Thread.hh" #include #include namespace openmsx { /** * Read the complete file once and discard result. Hopefully the file * sticks in the OS cache. Mainly useful to avoid CDROM spinups or to * speed up real floppy disk (/dev/fd0) reads. */ class PreCacheFile final : private Runnable { public: explicit PreCacheFile(const std::string& name); ~PreCacheFile(); private: // Runnable void run() override; const std::string name; Thread thread; std::atomic exitLoop; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/file/ReadDir.cc000066400000000000000000000005151257557151200177670ustar00rootroot00000000000000#include "ReadDir.hh" namespace openmsx { ReadDir::ReadDir(const std::string& directory) { dir = opendir(directory.empty() ? "." : directory.c_str()); } ReadDir::~ReadDir() { if (dir) { closedir(dir); } } struct dirent* ReadDir::getEntry() { if (!dir) { return nullptr; } return readdir(dir); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/file/ReadDir.hh000066400000000000000000000014111257557151200177750ustar00rootroot00000000000000#ifndef READDIR_HH #define READDIR_HH #include "noncopyable.hh" #include "direntp.hh" #include #include namespace openmsx { /** * Simple wrapper around openmdir() / readdir() / closedir() functions. * Mainly usefull to automatically call closedir() when object goes out * of scope. */ class ReadDir : private noncopyable { public: explicit ReadDir(const std::string& directory); ~ReadDir(); /** Get directory entry for next file. Returns nullptr when there * are no more entries or in case of error (e.g. given directory * does not exist). */ struct dirent* getEntry(); /** Is the given directory valid (does it exist)? */ bool isValid() const { return dir != nullptr; } private: DIR* dir; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/file/ZipFileAdapter.cc000066400000000000000000000022601257557151200213170ustar00rootroot00000000000000#include "ZipFileAdapter.hh" #include "ZlibInflate.hh" #include "FileException.hh" namespace openmsx { ZipFileAdapter::ZipFileAdapter(std::unique_ptr file_) : CompressedFileAdapter(std::move(file_)) { } void ZipFileAdapter::decompress(FileBase& file, Decompressed& decompressed) { size_t size; const byte* data = file.mmap(size); ZlibInflate zlib(data, size); if (zlib.get32LE() != 0x04034B50) { throw FileException("Invalid ZIP file"); } // skip "version needed to extract" and "general purpose bit flag" zlib.skip(2 + 2); // compression method if (zlib.get16LE() != 0x0008) { throw FileException("Unsupported zip compression method"); } // skip "last mod file time", "last mod file data", // "crc32", "compressed size" zlib.skip(2 + 2 + 4 + 4); unsigned origSize = zlib.get32LE(); // uncompressed size unsigned filenameLen = zlib.get16LE(); // filename length unsigned extraFieldLen = zlib.get16LE(); // extra field length decompressed.originalName = zlib.getString(filenameLen); // original filename zlib.skip(extraFieldLen); // skip "extra field" decompressed.size = zlib.inflate(decompressed.buf, origSize); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/file/ZipFileAdapter.hh000066400000000000000000000005411257557151200213310ustar00rootroot00000000000000#ifndef ZIPFILEADAPTER_HH #define ZIPFILEADAPTER_HH #include "CompressedFileAdapter.hh" namespace openmsx { class ZipFileAdapter final : public CompressedFileAdapter { public: explicit ZipFileAdapter(std::unique_ptr file); private: void decompress(FileBase& file, Decompressed& decompressed) override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/file/ZlibInflate.cc000066400000000000000000000045021257557151200206600ustar00rootroot00000000000000#include "ZlibInflate.hh" #include "FileException.hh" #include "MemBuffer.hh" #include "StringOp.hh" #include namespace openmsx { ZlibInflate::ZlibInflate(const byte* input, size_t inputLen_) { if (inputLen_ > std::numeric_limits::max()) { throw FileException( "Error while decompressing: input file too big"); } auto inputLen = static_cast(inputLen_); s.zalloc = nullptr; s.zfree = nullptr; s.opaque = nullptr; s.next_in = const_cast(input); s.avail_in = inputLen; wasInit = false; } ZlibInflate::~ZlibInflate() { if (wasInit) { inflateEnd(&s); } } void ZlibInflate::skip(size_t num) { for (size_t i = 0; i < num; ++i) { getByte(); } } byte ZlibInflate::getByte() { if (s.avail_in <= 0) { throw FileException( "Error while decompressing: unexpected end of file."); } --s.avail_in; return *(s.next_in++); } unsigned ZlibInflate::get16LE() { unsigned result = getByte(); result += getByte() << 8; return result; } unsigned ZlibInflate::get32LE() { unsigned result = getByte(); result += getByte() << 8; result += getByte() << 16; result += getByte() << 24; return result; } std::string ZlibInflate::getString(size_t len) { std::string result; for (size_t i = 0; i < len; ++i) { result.push_back(getByte()); } return result; } std::string ZlibInflate::getCString() { std::string result; while (char c = getByte()) { result.push_back(c); } return result; } size_t ZlibInflate::inflate(MemBuffer& output, size_t sizeHint) { int initErr = inflateInit2(&s, -MAX_WBITS); if (initErr != Z_OK) { throw FileException(StringOp::Builder() << "Error initializing inflate struct: " << zError(initErr)); } wasInit = true; size_t outSize = sizeHint; output.resize(outSize); s.avail_out = uInt(outSize); // TODO overflow? while (true) { s.next_out = output.data() + s.total_out; int err = ::inflate(&s, Z_NO_FLUSH); if (err == Z_STREAM_END) { break; } if (err != Z_OK) { throw FileException(StringOp::Builder() << "Error decompressing gzip: " << zError(err)); } auto oldSize = outSize; outSize = oldSize * 2; // double buffer size output.resize(outSize); s.avail_out = uInt(outSize - oldSize); // TODO overflow? } // set actual size output.resize(s.total_out); return s.total_out; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/file/ZlibInflate.hh000066400000000000000000000010221257557151200206640ustar00rootroot00000000000000#ifndef ZLIBINFLATE_HH #define ZLIBINFLATE_HH #include "MemBuffer.hh" #include "openmsx.hh" #include #include namespace openmsx { class ZlibInflate { public: ZlibInflate(const byte* buffer, size_t len); ~ZlibInflate(); void skip(size_t num); byte getByte(); unsigned get16LE(); unsigned get32LE(); std::string getString(size_t len); std::string getCString(); size_t inflate(MemBuffer& output, size_t sizeHint = 65536); private: z_stream s; bool wasInit; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/000077500000000000000000000000001257557151200157675ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/src/ide/.gitignore000066400000000000000000000001761257557151200177630ustar00rootroot00000000000000/*.ROM /*.bb /*.bbg /*.da /*.dsk /*.prof /*.rom /*.rpo /*.swp /*.vim /.debug /.kdbgrc.openmsx /.xvpics /gmon.out /GNUmakefile openMSX-RELEASE_0_12_0/src/ide/AbstractIDEDevice.cc000066400000000000000000000254631257557151200215150ustar00rootroot00000000000000#include "AbstractIDEDevice.hh" #include "MSXMotherBoard.hh" #include "LedStatus.hh" #include "Version.hh" #include "serialize.hh" #include "unreachable.hh" #include #include #include namespace openmsx { AbstractIDEDevice::AbstractIDEDevice(MSXMotherBoard& motherBoard_) : motherBoard(motherBoard_) { transferRead = false; transferWrite = false; transferIdx = 0; // avoid UMR on serialize memset(buffer, 0, sizeof(buffer)); bufferLeft = 0; transferCount = 0; } AbstractIDEDevice::~AbstractIDEDevice() { } byte AbstractIDEDevice::diagnostic() { // The Execute Device Diagnostic command is executed by both devices in // parallel. Fortunately, returning 0x01 is valid in all cases: // - for device 0 it means: device 0 passed, device 1 passed or not present // - for device 1 it means: device 1 passed return 0x01; } void AbstractIDEDevice::createSignature(bool preserveDevice) { sectorCountReg = 0x01; sectorNumReg = 0x01; if (isPacketDevice()) { cylinderLowReg = 0x14; cylinderHighReg = 0xEB; if (preserveDevice) { devHeadReg &= 0x10; } else { // The current implementation of SunriseIDE will substitute the // current device for DEV, so it will always act like DEV is // preserved. devHeadReg = 0x00; } } else { cylinderLowReg = 0x00; cylinderHighReg = 0x00; devHeadReg = 0x00; } } void AbstractIDEDevice::reset(EmuTime::param /*time*/) { errorReg = diagnostic(); statusReg = DRDY | DSC; featureReg = 0x00; createSignature(); setTransferRead(false); setTransferWrite(false); } byte AbstractIDEDevice::readReg(nibble reg, EmuTime::param /*time*/) { switch (reg) { case 1: // error register return errorReg; case 2: // sector count register return sectorCountReg; case 3: // sector number register / LBA low return sectorNumReg; case 4: // cyclinder low register / LBA mid return cylinderLowReg; case 5: // cyclinder high register / LBA high return cylinderHighReg; case 6: // device/head register // DEV bit is handled by IDE interface return devHeadReg; case 7: // status register return statusReg; case 8: case 9: case 10: case 11: case 12: case 13: case 15:// not used return 0x7F; case 0: // data register, converted to readData by IDE interface case 14:// alternate status reg, converted to read from normal // status register by IDE interface default: UNREACHABLE; return 0x7F; // avoid warning } } void AbstractIDEDevice::writeReg( nibble reg, byte value, EmuTime::param /*time*/ ) { switch (reg) { case 1: // feature register featureReg = value; break; case 2: // sector count register sectorCountReg = value; break; case 3: // sector number register / LBA low sectorNumReg = value; break; case 4: // cyclinder low register / LBA mid cylinderLowReg = value; break; case 5: // cyclinder high register / LBA high cylinderHighReg = value; break; case 6: // device/head register // DEV bit is handled by IDE interface devHeadReg = value; break; case 7: // command register statusReg &= ~(DRQ | ERR); setTransferRead(false); setTransferWrite(false); executeCommand(value); break; case 8: case 9: case 10: case 11: case 12: case 13: case 15: // not used case 14: // device control register, handled by IDE interface // do nothing break; case 0: // data register, converted to readData by IDE interface default: UNREACHABLE; break; } } word AbstractIDEDevice::readData(EmuTime::param /*time*/) { if (!transferRead) { // no read in progress return 0x7F7F; } assert((transferIdx + 1) < sizeof(buffer)); word result = (buffer[transferIdx + 0] << 0) + (buffer[transferIdx + 1] << 8); transferIdx += 2; bufferLeft -= 2; if (bufferLeft == 0) { if (transferCount == 0) { // End of transfer. setTransferRead(false); statusReg &= ~DRQ; readEnd(); } else { // Buffer empty, but transfer not done yet. readNextBlock(); } } return result; } void AbstractIDEDevice::readNextBlock() { bufferLeft = readBlockStart( buffer, std::min(sizeof(buffer), transferCount)); assert((bufferLeft & 1) == 0); transferIdx = 0; transferCount -= bufferLeft; } void AbstractIDEDevice::writeData(word value, EmuTime::param /*time*/) { if (!transferWrite) { // no write in progress return; } assert((transferIdx + 1) < sizeof(buffer)); buffer[transferIdx + 0] = value & 0xFF; buffer[transferIdx + 1] = value >> 8; transferIdx += 2; bufferLeft -= 2; if (bufferLeft == 0) { unsigned bytesInBuffer = transferIdx; if (transferCount == 0) { // End of transfer. setTransferWrite(false); statusReg &= ~DRQ; } else { // Buffer full, but transfer not done yet. writeNextBlock(); } // Packet commands can start a second transfer, so the command // execution must happen after we close this transfer. writeBlockComplete(buffer, bytesInBuffer); } } void AbstractIDEDevice::writeNextBlock() { transferIdx = 0; bufferLeft = std::min(sizeof(buffer), transferCount); transferCount -= bufferLeft; } void AbstractIDEDevice::setError(byte error) { errorReg = error; if (error) { statusReg |= ERR; } else { statusReg &= ~ERR; } statusReg &= ~DRQ; setTransferWrite(false); setTransferRead(false); } unsigned AbstractIDEDevice::getSectorNumber() const { return sectorNumReg | (cylinderLowReg << 8) | (cylinderHighReg << 16) | ((devHeadReg & 0x0F) << 24); } unsigned AbstractIDEDevice::getNumSectors() const { return (sectorCountReg == 0) ? 256 : sectorCountReg; } void AbstractIDEDevice::setInterruptReason(byte value) { sectorCountReg = value; } unsigned AbstractIDEDevice::getByteCount() { return cylinderLowReg | (cylinderHighReg << 8); } void AbstractIDEDevice::setByteCount(unsigned count) { cylinderLowReg = count & 0xFF; cylinderHighReg = count >> 8; } void AbstractIDEDevice::setSectorNumber(unsigned lba) { sectorNumReg = (lba & 0x000000FF) >> 0; cylinderLowReg = (lba & 0x0000FF00) >> 8; cylinderHighReg = (lba & 0x00FF0000) >> 16; devHeadReg = (lba & 0x0F000000) >> 24; // note: only 4 bits } void AbstractIDEDevice::readEnd() { } void AbstractIDEDevice::executeCommand(byte cmd) { switch (cmd) { case 0x08: // Device Reset if (isPacketDevice()) { errorReg = diagnostic(); createSignature(true); // TODO: Which is correct? //statusReg = 0x00; statusReg = DRDY | DSC; } else { // Command is only implemented for packet devices. setError(ABORT); } break; case 0x90: // Execute Device Diagnostic errorReg = diagnostic(); createSignature(); break; case 0x91: // Initialize Device Parameters // ignore command break; case 0xA1: // Identify Packet Device if (isPacketDevice()) { createIdentifyBlock(startShortReadTransfer(512)); } else { setError(ABORT); } break; case 0xEC: // Identify Device if (isPacketDevice()) { setError(ABORT); } else { createIdentifyBlock(startShortReadTransfer(512)); } break; case 0xEF: // Set Features switch (getFeatureReg()) { case 0x03: // Set Transfer Mode break; default: fprintf(stderr, "Unhandled set feature subcommand: %02X\n", getFeatureReg()); setError(ABORT); } break; default: // unsupported command fprintf(stderr, "unsupported IDE command %02X\n", cmd); setError(ABORT); } } AlignedBuffer& AbstractIDEDevice::startShortReadTransfer(unsigned count) { assert(count <= sizeof(buffer)); assert((count & 1) == 0); startReadTransfer(); transferCount = 0; bufferLeft = count; transferIdx = 0; memset(buffer, 0x00, count); return buffer; } void AbstractIDEDevice::startLongReadTransfer(unsigned count) { assert((count & 1) == 0); startReadTransfer(); transferCount = count; readNextBlock(); } void AbstractIDEDevice::startReadTransfer() { statusReg |= DRQ; setTransferRead(true); } void AbstractIDEDevice::abortReadTransfer(byte error) { setError(error | ABORT); setTransferRead(false); } void AbstractIDEDevice::startWriteTransfer(unsigned count) { statusReg |= DRQ; setTransferWrite(true); transferCount = count; writeNextBlock(); } void AbstractIDEDevice::abortWriteTransfer(byte error) { setError(error | ABORT); setTransferWrite(false); } void AbstractIDEDevice::setTransferRead(bool status) { if (status != transferRead) { transferRead = status; if (!transferWrite) { // (this is a bit of a hack!) motherBoard.getLedStatus().setLed(LedStatus::FDD, transferRead); } } } void AbstractIDEDevice::setTransferWrite(bool status) { if (status != transferWrite) { transferWrite = status; if (!transferRead) { // (this is a bit of a hack!) motherBoard.getLedStatus().setLed(LedStatus::FDD, transferWrite); } } } /** Writes a string to a location in the identify block. * Helper method for createIdentifyBlock. * @param p Pointer to write the characters to. * @param len Number of words to write. * @param s ASCII string to write. * If the string is longer than len*2 characters, it is truncated. * If the string is shorter than len*2 characters, it is padded with spaces. */ static void writeIdentifyString(byte* p, unsigned len, std::string s) { s.resize(2 * len, ' '); for (unsigned i = 0; i < len; ++i) { // copy and swap p[2 * i + 0] = s[2 * i + 1]; p[2 * i + 1] = s[2 * i + 0]; } } void AbstractIDEDevice::createIdentifyBlock(AlignedBuffer& buffer) { // According to the spec, the combination of model and serial should be // unique. But I don't know any MSX software that cares about this. writeIdentifyString(&buffer[10 * 2], 10, "s00000001"); // serial writeIdentifyString(&buffer[23 * 2], 4, // Use openMSX version as firmware revision, because most of our // IDE emulation code is in fact emulating the firmware. Version::RELEASE ? 'v' + std::string(Version::VERSION) : 'd' + std::string(Version::REVISION)); writeIdentifyString(&buffer[27 * 2], 20, getDeviceName()); // model fillIdentifyBlock(buffer); } template void AbstractIDEDevice::serialize(Archive& ar, unsigned /*version*/) { // no need to serialize IDEDevice base class ar.serialize_blob("buffer", buffer, sizeof(buffer)); ar.serialize("transferIdx", transferIdx); ar.serialize("bufferLeft", bufferLeft); ar.serialize("transferCount", transferCount); ar.serialize("errorReg", errorReg); ar.serialize("sectorCountReg", sectorCountReg); ar.serialize("sectorNumReg", sectorNumReg); ar.serialize("cylinderLowReg", cylinderLowReg); ar.serialize("cylinderHighReg", cylinderHighReg); ar.serialize("devHeadReg", devHeadReg); ar.serialize("statusReg", statusReg); ar.serialize("featureReg", featureReg); bool transferIdentifyBlock = false; // remove on next version increment // no need to break bw-compat now ar.serialize("transferIdentifyBlock", transferIdentifyBlock); ar.serialize("transferRead", transferRead); ar.serialize("transferWrite", transferWrite); } INSTANTIATE_SERIALIZE_METHODS(AbstractIDEDevice); } // namespace openmsx openMSX-RELEASE_0_12_0/src/ide/AbstractIDEDevice.hh000066400000000000000000000166251257557151200215270ustar00rootroot00000000000000#ifndef ABSTRACTIDEDEVICE_HH #define ABSTRACTIDEDEVICE_HH #include "IDEDevice.hh" #include "AlignedBuffer.hh" #include "serialize_meta.hh" #include namespace openmsx { class MSXMotherBoard; class AbstractIDEDevice : public IDEDevice { public: void reset(EmuTime::param time) override; word readData(EmuTime::param time) override; byte readReg(nibble reg, EmuTime::param time) override; void writeData(word value, EmuTime::param time) override; void writeReg(nibble reg, byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); protected: // Bit flags for the status register: static const byte DRDY = 0x40; static const byte DSC = 0x10; static const byte DRQ = 0x08; static const byte ERR = 0x01; // Bit flags for the error register: static const byte UNC = 0x40; static const byte IDNF = 0x10; static const byte ABORT = 0x04; explicit AbstractIDEDevice(MSXMotherBoard& motherBoard); ~AbstractIDEDevice(); /** Is this device a packet (ATAPI) device? * @return True iff this device supports the packet commands. */ virtual bool isPacketDevice() = 0; /** Gets the device name to insert as "model number" into the identify * block. * @return An ASCII string, up to 40 characters long. */ virtual const std::string& getDeviceName() = 0; /** Tells a subclass to fill the device specific parts of the identify * block located in the buffer. * The generic part is already written there. * @param buffer Array of 512 bytes. */ virtual void fillIdentifyBlock(AlignedBuffer& buffer) = 0; /** Called when a block of read data should be buffered by the controller: * when the buffer is empty or at the start of the transfer. * @param buffer Pointer to the start of a byte array. * @param count Number of bytes to be filled by this method. * This number will not exceed the array size nor the transfer length. * @return The number of bytes that was added to the array, * or 0 if the transfer was aborted (the implementation of this method * must set the relevant error flags as well). */ virtual unsigned readBlockStart(AlignedBuffer& buffer, unsigned count) = 0; /** Called when a read transfer completes. * The default implementation does nothing. */ virtual void readEnd(); /** Called when a block of written data has been buffered by the controller: * when the buffer is full or at the end of the transfer. * @param buffer Pointer to the start of a byte array. * @param count Number of data bytes in the array. */ virtual void writeBlockComplete(AlignedBuffer& buffer, unsigned count) = 0; /** Starts execution of an IDE command. * Override this to implement additional commands and make sure you call * the superclass implementation for all commands that you don't handle. */ virtual void executeCommand(byte cmd); /** Indicates an error: sets error register, error flag, aborts transfers. * @param error Value to be written to the error register. */ void setError(byte error); /** Creates an LBA sector address from the contents of the sectorNumReg, * cylinderLowReg, cylinderHighReg and devHeadReg registers. */ unsigned getSectorNumber() const; /** Gets the number of sectors indicated by the sector count register. */ unsigned getNumSectors() const; /** Writes the interrupt reason register. * This is the same as register as sector count, but serves a different * purpose. */ void setInterruptReason(byte value); /** Reads the byte count limit of a packet transfer in the registers. * The cylinder low/high registers are used for this. */ unsigned getByteCount(); /** Writes the byte count of a packet transfer in the registers. * The cylinder low/high registers are used for this. */ void setByteCount(unsigned count); /** Writes a 28-bit LBA sector number in the registers. * The cylinder low/high registers are used for this. */ void setSectorNumber(unsigned lba); /** Indicates the start of a read data transfer which uses blocks. * The readBlockStart() method is called at the start of each block. * The first block will be read immediately, so make sure you initialise * all variables needed by readBlockStart() before calling this method. * @param count Total number of bytes to transfer. */ void startLongReadTransfer(unsigned count); /** Indicates the start of a read data transfer where all data fits * into the buffer at once. * @param count Total number of bytes to transfer. * @return Pointer to the start of the buffer. * The caller should write the data there. * The relevant part of the buffer contains zeroes. */ AlignedBuffer& startShortReadTransfer(unsigned count); /** Aborts the read transfer in progress. */ void abortReadTransfer(byte error); /** Indicates the start of a write data transfer. * @param count Total number of bytes to transfer. */ void startWriteTransfer(unsigned count); /** Aborts the write transfer in progress. */ void abortWriteTransfer(byte error); byte getFeatureReg() const { return featureReg; } void setLBALow (byte value) { sectorNumReg = value; } void setLBAMid (byte value) { cylinderLowReg = value; } void setLBAHigh(byte value) { cylinderHighReg = value; } MSXMotherBoard& getMotherBoard() const { return motherBoard; } private: /** Perform diagnostic and return result. * Actually, just return success, because we don't emulate faulty hardware. */ byte diagnostic(); /** Puts special values in the sector address, sector count and device * registers to identify the type of device. * @param preserveDevice If true, preserve the value of the DEV bit; * if false, set the DEV bit to 0. */ void createSignature(bool preserveDevice = false); /** Puts the output for the IDENTIFY DEVICE command in the buffer. * @param buffer Pointer to the start of the buffer. * The buffer must be at least 512 bytes in size. */ void createIdentifyBlock(AlignedBuffer& buffer); /** Initialises registers for a data transfer. */ void startReadTransfer(); /** Initialises buffer related variables for the next data block. * Calls readBlockStart() to deliver the actual data. */ void readNextBlock(); /** Indicates that a read transfer starts. */ void setTransferRead(bool status); /** Initialises buffer related variables for the next data block. * Make sure transferCount is initialised before calling this method. */ void writeNextBlock(); /** Indicates that a write transfer starts. */ void setTransferWrite(bool status); MSXMotherBoard& motherBoard; /** Data buffer shared by all transfers. * The size must be a multiple of 512. * Right now I don't see any reason to make it larger than the minimum * size of 1 * 512. */ AlignedByteArray<512> buffer; /** Index of current read/write position in the buffer. */ unsigned transferIdx; /** Number of bytes remaining in the buffer. */ unsigned bufferLeft; /** Number of bytes remaining in the transfer after this buffer. * (total bytes remaining == transferCount + bufferLeft) */ unsigned transferCount; // ATA registers: byte errorReg; byte sectorCountReg; byte sectorNumReg; byte cylinderLowReg; byte cylinderHighReg; byte devHeadReg; byte statusReg; byte featureReg; bool transferRead; bool transferWrite; }; REGISTER_BASE_NAME_HELPER(AbstractIDEDevice, "IDEDevice"); } // namespace openmsx #endif // ABSTRACTIDEDEVICE_HH openMSX-RELEASE_0_12_0/src/ide/BeerIDE.cc000066400000000000000000000104701257557151200174770ustar00rootroot00000000000000#include "BeerIDE.hh" #include "IDEDeviceFactory.hh" #include "IDEDevice.hh" #include "serialize.hh" namespace openmsx { BeerIDE::BeerIDE(const DeviceConfig& config) : MSXDevice(config) , i8255(*this, getCurrentTime(), getCliComm()) , rom(getName() + " ROM", "rom", config) { device = IDEDeviceFactory::create( DeviceConfig(config, config.findChild("idedevice"))); powerUp(getCurrentTime()); } BeerIDE::~BeerIDE() { } void BeerIDE::reset(EmuTime::param time) { controlReg = 0; dataReg = 0; device->reset(time); i8255.reset(time); } byte BeerIDE::readMem(word address, EmuTime::param /*time*/) { if (0x4000 <= address && address < 0x8000) { return rom[address & 0x3FFF]; } return 0xFF; } const byte* BeerIDE::getReadCacheLine(word start) const { if (0x4000 <= start && start < 0x8000) { return &rom[start & 0x3FFF]; } return unmappedRead; } byte BeerIDE::readIO(word port, EmuTime::param time) { switch (port & 0x03) { case 0: return i8255.readPortA(time); case 1: return i8255.readPortB(time); case 2: return i8255.readPortC(time); case 3: return i8255.readControlPort(time); default: // unreachable, avoid warning UNREACHABLE; return 0; } } byte BeerIDE::peekIO(word port, EmuTime::param time) const { switch (port & 0x03) { case 0: return i8255.peekPortA(time); case 1: return i8255.peekPortB(time); case 2: return i8255.peekPortC(time); case 3: return i8255.readControlPort(time); default: // unreachable, avoid warning UNREACHABLE; return 0; } } void BeerIDE::writeIO(word port, byte value, EmuTime::param time) { switch (port & 0x03) { case 0: i8255.writePortA(value, time); break; case 1: i8255.writePortB(value, time); break; case 2: i8255.writePortC(value, time); break; case 3: i8255.writeControlPort(value, time); break; default: UNREACHABLE; } } // I8255Interface byte BeerIDE::readA(EmuTime::param time) { return peekA(time); } byte BeerIDE::peekA(EmuTime::param /*time*/) const { return (dataReg & 0xFF); } void BeerIDE::writeA(byte value, EmuTime::param /*time*/) { dataReg &= 0xFF00; dataReg |= value; } byte BeerIDE::readB(EmuTime::param time) { return peekB(time); } byte BeerIDE::peekB(EmuTime::param /*time*/) const { return (dataReg >> 8); } void BeerIDE::writeB(byte value, EmuTime::param /*time*/) { dataReg &= 0x00FF; dataReg |= (value << 8); } nibble BeerIDE::readC1(EmuTime::param time) { return peekC1(time); } nibble BeerIDE::peekC1(EmuTime::param /*time*/) const { return 0; // TODO check this } nibble BeerIDE::readC0(EmuTime::param time) { return peekC0(time); } nibble BeerIDE::peekC0(EmuTime::param /*time*/) const { return 0; // TODO check this } void BeerIDE::writeC1(nibble value, EmuTime::param time) { changeControl((controlReg & 0x0F) | (value << 4), time); } void BeerIDE::writeC0(nibble value, EmuTime::param time) { changeControl((controlReg & 0xF0) | value, time); } void BeerIDE::changeControl(byte value, EmuTime::param time) { byte diff = controlReg ^ value; controlReg = value; if ((diff & 0xE7) == 0) return; // nothing relevant changed byte address = controlReg & 7; switch (value & 0xE0) { case 0x40: // read /IORD=0, /IOWR=1, /CS0=0 if (address == 0) { dataReg = device->readData(time); } else { dataReg = device->readReg(address, time); } break; case 0x80: // write /IORD=1, /IOWR=0, /CS0=0 if (address == 0) { device->writeData(dataReg, time); } else { device->writeReg(address, dataReg & 0xFF, time); } break; default: // all (6) other cases, nothing break; } } template void BeerIDE::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("i8255", i8255); ar.serializePolymorphic("device", *device); ar.serialize("dataReg", dataReg); ar.serialize("controlReg", controlReg); } INSTANTIATE_SERIALIZE_METHODS(BeerIDE); REGISTER_MSXDEVICE(BeerIDE, "BeerIDE"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/ide/BeerIDE.hh000066400000000000000000000043601257557151200175120ustar00rootroot00000000000000#ifndef BEERIDE_HH #define BEERIDE_HH // Based on the blueMSX implementation: // Source: /cygdrive/d/Private/_SVNROOT/bluemsx/blueMSX/Src/Memory/romMapperBeerIDE.c,v Revision: 1.9 Date: 2008-03-31 19:42:22 /* PPI NAME IDE PIN --- ---- ------- PA0 HD0 17 D0 PA1 HD1 15 D1 PA2 HD2 13 D2 PA3 HD3 11 D3 PA4 HD4 9 D4 PA5 HD5 7 D5 PA6 HD6 5 D6 PA7 HD7 3 D7 PB0 HD8 4 D8 PB1 HD9 6 D9 PB2 HD10 8 D10 PB3 HD11 10 D11 PB4 HD12 12 D12 PB5 HD13 14 D13 PB6 HD14 16 D14 PB7 HD15 18 D15 PC0 HA0 35 A0 PC1 HA1 33 A1 PC2 HA2 36 A2 PC3 N/A PC4 N/A PC5 HCS 37 /CS0 PC6 HWR 23 /IOWR PC7 HRD 25 /IORD */ #include "MSXDevice.hh" #include "I8255Interface.hh" #include "I8255.hh" #include "Rom.hh" #include namespace openmsx { class IDEDevice; class BeerIDE final : public MSXDevice, public I8255Interface { public: explicit BeerIDE(const DeviceConfig& config); ~BeerIDE(); void reset(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte peekIO(word port, EmuTime::param time) const override; byte readIO(word port, EmuTime::param time) override; void writeIO(word port, byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: void changeControl(byte value, EmuTime::param time); // I8255Interface byte readA(EmuTime::param time) override; byte readB(EmuTime::param time) override; nibble readC0(EmuTime::param time) override; nibble readC1(EmuTime::param time) override; byte peekA(EmuTime::param time) const override; byte peekB(EmuTime::param time) const override; nibble peekC0(EmuTime::param time) const override; nibble peekC1(EmuTime::param time) const override; void writeA(byte value, EmuTime::param time) override; void writeB(byte value, EmuTime::param time) override; void writeC0(nibble value, EmuTime::param time) override; void writeC1(nibble value, EmuTime::param time) override; I8255 i8255; Rom rom; std::unique_ptr device; word dataReg; byte controlReg; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/CDImageCLI.cc000066400000000000000000000016771257557151200200720ustar00rootroot00000000000000#include "CDImageCLI.hh" #include "CommandLineParser.hh" #include "GlobalCommandController.hh" #include "TclObject.hh" #include "MSXException.hh" using std::string; namespace openmsx { CDImageCLI::CDImageCLI(CommandLineParser& parser_) : parser(parser_) { parser.registerOption("-cda", *this); // TODO: offer more options in case you want to specify 2 hard disk images? } void CDImageCLI::parseOption(const string& option, array_ref& cmdLine) { string_ref cd = string_ref(option).substr(1); // cda string filename = getArgument(option, cmdLine); if (!parser.getGlobalCommandController().hasCommand(cd)) { // TODO WIP throw MSXException("No CDROM named '" + cd + "'."); } TclObject command; command.addListElement(cd); command.addListElement(filename); command.executeCommand(parser.getInterpreter()); } string_ref CDImageCLI::optionHelp() const { return "Use iso image in argument for the CDROM extension"; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/ide/CDImageCLI.hh000066400000000000000000000006751257557151200201010ustar00rootroot00000000000000#ifndef CDIMAGECLI_HH #define CDIMAGECLI_HH #include "CLIOption.hh" namespace openmsx { class CommandLineParser; class CDImageCLI final : public CLIOption { public: explicit CDImageCLI(CommandLineParser& cmdLineParser); void parseOption(const std::string& option, array_ref& cmdLine) override; string_ref optionHelp() const override; private: CommandLineParser& parser; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/DummyIDEDevice.cc000066400000000000000000000014441257557151200210360ustar00rootroot00000000000000#include "DummyIDEDevice.hh" #include "serialize.hh" namespace openmsx { void DummyIDEDevice::reset(EmuTime::param /*time*/) { // do nothing } word DummyIDEDevice::readData(EmuTime::param /*time*/) { return 0x7F7F; } byte DummyIDEDevice::readReg(nibble /*reg*/, EmuTime::param /*time*/) { return 0x7F; } void DummyIDEDevice::writeData(word /*value*/, EmuTime::param /*time*/) { // do nothing } void DummyIDEDevice::writeReg(nibble /*reg*/, byte /*value*/, EmuTime::param /*time*/) { // do nothing } template void DummyIDEDevice::serialize(Archive& /*ar*/, unsigned /*version*/) { // nothing } INSTANTIATE_SERIALIZE_METHODS(DummyIDEDevice); REGISTER_POLYMORPHIC_INITIALIZER(IDEDevice, DummyIDEDevice, "DummyIDEDevice"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/ide/DummyIDEDevice.hh000066400000000000000000000010361257557151200210450ustar00rootroot00000000000000#ifndef DUMMYIDEDEVICE_HH #define DUMMYIDEDEVICE_HH #include "IDEDevice.hh" namespace openmsx { class DummyIDEDevice final : public IDEDevice { public: void reset(EmuTime::param time) override; word readData(EmuTime::param time) override; byte readReg(nibble reg, EmuTime::param time) override; void writeData(word value, EmuTime::param time) override; void writeReg(nibble reg, byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/DummySCSIDevice.cc000066400000000000000000000023771257557151200212040ustar00rootroot00000000000000#include "DummySCSIDevice.hh" #include "serialize.hh" namespace openmsx { void DummySCSIDevice::reset() { // do nothing } bool DummySCSIDevice::isSelected() { return false; } unsigned DummySCSIDevice::executeCmd( const byte* /*cdb*/, SCSI::Phase& /*phase*/, unsigned& /*blocks*/) { // do nothing return 0; } unsigned DummySCSIDevice::executingCmd(SCSI::Phase& /*phase*/, unsigned& /*blocks*/) { return 0; } byte DummySCSIDevice::getStatusCode() { return SCSI::ST_CHECK_CONDITION; } int DummySCSIDevice::msgOut(byte /*value*/) { return 0; // TODO: check if this is sane, but it doesn't seem to be used anyway } byte DummySCSIDevice::msgIn() { return 0; // TODO: check if this is sane, but it doesn't seem to be used anyway } void DummySCSIDevice::disconnect() { // do nothing } void DummySCSIDevice::busReset() { // do nothing } unsigned DummySCSIDevice::dataIn(unsigned& blocks) { blocks = 0; return 0; } unsigned DummySCSIDevice::dataOut(unsigned& blocks) { blocks = 0; return 0; } template void DummySCSIDevice::serialize(Archive& /*ar*/, unsigned /*version*/) { // nothing } INSTANTIATE_SERIALIZE_METHODS(DummySCSIDevice); REGISTER_POLYMORPHIC_INITIALIZER(SCSIDevice, DummySCSIDevice, "DummySCSIDevice"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/ide/DummySCSIDevice.hh000066400000000000000000000014101257557151200212010ustar00rootroot00000000000000#ifndef DUMMYSCSIDEVICE_HH #define DUMMYSCSIDEVICE_HH #include "SCSIDevice.hh" namespace openmsx { class DummySCSIDevice final : public SCSIDevice { public: void reset() override; bool isSelected() override; unsigned executeCmd(const byte* cdb, SCSI::Phase& phase, unsigned& blocks) override; unsigned executingCmd(SCSI::Phase& phase, unsigned& blocks) override; byte getStatusCode() override; int msgOut(byte value) override; byte msgIn() override; void disconnect() override; void busReset() override; // only used in MB89352 controller unsigned dataIn(unsigned& blocks) override; unsigned dataOut(unsigned& blocks) override; template void serialize(Archive& ar, unsigned version); }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/GoudaSCSI.cc000066400000000000000000000031421257557151200200170ustar00rootroot00000000000000#include "GoudaSCSI.hh" #include "serialize.hh" #include "unreachable.hh" namespace openmsx { GoudaSCSI::GoudaSCSI(const DeviceConfig& config) : MSXDevice(config) , rom(getName() + " ROM", "rom", config) , wd33c93(config) { reset(EmuTime::dummy()); } void GoudaSCSI::reset(EmuTime::param /*time*/) { wd33c93.reset(true); } byte GoudaSCSI::readIO(word port, EmuTime::param /*time*/) { switch (port & 0x03) { case 0: return wd33c93.readAuxStatus(); case 1: return wd33c93.readCtrl(); case 2: return 0xb0; // bit 4: 1 = Halt on SCSI parity error default: UNREACHABLE; return 0; } } byte GoudaSCSI::peekIO(word port, EmuTime::param /*time*/) const { switch (port & 0x03) { case 0: return wd33c93.peekAuxStatus(); case 1: return wd33c93.peekCtrl(); case 2: return 0xb0; // bit 4: 1 = Halt on SCSI parity error default: UNREACHABLE; return 0; } } void GoudaSCSI::writeIO(word port, byte value, EmuTime::param time) { switch (port & 0x03) { case 0: wd33c93.writeAdr(value); break; case 1: wd33c93.writeCtrl(value); break; case 2: reset(time); break; default: UNREACHABLE; } } byte GoudaSCSI::readMem(word address, EmuTime::param /*time*/) { return *GoudaSCSI::getReadCacheLine(address); } const byte* GoudaSCSI::getReadCacheLine(word start) const { return &rom[start & (rom.getSize() - 1)]; } template void GoudaSCSI::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("WD33C93", wd33c93); } INSTANTIATE_SERIALIZE_METHODS(GoudaSCSI); REGISTER_MSXDEVICE(GoudaSCSI, "GoudaSCSI"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/ide/GoudaSCSI.hh000066400000000000000000000013251257557151200200320ustar00rootroot00000000000000#ifndef GOUDASCSI_HH #define GOUDASCSI_HH #include "MSXDevice.hh" #include "Rom.hh" #include "WD33C93.hh" namespace openmsx { class GoudaSCSI final : public MSXDevice { public: explicit GoudaSCSI(const DeviceConfig& config); void reset(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte readIO(word port, EmuTime::param time) override; void writeIO(word port, byte value, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; template void serialize(Archive& ar, unsigned version); private: Rom rom; WD33C93 wd33c93; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/HD.cc000066400000000000000000000165451257557151200166040ustar00rootroot00000000000000#include "HD.hh" #include "FileContext.hh" #include "FileException.hh" #include "FilePool.hh" #include "DeviceConfig.hh" #include "CliComm.hh" #include "MSXMotherBoard.hh" #include "Reactor.hh" #include "GlobalSettings.hh" #include "MSXException.hh" #include "HDCommand.hh" #include "serialize.hh" #include "memory.hh" #include "xrange.hh" #include namespace openmsx { using std::string; using std::vector; HD::HD(const DeviceConfig& config) : motherBoard(config.getMotherBoard()) , name("hdX") { hdInUse = motherBoard.getSharedStuff("hdInUse"); unsigned id = 0; while ((*hdInUse)[id]) { ++id; if (id == MAX_HD) { throw MSXException("Too many HDs"); } } // for exception safety, set hdInUse only at the end name[2] = char('a' + id); // For the initial hd image, savestate should only try exactly this // (resolved) filename. For user-specified hd images (commandline or // via hda command) savestate will try to re-resolve the filename. string original = config.getChildData("filename"); string resolved = config.getFileContext().resolveCreate(original); filename = Filename(resolved); try { file = File(filename); filesize = file.getSize(); tigerTree = make_unique(*this, filesize, filename.getResolved()); } catch (FileException&) { // Image didn't exist yet, but postpone image creation: // we don't want to create images during 'testconfig' filesize = size_t(config.getChildDataAsInt("size")) * 1024 * 1024; } alreadyTried = false; (*hdInUse)[id] = true; hdCommand = make_unique( motherBoard.getCommandController(), motherBoard.getStateChangeDistributor(), motherBoard.getScheduler(), *this, motherBoard.getReactor().getGlobalSettings().getPowerSetting()); motherBoard.getMSXCliComm().update(CliComm::HARDWARE, name, "add"); } HD::~HD() { motherBoard.getMSXCliComm().update(CliComm::HARDWARE, name, "remove"); unsigned id = name[2] - 'a'; assert((*hdInUse)[id]); (*hdInUse)[id] = false; } void HD::openImage() { if (file.is_open()) return; // image didn't exist yet, create new if (alreadyTried) { throw FileException("No HD image"); } alreadyTried = true; try { file = File(filename, File::CREATE); file.truncate(filesize); tigerTree = make_unique(*this, filesize, filename.getResolved()); } catch (FileException& e) { motherBoard.getMSXCliComm().printWarning( "Couldn't create HD image: " + e.getMessage()); throw; } } void HD::switchImage(const Filename& name) { file = File(name); filename = name; filesize = file.getSize(); tigerTree = make_unique(*this, filesize, filename.getResolved()); motherBoard.getMSXCliComm().update(CliComm::MEDIA, getName(), filename.getResolved()); } size_t HD::getNbSectorsImpl() const { const_cast(*this).openImage(); return filesize / sizeof(SectorBuffer); } void HD::readSectorImpl(size_t sector, SectorBuffer& buf) { openImage(); file.seek(sector * sizeof(buf)); file.read(&buf, sizeof(buf)); } void HD::writeSectorImpl(size_t sector, const SectorBuffer& buf) { openImage(); file.seek(sector * sizeof(buf)); file.write(&buf, sizeof(buf)); tigerTree->notifyChange(sector * sizeof(buf), sizeof(buf), file.getModificationDate()); } bool HD::isWriteProtectedImpl() const { const_cast(*this).openImage(); return file.isReadOnly(); } Sha1Sum HD::getSha1SumImpl(FilePool& filePool) { openImage(); if (hasPatches()) { return SectorAccessibleDisk::getSha1SumImpl(filePool); } return filePool.getSha1Sum(file); } std::string HD::getTigerTreeHash() { openImage(); return tigerTree->calcHash().toString(); // calls HD::getData() } uint8_t* HD::getData(size_t offset, size_t size) { assert(size <= 1024); assert((offset % sizeof(SectorBuffer)) == 0); assert((size % sizeof(SectorBuffer)) == 0); struct Work { char extra; // at least one byte before 'bufs' // likely here are padding bytes inbetween SectorBuffer bufs[1024 / sizeof(SectorBuffer)]; }; static Work work; // not reentrant size_t sector = offset / sizeof(SectorBuffer); for (auto i : xrange(size / sizeof(SectorBuffer))) { // This possibly applies IPS patches. readSector(sector++, work.bufs[i]); } return work.bufs[0].raw; } bool HD::isCacheStillValid(time_t& cacheTime) { time_t fileTime = file.getModificationDate(); bool result = fileTime == cacheTime; cacheTime = fileTime; return result; } SectorAccessibleDisk* HD::getSectorAccessibleDisk() { return this; } const std::string& HD::getContainerName() const { return getName(); } bool HD::diskChanged() { return false; // TODO not implemented } int HD::insertDisk(string_ref filename) { try { switchImage(Filename(filename.str())); return 0; } catch (MSXException&) { return -1; } } // version 1: initial version // version 2: replaced 'checksum'(=sha1) with 'tthsum` template void HD::serialize(Archive& ar, unsigned version) { Filename tmp = file.is_open() ? filename : Filename(); ar.serialize("filename", tmp); if (ar.isLoader()) { if (tmp.empty()) { // Lazily open file specified in config. And close if // it was already opened (in the constructor). The // latter can occur in the following scenario: // - The hd image doesn't exist yet // - Reverse creates savestates, these still have // tmp="" (because file=nullptr) // - At some later point the hd image gets created // (e.g. on first access to the image) // - Now reverse to some point in EmuTime before the // first disk access // - The loadstate re-constructs this HD object, but // because the hd image does exist now, it gets // opened in the constructor (file!=nullptr). // - So to get in the same state as the initial // savestate we again close the file. Otherwise the // checksum-check code below goes wrong. file.close(); } else { tmp.updateAfterLoadState(); if (filename != tmp) switchImage(tmp); assert(file.is_open()); } } // store/check checksum if (file.is_open()) { bool mismatch = false; if (ar.versionAtLeast(version, 2)) { // use tiger-tree-hash string oldTiger = ar.isLoader() ? "" : getTigerTreeHash(); ar.serialize("tthsum", oldTiger); if (ar.isLoader()) { string newTiger = getTigerTreeHash(); mismatch = oldTiger != newTiger; } } else { // use sha1 auto& filepool = motherBoard.getReactor().getFilePool(); Sha1Sum oldChecksum; if (!ar.isLoader()) { oldChecksum = getSha1Sum(filepool); } string oldChecksumStr = oldChecksum.empty() ? "" : oldChecksum.toString(); ar.serialize("checksum", oldChecksumStr); oldChecksum = oldChecksumStr.empty() ? Sha1Sum() : Sha1Sum(oldChecksumStr); if (ar.isLoader()) { Sha1Sum newChecksum = getSha1Sum(filepool); mismatch = oldChecksum != newChecksum; } } if (ar.isLoader() && mismatch) { motherBoard.getMSXCliComm().printWarning( "The content of the harddisk " + tmp.getResolved() + " has changed since the time this savestate was " "created. This might result in emulation problems " "or even diskcorruption. To prevent the latter, " "the harddisk is now write-protected."); forceWriteProtect(); } } } INSTANTIATE_SERIALIZE_METHODS(HD); } // namespace openmsx openMSX-RELEASE_0_12_0/src/ide/HD.hh000066400000000000000000000035401257557151200166050ustar00rootroot00000000000000#ifndef HD_HH #define HD_HH #include "Filename.hh" #include "File.hh" #include "SectorAccessibleDisk.hh" #include "DiskContainer.hh" #include "TigerTree.hh" #include "serialize_meta.hh" #include #include #include namespace openmsx { class MSXMotherBoard; class HDCommand; class DeviceConfig; class HD : public SectorAccessibleDisk, public DiskContainer , public TTData { public: explicit HD(const DeviceConfig& config); virtual ~HD(); const std::string& getName() const { return name; } const Filename& getImageName() const { return filename; } void switchImage(const Filename& filename); std::string getTigerTreeHash(); template void serialize(Archive& ar, unsigned version); MSXMotherBoard& getMotherBoard() const { return motherBoard; } private: // SectorAccessibleDisk: void readSectorImpl (size_t sector, SectorBuffer& buf) override; void writeSectorImpl(size_t sector, const SectorBuffer& buf) override; size_t getNbSectorsImpl() const override; bool isWriteProtectedImpl() const override; Sha1Sum getSha1SumImpl(FilePool& filePool) override; // Diskcontainer: SectorAccessibleDisk* getSectorAccessibleDisk() override; const std::string& getContainerName() const override; bool diskChanged() override; int insertDisk(string_ref filename) override; // TTData uint8_t* getData(size_t offset, size_t size) override; bool isCacheStillValid(time_t& time) override; void openImage(); MSXMotherBoard& motherBoard; std::string name; std::unique_ptr hdCommand; std::unique_ptr tigerTree; File file; Filename filename; size_t filesize; bool alreadyTried; static const unsigned MAX_HD = 26; using HDInUse = std::bitset; std::shared_ptr hdInUse; }; REGISTER_BASE_CLASS(HD, "HD"); SERIALIZE_CLASS_VERSION(HD, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/HDCommand.cc000066400000000000000000000045611257557151200200760ustar00rootroot00000000000000#include "HDCommand.hh" #include "HD.hh" #include "FileContext.hh" #include "FileException.hh" #include "CommandException.hh" #include "BooleanSetting.hh" #include "TclObject.hh" namespace openmsx { using std::string; using std::vector; // class HDCommand HDCommand::HDCommand(CommandController& commandController, StateChangeDistributor& stateChangeDistributor, Scheduler& scheduler, HD& hd_, BooleanSetting& powerSetting_) : RecordedCommand(commandController, stateChangeDistributor, scheduler, hd_.getName()) , hd(hd_) , powerSetting(powerSetting_) { } void HDCommand::execute(array_ref tokens, TclObject& result, EmuTime::param /*time*/) { if (tokens.size() == 1) { result.addListElement(hd.getName() + ':'); result.addListElement(hd.getImageName().getResolved()); if (hd.isWriteProtected()) { TclObject options; options.addListElement("readonly"); result.addListElement(options); } } else if ((tokens.size() == 2) || ((tokens.size() == 3) && tokens[1] == "insert")) { if (powerSetting.getBoolean()) { throw CommandException( "Can only change hard disk image when MSX " "is powered down."); } int fileToken = 1; if (tokens[1] == "insert") { if (tokens.size() > 2) { fileToken = 2; } else { throw CommandException( "Missing argument to insert subcommand"); } } try { Filename filename(tokens[fileToken].getString().str(), userFileContext()); hd.switchImage(filename); // Note: the diskX command doesn't do this either, // so this has not been converted to TclObject style here // return filename; } catch (FileException& e) { throw CommandException("Can't change hard disk image: " + e.getMessage()); } } else { throw CommandException("Too many or wrong arguments."); } } string HDCommand::help(const vector& /*tokens*/) const { return hd.getName() + ": change the hard disk image for this hard disk drive\n"; } void HDCommand::tabCompletion(vector& tokens) const { vector extra; if (tokens.size() < 3) { extra = { "insert" }; } completeFileName(tokens, userFileContext(), extra); } bool HDCommand::needRecord(array_ref tokens) const { return tokens.size() > 1; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/ide/HDCommand.hh000066400000000000000000000015751257557151200201120ustar00rootroot00000000000000#ifndef HDCOMMAND_HH #define HDCOMMAND_HH #include "RecordedCommand.hh" #include #include namespace openmsx { class CommandController; class StateChangeDistributor; class Scheduler; class TclObject; class HD; class BooleanSetting; class HDCommand final : public RecordedCommand { public: HDCommand(CommandController& commandController, StateChangeDistributor& stateChangeDistributor, Scheduler& scheduler, HD& hd, BooleanSetting& powerSetting); void execute(array_ref tokens, TclObject& result, EmuTime::param time) override; std::string help(const std::vector& tokens) const override; void tabCompletion(std::vector& tokens) const override; bool needRecord(array_ref tokens) const override; private: HD& hd; const BooleanSetting& powerSetting; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/HDImageCLI.cc000066400000000000000000000017201257557151200200640ustar00rootroot00000000000000#include "HDImageCLI.hh" #include "CommandLineParser.hh" #include "GlobalCommandController.hh" #include "TclObject.hh" #include "MSXException.hh" using std::string; namespace openmsx { HDImageCLI::HDImageCLI(CommandLineParser& parser_) : parser(parser_) { parser.registerOption("-hda", *this); // TODO: offer more options in case you want to specify 2 hard disk images? } void HDImageCLI::parseOption(const string& option, array_ref& cmdLine) { string_ref hd = string_ref(option).substr(1); // hda string filename = getArgument(option, cmdLine); if (!parser.getGlobalCommandController().hasCommand(hd)) { // TODO WIP throw MSXException("No hard disk named '" + hd + "'."); } TclObject command; command.addListElement(hd); command.addListElement(filename); command.executeCommand(parser.getInterpreter()); } string_ref HDImageCLI::optionHelp() const { return "Use hard disk image in argument for the IDE or SCSI extensions"; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/ide/HDImageCLI.hh000066400000000000000000000006751257557151200201060ustar00rootroot00000000000000#ifndef HDIMAGECLI_HH #define HDIMAGECLI_HH #include "CLIOption.hh" namespace openmsx { class CommandLineParser; class HDImageCLI final : public CLIOption { public: explicit HDImageCLI(CommandLineParser& cmdLineParser); void parseOption(const std::string& option, array_ref& cmdLine) override; string_ref optionHelp() const override; private: CommandLineParser& parser; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/IDECDROM.cc000066400000000000000000000272031257557151200174700ustar00rootroot00000000000000#include "IDECDROM.hh" #include "DeviceConfig.hh" #include "MSXMotherBoard.hh" #include "FileContext.hh" #include "FileException.hh" #include "RecordedCommand.hh" #include "CommandException.hh" #include "TclObject.hh" #include "CliComm.hh" #include "endian.hh" #include "serialize.hh" #include "memory.hh" #include #include #include using std::string; using std::vector; namespace openmsx { class CDXCommand final : public RecordedCommand { public: CDXCommand(CommandController& commandController, StateChangeDistributor& stateChangeDistributor, Scheduler& scheduler, IDECDROM& cd); void execute(array_ref tokens, TclObject& result, EmuTime::param time) override; string help(const vector& tokens) const override; void tabCompletion(vector& tokens) const override; private: IDECDROM& cd; }; IDECDROM::IDECDROM(const DeviceConfig& config) : AbstractIDEDevice(config.getMotherBoard()) , name("cdX") { cdInUse = getMotherBoard().getSharedStuff("cdInUse"); unsigned id = 0; while ((*cdInUse)[id]) { ++id; if (id == MAX_CD) { throw MSXException("Too many CDs"); } } name[2] = char('a' + id); (*cdInUse)[id] = true; cdxCommand = make_unique( getMotherBoard().getCommandController(), getMotherBoard().getStateChangeDistributor(), getMotherBoard().getScheduler(), *this); senseKey = 0; remMedStatNotifEnabled = false; mediaChanged = false; getMotherBoard().getMSXCliComm().update(CliComm::HARDWARE, name, "add"); } IDECDROM::~IDECDROM() { getMotherBoard().getMSXCliComm().update(CliComm::HARDWARE, name, "remove"); unsigned id = name[2] - 'a'; assert((*cdInUse)[id]); (*cdInUse)[id] = false; } bool IDECDROM::isPacketDevice() { return true; } const std::string& IDECDROM::getDeviceName() { static const std::string NAME = "OPENMSX CD-ROM"; return NAME; } void IDECDROM::fillIdentifyBlock(AlignedBuffer& buffer) { // 1... ....: removable media // .10. ....: fast handling of packet command (immediate, in fact) // .... .1..: incomplete response: // fields that depend on medium are undefined // .... ..00: support for 12-byte packets buffer[0 * 2 + 0] = 0xC4; // 10.. ....: ATAPI // ...0 0101: CD-ROM device buffer[0 * 2 + 1] = 0x85; // ...1 ....: Removable Media Status Notification feature set supported buffer[ 83 * 2 + 0] = 0x10; // ...1 ....: Removable Media Status Notification feature set enabled buffer[ 86 * 2 + 0] = remMedStatNotifEnabled * 0x10; // .... ..01: Removable Media Status Notification feature set supported (again??) buffer[127 * 2 + 0] = 0x01; } unsigned IDECDROM::readBlockStart(AlignedBuffer& buffer, unsigned count) { assert(readSectorData); if (file.is_open()) { //fprintf(stderr, "read sector data at %08X\n", transferOffset); file.seek(transferOffset); file.read(buffer, count); transferOffset += count; return count; } else { //fprintf(stderr, "read sector failed: no medium\n"); // TODO: Check whether more error flags should be set. abortReadTransfer(0); return 0; } } void IDECDROM::readEnd() { setInterruptReason(I_O | C_D); } void IDECDROM::writeBlockComplete(AlignedBuffer& buffer, unsigned count) { // Currently, packet writes are the only kind of write transfer. assert(count == 12); (void)count; // avoid warning executePacketCommand(buffer); } void IDECDROM::executeCommand(byte cmd) { switch (cmd) { case 0xA0: // Packet Command (ATAPI) // Determine packet size for data packets. byteCountLimit = getByteCount(); //fprintf(stderr, "ATAPI Command, byte count limit %04X\n", // byteCountLimit); // Prepare to receive the command. startWriteTransfer(12); setInterruptReason(C_D); break; case 0xDA: // ATA Get Media Status if (remMedStatNotifEnabled) { setError(0); } else { // na WP MC na MCR ABRT NM obs byte err = 0; if (file.is_open()) { err |= 0x40; // WP (write protected) } else { err |= 0x02; // NM (no media inserted) } // MCR (media change request) is not yet supported if (mediaChanged) { err |= 0x20; // MC (media changed) mediaChanged = false; } //fprintf(stderr, "Get Media status: %02X\n", err); setError(err); } break; case 0xEF: // Set Features switch (getFeatureReg()) { case 0x31: // Disable Media Status Notification. remMedStatNotifEnabled = false; break; case 0x95: // Enable Media Status Notification setLBAMid(0x00); // version // .... .0..: capable of physically ejecting media // .... ..0.: capable of locking the media // .... ...X: previous enabled state setLBAHigh(remMedStatNotifEnabled); remMedStatNotifEnabled = true; break; default: // other subcommands handled by base class AbstractIDEDevice::executeCommand(cmd); } break; default: // all others AbstractIDEDevice::executeCommand(cmd); } } void IDECDROM::startPacketReadTransfer(unsigned count) { // TODO: Recompute for each packet. // TODO: Take even/odd stuff into account. // Note: The spec says maximum byte count is 0xFFFE, but I prefer // powers of two, so I'll use 0x8000 instead (the device is // free to set limitations of its own). unsigned packetSize = 512; /*std::min( byteCountLimit, // limit from user std::min(sizeof(buffer), 0x8000u) // device and spec limit );*/ unsigned size = std::min(packetSize, count); setByteCount(size); setInterruptReason(I_O); } void IDECDROM::executePacketCommand(AlignedBuffer& packet) { // It seems that unlike ATA which uses words at the basic data unit, // ATAPI uses bytes. //fprintf(stderr, "ATAPI Packet:"); //for (unsigned i = 0; i < 12; i++) { // fprintf(stderr, " %02X", packet[i]); //} //fprintf(stderr, "\n"); readSectorData = false; switch (packet[0]) { case 0x03: { // REQUEST SENSE Command // TODO: Find out what the purpose of the allocation length is. // In practice, it seems to be 18, which is the amount we want // to return, but what if it would be different? //int allocationLength = packet[4]; //fprintf(stderr, " request sense: %d bytes\n", allocationLength); const int byteCount = 18; startPacketReadTransfer(byteCount); auto& buffer = startShortReadTransfer(byteCount); for (int i = 0; i < byteCount; i++) { buffer[i] = 0x00; } buffer[ 0] = 0xF0; buffer[ 2] = senseKey >> 16; // sense key buffer[12] = (senseKey >> 8) & 0xFF; // ASC buffer[13] = senseKey & 0xFF; // ASQ buffer[ 7] = byteCount - 7; senseKey = 0; break; } case 0x43: { // READ TOC/PMA/ATIP Command //bool msf = packet[1] & 2; int format = packet[2] & 0x0F; //int trackOrSession = packet[6]; //int allocLen = (packet[7] << 8) | packet[8]; //int control = packet[9]; switch (format) { case 0: { // TOC //fprintf(stderr, " read TOC: %s addressing, " // "start track %d, allocation length 0x%04X\n", // msf ? "MSF" : "logical block", // trackOrSession, allocLen); setError(ABORT); break; } case 1: // Session Info case 2: // Full TOC case 3: // PMA case 4: // ATIP default: fprintf(stderr, " read TOC: format %d not implemented\n", format); setError(ABORT); } break; } case 0xA8: { // READ Command int sectorNumber = Endian::read_UA_B32(&packet[2]); int sectorCount = Endian::read_UA_B32(&packet[6]); //fprintf(stderr, " read(12): sector %d, count %d\n", // sectorNumber, sectorCount); // There are three block sizes: // - byteCountLimit: set by the host // maximum block size for transfers // - byteCount: determined by the device // actual block size for transfers // - transferCount wrap: emulation thingy // transparent to host //fprintf(stderr, "byte count limit: %04X\n", byteCountLimit); //unsigned byteCount = sectorCount * 2048; //unsigned byteCount = sizeof(buffer); //unsigned byteCount = packetSize; /* if (byteCount > byteCountLimit) { byteCount = byteCountLimit; } if (byteCount > 0xFFFE) { byteCount = 0xFFFE; } */ //fprintf(stderr, "byte count: %04X\n", byteCount); readSectorData = true; transferOffset = sectorNumber * 2048; unsigned count = sectorCount * 2048; startPacketReadTransfer(count); startLongReadTransfer(count); break; } default: fprintf(stderr, " unknown packet command 0x%02X\n", packet[0]); setError(ABORT); } } void IDECDROM::eject() { file.close(); mediaChanged = true; senseKey = 0x06 << 16; // unit attention (medium changed) getMotherBoard().getMSXCliComm().update(CliComm::MEDIA, name, ""); } void IDECDROM::insert(const string& filename) { file = File(filename); mediaChanged = true; senseKey = 0x06 << 16; // unit attention (medium changed) getMotherBoard().getMSXCliComm().update(CliComm::MEDIA, name, filename); } // class CDXCommand CDXCommand::CDXCommand(CommandController& commandController, StateChangeDistributor& stateChangeDistributor, Scheduler& scheduler, IDECDROM& cd_) : RecordedCommand(commandController, stateChangeDistributor, scheduler, cd_.name) , cd(cd_) { } void CDXCommand::execute(array_ref tokens, TclObject& result, EmuTime::param /*time*/) { if (tokens.size() == 1) { auto& file = cd.file; result.addListElement(cd.name + ':'); result.addListElement(file.is_open() ? file.getURL() : ""); if (!file.is_open()) result.addListElement("empty"); } else if ((tokens.size() == 2) && ((tokens[1] == "eject") || (tokens[1] == "-eject"))) { cd.eject(); // TODO check for locked tray if (tokens[1] == "-eject") { result.setString( "Warning: use of '-eject' is deprecated, " "instead use the 'eject' subcommand"); } } else if ((tokens.size() == 2) || ((tokens.size() == 3) && (tokens[1] == "insert"))) { int fileToken = 1; if (tokens[1] == "insert") { if (tokens.size() > 2) { fileToken = 2; } else { throw CommandException( "Missing argument to insert subcommand"); } } try { string filename = userFileContext().resolve( tokens[fileToken].getString().str()); cd.insert(filename); // return filename; // Note: the diskX command doesn't do this either, so this has not been converted to TclObject style here } catch (FileException& e) { throw CommandException("Can't change cd image: " + e.getMessage()); } } else { throw CommandException("Too many or wrong arguments."); } } string CDXCommand::help(const vector& /*tokens*/) const { return cd.name + " : display the cd image for this CDROM drive\n" + cd.name + " eject : eject the cd image from this CDROM drive\n" + cd.name + " insert : change the cd image for this CDROM drive\n" + cd.name + " : change the cd image for this CDROM drive\n"; } void CDXCommand::tabCompletion(vector& tokens) const { static const char* const extra[] = { "eject", "insert" }; completeFileName(tokens, userFileContext(), extra); } template void IDECDROM::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); string filename = file.is_open() ? file.getURL() : ""; ar.serialize("filename", filename); if (ar.isLoader()) { // re-insert CDROM before restoring 'mediaChanged', 'senseKey' if (filename.empty()) { eject(); } else { insert(filename); } } ar.serialize("byteCountLimit", byteCountLimit); ar.serialize("transferOffset", transferOffset); ar.serialize("senseKey", senseKey); ar.serialize("readSectorData", readSectorData); ar.serialize("remMedStatNotifEnabled", remMedStatNotifEnabled); ar.serialize("mediaChanged", mediaChanged); } INSTANTIATE_SERIALIZE_METHODS(IDECDROM); REGISTER_POLYMORPHIC_INITIALIZER(IDEDevice, IDECDROM, "IDECDROM"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/ide/IDECDROM.hh000066400000000000000000000034741257557151200175060ustar00rootroot00000000000000#ifndef IDECDROM_HH #define IDECDROM_HH #include "AbstractIDEDevice.hh" #include "File.hh" #include "noncopyable.hh" #include #include namespace openmsx { class DeviceConfig; class CDXCommand; class IDECDROM final : public AbstractIDEDevice, private noncopyable { public: explicit IDECDROM(const DeviceConfig& config); ~IDECDROM(); void eject(); void insert(const std::string& filename); template void serialize(Archive& ar, unsigned version); protected: // AbstractIDEDevice: bool isPacketDevice() override; const std::string& getDeviceName() override; void fillIdentifyBlock (AlignedBuffer& buffer) override; unsigned readBlockStart(AlignedBuffer& buffer, unsigned count) override; void readEnd() override; void writeBlockComplete(AlignedBuffer& buffer, unsigned count) override; void executeCommand(byte cmd) override; private: // Flags for the interrupt reason register: /** Bus release: 0 = normal, 1 = bus release */ static const byte REL = 0x04; /** I/O direction: 0 = host->device, 1 = device->host */ static const byte I_O = 0x02; /** Command/data: 0 = data, 1 = command */ static const byte C_D = 0x01; /** Indicates the start of a read data transfer performed in packets. * @param count Total number of bytes to transfer. */ void startPacketReadTransfer(unsigned count); void executePacketCommand(AlignedBuffer& packet); std::string name; std::unique_ptr cdxCommand; File file; unsigned byteCountLimit; unsigned transferOffset; unsigned senseKey; bool readSectorData; // Removable Media Status Notification Feature Set bool remMedStatNotifEnabled; bool mediaChanged; static const unsigned MAX_CD = 26; using CDInUse = std::bitset; std::shared_ptr cdInUse; friend class CDXCommand; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/IDEDevice.hh000066400000000000000000000007471257557151200200410ustar00rootroot00000000000000#ifndef IDEDEVICE_HH #define IDEDEVICE_HH #include "EmuTime.hh" #include "openmsx.hh" namespace openmsx { class IDEDevice { public: virtual ~IDEDevice() {} virtual void reset(EmuTime::param time) = 0; virtual word readData(EmuTime::param time) = 0; virtual byte readReg(nibble reg, EmuTime::param time) = 0; virtual void writeData(word value, EmuTime::param time) = 0; virtual void writeReg(nibble reg, byte value, EmuTime::param time) = 0; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/IDEDeviceFactory.cc000066400000000000000000000012541257557151200213510ustar00rootroot00000000000000#include "IDEDeviceFactory.hh" #include "DummyIDEDevice.hh" #include "IDEHD.hh" #include "IDECDROM.hh" #include "DeviceConfig.hh" #include "MSXException.hh" #include "memory.hh" using std::unique_ptr; namespace openmsx { namespace IDEDeviceFactory { unique_ptr create(const DeviceConfig& config) { if (!config.getXML()) { return make_unique(); } const std::string& type = config.getChildData("type"); if (type == "IDEHD") { return make_unique(config); } else if (type == "IDECDROM") { return make_unique(config); } throw MSXException("Unknown IDE device: " + type); } } // namespace IDEDeviceFactory } // namespace openmsx openMSX-RELEASE_0_12_0/src/ide/IDEDeviceFactory.hh000066400000000000000000000004061257557151200213610ustar00rootroot00000000000000#ifndef IDEDEVICEFACTORY_HH #define IDEDEVICEFACTORY_HH #include namespace openmsx { class IDEDevice; class DeviceConfig; namespace IDEDeviceFactory { std::unique_ptr create(const DeviceConfig& config); } } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/IDEHD.cc000066400000000000000000000070421257557151200171160ustar00rootroot00000000000000#include "IDEHD.hh" #include "MSXException.hh" #include "DeviceConfig.hh" #include "MSXMotherBoard.hh" #include "Reactor.hh" #include "DiskManipulator.hh" #include "endian.hh" #include "serialize.hh" #include namespace openmsx { IDEHD::IDEHD(const DeviceConfig& config) : HD(config) , AbstractIDEDevice(config.getMotherBoard()) , diskManipulator(config.getReactor().getDiskManipulator()) { diskManipulator.registerDrive(*this, config.getMotherBoard().getMachineID() + "::"); } IDEHD::~IDEHD() { diskManipulator.unregisterDrive(*this); } bool IDEHD::isPacketDevice() { return false; } const std::string& IDEHD::getDeviceName() { static const std::string NAME = "OPENMSX HARD DISK"; return NAME; } void IDEHD::fillIdentifyBlock(AlignedBuffer& buffer) { auto totalSectors = getNbSectors(); uint16_t heads = 16; uint16_t sectors = 32; uint16_t cylinders = uint16_t(totalSectors / (heads * sectors)); // TODO overflow? Endian::writeL16(&buffer[1 * 2], cylinders); Endian::writeL16(&buffer[3 * 2], heads); Endian::writeL16(&buffer[6 * 2], sectors); buffer[47 * 2 + 0] = 16; // max sector transfer per interrupt buffer[47 * 2 + 1] = 0x80; // specced value // .... 1...: IORDY supported (hardware signal used by PIO modes >3) // .... ..1.: LBA supported buffer[49 * 2 + 1] = 0x0A; // TODO check for overflow Endian::writeL32(&buffer[60 * 2], unsigned(totalSectors)); } unsigned IDEHD::readBlockStart(AlignedBuffer& buffer, unsigned count) { try { assert(count >= 512); (void)count; // avoid warning readSector(transferSectorNumber, *aligned_cast(buffer)); ++transferSectorNumber; return 512; } catch (MSXException&) { abortReadTransfer(UNC); return 0; } } void IDEHD::writeBlockComplete(AlignedBuffer& buffer, unsigned count) { try { assert((count % 512) == 0); unsigned num = count / 512; for (unsigned i = 0; i < num; ++i) { writeSector(transferSectorNumber++, *aligned_cast(buffer + 512 * i)); } } catch (MSXException&) { abortWriteTransfer(UNC); } } void IDEHD::executeCommand(byte cmd) { if (0x10 <= cmd && cmd < 0x20) { // Recalibrate setError(0); setByteCount(0); return; } switch (cmd) { case 0x20: // Read Sector case 0x21: // Read Sector without Retry case 0x30: // Write Sector case 0x31: { // Write Sector without Retry unsigned sectorNumber = getSectorNumber(); unsigned numSectors = getNumSectors(); if ((sectorNumber + numSectors) > getNbSectors()) { // Note: The original code set ABORT as well, but that is not // allowed according to the spec. setError(IDNF); break; } transferSectorNumber = sectorNumber; if (cmd < 0x30) { startLongReadTransfer(numSectors * 512); } else { startWriteTransfer(numSectors * 512); } break; } case 0xF8: // Read Native Max Address // We don't support the Host Protected Area feature set, but SymbOS // uses only this particular command, so we support just this one. // TODO this only supports 28-bit sector numbers setSectorNumber(unsigned(getNbSectors())); break; default: // all others AbstractIDEDevice::executeCommand(cmd); } } template void IDEHD::serialize(Archive& ar, unsigned /*version*/) { // don't serialize SectorAccessibleDisk, DiskContainer base classes ar.template serializeBase(*this); ar.template serializeBase(*this); ar.serialize("transferSectorNumber", transferSectorNumber); } INSTANTIATE_SERIALIZE_METHODS(IDEHD); REGISTER_POLYMORPHIC_INITIALIZER(IDEDevice, IDEHD, "IDEHD"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/ide/IDEHD.hh000066400000000000000000000015321257557151200171260ustar00rootroot00000000000000#ifndef IDEHD_HH #define IDEHD_HH #include "HD.hh" #include "AbstractIDEDevice.hh" #include "noncopyable.hh" namespace openmsx { class DeviceConfig; class DiskManipulator; class IDEHD final : public HD, public AbstractIDEDevice, private noncopyable { public: explicit IDEHD(const DeviceConfig& config); ~IDEHD(); template void serialize(Archive& ar, unsigned version); private: // AbstractIDEDevice: bool isPacketDevice() override; const std::string& getDeviceName() override; void fillIdentifyBlock (AlignedBuffer& buffer) override; unsigned readBlockStart(AlignedBuffer& buffer, unsigned count) override; void writeBlockComplete(AlignedBuffer& buffer, unsigned count) override; void executeCommand(byte cmd) override; DiskManipulator& diskManipulator; unsigned transferSectorNumber; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/MB89352.cc000066400000000000000000000433711257557151200172170ustar00rootroot00000000000000/* Ported from: ** Source: /cvsroot/bluemsx/blueMSX/Src/IoDevice/MB89352.c,v ** Revision: 1.9 ** Date: 2007/03/28 17:35:35 ** ** More info: http://www.bluemsx.com ** ** Copyright (C) 2003-2007 Daniel Vik, white cat */ /* * Notes: * Not suppport padding transfer and interrupt signal. (Not used MEGA-SCSI) * Message system might be imperfect. (Not used in MEGA-SCSI usually) */ #include "MB89352.hh" #include "SCSIDevice.hh" #include "DummySCSIDevice.hh" #include "SCSIHD.hh" #include "SCSILS120.hh" #include "DeviceConfig.hh" #include "XMLElement.hh" #include "MSXException.hh" #include "StringOp.hh" #include "serialize.hh" #include "memory.hh" #include #include #include using std::string; namespace openmsx { static const byte REG_BDID = 0; // Bus Device ID (r/w) static const byte REG_SCTL = 1; // Spc Control (r/w) static const byte REG_SCMD = 2; // Command (r/w) static const byte REG_OPEN = 3; // (open) static const byte REG_INTS = 4; // Interrupt Sense (r/w) static const byte REG_PSNS = 5; // Phase Sense (r) static const byte REG_SDGC = 5; // SPC Diag. Control (w) static const byte REG_SSTS = 6; // SPC SCSI::STATUS (r) static const byte REG_SERR = 7; // SPC Error SCSI::STATUS (r/w?) static const byte REG_PCTL = 8; // Phase Control (r/w) static const byte REG_MBC = 9; // Modified Byte Counter(r) static const byte REG_DREG = 10; // Data Register (r/w) static const byte REG_TEMP = 11; // Temporary Register (r/w) // Another value is maintained respec- // tively for writing and for reading static const byte REG_TCH = 12; // Transfer Counter High(r/w) static const byte REG_TCM = 13; // Transfer Counter Mid (r/w) static const byte REG_TCL = 14; // Transfer Counter Low (r/w) static const byte REG_TEMPWR = 13; // (TEMP register preservation place for writing) static const byte FIX_PCTL = 14; // (REG_PCTL & 7) static const byte PSNS_IO = 0x01; static const byte PSNS_CD = 0x02; static const byte PSNS_MSG = 0x04; static const byte PSNS_BSY = 0x08; static const byte PSNS_SEL = 0x10; static const byte PSNS_ATN = 0x20; static const byte PSNS_ACK = 0x40; static const byte PSNS_REQ = 0x80; static const byte PSNS_SELECTION = PSNS_SEL; static const byte PSNS_COMMAND = PSNS_CD; static const byte PSNS_DATAIN = PSNS_IO; static const byte PSNS_DATAOUT = 0; static const byte PSNS_STATUS = PSNS_CD | PSNS_IO; static const byte PSNS_MSGIN = PSNS_MSG | PSNS_CD | PSNS_IO; static const byte PSNS_MSGOUT = PSNS_MSG | PSNS_CD; static const byte INTS_ResetCondition = 0x01; static const byte INTS_SPC_HardError = 0x02; static const byte INTS_TimeOut = 0x04; static const byte INTS_ServiceRequited = 0x08; static const byte INTS_CommandComplete = 0x10; static const byte INTS_Disconnected = 0x20; static const byte INTS_ReSelected = 0x40; static const byte INTS_Selected = 0x80; static const byte CMD_BusRelease = 0x00; static const byte CMD_Select = 0x20; static const byte CMD_ResetATN = 0x40; static const byte CMD_SetATN = 0x60; static const byte CMD_Transfer = 0x80; static const byte CMD_TransferPause = 0xA0; static const byte CMD_Reset_ACK_REQ = 0xC0; static const byte CMD_Set_ACK_REQ = 0xE0; static const byte CMD_MASK = 0xE0; static const unsigned MAX_DEV = 8; MB89352::MB89352(const DeviceConfig& config) { // TODO: devBusy = false; // ALMOST COPY PASTED FROM WD33C93: for (auto* t : config.getXML()->getChildren("target")) { unsigned id = t->getAttributeAsInt("id"); if (id >= MAX_DEV) { throw MSXException(StringOp::Builder() << "Invalid SCSI id: " << id << " (should be 0.." + MAX_DEV - 1 << ')'); } if (dev[id]) { throw MSXException(StringOp::Builder() << "Duplicate SCSI id: " << id); } DeviceConfig conf(config, *t); auto& type = t->getChild("type").getData(); if (type == "SCSIHD") { dev[id] = make_unique(conf, buffer, SCSIDevice::MODE_SCSI2 | SCSIDevice::MODE_MEGASCSI); } else if (type == "SCSILS120") { dev[id] = make_unique(conf, buffer, SCSIDevice::MODE_SCSI2 | SCSIDevice::MODE_MEGASCSI); } else { throw MSXException("Unknown SCSI device: " + type); } } // fill remaining targets with dummy SCSI devices to prevent crashes for (auto& d : dev) { if (!d) d = make_unique(); } reset(false); // avoid UMR on savestate memset(buffer.data(), 0, SCSIDevice::BUFFER_SIZE); msgin = 0; blockCounter = 0; nextPhase = SCSI::UNDEFINED; targetId = 0; } MB89352::~MB89352() { } void MB89352::disconnect() { if (phase != SCSI::BUS_FREE) { assert(targetId < MAX_DEV); dev[targetId]->disconnect(); regs[REG_INTS] |= INTS_Disconnected; phase = SCSI::BUS_FREE; nextPhase = SCSI::UNDEFINED; } regs[REG_PSNS] = 0; isBusy = false; isTransfer = false; counter = 0; tc = 0; atn = 0; } void MB89352::softReset() { isEnabled = false; for (int i = 2; i < 15; ++i) { regs[i] = 0; } regs[15] = 0xFF; // un mapped memset(cdb, 0, sizeof(cdb)); cdbIdx = 0; bufIdx = 0; phase = SCSI::BUS_FREE; disconnect(); } void MB89352::reset(bool scsireset) { regs[REG_BDID] = 0x80; // Initial value regs[REG_SCTL] = 0x80; rst = false; atn = 0; myId = 7; softReset(); if (scsireset) { for (auto& d : dev) { d->reset(); } } } void MB89352::setACKREQ(byte& value) { // REQ check if ((regs[REG_PSNS] & (PSNS_REQ | PSNS_BSY)) != (PSNS_REQ | PSNS_BSY)) { // set ACK/REQ: REQ/BSY check error if (regs[REG_PSNS] & PSNS_IO) { // SCSI -> SPC value = 0xFF; } return; } // phase check if (regs[FIX_PCTL] != (regs[REG_PSNS] & 7)) { // set ACK/REQ: phase check error if (regs[REG_PSNS] & PSNS_IO) { // SCSI -> SPC value = 0xFF; } if (isTransfer) { regs[REG_INTS] |= INTS_ServiceRequited; } return; } switch (phase) { case SCSI::DATA_IN: // Transfer phase (data in) value = buffer[bufIdx]; ++bufIdx; regs[REG_PSNS] = PSNS_ACK | PSNS_BSY | PSNS_DATAIN; break; case SCSI::DATA_OUT: // Transfer phase (data out) buffer[bufIdx] = value; ++bufIdx; regs[REG_PSNS] = PSNS_ACK | PSNS_BSY | PSNS_DATAOUT; break; case SCSI::COMMAND: // Command phase if (counter < 0) { // Initialize command routine cdbIdx = 0; counter = (value < 0x20) ? 6 : ((value < 0xA0) ? 10 : 12); } cdb[cdbIdx] = value; ++cdbIdx; regs[REG_PSNS] = PSNS_ACK | PSNS_BSY | PSNS_COMMAND; break; case SCSI::STATUS: // SCSI::STATUS phase value = dev[targetId]->getStatusCode(); regs[REG_PSNS] = PSNS_ACK | PSNS_BSY | PSNS_STATUS; break; case SCSI::MSG_IN: // Message In phase value = dev[targetId]->msgIn(); regs[REG_PSNS] = PSNS_ACK | PSNS_BSY | PSNS_MSGIN; break; case SCSI::MSG_OUT: // Message Out phase msgin |= dev[targetId]->msgOut(value); regs[REG_PSNS] = PSNS_ACK | PSNS_BSY | PSNS_MSGOUT; break; default: // set ACK/REQ code error break; } } void MB89352::resetACKREQ() { // ACK check if ((regs[REG_PSNS] & (PSNS_ACK | PSNS_BSY)) != (PSNS_ACK | PSNS_BSY)) { // reset ACK/REQ: ACK/BSY check error return; } // phase check if (regs[FIX_PCTL] != (regs[REG_PSNS] & 7)) { // reset ACK/REQ: phase check error if (isTransfer) { regs[REG_INTS] |= INTS_ServiceRequited; } return; } switch (phase) { case SCSI::DATA_IN: // Transfer phase (data in) if (--counter > 0) { regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_DATAIN; } else { if (blockCounter > 0) { counter = dev[targetId]->dataIn(blockCounter); if (counter) { regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_DATAIN; bufIdx = 0; break; } } regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_STATUS; phase = SCSI::STATUS; } break; case SCSI::DATA_OUT: // Transfer phase (data out) if (--counter > 0) { regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_DATAOUT; } else { counter = dev[targetId]->dataOut(blockCounter); if (counter) { regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_DATAOUT; bufIdx = 0; break; } regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_STATUS; phase = SCSI::STATUS; } break; case SCSI::COMMAND: // Command phase if (--counter > 0) { regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_COMMAND; } else { bufIdx = 0; // reset buffer index // TODO: devBusy = true; counter = dev[targetId]->executeCmd(cdb, phase, blockCounter); switch (phase) { case SCSI::DATA_IN: regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_DATAIN; break; case SCSI::DATA_OUT: regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_DATAOUT; break; case SCSI::STATUS: regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_STATUS; break; case SCSI::EXECUTE: regs[REG_PSNS] = PSNS_BSY; return; // note: return iso break default: // phase error break; } // TODO: devBusy = false; } break; case SCSI::STATUS: // SCSI::STATUS phase regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_MSGIN; phase = SCSI::MSG_IN; break; case SCSI::MSG_IN: // Message In phase if (msgin <= 0) { disconnect(); break; } msgin = 0; // throw to SCSI::MSG_OUT... case SCSI::MSG_OUT: // Message Out phase if (msgin == -1) { disconnect(); return; } if (atn) { if (msgin & 2) { disconnect(); return; } regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_MSGOUT; return; } if (msgin & 1) { phase = SCSI::MSG_IN; } else { if (msgin & 4) { phase = SCSI::STATUS; nextPhase = SCSI::UNDEFINED; } else { phase = nextPhase; nextPhase = SCSI::UNDEFINED; } } msgin = 0; switch (phase) { case SCSI::COMMAND: regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_COMMAND; break; case SCSI::DATA_IN: regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_DATAIN; break; case SCSI::DATA_OUT: regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_DATAOUT; break; case SCSI::STATUS: regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_STATUS; break; case SCSI::MSG_IN: regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_MSGIN; break; default: // MsgOut code error break; } return; default: //UNREACHABLE; // reset ACK/REQ code error break; } if (atn) { nextPhase = phase; phase = SCSI::MSG_OUT; regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_MSGOUT; } } byte MB89352::readDREG() { if (isTransfer && (tc > 0)) { setACKREQ(regs[REG_DREG]); resetACKREQ(); --tc; if (tc == 0) { isTransfer = false; regs[REG_INTS] |= INTS_CommandComplete; } regs[REG_MBC] = (regs[REG_MBC] - 1) & 0x0F; return regs[REG_DREG]; } else { return 0xFF; } } void MB89352::writeDREG(byte value) { if (isTransfer && (tc > 0)) { setACKREQ(value); resetACKREQ(); --tc; if (tc == 0) { isTransfer = false; regs[REG_INTS] |= INTS_CommandComplete; } regs[REG_MBC] = (regs[REG_MBC] - 1) & 0x0F; } } void MB89352::writeRegister(byte reg, byte value) { switch (reg) { case REG_DREG: // write data Register writeDREG(value); break; case REG_SCMD: { if (!isEnabled) { break; } // bus reset if (value & 0x10) { if (((regs[REG_SCMD] & 0x10) == 0) & (regs[REG_SCTL] == 0)) { rst = true; regs[REG_INTS] |= INTS_ResetCondition; for (auto& d : dev) { d->busReset(); } disconnect(); // alternative routine } } else { rst = false; } regs[REG_SCMD] = value; // execute spc command switch (value & CMD_MASK) { case CMD_Set_ACK_REQ: switch (phase) { case SCSI::DATA_IN: case SCSI::STATUS: case SCSI::MSG_IN: setACKREQ(regs[REG_TEMP]); break; default: setACKREQ(regs[REG_TEMPWR]); } break; case CMD_Reset_ACK_REQ: resetACKREQ(); break; case CMD_Select: { if (rst) { regs[REG_INTS] |= INTS_TimeOut; break; } if (regs[REG_PCTL] & 1) { // reselection error regs[REG_INTS] |= INTS_TimeOut; disconnect(); break; } bool err = false; int x = regs[REG_BDID] & regs[REG_TEMPWR]; if (phase == SCSI::BUS_FREE && x && x != regs[REG_TEMPWR]) { x = regs[REG_TEMPWR] & ~regs[REG_BDID]; // the targetID is calculated. // It is given priority that the number is large. for (targetId = 0; targetId < MAX_DEV; ++targetId) { x >>= 1; if (x == 0) { break; } } if (/*!TODO: devBusy &&*/ dev[targetId]->isSelected()) { // target selection OK regs[REG_INTS] |= INTS_CommandComplete; isBusy = true; msgin = 0; counter = -1; err = false; if (atn) { phase = SCSI::MSG_OUT; nextPhase = SCSI::COMMAND; regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_MSGOUT; } else { phase = SCSI::COMMAND; nextPhase = SCSI::UNDEFINED; regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_COMMAND; } } else { err = true; } } else { err = true; } if (err) { // target selection error regs[REG_INTS] |= INTS_TimeOut; disconnect(); } break; } // hardware transfer case CMD_Transfer: if ((regs[FIX_PCTL] == (regs[REG_PSNS] & 7)) && (regs[REG_PSNS] & (PSNS_REQ | PSNS_BSY))) { isTransfer = true; // set Xfer in Progress } else { // phase error regs[REG_INTS] |= INTS_ServiceRequited; } break; case CMD_BusRelease: disconnect(); break; case CMD_SetATN: atn = PSNS_ATN; break; case CMD_ResetATN: atn = 0; break; case CMD_TransferPause: // nothing is done in the initiator. break; } break; // end of REG_SCMD } case REG_INTS: // Reset Interrupts regs[REG_INTS] &= ~value; if (rst) { regs[REG_INTS] |= INTS_ResetCondition; } break; case REG_TEMP: regs[REG_TEMPWR] = value; break; case REG_TCL: tc = (tc & 0xFFFF00) + (value << 0); break; case REG_TCM: tc = (tc & 0xFF00FF) + (value << 8); break; case REG_TCH: tc = (tc & 0x00FFFF) + (value << 16); break; case REG_PCTL: regs[REG_PCTL] = value; regs[FIX_PCTL] = value & 7; break; case REG_BDID: // set Bus Device ID value &= 7; myId = value; regs[REG_BDID] = 1 << value; break; // Nothing case REG_SDGC: case REG_SSTS: case REG_SERR: case REG_MBC: case 15: break; case REG_SCTL: { bool flag = !(value & 0xE0); if (flag != isEnabled) { isEnabled = flag; if (!flag) { softReset(); } } // throw to default } default: regs[reg] = value; } } byte MB89352::getSSTS() const { byte result = 1; // set fifo empty if (isTransfer) { if (regs[REG_PSNS] & PSNS_IO) { // SCSI -> SPC transfer if (tc >= 8) { result = 2; // set fifo full } else { if (tc != 0) { result = 0; // set fifo 1..7 bytes } } } } if (phase != SCSI::BUS_FREE) { result |= 0x80; // set iniciator } if (isBusy) { result |= 0x20; // set SPC_BSY } if ((phase >= SCSI::COMMAND) || isTransfer) { result |= 0x10; // set Xfer in Progress } if (rst) { result |= 0x08; // set SCSI RST } if (tc == 0) { result |= 0x04; // set tc = 0 } return result; } byte MB89352::readRegister(byte reg) { switch (reg) { case REG_DREG: return readDREG(); case REG_PSNS: if (phase == SCSI::EXECUTE) { counter = dev[targetId]->executingCmd(phase, blockCounter); if (atn && phase != SCSI::EXECUTE) { nextPhase = phase; phase = SCSI::MSG_OUT; regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_MSGOUT; } else { switch (phase) { case SCSI::DATA_IN: regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_DATAIN; break; case SCSI::DATA_OUT: regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_DATAOUT; break; case SCSI::STATUS: regs[REG_PSNS] = PSNS_REQ | PSNS_BSY | PSNS_STATUS; break; case SCSI::EXECUTE: regs[REG_PSNS] = PSNS_BSY; break; default: // phase error break; } } } return regs[REG_PSNS] | atn; default: return peekRegister(reg); } } byte MB89352::peekDREG() const { if (isTransfer && (tc > 0)) { return regs[REG_DREG]; } else { return 0xFF; } } byte MB89352::peekRegister(byte reg) const { switch (reg) { case REG_DREG: return peekDREG(); case REG_PSNS: return regs[REG_PSNS] | atn; case REG_SSTS: return getSSTS(); case REG_TCH: return (tc >> 16) & 0xFF; case REG_TCM: return (tc >> 8) & 0xFF; case REG_TCL: return (tc >> 0) & 0xFF; default: return regs[reg]; } } // TODO duplicated in WD33C93.cc static enum_string phaseInfo[] = { { "UNDEFINED", SCSI::UNDEFINED }, { "BUS_FREE", SCSI::BUS_FREE }, { "ARBITRATION", SCSI::ARBITRATION }, { "SELECTION", SCSI::SELECTION }, { "RESELECTION", SCSI::RESELECTION }, { "COMMAND", SCSI::COMMAND }, { "EXECUTE", SCSI::EXECUTE }, { "DATA_IN", SCSI::DATA_IN }, { "DATA_OUT", SCSI::DATA_OUT }, { "STATUS", SCSI::STATUS }, { "MSG_OUT", SCSI::MSG_OUT }, { "MSG_IN", SCSI::MSG_IN } }; SERIALIZE_ENUM(SCSI::Phase, phaseInfo); template void MB89352::serialize(Archive& ar, unsigned /*version*/) { ar.serialize_blob("buffer", buffer.data(), buffer.size()); char tag[8] = { 'd', 'e', 'v', 'i', 'c', 'e', 'X', 0 }; for (unsigned i = 0; i < MAX_DEV; ++i) { tag[6] = char('0' + i); ar.serializePolymorphic(tag, *dev[i]); } ar.serialize("bufIdx", bufIdx); ar.serialize("msgin", msgin); ar.serialize("counter", counter); ar.serialize("blockCounter", blockCounter); ar.serialize("tc", tc); ar.serialize("phase", phase); ar.serialize("nextPhase", nextPhase); ar.serialize("myId", myId); ar.serialize("targetId", targetId); ar.serialize_blob("registers", regs, sizeof(regs)); ar.serialize("rst", rst); ar.serialize("atn", atn); ar.serialize("isEnabled", isEnabled); ar.serialize("isBusy", isBusy); ar.serialize("isTransfer", isTransfer); ar.serialize("cdbIdx", cdbIdx); ar.serialize_blob("cdb", cdb, sizeof(cdb)); } INSTANTIATE_SERIALIZE_METHODS(MB89352); } // namespace openmsx openMSX-RELEASE_0_12_0/src/ide/MB89352.hh000066400000000000000000000042041257557151200172210ustar00rootroot00000000000000/* Ported from: ** Source: /cvsroot/bluemsx/blueMSX/Src/IoDevice/MB89352.h,v ** Revision: 1.4 ** Date: 2007/03/28 17:35:35 ** ** More info: http://www.bluemsx.com ** ** Copyright (C) 2003-2007 Daniel Vik, white cat */ #ifndef MB89352_HH #define MB89352_HH #include "SCSI.hh" #include "SCSIDevice.hh" #include "AlignedBuffer.hh" #include namespace openmsx { class DeviceConfig; class MB89352 { public: explicit MB89352(const DeviceConfig& config); ~MB89352(); void reset(bool scsireset); byte readRegister(byte reg); byte peekRegister(byte reg) const; byte readDREG(); byte peekDREG() const; void writeRegister(byte reg, byte value); void writeDREG(byte value); template void serialize(Archive& ar, unsigned version); private: void disconnect(); void softReset(); void setACKREQ(byte& value); void resetACKREQ(); byte getSSTS() const; std::unique_ptr dev[8]; AlignedByteArray buffer; // buffer for transfer unsigned cdbIdx; // cdb index unsigned bufIdx; // buffer index int msgin; // Message In flag int counter; // read and written number of bytes // within the range in the buffer unsigned blockCounter; // Number of blocks outside buffer // (512bytes / block) int tc; // counter for hardware transfer SCSI::Phase phase; // SCSI::Phase nextPhase; // for message system byte myId; // SPC SCSI ID 0..7 byte targetId; // SCSI Device target ID 0..7 byte regs[16]; // SPC register bool rst; // SCSI bus reset signal byte atn; // SCSI bus attention signal bool isEnabled; // spc enable flag bool isBusy; // spc now working bool isTransfer; // hardware transfer mode //TODO: bool devBusy; // CDROM busy (buffer conflict prevention) byte cdb[12]; // Command Descripter Block }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/MegaSCSI.cc000066400000000000000000000114001257557151200176250ustar00rootroot00000000000000/* * MEGA-SCSI and ESE-RAM cartridge: * The mapping does SRAM and MB89352A(MEGA-SCSI) to ASCII8 or * an interchangeable bank controller. * * Specification: * SRAM(MegaROM) controller: ASCII8 type * SRAM capacity : 128, 256, 512 and 1024KB * SCSI Protocol Controller: Fujitsu MB89352A * * Bank changing address: * bank 4(0x4000-0x5fff): 0x6000 - 0x67FF (0x6000 used) * bank 6(0x6000-0x7fff): 0x6800 - 0x6FFF (0x6800 used) * bank 8(0x8000-0x9fff): 0x7000 - 0x77FF (0x7000 used) * bank A(0xa000-0xbfff): 0x7800 - 0x7FFF (0x7800 used) * * ESE-RAM Bank Map: * BANK 00H-7FH (read only) * BANK 80H-FFH (write and read. mirror of 00H-7FH) * * MEGA-SCSI Bank Map: * BANK 00H-3FH (sram read only. mirror of 80H-BFH) * BANK 40H-7EH (mirror of 7FH. Use is prohibited) * BANK 7FH (SPC) * BANK 80H-FFH (sram write and read) * * SPC Bank: * 0x0000 - 0x0FFF : * SPC Data register r/w (mirror of all 0x1FFA) * 0x1000 - 0x1FEF : * mirror of 0x1FF0 - 0x1FFF * Use is prohibited about the image * 0x1FF0 - 0x1FFE : * SPC register * 0x1FFF : * un mapped * * Note: * It is possible to access it by putting it out to 8000H - BFFFH * though the SPC bank is arranged in chiefly 4000H-5FFF. */ #include "MegaSCSI.hh" #include "StringOp.hh" #include "MSXException.hh" #include "serialize.hh" #include namespace openmsx { static const byte SPC = 0x7F; unsigned MegaSCSI::getSramSize() const { unsigned sramSize = getDeviceConfig().getChildDataAsInt("sramsize", 1024); // size in kb if (sramSize != 1024 && sramSize != 512 && sramSize != 256 && sramSize != 128) { throw MSXException(StringOp::Builder() << "SRAM size for " << getName() << " should be 128, 256, 512 or 1024kB and not " << sramSize << "kB!"); } return sramSize * 1024; // in bytes } MegaSCSI::MegaSCSI(const DeviceConfig& config) : MSXDevice(config) , mb89352(config) , sram(getName() + " SRAM", getSramSize(), config) , romBlockDebug(*this, mapped, 0x4000, 0x8000, 13) , blockMask((sram.getSize() / 0x2000) - 1) { } void MegaSCSI::reset(EmuTime::param /*time*/) { for (int i = 0; i < 4; ++i) { setSRAM(i, 0); } mb89352.reset(true); } byte MegaSCSI::readMem(word address, EmuTime::param /*time*/) { byte result; if ((0x4000 <= address) && (address < 0xC000)) { unsigned page = (address / 0x2000) - 2; word addr = address & 0x1FFF; if (mapped[page] == SPC) { // SPC read if (addr < 0x1000) { // Data Register result = mb89352.readDREG(); } else { result = mb89352.readRegister(addr & 0x0F); } } else { result = sram[0x2000 * mapped[page] + addr]; } } else { result = 0xFF; } return result; } byte MegaSCSI::peekMem(word address, EmuTime::param /*time*/) const { if (const byte* cacheline = MegaSCSI::getReadCacheLine(address)) { return *cacheline; } else { address &= 0x1FFF; if (address < 0x1000) { return mb89352.peekDREG(); } else { return mb89352.peekRegister(address & 0x0F); } } } const byte* MegaSCSI::getReadCacheLine(word address) const { if ((0x4000 <= address) && (address < 0xC000)) { unsigned page = (address / 0x2000) - 2; address &= 0x1FFF; if (mapped[page] == SPC) { return nullptr; } else { return &sram[0x2000 * mapped[page] + address]; } } else { return unmappedRead; } } void MegaSCSI::writeMem(word address, byte value, EmuTime::param /*time*/) { if ((0x6000 <= address) && (address < 0x8000)) { byte region = ((address >> 11) & 3); setSRAM(region, value); } else if ((0x4000 <= address) && (address < 0xC000)) { unsigned page = (address / 0x2000) - 2; address &= 0x1FFF; if (mapped[page] == SPC) { if (address < 0x1000) { mb89352.writeDREG(value); } else { mb89352.writeRegister(address & 0x0F, value); } } else if (isWriteable[page]) { sram.write(0x2000 * mapped[page] + address, value); } } } byte* MegaSCSI::getWriteCacheLine(word address) const { if ((0x6000 <= address) && (address < 0x8000)) { return nullptr; } else if ((0x4000 <= address) && (address < 0xC000)) { unsigned page = (address / 0x2000) - 2; if (mapped[page] == SPC) { return nullptr; } else if (isWriteable[page]) { return nullptr; } } return unmappedWrite; } void MegaSCSI::setSRAM(unsigned region, byte block) { invalidateMemCache(region * 0x2000 + 0x4000, 0x2000); assert(region < 4); isWriteable[region] = (block & 0x80) != 0; mapped[region] = ((block & 0xC0) == 0x40) ? 0x7F : (block & blockMask); } template void MegaSCSI::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("SRAM", sram); ar.serialize("MB89352", mb89352); ar.serialize("isWriteable", isWriteable); ar.serialize("mapped", mapped); } INSTANTIATE_SERIALIZE_METHODS(MegaSCSI); REGISTER_MSXDEVICE(MegaSCSI, "MegaSCSI"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/ide/MegaSCSI.hh000066400000000000000000000017521257557151200176500ustar00rootroot00000000000000#ifndef MEGASCSI_HH #define MEGASCSI_HH #include "MSXDevice.hh" #include "MB89352.hh" #include "SRAM.hh" #include "RomBlockDebuggable.hh" namespace openmsx { class MegaSCSI final : public MSXDevice { public: explicit MegaSCSI(const DeviceConfig& config); void reset(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte* getWriteCacheLine(word start) const override; template void serialize(Archive& ar, unsigned version); private: unsigned getSramSize() const; void setSRAM(unsigned region, byte block); MB89352 mb89352; SRAM sram; RomBlockDebuggable romBlockDebug; bool isWriteable[4]; // which region is readonly? byte mapped[4]; // SPC block mapped in this region? const byte blockMask; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/SCSI.hh000066400000000000000000000102641257557151200170540ustar00rootroot00000000000000/* Ported from: ** Source: /cvsroot/bluemsx/blueMSX/Src/IoDevice/ScsiDefs.h,v ** Revision: 1.2 ** Date: 2007/03/24 08:01:48 ** ** More info: http://www.bluemsx.com ** ** Copyright (C) 2003-2007 Daniel Vik, white cat */ #ifndef SCSI_HH #define SCSI_HH #include "openmsx.hh" namespace openmsx { namespace SCSI { // Group 0: 6bytes cdb static const byte OP_TEST_UNIT_READY = 0x00; static const byte OP_REZERO_UNIT = 0x01; static const byte OP_REQUEST_SENSE = 0x03; static const byte OP_FORMAT_UNIT = 0x04; static const byte OP_REASSIGN_BLOCKS = 0x07; static const byte OP_READ6 = 0x08; static const byte OP_WRITE6 = 0x0A; static const byte OP_SEEK6 = 0x0B; static const byte OP_INQUIRY = 0x12; static const byte OP_RESERVE_UNIT = 0x16; static const byte OP_RELEASE_UNIT = 0x17; static const byte OP_MODE_SENSE = 0x1A; static const byte OP_START_STOP_UNIT = 0x1B; static const byte OP_SEND_DIAGNOSTIC = 0x1D; // Group 1: 10bytes cdb static const byte OP_GROUP1 = 0x20; static const byte OP_READ_CAPACITY = 0x25; static const byte OP_READ10 = 0x28; static const byte OP_WRITE10 = 0x2A; static const byte OP_SEEK10 = 0x2B; static const byte OP_GROUP2 = 0x40; static const byte OP_CHANGE_DEFINITION = 0x40; static const byte OP_READ_SUB_CHANNEL = 0x42; static const byte OP_READ_TOC = 0x43; static const byte OP_READ_HEADER = 0x44; static const byte OP_PLAY_AUDIO = 0x45; static const byte OP_PLAY_AUDIO_MSF = 0x47; static const byte OP_PLAY_TRACK_INDEX = 0x48; static const byte OP_PLAY_TRACK_RELATIVE = 0x49; static const byte OP_PAUSE_RESUME = 0x4B; static const byte OP_PLAY_AUDIO12 = 0xA5; static const byte OP_READ12 = 0xA8; static const byte OP_PLAY_TRACK_RELATIVE12 = 0xA9; static const byte OP_READ_CD_MSF = 0xB9; static const byte OP_READ_CD = 0xBE; // Sense data KEY | ASC | ASCQ static const unsigned SENSE_NO_SENSE = 0x000000; static const unsigned SENSE_NOT_READY = 0x020400; static const unsigned SENSE_MEDIUM_NOT_PRESENT = 0x023a00; static const unsigned SENSE_UNRECOVERED_READ_ERROR = 0x031100; static const unsigned SENSE_WRITE_FAULT = 0x040300; static const unsigned SENSE_INVALID_COMMAND_CODE = 0x052000; static const unsigned SENSE_ILLEGAL_BLOCK_ADDRESS = 0x052100; static const unsigned SENSE_INVALID_LUN = 0x052500; static const unsigned SENSE_POWER_ON = 0x062900; static const unsigned SENSE_WRITE_PROTECT = 0x072700; static const unsigned SENSE_MESSAGE_REJECT_ERROR = 0x0b4300; static const unsigned SENSE_INITIATOR_DETECTED_ERR = 0x0b4800; static const unsigned SENSE_ILLEGAL_MESSAGE = 0x0b4900; // Message static const byte MSG_COMMAND_COMPLETE = 0x00; static const byte MSG_INITIATOR_DETECT_ERROR = 0x05; static const byte MSG_ABORT = 0x06; static const byte MSG_REJECT = 0x07; static const byte MSG_NO_OPERATION = 0x08; static const byte MSG_PARITY_ERROR = 0x09; static const byte MSG_BUS_DEVICE_RESET = 0x0c; // Status static const byte ST_GOOD = 0; static const byte ST_CHECK_CONDITION = 2; static const byte ST_BUSY = 8; // Device type static const byte DT_DirectAccess = 0x00; static const byte DT_SequencialAccess = 0x01; static const byte DT_Printer = 0x02; static const byte DT_Processor = 0x03; static const byte DT_WriteOnce = 0x04; static const byte DT_CDROM = 0x05; static const byte DT_Scanner = 0x06; static const byte DT_OpticalMemory = 0x07; static const byte DT_MediaChanger = 0x08; static const byte DT_Communications = 0x09; static const byte DT_Undefined = 0x1f; enum Phase { UNDEFINED, // used in MB89532 BUS_FREE, ARBITRATION, SELECTION, RESELECTION, COMMAND, EXECUTE, DATA_IN, DATA_OUT, STATUS, MSG_OUT, MSG_IN, }; } // namespace SCSI } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/SCSIDevice.hh000066400000000000000000000025401257557151200201720ustar00rootroot00000000000000#ifndef SCSIDEVICE_HH #define SCSIDEVICE_HH #include "SCSI.hh" namespace openmsx { class SCSIDevice { public: static const unsigned BIT_SCSI2 = 0x0001; static const unsigned BIT_SCSI2_ONLY = 0x0002; static const unsigned BIT_SCSI3 = 0x0004; static const unsigned MODE_SCSI1 = 0x0000; static const unsigned MODE_SCSI2 = 0x0003; static const unsigned MODE_SCSI3 = 0x0005; static const unsigned MODE_UNITATTENTION = 0x0008; // report unit attention static const unsigned MODE_MEGASCSI = 0x0010; // report disk change when call of // 'test unit ready' static const unsigned MODE_NOVAXIS = 0x0100; static const unsigned BUFFER_SIZE = 0x10000; // 64KB virtual ~SCSIDevice() {}; virtual void reset() = 0; virtual bool isSelected() = 0; virtual unsigned executeCmd(const byte* cdb, SCSI::Phase& phase, unsigned& blocks) = 0; virtual unsigned executingCmd(SCSI::Phase& phase, unsigned& blocks) = 0; virtual byte getStatusCode() = 0; virtual int msgOut(byte value) = 0; virtual byte msgIn() = 0; virtual void disconnect() = 0; virtual void busReset() = 0; // only used in MB89352 controller virtual unsigned dataIn(unsigned& blocks) = 0; virtual unsigned dataOut(unsigned& blocks) = 0; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/SCSIHD.cc000066400000000000000000000332511257557151200172570ustar00rootroot00000000000000/* Ported from: ** Source: /cvsroot/bluemsx/blueMSX/Src/IoDevice/ScsiDevice.c,v ** Revision: 1.10 ** Date: 2007-05-21 21:38:29 +0200 (Mon, 21 May 2007) ** ** More info: http://www.bluemsx.com ** ** Copyright (C) 2003-2007 Daniel Vik, white cat */ /* * Notes: * It follows the SCSI1(CCS) standard or the SCSI2 standard. * Only the direct access device is supported now. * Message system might be imperfect. * * NOTE: this version only supports a non-removable harddisk, as the class * name suggests. Refer to revision 6526 of this file to see what was removed * from the generic/parameterised code. */ #include "SCSIHD.hh" #include "FileOperations.hh" #include "MSXException.hh" #include "LedStatus.hh" #include "MSXMotherBoard.hh" #include "DeviceConfig.hh" #include "endian.hh" #include "serialize.hh" #include #include using std::string; namespace openmsx { // Medium type (value like LS-120) static const byte MT_UNKNOWN = 0x00; static const byte MT_2DD_UN = 0x10; static const byte MT_2DD = 0x11; static const byte MT_2HD_UN = 0x20; static const byte MT_2HD_12_98 = 0x22; static const byte MT_2HD_12 = 0x23; static const byte MT_2HD_144 = 0x24; static const byte MT_LS120 = 0x31; static const byte MT_NO_DISK = 0x70; static const byte MT_DOOR_OPEN = 0x71; static const byte MT_FMT_ERROR = 0x72; static const byte inqdata[36] = { 0, // bit5-0 device type code. 0, // bit7 = 1 removable device 2, // bit7,6 ISO version. bit5,4,3 ECMA version. // bit2,1,0 ANSI Version (001=SCSI1, 010=SCSI2) 2, // bit7 AENC. bit6 TrmIOP. // bit3-0 Response Data Format. (0000=SCSI1, 0001=CCS, 0010=SCSI2) 51, // addtional length 0, 0,// reserved 0, // bit7 RelAdr, bit6 WBus32, bit5 Wbus16, bit4 Sync, bit3 Linked, // bit2 reseved bit1 CmdQue, bit0 SftRe 'o', 'p', 'e', 'n', 'M', 'S', 'X', ' ', // vendor ID (8bytes) 'S', 'C', 'S', 'I', '2', ' ', 'H', 'a', // product ID (16bytes) 'r', 'd', 'd', 'i', 's', 'k', ' ', ' ', '0', '1', '0', 'a' // product version (ASCII 4bytes) }; static const unsigned BUFFER_BLOCK_SIZE = SCSIHD::BUFFER_SIZE / SectorAccessibleDisk::SECTOR_SIZE; SCSIHD::SCSIHD(const DeviceConfig& targetconfig, AlignedBuffer& buf, unsigned mode_) : HD(targetconfig) , buffer(buf) , mode(mode_) , scsiId(targetconfig.getAttributeAsInt("id")) { reset(); } void SCSIHD::reset() { currentSector = 0; currentLength = 0; busReset(); } void SCSIHD::busReset() { keycode = 0; unitAttention = (mode & MODE_UNITATTENTION) != 0; } void SCSIHD::disconnect() { getMotherBoard().getLedStatus().setLed(LedStatus::FDD, false); } // Check the initiator in the call origin. bool SCSIHD::isSelected() { lun = 0; return true; } unsigned SCSIHD::inquiry() { unsigned length = currentLength; if (length == 0) return 0; memcpy(buffer + 2, inqdata + 2, 34); buffer[0] = SCSI::DT_DirectAccess; buffer[1] = 0; // removable if (!(mode & BIT_SCSI2)) { buffer[2] = 1; buffer[3] = 1; buffer[20] = '1'; } else { if (mode & BIT_SCSI3) { buffer[2] = 5; buffer[20] = '3'; } } if (mode & BIT_SCSI3) { length = std::min(length, 96u); buffer[4] = 91; if (length > 56) { memset(buffer + 56, 0, 40); buffer[58] = 0x03; buffer[60] = 0x01; buffer[61] = 0x80; } } else { length = std::min(length, 56u); } if (length > 36) { string filename = FileOperations::getFilename( getImageName().getOriginal()).str(); filename.resize(20, ' '); memcpy(buffer + 36, filename.data(), 20); } return length; } unsigned SCSIHD::modeSense() { byte* pBuffer = buffer; if ((currentLength > 0) && (cdb[2] == 3)) { // TODO check for too many sectors unsigned total = unsigned(getNbSectors()); byte media = MT_UNKNOWN; byte sectors = 64; byte blockLength = SECTOR_SIZE >> 8; byte tracks = 8; byte size = 4 + 24; byte removable = 0x80; // == not removable memset(pBuffer + 2, 0, 34); if (total == 0) { media = MT_NO_DISK; } // Mode Parameter Header 4bytes pBuffer[1] = media; // Medium Type pBuffer[3] = 8; // block descripter length pBuffer += 4; // Disable Block Descriptor check if (!(cdb[1] & 0x08)) { // Block Descriptor 8bytes pBuffer[1] = (total >> 16) & 0xff; // 1..3 Number of Blocks pBuffer[2] = (total >> 8) & 0xff; pBuffer[3] = (total >> 0) & 0xff; pBuffer[6] = blockLength & 0xff; // 5..7 Block Length in Bytes pBuffer += 8; size += 8; } // Format Device Page 24bytes pBuffer[ 0] = 3; // 0 Page pBuffer[ 1] = 0x16; // 1 Page Length pBuffer[ 3] = tracks; // 2, 3 Tracks per Zone pBuffer[11] = sectors; // 10,11 Sectors per Track pBuffer[12] = blockLength; // 12,13 Data Bytes per Physical Sector pBuffer[20] = removable; // 20 bit7 Soft Sector bit5 Removable buffer[0] = size - 1; // sense data length return std::min(currentLength, size); } keycode = SCSI::SENSE_INVALID_COMMAND_CODE; return 0; } unsigned SCSIHD::requestSense() { unsigned length = currentLength; unsigned tmpKeycode = unitAttention ? SCSI::SENSE_POWER_ON : keycode; unitAttention = false; keycode = SCSI::SENSE_NO_SENSE; memset(buffer + 1, 0, 17); if (length == 0) { if (mode & BIT_SCSI2) { return 0; } buffer[ 0] = (tmpKeycode >> 8) & 0xff; // Sense code length = 4; } else { buffer[ 0] = 0x70; buffer[ 2] = (tmpKeycode >> 16) & 0xff; // Sense key buffer[ 7] = 10; // Additional sense length buffer[12] = (tmpKeycode >> 8) & 0xff; // Additional sense code buffer[13] = (tmpKeycode >> 0) & 0xff; // Additional sense code qualifier length = std::min(length, 18u); } return length; } bool SCSIHD::checkReadOnly() { if (isWriteProtected()) { keycode = SCSI::SENSE_WRITE_PROTECT; return true; } return false; } unsigned SCSIHD::readCapacity() { // TODO check for overflow unsigned block = unsigned(getNbSectors()); if (block == 0) { // drive not ready keycode = SCSI::SENSE_MEDIUM_NOT_PRESENT; return 0; } --block; Endian::writeB32(&buffer[0], block); Endian::writeB32(&buffer[4], SECTOR_SIZE); // TODO is this a 32 bit field or 2x16-bit fields where the first field happens to have the value 0? return 8; } bool SCSIHD::checkAddress() { unsigned total = unsigned(getNbSectors()); if (total == 0) { // drive not ready keycode = SCSI::SENSE_MEDIUM_NOT_PRESENT; return false; } if ((currentLength > 0) && (currentSector + currentLength <= total)) { return true; } keycode = SCSI::SENSE_ILLEGAL_BLOCK_ADDRESS; return false; } // Execute scsiDeviceCheckAddress previously. unsigned SCSIHD::readSectors(unsigned& blocks) { getMotherBoard().getLedStatus().setLed(LedStatus::FDD, true); unsigned numSectors = std::min(currentLength, BUFFER_BLOCK_SIZE); unsigned counter = currentLength * SECTOR_SIZE; try { for (unsigned i = 0; i < numSectors; ++i) { auto* sbuf = aligned_cast(buffer); readSector(currentSector, sbuf[i]); ++currentSector; --currentLength; } blocks = currentLength; return counter; } catch (MSXException&) { blocks = 0; keycode = SCSI::SENSE_UNRECOVERED_READ_ERROR; return 0; } } unsigned SCSIHD::dataIn(unsigned& blocks) { if (cdb[0] == SCSI::OP_READ10) { unsigned counter = readSectors(blocks); if (counter) return counter; } // error blocks = 0; return 0; } // Execute scsiDeviceCheckAddress and scsiDeviceCheckReadOnly previously. unsigned SCSIHD::writeSectors(unsigned& blocks) { getMotherBoard().getLedStatus().setLed(LedStatus::FDD, true); unsigned numSectors = std::min(currentLength, BUFFER_BLOCK_SIZE); try { for (unsigned i = 0; i < numSectors; ++i) { auto* sbuf = aligned_cast(buffer); writeSector(currentSector, sbuf[i]); ++currentSector; --currentLength; } unsigned tmp = std::min(currentLength, BUFFER_BLOCK_SIZE); blocks = currentLength - tmp; unsigned counter = tmp * SECTOR_SIZE; return counter; } catch (MSXException&) { keycode = SCSI::SENSE_WRITE_FAULT; blocks = 0; return 0; } } unsigned SCSIHD::dataOut(unsigned& blocks) { if (cdb[0] == SCSI::OP_WRITE10) { return writeSectors(blocks); } // error blocks = 0; return 0; } // MBR erase only void SCSIHD::formatUnit() { if (!checkReadOnly()) { auto& sbuf = *aligned_cast(buffer); memset(&sbuf, 0, sizeof(sbuf)); try { writeSector(0, sbuf); unitAttention = true; } catch (MSXException&) { keycode = SCSI::SENSE_WRITE_FAULT; } } } byte SCSIHD::getStatusCode() { return keycode ? SCSI::ST_CHECK_CONDITION : SCSI::ST_GOOD; } unsigned SCSIHD::executeCmd(const byte* cdb_, SCSI::Phase& phase, unsigned& blocks) { memcpy(cdb, cdb_, sizeof(cdb)); message = 0; phase = SCSI::STATUS; blocks = 0; // check unit attention if (unitAttention && (mode & MODE_UNITATTENTION) && (cdb[0] != SCSI::OP_INQUIRY) && (cdb[0] != SCSI::OP_REQUEST_SENSE)) { unitAttention = false; keycode = SCSI::SENSE_POWER_ON; if (cdb[0] == SCSI::OP_TEST_UNIT_READY) { // changed = false; } // Unit Attention. This command is not executed. return 0; } // check LUN if (((cdb[1] & 0xe0) || lun) && (cdb[0] != SCSI::OP_REQUEST_SENSE) && !(cdb[0] == SCSI::OP_INQUIRY && !(mode & MODE_NOVAXIS))) { keycode = SCSI::SENSE_INVALID_LUN; // check LUN error return 0; } if (cdb[0] != SCSI::OP_REQUEST_SENSE) { keycode = SCSI::SENSE_NO_SENSE; } if (cdb[0] < SCSI::OP_GROUP1) { currentSector = ((cdb[1] & 0x1f) << 16) | (cdb[2] << 8) | cdb[3]; currentLength = cdb[4]; switch (cdb[0]) { case SCSI::OP_TEST_UNIT_READY: return 0; case SCSI::OP_INQUIRY: { unsigned counter = inquiry(); if (counter) { phase = SCSI::DATA_IN; } return counter; } case SCSI::OP_REQUEST_SENSE: { unsigned counter = requestSense(); if (counter) { phase = SCSI::DATA_IN; } return counter; } case SCSI::OP_READ6: if (currentLength == 0) { currentLength = SECTOR_SIZE / 2; } if (checkAddress()) { unsigned counter = readSectors(blocks); if (counter) { cdb[0] = SCSI::OP_READ10; phase = SCSI::DATA_IN; return counter; } } return 0; case SCSI::OP_WRITE6: if (currentLength == 0) { currentLength = SECTOR_SIZE / 2; } if (checkAddress() && !checkReadOnly()) { getMotherBoard().getLedStatus().setLed(LedStatus::FDD, true); unsigned tmp = std::min(currentLength, BUFFER_BLOCK_SIZE); blocks = currentLength - tmp; unsigned counter = tmp * SECTOR_SIZE; cdb[0] = SCSI::OP_WRITE10; phase = SCSI::DATA_OUT; return counter; } return 0; case SCSI::OP_SEEK6: getMotherBoard().getLedStatus().setLed(LedStatus::FDD, true); currentLength = 1; checkAddress(); return 0; case SCSI::OP_MODE_SENSE: { unsigned counter = modeSense(); if (counter) { phase = SCSI::DATA_IN; } return counter; } case SCSI::OP_FORMAT_UNIT: formatUnit(); return 0; case SCSI::OP_START_STOP_UNIT: // Not supported for this device return 0; case SCSI::OP_REZERO_UNIT: case SCSI::OP_REASSIGN_BLOCKS: case SCSI::OP_RESERVE_UNIT: case SCSI::OP_RELEASE_UNIT: case SCSI::OP_SEND_DIAGNOSTIC: // SCSI_Group0 dummy return 0; } } else { currentSector = Endian::read_UA_B32(&cdb[2]); currentLength = Endian::read_UA_B16(&cdb[7]); switch (cdb[0]) { case SCSI::OP_READ10: if (checkAddress()) { unsigned counter = readSectors(blocks); if (counter) { phase = SCSI::DATA_IN; return counter; } } return 0; case SCSI::OP_WRITE10: if (checkAddress() && !checkReadOnly()) { unsigned tmp = std::min(currentLength, BUFFER_BLOCK_SIZE); blocks = currentLength - tmp; unsigned counter = tmp * SECTOR_SIZE; phase = SCSI::DATA_OUT; return counter; } return 0; case SCSI::OP_READ_CAPACITY: { unsigned counter = readCapacity(); if (counter) { phase = SCSI::DATA_IN; } return counter; } case SCSI::OP_SEEK10: getMotherBoard().getLedStatus().setLed(LedStatus::FDD, true); currentLength = 1; checkAddress(); return 0; } } // unsupported command keycode = SCSI::SENSE_INVALID_COMMAND_CODE; return 0; } unsigned SCSIHD::executingCmd(SCSI::Phase& phase, unsigned& blocks) { phase = SCSI::EXECUTE; blocks = 0; return 0; // Always for non-CD-ROM it seems } byte SCSIHD::msgIn() { byte result = message; message = 0; return result; } /* scsiDeviceMsgOut() Notes: [out] -1: Busfree demand. (Please process it in the call origin.) bit2: Status phase demand. Error happend. bit1: Make it to a busfree if ATN has not been released. bit0: There is a message(MsgIn). */ int SCSIHD::msgOut(byte value) { if (value & 0x80) { lun = value & 7; return 0; } switch (value) { case SCSI::MSG_INITIATOR_DETECT_ERROR: keycode = SCSI::SENSE_INITIATOR_DETECTED_ERR; return 6; case SCSI::MSG_BUS_DEVICE_RESET: busReset(); // fall-through case SCSI::MSG_ABORT: return -1; case SCSI::MSG_REJECT: case SCSI::MSG_PARITY_ERROR: case SCSI::MSG_NO_OPERATION: return 2; } message = SCSI::MSG_REJECT; return ((value >= 0x04) && (value <= 0x11)) ? 3 : 1; } template void SCSIHD::serialize(Archive& ar, unsigned /*version*/) { // don't serialize SCSIDevice, SectorAccessibleDisk, DiskContainer // base classes ar.template serializeBase(*this); ar.serialize("keycode", keycode); ar.serialize("currentSector", currentSector); ar.serialize("currentLength", currentLength); ar.serialize("unitAttention", unitAttention); ar.serialize("message", message); ar.serialize("lun", lun); ar.serialize_blob("cdb", cdb, sizeof(cdb)); } INSTANTIATE_SERIALIZE_METHODS(SCSIHD); REGISTER_POLYMORPHIC_INITIALIZER(SCSIDevice, SCSIHD, "SCSIHD"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/ide/SCSIHD.hh000066400000000000000000000033061257557151200172670ustar00rootroot00000000000000/* Ported from: ** Source: /cvsroot/bluemsx/blueMSX/Src/IoDevice/ScsiDevice.h,v ** Revision: 1.6 ** Date: 2007-05-22 20:05:38 +0200 (Tue, 22 May 2007) ** ** More info: http://www.bluemsx.com ** ** Copyright (C) 2003-2007 Daniel Vik, white cat */ #ifndef SCSIHD_HH #define SCSIHD_HH #include "HD.hh" #include "SCSIDevice.hh" #include "noncopyable.hh" namespace openmsx { class DeviceConfig; class SCSIHD final : public HD, public SCSIDevice, private noncopyable { public: SCSIHD(const DeviceConfig& targetconfig, AlignedBuffer& buf, unsigned mode); template void serialize(Archive& ar, unsigned version); private: // SCSI Device void reset() override; bool isSelected() override; unsigned executeCmd(const byte* cdb, SCSI::Phase& phase, unsigned& blocks) override; unsigned executingCmd(SCSI::Phase& phase, unsigned& blocks) override; byte getStatusCode() override; int msgOut(byte value) override; byte msgIn() override; void disconnect() override; void busReset() override; unsigned dataIn(unsigned& blocks) override; unsigned dataOut(unsigned& blocks) override; unsigned inquiry(); unsigned modeSense(); unsigned requestSense(); bool checkReadOnly(); unsigned readCapacity(); bool checkAddress(); unsigned readSectors(unsigned& blocks); unsigned writeSectors(unsigned& blocks); void formatUnit(); AlignedBuffer& buffer; const unsigned mode; unsigned keycode; // Sense key, ASC, ASCQ unsigned currentSector; unsigned currentLength; const byte scsiId; // SCSI ID 0..7 bool unitAttention; // Unit Attention (was: reset) byte message; byte lun; byte cdb[12]; // Command Descriptor Block }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/SCSILS120.cc000066400000000000000000000500341257557151200175230ustar00rootroot00000000000000/* Ported from: ** Source: /cvsroot/bluemsx/blueMSX/Src/IoDevice/ScsiDevice.c,v ** Revision: 1.10 ** Date: 2007-05-21 21:38:29 +0200 (Mon, 21 May 2007) ** ** More info: http://www.bluemsx.com ** ** Copyright (C) 2003-2007 Daniel Vik, white cat */ /* * Notes: * It follows the SCSI1(CCS) standard or the SCSI2 standard. * Only the direct access device is supported now. * Message system might be imperfect. * * NOTE: this version supports a removable LS-120 disk, as the class * name suggests. Refer to revision 6526 of SCSIHD.cc to see what was removed * from the generic/parameterised code. */ #include "SCSILS120.hh" #include "FileOperations.hh" #include "FileException.hh" #include "FilePool.hh" #include "LedStatus.hh" #include "MSXMotherBoard.hh" #include "Reactor.hh" #include "DeviceConfig.hh" #include "RecordedCommand.hh" #include "CliComm.hh" #include "TclObject.hh" #include "CommandException.hh" #include "FileContext.hh" #include "endian.hh" #include "serialize.hh" #include "memory.hh" #include #include #include using std::string; using std::vector; namespace openmsx { // Medium type (value like LS-120) static const byte MT_UNKNOWN = 0x00; static const byte MT_2DD_UN = 0x10; static const byte MT_2DD = 0x11; static const byte MT_2HD_UN = 0x20; static const byte MT_2HD_12_98 = 0x22; static const byte MT_2HD_12 = 0x23; static const byte MT_2HD_144 = 0x24; static const byte MT_LS120 = 0x31; static const byte MT_NO_DISK = 0x70; static const byte MT_DOOR_OPEN = 0x71; static const byte MT_FMT_ERROR = 0x72; static const byte inqdata[36] = { 0, // bit5-0 device type code. 0, // bit7 = 1 removable device 2, // bit7,6 ISO version. bit5,4,3 ECMA version. // bit2,1,0 ANSI Version (001=SCSI1, 010=SCSI2) 2, // bit7 AENC. bit6 TrmIOP. // bit3-0 Response Data Format. (0000=SCSI1, 0001=CCS, 0010=SCSI2) 51, // addtional length 0, 0,// reserved 0, // bit7 RelAdr, bit6 WBus32, bit5 Wbus16, bit4 Sync, bit3 Linked, // bit2 reseved bit1 CmdQue, bit0 SftRe 'o', 'p', 'e', 'n', 'M', 'S', 'X', ' ', // vendor ID (8bytes) 'S', 'C', 'S', 'I', '2', ' ', 'L', 'S', // product ID (16bytes) '-', '1', '2', '0', 'd', 'i', 's', 'k', '0', '1', '0', 'a' // product version (ASCII 4bytes) }; // for FDSFORM.COM static const char fds120[28 + 1] = "IODATA LS-120 COSM 0001"; static const unsigned BUFFER_BLOCK_SIZE = SCSIDevice::BUFFER_SIZE / SectorAccessibleDisk::SECTOR_SIZE; class LSXCommand final : public RecordedCommand { public: LSXCommand(CommandController& commandController, StateChangeDistributor& stateChangeDistributor, Scheduler& scheduler, SCSILS120& ls); void execute(array_ref tokens, TclObject& result, EmuTime::param time) override; string help(const vector& tokens) const override; void tabCompletion(vector& tokens) const override; private: SCSILS120& ls; }; SCSILS120::SCSILS120(const DeviceConfig& targetconfig, AlignedBuffer& buf, unsigned mode_) : motherBoard(targetconfig.getMotherBoard()) , buffer(buf) , name("lsX") , mode(mode_) , scsiId(targetconfig.getAttributeAsInt("id")) { lsInUse = motherBoard.getSharedStuff("lsInUse"); unsigned id = 0; while ((*lsInUse)[id]) { ++id; if (id == MAX_LS) { throw MSXException("Too many LSs"); } } name[2] = char('a' + id); (*lsInUse)[id] = true; lsxCommand = make_unique( motherBoard.getCommandController(), motherBoard.getStateChangeDistributor(), motherBoard.getScheduler(), *this); reset(); } SCSILS120::~SCSILS120() { motherBoard.getMSXCliComm().update(CliComm::HARDWARE, name, "remove"); unsigned id = name[2] - 'a'; assert((*lsInUse)[id]); (*lsInUse)[id] = false; } void SCSILS120::reset() { mediaChanged = false; currentSector = 0; currentLength = 0; busReset(); } void SCSILS120::busReset() { keycode = 0; unitAttention = (mode & MODE_UNITATTENTION) != 0; } void SCSILS120::disconnect() { motherBoard.getLedStatus().setLed(LedStatus::FDD, false); } // Check the initiator in the call origin. bool SCSILS120::isSelected() { lun = 0; return file.is_open(); } bool SCSILS120::getReady() { if (file.is_open()) return true; keycode = SCSI::SENSE_MEDIUM_NOT_PRESENT; return false; } void SCSILS120::testUnitReady() { if ((mode & MODE_NOVAXIS) == 0) { if (getReady() && mediaChanged && (mode & MODE_MEGASCSI)) { // Disk change is surely sent for the driver of MEGA-SCSI. keycode = SCSI::SENSE_POWER_ON; } } mediaChanged = false; } void SCSILS120::startStopUnit() { switch (cdb[4]) { case 2: // Eject eject(); break; case 3: // Insert TODO: how can this happen? //if (!diskPresent(diskId)) { // *disk = disk; // updateExtendedDiskName(diskId, disk->fileName, disk->fileNameInZip); // boardChangeDiskette(diskId, disk->fileName, disk->fileNameInZip); //} break; } } unsigned SCSILS120::inquiry() { auto total = getNbSectors(); unsigned length = currentLength; bool fdsmode = (total > 0) && (total <= 2880); if (length == 0) return 0; if (fdsmode) { memcpy(buffer + 2, inqdata + 2, 6); memcpy(buffer + 8, fds120, 28); } else { memcpy(buffer + 2, inqdata + 2, 34); } buffer[0] = SCSI::DT_DirectAccess; buffer[1] = 0x80; // removable if (!(mode & BIT_SCSI2)) { buffer[2] = 1; buffer[3] = 1; if (!fdsmode) buffer[20] = '1'; } else { if (mode & BIT_SCSI3) { buffer[2] = 5; if (!fdsmode) buffer[20] = '3'; } } if (mode & BIT_SCSI3) { length = std::min(length, 96u); buffer[4] = 91; if (length > 56) { memset(buffer + 56, 0, 40); buffer[58] = 0x03; buffer[60] = 0x01; buffer[61] = 0x80; } } else { length = std::min(length, 56u); } if (length > 36) { string filename = FileOperations::getFilename(file.getURL()).str(); filename.resize(20, ' '); memcpy(buffer + 36, filename.data(), 20); } return length; } unsigned SCSILS120::modeSense() { byte* pBuffer = buffer; if ((currentLength > 0) && (cdb[2] == 3)) { auto total = getNbSectors(); byte media = MT_UNKNOWN; byte sectors = 64; byte blockLength = SECTOR_SIZE >> 8; byte tracks = 8; byte size = 4 + 24; byte removable = 0xa0; memset(pBuffer + 2, 0, 34); if (total == 0) { media = MT_NO_DISK; } else { if (total == 1440) { media = MT_2DD; sectors = 9; blockLength = 2048 >> 8; // FDS-120 value tracks = 160; } else { if (total == 2880) { media = MT_2HD_144; sectors = 18; blockLength = 2048 >> 8; tracks = 160; } } } // Mode Parameter Header 4bytes pBuffer[1] = media; // Medium Type pBuffer[3] = 8; // block descripter length pBuffer += 4; // Disable Block Descriptor check if (!(cdb[1] & 0x08)) { // Block Descriptor 8bytes pBuffer[1] = (total >> 16) & 0xff; // 1..3 Number of Blocks pBuffer[2] = (total >> 8) & 0xff; pBuffer[3] = (total >> 0) & 0xff; pBuffer[6] = blockLength & 0xff; // 5..7 Block Length in Bytes pBuffer += 8; size += 8; } // Format Device Page 24bytes pBuffer[ 0] = 3; // 0 Page pBuffer[ 1] = 0x16; // 1 Page Length pBuffer[ 3] = tracks; // 2, 3 Tracks per Zone pBuffer[11] = sectors; // 10,11 Sectors per Track pBuffer[12] = blockLength; // 12,13 Data Bytes per Physical Sector pBuffer[20] = removable; // 20 bit7 Soft Sector bit5 Removable buffer[0] = size - 1; // sense data length return std::min(currentLength, size); } keycode = SCSI::SENSE_INVALID_COMMAND_CODE; return 0; } unsigned SCSILS120::requestSense() { unsigned length = currentLength; unsigned tmpKeycode = unitAttention ? SCSI::SENSE_POWER_ON : keycode; unitAttention = false; keycode = SCSI::SENSE_NO_SENSE; memset(buffer + 1, 0, 17); if (length == 0) { if (mode & BIT_SCSI2) { return 0; } buffer[ 0] = (tmpKeycode >> 8) & 0xff; // Sense code length = 4; } else { buffer[ 0] = 0x70; buffer[ 2] = (tmpKeycode >> 16) & 0xff; // Sense key buffer[ 7] = 10; // Additional sense length buffer[12] = (tmpKeycode >> 8) & 0xff; // Additional sense code buffer[13] = (tmpKeycode >> 0) & 0xff; // Additional sense code qualifier length = std::min(length, 18u); } return length; } bool SCSILS120::checkReadOnly() { if (file.isReadOnly()) { keycode = SCSI::SENSE_WRITE_PROTECT; return true; } return false; } unsigned SCSILS120::readCapacity() { unsigned block = unsigned(getNbSectors()); if (block == 0) { // drive not ready keycode = SCSI::SENSE_MEDIUM_NOT_PRESENT; return 0; } --block; Endian::writeB32(&buffer[0], block); Endian::writeB32(&buffer[4], SECTOR_SIZE); // TODO see SCSIHD return 8; } bool SCSILS120::checkAddress() { unsigned total = unsigned(getNbSectors()); if (total == 0) { // drive not ready keycode = SCSI::SENSE_MEDIUM_NOT_PRESENT; return false; } if ((currentLength > 0) && (currentSector + currentLength <= total)) { return true; } keycode = SCSI::SENSE_ILLEGAL_BLOCK_ADDRESS; return false; } // Execute scsiDeviceCheckAddress previously. unsigned SCSILS120::readSector(unsigned& blocks) { motherBoard.getLedStatus().setLed(LedStatus::FDD, true); unsigned numSectors = std::min(currentLength, BUFFER_BLOCK_SIZE); unsigned counter = currentLength * SECTOR_SIZE; try { // TODO: somehow map this to SectorAccessibleDisk::readSector? file.seek(SECTOR_SIZE * currentSector); file.read(buffer, SECTOR_SIZE * numSectors); currentSector += numSectors; currentLength -= numSectors; blocks = currentLength; return counter; } catch (FileException&) { blocks = 0; keycode = SCSI::SENSE_UNRECOVERED_READ_ERROR; return 0; } } unsigned SCSILS120::dataIn(unsigned& blocks) { if (cdb[0] == SCSI::OP_READ10) { unsigned counter = readSector(blocks); if (counter) { return counter; } } // error blocks = 0; return 0; } // Execute scsiDeviceCheckAddress and scsiDeviceCheckReadOnly previously. unsigned SCSILS120::writeSector(unsigned& blocks) { motherBoard.getLedStatus().setLed(LedStatus::FDD, true); unsigned numSectors = std::min(currentLength, BUFFER_BLOCK_SIZE); // TODO: somehow map this to SectorAccessibleDisk::writeSector? try { file.seek(SECTOR_SIZE * currentSector); file.write(buffer, SECTOR_SIZE * numSectors); currentSector += numSectors; currentLength -= numSectors; unsigned tmp = std::min(currentLength, BUFFER_BLOCK_SIZE); blocks = currentLength - tmp; unsigned counter = tmp * SECTOR_SIZE; return counter; } catch (FileException&) { keycode = SCSI::SENSE_WRITE_FAULT; blocks = 0; return 0; } } unsigned SCSILS120::dataOut(unsigned& blocks) { if (cdb[0] == SCSI::OP_WRITE10) { return writeSector(blocks); } // error blocks = 0; return 0; } // MBR erase only void SCSILS120::formatUnit() { if (getReady() && !checkReadOnly()) { memset(buffer, 0, SECTOR_SIZE); try { file.seek(0); file.write(buffer, SECTOR_SIZE); unitAttention = true; mediaChanged = true; } catch (FileException&) { keycode = SCSI::SENSE_WRITE_FAULT; } } } byte SCSILS120::getStatusCode() { return keycode ? SCSI::ST_CHECK_CONDITION : SCSI::ST_GOOD; } void SCSILS120::eject() { file.close(); mediaChanged = true; if (mode & MODE_UNITATTENTION) { unitAttention = true; } motherBoard.getMSXCliComm().update(CliComm::MEDIA, name, ""); } void SCSILS120::insert(string_ref filename) { file = File(filename); mediaChanged = true; if (mode & MODE_UNITATTENTION) { unitAttention = true; } motherBoard.getMSXCliComm().update(CliComm::MEDIA, name, filename); } unsigned SCSILS120::executeCmd(const byte* cdb_, SCSI::Phase& phase, unsigned& blocks) { memcpy(cdb, cdb_, sizeof(cdb)); message = 0; phase = SCSI::STATUS; blocks = 0; // check unit attention if (unitAttention && (mode & MODE_UNITATTENTION) && (cdb[0] != SCSI::OP_INQUIRY) && (cdb[0] != SCSI::OP_REQUEST_SENSE)) { unitAttention = false; keycode = SCSI::SENSE_POWER_ON; if (cdb[0] == SCSI::OP_TEST_UNIT_READY) { mediaChanged = false; } // Unit Attention. This command is not executed. return 0; } // check LUN if (((cdb[1] & 0xe0) || lun) && (cdb[0] != SCSI::OP_REQUEST_SENSE) && !(cdb[0] == SCSI::OP_INQUIRY && !(mode & MODE_NOVAXIS))) { keycode = SCSI::SENSE_INVALID_LUN; // check LUN error return 0; } if (cdb[0] != SCSI::OP_REQUEST_SENSE) { keycode = SCSI::SENSE_NO_SENSE; } if (cdb[0] < SCSI::OP_GROUP1) { currentSector = ((cdb[1] & 0x1f) << 16) | (cdb[2] << 8) | cdb[3]; currentLength = cdb[4]; switch (cdb[0]) { case SCSI::OP_TEST_UNIT_READY: testUnitReady(); return 0; case SCSI::OP_INQUIRY: { unsigned counter = inquiry(); if (counter) { phase = SCSI::DATA_IN; } return counter; } case SCSI::OP_REQUEST_SENSE: { unsigned counter = requestSense(); if (counter) { phase = SCSI::DATA_IN; } return counter; } case SCSI::OP_READ6: if (currentLength == 0) { currentLength = SECTOR_SIZE >> 1; } if (checkAddress()) { unsigned counter = readSector(blocks); if (counter) { cdb[0] = SCSI::OP_READ10; phase = SCSI::DATA_IN; return counter; } } return 0; case SCSI::OP_WRITE6: if (currentLength == 0) { currentLength = SECTOR_SIZE >> 1; } if (checkAddress() && !checkReadOnly()) { motherBoard.getLedStatus().setLed(LedStatus::FDD, true); unsigned tmp = std::min(currentLength, BUFFER_BLOCK_SIZE); blocks = currentLength - tmp; unsigned counter = tmp * SECTOR_SIZE; cdb[0] = SCSI::OP_WRITE10; phase = SCSI::DATA_OUT; return counter; } return 0; case SCSI::OP_SEEK6: motherBoard.getLedStatus().setLed(LedStatus::FDD, true); currentLength = 1; checkAddress(); return 0; case SCSI::OP_MODE_SENSE: { unsigned counter = modeSense(); if (counter) { phase = SCSI::DATA_IN; } return counter; } case SCSI::OP_FORMAT_UNIT: formatUnit(); return 0; case SCSI::OP_START_STOP_UNIT: startStopUnit(); return 0; case SCSI::OP_REZERO_UNIT: case SCSI::OP_REASSIGN_BLOCKS: case SCSI::OP_RESERVE_UNIT: case SCSI::OP_RELEASE_UNIT: case SCSI::OP_SEND_DIAGNOSTIC: // SCSI_Group0 dummy return 0; } } else { currentSector = Endian::read_UA_B32(&cdb[2]); currentLength = Endian::read_UA_B16(&cdb[7]); switch (cdb[0]) { case SCSI::OP_READ10: if (checkAddress()) { unsigned counter = readSector(blocks); if (counter) { phase = SCSI::DATA_IN; return counter; } } return 0; case SCSI::OP_WRITE10: if (checkAddress() && !checkReadOnly()) { unsigned tmp = std::min(currentLength, BUFFER_BLOCK_SIZE); blocks = currentLength - tmp; unsigned counter = tmp * SECTOR_SIZE; phase = SCSI::DATA_OUT; return counter; } return 0; case SCSI::OP_READ_CAPACITY: { unsigned counter = readCapacity(); if (counter) { phase = SCSI::DATA_IN; } return counter; } case SCSI::OP_SEEK10: motherBoard.getLedStatus().setLed(LedStatus::FDD, true); currentLength = 1; checkAddress(); return 0; } } // unsupported command keycode = SCSI::SENSE_INVALID_COMMAND_CODE; return 0; } unsigned SCSILS120::executingCmd(SCSI::Phase& phase, unsigned& blocks) { phase = SCSI::EXECUTE; blocks = 0; return 0; // we're very fast } byte SCSILS120::msgIn() { byte result = message; message = 0; return result; } /* scsiDeviceMsgOut() Notes: [out] -1: Busfree demand. (Please process it in the call origin.) bit2: Status phase demand. Error happend. bit1: Make it to a busfree if ATN has not been released. bit0: There is a message(MsgIn). */ int SCSILS120::msgOut(byte value) { if (value & 0x80) { lun = value & 7; return 0; } switch (value) { case SCSI::MSG_INITIATOR_DETECT_ERROR: keycode = SCSI::SENSE_INITIATOR_DETECTED_ERR; return 6; case SCSI::MSG_BUS_DEVICE_RESET: busReset(); // fall-through case SCSI::MSG_ABORT: return -1; case SCSI::MSG_REJECT: case SCSI::MSG_PARITY_ERROR: case SCSI::MSG_NO_OPERATION: return 2; } message = SCSI::MSG_REJECT; return ((value >= 0x04) && (value <= 0x11)) ? 3 : 1; } size_t SCSILS120::getNbSectorsImpl() const { return file.is_open() ? (const_cast(file).getSize() / SECTOR_SIZE) : 0; } bool SCSILS120::isWriteProtectedImpl() const { return false; } Sha1Sum SCSILS120::getSha1SumImpl(FilePool& filePool) { if (hasPatches()) { return SectorAccessibleDisk::getSha1SumImpl(filePool); } return filePool.getSha1Sum(file); } void SCSILS120::readSectorImpl(size_t sector, SectorBuffer& buf) { file.seek(sizeof(buf) * sector); file.read(&buf, sizeof(buf)); } void SCSILS120::writeSectorImpl(size_t sector, const SectorBuffer& buf) { file.seek(sizeof(buf) * sector); file.write(&buf, sizeof(buf)); } SectorAccessibleDisk* SCSILS120::getSectorAccessibleDisk() { return this; } const std::string& SCSILS120::getContainerName() const { return name; } bool SCSILS120::diskChanged() { return mediaChanged; // TODO not reset on read } int SCSILS120::insertDisk(string_ref filename) { try { insert(filename); return 0; } catch (MSXException&) { return -1; } } // class LSXCommand LSXCommand::LSXCommand(CommandController& commandController, StateChangeDistributor& stateChangeDistributor, Scheduler& scheduler, SCSILS120& ls_) : RecordedCommand(commandController, stateChangeDistributor, scheduler, ls_.name) , ls(ls_) { } void LSXCommand::execute(array_ref tokens, TclObject& result, EmuTime::param /*time*/) { if (tokens.size() == 1) { auto& file = ls.file; result.addListElement(ls.name + ':'); result.addListElement(file.is_open() ? file.getURL() : ""); if (!file.is_open()) result.addListElement("empty"); } else if ((tokens.size() == 2) && ((tokens[1] == "eject") || (tokens[1] == "-eject"))) { ls.eject(); // TODO check for locked tray if (tokens[1] == "-eject") { result.setString( "Warning: use of '-eject' is deprecated, " "instead use the 'eject' subcommand"); } } else if ((tokens.size() == 2) || ((tokens.size() == 3) && (tokens[1] == "insert"))) { int fileToken = 1; if (tokens[1] == "insert") { if (tokens.size() > 2) { fileToken = 2; } else { throw CommandException( "Missing argument to insert subcommand"); } } try { string filename = userFileContext().resolve( tokens[fileToken].getString().str()); ls.insert(filename); // return filename; // Note: the diskX command doesn't do this either, so this has not been converted to TclObject style here } catch (FileException& e) { throw CommandException("Can't change disk image: " + e.getMessage()); } } else { throw CommandException("Too many or wrong arguments."); } } string LSXCommand::help(const vector& /*tokens*/) const { return ls.name + " : display the disk image for this LS-120 drive\n" + ls.name + " eject : eject the disk image from this LS-120 drive\n" + ls.name + " insert : change the disk image for this LS-120 drive\n" + ls.name + " : change the disk image for this LS-120 drive\n"; } void LSXCommand::tabCompletion(vector& tokens) const { static const char* const extra[] = { "eject", "insert" }; completeFileName(tokens, userFileContext(), extra); } template void SCSILS120::serialize(Archive& ar, unsigned /*version*/) { string filename = file.is_open() ? file.getURL() : ""; ar.serialize("filename", filename); if (ar.isLoader()) { // re-insert disk before restoring 'mediaChanged' if (filename.empty()) { eject(); } else { insert(filename); } } ar.serialize("keycode", keycode); ar.serialize("currentSector", currentSector); ar.serialize("currentLength", currentLength); ar.serialize("unitAttention", unitAttention); ar.serialize("mediaChanged", mediaChanged); ar.serialize("message", message); ar.serialize("lun", lun); ar.serialize_blob("cdb", cdb, sizeof(cdb)); } INSTANTIATE_SERIALIZE_METHODS(SCSILS120); REGISTER_POLYMORPHIC_INITIALIZER(SCSIDevice, SCSILS120, "SCSILS120"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/ide/SCSILS120.hh000066400000000000000000000055031257557151200175360ustar00rootroot00000000000000/* Ported from: ** Source: /cvsroot/bluemsx/blueMSX/Src/IoDevice/ScsiDevice.h,v ** Revision: 1.6 ** Date: 2007-05-22 20:05:38 +0200 (Tue, 22 May 2007) ** ** More info: http://www.bluemsx.com ** ** Copyright (C) 2003-2007 Daniel Vik, white cat */ #ifndef SCSILS120_HH #define SCSILS120_HH #include "SCSIDevice.hh" #include "SectorAccessibleDisk.hh" #include "DiskContainer.hh" #include "File.hh" #include "noncopyable.hh" #include #include namespace openmsx { class DeviceConfig; class MSXMotherBoard; class LSXCommand; class SCSILS120 final : public SCSIDevice, public SectorAccessibleDisk , public DiskContainer, private noncopyable { public: SCSILS120(const DeviceConfig& targetconfig, AlignedBuffer& buf, unsigned mode); ~SCSILS120(); template void serialize(Archive& ar, unsigned version); private: // SectorAccessibleDisk: void readSectorImpl (size_t sector, SectorBuffer& buf) override; void writeSectorImpl(size_t sector, const SectorBuffer& buf) override; size_t getNbSectorsImpl() const override; bool isWriteProtectedImpl() const override; Sha1Sum getSha1SumImpl(FilePool& filePool) override; // Diskcontainer: SectorAccessibleDisk* getSectorAccessibleDisk() override; const std::string& getContainerName() const override; bool diskChanged() override; int insertDisk(string_ref filename) override; // SCSI Device void reset() override; bool isSelected() override; unsigned executeCmd(const byte* cdb, SCSI::Phase& phase, unsigned& blocks) override; unsigned executingCmd(SCSI::Phase& phase, unsigned& blocks) override; byte getStatusCode() override; int msgOut(byte value) override; byte msgIn() override; void disconnect() override; void busReset() override; unsigned dataIn(unsigned& blocks) override; unsigned dataOut(unsigned& blocks) override; void eject(); void insert(string_ref filename); bool getReady(); void testUnitReady(); void startStopUnit(); unsigned inquiry(); unsigned modeSense(); unsigned requestSense(); bool checkReadOnly(); unsigned readCapacity(); bool checkAddress(); unsigned readSector(unsigned& blocks); unsigned writeSector(unsigned& blocks); void formatUnit(); MSXMotherBoard& motherBoard; AlignedBuffer& buffer; File file; std::unique_ptr lsxCommand; std::string name; const int mode; unsigned keycode; // Sense key, ASC, ASCQ unsigned currentSector; unsigned currentLength; const byte scsiId; // SCSI ID 0..7 bool unitAttention; // Unit Attention (was: reset) bool mediaChanged; // Enhanced change flag for MEGA-SCSI driver byte message; byte lun; byte cdb[12]; // Command Descriptor Block static const unsigned MAX_LS = 26; using LSInUse = std::bitset; std::shared_ptr lsInUse; friend class LSXCommand; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/SunriseIDE.cc000066400000000000000000000122141257557151200202500ustar00rootroot00000000000000#include "SunriseIDE.hh" #include "IDEDeviceFactory.hh" #include "IDEDevice.hh" #include "Math.hh" #include "serialize.hh" #include "memory.hh" namespace openmsx { SunriseIDE::SunriseIDE(const DeviceConfig& config) : MSXDevice(config) , rom(getName() + " ROM", "rom", config) , romBlockDebug(*this, &control, 0x4000, 0x4000, 14) { device[0] = IDEDeviceFactory::create( DeviceConfig(config, config.findChild("master"))); device[1] = IDEDeviceFactory::create( DeviceConfig(config, config.findChild("slave"))); // make valgrind happy internalBank = nullptr; ideRegsEnabled = false; powerUp(getCurrentTime()); } SunriseIDE::~SunriseIDE() { } void SunriseIDE::powerUp(EmuTime::param time) { writeControl(0xFF); reset(time); } void SunriseIDE::reset(EmuTime::param time) { selectedDevice = 0; softReset = false; device[0]->reset(time); device[1]->reset(time); } byte SunriseIDE::readMem(word address, EmuTime::param time) { if (ideRegsEnabled && ((address & 0x3E00) == 0x3C00)) { // 0x7C00 - 0x7DFF ide data register if ((address & 1) == 0) { return readDataLow(time); } else { return readDataHigh(time); } } if (ideRegsEnabled && ((address & 0x3F00) == 0x3E00)) { // 0x7E00 - 0x7EFF ide registers return readReg(address & 0xF, time); } if ((0x4000 <= address) && (address < 0x8000)) { // read normal (flash) rom return internalBank[address & 0x3FFF]; } // read nothing return 0xFF; } const byte* SunriseIDE::getReadCacheLine(word start) const { if (ideRegsEnabled && ((start & 0x3E00) == 0x3C00)) { return nullptr; } if (ideRegsEnabled && ((start & 0x3F00) == 0x3E00)) { return nullptr; } if ((0x4000 <= start) && (start < 0x8000)) { return &internalBank[start & 0x3FFF]; } return unmappedRead; } void SunriseIDE::writeMem(word address, byte value, EmuTime::param time) { if ((address & 0xBF04) == 0x0104) { // control register writeControl(value); return; } if (ideRegsEnabled && ((address & 0x3E00) == 0x3C00)) { // 0x7C00 - 0x7DFF ide data register if ((address & 1) == 0) { writeDataLow(value); } else { writeDataHigh(value, time); } return; } if (ideRegsEnabled && ((address & 0x3F00) == 0x3E00)) { // 0x7E00 - 0x7EFF ide registers writeReg(address & 0xF, value, time); return; } // all other writes ignored } void SunriseIDE::writeControl(byte value) { control = value; if (ideRegsEnabled != (control & 1)) { ideRegsEnabled = control & 1; invalidateMemCache(0x3C00, 0x0300); invalidateMemCache(0x7C00, 0x0300); invalidateMemCache(0xBC00, 0x0300); invalidateMemCache(0xFC00, 0x0300); } byte bank = Math::reverseByte(control & 0xF8); if (bank >= (rom.getSize() / 0x4000)) { bank &= ((rom.getSize() / 0x4000) - 1); } if (internalBank != &rom[0x4000 * bank]) { internalBank = &rom[0x4000 * bank]; invalidateMemCache(0x4000, 0x4000); } } byte SunriseIDE::readDataLow(EmuTime::param time) { word temp = readData(time); readLatch = temp >> 8; return temp & 0xFF; } byte SunriseIDE::readDataHigh(EmuTime::param /*time*/) { return readLatch; } word SunriseIDE::readData(EmuTime::param time) { word result = device[selectedDevice]->readData(time); return result; } byte SunriseIDE::readReg(nibble reg, EmuTime::param time) { byte result; if (reg == 14) { // alternate status register reg = 7; } if (softReset) { if (reg == 7) { // read status result = 0xFF; // BUSY } else { // all others result = 0x7F; // don't care } } else { if (reg == 0) { result = readData(time) & 0xFF; } else { result = device[selectedDevice]->readReg(reg, time); if (reg == 6) { result &= 0xEF; result |= selectedDevice ? 0x10 : 0x00; } } } return result; } void SunriseIDE::writeDataLow(byte value) { writeLatch = value; } void SunriseIDE::writeDataHigh(byte value, EmuTime::param time) { word temp = (value << 8) | writeLatch; writeData(temp, time); } void SunriseIDE::writeData(word value, EmuTime::param time) { device[selectedDevice]->writeData(value, time); } void SunriseIDE::writeReg(nibble reg, byte value, EmuTime::param time) { if (softReset) { if ((reg == 14) && !(value & 0x04)) { // clear SRST softReset = false; } // ignore all other writes } else { if (reg == 0) { writeData((value << 8) | value, time); } else { if ((reg == 14) && (value & 0x04)) { // set SRST softReset = true; device[0]->reset(time); device[1]->reset(time); } else { if (reg == 6) { selectedDevice = (value & 0x10) ? 1 : 0; } device[selectedDevice]->writeReg(reg, value, time); } } } } template void SunriseIDE::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serializePolymorphic("master", *device[0]); ar.serializePolymorphic("slave", *device[1]); ar.serialize("readLatch", readLatch); ar.serialize("writeLatch", writeLatch); ar.serialize("selectedDevice", selectedDevice); ar.serialize("control", control); ar.serialize("softReset", softReset); if (ar.isLoader()) { // restore internalBank, ideRegsEnabled writeControl(control); } } INSTANTIATE_SERIALIZE_METHODS(SunriseIDE); REGISTER_MSXDEVICE(SunriseIDE, "SunriseIDE"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/ide/SunriseIDE.hh000066400000000000000000000024441257557151200202660ustar00rootroot00000000000000#ifndef SUNRISEIDE_HH #define SUNRISEIDE_HH #include "MSXDevice.hh" #include "Rom.hh" #include "RomBlockDebuggable.hh" #include namespace openmsx { class IDEDevice; class SunriseIDE final : public MSXDevice { public: explicit SunriseIDE(const DeviceConfig& config); ~SunriseIDE(); void powerUp(EmuTime::param time) override; void reset(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; template void serialize(Archive& ar, unsigned version); private: void writeControl(byte value); byte readDataLow(EmuTime::param time); byte readDataHigh(EmuTime::param time); word readData(EmuTime::param time); byte readReg(nibble reg, EmuTime::param time); void writeDataLow(byte value); void writeDataHigh(byte value, EmuTime::param time); void writeData(word value, EmuTime::param time); void writeReg(nibble reg, byte value, EmuTime::param time); Rom rom; RomBlockDebuggable romBlockDebug; std::unique_ptr device[2]; const byte* internalBank; byte readLatch; byte writeLatch; byte selectedDevice; byte control; bool ideRegsEnabled; bool softReset; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/ide/WD33C93.cc000066400000000000000000000316331257557151200172430ustar00rootroot00000000000000/* Ported from: ** Source: /cvsroot/bluemsx/blueMSX/Src/IoDevice/wd33c93.c,v ** Revision: 1.12 ** Date: 2007/03/25 17:05:07 ** ** Based on the WD33C93 emulation in MESS (www.mess.org). ** ** More info: http://www.bluemsx.com ** ** Copyright (C) 2003-2006 Daniel Vik, Tomas Karlsson, white cat */ #include "WD33C93.hh" #include "SCSI.hh" #include "SCSIDevice.hh" #include "DummySCSIDevice.hh" #include "SCSIHD.hh" #include "SCSILS120.hh" #include "DeviceConfig.hh" #include "XMLElement.hh" #include "MSXException.hh" #include "StringOp.hh" #include "serialize.hh" #include "memory.hh" #include #include namespace openmsx { static const unsigned MAX_DEV = 8; static const byte REG_OWN_ID = 0x00; static const byte REG_CONTROL = 0x01; static const byte REG_TIMEO = 0x02; static const byte REG_TSECS = 0x03; static const byte REG_THEADS = 0x04; static const byte REG_TCYL_HI = 0x05; static const byte REG_TCYL_LO = 0x06; static const byte REG_ADDR_HI = 0x07; static const byte REG_ADDR_2 = 0x08; static const byte REG_ADDR_3 = 0x09; static const byte REG_ADDR_LO = 0x0a; static const byte REG_SECNO = 0x0b; static const byte REG_HEADNO = 0x0c; static const byte REG_CYLNO_HI = 0x0d; static const byte REG_CYLNO_LO = 0x0e; static const byte REG_TLUN = 0x0f; static const byte REG_CMD_PHASE = 0x10; static const byte REG_SYN = 0x11; static const byte REG_TCH = 0x12; static const byte REG_TCM = 0x13; static const byte REG_TCL = 0x14; static const byte REG_DST_ID = 0x15; static const byte REG_SRC_ID = 0x16; static const byte REG_SCSI_STATUS = 0x17; // (r) static const byte REG_CMD = 0x18; static const byte REG_DATA = 0x19; static const byte REG_QUEUE_TAG = 0x1a; static const byte REG_AUX_STATUS = 0x1f; // (r) static const byte REG_CDBSIZE = 0x00; static const byte REG_CDB1 = 0x03; static const byte REG_CDB2 = 0x04; static const byte REG_CDB3 = 0x05; static const byte REG_CDB4 = 0x06; static const byte REG_CDB5 = 0x07; static const byte REG_CDB6 = 0x08; static const byte REG_CDB7 = 0x09; static const byte REG_CDB8 = 0x0a; static const byte REG_CDB9 = 0x0b; static const byte REG_CDB10 = 0x0c; static const byte REG_CDB11 = 0x0d; static const byte REG_CDB12 = 0x0e; static const byte OWN_EAF = 0x08; // ENABLE ADVANCED FEATURES // SCSI STATUS static const byte SS_RESET = 0x00; // reset static const byte SS_RESET_ADV = 0x01; // reset w/adv. features static const byte SS_XFER_END = 0x16; // select and transfer complete static const byte SS_SEL_TIMEOUT = 0x42; // selection timeout static const byte SS_DISCONNECT = 0x85; // AUX STATUS static const byte AS_DBR = 0x01; // data buffer ready static const byte AS_CIP = 0x10; // command in progress, chip is busy static const byte AS_BSY = 0x20; // Level 2 command in progress static const byte AS_LCI = 0x40; // last command ignored static const byte AS_INT = 0x80; /* command phase 0x00 NO_SELECT 0x10 SELECTED 0x20 IDENTIFY_SENT 0x30 COMMAND_OUT 0x41 SAVE_DATA_RECEIVED 0x42 DISCONNECT_RECEIVED 0x43 LEGAL_DISCONNECT 0x44 RESELECTED 0x45 IDENTIFY_RECEIVED 0x46 DATA_TRANSFER_DONE 0x47 STATUS_STARTED 0x50 STATUS_RECEIVED 0x60 COMPLETE_RECEIVED */ WD33C93::WD33C93(const DeviceConfig& config) { devBusy = false; for (auto* t : config.getXML()->getChildren("target")) { unsigned id = t->getAttributeAsInt("id"); if (id >= MAX_DEV) { throw MSXException(StringOp::Builder() << "Invalid SCSI id: " << id << " (should be 0.." << MAX_DEV - 1 << ')'); } if (dev[id]) { throw MSXException(StringOp::Builder() << "Duplicate SCSI id: " << id); } DeviceConfig conf(config, *t); auto& type = t->getChild("type").getData(); if (type == "SCSIHD") { dev[id] = make_unique(conf, buffer, SCSIDevice::MODE_SCSI1 | SCSIDevice::MODE_UNITATTENTION | SCSIDevice::MODE_NOVAXIS); } else if (type == "SCSILS120") { dev[id] = make_unique(conf, buffer, SCSIDevice::MODE_SCSI1 | SCSIDevice::MODE_UNITATTENTION | SCSIDevice::MODE_NOVAXIS); } else { throw MSXException("Unknown SCSI device: " + type); } } // fill remaining targets with dummy SCSI devices to prevent crashes for (auto& d : dev) { if (!d) d = make_unique(); } reset(false); // avoid UMR on savestate memset(buffer.data(), 0, SCSIDevice::BUFFER_SIZE); counter = 0; blockCounter = 0; targetId = 0; } WD33C93::~WD33C93() { } void WD33C93::disconnect() { if (phase != SCSI::BUS_FREE) { assert(targetId < MAX_DEV); dev[targetId]->disconnect(); if (regs[REG_SCSI_STATUS] != SS_XFER_END) { regs[REG_SCSI_STATUS] = SS_DISCONNECT; } regs[REG_AUX_STATUS] = AS_INT; phase = SCSI::BUS_FREE; } tc = 0; } void WD33C93::execCmd(byte value) { if (regs[REG_AUX_STATUS] & AS_CIP) { // CIP error return; } //regs[REG_AUX_STATUS] |= AS_CIP; regs[REG_CMD] = value; bool atn = false; switch (value) { case 0x00: // Reset controller (software reset) memset(regs + 1, 0, 0x1a); disconnect(); latch = 0; // TODO: is this correct? Some doc says: reset to zero by masterreset-signal but not by reset command. regs[REG_SCSI_STATUS] = (regs[REG_OWN_ID] & OWN_EAF) ? SS_RESET_ADV : SS_RESET; break; case 0x02: // Assert ATN break; case 0x04: // Disconnect disconnect(); break; case 0x06: // Select with ATN (Lv2) atn = true; // fall-through case 0x07: // Select Without ATN (Lv2) targetId = regs[REG_DST_ID] & 7; regs[REG_SCSI_STATUS] = SS_SEL_TIMEOUT; tc = 0; regs[REG_AUX_STATUS] = AS_INT; break; case 0x08: // Select with ATN and transfer (Lv2) atn = true; // fall-through case 0x09: // Select without ATN and Transfer (Lv2) targetId = regs[REG_DST_ID] & 7; if (!devBusy && targetId < MAX_DEV && /* targetId != myId && */ dev[targetId]->isSelected()) { if (atn) { dev[targetId]->msgOut(regs[REG_TLUN] | 0x80); } devBusy = true; counter = dev[targetId]->executeCmd( ®s[REG_CDB1], phase, blockCounter); switch (phase) { case SCSI::STATUS: devBusy = false; regs[REG_TLUN] = dev[targetId]->getStatusCode(); dev[targetId]->msgIn(); regs[REG_SCSI_STATUS] = SS_XFER_END; disconnect(); break; case SCSI::EXECUTE: regs[REG_AUX_STATUS] = AS_CIP | AS_BSY; bufIdx = 0; break; default: devBusy = false; regs[REG_AUX_STATUS] = AS_CIP | AS_BSY | AS_DBR; bufIdx = 0; } //regs[REG_SRC_ID] |= regs[REG_DST_ID] & 7; } else { // timeout tc = 0; regs[REG_SCSI_STATUS] = SS_SEL_TIMEOUT; regs[REG_AUX_STATUS] = AS_INT; } break; case 0x18: // Translate Address (Lv2) default: // unsupport command break; } } void WD33C93::writeAdr(byte value) { latch = value & 0x1f; } // Latch incremented by one each time a register is accessed, // except for the address-, aux.status-, data- and command registers. void WD33C93::writeCtrl(byte value) { switch (latch) { case REG_OWN_ID: regs[REG_OWN_ID] = value; myId = value & 7; break; case REG_TCH: tc = (tc & 0x00ffff) + (value << 16); break; case REG_TCM: tc = (tc & 0xff00ff) + (value << 8); break; case REG_TCL: tc = (tc & 0xffff00) + (value << 0); break; case REG_CMD_PHASE: regs[REG_CMD_PHASE] = value; break; case REG_CMD: execCmd(value); return; // no latch-inc for address-, aux.status-, data- and command regs. case REG_DATA: regs[REG_DATA] = value; if (phase == SCSI::DATA_OUT) { buffer[bufIdx++] = value; --tc; if (--counter == 0) { counter = dev[targetId]->dataOut(blockCounter); if (counter) { bufIdx = 0; return; } regs[REG_TLUN] = dev[targetId]->getStatusCode(); dev[targetId]->msgIn(); regs[REG_SCSI_STATUS] = SS_XFER_END; disconnect(); } } return; // no latch-inc for address-, aux.status-, data- and command regs. case REG_AUX_STATUS: return; // no latch-inc for address-, aux.status-, data- and command regs. default: if (latch <= REG_SRC_ID) { regs[latch] = value; } break; } latch = (latch + 1) & 0x1f; } byte WD33C93::readAuxStatus() { byte rv = regs[REG_AUX_STATUS]; if (phase == SCSI::EXECUTE) { counter = dev[targetId]->executingCmd(phase, blockCounter); switch (phase) { case SCSI::STATUS: // TODO how can this ever be the case? regs[REG_TLUN] = dev[targetId]->getStatusCode(); dev[targetId]->msgIn(); regs[REG_SCSI_STATUS] = SS_XFER_END; disconnect(); break; case SCSI::EXECUTE: break; default: regs[REG_AUX_STATUS] |= AS_DBR; } } return rv; } // Latch incremented by one each time a register is accessed, // except for the address-, aux.status-, data- and command registers. byte WD33C93::readCtrl() { byte rv; switch (latch) { case REG_SCSI_STATUS: rv = regs[REG_SCSI_STATUS]; if (rv != SS_XFER_END) { regs[REG_AUX_STATUS] &= ~AS_INT; } else { regs[REG_SCSI_STATUS] = SS_DISCONNECT; regs[REG_AUX_STATUS] = AS_INT; } break; case REG_CMD: return regs[REG_CMD]; // no latch-inc for address-, aux.status-, data- and command regs. case REG_DATA: if (phase == SCSI::DATA_IN) { rv = buffer[bufIdx++]; regs[REG_DATA] = rv; --tc; if (--counter == 0) { if (blockCounter > 0) { counter = dev[targetId]->dataIn(blockCounter); if (counter) { bufIdx = 0; return rv; } } regs[REG_TLUN] = dev[targetId]->getStatusCode(); dev[targetId]->msgIn(); regs[REG_SCSI_STATUS] = SS_XFER_END; disconnect(); } } else { rv = regs[REG_DATA]; } return rv; // no latch-inc for address-, aux.status-, data- and command regs. case REG_TCH: rv = (tc >> 16) & 0xff; break; case REG_TCM: rv = (tc >> 8) & 0xff; break; case REG_TCL: rv = (tc >> 0) & 0xff; break; case REG_AUX_STATUS: return readAuxStatus(); // no latch-inc for address-, aux.status-, data- and command regs. default: rv = regs[latch]; break; } latch = (latch + 1) & 0x1f; return rv; } byte WD33C93::peekAuxStatus() const { return regs[REG_AUX_STATUS]; } byte WD33C93::peekCtrl() const { switch (latch) { case REG_TCH: return (tc >> 16) & 0xff; case REG_TCM: return (tc >> 8) & 0xff; case REG_TCL: return (tc >> 0) & 0xff; default: return regs[latch]; } } void WD33C93::reset(bool scsireset) { // initialized register memset(regs, 0, 0x1b); memset(regs + 0x1b, 0xff, 4); regs[REG_AUX_STATUS] = AS_INT; myId = 0; latch = 0; tc = 0; phase = SCSI::BUS_FREE; bufIdx = 0; if (scsireset) { for (auto& d : dev) { d->reset(); } } } static enum_string phaseInfo[] = { { "UNDEFINED", SCSI::UNDEFINED }, { "BUS_FREE", SCSI::BUS_FREE }, { "ARBITRATION", SCSI::ARBITRATION }, { "SELECTION", SCSI::SELECTION }, { "RESELECTION", SCSI::RESELECTION }, { "COMMAND", SCSI::COMMAND }, { "EXECUTE", SCSI::EXECUTE }, { "DATA_IN", SCSI::DATA_IN }, { "DATA_OUT", SCSI::DATA_OUT }, { "STATUS", SCSI::STATUS }, { "MSG_OUT", SCSI::MSG_OUT }, { "MSG_IN", SCSI::MSG_IN } }; SERIALIZE_ENUM(SCSI::Phase, phaseInfo); template void WD33C93::serialize(Archive& ar, unsigned /*version*/) { ar.serialize_blob("buffer", buffer.data(), buffer.size()); char tag[8] = { 'd', 'e', 'v', 'i', 'c', 'e', 'X', 0 }; for (unsigned i = 0; i < MAX_DEV; ++i) { tag[6] = char('0' + i); ar.serializePolymorphic(tag, *dev[i]); } ar.serialize("bufIdx", bufIdx); ar.serialize("counter", counter); ar.serialize("blockCounter", blockCounter); ar.serialize("tc", tc); ar.serialize("phase", phase); ar.serialize("myId", myId); ar.serialize("targetId", targetId); ar.serialize_blob("registers", regs, sizeof(regs)); ar.serialize("latch", latch); ar.serialize("devBusy", devBusy); } INSTANTIATE_SERIALIZE_METHODS(WD33C93); /* Here is some info on the parameters for SCSI devices: static SCSIDevice* wd33c93ScsiDevCreate(WD33C93* wd33c93, int id) { #if 1 // CD_UPDATE: Use dynamic parameters instead of hard coded ones int diskId, mode, type; diskId = diskGetHdDriveId(hdId, id); if (diskIsCdrom(diskId)) { mode = MODE_SCSI1 | MODE_UNITATTENTION | MODE_REMOVABLE | MODE_NOVAXIS; type = SDT_CDROM; } else { mode = MODE_SCSI1 | MODE_UNITATTENTION | MODE_FDS120 | MODE_REMOVABLE | MODE_NOVAXIS; type = SDT_DirectAccess; } return scsiDeviceCreate(id, diskId, buffer, nullptr, type, mode, (CdromXferCompCb)wd33c93XferCb, wd33c93); #else SCSIDEVICE* dev; int mode; int type; if (id != 2) { mode = MODE_SCSI1 | MODE_UNITATTENTION | MODE_FDS120 | MODE_REMOVABLE | MODE_NOVAXIS; type = SDT_DirectAccess; } else { mode = MODE_SCSI1 | MODE_UNITATTENTION | MODE_REMOVABLE | MODE_NOVAXIS; type = SDT_CDROM; } dev = scsiDeviceCreate(id, diskGetHdDriveId(hdId, id), buffer, nullptr, type, mode, (CdromXferCompCb)wd33c93XferCb, wd33c93); return dev; #endif } */ } // namespace openmsx openMSX-RELEASE_0_12_0/src/ide/WD33C93.hh000066400000000000000000000021051257557151200172450ustar00rootroot00000000000000/* Ported from: ** Source: /cvsroot/bluemsx/blueMSX/Src/IoDevice/wd33c93.h,v ** Revision: 1.6 ** Date: 2007/03/22 10:55:08 ** ** More info: http://www.bluemsx.com ** ** Copyright (C) 2003-2007 Daniel Vik, Ricardo Bittencourt, white cat */ #ifndef WD33C93_HH #define WD33C93_HH #include "SCSI.hh" #include "SCSIDevice.hh" #include "AlignedBuffer.hh" #include namespace openmsx { class DeviceConfig; class WD33C93 { public: explicit WD33C93(const DeviceConfig& config); ~WD33C93(); void reset(bool scsireset); byte readAuxStatus(); byte readCtrl(); byte peekAuxStatus() const; byte peekCtrl() const; void writeAdr(byte value); void writeCtrl(byte value); template void serialize(Archive& ar, unsigned version); private: void disconnect(); void execCmd(byte value); AlignedByteArray buffer; std::unique_ptr dev[8]; unsigned bufIdx; int counter; unsigned blockCounter; int tc; SCSI::Phase phase; byte myId; byte targetId; byte regs[32]; byte latch; bool devBusy; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/000077500000000000000000000000001257557151200163655ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/src/input/.gitignore000066400000000000000000000001141257557151200203510ustar00rootroot00000000000000/*.swp /*.rpo /*.rom /*.ROM /.xvpics /*.dsk /*.bb /*.bbg /*.da /GNUmakefile openMSX-RELEASE_0_12_0/src/input/ArkanoidPad.cc000066400000000000000000000131411257557151200210510ustar00rootroot00000000000000#include "ArkanoidPad.hh" #include "MSXEventDistributor.hh" #include "StateChangeDistributor.hh" #include "InputEvents.hh" #include "StateChange.hh" #include "checked_cast.hh" #include "serialize.hh" #include "serialize_meta.hh" #include // Implemented mostly according to the info here: http://www.msx.org/forumtopic7661.html // This is absolutely not accurate, but good enough to make the pad work in the // Arkanoid games. // The hardware works with a 556 dual timer that's unemulated here, it requires // short delays at each shift, and a long delay at latching. Too short delays // will cause garbage reads, and too long delays at shifting will eventually // cause the shift register bits to return to 0. using std::string; using std::shared_ptr; using std::make_shared; namespace openmsx { static const int POS_MIN = 55; // measured by hap static const int POS_MAX = 325; // measured by hap static const int POS_CENTER = 236; // approx. middle used by games static const int SCALE = 2; class ArkanoidState final : public StateChange { public: ArkanoidState() {} // for serialize ArkanoidState(EmuTime::param time, int delta_, bool press_, bool release_) : StateChange(time) , delta(delta_), press(press_), release(release_) {} int getDelta() const { return delta; } bool getPress() const { return press; } bool getRelease() const { return release; } template void serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("delta", delta); ar.serialize("press", press); ar.serialize("release", release); } private: int delta; bool press, release; }; REGISTER_POLYMORPHIC_CLASS(StateChange, ArkanoidState, "ArkanoidState"); ArkanoidPad::ArkanoidPad(MSXEventDistributor& eventDistributor_, StateChangeDistributor& stateChangeDistributor_) : eventDistributor(eventDistributor_) , stateChangeDistributor(stateChangeDistributor_) , shiftreg(0) // the 9 bit shift degrades to 0 , dialpos(POS_CENTER) , buttonStatus(0x3E) , lastValue(0) { } ArkanoidPad::~ArkanoidPad() { if (isPluggedIn()) { ArkanoidPad::unplugHelper(EmuTime::dummy()); } } // Pluggable const string& ArkanoidPad::getName() const { static const string name("arkanoidpad"); return name; } string_ref ArkanoidPad::getDescription() const { return "Arkanoid pad"; } void ArkanoidPad::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/) { eventDistributor.registerEventListener(*this); stateChangeDistributor.registerListener(*this); } void ArkanoidPad::unplugHelper(EmuTime::param /*time*/) { stateChangeDistributor.unregisterListener(*this); eventDistributor.unregisterEventListener(*this); } // JoystickDevice byte ArkanoidPad::read(EmuTime::param /*time*/) { return buttonStatus | ((shiftreg & 0x100) >> 8); } void ArkanoidPad::write(byte value, EmuTime::param /*time*/) { byte diff = lastValue ^ value; lastValue = value; if (diff & value & 0x4) { // pin 8 from low to high: copy dial position into shift reg shiftreg = dialpos; } if (diff & value & 0x1) { // pin 6 from low to high: shift the shift reg shiftreg = (shiftreg << 1) | (shiftreg & 1); // bit 0's value is 'shifted in' } } // MSXEventListener void ArkanoidPad::signalEvent(const shared_ptr& event, EmuTime::param time) { switch (event->getType()) { case OPENMSX_MOUSE_MOTION_EVENT: { auto& mEvent = checked_cast(*event); int newPos = std::min(POS_MAX, std::max(POS_MIN, dialpos + mEvent.getX() / SCALE)); int delta = newPos - dialpos; if (delta != 0) { stateChangeDistributor.distributeNew( make_shared( time, delta, false, false)); } break; } case OPENMSX_MOUSE_BUTTON_DOWN_EVENT: // any button will press the Arkanoid Pad button if (buttonStatus & 2) { stateChangeDistributor.distributeNew( make_shared( time, 0, true, false)); } break; case OPENMSX_MOUSE_BUTTON_UP_EVENT: // any button will unpress the Arkanoid Pad button if (!(buttonStatus & 2)) { stateChangeDistributor.distributeNew( make_shared( time, 0, false, true)); } break; default: // ignore break; } } // StateChangeListener void ArkanoidPad::signalStateChange(const shared_ptr& event) { auto as = dynamic_cast(event.get()); if (!as) return; dialpos += as->getDelta(); if (as->getPress()) buttonStatus &= ~2; if (as->getRelease()) buttonStatus |= 2; } void ArkanoidPad::stopReplay(EmuTime::param time) { // TODO Get actual mouse button(s) state. Is it worth the trouble? int delta = POS_CENTER - dialpos; bool release = (buttonStatus & 2) == 0; if ((delta != 0) || release) { stateChangeDistributor.distributeNew(make_shared( time, delta, false, release)); } } // version 1: Initial version, the variables dialpos and buttonStatus were not // serialized. // version 2: Also serialize the above variables, this is required for // record/replay, see comment in Keyboard.cc for more details. template void ArkanoidPad::serialize(Archive& ar, unsigned version) { ar.serialize("shiftreg", shiftreg); ar.serialize("lastValue", lastValue); if (ar.versionAtLeast(version, 2)) { ar.serialize("dialpos", dialpos); ar.serialize("buttonStatus", buttonStatus); } if (ar.isLoader() && isPluggedIn()) { plugHelper(*getConnector(), EmuTime::dummy()); } } INSTANTIATE_SERIALIZE_METHODS(ArkanoidPad); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, ArkanoidPad, "ArkanoidPad"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/ArkanoidPad.hh000066400000000000000000000027071257557151200210710ustar00rootroot00000000000000#ifndef ARKANOIDPAD_HH #define ARKANOIDPAD_HH #include "JoystickDevice.hh" #include "MSXEventListener.hh" #include "StateChangeListener.hh" #include "serialize_meta.hh" namespace openmsx { class MSXEventDistributor; class StateChangeDistributor; class ArkanoidPad final : public JoystickDevice, private MSXEventListener , private StateChangeListener { public: explicit ArkanoidPad(MSXEventDistributor& eventDistributor, StateChangeDistributor& stateChangeDistributor); ~ArkanoidPad(); template void serialize(Archive& ar, unsigned version); private: // Pluggable const std::string& getName() const override; string_ref getDescription() const override; void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; // JoystickDevice byte read(EmuTime::param time) override; void write(byte value, EmuTime::param time) override; // MSXEventListener void signalEvent(const std::shared_ptr& event, EmuTime::param time) override; // StateChangeListener void signalStateChange(const std::shared_ptr& event) override; void stopReplay(EmuTime::param time) override; MSXEventDistributor& eventDistributor; StateChangeDistributor& stateChangeDistributor; int shiftreg; int dialpos; byte buttonStatus; byte lastValue; }; SERIALIZE_CLASS_VERSION(ArkanoidPad, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/DummyJoystick.cc000066400000000000000000000007341257557151200215130ustar00rootroot00000000000000#include "DummyJoystick.hh" namespace openmsx { byte DummyJoystick::read(EmuTime::param /*time*/) { return 0x3F; } void DummyJoystick::write(byte /*value*/, EmuTime::param /*time*/) { // do nothing } string_ref DummyJoystick::getDescription() const { return ""; } void DummyJoystick::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/) { } void DummyJoystick::unplugHelper(EmuTime::param /*time*/) { } } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/DummyJoystick.hh000066400000000000000000000007131257557151200215220ustar00rootroot00000000000000#ifndef DUMMYJOYSTICK_HH #define DUMMYJOYSTICK_HH #include "JoystickDevice.hh" namespace openmsx { class DummyJoystick final : public JoystickDevice { public: byte read(EmuTime::param time) override; void write(byte value, EmuTime::param time) override; string_ref getDescription() const override; void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/EventDelay.cc000066400000000000000000000164711257557151200207450ustar00rootroot00000000000000#include "EventDelay.hh" #include "EventDistributor.hh" #include "MSXEventDistributor.hh" #include "ReverseManager.hh" #include "InputEvents.hh" #include "Timer.hh" #include "MSXException.hh" #include "checked_cast.hh" #include "stl.hh" #include using std::make_shared; namespace openmsx { EventDelay::EventDelay(Scheduler& scheduler, CommandController& commandController, EventDistributor& eventDistributor_, MSXEventDistributor& msxEventDistributor_, ReverseManager& reverseManager) : Schedulable(scheduler) , eventDistributor(eventDistributor_) , msxEventDistributor(msxEventDistributor_) , prevEmu(EmuTime::zero) , prevReal(Timer::getTime()) , delaySetting( commandController, "inputdelay", "delay input to avoid key-skips", 0.0, 0.0, 10.0) { eventDistributor.registerEventListener( OPENMSX_KEY_DOWN_EVENT, *this, EventDistributor::MSX); eventDistributor.registerEventListener( OPENMSX_KEY_UP_EVENT, *this, EventDistributor::MSX); eventDistributor.registerEventListener( OPENMSX_MOUSE_MOTION_EVENT, *this, EventDistributor::MSX); eventDistributor.registerEventListener( OPENMSX_MOUSE_BUTTON_DOWN_EVENT, *this, EventDistributor::MSX); eventDistributor.registerEventListener( OPENMSX_MOUSE_BUTTON_UP_EVENT, *this, EventDistributor::MSX); eventDistributor.registerEventListener( OPENMSX_JOY_AXIS_MOTION_EVENT, *this, EventDistributor::MSX); eventDistributor.registerEventListener( OPENMSX_JOY_HAT_EVENT, *this, EventDistributor::MSX); eventDistributor.registerEventListener( OPENMSX_JOY_BUTTON_DOWN_EVENT, *this, EventDistributor::MSX); eventDistributor.registerEventListener( OPENMSX_JOY_BUTTON_UP_EVENT, *this, EventDistributor::MSX); reverseManager.registerEventDelay(*this); } EventDelay::~EventDelay() { eventDistributor.unregisterEventListener( OPENMSX_KEY_DOWN_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_KEY_UP_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_MOUSE_MOTION_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_MOUSE_BUTTON_DOWN_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_MOUSE_BUTTON_UP_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_JOY_AXIS_MOTION_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_JOY_HAT_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_JOY_BUTTON_DOWN_EVENT, *this); eventDistributor.unregisterEventListener( OPENMSX_JOY_BUTTON_UP_EVENT, *this); } int EventDelay::signalEvent(const EventPtr& event) { toBeScheduledEvents.push_back(event); if (delaySetting.getDouble() == 0.0) { sync(getCurrentTime()); } return 0; } void EventDelay::sync(EmuTime::param curEmu) { auto curRealTime = Timer::getTime(); auto realDuration = curRealTime - prevReal; prevReal = curRealTime; auto emuDuration = curEmu - prevEmu; prevEmu = curEmu; double factor = emuDuration.toDouble() / realDuration; EmuDuration extraDelay(delaySetting.getDouble()); #if PLATFORM_ANDROID // The virtual keyboard on Android sends a key press and the // corresponding key release event directly after each other, without a // delay. It sends both events either when the user has finished a // short tap or alternatively after the user has hold the button // pressed for a few seconds and then has selected the appropriate // character from the multi-character-popup that the virtual keyboard // displays when the user holds a button pressed for a short while. // Either way, the key release event comes way too short after the // press event for the MSX to process it. The two events follow each // other within a few milliseconds at most. Therefore, on Android, // special logic must be foreseen to delay the release event for a // short while. This special logic correlates each key release event // with the corresponding press event for the same key. If they are // less then 2/50 second apart, the release event gets delayed until // the next sync call. The 2/50 second has been chosen because it can // take up to 2 vertical interrupts (2 screen refreshes) for the MSX to // see the key press in the keyboard matrix, thus, 2/50 seconds is the // minimum delay required for an MSX running in PAL mode. std::vector toBeRescheduledEvents; #endif EmuTime time = curEmu + extraDelay; for (auto& e : toBeScheduledEvents) { #if PLATFORM_ANDROID if (e->getType() == OPENMSX_KEY_DOWN_EVENT || e->getType() == OPENMSX_KEY_UP_EVENT) { auto keyEvent = checked_cast(e.get()); int maskedKeyCode = int(keyEvent->getKeyCode()) & int(Keys::K_MASK); auto it = find_if(begin(nonMatchedKeyPresses), end(nonMatchedKeyPresses), EqualTupleValue<0>(maskedKeyCode)); if (e->getType() == OPENMSX_KEY_DOWN_EVENT) { if (it == end(nonMatchedKeyPresses)) { nonMatchedKeyPresses.emplace_back(maskedKeyCode, e); } else { it->second = e; } } else { if (it != end(nonMatchedKeyPresses)) { auto timedPressEvent = checked_cast(it->second.get()); auto timedReleaseEvent = checked_cast(e.get()); auto pressRealTime = timedPressEvent->getRealTime(); auto releaseRealTime = timedReleaseEvent->getRealTime(); auto deltaTime = releaseRealTime - pressRealTime; if (deltaTime <= 2000000 / 50) { // The key release came less then 2 MSX interrupts from the key press. // Reschedule it for the next sync, with the realTime updated to now, so that it seems like the // key was released now and not when android released it. // Otherwise, the offset calculation for the emutime further down below will go wrong on the next sync EventPtr newKeyupEvent = make_shared(keyEvent->getKeyCode(), keyEvent->getUnicode()); toBeRescheduledEvents.push_back(newKeyupEvent); continue; // continue with next to be scheduled event } auto backIt = end(nonMatchedKeyPresses) - 1; if (it != backIt) std::swap(*it, *backIt); nonMatchedKeyPresses.pop_back(); } } } #endif scheduledEvents.push_back(e); auto timedEvent = checked_cast(e.get()); auto eventRealTime = timedEvent->getRealTime(); assert(eventRealTime <= curRealTime); auto offset = curRealTime - eventRealTime; EmuDuration emuOffset(factor * offset); auto schedTime = (emuOffset < extraDelay) ? time - emuOffset : curEmu; assert(curEmu <= schedTime); setSyncPoint(schedTime); } toBeScheduledEvents.clear(); #if PLATFORM_ANDROID toBeScheduledEvents.insert(end(toBeScheduledEvents), make_move_iterator(begin(toBeRescheduledEvents)), make_move_iterator(end (toBeRescheduledEvents))); #endif } void EventDelay::executeUntil(EmuTime::param time) { try { auto event = std::move(scheduledEvents.front()); scheduledEvents.pop_front(); msxEventDistributor.distributeEvent(std::move(event), time); } catch (MSXException&) { // ignore } } void EventDelay::flush() { EmuTime time = getCurrentTime(); for (auto& e : scheduledEvents) { msxEventDistributor.distributeEvent(e, time); } scheduledEvents.clear(); for (auto& e : toBeScheduledEvents) { msxEventDistributor.distributeEvent(e, time); } toBeScheduledEvents.clear(); removeSyncPoints(); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/EventDelay.hh000066400000000000000000000027501257557151200207520ustar00rootroot00000000000000#ifndef EVENTDELAY_HH #define EVENTDELAY_HH #include "EventListener.hh" #include "Schedulable.hh" #include "EmuTime.hh" #include "FloatSetting.hh" #include "build-info.hh" #include #include #include namespace openmsx { class Scheduler; class CommandController; class Event; class EventDistributor; class MSXEventDistributor; class ReverseManager; /** This class is responsible for translating host events into MSX events. * It also translates host event timing into EmuTime. To better do this * we introduce a small delay (default 0.03s) in this translation. */ class EventDelay final : private EventListener, private Schedulable { public: EventDelay(Scheduler& scheduler, CommandController& commandController, EventDistributor& eventDistributor, MSXEventDistributor& msxEventDistributor, ReverseManager& reverseManager); ~EventDelay(); void sync(EmuTime::param time); void flush(); private: using EventPtr = std::shared_ptr; // EventListener int signalEvent(const EventPtr& event) override; // Schedulable void executeUntil(EmuTime::param time) override; EventDistributor& eventDistributor; MSXEventDistributor& msxEventDistributor; std::vector toBeScheduledEvents; std::deque scheduledEvents; #if PLATFORM_ANDROID std::vector> nonMatchedKeyPresses; #endif EmuTime prevEmu; uint64_t prevReal; FloatSetting delaySetting; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/JoyMega.cc000066400000000000000000000225511257557151200202340ustar00rootroot00000000000000#include "JoyMega.hh" #include "PluggingController.hh" #include "MSXEventDistributor.hh" #include "StateChangeDistributor.hh" #include "InputEvents.hh" #include "InputEventGenerator.hh" #include "StateChange.hh" #include "checked_cast.hh" #include "serialize.hh" #include "serialize_meta.hh" #include "memory.hh" #include "unreachable.hh" #include "build-info.hh" using std::string; using std::shared_ptr; namespace openmsx { #if PLATFORM_ANDROID static const int THRESHOLD = 32768 / 4; #else static const int THRESHOLD = 32768 / 10; #endif void JoyMega::registerAll(MSXEventDistributor& eventDistributor, StateChangeDistributor& stateChangeDistributor, PluggingController& controller) { #ifdef SDL_JOYSTICK_DISABLED (void)eventDistributor; (void)stateChangeDistributor; (void)controller; #else unsigned numJoysticks = SDL_NumJoysticks(); for (unsigned i = 0; i < numJoysticks; i++) { if (SDL_Joystick* joystick = SDL_JoystickOpen(i)) { // Avoid devices that have axes but no buttons, like accelerometers. // SDL 1.2.14 in Linux has an issue where it rejects a device from // /dev/input/event* if it has no buttons but does not reject a // device from /dev/input/js* if it has no buttons, while // accelerometers do end up being symlinked as a joystick in // practice. if (InputEventGenerator::joystickNumButtons(joystick) != 0) { controller.registerPluggable( make_unique( eventDistributor, stateChangeDistributor, joystick)); } } } #endif } class JoyMegaState final : public StateChange { public: JoyMegaState() {} // for serialize JoyMegaState(EmuTime::param time, unsigned joyNum_, unsigned press_, unsigned release_) : StateChange(time) , joyNum(joyNum_), press(press_), release(release_) { assert((press != 0) || (release != 0)); assert((press & release) == 0); } unsigned getJoystick() const { return joyNum; } unsigned getPress() const { return press; } unsigned getRelease() const { return release; } template void serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("joyNum", joyNum); ar.serialize("press", press); ar.serialize("release", release); } private: unsigned joyNum; unsigned press, release; }; REGISTER_POLYMORPHIC_CLASS(StateChange, JoyMegaState, "JoyMegaState"); #ifndef SDL_JOYSTICK_DISABLED // Note: It's OK to open/close the same SDL_Joystick multiple times (we open it // once per MSX machine). The SDL documentation doesn't state this, but I // checked the implementation and a SDL_Joystick uses a 'reference count' on // the open/close calls. JoyMega::JoyMega(MSXEventDistributor& eventDistributor_, StateChangeDistributor& stateChangeDistributor_, SDL_Joystick* joystick_) : eventDistributor(eventDistributor_) , stateChangeDistributor(stateChangeDistributor_) , joystick(joystick_) , joyNum(SDL_JoystickIndex(joystick_)) , name("joymegaX") // 'X' is filled in below , desc(string(SDL_JoystickName(joyNum))) , lastTime(EmuTime::zero) { const_cast(name)[7] = char('1' + joyNum); } JoyMega::~JoyMega() { if (isPluggedIn()) { JoyMega::unplugHelper(EmuTime::dummy()); } SDL_JoystickClose(joystick); } // Pluggable const string& JoyMega::getName() const { return name; } string_ref JoyMega::getDescription() const { return desc; } void JoyMega::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/) { plugHelper2(); status = calcInitialState(); cycle = 0; // when mode button is pressed when joystick is plugged in, then // act as a 3-button joypad (otherwise 6-button) cycleMask = (status & 0x800) ? 7 : 1; } void JoyMega::plugHelper2() { eventDistributor.registerEventListener(*this); stateChangeDistributor.registerListener(*this); } void JoyMega::unplugHelper(EmuTime::param /*time*/) { stateChangeDistributor.unregisterListener(*this); eventDistributor.unregisterEventListener(*this); } // JoystickDevice byte JoyMega::read(EmuTime::param time) { // See http://segaretro.org/Control_Pad_(Mega_Drive) // and http://frs.badcoffee.info/hardware/joymega-en.html // for a detailed description of the MegaDrive joystick. checkTime(time); switch (cycle) { case 0: case 2: case 4: // up/down/left/right/b/c return (status & 0x00f) | ((status & 0x060) >> 1); case 1: case 3: // up/down/0/0/a/start return (status & 0x013) | ((status & 0x080) >> 2); case 5: // 0/0/0/0/a/start return (status & 0x010) | ((status & 0x080) >> 2); case 6: // z/y/x/mode/b/c return ((status & 0x400) >> 10) | // z ((status & 0xA00) >> 8) | // start+y ((status & 0x100) >> 6) | // x ((status & 0x060) >> 1); // c+b case 7: // 1/1/1/1/a/start return (status & 0x010) | ((status & 0x080) >> 2) | 0x0f; default: UNREACHABLE; return 0; } } void JoyMega::write(byte value, EmuTime::param time) { checkTime(time); lastTime = time; if (((value >> 2) & 1) != (cycle & 1)) { cycle = (cycle + 1) & cycleMask; } assert(((value >> 2) & 1) == (cycle & 1)); } void JoyMega::checkTime(EmuTime::param time) { if ((time - lastTime) > EmuDuration::usec(1500)) { // longer than 1.5ms since last write -> reset cycle cycle = 0; } } static unsigned encodeButton(unsigned button, byte cycleMask) { unsigned n = (cycleMask == 7) ? 7 : 3; // 6- or 3-button mode return 1 << (4 + (button & n)); } unsigned JoyMega::calcInitialState() { unsigned result = 0xfff; int xAxis = SDL_JoystickGetAxis(joystick, 0); if (xAxis < -THRESHOLD) { result &= ~JOY_LEFT; } else if (xAxis > THRESHOLD) { result &= ~JOY_RIGHT; } int yAxis = SDL_JoystickGetAxis(joystick, 1); if (yAxis < -THRESHOLD) { result &= ~JOY_UP; } else if (yAxis > THRESHOLD) { result &= ~JOY_DOWN; } int numButtons = InputEventGenerator::joystickNumButtons(joystick); for (int button = 0; button < numButtons; ++button) { if (InputEventGenerator::joystickGetButton(joystick, button)) { result &= ~encodeButton(button, 7); } } return result; } // MSXEventListener void JoyMega::signalEvent(const shared_ptr& event, EmuTime::param time) { auto joyEvent = dynamic_cast(event.get()); if (!joyEvent) return; // TODO: It would be more efficient to make a dispatcher instead of // sending the event to all joysticks. if (joyEvent->getJoystick() != joyNum) return; switch (event->getType()) { case OPENMSX_JOY_AXIS_MOTION_EVENT: { auto& mev = checked_cast(*event); short value = mev.getValue(); switch (mev.getAxis() & 1) { case JoystickAxisMotionEvent::X_AXIS: // Horizontal if (value < -THRESHOLD) { // left, not right createEvent(time, JOY_LEFT, JOY_RIGHT); } else if (value > THRESHOLD) { // not left, right createEvent(time, JOY_RIGHT, JOY_LEFT); } else { // not left, not right createEvent(time, 0, JOY_LEFT | JOY_RIGHT); } break; case JoystickAxisMotionEvent::Y_AXIS: // Vertical if (value < -THRESHOLD) { // up, not down createEvent(time, JOY_UP, JOY_DOWN); } else if (value > THRESHOLD) { // not up, down createEvent(time, JOY_DOWN, JOY_UP); } else { // not up, not down createEvent(time, 0, JOY_UP | JOY_DOWN); } break; default: // ignore other axis break; } break; } case OPENMSX_JOY_BUTTON_DOWN_EVENT: { auto& butEv = checked_cast(*event); createEvent(time, encodeButton(butEv.getButton(), cycleMask), 0); break; } case OPENMSX_JOY_BUTTON_UP_EVENT: { auto& butEv = checked_cast(*event); createEvent(time, 0, encodeButton(butEv.getButton(), cycleMask)); break; } default: UNREACHABLE; } } void JoyMega::createEvent(EmuTime::param time, unsigned press, unsigned release) { unsigned newStatus = (status & ~press) | release; createEvent(time, newStatus); } void JoyMega::createEvent(EmuTime::param time, unsigned newStatus) { unsigned diff = status ^ newStatus; if (!diff) { // event won't actually change the status, so ignore it return; } // make sure we create an event with minimal changes unsigned press = status & diff; unsigned release = newStatus & diff; stateChangeDistributor.distributeNew(std::make_shared( time, joyNum, press, release)); } // StateChangeListener void JoyMega::signalStateChange(const shared_ptr& event) { auto js = dynamic_cast(event.get()); if (!js) return; // TODO: It would be more efficient to make a dispatcher instead of // sending the event to all joysticks. // TODO an alternative is to log events based on the connector instead // of the joystick. That would make it possible to replay on a // different host without an actual SDL joystick connected. if (js->getJoystick() != joyNum) return; status = (status & ~js->getPress()) | js->getRelease(); } void JoyMega::stopReplay(EmuTime::param time) { createEvent(time, calcInitialState()); } template void JoyMega::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("lastTime", lastTime); ar.serialize("status", status); ar.serialize("cycle", cycle); ar.serialize("cycleMask", cycleMask); if (ar.isLoader()) { if (isPluggedIn()) { plugHelper2(); } } } INSTANTIATE_SERIALIZE_METHODS(JoyMega); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, JoyMega, "JoyMega"); #endif // SDL_JOYSTICK_DISABLED } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/JoyMega.hh000066400000000000000000000040211257557151200202360ustar00rootroot00000000000000#ifndef JOYMEGA_HH #define JOYMEGA_HH #include "JoystickDevice.hh" #include "MSXEventListener.hh" #include "StateChangeListener.hh" #include namespace openmsx { class MSXEventDistributor; class StateChangeDistributor; class PluggingController; class JoyMega final #ifndef SDL_JOYSTICK_DISABLED : public JoystickDevice, private MSXEventListener, private StateChangeListener #endif { public: static void registerAll(MSXEventDistributor& eventDistributor, StateChangeDistributor& stateChangeDistributor, PluggingController& controller); JoyMega(MSXEventDistributor& eventDistributor, StateChangeDistributor& stateChangeDistributor, SDL_Joystick* joystick); ~JoyMega(); #ifndef SDL_JOYSTICK_DISABLED // Pluggable const std::string& getName() const override; string_ref getDescription() const override; void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; // JoystickDevice byte read(EmuTime::param time) override; void write(byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: void plugHelper2(); unsigned calcInitialState(); void checkTime(EmuTime::param time); void createEvent(EmuTime::param time, unsigned press, unsigned release); void createEvent(EmuTime::param time, unsigned newStatus); // MSXEventListener void signalEvent(const std::shared_ptr& event, EmuTime::param time) override; // StateChangeListener void signalStateChange(const std::shared_ptr& event) override; void stopReplay(EmuTime::param time) override; MSXEventDistributor& eventDistributor; StateChangeDistributor& stateChangeDistributor; SDL_Joystick* const joystick; const unsigned joyNum; const std::string name; const std::string desc; EmuTime lastTime; unsigned status; byte cycle; // 0-7 byte cycleMask; // 1 or 7 #endif // SDL_JOYSTICK_DISABLED }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/JoyTap.cc000066400000000000000000000035411257557151200201050ustar00rootroot00000000000000#include "JoyTap.hh" #include "JoystickPort.hh" #include "PluggingController.hh" #include "StringOp.hh" #include "serialize.hh" #include "memory.hh" namespace openmsx { using std::string; JoyTap::JoyTap(PluggingController& pluggingController_, const string& name_) : pluggingController(pluggingController_) , name(name_) { } JoyTap::~JoyTap() { } void JoyTap::createPorts(PluggingController& pluggingController, const string& baseDescription) { for (int i = 0; i < 4; ++i) { slaves[i] = make_unique( pluggingController, StringOp::Builder() << name << "_port_" << char('1' + i), StringOp::Builder() << baseDescription << char('1' + i)); } } string_ref JoyTap::getDescription() const { return "MSX Joy Tap device"; } const std::string& JoyTap::getName() const { return name; } void JoyTap::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/) { createPorts(pluggingController, "Joy Tap port "); } void JoyTap::unplugHelper(EmuTime::param time) { for (auto& s : slaves) { s->unplug(time); s.reset(); } } byte JoyTap::read(EmuTime::param time) { byte value = 255; for (auto& s : slaves) { value &= s->read(time); } return value; } void JoyTap::write(byte value, EmuTime::param time) { for (auto& s : slaves) { s->write(value, time); } } template void JoyTap::serialize(Archive& ar, unsigned /*version*/) { // saving only happens when plugged in if (!ar.isLoader()) assert(isPluggedIn()); // restore plugged state when loading if (ar.isLoader()) { plugHelper(*getConnector(), pluggingController.getCurrentTime()); } char tag[6] = { 'p', 'o', 'r', 't', 'X', 0 }; for (int i = 0; i < 4; ++i) { tag[4] = char('0' + i); ar.serialize(tag, *slaves[i]); } } INSTANTIATE_SERIALIZE_METHODS(JoyTap); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, JoyTap, "JoyTap"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/JoyTap.hh000066400000000000000000000026541257557151200201230ustar00rootroot00000000000000#ifndef JOYTAP_HH #define JOYTAP_HH #include "JoystickDevice.hh" #include "serialize_meta.hh" #include namespace openmsx { class PluggingController; class JoystickPort; /** This device is pluged in into the joyports and consolidates several other * joysticks plugged into it. This joytap simply ANDs all the joystick * outputs, acting as a simple wiring of all digital joysticks into one * connector. * This is the base class for the NinjaTap device and the FNano2 multiplayer * extension, who basicly have other read and write methods */ class JoyTap : public JoystickDevice { public: JoyTap(PluggingController& pluggingController, const std::string& name); virtual ~JoyTap(); // Pluggable const std::string& getName() const override; string_ref getDescription() const override; void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; // JoystickDevice byte read(EmuTime::param time) override; void write(byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); protected: void createPorts(PluggingController& pluggingController, const std::string& baseDescription); std::unique_ptr slaves[4]; PluggingController& pluggingController; private: const std::string name; }; REGISTER_BASE_NAME_HELPER(JoyTap, "JoyTap"); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/Joystick.cc000066400000000000000000000271441257557151200205030ustar00rootroot00000000000000#include "Joystick.hh" #include "PluggingController.hh" #include "PlugException.hh" #include "MSXEventDistributor.hh" #include "StateChangeDistributor.hh" #include "InputEvents.hh" #include "InputEventGenerator.hh" #include "StateChange.hh" #include "TclObject.hh" #include "GlobalSettings.hh" #include "IntegerSetting.hh" #include "CommandController.hh" #include "CommandException.hh" #include "serialize.hh" #include "serialize_meta.hh" #include "memory.hh" #include "xrange.hh" #include "build-info.hh" using std::string; using std::shared_ptr; namespace openmsx { void Joystick::registerAll(MSXEventDistributor& eventDistributor, StateChangeDistributor& stateChangeDistributor, CommandController& commandController, GlobalSettings& globalSettings, PluggingController& controller) { #ifdef SDL_JOYSTICK_DISABLED (void)eventDistributor; (void)stateChangeDistributor; (void)controller; #else unsigned numJoysticks = SDL_NumJoysticks(); ad_printf("#joysticks: %d\n", numJoysticks); for (unsigned i = 0; i < numJoysticks; i++) { SDL_Joystick* joystick = SDL_JoystickOpen(i); if (joystick) { // Avoid devices that have axes but no buttons, like accelerometers. // SDL 1.2.14 in Linux has an issue where it rejects a device from // /dev/input/event* if it has no buttons but does not reject a // device from /dev/input/js* if it has no buttons, while // accelerometers do end up being symlinked as a joystick in // practice. if (InputEventGenerator::joystickNumButtons(joystick) != 0) { controller.registerPluggable( make_unique( eventDistributor, stateChangeDistributor, commandController, globalSettings, joystick)); } } } #endif } class JoyState final : public StateChange { public: JoyState() {} // for serialize JoyState(EmuTime::param time, unsigned joyNum_, byte press_, byte release_) : StateChange(time) , joyNum(joyNum_), press(press_), release(release_) { assert((press != 0) || (release != 0)); assert((press & release) == 0); } unsigned getJoystick() const { return joyNum; } byte getPress() const { return press; } byte getRelease() const { return release; } template void serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("joyNum", joyNum); ar.serialize("press", press); ar.serialize("release", release); } private: unsigned joyNum; byte press, release; }; REGISTER_POLYMORPHIC_CLASS(StateChange, JoyState, "JoyState"); #ifndef SDL_JOYSTICK_DISABLED static void checkJoystickConfig(Interpreter& interp, TclObject& newValue) { unsigned n = newValue.getListLength(interp); if (n & 1) { throw CommandException("Need an even number of elements"); } for (unsigned i = 0; i < n; i += 2) { string_ref key = newValue.getListIndex(interp, i + 0).getString(); TclObject value = newValue.getListIndex(interp, i + 1); if ((key != "A" ) && (key != "B" ) && (key != "LEFT") && (key != "RIGHT") && (key != "UP" ) && (key != "DOWN" )) { throw CommandException( "Invalid MSX joystick action: must be one of " "'A', 'B', 'LEFT', 'RIGHT', 'UP', 'DOWN'."); } for (auto j : xrange(value.getListLength(interp))) { string_ref host = value.getListIndex(interp, j).getString(); if (!host.starts_with("button") && !host.starts_with("+axis") && !host.starts_with("-axis") && !host.starts_with("L_hat") && !host.starts_with("R_hat") && !host.starts_with("U_hat") && !host.starts_with("D_hat")) { throw CommandException( "Invalid host joystick action: must be " "one of 'button', '+axis', '-axis', " "'L_hat', 'R_hat', 'U_hat', 'D_hat'"); } } } } static string getJoystickName(unsigned joyNum) { return string("joystick") + char('1' + joyNum); } static TclObject getConfigValue(SDL_Joystick* joystick) { TclObject value; value.addListElement("LEFT" ); value.addListElement("-axis0"); value.addListElement("RIGHT"); value.addListElement("+axis0"); value.addListElement("UP" ); value.addListElement("-axis1"); value.addListElement("DOWN" ); value.addListElement("+axis1"); TclObject listA, listB; for (auto i : xrange(InputEventGenerator::joystickNumButtons(joystick))) { string button = "button" + StringOp::toString(i); if (i & 1) { listB.addListElement(button); } else { listA.addListElement(button); } } value.addListElement("A"); value.addListElement(listA); value.addListElement("B"); value.addListElement(listB); return value; } // Note: It's OK to open/close the same SDL_Joystick multiple times (we open it // once per MSX machine). The SDL documentation doesn't state this, but I // checked the implementation and a SDL_Joystick uses a 'reference count' on // the open/close calls. Joystick::Joystick(MSXEventDistributor& eventDistributor_, StateChangeDistributor& stateChangeDistributor_, CommandController& commandController, GlobalSettings& globalSettings, SDL_Joystick* joystick_) : eventDistributor(eventDistributor_) , stateChangeDistributor(stateChangeDistributor_) , joystick(joystick_) , joyNum(SDL_JoystickIndex(joystick_)) , deadSetting(globalSettings.getJoyDeadzoneSetting(joyNum)) , name(getJoystickName(joyNum)) , desc(string(SDL_JoystickName(joyNum))) , configSetting(commandController, name + "_config", "joystick configuration", getConfigValue(joystick).getString()) { auto& interp = commandController.getInterpreter(); configSetting.setChecker([&interp](TclObject& newValue) { checkJoystickConfig(interp, newValue); }); pin8 = false; // avoid UMR } Joystick::~Joystick() { if (isPluggedIn()) { Joystick::unplugHelper(EmuTime::dummy()); } if (joystick) { SDL_JoystickClose(joystick); } } // Pluggable const string& Joystick::getName() const { return name; } string_ref Joystick::getDescription() const { return desc; } void Joystick::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/) { if (!joystick) { throw PlugException("Failed to open joystick device"); } plugHelper2(); status = calcState(); } void Joystick::plugHelper2() { eventDistributor.registerEventListener(*this); stateChangeDistributor.registerListener(*this); } void Joystick::unplugHelper(EmuTime::param /*time*/) { stateChangeDistributor.unregisterListener(*this); eventDistributor.unregisterEventListener(*this); } // JoystickDevice byte Joystick::read(EmuTime::param /*time*/) { return pin8 ? 0x3F : status; } void Joystick::write(byte value, EmuTime::param /*time*/) { pin8 = (value & 0x04) != 0; } byte Joystick::calcState() { byte result = JOY_UP | JOY_DOWN | JOY_LEFT | JOY_RIGHT | JOY_BUTTONA | JOY_BUTTONB; if (joystick) { int threshold = (deadSetting.getInt() * 32768) / 100; auto& interp = configSetting.getInterpreter(); auto& dict = configSetting.getValue(); if (getState(interp, dict, "A" , threshold)) result &= ~JOY_BUTTONA; if (getState(interp, dict, "B" , threshold)) result &= ~JOY_BUTTONB; if (getState(interp, dict, "UP" , threshold)) result &= ~JOY_UP; if (getState(interp, dict, "DOWN" , threshold)) result &= ~JOY_DOWN; if (getState(interp, dict, "LEFT" , threshold)) result &= ~JOY_LEFT; if (getState(interp, dict, "RIGHT", threshold)) result &= ~JOY_RIGHT; } return result; } bool Joystick::getState(Interpreter& interp, const TclObject& dict, string_ref key, int threshold) { try { const auto& list = dict.getDictValue(interp, TclObject(key)); for (auto i : xrange(list.getListLength(interp))) { const auto& elem = list.getListIndex(interp, i).getString(); if (elem.starts_with("button")) { unsigned n = fast_stou(elem.substr(6)); if (InputEventGenerator::joystickGetButton(joystick, n)) { return true; } } else if (elem.starts_with("+axis")) { unsigned n = fast_stou(elem.substr(5)); if (SDL_JoystickGetAxis(joystick, n) > threshold) { return true; } } else if (elem.starts_with("-axis")) { unsigned n = fast_stou(elem.substr(5)); if (SDL_JoystickGetAxis(joystick, n) < -threshold) { return true; } } else if (elem. starts_with("L_hat")) { unsigned n = fast_stou(elem.substr(5)); if (SDL_JoystickGetHat(joystick, n) & SDL_HAT_LEFT) { return true; } } else if (elem.starts_with("R_hat")) { unsigned n = fast_stou(elem.substr(5)); if (SDL_JoystickGetHat(joystick, n) & SDL_HAT_RIGHT) { return true; } } else if (elem.starts_with("U_hat")) { unsigned n = fast_stou(elem.substr(5)); if (SDL_JoystickGetHat(joystick, n) & SDL_HAT_UP) { return true; } } else if (elem.starts_with("D_hat")) { unsigned n = fast_stou(elem.substr(5)); if (SDL_JoystickGetHat(joystick, n) & SDL_HAT_DOWN) { return true; } } } } catch (...) { // Error, in getListLength()/getListIndex() or in fast_stou(). // In either case we can't do anything about it here, so ignore. } return false; } // MSXEventListener void Joystick::signalEvent(const shared_ptr& event, EmuTime::param time) { auto joyEvent = dynamic_cast(event.get()); if (!joyEvent) return; // TODO: It would be more efficient to make a dispatcher instead of // sending the event to all joysticks. if (joyEvent->getJoystick() != joyNum) return; // TODO: Currently this recalculates the whole joystick state. It might // be possible to implement this more efficiently by using the specific // event information. Though that's not trivial because e.g. multiple // host buttons can map to the same MSX button. Also calcState() // involves some string processing. It might be possible to only parse // the config once (per setting change). Though this solution is likely // good enough. createEvent(time, calcState()); } void Joystick::createEvent(EmuTime::param time, byte newStatus) { byte diff = status ^ newStatus; if (!diff) { // event won't actually change the status, so ignore it return; } // make sure we create an event with minimal changes byte press = status & diff; byte release = newStatus & diff; stateChangeDistributor.distributeNew(std::make_shared( time, joyNum, press, release)); } // StateChangeListener void Joystick::signalStateChange(const shared_ptr& event) { auto js = dynamic_cast(event.get()); if (!js) return; // TODO: It would be more efficient to make a dispatcher instead of // sending the event to all joysticks. // TODO an alternative is to log events based on the connector instead // of the joystick. That would make it possible to replay on a // different host without an actual SDL joystick connected. if (js->getJoystick() != joyNum) return; status = (status & ~js->getPress()) | js->getRelease(); } void Joystick::stopReplay(EmuTime::param time) { createEvent(time, calcState()); } // version 1: Initial version, the variable status was not serialized. // version 2: Also serialize the above variable, this is required for // record/replay, see comment in Keyboard.cc for more details. template void Joystick::serialize(Archive& ar, unsigned version) { if (ar.versionAtLeast(version, 2)) { ar.serialize("status", status); } if (ar.isLoader()) { if (joystick && isPluggedIn()) { plugHelper2(); } } // no need to serialize 'pin8' it's automatically restored via write() } INSTANTIATE_SERIALIZE_METHODS(Joystick); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, Joystick, "Joystick"); #endif // SDL_JOYSTICK_DISABLED } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/Joystick.hh000066400000000000000000000050331257557151200205060ustar00rootroot00000000000000#ifndef JOYSTICK_HH #define JOYSTICK_HH #include "JoystickDevice.hh" #include "MSXEventListener.hh" #include "StateChangeListener.hh" #include "StringSetting.hh" #include "serialize_meta.hh" #include namespace openmsx { class MSXEventDistributor; class StateChangeDistributor; class CommandController; class PluggingController; class GlobalSettings; class IntegerSetting; class TclObject; class Interpreter; /** Uses an SDL joystick to emulate an MSX joystick. */ class Joystick final #ifndef SDL_JOYSTICK_DISABLED : public JoystickDevice, private MSXEventListener, private StateChangeListener #endif { public: /** Register all available SDL joysticks. */ static void registerAll(MSXEventDistributor& eventDistributor, StateChangeDistributor& stateChangeDistributor, CommandController& commandController, GlobalSettings& globalSettings, PluggingController& controller); Joystick(MSXEventDistributor& eventDistributor, StateChangeDistributor& stateChangeDistributor, CommandController& commandController, GlobalSettings& globalSettings, SDL_Joystick* joystick); ~Joystick(); #ifndef SDL_JOYSTICK_DISABLED // Pluggable const std::string& getName() const override; string_ref getDescription() const override; void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; // JoystickDevice byte read(EmuTime::param time) override; void write(byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: void plugHelper2(); byte calcState(); bool getState(Interpreter& interp, const TclObject& dict, string_ref key, int threshold); void createEvent(EmuTime::param time, byte newStatus); // MSXEventListener void signalEvent(const std::shared_ptr& event, EmuTime::param time) override; // StateChangeListener void signalStateChange(const std::shared_ptr& event) override; void stopReplay(EmuTime::param time) override; MSXEventDistributor& eventDistributor; StateChangeDistributor& stateChangeDistributor; SDL_Joystick* const joystick; const unsigned joyNum; IntegerSetting& deadSetting; // must come after joyNum const std::string name; const std::string desc; StringSetting configSetting; byte status; bool pin8; #endif // SDL_JOYSTICK_DISABLED }; SERIALIZE_CLASS_VERSION(Joystick, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/JoystickDevice.cc000066400000000000000000000002241257557151200216110ustar00rootroot00000000000000#include "JoystickDevice.hh" namespace openmsx { string_ref JoystickDevice::getClass() const { return "Joystick Port"; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/JoystickDevice.hh000066400000000000000000000031441257557151200216270ustar00rootroot00000000000000#ifndef JOYSTICKDEVICE_HH #define JOYSTICKDEVICE_HH #include "Pluggable.hh" #include "openmsx.hh" namespace openmsx { class JoystickDevice : public Pluggable { public: /** * Read from the joystick device. The bits in the read byte have * following meaning: * 7 6 5 4 3 2 1 0 * | xx | xx | BUTTON_B | BUTTON_A | RIGHT | LEFT | DOWN | UP | * | xx | xx | pin7 | pin6 | pin4 | pin3 | pin2 | pin1| */ virtual byte read(EmuTime::param time) = 0; /** * Write a value to the joystick device. The bits in the written * byte have following meaning: * 7 6 5 4 3 2 1 0 * | xx | xx | xx | xx | xx | pin8 | pin7 | pin6 | * As an optimization, this method might not be called when the * new value is the same as the previous one. */ virtual void write(byte value, EmuTime::param time) = 0; string_ref getClass() const final override; /* Missing pin descriptions * pin 5 : +5V * pin 9 : GND */ // use in the read() method static const int JOY_UP = 0x01; static const int JOY_DOWN = 0x02; static const int JOY_LEFT = 0x04; static const int JOY_RIGHT = 0x08; static const int JOY_BUTTONA = 0x10; static const int JOY_BUTTONB = 0x20; static const int RD_PIN1 = 0x01; static const int RD_PIN2 = 0x02; static const int RD_PIN3 = 0x04; static const int RD_PIN4 = 0x08; static const int RD_PIN6 = 0x10; static const int RD_PIN7 = 0x20; // use in the write() method static const int WR_PIN6 = 0x01; static const int WR_PIN7 = 0x02; static const int WR_PIN8 = 0x04; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/JoystickPort.cc000066400000000000000000000037141257557151200213450ustar00rootroot00000000000000#include "JoystickPort.hh" #include "JoystickDevice.hh" #include "DummyJoystick.hh" #include "PluggingController.hh" #include "checked_cast.hh" #include "serialize.hh" #include "memory.hh" using std::string; namespace openmsx { JoystickPort::JoystickPort(PluggingController& pluggingController_, string_ref name, const string& description_) : Connector(pluggingController_, name, make_unique()) , lastValue(255) // != 0 , description(description_) { } JoystickPort::~JoystickPort() { } const string JoystickPort::getDescription() const { return description; } string_ref JoystickPort::getClass() const { return "Joystick Port"; } JoystickDevice& JoystickPort::getPluggedJoyDev() const { return *checked_cast(&getPlugged()); } void JoystickPort::plug(Pluggable& device, EmuTime::param time) { Connector::plug(device, time); getPluggedJoyDev().write(lastValue, time); } byte JoystickPort::read(EmuTime::param time) { return getPluggedJoyDev().read(time); } void JoystickPort::write(byte value, EmuTime::param time) { if (lastValue != value) writeDirect(value, time); } void JoystickPort::writeDirect(byte value, EmuTime::param time) { lastValue = value; getPluggedJoyDev().write(value, time); } template void JoystickPort::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); if (ar.isLoader()) { // The value of 'lastValue', is already restored via MSXPSG, // but we still need to re-write this value to the plugged // devices (do this after those devices have been re-plugged). writeDirect(lastValue, getPluggingController().getCurrentTime()); } } INSTANTIATE_SERIALIZE_METHODS(JoystickPort); // class DummyJoystickPort byte DummyJoystickPort::read(EmuTime::param /*time*/) { return 0x3F; // do as-if nothing is connected } void DummyJoystickPort::write(byte /*value*/, EmuTime::param /*time*/) { // ignore writes } } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/JoystickPort.hh000066400000000000000000000023751257557151200213610ustar00rootroot00000000000000#ifndef JOYSTICKPORT_HH #define JOYSTICKPORT_HH #include "Connector.hh" #include "openmsx.hh" namespace openmsx { class JoystickDevice; class PluggingController; class JoystickPortIf { public: virtual ~JoystickPortIf() {} virtual byte read(EmuTime::param time) = 0; virtual void write(byte value, EmuTime::param time) = 0; protected: JoystickPortIf() {} }; class JoystickPort final : public JoystickPortIf, public Connector { public: JoystickPort(PluggingController& pluggingController, string_ref name, const std::string& description); ~JoystickPort(); JoystickDevice& getPluggedJoyDev() const; // Connector const std::string getDescription() const override; string_ref getClass() const override; void plug(Pluggable& device, EmuTime::param time) override; byte read(EmuTime::param time) override; void write(byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: void writeDirect(byte value, EmuTime::param time); byte lastValue; const std::string description; }; class DummyJoystickPort final : public JoystickPortIf { public: byte read(EmuTime::param time) override; void write(byte value, EmuTime::param time) override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/KeyJoystick.cc000066400000000000000000000126751257557151200211570ustar00rootroot00000000000000#include "KeyJoystick.hh" #include "MSXEventDistributor.hh" #include "StateChangeDistributor.hh" #include "InputEvents.hh" #include "StateChange.hh" #include "checked_cast.hh" #include "serialize.hh" #include "serialize_meta.hh" using std::string; using std::shared_ptr; namespace openmsx { class KeyJoyState final : public StateChange { public: KeyJoyState() {} // for serialize KeyJoyState(EmuTime::param time, string name_, byte press_, byte release_) : StateChange(time) , name(std::move(name_)), press(press_), release(release_) {} const string& getName() const { return name; } byte getPress() const { return press; } byte getRelease() const { return release; } template void serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("name", name); ar.serialize("press", press); ar.serialize("release", release); } private: string name; byte press, release; }; REGISTER_POLYMORPHIC_CLASS(StateChange, KeyJoyState, "KeyJoyState"); KeyJoystick::KeyJoystick(CommandController& commandController, MSXEventDistributor& eventDistributor_, StateChangeDistributor& stateChangeDistributor_, const string& name_) : eventDistributor(eventDistributor_) , stateChangeDistributor(stateChangeDistributor_) , name(name_) , up (commandController, name + ".up", "key for direction up", Keys::K_UP) , down (commandController, name + ".down", "key for direction down", Keys::K_DOWN) , left (commandController, name + ".left", "key for direction left", Keys::K_LEFT) , right(commandController, name + ".right", "key for direction right", Keys::K_RIGHT) , trigA(commandController, name + ".triga", "key for trigger A", Keys::K_SPACE) , trigB(commandController, name + ".trigb", "key for trigger B", Keys::K_M) { status = JOY_UP | JOY_DOWN | JOY_LEFT | JOY_RIGHT | JOY_BUTTONA | JOY_BUTTONB; } KeyJoystick::~KeyJoystick() { if (isPluggedIn()) { KeyJoystick::unplugHelper(EmuTime::dummy()); } } // Pluggable const string& KeyJoystick::getName() const { return name; } string_ref KeyJoystick::getDescription() const { return "Key-Joystick, use your keyboard to emulate an MSX joystick. " "See manual for information on how to configure this."; } void KeyJoystick::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/) { eventDistributor.registerEventListener(*this); stateChangeDistributor.registerListener(*this); } void KeyJoystick::unplugHelper(EmuTime::param /*time*/) { stateChangeDistributor.unregisterListener(*this); eventDistributor.unregisterEventListener(*this); } // KeyJoystickDevice byte KeyJoystick::read(EmuTime::param /*time*/) { return pin8 ? 0x3F : status; } void KeyJoystick::write(byte value, EmuTime::param /*time*/) { pin8 = (value & 0x04) != 0; } // MSXEventListener void KeyJoystick::signalEvent(const shared_ptr& event, EmuTime::param time) { byte press = 0; byte release = 0; switch (event->getType()) { case OPENMSX_KEY_DOWN_EVENT: case OPENMSX_KEY_UP_EVENT: { auto& keyEvent = checked_cast(*event); auto key = static_cast( int(keyEvent.getKeyCode()) & int(Keys::K_MASK)); if (event->getType() == OPENMSX_KEY_DOWN_EVENT) { if (key == up .getKey()) press = JOY_UP; else if (key == down .getKey()) press = JOY_DOWN; else if (key == left .getKey()) press = JOY_LEFT; else if (key == right.getKey()) press = JOY_RIGHT; else if (key == trigA.getKey()) press = JOY_BUTTONA; else if (key == trigB.getKey()) press = JOY_BUTTONB; } else { if (key == up .getKey()) release = JOY_UP; else if (key == down .getKey()) release = JOY_DOWN; else if (key == left .getKey()) release = JOY_LEFT; else if (key == right.getKey()) release = JOY_RIGHT; else if (key == trigA.getKey()) release = JOY_BUTTONA; else if (key == trigB.getKey()) release = JOY_BUTTONB; } break; } default: // ignore break; } if (((status & ~press) | release) != status) { stateChangeDistributor.distributeNew(std::make_shared( time, name, press, release)); } } // StateChangeListener void KeyJoystick::signalStateChange(const shared_ptr& event) { auto kjs = dynamic_cast(event.get()); if (!kjs) return; if (kjs->getName() != name) return; status = (status & ~kjs->getPress()) | kjs->getRelease(); } void KeyJoystick::stopReplay(EmuTime::param time) { // TODO read actual host key state byte newStatus = JOY_UP | JOY_DOWN | JOY_LEFT | JOY_RIGHT | JOY_BUTTONA | JOY_BUTTONB; if (newStatus != status) { byte release = newStatus & ~status; stateChangeDistributor.distributeNew(std::make_shared( time, name, 0, release)); } } // version 1: Initial version, the variable status was not serialized. // version 2: Also serialize the above variable, this is required for // record/replay, see comment in Keyboard.cc for more details. template void KeyJoystick::serialize(Archive& ar, unsigned version) { if (ar.versionAtLeast(version, 2)) { ar.serialize("status", status); } if (ar.isLoader() && isPluggedIn()) { plugHelper(*getConnector(), EmuTime::dummy()); } // no need to serialize 'pin8' } INSTANTIATE_SERIALIZE_METHODS(KeyJoystick); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, KeyJoystick, "KeyJoystick"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/KeyJoystick.hh000066400000000000000000000032761257557151200211660ustar00rootroot00000000000000#ifndef KEYJOYSTICK_HH #define KEYJOYSTICK_HH #include "JoystickDevice.hh" #include "MSXEventListener.hh" #include "StateChangeListener.hh" #include "KeyCodeSetting.hh" #include "serialize_meta.hh" namespace openmsx { class CommandController; class MSXEventDistributor; class StateChangeDistributor; class KeyJoystick final : public JoystickDevice, private MSXEventListener , private StateChangeListener { public: KeyJoystick(CommandController& commandController, MSXEventDistributor& eventDistributor, StateChangeDistributor& stateChangeDistributor, const std::string& name); ~KeyJoystick(); template void serialize(Archive& ar, unsigned version); private: // Pluggable const std::string& getName() const override; string_ref getDescription() const override; void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; // KeyJoystickDevice byte read(EmuTime::param time) override; void write(byte value, EmuTime::param time) override; // MSXEventListener void signalEvent(const std::shared_ptr& event, EmuTime::param time) override; // StateChangeListener void signalStateChange(const std::shared_ptr& event) override; void stopReplay(EmuTime::param time) override; MSXEventDistributor& eventDistributor; StateChangeDistributor& stateChangeDistributor; const std::string name; KeyCodeSetting up; KeyCodeSetting down; KeyCodeSetting left; KeyCodeSetting right; KeyCodeSetting trigA; KeyCodeSetting trigB; byte status; bool pin8; }; SERIALIZE_CLASS_VERSION(KeyJoystick, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/Keyboard.cc000066400000000000000000001342101257557151200204350ustar00rootroot00000000000000#include "Keyboard.hh" #include "Keys.hh" #include "EventDistributor.hh" #include "InputEventFactory.hh" #include "MSXEventDistributor.hh" #include "StateChangeDistributor.hh" #include "MSXMotherBoard.hh" #include "ReverseManager.hh" #include "CommandController.hh" #include "CommandException.hh" #include "InputEvents.hh" #include "StateChange.hh" #include "utf8_checked.hh" #include "checked_cast.hh" #include "unreachable.hh" #include "serialize.hh" #include "serialize_stl.hh" #include "serialize_meta.hh" #include "openmsx.hh" #include "outer.hh" #include "stl.hh" #include #include #include #include #include using std::string; using std::vector; using std::shared_ptr; using std::make_shared; namespace openmsx { static const byte SHIFT_MASK = 0x01; static const byte CTRL_MASK = 0x02; static const byte GRAPH_MASK = 0x04; static const byte CAPS_MASK = 0x08; static const byte CODE_MASK = 0x10; class KeyMatrixState final : public StateChange { public: KeyMatrixState() {} // for serialize KeyMatrixState(EmuTime::param time, byte row_, byte press_, byte release_) : StateChange(time) , row(row_), press(press_), release(release_) { // disallow useless events assert((press != 0) || (release != 0)); // avoid confusion about what happens when some bits are both // set and reset (in other words: don't rely on order of and- // and or-operations) assert((press & release) == 0); } byte getRow() const { return row; } byte getPress() const { return press; } byte getRelease() const { return release; } template void serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("row", row); ar.serialize("press", press); ar.serialize("release", release); } private: byte row, press, release; }; REGISTER_POLYMORPHIC_CLASS(StateChange, KeyMatrixState, "KeyMatrixState"); static bool checkSDLReleasesCapslock() { const SDL_version* v = SDL_Linked_Version(); if (SDL_VERSIONNUM(v->major, v->minor, v->patch) < SDL_VERSIONNUM(1, 2, 14)) { // Feature was introduced in SDL 1.2.14. return false; } else { // Check whether feature was enabled by envvar. char *val = SDL_getenv("SDL_DISABLE_LOCK_KEYS"); return val && (strcmp(val, "1") == 0 || strcmp(val, "2") == 0); } } Keyboard::Keyboard(MSXMotherBoard& motherBoard, Scheduler& scheduler, CommandController& commandController_, EventDistributor& eventDistributor, MSXEventDistributor& msxEventDistributor_, StateChangeDistributor& stateChangeDistributor_, string_ref keyboardType, bool hasKP, bool hasYNKeys, bool keyGhosting_, bool keyGhostSGCprotected, bool codeKanaLocks_, bool graphLocks_) : Schedulable(scheduler) , commandController(commandController_) , msxEventDistributor(msxEventDistributor_) , stateChangeDistributor(stateChangeDistributor_) , keyMatrixUpCmd (commandController, stateChangeDistributor, scheduler) , keyMatrixDownCmd(commandController, stateChangeDistributor, scheduler) , keyTypeCmd (commandController, stateChangeDistributor, scheduler) , capsLockAligner(eventDistributor, scheduler) , keyboardSettings(commandController) , msxKeyEventQueue(scheduler, commandController.getInterpreter()) , keybDebuggable(motherBoard) , unicodeKeymap(keyboardType) , hasKeypad(hasKP) , hasYesNoKeys(hasYNKeys) , keyGhosting(keyGhosting_) , keyGhostingSGCprotected(keyGhostSGCprotected) , codeKanaLocks(codeKanaLocks_) , graphLocks(graphLocks_) , sdlReleasesCapslock(checkSDLReleasesCapslock()) { // SDL version >= 1.2.14 releases caps-lock key when SDL_DISABLED_LOCK_KEYS // environment variable is already set in main.cc (because here it // would be too late) keysChanged = false; msxCapsLockOn = false; msxCodeKanaLockOn = false; msxGraphLockOn = false; msxmodifiers = 0xff; memset(keyMatrix, 255, sizeof(keyMatrix)); memset(cmdKeyMatrix, 255, sizeof(cmdKeyMatrix)); memset(userKeyMatrix, 255, sizeof(userKeyMatrix)); memset(hostKeyMatrix, 255, sizeof(hostKeyMatrix)); memset(dynKeymap, 0, sizeof(dynKeymap)); msxEventDistributor.registerEventListener(*this); stateChangeDistributor.registerListener(*this); // We do not listen for CONSOLE_OFF_EVENTS because rescanning the // keyboard can have unwanted side effects motherBoard.getReverseManager().registerKeyboard(*this); } Keyboard::~Keyboard() { stateChangeDistributor.unregisterListener(*this); msxEventDistributor.unregisterEventListener(*this); } const byte* Keyboard::getKeys() { if (keysChanged) { keysChanged = false; for (unsigned i = 0; i < NR_KEYROWS; ++i) { keyMatrix[i] = cmdKeyMatrix[i] & userKeyMatrix[i]; } if (keyGhosting) { doKeyGhosting(); } } return keyMatrix; } void Keyboard::transferHostKeyMatrix(const Keyboard& source) { // This mechanism exists to solve the following problem: // - play a game where the spacebar is constantly pressed (e.g. // Road Fighter) // - go back in time (press the reverse hotkey) while keeping the // spacebar pressed // - interrupt replay by pressing the cursor keys, still while // keeping spacebar pressed // At the moment replay is interrupted, we need to resynchronize the // msx keyboard with the host keyboard. In the past we assumed the host // keyboard had no keys pressed. But this is wrong in the above // scenario. Now we remember the state of the host keyboard and // transfer that to the new keyboard(s) that get created for reverese. // When replay is stopped we restore this host keyboard state, see // stopReplay(). for (unsigned row = 0; row < NR_KEYROWS; ++row) { hostKeyMatrix[row] = source.hostKeyMatrix[row]; } } /* Received an MSX event * Following events get processed: * OPENMSX_KEY_DOWN_EVENT * OPENMSX_KEY_UP_EVENT */ void Keyboard::signalEvent(const shared_ptr& event, EmuTime::param time) { EventType type = event->getType(); if ((type == OPENMSX_KEY_DOWN_EVENT) || (type == OPENMSX_KEY_UP_EVENT)) { // Ignore possible console on/off events: // we do not rescan the keyboard since this may lead to // an unwanted pressing of in MSX after typing // "set console off" in the console. msxKeyEventQueue.process_asap(time, event); } } void Keyboard::signalStateChange(const shared_ptr& event) { auto kms = dynamic_cast(event.get()); if (!kms) return; userKeyMatrix[kms->getRow()] &= ~kms->getPress(); userKeyMatrix[kms->getRow()] |= kms->getRelease(); keysChanged = true; // do ghosting at next getKeys() } void Keyboard::stopReplay(EmuTime::param time) { for (unsigned row = 0; row < NR_KEYROWS; ++row) { changeKeyMatrixEvent(time, row, hostKeyMatrix[row]); } msxmodifiers = 0xff; msxKeyEventQueue.clear(); memset(dynKeymap, 0, sizeof(dynKeymap)); } void Keyboard::pressKeyMatrixEvent(EmuTime::param time, byte row, byte press) { assert(press); if (((hostKeyMatrix[row] & press) == 0) && ((userKeyMatrix[row] & press) == 0)) { // Won't have any effect, ignore. return; } changeKeyMatrixEvent(time, row, hostKeyMatrix[row] & ~press); } void Keyboard::releaseKeyMatrixEvent(EmuTime::param time, byte row, byte release) { assert(release); if (((hostKeyMatrix[row] & release) == release) && ((userKeyMatrix[row] & release) == release)) { // Won't have any effect, ignore. // Test scenario: during replay, exit the openmsx console with // the 'toggle console' command. The 'enter,release' event will // end up here. But it shouldn't stop replay. return; } changeKeyMatrixEvent(time, row, hostKeyMatrix[row] | release); } void Keyboard::changeKeyMatrixEvent(EmuTime::param time, byte row, byte newValue) { // This method already updates hostKeyMatrix[], // userKeyMatrix[] will soon be updated via KeyMatrixState events. hostKeyMatrix[row] = newValue; byte diff = userKeyMatrix[row] ^ newValue; if (diff == 0) return; byte press = userKeyMatrix[row] & diff; byte release = newValue & diff; stateChangeDistributor.distributeNew(make_shared( time, row, press, release)); } bool Keyboard::processQueuedEvent(const Event& event, EmuTime::param time) { bool insertCodeKanaRelease = false; auto& keyEvent = checked_cast(event); bool down = event.getType() == OPENMSX_KEY_DOWN_EVENT; auto key = static_cast( int(keyEvent.getKeyCode()) & int(Keys::K_MASK)); if (down) { // TODO: refactor debug(...) method to expect a std::string and then adapt // all invocations of it to provide a properly formatted string, using the C++ // features for it. // Once that is done, debug(...) can pass the c_str() version of that string // to ad_printf(...) so that I don't have to make an explicit ad_printf(...) // invocation for each debug(...) invocation ad_printf("Key pressed, unicode: 0x%04x, keyCode: 0x%05x, keyName: %s\n", keyEvent.getUnicode(), keyEvent.getKeyCode(), Keys::getName(keyEvent.getKeyCode()).c_str()); debug("Key pressed, unicode: 0x%04x, keyCode: 0x%05x, keyName: %s\n", keyEvent.getUnicode(), keyEvent.getKeyCode(), Keys::getName(keyEvent.getKeyCode()).c_str()); } else { ad_printf("Key released, unicode: 0x%04x, keyCode: 0x%05x, keyName: %s\n", keyEvent.getUnicode(), keyEvent.getKeyCode(), Keys::getName(keyEvent.getKeyCode()).c_str()); debug("Key released, unicode: 0x%04x, keyCode: 0x%05x, keyName: %s\n", keyEvent.getUnicode(), keyEvent.getKeyCode(), Keys::getName(keyEvent.getKeyCode()).c_str()); } if (key == keyboardSettings.getDeadkeyHostKey(0) && keyboardSettings.getMappingMode() == KeyboardSettings::CHARACTER_MAPPING) { processDeadKeyEvent(0, time, down); } else if (key == keyboardSettings.getDeadkeyHostKey(1) && keyboardSettings.getMappingMode() == KeyboardSettings::CHARACTER_MAPPING) { processDeadKeyEvent(1, time, down); } else if (key == keyboardSettings.getDeadkeyHostKey(2) && keyboardSettings.getMappingMode() == KeyboardSettings::CHARACTER_MAPPING) { processDeadKeyEvent(2, time, down); } else if (key == Keys::K_CAPSLOCK) { processCapslockEvent(time, down); } else if (key == keyboardSettings.getCodeKanaHostKey()) { processCodeKanaChange(time, down); } else if (key == Keys::K_LALT) { processGraphChange(time, down); } else if (key == Keys::K_KP_ENTER) { processKeypadEnterKey(time, down); } else { insertCodeKanaRelease = processKeyEvent(time, down, keyEvent); } return insertCodeKanaRelease; } /* * Process a change (up or down event) of the CODE/KANA key * It presses or releases the key in the MSX keyboard matrix * and changes the kanalock state in case of a press */ void Keyboard::processCodeKanaChange(EmuTime::param time, bool down) { if (down) { msxCodeKanaLockOn = !msxCodeKanaLockOn; } updateKeyMatrix(time, down, 6, CODE_MASK); } /* * Process a change (up or down event) of the GRAPH key * It presses or releases the key in the MSX keyboard matrix * and changes the graphlock state in case of a press */ void Keyboard::processGraphChange(EmuTime::param time, bool down) { if (down) { msxGraphLockOn = !msxGraphLockOn; } updateKeyMatrix(time, down, 6, GRAPH_MASK); } /* * Process deadkey N by pressing or releasing the deadkey * at the correct location in the keyboard matrix */ void Keyboard::processDeadKeyEvent(unsigned n, EmuTime::param time, bool down) { UnicodeKeymap::KeyInfo deadkey = unicodeKeymap.getDeadkey(n); if (deadkey.keymask) { updateKeyMatrix(time, down, deadkey.row, deadkey.keymask); } } /* * Process a change event of the CAPSLOCK *STATUS*; * SDL up to version 1.2.13 sends a CAPSLOCK press event at the moment that * the host CAPSLOCK status goes 'on' and it sends the release event only when * the host CAPSLOCK status goes 'off'. However, the emulated MSX must see a * press and release event when CAPSLOCK status goes on and another press and * release event when it goes off again. This is achieved by pressing CAPSLOCK * key at the moment that the host CAPSLOCK status changes and releasing the * CAPSLOCK key shortly after (via a timed event) * * SDL as of version 1.2.14 can send a press and release event at the moment * that the user presses and releases the CAPS lock. Though, this changed * behaviour is only enabled when a special environment variable is set. * * This version of openMSX supports both behaviours; when SDL version is at * least 1.2.14, it will set the environment variable to trigger the new * behaviour and simply process the press and release events as they come in. * For older SDL versions, it will still treat each change as a press that must * be followed by a scheduled release event */ void Keyboard::processCapslockEvent(EmuTime::param time, bool down) { if (sdlReleasesCapslock) { debug("Changing CAPS lock state according to SDL request\n"); if (down) { msxCapsLockOn = !msxCapsLockOn; } updateKeyMatrix(time, down, 6, CAPS_MASK); } else { debug("Pressing CAPS lock and scheduling a release\n"); msxCapsLockOn = !msxCapsLockOn; updateKeyMatrix(time, true, 6, CAPS_MASK); setSyncPoint(time + EmuDuration::hz(10)); // 0.1s (in MSX time) } } void Keyboard::executeUntil(EmuTime::param time) { debug("Releasing CAPS lock\n"); updateKeyMatrix(time, false, 6, CAPS_MASK); } void Keyboard::processKeypadEnterKey(EmuTime::param time, bool down) { if (!hasKeypad && !keyboardSettings.getAlwaysEnableKeypad()) { // User entered on host keypad but this MSX model does not have one // Ignore the keypress/release return; } int row; byte mask; if (keyboardSettings.getKpEnterMode() == KeyboardSettings::MSX_KP_COMMA) { row = 10; mask = 0x40; } else { row = 7; mask = 0x80; } updateKeyMatrix(time, down, row, mask); } /* * Process an SDL key press/release event. It concerns a * special key (e.g. SHIFT, UP, DOWN, F1, F2, ...) that can not * be unambiguously derived from a unicode character; * Map the SDL key to an equivalent MSX key press/release event */ void Keyboard::processSdlKey(EmuTime::param time, bool down, int key) { if (key < MAX_KEYSYM) { int row = keyTab[key] >> 4; byte mask = 1 << (keyTab[key] & 0xf); if (keyTab[key] == 0xff) assert(mask == 0); if ((row == 11) && !hasYesNoKeys) { // do not process row 11 if we have no Yes/No keys return; } if (mask) { updateKeyMatrix(time, down, row, mask); } } } /* * Update the MSX keyboard matrix */ void Keyboard::updateKeyMatrix(EmuTime::param time, bool down, int row, byte mask) { assert(mask); if (down) { pressKeyMatrixEvent(time, row, mask); if (row == 6) { // Keep track of the MSX modifiers (CTRL, GRAPH, CODE, SHIFT) // The MSX modifiers in row 6 of the matrix sometimes get // overruled by the unicode character processing, in // which case the unicode processing must be able to restore // them to the real key-combinations pressed by the user msxmodifiers &= ~(mask & 0x17); } } else { releaseKeyMatrixEvent(time, row, mask); if (row == 6) { msxmodifiers |= (mask & 0x17); } } } /* * Process an SDL key event; * Check if it is a special key, in which case it can be directly * mapped to the MSX matrix. * Otherwise, retrieve the unicode character value for the event * and map the unicode character to the key-combination that must * be pressed to generate the equivalent character on the MSX */ bool Keyboard::processKeyEvent(EmuTime::param time, bool down, const KeyEvent& keyEvent) { bool insertCodeKanaRelease = false; Keys::KeyCode keyCode = keyEvent.getKeyCode(); auto key = static_cast( int(keyCode) & int(Keys::K_MASK)); unsigned unicode; bool isOnKeypad = ( (key >= Keys::K_KP0 && key <= Keys::K_KP9) || (key == Keys::K_KP_PERIOD) || (key == Keys::K_KP_DIVIDE) || (key == Keys::K_KP_MULTIPLY) || (key == Keys::K_KP_MINUS) || (key == Keys::K_KP_PLUS)); if (isOnKeypad && !hasKeypad && !keyboardSettings.getAlwaysEnableKeypad()) { // User entered on host keypad but this MSX model does not have one // Ignore the keypress/release return false; } if (down) { if (/*___(userKeyMatrix[6] & 2) == 0 || */ isOnKeypad || keyboardSettings.getMappingMode() == KeyboardSettings::KEY_MAPPING) { // /*CTRL-key is active,*/ user entered a key on numeric // keypad or the driver is in KEY mapping mode. // First /*two*/ option/*s*/ (/*CTRL key active,*/ keypad keypress) maps // to same unicode as some other key combinations (e.g. digit // on main keyboard or TAB/DEL) // Use unicode to handle the more common combination // and use direct matrix to matrix mapping for the exceptional // cases (/*CTRL+character or*/ numeric keypad usage) unicode = 0; #if defined(__APPLE__) } else if ((keyCode & (Keys::K_MASK | Keys::KM_META)) == (Keys::K_I | Keys::KM_META)) { // Apple keyboards don't have an Insert key, use Cmd+I as an alternative. keyCode = key = Keys::K_INSERT; unicode = 0; #endif } else { unicode = keyEvent.getUnicode(); if ((unicode < 0x20) || ((0x7F <= unicode) && (unicode < 0xA0))) { // Control character in C0 or C1 range. // Use SDL's interpretation instead. unicode = 0; } else if ((0xE000 <= unicode) && (unicode < 0xF900)) { // Code point in Private Use Area: undefined by Unicode, // so we rely on SDL's interpretation instead. // For example the Mac's cursor keys are in this range. unicode = 0; } } if (key < MAX_KEYSYM) { // Remember which unicode character is currently derived // from this SDL key. It must be stored here (during key-press) // because during key-release SDL never returns the unicode // value (it always returns the value 0). But we must know // the unicode value in order to be able to perform the correct // key-combination-release in the MSX dynKeymap[key] = unicode; } else { // Unexpectedly high key-code. Can't store the unicode // character for this key. Instead directly treat the key // via matrix to matrix mapping unicode = 0; } if (unicode == 0) { // It was an ambiguous key (numeric key-pad, CTRL+character) // or a special key according to SDL (like HOME, INSERT, etc) // or a first keystroke of a composed key // (e.g. altr-gr + = on azerty keyboard) or driver is in // direct SDL mapping mode: // Perform direct SDL matrix to MSX matrix mapping // But only when it is not a first keystroke of a // composed key if ((keyCode & Keys::KM_MODE) == 0) { processSdlKey(time, down, key); } } else { // It is a unicode character; map it to the right key-combination insertCodeKanaRelease = pressUnicodeByUser(time, unicode, true); } } else { // key was released #if defined(__APPLE__) if ((keyCode & (Keys::K_MASK | Keys::KM_META)) == (Keys::K_I | Keys::KM_META)) { keyCode = key = Keys::K_INSERT; } #endif if (key < MAX_KEYSYM) { unicode = dynKeymap[key]; // Get the unicode that was derived from this key } else { unicode = 0; } if (unicode == 0) { // It was a special key, perform matrix to matrix mapping // But only when it is not a first keystroke of a // composed key if ((keyCode & Keys::KM_MODE) == 0) { processSdlKey(time, down, key); } } else { // It was a unicode character; map it to the right key-combination pressUnicodeByUser(time, unicode, false); } } return insertCodeKanaRelease; } void Keyboard::doKeyGhosting() { // This routine enables keyghosting as seen on a real MSX // // If on a real MSX in the keyboardmatrix the // real buttons are pressed as in the left matrix // then the matrix to the // 10111111 right will be read by 10110101 // 11110101 because of the simple 10110101 // 10111101 electrical connections 10110101 // that are established by // the closed switches // However, some MSX models have protection against // key-ghosting for SHIFT, GRAPH and CODE keys // On those models, SHIFT, GRAPH and CODE are // connected to row 6 via a diode. It prevents that // SHIFT, GRAPH and CODE get ghosted to another // row. bool changedSomething; do { changedSomething = false; for (unsigned i = 0; i < NR_KEYROWS - 1; i++) { byte row1 = keyMatrix[i]; for (unsigned j = i + 1; j < NR_KEYROWS; j++) { byte row2 = keyMatrix[j]; if ((row1 != row2) && ((row1 | row2) != 0xff)) { byte rowIold = keyMatrix[i]; byte rowJold = keyMatrix[j]; if (keyGhostingSGCprotected && i == 6) { keyMatrix[i] = row1 & row2; keyMatrix[j] = (row1 | 0x15) & row2; row1 &= row2; } else if (keyGhostingSGCprotected && j == 6) { keyMatrix[i] = row1 & (row2 | 0x15); keyMatrix[j] = row1 & row2; row1 &= (row2 | 0x15); } else { // not same and some common zero's // --> inherit other zero's byte newRow = row1 & row2; keyMatrix[i] = newRow; keyMatrix[j] = newRow; row1 = newRow; } if (rowIold != keyMatrix[i] || rowJold != keyMatrix[j]) { changedSomething = true; } } } } } while (changedSomething); } void Keyboard::processCmd(Interpreter& interp, array_ref tokens, bool up) { if (tokens.size() != 3) { throw SyntaxError(); } unsigned row = tokens[1].getInt(interp); unsigned mask = tokens[2].getInt(interp); if (row >= NR_KEYROWS) { throw CommandException("Invalid row"); } if (mask >= 256) { throw CommandException("Invalid mask"); } if (up) { cmdKeyMatrix[row] |= mask; } else { cmdKeyMatrix[row] &= ~mask; } keysChanged = true; } /* * This routine processes unicode characters. It maps a unicode character * to the correct key-combination on the MSX. * * There are a few caveats with respect to the MSX and Host modifier keys * that you must be aware about if you want to understand why the routine * works as it works. * * Row 6 of the MSX keyboard matrix contains the MSX modifier keys: * CTRL, CODE, GRAPH and SHIFT * * The SHIFT key is also a modifier key on the host machine. However, the * SHIFT key behaviour can differ between HOST and MSX for all 'special' * characters (anything but A-Z). * For example, on AZERTY host keyboard, user presses SHIFT+& to make the '1' * On MSX QWERTY keyboard, the same key-combination leads to '!'. * So this routine must not only PRESS the SHIFT key when required according * to the unicode mapping table but it must also RELEASE the SHIFT key for all * these special keys when the user PRESSES the key/character. * * On the other hand, for A-Z, this routine must not touch the SHIFT key at all. * Otherwise it might give strange behaviour when CAPS lock is on (which also * acts as a key-modifier for A-Z). The routine can rely on the fact that * SHIFT+A-Z behaviour is the same on all host and MSX keyboards. It is * approximately the only part of keyboards that is de-facto standardized :-) * * For the other modifiers (CTRL, CODE and GRAPH), the routine must be able to * PRESS them when required but there is no need to RELEASE them during * character press. On the contrary; the host keys that map to CODE and GRAPH * do not work as modifiers on the host itself, so if the routine would release * them, it would give wrong result. * For example, 'ALT-A' on Host will lead to unicode character 'a', just like * only pressing the 'A' key. The MSX however must know about the difference. * * As a reminder: here is the build-up of row 6 of the MSX key matrix * 7 6 5 4 3 2 1 0 * row 6 | F3 | F2 | F1 | code| caps|graph| ctrl|shift| */ bool Keyboard::pressUnicodeByUser(EmuTime::param time, unsigned unicode, bool down) { bool insertCodeKanaRelease = false; UnicodeKeymap::KeyInfo keyInfo = unicodeKeymap.get(unicode); if (keyInfo.keymask == 0) { return insertCodeKanaRelease; } if (down) { if (codeKanaLocks && keyboardSettings.getAutoToggleCodeKanaLock() && msxCodeKanaLockOn != ((keyInfo.modmask & CODE_MASK) == CODE_MASK) && keyInfo.row < 6) { // only toggle CODE lock for 'normal' characters // Code Kana locks, is in wrong state and must be auto-toggled: // Toggle it by pressing the lock key and scheduling a // release event msxCodeKanaLockOn = !msxCodeKanaLockOn; pressKeyMatrixEvent(time, 6, CODE_MASK); insertCodeKanaRelease = true; } else { // Press the character key and related modifiers // Ignore the CODE key in case that Code Kana locks // (e.g. do not press it). // Ignore the GRAPH key in case that Graph locks // Always ignore CAPSLOCK mask (assume that user will // use real CAPS lock to switch/ between hiragana and // katanana on japanese model) assert(keyInfo.keymask); pressKeyMatrixEvent(time, keyInfo.row, keyInfo.keymask); byte modmask = keyInfo.modmask & ~CAPS_MASK; if (codeKanaLocks) modmask &= ~CODE_MASK; if (graphLocks) modmask &= ~GRAPH_MASK; if (('A' <= unicode && unicode <= 'Z') || ('a' <= unicode && unicode <= 'z')) { // for a-z and A-Z, leave shift unchanged, this to cater // for difference in behaviour between host and emulated // machine with respect to how the combination of CAPSLOCK // and shift-key is interpreted for these characters. // Note that other modifiers are only pressed, never released byte press = modmask & ~SHIFT_MASK; if (press) { pressKeyMatrixEvent(time, 6, press); } } else { // for other keys, set shift according to modmask // so also release shift when required (other // modifiers are only pressed, never released) byte newRow = (userKeyMatrix[6] | SHIFT_MASK) & ~modmask; changeKeyMatrixEvent(time, 6, newRow); } } } else { assert(keyInfo.keymask); releaseKeyMatrixEvent(time, keyInfo.row, keyInfo.keymask); // Do not simply unpress graph, ctrl, code and shift but // restore them to the values currently pressed by the user byte mask = SHIFT_MASK | CTRL_MASK; if (!codeKanaLocks) mask |= CODE_MASK; if (!graphLocks) mask |= GRAPH_MASK; byte newRow = userKeyMatrix[6]; newRow &= msxmodifiers | ~mask; newRow |= msxmodifiers & mask; changeKeyMatrixEvent(time, 6, newRow); } keysChanged = true; return insertCodeKanaRelease; } /* * Press an ASCII character. It is used by the 'Insert characters' * function that is exposed to the console. * The characters are inserted in a separate keyboard matrix, to prevent * interference with the keypresses of the user on the MSX itself */ int Keyboard::pressAscii(unsigned unicode, bool down) { int releaseMask = 0; UnicodeKeymap::KeyInfo keyInfo = unicodeKeymap.get(unicode); byte modmask = keyInfo.modmask & (~CAPS_MASK); // ignore CAPSLOCK mask; if (codeKanaLocks) { modmask &= (~CODE_MASK); // ignore CODE mask if CODE locks } if (graphLocks) { modmask &= (~GRAPH_MASK); // ignore GRAPH mask if GRAPH locks } if (down) { if (codeKanaLocks && msxCodeKanaLockOn != ((keyInfo.modmask & CODE_MASK) == CODE_MASK) && keyInfo.row < 6) { // only toggle CODE lock for 'normal' characters debug("Toggling CODE/KANA lock\n"); msxCodeKanaLockOn = !msxCodeKanaLockOn; cmdKeyMatrix[6] &= (~CODE_MASK); releaseMask = CODE_MASK; } if (graphLocks && msxGraphLockOn != ((keyInfo.modmask & GRAPH_MASK) == GRAPH_MASK) && keyInfo.row < 6) { // only toggle GRAPH lock for 'normal' characters debug("Toggling GRAPH lock\n"); msxGraphLockOn = !msxGraphLockOn; cmdKeyMatrix[6] &= (~GRAPH_MASK); releaseMask |= GRAPH_MASK; } if (msxCapsLockOn != ((keyInfo.modmask & CAPS_MASK) == CAPS_MASK) && keyInfo.row < 6) { // only toggle CAPS lock for 'normal' characters debug("Toggling CAPS lock\n"); msxCapsLockOn = !msxCapsLockOn; cmdKeyMatrix[6] &= (~CAPS_MASK); releaseMask |= CAPS_MASK; } if (releaseMask == 0) { debug("Key pasted, unicode: 0x%04x, row: %02d, mask: %02x, modmask: %02x\n", unicode, keyInfo.row, keyInfo.keymask, modmask); cmdKeyMatrix[keyInfo.row] &= ~keyInfo.keymask; cmdKeyMatrix[6] &= ~modmask; } } else { cmdKeyMatrix[keyInfo.row] |= keyInfo.keymask; cmdKeyMatrix[6] |= modmask; } keysChanged = true; return releaseMask; } /* * Press a lock key. It is used by the 'Insert characters' * function that is exposed to the console. * The characters are inserted in a separate keyboard matrix, to prevent * interference with the keypresses of the user on the MSX itself */ void Keyboard::pressLockKeys(int lockKeysMask, bool down) { if (down) { // press CAPS and/or CODE/KANA lock key cmdKeyMatrix[6] &= (~lockKeysMask); } else { // release CAPS and/or CODE/KANA lock key cmdKeyMatrix[6] |= lockKeysMask; } keysChanged = true; } /* * Check if there are common keys in the MSX matrix for * two different unicodes. * It is used by the 'insert keys' function to determine if it has to wait for * a short while after releasing a key (to enter a certain character) before * pressing the next key (to enter the next character) */ bool Keyboard::commonKeys(unsigned unicode1, unsigned unicode2) { // get row / mask of key (note: ignore modifier mask) UnicodeKeymap::KeyInfo keyInfo1 = unicodeKeymap.get(unicode1); UnicodeKeymap::KeyInfo keyInfo2 = unicodeKeymap.get(unicode2); return ((keyInfo1.row == keyInfo2.row) && (keyInfo1.keymask & keyInfo2.keymask)); } void Keyboard::debug(const char* format, ...) { if (keyboardSettings.getTraceKeyPresses()) { va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); } } // class KeyMatrixUpCmd Keyboard::KeyMatrixUpCmd::KeyMatrixUpCmd( CommandController& commandController, StateChangeDistributor& stateChangeDistributor, Scheduler& scheduler) : RecordedCommand(commandController, stateChangeDistributor, scheduler, "keymatrixup") { } void Keyboard::KeyMatrixUpCmd::execute( array_ref tokens, TclObject& /*result*/, EmuTime::param /*time*/) { auto& keyboard = OUTER(Keyboard, keyMatrixUpCmd); return keyboard.processCmd(getInterpreter(), tokens, true); } string Keyboard::KeyMatrixUpCmd::help(const vector& /*tokens*/) const { static const string helpText = "keymatrixup release a key in the keyboardmatrix\n"; return helpText; } // class KeyMatrixDownCmd Keyboard::KeyMatrixDownCmd::KeyMatrixDownCmd(CommandController& commandController, StateChangeDistributor& stateChangeDistributor, Scheduler& scheduler) : RecordedCommand(commandController, stateChangeDistributor, scheduler, "keymatrixdown") { } void Keyboard::KeyMatrixDownCmd::execute(array_ref tokens, TclObject& /*result*/, EmuTime::param /*time*/) { auto& keyboard = OUTER(Keyboard, keyMatrixDownCmd); return keyboard.processCmd(getInterpreter(), tokens, false); } string Keyboard::KeyMatrixDownCmd::help(const vector& /*tokens*/) const { static const string helpText = "keymatrixdown press a key in the keyboardmatrix\n"; return helpText; } // class MsxKeyEventQueue Keyboard::MsxKeyEventQueue::MsxKeyEventQueue( Scheduler& scheduler, Interpreter& interp_) : Schedulable(scheduler) , interp(interp_) { } void Keyboard::MsxKeyEventQueue::process_asap( EmuTime::param time, const shared_ptr& event) { bool processImmediately = eventQueue.empty(); eventQueue.push_back(event); if (processImmediately) { executeUntil(time); } } void Keyboard::MsxKeyEventQueue::clear() { eventQueue.clear(); removeSyncPoint(); } void Keyboard::MsxKeyEventQueue::executeUntil(EmuTime::param time) { // Get oldest event from the queue and process it shared_ptr event = eventQueue.front(); auto& keyboard = OUTER(Keyboard, msxKeyEventQueue); bool insertCodeKanaRelease = keyboard.processQueuedEvent(*event, time); if (insertCodeKanaRelease) { // The processor pressed the CODE/KANA key // Schedule a CODE/KANA release event, to be processed // before any of the other events in the queue eventQueue.push_front(make_shared( keyboard.keyboardSettings.getCodeKanaHostKey())); } else { // The event has been completely processed. Delete it from the queue if (!eventQueue.empty()) { eventQueue.pop_front(); } else { // it's possible clear() has been called // (indirectly from keyboard.processQueuedEvent()) } } if (!eventQueue.empty()) { // There are still events. Process them in 1/15s from now setSyncPoint(time + EmuDuration::hz(15)); } } // class KeyInserter Keyboard::KeyInserter::KeyInserter( CommandController& commandController, StateChangeDistributor& stateChangeDistributor, Scheduler& scheduler) : RecordedCommand(commandController, stateChangeDistributor, scheduler, "type") , Schedulable(scheduler) , lockKeysMask(0) , releaseLast(false) { // avoid UMR last = 0; oldCodeKanaLockOn = false; oldGraphLockOn = false; oldCapsLockOn = false; releaseBeforePress = false; typingFrequency = 15; } void Keyboard::KeyInserter::execute( array_ref tokens, TclObject& /*result*/, EmuTime::param /*time*/) { if (tokens.size() < 2) { throw SyntaxError(); } releaseBeforePress = false; typingFrequency = 15; // for full backwards compatibility: one option means type it... if (tokens.size() == 2) { type(tokens[1].getString()); return; } vector arguments; for (unsigned i = 1; i < tokens.size(); ++i) { string_ref token = tokens[i].getString(); if (token == "-release") { releaseBeforePress = true; } else if (token == "-freq") { if (++i == tokens.size()) { throw CommandException("Missing argument"); } int tmp = tokens[1].getInt(getInterpreter()); if (tmp <= 0) { throw CommandException("Wrong argument for -freq (should be a positive number)"); } typingFrequency = tmp; } else { arguments.push_back(token); } } if (arguments.size() != 1) throw SyntaxError(); type(arguments[0]); } string Keyboard::KeyInserter::help(const vector& /*tokens*/) const { static const string helpText = "Type a string in the emulated MSX.\n" \ "Use -release to make sure the keys are always released before typing new ones (necessary for some game input routines, but in general, this means typing is twice as slow).\n" \ "Use -freq to tweak how fast typing goes and how long the keys will be pressed (and released in case -release was used). Keys will be typed at the given frequency and will remain pressed/released for 1/freq seconds"; return helpText; } void Keyboard::KeyInserter::tabCompletion(vector& tokens) const { vector options; if (!contains(tokens, "-release")) { options.push_back("-release"); } if (!contains(tokens, "-freq")) { options.push_back("-freq"); } completeString(tokens, options); } void Keyboard::KeyInserter::type(string_ref str) { if (str.empty()) { return; } auto& keyboard = OUTER(Keyboard, keyTypeCmd); oldCodeKanaLockOn = keyboard.msxCodeKanaLockOn; oldGraphLockOn = keyboard.msxGraphLockOn; oldCapsLockOn = keyboard.msxCapsLockOn; if (text_utf8.empty()) { reschedule(getCurrentTime()); } text_utf8.append(str.data(), str.size()); } void Keyboard::KeyInserter::executeUntil(EmuTime::param time) { auto& keyboard = OUTER(Keyboard, keyTypeCmd); if (lockKeysMask != 0) { // release CAPS and/or Code/Kana Lock keys keyboard.pressLockKeys(lockKeysMask, false); } if (releaseLast) { keyboard.pressAscii(last, false); // release previous character } if (text_utf8.empty()) { releaseLast = false; lockKeysMask = 0; if (oldCodeKanaLockOn != keyboard.msxCodeKanaLockOn) { keyboard.debug("Restoring CODE/KANA lock\n"); lockKeysMask = CODE_MASK; keyboard.msxCodeKanaLockOn = !keyboard.msxCodeKanaLockOn; } if (oldGraphLockOn != keyboard.msxGraphLockOn) { keyboard.debug("Restoring GRAPH lock\n"); lockKeysMask |= GRAPH_MASK; keyboard.msxGraphLockOn = !keyboard.msxGraphLockOn; } if (oldCapsLockOn != keyboard.msxCapsLockOn) { keyboard.debug("Restoring CAPS lock\n"); lockKeysMask |= CAPS_MASK; keyboard.msxCapsLockOn = !keyboard.msxCapsLockOn; } if (lockKeysMask != 0) { // press CAPS, GRAPH and/or Code/Kana Lock keys keyboard.pressLockKeys(lockKeysMask, true); reschedule(time); } return; } try { auto it = begin(text_utf8); unsigned current = utf8::next(it, end(text_utf8)); if (releaseLast == true && (releaseBeforePress || keyboard.commonKeys(last, current))) { // There are common keys between previous and current character // Do not immediately press again but give MSX the time to notice // that the keys have been released releaseLast = false; } else { // All keys in current char differ from previous char. The new keys // can immediately be pressed lockKeysMask = keyboard.pressAscii(current, true); if (lockKeysMask == 0) { last = current; releaseLast = true; text_utf8.erase(begin(text_utf8), it); } if (releaseBeforePress) releaseLast = true; } reschedule(time); } catch (std::exception&) { // utf8 encoding error text_utf8.clear(); } } void Keyboard::KeyInserter::reschedule(EmuTime::param time) { setSyncPoint(time + EmuDuration::hz(typingFrequency)); } /* * class CapsLockAligner * * It is used to align MSX CAPS lock status with the host CAPS lock status * during the reset of the MSX or after the openMSX window regains focus. * * It listens to the 'BOOT' event and schedules the real alignment * 2 seconds later. Reason is that it takes a while before the MSX * reset routine starts monitoring the MSX keyboard. * * For focus regain, the alignment is done immediately. */ Keyboard::CapsLockAligner::CapsLockAligner( EventDistributor& eventDistributor_, Scheduler& scheduler) : Schedulable(scheduler) , eventDistributor(eventDistributor_) { state = IDLE; eventDistributor.registerEventListener(OPENMSX_BOOT_EVENT, *this); eventDistributor.registerEventListener(OPENMSX_FOCUS_EVENT, *this); } Keyboard::CapsLockAligner::~CapsLockAligner() { eventDistributor.unregisterEventListener(OPENMSX_FOCUS_EVENT, *this); eventDistributor.unregisterEventListener(OPENMSX_BOOT_EVENT, *this); } int Keyboard::CapsLockAligner::signalEvent(const shared_ptr& event) { if (state == IDLE) { EmuTime::param time = getCurrentTime(); EventType type = event->getType(); if (type == OPENMSX_FOCUS_EVENT) { alignCapsLock(time); } else if (type == OPENMSX_BOOT_EVENT) { state = MUST_ALIGN_CAPSLOCK; setSyncPoint(time + EmuDuration::sec(2)); // 2s (MSX time) } else { UNREACHABLE; } } return 0; } void Keyboard::CapsLockAligner::executeUntil(EmuTime::param time) { switch (state) { case MUST_ALIGN_CAPSLOCK: alignCapsLock(time); break; case MUST_DISTRIBUTE_KEY_RELEASE: { auto& keyboard = OUTER(Keyboard, capsLockAligner); assert(keyboard.sdlReleasesCapslock); auto event = make_shared(Keys::K_CAPSLOCK); keyboard.msxEventDistributor.distributeEvent(event, time); state = IDLE; break; } default: UNREACHABLE; } } /* * Align MSX caps lock state with host caps lock state * WARNING: This function assumes that the MSX will see and * process the caps lock key press. * If MSX misses the key press for whatever reason (e.g. * interrupts are disabled), the caps lock state in this * module will mismatch with the real MSX caps lock state * TODO: Find a solution for the above problem. For example by monitoring * the MSX caps-lock LED state. */ void Keyboard::CapsLockAligner::alignCapsLock(EmuTime::param time) { bool hostCapsLockOn = ((SDL_GetModState() & KMOD_CAPS) != 0); auto& keyboard = OUTER(Keyboard, capsLockAligner); if (keyboard.msxCapsLockOn != hostCapsLockOn) { keyboard.debug("Resyncing host and MSX CAPS lock\n"); // note: send out another event iso directly calling // processCapslockEvent() because we want this to be recorded auto event = make_shared(Keys::K_CAPSLOCK); keyboard.msxEventDistributor.distributeEvent(event, time); if (keyboard.sdlReleasesCapslock) { keyboard.debug("Sending fake CAPS release\n"); state = MUST_DISTRIBUTE_KEY_RELEASE; setSyncPoint(time + EmuDuration::hz(10)); // 0.1s (MSX time) } else { state = IDLE; } } else { state = IDLE; } } // class KeybDebuggable Keyboard::KeybDebuggable::KeybDebuggable(MSXMotherBoard& motherBoard) : SimpleDebuggable(motherBoard, "keymatrix", "MSX Keyboard Matrix", Keyboard::NR_KEYROWS) { } byte Keyboard::KeybDebuggable::read(unsigned address) { auto& keyboard = OUTER(Keyboard, keybDebuggable); return keyboard.getKeys()[address]; } void Keyboard::KeybDebuggable::write(unsigned /*address*/, byte /*value*/) { // ignore } template void Keyboard::KeyInserter::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("text", text_utf8); ar.serialize("last", last); ar.serialize("lockKeysMask", lockKeysMask); ar.serialize("releaseLast", releaseLast); ar.serialize("oldCodeKanaLockOn", oldCodeKanaLockOn); ar.serialize("oldGraphLockOn", oldGraphLockOn); ar.serialize("oldCapsLockOn", oldCapsLockOn); } // version 1: Initial version: {userKeyMatrix, dynKeymap, msxmodifiers, // msxKeyEventQueue} was intentionally not serialized. The reason // was that after a loadstate, you want the MSX keyboard to reflect // the state of the host keyboard. So any pressed MSX keys from the // time the savestate was created are cleared. // version 2: For reverse-replay it is important that snapshots contain the // full state of the MSX keyboard, so now we do serialize it. // TODO Is the assumption in version 1 correct (clear keyb state on load)? // If it is still useful for 'regular' loadstate, then we could implement // it by explicitly clearing the keyb state from the actual loadstate // command. (But let's only do this when experience shows it's really // better). template void Keyboard::serialize(Archive& ar, unsigned version) { ar.serialize("keyTypeCmd", keyTypeCmd); ar.serialize("cmdKeyMatrix", cmdKeyMatrix); ar.serialize("msxCapsLockOn", msxCapsLockOn); ar.serialize("msxCodeKanaLockOn", msxCodeKanaLockOn); ar.serialize("msxGraphLockOn", msxGraphLockOn); if (ar.versionAtLeast(version, 2)) { ar.serialize("userKeyMatrix", userKeyMatrix); ar.serialize("dynKeymap", dynKeymap); ar.serialize("msxmodifiers", msxmodifiers); ar.serialize("msxKeyEventQueue", msxKeyEventQueue); } // don't serialize hostKeyMatrix if (ar.isLoader()) { // force recalculation of keyMatrix // (from cmdKeyMatrix and userKeyMatrix) keysChanged = true; } } INSTANTIATE_SERIALIZE_METHODS(Keyboard); template void Keyboard::MsxKeyEventQueue::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); // serialization of deque> is not directly // supported by the serialization framework (main problem is the // constness, collections of shared_ptr to polymorhpic objects are // not a problem). Worked around this by serializing the events in // ascii format. (In all practical cases this queue will anyway be // empty or contain very few elements). //ar.serialize("eventQueue", eventQueue); vector eventStrs; if (!ar.isLoader()) { for (auto& e : eventQueue) { eventStrs.push_back(e->toString()); } } ar.serialize("eventQueue", eventStrs); if (ar.isLoader()) { assert(eventQueue.empty()); for (auto& s : eventStrs) { eventQueue.push_back( InputEventFactory::createInputEvent(s, interp)); } } } INSTANTIATE_SERIALIZE_METHODS(Keyboard::MsxKeyEventQueue); /** Keyboard bindings ****************************************/ // MSX Key-Matrix table // // row/bit 7 6 5 4 3 2 1 0 // +-----+-----+-----+-----+-----+-----+-----+-----+ // 0 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | // 1 | ; | ] | [ | \ | = | - | 9 | 8 | // 2 | B | A | Acc | / | . | , | ` | ' | // 3 | J | I | H | G | F | E | D | C | // 4 | R | Q | P | O | N | M | L | K | // 5 | Z | Y | X | W | V | U | T | S | // 6 | F3 | F2 | F1 | code| caps|graph| ctrl|shift| // 7 | ret |selec| bs | stop| tab | esc | F5 | F4 | // 8 |right| down| up | left| del | ins | hom |space| // 9 | 4 | 3 | 2 | 1 | 0 | / | + | * | // 10 | . | , | - | 9 | 8 | 7 | 6 | 5 | // 11 | | | | | 'NO'| |'YES'| | // +-----+-----+-----+-----+-----+-----+-----+-----+ // Mapping from SDL keys to MSX keys static const byte x = 0xff; const byte Keyboard::keyTab[MAX_KEYSYM] = { // 0 1 2 3 4 5 6 7 8 9 a b c d e f x , x , x , x , x , x , x , x ,0x75,0x73, x , x , x ,0x77, x , x , //000 x , x , x , x , x , x , x , x , x , x , x ,0x72, x , x , x , x , //010 0x80, x , x , x , x , x , x ,0x20, x , x , x , x ,0x22,0x12,0x23,0x24, //020 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x10,0x11, x ,0x17, x ,0x13, x , x , //030 x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //040 x ,0x84,0x85,0x87,0x86, x , x , x , x , x , x ,0x15,0x14,0x16, x , x , //050 0x21,0x26,0x27,0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x40,0x41,0x42,0x43,0x44, //060 0x45,0x46,0x47,0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57, x , x , x ,0x62,0x83, //070 x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //080 x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //090 x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0A0 x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0B0 x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0C0 x , x , x , x , x , x , x , x ,0x81, x , x , x , x , x , x , x , //0D0 x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0E0 x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //0F0 0x93,0x94,0x95,0x96,0x97,0xA0,0xA1,0xA2,0xA3,0xA4,0xA7,0x92,0x90,0xA5,0x91,0xA6, //100 x ,0x85,0x86,0x87,0x84,0x82,0x81, x , x , x ,0x65,0x66,0x67,0x70,0x71, x , //110 0x76,0x74, x , x , x , x , x , x , x , x , x , x , x ,0x63, x ,0x60, //120 0x60,0x25,0x61,0x64,0x62,0xB3,0xB1,0xB3,0xB1,0xB1,0xB3, x , x , x , x , x , //130 x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , x , //140 }; } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/Keyboard.hh000066400000000000000000000173301257557151200204520ustar00rootroot00000000000000#ifndef KEYBOARD_HH #define KEYBOARD_HH #include "KeyboardSettings.hh" #include "UnicodeKeymap.hh" #include "MSXEventListener.hh" #include "StateChangeListener.hh" #include "Schedulable.hh" #include "RecordedCommand.hh" #include "SimpleDebuggable.hh" #include "EventListener.hh" #include "serialize_meta.hh" #include "array_ref.hh" #include "string_ref.hh" #include "openmsx.hh" #include #include #include namespace openmsx { class MSXMotherBoard; class Scheduler; class CommandController; class EventDistributor; class MSXEventDistributor; class StateChangeDistributor; class KeyEvent; class StateChange; class TclObject; class Interpreter; class Keyboard final : private MSXEventListener, private StateChangeListener , private Schedulable { public: static const unsigned NR_KEYROWS = 16; /** Constructs a new Keyboard object. * @param motherBoard ref to the motherBoard * @param scheduler ref to the scheduler * @param commandController ref to the command controller * @param eventDistributor ref to the emu event distributor * @param msxEventDistributor ref to the user input event distributor * @param stateChangeDistributor ref to the state change distributor * @param keyboardType contains filename extension of unicode keymap file * @param hasKeypad turn MSX keypad on/off * @param hasYesNoKeys this keyboard has (Japanese) Yes/No keys * @param keyGhosting turn keyGhosting on/off * @param keyGhostingSGCprotected Shift, Graph and Code are keyGhosting protected * @param codeKanaLocks CodeKana key behave as a lock key on this machine * @param graphLocks Graph key behave as a lock key on this machine */ Keyboard(MSXMotherBoard& motherBoard, Scheduler& scheduler, CommandController& commandController, EventDistributor& eventDistributor, MSXEventDistributor& msxEventDistributor, StateChangeDistributor& stateChangeDistributor, string_ref keyboardType, bool hasKeypad, bool hasYesNoKeys, bool keyGhosting, bool keyGhostingSGCprotected, bool codeKanaLocks, bool graphLocks); ~Keyboard(); /** Returns a pointer to the current KeyBoard matrix */ const byte* getKeys(); void transferHostKeyMatrix(const Keyboard& source); template void serialize(Archive& ar, unsigned version); private: // MSXEventListener void signalEvent(const std::shared_ptr& event, EmuTime::param time) override; // StateChangeListener void signalStateChange(const std::shared_ptr& event) override; void stopReplay(EmuTime::param time) override; // Schedulable void executeUntil(EmuTime::param time) override; void pressKeyMatrixEvent (EmuTime::param time, byte row, byte press); void releaseKeyMatrixEvent(EmuTime::param time, byte row, byte release); void changeKeyMatrixEvent (EmuTime::param time, byte row, byte newValue); void processDeadKeyEvent(unsigned n, EmuTime::param time, bool down); void processCapslockEvent(EmuTime::param time, bool down); void processCodeKanaChange(EmuTime::param time, bool down); void processGraphChange(EmuTime::param time, bool down); void processKeypadEnterKey(EmuTime::param time, bool down); void processSdlKey(EmuTime::param time, bool down, int key); bool processQueuedEvent(const Event& event, EmuTime::param time); bool processKeyEvent(EmuTime::param time, bool down, const KeyEvent& keyEvent); void updateKeyMatrix(EmuTime::param time, bool down, int row, byte mask); void doKeyGhosting(); void processCmd(Interpreter& interp, array_ref tokens, bool up); bool pressUnicodeByUser(EmuTime::param time, unsigned unicode, bool down); int pressAscii(unsigned unicode, bool down); void pressLockKeys(int lockKeysMask, bool down); bool commonKeys(unsigned unicode1, unsigned unicode2); void debug(const char* format, ...); CommandController& commandController; MSXEventDistributor& msxEventDistributor; StateChangeDistributor& stateChangeDistributor; static const int MAX_KEYSYM = 0x150; static const byte keyTab[MAX_KEYSYM]; struct KeyMatrixUpCmd final : RecordedCommand { KeyMatrixUpCmd(CommandController& commandController, StateChangeDistributor& stateChangeDistributor, Scheduler& scheduler); void execute(array_ref tokens, TclObject& result, EmuTime::param time) override; std::string help(const std::vector& tokens) const override; } keyMatrixUpCmd; struct KeyMatrixDownCmd final : RecordedCommand { KeyMatrixDownCmd(CommandController& commandController, StateChangeDistributor& stateChangeDistributor, Scheduler& scheduler); void execute(array_ref tokens, TclObject& result, EmuTime::param time) override; std::string help(const std::vector& tokens) const override; } keyMatrixDownCmd; class KeyInserter final : public RecordedCommand, public Schedulable { public: KeyInserter(CommandController& commandController, StateChangeDistributor& stateChangeDistributor, Scheduler& scheduler); template void serialize(Archive& ar, unsigned version); private: void type(string_ref str); void reschedule(EmuTime::param time); // Command void execute(array_ref tokens, TclObject& result, EmuTime::param time) override; std::string help(const std::vector& tokens) const override; void tabCompletion(std::vector& tokens) const override; // Schedulable void executeUntil(EmuTime::param time) override; std::string text_utf8; unsigned last; int lockKeysMask; bool releaseLast; bool oldCodeKanaLockOn; bool oldGraphLockOn; bool oldCapsLockOn; bool releaseBeforePress; unsigned typingFrequency; } keyTypeCmd; class CapsLockAligner final : private EventListener, private Schedulable { public: CapsLockAligner(EventDistributor& eventDistributor, Scheduler& scheduler); ~CapsLockAligner(); private: // EventListener int signalEvent(const std::shared_ptr& event) override; // Schedulable void executeUntil(EmuTime::param time) override; void alignCapsLock(EmuTime::param time); EventDistributor& eventDistributor; enum CapsLockAlignerStateType { MUST_ALIGN_CAPSLOCK, MUST_DISTRIBUTE_KEY_RELEASE, IDLE } state; } capsLockAligner; KeyboardSettings keyboardSettings; class MsxKeyEventQueue final : public Schedulable { public: MsxKeyEventQueue(Scheduler& scheduler, Interpreter& interp); void process_asap(EmuTime::param time, const std::shared_ptr& event); void clear(); template void serialize(Archive& ar, unsigned version); private: // Schedulable void executeUntil(EmuTime::param time) override; std::deque> eventQueue; Interpreter& interp; } msxKeyEventQueue; struct KeybDebuggable final : SimpleDebuggable { KeybDebuggable(MSXMotherBoard& motherBoard); byte read(unsigned address) override; void write(unsigned address, byte value) override; } keybDebuggable; UnicodeKeymap unicodeKeymap; unsigned dynKeymap[MAX_KEYSYM]; byte cmdKeyMatrix [NR_KEYROWS]; // for keymatrix/type command byte userKeyMatrix[NR_KEYROWS]; // pressed user keys (live or replay) byte hostKeyMatrix[NR_KEYROWS]; // always in sync with host keyb, also during replay byte keyMatrix [NR_KEYROWS]; // combination of cmdKeyMatrix and userKeyMatrix byte msxmodifiers; const bool hasKeypad; const bool hasYesNoKeys; const bool keyGhosting; const bool keyGhostingSGCprotected; const bool codeKanaLocks; const bool graphLocks; const bool sdlReleasesCapslock; bool keysChanged; bool msxCapsLockOn; bool msxCodeKanaLockOn; bool msxGraphLockOn; }; SERIALIZE_CLASS_VERSION(Keyboard, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/KeyboardSettings.cc000066400000000000000000000051701257557151200221600ustar00rootroot00000000000000#include "KeyboardSettings.hh" #include "memory.hh" namespace openmsx { static EnumSetting::Map getAllowedKeysMap() { return { {"RALT", Keys::K_RALT}, {"MENU", Keys::K_MENU}, {"RCTRL", Keys::K_RCTRL}, {"HENKAN_MODE", Keys::K_HENKAN_MODE}, {"RSHIFT", Keys::K_RSHIFT}, {"RMETA", Keys::K_RMETA}, {"LMETA", Keys::K_LMETA}, {"LSUPER", Keys::K_LSUPER}, {"RSUPER", Keys::K_RSUPER}, {"HELP", Keys::K_HELP}, {"UNDO", Keys::K_UNDO}, {"END", Keys::K_END}, {"PAGEUP", Keys::K_PAGEUP}, {"PAGEDOWN", Keys::K_PAGEDOWN} }; } KeyboardSettings::KeyboardSettings(CommandController& commandController) : codeKanaHostKey(commandController, "kbd_code_kana_host_key", "Host key that maps to the MSX CODE/KANA key. Please note that the HENKAN_MODE key only exists on Japanese host keyboards)", Keys::K_RALT, getAllowedKeysMap()) , kpEnterMode(commandController, "kbd_numkeypad_enter_key", "MSX key that the enter key on the host numeric keypad must map to", MSX_KP_COMMA, EnumSetting::Map{ {"KEYPAD_COMMA", MSX_KP_COMMA}, {"ENTER", MSX_ENTER}}) , mappingMode(commandController, "kbd_mapping_mode", "Keyboard mapping mode", CHARACTER_MAPPING, EnumSetting::Map{ {"KEY", KEY_MAPPING}, {"CHARACTER", CHARACTER_MAPPING}}) , alwaysEnableKeypad(commandController, "kbd_numkeypad_always_enabled", "Numeric keypad is always enabled, even on an MSX that does not have one", false) , traceKeyPresses(commandController, "kbd_trace_key_presses", "Trace key presses (show SDL key code, SDL modifiers and Unicode code-point value)", false, Setting::DONT_SAVE) , autoToggleCodeKanaLock(commandController, "kbd_auto_toggle_code_kana_lock", "Automatically toggle the CODE/KANA lock, based on the characters entered on the host keyboard", true) { deadkeyHostKey[0] = make_unique>( commandController, "kbd_deadkey1_host_key", "Host key that maps to deadkey 1. Not applicable to Japanese and Korean MSX models", Keys::K_RCTRL, getAllowedKeysMap()); deadkeyHostKey[1] = make_unique>( commandController, "kbd_deadkey2_host_key", "Host key that maps to deadkey 2. Only applicable to Brazilian MSX models (Sharp Hotbit and Gradiente)", Keys::K_PAGEUP, getAllowedKeysMap()); deadkeyHostKey[2] = make_unique>( commandController, "kbd_deadkey3_host_key", "Host key that maps to deadkey 3. Only applicable to Brazilian Sharp Hotbit MSX models", Keys::K_PAGEDOWN, getAllowedKeysMap()); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/KeyboardSettings.hh000066400000000000000000000025071257557151200221730ustar00rootroot00000000000000#ifndef KEYBOARDSETTINGS_HH #define KEYBOARDSETTINGS_HH #include "Keys.hh" #include "EnumSetting.hh" #include "BooleanSetting.hh" #include #include namespace openmsx { class CommandController; class KeyboardSettings { public: enum KpEnterMode { MSX_KP_COMMA, MSX_ENTER }; enum MappingMode { KEY_MAPPING, CHARACTER_MAPPING }; explicit KeyboardSettings(CommandController& commandController); Keys::KeyCode getDeadkeyHostKey(unsigned n) const { assert(n < 3); return deadkeyHostKey[n]->getEnum(); } Keys::KeyCode getCodeKanaHostKey() const { return codeKanaHostKey.getEnum(); } KpEnterMode getKpEnterMode() const { return kpEnterMode.getEnum(); } MappingMode getMappingMode() const { return mappingMode.getEnum(); } bool getAlwaysEnableKeypad() const { return alwaysEnableKeypad.getBoolean(); } bool getTraceKeyPresses() const { return traceKeyPresses.getBoolean(); } bool getAutoToggleCodeKanaLock() const { return autoToggleCodeKanaLock.getBoolean(); } private: std::unique_ptr> deadkeyHostKey[3]; EnumSetting codeKanaHostKey; EnumSetting kpEnterMode; EnumSetting mappingMode; BooleanSetting alwaysEnableKeypad; BooleanSetting traceKeyPresses; BooleanSetting autoToggleCodeKanaLock; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/MSXEventDistributor.cc000066400000000000000000000023531257557151200226030ustar00rootroot00000000000000#include "MSXEventDistributor.hh" #include "MSXEventListener.hh" #include "stl.hh" #include #include namespace openmsx { MSXEventDistributor::MSXEventDistributor() { } MSXEventDistributor::~MSXEventDistributor() { assert(listeners.empty()); } bool MSXEventDistributor::isRegistered(MSXEventListener* listener) const { return contains(listeners, listener); } void MSXEventDistributor::registerEventListener(MSXEventListener& listener) { assert(!isRegistered(&listener)); listeners.push_back(&listener); } void MSXEventDistributor::unregisterEventListener(MSXEventListener& listener) { listeners.erase(find_unguarded(listeners, &listener)); } void MSXEventDistributor::distributeEvent(const EventPtr& event, EmuTime::param time) { // Iterate over a copy because signalEvent() may indirect call back into // registerEventListener(). // e.g. signalEvent() -> .. -> PlugCmd::execute() -> .. -> // Connector::plug() -> .. -> Joystick::plugHelper() -> // registerEventListener() auto copy = listeners; for (auto& l : copy) { if (isRegistered(l)) { // it's possible the listener unregistered itself // (but is still present in the copy) l->signalEvent(event, time); } } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/MSXEventDistributor.hh000066400000000000000000000023441257557151200226150ustar00rootroot00000000000000#ifndef MSXEVENTDISTRIBUTOR_HH #define MSXEVENTDISTRIBUTOR_HH #include "EmuTime.hh" #include "noncopyable.hh" #include #include namespace openmsx { class MSXEventListener; class Event; class MSXEventDistributor : private noncopyable { public: using EventPtr = std::shared_ptr; MSXEventDistributor(); ~MSXEventDistributor(); /** * Registers a given object to receive certain events. * @param listener Listener that will be notified when an event arrives. */ void registerEventListener(MSXEventListener& listener); /** * Unregisters a previously registered event listener. * @param listener Listener to unregister. */ void unregisterEventListener(MSXEventListener& listener); /** Deliver the event to all registered listeners * @param event The event * @param time Current time * Note: MSXEventListener's are allowed to throw exceptions, and this * method doesn't catch them (in case of an exception it's * undefined which listeners receive the event) */ void distributeEvent(const EventPtr& event, EmuTime::param time); private: bool isRegistered(MSXEventListener* listener) const; std::vector listeners; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/MSXEventListener.hh000066400000000000000000000007141257557151200220670ustar00rootroot00000000000000#ifndef MSXEVENTLISTENER_HH #define MSXEVENTLISTENER_HH #include "EmuTime.hh" #include namespace openmsx { class Event; class MSXEventListener { public: /** This method gets called when an event you are subscribed to occurs. */ virtual void signalEvent(const std::shared_ptr& event, EmuTime::param time) = 0; protected: MSXEventListener() {} ~MSXEventListener() {} }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/MagicKey.cc000066400000000000000000000016031257557151200203650ustar00rootroot00000000000000#include "MagicKey.hh" #include "serialize.hh" #include "serialize_meta.hh" namespace openmsx { // Pluggable const std::string& MagicKey::getName() const { static const std::string NAME = "magic-key"; return NAME; } string_ref MagicKey::getDescription() const { return "Dongle used by some Japanese games to enable cheat mode"; } void MagicKey::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/) { } void MagicKey::unplugHelper(EmuTime::param /*time*/) { } // JoystickDevice byte MagicKey::read(EmuTime::param /*time*/) { return JOY_BUTTONB | JOY_BUTTONA | JOY_RIGHT | JOY_LEFT; } void MagicKey::write(byte /*value*/, EmuTime::param /*time*/) { } template void MagicKey::serialize(Archive& /*ar*/, unsigned /*version*/) { } INSTANTIATE_SERIALIZE_METHODS(MagicKey); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, MagicKey, "MagicKey"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/MagicKey.hh000066400000000000000000000011311257557151200203730ustar00rootroot00000000000000#ifndef MAGICKEY_HH #define MAGICKEY_HH #include "JoystickDevice.hh" namespace openmsx { class MagicKey final : public JoystickDevice { public: // Pluggable const std::string& getName() const override; string_ref getDescription() const override; void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; // JoystickDevice byte read(EmuTime::param time) override; void write(byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/Mouse.cc000066400000000000000000000227301257557151200177700ustar00rootroot00000000000000#include "Mouse.hh" #include "MSXEventDistributor.hh" #include "StateChangeDistributor.hh" #include "InputEvents.hh" #include "StateChange.hh" #include "Clock.hh" #include "checked_cast.hh" #include "serialize.hh" #include "serialize_meta.hh" #include "unreachable.hh" #include using std::string; using std::min; using std::max; using std::shared_ptr; namespace openmsx { static const int TRESHOLD = 2; static const int SCALE = 2; static const int PHASE_XHIGH = 0; static const int PHASE_XLOW = 1; static const int PHASE_YHIGH = 2; static const int PHASE_YLOW = 3; static const int STROBE = 0x04; class MouseState final : public StateChange { public: MouseState() {} // for serialize MouseState(EmuTime::param time, int deltaX_, int deltaY_, byte press_, byte release_) : StateChange(time) , deltaX(deltaX_), deltaY(deltaY_) , press(press_), release(release_) {} int getDeltaX() const { return deltaX; } int getDeltaY() const { return deltaY; } byte getPress() const { return press; } byte getRelease() const { return release; } template void serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("deltaX", deltaX); ar.serialize("deltaY", deltaY); ar.serialize("press", press); ar.serialize("release", release); } private: int deltaX, deltaY; byte press, release; }; REGISTER_POLYMORPHIC_CLASS(StateChange, MouseState, "MouseState"); Mouse::Mouse(MSXEventDistributor& eventDistributor_, StateChangeDistributor& stateChangeDistributor_) : eventDistributor(eventDistributor_) , stateChangeDistributor(stateChangeDistributor_) , lastTime(EmuTime::zero) { status = JOY_BUTTONA | JOY_BUTTONB; phase = PHASE_YLOW; xrel = yrel = curxrel = curyrel = 0; absHostX = absHostY = 0; mouseMode = true; } Mouse::~Mouse() { if (isPluggedIn()) { Mouse::unplugHelper(EmuTime::dummy()); } } // Pluggable const string& Mouse::getName() const { static const string name("mouse"); return name; } string_ref Mouse::getDescription() const { return "MSX mouse"; } void Mouse::plugHelper(Connector& /*connector*/, EmuTime::param time) { if (SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON(SDL_BUTTON_LEFT)) { // left mouse button pressed, joystick emulation mode mouseMode = false; } else { // not pressed, mouse mode mouseMode = true; lastTime = time; } plugHelper2(); } void Mouse::plugHelper2() { eventDistributor.registerEventListener(*this); stateChangeDistributor.registerListener(*this); } void Mouse::unplugHelper(EmuTime::param /*time*/) { stateChangeDistributor.unregisterListener(*this); eventDistributor.unregisterEventListener(*this); } // JoystickDevice byte Mouse::read(EmuTime::param /*time*/) { if (mouseMode) { switch (phase) { case PHASE_XHIGH: return ((xrel >> 4) & 0x0F) | status; case PHASE_XLOW: return (xrel & 0x0F) | status; case PHASE_YHIGH: return ((yrel >> 4) & 0x0F) | status; case PHASE_YLOW: return (yrel & 0x0F) | status; default: UNREACHABLE; return 0; } } else { emulateJoystick(); return status; } } void Mouse::emulateJoystick() { status &= ~(JOY_UP | JOY_DOWN | JOY_LEFT | JOY_RIGHT); int deltax = curxrel; curxrel = 0; int deltay = curyrel; curyrel = 0; int absx = (deltax > 0) ? deltax : -deltax; int absy = (deltay > 0) ? deltay : -deltay; if ((absx < TRESHOLD) && (absy < TRESHOLD)) { return; } // tan(pi/8) ~= 5/12 if (deltax > 0) { if (deltay > 0) { if ((12 * absx) > (5 * absy)) { status |= JOY_RIGHT; } if ((12 * absy) > (5 * absx)) { status |= JOY_DOWN; } } else { if ((12 * absx) > (5 * absy)) { status |= JOY_RIGHT; } if ((12 * absy) > (5 * absx)) { status |= JOY_UP; } } } else { if (deltay > 0) { if ((12 * absx) > (5 * absy)) { status |= JOY_LEFT; } if ((12 * absy) > (5 * absx)) { status |= JOY_DOWN; } } else { if ((12 * absx) > (5 * absy)) { status |= JOY_LEFT; } if ((12 * absy) > (5 * absx)) { status |= JOY_UP; } } } } void Mouse::write(byte value, EmuTime::param time) { if (mouseMode) { // TODO figure out the exact timeout value. Is there even such // an exact value or can it vary between different mouse // models? // // Initially we used a timeout of 1 full second. This caused bug // [3520394] Mouse behaves badly (unusable) in HiBrid // Slightly lowering the value to around 0.94s was already // enough to fix that bug. Later we found that to make FRS's // joytest program work we need a value that is less than the // duration of one (NTSC) frame. See bug // #474 Mouse doesn't work properly on Joytest v2.2 // We still don't know the exact value that an actual MSX mouse // uses, but 1.5ms is also the timeout value that is used for // JoyMega, so it seems like a reasonable value. if ((time - lastTime) > EmuDuration::usec(1500)) { phase = PHASE_YLOW; } lastTime = time; switch (phase) { case PHASE_XHIGH: if ((value & STROBE) == 0) phase = PHASE_XLOW; break; case PHASE_XLOW: if ((value & STROBE) != 0) phase = PHASE_YHIGH; break; case PHASE_YHIGH: if ((value & STROBE) == 0) phase = PHASE_YLOW; break; case PHASE_YLOW: if ((value & STROBE) != 0) { phase = PHASE_XHIGH; xrel = curxrel; yrel = curyrel; curxrel = 0; curyrel = 0; } break; } } else { // ignore } } // MSXEventListener void Mouse::signalEvent(const shared_ptr& event, EmuTime::param time) { switch (event->getType()) { case OPENMSX_MOUSE_MOTION_EVENT: { auto& mev = checked_cast(*event); if (mev.getX() || mev.getY()) { // note: X/Y are negated, do this already in this // routine to keep replays bw-compat. In a new // savestate version it may (or may not) be cleaner // to perform this operation closer to the MSX code. createMouseStateChange(time, -mev.getX(), -mev.getY(), 0, 0); } break; } case OPENMSX_MOUSE_BUTTON_DOWN_EVENT: { auto& butEv = checked_cast(*event); switch (butEv.getButton()) { case MouseButtonEvent::LEFT: createMouseStateChange(time, 0, 0, JOY_BUTTONA, 0); break; case MouseButtonEvent::RIGHT: createMouseStateChange(time, 0, 0, JOY_BUTTONB, 0); break; default: // ignore other buttons break; } break; } case OPENMSX_MOUSE_BUTTON_UP_EVENT: { auto& butEv = checked_cast(*event); switch (butEv.getButton()) { case MouseButtonEvent::LEFT: createMouseStateChange(time, 0, 0, 0, JOY_BUTTONA); break; case MouseButtonEvent::RIGHT: createMouseStateChange(time, 0, 0, 0, JOY_BUTTONB); break; default: // ignore other buttons break; } break; } default: // ignore break; } } void Mouse::createMouseStateChange( EmuTime::param time, int deltaX, int deltaY, byte press, byte release) { stateChangeDistributor.distributeNew(std::make_shared( time, deltaX, deltaY, press, release)); } void Mouse::signalStateChange(const shared_ptr& event) { auto ms = dynamic_cast(event.get()); if (!ms) return; // This is almost the same as // relMsxXY = ms->getDeltaXY() / SCALE // except that it doesn't accumulate rounding errors int oldMsxX = absHostX / SCALE; int oldMsxY = absHostY / SCALE; absHostX += ms->getDeltaX(); absHostY += ms->getDeltaY(); int newMsxX = absHostX / SCALE; int newMsxY = absHostY / SCALE; int relMsxX = newMsxX - oldMsxX; int relMsxY = newMsxY - oldMsxY; // Verified with a real MSX-mouse (Philips SBC3810): // this value is not clipped to -128 .. 127. curxrel += relMsxX; curyrel += relMsxY; status = (status & ~ms->getPress()) | ms->getRelease(); } void Mouse::stopReplay(EmuTime::param time) { // TODO read actual host mouse button state int dx = 0 - curxrel; int dy = 0 - curyrel; byte release = (JOY_BUTTONA | JOY_BUTTONB) & ~status; if ((dx != 0) || (dy != 0) || (release != 0)) { createMouseStateChange(time, dx, dy, 0, release); } } // version 1: Initial version, the variables curxrel, curyrel and status were // not serialized. // version 2: Also serialize the above variables, this is required for // record/replay, see comment in Keyboard.cc for more details. // version 3: variables '(cur){x,y}rel' are scaled to MSX coordinates // version 4: simplified type of 'lastTime' from Clock<> to EmuTime template void Mouse::serialize(Archive& ar, unsigned version) { if (ar.isLoader() && isPluggedIn()) { // Do this early, because if something goes wrong while loading // some state below, then unplugHelper() gets called and that // will assert when plugHelper2() wasn't called yet. plugHelper2(); } if (ar.versionBelow(version, 4)) { assert(ar.isLoader()); Clock<1000> tmp(EmuTime::zero); ar.serialize("lastTime", tmp); lastTime = tmp.getTime(); } else { ar.serialize("lastTime", lastTime); } ar.serialize("faze", phase); // TODO fix spelling if there's ever a need // to bump the serialization verion ar.serialize("xrel", xrel); ar.serialize("yrel", yrel); ar.serialize("mouseMode", mouseMode); if (ar.versionAtLeast(version, 2)) { ar.serialize("curxrel", curxrel); ar.serialize("curyrel", curyrel); ar.serialize("status", status); } if (ar.versionBelow(version, 3)) { xrel /= SCALE; yrel /= SCALE; curxrel /= SCALE; curyrel /= SCALE; } // no need to serialize absHostX,Y } INSTANTIATE_SERIALIZE_METHODS(Mouse); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, Mouse, "Mouse"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/Mouse.hh000066400000000000000000000033551257557151200200040ustar00rootroot00000000000000#ifndef MOUSE_HH #define MOUSE_HH #include "JoystickDevice.hh" #include "MSXEventListener.hh" #include "StateChangeListener.hh" #include "serialize_meta.hh" namespace openmsx { class MSXEventDistributor; class StateChangeDistributor; class Mouse final : public JoystickDevice, private MSXEventListener , private StateChangeListener { public: Mouse(MSXEventDistributor& eventDistributor, StateChangeDistributor& stateChangeDistributor); ~Mouse(); template void serialize(Archive& ar, unsigned version); private: // Pluggable const std::string& getName() const override; string_ref getDescription() const override; void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; // JoystickDevice byte read(EmuTime::param time) override; void write(byte value, EmuTime::param time) override; // MSXEventListener void signalEvent(const std::shared_ptr& event, EmuTime::param time) override; // StateChangeListener void signalStateChange(const std::shared_ptr& event) override; void stopReplay(EmuTime::param time) override; void createMouseStateChange(EmuTime::param time, int deltaX, int deltaY, byte press, byte release); void emulateJoystick(); void plugHelper2(); MSXEventDistributor& eventDistributor; StateChangeDistributor& stateChangeDistributor; EmuTime lastTime; int phase; int xrel, yrel; // latched X/Y values, these are returned to the MSX int curxrel, curyrel; // running X/Y values, already scaled down int absHostX, absHostY; // running X/Y values, not yet scaled down byte status; bool mouseMode; }; SERIALIZE_CLASS_VERSION(Mouse, 4); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/NinjaTap.cc000066400000000000000000000035071257557151200204050ustar00rootroot00000000000000#include "NinjaTap.hh" #include "JoystickPort.hh" #include "serialize.hh" namespace openmsx { NinjaTap::NinjaTap(PluggingController& pluggingController, const std::string& name) : JoyTap(pluggingController, name) { status = 0x3F; // TODO check initial value previous = 0; for (auto& b : buf) { b = 0xFF; } } string_ref NinjaTap::getDescription() const { return "MSX Ninja Tap device"; } void NinjaTap::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/) { createPorts(pluggingController, "Ninja Tap port "); } byte NinjaTap::read(EmuTime::param /*time*/) { return status; } void NinjaTap::write(byte value, EmuTime::param time) { // bit 0 -> pin 6 // bit 1 -> pin 7 // bit 2 -> pin 8 if (value & 2) { // pin7 = 1 : read mode if (!(value & 1) && (previous & 1)) { // pin 6 1->0 : query joysticks // TODO does output change? for (int i = 0; i < 4; ++i) { byte t = slaves[i]->read(time); buf[i] = ((t & 0x0F) << 4) | ((t & 0x30) >> 4) | 0x0C; } } if (!(value & 4) && (previous & 4)) { // pin 8 1->0 : shift values // TODO what about b4 and b5? byte t = 0; for (int i = 0; i < 4; ++i) { if (buf[i] & 1) t |= (1 << i); buf[i] >>= 1; } status = (status & ~0x0F) | t; } } else { // pin 7 = 0 : detect mode, b5 is inverse of pin8 // TODO what happens with other bits? if (value & 4) { status &= ~0x20; } else { status |= 0x20; } } previous = value; } template void NinjaTap::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("status", status); ar.serialize("previous", previous); ar.serialize("buf", buf); } INSTANTIATE_SERIALIZE_METHODS(NinjaTap); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, NinjaTap, "NinjaTap"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/NinjaTap.hh000066400000000000000000000011661257557151200204160ustar00rootroot00000000000000#ifndef NINJATAP_HH #define NINJATAP_HH #include "JoyTap.hh" namespace openmsx { class NinjaTap final : public JoyTap { public: NinjaTap(PluggingController& pluggingController, const std::string& name); // Pluggable string_ref getDescription() const override; void plugHelper(Connector& connector, EmuTime::param time) override; // JoystickDevice byte read(EmuTime::param time) override; void write(byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: byte status; byte previous; byte buf[4]; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/RecordedCommand.cc000066400000000000000000000065411257557151200217300ustar00rootroot00000000000000#include "RecordedCommand.hh" #include "StateChangeDistributor.hh" #include "TclObject.hh" #include "Scheduler.hh" #include "StateChange.hh" #include "ScopedAssign.hh" #include "serialize.hh" #include "serialize_stl.hh" #include "unreachable.hh" using std::vector; using std::string; namespace openmsx { RecordedCommand::RecordedCommand(CommandController& commandController, StateChangeDistributor& stateChangeDistributor_, Scheduler& scheduler_, string_ref name) : Command(commandController, name) , stateChangeDistributor(stateChangeDistributor_) , scheduler(scheduler_) , currentResultObject(&dummyResultObject) { stateChangeDistributor.registerListener(*this); } RecordedCommand::~RecordedCommand() { stateChangeDistributor.unregisterListener(*this); } void RecordedCommand::execute(array_ref tokens, TclObject& result) { auto time = scheduler.getCurrentTime(); if (needRecord(tokens)) { ScopedAssign sa(currentResultObject, &result); stateChangeDistributor.distributeNew( std::make_shared(tokens, time)); } else { execute(tokens, result, time); } } bool RecordedCommand::needRecord(array_ref /*tokens*/) const { return true; } static string_ref getBaseName(string_ref str) { auto pos = str.rfind("::"); return (pos == string_ref::npos) ? str : str.substr(pos + 2); } void RecordedCommand::signalStateChange(const std::shared_ptr& event) { auto* commandEvent = dynamic_cast(event.get()); if (!commandEvent) return; auto& tokens = commandEvent->getTokens(); if (getBaseName(tokens[0].getString()) != getName()) return; if (needRecord(tokens)) { execute(tokens, *currentResultObject, commandEvent->getTime()); } else { // Normally this shouldn't happen. But it's possible in case // we're replaying a replay file that has manual edits in the // event log. It's crucial for security that we don't blindly // execute such commands. We already only execute // RecordedCommands, but we also need a strict check that // only commands that would be recorded are also replayed. // For example: // debug set_bp 0x0038 true {} // The debug write/write_block commands should be recorded and // replayed, but via the set_bp it would be possible to // execute arbitrary Tcl code. } } void RecordedCommand::stopReplay(EmuTime::param /*time*/) { // nothing } // class MSXCommandEvent MSXCommandEvent::MSXCommandEvent(array_ref tokens_, EmuTime::param time) : StateChange(time) { for (auto& t : tokens_) { tokens.emplace_back(t); } } MSXCommandEvent::MSXCommandEvent(array_ref tokens_, EmuTime::param time) : StateChange(time) , tokens(tokens_.begin(), tokens_.end()) { } template void MSXCommandEvent::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); // serialize vector as vector vector str; if (!ar.isLoader()) { for (auto& t : tokens) { str.push_back(t.getString().str()); } } ar.serialize("tokens", str); if (ar.isLoader()) { assert(tokens.empty()); for (auto& s : str) { tokens.push_back(TclObject(s)); } } } REGISTER_POLYMORPHIC_CLASS(StateChange, MSXCommandEvent, "MSXCommandEvent"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/RecordedCommand.hh000066400000000000000000000052041257557151200217350ustar00rootroot00000000000000#ifndef RECORDEDCOMMAND_HH #define RECORDEDCOMMAND_HH #include "Command.hh" #include "StateChangeListener.hh" #include "StateChange.hh" #include "TclObject.hh" #include "EmuTime.hh" namespace openmsx { class CommandController; class StateChangeDistributor; class Scheduler; /** This class is used to for Tcl commands that directly influence the MSX * state (e.g. plug, disk, cassetteplayer, reset). It's passed via an * event because the recording needs to see these. */ class MSXCommandEvent final : public StateChange { public: MSXCommandEvent() {} // for serialize MSXCommandEvent(array_ref tokens, EmuTime::param time); MSXCommandEvent(array_ref tokens, EmuTime::param time); const std::vector& getTokens() const { return tokens; } template void serialize(Archive& ar, unsigned version); private: std::vector tokens; }; /** Commands that directly influence the MSX state should send and events * so that they can be recorded by the event recorder. This class helps to * implement that. * * Note: when a recorded command is later replayed, it's important to check * whether it's actually a recorded command and not some arbitrary other * command that someone might have inserted in a replay file. IOW when you * load a replay file from an untrusted source, it should never be able to * cause any harm. Blindly executing any Tcl command would be bad. */ class RecordedCommand : public Command, private StateChangeListener { public: /** This is like the execute() method of the Command class, it only * has an extra time parameter. */ virtual void execute( array_ref tokens, TclObject& result, EmuTime::param time) = 0; /** It's possible that in some cases the command doesn't need to be * recorded after all (e.g. a query subcommand). In that case you can * override this method. Return false iff the command doesn't need * to be recorded. */ virtual bool needRecord(array_ref tokens) const; protected: RecordedCommand(CommandController& commandController, StateChangeDistributor& stateChangeDistributor, Scheduler& scheduler, string_ref name); ~RecordedCommand(); private: // Command void execute(array_ref tokens, TclObject& result) override; // StateChangeListener void signalStateChange(const std::shared_ptr& event) override; void stopReplay(EmuTime::param time) override; StateChangeDistributor& stateChangeDistributor; Scheduler& scheduler; TclObject dummyResultObject; TclObject* currentResultObject; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/SETetrisDongle.cc000066400000000000000000000023621257557151200215320ustar00rootroot00000000000000#include "SETetrisDongle.hh" #include "serialize.hh" #include "serialize_meta.hh" namespace openmsx { SETetrisDongle::SETetrisDongle() { status = JOY_UP | JOY_DOWN | JOY_LEFT | JOY_RIGHT | JOY_BUTTONA | JOY_BUTTONB; } // Pluggable const std::string& SETetrisDongle::getName() const { static const std::string name = "tetris2-protection"; return name; } string_ref SETetrisDongle::getDescription() const { return "Tetris II Special Edition dongle"; } void SETetrisDongle::plugHelper( Connector& /*connector*/, EmuTime::param /*time*/) { } void SETetrisDongle::unplugHelper(EmuTime::param /*time*/) { } // JoystickDevice byte SETetrisDongle::read(EmuTime::param /*time*/) { return status; } void SETetrisDongle::write(byte value, EmuTime::param /*time*/) { // Original device used 4 NOR ports // pin4 will be value of pin7 if (value & 2) { status |= JOY_RIGHT; } else { status &= ~JOY_RIGHT; } } template void SETetrisDongle::serialize(Archive& /*ar*/, unsigned /*version*/) { // no need to serialize 'status', port will anyway be re-written // on de-serialize } INSTANTIATE_SERIALIZE_METHODS(SETetrisDongle); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, SETetrisDongle, "SETetrisDongle"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/SETetrisDongle.hh000066400000000000000000000012271257557151200215430ustar00rootroot00000000000000#ifndef SETETRISDONGLE_HH #define SETETRISDONGLE_HH #include "JoystickDevice.hh" namespace openmsx { class SETetrisDongle final : public JoystickDevice { public: SETetrisDongle(); // Pluggable const std::string& getName() const override; string_ref getDescription() const override; void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; // JoystickDevice byte read(EmuTime::param time) override; void write(byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: byte status; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/StateChange.hh000066400000000000000000000015441257557151200211000ustar00rootroot00000000000000#ifndef STATECHANGE_HH #define STATECHANGE_HH #include "EmuTime.hh" #include "serialize_meta.hh" #include "noncopyable.hh" namespace openmsx { /** Base class for all external MSX state changing events. * These are typically triggered by user input, like keyboard presses. The main * reason why these events exist is to be able to record and replay them. */ class StateChange : private noncopyable { public: virtual ~StateChange() {} // must be polymorhpic EmuTime::param getTime() const { return time; } template void serialize(Archive& ar, unsigned /*version*/) { ar.serialize("time", time); } protected: StateChange() : time(EmuTime::zero) {} // for serialize StateChange(EmuTime::param time_) : time(time_) { } private: EmuTime time; }; REGISTER_BASE_CLASS(StateChange, "StateChange"); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/StateChangeDistributor.cc000066400000000000000000000043711257557151200233220ustar00rootroot00000000000000#include "StateChangeDistributor.hh" #include "StateChangeListener.hh" #include "StateChange.hh" #include "stl.hh" #include #include namespace openmsx { StateChangeDistributor::StateChangeDistributor() : recorder(nullptr) , viewOnlyMode(false) { } StateChangeDistributor::~StateChangeDistributor() { assert(listeners.empty()); } bool StateChangeDistributor::isRegistered(StateChangeListener* listener) const { return contains(listeners, listener); } void StateChangeDistributor::registerListener(StateChangeListener& listener) { assert(!isRegistered(&listener)); listeners.push_back(&listener); } void StateChangeDistributor::unregisterListener(StateChangeListener& listener) { listeners.erase(find_unguarded(listeners, &listener)); } void StateChangeDistributor::registerRecorder(StateChangeRecorder& recorder_) { assert(!recorder); recorder = &recorder_; } void StateChangeDistributor::unregisterRecorder(StateChangeRecorder& recorder_) { (void)recorder_; assert(recorder == &recorder_); recorder = nullptr; } void StateChangeDistributor::distributeNew(const EventPtr& event) { if (viewOnlyMode && isReplaying()) return; if (isReplaying()) { stopReplay(event->getTime()); } distribute(event); } void StateChangeDistributor::distributeReplay(const EventPtr& event) { assert(isReplaying()); distribute(event); } void StateChangeDistributor::distribute(const EventPtr& event) { // Iterate over a copy because signalStateChange() may indirect call // back into registerListener(). // e.g. signalStateChange() -> .. -> PlugCmd::execute() -> .. -> // Connector::plug() -> .. -> Joystick::plugHelper() -> // registerListener() if (recorder) recorder->signalStateChange(event); auto copy = listeners; for (auto& l : copy) { if (isRegistered(l)) { // it's possible the listener unregistered itself // (but is still present in the copy) l->signalStateChange(event); } } } void StateChangeDistributor::stopReplay(EmuTime::param time) { if (!isReplaying()) return; if (recorder) recorder->stopReplay(time); for (auto& l : listeners) { l->stopReplay(time); } } bool StateChangeDistributor::isReplaying() const { if (recorder) { return recorder->isReplaying(); } return false; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/StateChangeDistributor.hh000066400000000000000000000053451257557151200233360ustar00rootroot00000000000000#ifndef STATECHANGEDISTRIBUTOR_HH #define STATECHANGEDISTRIBUTOR_HH #include "EmuTime.hh" #include "noncopyable.hh" #include #include namespace openmsx { class StateChangeListener; class StateChangeRecorder; class StateChange; class StateChangeDistributor : private noncopyable { public: using EventPtr = std::shared_ptr; StateChangeDistributor(); ~StateChangeDistributor(); /** (Un)registers the given object to receive state change events. * @param listener Listener that will be notified when an event arrives. */ void registerListener (StateChangeListener& listener); void unregisterListener(StateChangeListener& listener); /** (Un)registers the given object to receive state change events. * @param recorder Listener that will be notified when an event arrives. * These two methods are very similar to the two above. The difference * is that there can be at most one registered recorder. This recorder * object is always the first object that gets informed about state * changing events. */ void registerRecorder (StateChangeRecorder& recorder); void unregisterRecorder(StateChangeRecorder& recorder); /** Deliver the event to all registered listeners * MSX input devices should call the distributeNew() version, only the * replayer should call the distributeReplay() version. * These two different versions are used to detect the transition from * replayed events to live events. Note that a transition from live to * replay is not allowed. This transition should be done by creating a * new StateChangeDistributor object (object always starts in replay * state), but this is automatically taken care of because replay * always starts from a freshly restored snapshot. * @param event The event */ void distributeNew (const EventPtr& event); void distributeReplay(const EventPtr& event); /** Explicitly stop replay. * Should be called when replay->live transition cannot be signaled via * a new event, so for example when we reach the end of the replay log. * It's OK to call this method when replay was already stopped, in that * case this call has no effect. */ void stopReplay(EmuTime::param time); /** * Set viewOnlyMode. Call this if you don't want distributeNew events * to stop replaying and go to live events (value=true). * @param value false if new events stop replay mode */ void setViewOnlyMode(bool value) { viewOnlyMode = value; } bool isViewOnlyMode() const { return viewOnlyMode; } bool isReplaying() const; private: bool isRegistered(StateChangeListener* listener) const; void distribute(const EventPtr& event); std::vector listeners; StateChangeRecorder* recorder; bool viewOnlyMode; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/StateChangeListener.hh000066400000000000000000000021321257557151200226000ustar00rootroot00000000000000#ifndef STATECHANGELISTENER_HH #define STATECHANGELISTENER_HH #include "EmuTime.hh" #include namespace openmsx { class StateChange; class StateChangeListener { public: /** This method gets called when a StateChange event occurs. * This can be either a replayed or a 'live' event, (though that * shouldn't matter, it should be handled in exactly the same way). */ virtual void signalStateChange( const std::shared_ptr& event) = 0; /** This method gets called when we switch from replayed events to * live events. A input device should resync its state with the current * host state. A replayer/recorder should switch from replay to record. * Note that it's not possible to switch back to replay state (when * the user triggers a replay, we always start from a snapshot, so * we create 'fresh' objects). */ virtual void stopReplay(EmuTime::param time) = 0; protected: StateChangeListener() {} ~StateChangeListener() {} }; class StateChangeRecorder : public StateChangeListener { public: virtual bool isReplaying() const = 0; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/Touchpad.cc000066400000000000000000000201431257557151200204430ustar00rootroot00000000000000// This implementation is based on the reverse-engineering effort done // by 'SD-snatcher'. See here: // http://www.msx.org/news/en/msx-touchpad-protocol-reverse-engineered // http://www.msx.org/wiki/Touchpad // Also thanks to Manuel Bilderbeek for donating a Philips NMS-1150 that // made this effort possible. #include "Touchpad.hh" #include "MSXEventDistributor.hh" #include "StateChangeDistributor.hh" #include "InputEvents.hh" #include "StateChange.hh" #include "CommandController.hh" #include "CommandException.hh" #include "Clock.hh" #include "Math.hh" #include "checked_cast.hh" #include "serialize.hh" #include "serialize_meta.hh" #include #include using std::shared_ptr; using namespace gl; namespace openmsx { class TouchpadState final : public StateChange { public: TouchpadState() {} // for serialize TouchpadState(EmuTime::param time, byte x_, byte y_, bool touch_, bool button_) : StateChange(time) , x(x_), y(y_), touch(touch_), button(button_) {} byte getX() const { return x; } byte getY() const { return y; } bool getTouch() const { return touch; } bool getButton() const { return button; } template void serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("x", x); ar.serialize("y", y); ar.serialize("touch", touch); ar.serialize("button", button); } private: byte x, y; bool touch, button; }; REGISTER_POLYMORPHIC_CLASS(StateChange, TouchpadState, "TouchpadState"); Touchpad::Touchpad(MSXEventDistributor& eventDistributor_, StateChangeDistributor& stateChangeDistributor_, CommandController& commandController) : eventDistributor(eventDistributor_) , stateChangeDistributor(stateChangeDistributor_) , transformSetting(commandController, "touchpad_transform_matrix", "2x3 matrix to transform host mouse coordinates to " "MSX touchpad coordinates, see manual for details", "{ 256 0 0 } { 0 256 0 }") , start(EmuTime::zero) , hostButtons(0) , x(0), y(0), touch(false), button(false) , shift(0), channel(0), last(0) { auto& interp = commandController.getInterpreter(); transformSetting.setChecker([this, &interp](TclObject& newValue) { try { parseTransformMatrix(interp, newValue); } catch (CommandException& e) { throw CommandException( "Invalid transformation matrix: " + e.getMessage()); } }); try { parseTransformMatrix(interp, transformSetting.getValue()); } catch (CommandException& e) { // should only happen when settings.xml was manually edited std::cerr << e.getMessage() << std::endl; // fill in safe default values m[0][0] = 256.0f; m[0][1] = 0.0f; m[0][2] = 0.0f; m[1][0] = 0.0f; m[1][1] = 256.0f; m[1][2] = 0.0f; } } Touchpad::~Touchpad() { if (isPluggedIn()) { Touchpad::unplugHelper(EmuTime::dummy()); } } void Touchpad::parseTransformMatrix(Interpreter& interp, const TclObject& value) { if (value.getListLength(interp) != 2) { throw CommandException("must have 2 rows"); } for (int i = 0; i < 2; ++i) { TclObject row = value.getListIndex(interp, i); if (row.getListLength(interp) != 3) { throw CommandException("each row must have 3 elements"); } for (int j = 0; j < 3; ++j) { m[i][j] = row.getListIndex(interp, j).getDouble(interp); } } } // Pluggable const std::string& Touchpad::getName() const { static const std::string name("touchpad"); return name; } string_ref Touchpad::getDescription() const { return "MSX Touchpad"; } void Touchpad::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/) { eventDistributor.registerEventListener(*this); stateChangeDistributor.registerListener(*this); } void Touchpad::unplugHelper(EmuTime::param /*time*/) { stateChangeDistributor.unregisterListener(*this); eventDistributor.unregisterEventListener(*this); } // JoystickDevice static const byte SENSE = JoystickDevice::RD_PIN1; static const byte EOC = JoystickDevice::RD_PIN2; static const byte SO = JoystickDevice::RD_PIN3; static const byte BUTTON = JoystickDevice::RD_PIN4; static const byte SCK = JoystickDevice::WR_PIN6; static const byte SI = JoystickDevice::WR_PIN7; static const byte CS = JoystickDevice::WR_PIN8; byte Touchpad::read(EmuTime::param time) { byte result = SENSE | BUTTON; // 1-bit means not pressed if (touch) result &= ~SENSE; if (button) result &= ~BUTTON; // EOC remains zero for 56 cycles after CS 0->1 // TODO at what clock frequency does the UPD7001 run? // 400kHz is only the recommended value from the UPD7001 datasheet. static const EmuDuration delta = Clock<400000>::duration(56); if ((time - start) > delta) { result |= EOC; } if (shift & 0x80) result |= SO; if (last & CS) result |= SO; return result | 0x30; } void Touchpad::write(byte value, EmuTime::param time) { byte diff = last ^ value; last = value; if (diff & CS) { if (value & CS) { // CS 0->1 channel = shift & 3; start = time; // to keep EOC=0 for 56 cycles } else { // CS 1->0 // Tested by SD-Snatcher (see RFE #252): // When not touched X is always 0, and Y floats // between 147 and 149 (mostly 148). shift = (channel == 0) ? (touch ? x : 0) : (channel == 3) ? (touch ? y : 148) : 0; // channel 1 and 2 return 0 } } if (((value & (CS | SCK)) == SCK) && (diff & SCK)) { // SC=0 & SCK 0->1 shift <<= 1; shift |= (value & SI) != 0; } } ivec2 Touchpad::transformCoords(ivec2 xy) { if (SDL_Surface* surf = SDL_GetVideoSurface()) { vec2 uv = vec2(xy) / vec2(surf->w, surf->h); xy = ivec2(m * vec3(uv, 1.0f)); } return clamp(xy, 0, 255); } // MSXEventListener void Touchpad::signalEvent(const shared_ptr& event, EmuTime::param time) { ivec2 pos = hostPos; int b = hostButtons; switch (event->getType()) { case OPENMSX_MOUSE_MOTION_EVENT: { auto& mev = checked_cast(*event); pos = transformCoords(ivec2(mev.getAbsX(), mev.getAbsY())); break; } case OPENMSX_MOUSE_BUTTON_DOWN_EVENT: { auto& butEv = checked_cast(*event); switch (butEv.getButton()) { case MouseButtonEvent::LEFT: b |= 1; break; case MouseButtonEvent::RIGHT: b |= 2; break; default: // ignore other buttons break; } break; } case OPENMSX_MOUSE_BUTTON_UP_EVENT: { auto& butEv = checked_cast(*event); switch (butEv.getButton()) { case MouseButtonEvent::LEFT: b &= ~1; break; case MouseButtonEvent::RIGHT: b &= ~2; break; default: // ignore other buttons break; } break; } default: // ignore break; } if ((pos != hostPos) || (b != hostButtons)) { hostPos = pos; hostButtons = b; createTouchpadStateChange( time, pos[0], pos[1], (hostButtons & 1) != 0, (hostButtons & 2) != 0); } } void Touchpad::createTouchpadStateChange( EmuTime::param time, byte x, byte y, bool touch, bool button) { stateChangeDistributor.distributeNew(std::make_shared( time, x, y, touch, button)); } // StateChangeListener void Touchpad::signalStateChange(const shared_ptr& event) { if (auto* ts = dynamic_cast(event.get())) { x = ts->getX(); y = ts->getY(); touch = ts->getTouch(); button = ts->getButton(); } } void Touchpad::stopReplay(EmuTime::param time) { // TODO Get actual mouse state. Is it worth the trouble? if (x || y || touch || button) { stateChangeDistributor.distributeNew( std::make_shared( time, 0, 0, false, false)); } } template void Touchpad::serialize(Archive& ar, unsigned /*version*/) { // no need to serialize hostX, hostY, hostButtons, // transformSetting, m[][] ar.serialize("start", start); ar.serialize("x", x); ar.serialize("y", y); ar.serialize("touch", touch); ar.serialize("button", button); ar.serialize("shift", shift); ar.serialize("channel", channel); ar.serialize("last", last); if (ar.isLoader() && isPluggedIn()) { plugHelper(*getConnector(), EmuTime::dummy()); } } INSTANTIATE_SERIALIZE_METHODS(Touchpad); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, Touchpad, "Touchpad"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/Touchpad.hh000066400000000000000000000041431257557151200204570ustar00rootroot00000000000000#ifndef TOUCHPAD_HH #define TOUCHPAD_HH #include "JoystickDevice.hh" #include "MSXEventListener.hh" #include "StateChangeListener.hh" #include "StringSetting.hh" #include "gl_mat.hh" namespace openmsx { class MSXEventDistributor; class StateChangeDistributor; class CommandController; class TclObject; class Interpreter; class Touchpad final : public JoystickDevice, private MSXEventListener , private StateChangeListener { public: Touchpad(MSXEventDistributor& eventDistributor, StateChangeDistributor& stateChangeDistributor, CommandController& commandController); ~Touchpad(); template void serialize(Archive& ar, unsigned version); private: void createTouchpadStateChange(EmuTime::param time, byte x, byte y, bool touch, bool button); void parseTransformMatrix(Interpreter& interp, const TclObject& value); gl::ivec2 transformCoords(gl::ivec2 xy); // Pluggable const std::string& getName() const override; string_ref getDescription() const override; void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; // JoystickDevice byte read(EmuTime::param time) override; void write(byte value, EmuTime::param time) override; // MSXEventListener void signalEvent(const std::shared_ptr& event, EmuTime::param time) override; // StateChangeListener void signalStateChange(const std::shared_ptr& event) override; void stopReplay(EmuTime::param time) override; MSXEventDistributor& eventDistributor; StateChangeDistributor& stateChangeDistributor; StringSetting transformSetting; gl::matMxN<2, 3, float> m; // transformation matrix EmuTime start; // last time when CS switched 0->1 gl::ivec2 hostPos; // host state byte hostButtons; // byte x, y; // msx state (different from host state bool touch, button; // during replay) byte shift; // shift register to both transmit and receive data byte channel; // [0..3] 0->x, 3->y, 1,2->not used byte last; // last written data, to detect transitions }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/Trackball.cc000066400000000000000000000245131257557151200206000ustar00rootroot00000000000000#include "Trackball.hh" #include "MSXEventDistributor.hh" #include "StateChangeDistributor.hh" #include "InputEvents.hh" #include "StateChange.hh" #include "Math.hh" #include "checked_cast.hh" #include "serialize.hh" #include "serialize_meta.hh" #include // * Implementation based on information we received from 'n_n'. // It might not be 100% accurate. But games like 'Hole in one' already work. // * Initially the 'trackball detection' code didn't work properly in openMSX // while it did work in meisei. Meisei had some special cases implemented for // the first read after reset. After some investigation I figured out some // code without special cases that also works as expected. Most software // seems to work now, though the detailed behaviour is still not tested // against the real hardware. using std::string; using std::shared_ptr; namespace openmsx { class TrackballState final : public StateChange { public: TrackballState() {} // for serialize TrackballState(EmuTime::param time, int deltaX_, int deltaY_, byte press_, byte release_) : StateChange(time) , deltaX(deltaX_), deltaY(deltaY_) , press(press_), release(release_) {} int getDeltaX() const { return deltaX; } int getDeltaY() const { return deltaY; } byte getPress() const { return press; } byte getRelease() const { return release; } template void serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("deltaX", deltaX); ar.serialize("deltaY", deltaY); ar.serialize("press", press); ar.serialize("release", release); } private: int deltaX, deltaY; byte press, release; }; REGISTER_POLYMORPHIC_CLASS(StateChange, TrackballState, "TrackballState"); Trackball::Trackball(MSXEventDistributor& eventDistributor_, StateChangeDistributor& stateChangeDistributor_) : eventDistributor(eventDistributor_) , stateChangeDistributor(stateChangeDistributor_) , lastSync(EmuTime::zero) , targetDeltaX(0), targetDeltaY(0) , currentDeltaX(0), currentDeltaY(0) , lastValue(0) , status(JOY_BUTTONA | JOY_BUTTONB) , smooth(true) { } Trackball::~Trackball() { if (isPluggedIn()) { Trackball::unplugHelper(EmuTime::dummy()); } } // Pluggable const string& Trackball::getName() const { static const string name("trackball"); return name; } string_ref Trackball::getDescription() const { return "MSX Trackball"; } void Trackball::plugHelper(Connector& /*connector*/, EmuTime::param time) { eventDistributor.registerEventListener(*this); stateChangeDistributor.registerListener(*this); lastSync = time; targetDeltaX = targetDeltaY = 0; currentDeltaX = currentDeltaY = 0; } void Trackball::unplugHelper(EmuTime::param /*time*/) { stateChangeDistributor.unregisterListener(*this); eventDistributor.unregisterEventListener(*this); } // JoystickDevice byte Trackball::read(EmuTime::param time) { // From the Sony GB-7 Service manual: // http://cdn.preterhuman.net/texts/computing/msx/sonygb7sm.pdf // * The counter seems to be 8-bit wide, though only 4 bits (bit 7 and // 2-0) are connected to the MSX. Looking at the M60226 block diagram // in more detail shows that the (up/down-)counters have a 'HP' // input, in the GB7 this input is hardwired to GND. My *guess* is // that HP stands for either 'Half-' or 'High-precision' and that it // selects between either 4 or 8 bits saturation. // The bug report '#477 Trackball emulation overflow values too // easily' contains a small movie. Some very rough calculations // indicate that when you move a cursor 50 times per second, 8 pixels // per step, you get about the same speed as in that move. // So even though both 4 and 8 bit clipping *seem* to be possible, // this code only implements 4 bit clipping. // * It also contains a test program to read the trackball position. // This program first reads the (X or Y) value and only then toggles // pin 8. This seems to suggest the actual (X or Y) value is always // present on reads and toggling pin 8 resets this value and switches // to the other axis. syncCurrentWithTarget(time); auto delta = (lastValue & 4) ? currentDeltaY : currentDeltaX; return (status & ~0x0F) | ((delta + 8) & 0x0F); } void Trackball::write(byte value, EmuTime::param time) { syncCurrentWithTarget(time); byte diff = lastValue ^ value; lastValue = value; if (diff & 0x4) { // pin 8 flipped if (value & 4) { targetDeltaX = Math::clip<-8, 7>(targetDeltaX - currentDeltaX); currentDeltaX = 0; } else { targetDeltaY = Math::clip<-8, 7>(targetDeltaY - currentDeltaY); currentDeltaY = 0; } } } void Trackball::syncCurrentWithTarget(EmuTime::param time) { // In the past we only had 'targetDeltaXY' (was named 'deltaXY' then). // 'currentDeltaXY' was introduced to (slightly) smooth-out the // discrete host mouse movement events over time. This method performs // that smoothing calculation. // // In emulation, the trackball movement is driven by host mouse // movement events. These events are discrete. So for example if we // receive a host mouse event with offset (3,-4) we immediately adjust // 'targetDeltaXY' by that offset. However in reality mouse movement // doesn't make such discrete jumps. If you look at small enough time // intervals you'll see that the offset smoothly varies from (0,0) to // (3,-4). // // Most often this discretization step doesn't matter. However the BIOS // routine GTPAD to read mouse/trackball (more specifically PAD(12) and // PAD(16) has an heuristic to distinguish the mouse from the trackball // protocol. I won't go into detail, but in short it's reading the // mouse/trackball two times shortly after each other and checks // whether the offset of the 2nd read is centered around 0 (for mouse) // or 8 (for trackball). There's only about 1 millisecond between the // two reads, so in reality the mouse/trackball won't have moved much // during that time. However in emulation because of the discretization // we can get unlucky and do see a large offset for the 2nd read. This // confuses the BIOS (it thinks it's talking to a mouse instead of // trackball) and it results in erratic trackball movement. // // Thus to work around this problem (=make the heuristic in the BIOS // work) we smear-out host mouse events over (emulated) time. Instead // of immediately following the host movement, we limit changes in the // emulated offsets to a rate of 1 step per millisecond. This // introduces some delay, but usually (for not too fast movements) it's // not noticeable. if (!smooth) { // for backwards-compatible replay files currentDeltaX = targetDeltaX; currentDeltaY = targetDeltaY; return; } static const EmuDuration INTERVAL = EmuDuration::msec(1); int maxSteps = (time - lastSync) / INTERVAL; lastSync += INTERVAL * maxSteps; if (targetDeltaX >= currentDeltaX) { currentDeltaX = std::min(currentDeltaX + maxSteps, targetDeltaX); } else { currentDeltaX = std::max(currentDeltaX - maxSteps, targetDeltaX); } if (targetDeltaY >= currentDeltaY) { currentDeltaY = std::min(currentDeltaY + maxSteps, targetDeltaY); } else { currentDeltaY = std::max(currentDeltaY - maxSteps, targetDeltaY); } } // MSXEventListener void Trackball::signalEvent(const shared_ptr& event, EmuTime::param time) { switch (event->getType()) { case OPENMSX_MOUSE_MOTION_EVENT: { auto& mev = checked_cast(*event); static const int SCALE = 2; int dx = mev.getX() / SCALE; int dy = mev.getY() / SCALE; if ((dx != 0) || (dy != 0)) { createTrackballStateChange(time, dx, dy, 0, 0); } break; } case OPENMSX_MOUSE_BUTTON_DOWN_EVENT: { auto& butEv = checked_cast(*event); switch (butEv.getButton()) { case MouseButtonEvent::LEFT: createTrackballStateChange(time, 0, 0, JOY_BUTTONA, 0); break; case MouseButtonEvent::RIGHT: createTrackballStateChange(time, 0, 0, JOY_BUTTONB, 0); break; default: // ignore other buttons break; } break; } case OPENMSX_MOUSE_BUTTON_UP_EVENT: { auto& butEv = checked_cast(*event); switch (butEv.getButton()) { case MouseButtonEvent::LEFT: createTrackballStateChange(time, 0, 0, 0, JOY_BUTTONA); break; case MouseButtonEvent::RIGHT: createTrackballStateChange(time, 0, 0, 0, JOY_BUTTONB); break; default: // ignore other buttons break; } break; } default: // ignore break; } } void Trackball::createTrackballStateChange( EmuTime::param time, int deltaX, int deltaY, byte press, byte release) { stateChangeDistributor.distributeNew(std::make_shared( time, deltaX, deltaY, press, release)); } // StateChangeListener void Trackball::signalStateChange(const shared_ptr& event) { auto ts = dynamic_cast(event.get()); if (!ts) return; targetDeltaX = Math::clip<-8, 7>(targetDeltaX + ts->getDeltaX()); targetDeltaY = Math::clip<-8, 7>(targetDeltaY + ts->getDeltaY()); status = (status & ~ts->getPress()) | ts->getRelease(); } void Trackball::stopReplay(EmuTime::param time) { syncCurrentWithTarget(time); // TODO Get actual mouse button(s) state. Is it worth the trouble? byte release = (JOY_BUTTONA | JOY_BUTTONB) & ~status; if ((currentDeltaX != 0) || (currentDeltaY != 0) || (release != 0)) { stateChangeDistributor.distributeNew( std::make_shared( time, -currentDeltaX, -currentDeltaY, 0, release)); } } // version 1: initial version // version 2: replaced deltaXY with targetDeltaXY and currentDeltaXY template void Trackball::serialize(Archive& ar, unsigned version) { if (ar.versionAtLeast(version, 2)) { ar.serialize("lastSync" ,lastSync); ar.serialize("targetDeltaX", targetDeltaX); ar.serialize("targetDeltaY", targetDeltaY); ar.serialize("currentDeltaX", currentDeltaX); ar.serialize("currentDeltaY", currentDeltaY); } else { ar.serialize("deltaX", targetDeltaX); ar.serialize("deltaY", targetDeltaY); currentDeltaX = targetDeltaX; currentDeltaY = targetDeltaY; smooth = false; } ar.serialize("lastValue", lastValue); ar.serialize("status", status); if (ar.isLoader() && isPluggedIn()) { plugHelper(*getConnector(), EmuTime::dummy()); } } INSTANTIATE_SERIALIZE_METHODS(Trackball); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, Trackball, "Trackball"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/Trackball.hh000066400000000000000000000034711257557151200206120ustar00rootroot00000000000000#ifndef TRACKBALL_HH #define TRACKBALL_HH #include "JoystickDevice.hh" #include "MSXEventListener.hh" #include "StateChangeListener.hh" #include "serialize_meta.hh" namespace openmsx { class MSXEventDistributor; class StateChangeDistributor; class Trackball final : public JoystickDevice, private MSXEventListener , private StateChangeListener { public: Trackball(MSXEventDistributor& eventDistributor, StateChangeDistributor& stateChangeDistributor); ~Trackball(); template void serialize(Archive& ar, unsigned version); private: void createTrackballStateChange(EmuTime::param time, int deltaX, int deltaY, byte press, byte release); void syncCurrentWithTarget(EmuTime::param time); // Pluggable const std::string& getName() const override; string_ref getDescription() const override; void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; // JoystickDevice byte read(EmuTime::param time) override; void write(byte value, EmuTime::param time) override; // MSXEventListener void signalEvent(const std::shared_ptr& event, EmuTime::param time) override; // StateChangeListener void signalStateChange(const std::shared_ptr& event) override; void stopReplay(EmuTime::param time) override; MSXEventDistributor& eventDistributor; StateChangeDistributor& stateChangeDistributor; EmuTime lastSync; // last time we synced current with target signed char targetDeltaX, targetDeltaY; // immediately follows host events signed char currentDeltaX, currentDeltaY; // follows targetXY with some delay byte lastValue; byte status; bool smooth; // always true, except for bw-compat savestates }; SERIALIZE_CLASS_VERSION(Trackball, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/input/UnicodeKeymap.cc000066400000000000000000000142411257557151200214330ustar00rootroot00000000000000#include "UnicodeKeymap.hh" #include "MSXException.hh" #include "File.hh" #include "FileContext.hh" #include "FileException.hh" #include "StringOp.hh" #include "stl.hh" #include #include namespace openmsx { /** Parses the string given by the inclusive begin point and exclusive end * pointer as a hexadecimal integer. * If successful, returns the parsed value and sets "ok" to true. * If unsuccessful, returns 0 and sets "ok" to false. */ static unsigned parseHex(const char* begin, const char* end, bool& ok) { unsigned value = 0; for (; begin != end; begin++) { value *= 16; const char c = *begin; if ('0' <= c && c <= '9') { value += c - '0'; } else if ('A' <= c && c <= 'F') { value += c - 'A' + 10; } else if ('a' <= c && c <= 'f') { value += c - 'a' + 10; } else { ok = false; return 0; } } ok = true; return value; } /** Returns true iff the given character is a separator. * Separators are: comma, whitespace and hash mark. */ static inline bool isSep(char c) { return c == ',' // comma || c == ' ' || c == '\t' || c == '\r' // whitespace || c == '#'; // comment } /** Returns a pointer to the first separator character in the given string, * or a pointer to the end of the line if no separator is found. */ static const char* findSep(const char* begin, const char* end) { while (begin != end && *begin != '\n' && !isSep(*begin)) begin++; return begin; } /** Returns a pointer to the first non-separator character in the given string, * or a pointer to the end of the line if only separators are found. * Characters between a hash mark and the following newline are also skipped. */ static const char* skipSep(const char* begin, const char* end) { while (begin != end) { const char c = *begin; if (!isSep(c)) break; begin++; if (c == '#') { // Skip till end of line. while (begin != end && *begin != '\n') begin++; break; } } return begin; } /** Return true iff the substring [begin, end) equals the given string literal */ template static bool segmentEquals(const char* begin, const char* end, const char (&string)[N]) { return ((end - begin) == (N - 1)) && (strncmp(begin, string, N - 1) == 0); } /** Return true iff the substring [begin, end) starts with the given string literal */ template static bool segmentStartsWith(const char* begin, const char* end, const char (&string)[N]) { return ((end - begin) >= (N - 1)) && (strncmp(begin, string, N - 1) == 0); } UnicodeKeymap::UnicodeKeymap(string_ref keyboardType) : emptyInfo(KeyInfo()) { auto filename = systemFileContext().resolve( "unicodemaps/unicodemap." + keyboardType); try { File file(filename); size_t size; const byte* buf = file.mmap(size); parseUnicodeKeymapfile( reinterpret_cast(buf), reinterpret_cast(buf + size)); } catch (FileException&) { throw MSXException("Couldn't load unicode keymap file: " + filename); } } UnicodeKeymap::KeyInfo UnicodeKeymap::get(int unicode) const { auto it = lower_bound(begin(mapdata), end(mapdata), unicode, LessTupleElement<0>()); return ((it != end(mapdata)) && (it->first == unicode)) ? it->second : emptyInfo; } UnicodeKeymap::KeyInfo UnicodeKeymap::getDeadkey(unsigned n) const { assert(n < NUM_DEAD_KEYS); return deadKeys[n]; } void UnicodeKeymap::parseUnicodeKeymapfile(const char* b, const char* e) { b = skipSep(b, e); while (true) { // Find a line containing tokens. while (b != e && *b == '\n') { // Next line. b = skipSep(b + 1, e); } if (b == e) break; // Parse first token: a unicode value or the keyword DEADKEY. const char* tokenEnd = findSep(b, e); int unicode = 0; unsigned deadKeyIndex = 0; bool isDeadKey = segmentStartsWith(b, tokenEnd, "DEADKEY"); if (isDeadKey) { const char* begin2 = b + strlen("DEADKEY"); if (begin2 == tokenEnd) { // The normal keywords are // DEADKEY1 DEADKEY2 DEADKEY3 // but for backwards compatibility also still recognize // DEADKEY } else { bool ok; deadKeyIndex = parseHex(begin2, tokenEnd, ok); deadKeyIndex--; // Make index 0 based instead of 1 based if (!ok || deadKeyIndex >= NUM_DEAD_KEYS) { throw MSXException(StringOp::Builder() << "Wrong deadkey number in keymap file. " "It must be 1.." << NUM_DEAD_KEYS); } } } else { bool ok; unicode = parseHex(b, tokenEnd, ok); if (!ok || unicode > 0xFFFF) { throw MSXException("Wrong unicode value in keymap file"); } } b = skipSep(tokenEnd, e); // Parse second token. It must be tokenEnd = findSep(b, e); if (tokenEnd == e) { throw MSXException("Missing in unicode file"); } bool ok; int rowcol = parseHex(b, tokenEnd, ok); if (!ok || rowcol >= 0x100) { throw MSXException("Wrong rowcol value in keymap file"); } if ((rowcol >> 4) >= 11) { throw MSXException("Too high row value in keymap file"); } if ((rowcol & 0x0F) >= 8) { throw MSXException("Too high column value in keymap file"); } b = skipSep(tokenEnd, e); // Parse remaining tokens. It is an optional list of modifier keywords. byte modmask = 0; while (b != e && *b != '\n') { tokenEnd = findSep(b, e); if (segmentEquals(b, tokenEnd, "SHIFT")) { modmask |= 1; } else if (segmentEquals(b, tokenEnd, "CTRL")) { modmask |= 2; } else if (segmentEquals(b, tokenEnd, "GRAPH")) { modmask |= 4; } else if (segmentEquals(b, tokenEnd, "CAPSLOCK")) { modmask |= 8; } else if (segmentEquals(b, tokenEnd, "CODE")) { modmask |= 16; } else { throw MSXException(StringOp::Builder() << "Invalid modifier \"" << string_ref(b, tokenEnd) << "\" in keymap file"); } b = skipSep(tokenEnd, e); } if (isDeadKey) { deadKeys[deadKeyIndex] = KeyInfo( (rowcol >> 4) & 0x0f, // row 1 << (rowcol & 7), // keymask 0); // modmask } else { mapdata.emplace_back(unicode, KeyInfo( (rowcol >> 4) & 0x0f, // row 1 << (rowcol & 7), // keymask modmask)); // modmask } } sort(begin(mapdata), end(mapdata), LessTupleElement<0>()); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/input/UnicodeKeymap.hh000066400000000000000000000015741257557151200214520ustar00rootroot00000000000000#ifndef UNICODEKEYMAP_HH #define UNICODEKEYMAP_HH #include "openmsx.hh" #include "string_ref.hh" #include #include #include namespace openmsx { class UnicodeKeymap { public: struct KeyInfo { KeyInfo(byte row_, byte keymask_, byte modmask_) : row(row_), keymask(keymask_), modmask(modmask_) { if (keymask == 0) { assert(row == 0); assert(modmask == 0); } } KeyInfo() : row(0), keymask(0), modmask(0) { } byte row, keymask, modmask; }; explicit UnicodeKeymap(string_ref keyboardType); KeyInfo get(int unicode) const; KeyInfo getDeadkey(unsigned n) const; private: static const unsigned NUM_DEAD_KEYS = 3; void parseUnicodeKeymapfile(const char* begin, const char* end); std::vector> mapdata; KeyInfo deadKeys[NUM_DEAD_KEYS]; const KeyInfo emptyInfo; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/laserdisc/000077500000000000000000000000001257557151200171775ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/src/laserdisc/LaserdiscPlayer.cc000066400000000000000000000710571257557151200226060ustar00rootroot00000000000000#include "LaserdiscPlayer.hh" #include "CommandException.hh" #include "CommandController.hh" #include "EventDistributor.hh" #include "FileContext.hh" #include "DeviceConfig.hh" #include "HardwareConfig.hh" #include "XMLElement.hh" #include "CassettePort.hh" #include "CliComm.hh" #include "Display.hh" #include "GlobalSettings.hh" #include "Reactor.hh" #include "MSXMotherBoard.hh" #include "PioneerLDControl.hh" #include "OggReader.hh" #include "LDRenderer.hh" #include "RendererFactory.hh" #include "Math.hh" #include "likely.hh" #include "memory.hh" #include #include #include using std::string; using std::vector; namespace openmsx { // Command LaserdiscPlayer::Command::Command( CommandController& commandController_, StateChangeDistributor& stateChangeDistributor, Scheduler& scheduler) : RecordedCommand(commandController_, stateChangeDistributor, scheduler, "laserdiscplayer") { } void LaserdiscPlayer::Command::execute( array_ref tokens, TclObject& result, EmuTime::param time) { auto& laserdiscPlayer = OUTER(LaserdiscPlayer, laserdiscCommand); if (tokens.size() == 1) { // Returning Tcl lists here, similar to the disk commands in // DiskChanger result.addListElement(getName() + ':'); result.addListElement(laserdiscPlayer.getImageName().getResolved()); } else if (tokens.size() == 2 && tokens[1] == "eject") { result.setString("Ejecting laserdisc."); laserdiscPlayer.eject(time); } else if (tokens.size() == 3 && tokens[1] == "insert") { try { result.setString("Changing laserdisc."); laserdiscPlayer.setImageName(tokens[2].getString().str(), time); } catch (MSXException& e) { throw CommandException(e.getMessage()); } } else { throw SyntaxError(); } } string LaserdiscPlayer::Command::help(const vector& tokens) const { if (tokens.size() >= 2) { if (tokens[1] == "insert") { return "Inserts the specfied laserdisc image into " "the laserdisc player."; } else if (tokens[1] == "eject") { return "Eject the laserdisc."; } } return "laserdiscplayer insert " ": insert a (different) laserdisc image\n" "laserdiscplayer eject " ": eject the laserdisc\n"; } void LaserdiscPlayer::Command::tabCompletion(vector& tokens) const { if (tokens.size() == 2) { static const char* const extra[] = { "eject", "insert" }; completeString(tokens, extra); } else if (tokens.size() == 3 && tokens[1] == "insert") { completeFileName(tokens, userFileContext()); } } // LaserdiscPlayer static XMLElement createXML() { XMLElement xml("laserdiscplayer"); xml.addChild("sound").addChild("volume", "30000"); return xml; } LaserdiscPlayer::LaserdiscPlayer( const HardwareConfig& hwConf, PioneerLDControl& ldcontrol_) : ResampledSoundDevice(hwConf.getMotherBoard(), "laserdiscplayer", "Laserdisc Player", 1, true) , syncAck (hwConf.getMotherBoard().getScheduler()) , syncOdd (hwConf.getMotherBoard().getScheduler()) , syncEven(hwConf.getMotherBoard().getScheduler()) , motherBoard(hwConf.getMotherBoard()) , ldcontrol(ldcontrol_) , laserdiscCommand(motherBoard.getCommandController(), motherBoard.getStateChangeDistributor(), motherBoard.getScheduler()) , sampleClock(EmuTime::zero) , start(EmuTime::zero) , muteLeft(false) , muteRight(false) , remoteState(REMOTE_IDLE) , remoteLastEdge(EmuTime::zero) , remoteLastBit(false) , remoteProtocol(IR_NONE) , ack(false) , seeking(false) , playerState(PLAYER_STOPPED) , autoRunSetting( motherBoard.getCommandController(), "autorunlaserdisc", "automatically try to run Laserdisc", true) , loadingIndicator( motherBoard.getReactor().getGlobalSettings().getThrottleManager()) , sampleReads(0) { motherBoard.getCassettePort().setLaserdiscPlayer(this); Reactor& reactor = motherBoard.getReactor(); reactor.getDisplay().attach(*this); createRenderer(); reactor.getEventDistributor().registerEventListener(OPENMSX_BOOT_EVENT, *this); scheduleDisplayStart(getCurrentTime()); setInputRate(44100); // Initialize with dummy value static XMLElement xml = createXML(); registerSound(DeviceConfig(hwConf, xml)); } LaserdiscPlayer::~LaserdiscPlayer() { unregisterSound(); Reactor& reactor = motherBoard.getReactor(); reactor.getDisplay().detach(*this); reactor.getEventDistributor().unregisterEventListener(OPENMSX_BOOT_EVENT, *this); } void LaserdiscPlayer::scheduleDisplayStart(EmuTime::param time) { Clock<60000, 1001> frameClock(time); // The video is 29.97Hz, however we need to do vblank processing // at the full 59.94Hz syncOdd .setSyncPoint(frameClock + 1); syncEven.setSyncPoint(frameClock + 2); } // The protocol used to communicate over the cable for commands to the // laserdisc player is the NEC infrared protocol with minor deviations: // 1) The leader pulse and space is a little shorter. // 2) The remote does not send NEC repeats; full NEC codes are repeated // after 20ms. The main unit does not understand NEC repeats. // 3) No carrier modulation is done over the ext protocol. // // My Laserdisc player is an Pioneer LD-700 which has a remote called // the CU-700. This is much like the CU-CLD106 which is described // here: http://lirc.sourceforge.net/remotes/pioneer/CU-CLD106 // The codes and protocol are exactly the same. void LaserdiscPlayer::extControl(bool bit, EmuTime::param time) { if (remoteLastBit == bit) return; remoteLastBit = bit; // The tolerance here is based on actual measurements of an LD-700 EmuDuration duration = time - remoteLastEdge; remoteLastEdge = time; unsigned usec = duration.getTicksAt(1000000); // microseconds switch (remoteState) { case REMOTE_IDLE: if (bit) { remoteBits = remoteBitNr = 0; remoteState = REMOTE_HEADER_PULSE; } break; case REMOTE_HEADER_PULSE: if (5800 <= usec && usec < 11200) { remoteState = NEC_HEADER_SPACE; } else { remoteState = REMOTE_IDLE; } break; case NEC_HEADER_SPACE: if (3400 <= usec && usec < 6200) { remoteState = NEC_BITS_PULSE; } else { remoteState = REMOTE_IDLE; } break; case NEC_BITS_PULSE: if (usec >= 380 && usec < 1070) { remoteState = NEC_BITS_SPACE; } else { remoteState = REMOTE_IDLE; } break; case NEC_BITS_SPACE: if (1260 <= usec && usec < 4720) { // bit 1 remoteBits |= 1 << remoteBitNr; } else if (usec < 300 || usec >= 1065) { // error remoteState = REMOTE_IDLE; break; } // since it does not matter how long the trailing pulse // is, we process the button here. Note that real hardware // needs the trailing pulse to be at least 200µs if (++remoteBitNr == 32) { byte custom = ( remoteBits >> 0) & 0xff; byte customCompl = (~remoteBits >> 8) & 0xff; byte code = ( remoteBits >> 16) & 0xff; byte codeCompl = (~remoteBits >> 24) & 0xff; if (custom == customCompl && custom == 0xa8 && code == codeCompl) { submitRemote(IR_NEC, code); } remoteState = REMOTE_IDLE; } else { remoteState = NEC_BITS_PULSE; } break; } } void LaserdiscPlayer::submitRemote(RemoteProtocol protocol, unsigned code) { // The END command for seeking/waiting acknowledges repeats, // Esh's Aurunmilla needs play as well. if (protocol != remoteProtocol || code != remoteCode || (protocol == IR_NEC && (code == 0x42 || code == 0x17))) { remoteProtocol = protocol; remoteCode = code; remoteVblanksBack = 0; remoteExecuteDelayed = true; } else { // remote ignored remoteVblanksBack = 0; remoteExecuteDelayed = false; } } const RawFrame* LaserdiscPlayer::getRawFrame() const { return renderer->getRawFrame(); } void LaserdiscPlayer::setAck(EmuTime::param time, int wait) { // activate ACK for 'wait' milliseconds syncAck.removeSyncPoint(); syncAck.setSyncPoint(time + EmuDuration::msec(wait)); ack = true; } void LaserdiscPlayer::remoteButtonNEC(unsigned code, EmuTime::param time) { #ifdef DEBUG string f; switch (code) { case 0x47: f = "C+"; break; // Increase playing speed case 0x46: f = "C-"; break; // Decrease playing speed case 0x43: f = "D+"; break; // Show Frame# & Chapter# OSD case 0x4b: f = "L+"; break; // right case 0x49: f = "L-"; break; // left case 0x4a: f = "L@"; break; // stereo case 0x58: f = "M+"; break; // multi speed forwards case 0x55: f = "M-"; break; // multi speed backwards case 0x17: f = "P+"; break; // play case 0x16: f = "P@"; break; // stop case 0x18: f = "P/"; break; // pause case 0x54: f = "S+"; break; // frame step forward case 0x50: f = "S-"; break; // frame step backwards case 0x45: f = "X+"; break; // clear case 0x41: f = 'F'; break; // seek frame case 0x40: f = 'C'; break; // seek chapter case 0x42: f = "END"; break; // done seek frame/chapter case 0x00: f = '0'; break; case 0x01: f = '1'; break; case 0x02: f = '2'; break; case 0x03: f = '3'; break; case 0x04: f = '4'; break; case 0x05: f = '5'; break; case 0x06: f = '6'; break; case 0x07: f = '7'; break; case 0x08: f = '8'; break; case 0x09: f = '9'; break; case 0x5f: f = "WAIT FRAME"; break; case 0x53: // previous chapter case 0x52: // next chapter default: break; } if (!f.empty()) { std::cerr << "LaserdiscPlayer::remote " << f << std::endl; } else { std::cerr << "LaserdiscPlayer::remote unknown " << std::hex << code << std::endl; } #endif // When not playing the following buttons work // 0x17: start playing (ack sent) // 0x16: eject (no ack) // 0x49, 0x4a, 0x4b (ack sent) // if 0x49 is a repeat then no ACK is sent // if 0x49 is followed by 0x4a then ACK is sent if (code == 0x49 || code == 0x4a || code == 0x4b) { updateStream(time); switch (code) { case 0x4b: // L+ (both channels play the left channel) stereoMode = LEFT; break; case 0x49: // L- (both channels play the right channel) stereoMode = RIGHT; break; case 0x4a: // L@ (normal stereo) stereoMode = STEREO; break; } setAck(time, 46); } else if (playerState == PLAYER_STOPPED) { switch (code) { case 0x16: // P@ motherBoard.getMSXCliComm().printWarning( "ejecting laserdisc"); eject(time); break; case 0x17: // P+ play(time); break; } // During playing, playing will be acked if not repeated // within less than 115ms } else { // FIXME: while seeking, only a small subset of buttons work bool nonseekack = true; switch (code) { case 0x5f: seekState = SEEK_WAIT; seekNum = 0; stillOnWaitFrame = false; nonseekack = false; break; case 0x41: seekState = SEEK_FRAME; seekNum = 0; break; case 0x40: seekState = SEEK_CHAPTER; seekNum = 0; nonseekack = video->chapter(0) != 0; break; case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: case 0x08: case 0x09: seekNum = seekNum * 10 + code; break; case 0x42: switch (seekState) { case SEEK_FRAME: seekState = SEEK_NONE; seekFrame(seekNum % 100000, time); nonseekack = false; break; case SEEK_CHAPTER: seekState = SEEK_NONE; seekChapter(seekNum % 100, time); nonseekack = false; break; case SEEK_WAIT: seekState = SEEK_NONE; waitFrame = seekNum % 100000; if (waitFrame >= 101 && waitFrame < 200) { auto frame = video->chapter( int(waitFrame - 100)); if (frame) waitFrame = frame; } break; default: seekState = SEEK_NONE; break; } break; case 0x45: // Clear "X+" if (seekState != SEEK_NONE && seekNum != 0) { seekNum = 0; } else { seekState = SEEK_NONE; seekNum = 0; } waitFrame = 0; break; case 0x18: // P/ pause(time); nonseekack = false; break; case 0x17: // P+ play(time); nonseekack = false; break; case 0x16: // P@ (stop/eject) stop(time); nonseekack = false; break; case 0xff: nonseekack = false; seekState = SEEK_NONE; break; case 0x54: // S+ (frame step forward) if (seekState == SEEK_WAIT) { stillOnWaitFrame = true; } else { stepFrame(true); } break; case 0x50: // S- (frame step backwards) stepFrame(false); break; case 0x55: // M- (multispeed backwards) // Not supported motherBoard.getMSXCliComm().printWarning( "The Laserdisc player received a command to " "play backwards (M-). This is currently not " "supported."); nonseekack = false; break; case 0x58: // M+ (multispeed forwards) playerState = PLAYER_MULTISPEED; setFrameStep(); break; case 0x46: // C- (play slower) if (playingSpeed >= SPEED_STEP1) { playingSpeed--; frameStep = 1; // FIXME: is this correct? } break; case 0x47: // C+ (play faster) if (playingSpeed <= SPEED_X2) { playingSpeed++; frameStep = 1; // FIXME: is this correct? } break; default: motherBoard.getMSXCliComm().printWarning( "The Laserdisc player received an unknown " "command 0x" + StringOp::toHexString(code, 2)); nonseekack = false; break; } if (nonseekack) { // All ACKs for operations which do not // require seeking setAck(time, 46); } } } void LaserdiscPlayer::execSyncAck(EmuTime::param time) { updateStream(time); if (seeking && playerState == PLAYER_PLAYING) { sampleClock.advance(time); } ack = false; seeking = false; } void LaserdiscPlayer::execSyncFrame(EmuTime::param time, bool odd) { updateStream(time); if (!odd || (video && video->getFrameRate() == 60)) { if ((playerState != PLAYER_STOPPED) && (currentFrame > video->getFrames())) { playerState = PLAYER_STOPPED; } if (auto* rawFrame = renderer->getRawFrame()) { renderer->frameStart(time); if (isVideoOutputAvailable(time)) { auto frame = currentFrame; if (video->getFrameRate() == 60) { frame *= 2; if (odd) frame--; } video->getFrameNo(*rawFrame, frame); if (!odd) { nextFrame(time); } } else { renderer->drawBlank(0, 128, 196); } renderer->frameEnd(); } // Update throttling loadingIndicator.update(seeking || sampleReads > 500); sampleReads = 0; if (!odd) { scheduleDisplayStart(time); } } // Processing of the remote control happens at each frame // (even and odd, so at 59.94Hz) if (remoteProtocol == IR_NEC) { if (remoteExecuteDelayed) { remoteButtonNEC(remoteCode, time); } if (++remoteVblanksBack > 6) { remoteProtocol = IR_NONE; } } remoteExecuteDelayed = false; } void LaserdiscPlayer::setFrameStep() { switch (playingSpeed) { case SPEED_X3: case SPEED_X2: case SPEED_X1: frameStep = 1; break; case SPEED_1IN2: frameStep = 2; break; case SPEED_1IN4: frameStep = 4; break; case SPEED_1IN8: frameStep = 8; break; case SPEED_1IN16: frameStep = 16; break; case SPEED_STEP1: frameStep = 30; break; case SPEED_STEP3: frameStep = 90; break; } } void LaserdiscPlayer::nextFrame(EmuTime::param time) { if (waitFrame && waitFrame == currentFrame) { // Leave ACK raised until the next command ack = true; waitFrame = 0; if (stillOnWaitFrame) { playingFromSample = getCurrentSample(time); playerState = PLAYER_STILL; stillOnWaitFrame = false; } } if (playerState == PLAYER_MULTISPEED) { if (--frameStep) { return; } switch (playingSpeed) { case SPEED_X3: currentFrame += 3; break; case SPEED_X2: currentFrame += 2; break; default: currentFrame += 1; break; } setFrameStep(); } else if (playerState == PLAYER_PLAYING) { currentFrame++; } // freeze if stop frame if ((playerState == PLAYER_PLAYING || playerState == PLAYER_MULTISPEED) && video->stopFrame(currentFrame)) { // stop frame reached playingFromSample = getCurrentSample(time); playerState = PLAYER_STILL; } } void LaserdiscPlayer::setImageName(string newImage, EmuTime::param time) { stop(time); oggImage = Filename(std::move(newImage), userFileContext()); video = make_unique(oggImage, motherBoard.getMSXCliComm()); unsigned inputRate = video->getSampleRate(); sampleClock.setFreq(inputRate); if (inputRate != getInputRate()) { setInputRate(inputRate); createResampler(); } } int LaserdiscPlayer::signalEvent(const std::shared_ptr& event) { if (event->getType() == OPENMSX_BOOT_EVENT && video) { autoRun(); } return 0; } void LaserdiscPlayer::autoRun() { if (!autoRunSetting.getBoolean()) return; string var = "::auto_run_ld_counter"; string command = "if ![info exists " + var + "] { set " + var + " 0 }\n" "incr " + var + "\n" "after time 2 \"if $" + var + "==\\$" + var + " { " "type 1CALLLD\\\\r }\""; try { motherBoard.getCommandController().executeCommand(command); } catch (CommandException& e) { motherBoard.getMSXCliComm().printWarning( "Error executing loading instruction for AutoRun: " + e.getMessage() + "\n Please report a bug."); } } void LaserdiscPlayer::generateChannels(int** buffers, unsigned num) { // Single channel device: replace content of buffers[0] (not add to it). if (playerState != PLAYER_PLAYING || seeking || (muteLeft && muteRight)) { buffers[0] = nullptr; return; } unsigned pos = 0; size_t currentSample; if (unlikely(!sampleClock.before(start))) { // Before playing of sounds begins EmuDuration duration = sampleClock.getTime() - start; unsigned len = duration.getTicksAt(video->getSampleRate()); if (len >= num) { buffers[0] = nullptr; return; } for (/**/; pos < len; ++pos) { buffers[0][pos * 2 + 0] = 0; buffers[0][pos * 2 + 1] = 0; } currentSample = playingFromSample; } else { currentSample = getCurrentSample(start); } unsigned drift = video->getSampleRate() / 30; if (currentSample > (lastPlayedSample + drift) || (currentSample + drift) < lastPlayedSample) { // audio drift lastPlayedSample = currentSample; } int left = stereoMode == RIGHT ? 1 : 0; int right = stereoMode == LEFT ? 0 : 1; while (pos < num) { const AudioFragment* audio = video->getAudio(lastPlayedSample); if (!audio) { if (pos == 0) { buffers[0] = nullptr; break; } else for (/**/; pos < num; ++pos) { buffers[0][pos * 2 + 0] = 0; buffers[0][pos * 2 + 1] = 0; } } else { auto offset = unsigned(lastPlayedSample - audio->position); unsigned len = std::min(audio->length - offset, num - pos); // maybe muting should be moved out of the loop? for (unsigned i = 0; i < len; ++i, ++pos) { buffers[0][pos * 2 + 0] = muteLeft ? 0 : int(audio->pcm[left][offset + i] * 65536.f); buffers[0][pos * 2 + 1] = muteRight ? 0 : int(audio->pcm[right][offset + i] * 65536.f); } lastPlayedSample += len; } } } bool LaserdiscPlayer::updateBuffer(unsigned length, int* buffer, EmuTime::param time) { bool result = ResampledSoundDevice::updateBuffer(length, buffer, time); start = time; // current end-time is next start-time return result; } void LaserdiscPlayer::setMuting(bool left, bool right, EmuTime::param time) { updateStream(time); muteLeft = left; muteRight = right; } void LaserdiscPlayer::play(EmuTime::param time) { if (video) { updateStream(time); if (seeking) { // Do not ACK, play while seeking } else if (playerState == PLAYER_STOPPED) { // Disk needs to spin up, which takes 9.6s on // my Pioneer LD-92000. Also always seek to // beginning (confirmed on real MSX and LD) video->seek(1, 0); lastPlayedSample = 0; playingFromSample = 0; currentFrame = 1; // Note that with "fullspeedwhenloading" this // should be reduced to. setAck(time, 9600); seekState = SEEK_NONE; seeking = true; waitFrame = 0; stereoMode = STEREO; playingSpeed = SPEED_1IN4; } else if (playerState == PLAYER_PLAYING) { // If Play command is issued while the player // is already playing, then if no ACK is sent then // Astron Belt will send LD1100 commands setAck(time, 46); } else if (playerState == PLAYER_MULTISPEED) { // Should be hearing stuff again playingFromSample = (currentFrame - 1ll) * 1001ll * video->getSampleRate() / 30000ll; sampleClock.advance(time); setAck(time, 46); } else { // STILL or PAUSED sampleClock.advance(time); setAck(time, 46); } playerState = PLAYER_PLAYING; } } size_t LaserdiscPlayer::getCurrentSample(EmuTime::param time) { switch(playerState) { case PLAYER_PAUSED: case PLAYER_STILL: return playingFromSample; default: return playingFromSample + sampleClock.getTicksTill(time); } } void LaserdiscPlayer::pause(EmuTime::param time) { if (playerState != PLAYER_STOPPED) { updateStream(time); if (playerState == PLAYER_PLAYING) { playingFromSample = getCurrentSample(time); } else if (playerState == PLAYER_MULTISPEED) { playingFromSample = (currentFrame - 1ll) * 1001ll * video->getSampleRate() / 30000ll; sampleClock.advance(time); } playerState = PLAYER_PAUSED; setAck(time, 46); } } void LaserdiscPlayer::stop(EmuTime::param time) { if (playerState != PLAYER_STOPPED) { updateStream(time); playerState = PLAYER_STOPPED; } } void LaserdiscPlayer::eject(EmuTime::param time) { stop(time); video.reset(); } // Step one frame forwards or backwards. The frame will be visible and // we won't be playing afterwards void LaserdiscPlayer::stepFrame(bool forwards) { bool needseek = false; // Note that on real hardware, the screen goes dark momentarily // if you try to step before the first frame or after the last one if (playerState == PLAYER_STILL) { if (forwards) { if (currentFrame < video->getFrames()) { currentFrame++; } } else { if (currentFrame > 1) { currentFrame--; needseek = true; } } } playerState = PLAYER_STILL; int64_t samplePos = (currentFrame - 1ll) * 1001ll * video->getSampleRate() / 30000ll; playingFromSample = samplePos; if (needseek) { if (video->getFrameRate() == 60) video->seek(currentFrame * 2, samplePos); else video->seek(currentFrame, samplePos); } } void LaserdiscPlayer::seekFrame(size_t toframe, EmuTime::param time) { if ((playerState != PLAYER_STOPPED) && video) { updateStream(time); if (toframe <= 0) toframe = 1; if (toframe > video->getFrames()) toframe = video->getFrames(); // Seek time needs to be emulated correctly since // e.g. Astron Belt does not wait for the seek // to complete, it simply assumes a certain // delay. // // This calculation is based on measurements on // a Pioneer LD-92000. auto dist = std::abs(int64_t(toframe) - int64_t(currentFrame)); int seektime; // time in ms if (dist < 1000) { seektime = dist + 300; } else { seektime = 1800 + dist / 12; } int64_t samplePos = (toframe - 1ll) * 1001ll * video->getSampleRate() / 30000ll; if (video->getFrameRate() == 60) { video->seek(toframe * 2, samplePos); } else { video->seek(toframe, samplePos); } playerState = PLAYER_STILL; playingFromSample = samplePos; currentFrame = toframe; // Seeking clears the frame to wait for waitFrame = 0; seeking = true; setAck(time, seektime); } } void LaserdiscPlayer::seekChapter(int chapter, EmuTime::param time) { if ((playerState != PLAYER_STOPPED) && video) { auto frameno = video->chapter(chapter); if (!frameno) return; seekFrame(frameno, time); } } short LaserdiscPlayer::readSample(EmuTime::param time) { // Here we should return the value of the sample on the // right audio channel, ignoring muting (this is done in the MSX) // but honouring the stereo mode as this is done in the // Laserdisc player if (playerState == PLAYER_PLAYING && !seeking) { auto sample = getCurrentSample(time); if (const AudioFragment* audio = video->getAudio(sample)) { ++sampleReads; int channel = stereoMode == LEFT ? 0 : 1; return int(audio->pcm[channel][sample - audio->position] * 32767.f); } } return 0; } bool LaserdiscPlayer::isVideoOutputAvailable(EmuTime::param time) { updateStream(time); bool videoOut; switch (playerState) { case PLAYER_PLAYING: case PLAYER_MULTISPEED: case PLAYER_STILL: videoOut = !seeking; break; default: videoOut = false; break; } ldcontrol.videoIn(videoOut); return videoOut; } void LaserdiscPlayer::preVideoSystemChange() { renderer.reset(); } void LaserdiscPlayer::postVideoSystemChange() { createRenderer(); } void LaserdiscPlayer::createRenderer() { Display& display = getMotherBoard().getReactor().getDisplay(); renderer = RendererFactory::createLDRenderer(*this, display); } static enum_string RemoteStateInfo[] = { { "IDLE", LaserdiscPlayer::REMOTE_IDLE }, { "HEADER_PULSE", LaserdiscPlayer::REMOTE_HEADER_PULSE }, { "NEC_HEADER_SPACE", LaserdiscPlayer::NEC_HEADER_SPACE }, { "NEC_BITS_PULSE", LaserdiscPlayer::NEC_BITS_PULSE }, { "NEC_BITS_SPACE", LaserdiscPlayer::NEC_BITS_SPACE }, }; SERIALIZE_ENUM(LaserdiscPlayer::RemoteState, RemoteStateInfo); static enum_string PlayerStateInfo[] = { { "STOPPED", LaserdiscPlayer::PLAYER_STOPPED }, { "PLAYING", LaserdiscPlayer::PLAYER_PLAYING }, { "MULTISPEED", LaserdiscPlayer::PLAYER_MULTISPEED }, { "PAUSED", LaserdiscPlayer::PLAYER_PAUSED }, { "STILL", LaserdiscPlayer::PLAYER_STILL } }; SERIALIZE_ENUM(LaserdiscPlayer::PlayerState, PlayerStateInfo); static enum_string SeekStateInfo[] = { { "NONE", LaserdiscPlayer::SEEK_NONE }, { "CHAPTER", LaserdiscPlayer::SEEK_CHAPTER }, { "FRAME", LaserdiscPlayer::SEEK_FRAME }, { "WAIT", LaserdiscPlayer::SEEK_WAIT } }; SERIALIZE_ENUM(LaserdiscPlayer::SeekState, SeekStateInfo); static enum_string StereoModeInfo[] = { { "LEFT", LaserdiscPlayer::LEFT }, { "RIGHT", LaserdiscPlayer::RIGHT }, { "STEREO", LaserdiscPlayer::STEREO } }; SERIALIZE_ENUM(LaserdiscPlayer::StereoMode, StereoModeInfo); static enum_string RemoteProtocolInfo[] = { { "NONE", LaserdiscPlayer::IR_NONE }, { "NEC", LaserdiscPlayer::IR_NEC }, }; SERIALIZE_ENUM(LaserdiscPlayer::RemoteProtocol, RemoteProtocolInfo); // version 1: initial version // version 2: added 'stillOnWaitFrame' // version 3: reversed bit order of 'remoteBits' and 'remoteCode' // version 4: removed 'userData' from Schedulable template void LaserdiscPlayer::serialize(Archive& ar, unsigned version) { // Serialize remote control ar.serialize("RemoteState", remoteState); if (remoteState != REMOTE_IDLE) { ar.serialize("RemoteBitNr", remoteBitNr); ar.serialize("RemoteBits", remoteBits); if (ar.versionBelow(version, 3)) { assert(ar.isLoader()); remoteBits = Math::reverseNBits(remoteBits, remoteBitNr); } } ar.serialize("RemoteLastBit", remoteLastBit); ar.serialize("RemoteLastEdge", remoteLastEdge); ar.serialize("RemoteProtocol", remoteProtocol); if (remoteProtocol != IR_NONE) { ar.serialize("RemoteCode", remoteCode); if (ar.versionBelow(version, 3)) { assert(ar.isLoader()); remoteCode = Math::reverseByte(remoteCode); } ar.serialize("RemoteExecuteDelayed", remoteExecuteDelayed); ar.serialize("RemoteVblanksBack", remoteVblanksBack); } // Serialize filename ar.serialize("OggImage", oggImage); if (ar.isLoader()) { sampleReads = 0; if (!oggImage.empty()) { setImageName(oggImage.getResolved(), getCurrentTime()); } else { video.reset(); } } ar.serialize("PlayerState", playerState); if (playerState != PLAYER_STOPPED) { // Serialize seek state ar.serialize("SeekState", seekState); if (seekState != SEEK_NONE) { ar.serialize("SeekNum", seekNum); } ar.serialize("seeking", seeking); // Playing state ar.serialize("WaitFrame", waitFrame); // This was not yet implemented in openmsx 0.8.1 and earlier if (ar.versionAtLeast(version, 2)) { ar.serialize("StillOnWaitFrame", stillOnWaitFrame); } ar.serialize("ACK", ack); ar.serialize("PlayingSpeed", playingSpeed); // Frame position ar.serialize("CurrentFrame", currentFrame); if (playerState == PLAYER_MULTISPEED) { ar.serialize("FrameStep", frameStep); } // Audio position ar.serialize("StereoMode", stereoMode); ar.serialize("FromSample", playingFromSample); ar.serialize("SampleClock", sampleClock); if (ar.isLoader()) { // If the samplerate differs, adjust accordingly if (video->getSampleRate() != sampleClock.getFreq()) { uint64_t pos = playingFromSample; pos *= video->getSampleRate(); pos /= sampleClock.getFreq(); playingFromSample = pos; sampleClock.setFreq(video->getSampleRate()); } auto sample = getCurrentSample(getCurrentTime()); if (video->getFrameRate() == 60) video->seek(currentFrame * 2, sample); else video->seek(currentFrame, sample); lastPlayedSample = sample; } } if (ar.versionAtLeast(version, 4)) { ar.serialize("syncEven", syncEven); ar.serialize("syncOdd", syncOdd); ar.serialize("syncAck", syncAck); } else { Schedulable::restoreOld(ar, {&syncEven, &syncOdd, &syncAck}); } if (ar.isLoader()) { isVideoOutputAvailable(getCurrentTime()); } } INSTANTIATE_SERIALIZE_METHODS(LaserdiscPlayer); } // namespace openmsx openMSX-RELEASE_0_12_0/src/laserdisc/LaserdiscPlayer.hh000066400000000000000000000135631257557151200226160ustar00rootroot00000000000000#ifndef LASERDISCPLAYER_HH #define LASERDISCPLAYER_HH #include "ResampledSoundDevice.hh" #include "BooleanSetting.hh" #include "RecordedCommand.hh" #include "EmuTime.hh" #include "Schedulable.hh" #include "DynamicClock.hh" #include "Filename.hh" #include "VideoSystemChangeListener.hh" #include "EventListener.hh" #include "ThrottleManager.hh" #include "outer.hh" namespace openmsx { class PioneerLDControl; class HardwareConfig; class MSXMotherBoard; class OggReader; class LDRenderer; class RawFrame; class LaserdiscPlayer final : public ResampledSoundDevice , private EventListener , private VideoSystemChangeListener { public: LaserdiscPlayer(const HardwareConfig& hwConf, PioneerLDControl& ldcontrol); ~LaserdiscPlayer(); // Called from CassettePort short readSample(EmuTime::param time); // Called from PioneerLDControl void setMuting(bool left, bool right, EmuTime::param time); bool extAck(EmuTime::param /*time*/) const { return ack; } void extControl(bool bit, EmuTime::param time); const RawFrame* getRawFrame() const; template void serialize(Archive& ar, unsigned version); // video interface MSXMotherBoard& getMotherBoard() { return motherBoard; } enum RemoteState { REMOTE_IDLE, REMOTE_HEADER_PULSE, NEC_HEADER_SPACE, NEC_BITS_PULSE, NEC_BITS_SPACE, }; enum PlayerState { PLAYER_STOPPED, PLAYER_PLAYING, PLAYER_MULTISPEED, PLAYER_PAUSED, PLAYER_STILL }; enum SeekState { SEEK_NONE, SEEK_CHAPTER, SEEK_FRAME, SEEK_WAIT, }; enum StereoMode { LEFT, RIGHT, STEREO }; enum RemoteProtocol { IR_NONE, IR_NEC, }; private: void setImageName(std::string newImage, EmuTime::param time); const Filename& getImageName() const { return oggImage; } void autoRun(); /** Laserdisc player commands */ void play(EmuTime::param time); void pause(EmuTime::param time); void stop(EmuTime::param time); void eject(EmuTime::param time); void seekFrame(size_t frame, EmuTime::param time); void stepFrame(bool); void seekChapter(int chapter, EmuTime::param time); // Control from MSX /** Is video output being generated? */ void scheduleDisplayStart(EmuTime::param time); bool isVideoOutputAvailable(EmuTime::param time); void remoteButtonNEC(unsigned code, EmuTime::param time); void submitRemote(RemoteProtocol protocol, unsigned code); void setAck(EmuTime::param time, int wait); size_t getCurrentSample(EmuTime::param time); void createRenderer(); // SoundDevice void generateChannels(int** bufs, unsigned num) override; bool updateBuffer(unsigned length, int* buffer, EmuTime::param time) override; // Schedulable struct SyncAck : public Schedulable { friend class LaserdiscPlayer; SyncAck(Scheduler& s) : Schedulable(s) {} void executeUntil(EmuTime::param time) override { auto& player = OUTER(LaserdiscPlayer, syncAck); player.execSyncAck(time); } } syncAck; struct SyncOdd : public Schedulable { friend class LaserdiscPlayer; SyncOdd(Scheduler& s) : Schedulable(s) {} void executeUntil(EmuTime::param time) override { auto& player = OUTER(LaserdiscPlayer, syncOdd); player.execSyncFrame(time, true); } } syncOdd; struct SyncEven : public Schedulable { friend class LaserdiscPlayer; SyncEven(Scheduler& s) : Schedulable(s) {} void executeUntil(EmuTime::param time) override { auto& player = OUTER(LaserdiscPlayer, syncEven); player.execSyncFrame(time, false); } } syncEven; void execSyncAck(EmuTime::param time); void execSyncFrame(EmuTime::param time, bool odd); EmuTime::param getCurrentTime() const { return syncAck.getCurrentTime(); } // EventListener int signalEvent(const std::shared_ptr& event) override; // VideoSystemChangeListener interface: void preVideoSystemChange() override; void postVideoSystemChange() override; MSXMotherBoard& motherBoard; PioneerLDControl& ldcontrol; struct Command final : RecordedCommand { Command(CommandController& commandController, StateChangeDistributor& stateChangeDistributor, Scheduler& scheduler); void execute(array_ref tokens, TclObject& result, EmuTime::param time) override; std::string help(const std::vector& tokens) const override; void tabCompletion(std::vector& tokens) const override; } laserdiscCommand; std::unique_ptr video; Filename oggImage; std::unique_ptr renderer; void nextFrame(EmuTime::param time); void setFrameStep(); size_t currentFrame; int frameStep; // Audio state DynamicClock sampleClock; EmuTime start; size_t playingFromSample; size_t lastPlayedSample; bool muteLeft, muteRight; StereoMode stereoMode; // Ext Control RemoteState remoteState; EmuTime remoteLastEdge; unsigned remoteBitNr; unsigned remoteBits; bool remoteLastBit; RemoteProtocol remoteProtocol; unsigned remoteCode; bool remoteExecuteDelayed; // Number of v-blank since code was sent int remoteVblanksBack; /* We need to maintain some state for seeking */ SeekState seekState; /* frame the MSX has requested to wait for */ size_t waitFrame; // pause playing back on reaching wait frame bool stillOnWaitFrame; /* The specific frame or chapter we are seeking to */ int seekNum; // For ack bool ack; // State of the video itself bool seeking; PlayerState playerState; enum PlayingSpeed { SPEED_STEP3 = -5, // Each frame is repeated 90 times SPEED_STEP1 = -4, // Each frame is repeated 30 times SPEED_1IN16 = -3, // Each frame is repeated 16 times SPEED_1IN8 = -2, // Each frame is repeated 8 times SPEED_1IN4 = -1, // Each frame is repeated 4 times SPEED_1IN2 = 0, SPEED_X1 = 1, SPEED_X2 = 2, SPEED_X3 = 3 }; int playingSpeed; // Loading indicator BooleanSetting autoRunSetting; LoadingIndicator loadingIndicator; int sampleReads; }; SERIALIZE_CLASS_VERSION(LaserdiscPlayer, 4); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/laserdisc/LaserdiscPlayerCLI.cc000066400000000000000000000023251257557151200231260ustar00rootroot00000000000000#include "LaserdiscPlayerCLI.hh" #include "CommandLineParser.hh" #include "GlobalCommandController.hh" #include "MSXException.hh" #include "TclObject.hh" using std::string; namespace openmsx { LaserdiscPlayerCLI::LaserdiscPlayerCLI(CommandLineParser& parser_) : parser(parser_) { parser.registerOption("-laserdisc", *this); parser.registerFileType("ogv", *this); } void LaserdiscPlayerCLI::parseOption(const string& option, array_ref& cmdLine) { parseFileType(getArgument(option, cmdLine), cmdLine); } string_ref LaserdiscPlayerCLI::optionHelp() const { return "Put laserdisc image specified in argument in " "virtual laserdiscplayer"; } void LaserdiscPlayerCLI::parseFileType(const string& filename, array_ref& /*cmdLine*/) { if (!parser.getGlobalCommandController().hasCommand("laserdiscplayer")) { throw MSXException("No laserdiscplayer."); } TclObject command; command.addListElement("laserdiscplayer"); command.addListElement("insert"); command.addListElement(filename); command.executeCommand(parser.getInterpreter()); } string_ref LaserdiscPlayerCLI::fileTypeHelp() const { return "Laserdisc image, Ogg Vorbis/Theora"; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/laserdisc/LaserdiscPlayerCLI.hh000066400000000000000000000012201257557151200231310ustar00rootroot00000000000000#ifndef LASERDISCPLAYERCLI_HH #define LASERDISCPLAYERCLI_HH #include "CLIOption.hh" namespace openmsx { class CommandLineParser; class LaserdiscPlayerCLI final : public CLIOption, public CLIFileType { public: explicit LaserdiscPlayerCLI(CommandLineParser& commandLineParser); void parseOption(const std::string& option, array_ref& cmdLine) override; string_ref optionHelp() const override; void parseFileType(const std::string& filename, array_ref& cmdLine) override; string_ref fileTypeHelp() const override; private: CommandLineParser& parser; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/laserdisc/OggReader.cc000066400000000000000000000550101257557151200213460ustar00rootroot00000000000000#include "OggReader.hh" #include "MSXException.hh" #include "yuv2rgb.hh" #include "likely.hh" #include "CliComm.hh" #include "StringOp.hh" #include "MemoryOps.hh" #include "memory.hh" #include "stl.hh" #include "stringsp.hh" // for strncasecmp #include #include // for memcpy, memcmp #include // for atoi // TODO // - Improve error handling // - When an non-ogg file is passed, the entire file is scanned // - Clean up this mess! namespace openmsx { Frame::Frame(const th_ycbcr_buffer& yuv) { unsigned y_size = yuv[0].height * yuv[0].stride; unsigned uv_size = yuv[1].height * yuv[1].stride; buffer[0] = yuv[0]; buffer[0].data = static_cast( MemoryOps::mallocAligned(16, y_size)); buffer[1] = yuv[1]; buffer[1].data = static_cast( MemoryOps::mallocAligned(16, uv_size)); buffer[2] = yuv[2]; buffer[2].data = static_cast( MemoryOps::mallocAligned(16, uv_size)); } Frame::~Frame() { MemoryOps::freeAligned(buffer[0].data); MemoryOps::freeAligned(buffer[1].data); MemoryOps::freeAligned(buffer[2].data); } OggReader::OggReader(const Filename& filename, CliComm& cli_) : cli(cli_) , file(filename) { audioSerial = -1; videoSerial = -1; skeletonSerial = -1; audioHeaders = 3; keyFrame = size_t(-1); currentSample = 0; currentFrame = 1; vorbisPos = 0; th_info ti; th_comment tc; th_setup_info* tsi = nullptr; th_info_init(&ti); th_comment_init(&tc); theora = nullptr; vorbis_info_init(&vi); vorbis_comment_init(&vc); ogg_sync_init(&sync); state = PLAYING; fileOffset = 0; fileSize = file.getSize(); ogg_page page; try { while ((audioHeaders || !theora) && nextPage(&page)) { int serial = ogg_page_serialno(&page); if (serial == audioSerial) { vorbisHeaderPage(&page); continue; } else if (serial == videoSerial) { theoraHeaderPage(&page, ti, tc, tsi); continue; } else if (serial == skeletonSerial) { continue; } if (!ogg_page_bos(&page)) { if (videoSerial == -1) { throw MSXException("No video track found"); } if (audioSerial == -1) { throw MSXException("No audio track found"); } // This should be unreachable, right? continue; } ogg_stream_state stream; ogg_packet packet; ogg_stream_init(&stream, serial); ogg_stream_pagein(&stream, &page); if (ogg_stream_packetout(&stream, &packet) <= 0) { ogg_stream_clear(&stream); throw MSXException("Invalid header"); } if (packet.bytes < 8) { ogg_stream_clear(&stream); throw MSXException("Header to small"); } if (memcmp(packet.packet, "\x01vorbis", 7) == 0) { if (audioSerial != -1) { ogg_stream_clear(&stream); throw MSXException("Duplicate audio stream"); } audioSerial = serial; ogg_stream_init(&vorbisStream, serial); vorbisHeaderPage(&page); } else if (memcmp(packet.packet, "\x80theora", 7) == 0) { if (videoSerial != -1) { ogg_stream_clear(&stream); throw MSXException("Duplicate video stream"); } videoSerial = serial; ogg_stream_init(&theoraStream, serial); theoraHeaderPage(&page, ti, tc, tsi); } else if (memcmp(packet.packet, "fishead", 8) == 0) { skeletonSerial = serial; } else if (memcmp(packet.packet, "BBCD", 4) == 0) { ogg_stream_clear(&stream); throw MSXException("DIRAC not supported"); } else if (memcmp(packet.packet, "\177FLAC", 5) == 0) { ogg_stream_clear(&stream); throw MSXException("FLAC not supported"); } else { ogg_stream_clear(&stream); throw MSXException("Unknown stream in ogg file"); } ogg_stream_clear(&stream); } if (videoSerial == -1) { throw MSXException("No video track found"); } if (audioSerial == -1) { throw MSXException("No audio track found"); } if (vi.channels != 2) { throw MSXException("Audio must be stereo"); } if (ti.frame_width != 640 || ti.frame_height != 480) { throw MSXException("Video must be size 640x480"); } if (ti.fps_numerator == 30000 && ti.fps_denominator == 1001) { frameRate = 30; } else if (ti.fps_numerator == 60000 && ti.fps_denominator == 1001) { frameRate = 60; } else { throw MSXException("Video frame rate must be 59.94Hz or 29.97Hz"); } // FIXME: Support YUV444 before release // It would be much better to use YUV444, however the existing // captures are in YUV420 format. yuv2rgb will have to be // updated too. if (ti.pixel_fmt != TH_PF_420) { throw MSXException("Video must be YUV420"); } } catch (MSXException&) { th_setup_free(tsi); th_info_clear(&ti); th_comment_clear(&tc); cleanup(); throw; } th_setup_free(tsi); th_info_clear(&ti); th_comment_clear(&tc); } void OggReader::cleanup() { if (audioHeaders == 0) { vorbis_dsp_clear(&vd); vorbis_block_clear(&vb); } th_decode_free(theora); vorbis_info_clear(&vi); vorbis_comment_clear(&vc); if (audioSerial != -1) { ogg_stream_clear(&vorbisStream); } if (videoSerial != -1) { ogg_stream_clear(&theoraStream); } ogg_sync_clear(&sync); } OggReader::~OggReader() { cleanup(); } /** Vorbis only records the ogg position (in no. of samples) once per ogg * page. After seeking we have already decoded some audio before we encounter * the exact position we are at. Fixup the positions and discard any unwanted * audio. This function expects vorbisPos to be set correctly. */ void OggReader::vorbisFoundPosition() { auto last = vorbisPos; for (auto it = audioList.rbegin(); it != audioList.rend(); ++it) { last -= (*it)->length; (*it)->position = last; } // last is now the first vorbis audio decoded if (last > currentSample) { cli.printWarning("missing part of audio stream"); } if (vorbisPos > currentSample) { currentSample = vorbisPos; } } void OggReader::vorbisHeaderPage(ogg_page* page) { ogg_stream_pagein(&vorbisStream, page); while (true) { ogg_packet packet; int res = ogg_stream_packetout(&vorbisStream, &packet); if (res < 0) { throw MSXException("error in vorbis stream"); } if (res == 0) break; if (audioHeaders == 0) { // ignore, we'll seek to the beginning again before playing continue; } if (packet.packetno <= 2) { if (vorbis_synthesis_headerin(&vi, &vc, &packet) < 0) { throw MSXException("invalid vorbis header"); } --audioHeaders; } if (packet.packetno == 2) { vorbis_synthesis_init(&vd, &vi) ; vorbis_block_init(&vd, &vb); } } } void OggReader::theoraHeaderPage(ogg_page* page, th_info& ti, th_comment& tc, th_setup_info*& tsi) { ogg_stream_pagein(&theoraStream, page); while (true) { ogg_packet packet; int res = ogg_stream_packetout(&theoraStream, &packet); if (res < 0) { throw MSXException("error in vorbis stream"); } if (res == 0) break; if (theora) { // ignore, we'll seek to the beginning again before playing continue; } if (packet.packetno <= 2) { res = th_decode_headerin(&ti, &tc, &tsi, &packet); if (res <= 0) { throw MSXException("invalid theora header"); } } if (packet.packetno == 2) { theora = th_decode_alloc(&ti, tsi); readMetadata(tc); granuleShift = ti.keyframe_granule_shift; } } } void OggReader::readVorbis(ogg_packet* packet) { // deal with header packets if (unlikely(packet->packetno <= 2)) { return; } if (state == FIND_LAST) { if (packet->granulepos != -1) { if ((currentSample == AudioFragment::UNKNOWN_POS) || (size_t(packet->granulepos) > currentSample)) { currentSample = packet->granulepos; } } return; } else if (state == FIND_FIRST) { if (packet->granulepos != -1 && currentSample == AudioFragment::UNKNOWN_POS) { currentSample = packet->granulepos; } return; } else if (state == FIND_KEYFRAME) { // Not relevant for vorbis return; } // generate pcm if (vorbis_synthesis(&vb, packet) != 0) { return; } vorbis_synthesis_blockin(&vd, &vb); float** pcm; long decoded = vorbis_synthesis_pcmout(&vd, &pcm); long pos = 0; while (pos < decoded) { // Find memory to copy PCM into if (recycleAudioList.empty()) { auto audio = make_unique(); audio->length = 0; recycleAudioList.push_back(std::move(audio)); } auto& audio = recycleAudioList.front(); if (audio->length == 0) { audio->position = vorbisPos; } else { // first element was already partially filled } // Copy PCM unsigned len = std::min(decoded - pos, AudioFragment::MAX_SAMPLES - audio->length); memcpy(audio->pcm[0] + audio->length, pcm[0] + pos, len * sizeof(float)); memcpy(audio->pcm[1] + audio->length, pcm[1] + pos, len * sizeof(float)); audio->length += len; pos += len; // Last packet or found position after seeking? bool last = (decoded == pos && (packet->e_o_s || (vorbisPos == AudioFragment::UNKNOWN_POS && packet->granulepos != -1))); if (vorbisPos != AudioFragment::UNKNOWN_POS) { vorbisPos += len; currentSample += len; } if (audio->length == AudioFragment::MAX_SAMPLES || last) { audioList.push_back(recycleAudioList.pop_front()); } } // The granulepos is the no. of samples since the begining of the // stream. Only once per ogg page is this populated. if (packet->granulepos != -1) { if (vorbisPos == AudioFragment::UNKNOWN_POS) { vorbisPos = packet->granulepos; vorbisFoundPosition(); } else { if (vorbisPos != size_t(packet->granulepos)) { cli.printWarning("vorbis audio out of sync, " "expected " + StringOp::toString(vorbisPos) + ", got " + StringOp::toString(packet->granulepos)); vorbisPos = packet->granulepos; } } } // done with PCM data vorbis_synthesis_read(&vd, decoded); } size_t OggReader::frameNo(ogg_packet* packet) { if (packet->granulepos == -1) { return size_t(-1); } size_t intra = packet->granulepos & ((1 << granuleShift) - 1); size_t key = packet->granulepos >> granuleShift; return key + intra; } void OggReader::readMetadata(th_comment& tc) { char* metadata = nullptr; for (int i = 0; i < tc.comments; ++i) { if (!strncasecmp(tc.user_comments[i], "location=", strlen("location="))) { metadata = tc.user_comments[i] + strlen("location="); break; } } if (!metadata) { return; } // Maybe there is a better way of doing this parsing in C++ char* p = metadata; while (p) { if (strncasecmp(p, "chapter: ", 9) == 0) { int chapter = atoi(p + 9); p = strchr(p, ','); if (!p) break; ++p; size_t frame = atol(p); if (frame) { chapters.emplace_back(chapter, frame); } } else if (strncasecmp(p, "stop: ", 6) == 0) { size_t stopframe = atol(p + 6); if (stopframe) { stopFrames.push_back(stopframe); } } p = strchr(p, '\n'); if (p) ++p; } sort(begin(stopFrames), end(stopFrames)); sort(begin(chapters ), end(chapters ), LessTupleElement<0>()); } void OggReader::readTheora(ogg_packet* packet) { if (th_packet_isheader(packet)) { return; } size_t frameno = frameNo(packet); // If we're seeking, we're only interested in packets with // frame numbers if ((state != PLAYING) && (frameno == size_t(-1))) { return; } if (state == FIND_LAST) { if ((currentFrame == size_t(-1)) || (currentFrame < frameno)) { currentFrame = frameno; } return; } else if (state == FIND_FIRST) { if ((currentFrame == size_t(-1)) || (currentFrame > frameno)) { currentFrame = frameno; } return; } else if (state == FIND_KEYFRAME) { if (frameno < currentFrame) { keyFrame = packet->granulepos >> granuleShift; } else if (currentFrame == frameno) { keyFrame = packet->granulepos >> granuleShift; currentSample = 1; } else if (frameno > currentFrame) { currentSample = 1; } return; } if ((keyFrame != size_t(-1)) && (frameno != size_t(-1)) && (frameno < keyFrame)) { // We're reading before the keyframe, discard return; } if (packet->bytes == 0 && frameList.empty()) { // No use passing empty packets (which represent dup frame) // before we've read any frame. return; } keyFrame = size_t(-1); int rc = th_decode_packetin(theora, packet, nullptr); switch (rc) { case TH_DUPFRAME: if (frameList.empty()) { cli.printWarning("Theora error: dup frame encountered " "without preceding frame"); } else { frameList.back()->length++; } break; case TH_EIMPL: cli.printWarning("Theora error: not capable of reading this"); break; case TH_EFAULT: cli.printWarning("Theora error: API not used correctly"); break; case TH_EBADPACKET: cli.printWarning("Theora error: bad packet"); break; case 0: break; default: cli.printWarning("Theora error: unknown error " + StringOp::toString(rc)); break; } if (rc) { return; } th_ycbcr_buffer yuv; if (th_decode_ycbcr_out(theora, yuv) != 0) { return; } if ((frameno != size_t(-1)) && (frameno < currentFrame)) { return; } currentFrame = frameno + 1; std::unique_ptr frame; if (recycleFrameList.empty()) { frame = make_unique(yuv); } else { frame = std::move(recycleFrameList.back()); recycleFrameList.pop_back(); } int y_size = yuv[0].height * yuv[0].stride; int uv_size = yuv[1].height * yuv[1].stride; memcpy(frame->buffer[0].data, yuv[0].data, y_size); memcpy(frame->buffer[1].data, yuv[1].data, uv_size); memcpy(frame->buffer[2].data, yuv[2].data, uv_size); // At lot of frames have framenumber -1, only some have the correct // frame number. We continue counting from the previous known // postion Frame* last = frameList.empty() ? nullptr : frameList.back().get(); if (last && (last->no != size_t(-1))) { if ((frameno != size_t(-1)) && (frameno != last->no + last->length)) { cli.printWarning("Theora frame sequence wrong"); } else { frameno = last->no + last->length; } } frame->no = frameno; frame->length = 1; // We may read some frames before we encounter one with a proper // frame number. When we do, go back and populate the frame // numbers correctly if (!frameList.empty() && (frameno != size_t(-1)) && (frameList[0]->no == size_t(-1))) { for (auto it = frameList.rbegin(); it != frameList.rend(); ++it) { frameno -= (*it)->length; (*it)->no = frameno; } } frameList.push_back(std::move(frame)); } void OggReader::getFrameNo(RawFrame& rawFrame, size_t frameno) { Frame* frame; while (true) { // If there are no frames or the frames we have read // does not include a proper frame number, just read // more data if (frameList.empty() || (frameList[0]->no == size_t(-1))) { if (!nextPacket()) { return; } continue; } // Remove unneeded frames. Note that at 60Hz the odd and // and even frame are displayed during still, so we can // only throw away the one two frames ago while (frameList.size() >= 3 && frameList[2]->no <= frameno) { recycleFrameList.push_back(frameList.pop_front()); } if (!frameList.empty() && frameList[0]->no > frameno) { // we're missing frames! frame = frameList[0].get(); cli.printWarning("Cannot find frame " + StringOp::toString(frameno) + " using " + StringOp::toString(frame->no) + " instead"); break; } if ((frameList.size() >= 2) && ((frameno >= frameList[0]->no) && (frameno < frameList[1]->no))) { frame = frameList[0].get(); break; } if ((frameList.size() >= 3) && ((frameno >= frameList[1]->no) && (frameno < frameList[2]->no))) { frame = frameList[1].get(); break; } // Sanity check, should not happen if (frameList.size() > (2u << granuleShift)) { // We've got more than twice as many frames // as the maximum distance between key frames. cli.printWarning("Cannot find frame " + StringOp::toString(frameno)); return; } // ..add read some new ones if (!nextPacket()) { return; } } yuv2rgb::convert(frame->buffer, rawFrame); } void OggReader::recycleAudio(std::unique_ptr audio) { audio->length = 0; recycleAudioList.push_back(std::move(audio)); } const AudioFragment* OggReader::getAudio(size_t sample) { // Read while position is unknown while (audioList.empty() || audioList.front()->position == AudioFragment::UNKNOWN_POS) { if (!nextPacket()) { return nullptr; } } auto it = begin(audioList); while (true) { auto& audio = *it; if (audio->position + audio->length + getSampleRate() <= sample) { // Dispose if this, more than 1 second old recycleAudio(std::move(*it)); it = audioList.erase(it); } else if (audio->position + audio->length <= sample) { ++it; } else { if (audio->position <= sample) { return audio.get(); } else { // gone too far? return nullptr; } } // read more if we're at the end of the list if (it == end(audioList)) { size_t size = audioList.size(); while (size == audioList.size()) { if (!nextPacket()) { return nullptr; } } // reset the iterator to not point to the end it = begin(audioList); } } } bool OggReader::nextPacket() { ogg_packet packet; ogg_page page; while (true) { int ret = ogg_stream_packetout(&vorbisStream, &packet); if (ret == 1) { readVorbis(&packet); return true; } else if (ret == -1) { // recoverable error continue; } ret = ogg_stream_packetout(&theoraStream, &packet); if (ret == 1) { readTheora(&packet); return true; } else if (ret == -1) { // recoverable error continue; } if (!nextPage(&page)) { return false; } int serial = ogg_page_serialno(&page); if (serial == audioSerial) { if (ogg_stream_pagein(&vorbisStream, &page)) { cli.printWarning("Failed to submit vorbis page"); } } else if (serial == videoSerial) { if (ogg_stream_pagein(&theoraStream, &page)) { cli.printWarning("Failed to submit theora page"); } } else if (serial != skeletonSerial) { cli.printWarning("Unexpected stream with serial " + StringOp::toString(serial) + " in ogg file"); } } } bool OggReader::nextPage(ogg_page* page) { static const size_t CHUNK = 4096; int ret; while ((ret = ogg_sync_pageseek(&sync, page)) <= 0) { if (ret < 0) { //throw MSXException("Invalid Ogg file"); } size_t chunk; if (fileSize - fileOffset >= CHUNK) { chunk = CHUNK; } else if (fileOffset < fileSize) { chunk = fileSize - fileOffset; } else { return false; } char* buffer = ogg_sync_buffer(&sync, long(chunk)); file.read(buffer, chunk); fileOffset += chunk; if (ogg_sync_wrote(&sync, long(chunk)) == -1) { cli.printWarning("Internal error: ogg_sync_wrote failed"); } } return true; } size_t OggReader::bisection( size_t frame, size_t sample, size_t maxOffset, size_t maxSamples, size_t maxFrames) { // Defined to be a power-of-two such that the arthmetic can be done faster. // Note that the sample-number is in the range of: 1..(44100*60*60) static const uint64_t SHIFT = 0x20000000ull; uint64_t offsetA = 0, offsetB = maxOffset; uint64_t sampleA = 0, sampleB = maxSamples; uint64_t frameA = 1, frameB = maxFrames; while (true) { uint64_t ratio = (frame - frameA) * SHIFT / (frameB - frameA); if (ratio < 5) { return offsetA; } uint64_t frameOffset = ratio * (offsetB - offsetA) / SHIFT + offsetA; ratio = (sample - sampleA) * SHIFT / (sampleB - sampleA); if (ratio < 5) { return offsetA; } uint64_t sampleOffset = ratio * (offsetB - offsetA) / SHIFT + offsetA; auto offset = std::min(sampleOffset, frameOffset); file.seek(offset); fileOffset = offset; ogg_sync_reset(&sync); currentFrame = size_t(-1); currentSample = AudioFragment::UNKNOWN_POS; state = FIND_FIRST; while (((currentFrame == size_t(-1)) || (currentSample == AudioFragment::UNKNOWN_POS)) && nextPacket()) { // continue reading } state = PLAYING; if (currentSample > sample || currentFrame > frame) { offsetB = offset; sampleB = currentSample; frameB = currentFrame; } else if (currentSample + getSampleRate() < sample && currentFrame + 64 < frame) { offsetA = offset; sampleA = currentSample; frameA = currentFrame; } else { return offset; } } } size_t OggReader::findOffset(size_t frame, size_t sample) { static const size_t STEP = 32 * 1024; // first calculate total length in bytes, samples and frames // The file might have changed since we last requested its size, // we assume that only data will be added to it and the ogg streams // are exactly as before fileSize = file.getSize(); auto offset = fileSize - 1; while (offset > 0) { if (offset > STEP) { offset -= STEP; } else { offset = 0; } file.seek(offset); fileOffset = offset; ogg_sync_reset(&sync); currentFrame = size_t(-1); currentSample = AudioFragment::UNKNOWN_POS; state = FIND_LAST; while (nextPacket()) { // continue reading } state = PLAYING; if ((currentFrame != size_t(-1)) && (currentSample != AudioFragment::UNKNOWN_POS)) { break; } } totalFrames = currentFrame; // If we're close to beginning, don't bother searching for it, // just start at the beginning (arbitrary boundary of 1 second). if (sample < getSampleRate() || frame <= 30) { keyFrame = 1; return 0; } auto maxOffset = offset; auto maxSamples = currentSample; auto maxFrames = currentFrame; if ((sample > maxSamples) || (frame > maxFrames)) { sample = maxSamples; frame = maxFrames; } offset = bisection(frame, sample, maxOffset, maxSamples, maxFrames); // Find key frame file.seek(offset); fileOffset = offset; ogg_sync_reset(&sync); currentFrame = frame; currentSample = 0; keyFrame = size_t(-1); state = FIND_KEYFRAME; while (currentSample == 0 && nextPacket()) { // continue reading } state = PLAYING; if ((keyFrame == size_t(-1)) || (frame == keyFrame)) { return offset; } return bisection(keyFrame, sample, maxOffset, maxSamples, maxFrames); } bool OggReader::seek(size_t frame, size_t samples) { // Remove all queued frames recycleFrameList.insert(end(recycleFrameList), make_move_iterator(begin(frameList)), make_move_iterator(end (frameList))); frameList.clear(); // Remove all queued audio if (!recycleAudioList.empty()) { recycleAudioList.front()->length = 0; } for (auto& a : audioList) { recycleAudio(std::move(a)); } audioList.clear(); fileOffset = findOffset(frame, samples); file.seek(fileOffset); ogg_sync_reset(&sync); vorbisPos = AudioFragment::UNKNOWN_POS; currentFrame = frame; currentSample = samples; vorbis_synthesis_restart(&vd); return true; } bool OggReader::stopFrame(size_t frame) const { return std::binary_search(begin(stopFrames), end(stopFrames), frame); } size_t OggReader::chapter(int chapterNo) const { auto it = std::lower_bound(begin(chapters), end(chapters), chapterNo, LessTupleElement<0>()); return ((it != end(chapters)) && (it->first == chapterNo)) ? it->second : 0; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/laserdisc/OggReader.hh000066400000000000000000000051621257557151200213630ustar00rootroot00000000000000#ifndef OGGREADER_HH #define OGGREADER_HH #include "File.hh" #include "circular_buffer.hh" #include "noncopyable.hh" #include #include #include #include #include #include #include namespace openmsx { class CliComm; class RawFrame; class Filename; struct AudioFragment { static const size_t UNKNOWN_POS = size_t(-1); static const unsigned MAX_SAMPLES = 2048; size_t position; unsigned length; float pcm[2][MAX_SAMPLES]; }; struct Frame { Frame(const th_ycbcr_buffer& yuv); ~Frame(); th_ycbcr_buffer buffer; size_t no; int length; }; class OggReader : private noncopyable { public: OggReader(const Filename& filename, CliComm& cli); ~OggReader(); bool seek(size_t frame, size_t sample); unsigned getSampleRate() const { return vi.rate; } void getFrameNo(RawFrame& frame, size_t frameno); const AudioFragment* getAudio(size_t sample); size_t getFrames() const { return totalFrames; } int getFrameRate() const { return frameRate; } // metadata bool stopFrame(size_t frame) const; size_t chapter(int chapterNo) const; private: void cleanup(); void readTheora(ogg_packet* packet); void theoraHeaderPage(ogg_page* page, th_info& ti, th_comment& tc, th_setup_info*& tsi); void readMetadata(th_comment& tc); void readVorbis(ogg_packet* packet); void vorbisHeaderPage(ogg_page* page); bool nextPage(ogg_page* page); bool nextPacket(); void recycleAudio(std::unique_ptr audio); void vorbisFoundPosition(); size_t frameNo(ogg_packet* packet); size_t findOffset(size_t frame, size_t sample); size_t bisection(size_t frame, size_t sample, size_t maxOffset, size_t maxSamples, size_t maxFrames); CliComm& cli; File file; enum State { PLAYING, FIND_LAST, FIND_FIRST, FIND_KEYFRAME } state; // ogg state ogg_sync_state sync; ogg_stream_state vorbisStream, theoraStream; int audioSerial; int videoSerial; int skeletonSerial; size_t fileOffset; size_t fileSize; // video th_dec_ctx* theora; int frameRate; size_t keyFrame; size_t currentFrame; int granuleShift; size_t totalFrames; cb_queue> frameList; std::vector> recycleFrameList; // audio int audioHeaders; vorbis_info vi; vorbis_comment vc; vorbis_dsp_state vd; vorbis_block vb; size_t currentSample; size_t vorbisPos; std::list> audioList; cb_queue> recycleAudioList; // Metadata std::vector stopFrames; std::vector> chapters; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/laserdisc/PioneerLDControl.cc000066400000000000000000000123161257557151200226730ustar00rootroot00000000000000#include "PioneerLDControl.hh" #include "CacheLine.hh" #include "serialize.hh" #include "LaserdiscPlayer.hh" #include "MSXPPI.hh" #include "MSXException.hh" #include "VDP.hh" #include "memory.hh" namespace openmsx { /* * Laserdisc Control: there are three bits involved here. There are three * bits/connections involved. * - EXTACK (from Laserdisc -> MSX) will remain low for a while to acknowledge * it has received a command and is executing * - EXTCONTROL (from MSX -> Laserdisc) one bit information which is used for * sending commands * - PULSE (internal to MSX) 8175.5hz signal which used by software to * create the right pulses for communicating with the Laserdisc over * EXTCONTROL. * * Sound Muting: left and right audio channels from Laserdisc input can * be muted independently. After reset or startup both channels are muted. * The left muting is controlled by bit 7 of 0x7fff; set is not muted, * cleared is muted. If this bit changed from 0->1 (rising edge triggered * and inverted) then bit 4 of register C of PPI switches L muting; set * for muting disabled, clear for muting enabled. * * Cassette Input: If the motor relay is OFF then audio on the R channel * ends up in the PSG (regardless of muting); if the motor relay is ON * then normal tape input is used. */ PioneerLDControl::PioneerLDControl(const DeviceConfig& config) : MSXDevice(config) , rom(getName() + " ROM", "rom", config) , clock(EmuTime::zero) , irq(getMotherBoard(), "PioneerLDControl.IRQdisplayoff") , videoEnabled(false) { if (config.getChildDataAsBool("laserdisc", true)) { laserdisc = make_unique( getHardwareConfig(), *this); } reset(getCurrentTime()); } void PioneerLDControl::init() { MSXDevice::init(); const auto& references = getReferences(); ppi = references.size() >= 1 ? dynamic_cast(references[0]) : nullptr; if (!ppi) { throw MSXException("Invalid PioneerLDControl configuration: " "need reference to PPI device."); } vdp = references.size() == 2 ? dynamic_cast(references[1]) : nullptr; if (!vdp) { throw MSXException("Invalid PioneerLDControl configuration: " "need reference to VDP device."); } } PioneerLDControl::~PioneerLDControl() { } void PioneerLDControl::reset(EmuTime::param time) { mutel = muter = true; superimposing = false; extint = false; irq.reset(); if (laserdisc) laserdisc->setMuting(mutel, muter, time); } byte PioneerLDControl::readMem(word address, EmuTime::param time) { byte val = PioneerLDControl::peekMem(address, time); if (address == 0x7fff) { extint = false; irq.reset(); } return val; } byte PioneerLDControl::peekMem(word address, EmuTime::param time) const { byte val = 0xff; if (address == 0x7fff) { if (videoEnabled) { val &= 0x7f; } if (!extint) { val &= 0xfe; } } else if (address == 0x7ffe) { if (clock.getTicksTill(time) & 1) { val &= 0xfe; } if (laserdisc && laserdisc->extAck(time)) { val &= 0x7f; } } else if (0x4000 <= address && address < 0x6000) { val = rom[address & 0x1fff]; } return val; } const byte* PioneerLDControl::getReadCacheLine(word start) const { if ((start & CacheLine::HIGH) == (0x7FFE & CacheLine::HIGH)) { return nullptr; } else if (0x4000 <= start && start < 0x6000) { return &rom[start & 0x1fff]; } else { return unmappedRead; } } void PioneerLDControl::writeMem(word address, byte value, EmuTime::param time) { if (address == 0x7fff) { // superimpose superimposing = !(value & 1); irq.set(superimposing && extint); updateVideoSource(); // Muting if (!mutel && !(value & 0x80)) { muter = !(ppi->peekIO(2, time) & 0x10); } mutel = !(value & 0x80); if (laserdisc) laserdisc->setMuting(mutel, muter, time); } else if (address == 0x7ffe) { if (laserdisc) laserdisc->extControl(value & 1, time); } } byte* PioneerLDControl::getWriteCacheLine(word start) const { if ((start & CacheLine::HIGH) == (0x7FFE & CacheLine::HIGH)) { return nullptr; } else { return unmappedWrite; } } void PioneerLDControl::videoIn(bool enabled) { if (videoEnabled && !enabled) { // raise an interrupt when external video goes off extint = true; if (superimposing) irq.set(); } videoEnabled = enabled; updateVideoSource(); } void PioneerLDControl::updateVideoSource() { auto* videoSource = (videoEnabled && superimposing && laserdisc) ? laserdisc->getRawFrame() : nullptr; vdp->setExternalVideoSource(videoSource); } template void PioneerLDControl::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("clock", clock); ar.serialize("mutel", mutel); ar.serialize("muter", muter); // videoEnabled is restored from LaserdiscPlayer. Set to false // for now so that the irq does not get changed during load if (ar.isLoader()) { videoEnabled = false; } ar.serialize("superimposing", superimposing); ar.serialize("extint", extint); ar.serialize("irq", irq); if (laserdisc) ar.serialize("laserdisc", *laserdisc); if (ar.isLoader()) { updateVideoSource(); if (laserdisc) { laserdisc->setMuting(mutel, muter, getCurrentTime()); } } } REGISTER_MSXDEVICE(PioneerLDControl, "PBASIC"); INSTANTIATE_SERIALIZE_METHODS(PioneerLDControl); } // namespace openmsx openMSX-RELEASE_0_12_0/src/laserdisc/PioneerLDControl.hh000066400000000000000000000026711257557151200227100ustar00rootroot00000000000000#ifndef PIONEERLDCONTROL_HH #define PIONEERLDCONTROL_HH #include "MSXDevice.hh" #include "Clock.hh" #include "IRQHelper.hh" #include "Rom.hh" #include namespace openmsx { class LaserdiscPlayer; class MSXPPI; class VDP; class PioneerLDControl final : public MSXDevice { public: explicit PioneerLDControl(const DeviceConfig& config); ~PioneerLDControl(); void reset(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte* getWriteCacheLine(word address) const override; void init() override; void videoIn(bool enabled); template void serialize(Archive& ar, unsigned version); private: void updateVideoSource(); Rom rom; std::unique_ptr laserdisc; // can be nullptr MSXPPI* ppi; VDP* vdp; /** * The reference clock for the pulse is 500kHz / 128, which * alternates after each period. We double the frequency * and use modulo 2 for whether it will be high or low. * * See page 88 of the PX-7 Service Manual. */ Clock<2 * 500000, 128> clock; // 2*500kHz / 128 = 7812.5Hz /** * When video output stops, generate an IRQ */ IRQHelper irq; bool extint; bool mutel, muter; bool videoEnabled; bool superimposing; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/laserdisc/yuv2rgb.cc000066400000000000000000000341601257557151200211120ustar00rootroot00000000000000#include "yuv2rgb.hh" #include "RawFrame.hh" #include "Math.hh" #include #include #include #ifdef __SSE2__ #include #endif namespace openmsx { namespace yuv2rgb { #ifdef __SSE2__ /* * This implementation of yuv420 to rgb is based upon the corresponding routine * from Mono. See this blog entry: * http://blog.sublimeintervention.com/archive/2008/Mar-21.html * Source code: * http://anonsvn.mono-project.com/viewvc/trunk/moon/src/yuv-converter.cpp?revision=136072 * This code is GPL2 (only) * * Copyright 2008 Novell, Inc. (http://www.novell.com) * * There are other implementations: * - ffmpeg * - mythtv * - pcsx2 * I have not done a comparison of these implementations. */ /* R = 1.164 * (Y - 16) + 1.596 * (V - 128) * G = 1.164 * (Y - 16) - 0.813 * (V - 128) - 0.391 * (U - 128) * B = 1.164 * (Y - 16) + 2.018 * (U - 128) * OR * R = 1.164 * Y + 1.596 * V - 222.921 * G = 1.164 * Y - 0.813 * V - 0.391 * U + 135.576 * B = 1.164 * Y + 2.018 * U - 276.836 */ static inline void yuv2rgb_sse2( const uint8_t* u_ , const uint8_t* v_, const uint8_t* y0_, const uint8_t* y1_, uint32_t* out0_, uint32_t* out1_) { // This routine calculates 32x2 RGBA pixels. Each output pixel uses a // unique corresponding input Y value, but a group of 2x2 ouput pixels // shares the same U and V input value. auto* u = reinterpret_cast(u_); auto* v = reinterpret_cast(v_); auto* y0 = reinterpret_cast(y0_); auto* y1 = reinterpret_cast(y1_); auto* out0 = reinterpret_cast< __m128i*>(out0_); auto* out1 = reinterpret_cast< __m128i*>(out1_); // constants const __m128i ZERO = _mm_setzero_si128(); const __m128i ALPHA = _mm_set1_epi16( -1); // 0xFFFF const __m128i RED_V = _mm_set1_epi16( 102); // 102/64 = 1.59 const __m128i GREEN_U = _mm_set1_epi16( -25); // -25/64 = -0.39 const __m128i GREEN_V = _mm_set1_epi16( -52); // -52/64 = -0.81 const __m128i BLUE_U = _mm_set1_epi16( 129); // 129/64 = 2.02 const __m128i COEF_Y = _mm_set1_epi16( 74); // 74/64 = 1.16 const __m128i CNST_R = _mm_set1_epi16( -223); // -222.921 const __m128i CNST_G = _mm_set1_epi16( 136); // 135.576 const __m128i CNST_B = _mm_set1_epi16( -277); // -276.836 const __m128i Y_MASK = _mm_set1_epi16(0x00FF); // left __m128i u0f = _mm_load_si128(u); __m128i v0f = _mm_load_si128(v); __m128i u07 = _mm_unpacklo_epi8(u0f, ZERO); __m128i v07 = _mm_unpacklo_epi8(v0f, ZERO); __m128i mr07 = _mm_srai_epi16(_mm_mullo_epi16(v07, RED_V), 6); __m128i sg07 = _mm_mullo_epi16(v07, GREEN_V); __m128i tg07 = _mm_mullo_epi16(u07, GREEN_U); __m128i mg07 = _mm_srai_epi16(_mm_adds_epi16(sg07, tg07), 6); __m128i mb07 = _mm_srli_epi16(_mm_mullo_epi16(u07, BLUE_U), 6); // logical shift __m128i dr07 = _mm_adds_epi16(mr07, CNST_R); __m128i dg07 = _mm_adds_epi16(mg07, CNST_G); __m128i db07 = _mm_adds_epi16(mb07, CNST_B); // block top,left __m128i y00_0f = _mm_load_si128(y0 + 0); __m128i y00_even = _mm_and_si128(y00_0f, Y_MASK); __m128i y00_odd = _mm_srli_epi16(y00_0f, 8); __m128i dy00_even = _mm_srai_epi16(_mm_mullo_epi16(y00_even, COEF_Y), 6); __m128i dy00_odd = _mm_srai_epi16(_mm_mullo_epi16(y00_odd, COEF_Y), 6); __m128i r00_even = _mm_adds_epi16(dr07, dy00_even); __m128i g00_even = _mm_adds_epi16(dg07, dy00_even); __m128i b00_even = _mm_adds_epi16(db07, dy00_even); __m128i r00_odd = _mm_adds_epi16(dr07, dy00_odd); __m128i g00_odd = _mm_adds_epi16(dg07, dy00_odd); __m128i b00_odd = _mm_adds_epi16(db07, dy00_odd); __m128i r00_0f = _mm_unpackhi_epi8(_mm_packus_epi16(r00_even, r00_even), _mm_packus_epi16(r00_odd, r00_odd)); __m128i g00_0f = _mm_unpackhi_epi8(_mm_packus_epi16(g00_even, g00_even), _mm_packus_epi16(g00_odd, g00_odd)); __m128i b00_0f = _mm_unpackhi_epi8(_mm_packus_epi16(b00_even, b00_even), _mm_packus_epi16(b00_odd, b00_odd)); __m128i br00_07 = _mm_unpacklo_epi8(b00_0f, r00_0f); __m128i br00_8f = _mm_unpackhi_epi8(b00_0f, r00_0f); __m128i ga00_07 = _mm_unpacklo_epi8(g00_0f, ALPHA); __m128i ga00_8f = _mm_unpackhi_epi8(g00_0f, ALPHA); __m128i bgra00_03 = _mm_unpacklo_epi8(br00_07, ga00_07); __m128i bgra00_47 = _mm_unpackhi_epi8(br00_07, ga00_07); __m128i bgra00_8b = _mm_unpacklo_epi8(br00_8f, ga00_8f); __m128i bgra00_cf = _mm_unpackhi_epi8(br00_8f, ga00_8f); _mm_store_si128(out0 + 0, bgra00_03); _mm_store_si128(out0 + 1, bgra00_47); _mm_store_si128(out0 + 2, bgra00_8b); _mm_store_si128(out0 + 3, bgra00_cf); // block bottom,left __m128i y10_0f = _mm_load_si128(y1 + 0); __m128i y10_even = _mm_and_si128(y10_0f, Y_MASK); __m128i y10_odd = _mm_srli_epi16(y10_0f, 8); __m128i dy10_even = _mm_srai_epi16(_mm_mullo_epi16(y10_even, COEF_Y), 6); __m128i dy10_odd = _mm_srai_epi16(_mm_mullo_epi16(y10_odd, COEF_Y), 6); __m128i r10_even = _mm_adds_epi16(dr07, dy10_even); __m128i g10_even = _mm_adds_epi16(dg07, dy10_even); __m128i b10_even = _mm_adds_epi16(db07, dy10_even); __m128i r10_odd = _mm_adds_epi16(dr07, dy10_odd); __m128i g10_odd = _mm_adds_epi16(dg07, dy10_odd); __m128i b10_odd = _mm_adds_epi16(db07, dy10_odd); __m128i r10_0f = _mm_unpackhi_epi8(_mm_packus_epi16(r10_even, r10_even), _mm_packus_epi16(r10_odd, r10_odd)); __m128i g10_0f = _mm_unpackhi_epi8(_mm_packus_epi16(g10_even, g10_even), _mm_packus_epi16(g10_odd, g10_odd)); __m128i b10_0f = _mm_unpackhi_epi8(_mm_packus_epi16(b10_even, b10_even), _mm_packus_epi16(b10_odd, b10_odd)); __m128i br10_07 = _mm_unpacklo_epi8(b10_0f, r10_0f); __m128i br10_8f = _mm_unpackhi_epi8(b10_0f, r10_0f); __m128i ga10_07 = _mm_unpacklo_epi8(g10_0f, ALPHA); __m128i ga10_8f = _mm_unpackhi_epi8(g10_0f, ALPHA); __m128i bgra10_03 = _mm_unpacklo_epi8(br10_07, ga10_07); __m128i bgra10_47 = _mm_unpackhi_epi8(br10_07, ga10_07); __m128i bgra10_8b = _mm_unpacklo_epi8(br10_8f, ga10_8f); __m128i bgra10_cf = _mm_unpackhi_epi8(br10_8f, ga10_8f); _mm_store_si128(out1 + 0, bgra10_03); _mm_store_si128(out1 + 1, bgra10_47); _mm_store_si128(out1 + 2, bgra10_8b); _mm_store_si128(out1 + 3, bgra10_cf); // right __m128i u8f = _mm_unpackhi_epi8(u0f, ZERO); __m128i v8f = _mm_unpackhi_epi8(v0f, ZERO); __m128i mr8f = _mm_srai_epi16(_mm_mullo_epi16(v8f, RED_V), 6); __m128i sg8f = _mm_mullo_epi16(v8f, GREEN_V); __m128i tg8f = _mm_mullo_epi16(u8f, GREEN_U); __m128i mg8f = _mm_srai_epi16(_mm_adds_epi16(sg8f, tg8f), 6); __m128i mb8f = _mm_srli_epi16(_mm_mullo_epi16(u8f, BLUE_U), 6); // logical shift __m128i dr8f = _mm_adds_epi16(mr8f, CNST_R); __m128i dg8f = _mm_adds_epi16(mg8f, CNST_G); __m128i db8f = _mm_adds_epi16(mb8f, CNST_B); // block top,right __m128i y01_0f = _mm_load_si128(y0 + 1); __m128i y01_even = _mm_and_si128(y01_0f, Y_MASK); __m128i y01_odd = _mm_srli_epi16(y01_0f, 8); __m128i dy01_even = _mm_srai_epi16(_mm_mullo_epi16(y01_even, COEF_Y), 6); __m128i dy01_odd = _mm_srai_epi16(_mm_mullo_epi16(y01_odd, COEF_Y), 6); __m128i r01_even = _mm_adds_epi16(dr8f, dy01_even); __m128i g01_even = _mm_adds_epi16(dg8f, dy01_even); __m128i b01_even = _mm_adds_epi16(db8f, dy01_even); __m128i r01_odd = _mm_adds_epi16(dr8f, dy01_odd); __m128i g01_odd = _mm_adds_epi16(dg8f, dy01_odd); __m128i b01_odd = _mm_adds_epi16(db8f, dy01_odd); __m128i r01_0f = _mm_unpackhi_epi8(_mm_packus_epi16(r01_even, r01_even), _mm_packus_epi16(r01_odd, r01_odd)); __m128i g01_0f = _mm_unpackhi_epi8(_mm_packus_epi16(g01_even, g01_even), _mm_packus_epi16(g01_odd, g01_odd)); __m128i b01_0f = _mm_unpackhi_epi8(_mm_packus_epi16(b01_even, b01_even), _mm_packus_epi16(b01_odd, b01_odd)); __m128i br01_07 = _mm_unpacklo_epi8(b01_0f, r01_0f); __m128i br01_8f = _mm_unpackhi_epi8(b01_0f, r01_0f); __m128i ga01_07 = _mm_unpacklo_epi8(g01_0f, ALPHA); __m128i ga01_8f = _mm_unpackhi_epi8(g01_0f, ALPHA); __m128i bgra01_03 = _mm_unpacklo_epi8(br01_07, ga01_07); __m128i bgra01_47 = _mm_unpackhi_epi8(br01_07, ga01_07); __m128i bgra01_8b = _mm_unpacklo_epi8(br01_8f, ga01_8f); __m128i bgra01_cf = _mm_unpackhi_epi8(br01_8f, ga01_8f); _mm_store_si128(out0 + 4, bgra01_03); _mm_store_si128(out0 + 5, bgra01_47); _mm_store_si128(out0 + 6, bgra01_8b); _mm_store_si128(out0 + 7, bgra01_cf); // block bottom,right __m128i y11_0f = _mm_load_si128(y1 + 1); __m128i y11_even = _mm_and_si128(y11_0f, Y_MASK); __m128i y11_odd = _mm_srli_epi16(y11_0f, 8); __m128i dy11_even = _mm_srai_epi16(_mm_mullo_epi16(y11_even, COEF_Y), 6); __m128i dy11_odd = _mm_srai_epi16(_mm_mullo_epi16(y11_odd, COEF_Y), 6); __m128i r11_even = _mm_adds_epi16(dr8f, dy11_even); __m128i g11_even = _mm_adds_epi16(dg8f, dy11_even); __m128i b11_even = _mm_adds_epi16(db8f, dy11_even); __m128i r11_odd = _mm_adds_epi16(dr8f, dy11_odd); __m128i g11_odd = _mm_adds_epi16(dg8f, dy11_odd); __m128i b11_odd = _mm_adds_epi16(db8f, dy11_odd); __m128i r11_0f = _mm_unpackhi_epi8(_mm_packus_epi16(r11_even, r11_even), _mm_packus_epi16(r11_odd, r11_odd)); __m128i g11_0f = _mm_unpackhi_epi8(_mm_packus_epi16(g11_even, g11_even), _mm_packus_epi16(g11_odd, g11_odd)); __m128i b11_0f = _mm_unpackhi_epi8(_mm_packus_epi16(b11_even, b11_even), _mm_packus_epi16(b11_odd, b11_odd)); __m128i br11_07 = _mm_unpacklo_epi8(b11_0f, r11_0f); __m128i br11_8f = _mm_unpackhi_epi8(b11_0f, r11_0f); __m128i ga11_07 = _mm_unpacklo_epi8(g11_0f, ALPHA); __m128i ga11_8f = _mm_unpackhi_epi8(g11_0f, ALPHA); __m128i bgra11_03 = _mm_unpacklo_epi8(br11_07, ga11_07); __m128i bgra11_47 = _mm_unpackhi_epi8(br11_07, ga11_07); __m128i bgra11_8b = _mm_unpacklo_epi8(br11_8f, ga11_8f); __m128i bgra11_cf = _mm_unpackhi_epi8(br11_8f, ga11_8f); _mm_store_si128(out1 + 4, bgra11_03); _mm_store_si128(out1 + 5, bgra11_47); _mm_store_si128(out1 + 6, bgra11_8b); _mm_store_si128(out1 + 7, bgra11_cf); } static inline void convertHelperSSE2( const th_ycbcr_buffer& buffer, RawFrame& output) { const int width = buffer[0].width; const int y_stride = buffer[0].stride; const int uv_stride2 = buffer[1].stride / 2; assert((width % 32) == 0); assert((buffer[0].height % 2) == 0); for (int y = 0; y < buffer[0].height; y += 2) { const uint8_t* pY1 = buffer[0].data + y * y_stride; const uint8_t* pY2 = buffer[0].data + (y + 1) * y_stride; const uint8_t* pCb = buffer[1].data + y * uv_stride2; const uint8_t* pCr = buffer[2].data + y * uv_stride2; uint32_t* out0 = output.getLinePtrDirect(y + 0); uint32_t* out1 = output.getLinePtrDirect(y + 1); for (int x = 0; x < width; x += 32) { // convert a block of (32 x 2) pixels yuv2rgb_sse2(pCb, pCr, pY1, pY2, out0, out1); pCb += 16; pCr += 16; pY1 += 32; pY2 += 32; out0 += 32; out1 += 32; } output.setLineWidth(y + 0, width); output.setLineWidth(y + 1, width); } } #endif // __SSE2__ static int coefs_gu[256]; static int coefs_gv[256]; static int coefs_bu[256]; static int coefs_rv[256]; static int coefs_y [256]; static const int PREC = 15; static const int COEF_Y = int(1.164 * (1 << PREC) + 0.5); static const int COEF_RV = int(1.596 * (1 << PREC) + 0.5); static const int COEF_GU = int(0.391 * (1 << PREC) + 0.5); static const int COEF_GV = int(0.813 * (1 << PREC) + 0.5); static const int COEF_BU = int(2.018 * (1 << PREC) + 0.5); static void initTables() { static bool init = false; if (init) return; init = true; for (int i = 0; i < 256; ++i) { coefs_gu[i] = -COEF_GU * (i - 128); coefs_gv[i] = -COEF_GV * (i - 128); coefs_bu[i] = COEF_BU * (i - 128); coefs_rv[i] = COEF_RV * (i - 128); coefs_y[i] = COEF_Y * (i - 16) + (PREC / 2); } } template static inline Pixel calc(const SDL_PixelFormat& format, int y, int ruv, int guv, int buv) { uint8_t r = Math::clipIntToByte((y + ruv) >> PREC); uint8_t g = Math::clipIntToByte((y + guv) >> PREC); uint8_t b = Math::clipIntToByte((y + buv) >> PREC); if (sizeof(Pixel) == 4) { return (r << 16) | (g << 8) | (b << 0); } else { return static_cast(SDL_MapRGB(&format, r, g, b)); } } template static void convertHelper(const th_ycbcr_buffer& buffer, RawFrame& output, const SDL_PixelFormat& format) { assert(buffer[1].width * 2 == buffer[0].width); assert(buffer[1].height * 2 == buffer[0].height); const int width = buffer[0].width; const int y_stride = buffer[0].stride; const int uv_stride2 = buffer[1].stride / 2; for (int y = 0; y < buffer[0].height; y += 2) { const uint8_t* pY = buffer[0].data + y * y_stride; const uint8_t* pCb = buffer[1].data + y * uv_stride2; const uint8_t* pCr = buffer[2].data + y * uv_stride2; Pixel* out0 = output.getLinePtrDirect(y + 0); Pixel* out1 = output.getLinePtrDirect(y + 1); for (int x = 0; x < width; x += 2, pY += 2, ++pCr, ++pCb, out0 += 2, out1 += 2) { int ruv = coefs_rv[*pCr]; int guv = coefs_gu[*pCb] + coefs_gv[*pCr]; int buv = coefs_bu[*pCb]; int Y00 = coefs_y[pY[0]]; out0[0] = calc(format, Y00, ruv, guv, buv); int Y01 = coefs_y[pY[1]]; out0[1] = calc(format, Y01, ruv, guv, buv); int Y10 = coefs_y[pY[y_stride + 0]]; out1[0] = calc(format, Y10, ruv, guv, buv); int Y11 = coefs_y[pY[y_stride + 1]]; out1[1] = calc(format, Y11, ruv, guv, buv); } output.setLineWidth(y + 0, width); output.setLineWidth(y + 1, width); } } void convert(const th_ycbcr_buffer& input, RawFrame& output) { initTables(); const SDL_PixelFormat& format = output.getSDLPixelFormat(); if (format.BytesPerPixel == 4) { #ifdef __SSE2__ convertHelperSSE2(input, output); #else convertHelper(input, output, format); #endif } else { assert(format.BytesPerPixel == 2); convertHelper(input, output, format); } } } // namespace yuv2rgb } // namespace openmsx openMSX-RELEASE_0_12_0/src/laserdisc/yuv2rgb.hh000066400000000000000000000003661257557151200211250ustar00rootroot00000000000000#ifndef YUV2RGB_HH #define YUV2RGB_HH #include namespace openmsx { class RawFrame; namespace yuv2rgb { void convert(const th_ycbcr_buffer& input, RawFrame& output); } // namespace yuv2rgb } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/main.cc000066400000000000000000000117171257557151200164700ustar00rootroot00000000000000/* * openmsx - the MSX emulator that aims for perfection * */ #include "openmsx.hh" #include "Date.hh" #include "Reactor.hh" #include "CommandLineParser.hh" #include "CliServer.hh" #include "Display.hh" #include "EventDistributor.hh" #include "RenderSettings.hh" #include "EnumSetting.hh" #include "MSXException.hh" #include "StringOp.hh" #include "Thread.hh" #include "build-info.hh" #include "random.hh" #include #include #include #include #include #include #ifdef _WIN32 #include "win32-arggen.hh" #endif // Set LOG_TO_FILE to 1 for any platform on which stdout and stderr must // be redirected to a file // Also, specify the appropriate file names, depending on the platform conventions #if PLATFORM_ANDROID #define LOG_TO_FILE 1 #define STDOUT_LOG_FILE_NAME "openmsx_system/openmsx.stdout" #define STDERR_LOG_FILE_NAME "openmsx_system/openmsx.stderr" #else #define LOG_TO_FILE 0 #endif using std::cerr; using std::cout; using std::endl; using std::string; namespace openmsx { static void initializeSDL() { int flags = 0; #ifndef SDL_JOYSTICK_DISABLED flags |= SDL_INIT_JOYSTICK; #endif #ifndef NDEBUG flags |= SDL_INIT_NOPARACHUTE; #endif if (SDL_Init(flags) < 0) { throw FatalError(StringOp::Builder() << "Couldn't init SDL: " << SDL_GetError()); } // In SDL 1.2.9 and before SDL_putenv has different semantics and is not // guaranteed to exist on all platforms. #if SDL_VERSION_ATLEAST(1, 2, 10) // On Mac OS X, send key combos like Cmd+H and Cmd+M to Cocoa, so it can // perform the corresponding actions. #if defined(__APPLE__) SDL_putenv(const_cast("SDL_ENABLEAPPEVENTS=1")); #endif #endif } static int main(int argc, char **argv) { #if LOG_TO_FILE ad_printf("Redirecting stdout to %s and stderr to %s\n", STDOUT_LOG_FILE_NAME, STDERR_LOG_FILE_NAME); if (!freopen(STDOUT_LOG_FILE_NAME, "a", stdout)) { ad_printf("Couldn't redirect stdout to logfile, aborting\n"); cerr << "Couldn't redirect stdout to " STDOUT_LOG_FILE_NAME << endl; exit(1); } if (!freopen(STDERR_LOG_FILE_NAME, "a", stderr)) { ad_printf("Couldn't redirect stderr to logfile, aborting\n"); cout << "Couldn't redirect stderr to " STDERR_LOG_FILE_NAME << endl; exit(1); } string msg = Date::toString(time(nullptr)) + ": starting openMSX"; cout << msg << endl; cerr << msg << endl; #endif int err = 0; try { // Constructing Reactor already causes parts of SDL to be used // and initialized. If we want to set environment variables // before this, we have to do it here... // // This is to make sure we get no annoying behaviour from SDL // with regards to CAPS lock. This only works in SDL 1.2.14 or // later, but it can't hurt to always set it (if we can rely on // SDL_putenv, so on 1.2.10+). // // On Mac OS X, Cocoa does not report CAPS lock release events. // The input driver inside SDL works around that by sending a // pressed;released combo when CAPS status changes. However, // because there is no time inbetween this does not give the // MSX BIOS a chance to see the CAPS key in a pressed state. #if SDL_VERSION_ATLEAST(1, 2, 10) SDL_putenv(const_cast("SDL_DISABLE_LOCK_KEYS=" #if defined(__APPLE__) "0" #else "1" #endif )); #endif randomize(); // seed global random generator initializeSDL(); Thread::setMainThread(); Reactor reactor; #ifdef _WIN32 ArgumentGenerator arggen; argv = arggen.GetArguments(argc); #endif CommandLineParser parser(reactor); parser.parse(argc, argv); CommandLineParser::ParseStatus parseStatus = parser.getParseStatus(); if (parseStatus != CommandLineParser::EXIT) { if (!parser.isHiddenStartup()) { auto& render = reactor.getDisplay().getRenderSettings().getRendererSetting(); render.setValue(render.getRestoreValue()); // Switching renderer requires events, handle // these events before continuing with the rest // of initialization. This fixes a bug where // you have a '-script bla.tcl' command line // argument where bla.tcl contains a line like // 'ext gfx9000'. reactor.getEventDistributor().deliverEvents(); } if (parseStatus != CommandLineParser::TEST) { CliServer cliServer(reactor.getCommandController(), reactor.getEventDistributor(), reactor.getGlobalCliComm()); reactor.run(parser); } } } catch (FatalError& e) { cerr << "Fatal error: " << e.getMessage() << endl; err = 1; } catch (MSXException& e) { cerr << "Uncaught exception: " << e.getMessage() << endl; err = 1; } catch (std::exception& e) { cerr << "Uncaught std::exception: " << e.what() << endl; err = 1; } catch (...) { cerr << "Uncaught exception of unexpected type." << endl; err = 1; } // Clean up. if (SDL_WasInit(SDL_INIT_EVERYTHING)) { SDL_Quit(); } return err; } } // namespace openmsx // Enter the openMSX namespace. int main(int argc, char **argv) { exit(openmsx::main(argc, argv)); // need exit() iso return on win32/SDL } openMSX-RELEASE_0_12_0/src/memory/000077500000000000000000000000001257557151200165365ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/src/memory/.gitignore000066400000000000000000000001761257557151200205320ustar00rootroot00000000000000/*.ROM /*.bb /*.bbg /*.da /*.dsk /*.prof /*.rom /*.rpo /*.swp /*.vim /.debug /.kdbgrc.openmsx /.xvpics /gmon.out /GNUmakefile openMSX-RELEASE_0_12_0/src/memory/AmdFlash.cc000066400000000000000000000205701257557151200205300ustar00rootroot00000000000000#include "AmdFlash.hh" #include "Rom.hh" #include "SRAM.hh" #include "MSXMotherBoard.hh" #include "MSXCPU.hh" #include "MSXDevice.hh" #include "Math.hh" #include "serialize.hh" #include "memory.hh" #include "xrange.hh" #include "countof.hh" #include #include #include using std::vector; namespace openmsx { // writeProtectedFlags: i-th bit=1 -> i-th sector write-protected AmdFlash::AmdFlash(const Rom& rom_, const vector& sectorInfo_, word ID_, bool use12bitAddressing_, const DeviceConfig& config, bool load) : motherBoard(config.getMotherBoard()) , rom(rom_) , sectorInfo(sectorInfo_) , size(std::accumulate(begin(sectorInfo), end(sectorInfo), 0, [](int t, SectorInfo i) { return t + i.size;})) , ID(ID_) , use12bitAddressing(use12bitAddressing_) , state(ST_IDLE) , vppWpPinLow(false) { assert(Math::isPowerOfTwo(getSize())); auto numSectors = sectorInfo.size(); unsigned writableSize = 0; writeAddress.resize(numSectors); for (auto i : xrange(numSectors)) { if (sectorInfo[i].writeProtected) { writeAddress[i] = -1; } else { writeAddress[i] = writableSize; writableSize += sectorInfo[i].size; } } bool loaded = false; if (writableSize) { if (load) { ram = make_unique( rom.getName() + "_flash", "flash rom", writableSize, config, nullptr, &loaded); } else { // Hack for 'Matra INK', flash chip is wired-up so that // writes are never visible to the MSX (but the flash // is not made write-protected). In this case it doesn't // make sense to load/save the SRAM file. ram = make_unique( rom.getName() + "_flash", "flash rom", writableSize, config, SRAM::DONT_LOAD); } } readAddress.resize(numSectors); unsigned romSize = rom.getSize(); unsigned offset = 0; for (auto i : xrange(numSectors)) { unsigned sectorSize = sectorInfo[i].size; if (isSectorWritable(unsigned(i))) { readAddress[i] = &(*ram)[writeAddress[i]]; if (!loaded) { auto ramPtr = const_cast( &(*ram)[writeAddress[i]]); if (offset >= romSize) { // completely past end of rom memset(ramPtr, 0xFF, sectorSize); } else if (offset + sectorSize >= romSize) { // partial overlap unsigned last = romSize - offset; unsigned missing = sectorSize - last; const byte* romPtr = &rom[offset]; memcpy(ramPtr, romPtr, last); memset(ramPtr + last, 0xFF, missing); } else { // completely before end of rom const byte* romPtr = &rom[offset]; memcpy(ramPtr, romPtr, sectorSize); } } } else { if ((offset + sectorSize) <= romSize) { readAddress[i] = &rom[offset]; } else { readAddress[i] = nullptr; } } offset += sectorSize; } assert(offset == getSize()); reset(); } AmdFlash::~AmdFlash() { } void AmdFlash::getSectorInfo(unsigned address, unsigned& sector, unsigned& sectorSize, unsigned& offset) const { address &= getSize() - 1; auto it = begin(sectorInfo); sector = 0; while (address >= it->size) { address -= it->size; ++sector; ++it; assert(it != end(sectorInfo)); } sectorSize = it->size; offset = address; } void AmdFlash::reset() { cmdIdx = 0; setState(ST_IDLE); } void AmdFlash::setState(State newState) { if (state == newState) return; state = newState; motherBoard.getCPU().invalidateMemCache(0x0000, 0x10000); } byte AmdFlash::peek(unsigned address) const { unsigned sector, sectorSize, offset; getSectorInfo(address, sector, sectorSize, offset); if (state == ST_IDLE) { if (const byte* addr = readAddress[sector]) { return addr[offset]; } else { return 0xFF; } } else { if (use12bitAddressing) { // convert the address to the '11 bit case' address >>= 1; } switch (address & 3) { case 0: return ID >> 8; case 1: return ID & 0xFF; case 2: // 1 -> write protected return isSectorWritable(sector) ? 0 : 1; case 3: default: // TODO what is this? According to this it reads as '1' // http://www.msx.org/forumtopicl8329.html return 1; } } } bool AmdFlash::isSectorWritable(unsigned sector) const { return vppWpPinLow && (sector == 0 || sector == 1) ? false : (writeAddress[sector] != -1) ; } byte AmdFlash::read(unsigned address) { // note: after a read we stay in the same mode return peek(address); } const byte* AmdFlash::getReadCacheLine(unsigned address) const { if (state == ST_IDLE) { unsigned sector, sectorSize, offset; getSectorInfo(address, sector, sectorSize, offset); const byte* addr = readAddress[sector]; return addr ? &addr[offset] : MSXDevice::unmappedRead; } else { return nullptr; } } void AmdFlash::write(unsigned address, byte value) { assert(cmdIdx < MAX_CMD_SIZE); cmd[cmdIdx].addr = address; cmd[cmdIdx].value = value; ++cmdIdx; if (checkCommandManifacturer() || checkCommandEraseSector() || checkCommandProgram() || checkCommandQuadrupleByteProgram() || checkCommandEraseChip() || checkCommandReset()) { // do nothing, we're still matching a command, but it is not complete yet } else { reset(); } } // The checkCommandXXX() methods below return // true -> if the command sequence still matches, but is not complete yet // false -> if the command was fully matched or does not match with // the current command sequence. // If there was a full match, the command is also executed. bool AmdFlash::checkCommandReset() { if (cmd[0].value == 0xf0) { reset(); } return false; } bool AmdFlash::checkCommandEraseSector() { static const byte cmdSeq[] = { 0xaa, 0x55, 0x80, 0xaa, 0x55 }; if (partialMatch(5, cmdSeq)) { if (cmdIdx < 6) return true; if (cmd[5].value == 0x30) { unsigned addr = cmd[5].addr; unsigned sector, sectorSize, offset; getSectorInfo(addr, sector, sectorSize, offset); if (isSectorWritable(sector)) { ram->memset(writeAddress[sector], 0xff, sectorSize); } } } return false; } bool AmdFlash::checkCommandEraseChip() { static const byte cmdSeq[] = { 0xaa, 0x55, 0x80, 0xaa, 0x55 }; if (partialMatch(5, cmdSeq)) { if (cmdIdx < 6) return true; if (cmd[5].value == 0x10) { if (ram) ram->memset(0, 0xff, ram->getSize()); } } return false; } bool AmdFlash::checkCommandProgramHelper(unsigned numBytes, const byte* cmdSeq, size_t cmdLen) { if (partialMatch(cmdLen, cmdSeq)) { if (cmdIdx < (cmdLen + numBytes)) return true; for (auto i = cmdLen; i < (cmdLen + numBytes); ++i) { unsigned addr = cmd[i].addr; unsigned sector, sectorSize, offset; getSectorInfo(addr, sector, sectorSize, offset); if (isSectorWritable(sector)) { unsigned ramAddr = writeAddress[sector] + offset; ram->write(ramAddr, (*ram)[ramAddr] & cmd[i].value); } } } return false; } bool AmdFlash::checkCommandProgram() { static const byte cmdSeq[] = { 0xaa, 0x55, 0xa0 }; return checkCommandProgramHelper(1, cmdSeq, countof(cmdSeq)); } bool AmdFlash::checkCommandQuadrupleByteProgram() { static const byte cmdSeq[] = { 0x56 }; return checkCommandProgramHelper(4, cmdSeq, countof(cmdSeq)); } bool AmdFlash::checkCommandManifacturer() { static const byte cmdSeq[] = { 0xaa, 0x55, 0x90 }; if (partialMatch(3, cmdSeq)) { if (cmdIdx == 3) { setState(ST_IDENT); } if (cmdIdx < 4) return true; } return false; } bool AmdFlash::partialMatch(size_t len, const byte* dataSeq) const { static const unsigned addrSeq[] = { 0, 1, 0, 0, 1 }; unsigned cmdAddr[2] = { 0x555, 0x2aa }; assert(len <= 5); unsigned n = std::min(unsigned(len), cmdIdx); for (unsigned i = 0; i < n; ++i) { // convert the address to the '11 bit case' unsigned addr = use12bitAddressing ? cmd[i].addr >> 1 : cmd[i].addr; if (((addr & 0x7FF) != cmdAddr[addrSeq[i]]) || (cmd[i].value != dataSeq[i])) { return false; } } return true; } static enum_string stateInfo[] = { { "IDLE", AmdFlash::ST_IDLE }, { "IDENT", AmdFlash::ST_IDENT } }; SERIALIZE_ENUM(AmdFlash::State, stateInfo); template void AmdFlash::AmdCmd::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("address", addr); ar.serialize("value", value); } template void AmdFlash::serialize(Archive& ar, unsigned version) { ar.serialize("ram", *ram); ar.serialize("cmd", cmd); ar.serialize("cmdIdx", cmdIdx); ar.serialize("state", state); if (ar.versionAtLeast(version, 2)) { ar.serialize("vppWpPinLow", vppWpPinLow); } } INSTANTIATE_SERIALIZE_METHODS(AmdFlash); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/AmdFlash.hh000066400000000000000000000062641257557151200205460ustar00rootroot00000000000000#ifndef AMDFLASH_HH #define AMDFLASH_HH #include "MemBuffer.hh" #include "openmsx.hh" #include "noncopyable.hh" #include "serialize_meta.hh" #include #include namespace openmsx { class MSXMotherBoard; class Rom; class SRAM; class DeviceConfig; class AmdFlash : private noncopyable { public: struct SectorInfo { unsigned size; bool writeProtected; }; /** Create AmdFlash with given configuration. * @param rom The initial content for this flash * @param sectorInfo * A vector containing the size and write protected status of each * sector in the flash. This implicitly also communicates the number * of sectors (a sector is a region in the flash that can be erased * individually). There exist flash roms were the different sectors * are not all equally large, that's why it's required to enumerate * the size of each sector (instead of simply specifying the size and * the number of sectors). * @param ID * Contains manufacturer and device ID for this flash. * @param use12bitAddressing set to true for 12-bit command addresses, false for 11-bit command addresses * @param config The motherboard this flash belongs to * @param load Load initial content (hack for 'Matra INK') */ AmdFlash(const Rom& rom, const std::vector& sectorInfo, word ID, bool use12bitAddressing, const DeviceConfig& config, bool load = true); ~AmdFlash(); void reset(); /** * Setting the Vpp/WP# pin LOW enables a certain kind of write * protection of some sectors. Currently it is implemented that it will * enable protection of the first two sectors. (As for example in * Numonix/Micron M29W640FB/M29W640GB.) */ void setVppWpPinLow(bool value) { vppWpPinLow = value; } unsigned getSize() const { return size; } byte read(unsigned address); byte peek(unsigned address) const; void write(unsigned address, byte value); const byte* getReadCacheLine(unsigned address) const; template void serialize(Archive& ar, unsigned version); //private: struct AmdCmd { unsigned addr; byte value; template void serialize(Archive& ar, unsigned version); }; enum State { ST_IDLE, ST_IDENT }; private: void getSectorInfo(unsigned address, unsigned& sector, unsigned& sectorSize, unsigned& offset) const; void setState(State newState); bool checkCommandReset(); bool checkCommandEraseSector(); bool checkCommandEraseChip(); bool checkCommandProgramHelper(unsigned, const byte*, size_t cmdLen); bool checkCommandProgram(); bool checkCommandQuadrupleByteProgram(); bool checkCommandManifacturer(); bool partialMatch(size_t len, const byte* dataSeq) const; bool isSectorWritable(unsigned sector) const; MSXMotherBoard& motherBoard; const Rom& rom; std::unique_ptr ram; MemBuffer writeAddress; MemBuffer readAddress; const std::vector sectorInfo; const unsigned size; const word ID; const bool use12bitAddressing; static const unsigned MAX_CMD_SIZE = 8; AmdCmd cmd[MAX_CMD_SIZE]; unsigned cmdIdx; State state; bool vppWpPinLow; // true = protection on }; SERIALIZE_CLASS_VERSION(AmdFlash, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/CheckedRam.cc000066400000000000000000000053071257557151200210400ustar00rootroot00000000000000#include "CheckedRam.hh" #include "MSXCPU.hh" #include "MSXMotherBoard.hh" #include "DeviceConfig.hh" #include "GlobalSettings.hh" #include "StringSetting.hh" #include "likely.hh" #include namespace openmsx { static std::bitset getBitSetAllTrue() { std::bitset result; result.set(); return result; } CheckedRam::CheckedRam(const DeviceConfig& config, const std::string& name, const std::string& description, unsigned size) : completely_initialized_cacheline(size / CacheLine::SIZE, false) , uninitialized(size / CacheLine::SIZE, getBitSetAllTrue()) , ram(config, name, description, size) , msxcpu(config.getMotherBoard().getCPU()) , umrCallback(config.getGlobalSettings().getUMRCallBackSetting()) { umrCallback.getSetting().attach(*this); init(); } CheckedRam::~CheckedRam() { umrCallback.getSetting().detach(*this); } byte CheckedRam::read(unsigned addr) { unsigned line = addr >> CacheLine::BITS; if (unlikely(!completely_initialized_cacheline[line])) { if (unlikely(uninitialized[line][addr & CacheLine::LOW])) { umrCallback.execute(addr, ram.getName()); } } return ram[addr]; } const byte* CheckedRam::getReadCacheLine(unsigned addr) const { return (completely_initialized_cacheline[addr >> CacheLine::BITS]) ? &ram[addr] : nullptr; } byte* CheckedRam::getWriteCacheLine(unsigned addr) const { return (completely_initialized_cacheline[addr >> CacheLine::BITS]) ? const_cast(&ram[addr]) : nullptr; } void CheckedRam::write(unsigned addr, const byte value) { unsigned line = addr >> CacheLine::BITS; if (unlikely(!completely_initialized_cacheline[line])) { uninitialized[line][addr & CacheLine::LOW] = false; if (unlikely(uninitialized[line].none())) { completely_initialized_cacheline[line] = true; msxcpu.invalidateMemCache(addr & CacheLine::HIGH, CacheLine::SIZE); } } ram[addr] = value; } void CheckedRam::clear() { ram.clear(); init(); } void CheckedRam::init() { if (umrCallback.getValue().empty()) { // there is no callback function, // do as if everything is initialized completely_initialized_cacheline.assign( completely_initialized_cacheline.size(), true); uninitialized.assign( uninitialized.size(), std::bitset()); } else { // new callback function, forget about initialized areas completely_initialized_cacheline.assign( completely_initialized_cacheline.size(), false); uninitialized.assign( uninitialized.size(), getBitSetAllTrue()); } msxcpu.invalidateMemCache(0, 0x10000); } void CheckedRam::update(const Setting& setting) { assert(&setting == &umrCallback.getSetting()); (void)setting; init(); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/CheckedRam.hh000066400000000000000000000037421257557151200210530ustar00rootroot00000000000000#ifndef CHECKEDRAM_HH #define CHECKEDRAM_HH #include "Ram.hh" #include "TclCallback.hh" #include "CacheLine.hh" #include "Observer.hh" #include "openmsx.hh" #include "noncopyable.hh" #include #include namespace openmsx { class DeviceConfig; class MSXCPU; class Setting; /** * This class keeps track of which bytes in the Ram have been written to. It * can be used for debugging MSX programs, because you can see if you are * trying to read/execute uninitialized memory. Currently all normal RAM * (MSXRam) and all normal memory mappers (MSXMemoryMappers) use CheckedRam. On * the turboR, only the normal memory mapper runs via CheckedRam. The RAM * accessed in DRAM mode or via the ROM mapper are unchecked! Note that there * is basically no overhead for using CheckedRam over Ram, thanks to Wouter. */ class CheckedRam final : private Observer, private noncopyable { public: CheckedRam(const DeviceConfig& config, const std::string& name, const std::string& description, unsigned size); ~CheckedRam(); byte read(unsigned addr); byte peek(unsigned addr) const { return ram[addr]; } void write(unsigned addr, const byte value); const byte* getReadCacheLine(unsigned addr) const; byte* getWriteCacheLine(unsigned addr) const; unsigned getSize() const { return ram.getSize(); } void clear(); /** * Give access to the unchecked Ram. No problem to use it, but there * will just be no checking done! Keep in mind that you should use this * consistently, so that the initialized-administration will be always * up to date! */ Ram& getUncheckedRam() { return ram; } // TODO //template //void serialize(Archive& ar, unsigned version); private: void init(); // Observer void update(const Setting& setting) override; std::vector completely_initialized_cacheline; std::vector> uninitialized; Ram ram; MSXCPU& msxcpu; TclCallback umrCallback; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/ESE_RAM.cc000066400000000000000000000065301257557151200201640ustar00rootroot00000000000000/* * MEGA-SCSI and ESE-RAM cartridge: * The mapping does SRAM and MB89352A(MEGA-SCSI) to ASCII8 or * an interchangeable bank controller. * The rest of this documentation is only about ESE-RAM specifically. * * Specification: * SRAM(MegaROM) controller: ASCII8 type * SRAM capacity : 128, 256, 512 and 1024KB * * Bank changing address: * bank 4(0x4000-0x5fff): 0x6000 - 0x67FF (0x6000 used) * bank 6(0x6000-0x7fff): 0x6800 - 0x6FFF (0x6800 used) * bank 8(0x8000-0x9fff): 0x7000 - 0x77FF (0x7000 used) * bank A(0xa000-0xbfff): 0x7800 - 0x7FFF (0x7800 used) * * ESE-RAM Bank Map: * BANK 00H-7FH (read only) * BANK 80H-FFH (write and read. mirror of 00H-7FH) */ #include "ESE_RAM.hh" #include "StringOp.hh" #include "MSXException.hh" #include "serialize.hh" #include "memory.hh" #include namespace openmsx { unsigned ESE_RAM::getSramSize() const { unsigned sramSize = getDeviceConfig().getChildDataAsInt("sramsize", 256); // size in kb if (sramSize != 1024 && sramSize != 512 && sramSize != 256 && sramSize != 128) { throw MSXException(StringOp::Builder() << "SRAM size for " << getName() << " should be 128, 256, 512 or 1024kB and not " << sramSize << "kB!"); } return sramSize * 1024; // in bytes } ESE_RAM::ESE_RAM(const DeviceConfig& config) : MSXDevice(config) , sram(getName() + " SRAM", getSramSize(), config) , romBlockDebug(*this, mapped, 0x4000, 0x8000, 13) , blockMask((sram.getSize() / 8192) - 1) { reset(EmuTime::dummy()); } void ESE_RAM::reset(EmuTime::param /*time*/) { for (int i = 0; i < 4; ++i) { setSRAM(i, 0); } } byte ESE_RAM::readMem(word address, EmuTime::param /*time*/) { byte result; if ((0x4000 <= address) && (address < 0xC000)) { unsigned page = (address / 8192) - 2; word addr = address & 0x1FFF; result = sram[8192 * mapped[page] + addr]; } else { result = 0xFF; } return result; } const byte* ESE_RAM::getReadCacheLine(word address) const { if ((0x4000 <= address) && (address < 0xC000)) { unsigned page = (address / 8192) - 2; address &= 0x1FFF; return &sram[8192 * mapped[page] + address]; } else { return unmappedRead; } } void ESE_RAM::writeMem(word address, byte value, EmuTime::param /*time*/) { if ((0x6000 <= address) && (address < 0x8000)) { byte region = ((address >> 11) & 3); setSRAM(region, value); } else if ((0x4000 <= address) && (address < 0xC000)) { unsigned page = (address / 8192) - 2; address &= 0x1FFF; if (isWriteable[page]) { sram.write(8192 * mapped[page] + address, value); } } } byte* ESE_RAM::getWriteCacheLine(word address) const { if ((0x6000 <= address) && (address < 0x8000)) { return nullptr; } else if ((0x4000 <= address) && (address < 0xC000)) { unsigned page = (address / 8192) - 2; if (isWriteable[page]) { return nullptr; } } return unmappedWrite; } void ESE_RAM::setSRAM(unsigned region, byte block) { invalidateMemCache(region * 0x2000 + 0x4000, 0x2000); assert(region < 4); isWriteable[region] = (block & 0x80) != 0; mapped[region] = block & blockMask; } template void ESE_RAM::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("SRAM", sram); ar.serialize("isWriteable", isWriteable); ar.serialize("mapped", mapped); } INSTANTIATE_SERIALIZE_METHODS(ESE_RAM); REGISTER_MSXDEVICE(ESE_RAM, "ESE_RAM"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/ESE_RAM.hh000066400000000000000000000015711257557151200201760ustar00rootroot00000000000000#ifndef ESE_RAM_HH #define ESE_RAM_HH #include "MSXDevice.hh" #include "SRAM.hh" #include "RomBlockDebuggable.hh" namespace openmsx { class ESE_RAM final : public MSXDevice { public: ESE_RAM(const DeviceConfig& config); void reset(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte* getWriteCacheLine(word start) const override; template void serialize(Archive& ar, unsigned version); private: unsigned getSramSize() const; void setSRAM(unsigned region, byte block); SRAM sram; RomBlockDebuggable romBlockDebug; bool isWriteable[4]; // which region is readonly? byte mapped[4]; // which block is mapped in this region? const byte blockMask; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/ESE_SCC.cc000066400000000000000000000146731257557151200201640ustar00rootroot00000000000000/* * 'Ese-SCC' cartride and 'MEGA-SCSI with SCC'(alias WAVE-SCSI) cartrige. * * Specification: * SRAM(MegaROM) controller: KONAMI_SCC type * SRAM capacity : 128, 256, 512kB and 1024kb(WAVE-SCSI only) * SCSI Protocol controller: Fujitsu MB89352A * * ESE-SCC sram write control register: * 7FFEH, 7FFFH SRAM write control. * bit4 = 1..SRAM writing in 4000H-7FFDH can be done. * It is given priority more than bank changing. * = 0..SRAM read only * othet bit = not used * * WAVE-SCSI bank control register * 6bit register (MA13-MA18, B0-B5) * 5000-57FF: 4000-5FFF change * 7000-77FF: 6000-7FFF change * 9000-97FF: 8000-9FFF change * B000-B7FF: A000-BFFF change * * 7FFE,7FFF: 2bit register * bit4: bank control MA20 (B7) * bit6: bank control MA19 (B6) * other bit: not used * * WAVE-SCSI bank map: * 00-3F: SRAM read only. mirror of 80-BF. * 3F: SCC * 40-7F: SPC (MB89352A) * 80-FF: SRAM read and write * * SPC BANK address map: * 4000-4FFF spc data register (mirror of 5FFA) * 5000-5FEF undefined specification * 5FF0-5FFE spc register * 5FFF unmapped * * WAVE-SCSI bank access map (X = accessible) * 00-3F 80-BF C0-FF SPC SCC * 4000-5FFF X X X X . * 6000-7FFF X X . . . * 8000-9FFF X . . . X * A000-BFFF X . . . . */ #include "ESE_SCC.hh" #include "MB89352.hh" #include "StringOp.hh" #include "MSXException.hh" #include "serialize.hh" #include "memory.hh" namespace openmsx { unsigned ESE_SCC::getSramSize(bool withSCSI) const { unsigned sramSize = getDeviceConfig().getChildDataAsInt("sramsize", 256); // size in kb if (sramSize != 1024 && sramSize != 512 && sramSize != 256 && sramSize != 128) { throw MSXException(StringOp::Builder() << "SRAM size for " << getName() << " should be 128, 256, 512 or 1024kB and not " << sramSize << "kB!"); } if (!withSCSI && sramSize == 1024) { throw MSXException("1024kB SRAM is only allowed in WAVE-SCSI!"); } return sramSize * 1024; // in bytes } ESE_SCC::ESE_SCC(const DeviceConfig& config, bool withSCSI) : MSXDevice(config) , sram(getName() + " SRAM", getSramSize(withSCSI), config) , scc(getName(), config, getCurrentTime()) , spc(withSCSI ? make_unique(config) : nullptr) , romBlockDebug(*this, mapper, 0x4000, 0x8000, 13) , mapperMask((sram.getSize() / 0x2000) - 1) { // initialized mapper sccEnable = false; spcEnable = false; writeEnable = false; for (int i = 0; i < 4; ++i) { mapper[i] = i; } } void ESE_SCC::powerUp(EmuTime::param time) { scc.powerUp(time); reset(time); } void ESE_SCC::reset(EmuTime::param time) { setMapperHigh(0); for (int i = 0; i < 4; ++i) { setMapperLow(i, i); } scc.reset(time); if (spc) spc->reset(true); } void ESE_SCC::setMapperLow(unsigned page, byte value) { value &= 0x3f; // use only 6bit bool flush = false; if (page == 2) { bool newSccEnable = (value == 0x3f); if (newSccEnable != sccEnable) { sccEnable = newSccEnable; flush = true; } } byte newValue = value; if (page == 0) newValue |= mapper[0] & 0x40; newValue &= mapperMask; if (mapper[page] != newValue) { mapper[page] = newValue; flush = true; } if (flush) { invalidateMemCache(0x4000 + 0x2000 * page, 0x2000); } } void ESE_SCC::setMapperHigh(byte value) { writeEnable = (value & 0x10) != 0; // no need to flush cache if (!spc) return; // only WAVE-SCSI supports 1024kB bool flush = false; byte mapperHigh = value & 0x40; bool newSpcEnable = mapperHigh && !writeEnable; if (spcEnable != newSpcEnable) { spcEnable = newSpcEnable; flush = true; } byte newValue = ((mapper[0] & 0x3F) | mapperHigh) & mapperMask; if (mapper[0] != newValue) { mapper[0] = newValue; flush = true; } if (flush) { invalidateMemCache(0x4000, 0x2000); } } byte ESE_SCC::readMem(word address, EmuTime::param time) { unsigned page = address / 0x2000 - 2; // SPC if (spcEnable && (page == 0)) { address &= 0x1fff; if (address < 0x1000) { return spc->readDREG(); } else { return spc->readRegister(address & 0x0f); } } // SCC bank if (sccEnable && (address >= 0x9800) && (address < 0xa000)) { return scc.readMem(address & 0xff, time); } // SRAM read return sram[mapper[page] * 0x2000 + (address & 0x1fff)]; } byte ESE_SCC::peekMem(word address, EmuTime::param time) const { unsigned page = address / 0x2000 - 2; // SPC if (spcEnable && (page == 0)) { address &= 0x1fff; if (address < 0x1000) { return spc->peekDREG(); } else { return spc->peekRegister(address & 0x0f); } } // SCC bank if (sccEnable && (address >= 0x9800) && (address < 0xa000)) { return scc.peekMem(address & 0xff, time); } // SRAM read return sram[mapper[page] * 0x2000 + (address & 0x1fff)]; } const byte* ESE_SCC::getReadCacheLine(word address) const { unsigned page = address / 0x2000 - 2; // SPC if (spcEnable && (page == 0)) { return nullptr; } // SCC bank if (sccEnable && (address >= 0x9800) && (address < 0xa000)) { return nullptr; } // SRAM read return &sram[mapper[page] * 0x2000 + (address & 0x1fff)]; } void ESE_SCC::writeMem(word address, byte value, EmuTime::param time) { unsigned page = address / 0x2000 - 2; // SPC Write if (spcEnable && (page == 0)) { address &= 0x1fff; if (address < 0x1000) { spc->writeDREG(value); } else { spc->writeRegister(address & 0x0f, value); } return; } // SCC write if (sccEnable && (0x9800 <= address) && (address < 0xa000)) { scc.writeMem(address & 0xff, value, time); return; } // set mapper high control register if ((address | 0x0001) == 0x7FFF) { setMapperHigh(value); return; } // SRAM write (processing of 4000-7FFDH) if (writeEnable && (page < 2)) { sram.write(mapper[page] * 0x2000 + (address & 0x1FFF), value); return; } // Bank change if (((address & 0x1800) == 0x1000)) { setMapperLow(page, value); return; } } byte* ESE_SCC::getWriteCacheLine(word /*address*/) const { return nullptr; // not cacheable } template void ESE_SCC::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("sram", sram); ar.serialize("scc", scc); if (spc) ar.serialize("MB89352", *spc); ar.serialize("mapper", mapper); ar.serialize("spcEnable", spcEnable); ar.serialize("sccEnable", sccEnable); ar.serialize("writeEnable", writeEnable); } INSTANTIATE_SERIALIZE_METHODS(ESE_SCC); REGISTER_MSXDEVICE(ESE_SCC, "ESE_SCC"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/ESE_SCC.hh000066400000000000000000000021441257557151200201640ustar00rootroot00000000000000#ifndef ESE_SCC_HH #define ESE_SCC_HH #include "MSXDevice.hh" #include "SRAM.hh" #include "SCC.hh" #include "RomBlockDebuggable.hh" namespace openmsx { class MB89352; class ESE_SCC final : public MSXDevice { public: ESE_SCC(const DeviceConfig& config, bool withSCSI); void powerUp(EmuTime::param time) override; void reset(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte* getWriteCacheLine(word start) const override; template void serialize(Archive& ar, unsigned version); private: unsigned getSramSize(bool withSCSI) const; void setMapperLow(unsigned page, byte value); void setMapperHigh(byte value); SRAM sram; SCC scc; const std::unique_ptr spc; // can be nullptr RomBlockDebuggable romBlockDebug; const byte mapperMask; byte mapper[4]; bool spcEnable; bool sccEnable; bool writeEnable; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/MSXHBI55.cc000066400000000000000000000116421257557151200202150ustar00rootroot00000000000000/* Submitted By: Albert Beevendorp (bifimsx) * * Some Sony MSX machines have built-in firmware * containing an address 'book', memo and scheduler and a * few of these can store these data on a data cartridge. * * In Basic it's possible to use it with the "CAT:" device. * * I'm not sure if there are more types, though the HBI-55 * is a 4KB SRAM containing cartridge accessed by I/O * ports connected to a 8255 chip: * * B0 = LSB address * B1 = MSB address (D7-D6 = 01 write, 11 read) * B2 = Data port * B3 = Access control * * Sample basic program: * * 10 FOR I=0 TO 4095: OUT &HB3,128: OUT &HB0,I MOD 256: * OUT &HB1,64 OR I\256: OUT &HB2,I MOD 256: NEXT I * 20 FOR I=0 TO 4095:OUT &HB3,128-9*(I<>0): OUT &HB0,I MOD 256: * OUT &HB1,192 OR I\256: IF I MOD 256=INP(&HB2) THEN NEXT * ELSE PRINT "Error comparing byte:";I: END * 30 PRINT "Done!" * * ----- * * Improved code mostly copied from blueMSX, many thanks to Daniel Vik. * http://cvs.sourceforge.net/viewcvs.py/bluemsx/blueMSX/Src/Memory/romMapperSonyHBI55.c */ #include "MSXHBI55.hh" #include "unreachable.hh" #include "serialize.hh" namespace openmsx { // MSXDevice MSXHBI55::MSXHBI55(const DeviceConfig& config) : MSXDevice(config) , i8255(*this, getCurrentTime(), getCliComm()) , sram(getName() + " SRAM", 0x1000, config) { reset(getCurrentTime()); } void MSXHBI55::reset(EmuTime::param time) { readAddress = 0; writeAddress = 0; addressLatch = 0; writeLatch = 0; mode = 0; i8255.reset(time); } byte MSXHBI55::readIO(word port, EmuTime::param time) { byte result; switch (port & 0x03) { case 0: result = i8255.readPortA(time); break; case 1: result = i8255.readPortB(time); break; case 2: result = i8255.readPortC(time); break; case 3: result = i8255.readControlPort(time); break; default: // unreachable, avoid warning UNREACHABLE; result = 0; } return result; } byte MSXHBI55::peekIO(word port, EmuTime::param time) const { byte result; switch (port & 0x03) { case 0: result = i8255.peekPortA(time); break; case 1: result = i8255.peekPortB(time); break; case 2: result = i8255.peekPortC(time); break; case 3: result = i8255.readControlPort(time); break; default: // unreachable, avoid warning UNREACHABLE; result = 0; } return result; } void MSXHBI55::writeIO(word port, byte value, EmuTime::param time) { switch (port & 0x03) { case 0: i8255.writePortA(value, time); break; case 1: i8255.writePortB(value, time); break; case 2: i8255.writePortC(value, time); break; case 3: i8255.writeControlPort(value, time); break; default: UNREACHABLE; } } // I8255Interface byte MSXHBI55::readA(EmuTime::param time) { return peekA(time); } byte MSXHBI55::peekA(EmuTime::param /*time*/) const { // TODO check this return 255; } byte MSXHBI55::readB(EmuTime::param time) { return peekB(time); } byte MSXHBI55::peekB(EmuTime::param /*time*/) const { // TODO check this return 255; } void MSXHBI55::writeA(byte value, EmuTime::param /*time*/) { addressLatch = value; } void MSXHBI55::writeB(byte value, EmuTime::param /*time*/) { word address = addressLatch | ((value & 0x0F) << 8); mode = value >> 6; switch (mode) { case 0: readAddress = 0; writeAddress = 0; break; case 1: writeAddress = address; break; case 2: sram.write(writeAddress, writeLatch); break; case 3: readAddress = address; break; } } nibble MSXHBI55::readC0(EmuTime::param time) { return peekC0(time); } nibble MSXHBI55::peekC0(EmuTime::param /*time*/) const { return readSRAM(readAddress) & 0x0F; } nibble MSXHBI55::readC1(EmuTime::param time) { return peekC1(time); } nibble MSXHBI55::peekC1(EmuTime::param /*time*/) const { return readSRAM(readAddress) >> 4; } // When only one of the nibbles is written (actually when one is set as // input the other as output), the other nibble gets the value of the // last time that nibble was written. Thanks to Laurens Holst for // investigating this. See this bug report for more details: // https://sourceforge.net/p/openmsx/bugs/536/ void MSXHBI55::writeC0(nibble value, EmuTime::param /*time*/) { writeLatch = (writeLatch & 0xF0) | value; if (mode == 1) { sram.write(writeAddress, writeLatch); } } void MSXHBI55::writeC1(nibble value, EmuTime::param /*time*/) { writeLatch = (writeLatch & 0x0F) | (value << 4); if (mode == 1) { sram.write(writeAddress, writeLatch); } } byte MSXHBI55::readSRAM(word address) const { return address ? sram[address] : 0x53; } template void MSXHBI55::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("i8255", i8255); ar.serialize("SRAM", sram); ar.serialize("readAddress", readAddress); ar.serialize("writeAddress", writeAddress); ar.serialize("addressLatch", addressLatch); ar.serialize("writeLatch", writeLatch); ar.serialize("mode", mode); } INSTANTIATE_SERIALIZE_METHODS(MSXHBI55); REGISTER_MSXDEVICE(MSXHBI55, "MSXHBI55"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/MSXHBI55.hh000066400000000000000000000026001257557151200202210ustar00rootroot00000000000000#ifndef MSXHBI55_HH #define MSXHBI55_HH #include "MSXDevice.hh" #include "I8255Interface.hh" #include "I8255.hh" #include "SRAM.hh" namespace openmsx { class MSXHBI55 final : public MSXDevice, public I8255Interface { public: explicit MSXHBI55(const DeviceConfig& config); void reset(EmuTime::param time) override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: // I8255Interface byte readA(EmuTime::param time) override; byte readB(EmuTime::param time) override; nibble readC0(EmuTime::param time) override; nibble readC1(EmuTime::param time) override; byte peekA(EmuTime::param time) const override; byte peekB(EmuTime::param time) const override; nibble peekC0(EmuTime::param time) const override; nibble peekC1(EmuTime::param time) const override; void writeA(byte value, EmuTime::param time) override; void writeB(byte value, EmuTime::param time) override; void writeC0(nibble value, EmuTime::param time) override; void writeC1(nibble value, EmuTime::param time) override; byte readSRAM(word address) const; I8255 i8255; SRAM sram; word readAddress; word writeAddress; byte addressLatch; byte writeLatch; byte mode; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/MSXMapperIO.cc000066400000000000000000000054331257557151200211160ustar00rootroot00000000000000#include "MSXMapperIO.hh" #include "MSXMotherBoard.hh" #include "HardwareConfig.hh" #include "XMLElement.hh" #include "MSXException.hh" #include "Math.hh" #include "outer.hh" #include "serialize.hh" #include "stl.hh" namespace openmsx { static byte calcEngineMask(MSXMotherBoard& motherBoard) { string_ref type = motherBoard.getMachineConfig()->getConfig().getChildData( "MapperReadBackBits", "largest"); if (type == "5") { return 0xE0; // upper 3 bits always read "1" } else if (type == "largest") { return 0x00; // all bits can be read } else { throw FatalError("Unknown mapper type: \"" + type + "\"."); } } MSXMapperIO::MSXMapperIO(const DeviceConfig& config) : MSXDevice(config) , debuggable(getMotherBoard(), getName()) , engineMask(calcEngineMask(getMotherBoard())) { updateMask(); reset(EmuTime::dummy()); } void MSXMapperIO::updateMask() { // unused bits always read "1" unsigned largest = (mapperSizes.empty()) ? 1 : mapperSizes.back(); mask = ((256 - Math::powerOfTwo(largest)) & 255) | engineMask; } void MSXMapperIO::registerMapper(unsigned blocks) { auto it = upper_bound(begin(mapperSizes), end(mapperSizes), blocks); mapperSizes.insert(it, blocks); updateMask(); } void MSXMapperIO::unregisterMapper(unsigned blocks) { mapperSizes.erase(find_unguarded(mapperSizes, blocks)); updateMask(); } void MSXMapperIO::reset(EmuTime::param /*time*/) { // TODO in what state is mapper after reset? // Zeroed is most likely. // To find out for real, insert an external memory mapper on an MSX1. for (auto& reg : registers) { reg = 0; } } byte MSXMapperIO::readIO(word port, EmuTime::param time) { return peekIO(port, time); } byte MSXMapperIO::peekIO(word port, EmuTime::param /*time*/) const { return getSelectedPage(port & 0x03) | mask; } void MSXMapperIO::writeIO(word port, byte value, EmuTime::param /*time*/) { write(port & 0x03, value); } void MSXMapperIO::write(unsigned address, byte value) { registers[address] = value; invalidateMemCache(0x4000 * address, 0x4000); } // SimpleDebuggable MSXMapperIO::Debuggable::Debuggable(MSXMotherBoard& motherBoard, const std::string& name) : SimpleDebuggable(motherBoard, name, "Memory mapper registers", 4) { } byte MSXMapperIO::Debuggable::read(unsigned address) { auto& mapperIO = OUTER(MSXMapperIO, debuggable); return mapperIO.getSelectedPage(address); } void MSXMapperIO::Debuggable::write(unsigned address, byte value) { auto& mapperIO = OUTER(MSXMapperIO, debuggable); mapperIO.write(address, value); } template void MSXMapperIO::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("registers", registers); // all other state is reconstructed in another way } INSTANTIATE_SERIALIZE_METHODS(MSXMapperIO); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/MSXMapperIO.hh000066400000000000000000000032411257557151200211230ustar00rootroot00000000000000#ifndef MSXMAPPERIO_HH #define MSXMAPPERIO_HH #include "MSXDevice.hh" #include "SimpleDebuggable.hh" #include namespace openmsx { class MSXMapperIO final : public MSXDevice { public: explicit MSXMapperIO(const DeviceConfig& config); void reset(EmuTime::param time) override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; /** * Every MSXMemoryMapper must (un)register its size. * This is used to influence the result returned in readIO(). */ void registerMapper(unsigned blocks); void unregisterMapper(unsigned blocks); /** * Returns the actual selected page for the given bank. */ byte getSelectedPage(byte bank) const { return registers[bank]; } template void serialize(Archive& ar, unsigned version); private: void write(unsigned address, byte value); /** * Updates the "mask" field after a mapper was registered or unregistered. */ void updateMask(); struct Debuggable final : SimpleDebuggable { Debuggable(MSXMotherBoard& motherBoard, const std::string& name); byte read(unsigned address) override; void write(unsigned address, byte value) override; } debuggable; std::vector mapperSizes; byte registers[4]; /** * The limit on which bits can be read back as imposed by the engine. * The S1990 engine of the MSX turbo R has such a limit, but other engines * do not. */ byte engineMask; /** * Effective read mask: a combination of engineMask and the limit imposed * by the sizes of the inserted mappers. */ byte mask; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/MSXMegaRam.cc000066400000000000000000000102171257557151200207470ustar00rootroot00000000000000/* * Adriano Camargo Rodrigues da Cunha wrote: * * Any address inside a 8k page can change the page. In other * words: * * for 4000h-5FFFh, mapping addresses are 4000h-5FFFh * for 6000h-7FFFh, mapping addresses are 6000h-7FFFh * for 8000h-9FFFh, mapping addresses are 8000h-9FFFh * for A000h-BFFFh, mapping addresses are A000h-BFFFh * * If you do an IN A,(8Eh) (the value of A register is unknown and * never used) you can write on MegaRAM pages, but you can't map * pages. If you do an OUT (8Eh),A (the value of A register doesn't * matter) you can't write to MegaRAM pages, but you can map them. * * Another thing: the MegaRAMs of Ademir Carchano have a mirror * effect: if you map the page 0 of MegaRAM slot, you'll be * acessing the same area of 8000h-BFFFh of this slot; if you map * the page 3 of MegaRAM slot, you'll be acessing the same area of * 4000h-7FFFh of this slot. I don't know any software that makes * use of this feature, except UZIX for MSX1. */ #include "MSXMegaRam.hh" #include "Rom.hh" #include "Math.hh" #include "serialize.hh" #include "memory.hh" namespace openmsx { MSXMegaRam::MSXMegaRam(const DeviceConfig& config) : MSXDevice(config) , numBlocks(config.getChildDataAsInt("size") / 8) // 8kB blocks , ram(config, getName() + " RAM", "Mega-RAM", numBlocks * 0x2000) , rom(config.findChild("rom") ? make_unique(getName() + " ROM", "Mega-RAM DiskROM", config) : nullptr) , romBlockDebug(*this, bank, 0x0000, 0x10000, 13, 0, 3) , maskBlocks(Math::powerOfTwo(numBlocks) - 1) { powerUp(EmuTime::dummy()); } MSXMegaRam::~MSXMegaRam() { } void MSXMegaRam::powerUp(EmuTime::param time) { for (unsigned i = 0; i < 4; i++) { setBank(i, 0); } writeMode = false; ram.clear(); reset(time); } void MSXMegaRam::reset(EmuTime::param /*time*/) { // selected banks nor writeMode does change after reset romMode = rom != nullptr; // select rom mode if there is a rom } byte MSXMegaRam::readMem(word address, EmuTime::param /*time*/) { return *MSXMegaRam::getReadCacheLine(address); } const byte* MSXMegaRam::getReadCacheLine(word address) const { if (romMode) { if (address >= 0x4000 && address <= 0xBFFF) { return &(*rom)[address - 0x4000]; } return unmappedRead; } unsigned block = bank[(address & 0x7FFF) / 0x2000]; return (block < numBlocks) ? &ram[(block * 0x2000) + (address & 0x1FFF)] : unmappedRead; } void MSXMegaRam::writeMem(word address, byte value, EmuTime::param /*time*/) { if (byte* tmp = getWriteCacheLine(address)) { *tmp = value; } else { assert(!romMode && !writeMode); setBank((address & 0x7FFF) / 0x2000, value); } } byte* MSXMegaRam::getWriteCacheLine(word address) const { if (romMode) return unmappedWrite; if (writeMode) { unsigned block = bank[(address & 0x7FFF) / 0x2000]; return (block < numBlocks) ? const_cast(&ram[(block * 0x2000) + (address & 0x1FFF)]) : unmappedWrite; } else { return nullptr; } } byte MSXMegaRam::readIO(word port, EmuTime::param /*time*/) { switch (port & 1) { case 0: // enable writing writeMode = true; romMode = false; break; case 1: if (rom) romMode = true; break; } invalidateMemCache(0x0000, 0x10000); return 0xFF; // return value doesn't matter } byte MSXMegaRam::peekIO(word /*port*/, EmuTime::param /*time*/) const { return 0xFF; } void MSXMegaRam::writeIO(word port, byte /*value*/, EmuTime::param /*time*/) { switch (port & 1) { case 0: // enable switching writeMode = false; romMode = false; break; case 1: if (rom) romMode = true; break; } invalidateMemCache(0x0000, 0x10000); } void MSXMegaRam::setBank(byte page, byte block) { bank[page] = block & maskBlocks; word adr = page * 0x2000; invalidateMemCache(adr + 0x0000, 0x2000); invalidateMemCache(adr + 0x8000, 0x2000); } template void MSXMegaRam::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("ram", ram); ar.serialize("bank", bank); ar.serialize("writeMode", writeMode); ar.serialize("romMode", romMode); } INSTANTIATE_SERIALIZE_METHODS(MSXMegaRam); REGISTER_MSXDEVICE(MSXMegaRam, "MegaRAM"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/MSXMegaRam.hh000066400000000000000000000022641257557151200207640ustar00rootroot00000000000000#ifndef MSXMEGARAM_HH #define MSXMEGARAM_HH #include "MSXDevice.hh" #include "Ram.hh" #include "RomBlockDebuggable.hh" #include namespace openmsx { class Rom; class MSXMegaRam final : public MSXDevice { public: explicit MSXMegaRam(const DeviceConfig& config); ~MSXMegaRam(); void powerUp(EmuTime::param time) override; void reset(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; const byte* getReadCacheLine(word address) const override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: void setBank(byte page, byte block); const unsigned numBlocks; // must come before ram Ram ram; const std::unique_ptr rom; // can be nullptr RomBlockDebuggable romBlockDebug; const byte maskBlocks; byte bank[4]; bool writeMode; bool romMode; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/MSXMemoryMapper.cc000066400000000000000000000046061257557151200220600ustar00rootroot00000000000000#include "MSXMemoryMapper.hh" #include "MSXMapperIO.hh" #include "MSXMotherBoard.hh" #include "StringOp.hh" #include "MSXException.hh" #include "serialize.hh" #include "memory.hh" #include "Ram.hh" // because we serialize Ram instead of CheckedRam namespace openmsx { unsigned MSXMemoryMapper::getRamSize() const { int kSize = getDeviceConfig().getChildDataAsInt("size"); if ((kSize % 16) != 0) { throw MSXException(StringOp::Builder() << "Mapper size is not a multiple of 16K: " << kSize); } return kSize * 1024; // in bytes } MSXMemoryMapper::MSXMemoryMapper(const DeviceConfig& config) : MSXDevice(config) , checkedRam(config, getName(), "memory mapper", getRamSize()) , mapperIO(*getMotherBoard().createMapperIO()) { unsigned nbBlocks = checkedRam.getSize() / 0x4000; mapperIO.registerMapper(nbBlocks); } MSXMemoryMapper::~MSXMemoryMapper() { unsigned nbBlocks = checkedRam.getSize() / 0x4000; mapperIO.unregisterMapper(nbBlocks); getMotherBoard().destroyMapperIO(); } void MSXMemoryMapper::powerUp(EmuTime::param time) { checkedRam.clear(); reset(time); } void MSXMemoryMapper::reset(EmuTime::param time) { mapperIO.reset(time); } unsigned MSXMemoryMapper::calcAddress(word address) const { unsigned page = mapperIO.getSelectedPage(address >> 14); unsigned nbBlocks = checkedRam.getSize() / 0x4000; page = (page < nbBlocks) ? page : page & (nbBlocks - 1); return (page << 14) | (address & 0x3FFF); } byte MSXMemoryMapper::peekMem(word address, EmuTime::param /*time*/) const { return checkedRam.peek(calcAddress(address)); } byte MSXMemoryMapper::readMem(word address, EmuTime::param /*time*/) { return checkedRam.read(calcAddress(address)); } void MSXMemoryMapper::writeMem(word address, byte value, EmuTime::param /*time*/) { checkedRam.write(calcAddress(address), value); } const byte* MSXMemoryMapper::getReadCacheLine(word start) const { return checkedRam.getReadCacheLine(calcAddress(start)); } byte* MSXMemoryMapper::getWriteCacheLine(word start) const { return checkedRam.getWriteCacheLine(calcAddress(start)); } template void MSXMemoryMapper::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); // TODO ar.serialize("checkedRam", checkedRam); ar.serialize("ram", checkedRam.getUncheckedRam()); } INSTANTIATE_SERIALIZE_METHODS(MSXMemoryMapper); REGISTER_MSXDEVICE(MSXMemoryMapper, "MemoryMapper"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/MSXMemoryMapper.hh000066400000000000000000000021601257557151200220630ustar00rootroot00000000000000#ifndef MSXMEMORYMAPPER_HH #define MSXMEMORYMAPPER_HH #include "MSXDevice.hh" #include "CheckedRam.hh" namespace openmsx { class MSXMapperIO; class MSXMemoryMapper : public MSXDevice { public: explicit MSXMemoryMapper(const DeviceConfig& config); virtual ~MSXMemoryMapper(); void reset(EmuTime::param time) override; void powerUp(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte* getWriteCacheLine(word start) const override; byte peekMem(word address, EmuTime::param time) const override; template void serialize(Archive& ar, unsigned version); protected: /** Converts a Z80 address to a RAM address. * @param address Index in Z80 address space. * @return Index in RAM address space. */ unsigned calcAddress(word address) const; CheckedRam checkedRam; private: unsigned getRamSize() const; MSXMapperIO& mapperIO; }; REGISTER_BASE_NAME_HELPER(MSXMemoryMapper, "MemoryMapper"); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/MSXMirrorDevice.cc000066400000000000000000000026771257557151200220430ustar00rootroot00000000000000#include "MSXMirrorDevice.hh" #include "MSXCPUInterface.hh" #include "MSXException.hh" #include "serialize.hh" namespace openmsx { static unsigned getAddressHigh(const DeviceConfig& config) { unsigned prim = config.getChildDataAsInt("ps"); unsigned sec = config.getChildDataAsInt("ss", 0); if ((prim >= 4) || (sec >= 4)) { throw MSXException("Invalid slot in mirror device."); } return (prim << 18) | (sec << 16); } MSXMirrorDevice::MSXMirrorDevice(const DeviceConfig& config) : MSXDevice(config) , interface(getCPUInterface()) // frequently used, so cache , addressHigh(getAddressHigh(config)) { } byte MSXMirrorDevice::peekMem(word address, EmuTime::param time) const { return interface.peekSlottedMem(addressHigh | address, time); } byte MSXMirrorDevice::readMem(word address, EmuTime::param time) { return interface.readSlottedMem(addressHigh | address, time); } void MSXMirrorDevice::writeMem(word address, byte value, EmuTime::param time) { interface.writeSlottedMem(addressHigh | address, value, time); } const byte* MSXMirrorDevice::getReadCacheLine(word /*start*/) const { return nullptr; } byte* MSXMirrorDevice::getWriteCacheLine(word /*start*/) const { return nullptr; } template void MSXMirrorDevice::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); } INSTANTIATE_SERIALIZE_METHODS(MSXMirrorDevice); REGISTER_MSXDEVICE(MSXMirrorDevice, "Mirror"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/MSXMirrorDevice.hh000066400000000000000000000013251257557151200220420ustar00rootroot00000000000000#ifndef MSXMIRRORDEVICE_HH #define MSXMIRRORDEVICE_HH #include "MSXDevice.hh" namespace openmsx { class MSXCPUInterface; class MSXMirrorDevice final : public MSXDevice { public: explicit MSXMirrorDevice(const DeviceConfig& config); byte readMem(word address, EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte* getWriteCacheLine(word start) const override; byte peekMem(word address, EmuTime::param time) const override; template void serialize(Archive& ar, unsigned version); private: MSXCPUInterface& interface; const unsigned addressHigh; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/MSXPac.cc000066400000000000000000000042761257557151200201510ustar00rootroot00000000000000#include "MSXPac.hh" #include "CacheLine.hh" #include "serialize.hh" namespace openmsx { static const char* const PAC_Header = "PAC2 BACKUP DATA"; MSXPac::MSXPac(const DeviceConfig& config) : MSXDevice(config) , sram(getName() + " SRAM", 0x1FFE, config, PAC_Header) { reset(EmuTime::dummy()); } void MSXPac::reset(EmuTime::param /*time*/) { sramEnabled = false; r1ffe = r1fff = 0xFF; // TODO check } byte MSXPac::readMem(word address, EmuTime::param /*time*/) { byte result; address &= 0x3FFF; if (sramEnabled) { if (address < 0x1FFE) { result = sram[address]; } else if (address == 0x1FFE) { result = r1ffe; } else if (address == 0x1FFF) { result = r1fff; } else { result = 0xFF; } } else { result = 0xFF; } return result; } const byte* MSXPac::getReadCacheLine(word address) const { address &= 0x3FFF; if (sramEnabled) { if (address < (0x1FFE & CacheLine::HIGH)) { return &sram[address]; } else if (address == (0x1FFE & CacheLine::HIGH)) { return nullptr; } else { return unmappedRead; } } else { return unmappedRead; } } void MSXPac::writeMem(word address, byte value, EmuTime::param /*time*/) { address &= 0x3FFF; switch (address) { case 0x1FFE: r1ffe = value; checkSramEnable(); break; case 0x1FFF: r1fff = value; checkSramEnable(); break; default: if (sramEnabled && (address < 0x1FFE)) { sram.write(address, value); } } } byte* MSXPac::getWriteCacheLine(word address) const { address &= 0x3FFF; if (address == (0x1FFE & CacheLine::HIGH)) { return nullptr; } if (sramEnabled && (address < 0x1FFE)) { return nullptr; } else { return unmappedWrite; } } void MSXPac::checkSramEnable() { bool newEnabled = (r1ffe == 0x4D) && (r1fff == 0x69); if (sramEnabled != newEnabled) { sramEnabled = newEnabled; invalidateMemCache(0x0000, 0x10000); } } template void MSXPac::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("SRAM", sram); ar.serialize("r1ffe", r1ffe); ar.serialize("r1fff", r1fff); if (ar.isLoader()) { checkSramEnable(); } } INSTANTIATE_SERIALIZE_METHODS(MSXPac); REGISTER_MSXDEVICE(MSXPac, "PAC"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/MSXPac.hh000066400000000000000000000012551257557151200201550ustar00rootroot00000000000000#ifndef MSXPAC_HH #define MSXPAC_HH #include "MSXDevice.hh" #include "SRAM.hh" namespace openmsx { class MSXPac final : public MSXDevice { public: explicit MSXPac(const DeviceConfig& config); void reset(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word address) const override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: void checkSramEnable(); SRAM sram; byte r1ffe, r1fff; bool sramEnabled; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/MSXRam.cc000066400000000000000000000037451257557151200201650ustar00rootroot00000000000000#include "MSXRam.hh" #include "CheckedRam.hh" #include "Ram.hh" // because we serialize Ram instead of CheckedRam #include "XMLElement.hh" #include "serialize.hh" #include "memory.hh" #include namespace openmsx { MSXRam::MSXRam(const DeviceConfig& config) : MSXDevice(config) { // Actual initialization is done in init() because tags // are not yet processed. } MSXRam::~MSXRam() { } void MSXRam::init() { MSXDevice::init(); // parse mem regions // by default get base/size from the (union of) the tag(s) getVisibleMemRegion(base, size); // but allow to override these two parameters base = getDeviceConfig().getChildDataAsInt("base", base); size = getDeviceConfig().getChildDataAsInt("size", size); assert( base < 0x10000); assert((base + size) <= 0x10000); checkedRam = make_unique( getDeviceConfig2(), getName(), "ram", size); } void MSXRam::powerUp(EmuTime::param /*time*/) { checkedRam->clear(); } unsigned MSXRam::translate(unsigned address) const { address -= base; if (address >= size) address &= (size - 1); return address; } byte MSXRam::peekMem(word address, EmuTime::param /*time*/) const { return checkedRam->peek(translate(address)); } byte MSXRam::readMem(word address, EmuTime::param /*time*/) { return checkedRam->read(translate(address)); } void MSXRam::writeMem(word address, byte value, EmuTime::param /*time*/) { checkedRam->write(translate(address), value); } const byte* MSXRam::getReadCacheLine(word start) const { return checkedRam->getReadCacheLine(translate(start)); } byte* MSXRam::getWriteCacheLine(word start) const { return checkedRam->getWriteCacheLine(translate(start)); } template void MSXRam::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); // TODO ar.serialize("checkedRam", checkedRam); ar.serialize("ram", checkedRam->getUncheckedRam()); } INSTANTIATE_SERIALIZE_METHODS(MSXRam); REGISTER_MSXDEVICE(MSXRam, "Ram"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/MSXRam.hh000066400000000000000000000015601257557151200201700ustar00rootroot00000000000000#ifndef MSXRAM_HH #define MSXRAM_HH #include "MSXDevice.hh" #include namespace openmsx { class CheckedRam; class MSXRam final : public MSXDevice { public: explicit MSXRam(const DeviceConfig& config); ~MSXRam(); void powerUp(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte* getWriteCacheLine(word start) const override; byte peekMem(word address, EmuTime::param time) const override; template void serialize(Archive& ar, unsigned version); private: void init() override; inline unsigned translate(unsigned address) const; /*const*/ unsigned base; /*const*/ unsigned size; /*const*/ std::unique_ptr checkedRam; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/MSXRom.cc000066400000000000000000000021501257557151200201700ustar00rootroot00000000000000#include "MSXRom.hh" #include "XMLElement.hh" #include "TclObject.hh" namespace openmsx { MSXRom::MSXRom(const DeviceConfig& config, Rom&& rom_) : MSXDevice(config, rom_.getName()) , rom(std::move(rom_)) { } void MSXRom::writeMem(word /*address*/, byte /*value*/, EmuTime::param /*time*/) { // nothing } byte* MSXRom::getWriteCacheLine(word /*address*/) const { return unmappedWrite; } void MSXRom::getExtraDeviceInfo(TclObject& result) const { // // TODO: change all of this to return a dict! // // Add detected rom type. This value is guaranteed to be stored in // the device config (and 'auto' is already changed to actual type). const XMLElement* mapper = getDeviceConfig().findChild("mappertype"); assert(mapper); result.addListElement(mapper->getData()); // add sha1sum, to be able to get a unique key for this ROM device, // so that it can be used to look up things in databases result.addListElement(rom.getOriginalSHA1().toString()); // add original filename, e.g. to be able to see whether it comes // from a system_rom pool result.addListElement(rom.getFilename()); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/MSXRom.hh000066400000000000000000000006651257557151200202130ustar00rootroot00000000000000#ifndef MSXROM_HH #define MSXROM_HH #include "MSXDevice.hh" #include "Rom.hh" namespace openmsx { class MSXRom : public MSXDevice { public: void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; void getExtraDeviceInfo(TclObject& result) const override; protected: MSXRom(const DeviceConfig& config, Rom&& rom); Rom rom; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/MSXRomCLI.cc000066400000000000000000000052501257557151200205240ustar00rootroot00000000000000#include "MSXRomCLI.hh" #include "CommandLineParser.hh" #include "HardwareConfig.hh" #include "MSXMotherBoard.hh" #include "MSXException.hh" #include using std::string; namespace openmsx { MSXRomCLI::MSXRomCLI(CommandLineParser& cmdLineParser_) : cmdLineParser(cmdLineParser_) { cmdLineParser.registerOption("-ips", ipsOption); cmdLineParser.registerOption("-romtype", romTypeOption); cmdLineParser.registerOption("-cart", *this); cmdLineParser.registerOption("-carta", *this); cmdLineParser.registerOption("-cartb", *this); cmdLineParser.registerOption("-cartc", *this); cmdLineParser.registerOption("-cartd", *this); cmdLineParser.registerFileType("ri,rom", *this); } void MSXRomCLI::parseOption(const string& option, array_ref& cmdLine) { string arg = getArgument(option, cmdLine); string slotname; if (option.size() == 6) { slotname = option[5]; } else { slotname = "any"; } parse(arg, slotname, cmdLine); } string_ref MSXRomCLI::optionHelp() const { return "Insert the ROM file (cartridge) specified in argument"; } void MSXRomCLI::parseFileType(const string& arg, array_ref& cmdLine) { parse(arg, "any", cmdLine); } string_ref MSXRomCLI::fileTypeHelp() const { static const string text("ROM image of a cartridge"); return text; } void MSXRomCLI::parse(const string& arg, const string& slotname, array_ref& cmdLine) { // parse extra options -ips and -romtype std::vector options; while (true) { string option = peekArgument(cmdLine); if ((option == "-ips") || (option == "-romtype")) { options.emplace_back(option); cmdLine.pop_front(); options.emplace_back(getArgument(option, cmdLine)); } else { break; } } MSXMotherBoard* motherboard = cmdLineParser.getMotherBoard(); assert(motherboard); motherboard->insertExtension("ROM", HardwareConfig::createRomConfig(*motherboard, arg, slotname, options)); } void MSXRomCLI::IpsOption::parseOption(const string& /*option*/, array_ref& /*cmdLine*/) { throw FatalError( "-ips options should immediately follow a ROM or disk image."); } string_ref MSXRomCLI::IpsOption::optionHelp() const { return "Apply the given IPS patch to the ROM or disk image specified " "in the preceding option"; } void MSXRomCLI::RomTypeOption::parseOption(const string& /*option*/, array_ref& /*cmdLine*/) { throw FatalError("-romtype options should immediately follow a ROM."); } string_ref MSXRomCLI::RomTypeOption::optionHelp() const { return "Specify the rom type for the ROM image specified in the " "preceding option"; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/MSXRomCLI.hh000066400000000000000000000021761257557151200205420ustar00rootroot00000000000000#ifndef MSXROMCLI_HH #define MSXROMCLI_HH #include "CLIOption.hh" namespace openmsx { class CommandLineParser; class MSXRomCLI final : public CLIOption, public CLIFileType { public: explicit MSXRomCLI(CommandLineParser& cmdLineParser); void parseOption(const std::string& option, array_ref& cmdLine) override; string_ref optionHelp() const override; void parseFileType(const std::string& filename, array_ref& cmdLine) override; string_ref fileTypeHelp() const override; private: void parse(const std::string& arg, const std::string& slotname, array_ref& cmdLine); CommandLineParser& cmdLineParser; struct IpsOption final : CLIOption { void parseOption(const std::string& option, array_ref& cmdLine) override; string_ref optionHelp() const override; } ipsOption; struct RomTypeOption final : CLIOption { void parseOption(const std::string& option, array_ref& cmdLine) override; string_ref optionHelp() const override; } romTypeOption; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/MegaFlashRomSCCPlus.cc000066400000000000000000000432161257557151200225550ustar00rootroot00000000000000#include "MegaFlashRomSCCPlus.hh" #include "DummyAY8910Periphery.hh" #include "MSXCPUInterface.hh" #include "CacheLine.hh" #include "serialize.hh" #include #include /****************************************************************************** * DOCUMENTATION AS PROVIDED BY MANUEL PAZOS, WHO DEVELOPED THE CARTRIDGE * ****************************************************************************** -------------------------------------------------------------------------------- MegaFlashROM SCC+ Technical Details (c) Manuel Pazos 28-09-2010 -------------------------------------------------------------------------------- Main features: - 1024KB flashROM memory (M29W800DB) - SCC-I (2312P001) - PSG (AY-3-8910/YM2149) - Mappers: ASCII8, ASCII16, Konami, Konami SCC, linear 64K - Subslot simulation (4 x 256K) -------------------------------------------------------------------------------- [Memory] - Model Numonix M29W800DB TSOP48 - Datasheet: http://www.numonyx.com/Documents/Datasheets/M29W800D.PDF - Block layout: #00000 16K #04000 8K #06000 8K #08000 32K #10000 64K x 15 - Command addresses #4555 and #5AAA - Commands: AUTOSELECT #90 WRITE #A0 CHIP_ERASE #10 BLOCK_ERASE #30 RESET #F0 - FlashROM ID = #5B -------------------------------------------------------------------------------- [PSG] The PSG included in the cartridge is mapped to ports #10-#12 Port #A0 -> #10 Port #A1 -> #11 Port #A2 -> #12 -------------------------------------------------------------------------------- [REGISTERS] -#7FFE or #7FFF = CONFIGURATION REGISTER: 7 mapper mode 1: \ %00 = SCC, %01 = 64K 6 mapper mode 0: / %10 = ASC8, %11 = ASC16 5 mapper mode : Select Konami mapper (0 = SCC, 1 = normal) 4 Enable subslot mode and register #FFFF (1 = Enable, 0 = Disable) 3 Disable #4000-#5FFF mapper in Konami mode (0 = Enable, 1 = Disable) 2 Disable configuration register (1 = Disabled) 1 Disable mapper registers (0 = Enable, 1 = Disable) 0 Enable 512K mapper limit in SCC mapper or 256K limit in Konami mapper (1 = Enable, 0 = Disable) -------------------------------------------------------------------------------- [MAPPERS] - ASCII 8: Common ASC8 mapper - ASCII 16: Common ASC16 mapper - Konami: Common Konami mapper. Bank0 (#4000-#5FFF) can be also changed unless CONF_REG bit3 is 1 - Konami SCC: Common Konami SCC mapper Bank0 mapper register: if bank number bit 7 is 1 then bit 6-0 sets mapper offset. - Linear 64: #0000-#3FFF bank0 #4000-#7FFF bank1 #8000-#BFFF bank2 #C000-#FFFF bank3 Banks mapper registers addresses = Konami -------------------------------------------------------------------------------- [DEFAULT VALUES] - CONFIGURATION REGISTER = 0 - Bank0 = 0 - Bank1 = 1 - Bank2 = 2 - Bank3 = 3 - Bank offset = 0 - Subslot register = 0 -------------------------------------------------------------------------------- [LOGIC] Banks0-3: are set depending on the mapper mode. Subslots: are set writting to #FFFF in the MegaFlashROM SCC+ slot when CONFIG_REG bit 4 is set. Offset: is set by writing offset value + #80 to bank0 mapper register in Konami SCC mode. -- Subslots offsets SubOff0 = subslot(1-0) & "0000" + mapOff; -- In 64K mode, the banks are 16K, with the offsets halved SubOff1 = subslot(3-2) & "0000" + mapOff when maptyp = "10" else subslot(3-2) & "00000" + mapOff; SubOff2 = subslot(5-4) & "0000" + mapOff when maptyp = "10" else subslot(5-4) & "00000" + mapOff; SubOff3 = subslot(7-6) & "0000" + mapOff when maptyp = "10" else subslot(7-6) & "00000"; -- Calculate the bank mapper to use taking into account the offset Bank0 = SubBank0(x) + SubOff0 when SccModeA(4) = '1' and subslot(1 downto 0) = "00" and maptyp = "10" else SubBank0(1) + SubOff0 when SccModeA(4) = '1' and subslot(1 downto 0) = "01" and maptyp = "10" else SubBank0(2) + SubOff0 when SccModeA(4) = '1' and subslot(1 downto 0) = "10" and maptyp = "10" else SubBank0(3) + SubOff0 when SccModeA(4) = '1' and subslot(1 downto 0) = "11" and maptyp = "10" else SubBank0(0) + SubOff1 when SccModeA(4) = '1' and subslot(3 downto 2) = "00" else SubBank0(1) + SubOff1 when SccModeA(4) = '1' and subslot(3 downto 2) = "01" else SubBank0(2) + SubOff1 when SccModeA(4) = '1' and subslot(3 downto 2) = "10" else SubBank0(3) + SubOff1 when SccModeA(4) = '1' and subslot(3 downto 2) = "11" else SccBank0 + mapOff; Bank1 <= SubBank1(0) + SubOff1 when SccModeA(4) = '1' and subslot(3 downto 2) = "00" else SubBank1(1) + SubOff1 when SccModeA(4) = '1' and subslot(3 downto 2) = "01" else SubBank1(2) + SubOff1 when SccModeA(4) = '1' and subslot(3 downto 2) = "10" else SubBank1(3) + SubOff1 when SccModeA(4) = '1' and subslot(3 downto 2) = "11" else SccBank1 + mapOff; Bank2 <= SubBank2(0) + SubOff2 when SccModeA(4) = '1' and subslot(5 downto 4) = "00" else SubBank2(1) + SubOff2 when SccModeA(4) = '1' and subslot(5 downto 4) = "01" else SubBank2(2) + SubOff2 when SccModeA(4) = '1' and subslot(5 downto 4) = "10" else SubBank2(3) + SubOff2 when SccModeA(4) = '1' and subslot(5 downto 4) = "11" else SccBank2 + mapOff; Bank3 <= SubBank3(0) + SubOff3 when SccModeA(4) = '1' and subslot(7 downto 6) = "00" and maptyp = "10" else SubBank3(1) + SubOff3 when SccModeA(4) = '1' and subslot(7 downto 6) = "01" and maptyp = "10" else SubBank3(2) + SubOff3 when SccModeA(4) = '1' and subslot(7 downto 6) = "10" and maptyp = "10" else SubBank3(3) + SubOff3 when SccModeA(4) = '1' and subslot(7 downto 6) = "11" and maptyp = "10" else SubBank3(0) + SubOff2 when SccModeA(4) = '1' and subslot(5 downto 4) = "00" else SubBank3(1) + SubOff2 when SccModeA(4) = '1' and subslot(5 downto 4) = "01" else SubBank3(2) + SubOff2 when SccModeA(4) = '1' and subslot(5 downto 4) = "10" else SubBank3(3) + SubOff2 when SccModeA(4) = '1' and subslot(5 downto 4) = "11" else SccBank3 + mapOff; -- 64K Mode RamAdr <= Bank0(5 downto 0) & adr(13 downto 0) when adr(15 downto 14) = "00" and maptyp = "10" else --#0000-#3FFF Bank1(5 downto 0) & adr(13 downto 0) when adr(15 downto 14) = "01" and maptyp = "10" else --#4000-#7FFF Bank2(5 downto 0) & adr(13 downto 0) when adr(15 downto 14) = "10" and maptyp = "10" else --#8000-#BFFF Bank3(5 downto 0) & adr(13 downto 0) when adr(15 downto 14) = "11" and maptyp = "10" else --#C000-#FFFF -- Modes SCC, ASC8 and ASC16 Bank0(6 downto 0) & adr(12 downto 0) when adr(14 downto 13) = "10" else --#4000-#5FFF Bank1(6 downto 0) & adr(12 downto 0) when adr(14 downto 13) = "11" else --#6000-#7FFF Bank2(6 downto 0) & adr(12 downto 0) when adr(14 downto 13) = "00" else --#8000-#9FFF Bank3(6 downto 0) & adr(12 downto 0); ******************************************************************************/ namespace openmsx { static std::vector getSectorInfo() { std::vector sectorInfo; // 1 * 16kB sectorInfo.insert(sectorInfo.end(), 1, {16 * 1024, false}); // 2 * 8kB sectorInfo.insert(sectorInfo.end(), 2, {8 * 1024, false}); // 1 * 32kB sectorInfo.insert(sectorInfo.end(), 1, {32 * 1024, false}); // 15 * 64kB sectorInfo.insert(sectorInfo.end(), 15, {64 * 1024, false}); return sectorInfo; }; MegaFlashRomSCCPlus::MegaFlashRomSCCPlus( const DeviceConfig& config, Rom&& rom_) : MSXRom(config, std::move(rom_)) , scc("MFR SCC+ SCC-I", config, getCurrentTime(), SCC::SCC_Compatible) , psg("MFR SCC+ PSG", DummyAY8910Periphery::instance(), config, getCurrentTime()) , flash(rom, getSectorInfo(), 0x205B, false, config) { powerUp(getCurrentTime()); getCPUInterface().register_IO_Out(0x10, this); getCPUInterface().register_IO_Out(0x11, this); getCPUInterface().register_IO_In (0x12, this); } MegaFlashRomSCCPlus::~MegaFlashRomSCCPlus() { getCPUInterface().unregister_IO_Out(0x10, this); getCPUInterface().unregister_IO_Out(0x11, this); getCPUInterface().unregister_IO_In (0x12, this); } void MegaFlashRomSCCPlus::powerUp(EmuTime::param time) { scc.powerUp(time); reset(time); } void MegaFlashRomSCCPlus::reset(EmuTime::param time) { configReg = 0; offsetReg = 0; subslotReg = 0; for (int subslot = 0; subslot < 4; ++subslot) { for (int bank = 0; bank < 4; ++bank) { bankRegs[subslot][bank] = bank; } } sccMode = 0; for (int i = 0; i < 4; ++i) { sccBanks[i] = i; } scc.reset(time); psgLatch = 0; psg.reset(time); flash.reset(); invalidateMemCache(0x0000, 0x10000); // flush all to be sure } MegaFlashRomSCCPlus::SCCEnable MegaFlashRomSCCPlus::getSCCEnable() const { if ((sccMode & 0x20) && (sccBanks[3] & 0x80)) { return EN_SCCPLUS; } else if ((!(sccMode & 0x20)) && ((sccBanks[2] & 0x3F) == 0x3F)) { return EN_SCC; } else { return EN_NONE; } } unsigned MegaFlashRomSCCPlus::getSubslot(unsigned addr) const { return (configReg & 0x10) ? (subslotReg >> (2 * (addr >> 14))) & 0x03 : 0; } unsigned MegaFlashRomSCCPlus::getFlashAddr(unsigned addr) const { unsigned subslot = getSubslot(addr); unsigned tmp; if ((configReg & 0xC0) == 0x40) { unsigned bank = bankRegs[subslot][addr >> 14] + offsetReg; tmp = (bank * 0x4000) + (addr & 0x3FFF); } else { unsigned page = (addr >> 13) - 2; if (page >= 4) { // Bank: -2, -1, 4, 5. So not mapped in this region, // returned value should not be used. But querying it // anyway is easier, see start of writeMem(). return unsigned(-1); } unsigned bank = bankRegs[subslot][page] + offsetReg; tmp = (bank * 0x2000) + (addr & 0x1FFF); } return ((0x40000 * subslot) + tmp) & 0xFFFFF; // wrap at 1MB } byte MegaFlashRomSCCPlus::peekMem(word addr, EmuTime::param time) const { if ((configReg & 0x10) && (addr == 0xFFFF)) { // read subslot register return subslotReg ^ 0xFF; } if ((configReg & 0xE0) == 0x00) { SCCEnable enable = getSCCEnable(); if (((enable == EN_SCC) && (0x9800 <= addr) && (addr < 0xA000)) || ((enable == EN_SCCPLUS) && (0xB800 <= addr) && (addr < 0xC000))) { return scc.peekMem(addr & 0xFF, time); } } if (((configReg & 0xC0) == 0x40) || ((0x4000 <= addr) && (addr < 0xC000))) { // read (flash)rom content unsigned flashAddr = getFlashAddr(addr); assert(flashAddr != unsigned(-1)); return flash.peek(flashAddr); } else { // unmapped read return 0xFF; } } byte MegaFlashRomSCCPlus::readMem(word addr, EmuTime::param time) { if ((configReg & 0x10) && (addr == 0xFFFF)) { // read subslot register return subslotReg ^ 0xFF; } if ((configReg & 0xE0) == 0x00) { SCCEnable enable = getSCCEnable(); if (((enable == EN_SCC) && (0x9800 <= addr) && (addr < 0xA000)) || ((enable == EN_SCCPLUS) && (0xB800 <= addr) && (addr < 0xC000))) { return scc.readMem(addr & 0xFF, time); } } if (((configReg & 0xC0) == 0x40) || ((0x4000 <= addr) && (addr < 0xC000))) { // read (flash)rom content unsigned flashAddr = getFlashAddr(addr); assert(flashAddr != unsigned(-1)); return flash.read(flashAddr); } else { // unmapped read return 0xFF; } } const byte* MegaFlashRomSCCPlus::getReadCacheLine(word addr) const { if ((configReg & 0x10) && ((addr & CacheLine::HIGH) == (0xFFFF & CacheLine::HIGH))) { // read subslot register return nullptr; } if ((configReg & 0xE0) == 0x00) { SCCEnable enable = getSCCEnable(); if (((enable == EN_SCC) && (0x9800 <= addr) && (addr < 0xA000)) || ((enable == EN_SCCPLUS) && (0xB800 <= addr) && (addr < 0xC000))) { return nullptr; } } if (((configReg & 0xC0) == 0x40) || ((0x4000 <= addr) && (addr < 0xC000))) { // read (flash)rom content unsigned flashAddr = getFlashAddr(addr); assert(flashAddr != unsigned(-1)); return flash.getReadCacheLine(flashAddr); } else { // unmapped read return unmappedRead; } } void MegaFlashRomSCCPlus::writeMem(word addr, byte value, EmuTime::param time) { // address is calculated before writes to other regions take effect unsigned flashAddr = getFlashAddr(addr); // There are several overlapping functional regions in the address // space. A single write can trigger behaviour in multiple regions. In // other words there's no priority amongst the regions where a higher // priority region blocks the write from the lower priority regions. if ((configReg & 0x10) && (addr == 0xFFFF)) { // write subslot register byte diff = value ^ subslotReg; subslotReg = value; for (int i = 0; i < 4; ++i) { if (diff & (3 << (2 * i))) { invalidateMemCache(0x4000 * i, 0x4000); } } } if (((configReg & 0x04) == 0x00) && ((addr & 0xFFFE) == 0x7FFE)) { // write config register configReg = value; invalidateMemCache(0x0000, 0x10000); // flush all to be sure } if ((configReg & 0xE0) == 0x00) { // Konami-SCC if ((addr & 0xFFFE) == 0xBFFE) { sccMode = value; scc.setChipMode((value & 0x20) ? SCC::SCC_plusmode : SCC::SCC_Compatible); invalidateMemCache(0x9800, 0x800); invalidateMemCache(0xB800, 0x800); } SCCEnable enable = getSCCEnable(); bool isRamSegment2 = ((sccMode & 0x24) == 0x24) || ((sccMode & 0x10) == 0x10); bool isRamSegment3 = ((sccMode & 0x10) == 0x10); if (((enable == EN_SCC) && !isRamSegment2 && (0x9800 <= addr) && (addr < 0xA000)) || ((enable == EN_SCCPLUS) && !isRamSegment3 && (0xB800 <= addr) && (addr < 0xC000))) { scc.writeMem(addr & 0xFF, value, time); return; // Pazos: when SCC registers are selected flashROM is not seen, so it does not accept commands. } } unsigned subslot = getSubslot(addr); unsigned page8kB = (addr >> 13) - 2; if (((configReg & 0x02) == 0x00) && (page8kB < 4)) { // (possibly) write to bank registers switch (configReg & 0xE0) { case 0x00: // Konami-SCC if ((addr & 0x1800) == 0x1000) { // Storing 'sccBanks' may seem redundant at // first, but it's required to calculate // whether the SCC is enabled or not. sccBanks[page8kB] = value; if ((value & 0x80) && (page8kB == 0)) { offsetReg = value & 0x7F; invalidateMemCache(0x4000, 0x8000); } else { // Masking of the mapper bits is done on // write (and only in Konami(-scc) mode) byte mask = (configReg & 0x01) ? 0x3F : 0x7F; bankRegs[subslot][page8kB] = value & mask; invalidateMemCache(0x4000 + 0x2000 * page8kB, 0x2000); } } break; case 0x20: { // Konami if (((configReg & 0x08) == 0x08) && (addr < 0x6000)) { // Switching 0x4000-0x5FFF disabled. // This bit blocks writing to the bank register // (an alternative was forcing a 0 on read). // It only has effect in Konami mode. break; } // Making of the mapper bits is done on // write (and only in Konami(-scc) mode) if ((addr < 0x5000) || ((0x5800 <= addr) && (addr < 0x6000))) break; // only SCC range works byte mask = (configReg & 0x01) ? 0x1F : 0x7F; bankRegs[subslot][page8kB] = value & mask; invalidateMemCache(0x4000 + 0x2000 * page8kB, 0x2000); break; } case 0x40: case 0x60: // 64kB bankRegs[subslot][page8kB] = value; invalidateMemCache(0x0000 + 0x4000 * page8kB, 0x4000); break; case 0x80: case 0xA0: // ASCII-8 if ((0x6000 <= addr) && (addr < 0x8000)) { byte bank = (addr >> 11) & 0x03; bankRegs[subslot][bank] = value; invalidateMemCache(0x4000 + 0x2000 * page8kB, 0x2000); } break; case 0xC0: case 0xE0: // ASCII-16 // This behaviour is confirmed by Manuel Pazos (creator // of the cartridge): ASCII-16 uses all 4 bank registers // and one bank switch changes 2 registers at once. // This matters when switching mapper mode, because // the content of the bank registers is unchanged after // a switch. if ((0x6000 <= addr) && (addr < 0x6800)) { bankRegs[subslot][0] = 2 * value + 0; bankRegs[subslot][1] = 2 * value + 1; invalidateMemCache(0x4000, 0x4000); } if ((0x7000 <= addr) && (addr < 0x7800)) { bankRegs[subslot][2] = 2 * value + 0; bankRegs[subslot][3] = 2 * value + 1; invalidateMemCache(0x8000, 0x4000); } break; } } // write to flash if (((configReg & 0xC0) == 0x40) || ((0x4000 <= addr) && (addr < 0xC000))) { assert(flashAddr != unsigned(-1)); return flash.write(flashAddr, value); } } byte* MegaFlashRomSCCPlus::getWriteCacheLine(word /*addr*/) const { return nullptr; } byte MegaFlashRomSCCPlus::readIO(word port, EmuTime::param time) { assert((port & 0xFF) == 0x12); (void)port; return psg.readRegister(psgLatch, time); } byte MegaFlashRomSCCPlus::peekIO(word port, EmuTime::param time) const { assert((port & 0xFF) == 0x12); (void)port; return psg.peekRegister(psgLatch, time); } void MegaFlashRomSCCPlus::writeIO(word port, byte value, EmuTime::param time) { if ((port & 0xFF) == 0x10) { psgLatch = value & 0x0F; } else { assert((port & 0xFF) == 0x11); psg.writeRegister(psgLatch, value, time); } } template void MegaFlashRomSCCPlus::serialize(Archive& ar, unsigned /*version*/) { // skip MSXRom base class ar.template serializeBase(*this); ar.serialize("scc", scc); ar.serialize("psg", psg); ar.serialize("flash", flash); ar.serialize("configReg", configReg); ar.serialize("offsetReg", offsetReg); ar.serialize("subslotReg", subslotReg); ar.serialize("bankRegs", bankRegs); ar.serialize("psgLatch", psgLatch); ar.serialize("sccMode", sccMode); ar.serialize("sccBanks", sccBanks); } INSTANTIATE_SERIALIZE_METHODS(MegaFlashRomSCCPlus); REGISTER_MSXDEVICE(MegaFlashRomSCCPlus, "MegaFlashRomSCCPlus"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/MegaFlashRomSCCPlus.hh000066400000000000000000000026001257557151200225570ustar00rootroot00000000000000#ifndef MEGAFLASHROMSCCPLUS_HH #define MEGAFLASHROMSCCPLUS_HH #include "MSXRom.hh" #include "SCC.hh" #include "AY8910.hh" #include "AmdFlash.hh" namespace openmsx { class MegaFlashRomSCCPlus final : public MSXRom { public: MegaFlashRomSCCPlus(const DeviceConfig& config, Rom&& rom); ~MegaFlashRomSCCPlus(); void powerUp(EmuTime::param time) override; void reset(EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; byte readMem(word address, EmuTime::param time) override; const byte* getReadCacheLine(word address) const override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: byte readMem2(word addr, EmuTime::param time); enum SCCEnable { EN_NONE, EN_SCC, EN_SCCPLUS }; SCCEnable getSCCEnable() const; unsigned getSubslot(unsigned address) const; unsigned getFlashAddr(unsigned addr) const; SCC scc; AY8910 psg; AmdFlash flash; byte configReg; byte offsetReg; byte subslotReg; byte bankRegs[4][4]; byte psgLatch; byte sccMode; byte sccBanks[4]; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/MegaFlashRomSCCPlusSD.cc000066400000000000000000000666661257557151200230220ustar00rootroot00000000000000#include "MegaFlashRomSCCPlusSD.hh" #include "DummyAY8910Periphery.hh" #include "MSXCPUInterface.hh" #include "CacheLine.hh" #include "Ram.hh" #include "CheckedRam.hh" #include "SdCard.hh" #include "serialize.hh" #include "memory.hh" #include #include /****************************************************************************** * DOCUMENTATION AS PROVIDED BY MANUEL PAZOS, WHO DEVELOPED THE CARTRIDGE * ****************************************************************************** -------------------------------------------------------------------------------- MegaFlashROM SCC+ SD Technical Details (c) Manuel Pazos 24-02-2014 -------------------------------------------------------------------------------- [Main features] - 8192 KB flashROM memory - SD interface (MegaSD) - SCC-I (2312P001) - PSG (AY-3-8910/YM2149) - Mappers: ASCII8, ASCII16, Konami, Konami SCC, linear 64K - Slot expander -------------------------------------------------------------------------------- [Memory] - Model Numonix/Micron M29W640FB/M29W640GB TSOP48 - Datasheet: http://www.micron.com/~/media/Documents/Products/Data%20Sheet/NOR%20Flash/Parallel/M29W/M29W640F.pdf - Block layout: #00000 8K x 8 #10000 64K x 127 - Command addresses: #4555 and #4AAA - FlashROM ID: ID_M29W640FB #FD ID_M29W640GB #7E -------------------------------------------------------------------------------- [PSG] The PSG included in the cartridge is mapped to ports #10-#12 Port #A0 -> #10 Port #A1 -> #11 Port #A2 -> #12 -------------------------------------------------------------------------------- [Cartridge layout] - Subslot 0: Recovery - 16K linear (Visible on page 1 and 2) - Subslot 1: MegaFlashROM SCC+ - 7104K (multiple mapper support) - Subslot 2: RAM (when available) - Subslot 3: MegaSD - 1024K (ASC8 mapper) -------------------------------------------------------------------------------- [FlashROM layout] #000000+----------------+ | Recovery | 16K - Subslot 0 (Note: Blocks are write protected by VPP/WD pin) +----------------+ | DSK Kernel | 16K +----------------+ | Not used | 32K #010000+----------------+ | | | | | MegaFlashROM | 7104K - Subslot 1 | | | | #700000+----------------+ | | | MegaSD | 1024K - Subslot 3 | | +----------------+ -------------------------------------------------------------------------------- [Subslot register (#FFFF)] Available when writing at #FFFF in the cartridge slot. Reading that address will return all the bits inverted. Default value = 0 -------------------------------------------------------------------------------- [Subslot 0: RECOVERY] Size 16K Common ROM (Without mapper). Visible on pages 1 and 2 (mirrored all over the slot) -------------------------------------------------------------------------------- [Subslot 1: MegaFlashROM SCC+ SD] [REGISTERS] [MAPPER REGISTER (#7FFF)] 7 mapper mode 1: \ #00 = SCC, #40 = 64K 6 mapper mode 0: / #80 = ASC8, #C0 = ASC16 5 mapper mode : Select Konami mapper (0=SCC or 1=normal) 4 3 Disable #4000-#5FFF mapper in Konami mode 2 Disable this mapper register #7FFF 1 Disable mapper and offset registers 0 Enable 512K mapper limit in SCC mapper or 256K limit in Konami mapper [OFFSET REGISTER (#7FFD)] 7-0 Offset value bits 7-0 [OFFSET REGISTER (#7FFE)] 1 Offset bit 9 0 Offset bit 8 [CONFIG REGISTER (#7FFC)] 7 Disable config register (1 = Disabled) 6 5 Disable SRAM (i.e. the RAM in subslot 2) 4 DSK mode (1 = On): Bank 0 and 1 are remaped to DSK kernel (config banks 2-3) 3 Cartridge PSG also mapped to ports #A0-#A3 2 Subslots disabled (1 = Disabled) Only MegaflashROM SCC+ is available. 1 FlashROM Block protect (1 = Protect) VPP_WD pin 0 FlashROM write enable (1 = Enabled) [MAPPERS] - ASCII 8: Common ASC8 mapper - ASCII 16: Common ASC16 mapper - Konami: Common Konami mapper. Bank0 (#4000-#5FFF) can be also changed unless [MAPPER REGISTER] bit 3 is 1 - Konami SCC: Common Konami SCC mapper - Linear 64: #0000-#3FFF bank0 #4000-#7FFF bank1 #8000-#BFFF bank2 #C000-#FFFF bank3 Banks mapper registers addresses = Konami [DEFAULT VALUES] - MAPPER REGISTER = 0 - CONFIG REGISTER = %00000011 - MapperBank0 = 0 - MapperBank1 = 1 - MapperBank2 = 2 - MapperBank3 = 3 - BankOffset = 0 - Subslot register = 0 [LOGIC] Bank0 <= "1111111010" when CONFIG REGISTER(4) = '1' and MapperBank0 = "0000000000" else ; DSK mode MapperBank0 + bankOffset; Bank1 <= "1111111011" when CONFIG REGISTER(4) = '1' and MapperBank1 = "0000000001" else ; DSK mode MapperBank1 + bankOffset; Bank2 <= MapperBank2 + bankOffset; Bank3 <= MapperBank3 + bankOffset; RamAdr <= -- Mapper in 64K mode Bank0(8 downto 0) & adr(13 downto 0) when adr(15 downto 14) = "00" else --#0000-#3FFF Bank1(8 downto 0) & adr(13 downto 0) when adr(15 downto 14) = "01" else --#4000-#7FFF Bank2(8 downto 0) & adr(13 downto 0) when adr(15 downto 14) = "10" else --#8000-#BFFF Bank3(8 downto 0) & adr(13 downto 0) when adr(15 downto 14) = "11" else --#C000-#FFFF -- Mapper in SCC, ASC8 or ASC16 modes Bank0(9 downto 0) & adr(12 downto 0) when adr(14 downto 13) = "10" else --#4000-#5FFF Bank1(9 downto 0) & adr(12 downto 0) when adr(14 downto 13) = "11" else --#6000-#7FFF Bank2(9 downto 0) & adr(12 downto 0) when adr(14 downto 13) = "00" else --#8000-#9FFF Bank3(9 downto 0) & adr(12 downto 0); --#A000-#BFFF Note: It is possible to access the whole flashROM from the MegaFlashROM SCC+ SD using the offsets register! -------------------------------------------------------------------------------- [Subslot 2: 512K RAM expansion] Mapper ports: Page 0 = #FC Page 1 = #FD Page 2 = #FE Page 3 = #FF Default bank values: Page 0 = 3 Page 1 = 2 Page 2 = 1 Page 3 = 0 Disabled when [CONFIG REGISTER] bit 5 = 1 Since mapper ports must not be read, as stated on MSX Technical Handbook, and mapper ports as read only, as stated on MSX Datapack, all read operations on these ports will not return any value. -------------------------------------------------------------------------------- [Subslot 3: MegaSD] Mapper type: ASCII8 Default mapper values: Bank0 = 0 Bank1 = 1 Bank2 = 0 Bank3 = 0 Memory range 1024K: Banks #00-#7F are mirrored in #80-#FF (except registers bank #40) Memory registers area (Bank #40): #4000-#57FF: SD card access (R/W) #4000-#4FFF: /CS signal = 0 - SD enabled #5000-#5FFF: /CS signal = 1 - SD disabled #5800-#5FFF: SD slot select (bit 0: 0 = SD slot 1, 1 = SD slot 2) Cards work in SPI mode. Signals used: CS, DI, DO, SCLK When reading, 8 bits are read from DO When writing, 8 bits are written to DI SD specifications: https://www.sdcard.org/downloads/pls/simplified_specs/part1_410.pdf ******************************************************************************/ static const unsigned MEMORY_MAPPER_SIZE = 512; namespace openmsx { static std::vector getSectorInfo() { std::vector sectorInfo; // 8 * 8kB sectorInfo.insert(end(sectorInfo), 8, {8 * 1024, false}); // 127 * 64kB sectorInfo.insert(end(sectorInfo), 127, {64 * 1024, false}); return sectorInfo; } MegaFlashRomSCCPlusSD::MegaFlashRomSCCPlusSD( const DeviceConfig& config, Rom&& rom_) : MSXRom(config, std::move(rom_)) , flash(rom, getSectorInfo(), 0x207E, true, config) , scc("MFR SCC+ SD SCC-I", config, getCurrentTime(), SCC::SCC_Compatible) , psg("MFR SCC+ SD PSG", DummyAY8910Periphery::instance(), config, getCurrentTime()) , configReg(3) // avoid UMR , checkedRam(config.getChildDataAsBool("hasmemorymapper", true) ? make_unique(config, getName() + " memory mapper", "memory mapper", MEMORY_MAPPER_SIZE * 1024) : nullptr) { powerUp(getCurrentTime()); getCPUInterface().register_IO_Out(0x10, this); getCPUInterface().register_IO_Out(0x11, this); getCPUInterface().register_IO_In (0x12, this); if (checkedRam) { getCPUInterface().register_IO_Out(0xFF, this); getCPUInterface().register_IO_Out(0xFE, this); getCPUInterface().register_IO_Out(0xFD, this); getCPUInterface().register_IO_Out(0xFC, this); } sdCard[0] = make_unique(DeviceConfig(config, config.findChild("sdcard1"))); sdCard[1] = make_unique(DeviceConfig(config, config.findChild("sdcard2"))); } MegaFlashRomSCCPlusSD::~MegaFlashRomSCCPlusSD() { // unregister extra PSG I/O ports updateConfigReg(3); getCPUInterface().unregister_IO_Out(0x10, this); getCPUInterface().unregister_IO_Out(0x11, this); getCPUInterface().unregister_IO_In (0x12, this); if (checkedRam) { getCPUInterface().unregister_IO_Out(0xFF, this); getCPUInterface().unregister_IO_Out(0xFE, this); getCPUInterface().unregister_IO_Out(0xFD, this); getCPUInterface().unregister_IO_Out(0xFC, this); } } void MegaFlashRomSCCPlusSD::powerUp(EmuTime::param time) { scc.powerUp(time); reset(time); } void MegaFlashRomSCCPlusSD::reset(EmuTime::param time) { mapperReg = 0; offsetReg = 0; updateConfigReg(3); subslotReg = 0; for (int bank = 0; bank < 4; ++bank) { bankRegsSubSlot1[bank] = bank; } sccMode = 0; for (int i = 0; i < 4; ++i) { sccBanks[i] = i; } scc.reset(time); psgLatch = 0; psg.reset(time); flash.reset(); // memory mapper for (auto i = 0; i < 4; ++i) { memMapperRegs[i] = 3 - i; } for (int bank = 0; bank < 4; ++bank) { bankRegsSubSlot3[bank] = (bank == 1) ? 1 : 0; } selectedCard = 0; invalidateMemCache(0x0000, 0x10000); // flush all to be sure } byte MegaFlashRomSCCPlusSD::getSubSlot(unsigned addr) const { return isSlotExpanderEnabled() ? (subslotReg >> (2 * (addr >> 14))) & 3 : 1; } void MegaFlashRomSCCPlusSD::writeToFlash(unsigned addr, byte value) { if (isFlashRomWriteEnabled()) { flash.write(addr, value); } else { // flash is write protected, this is implemented by not passing // writes to flash at all. } } byte MegaFlashRomSCCPlusSD::peekMem(word addr, EmuTime::param time) const { if (isSlotExpanderEnabled() && (addr == 0xFFFF)) { // read subslot register return subslotReg ^ 0xFF; } switch (getSubSlot(addr)) { case 0: return peekMemSubSlot0(addr); case 1: return peekMemSubSlot1(addr, time); case 2: return isMemoryMapperEnabled() ? peekMemSubSlot2(addr) : 0xFF; case 3: return peekMemSubSlot3(addr, time); default: UNREACHABLE; return 0; } } byte MegaFlashRomSCCPlusSD::readMem(word addr, EmuTime::param time) { if (isSlotExpanderEnabled() && (addr == 0xFFFF)) { // read subslot register return subslotReg ^ 0xFF; } switch (getSubSlot(addr)) { case 0: return readMemSubSlot0(addr); case 1: return readMemSubSlot1(addr, time); case 2: return isMemoryMapperEnabled() ? readMemSubSlot2(addr) : 0xFF; case 3: return readMemSubSlot3(addr, time); default: UNREACHABLE; return 0; } } const byte* MegaFlashRomSCCPlusSD::getReadCacheLine(word addr) const { if (isSlotExpanderEnabled() && ((addr & CacheLine::HIGH) == (0xFFFF & CacheLine::HIGH))) { // read subslot register return nullptr; } switch (getSubSlot(addr)) { case 0: return getReadCacheLineSubSlot0(addr); case 1: return getReadCacheLineSubSlot1(addr); case 2: return isMemoryMapperEnabled() ? getReadCacheLineSubSlot2(addr) : unmappedRead; case 3: return getReadCacheLineSubSlot3(addr); default: UNREACHABLE; return nullptr; } } void MegaFlashRomSCCPlusSD::writeMem(word addr, byte value, EmuTime::param time) { if (isSlotExpanderEnabled() && (addr == 0xFFFF)) { // write subslot register byte diff = value ^ subslotReg; subslotReg = value; for (int i = 0; i < 4; ++i) { if (diff & (3 << (2 * i))) { invalidateMemCache(0x4000 * i, 0x4000); } } } switch (getSubSlot(addr)) { case 0: writeMemSubSlot0(addr, value); break; case 1: writeMemSubSlot1(addr, value, time); break; case 2: if (isMemoryMapperEnabled()) { writeMemSubSlot2(addr, value); } break; case 3: writeMemSubSlot3(addr, value, time); break; default: UNREACHABLE; } } byte* MegaFlashRomSCCPlusSD::getWriteCacheLine(word addr) const { if (isSlotExpanderEnabled() && ((addr & CacheLine::HIGH) == (0xFFFF & CacheLine::HIGH))) { // read subslot register return nullptr; } switch (getSubSlot(addr)) { case 0: return getWriteCacheLineSubSlot0(addr); case 1: return getWriteCacheLineSubSlot1(addr); case 2: return isMemoryMapperEnabled() ? getWriteCacheLineSubSlot2(addr) : unmappedWrite; case 3: return getWriteCacheLineSubSlot3(addr); default: UNREACHABLE; return nullptr; } } /////////////////////// sub slot 0 //////////////////////////////////////////// byte MegaFlashRomSCCPlusSD::readMemSubSlot0(word addr) { // read from the first 16kB of flash // Pazos: ROM and flash can be accessed in all pages (0,1,2,3) (#0000-#FFFF) return flash.read(addr & 0x3FFF); } byte MegaFlashRomSCCPlusSD::peekMemSubSlot0(word addr) const { // read from the first 16kB of flash // Pazos: ROM and flash can be accessed in all pages (0,1,2,3) (#0000-#FFFF) return flash.peek(addr & 0x3FFF); } const byte* MegaFlashRomSCCPlusSD::getReadCacheLineSubSlot0(word addr) const { return flash.getReadCacheLine(addr & 0x3FFF); } void MegaFlashRomSCCPlusSD::writeMemSubSlot0(word addr, byte value) { // Pazos: ROM and flash can be accessed in all pages (0,1,2,3) (#0000-#FFFF) writeToFlash(addr & 0x3FFF, value); } byte* MegaFlashRomSCCPlusSD::getWriteCacheLineSubSlot0(word /*addr*/) const { return nullptr; // flash isn't cacheable } /////////////////////// sub slot 1 //////////////////////////////////////////// void MegaFlashRomSCCPlusSD::updateConfigReg(byte value) { if ((value ^ configReg) & 0x08) { if (value & 0x08) { getCPUInterface().register_IO_Out(0xA0, this); getCPUInterface().register_IO_Out(0xA1, this); getCPUInterface().register_IO_In (0xA2, this); } else { getCPUInterface().unregister_IO_Out(0xA0, this); getCPUInterface().unregister_IO_Out(0xA1, this); getCPUInterface().unregister_IO_In (0xA2, this); } } configReg = value; flash.setVppWpPinLow(isFlashRomBlockProtectEnabled()); invalidateMemCache(0x0000, 0x10000); // flush all to be sure } MegaFlashRomSCCPlusSD::SCCEnable MegaFlashRomSCCPlusSD::getSCCEnable() const { if ((sccMode & 0x20) && (sccBanks[3] & 0x80)) { return EN_SCCPLUS; } else if ((!(sccMode & 0x20)) && ((sccBanks[2] & 0x3F) == 0x3F)) { return EN_SCC; } else { return EN_NONE; } } unsigned MegaFlashRomSCCPlusSD::getFlashAddrSubSlot1(unsigned addr) const { unsigned page = is64KmapperConfigured() ? (addr >> 14) : ((addr >> 13) - 2); unsigned size = is64KmapperConfigured() ? 0x4000 : 0x2000; if (page >= 4) return unsigned(-1); // outside [0x4000, 0xBFFF] for non-64K mapper unsigned bank = bankRegsSubSlot1[page]; if (isDSKmodeEnabled() && (page == 0) && (bank == 0)) { bank = 0x3FA; } else if (isDSKmodeEnabled() && (page == 1) && (bank == 1)) { bank = 0x3FB; } else { // not DSK mode bank += offsetReg; } unsigned tmp = (bank * size) + (addr & (size - 1)); return (tmp + 0x010000) & 0x7FFFFF; // wrap at 8MB } byte MegaFlashRomSCCPlusSD::readMemSubSlot1(word addr, EmuTime::param time) { if (isKonamiSCCmapperConfigured()) { // Konami SCC SCCEnable enable = getSCCEnable(); if (((enable == EN_SCC) && (0x9800 <= addr) && (addr < 0xA000)) || ((enable == EN_SCCPLUS) && (0xB800 <= addr) && (addr < 0xC000))) { byte val = scc.readMem(addr & 0xFF, time); return val; } } unsigned flashAddr = getFlashAddrSubSlot1(addr); return (flashAddr != unsigned(-1)) ? flash.read(flashAddr) : 0xFF; // unmapped read } byte MegaFlashRomSCCPlusSD::peekMemSubSlot1(word addr, EmuTime::param time) const { if (isKonamiSCCmapperConfigured()) { // Konami SCC SCCEnable enable = getSCCEnable(); if (((enable == EN_SCC) && (0x9800 <= addr) && (addr < 0xA000)) || ((enable == EN_SCCPLUS) && (0xB800 <= addr) && (addr < 0xC000))) { byte val = scc.peekMem(addr & 0xFF, time); return val; } } unsigned flashAddr = getFlashAddrSubSlot1(addr); return (flashAddr != unsigned(-1)) ? flash.peek(flashAddr) : 0xFF; // unmapped read } const byte* MegaFlashRomSCCPlusSD::getReadCacheLineSubSlot1(word addr) const { if (isKonamiSCCmapperConfigured()) { SCCEnable enable = getSCCEnable(); if (((enable == EN_SCC) && (0x9800 <= addr) && (addr < 0xA000)) || ((enable == EN_SCCPLUS) && (0xB800 <= addr) && (addr < 0xC000))) { return nullptr; } } unsigned flashAddr = getFlashAddrSubSlot1(addr); return (flashAddr != unsigned(-1)) ? flash.getReadCacheLine(flashAddr) : unmappedRead; } void MegaFlashRomSCCPlusSD::writeMemSubSlot1(word addr, byte value, EmuTime::param time) { // address is calculated before writes to other regions take effect unsigned flashAddr = getFlashAddrSubSlot1(addr); // There are several overlapping functional regions in the address // space. A single write can trigger behaviour in multiple regions. In // other words there's no priority amongst the regions where a higher // priority region blocks the write from the lower priority regions. // This only goes for places where the flash is 'seen', so not for the // SCC registers and the SSR if (!isConfigRegDisabled() && (addr == 0x7FFC)) { // write config register updateConfigReg(value); } if (!isMapperRegisterDisabled() && (addr == 0x7FFF)) { // write mapper register mapperReg = value; invalidateMemCache(0x0000, 0x10000); // flush all to be sure } if (!areBankRegsAndOffsetRegsDisabled() && (addr == 0x7FFD)) { // write offset register low part offsetReg = (offsetReg & 0x300) | value; invalidateMemCache(0x0000, 0x10000); } if (!areBankRegsAndOffsetRegsDisabled() && (addr == 0x7FFE)) { // write offset register high part (bit 8 and 9) offsetReg = (offsetReg & 0xFF) + ((value & 0x3) << 8); invalidateMemCache(0x0000, 0x10000); } if (isKonamiSCCmapperConfigured()) { // Konami-SCC if ((addr & 0xFFFE) == 0xBFFE) { sccMode = value; scc.setChipMode((value & 0x20) ? SCC::SCC_plusmode : SCC::SCC_Compatible); invalidateMemCache(0x9800, 0x800); invalidateMemCache(0xB800, 0x800); } SCCEnable enable = getSCCEnable(); bool isRamSegment2 = ((sccMode & 0x24) == 0x24) || ((sccMode & 0x10) == 0x10); bool isRamSegment3 = ((sccMode & 0x10) == 0x10); if (((enable == EN_SCC) && !isRamSegment2 && (0x9800 <= addr) && (addr < 0xA000)) || ((enable == EN_SCCPLUS) && !isRamSegment3 && (0xB800 <= addr) && (addr < 0xC000))) { scc.writeMem(addr & 0xFF, value, time); return; // Pazos: when SCC registers are selected flashROM is not seen, so it does not accept commands. } } unsigned page8kB = (addr >> 13) - 2; if (!areBankRegsAndOffsetRegsDisabled() && (page8kB < 4)) { // (possibly) write to bank registers switch (mapperReg & 0xE0) { case 0x00: // Konami-SCC if ((addr & 0x1800) == 0x1000) { // Storing 'sccBanks' may seem redundant at // first, but it's required to calculate // whether the SCC is enabled or not. sccBanks[page8kB] = value; // Masking of the mapper bits is done on // write (and only in Konami(-scc) mode) byte mask = areKonamiMapperLimitsEnabled() ? 0x3F : 0x7F; bankRegsSubSlot1[page8kB] = value & mask; invalidateMemCache(0x4000 + 0x2000 * page8kB, 0x2000); } break; case 0x20: { // Konami if (isWritingKonamiBankRegisterDisabled() && (addr < 0x6000)) { // Switching 0x4000-0x5FFF disabled. // This bit blocks writing to the bank register // (an alternative was forcing a 0 on read). // It only has effect in Konami mode. break; } // Making of the mapper bits is done on // write (and only in Konami(-scc) mode) if ((addr < 0x5000) || ((0x5800 <= addr) && (addr < 0x6000))) break; // only SCC range works byte mask = areKonamiMapperLimitsEnabled() ? 0x1F : 0x7F; bankRegsSubSlot1[page8kB] = value & mask; invalidateMemCache(0x4000 + 0x2000 * page8kB, 0x2000); break; } case 0x40: case 0x60: // 64kB bankRegsSubSlot1[page8kB] = value; invalidateMemCache(0x0000 + 0x4000 * page8kB, 0x4000); break; case 0x80: case 0xA0: // ASCII-8 if ((0x6000 <= addr) && (addr < 0x8000)) { byte bank = (addr >> 11) & 0x03; bankRegsSubSlot1[bank] = value; invalidateMemCache(0x4000 + 0x2000 * page8kB, 0x2000); } break; case 0xC0: case 0xE0: // ASCII-16 // This behaviour is confirmed by Manuel Pazos (creator // of the cartridge): ASCII-16 uses all 4 bank registers // and one bank switch changes 2 registers at once. // This matters when switching mapper mode, because // the content of the bank registers is unchanged after // a switch. if ((0x6000 <= addr) && (addr < 0x6800)) { bankRegsSubSlot1[0] = 2 * value + 0; bankRegsSubSlot1[1] = 2 * value + 1; invalidateMemCache(0x4000, 0x4000); } if ((0x7000 <= addr) && (addr < 0x7800)) { bankRegsSubSlot1[2] = 2 * value + 0; bankRegsSubSlot1[3] = 2 * value + 1; invalidateMemCache(0x8000, 0x4000); } break; } } if (flashAddr != unsigned(-1)) { writeToFlash(flashAddr, value); } } byte* MegaFlashRomSCCPlusSD::getWriteCacheLineSubSlot1(word /*addr*/) const { return nullptr; // flash isn't cacheable } /////////////////////// sub slot 2 //////////////////////////////////////////// unsigned MegaFlashRomSCCPlusSD::calcMemMapperAddress(word address) const { static const unsigned MASK = (MEMORY_MAPPER_SIZE / 16) - 1; unsigned bank = memMapperRegs[address >> 14]; return ((bank & MASK) << 14) | (address & 0x3FFF); } byte MegaFlashRomSCCPlusSD::readMemSubSlot2(word addr) { // read from the memory mapper return checkedRam->read(calcMemMapperAddress(addr)); } byte MegaFlashRomSCCPlusSD::peekMemSubSlot2(word addr) const { return checkedRam->peek(calcMemMapperAddress(addr)); } const byte* MegaFlashRomSCCPlusSD::getReadCacheLineSubSlot2(word addr) const { return checkedRam->getReadCacheLine(calcMemMapperAddress(addr)); } void MegaFlashRomSCCPlusSD::writeMemSubSlot2(word addr, byte value) { // write to the memory mapper checkedRam->write(calcMemMapperAddress(addr), value); } byte* MegaFlashRomSCCPlusSD::getWriteCacheLineSubSlot2(word addr) const { return checkedRam->getWriteCacheLine(calcMemMapperAddress(addr)); } /////////////////////// sub slot 3 //////////////////////////////////////////// unsigned MegaFlashRomSCCPlusSD::getFlashAddrSubSlot3(unsigned addr) const { unsigned page8kB = (addr >> 13) - 2; return (bankRegsSubSlot3[page8kB] & 0x7f) * 0x2000 + (addr & 0x1fff) + 0x700000; } byte MegaFlashRomSCCPlusSD::readMemSubSlot3(word addr, EmuTime::param /*time*/) { if (((bankRegsSubSlot3[0] & 0xC0) == 0x40) && ((0x4000 <= addr) && (addr < 0x6000))) { // transfer from SD card return sdCard[selectedCard]->transfer(0xFF, (addr & 0x1000) != 0); } if ((0x4000 <= addr) && (addr < 0xC000)) { // read (flash)rom content unsigned flashAddr = getFlashAddrSubSlot3(addr); return flash.read(flashAddr); } else { // unmapped read return 0xFF; } } byte MegaFlashRomSCCPlusSD::peekMemSubSlot3(word addr, EmuTime::param /*time*/) const { if ((0x4000 <= addr) && (addr < 0xC000)) { // read (flash)rom content unsigned flashAddr = getFlashAddrSubSlot3(addr); return flash.peek(flashAddr); } else { // unmapped read return 0xFF; } // no peek possible for SD card return 0xFF; } const byte* MegaFlashRomSCCPlusSD::getReadCacheLineSubSlot3(word addr) const { if (((bankRegsSubSlot3[0] & 0xC0) == 0x40) && ((0x4000 <= addr) && (addr < 0x6000))) { return nullptr; } if ((0x4000 <= addr) && (addr < 0xC000)) { // (flash)rom content unsigned flashAddr = getFlashAddrSubSlot3(addr); return flash.getReadCacheLine(flashAddr); } else { return unmappedRead; } return nullptr; } void MegaFlashRomSCCPlusSD::writeMemSubSlot3(word addr, byte value, EmuTime::param /*time*/) { if (((bankRegsSubSlot3[0] & 0xC0) == 0x40) && ((0x4000 <= addr) && (addr < 0x6000))) { if (addr >= 0x5800) { selectedCard = value & 1; } else { // transfer to SD card sdCard[selectedCard]->transfer(value, (addr & 0x1000) != 0); // ignore return value } } // write to flash (first, before modifying bank regs) if ((0x4000 <= addr) && (addr < 0xC000)) { unsigned flashAddr = getFlashAddrSubSlot3(addr); writeToFlash(flashAddr, value); } // ASCII-8 mapper if ((0x6000 <= addr) && (addr < 0x8000)) { byte page8kB = (addr >> 11) & 0x03; bankRegsSubSlot3[page8kB] = value; invalidateMemCache(0x4000 + 0x2000 * page8kB, 0x2000); } } byte* MegaFlashRomSCCPlusSD::getWriteCacheLineSubSlot3(word /*addr*/) const { return nullptr; // flash isn't cacheable } /////////////////////// I/O //////////////////////////////////////////// byte MegaFlashRomSCCPlusSD::readIO(word port, EmuTime::param time) { // Note: it's not possible to read from the Memory Mapper ports assert((port & 0xFF) == 0x12 || (isPSGalsoMappedToNormalPorts() && ((port & 0xFF) == 0xA2))); (void)port; return psg.readRegister(psgLatch, time); } byte MegaFlashRomSCCPlusSD::peekIO(word port, EmuTime::param time) const { assert((port & 0xFF) == 0x12 || (isPSGalsoMappedToNormalPorts() && ((port & 0xFF) == 0xA2))); (void)port; return psg.peekRegister(psgLatch, time); } void MegaFlashRomSCCPlusSD::writeIO(word port, byte value, EmuTime::param time) { switch (port & 0xFF) { case 0xA0: if (!isPSGalsoMappedToNormalPorts()) return; case 0x10: psgLatch = value & 0x0F; break; case 0xA1: if (!isPSGalsoMappedToNormalPorts()) return; case 0x11: psg.writeRegister(psgLatch, value, time); break; case 0xFC: case 0xFD: case 0xFE: case 0xFF: memMapperRegs[port & 3] = value; invalidateMemCache(0x4000 * (port & 3), 0x4000); break; default: UNREACHABLE; } } template void MegaFlashRomSCCPlusSD::serialize(Archive& ar, unsigned /*version*/) { // skip MSXRom base class ar.template serializeBase(*this); // overall ar.serialize("flash", flash); ar.serialize("subslotReg", subslotReg); // subslot 0 stuff // (nothing) // subslot 1 stuff ar.serialize("scc", scc); ar.serialize("sccMode", sccMode); ar.serialize("sccBanks", sccBanks); ar.serialize("psg", psg); ar.serialize("psgLatch", psgLatch); ar.serialize("configReg", configReg); ar.serialize("mapperReg", mapperReg); ar.serialize("offsetReg", offsetReg); ar.serialize("bankRegsSubSlot1", bankRegsSubSlot1); if (ar.isLoader()) { // Re-register PSG ports (if needed) byte tmp = configReg; configReg = 3; // set to un-registered updateConfigReg(tmp); // restore correct value } // subslot 2 stuff // TODO ar.serialize("checkedRam", checkedRam); if (checkedRam) ar.serialize("ram", checkedRam->getUncheckedRam()); ar.serialize("memMapperRegs", memMapperRegs); // subslot 3 stuff ar.serialize("bankRegsSubSlot3", bankRegsSubSlot3); ar.serialize("selectedCard", selectedCard); ar.serialize("sdCard0", *sdCard[0]); ar.serialize("sdCard1", *sdCard[1]); } INSTANTIATE_SERIALIZE_METHODS(MegaFlashRomSCCPlusSD); REGISTER_MSXDEVICE(MegaFlashRomSCCPlusSD, "MegaFlashRomSCCPlusSD"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/MegaFlashRomSCCPlusSD.hh000066400000000000000000000102071257557151200230100ustar00rootroot00000000000000#ifndef MEGAFLASHROMSCCPLUSSD_HH #define MEGAFLASHROMSCCPLUSSD_HH #include "MSXRom.hh" #include "AmdFlash.hh" #include "SCC.hh" #include "AY8910.hh" #include namespace openmsx { class CheckedRam; class SdCard; class MegaFlashRomSCCPlusSD final : public MSXRom { public: MegaFlashRomSCCPlusSD(const DeviceConfig& config, Rom&& rom); ~MegaFlashRomSCCPlusSD(); void powerUp(EmuTime::param time) override; void reset(EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; byte readMem(word address, EmuTime::param time) override; const byte* getReadCacheLine(word address) const override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: enum SCCEnable { EN_NONE, EN_SCC, EN_SCCPLUS }; SCCEnable getSCCEnable() const; void updateConfigReg(byte value); byte getSubSlot(unsigned addr) const; /** * Writes to flash only if it was not write protected. */ void writeToFlash(unsigned addr, byte value); AmdFlash flash; byte subslotReg; // subslot 0 byte readMemSubSlot0(word address); byte peekMemSubSlot0(word address) const; const byte* getReadCacheLineSubSlot0(word address) const; byte* getWriteCacheLineSubSlot0(word address) const; void writeMemSubSlot0(word address, byte value); // subslot 1 // mega flash rom scc+ byte readMemSubSlot1(word address, EmuTime::param time); byte peekMemSubSlot1(word address, EmuTime::param time) const; const byte* getReadCacheLineSubSlot1(word address) const; byte* getWriteCacheLineSubSlot1(word address) const; void writeMemSubSlot1(word address, byte value, EmuTime::param time); unsigned getFlashAddrSubSlot1(unsigned addr) const; SCC scc; AY8910 psg; byte mapperReg; bool is64KmapperConfigured() const { return (mapperReg & 0xC0) == 0x40; } bool isKonamiSCCmapperConfigured() const { return (mapperReg & 0xE0) == 0x00; } bool isWritingKonamiBankRegisterDisabled() const { return (mapperReg & 0x08) != 0; } bool isMapperRegisterDisabled() const { return (mapperReg & 0x04) != 0; } bool areBankRegsAndOffsetRegsDisabled() const { return (mapperReg & 0x02) != 0; } bool areKonamiMapperLimitsEnabled() const { return (mapperReg & 0x01) != 0; } unsigned offsetReg; byte configReg; bool isConfigRegDisabled() const { return (configReg & 0x80) != 0; } bool isMemoryMapperEnabled() const { return ((configReg & 0x20) == 0) && checkedRam; } bool isDSKmodeEnabled() const { return (configReg & 0x10) != 0; } bool isPSGalsoMappedToNormalPorts() const { return (configReg & 0x08) != 0; } bool isSlotExpanderEnabled() const { return (configReg & 0x04) == 0; } bool isFlashRomBlockProtectEnabled() const { return (configReg & 0x02) != 0; } bool isFlashRomWriteEnabled() const { return (configReg & 0x01) != 0; } byte bankRegsSubSlot1[4]; byte psgLatch; byte sccMode; byte sccBanks[4]; // subslot 2 // 512k memory mapper byte readMemSubSlot2(word address); byte peekMemSubSlot2(word address) const; const byte* getReadCacheLineSubSlot2(word address) const; byte* getWriteCacheLineSubSlot2(word address) const; void writeMemSubSlot2(word address, byte value); unsigned calcMemMapperAddress(word address) const; unsigned calcAddress(word address) const; const std::unique_ptr checkedRam; // can be nullptr byte memMapperRegs[4]; // subslot 3 byte readMemSubSlot3(word address, EmuTime::param time); byte peekMemSubSlot3(word address, EmuTime::param time) const; const byte* getReadCacheLineSubSlot3(word address) const; byte* getWriteCacheLineSubSlot3(word address) const; void writeMemSubSlot3(word address, byte value, EmuTime::param time); unsigned getFlashAddrSubSlot3(unsigned addr) const; byte bankRegsSubSlot3[4]; byte selectedCard; std::unique_ptr sdCard[2]; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/PanasonicMemory.cc000066400000000000000000000052051257557151200221530ustar00rootroot00000000000000#include "PanasonicMemory.hh" #include "MSXMotherBoard.hh" #include "MSXCPU.hh" #include "Ram.hh" #include "Rom.hh" #include "DeviceConfig.hh" #include "HardwareConfig.hh" #include "XMLElement.hh" #include "MSXException.hh" #include "memory.hh" namespace openmsx { static std::unique_ptr createRom(MSXMotherBoard& motherBoard) { const XMLElement* elem = motherBoard.getMachineConfig()-> getConfig().findChild("PanasonicRom"); if (!elem) return nullptr; const HardwareConfig* hwConf = motherBoard.getMachineConfig(); assert(hwConf); return make_unique( "PanasonicRom", "Turbor-R main ROM", DeviceConfig(*hwConf, *elem)); } PanasonicMemory::PanasonicMemory(MSXMotherBoard& motherBoard) : msxcpu(motherBoard.getCPU()) , rom(createRom(motherBoard)) , ram(nullptr), ramSize(0) , dram(false) { } PanasonicMemory::~PanasonicMemory() { } void PanasonicMemory::registerRam(Ram& ram_) { ram = &ram_[0]; ramSize = ram_.getSize(); } const byte* PanasonicMemory::getRomBlock(unsigned block) { if (!rom) { throw MSXException("Missing PanasonicRom."); } if (dram && (((0x28 <= block) && (block < 0x2C)) || ((0x38 <= block) && (block < 0x3C)))) { assert(ram); unsigned offset = (block & 0x03) * 0x2000; unsigned ramOffset = (block < 0x30) ? ramSize - 0x10000 : ramSize - 0x08000; return ram + ramOffset + offset; } else { unsigned offset = block * 0x2000; if (offset >= rom->getSize()) { offset &= rom->getSize() - 1; } return &(*rom)[offset]; } } const byte* PanasonicMemory::getRomRange(unsigned first, unsigned last) { if (!rom) { throw MSXException("Missing PanasonicRom."); } if (last < first) { throw MSXException("Error in config file: firstblock must " "be smaller than lastblock"); } unsigned start = first * 0x2000; if (start >= rom->getSize()) { throw MSXException("Error in config file: firstblock lies " "outside of rom image."); } unsigned stop = (last + 1) * 0x2000; if (stop > rom->getSize()) { throw MSXException("Error in config file: lastblock lies " "outside of rom image."); } return &(*rom)[start]; } byte* PanasonicMemory::getRamBlock(unsigned block) { if (!ram) return nullptr; unsigned offset = block * 0x2000; if (offset >= ramSize) { offset &= ramSize - 1; } return ram + offset; } void PanasonicMemory::setDRAM(bool dram_) { if (dram_ != dram) { dram = dram_; msxcpu.invalidateMemCache(0x0000, 0x10000); } } bool PanasonicMemory::isWritable(unsigned address) const { return !dram || (address < (ramSize - 0x10000)); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/PanasonicMemory.hh000066400000000000000000000020501257557151200221600ustar00rootroot00000000000000#ifndef PANASONICMEMORY_HH #define PANASONICMEMORY_HH #include "openmsx.hh" #include "noncopyable.hh" #include namespace openmsx { class MSXMotherBoard; class MSXCPU; class Ram; class Rom; class PanasonicMemory : private noncopyable { public: explicit PanasonicMemory(MSXMotherBoard& motherBoard); ~PanasonicMemory(); /** * Pass reference of the actual Ram block for use in DRAM mode and RAM * access via the ROM mapper. Note that this is always unchecked Ram! */ void registerRam(Ram& ram); const byte* getRomBlock(unsigned block); const byte* getRomRange(unsigned first, unsigned last); /** * Note that this is always unchecked RAM! There is no UMR detection * when accessing Ram in DRAM mode or via the ROM mapper! */ byte* getRamBlock(unsigned block); unsigned getRamSize() const { return ramSize; } void setDRAM(bool dram); bool isWritable(unsigned address) const; private: MSXCPU& msxcpu; const std::unique_ptr rom; // can be nullptr byte* ram; unsigned ramSize; bool dram; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/PanasonicRam.cc000066400000000000000000000020001257557151200214100ustar00rootroot00000000000000#include "PanasonicRam.hh" #include "MSXMotherBoard.hh" #include "PanasonicMemory.hh" #include "serialize.hh" namespace openmsx { PanasonicRam::PanasonicRam(const DeviceConfig& config) : MSXMemoryMapper(config) , panasonicMemory(getMotherBoard().getPanasonicMemory()) { panasonicMemory.registerRam(checkedRam.getUncheckedRam()); } void PanasonicRam::writeMem(word address, byte value, EmuTime::param /*time*/) { unsigned addr = calcAddress(address); if (panasonicMemory.isWritable(addr)) { checkedRam.write(addr, value); } } byte* PanasonicRam::getWriteCacheLine(word start) const { unsigned addr = calcAddress(start); if (panasonicMemory.isWritable(addr)) { return checkedRam.getWriteCacheLine(addr); } else { return unmappedWrite; } } template void PanasonicRam::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); } INSTANTIATE_SERIALIZE_METHODS(PanasonicRam); REGISTER_MSXDEVICE(PanasonicRam, "PanasonicRam"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/PanasonicRam.hh000066400000000000000000000010101257557151200214220ustar00rootroot00000000000000#ifndef PANASONICRAM_HH #define PANASONICRAM_HH #include "MSXMemoryMapper.hh" namespace openmsx { class PanasonicMemory; class PanasonicRam final : public MSXMemoryMapper { public: explicit PanasonicRam(const DeviceConfig& config); void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word start) const override; template void serialize(Archive& ar, unsigned version); private: PanasonicMemory& panasonicMemory; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/Ram.cc000066400000000000000000000055461257557151200175760ustar00rootroot00000000000000#include "Ram.hh" #include "DeviceConfig.hh" #include "SimpleDebuggable.hh" #include "XMLElement.hh" #include "Base64.hh" #include "HexDump.hh" #include "MSXException.hh" #include "serialize.hh" #include "memory.hh" #include #include #include using std::string; namespace openmsx { class RamDebuggable final : public SimpleDebuggable { public: RamDebuggable(MSXMotherBoard& motherBoard, const string& name, const string& description, Ram& ram); byte read(unsigned address) override; void write(unsigned address, byte value) override; private: Ram& ram; }; Ram::Ram(const DeviceConfig& config, const string& name, const string& description, unsigned size_) : xml(*config.getXML()) , ram(size_) , size(size_) , debuggable(make_unique( config.getMotherBoard(), name, description, *this)) { clear(); } Ram::Ram(const DeviceConfig& config, unsigned size_) : xml(*config.getXML()) , ram(size_) , size(size_) { clear(); } Ram::~Ram() { } void Ram::clear(byte c) { if (const XMLElement* init = xml.findChild("initialContent")) { // get pattern (and decode) const string& encoding = init->getAttribute("encoding"); size_t done = 0; if (encoding == "gz-base64") { string tmp = Base64::decode(init->getData()); uLongf dstLen = getSize(); if (uncompress(reinterpret_cast(ram.data()), &dstLen, reinterpret_cast(tmp.data()), uLong(tmp.size())) != Z_OK) { throw MSXException("Error while decompressing initialContent."); } done = dstLen; } else if ((encoding == "hex") || (encoding == "base64")) { string out = (encoding == "hex") ? HexDump::decode(init->getData()) : Base64 ::decode(init->getData()); done = std::min(size, unsigned(out.size())); memcpy(ram.data(), out.data(), done); } else { throw MSXException("Unsupported encoding \"" + encoding + "\" for initialContent"); } // repeat pattern over whole ram auto left = size - done; while (left) { auto tmp = std::min(done, left); memcpy(&ram[done], &ram[0], tmp); done += tmp; left -= tmp; } } else { // no init pattern specified memset(ram.data(), c, size); } } const string& Ram::getName() const { return debuggable->getName(); } RamDebuggable::RamDebuggable(MSXMotherBoard& motherBoard, const string& name, const string& description, Ram& ram_) : SimpleDebuggable(motherBoard, name, description, ram_.getSize()) , ram(ram_) { } byte RamDebuggable::read(unsigned address) { return ram[address]; } void RamDebuggable::write(unsigned address, byte value) { ram[address] = value; } template void Ram::serialize(Archive& ar, unsigned /*version*/) { ar.serialize_blob("ram", ram.data(), size); } INSTANTIATE_SERIALIZE_METHODS(Ram); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/Ram.hh000066400000000000000000000020451257557151200175770ustar00rootroot00000000000000#ifndef RAM_HH #define RAM_HH #include "MemBuffer.hh" #include "openmsx.hh" #include "noncopyable.hh" #include #include namespace openmsx { class XMLElement; class DeviceConfig; class RamDebuggable; class Ram : private noncopyable { public: /** Create Ram object with an associated debuggable. */ Ram(const DeviceConfig& config, const std::string& name, const std::string& description, unsigned size); /** Create Ram object without debuggable. */ Ram(const DeviceConfig& config, unsigned size); ~Ram(); const byte& operator[](unsigned addr) const { return ram[addr]; } byte& operator[](unsigned addr) { return ram[addr]; } unsigned getSize() const { return size; } const std::string& getName() const; void clear(byte c = 0xff); template void serialize(Archive& ar, unsigned version); private: const XMLElement& xml; MemBuffer ram; unsigned size; // must come before debuggable const std::unique_ptr debuggable; // can be nullptr }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/Rom.cc000066400000000000000000000262141257557151200176070ustar00rootroot00000000000000#include "Rom.hh" #include "DeviceConfig.hh" #include "XMLElement.hh" #include "RomInfo.hh" #include "RomDatabase.hh" #include "FileContext.hh" #include "Filename.hh" #include "FileException.hh" #include "PanasonicMemory.hh" #include "MSXMotherBoard.hh" #include "Reactor.hh" #include "Debugger.hh" #include "Debuggable.hh" #include "CliComm.hh" #include "FilePool.hh" #include "ConfigException.hh" #include "EmptyPatch.hh" #include "IPSPatch.hh" #include "StringOp.hh" #include "sha1.hh" #include "memory.hh" #include #include using std::string; using std::unique_ptr; namespace openmsx { class RomDebuggable final : public Debuggable { public: RomDebuggable(Debugger& debugger, Rom& rom); ~RomDebuggable(); unsigned getSize() const override; const std::string& getDescription() const override; byte read(unsigned address) override; void write(unsigned address, byte value) override; void moved(Rom& r); private: Debugger& debugger; Rom* rom; }; Rom::Rom(const string& name_, const string& description_, const DeviceConfig& config, const string& id /*= ""*/) : name(name_), description(description_) { // Try all tags with matching "id" attribute. string errors; for (auto& c : config.getXML()->getChildren("rom")) { if (c->getAttribute("id", "") == id) { try { init(config.getMotherBoard(), *c, config.getFileContext()); return; } catch (MSXException& e) { // remember error message, and try next if (!errors.empty() && (errors.back() != '\n')) { errors += '\n'; } errors += e.getMessage(); } } } if (errors.empty()) { // No matching tag. StringOp::Builder err; err << "Missing tag"; if (!id.empty()) { err << " with id=\"" << id << '"'; } throw ConfigException(err); } else { // We got at least one matching , but it failed to load. // Report error messages of all failed attempts. throw ConfigException(errors); } } void Rom::init(MSXMotherBoard& motherBoard, const XMLElement& config, const FileContext& context) { // (Only) if the content of this ROM depends on state that is not part // of a savestate, we want to compare the sha1sum of the ROM from the // time the savestate was created with the one from the loaded // savestate. External state can be a .rom file or a patch file. bool checkResolvedSha1 = false; auto sums = config.getChildren("sha1"); auto filenames = config.getChildren("filename"); auto* resolvedFilenameElem = config.findChild("resolvedFilename"); auto* resolvedSha1Elem = config.findChild("resolvedSha1"); if (config.findChild("firstblock")) { // part of the TurboR main ROM // If there is a firstblock/lastblock tag, (only) use these to // locate the rom. In the past we would also write add a // resolvedSha1 tag for this type of ROM (before we used // 'checkResolvedSha1'). So there are old savestates that have // both type of tags. For such a savestate it's important to // first check firstblock, otherwise it will only load when // there is a file that matches the sha1sums of the // firstblock-lastblock portion of the containing file. int first = config.getChildDataAsInt("firstblock"); int last = config.getChildDataAsInt("lastblock"); size = (last - first + 1) * 0x2000; rom = motherBoard.getPanasonicMemory().getRomRange(first, last); assert(rom); // Part of a bigger (already checked) rom, no need to check. checkResolvedSha1 = false; } else if (resolvedFilenameElem || resolvedSha1Elem || !sums.empty() || !filenames.empty()) { auto& filepool = motherBoard.getReactor().getFilePool(); // first try already resolved filename .. if (resolvedFilenameElem) { try { file = File(resolvedFilenameElem->getData()); } catch (FileException&) { // ignore } } // .. then try the actual sha1sum .. auto fileType = context.isUserContext() ? FilePool::ROM : FilePool::SYSTEM_ROM; if (!file.is_open() && resolvedSha1Elem) { Sha1Sum sha1(resolvedSha1Elem->getData()); file = filepool.getFile(fileType, sha1); if (file.is_open()) { // avoid recalculating same sha1 later originalSha1 = sha1; } } // .. and then try filename as originally given by user .. if (!file.is_open()) { for (auto& f : filenames) { try { Filename filename(f->getData(), context); file = File(filename); } catch (FileException&) { // ignore } } } // .. then try all alternative sha1sums .. // (this might retry the actual sha1sum) if (!file.is_open()) { for (auto& s : sums) { Sha1Sum sha1(s->getData()); file = filepool.getFile(fileType, sha1); if (file.is_open()) { // avoid recalculating same sha1 later originalSha1 = sha1; break; } } } // .. still no file, then error if (!file.is_open()) { StringOp::Builder error; error << "Couldn't find ROM file for \"" << name << '"'; if (!filenames.empty()) { error << ' ' << filenames.front()->getData(); } if (resolvedSha1Elem) { error << " (sha1: " << resolvedSha1Elem->getData() << ')'; } else if (!sums.empty()) { error << " (sha1: " << sums.front()->getData() << ')'; } error << '.'; throw MSXException(error); } // actually read file content if (config.findChild("filesize") || config.findChild("skip_headerbytes")) { throw MSXException( "The and tags " "inside a section are no longer " "supported."); } try { size_t size2; rom = file.mmap(size2); if (size2 > std::numeric_limits::max()) { throw MSXException("Rom file too big: " + file.getURL()); } size = unsigned(size2); } catch (FileException&) { throw MSXException("Error reading ROM image: " + file.getURL()); } // For file-based roms, calc sha1 via File::getSha1Sum(). It can // possibly use the FilePool cache to avoid the calculation. if (originalSha1.empty()) { originalSha1 = filepool.getSha1Sum(file); } // verify SHA1 if (!checkSHA1(config)) { motherBoard.getMSXCliComm().printWarning( StringOp::Builder() << "SHA1 sum for '" << name << "' does not match with sum of '" << file.getURL() << "'."); } // We loaded an extrenal file, so check. checkResolvedSha1 = true; } else { // for an empty SCC the tag is missing, so take 0 // for MegaFlashRomSCC the tag is used to specify // the size of the mapper (and you don't care about initial // content) size = config.getChildDataAsInt("size", 0) * 1024; // in kb extendedRom.resize(size); memset(extendedRom.data(), 0xff, size); rom = extendedRom.data(); // Content does not depend on external files. No need to check checkResolvedSha1 = false; } Sha1Sum patchedSha1; if (size != 0) { if (auto* patchesElem = config.findChild("patches")) { // calculate before content is altered getOriginalSHA1(); unique_ptr patch = make_unique(rom, size); for (auto& p : patchesElem->getChildren("ips")) { Filename filename(p->getData(), context); patch = make_unique( filename, std::move(patch)); } auto patchSize = unsigned(patch->getSize()); if (patchSize <= size) { patch->copyBlock(0, const_cast(rom), size); } else { size = patchSize; extendedRom.resize(size); patch->copyBlock(0, extendedRom.data(), size); rom = extendedRom.data(); } // calculated because it's different from original patchedSha1 = SHA1::calc(rom, size); // Content altered by external patch file -> check. checkResolvedSha1 = true; } } // TODO fix this, this is a hack that depends heavily on // HardwareConig::createRomConfig if (StringOp::startsWith(name, "MSXRom")) { auto& db = motherBoard.getReactor().getSoftwareDatabase(); string_ref title; if (const auto* romInfo = db.fetchRomInfo(getOriginalSHA1())) { title = romInfo->getTitle(db.getBufferStart()); } if (!title.empty()) { name = title.str(); } else { // unknown ROM, use file name name = file.getOriginalName(); } } if (size) { auto& debugger = motherBoard.getDebugger(); if (debugger.findDebuggable(name)) { unsigned n = 0; string tmp; do { tmp = StringOp::Builder() << name << " (" << ++n << ')'; } while (debugger.findDebuggable(tmp)); name = tmp; } romDebuggable = make_unique(debugger, *this); } if (checkResolvedSha1) { auto& mutableConfig = const_cast(config); auto& psha1 = patchedSha1.empty() ? getOriginalSHA1() : patchedSha1; string patchedSha1Str = psha1.toString(); const auto& actualSha1Elem = mutableConfig.getCreateChild( "resolvedSha1", patchedSha1Str); if (actualSha1Elem.getData() != patchedSha1Str) { string tmp = file.is_open() ? file.getURL() : name; // can only happen in case of loadstate motherBoard.getMSXCliComm().printWarning( "The content of the rom " + tmp + " has " "changed since the time this savestate was " "created. This might result in emulation " "problems."); } } // This must come after we store the 'resolvedSha1', because on // loadstate we use that tag to search the complete rom in a filepool. if (auto* windowElem = config.findChild("window")) { unsigned windowBase = windowElem->getAttributeAsInt("base", 0); unsigned windowSize = windowElem->getAttributeAsInt("size", size); if ((windowBase + windowSize) > size) { throw MSXException(StringOp::Builder() << "The specified window [" << windowBase << ',' << windowBase + windowSize << ") falls outside " "the rom (with size " << size << ")."); } rom = &rom[windowBase]; size = windowSize; } } bool Rom::checkSHA1(const XMLElement& config) { auto sums = config.getChildren("sha1"); if (sums.empty()) { return true; } auto& sha1sum = getOriginalSHA1(); for (auto& s : sums) { if (Sha1Sum(s->getData()) == sha1sum) { return true; } } return false; } Rom::Rom(Rom&& r) : rom (std::move(r.rom)) , extendedRom (std::move(r.extendedRom)) , file (std::move(r.file)) , originalSha1 (std::move(r.originalSha1)) , name (std::move(r.name)) , description (std::move(r.description)) , size (std::move(r.size)) , romDebuggable(std::move(r.romDebuggable)) { if (romDebuggable) romDebuggable->moved(*this); } Rom::~Rom() { } string Rom::getFilename() const { return file.is_open() ? file.getURL() : ""; } const Sha1Sum& Rom::getOriginalSHA1() const { if (originalSha1.empty()) { originalSha1 = SHA1::calc(rom, size); } return originalSha1; } RomDebuggable::RomDebuggable(Debugger& debugger_, Rom& rom_) : debugger(debugger_), rom(&rom_) { debugger.registerDebuggable(rom->getName(), *this); } RomDebuggable::~RomDebuggable() { debugger.unregisterDebuggable(rom->getName(), *this); } unsigned RomDebuggable::getSize() const { return rom->getSize(); } const string& RomDebuggable::getDescription() const { return rom->getDescription(); } byte RomDebuggable::read(unsigned address) { assert(address < getSize()); return (*rom)[address]; } void RomDebuggable::write(unsigned /*address*/, byte /*value*/) { // ignore } void RomDebuggable::moved(Rom& r) { rom = &r; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/Rom.hh000066400000000000000000000027001257557151200176130ustar00rootroot00000000000000#ifndef ROM_HH #define ROM_HH #include "File.hh" #include "MemBuffer.hh" #include "sha1.hh" #include "openmsx.hh" #include #include #include namespace openmsx { class MSXMotherBoard; class XMLElement; class DeviceConfig; class FileContext; class RomDebuggable; class Rom final { public: Rom(const std::string& name, const std::string& description, const DeviceConfig& config, const std::string& id = ""); Rom(Rom&& other); ~Rom(); const byte& operator[](unsigned address) const { assert(address < size); return rom[address]; } unsigned getSize() const { return size; } std::string getFilename() const; const std::string& getName() const { return name; } const std::string& getDescription() const { return description; } const Sha1Sum& getOriginalSHA1() const; private: void init(MSXMotherBoard& motherBoard, const XMLElement& config, const FileContext& context); bool checkSHA1(const XMLElement& config); private: // !! update the move constructor when changing these members !! const byte* rom; MemBuffer extendedRom; File file; // can be a closed file mutable Sha1Sum originalSha1; std::string name; const std::string description; unsigned size; // This must come after 'name': // the destructor of RomDebuggable calls Rom::getName(), which still // needs the Rom::name member. std::unique_ptr romDebuggable; // can be nullptr }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomArc.cc000066400000000000000000000022521257557151200202310ustar00rootroot00000000000000// Parallax' Arc #include "RomArc.hh" #include "MSXCPUInterface.hh" #include "serialize.hh" namespace openmsx { RomArc::RomArc(const DeviceConfig& config, Rom&& rom_) : Rom16kBBlocks(config, std::move(rom_)) { setUnmapped(0); setRom(1, 0); setRom(2, 1); setUnmapped(3); reset(EmuTime::dummy()); getCPUInterface().register_IO_Out(0x7f, this); getCPUInterface().register_IO_In (0x7f, this); } RomArc::~RomArc() { getCPUInterface().unregister_IO_Out(0x7f, this); getCPUInterface().unregister_IO_In (0x7f, this); } void RomArc::reset(EmuTime::param /*time*/) { offset = 0x00; } void RomArc::writeIO(word /*port*/, byte value, EmuTime::param /*time*/) { if (value == 0x35) { ++offset; } } byte RomArc::readIO(word port, EmuTime::param time) { return RomArc::peekIO(port, time); } byte RomArc::peekIO(word /*port*/, EmuTime::param /*time*/) const { return ((offset & 0x03) == 0x03) ? 0xda : 0xff; } template void RomArc::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("offset", offset); } INSTANTIATE_SERIALIZE_METHODS(RomArc); REGISTER_MSXDEVICE(RomArc, "RomArc"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomArc.hh000066400000000000000000000010561257557151200202440ustar00rootroot00000000000000#ifndef ROMARC_HH #define ROMARC_HH #include "RomBlocks.hh" namespace openmsx { class RomArc final : public Rom16kBBlocks { public: RomArc(const DeviceConfig& config, Rom&& rom); ~RomArc(); void reset(EmuTime::param time) override; void writeIO(word port, byte value, EmuTime::param time) override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; template void serialize(Archive& ar, unsigned version); private: byte offset; }; } // namspace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomAscii16_2.cc000066400000000000000000000050741257557151200211510ustar00rootroot00000000000000// ASCII 16kB based cartridges with SRAM // // Examples 2kB SRAM: Daisenryaku, Harry Fox MSX Special, Hydlide 2, Jyansei // Examples 8kB SRAM: A-Train // // this type is is almost completely a ASCII16 cartrdige // However, it has 2 or 8kB of SRAM (and 128 kB ROM) // Use value 0x10 to select the SRAM. // SRAM in page 1 => read-only // SRAM in page 2 => read-write // The SRAM is mirrored in the 16 kB block // // The address to change banks (from ASCII16): // first 16kb: 0x6000 - 0x67FF (0x6000 used) // second 16kb: 0x7000 - 0x77FF (0x7000 and 0x77FF used) #include "RomAscii16_2.hh" #include "SRAM.hh" #include "serialize.hh" #include "memory.hh" namespace openmsx { RomAscii16_2::RomAscii16_2(const DeviceConfig& config, Rom&& rom_, SubType subType) : RomAscii16kB(config, std::move(rom_)) { unsigned size = (subType == ASCII16_8) ? 0x2000 // 8kB : 0x0800; // 2kB sram = make_unique(getName() + " SRAM", size, config); reset(EmuTime::dummy()); } void RomAscii16_2::reset(EmuTime::param dummy) { sramEnabled = 0; RomAscii16kB::reset(dummy); } byte RomAscii16_2::readMem(word address, EmuTime::param time) { if ((1 << (address >> 14)) & sramEnabled) { return (*sram)[address & (sram->getSize() - 1)]; } else { return RomAscii16kB::readMem(address, time); } } const byte* RomAscii16_2::getReadCacheLine(word address) const { if ((1 << (address >> 14)) & sramEnabled) { return &(*sram)[address & (sram->getSize() - 1)]; } else { return RomAscii16kB::getReadCacheLine(address); } } void RomAscii16_2::writeMem(word address, byte value, EmuTime::param /*time*/) { if ((0x6000 <= address) && (address < 0x7800) && !(address & 0x0800)) { // bank switch byte region = ((address >> 12) & 1) + 1; if (value == 0x10) { // SRAM block sramEnabled |= (1 << region); invalidateMemCache(0x4000 * region, 0x4000); } else { // ROM block setRom(region, value); sramEnabled &= ~(1 << region); } } else { // write sram if ((1 << (address >> 14)) & sramEnabled & 0x04) { sram->write(address & (sram->getSize() - 1), value); } } } byte* RomAscii16_2::getWriteCacheLine(word address) const { if ((1 << (address >> 14)) & sramEnabled & 0x04) { // write sram return nullptr; } else { return RomAscii16kB::getWriteCacheLine(address); } } template void RomAscii16_2::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("sramEnabled", sramEnabled); } INSTANTIATE_SERIALIZE_METHODS(RomAscii16_2); REGISTER_MSXDEVICE(RomAscii16_2, "RomAscii16_2"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomAscii16_2.hh000066400000000000000000000012711257557151200211560ustar00rootroot00000000000000#ifndef ROMASCII16_2_HH #define ROMASCII16_2_HH #include "RomAscii16kB.hh" namespace openmsx { class RomAscii16_2 final : public RomAscii16kB { public: enum SubType { ASCII16_2, ASCII16_8 }; RomAscii16_2(const DeviceConfig& config, Rom&& rom, SubType subType); void reset(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; const byte* getReadCacheLine(word address) const override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: byte sramEnabled; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomAscii16kB.cc000066400000000000000000000021571257557151200212040ustar00rootroot00000000000000// ASCII 16kB cartridges // // this type is used in a few cartridges. // example of cartridges: Xevious, Fantasy Zone 2, // Return of Ishitar, Androgynus, Gallforce ... // // The address to change banks: // first 16kb: 0x6000 - 0x67ff (0x6000 used) // second 16kb: 0x7000 - 0x77ff (0x7000 and 0x77ff used) #include "RomAscii16kB.hh" #include "serialize.hh" namespace openmsx { RomAscii16kB::RomAscii16kB(const DeviceConfig& config, Rom&& rom_) : Rom16kBBlocks(config, std::move(rom_)) { reset(EmuTime::dummy()); } void RomAscii16kB::reset(EmuTime::param /*time*/) { setUnmapped(0); setRom(1, 0); setRom(2, 0); setUnmapped(3); } void RomAscii16kB::writeMem(word address, byte value, EmuTime::param /*time*/) { if ((0x6000 <= address) && (address < 0x7800) && !(address & 0x0800)) { byte region = ((address >> 12) & 1) + 1; setRom(region, value); } } byte* RomAscii16kB::getWriteCacheLine(word address) const { if ((0x6000 <= address) && (address < 0x7800) && !(address & 0x0800)) { return nullptr; } else { return unmappedWrite; } } REGISTER_MSXDEVICE(RomAscii16kB, "RomAscii16kB"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomAscii16kB.hh000066400000000000000000000007471257557151200212210ustar00rootroot00000000000000#ifndef ROMASCII16KB_HH #define ROMASCII16KB_HH #include "RomBlocks.hh" namespace openmsx { class RomAscii16kB : public Rom16kBBlocks { public: RomAscii16kB(const DeviceConfig& config, Rom&& rom); virtual ~RomAscii16kB() {} void reset(EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; }; REGISTER_BASE_CLASS(RomAscii16kB, "RomAscii16kB"); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomAscii8_8.cc000066400000000000000000000071411257557151200210750ustar00rootroot00000000000000// ASCII 8kB based cartridges with SRAM // - ASCII8-8 / KOEI-8 / KOEI-32 / WIZARDRY / ASCII8-2 // // The address to change banks: // bank 1: 0x6000 - 0x67ff (0x6000 used) // bank 2: 0x6800 - 0x6fff (0x6800 used) // bank 3: 0x7000 - 0x77ff (0x7000 used) // bank 4: 0x7800 - 0x7fff (0x7800 used) // // To select SRAM set bit 7 (for WIZARDRY) or the bit just above the // rom selection bits (bit 5/6/7 depending on ROM size). For KOEI-32 // the lowest bits indicate which SRAM page is selected. SRAM is // readable at 0x8000-0xBFFF. For the KOEI-x types SRAM is also // readable at 0x4000-0x5FFF #include "RomAscii8_8.hh" #include "SRAM.hh" #include "serialize.hh" #include "memory.hh" namespace openmsx { RomAscii8_8::RomAscii8_8(const DeviceConfig& config, Rom&& rom_, SubType subType) : Rom8kBBlocks(config, std::move(rom_)) , sramEnableBit((subType == WIZARDRY) ? 0x80 : rom.getSize() / BANK_SIZE) , sramPages(((subType == KOEI_8) || (subType == KOEI_32)) ? 0x34 : 0x30) { unsigned size = (subType == KOEI_32 ) ? 0x8000 // 32kB : (subType == ASCII8_2) ? 0x0800 // 2kB : 0x2000; // 8kB sram = make_unique(getName() + " SRAM", size, config); reset(EmuTime::dummy()); } void RomAscii8_8::reset(EmuTime::param /*time*/) { setUnmapped(0); setUnmapped(1); for (int i = 2; i < 6; i++) { setRom(i, 0); } setUnmapped(6); setUnmapped(7); sramEnabled = 0; } byte RomAscii8_8::readMem(word address, EmuTime::param time) { byte bank = address / BANK_SIZE; if ((1 << bank) & sramEnabled) { // read from SRAM (possibly mirror) word addr = (sramBlock[bank] * BANK_SIZE) + (address & (sram->getSize() - 1) & BANK_MASK); return (*sram)[addr]; } else { return Rom8kBBlocks::readMem(address, time); } } const byte* RomAscii8_8::getReadCacheLine(word address) const { byte bank = address / BANK_SIZE; if ((1 << bank) & sramEnabled) { // read from SRAM (possibly mirror) word addr = (sramBlock[bank] * BANK_SIZE) + (address & (sram->getSize() - 1) & BANK_MASK); return &(*sram)[addr]; } else { return Rom8kBBlocks::getReadCacheLine(address); } } void RomAscii8_8::writeMem(word address, byte value, EmuTime::param /*time*/) { if ((0x6000 <= address) && (address < 0x8000)) { // bank switching byte region = ((address >> 11) & 3) + 2; if (value & sramEnableBit) { unsigned numBlocks = (sram->getSize() + BANK_MASK) / BANK_SIZE; // round up; sramEnabled |= (1 << region) & sramPages; sramBlock[region] = value & (numBlocks - 1); setBank(region, &(*sram)[sramBlock[region] * BANK_SIZE], value); } else { sramEnabled &= ~(1 << region); setRom(region, value); } } else { byte bank = address / BANK_SIZE; if ((1 << bank) & sramEnabled) { // write to SRAM (possibly mirror) word addr = (sramBlock[bank] * BANK_SIZE) + (address & (sram->getSize() - 1) & BANK_MASK); sram->write(addr, value); } } } byte* RomAscii8_8::getWriteCacheLine(word address) const { if ((0x6000 <= address) && (address < 0x8000)) { // bank switching return nullptr; } else if ((1 << (address / BANK_SIZE)) & sramEnabled) { // write to SRAM return nullptr; } else { return unmappedWrite; } } template void RomAscii8_8::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("sramEnabled", sramEnabled); ar.serialize("sramBlock", sramBlock); } INSTANTIATE_SERIALIZE_METHODS(RomAscii8_8); REGISTER_MSXDEVICE(RomAscii8_8, "RomAscii8_8"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomAscii8_8.hh000066400000000000000000000014441257557151200211070ustar00rootroot00000000000000#ifndef ROMASCII8_8_HH #define ROMASCII8_8_HH #include "RomBlocks.hh" namespace openmsx { class RomAscii8_8 final : public Rom8kBBlocks { public: enum SubType { ASCII8_8, KOEI_8, KOEI_32, WIZARDRY, ASCII8_2 }; RomAscii8_8(const DeviceConfig& config, Rom&& rom, SubType subType); void reset(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: const byte sramEnableBit; const byte sramPages; byte sramEnabled; byte sramBlock[NUM_BANKS]; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomAscii8kB.cc000066400000000000000000000023061257557151200211210ustar00rootroot00000000000000// ASCII 8kB cartridges // // this type is used in many japanese-only cartridges. // example of cartridges: Valis(Fantasm Soldier), Dragon Slayer, Outrun, // Ashguine 2, ... // The address to change banks: // bank 1: 0x6000 - 0x67ff (0x6000 used) // bank 2: 0x6800 - 0x6fff (0x6800 used) // bank 3: 0x7000 - 0x77ff (0x7000 used) // bank 4: 0x7800 - 0x7fff (0x7800 used) #include "RomAscii8kB.hh" #include "serialize.hh" namespace openmsx { RomAscii8kB::RomAscii8kB(const DeviceConfig& config, Rom&& rom_) : Rom8kBBlocks(config, std::move(rom_)) { reset(EmuTime::dummy()); } void RomAscii8kB::reset(EmuTime::param /*time*/) { setUnmapped(0); setUnmapped(1); for (int i = 2; i < 6; i++) { setRom(i, 0); } setUnmapped(6); setUnmapped(7); } void RomAscii8kB::writeMem(word address, byte value, EmuTime::param /*time*/) { if ((0x6000 <= address) && (address < 0x8000)) { byte region = ((address >> 11) & 3) + 2; setRom(region, value); } } byte* RomAscii8kB::getWriteCacheLine(word address) const { if ((0x6000 <= address) && (address < 0x8000)) { return nullptr; } else { return unmappedWrite; } } REGISTER_MSXDEVICE(RomAscii8kB, "RomAscii8kB"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomAscii8kB.hh000066400000000000000000000006551257557151200211400ustar00rootroot00000000000000#ifndef ROMASCII8KB_HH #define ROMASCII8KB_HH #include "RomBlocks.hh" namespace openmsx { class RomAscii8kB : public Rom8kBBlocks { public: RomAscii8kB(const DeviceConfig& config, Rom&& rom); virtual ~RomAscii8kB() {} void reset(EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomBlockDebuggable.hh000066400000000000000000000035531257557151200225370ustar00rootroot00000000000000#ifndef ROMBLOCKDEBUGGABLE_HH #define ROMBLOCKDEBUGGABLE_HH #include "SimpleDebuggable.hh" #include "MSXDevice.hh" #include namespace openmsx { class RomBlockDebuggableBase : public SimpleDebuggable { public: RomBlockDebuggableBase(const MSXDevice& device) : SimpleDebuggable( device.getMotherBoard(), device.getName() + " romblocks", "Shows for each byte of the mapper which memory block is selected.", 0x10000) { } }; class RomBlockDebuggable final : public RomBlockDebuggableBase { public: RomBlockDebuggable(const MSXDevice& device, const byte* blockNr_, unsigned startAddress_, unsigned mappedSize_, unsigned bankSizeShift_, unsigned debugShift_ = 0) : RomBlockDebuggableBase(device) , blockNr(blockNr_), startAddress(startAddress_) , mappedSize(mappedSize_), bankSizeShift(bankSizeShift_) , debugShift(debugShift_), debugMask(~((1 << debugShift) - 1)) { } RomBlockDebuggable(const MSXDevice& device, const byte* blockNr_, unsigned startAddress_, unsigned mappedSize_, unsigned bankSizeShift_, unsigned debugShift_, unsigned debugMask_) : RomBlockDebuggableBase(device) , blockNr(blockNr_), startAddress(startAddress_) , mappedSize(mappedSize_), bankSizeShift(bankSizeShift_) , debugShift(debugShift_), debugMask(debugMask_) { } byte read(unsigned address) override { unsigned addr = address - startAddress; if (addr < mappedSize) { byte tmp = blockNr[(addr >> bankSizeShift) & debugMask]; return (tmp != 255) ? (tmp >> debugShift) : tmp; } else { return 255; // outside mapped address space } } private: const byte* blockNr; const unsigned startAddress; const unsigned mappedSize; const unsigned bankSizeShift; const unsigned debugShift; const unsigned debugMask; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomBlocks.cc000066400000000000000000000117371257557151200207510ustar00rootroot00000000000000#include "RomBlocks.hh" #include "SRAM.hh" #include "MSXException.hh" #include "StringOp.hh" #include "serialize.hh" #include "unreachable.hh" namespace openmsx { template struct if_log2_ : F {}; template< class T, class F> struct if_log2_ : T {}; template struct log2 : if_log2_, log2> {}; template RomBlocks::RomBlocks( const DeviceConfig& config, Rom&& rom_, unsigned debugBankSizeShift) : MSXRom(config, std::move(rom_)) , romBlockDebug( *this, blockNr, 0x0000, 0x10000, log2::value, debugBankSizeShift) , nrBlocks(rom.getSize() / BANK_SIZE) { if ((nrBlocks * BANK_SIZE) != rom.getSize()) { throw MSXException(StringOp::Builder() << "(uncompressed) ROM image filesize must be a multiple of " << BANK_SIZE / 1024 << " kB (for this mapper type)."); } // by default no extra mappable memory block extraMem = nullptr; extraSize = 0; // Default mask: wraps at end of ROM image. blockMask = nrBlocks - 1; for (unsigned i = 0; i < NUM_BANKS; i++) { setRom(i, 0); } } template RomBlocks::~RomBlocks() { } template byte RomBlocks::readMem(word address, EmuTime::param /*time*/) { return bank[address / BANK_SIZE][address & BANK_MASK]; } template const byte* RomBlocks::getReadCacheLine(word address) const { return &bank[address / BANK_SIZE][address & BANK_MASK]; } template void RomBlocks::setBank(byte region, const byte* adr, int block) { assert("address passed to setBank() is not serializable" && ((adr == unmappedRead) || ((&rom[0] <= adr) && (adr <= &rom[rom.getSize() - 1])) || (sram && (&(*sram)[0] <= adr) && (adr <= &(*sram)[sram->getSize() - 1])) || ((extraMem <= adr) && (adr <= &extraMem[extraSize - 1])))); bank[region] = adr; blockNr[region] = block; // only for debuggable invalidateMemCache(region * BANK_SIZE, BANK_SIZE); } template void RomBlocks::setUnmapped(byte region) { setBank(region, unmappedRead, 255); } template void RomBlocks::setExtraMemory(const byte* mem, unsigned size) { extraMem = mem; extraSize = size; } template void RomBlocks::setRom(byte region, int block) { // Note: Some cartridges have a number of blocks that is not a power of 2, // for those we have to make an exception for "block < nrBlocks". block = (block < nrBlocks) ? block : block & blockMask; if (block < nrBlocks) { setBank(region, &rom[block * BANK_SIZE], block); } else { setBank(region, unmappedRead, 255); } } // version 1: initial version // version 2: added blockNr template template void RomBlocks::serialize(Archive& ar, unsigned /*version*/) { // skip MSXRom base class ar.template serializeBase(*this); if (sram) ar.serialize("sram", *sram); unsigned offsets[NUM_BANKS]; unsigned romSize = rom.getSize(); unsigned sramSize = sram ? sram->getSize() : 0; if (ar.isLoader()) { ar.serialize("banks", offsets); for (unsigned i = 0; i < NUM_BANKS; ++i) { if (offsets[i] == unsigned(-1)) { bank[i] = unmappedRead; } else if (offsets[i] < romSize) { bank[i] = &rom[offsets[i]]; } else if (offsets[i] < (romSize + sramSize)) { assert(sram); bank[i] = &(*sram)[offsets[i] - romSize]; } else if (offsets[i] < (romSize + sramSize + extraSize)) { bank[i] = &extraMem[offsets[i] - romSize - sramSize]; } else { // TODO throw UNREACHABLE; } } } else { for (unsigned i = 0; i < NUM_BANKS; ++i) { if (bank[i] == unmappedRead) { offsets[i] = unsigned(-1); } else if ((&rom[0] <= bank[i]) && (bank[i] <= &rom[romSize - 1])) { offsets[i] = unsigned(bank[i] - &rom[0]); } else if (sram && (&(*sram)[0] <= bank[i]) && (bank[i] <= &(*sram)[sramSize - 1])) { offsets[i] = unsigned(bank[i] - &(*sram)[0] + romSize); } else if ((extraMem <= bank[i]) && (bank[i] <= &extraMem[extraSize - 1])) { offsets[i] = unsigned(bank[i] - extraMem + romSize + sramSize); } else { UNREACHABLE; } } ar.serialize("banks", offsets); } // Commented out because versioning doesn't work correct on subclasses // that don't override the serialize() method (e.g. RomPlain) /*if (ar.versionAtLeast(version, 2)) { ar.serialize("blockNr", blockNr); } else { assert(ar.isLoader()); // set dummy value, anyway only used for debuggable for (unsigned i = 0; i < NUM_BANKS; ++i) { blockNr[i] = 255; } }*/ } template class RomBlocks<0x1000>; template class RomBlocks<0x2000>; template class RomBlocks<0x4000>; INSTANTIATE_SERIALIZE_METHODS(Rom4kBBlocks); INSTANTIATE_SERIALIZE_METHODS(Rom8kBBlocks); INSTANTIATE_SERIALIZE_METHODS(Rom16kBBlocks); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomBlocks.hh000066400000000000000000000067511257557151200207630ustar00rootroot00000000000000#ifndef ROMBLOCKS_HH #define ROMBLOCKS_HH #include "MSXRom.hh" #include "RomBlockDebuggable.hh" #include "serialize_meta.hh" namespace openmsx { class SRAM; template class RomBlocks : public MSXRom { public: static const unsigned BANK_SIZE = BANK_SIZE_; static const unsigned NUM_BANKS = 0x10000 / BANK_SIZE; static const unsigned BANK_MASK = BANK_SIZE - 1; byte readMem(word address, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; template void serialize(Archive& ar, unsigned version); protected: /** Constructor * @param config * @param rom * @param debugBankSizeShift Sometimes the mapper is implemented with a * smaller block size than the blocks that get logically switched. * For example RomGameMaster2 is implemented as a 4kB mapper but * blocks get switched in 8kB chunks (done like this because there * is only 4kB SRAM, that needs to be mirrored in a 8kB chunk). * This parameter indicates how much bigger the logical blocks are * compared to the implementation block size, it's only used to * correctly implement the 'romblocks' debuggable. */ RomBlocks(const DeviceConfig& config, Rom&& rom, unsigned debugBankSizeShift = 0); ~RomBlocks(); /** Select 'unmapped' memory for this region. Reads from this * region will return 0xff. */ void setUnmapped(byte region); /** Sets the memory visible for reading in a certain region. * @param region number of 8kB region in Z80 address space * (region i starts at Z80 address i * 0x2000) * @param adr pointer to memory, area must be at least 0x2000 bytes long * @param block Block number, only used for the 'romblock' debuggable. */ void setBank(byte region, const byte* adr, int block); /** Selects a block of the ROM image for reading in a certain region. * @param region number of 8kB region in Z80 address space * (region i starts at Z80 address i * 0x2000) * @param block number of 8kB block in the ROM image * (block i starts at ROM image offset i * 0x2000) */ void setRom(byte region, int block); /** Sets a mask for the block numbers. * On every call to setRom, the given block number is AND-ed with this * mask; if the resulting number if outside of the ROM, unmapped memory * is selected. * By default the mask is set up to wrap at the end of the ROM image, * meaning the entire ROM is reachable and there is no unmapped memory. */ void setBlockMask(int mask) { blockMask = mask; } /** Inform this base class of extra mapable memory block. * This is needed for serialization of mappings in this block. * Should only be called from subclass constructor. * (e.g. used by RomPanasonic) */ void setExtraMemory(const byte* mem, unsigned size); const byte* bank[NUM_BANKS]; std::unique_ptr sram; // can be nullptr byte blockNr[NUM_BANKS]; private: RomBlockDebuggable romBlockDebug; const byte* extraMem; unsigned extraSize; const int nrBlocks; int blockMask; }; using Rom4kBBlocks = RomBlocks<0x1000>; using Rom8kBBlocks = RomBlocks<0x2000>; using Rom16kBBlocks = RomBlocks<0x4000>; REGISTER_BASE_CLASS(Rom4kBBlocks, "Rom4kBBlocks"); REGISTER_BASE_CLASS(Rom8kBBlocks, "Rom8kBBlocks"); REGISTER_BASE_CLASS(Rom16kBBlocks, "Rom16kBBlocks"); // TODO see comment in .cc //SERIALIZE_CLASS_VERSION(Rom4kBBlocks, 2); //SERIALIZE_CLASS_VERSION(Rom8kBBlocks, 2); //SERIALIZE_CLASS_VERSION(Rom16kBBlocks, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomCrossBlaim.cc000066400000000000000000000027231257557151200215650ustar00rootroot00000000000000// Thanks to hap (enen) for buying the real cartridge and // investigating it in detail. See his results on: // // http://www.msx.org/forumtopicl8629.html // // To summarize: // The whole 0x0000-0xffff region acts as a single switch region. Only // the lower 2 bits of the written value have any effect. The mapping // is like the table below. The initial state is 00. // // | 0x | 10 | 11 // --------------+----+----+---- // 0x0000-0x3fff | 1 | x | x (x means unmapped, reads as 0xff) // 0x4000-0x7fff | 0 | 0 | 0 // 0x8000-0xbfff | 1 | 2 | 3 // 0xc000-0xffff | 1 | x | x #include "RomCrossBlaim.hh" #include "serialize.hh" namespace openmsx { RomCrossBlaim::RomCrossBlaim(const DeviceConfig& config, Rom&& rom_) : Rom16kBBlocks(config, std::move(rom_)) { reset(EmuTime::dummy()); } void RomCrossBlaim::reset(EmuTime::param dummy) { writeMem(0, 0, dummy); } void RomCrossBlaim::writeMem(word /*address*/, byte value, EmuTime::param /*time*/) { switch (value & 3) { case 0: case 1: setRom(0, 1); setRom(1, 0); setRom(2, 1); setRom(3, 1); break; case 2: setUnmapped(0); setRom(1, 0); setRom(2, 2); setUnmapped(3); break; case 3: setUnmapped(0); setRom(1, 0); setRom(2, 3); setUnmapped(3); break; } } byte* RomCrossBlaim::getWriteCacheLine(word /*address*/) const { return nullptr; } REGISTER_MSXDEVICE(RomCrossBlaim, "RomCrossBlaim"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomCrossBlaim.hh000066400000000000000000000006411257557151200215740ustar00rootroot00000000000000#ifndef ROMCROSSBLAIM_HH #define ROMCROSSBLAIM_HH #include "RomBlocks.hh" namespace openmsx { class RomCrossBlaim final : public Rom16kBBlocks { public: RomCrossBlaim(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomDRAM.cc000066400000000000000000000017651257557151200202570ustar00rootroot00000000000000#include "RomDRAM.hh" #include "PanasonicMemory.hh" #include "MSXMotherBoard.hh" #include "XMLElement.hh" #include "serialize.hh" namespace openmsx { static unsigned calcBaseAddr(const DeviceConfig& config) { int base = config.getChild("mem").getAttributeAsInt("base"); int first = config.getChild("rom").getChildDataAsInt("firstblock"); return first * 0x2000 - base; } RomDRAM::RomDRAM(const DeviceConfig& config, Rom&& rom_) : MSXRom(config, std::move(rom_)) , panasonicMemory(getMotherBoard().getPanasonicMemory()) , baseAddr(calcBaseAddr(config)) { // ignore result, only called to trigger 'missing rom' error early panasonicMemory.getRomBlock(baseAddr); } byte RomDRAM::readMem(word address, EmuTime::param /*time*/) { return *RomDRAM::getReadCacheLine(address); } const byte* RomDRAM::getReadCacheLine(word address) const { unsigned addr = address + baseAddr; return &panasonicMemory.getRomBlock(addr >> 13)[addr & 0x1FFF]; } REGISTER_MSXDEVICE(RomDRAM, "RomDRAM"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomDRAM.hh000066400000000000000000000006511257557151200202620ustar00rootroot00000000000000#ifndef ROMDRAM_HH #define ROMDRAM_HH #include "MSXRom.hh" namespace openmsx { class PanasonicMemory; class RomDRAM final : public MSXRom { public: RomDRAM(const DeviceConfig& config, Rom&& rom); byte readMem(word address, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; private: PanasonicMemory& panasonicMemory; const unsigned baseAddr; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomDatabase.cc000066400000000000000000000344441257557151200212400ustar00rootroot00000000000000#include "RomDatabase.hh" #include "CommandException.hh" #include "TclObject.hh" #include "FileContext.hh" #include "File.hh" #include "FileOperations.hh" #include "GlobalCommandController.hh" #include "CliComm.hh" #include "StringOp.hh" #include "String32.hh" #include "hash_map.hh" #include "outer.hh" #include "rapidsax.hh" #include "unreachable.hh" #include "stl.hh" #include "xxhash.hh" #include using std::string; using std::vector; namespace openmsx { using UnknownTypes = hash_map; class DBParser : public rapidsax::NullHandler { public: DBParser(RomDatabase::RomDB& db_, UnknownTypes& unknownTypes_, CliComm& cliComm_, char* bufStart_) : db(db_) , unknownTypes(unknownTypes_) , cliComm(cliComm_) , bufStart(bufStart_) , state(BEGIN) , unknownLevel(0) , initialSize(db.size()) { } // rapidsax handler interface void start(string_ref name); void attribute(string_ref name, string_ref value); void text(string_ref text); void stop(); void doctype(string_ref text); string_ref getSystemID() const { return systemID; } private: String32 cIndex(string_ref str); void addEntries(); void addAllEntries(); enum State { BEGIN, SOFTWAREDB, SOFTWARE, SYSTEM, TITLE, COMPANY, YEAR, COUNTRY, GENMSXID, DUMP_REMARK, DUMP_TEXT, DUMP, ORIGINAL, ROM, TYPE, START, HASH, END }; struct Dump { String32 remark; Sha1Sum hash; String32 origData; RomType type; bool origValue; }; RomDatabase::RomDB& db; UnknownTypes& unknownTypes; CliComm& cliComm; char* bufStart; string_ref systemID; string_ref type; string_ref startVal; vector dumps; string_ref system; String32 title; String32 company; String32 year; String32 country; int genMSXid; State state; unsigned unknownLevel; size_t initialSize; }; void DBParser::start(string_ref tag) { if (unknownLevel) { ++unknownLevel; return; } switch (state) { case BEGIN: if (tag == "softwaredb") { state = SOFTWAREDB; return; } throw MSXException("Expected as root tag."); case SOFTWAREDB: if (tag == "software") { system.clear(); toString32(bufStart, bufStart, title); toString32(bufStart, bufStart, company); toString32(bufStart, bufStart, year); toString32(bufStart, bufStart, country); genMSXid = 0; dumps.clear(); state = SOFTWARE; return; } break; case SOFTWARE: { char c = tag.front(); tag.pop_front(); switch (c) { case 's': if (tag == "ystem") { state = SYSTEM; return; } break; case 't': if (tag == "itle") { state = TITLE; return; } break; case 'c': if (tag == "ompany") { state = COMPANY; return; } else if (tag == "ountry") { state = COUNTRY; return; } break; case 'y': if (tag == "ear") { state = YEAR; return; } break; case 'g': if (tag == "enmsxid") { state = GENMSXID; return; } break; case 'd': if (tag == "ump") { dumps.resize(dumps.size() + 1); dumps.back().type = ROM_UNKNOWN; dumps.back().origValue = false; toString32(bufStart, bufStart, dumps.back().remark); toString32(bufStart, bufStart, dumps.back().origData); state = DUMP; return; } break; } break; } case DUMP: { char c = tag.front(); tag.pop_front(); switch (c) { case 'o': if (tag == "riginal") { dumps.back().origValue = false; state = ORIGINAL; return; } break; case 'm': if (tag == "egarom") { type.clear(); startVal.clear(); state = ROM; return; } break; case 'r': if (tag == "om") { type = "Mirrored"; startVal.clear(); state = ROM; return; } break; } break; } case ROM: { char c = tag.front(); tag.pop_front(); switch (c) { case 't': if (tag == "ype") { state = TYPE; return; } break; case 's': if (tag == "tart") { state = START; return; } break; case 'r': if (tag == "emark") { state = DUMP_REMARK; return; } break; case 'h': if (tag == "ash") { state = HASH; return; } break; } break; } case DUMP_REMARK: if (tag == "text") { state = DUMP_TEXT; return; } break; case SYSTEM: case TITLE: case COMPANY: case YEAR: case COUNTRY: case GENMSXID: case ORIGINAL: case TYPE: case START: case HASH: case DUMP_TEXT: break; case END: throw MSXException("Unexpected opening tag: " + tag); default: UNREACHABLE; } ++unknownLevel; } void DBParser::attribute(string_ref name, string_ref value) { if (unknownLevel) return; switch (state) { case ORIGINAL: if (name == "value") { dumps.back().origValue = StringOp::stringToBool(value); } break; case HASH: case BEGIN: case SOFTWAREDB: case SOFTWARE: case SYSTEM: case TITLE: case COMPANY: case YEAR: case COUNTRY: case GENMSXID: case DUMP_REMARK: case DUMP_TEXT: case DUMP: case ROM: case TYPE: case START: case END: break; default: UNREACHABLE; } } void DBParser::text(string_ref text) { if (unknownLevel) return; switch (state) { case SYSTEM: system = text; break; case TITLE: title = cIndex(text); break; case COMPANY: company = cIndex(text); break; case YEAR: year = cIndex(text); break; case COUNTRY: country = cIndex(text); break; case GENMSXID: try { genMSXid = fast_stou(text); } catch (std::invalid_argument&) { cliComm.printWarning(StringOp::Builder() << "Ignoring bad Generation MSX id (genmsxid) " "in entry with title '" << title << ": " << text); } break; case ORIGINAL: dumps.back().origData = cIndex(text); break; case TYPE: type = text; break; case START: startVal = text; break; case HASH: dumps.back().hash = Sha1Sum(text); break; case DUMP_REMARK: case DUMP_TEXT: dumps.back().remark = cIndex(text); break; case BEGIN: case SOFTWAREDB: case SOFTWARE: case DUMP: case ROM: case END: break; default: UNREACHABLE; } } String32 DBParser::cIndex(string_ref str) { auto* begin = const_cast(str.data()); auto* end = begin + str.size(); *end = 0; String32 result; toString32(bufStart, begin, result); return result; } // called on void DBParser::addEntries() { if (!system.empty() && (system != "MSX")) { // skip non-MSX entries return; } for (auto& d : dumps) { db.emplace_back(d.hash, RomInfo( title, year, company, country, d.origValue, d.origData, d.remark, d.type, genMSXid)); } } // called on void DBParser::addAllEntries() { // Calculate boundary between old and new entries. // old: [first, mid) already sorted, no duplicates // new: [mid, last) not yet sorted, may have duplicates // there may also be duplicates between old and new const auto first = begin(db); const auto last = end (db); const auto mid = first + initialSize; if (mid == last) return; // no new entries // Sort new entries, old entries are already sorted. sort(mid, last, LessTupleElement<0>()); // Filter duplicates from new entries. This is similar to the // unique() algorithm, except that it also warns about duplicates. auto it1 = mid; auto it2 = mid + 1; // skip initial non-duplicates while (it2 != last) { if (it1->first == it2->first) break; ++it1; ++it2; } // move non-duplicates up while (it2 != last) { if (it1->first == it2->first) { cliComm.printWarning( "duplicate softwaredb entry SHA1: " + it2->first.toString()); } else { ++it1; *it1 = std::move(*it2); } ++it2; } // actually erase the duplicates (typically none) db.erase(it1 + 1, last); // At this point both old and new entries are sorted and unique. But // there may still be duplicates between old and new. // Merge new and old entries. This is similar to the inplace_merge() // algorithm, except that duplicates (between old and new) are removed. if (first == mid) return; // no old entries (common case) RomDatabase::RomDB result; result.reserve(db.size()); it1 = first; it2 = mid; // while both new and old still have elements while (it1 != mid && it2 != last) { if (it1->first < it2->first) { result.push_back(std::move(*it1)); ++it1; } else { if (it1->first != it2->first) { // *it2 < *it1 result.push_back(std::move(*it2)); ++it2; } else { // pick old entry, silently ignore new result.push_back(std::move(*it1)); ++it1; ++it2; } } } // move remaining old or new entries (one of these is empty) move(it1, mid, back_inserter(result)); move(it2, last, back_inserter(result)); // make result the new current database swap(result, db); } static const char* parseStart(string_ref s) { // we expect "0x0000", "0x4000", "0x8000", "0xc000" or "" return ((s.size() == 6) && s.starts_with("0x")) ? (s.data() + 2) : nullptr; } void DBParser::stop() { if (unknownLevel) { --unknownLevel; return; } switch (state) { case SOFTWAREDB: addAllEntries(); state = END; break; case SOFTWARE: addEntries(); state = SOFTWAREDB; break; case SYSTEM: case TITLE: case COMPANY: case YEAR: case COUNTRY: case GENMSXID: state = SOFTWARE; break; case DUMP: if (dumps.back().hash.empty()) { // no sha1 sum specified, drop this dump dumps.pop_back(); } state = SOFTWARE; break; case ORIGINAL: state = DUMP; break; case ROM: { string_ref t = type; char buf[12]; if (t == "Mirrored") { if (const char* start = parseStart(startVal)) { memcpy(buf, t.data(), 8); memcpy(buf + 8, start, 4); t = string_ref(buf, 12); } } else if (t == "Normal") { if (const char* start = parseStart(startVal)) { memcpy(buf, t.data(), 6); memcpy(buf + 6, start, 4); t = string_ref(buf, 10); } } RomType romType = RomInfo::nameToRomType(t); if (romType == ROM_UNKNOWN) { unknownTypes[t.str()]++; } dumps.back().type = romType; state = DUMP; break; } case TYPE: case START: case HASH: case DUMP_REMARK: state = ROM; break; case DUMP_TEXT: state = DUMP_REMARK; break; case BEGIN: case END: throw MSXException("Unexpected closing tag"); default: UNREACHABLE; } } void DBParser::doctype(string_ref text) { auto pos1 = text.find(" SYSTEM \""); if (pos1 == string_ref::npos) return; auto t = text.substr(pos1 + 9); auto pos2 = t.find('"'); if (pos2 == string_ref::npos) return; systemID = t.substr(0, pos2); } static void parseDB(CliComm& cliComm, char* buf, char* bufStart, RomDatabase::RomDB& db, UnknownTypes& unknownTypes) { DBParser handler(db, unknownTypes, cliComm, bufStart); rapidsax::parse(handler, buf); if (handler.getSystemID() != "softwaredb1.dtd") { throw rapidsax::ParseError( "Missing or wrong systemID.\n" "You're probably using an old incompatible file format.", nullptr); } } RomDatabase::RomDatabase(GlobalCommandController& commandController, CliComm& cliComm) : softwareInfoTopic(commandController.getOpenMSXInfoCommand()) { db.reserve(3500); UnknownTypes unknownTypes; // first user- then system-directory vector paths = systemFileContext().getPaths(); vector files; size_t bufferSize = 0; for (auto& p : paths) { try { files.emplace_back(FileOperations::join(p, "softwaredb.xml")); bufferSize += files.back().getSize() + 1; } catch (MSXException& /*e*/) { // Ignore. It's not unusual the DB in the user // directory is not found. In case there's an error // with both user and system DB, we must give a // warning, but that's done below. } } buffer.resize(bufferSize); size_t bufferOffset = 0; for (auto& file : files) { try { auto size = file.getSize(); auto* buf = &buffer[bufferOffset]; bufferOffset += size + 1; file.read(buf, size); buf[size] = 0; parseDB(cliComm, buf, buffer.data(), db, unknownTypes); } catch (rapidsax::ParseError& e) { cliComm.printWarning(StringOp::Builder() << "Rom database parsing failed: " << e.what()); } catch (MSXException& /*e*/) { // Ignore, see above } } if (bufferSize) buffer[0] = 0; if (db.empty()) { cliComm.printWarning( "Couldn't load software database.\n" "This may cause incorrect ROM mapper types to be used."); } if (!unknownTypes.empty()) { StringOp::Builder output; output << "Unknown mapper types in software database: "; for (auto& p : unknownTypes) { output << p.first << " (" << p.second << "x); "; } cliComm.printWarning(output); } } const RomInfo* RomDatabase::fetchRomInfo(const Sha1Sum& sha1sum) const { auto it = lower_bound(begin(db), end(db), sha1sum, LessTupleElement<0>()); return ((it != end(db)) && (it->first == sha1sum)) ? &it->second : nullptr; } // SoftwareInfoTopic RomDatabase::SoftwareInfoTopic::SoftwareInfoTopic( InfoCommand& openMSXInfoCommand) : InfoTopic(openMSXInfoCommand, "software") { } void RomDatabase::SoftwareInfoTopic::execute( array_ref tokens, TclObject& result) const { if (tokens.size() != 3) { throw CommandException("Wrong number of parameters"); } Sha1Sum sha1sum = Sha1Sum(tokens[2].getString()); auto& romDatabase = OUTER(RomDatabase, softwareInfoTopic); const RomInfo* romInfo = romDatabase.fetchRomInfo(sha1sum); if (!romInfo) { // no match found throw CommandException( "Software with sha1sum " + sha1sum.toString() + " not found"); } const char* bufStart = romDatabase.buffer.data(); result.addListElement("title"); result.addListElement(romInfo->getTitle(bufStart)); result.addListElement("year"); result.addListElement(romInfo->getYear(bufStart)); result.addListElement("company"); result.addListElement(romInfo->getCompany(bufStart)); result.addListElement("country"); result.addListElement(romInfo->getCountry(bufStart)); result.addListElement("orig_type"); result.addListElement(romInfo->getOrigType(bufStart)); result.addListElement("remark"); result.addListElement(romInfo->getRemark(bufStart)); result.addListElement("original"); result.addListElement(romInfo->getOriginal()); result.addListElement("mapper_type_name"); result.addListElement(RomInfo::romTypeToName(romInfo->getRomType())); result.addListElement("genmsxid"); result.addListElement(romInfo->getGenMSXid()); } string RomDatabase::SoftwareInfoTopic::help(const vector& /*tokens*/) const { return "Returns information about the software " "given its sha1sum, in a paired list."; } void RomDatabase::SoftwareInfoTopic::tabCompletion(vector& /*tokens*/) const { // no useful completion possible } } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomDatabase.hh000066400000000000000000000021461257557151200212440ustar00rootroot00000000000000#ifndef ROMDATABASE_HH #define ROMDATABASE_HH #include "RomInfo.hh" #include "MemBuffer.hh" #include "InfoTopic.hh" #include "sha1.hh" #include "noncopyable.hh" #include #include namespace openmsx { class CliComm; class GlobalCommandController; class RomDatabase : private noncopyable { public: using RomDB = std::vector>; RomDatabase(GlobalCommandController& commandController, CliComm& cliComm); /** Lookup an entry in the database by sha1sum. * Returns nullptr when no corresponding entry was found. */ const RomInfo* fetchRomInfo(const Sha1Sum& sha1sum) const; const char* getBufferStart() const { return buffer.data(); } private: RomDB db; MemBuffer buffer; struct SoftwareInfoTopic final : InfoTopic { SoftwareInfoTopic(InfoCommand& openMSXInfoCommand); void execute(array_ref tokens, TclObject& result) const override; std::string help(const std::vector& tokens) const override; void tabCompletion(std::vector& tokens) const override; } softwareInfoTopic; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomDooly.cc000066400000000000000000000036641257557151200206220ustar00rootroot00000000000000// // Agigongnyong Dooly // #include "RomDooly.hh" #include "CacheLine.hh" #include "serialize.hh" namespace openmsx { RomDooly::RomDooly(const DeviceConfig& config, Rom&& rom_) : MSXRom(config, std::move(rom_)) , romBlockDebug(*this, &conversion, 0x4000, 0x8000, 15) { conversion = 0; } void RomDooly::reset(EmuTime::param /*time*/) { conversion = 0; } byte RomDooly::peekMem(word address, EmuTime::param /*time*/) const { if ((0x4000 <= address) && (address < 0xc000)) { byte value = rom[address - 0x4000]; switch (conversion) { case 0: return value; case 1: return (value & 0xf8) | (value << 2 & 0x04) | (value >> 1 & 0x03); case 4: return (value & 0xf8) | (value >> 2 & 0x01) | (value << 1 & 0x06); case 2: case 5: case 6: switch (value & 0x07) { case 1: case 2: case 4: return value & 0xf8; case 3: case 5: case 6: if (conversion == 2) return (value & 0xf8) | (((value << 2 & 0x04) | (value >> 1 & 0x03)) ^ 0x07); if (conversion == 5) return value ^ 0x07; if (conversion == 6) return (value & 0xf8) | (((value >> 2 & 0x01) | (value << 1 & 0x06)) ^ 0x07); default: return value; } default: return value | 0x07; } } return 0xff; } byte RomDooly::readMem(word address, EmuTime::param time) { return peekMem(address, time); } void RomDooly::writeMem(word address, byte value, EmuTime::param /*time*/) { if ((0x4000 <= address) && (address < 0xc000)) { conversion = value & 0x07; } } byte* RomDooly::getWriteCacheLine(word address) const { if (((0x4000 & CacheLine::HIGH) <= address) && (address < (0xc000 & CacheLine::HIGH))) { return nullptr; } else { return unmappedWrite; } } template void RomDooly::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("conversion", conversion); } INSTANTIATE_SERIALIZE_METHODS(RomDooly); REGISTER_MSXDEVICE(RomDooly, "RomDooly"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomDooly.hh000066400000000000000000000012531257557151200206240ustar00rootroot00000000000000#ifndef ROMDOOLY_HH #define ROMDOOLY_HH #include "MSXRom.hh" #include "RomBlockDebuggable.hh" namespace openmsx { class RomDooly final : public MSXRom { public: RomDooly(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; byte readMem(word address, EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: RomBlockDebuggable romBlockDebug; byte conversion; }; } // namspace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomFSA1FM.cc000066400000000000000000000174421257557151200204500ustar00rootroot00000000000000// FS-A1FM mappers // based on info from: // http://sourceforge.net/tracker/index.php?func=detail&aid=672508&group_id=38274&atid=421864 // // Slot 3-1 // 0x4000-0x5FFF 16 x 8kB ROM, same 16 pages as mapper in slot 3-3 // 0x6000-0x7FFF 8kB RAM (mapped ???) // 0x7FC0-0x7FCF I/O area // 0x7FC0 (R/W) 8251 (UART) data register // 0x7FC1 (R/W) 8251 (UART) command/status register // 0x7FC4 (?) ROM switch address, lower 4 bits -> selected bank // upper 4 bits -> ??? (always preserved) // 0x7FC5 (W) ??? // 0x7FC6 (R) ??? // 0x7FC8 (W) ??? // 0x7FCC (W) ??? // // Slot 3-3 // Very similar to other Panasonic-8kB rom mappers // // 0x0000-0x1FFF switch address 0x6400, read-back 0x7FF0, initial value 0xA8 // 0x2000-0x3FFF switch address 0x6C00, read-back 0x7FF1, initial value 0xA8 // 0x4000-0x5FFF switch address 0x6000, read-back 0x7FF2, initial value 0xA8 // 0x6000-0x7FFF switch address 0x6800, read-back 0x7FF3, initial value 0xA8 // 0x8000-0x9FFF switch address 0x7000, read-back 0x7FF4, initial value 0xA8 // 0xA000-0xBFFF switch address 0x7800, read-back 0x7FF5, initial value 0xA8 // // 0x7FF6-0x7FF7 always read 0 // 0x7FF9 control byte, bit2 activates 0x7FF0-0x7FF7 // // mapper pages 0x10-0x7F mirrored at 0x90-0xFF // 0x80-0x83 0x88-0x8B unmapped (read 0xFF) // 0x84-0x87 0x8C-0x8F contain (same) 8kB RAM #include "RomFSA1FM.hh" #include "CacheLine.hh" #include "SRAM.hh" #include "MSXMotherBoard.hh" #include "MSXException.hh" #include "serialize.hh" #include "memory.hh" namespace openmsx { // 8kb shared sram // static std::shared_ptr getSram(const DeviceConfig& config) { return config.getMotherBoard().getSharedStuff( "FSA1FM-sram", config.getAttribute("id") + " SRAM", 0x2000, config); } // Mapper for slot 3-1 // RomFSA1FM1::RomFSA1FM1(const DeviceConfig& config, Rom&& rom_) : MSXRom(config, std::move(rom_)) , fsSram(getSram(config)) , firmwareSwitch(config) { if ((rom.getSize() != 0x100000) && (rom.getSize() != 0x200000)) { throw MSXException( "Rom for FSA1FM mapper must be 1MB in size " "(some dumps are 2MB, those can be used as well)."); } } RomFSA1FM1::~RomFSA1FM1() { } void RomFSA1FM1::reset(EmuTime::param /*time*/) { // initial rom bank is undefined } byte RomFSA1FM1::peekMem(word address, EmuTime::param /*time*/) const { if ((0x4000 <= address) && (address < 0x6000)) { // read rom return rom[(0x2000 * ((*fsSram)[0x1FC4] & 0x0F)) + (address & 0x1FFF)]; } else if ((0x7FC0 <= address) && (address < 0x7FD0)) { switch (address & 0x0F) { case 4: return (*fsSram)[address & 0x1FFF]; case 6: return firmwareSwitch.getStatus() ? 0xFB : 0xFF; default: return 0xFF; } } else if ((0x6000 <= address) && (address < 0x8000)) { // read sram // TODO are there multiple sram blocks? return (*fsSram)[address & 0x1FFF]; } else { return 0xFF; } } byte RomFSA1FM1::readMem(word address, EmuTime::param time) { return RomFSA1FM1::peekMem(address, time); } const byte* RomFSA1FM1::getReadCacheLine(word address) const { if (address == (0x7FC0 & CacheLine::HIGH)) { // dont't cache IO area return nullptr; } else if ((0x4000 <= address) && (address < 0x6000)) { // read rom return &rom[(0x2000 * ((*fsSram)[0x1FC4] & 0x0F)) + (address & 0x1FFF)]; } else if ((0x6000 <= address) && (address < 0x8000)) { // read sram return &(*fsSram)[address & 0x1FFF]; } else { return unmappedRead; } } void RomFSA1FM1::writeMem(word address, byte value, EmuTime::param /*time*/) { // TODO 0x7FC0 - 0x7FCF is modem IO area if ((0x6000 <= address) && (address < 0x8000)) { if (address == 0x7FC4) { // switch rom bank invalidateMemCache(0x4000, 0x2000); } fsSram->write(address & 0x1FFF, value); } } byte* RomFSA1FM1::getWriteCacheLine(word address) const { if (address == (0x7FC0 & CacheLine::HIGH)) { // dont't cache IO area return nullptr; } else if ((0x6000 <= address) && (address < 0x8000)) { // don't cache SRAM writes return nullptr; } else { return unmappedWrite; } } template void RomFSA1FM1::serialize(Archive& ar, unsigned /*version*/) { // skip MSXRom base class ar.template serializeBase(*this); // don't serialize (shared) sram here, rely on RomFSA1FM2 to do that } INSTANTIATE_SERIALIZE_METHODS(RomFSA1FM1); REGISTER_MSXDEVICE(RomFSA1FM1, "RomFSA1FM1"); // Mapper for slot 3-3 // RomFSA1FM2::RomFSA1FM2(const DeviceConfig& config, Rom&& rom_) : Rom8kBBlocks(config, std::move(rom_)) , fsSram(getSram(config)) { reset(EmuTime::dummy()); } RomFSA1FM2::~RomFSA1FM2() { } void RomFSA1FM2::reset(EmuTime::param /*time*/) { control = 0; for (int region = 0; region < 6; ++region) { changeBank(region, 0xA8); } changeBank(6, 0); changeBank(7, 0); } byte RomFSA1FM2::peekMem(word address, EmuTime::param time) const { byte result; if (0xC000 <= address) { result = 0xFF; } else if ((control & 0x04) && (0x7FF0 <= address) && (address < 0x7FF8)) { // read mapper state result = bankSelect[address & 7]; } else if (isRam[address >> 13]) { result = (*fsSram)[address & 0x1FFF]; } else if (isEmpty[address >> 13]) { result = 0xFF; } else { result = Rom8kBBlocks::peekMem(address, time); } return result; } byte RomFSA1FM2::readMem(word address, EmuTime::param time) { return RomFSA1FM2::peekMem(address, time); } const byte* RomFSA1FM2::getReadCacheLine(word address) const { if (0xC000 <= address) { return unmappedRead; } else if ((0x7FF0 & CacheLine::HIGH) == address) { return nullptr; } else if (isRam[address >> 13]) { return &(*fsSram)[address & 0x1FFF]; } else if (isEmpty[address >> 13]) { return unmappedRead; } else { return Rom8kBBlocks::getReadCacheLine(address); } } void RomFSA1FM2::writeMem(word address, byte value, EmuTime::param /*time*/) { if ((0x6000 <= address) && (address < 0x7FF0)) { // set mapper state switch (address & 0x1C00) { case 0x0000: changeBank(2, value); break; case 0x0400: changeBank(0, value); break; case 0x0800: changeBank(3, value); break; case 0x0C00: changeBank(1, value); break; case 0x1000: changeBank(4, value); break; case 0x1400: // nothing break; case 0x1800: changeBank(5, value); break; case 0x1C00: // nothing break; } } else if (address == 0x7FF9) { // write control byte control = value; } else if (isRam[address >> 13]) { fsSram->write(address & 0x1FFF, value); } } byte* RomFSA1FM2::getWriteCacheLine(word address) const { if ((0x6000 <= address) && (address < 0x8000)) { return nullptr; } else if (isRam[address >> 13]) { return nullptr; } else { return unmappedWrite; } } void RomFSA1FM2::changeBank(byte region, byte bank) { bankSelect[region] = bank; if ((0x80 <= bank) && (bank < 0x90)) { if (bank & 0x04) { isRam[region] = true; isEmpty[region] = false; } else { isRam[region] = false; isEmpty[region] = true; } invalidateMemCache(0x2000 * region, 0x2000); } else { isRam[region] = false; isEmpty[region] = false; setRom(region, bank & 0x7F); } } template void RomFSA1FM2::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); // note: SRAM can be serialized in this class (as opposed to // Rom8kBBlocks), because we don't use setBank to map it ar.serialize("SRAM", *fsSram); ar.serialize("bankSelect", bankSelect); ar.serialize("control", control); if (ar.isLoader()) { // recalculate 'isRam' and 'isEmpty' from bankSelect for (int region = 0; region < 8; ++region) { changeBank(region, bankSelect[region]); } } } INSTANTIATE_SERIALIZE_METHODS(RomFSA1FM2); REGISTER_MSXDEVICE(RomFSA1FM2, "RomFSA1FM2"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomFSA1FM.hh000066400000000000000000000030501257557151200204500ustar00rootroot00000000000000#ifndef ROMFSA1FM1_HH #define ROMFSA1FM1_HH #include "MSXRom.hh" #include "RomBlocks.hh" #include "FirmwareSwitch.hh" namespace openmsx { class MSXMotherBoard; class SRAM; class RomFSA1FM1 final : public MSXRom { public: RomFSA1FM1(const DeviceConfig& config, Rom&& rom); ~RomFSA1FM1(); void reset(EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; byte readMem(word address, EmuTime::param time) override; const byte* getReadCacheLine(word address) const override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: std::shared_ptr fsSram; FirmwareSwitch firmwareSwitch; }; class RomFSA1FM2 final : public Rom8kBBlocks { public: RomFSA1FM2(const DeviceConfig& config, Rom&& rom); ~RomFSA1FM2(); void reset(EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; byte readMem(word address, EmuTime::param time) override; const byte* getReadCacheLine(word address) const override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: void changeBank(byte region, byte bank); std::shared_ptr fsSram; byte bankSelect[8]; bool isRam[8]; bool isEmpty[8]; byte control; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomFactory.cc000066400000000000000000000250511257557151200211350ustar00rootroot00000000000000#include "RomFactory.hh" #include "RomTypes.hh" #include "RomInfo.hh" #include "RomPageNN.hh" #include "RomPlain.hh" #include "RomDRAM.hh" #include "RomGeneric8kB.hh" #include "RomGeneric16kB.hh" #include "RomKonami.hh" #include "RomKonamiSCC.hh" #include "RomKonamiKeyboardMaster.hh" #include "RomAscii8kB.hh" #include "RomAscii8_8.hh" #include "RomAscii16kB.hh" #include "RomPadial8kB.hh" #include "RomPadial16kB.hh" #include "RomSuperLodeRunner.hh" #include "RomSuperSwangi.hh" #include "RomMitsubishiMLTS2.hh" #include "RomMSXDOS2.hh" #include "RomAscii16_2.hh" #include "RomRType.hh" #include "RomCrossBlaim.hh" #include "RomHarryFox.hh" #include "RomPanasonic.hh" #include "RomNational.hh" #include "RomMajutsushi.hh" #include "RomSynthesizer.hh" #include "RomPlayBall.hh" #include "RomNettouYakyuu.hh" #include "RomGameMaster2.hh" #include "RomHalnote.hh" #include "RomZemina80in1.hh" #include "RomZemina90in1.hh" #include "RomZemina126in1.hh" #include "RomHolyQuran.hh" #include "RomHolyQuran2.hh" #include "RomFSA1FM.hh" #include "RomManbow2.hh" #include "RomMatraInk.hh" #include "RomArc.hh" #include "MegaFlashRomSCCPlus.hh" #include "MegaFlashRomSCCPlusSD.hh" #include "RomDooly.hh" #include "RomMSXtra.hh" #include "RomMultiRom.hh" #include "Rom.hh" #include "Reactor.hh" #include "RomDatabase.hh" #include "DeviceConfig.hh" #include "XMLElement.hh" #include "MSXException.hh" #include "memory.hh" using std::unique_ptr; using std::move; using std::string; namespace openmsx { namespace RomFactory { static RomType guessRomType(const Rom& rom) { int size = rom.getSize(); if (size == 0) { return ROM_NORMAL; } const byte* data = &rom[0]; if (size < 0x10000) { if ((size <= 0x4000) && (data[0] == 'A') && (data[1] == 'B')) { word initAddr = data[2] + 256 * data[3]; word textAddr = data[8] + 256 * data[9]; if ((textAddr & 0xC000) == 0x8000) { if ((initAddr == 0) || (((initAddr & 0xC000) == 0x8000) && (data[initAddr & (size - 1)] == 0xC9))) { return ROM_PAGE2; } } } // not correct for Konami-DAC, but does this really need // to be correct for _every_ rom? return ROM_MIRRORED; } else if (size == 0x10000 && !((data[0] == 'A') && (data[1] == 'B'))) { // 64 kB ROMs can be plain or memory mapped... // check here for plain, if not, try the auto detection // (thanks for the hint, hap) return ROM_MIRRORED; } else { // GameCartridges do their bankswitching by using the Z80 // instruction ld(nn),a in the middle of program code. The // adress nn depends upon the GameCartridge mappertype used. // To guess which mapper it is, we will look how much writes // with this instruction to the mapper-registers-addresses // occur. unsigned typeGuess[ROM_END_OF_UNORDERED_LIST] = {}; // 0-initialized for (int i = 0; i < size - 3; ++i) { if (data[i] == 0x32) { word value = data[i + 1] + (data[i + 2] << 8); switch (value) { case 0x5000: case 0x9000: case 0xb000: typeGuess[ROM_KONAMI_SCC]++; break; case 0x4000: typeGuess[ROM_KONAMI]++; break; case 0x8000: case 0xa000: typeGuess[ROM_KONAMI]++; break; case 0x6800: case 0x7800: typeGuess[ROM_ASCII8]++; break; case 0x6000: typeGuess[ROM_KONAMI]++; typeGuess[ROM_ASCII8]++; typeGuess[ROM_ASCII16]++; break; case 0x7000: typeGuess[ROM_KONAMI_SCC]++; typeGuess[ROM_ASCII8]++; typeGuess[ROM_ASCII16]++; break; case 0x77ff: typeGuess[ROM_ASCII16]++; break; } } } if (typeGuess[ROM_ASCII8]) typeGuess[ROM_ASCII8]--; // -1 -> max_int RomType type = ROM_GENERIC_8KB; for (int i = 0; i < ROM_END_OF_UNORDERED_LIST; ++i) { // debug: fprintf(stderr, "%d: %d\n", i, typeGuess[i]); if (typeGuess[i] && (typeGuess[i] >= typeGuess[type])) { type = static_cast(i); } } // in case of doubt we go for type ROM_GENERIC_8KB // in case of even type ROM_ASCII16 and ROM_ASCII8 we would // prefer ROM_ASCII16 but we would still prefer ROM_GENERIC_8KB // above ROM_ASCII8 or ROM_ASCII16 if ((type == ROM_ASCII16) && (typeGuess[ROM_GENERIC_8KB] == typeGuess[ROM_ASCII16])) { type = ROM_GENERIC_8KB; } return type; } } unique_ptr create(const DeviceConfig& config) { Rom rom(config.getAttribute("id"), "rom", config); // Get specified mapper type from the config. RomType type; // if no type is mentioned, we assume 'mirrored' which works for most // plain ROMs... string_ref typestr = config.getChildData("mappertype", "Mirrored"); if (typestr == "auto") { // Guess mapper type, if it was not in DB. const RomInfo* romInfo = config.getReactor().getSoftwareDatabase().fetchRomInfo(rom.getOriginalSHA1()); if (!romInfo) { type = guessRomType(rom); } else { type = romInfo->getRomType(); } } else { // Use mapper type from config, even if this overrides DB. type = RomInfo::nameToRomType(typestr); } unique_ptr result; switch (type) { case ROM_MIRRORED: result = make_unique( config, move(rom), RomPlain::MIRRORED); break; case ROM_NORMAL: result = make_unique( config, move(rom), RomPlain::NOT_MIRRORED); break; case ROM_MIRRORED0000: case ROM_MIRRORED4000: case ROM_MIRRORED8000: case ROM_MIRROREDC000: result = make_unique( config, move(rom), RomPlain::MIRRORED, (type & 7) * 0x2000); break; case ROM_NORMAL0000: case ROM_NORMAL4000: case ROM_NORMAL8000: case ROM_NORMALC000: result = make_unique( config, move(rom), RomPlain::NOT_MIRRORED, (type & 7) * 0x2000); break; case ROM_PAGE0: case ROM_PAGE1: case ROM_PAGE01: case ROM_PAGE2: case ROM_PAGE12: case ROM_PAGE012: case ROM_PAGE3: case ROM_PAGE23: case ROM_PAGE123: case ROM_PAGE0123: result = make_unique(config, move(rom), type & 0xF); break; case ROM_DRAM: result = make_unique(config, move(rom)); break; case ROM_GENERIC_8KB: result = make_unique(config, move(rom)); break; case ROM_GENERIC_16KB: result = make_unique(config, move(rom)); break; case ROM_KONAMI_SCC: result = make_unique(config, move(rom)); break; case ROM_KONAMI: result = make_unique(config, move(rom)); break; case ROM_KBDMASTER: result = make_unique(config, move(rom)); break; case ROM_ASCII8: result = make_unique(config, move(rom)); break; case ROM_ASCII16: result = make_unique(config, move(rom)); break; case ROM_PADIAL8: result = make_unique(config, move(rom)); break; case ROM_PADIAL16: result = make_unique(config, move(rom)); break; case ROM_SUPERLODERUNNER: result = make_unique(config, move(rom)); break; case ROM_SUPERSWANGI: result = make_unique(config, move(rom)); break; case ROM_MITSUBISHIMLTS2: result = make_unique(config, move(rom)); break; case ROM_MSXDOS2: result = make_unique(config, move(rom)); break; case ROM_R_TYPE: result = make_unique(config, move(rom)); break; case ROM_CROSS_BLAIM: result = make_unique(config, move(rom)); break; case ROM_HARRY_FOX: result = make_unique(config, move(rom)); break; case ROM_ASCII8_8: result = make_unique( config, move(rom), RomAscii8_8::ASCII8_8); break; case ROM_ASCII8_2: result = make_unique( config, move(rom), RomAscii8_8::ASCII8_2); break; case ROM_KOEI_8: result = make_unique( config, move(rom), RomAscii8_8::KOEI_8); break; case ROM_KOEI_32: result = make_unique( config, move(rom), RomAscii8_8::KOEI_32); break; case ROM_WIZARDRY: result = make_unique( config, move(rom), RomAscii8_8::WIZARDRY); break; case ROM_ASCII16_2: result = make_unique(config, move(rom), RomAscii16_2::ASCII16_2); break; case ROM_ASCII16_8: result = make_unique(config, move(rom), RomAscii16_2::ASCII16_8); break; case ROM_GAME_MASTER2: result = make_unique(config, move(rom)); break; case ROM_PANASONIC: result = make_unique(config, move(rom)); break; case ROM_NATIONAL: result = make_unique(config, move(rom)); break; case ROM_MAJUTSUSHI: result = make_unique(config, move(rom)); break; case ROM_SYNTHESIZER: result = make_unique(config, move(rom)); break; case ROM_PLAYBALL: result = make_unique(config, move(rom)); break; case ROM_NETTOU_YAKYUU: result = make_unique(config, move(rom)); break; case ROM_HALNOTE: result = make_unique(config, move(rom)); break; case ROM_ZEMINA80IN1: result = make_unique(config, move(rom)); break; case ROM_ZEMINA90IN1: result = make_unique(config, move(rom)); break; case ROM_ZEMINA126IN1: result = make_unique(config, move(rom)); break; case ROM_HOLY_QURAN: result = make_unique(config, move(rom)); break; case ROM_HOLY_QURAN2: result = make_unique(config, move(rom)); break; case ROM_FSA1FM1: result = make_unique(config, move(rom)); break; case ROM_FSA1FM2: result = make_unique(config, move(rom)); break; case ROM_MANBOW2: case ROM_MANBOW2_2: case ROM_HAMARAJANIGHT: case ROM_MEGAFLASHROMSCC: result = make_unique(config, move(rom), type); break; case ROM_MATRAINK: result = make_unique(config, move(rom)); break; case ROM_ARC: result = make_unique(config, move(rom)); break; case ROM_MEGAFLASHROMSCCPLUS: result = make_unique(config, move(rom)); break; case ROM_MEGAFLASHROMSCCPLUSSD: result = make_unique(config, move(rom)); break; case ROM_DOOLY: result = make_unique(config, move(rom)); break; case ROM_MSXTRA: result = make_unique(config, move(rom)); break; case ROM_MULTIROM: result = make_unique(config, move(rom)); break; default: throw MSXException("Unknown ROM type"); } // Store actual detected mapper type in config (override the possible // 'auto' value). This way we're sure that on savestate/loadstate we're // using the same mapper type (for example when the user's rom-database // was updated). auto& writableConfig = const_cast(*config.getXML()); writableConfig.setChildData("mappertype", RomInfo::romTypeToName(type)); return move(result); } } // namespace RomFactory } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomFactory.hh000066400000000000000000000003641257557151200211470ustar00rootroot00000000000000#ifndef ROMFACTORY_HH #define ROMFACTORY_HH #include namespace openmsx { class MSXDevice; class DeviceConfig; namespace RomFactory { std::unique_ptr create(const DeviceConfig& config); } } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomGameMaster2.cc000066400000000000000000000067031257557151200216400ustar00rootroot00000000000000// GAME MASTER 2 // // This is a 1 megabit ROM cartridge with 8 Kb SRAM. Because of the SRAM, // the mappers have special features. // // Since the size of the mapper is 8Kb, the memory banks are: // Bank 1: 4000h - 5FFFh // Bank 2: 6000h - 7FFFh // Bank 3: 8000h - 9FFFh // Bank 4: A000h - BFFFh // // And the addresses to change banks: // Bank 1: // Bank 2: 6000h - 6FFFh (6000h used) // Bank 3: 8000h - 8FFFh (8000h used) // Bank 4: A000h - AFFFh (A000h used) // SRAM write: B000h - BFFFh // // If SRAM is selected in bank 4, you can write to it in the memory area // B000h - BFFFh. // // The value you write to change banks also determines whether you select ROM // or SRAM. SRAM can be in any memory bank (except bank 1 which can't be // modified) but it can only be written too in bank 4. // // bit | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | // ---------------------------------------------------------- // function | R0 | R1 | R2 | R3 | 1=SRAM/0=ROM | S0 | X | X | // // If bit 4 is reset, bits 0 - 3 select the ROM page as you would expect them // to do. Bits 5 - 7 are ignored now. If bit 4 is set, bit 5 selects the SRAM // page (first or second 4Kb of the 8Kb). Bits 6 - 7 and bits 0 - 3 are // ignored now. // // Since you can only select 4Kb of the SRAM at once in a memory bank and a // memory bank is 8Kb in size, the first and second 4Kb of the memory bank // read the same 4Kb of SRAM if SRAM is selected. #include "RomGameMaster2.hh" #include "SRAM.hh" #include "serialize.hh" #include "memory.hh" namespace openmsx { RomGameMaster2::RomGameMaster2(const DeviceConfig& config, Rom&& rom_) : Rom4kBBlocks(config, std::move(rom_), 1) { sram = make_unique(getName() + " SRAM", 0x2000, config); reset(EmuTime::dummy()); } void RomGameMaster2::reset(EmuTime::param /*time*/) { for (int i = 0; i < 4; i++) { setUnmapped(i); } for (int i = 4; i < 12; i++) { setRom(i, i - 4); } for (int i = 12; i < 16; i++) { setUnmapped(i); } sramOffset = 0; sramEnabled = false; } void RomGameMaster2::writeMem(word address, byte value, EmuTime::param /*time*/) { if ((0x6000 <= address) && (address < 0xB000)) { if (!(address & 0x1000)) { byte region = address >> 12; // 0x6, 0x8 or 0xA if (region == 0x0A) { sramEnabled = (value & 0x10) != 0; } if (value & 0x10) { // switch SRAM sramOffset = (value & 0x20) ? 0x1000: 0x0000; setBank(region, &(*sram)[sramOffset], value); setBank(region + 1, &(*sram)[sramOffset], value); } else { // switch ROM setRom(region, 2 * (value & 0x0F)); setRom(region + 1, 2 * (value & 0x0F) + 1); } } } else if ((0xB000 <= address) && (address < 0xC000)) { // write SRAM if (sramEnabled) { sram->write(sramOffset | (address & 0x0FFF), value); } } } byte* RomGameMaster2::getWriteCacheLine(word address) const { if ((0x6000 <= address) && (address < 0xB000)) { if (!(address & 0x1000)) { return nullptr; } else { return unmappedWrite; } } else if ((0xB000 <= address) && (address < 0xC000) && sramEnabled) { // write SRAM return nullptr; } else { return unmappedWrite; } } template void RomGameMaster2::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("sramOffset", sramOffset); ar.serialize("sramEnabled", sramEnabled); } INSTANTIATE_SERIALIZE_METHODS(RomGameMaster2); REGISTER_MSXDEVICE(RomGameMaster2, "RomGameMaster2"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomGameMaster2.hh000066400000000000000000000010401257557151200216370ustar00rootroot00000000000000#ifndef ROMGAMEMASTER2_HH #define ROMGAMEMASTER2_HH #include "RomBlocks.hh" namespace openmsx { class RomGameMaster2 final : public Rom4kBBlocks { public: RomGameMaster2(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: word sramOffset; bool sramEnabled; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomGeneric16kB.cc000066400000000000000000000013011257557151200215160ustar00rootroot00000000000000#include "RomGeneric16kB.hh" #include "serialize.hh" namespace openmsx { RomGeneric16kB::RomGeneric16kB(const DeviceConfig& config, Rom&& rom_) : Rom16kBBlocks(config, std::move(rom_)) { reset(EmuTime::dummy()); } void RomGeneric16kB::reset(EmuTime::param /*time*/) { setUnmapped(0); setRom(1, 0); setRom(2, 1); setUnmapped(3); } void RomGeneric16kB::writeMem(word address, byte value, EmuTime::param /*time*/) { setRom(address >> 14, value); } byte* RomGeneric16kB::getWriteCacheLine(word address) const { if ((0x4000 <= address) && (address < 0xC000)) { return nullptr; } else { return unmappedWrite; } } REGISTER_MSXDEVICE(RomGeneric16kB, "RomGeneric16kB"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomGeneric16kB.hh000066400000000000000000000006451257557151200215420ustar00rootroot00000000000000#ifndef ROMGENERIC16KB_HH #define ROMGENERIC16KB_HH #include "RomBlocks.hh" namespace openmsx { class RomGeneric16kB final : public Rom16kBBlocks { public: RomGeneric16kB(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomGeneric8kB.cc000066400000000000000000000013621257557151200214460ustar00rootroot00000000000000#include "RomGeneric8kB.hh" #include "serialize.hh" namespace openmsx { RomGeneric8kB::RomGeneric8kB(const DeviceConfig& config, Rom&& rom_) : Rom8kBBlocks(config, std::move(rom_)) { reset(EmuTime::dummy()); } void RomGeneric8kB::reset(EmuTime::param /*time*/) { setUnmapped(0); setUnmapped(1); for (int i = 2; i < 6; i++) { setRom(i, i - 2); } setUnmapped(6); setUnmapped(7); } void RomGeneric8kB::writeMem(word address, byte value, EmuTime::param /*time*/) { setRom(address >> 13, value); } byte* RomGeneric8kB::getWriteCacheLine(word address) const { if ((0x4000 <= address) && (address < 0xC000)) { return nullptr; } else { return unmappedWrite; } } REGISTER_MSXDEVICE(RomGeneric8kB, "RomGeneric8kB"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomGeneric8kB.hh000066400000000000000000000006401257557151200214560ustar00rootroot00000000000000#ifndef ROMGENERIC8KB_HH #define ROMGENERIC8KB_HH #include "RomBlocks.hh" namespace openmsx { class RomGeneric8kB final : public Rom8kBBlocks { public: RomGeneric8kB(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomHalnote.cc000066400000000000000000000106401257557151200211160ustar00rootroot00000000000000/* * HALNOTE mapper * * This info is extracted from the romMapperHalnote.c source in blueMSX, * implemented by white_cat. * * This is a 1024kB mapper, it's divided in 128 pages of 8kB. The last 512kB * can also be mapped as 256 pages of 2kB. There is 16kB SRAM. * * Main bankswitch registers: * bank 0, region: [0x4000-0x5FFF], switch addr: 0x4FFF * bank 1, region: [0x6000-0x7FFF], switch addr: 0x6FFF * bank 2, region: [0x8000-0x9FFF], switch addr: 0x8FFF * bank 3, region: [0xA000-0xBFFF], switch addr: 0xAFFF * Sub-bankswitch registers: * bank 0, region: [0x7000-0x77FF], switch addr: 0x77FF * bank 1, region: [0x7800-0x7FFF], switch addr: 0x7FFF * Note that the two sub-banks overlap with main bank 1! * * The upper bit (0x80) of the first two main bankswitch registers are special: * bank 0, bit7 SRAM enabled in [0x0000-0x3FFF] (1=enabled) * bank 1, bit7 submapper enabled in [0x7000-0x7FFF] (1=enabled) * If enabled, the submapper shadows (part of) main bank 1. */ #include "RomHalnote.hh" #include "CacheLine.hh" #include "SRAM.hh" #include "MSXException.hh" #include "serialize.hh" #include "memory.hh" namespace openmsx { RomHalnote::RomHalnote(const DeviceConfig& config, Rom&& rom_) : Rom8kBBlocks(config, std::move(rom_)) { if (rom.getSize() != 0x100000) { throw MSXException( "Rom for HALNOTE mapper must be exactly 1MB in size."); } sram = make_unique(getName() + " SRAM", 0x4000, config); reset(EmuTime::dummy()); } void RomHalnote::reset(EmuTime::param /*time*/) { subBanks[0] = subBanks[1] = 0; sramEnabled = false; subMapperEnabled = false; setUnmapped(0); setUnmapped(1); for (int i = 2; i < 6; i++) { setRom(i, 0); } setUnmapped(6); setUnmapped(7); } const byte* RomHalnote::getReadCacheLine(word address) const { if (subMapperEnabled && (0x7000 <= address) && (address < 0x8000)) { // sub-mapper int subBank = address < 0x7800 ? 0 : 1; return &rom[0x80000 + subBanks[subBank] * 0x800 + (address & 0x7FF)]; } else { // default mapper implementation return Rom8kBBlocks::getReadCacheLine(address); } } byte RomHalnote::readMem(word address, EmuTime::param /*time*/) { // all reads are cacheable, reuse that implementation return *RomHalnote::getReadCacheLine(address); } void RomHalnote::writeMem(word address, byte value, EmuTime::param /*time*/) { if (address < 0x4000) { // SRAM region if (sramEnabled) { sram->write(address, value); } } else if (address < 0xC000) { if ((address == 0x77FF) || (address == 0x7FFF)) { // sub-mapper bank switch region int subBank = address < 0x7800 ? 0 : 1; if (subBanks[subBank] != value) { subBanks[subBank] = value; if (subMapperEnabled) { invalidateMemCache( 0x7000 + subBank * 0x800, 0x800); } } } else if ((address & 0x1FFF) == 0x0FFF) { // normal bank switch region int bank = address >> 13; // 2-5 setRom(bank, value); if (bank == 2) { // sram enable/disable bool newSramEnabled = (value & 0x80) != 0; if (newSramEnabled != sramEnabled) { sramEnabled = newSramEnabled; if (sramEnabled) { setBank(0, &(*sram)[0x0000], value); setBank(1, &(*sram)[0x2000], value); } else { setUnmapped(0); setUnmapped(1); } } } else if (bank == 3) { // sub-mapper enable/disable bool newSubMapperEnabled = (value & 0x80) != 0; if (newSubMapperEnabled != subMapperEnabled) { subMapperEnabled = newSubMapperEnabled; invalidateMemCache(0x7000, 0x1000); } } } } } byte* RomHalnote::getWriteCacheLine(word address) const { if (address < 0x4000) { // SRAM region if (sramEnabled) { return nullptr; } } else if (address < 0xC000) { if (((address & CacheLine::HIGH) == (0x77FF & CacheLine::HIGH)) || ((address & CacheLine::HIGH) == (0x7FFF & CacheLine::HIGH))) { // sub-mapper bank switch region return nullptr; } else if ((address & 0x1FFF & CacheLine::HIGH) == (0x0FFF & CacheLine::HIGH)) { // normal bank switch region return nullptr; } } return unmappedWrite; } template void RomHalnote::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("subBanks", subBanks); ar.serialize("sramEnabled", sramEnabled); ar.serialize("subMapperEnabled", subMapperEnabled); } INSTANTIATE_SERIALIZE_METHODS(RomHalnote); REGISTER_MSXDEVICE(RomHalnote, "RomHalnote"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomHalnote.hh000066400000000000000000000012401257557151200211240ustar00rootroot00000000000000#ifndef ROMHALNOTE_HH #define ROMHALNOTE_HH #include "RomBlocks.hh" namespace openmsx { class RomHalnote final : public Rom8kBBlocks { public: RomHalnote(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; const byte* getReadCacheLine(word address) const override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: byte subBanks[2]; bool sramEnabled; bool subMapperEnabled; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomHarryFox.cc000066400000000000000000000022221257557151200212630ustar00rootroot00000000000000// This mapper is used for the game "Harry Fox Yki no Maoh" /** Thanks to enen for testing this on a real cartridge: * * Writing to 0x6xxx, 0 or 1, will switch bank 0 or 2 into 0x4000-0x7fff * Writing to 0x7xxx, 0 or 1, will switch bank 1 or 3 into 0x8000-0xbfff * 0x0000-0x3fff and 0xc000-0xffff are unmapped (read as 0xff) */ #include "RomHarryFox.hh" #include "serialize.hh" namespace openmsx { RomHarryFox::RomHarryFox(const DeviceConfig& config, Rom&& rom_) : Rom16kBBlocks(config, std::move(rom_)) { reset(EmuTime::dummy()); } void RomHarryFox::reset(EmuTime::param /*time*/) { setUnmapped(0); setRom(1, 0); setRom(2, 1); setUnmapped(3); } void RomHarryFox::writeMem(word address, byte value, EmuTime::param /*time*/) { if ((0x6000 <= address) && (address < 0x7000)) { setRom(1, 2 * (value & 1) + 0); } else if ((0x7000 <= address) && (address < 0x8000)) { setRom(2, 2 * (value & 1) + 1); } } byte* RomHarryFox::getWriteCacheLine(word address) const { if ((0x6000 <= address) && (address < 0x8000)) { return nullptr; } else { return unmappedWrite; } } REGISTER_MSXDEVICE(RomHarryFox, "RomHarryFox"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomHarryFox.hh000066400000000000000000000006311257557151200212770ustar00rootroot00000000000000#ifndef ROMHARRYFOX_HH #define ROMHARRYFOX_HH #include "RomBlocks.hh" namespace openmsx { class RomHarryFox final : public Rom16kBBlocks { public: RomHarryFox(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomHolyQuran.cc000066400000000000000000000017331257557151200214510ustar00rootroot00000000000000// Holy Qu'ran cartridge // It is like an ASCII 8KB, but using the 5000h, 5400h, 5800h and 5C00h // addresses. #include "RomHolyQuran.hh" #include "serialize.hh" namespace openmsx { RomHolyQuran::RomHolyQuran(const DeviceConfig& config, Rom&& rom_) : Rom8kBBlocks(config, std::move(rom_)) { reset(EmuTime::dummy()); } void RomHolyQuran::reset(EmuTime::param /*time*/) { setUnmapped(0); setUnmapped(1); for (int i = 2; i < 6; i++) { setRom(i, 0); } setUnmapped(6); setUnmapped(7); } void RomHolyQuran::writeMem(word address, byte value, EmuTime::param /*time*/) { // TODO are switch addresses mirrored? if ((0x5000 <= address) && (address < 0x6000)) { byte region = ((address >> 10) & 3) + 2; setRom(region, value); } } byte* RomHolyQuran::getWriteCacheLine(word address) const { if ((0x5000 <= address) && (address < 0x6000)) { return nullptr; } else { return unmappedWrite; } } REGISTER_MSXDEVICE(RomHolyQuran, "RomHolyQuran"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomHolyQuran.hh000066400000000000000000000006341257557151200214620ustar00rootroot00000000000000#ifndef ROMHOLYQURAN_HH #define ROMHOLYQURAN_HH #include "RomBlocks.hh" namespace openmsx { class RomHolyQuran final : public Rom8kBBlocks { public: RomHolyQuran(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomHolyQuran2.cc000066400000000000000000000066221257557151200215350ustar00rootroot00000000000000// Holy Qu'ran cartridge // It is like an ASCII 8KB, but using the 5000h, 5400h, 5800h and 5C00h // addresses. // // This is very similar to RomHolyQuran, but this mapper type works with the // encrypted ROM content. Thanks to n_n for implementing it in meisei and // sharing his implementation with us (and pointing us to it). #include "RomHolyQuran2.hh" #include "MSXCPU.hh" #include "MSXException.hh" #include "likely.hh" #include "outer.hh" #include "serialize.hh" namespace openmsx { static byte decryptLUT[256]; RomHolyQuran2::RomHolyQuran2(const DeviceConfig& config, Rom&& rom_) : MSXRom(config, std::move(rom_)) , romBlocks(*this) { // protection uses a simple rotation on databus, some lines inverted: // out0 = ~in3 out1 = in7 out2 = ~in5 out3 = ~in1 // out4 = in0 out5 = in4 out6 = ~in2 out7 = in6 for (int i = 0; i < 256; ++i) { decryptLUT[i] = (((i << 4) & 0x50) | ((i >> 3) & 0x05) | ((i << 1) & 0xa0) | ((i << 2) & 0x08) | ((i >> 6) & 0x02)) ^ 0x4d; } if (rom.getSize() != 0x100000) { // 1MB throw MSXException("Holy Quaran ROM should be exactly 1MB in size"); } reset(EmuTime::dummy()); } void RomHolyQuran2::reset(EmuTime::param /*time*/) { for (auto& b : bank) { b = &rom[0]; } decrypt = false; } byte RomHolyQuran2::readMem(word address, EmuTime::param time) { byte result = RomHolyQuran2::peekMem(address, time); if (unlikely(!decrypt)) { if (getCPU().isM1Cycle(address)) { // start decryption when we start executing the rom decrypt = true; } } return result; } byte RomHolyQuran2::peekMem(word address, EmuTime::param /*time*/) const { if ((0x4000 <= address) && (address < 0xc000)) { unsigned b = (address - 0x4000) >> 13; byte raw = bank[b][address & 0x1fff]; return decrypt ? decryptLUT[raw] : raw; } else { return 0xff; } } void RomHolyQuran2::writeMem(word address, byte value, EmuTime::param /*time*/) { // TODO are switch addresses mirrored? if ((0x5000 <= address) && (address < 0x6000)) { byte region = (address >> 10) & 3; bank[region] = &rom[(value & 127) * 0x2000]; } } const byte* RomHolyQuran2::getReadCacheLine(word address) const { if ((0x4000 <= address) && (address < 0xc000)) { return nullptr; } else { return unmappedRead; } } byte* RomHolyQuran2::getWriteCacheLine(word address) const { if ((0x5000 <= address) && (address < 0x6000)) { return nullptr; } else { return unmappedWrite; } } template void RomHolyQuran2::serialize(Archive& ar, unsigned /*version*/) { // skip MSXRom base class ar.template serializeBase(*this); unsigned b[4]; if (ar.isLoader()) { ar.serialize("banks", b); for (unsigned i = 0; i < 4; ++i) { bank[i] = &rom[(b[i] & 127) * 0x2000]; } } else { for (unsigned i = 0; i < 4; ++i) { b[i] = (bank[i] - &rom[0]) / 0x2000; } ar.serialize("banks", b); } ar.serialize("decrypt", decrypt); } INSTANTIATE_SERIALIZE_METHODS(RomHolyQuran2); REGISTER_MSXDEVICE(RomHolyQuran2, "RomHolyQuran2"); RomHolyQuran2::Blocks::Blocks(RomHolyQuran2& device_) : RomBlockDebuggableBase(device_) { } byte RomHolyQuran2::Blocks::read(unsigned address) { if ((address < 0x4000) || (address >= 0xc000)) return 255; unsigned page = (address - 0x4000) / 0x2000; auto& device = OUTER(RomHolyQuran2, romBlocks); return (device.bank[page] - &device.rom[0]) / 0x2000; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomHolyQuran2.hh000066400000000000000000000015521257557151200215440ustar00rootroot00000000000000#ifndef ROMHOLYQURAN2_HH #define ROMHOLYQURAN2_HH #include "MSXRom.hh" #include "RomBlockDebuggable.hh" namespace openmsx { class RomHolyQuran2 : public MSXRom { public: RomHolyQuran2(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte* getWriteCacheLine(word start) const override; template void serialize(Archive& ar, unsigned version); private: struct Blocks final : RomBlockDebuggableBase { Blocks(RomHolyQuran2& device); byte read(unsigned address) override; } romBlocks; const byte* bank[4]; bool decrypt; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomInfo.cc000066400000000000000000000225461257557151200204270ustar00rootroot00000000000000#include "RomInfo.hh" #include "StringOp.hh" #include "hash_map.hh" #include "stl.hh" #include "unreachable.hh" #include "xxhash.hh" #include #include using std::vector; using std::string; namespace openmsx { static inline RomType makeAlias(RomType type) { return static_cast(ROM_ALIAS | type); } static inline RomType removeAlias(RomType type) { return static_cast(type & ~ROM_ALIAS); } static inline bool isAlias(RomType type) { return (type & ROM_ALIAS) != 0; } static bool isInit = false; // This maps a name to a RomType. There can be multiple names (aliases) for the // same type. using RomTypeMap = hash_map; static RomTypeMap romTypeMap(256); // initial hashtable size // (big enough so that no rehash is needed) // This contains extra information for each RomType. This structure only // contains the primary (non-alias) romtypes. using RomTypeInfo = std::tuple< RomType, // rom type string_ref, // description unsigned>; // blockSize using RomTypeInfoMap = vector; using RomTypeInfoMapLess = LessTupleElement<0>; static RomTypeInfoMap romTypeInfoMap; static void init(RomType type, string_ref name, unsigned blockSize, string_ref description) { romTypeMap.emplace_noCapacityCheck_noDuplicateCheck(name, type); romTypeInfoMap.emplace_back(type, description, blockSize); } static void initAlias(RomType type, string_ref name) { romTypeMap.emplace_noCapacityCheck_noDuplicateCheck(name, makeAlias(type)); } static void init() { if (isInit) return; isInit = true; // Generic ROM types that don't exist in real ROMs // (should not occur in any database!) init(ROM_GENERIC_8KB, "8kB", 0x2000, "Generic 8kB"); init(ROM_GENERIC_16KB, "16kB", 0x4000, "Generic 16kB"); // ROM mapper types for normal software (mainly games) init(ROM_KONAMI, "Konami", 0x2000, "Konami MegaROM"); init(ROM_KONAMI_SCC, "KonamiSCC", 0x2000, "Konami with SCC"); init(ROM_KBDMASTER, "KeyboardMaster", 0x4000, "Konami Keyboard Master with VLM5030"); // officially plain 16K init(ROM_ASCII8, "ASCII8", 0x2000, "ASCII 8kB"); init(ROM_ASCII16, "ASCII16", 0x4000, "ASCII 16kB"); init(ROM_R_TYPE, "R-Type", 0x4000, "R-Type"); init(ROM_CROSS_BLAIM, "CrossBlaim", 0x4000, "Cross Blaim"); init(ROM_HARRY_FOX, "HarryFox", 0x4000, "Harry Fox"); init(ROM_HALNOTE, "Halnote", 0x2000, "Halnote"); init(ROM_ZEMINA80IN1, "Zemina80in1", 0x2000, "Zemina 80 in 1"); init(ROM_ZEMINA90IN1, "Zemina90in1", 0x2000, "Zemina 90 in 1"); init(ROM_ZEMINA126IN1, "Zemina126in1", 0x2000, "Zemina 126 in 1"); init(ROM_ASCII16_2, "ASCII16SRAM2", 0x4000, "ASCII 16kB with 2kB SRAM"); init(ROM_ASCII16_8, "ASCII16SRAM8", 0x4000, "ASCII 16kB with 8kB SRAM"); init(ROM_ASCII8_8, "ASCII8SRAM8", 0x2000, "ASCII 8kB with 8kB SRAM"); init(ROM_ASCII8_2, "ASCII8SRAM2", 0x2000, "ASCII 8kB with 2kB SRAM"); init(ROM_KOEI_8, "KoeiSRAM8", 0x2000, "Koei with 8kB SRAM"); init(ROM_KOEI_32, "KoeiSRAM32", 0x2000, "Koei with 32kB SRAM"); init(ROM_WIZARDRY, "Wizardry", 0x2000, "Wizardry"); init(ROM_GAME_MASTER2, "GameMaster2", 0x1000, "Konami's Game Master 2"); init(ROM_MAJUTSUSHI, "Majutsushi", 0x2000, "Hai no Majutsushi"); init(ROM_SYNTHESIZER, "Synthesizer", 0x4000, "Konami's Synthesizer"); // officially plain 32K init(ROM_PLAYBALL, "PlayBall", 0x4000, "Sony's PlayBall"); // officially plain 32K init(ROM_NETTOU_YAKYUU, "NettouYakyuu", 0x2000, "Nettou Yakuu"); init(ROM_HOLY_QURAN, "AlQuranDecoded", 0x2000, "Holy Qu'ran (pre-decrypted)"); init(ROM_HOLY_QURAN2, "AlQuran", 0x2000, "Holy Qu'ran"); init(ROM_PADIAL8, "Padial8", 0x2000, "Padial 8kB"); init(ROM_PADIAL16, "Padial16", 0x4000, "Padial 16kB"); init(ROM_SUPERLODERUNNER,"SuperLodeRunner",0x4000, "Super Lode Runner"); init(ROM_SUPERSWANGI, "SuperSwangi" ,0x4000, "Super Swangi"); init(ROM_MSXDOS2, "MSXDOS2", 0x4000, "MSX-DOS2"); init(ROM_MITSUBISHIMLTS2,"MitsubishiMLTS2",0x2000, "Mitsubishi ML-TS2 firmware"); init(ROM_MANBOW2, "Manbow2", 0x2000, "Manbow2"); init(ROM_MANBOW2_2, "Manbow2_2", 0x2000, "Manbow2 - Second Release"); init(ROM_HAMARAJANIGHT, "HamarajaNight", 0x2000, "Best of Hamaraja Night"); init(ROM_MEGAFLASHROMSCC,"MegaFlashRomScc",0x2000, "Mega Flash ROM SCC"); init(ROM_MATRAINK, "MatraInk", 0x0000, "Matra Ink"); init(ROM_ARC, "Arc", 0x4000, "Parallax' ARC"); // officially plain 32K init(ROM_DOOLY, "Dooly", 0x4000, "Baby Dinosaur Dooly"); // officially 32K blocksize, but spread over 2 pages init(ROM_MSXTRA, "MSXtra", 0x0000, "PTC MSXtra"); init(ROM_MULTIROM, "MultiRom", 0x0000, "MultiRom Collection"); init(ROM_MEGAFLASHROMSCCPLUS,"MegaFlashRomSccPlus",0x0000, "Mega Flash ROM SCC Plus"); init(ROM_MEGAFLASHROMSCCPLUSSD,"MegaFlashRomSccPlusSD",0x0000, "Mega Flash ROM SCC Plus SD"); // **** // ROM mapper types used for system ROMs in machines init(ROM_PANASONIC, "Panasonic", 0x2000, "Panasonic internal mapper"); init(ROM_NATIONAL, "National", 0x4000, "National internal mapper"); init(ROM_FSA1FM1, "FSA1FM1", 0x0000, "Panasonic FS-A1FM internal mapper 1"); // TODO: romblocks debuggable? init(ROM_FSA1FM2, "FSA1FM2", 0x2000, "Panasonic FS-A1FM internal mapper 2"); init(ROM_DRAM, "DRAM", 0x2000, "MSXturboR DRAM"); // Non-mapper ROM types init(ROM_MIRRORED, "Mirrored", 0x2000, "Plain rom, mirrored (any size)"); init(ROM_MIRRORED0000,"Mirrored0000",0x2000, "Plain rom, mirrored start at 0x0000"); init(ROM_MIRRORED4000,"Mirrored4000",0x2000, "Plain rom, mirrored start at 0x4000"); init(ROM_MIRRORED8000,"Mirrored8000",0x2000, "Plain rom, mirrored start at 0x8000"); init(ROM_MIRROREDC000,"MirroredC000",0x2000, "Plain rom, mirrored start at 0xC000"); init(ROM_NORMAL, "Normal", 0x2000, "Plain rom (any size)"); init(ROM_NORMAL0000, "Normal0000", 0x2000, "Plain rom start at 0x0000"); init(ROM_NORMAL4000, "Normal4000", 0x2000, "Plain rom start at 0x4000"); init(ROM_NORMAL8000, "Normal8000", 0x2000, "Plain rom start at 0x8000"); init(ROM_NORMALC000, "NormalC000", 0x2000, "Plain rom start at 0xC000"); init(ROM_PAGE0, "Page0", 0x2000, "Plain 16kB page 0"); init(ROM_PAGE1, "Page1", 0x2000, "Plain 16kB page 1"); init(ROM_PAGE2, "Page2", 0x2000, "Plain 16kB page 2 (BASIC)"); init(ROM_PAGE3, "Page3", 0x2000, "Plain 16kB page 3"); init(ROM_PAGE01, "Page01", 0x2000, "Plain 32kB page 0-1"); init(ROM_PAGE12, "Page12", 0x2000, "Plain 32kB page 1-2"); init(ROM_PAGE23, "Page23", 0x2000, "Plain 32kB page 2-3"); init(ROM_PAGE012, "Page012", 0x2000, "Plain 48kB page 0-2"); init(ROM_PAGE123, "Page123", 0x2000, "Plain 48kB page 1-3"); init(ROM_PAGE0123, "Page0123", 0x2000, "Plain 64kB"); // Alternative names for rom types, mainly for backwards compatibility initAlias(ROM_GENERIC_8KB, "0"); initAlias(ROM_GENERIC_8KB, "GenericKonami"); // probably actually used in a Zemina Box initAlias(ROM_GENERIC_16KB,"1"); initAlias(ROM_KONAMI_SCC, "2"); initAlias(ROM_KONAMI_SCC, "SCC"); initAlias(ROM_KONAMI_SCC, "KONAMI5"); initAlias(ROM_KONAMI, "KONAMI4"); initAlias(ROM_KONAMI, "3"); initAlias(ROM_ASCII8, "4"); initAlias(ROM_ASCII16, "5"); initAlias(ROM_MIRRORED, "64kB"); initAlias(ROM_MIRRORED, "Plain"); initAlias(ROM_NORMAL0000, "0x0000"); initAlias(ROM_NORMAL4000, "0x4000"); initAlias(ROM_NORMAL8000, "0x8000"); initAlias(ROM_NORMALC000, "0xC000"); initAlias(ROM_ASCII16_2, "HYDLIDE2"); initAlias(ROM_GAME_MASTER2,"RC755"); initAlias(ROM_NORMAL8000, "ROMBAS"); initAlias(ROM_R_TYPE, "RTYPE"); initAlias(ROM_ZEMINA80IN1, "KOREAN80IN1"); initAlias(ROM_ZEMINA90IN1, "KOREAN90IN1"); initAlias(ROM_ZEMINA126IN1,"KOREAN126IN1"); initAlias(ROM_HOLY_QURAN, "HolyQuran"); sort(begin(romTypeInfoMap), end(romTypeInfoMap), RomTypeInfoMapLess()); } static const RomTypeMap& getRomTypeMap() { init(); return romTypeMap; } static const RomTypeInfoMap& getRomTypeInfoMap() { init(); return romTypeInfoMap; } RomType RomInfo::nameToRomType(string_ref name) { auto& m = getRomTypeMap(); auto it = m.find(name); return (it != end(m)) ? removeAlias(it->second) : ROM_UNKNOWN; } string_ref RomInfo::romTypeToName(RomType type) { assert(!isAlias(type)); for (auto& p : getRomTypeMap()) { if (p.second == type) { return p.first; } } UNREACHABLE; return ""; } vector RomInfo::getAllRomTypes() { vector result; for (auto& p : getRomTypeMap()) { if (!isAlias(p.second)) { result.push_back(p.first); } } return result; } string_ref RomInfo::getDescription(RomType type) { auto& m = getRomTypeInfoMap(); auto it = lower_bound(begin(m), end(m), type, RomTypeInfoMapLess()); assert(it != end(m)); assert(std::get<0>(*it) == type); return std::get<1>(*it); } unsigned RomInfo::getBlockSize(RomType type) { auto& m = getRomTypeInfoMap(); auto it = lower_bound(begin(m), end(m), type, RomTypeInfoMapLess()); assert(it != end(m)); assert(std::get<0>(*it) == type); return std::get<2>(*it); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomInfo.hh000066400000000000000000000034631257557151200204360ustar00rootroot00000000000000#ifndef ROMINFO_HH #define ROMINFO_HH #include "RomTypes.hh" #include "String32.hh" #include "string_ref.hh" #include #include namespace openmsx { class RomInfo { public: RomInfo(String32 title_, String32 year_, String32 company_, String32 country_, bool original_, String32 origType_, String32 remark_, RomType romType_, int genMSXid_) : title (title_) , year (year_) , company (company_) , country (country_) , origType(origType_) , remark (std::move(remark_)) , romType(romType_) , genMSXid(genMSXid_) , original(original_) { } const string_ref getTitle (const char* buf) const { return fromString32(buf, title); } const string_ref getYear (const char* buf) const { return fromString32(buf, year); } const string_ref getCompany (const char* buf) const { return fromString32(buf, company); } const string_ref getCountry (const char* buf) const { return fromString32(buf, country); } const string_ref getOrigType(const char* buf) const { return fromString32(buf, origType); } const string_ref getRemark (const char* buf) const { return fromString32(buf, remark); } RomType getRomType() const { return romType; } bool getOriginal() const { return original; } int getGenMSXid() const { return genMSXid; } static RomType nameToRomType(string_ref name); static string_ref romTypeToName(RomType type); static std::vector getAllRomTypes(); static string_ref getDescription(RomType type); static unsigned getBlockSize (RomType type); private: String32 title; String32 year; String32 company; String32 country; String32 origType; String32 remark; RomType romType; int genMSXid; bool original; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomInfoTopic.cc000066400000000000000000000023201257557151200214120ustar00rootroot00000000000000#include "RomInfoTopic.hh" #include "RomInfo.hh" #include "TclObject.hh" #include "CommandException.hh" using std::vector; using std::string; namespace openmsx { RomInfoTopic::RomInfoTopic(InfoCommand& openMSXInfoCommand) : InfoTopic(openMSXInfoCommand, "romtype") { } void RomInfoTopic::execute(array_ref tokens, TclObject& result) const { switch (tokens.size()) { case 2: { result.addListElements(RomInfo::getAllRomTypes()); break; } case 3: { RomType type = RomInfo::nameToRomType(tokens[2].getString()); if (type == ROM_UNKNOWN) { throw CommandException("Unknown rom type"); } result.addListElement("description"); result.addListElement(RomInfo::getDescription(type)); result.addListElement("blocksize"); result.addListElement(int(RomInfo::getBlockSize(type))); break; } default: throw CommandException("Too many parameters"); } } string RomInfoTopic::help(const vector& /*tokens*/) const { return "Shows a list of supported rom types. " "Or show info on a specific rom type."; } void RomInfoTopic::tabCompletion(vector& tokens) const { if (tokens.size() == 3) { completeString(tokens, RomInfo::getAllRomTypes(), false); } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomInfoTopic.hh000066400000000000000000000007361257557151200214350ustar00rootroot00000000000000#ifndef ROMINFOTOPIC_HH #define ROMINFOTOPIC_HH #include "InfoTopic.hh" namespace openmsx { class RomInfoTopic final : public InfoTopic { public: explicit RomInfoTopic(InfoCommand& openMSXInfoCommand); void execute(array_ref tokens, TclObject& result) const override; std::string help(const std::vector& tokens) const override; void tabCompletion(std::vector& tokens) const override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomKonami.cc000066400000000000000000000037771257557151200207570ustar00rootroot00000000000000// KONAMI 8kB cartridges (without SCC) // // this type is used by Konami cartridges without SCC and some others // examples of cartridges: Nemesis, Penguin Adventure, Usas, Metal Gear, Shalom, // The Maze of Galious, Aleste 1, 1942, Heaven, Mystery World, ... // (the latter four are hacked ROM images with modified mappers) // // page at 4000 is fixed, other banks are switched by writting at // 0x6000, 0x8000 and 0xA000 (those addresses are used by the games, but any // other address in a page switches that page as well) #include "RomKonami.hh" #include "MSXMotherBoard.hh" #include "CliComm.hh" #include "serialize.hh" namespace openmsx { RomKonami::RomKonami(const DeviceConfig& config, Rom&& rom_) : Rom8kBBlocks(config, std::move(rom_)) { // Konami mapper is 256kB in size, even if ROM is smaller. setBlockMask(31); // warn if a ROM is used that would not work on a real Konami mapper if (rom.getSize() > 256 * 1024) { getMotherBoard().getMSXCliComm().printWarning("The size of " "this ROM image is larger than 256kB, which is " "not supported on real Konami mapper chips!"); } // Do not call reset() here, since it can be overridden and the subclass // constructor has not been run yet. And there will be a reset() at power // up anyway. } void RomKonami::reset(EmuTime::param /*time*/) { setUnmapped(0); setUnmapped(1); for (int i = 2; i < 6; i++) { setRom(i, i - 2); } setUnmapped(6); setUnmapped(7); } void RomKonami::writeMem(word address, byte value, EmuTime::param /*time*/) { // Note: [0x4000..0x6000) is fixed at segment 0. if (0x6000 <= address && address < 0xC000) { setRom(address >> 13, value); } } byte* RomKonami::getWriteCacheLine(word address) const { return (0x6000 <= address && address < 0xC000) ? nullptr : unmappedWrite; } template void RomKonami::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); } INSTANTIATE_SERIALIZE_METHODS(RomKonami); REGISTER_MSXDEVICE(RomKonami, "RomKonami"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomKonami.hh000066400000000000000000000010361257557151200207530ustar00rootroot00000000000000#ifndef ROMKONAMI_HH #define ROMKONAMI_HH #include "RomBlocks.hh" namespace openmsx { class RomKonami : public Rom8kBBlocks { public: RomKonami(const DeviceConfig& config, Rom&& rom); virtual ~RomKonami() {} void reset(EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); }; REGISTER_BASE_CLASS(RomKonami, "RomKonami"); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomKonamiKeyboardMaster.cc000066400000000000000000000036271257557151200236060ustar00rootroot00000000000000#include "RomKonamiKeyboardMaster.hh" #include "MSXCPUInterface.hh" #include "serialize.hh" #include "unreachable.hh" namespace openmsx { RomKonamiKeyboardMaster::RomKonamiKeyboardMaster( const DeviceConfig& config, Rom&& rom_) : Rom16kBBlocks(config, std::move(rom_)) , vlm5030("VLM5030", "Konami Keyboard Master's VLM5030", rom.getFilename(), config) { setUnmapped(0); setRom(1, 0); setUnmapped(2); setUnmapped(3); reset(EmuTime::dummy()); getCPUInterface().register_IO_Out(0x00, this); getCPUInterface().register_IO_Out(0x20, this); getCPUInterface().register_IO_In(0x00, this); getCPUInterface().register_IO_In(0x20, this); } RomKonamiKeyboardMaster::~RomKonamiKeyboardMaster() { getCPUInterface().unregister_IO_Out(0x00, this); getCPUInterface().unregister_IO_Out(0x20, this); getCPUInterface().unregister_IO_In(0x00, this); getCPUInterface().unregister_IO_In(0x20, this); } void RomKonamiKeyboardMaster::reset(EmuTime::param /*time*/) { vlm5030.reset(); } void RomKonamiKeyboardMaster::writeIO(word port, byte value, EmuTime::param time) { switch (port & 0xFF) { case 0x00: vlm5030.writeData(value); break; case 0x20: vlm5030.writeControl(value, time); break; default: UNREACHABLE; } } byte RomKonamiKeyboardMaster::readIO(word port, EmuTime::param time) { return RomKonamiKeyboardMaster::peekIO(port, time); } byte RomKonamiKeyboardMaster::peekIO(word port, EmuTime::param time) const { switch (port & 0xFF) { case 0x00: return vlm5030.getBSY(time) ? 0x10 : 0x00; case 0x20: return 0xFF; default: UNREACHABLE; return 0xFF; } } template void RomKonamiKeyboardMaster::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("VLM5030", vlm5030); } INSTANTIATE_SERIALIZE_METHODS(RomKonamiKeyboardMaster); REGISTER_MSXDEVICE(RomKonamiKeyboardMaster, "RomKonamiKeyboardMaster"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomKonamiKeyboardMaster.hh000066400000000000000000000012361257557151200236120ustar00rootroot00000000000000#ifndef ROMKONAMIKEYBOARDMASTER_HH #define ROMKONAMIKEYBOARDMASTER_HH #include "RomBlocks.hh" #include "VLM5030.hh" namespace openmsx { class RomKonamiKeyboardMaster final : public Rom16kBBlocks { public: RomKonamiKeyboardMaster(const DeviceConfig& config, Rom&& rom); ~RomKonamiKeyboardMaster(); void reset(EmuTime::param time) override; void writeIO(word port, byte value, EmuTime::param time) override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; template void serialize(Archive& ar, unsigned version); private: VLM5030 vlm5030; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomKonamiSCC.cc000066400000000000000000000066441257557151200213040ustar00rootroot00000000000000// KONAMI 8kB cartridges with SCC // // this type is used by Konami cartridges that do have an SCC and some others // examples of cartridges: Nemesis 2, Nemesis 3, King's Valley 2, Space Manbow // Solid Snake, Quarth, Ashguine 1, Animal, Arkanoid 2, ... // Those last 3 were probably modified ROM images, they should be ASCII8 // // The address to change banks: // bank 1: 0x5000 - 0x57ff (0x5000 used) // bank 2: 0x7000 - 0x77ff (0x7000 used) // bank 3: 0x9000 - 0x97ff (0x9000 used) // bank 4: 0xB000 - 0xB7ff (0xB000 used) #include "RomKonamiSCC.hh" #include "CacheLine.hh" #include "MSXMotherBoard.hh" #include "CliComm.hh" #include "serialize.hh" namespace openmsx { RomKonamiSCC::RomKonamiSCC(const DeviceConfig& config, Rom&& rom_) : Rom8kBBlocks(config, std::move(rom_)) , scc("SCC", config, getCurrentTime()) { // warn if a ROM is used that would not work on a real KonamiSCC mapper if (rom.getSize() > 512 * 1024) { getMotherBoard().getMSXCliComm().printWarning( "The size of this ROM image is larger than 512kB, " "which is not supported on real Konami SCC mapper " "chips!"); } powerUp(getCurrentTime()); } void RomKonamiSCC::powerUp(EmuTime::param time) { scc.powerUp(time); reset(time); } void RomKonamiSCC::reset(EmuTime::param time) { setUnmapped(0); setUnmapped(1); for (int i = 2; i < 6; i++) { setRom(i, i - 2); } setUnmapped(6); setUnmapped(7); sccEnabled = false; scc.reset(time); } byte RomKonamiSCC::peekMem(word address, EmuTime::param time) const { if (sccEnabled && (0x9800 <= address) && (address < 0xA000)) { return scc.peekMem(address & 0xFF, time); } else { return Rom8kBBlocks::peekMem(address, time); } } byte RomKonamiSCC::readMem(word address, EmuTime::param time) { if (sccEnabled && (0x9800 <= address) && (address < 0xA000)) { return scc.readMem(address & 0xFF, time); } else { return Rom8kBBlocks::readMem(address, time); } } const byte* RomKonamiSCC::getReadCacheLine(word address) const { if (sccEnabled && (0x9800 <= address) && (address < 0xA000)) { // don't cache SCC return nullptr; } else { return Rom8kBBlocks::getReadCacheLine(address); } } void RomKonamiSCC::writeMem(word address, byte value, EmuTime::param time) { if ((address < 0x5000) || (address >= 0xC000)) { return; } if (sccEnabled && (0x9800 <= address) && (address < 0xA000)) { // write to SCC scc.writeMem(address & 0xFF, value, time); return; } if ((address & 0xF800) == 0x9000) { // SCC enable/disable sccEnabled = ((value & 0x3F) == 0x3F); invalidateMemCache(0x9800, 0x0800); } if ((address & 0x1800) == 0x1000) { // page selection setRom(address >> 13, value); } } byte* RomKonamiSCC::getWriteCacheLine(word address) const { if ((address < 0x5000) || (address >= 0xC000)) { return unmappedWrite; } else if (sccEnabled && (0x9800 <= address) && (address < 0xA000)) { // write to SCC return nullptr; } else if ((address & 0xF800) == (0x9000 & CacheLine::HIGH)) { // SCC enable/disable return nullptr; } else if ((address & 0x1800) == (0x1000 & CacheLine::HIGH)) { // page selection return nullptr; } else { return unmappedWrite; } } template void RomKonamiSCC::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("scc", scc); ar.serialize("sccEnabled", sccEnabled); } INSTANTIATE_SERIALIZE_METHODS(RomKonamiSCC); REGISTER_MSXDEVICE(RomKonamiSCC, "RomKonamiSCC"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomKonamiSCC.hh000066400000000000000000000014061257557151200213050ustar00rootroot00000000000000#ifndef ROMKONAMISCC_HH #define ROMKONAMISCC_HH #include "RomBlocks.hh" #include "SCC.hh" namespace openmsx { class RomKonamiSCC final : public Rom8kBBlocks { public: RomKonamiSCC(const DeviceConfig& config, Rom&& rom); void powerUp(EmuTime::param time) override; void reset(EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; byte readMem(word address, EmuTime::param time) override; const byte* getReadCacheLine(word address) const override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: SCC scc; bool sccEnabled; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomMSXDOS2.cc000066400000000000000000000024251257557151200206250ustar00rootroot00000000000000#include "RomMSXDOS2.hh" #include "CacheLine.hh" #include "MSXException.hh" #include "serialize.hh" #include "unreachable.hh" namespace openmsx { RomMSXDOS2::RomMSXDOS2(const DeviceConfig& config, Rom&& rom_) : Rom16kBBlocks(config, std::move(rom_)) , range(rom[0x94]) { if ((range != 0x00) && (range != 0x60) && (range != 0x7f)) { throw MSXException("Invalid rom for MSXDOS2 mapper"); } reset(EmuTime::dummy()); } void RomMSXDOS2::reset(EmuTime::param /*time*/) { setUnmapped(0); setRom(1, 0); setUnmapped(2); setUnmapped(3); } void RomMSXDOS2::writeMem(word address, byte value, EmuTime::param /*time*/) { switch (range) { case 0x00: if (address != 0x7ff0) return; break; case 0x60: if ((address & 0xf000) != 0x6000) return; break; case 0x7f: if (address != 0x7ffe) return; break; default: UNREACHABLE; } setRom(1, value); } byte* RomMSXDOS2::getWriteCacheLine(word address) const { switch (range) { case 0x00: if (address == (0x7ff0 & CacheLine::HIGH)) return nullptr; break; case 0x60: if ((address & 0xf000) == 0x6000) return nullptr; break; case 0x7f: if (address == (0x7ffe & CacheLine::HIGH)) return nullptr; break; default: UNREACHABLE; } return unmappedWrite; } REGISTER_MSXDEVICE(RomMSXDOS2, "RomMSXDOS2"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomMSXDOS2.hh000066400000000000000000000006621257557151200206400ustar00rootroot00000000000000#ifndef ROMMSXDOS2_HH #define ROMMSXDOS2_HH #include "RomBlocks.hh" namespace openmsx { class RomMSXDOS2 final : public Rom16kBBlocks { public: RomMSXDOS2(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; private: const byte range; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomMSXtra.cc000066400000000000000000000030201257557151200206740ustar00rootroot00000000000000#include "RomMSXtra.hh" #include "serialize.hh" namespace openmsx { RomMSXtra::RomMSXtra(const DeviceConfig& config, Rom&& rom_) : MSXRom(config, std::move(rom_)) , ram(config, getName() + " RAM", "MSXtra RAM", 0x0800) { for (int i = 0; i < 0x800; ++i) { ram[i] = (i & 1) ? 0x5a : 0xa5; } } byte RomMSXtra::readMem(word address, EmuTime::param /*time*/) { if ((0x4000 <= address) && (address < 0x6000)) { return rom[address & 0x1fff]; } else if ((0x6000 <= address) && (address < 0x8000)) { return ram[address & 0x07ff]; } else { return 0xff; } } const byte* RomMSXtra::getReadCacheLine(word address) const { if ((0x4000 <= address) && (address < 0x6000)) { return &rom[address & 0x1fff]; } else if ((0x6000 <= address) && (address < 0x8000)) { return &ram[address & 0x07ff]; } else { return unmappedRead; } } // default peekMem() implementation is OK void RomMSXtra::writeMem(word address, byte value, EmuTime::param /*time*/) { if ((0x6000 <= address) && (address < 0x8000)) { ram[address & 0x07ff] = value; } } byte* RomMSXtra::getWriteCacheLine(word address) const { if ((0x6000 <= address) && (address < 0x8000)) { return const_cast(&ram[address & 0x07ff]); } else { return unmappedWrite; } } template void RomMSXtra::serialize(Archive& ar, unsigned /*version*/) { // skip MSXRom base class ar.template serializeBase(*this); ar.serialize("ram", ram); } INSTANTIATE_SERIALIZE_METHODS(RomMSXtra); REGISTER_MSXDEVICE(RomMSXtra, "RomMSXtra"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomMSXtra.hh000066400000000000000000000011061257557151200207110ustar00rootroot00000000000000#ifndef ROMMSXTRA_HH #define ROMMSXTRA_HH #include "MSXRom.hh" #include "Ram.hh" namespace openmsx { class RomMSXtra final : public MSXRom { public: RomMSXtra(const DeviceConfig& config, Rom&& rom); byte readMem(word address, EmuTime::param time) override; const byte* getReadCacheLine(word address) const override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: Ram ram; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomMajutsushi.cc000066400000000000000000000021161257557151200216570ustar00rootroot00000000000000// Mapper for "Hai no Majutsushi" from Konami. // It's a Konami mapper with a DAC. #include "RomMajutsushi.hh" #include "serialize.hh" namespace openmsx { RomMajutsushi::RomMajutsushi(const DeviceConfig& config, Rom&& rom_) : RomKonami(config, std::move(rom_)) , dac("Majutsushi-DAC", "Hai no Majutsushi's DAC", config) { } void RomMajutsushi::reset(EmuTime::param time) { RomKonami::reset(time); dac.reset(time); } void RomMajutsushi::writeMem(word address, byte value, EmuTime::param time) { if (0x5000 <= address && address < 0x6000) { dac.writeDAC(value, time); } else { RomKonami::writeMem(address, value, time); } } byte* RomMajutsushi::getWriteCacheLine(word address) const { return (0x5000 <= address && address < 0x6000) ? nullptr : RomKonami::getWriteCacheLine(address); } template void RomMajutsushi::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("DAC", dac); } INSTANTIATE_SERIALIZE_METHODS(RomMajutsushi); REGISTER_MSXDEVICE(RomMajutsushi, "RomMajutsushi"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomMajutsushi.hh000066400000000000000000000010361257557151200216710ustar00rootroot00000000000000#ifndef ROMMAJUTSUSHI_HH #define ROMMAJUTSUSHI_HH #include "RomKonami.hh" #include "DACSound8U.hh" namespace openmsx { class RomMajutsushi final : public RomKonami { public: RomMajutsushi(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: DACSound8U dac; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomManbow2.cc000066400000000000000000000125331257557151200210340ustar00rootroot00000000000000#include "RomManbow2.hh" #include "AY8910.hh" #include "DummyAY8910Periphery.hh" #include "MSXCPUInterface.hh" #include "serialize.hh" #include "memory.hh" #include #include namespace openmsx { static std::vector getSectorInfo(RomType type) { std::vector sectorInfo(512 / 64, {0x10000, true}); // none writable assert((type == ROM_MANBOW2) || (type == ROM_MEGAFLASHROMSCC) || (type == ROM_MANBOW2_2) || (type == ROM_HAMARAJANIGHT)); switch (type) { case ROM_MANBOW2: case ROM_MANBOW2_2: // only the last 64kb is writeable sectorInfo[7].writeProtected = false; break; case ROM_HAMARAJANIGHT: // only 128kb is writeable sectorInfo[4].writeProtected = false; sectorInfo[5].writeProtected = false; break; default: // fully writeable for (auto& i : sectorInfo) i.writeProtected = false; } return sectorInfo; } RomManbow2::RomManbow2(const DeviceConfig& config, Rom&& rom_, RomType type) : MSXRom(config, std::move(rom_)) , scc(getName() + " SCC", config, getCurrentTime()) , psg(((type == ROM_MANBOW2_2) || (type == ROM_HAMARAJANIGHT)) ? make_unique( getName() + " PSG", DummyAY8910Periphery::instance(), config, getCurrentTime()) : nullptr) , flash(rom, getSectorInfo(type), 0x01A4, false, config) , romBlockDebug(*this, bank, 0x4000, 0x8000, 13) { powerUp(getCurrentTime()); if (psg) { getCPUInterface().register_IO_Out(0x10, this); getCPUInterface().register_IO_Out(0x11, this); getCPUInterface().register_IO_In (0x12, this); } } RomManbow2::~RomManbow2() { if (psg) { getCPUInterface().unregister_IO_Out(0x10, this); getCPUInterface().unregister_IO_Out(0x11, this); getCPUInterface().unregister_IO_In (0x12, this); } } void RomManbow2::powerUp(EmuTime::param time) { scc.powerUp(time); reset(time); } void RomManbow2::reset(EmuTime::param time) { for (int i = 0; i < 4; i++) { setRom(i, i); } sccEnabled = false; scc.reset(time); if (psg) { psgLatch = 0; psg->reset(time); } flash.reset(); } void RomManbow2::setRom(unsigned region, unsigned block) { assert(region < 4); unsigned nrBlocks = flash.getSize() / 0x2000; bank[region] = block & (nrBlocks - 1); invalidateMemCache(0x4000 + region * 0x2000, 0x2000); } byte RomManbow2::peekMem(word address, EmuTime::param time) const { if (sccEnabled && (0x9800 <= address) && (address < 0xA000)) { return scc.peekMem(address & 0xFF, time); } else if ((0x4000 <= address) && (address < 0xC000)) { unsigned page = (address - 0x4000) / 0x2000; unsigned addr = (address & 0x1FFF) + 0x2000 * bank[page]; return flash.peek(addr); } else { return 0xFF; } } byte RomManbow2::readMem(word address, EmuTime::param time) { if (sccEnabled && (0x9800 <= address) && (address < 0xA000)) { return scc.readMem(address & 0xFF, time); } else if ((0x4000 <= address) && (address < 0xC000)) { unsigned page = (address - 0x4000) / 0x2000; unsigned addr = (address & 0x1FFF) + 0x2000 * bank[page]; return flash.read(addr); } else { return 0xFF; } } const byte* RomManbow2::getReadCacheLine(word address) const { if (sccEnabled && (0x9800 <= address) && (address < 0xA000)) { return nullptr; } else if ((0x4000 <= address) && (address < 0xC000)) { unsigned page = (address - 0x4000) / 0x2000; unsigned addr = (address & 0x1FFF) + 0x2000 * bank[page]; return flash.getReadCacheLine(addr); } else { return unmappedRead; } } void RomManbow2::writeMem(word address, byte value, EmuTime::param time) { if (sccEnabled && (0x9800 <= address) && (address < 0xA000)) { // write to SCC scc.writeMem(address & 0xff, value, time); // note: writes to SCC also go to flash // thanks to 'enen' for testing this } if ((0x4000 <= address) && (address < 0xC000)) { unsigned page = (address - 0x4000) / 0x2000; unsigned addr = (address & 0x1FFF) + 0x2000 * bank[page]; flash.write(addr, value); if ((address & 0xF800) == 0x9000) { // SCC enable/disable sccEnabled = ((value & 0x3F) == 0x3F); invalidateMemCache(0x9800, 0x0800); } if ((address & 0x1800) == 0x1000) { // page selection setRom(page, value); } } } byte* RomManbow2::getWriteCacheLine(word address) const { if ((0x4000 <= address) && (address < 0xC000)) { return nullptr; } else { return unmappedWrite; } } byte RomManbow2::readIO(word port, EmuTime::param time) { assert((port & 0xFF) == 0x12); (void)port; return psg->readRegister(psgLatch, time); } byte RomManbow2::peekIO(word port, EmuTime::param time) const { assert((port & 0xFF) == 0x12); (void)port; return psg->peekRegister(psgLatch, time); } void RomManbow2::writeIO(word port, byte value, EmuTime::param time) { if ((port & 0xFF) == 0x10) { psgLatch = value & 0x0F; } else { assert((port & 0xFF) == 0x11); psg->writeRegister(psgLatch, value, time); } } // version 1: initial version // version 2: added optional built-in PSG template void RomManbow2::serialize(Archive& ar, unsigned version) { // skip MSXRom base class ar.template serializeBase(*this); ar.serialize("scc", scc); if ((ar.versionAtLeast(version, 2)) && psg) { ar.serialize("psg", *psg); ar.serialize("psgLatch", psgLatch); } ar.serialize("flash", flash); ar.serialize("bank", bank); ar.serialize("sccEnabled", sccEnabled); } INSTANTIATE_SERIALIZE_METHODS(RomManbow2); REGISTER_MSXDEVICE(RomManbow2, "RomManbow2"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomManbow2.hh000066400000000000000000000024721257557151200210470ustar00rootroot00000000000000#ifndef ROMMANBOW2_HH #define ROMMANBOW2_HH #include "MSXRom.hh" #include "RomTypes.hh" #include "SCC.hh" #include "AmdFlash.hh" #include "RomBlockDebuggable.hh" #include "serialize_meta.hh" #include namespace openmsx { class AY8910; class RomManbow2 final : public MSXRom { public: RomManbow2(const DeviceConfig& config, Rom&& rom, RomType type); ~RomManbow2(); void powerUp(EmuTime::param time) override; void reset(EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; byte readMem(word address, EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word address) const override; byte* getWriteCacheLine(word address) const override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: void setRom(unsigned region, unsigned block); SCC scc; const std::unique_ptr psg; // can be nullptr AmdFlash flash; RomBlockDebuggable romBlockDebug; byte psgLatch; byte bank[4]; bool sccEnabled; }; SERIALIZE_CLASS_VERSION(RomManbow2, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomMatraInk.cc000066400000000000000000000023221257557151200212300ustar00rootroot00000000000000#include "RomMatraInk.hh" #include "serialize.hh" namespace openmsx { RomMatraInk::RomMatraInk(const DeviceConfig& config, Rom&& rom_) : MSXRom(config, std::move(rom_)) , flash(rom, std::vector(2, {0x10000, false}), 0x01A4, false, config, false) // don't load/save { reset(EmuTime::dummy()); } void RomMatraInk::reset(EmuTime::param /*time*/) { flash.reset(); } byte RomMatraInk::peekMem(word address, EmuTime::param /*time*/) const { return flash.peek(address); } byte RomMatraInk::readMem(word address, EmuTime::param /*time*/) { return flash.read(address); } void RomMatraInk::writeMem(word address, byte value, EmuTime::param /*time*/) { flash.write(address + 0x10000, value); } const byte* RomMatraInk::getReadCacheLine(word address) const { return flash.getReadCacheLine(address); } byte* RomMatraInk::getWriteCacheLine(word /*address*/) const { return nullptr; } template void RomMatraInk::serialize(Archive& ar, unsigned /*version*/) { // skip MSXRom base class ar.template serializeBase(*this); ar.serialize("flash", flash); } INSTANTIATE_SERIALIZE_METHODS(RomMatraInk); REGISTER_MSXDEVICE(RomMatraInk, "RomMatraInk"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomMatraInk.hh000066400000000000000000000013061257557151200212430ustar00rootroot00000000000000#ifndef ROMMATRAINK_HH #define ROMMATRAINK_HH #include "MSXRom.hh" #include "AmdFlash.hh" namespace openmsx { class RomMatraInk final : public MSXRom { public: RomMatraInk(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; byte readMem(word address, EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word address) const override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: AmdFlash flash; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomMitsubishiMLTS2.cc000066400000000000000000000053101257557151200224240ustar00rootroot00000000000000#include "RomMitsubishiMLTS2.hh" #include "CacheLine.hh" #include "serialize.hh" #include namespace openmsx { RomMitsubishiMLTS2::RomMitsubishiMLTS2(const DeviceConfig& config, Rom&& rom_) : Rom8kBBlocks(config, std::move(rom_)) , ram(config, getName() + " RAM", "ML-TS2 RAM", 0x2000) { reset(EmuTime::dummy()); } void RomMitsubishiMLTS2::reset(EmuTime::param /*time*/) { setRom(2, 0); } void RomMitsubishiMLTS2::writeMem(word address, byte value, EmuTime::param /*time*/) { // TODO What are these 4 registers? Are there any more? // Is there any mirroring going on? if (address == 0x7f00) { // TODO } else if (address == 0x7f01) { // TODO } else if (address == 0x7f02) { // TODO } else if (address == 0x7f03) { // TODO } else if (address == 0x7FC0) { byte bank = ((value & 0x10) >> 2) | // transform bit-pattern ((value & 0x04) >> 1) | // ...a.b.c ((value & 0x01) >> 0); // into 00000abc std::cerr << "Setting MLTS2 mapper page 1 to bank " << int(bank) << std::endl; setRom(2, bank); } else if ((0x6000 <= address) && (address < 0x8000)) { ram[address & 0x1FFF] = value; } } byte RomMitsubishiMLTS2::readMem(word address, EmuTime::param time) { return peekMem(address, time); } byte RomMitsubishiMLTS2::peekMem(word address, EmuTime::param time) const { // TODO What are these 4 registers? Are there any more? // Is there any mirroring going on? // Is the bank select register readable? (0x7fc0) if (address == 0x7f00) { return 0xff; // TODO } else if (address == 0x7f01) { return 0xff; // TODO } else if (address == 0x7f02) { return 0xff; // TODO } else if (address == 0x7f03) { return 0xff; // TODO } else if ((0x6000 <= address) && (address < 0x8000)) { return ram[address & 0x1FFF]; } else { return Rom8kBBlocks::peekMem(address, time); } } const byte* RomMitsubishiMLTS2::getReadCacheLine(word address) const { if (address == (0x7FC0 & CacheLine::HIGH)) return nullptr; if ((0x6000 <= address) && (address < 0x8000)) { return &ram[address & 0x1FFF]; } return Rom8kBBlocks::getReadCacheLine(address); } byte* RomMitsubishiMLTS2::getWriteCacheLine(word address) const { if (address == (0x7FC0 & CacheLine::HIGH)) return nullptr; if ((0x6000 <= address) && (address < 0x8000)) { return const_cast(&ram[address & 0x1FFF]); } return unmappedWrite; } template void RomMitsubishiMLTS2::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); } INSTANTIATE_SERIALIZE_METHODS(RomMitsubishiMLTS2); REGISTER_MSXDEVICE(RomMitsubishiMLTS2, "RomMitsubishiMLTS2"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomMitsubishiMLTS2.hh000066400000000000000000000016241257557151200224420ustar00rootroot00000000000000#ifndef ROMMITSUBISHIMLTS2_HH #define ROMMITSUBISHIMLTS2_HH #include "RomBlocks.hh" #include "Ram.hh" // PLEASE NOTE! // // This mapper is work in progress. It's just a guess based on some reverse // engineering of the ROM by BiFi. It even contains some debug prints :) namespace openmsx { class Ram; class RomMitsubishiMLTS2 final : public Rom8kBBlocks { public: RomMitsubishiMLTS2(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; byte* getWriteCacheLine(word address) const override; const byte* getReadCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: Ram ram; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomMultiRom.cc000066400000000000000000000013111257557151200212670ustar00rootroot00000000000000// // Manuel Pazos MultiROM Collection // #include "RomMultiRom.hh" #include "serialize.hh" namespace openmsx { RomMultiRom::RomMultiRom(const DeviceConfig& config, Rom&& rom_) : Rom16kBBlocks(config, std::move(rom_)) { counter = 0; for (int i=0; i<4; i++) setRom(i, counter * 4 + i); } void RomMultiRom::reset(EmuTime::param /*time*/) { ++counter &= 7; for (int i=0; i<4; i++) setRom(i, counter * 4 + i); } template void RomMultiRom::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("counter", counter); } INSTANTIATE_SERIALIZE_METHODS(RomMultiRom); REGISTER_MSXDEVICE(RomMultiRom, "RomMultiRom"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomMultiRom.hh000066400000000000000000000006041257557151200213050ustar00rootroot00000000000000// #ifndef ROMMULTIROM_HH #define ROMMULTIROM_HH #include "RomBlocks.hh" namespace openmsx { class RomMultiRom final : public Rom16kBBlocks { public: RomMultiRom(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: byte counter; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomNational.cc000066400000000000000000000061451257557151200212760ustar00rootroot00000000000000#include "RomNational.hh" #include "CacheLine.hh" #include "SRAM.hh" #include "serialize.hh" #include "memory.hh" namespace openmsx { RomNational::RomNational(const DeviceConfig& config, Rom&& rom_) : Rom16kBBlocks(config, std::move(rom_)) { sram = make_unique(getName() + " SRAM", 0x1000, config); reset(EmuTime::dummy()); } void RomNational::reset(EmuTime::param /*time*/) { control = 0; for (int region = 0; region < 4; ++region) { setRom(region, 0); bankSelect[region] = 0; } sramAddr = 0; // TODO check this } byte RomNational::peekMem(word address, EmuTime::param time) const { if ((control & 0x04) && ((address & 0x7FF9) == 0x7FF0)) { // TODO check mirrored // 7FF0 7FF2 7FF4 7FF6 bank select read back int bank = (address & 6) / 2; return bankSelect[bank]; } if ((control & 0x02) && ((address & 0x3FFF) == 0x3FFD)) { // SRAM read return (*sram)[sramAddr & 0x0FFF]; } return Rom16kBBlocks::peekMem(address, time); } byte RomNational::readMem(word address, EmuTime::param time) { byte result = RomNational::peekMem(address, time); if ((control & 0x02) && ((address & 0x3FFF) == 0x3FFD)) { ++sramAddr; } return result; } const byte* RomNational::getReadCacheLine(word address) const { if ((0x3FF0 & CacheLine::HIGH) == (address & 0x3FFF)) { return nullptr; } else { return Rom16kBBlocks::getReadCacheLine(address); } } void RomNational::writeMem(word address, byte value, EmuTime::param /*time*/) { // TODO bank switch address mirrored? if (address == 0x6000) { bankSelect[1] = value; setRom(1, value); // !! } else if (address == 0x6400) { bankSelect[0] = value; setRom(0, value); // !! } else if (address == 0x7000) { bankSelect[2] = value; setRom(2, value); } else if (address == 0x7400) { bankSelect[3] = value; setRom(3, value); } else if (address == 0x7FF9) { // write control byte control = value; } else if (control & 0x02) { address &= 0x3FFF; if (address == 0x3FFA) { // SRAM address bits 23-16 sramAddr = (sramAddr & 0x00FFFF) | value << 16; } else if (address == 0x3FFB) { // SRAM address bits 15-8 sramAddr = (sramAddr & 0xFF00FF) | value << 8; } else if (address == 0x3FFC) { // SRAM address bits 7-0 sramAddr = (sramAddr & 0xFFFF00) | value; } else if (address == 0x3FFD) { sram->write((sramAddr++ & 0x0FFF), value); } } } byte* RomNational::getWriteCacheLine(word address) const { if ((address == (0x6000 & CacheLine::HIGH)) || (address == (0x6400 & CacheLine::HIGH)) || (address == (0x7000 & CacheLine::HIGH)) || (address == (0x7400 & CacheLine::HIGH)) || (address == (0x7FF9 & CacheLine::HIGH))) { return nullptr; } else if ((address & 0x3FFF) == (0x3FFA & CacheLine::HIGH)) { return nullptr; } else { return unmappedWrite; } } template void RomNational::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("control", control); ar.serialize("sramAddr", sramAddr); ar.serialize("bankSelect", bankSelect); } INSTANTIATE_SERIALIZE_METHODS(RomNational); REGISTER_MSXDEVICE(RomNational, "RomNational"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomNational.hh000066400000000000000000000013331257557151200213020ustar00rootroot00000000000000#ifndef ROMNATIONAL_HH #define ROMNATIONAL_HH #include "RomBlocks.hh" namespace openmsx { class RomNational final : public Rom16kBBlocks { public: RomNational(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; byte readMem(word address, EmuTime::param time) override; const byte* getReadCacheLine(word address) const override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: int sramAddr; byte control; byte bankSelect[4]; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomNettouYakyuu.cc000066400000000000000000000073011257557151200222120ustar00rootroot00000000000000// Based on information from hap/enen, published here: // http://www.msx.org/forumtopicl8552.html // and his implementation in blueMSX // // Jaleco - Moero!! Nettou Yakyuu '88, 256KB ROM, CRC32 68745C64 // NEC D7756C 146 sample player with internal 256Kbit ROM, undumped. Same // samples as Famicom Moero!! Pro Yakyuu (Black/Red), recording available at // http://home.planet.nl/~haps/segali_archive/ // 4*8KB pages at $4000-$BFFF, initialized to 0. // Mapper chip is exactly the same as ASCII8, except for data bit 7: // a:011ppxxx xxxxxxxx d:sxxbbbbb // a:address, d:data, p: page, b:bank, x:don't care // s:if set, page reads/writes redirect to sample player via a 74HC174, eg. // write $80 to $7800 -> $A000-$BFFF = sample player I/O // Sample player (datasheet, 690KB one on // http://www.datasheetarchive.com/search.php?t=0&q=uPD7756 ): // reads: always $FF (/BUSY not used) // writes: d:rsxxiiii // i:I0-I3 (sample number, latched on /ST rising edge when not playing) // s:/ST (start), r:1=enable continuous rising edges to /ST (ORed with s), // 0=falling edge to /RESET // As I'm unable to measure, s and r bits pin setups are guessed from knowing // the behaviour (examples underneath). // examples: // - $C0: nothing // - $80: "strike, strike, strike, strike, ..." // - $80, $C0: "strike" // - $80, short delay, $00/$40: "stri" // - $80, short delay, $81: "strike, ball, ball, ball, ball, ..." // note: writes to sample player are ignored if on page $6000-$7FFF, possibly // due to conflict with mapper chip, reads still return $FF though. // regarding this implementation: part of the above behaviour is put in the // sample player. It may be a good idea to move this behaviour to a more // specialized class some time in the future. #include "RomNettouYakyuu.hh" #include "FileOperations.hh" #include "serialize.hh" namespace openmsx { RomNettouYakyuu::RomNettouYakyuu(const DeviceConfig& config, Rom&& rom_) : Rom8kBBlocks(config, std::move(rom_)) , samplePlayer( "Nettou Yakyuu-DAC", "Jaleco Moero!! Nettou Yakuu '88 DAC", config, FileOperations::stripExtension(rom.getFilename()) + '_', 16, "nettou_yakyuu/nettou_yakyuu_") { reset(EmuTime::dummy()); } void RomNettouYakyuu::reset(EmuTime::param /*time*/) { // ASCII8 behaviour setUnmapped(0); setUnmapped(1); for (int i = 2; i < 6; i++) { setRom(i, 0); redirectToSamplePlayer[i - 2] = false; } setUnmapped(6); setUnmapped(7); samplePlayer.reset(); } void RomNettouYakyuu::writeMem(word address, byte value, EmuTime::param /*time*/) { if ((address < 0x4000) || (0xC000 <= address)) return; // mapper stuff, like ASCII8 if ((0x6000 <= address) && (address < 0x8000)) { // calculate region in switch zone byte region = (address >> 11) & 3; redirectToSamplePlayer[region] = (value & 0x80) != 0; if (redirectToSamplePlayer[region]) { setUnmapped(region + 2); } else { setRom(region + 2, value); } return; } // sample player stuff if (!redirectToSamplePlayer[(address >> 13) - 2]) { // region not redirected to sample player return; } // bit 7==0: reset if (!(value & 0x80)) { samplePlayer.reset(); return; } // bit 6==1: set no retrigger, don't alter playing sample if (value & 0x40) { samplePlayer.stopRepeat(); return; } samplePlayer.repeat(value & 0xF); } byte* RomNettouYakyuu::getWriteCacheLine(word /*address*/) const { return nullptr; } template void RomNettouYakyuu::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("SamplePlayer", samplePlayer); ar.serialize("redirectToSamplePlayer", redirectToSamplePlayer); } INSTANTIATE_SERIALIZE_METHODS(RomNettouYakyuu); REGISTER_MSXDEVICE(RomNettouYakyuu, "RomNettouYakyuu"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomNettouYakyuu.hh000066400000000000000000000013141257557151200222220ustar00rootroot00000000000000#ifndef ROMNETTOUYAKYUU_HH #define ROMNETTOUYAKYUU_HH #include "RomBlocks.hh" #include "SamplePlayer.hh" namespace openmsx { class RomNettouYakyuu final : public Rom8kBBlocks { public: RomNettouYakyuu(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: SamplePlayer samplePlayer; // remember per region if writes are for the sample player or not // there are 4 x 8kB regions in [0x4000-0xBFFF] bool redirectToSamplePlayer[4]; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomPadial16kB.cc000066400000000000000000000010651257557151200213430ustar00rootroot00000000000000// Padial 16kB // // The address to change banks: // first 16kb: 0x6000 - 0x67ff (0x6000 used) // second 16kb: 0x7000 - 0x77ff (0x7000 and 0x77ff used) #include "RomPadial16kB.hh" #include "serialize.hh" namespace openmsx { RomPadial16kB::RomPadial16kB(const DeviceConfig& config, Rom&& rom_) : RomAscii16kB(config, std::move(rom_)) { reset(EmuTime::dummy()); } void RomPadial16kB::reset(EmuTime::param /*time*/) { setRom(0, 0); setRom(1, 0); setRom(2, 2); setUnmapped(3); } REGISTER_MSXDEVICE(RomPadial16kB, "RomPadial16kB"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomPadial16kB.hh000066400000000000000000000004441257557151200213550ustar00rootroot00000000000000#ifndef ROMPADIAL16KB_HH #define ROMPADIAL16KB_HH #include "RomAscii16kB.hh" namespace openmsx { class RomPadial16kB final : public RomAscii16kB { public: RomPadial16kB(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomPadial8kB.cc000066400000000000000000000012441257557151200212630ustar00rootroot00000000000000// Padial 8kB // // The address to change banks: // bank 1: 0x6000 - 0x67ff (0x6000 used) // bank 2: 0x6800 - 0x6fff (0x6800 used) // bank 3: 0x7000 - 0x77ff (0x7000 used) // bank 4: 0x7800 - 0x7fff (0x7800 used) #include "RomPadial8kB.hh" #include "serialize.hh" namespace openmsx { RomPadial8kB::RomPadial8kB(const DeviceConfig& config, Rom&& rom_) : RomAscii8kB(config, std::move(rom_)) { reset(EmuTime::dummy()); } void RomPadial8kB::reset(EmuTime::param /*time*/) { setRom(0, 0); setRom(1, 0); for (int i = 2; i < 6; ++i) { setRom(i, i - 2); } setUnmapped(6); setUnmapped(7); } REGISTER_MSXDEVICE(RomPadial8kB, "RomPadial8kB"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomPadial8kB.hh000066400000000000000000000004361257557151200212770ustar00rootroot00000000000000#ifndef ROMPADIAL8KB_HH #define ROMPADIAL8KB_HH #include "RomAscii8kB.hh" namespace openmsx { class RomPadial8kB final : public RomAscii8kB { public: RomPadial8kB(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomPageNN.cc000066400000000000000000000007451257557151200206410ustar00rootroot00000000000000#include "RomPageNN.hh" #include "serialize.hh" namespace openmsx { RomPageNN::RomPageNN(const DeviceConfig& config, Rom&& rom_, byte pages) : Rom8kBBlocks(config, std::move(rom_)) { int bank = 0; for (int page = 0; page < 4; ++page) { if (pages & (1 << page)) { setRom(page * 2 + 0, bank++); setRom(page * 2 + 1, bank++); } else { setUnmapped(page * 2 + 0); setUnmapped(page * 2 + 1); } } } REGISTER_MSXDEVICE(RomPageNN, "RomPageNN"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomPageNN.hh000066400000000000000000000003611257557151200206450ustar00rootroot00000000000000#ifndef ROMPAGENN_HH #define ROMPAGENN_HH #include "RomBlocks.hh" namespace openmsx { class RomPageNN final : public Rom8kBBlocks { public: RomPageNN(const DeviceConfig& config, Rom&& rom, byte pages); }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomPanasonic.cc000066400000000000000000000121271257557151200214410ustar00rootroot00000000000000#include "RomPanasonic.hh" #include "PanasonicMemory.hh" #include "MSXMotherBoard.hh" #include "DeviceConfig.hh" #include "SRAM.hh" #include "CacheLine.hh" #include "serialize.hh" #include "memory.hh" namespace openmsx { const int SRAM_BASE = 0x80; const int RAM_BASE = 0x180; RomPanasonic::RomPanasonic(const DeviceConfig& config, Rom&& rom_) : Rom8kBBlocks(config, std::move(rom_)) , panasonicMem(getMotherBoard().getPanasonicMemory()) { unsigned sramSize = config.getChildDataAsInt("sramsize", 0); if (sramSize) { sram = make_unique( getName() + " SRAM", sramSize * 1024, config); } if (config.getChildDataAsBool("sram-mirrored", false)) { maxSRAMBank = SRAM_BASE + 8; } else { maxSRAMBank = SRAM_BASE + (sramSize / 8); } // Inform baseclass about PanasonicMemory (needed for serialization). // This relies on the order of MSXDevice instantiation, the PanasonicRam // device must be create before this one. (In the hardwareconfig.xml // we added a device-reference from this device to the PanasonicRam // device, this should protected against wrong future edits in the // config file). setExtraMemory(panasonicMem.getRamBlock(0), panasonicMem.getRamSize()); reset(EmuTime::dummy()); } void RomPanasonic::reset(EmuTime::param /*time*/) { control = 0; for (int region = 0; region < 8; ++region) { bankSelect[region] = 0; setRom(region, 0); } } byte RomPanasonic::peekMem(word address, EmuTime::param time) const { byte result; if ((control & 0x04) && (0x7FF0 <= address) && (address < 0x7FF8)) { // read mapper state (lower 8 bit) result = bankSelect[address & 7] & 0xFF; } else if ((control & 0x10) && (address == 0x7FF8)) { // read mapper state (9th bit) result = 0; for (int i = 7; i >= 0; i--) { result <<= 1; if (bankSelect[i] & 0x100) { result++; } } } else if ((control & 0x08) && (address == 0x7FF9)) { // read control byte result = control; } else { result = Rom8kBBlocks::peekMem(address, time); } return result; } byte RomPanasonic::readMem(word address, EmuTime::param time) { return RomPanasonic::peekMem(address, time); } const byte* RomPanasonic::getReadCacheLine(word address) const { if ((0x7FF0 & CacheLine::HIGH) == address) { // TODO check mirrored return nullptr; } else { return Rom8kBBlocks::getReadCacheLine(address); } } void RomPanasonic::writeMem(word address, byte value, EmuTime::param /*time*/) { if ((0x6000 <= address) && (address < 0x7FF0)) { // set mapper state (lower 8 bits) int region = (address & 0x1C00) >> 10; if ((region == 5) || (region == 6)) region ^= 3; int selectedBank = bankSelect[region]; int newBank = (selectedBank & ~0xFF) | value; changeBank(region, newBank); } else if (address == 0x7FF8) { // set mapper state (9th bit) for (int region = 0; region < 8; region++) { if (value & 1) { changeBank(region, bankSelect[region] | 0x100); } else { changeBank(region, bankSelect[region] & ~0x100); } value >>= 1; } } else if (address == 0x7FF9) { // write control byte control = value; } else { // Tested on real FS-A1GT: // SRAM/RAM can be written to from all regions, including e.g. // address 0x7ff0. (I didn't actually test 0xc000-0xffff). int region = address >> 13; int selectedBank = bankSelect[region]; if (sram && (SRAM_BASE <= selectedBank) && (selectedBank < maxSRAMBank)) { // SRAM int block = selectedBank - SRAM_BASE; sram->write((block * 0x2000) | (address & 0x1FFF), value); } else if (RAM_BASE <= selectedBank) { // RAM const_cast(bank[region])[address & 0x1FFF] = value; } } } byte* RomPanasonic::getWriteCacheLine(word address) const { if ((0x6000 <= address) && (address < 0x8000)) { // mapper select (low/high), control return nullptr; } else { int region = address >> 13; int selectedBank = bankSelect[region]; if (sram && (SRAM_BASE <= selectedBank) && (selectedBank < maxSRAMBank)) { // SRAM return nullptr; } else if (RAM_BASE <= selectedBank) { // RAM return const_cast(&bank[region][address & 0x1FFF]); } else { return unmappedWrite; } } } void RomPanasonic::changeBank(byte region, int bank) { if (bank == bankSelect[region]) { return; } bankSelect[region] = bank; if (sram && (SRAM_BASE <= bank) && (bank < maxSRAMBank)) { // SRAM int offset = (bank - SRAM_BASE) * 0x2000; int sramSize = sram->getSize(); if (offset >= sramSize) { offset &= (sramSize - 1); } // TODO romblock debuggable is only 8 bits, here bank is 9 bits setBank(region, &sram->operator[](offset), bank); } else if (panasonicMem.getRamSize() && (RAM_BASE <= bank)) { // RAM // TODO romblock debuggable is only 8 bits, here bank is 9 bits setBank(region, panasonicMem.getRamBlock(bank - RAM_BASE), bank); } else { // ROM setRom(region, bank); } } template void RomPanasonic::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("bankSelect", bankSelect); ar.serialize("control", control); } INSTANTIATE_SERIALIZE_METHODS(RomPanasonic); REGISTER_MSXDEVICE(RomPanasonic, "RomPanasonic"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomPanasonic.hh000066400000000000000000000015031257557151200214470ustar00rootroot00000000000000#ifndef ROMPANASONIC_HH #define ROMPANASONIC_HH #include "RomBlocks.hh" namespace openmsx { class PanasonicMemory; class RomPanasonic final : public Rom8kBBlocks { public: RomPanasonic(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; byte readMem(word address, EmuTime::param time) override; const byte* getReadCacheLine(word address) const override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: void changeBank(byte region, int bank); PanasonicMemory& panasonicMem; int maxSRAMBank; int bankSelect[8]; byte control; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomPlain.cc000066400000000000000000000067551257557151200206030ustar00rootroot00000000000000#include "RomPlain.hh" #include "XMLElement.hh" #include "MSXException.hh" #include "StringOp.hh" #include "serialize.hh" namespace openmsx { // return x inside [start, start+len) static inline bool isInside(unsigned x, unsigned start, unsigned len) { unsigned tmp = x - start; return tmp < len; } static std::string toString(unsigned start, unsigned len) { return "[0x" + StringOp::toHexString(start, 4) + ", " + "0x" + StringOp::toHexString(start + len, 4) + ']'; } RomPlain::RomPlain(const DeviceConfig& config, Rom&& rom_, MirrorType mirrored, int start) : Rom8kBBlocks(config, std::move(rom_)) { unsigned windowBase = 0x0000; unsigned windowSize = 0x10000; if (auto* mem = config.findChild("mem")) { windowBase = mem->getAttributeAsInt("base"); windowSize = mem->getAttributeAsInt("size"); } unsigned romSize = rom.getSize(); if ((romSize > 0x10000) || (romSize & 0x1FFF)) { throw MSXException(StringOp::Builder() << rom.getName() << ": invalid rom size: must be smaller than or equal to 64kB " "and must be a multiple of 8kB."); } unsigned romBase = (start == -1) ? guessLocation(windowBase, windowSize) : unsigned(start); if ((start == -1) && (!isInside(romBase, windowBase, windowSize) || !isInside(romBase + romSize - 1, windowBase, windowSize))) { // ROM must fall inside the boundaries given by the // tag (this code only looks at one tag), but only // check when the start address was not explicitly specified throw MSXException(StringOp::Builder() << rom.getName() << ": invalid rom position: interval " << toString(romBase, romSize) << " must fit in " << toString(windowBase, windowSize) << '.'); } if ((romBase & 0x1FFF)) { throw MSXException(StringOp::Builder() << rom.getName() << ": invalid rom position: must start at a 8kB boundary."); } unsigned firstPage = romBase / 0x2000; unsigned numPages = romSize / 0x2000; for (unsigned page = 0; page < 8; ++page) { unsigned romPage = page - firstPage; if (romPage < numPages) { setRom(page, romPage); } else { if (mirrored == MIRRORED) { setRom(page, romPage & (numPages - 1)); } else { setUnmapped(page); } } } } void RomPlain::guessHelper(unsigned offset, int* pages) { if ((rom[offset++] == 'A') && (rom[offset++] =='B')) { for (int i = 0; i < 4; i++) { word addr = rom[offset + 0] + rom[offset + 1] * 256; offset += 2; if (addr) { int page = (addr >> 14) - (offset >> 14); if ((0 <= page) && (page <= 2)) { pages[page]++; } } } } } unsigned RomPlain::guessLocation(unsigned windowBase, unsigned windowSize) { int pages[3] = { 0, 0, 0 }; // count number of possible routine pointers if (rom.getSize() >= 0x0010) { guessHelper(0x0000, pages); } if (rom.getSize() >= 0x4010) { guessHelper(0x4000, pages); } // start address must be inside memory window if (!isInside(0x0000, windowBase, windowSize)) pages[0] = 0; if (!isInside(0x4000, windowBase, windowSize)) pages[1] = 0; if (!isInside(0x8000, windowBase, windowSize)) pages[2] = 0; // we prefer 0x4000, then 0x000 and then 0x8000 if (pages[1] && (pages[1] >= pages[0]) && (pages[1] >= pages[2])) { return 0x4000; } else if (pages[0] && pages[0] >= pages[2]) { return 0x0000; } else if (pages[2]) { return 0x8000; } // heuristics didn't work, return start of window return windowBase; } REGISTER_MSXDEVICE(RomPlain, "RomPlain"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomPlain.hh000066400000000000000000000006731257557151200206060ustar00rootroot00000000000000#ifndef ROMPLAIN_HH #define ROMPLAIN_HH #include "RomBlocks.hh" namespace openmsx { class RomPlain final : public Rom8kBBlocks { public: enum MirrorType { MIRRORED, NOT_MIRRORED }; RomPlain(const DeviceConfig& config, Rom&& rom, MirrorType mirrored, int start = -1); private: void guessHelper(unsigned offset, int* pages); unsigned guessLocation(unsigned windowBase, unsigned windowSize); }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomPlayBall.cc000066400000000000000000000033611257557151200212260ustar00rootroot00000000000000#include "RomPlayBall.hh" #include "CacheLine.hh" #include "FileOperations.hh" #include "serialize.hh" namespace openmsx { RomPlayBall::RomPlayBall(const DeviceConfig& config, Rom&& rom_) : Rom16kBBlocks(config, std::move(rom_)) , samplePlayer( "Playball-DAC", "Sony Playball's DAC", config, FileOperations::stripExtension(rom.getFilename()) + '_', 15, "playball/playball_") { setUnmapped(0); setRom(1, 0); setRom(2, 1); setUnmapped(3); reset(EmuTime::dummy()); } void RomPlayBall::reset(EmuTime::param /*time*/) { samplePlayer.reset(); } byte RomPlayBall::peekMem(word address, EmuTime::param time) const { if (address == 0xBFFF) { return samplePlayer.isPlaying() ? 0xFE : 0xFF; } else { return Rom16kBBlocks::peekMem(address, time); } } byte RomPlayBall::readMem(word address, EmuTime::param time) { return RomPlayBall::peekMem(address, time); } const byte* RomPlayBall::getReadCacheLine(word address) const { if ((address & CacheLine::HIGH) == (0xBFFF & CacheLine::HIGH)) { return nullptr; } else { return Rom16kBBlocks::getReadCacheLine(address); } } void RomPlayBall::writeMem(word address, byte value, EmuTime::param /*time*/) { if (address == 0xBFFF) { if ((value <= 14) && !samplePlayer.isPlaying()) { samplePlayer.play(value); } } } byte* RomPlayBall::getWriteCacheLine(word address) const { if ((address & CacheLine::HIGH) == (0xBFFF & CacheLine::HIGH)) { return nullptr; } else { return unmappedWrite; } } template void RomPlayBall::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("SamplePlayer", samplePlayer); } INSTANTIATE_SERIALIZE_METHODS(RomPlayBall); REGISTER_MSXDEVICE(RomPlayBall, "RomPlayBall"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomPlayBall.hh000066400000000000000000000013371257557151200212410ustar00rootroot00000000000000#ifndef ROMPLAYBALL_HH #define ROMPLAYBALL_HH #include "RomBlocks.hh" #include "SamplePlayer.hh" namespace openmsx { class RomPlayBall final : public Rom16kBBlocks { public: RomPlayBall(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; byte readMem(word address, EmuTime::param time) override; const byte* getReadCacheLine(word address) const override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: SamplePlayer samplePlayer; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomRType.cc000066400000000000000000000021241257557151200205650ustar00rootroot00000000000000// R-Type cartridges // // The address to change banks: // first 16kb: fixed at 0x0f or 0x17 (both have the same content) // second 16kb: 0x4000 - 0x7FFF (0x7000 and 0x7800 used) // bit 4 selects ROM chip, // if low bit 3-0 select page // high 2-0 // Thanks to n_n for investigating this on a real cartridge. #include "RomRType.hh" #include "serialize.hh" namespace openmsx { RomRType::RomRType(const DeviceConfig& config, Rom&& rom_) : Rom16kBBlocks(config, std::move(rom_)) { reset(EmuTime::dummy()); } void RomRType::reset(EmuTime::param /*time*/) { setUnmapped(0); setRom(1, 0x17); setRom(2, 0); setUnmapped(3); } void RomRType::writeMem(word address, byte value, EmuTime::param /*time*/) { if ((0x4000 <= address) && (address < 0x8000)) { value &= (value & 0x10) ? 0x17 : 0x1F; setRom(2, value); } } byte* RomRType::getWriteCacheLine(word address) const { if ((0x4000 <= address) && (address < 0x8000)) { return nullptr; } else { return unmappedWrite; } } REGISTER_MSXDEVICE(RomRType, "RomRType"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomRType.hh000066400000000000000000000006151257557151200206020ustar00rootroot00000000000000#ifndef ROMRTYPE_HH #define ROMRTYPE_HH #include "RomBlocks.hh" namespace openmsx { class RomRType final : public Rom16kBBlocks { public: RomRType(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomSuperLodeRunner.cc000066400000000000000000000014461257557151200226240ustar00rootroot00000000000000#include "RomSuperLodeRunner.hh" #include "MSXCPUInterface.hh" #include "serialize.hh" #include namespace openmsx { RomSuperLodeRunner::RomSuperLodeRunner( const DeviceConfig& config, Rom&& rom_) : Rom16kBBlocks(config, std::move(rom_)) { getCPUInterface().registerGlobalWrite(*this, 0x0000); reset(EmuTime::dummy()); } RomSuperLodeRunner::~RomSuperLodeRunner() { getCPUInterface().unregisterGlobalWrite(*this, 0x0000); } void RomSuperLodeRunner::reset(EmuTime::param /*time*/) { setUnmapped(0); setUnmapped(1); setRom(2, 0); setUnmapped(3); } void RomSuperLodeRunner::globalWrite(word address, byte value, EmuTime::param /*time*/) { assert(address == 0); (void)address; setRom(2, value); } REGISTER_MSXDEVICE(RomSuperLodeRunner, "RomSuperLodeRunner"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomSuperLodeRunner.hh000066400000000000000000000006311257557151200226310ustar00rootroot00000000000000#ifndef ROMSUPERLODERUNNER_HH #define ROMSUPERLODERUNNER_HH #include "RomBlocks.hh" namespace openmsx { class RomSuperLodeRunner final : public Rom16kBBlocks { public: RomSuperLodeRunner(const DeviceConfig& config, Rom&& rom); ~RomSuperLodeRunner(); void reset(EmuTime::param time) override; void globalWrite(word address, byte value, EmuTime::param time) override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomSuperSwangi.cc000066400000000000000000000013321257557151200217710ustar00rootroot00000000000000#include "RomSuperSwangi.hh" #include "CacheLine.hh" #include "serialize.hh" namespace openmsx { RomSuperSwangi::RomSuperSwangi(const DeviceConfig& config, Rom&& rom_) : Rom16kBBlocks(config, std::move(rom_)) { reset(EmuTime::dummy()); } void RomSuperSwangi::reset(EmuTime::param /*time*/) { setUnmapped(0); setRom(1, 0); setRom(2, 0); setUnmapped(3); } void RomSuperSwangi::writeMem(word address, byte value, EmuTime::param /*time*/) { if (address == 0x8000) { setRom(2, value >> 1); } } byte* RomSuperSwangi::getWriteCacheLine(word address) const { if (address == (0x8000 & CacheLine::HIGH)) return nullptr; return unmappedWrite; } REGISTER_MSXDEVICE(RomSuperSwangi, "RomSuperSwangi"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomSuperSwangi.hh000066400000000000000000000006451257557151200220110ustar00rootroot00000000000000#ifndef ROMSUPERSWANGI_HH #define ROMSUPERSWANGI_HH #include "RomBlocks.hh" namespace openmsx { class RomSuperSwangi final : public Rom16kBBlocks { public: RomSuperSwangi(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomSynthesizer.cc000066400000000000000000000030271257557151200220540ustar00rootroot00000000000000/* On Sat, 3 Apr 2004, Manuel Pazos wrote: * * As you know, the cartridge has an 8bit D/A, accessed through * memory-mapped at address #4000 by the program. openMSX also uses that * address to access it. But examining the cartridge board I found that the * address is decoded by a LS138 in this way: * * /WR = L * A15 = L * A4 = L * /MERQ = L * /SLT = L * A14 = H * * So any value equal to %01xxxxxxxxx0xxxx should work (i.e.: #4000, #4020, * #7C00, etc.) */ #include "RomSynthesizer.hh" #include "CacheLine.hh" #include "serialize.hh" namespace openmsx { RomSynthesizer::RomSynthesizer(const DeviceConfig& config, Rom&& rom_) : Rom16kBBlocks(config, std::move(rom_)) , dac("Synthesizer-DAC", "Konami Synthesizer's DAC", config) { setUnmapped(0); setRom(1, 0); setRom(2, 1); setUnmapped(3); reset(getCurrentTime()); } void RomSynthesizer::reset(EmuTime::param time) { dac.reset(time); } void RomSynthesizer::writeMem(word address, byte value, EmuTime::param time) { if ((address & 0xC010) == 0x4000) { dac.writeDAC(value, time); } } byte* RomSynthesizer::getWriteCacheLine(word address) const { if ((address & 0xC010 & CacheLine::HIGH) == 0x4000) { return nullptr; } else { return unmappedWrite; } } template void RomSynthesizer::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("DAC", dac); } INSTANTIATE_SERIALIZE_METHODS(RomSynthesizer); REGISTER_MSXDEVICE(RomSynthesizer, "RomSynthesizer"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomSynthesizer.hh000066400000000000000000000010461257557151200220650ustar00rootroot00000000000000#ifndef ROMSYNTHESIZER_HH #define ROMSYNTHESIZER_HH #include "RomBlocks.hh" #include "DACSound8U.hh" namespace openmsx { class RomSynthesizer final : public Rom16kBBlocks { public: RomSynthesizer(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: DACSound8U dac; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomTypes.hh000066400000000000000000000037661257557151200206550ustar00rootroot00000000000000#ifndef ROMTYPES_HH #define ROMTYPES_HH namespace openmsx { enum RomType { // Order doesn't matter (I sorted them alphabetically) ROM_ARC, ROM_ASCII8, ROM_ASCII8_2, ROM_ASCII8_8, ROM_ASCII16, ROM_ASCII16_2, ROM_ASCII16_8, ROM_CROSS_BLAIM, ROM_DOOLY, ROM_DRAM, ROM_FSA1FM1, ROM_FSA1FM2, ROM_GAME_MASTER2, ROM_GENERIC_8KB, ROM_GENERIC_16KB, ROM_HALNOTE, ROM_HAMARAJANIGHT, ROM_HARRY_FOX, ROM_HOLY_QURAN, ROM_HOLY_QURAN2, ROM_KBDMASTER, ROM_KOEI_8, ROM_KOEI_32, ROM_KONAMI, ROM_KONAMI_SCC, ROM_MAJUTSUSHI, ROM_MANBOW2, ROM_MANBOW2_2, ROM_MATRAINK, ROM_MEGAFLASHROMSCC, ROM_MEGAFLASHROMSCCPLUS, ROM_MEGAFLASHROMSCCPLUSSD, ROM_MIRRORED, ROM_MITSUBISHIMLTS2, ROM_MSXDOS2, ROM_MSXTRA, ROM_MULTIROM, ROM_NATIONAL, ROM_NETTOU_YAKYUU, ROM_NORMAL, ROM_PADIAL8, ROM_PADIAL16, ROM_PANASONIC, ROM_PLAYBALL, ROM_R_TYPE, ROM_SUPERLODERUNNER, ROM_SUPERSWANGI, ROM_SYNTHESIZER, ROM_WIZARDRY, ROM_ZEMINA80IN1, ROM_ZEMINA90IN1, ROM_ZEMINA126IN1, ROM_END_OF_UNORDERED_LIST, // not an actual romtype // For these the numeric value does matter ROM_PAGE0 = 128 + 1, // value of lower 4 bits matters ROM_PAGE1 = 128 + 2, ROM_PAGE01 = 128 + 3, ROM_PAGE2 = 128 + 4, ROM_PAGE12 = 128 + 6, ROM_PAGE012 = 128 + 7, ROM_PAGE3 = 128 + 8, ROM_PAGE23 = 128 + 12, ROM_PAGE123 = 128 + 14, ROM_PAGE0123 = 128 + 15, ROM_MIRRORED0000 = 144 + 0, // value of lower 3 bits matters ROM_MIRRORED4000 = 144 + 2, ROM_MIRRORED8000 = 144 + 4, ROM_MIRROREDC000 = 144 + 6, ROM_NORMAL0000 = 152 + 0, // value of lower 3 bits matters ROM_NORMAL4000 = 152 + 2, ROM_NORMAL8000 = 152 + 4, ROM_NORMALC000 = 152 + 6, ROM_UNKNOWN = 256, ROM_ALIAS = 512, // no other enum value can have this bit set }; // Make sure there is no overlap in numeric enum values between the unordered // and ordered part of the enum list. static_assert(int(ROM_END_OF_UNORDERED_LIST) < int(ROM_PAGE0), "renumber romtypes"); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomZemina126in1.cc000066400000000000000000000016641257557151200216160ustar00rootroot00000000000000// Zemina 126-in-1 cartridge // // Information obtained by studying MESS sources: // 0x4001 : 0x4000-0x7FFF // 0x4002 : 0x8000-0xBFFF #include "RomZemina126in1.hh" #include "CacheLine.hh" #include "serialize.hh" namespace openmsx { RomZemina126in1::RomZemina126in1( const DeviceConfig& config, Rom&& rom_) : Rom16kBBlocks(config, std::move(rom_)) { reset(EmuTime::dummy()); } void RomZemina126in1::reset(EmuTime::param /*time*/) { setUnmapped(0); setRom(1, 0); setRom(2, 1); setUnmapped(3); } void RomZemina126in1::writeMem(word address, byte value, EmuTime::param /*time*/) { if (address == 0x4000) { setRom(1, value); } else if (address == 0x4001) { setRom(2, value); } } byte* RomZemina126in1::getWriteCacheLine(word address) const { if (address == (0x4000 & CacheLine::HIGH)) { return nullptr; } else { return unmappedWrite; } } REGISTER_MSXDEVICE(RomZemina126in1, "RomZemina126in1"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomZemina126in1.hh000066400000000000000000000006511257557151200216230ustar00rootroot00000000000000#ifndef ROMZEMINA126IN1_HH #define ROMZEMINA126IN1_HH #include "RomBlocks.hh" namespace openmsx { class RomZemina126in1 final : public Rom16kBBlocks { public: RomZemina126in1(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomZemina80in1.cc000066400000000000000000000020171257557151200215260ustar00rootroot00000000000000// Zemina 80-in-1 cartridge // // Information obtained by studying MESS sources: // 0x4000 : 0x4000-0x5FFF // 0x4001 : 0x6000-0x7FFF // 0x4002 : 0x8000-0x9FFF // 0x4003 : 0xA000-0xBFFF #include "RomZemina80in1.hh" #include "CacheLine.hh" #include "serialize.hh" namespace openmsx { RomZemina80in1::RomZemina80in1(const DeviceConfig& config, Rom&& rom_) : Rom8kBBlocks(config, std::move(rom_)) { reset(EmuTime::dummy()); } void RomZemina80in1::reset(EmuTime::param /*time*/) { setUnmapped(0); setUnmapped(1); for (int i = 2; i < 6; i++) { setRom(i, i - 2); } setUnmapped(6); setUnmapped(7); } void RomZemina80in1::writeMem(word address, byte value, EmuTime::param /*time*/) { if ((0x4000 <= address) && (address < 0x4004)) { setRom(2 + (address - 0x4000), value); } } byte* RomZemina80in1::getWriteCacheLine(word address) const { if (address == (0x4000 & CacheLine::HIGH)) { return nullptr; } else { return unmappedWrite; } } REGISTER_MSXDEVICE(RomZemina80in1, "RomZemina80in1"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomZemina80in1.hh000066400000000000000000000006441257557151200215440ustar00rootroot00000000000000#ifndef ROMZEMINA80IN1_HH #define ROMZEMINA80IN1_HH #include "RomBlocks.hh" namespace openmsx { class RomZemina80in1 final : public Rom8kBBlocks { public: RomZemina80in1(const DeviceConfig& config, Rom&& rom); void reset(EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/RomZemina90in1.cc000066400000000000000000000033551257557151200215350ustar00rootroot00000000000000// Zemina 90-in-1 cartridge // // 90 in 1 uses Port &H77 for mapping: // bits 0-5: selected 16KB page // bits 6-7: addressing mode... // 00 = same page at 4000-7FFF and 8000-BFFF (normal mode) // 01 = same page at 4000-7FFF and 8000-BFFF (normal mode) // 10 = [page AND 3E] at 4000-7FFF, [page AND 3E OR 01] at 8000-BFFF // (32KB mode) // 11 = same page at 4000-7FFF and 8000-BFFF, but 8000-BFFF has high 8KB // and low 8KB swapped (Namco mode) #include "RomZemina90in1.hh" #include "MSXCPUInterface.hh" #include "serialize.hh" #include "unreachable.hh" namespace openmsx { RomZemina90in1::RomZemina90in1(const DeviceConfig& config, Rom&& rom_) : Rom8kBBlocks(config, std::move(rom_)) { reset(EmuTime::dummy()); getCPUInterface().register_IO_Out(0x77, this); } RomZemina90in1::~RomZemina90in1() { getCPUInterface().unregister_IO_Out(0x77, this); } void RomZemina90in1::reset(EmuTime::param dummy) { setUnmapped(0); setUnmapped(1); setUnmapped(6); setUnmapped(7); writeIO(0x77, 0, dummy); } void RomZemina90in1::writeIO(word /*port*/, byte value, EmuTime::param /*time*/) { byte page = 2 * (value & 0x3F); switch (value & 0xC0) { case 0x00: case 0x40: setRom(2, page + 0); setRom(3, page + 1); setRom(4, page + 0); setRom(5, page + 1); break; case 0x80: setRom(2, (page & ~2) + 0); setRom(3, (page & ~2) + 1); setRom(4, (page | 2) + 0); setRom(5, (page | 2) + 1); break; case 0xC0: setRom(2, page + 0); setRom(3, page + 1); setRom(4, page + 1); setRom(5, page + 0); break; default: UNREACHABLE; } } byte* RomZemina90in1::getWriteCacheLine(word /*address*/) const { return unmappedWrite; } REGISTER_MSXDEVICE(RomZemina90in1, "RomZemina90in1"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/RomZemina90in1.hh000066400000000000000000000006641257557151200215470ustar00rootroot00000000000000#ifndef ROMZEMINA90IN1_HH #define ROMZEMINA90IN1_HH #include "RomBlocks.hh" namespace openmsx { class RomZemina90in1 final : public Rom8kBBlocks { public: RomZemina90in1(const DeviceConfig& config, Rom&& rom); ~RomZemina90in1(); void reset(EmuTime::param time) override; void writeIO(word port, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word address) const override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/SRAM.cc000066400000000000000000000065041257557151200176140ustar00rootroot00000000000000#include "SRAM.hh" #include "DeviceConfig.hh" #include "File.hh" #include "FileContext.hh" #include "FileException.hh" #include "FileNotFoundException.hh" #include "Reactor.hh" #include "CliComm.hh" #include "serialize.hh" #include "openmsx.hh" #include "vla.hh" #include "memory.hh" #include using std::string; namespace openmsx { // class SRAM /* Creates a SRAM that is not loaded from or saved to a file. * The only reason to use this (instead of a plain Ram object) is when you * dynamically need to decide whether load/save is needed. */ SRAM::SRAM(const std::string& name, const std::string& description, int size, const DeviceConfig& config, DontLoad) : RTSchedulable(config.getReactor().getRTScheduler()) , ram(config, name, description, size) , header(nullptr) // not used { } SRAM::SRAM(const string& name, int size, const DeviceConfig& config_, const char* header_, bool* loaded) : RTSchedulable(config_.getReactor().getRTScheduler()) , config(config_) , ram(config, name, "sram", size) , header(header_) { load(loaded); } SRAM::SRAM(const string& name, const string& description, int size, const DeviceConfig& config_, const char* header_, bool* loaded) : RTSchedulable(config_.getReactor().getRTScheduler()) , config(config_) , ram(config, name, description, size) , header(header_) { load(loaded); } SRAM::~SRAM() { save(); } void SRAM::write(unsigned addr, byte value) { if (!isPendingRT()) { scheduleRT(5000000); // sync to disk after 5s } assert(addr < getSize()); ram[addr] = value; } void SRAM::memset(unsigned addr, byte c, unsigned size) { if (!isPendingRT()) { scheduleRT(5000000); // sync to disk after 5s } assert((addr + size) <= getSize()); ::memset(&ram[addr], c, size); } void SRAM::load(bool* loaded) { assert(config.getXML()); if (loaded) *loaded = false; const string& filename = config.getChildData("sramname"); try { bool headerOk = true; File file(config.getFileContext().resolveCreate(filename), File::LOAD_PERSISTENT); if (header) { size_t length = strlen(header); VLA(char, temp, length); file.read(temp, length); if (memcmp(temp, header, length) != 0) { headerOk = false; } } if (headerOk) { file.read(&ram[0], getSize()); if (loaded) *loaded = true; } else { config.getCliComm().printWarning( "Warning no correct SRAM file: " + filename); } } catch (FileNotFoundException& /*e*/) { config.getCliComm().printInfo( "SRAM file " + filename + " not found" + ", assuming blank SRAM content."); } catch (FileException& e) { config.getCliComm().printWarning( "Couldn't load SRAM " + filename + " (" + e.getMessage() + ")."); } } void SRAM::save() { if (!config.getXML()) return; const string& filename = config.getChildData("sramname"); try { File file(config.getFileContext().resolveCreate(filename), File::SAVE_PERSISTENT); if (header) { int length = int(strlen(header)); file.write(header, length); } file.write(&ram[0], getSize()); } catch (FileException& e) { config.getCliComm().printWarning( "Couldn't save SRAM " + filename + " (" + e.getMessage() + ")."); } } void SRAM::executeRT() { save(); } template void SRAM::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("ram", ram); } INSTANTIATE_SERIALIZE_METHODS(SRAM); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/SRAM.hh000066400000000000000000000023151257557151200176220ustar00rootroot00000000000000#ifndef SRAM_HH #define SRAM_HH #include "Ram.hh" #include "DeviceConfig.hh" #include "RTSchedulable.hh" namespace openmsx { class SRAM final : private RTSchedulable { public: enum DontLoad { DONT_LOAD }; SRAM(const std::string& name, const std::string& description, int size, const DeviceConfig& config, DontLoad); SRAM(const std::string& name, int size, const DeviceConfig& config, const char* header = nullptr, bool* loaded = nullptr); SRAM(const std::string& name, const std::string& description, int size, const DeviceConfig& config, const char* header = nullptr, bool* loaded = nullptr); ~SRAM(); const byte& operator[](unsigned addr) const { assert(addr < getSize()); return ram[addr]; } // write() is non-inline because of the auto-sync to disk feature void write(unsigned addr, byte value); void memset(unsigned addr, byte c, unsigned size); unsigned getSize() const { return ram.getSize(); } template void serialize(Archive& ar, unsigned version); private: // RTSchedulable void executeRT() override; void load(bool* loaded); void save(); const DeviceConfig config; Ram ram; const char* const header; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/memory/SdCard.cc000066400000000000000000000234171257557151200202140ustar00rootroot00000000000000#include "SdCard.hh" #include "DeviceConfig.hh" #include "HD.hh" #include "MSXException.hh" #include "memory.hh" #include "unreachable.hh" #include "endian.hh" #include "serialize.hh" #include "serialize_stl.hh" #include // TODO: // - replace transferDelayCounter with 0xFF's in responseQueue? // - remove duplication between WRITE and MULTI_WRITE (is it worth it?) // - see TODOs in the code below namespace openmsx { // data response tokens static const byte DRT_ACCEPTED = 0x05; static const byte DRT_WRITE_ERROR = 0x0D; // start block tokens and stop tran token static const byte START_BLOCK_TOKEN = 0xFE; static const byte START_BLOCK_TOKEN_MBW = 0xFC; static const byte STOP_TRAN_TOKEN = 0xFD; // data error token static const byte DATA_ERROR_TOKEN_ERROR = 0x01; static const byte DATA_ERROR_TOKEN_OUT_OF_RANGE = 0x08; // responses static const byte R1_BUSY = 0x00; static const byte R1_IDLE = 0x01; // TODO: why is lots of code checking for this instead of R1_BUSY? static const byte R1_ILLEGAL_COMMAND = 0x04; static const byte R1_PARAMETER_ERROR = 0x80; SdCard::SdCard(const DeviceConfig& config) : hd(config.getXML() ? make_unique(config) : nullptr) , cmdIdx(0) , transferDelayCounter(0) , mode(COMMAND) , currentSector(0) , currentByteInSector(0) { } SdCard::~SdCard() { } // helper methods for 'transfer' to avoid duplication byte SdCard::readCurrentByteFromCurrentSector() { byte retval; if (currentByteInSector == -1) { retval = START_BLOCK_TOKEN; try { hd->readSector(currentSector, sectorBuf); } catch (MSXException&) { retval = DATA_ERROR_TOKEN_ERROR; } } else { // output next byte from stream retval = sectorBuf.raw[currentByteInSector]; } currentByteInSector++; if (currentByteInSector == sizeof(sectorBuf)) { responseQueue.push_back({0x00, 0x00}); // 2 CRC's (dummy) } return retval; } byte SdCard::transfer(byte value, bool cs) { if (!hd) return 0xFF; // no card inserted if (cs) { // /CS is true: not for this chip return 0xFF; } // process output byte retval = 0xFF; if (transferDelayCounter > 0) { --transferDelayCounter; } else { if (responseQueue.empty()) { switch (mode) { case READ: retval = readCurrentByteFromCurrentSector(); if (currentByteInSector == sizeof(sectorBuf)) { mode = COMMAND; } break; case MULTI_READ: // when there's an error, you probably have to send a CMD12 // to go back to the COMMAND mode. This is not // clear in the spec (it's wrongly suggested // for the MULTI_WRITE mode!) if (currentSector >= hd->getNbSectors()) { // data out of range, send data error token retval = DATA_ERROR_TOKEN_OUT_OF_RANGE; } else { retval = readCurrentByteFromCurrentSector(); if (currentByteInSector == sizeof(sectorBuf)) { currentSector++; currentByteInSector = -1; } } break; case WRITE: case MULTI_WRITE: // apparently nothing is returned while writing case COMMAND: default: break; } } else { retval = responseQueue.pop_front(); } } // process input switch (mode) { case WRITE: // first check for data token if (currentByteInSector == -1) { if (value == START_BLOCK_TOKEN) { currentByteInSector++; } break; } if (currentByteInSector < int(sizeof(sectorBuf))) { sectorBuf.raw[currentByteInSector] = value; } currentByteInSector++; if (currentByteInSector == (sizeof(sectorBuf) + 2)) { byte response = DRT_ACCEPTED; // copy buffer to SD card try { hd->writeSector(currentSector, sectorBuf); } catch (MSXException&) { response = DRT_WRITE_ERROR; } mode = COMMAND; transferDelayCounter = 1; responseQueue.push_back(response); } break; case MULTI_WRITE: // first check for stop or start token if (currentByteInSector == -1) { if (value == STOP_TRAN_TOKEN) { mode = COMMAND; } if (value == START_BLOCK_TOKEN_MBW) { currentByteInSector++; } break; } if (currentByteInSector < int(sizeof(sectorBuf))) { sectorBuf.raw[currentByteInSector] = value; } currentByteInSector++; if (currentByteInSector == (sizeof(sectorBuf) + 2)) { // check if still in valid range byte response = DRT_ACCEPTED; if (currentSector >= hd->getNbSectors()) { response = DRT_WRITE_ERROR; // note: mode is not changed, should be done by // the host with CMD12 (STOP_TRANSMISSION) - // however, this makes no sense, CMD12 is only // for Multiple Block Read!? Wrong in the spec? } else { // copy buffer to SD card try { hd->writeSector(currentSector, sectorBuf); currentByteInSector = -1; currentSector++; } catch (MSXException&) { response = DRT_WRITE_ERROR; // note: mode is not changed, should be // done by the host with CMD12 // (STOP_TRANSMISSION) - however, this // makes no sense, CMD12 is only for // Multiple Block Read!? Wrong in the // spec? } } transferDelayCounter = 1; responseQueue.push_back(response); } break; case COMMAND: default: if ((cmdIdx == 0 && (value >> 6) == 1) // command start || cmdIdx > 0) { // command in progress cmdBuf[cmdIdx] = value; ++cmdIdx; if (cmdIdx == 6) { executeCommand(); cmdIdx = 0; } } break; } return retval; } void SdCard::executeCommand() { // it takes 2 transfers (2x8 cycles) before a reply // can be given to a command transferDelayCounter = 2; byte command = cmdBuf[0] & 0x3F; switch (command) { case 0: // GO_IDLE_STATE responseQueue.clear(); mode = COMMAND; responseQueue.push_back(R1_IDLE); break; case 8: // SEND_IF_COND // conditions are always OK responseQueue.push_back({ R1_IDLE, // R1 (OK) SDHC (checked by MegaSD and FUZIX) byte(0x02), // command version byte(0x00), // reserved byte(0x01), // voltage accepted cmdBuf[4]});// check pattern break; case 9:{ // SEND_CSD responseQueue.push_back({ R1_BUSY, // OK (ignored on MegaSD code, used in FUZIX) // now follows a CSD version 2.0 (for SDHC) START_BLOCK_TOKEN, // data token byte(0x40), // CSD_STRUCTURE [127:120] byte(0x0E), // (TAAC) byte(0x00), // (NSAC) byte(0x32), // (TRAN_SPEED) byte(0x00), // CCC byte(0x00), // CCC / (READ_BL_LEN) byte(0x00)}); // (RBP)/(WBM)/(RBM)/ DSR_IMP // SD_CARD_SIZE = (C_SIZE + 1) * 512kByte unsigned c_size = unsigned((hd->getNbSectors() * sizeof(sectorBuf)) / (512 * 1024) - 1); responseQueue.push_back({ byte((c_size >> 16) & 0x3F), // C_SIZE 1 byte((c_size >> 8) & 0xFF), // C_SIZE 2 byte((c_size >> 0) & 0xFF), // C_SIZE 3 byte(0x00), // res/(EBE)/(SS1) byte(0x00), // (SS2)/(WGS) byte(0x00), // (WGE)/res/(RF)/(WBL1) byte(0x00), // (WBL2)/(WBP)/res byte(0x00), // (FFG)/COPY/PWP/TWP/(FF)/res byte(0x01)}); // CRC / 1 break;} case 10: // SEND_CID responseQueue.push_back({ R1_BUSY, // OK (ignored on MegaSD, unused in FUZIX so far) START_BLOCK_TOKEN, // data token byte(0xAA), // CID01 // manuf ID byte('o' ), // CID02 // OEM/App ID 1 byte('p' ), // CID03 // OEM/App ID 2 byte('e' ), // CID04 // Prod name 1 byte('n' ), // CID05 // Prod name 2 byte('M' ), // CID06 // Prod name 3 byte('S' ), // CID07 // Prod name 4 byte('X' ), // CID08 // Prod name 5 byte(0x01), // CID09 // Prod Revision byte(0x12), // CID10 // Prod Serial 1 byte(0x34), // CID11 // Prod Serial 2 byte(0x56), // CID12 // Prod Serial 3 byte(0x78), // CID13 // Prod Serial 4 byte(0x00), // CID14 // reserved / Y1 byte(0xE6), // CID15 // Y2 / M byte(0x01)}); // CID16 // CRC / not used break; case 12: // STOP TRANSMISSION responseQueue.push_back(R1_IDLE); // R1 (OK) mode = COMMAND; break; case 16: // SET_BLOCKLEN responseQueue.push_back(R1_IDLE); // OK, we don't really care break; case 17: // READ_SINGLE_BLOCK case 18: // READ_MULTIPLE_BLOCK case 24: // WRITE_BLOCK case 25: // WRITE_MULTIPLE_BLOCK // SDHC so the address is the sector currentSector = Endian::readB32(&cmdBuf[1]); if (currentSector >= hd->getNbSectors()) { responseQueue.push_back(R1_PARAMETER_ERROR); } else { responseQueue.push_back(R1_BUSY); switch (command) { case 17: mode = READ; break; case 18: mode = MULTI_READ; break; case 24: mode = WRITE; break; case 25: mode = MULTI_WRITE; break; default: UNREACHABLE; } currentByteInSector = -1; // wait for token } break; case 41: // implementation of ACMD 41!! // SD_SEND_OP_COND responseQueue.push_back(R1_BUSY); break; case 55: // APP_CMD // TODO: go to ACMD mode, but not necessary now responseQueue.push_back(R1_IDLE); break; case 58: // READ_OCR responseQueue.push_back({ R1_BUSY,// R1 (OK) (ignored on MegaSD, checked in FUZIX) byte(0x40), // OCR Reg part 1 (SDHC: CCS=1) byte(0x00), // OCR Reg part 2 byte(0x00), // OCR Reg part 3 byte(0x00)}); // OCR Reg part 4 break; default: responseQueue.push_back(R1_ILLEGAL_COMMAND); break; } } static enum_string modeInfo[] = { { "COMMAND", SdCard::COMMAND }, { "READ", SdCard::READ }, { "MULTI_READ", SdCard::MULTI_READ }, { "WRITE", SdCard::WRITE }, { "MULTI_WRITE", SdCard::MULTI_WRITE } }; SERIALIZE_ENUM(SdCard::Mode, modeInfo); template void SdCard::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("mode", mode); ar.serialize("cmdBuf", cmdBuf); ar.serialize_blob("sectorBuf", sectorBuf.raw, sizeof(sectorBuf)); if (hd) ar.serialize("hd", *hd); ar.serialize("cmdIdx", cmdIdx); ar.serialize("transferDelayCounter", transferDelayCounter); ar.serialize("responseQueue", responseQueue); ar.serialize("currentSector", currentSector); ar.serialize("currentByteInSector", currentByteInSector); } INSTANTIATE_SERIALIZE_METHODS(SdCard); } // namespace openmsx openMSX-RELEASE_0_12_0/src/memory/SdCard.hh000066400000000000000000000015101257557151200202140ustar00rootroot00000000000000#ifndef SDCARD_HH #define SDCARD_HH #include "openmsx.hh" #include "circular_buffer.hh" #include "DiskImageUtils.hh" #include #include namespace openmsx { class DeviceConfig; class HD; class SdCard { public: SdCard(const DeviceConfig& config); ~SdCard(); byte transfer(byte value, bool cs); template void serialize(Archive& ar, unsigned version); // private: enum Mode { COMMAND, READ, MULTI_READ, WRITE, MULTI_WRITE }; private: void executeCommand(); byte readCurrentByteFromCurrentSector(); const std::unique_ptr hd; // can be nullptr byte cmdBuf[6]; SectorBuffer sectorBuf; unsigned cmdIdx; cb_queue responseQueue; byte transferDelayCounter; Mode mode; unsigned currentSector; int currentByteInSector; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/openmsx.hh000066400000000000000000000016051257557151200172420ustar00rootroot00000000000000#ifndef OPENMSX_HH #define OPENMSX_HH #include "build-info.hh" #if PLATFORM_ANDROID #include #define ad_printf(...) __android_log_print(ANDROID_LOG_INFO, "openMSX", __VA_ARGS__) #else #define ad_printf(...) #endif /// Namespace of the openMSX emulation core. /** openMSX: the MSX emulator that aims for perfection * * Copyrights: see AUTHORS file. * License: GPL. */ namespace openmsx { /** 4 bit integer */ using nibble = unsigned char; /** 8 bit unsigned integer */ using byte = unsigned char; /** 16 bit unsigned integer */ using word = unsigned short; #if defined(__GNUC__) && \ ((__GNUC__ * 100 + __GNUC_MINOR__ * 10 + __GNUC_PATCHLEVEL__) < 472) // gcc versions before 4.7.2 had a bug in ~unique_ptr(), // see http://gcc.gnu.org/bugzilla/show_bug.cgi?id=54351 #define UNIQUE_PTR_BUG 1 #else #define UNIQUE_PTR_BUG 0 #endif } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/resource/000077500000000000000000000000001257557151200170555ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/src/resource/openmsx.ico000066400000000000000000013667261257557151200212670ustar00rootroot00000000000000(h v ^ 00h00n@@h (@@(~2 (H hP ( 6U  ^u00 %@@ (B( A@s34333@s33s33337U37UU337USqwUPqsP##P72PUU?( cfmxz~~~%2إ:{{{՜tDmmJK۩佽""""""" """"" """) ""   $$ # %*   &! '  ' ( ?( @ DDDES5335333333835333355S33335T33333453333363333334S3333335333DFfd43335fffffc3334ffff`3334fffffd3334ffffff3333FUffffS333336fffc333334f`ff333334f`e5U336ffHw5f`f ffh`d`d53F`eTC6`eU3S6`e333V`d3U3fffffff????( @rs45IY\egjr88Dp%%I }NRTX[_or^`bc em ,iqmqvy|j78 Tswj:D4OU S7: .98>ddf!kU_a\68r()w@D7<futisnemr9{9s01 l>Kn"#s+,  @xr@ f3M3T,u8]>@|u$$$Q$P (U$$$$$$$;8610+(4FOIGF?=:851,+( {g9SOHGE?=:751,(;#*SOKB6D>;:610+(;B1#UaOHqs>;8510+HGBR 'g'=:851+;OG6?;:75+TO?g>;:5(OFFL}?>:1 +1; 4[E>8 6 :65(BD*o3^L[7 8ED?3"]#{' 501HFFfpCc55111G CCWZ 7655Fj~.| 1868H).-Qf5:8:OG v]^Qn;::;OO'_`]Q?;;>SS {v2]2LX>=>TTX]_*???Tav# DBET ^FETAHGFTy4{4HHTXiTS9eOOTTTTTTTSSOTTTTS??????????(@" EUTHdE33D33333TTDUTE33333333333XUS3333333333335A3333333333333X33343333333338335C3333333334(35E333333333343U33333333333453333333333330!UE33333333333@SE33333333333@C3333333333333PS33333333333330333333333333336333333333333336S3333333333333363333335ETFffDS36S333333FfffffffS6C3333334ffffffffdPS33333336fffffffff33333333Vfffffffff`33333333Fffffffffff33333333FffffffffffS3333333VfffffffffffC33333336fffffffffff33333334ffffffffff`f33333333ffdVfffffffff333333334DS3Ffffffff`S333333333334ffff`ffc333333333333ffff`ff333333333333Vfffff`S333333333336ff`ff333333333336ff`ffS33333333336fffe333U333333VfffC3T33333f`ffU'(333Vf`ff('wwS5Fffff" fff`fhfff`fffhAffdHAfffDEAETfffd3QS3Ffffc3CQ336fffC31Q335fffS4EQE33ff`fS4C33E33fffS3333335fffC3333335ffc3333334f`e333S33VffS34e35fffDfffff`ffffff`fffff????????????(@^uq88\m4AH&jj+-`|q.9tKLMKQUn77WXY[^__Xd`cnnegkopwwty|[o3l|@M}hhk-.JDQVxvqr&/\p =9 < pvy> 7d :GQ^ 'Wgv+6m"p44b#j"#i&&6 GTPKWB }}ia}fr4Ud}4dUc4dc}_cOU4Oi gq zDxUdUri-im R9ngU77iTn~e]n^ pyzK7drqX v' K~N eOc V yD vcr\~ n9 ~kvR "`F ^O% ' gv%%% l Pw%%%%%Ol{ gmG%%%%%%% 9{ ( -}%GGG%%%%%% WpM TGGGGG%%%%%%Nvp R Z+GGGGGG%%%wW Z }[+GGGGGG%wvwK -\+++GGGGowv SS -+++++w_GY nghj# -wo__%wvV>=<=8320,,**j-ty}vw\(MnAA>>=<:8310,**$$jF#gCA?>>=::521.,**$#$#EECA?>=<:8321.,*&###EECCA?>=<:8320,,*&###HHECCA>>=<:531.,**$##`qHHEECA?>>=::521.,*&## *#gAIHECCA?>=<:8320.,*&##&=:R;LIHECj?>=<:8310,**&#A>=D hJHCD>=<:521.,*&*EC>39R>=:8321.*#&HEA>(=<:8321*CHEA:>=<:833#,LHE>F>>=::8*:LH3?`y8>=<::#&EHE=5?>=<.##<3 0A>=2,1 "'@ T?>:1<>;Z_Yu=*1?>>z'6uD.1,3A?>>kbB10.2CAA?k}f;332212CAm}_Kma8853222CO++w_\K88535CKkr+++)+++++K:888E~\+++s+++Nw::8:EA )))+++q<<:::HEE;)+++]^==<<<HHMu/++Ns==<=JIH;p){++Fq>===LJIqx)w{i@>>>>LLL xQr >>>>LLTb???>LLDAA??L!CAALL(9 t|CCL|eZ|(9ECCLLB;LLI|EEELLLLLLLLJIIHHLLLLLLLLJI????????????( 8r(&53F@X6J(7(6hr)8Kgj3FYzPn>VG3"Rq;Q@X-?Jf$~bb Kgx -7Kmmm_VT OaSf?O'4ry{5214HBw=?@51/^wsPLKGVY (7 o-`i/EV E\f"}}}!'-Rb}|u%0CMXxHcF\jKhnttt999Gfi  2\j=ddd=##-bnMYMMMMMMaLL{{{{{{{{(_jD@?%P[)0>$$|'JQVbj*0?H ucp=#7MW-4lTT%*y_h"&oo&%&}{X7;yy'%+;Czzz'&1Yt,'&(&6[cv5<)'9,0w rqqeNN%(? bVVx{{*(9^FMBIbXXEVhhxrqq6&XY`[\ZXVUTUTSF1RGG||4/8=B49PWcm!XRR[ vkljlmgno`_^\[YXWUTSRQPQRS2H;;~~<24[c\dU\NVHM]euxx1%%wqqmkjigfccb`_^\[YYWUTRQOMSSJL9?..B=01BG9@A65ZNNvtpomlkjhgfcca`_]\ZYXVUTRQPNLKKMS:}}}?>5-| tCIzhq'+KCC] tsqpnnlkihfedba`^\\ZYWVTSRPOMLLLLLI-xx@?9 QWEJO)xrq}}B&&xutsqpnmljigfdcba_]][YYWVTRRPONLKLMMR7A@5( ZLtvutrponlliigfeca`_]\[YXVUTRQPNMLJIIMP>@=.~Izxwvtsrqomlkiiffdca`^][[YWVUTRQONMKJIIIF68_8跏eVV~zxwutsqpomlkiggedb``^][ZYWVTSRQONLKIHGEA4|)ll@tzyxvusrqonmkjigfecba_^\[ZXWVTSQPNMKKIHFG) 5v""0|yzyxvutrqonlkjigeeca`_^\ZZXWUTSQONLKJIHHQT*|{{zywutsrponlkjhgedba`^][ZYXVUSRQONLKIHGL(y@ǧ3|z|{yxvvtsqpomliihgddb`_]][ZXWVTSQPONLKIHFCsy"qqC!!x~~|{yxvutsqonmhjhgfecb`_]\[YXWUTSQPNMLJIHL\1k g``}{{yxvusqqonlkjhgfdbb__]\[YXWUTRQONLKJHO#*|^_Jt~|{yywvtsqpnnljjhgedc``^][ZXWVTSRPOMLKIK)Sib`9"@}|{yvvvtsrpnmljhgfedba_^\[YXWVTSQPNNLJM9khebd}))tww ~}{zywvp>yqomlkjhgedca`_]\[YXWUTRRPNMMP,DCnkhec!;M~}{zywtdUsqnlkihgfdba__][ZYWVUSRPONME tpmkgeK\\U ~|{tos=ijj?rnljihfedba_^][ZYWVUSRQNN3(xspmjgkHI~}uqyH ?""mmokigfecba_]][ZXWUTSQNQ3Jyvrpmjg=ii9X~nGMCCoee^pnihgfdca`_]\[YWWUTON8V{xvsomjoYxNqljhfedca_^]\ZYXVTNSBR~{xuspli<mmSplihfdcba_^\\YYWRN:D}{xurpmrZ{ssopjigfdca`_]\[[XNRK ~zxurplFKK@##ulihgedca`_][cZM::K}zxurorA׼Qpjihfedba_^`eQP6 ~zwurvl T77rkjigfecb`__XN4P}{xu[B0([[oolihgedcb`eUHjn}{w?hwJʦOplkihgfdca`WOn}{1{rdbBnmkkigfedde,Q}~wtrRw QpnmljhgfedJ=[}zxuu.,&&5qonlkjhfeZED,D"Pm|xtw=))-qponlkhe^ ^ 68{utJ**<pqonmgel0 HisqP&&hlsqpmejPmls3ivpo`!!lpnggemCyylllkauuJil DHyy``nmg_*/9d[Jmmllkk22`lm HS1` RVç]lhfe6qnmlllka{{otEV .a`-eii>>RUUUY9tnnnmlllk[3Dg . sxssPXWVU].]ponnmmlkn-v} Q5_frrSXYXWWV\Wqpponnmmn@TZ19=,LT[[ZZYYXXQz"qpponon^bhLsGLvwwOY^]\\\[ZZYYZrqpppodUZEb``_^^]]\[[ZZ`Otsqqpt4%(qy 79sz')X a`_`_^]]\[\[\~vsrri+   AEaaa``_^^^]\_V utsajp?CIN mtFJ "3ovbaa``___]]duur `f kqHL!UZdbaa`__^^avvM ebaa``_^bwv;$sr sz\cbaa```dxwN( 36ut-0bdcbbaa`fxx{(IL]d'AF~ /gdddcbaae{yxg#LOGBRlnJOkfeddcccbfzyyx|CD12aqsYyz{zy{zeegjggffeddcbi{zzyxxxuu mtjhgggfeedcj||{zyy--'| }Z[[rrghhgfffedk}|{{ztzuu7 yiihhfffem}|{{EEW]%PLsuǠiiihhhffl~~}yܻxzjiiihhgl~~{.MOPabhjjiihgj~//bbbGGGWZQ"^b555fkkjiihi|SS(((a {sssellkkjlh}tt(((555dmllkkkf }}bbbGGGsssfmmmlkoƂeejnnmmnlK|AAmponnmm GGpppoono}}rqpppon ąorrqqpn MWWstrrrrKŇyutsss΅==uvutupA __uwvvwnI ~ƐMMwyxwvtAЁ??ܶܶ??}|@@__}}ww]]yyyzzy }~~~}}||{z{{MP~~~}|}|v ~~~|E }ņD{D~ ???C??????????????????(  xtxX:zKKaإJ2՜2%fc2Dmm۩~~~{{{۩w佽mmߖ?( +Rt '5F`RqNk8M( ((8>UA *e->QonlQo->!,<Id" X "/8NLh'5Yzd,<)8`Z '5LilVwiz4H(7KgDh/#0Nlw~Pna)=TlhNkE%4Yz,='6p".8N{C] >A HbnwAY->z_Q# Z|/@4G*_oPu Yzqp %3s=)  Uu^o7Lk^UtC] $Rq@X/@#-  !-/@@XSri|kl(7U /5Nbj7K3F6JE1 39;_abddd=== &/ Ql~wz ,x,,,lllppp222(Tr[}!.;Q%3m66=*^'{'&&'&%&&%$$$######""!"! PPPTTT,GQ4GPnuHc* 56:-n))()((+cHHúoZZ" ???CCC4?q~JeC\k,<  67O.+++**,W881--gggiii1C~z6J E_zh#1`91;|0,,/I%%V44|cc÷xxxssstttId:P(_ )PPPI>>>''')))$$$  b2851.---oPP---___aaa'uYzb9N(...000YYY쎎{Z^_(((s(:<x2/06 ^::Ƚ///000Vu4HNkdYYYJJJfffܵVgk555.0?iittt=S$+zR"555ccc.8:  &.JZRHH&Y33 /?;Q p444tZ[\ )1mat,6/00a,,,)7Kg6J;;;lzzz55508xXiJ===->Qo  [iIX$>>> ~~tttRRR6"!$ #BL'''IIIIII4@@@222222$-2]]]n<@IEEEH-2nnnssssssl2oeeegggaLL5&,jjj&+BBCzzzN66 |t!FP~~~7ju4T_29XemmmkXX\e"'Xe^k[]]úB##$#wXd!^CC$$  isVaBZ]t]]$$$ JQ _k%%$$& X"&/65<?H[pr%%%%3$F$ MV &&%%(&$#bo&&%%%'$*q~WbKLL'&&&%*%*OVwn{'''&&-%*]wfr(''&'0%*"mzfrCLYYY(('''3&*[en{jw((((((''6'*DIBI)((('7'*O "))(((9'*my;A*))():(*W^4:kv"sssc^^7  444KHHd``**))):(*T ^h16"%klmkee2((C55cMM]>ELQSTUTPLGA>?;8!!:::nll+***):)* q| yeop|||<77B22_>>U;;ssOLL >++i@@FY]]\[ZZYYXVVVUSSSTSTQOK== ?,, 777.,/4*9)*59kufp Fuz822Rs^hh}nJJ@..=''e00_Tga`__^]\\ZZYYXXVVUTSSSSSQPRSRUQSbYY%$$)%%~zz;<:6*8**/KRlwx{VVV)##drokklllihggee`kttja``^^]]\[ZZYYWXVVTTSSTSQPRSQPRSTVDN%% MII=<;;+6**OUQXBHOW Lxttrmllllihhgfeeennfaa`__^]\\[ZYYXXWVUUUSSSQQPOMPRSSSTJH,'2))=<<9+4**pzKQ #LSBUW_tsstqmlljjjihgffeebdbbba__^^]\\[ZYYYXWVUUTTRRQPOMNOUSSTJJII0+2((rFF>=<6+2+*>==7,0+* iqeobjk]ttsrooonmlllkjihhggfedccbaaa_^^]]\[ZZYXXXVVUTTRRRQQOONNLLKKJKLMM[]00@?>>=;-.,*,?D]eW\\VPPHtttsqpponmmlkkkiihggfecddcbaa``_^]]\[[ZYYXWVUUTTRRQQPPONMLKKJLMMMMKd8((JJJ@@?>=;1.,#d:>xx37^op*!!uutsrqqppnnnmllkjihhhgfeeddbbaa`_^^\\\[ZZYYWWVVUTSRRQPPONMMLLKLLMMLLLM3 A@@?>::-F-b  48[c}xKQ#24^aa($$huutssrqqponnnmlljiihhgffeeccbbaa`^^]]\[ZZZYXWVVUTTSSQQPPONMMLLKKLLMMMMM? AA@??98-*"CHsJONT6:!$ $%EKK9uuuutssrqpponmmmlkjjihggffdecbba``__^]]\[ZZYYXWVVUTSRRRQPPONNMLKKJLMMMMMMP/DAA@??:4-* \ V (Tuuuuttssrqpooommllkjjhhgffecdccbba`_^]]]\[ZYYXWWVVTTSRRQQPOONMLLLKKLLMMMLLHBAA@@8/.*MaZ rrvwuuttsrqppponmmllkjihhgffeedccba``__]]\\[ZYYXWVVUUTTSQRPPONNMLKKJIIKMKLMMKBAA@:..#^ZkkkLrsvwvuuussrqqpoommllkjiiigggeeddcbaa``__]]\\[ZZYXXVVUTTSRRQQPONMMMLJJIHHGLMMLF"BA>L/F/E[ʮ|uvwxvvvtssrrqqpoonmmkkjjiihffeedccbaa`_^^]\[[[ZYXWWVUUTTSRRQPPNNMLLKJJIIHILLJF= *<6;0*,[{zyyxwwvuttsrrqpponmmlkkjiihhfffddccaaa`__^]\[[ZYYXXWVUUTTSRQQOPONNMLLKJIIHGGFDD4,080*\MMI..{{zyxwwvuuusssqqpoonmmkkkjiihgffeeccbba``_^^]\[ZZYYXXVUUUTSRRQQPOONMLKKJJHHHGFEDD& 140*]\lllg22{{zyyxxwvuttsrrqqponmmmlkjjihgggededcba``__^^\\[[YYXWWWVUTTSSRQPPONNMMKKJIIHGGFEDD21#n`\EEEinrxzyywxvvuutsrrqqponnmlkkjjihggfeedccbaa`__^]\\[[ZYXXWVVUTTSRQPPPONMLLKKJIHHGGEEE7_1K\&&&d{wx{zyxxwvvutssrrqpoonnllkkjiihgfffedcbbaa__^^\]\[ZYYXXWVUUTSSRRPOONNMLKKJJIHHGFEKN&2]r|{x{zyyyxwvvutssrrqponnnllkkjiihgffeedcbaa`_^^^]\\ZYZYXWWVUTSSSRQPOONMMLLJKIIHHGFMNJH]x++v{{w{{zzxxxwuvuttrqqqponmmllkjjiihgfeeddcbaa``_^]\\[[ZYXXWVVUUTSSQQPONNMLLKJJJIHGFIMM( ^^rz{y|{{zzyxwvuuttsrrqpooonmllkjjihggffddcbbaa`_^]]\\[[YYXWWVVUTSSRQPPONNMMLKJIIHHGFLMFbG^t|{z|{{zyyyxwvuutssrqpqponnmllkjihhgffeddccbaa__^]]\[[ZYYXXWVUUSSSRQQOOONMLLKJJIHHGHKJo1^p|{w}||{{yyxwvvvuttsrrqponnmmlkiiihgggfdddcbba`__^]\\[[ZYXXXVVVUTSRQQPOONNMLKKJIHGGFFE/^g'''c{zx}}|{zzyxxwwvuutssrqqooonllkhjjhhhfgeedccba``_^]^\\[[ZYXXWWVUTTRRRQPPONMLLLKJJIHGFFDVg DCCRzz|~~}||{zyyxwvvuutssrpqpoommlckjihhgffeddcbba``_^^]\[[[YYXWVVVUTSSRQPPPOMMMLLKJIHHGFFO'|;dαlllCrt~~~}|||zyyywwvuuttsrqqpoonmmjlkjihggffeeccbba``_^]]\\[ZYXXXWUUTTSSRQPOONNMLKKJJIHHKM!*J `>>8w{~|}{{{zyxxwvuutssrqqponmmllkjjihggffeddcbba`__]]]\[ZZYYXWVVUTTSRRQQOONNLLLJJJHHMN8ZnGZ \|-- ~~||{{zzyxwvvuussrqqqoonnmllkjihhggffedcbba`___^]\\[[YYXXWWUUUSRRQPPOONMLLKJJIIMNG] @^]['?nԻl~}|{{{yyyxwwvuttsrrqppnnnmlkkjjihggfeedccba``__]]][[ZZYXWWVUTTSSRRPPOOMMLLKKIIIIG<sWa`^]S!adddZZT~~}}|{zzyxwwvvtttsrqqponnmmlkjjiihgfeeddcba```_^^]\[[ZYXXWWVUTTSRRQPPONMLLLKJJLMK a adca_^\]JJ"~}}}{{zyxuuvvvttssrqqponnmmlkkiihgfgeedccbba`_^^]]\\ZYYYWWVVUTTSRQPPPONNMLKJJMMMy2_fecba`^$;lҷ-&&p ~~}|{{zywwwvvvttsrrrqoonmmlljjihhhfffeddcbba`_^^]\\[ZYYXXWWVUTSSRQQPONNMLLKKMMV SzOihfecba`Ua:""~~~}{|zzyyxwvuujorrrrqpnnmllkkjihggfeedcccbaa`_^]]\\[[YXXWWVUUTSSQRQPONNMLKNNL+w41kkihfedba_ SAA*((z''~}}|{zzzyxwwvub5jqqqpmmnmlkkjjihhggeedcbba``__^]]\[ZYYYXWVUUUSSRRQPPONMMLNNPk ggnlkiggedc`-/c׿+"" ~~}|{{zyxxwwvs]2l qonppnmlkkjihhggffedccbb``__^]\\[ZZYYWWVVUTTRRQQPOONNLNNKM4ponljihfecba cssYWWV))~}}|{{zyyxurrrr5.**2,,\<3o:vssqpomkjigfedNct**~}}|torontfPFF%w nnllmmkiihggfeedcbbaa`_^^]]\[ZYYYXWVUTTSSRRQOOONRwutsroomljhgfe0&dȦ@~}}xmrrrU"" Vnnnqqmjihgggfedccbbaa__^]]\[[ZZXXXWVUUSSSRONNNKZ6dxwutsqpnmljigfdWi D++V~umor`,''VVVB44;knqqokjihhgfeeedcbb``__^]]\[[ZYXXWWVUTSSQNNNN&qzxwvtrronmkjigf$-gǤxx0 Y,,sq||k]@<<LpѲ>oonkkjjihggefedccba``_a^SNNN7/z<}|zyxvusrOkAh55pqqllkijhhgffeecccbaa_cd\NNS|1f~}|{yxvus1,pn1gjjQII[qqnlkjiiihgfeeddcbb``cd\NX 4 |~{zywvuKqpmLiȣJqqolkkjjihggffedccbaa_ZTP';z}|{yxw `rqoJdi4jpnlllkjihhhffeedccacccZQ"y~}{{ywotrqo''i{!!hnnmmlkkjihggffeedccdddi|}|zyuutrpo>nKK@oonnmlkkkjiiggfeeddcdb`&* k}|zyxvtsrqoQmppNpoonnmlkkjjihhffeedcd^I\u B~}{zywvttrpNgm9pppnnmmlljjihhgfffddcS/<& Z}|zywvttqq4"jsͫ rqooommmkkkjiiggfeedc0Җ( 0}M~}{zyxvusrp"2jw rrqpoonmllkjihhgeeeh*[; ?=O7[}~}|zywvtsro=jrrqqqoonnmlkjjifeeee5"KT'S$M\>-JWft|zywvusqlHjirrqppponmllkkjeeee>"r%O  %byxvusqj PjjssrqpoonnmligeeeeV 4pdwvurllUjrrrr jmmqqpponnmgeeees! C2wuunkg YkxԶ+**L lmmrrppponneeeep9 } wvrllg[jnǠ4**gmmmsrqqppmheeetA| ^ wullll XjjTGGu##mmmrsnihijeeeey8u a0wplmlkklkjj__PVV PUip OT68kmFFnmlmsofeeeeeeev{Zumlmlklkkjjp 0>?(*~u|aaai88ommmmfeaRM^d^O1 xnmlmlllkkjkjj9GH46 MRDPQlmllkeebQ!$'<qmmmlmlklkkjkjrrr#,,#]covw~qvL`amkmllgeedifeTe^vnmnmllmklkkjjjjv|fm?C FsUSX&in ] X`hjhdffcR#YnnnnmmmllkklkkjjWopBD~mt6^fc R$4 C5KMLQRBBVVVUWWWWWZS?R/|onnnnmmlllllkkkkjj)45(z FXSKo4Y")){{WVVVVUUUUV^T?SYsonnnnnmmmlllklkkjjj(12#UZ>BNlPU BEsĥeXWWVVVVUUW`T?T*voonnonnmmmlllklkkjjjAGGHJO Ssa7 (jnѹv--YXXXWWWVVUU^UFUNNpoononnnmmmmmllklkjf 2!,/ho:>KG#`ov"lӼo!!ZYYXXXXWWWVVV]V*Ipppoooonnmnmlllkkkk-%' JNpw"$"?CkĠe[ZZYYYYXXXXXWWX\U#GqqopooononnmmmllllC@C:>. 1~\\\\[[[ZYZYYYXYXXXWZVFWrqppppoononnmnmml@#% FKW]Y;.w{]]]]\\\[[[ZZZZZYYXYXXXXW#upqqqpppoonnnnmnnfBGmt$&>~0#% Svx__^^^^]\\\\\[[Z[ZZZZYXYY[XFWuEqrqpqpopoooonnj mt}k@D!m```___^^^^^]]]]\\\[[ZZZZZYYYXY#u rqqqppppopooolcizwGaaa`_``_^^^^^]]\]\\[[[[[[ZZZbY?Xrrrqqqpppppoo2 03&\ a`aa_`_____^]]]]\\\\\[ZZZZbZFYtrrrqqqqppppf)SX KPXba````_`__^^]]]]]\\[[\[[Z^Z*wrssrrrqqqpk #u}&) ekry3nu_aaa``_``__^^]]]]]\\\[\[\ZZ#ytssrrrrqqn,v~EI X] P&)`aaaa```___^^^^]^]]\\\\a[F[x}tsssrrrr: ]b03 !#v}9<69'U!babaa````__^^^^^]]]\\]`\#xetttssrm RV{>B HLX^Y_`fX^W\SXHM:?(+?C Bbcbbaaaaa```_`__c^*w wvvv478;+03.*,,/s{Tcccbbaaaa```_`_c^*wvvwv>BLR#%;>KPHccbbbbabaa`a```e_*xxwwv gmzBE)#%x`fMddcccbbbaaaa`a`f`*xwwww2x\ ~}z hn') ^edddcccbbbaaaa`f`*yxwxxj_*GLBF/eedddcdccbbaaaaaf`*yxxxww5QV)12# .0GJpw7;59fm , -ffeeddddccccbbabaea*y|xyxxt!JO:beUZ - EI$&ag8?@z)_e afeffeeddcdccccbbbfa*z yyyyxwv1w[3} `eRYZu3<<Y_Bq3[]+Lggffeeedeecddccbcbgb*zyyyyxxxwj<;JKhODz,,,|'22 =T_gghgggffefeeedddcccbib*z{zzyyyxyxxwc?!.sss999 KOŞ~++ihhgghgfffeeeddddcdccib*{+{{zzzyyxxxxxxwzzaeku~"$$55jhhgggggffffededddccjc*{9{{zzzyzyyyxxxee25[a???έ**ihhghggggfffeeeeddckc*|J{|{{{zzyyyxx::/@@R>>Ikz`R 2E^N`oƺ2pW1sEO;v)95  :LdddjVV&* T>>MhrR ^"+c QQPPj##OP l661c(+x%urmhb]WRMY^--6Fc99PPwqlfa[VPK>$ II{upje_ZTOI/e7aWyimic^XSM@Zdme,,gg((w!!gb\WN) ix^ne`\GI4zoUjjjdX0 A>cep66{{neP:Sԃ11f11^\E[=nqnV=ģs**WSurJ36<@ ?u)7yd][[0U%' ls3QVa_iY0'?A::=dc{]]J llgfGZ[HkZZZkinlNթ׶qoNAA˘88u88zjxVdQ???????????(0` $N;R6J\~qpD]3G6Jdjs{xl 3% -:fhn` 1CN{aa~¸¸D::vkm)9j]hvxd腔ԓǻlz_{$Le19$ ;#1aEO67p%3o29+ 6 Cziikw*dPXxA@%%/kusv__#"%<enn)=Ľoo.qzQssj j``^ZWSOT``~UU2[cy@rolhda]YVRNLO H 5t(gu%%vrnkgc`\XTQMJF38BIrxuqmjfb_[WTPLI?/ffq{wtplhea]ZVSOKH+!6Q!!u~zvsokhd`]YUQNJ8?f@zz}ym_njfc_[XTPMCrfmZ]]BB|ux66jjfb^[WSM)4"utmAȾȺk hda]ZTEg{t_|UUkgc`^S3+Lp{l7ũnjfc^DP{pqM::lieZ 5XTZf\!!x77ojaE)0_pj e_]3RmnxxhE~88`IFbBonlI`d Sgxжcc]WYeuqpM,/|# cF/W\mvyk^\[[Y tN,. U[U[69 T!`_]^sp hnB(ci Za_aSuW; 4 &^cbcTzsVVuNw((gedgV|{׳ukgfiWlzFFjhiSҢFFzzlkl'DDccnmo#))po׬usq //˙))vu2 IIOO ,, zz#=~c| ???(@ @>hu&47K f&4prcF_l5jfdcd|zycb}egfef}||O %%ihfh~ll%ͩgihi}ө KF dkjj} elkmbKٲjomjŗopoP??sqpNҡ޿rttݷ99{ϡ~ wv44xxbb} 00..yz{sQ~~}|*Lυ҄҂́.????????????openMSX-RELEASE_0_12_0/src/resource/openmsx.rc000066400000000000000000000067731257557151200211110ustar00rootroot00000000000000// Originally a Microsoft Developer Studio generated resource script, // but now maintained manually because we want to build with MinGW32+MSYS. // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 103 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif // Include resource info from the openMSX build process. #include "resource-info.h" //#include "tk_base.rc" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. OPENMSX ICON DISCARDABLE "openmsx.ico" #ifndef _MAC ///////////////////////////////////////////////////////////////////////////// // // Version // VS_VERSION_INFO VERSIONINFO FILEVERSION OPENMSX_VERSION_INT PRODUCTVERSION OPENMSX_VERSION_INT FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x40004L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "080004b0" BEGIN VALUE "Comments", "\0" VALUE "CompanyName", " \0" VALUE "FileDescription", "openMSX\0" VALUE "FileVersion", OPENMSX_VERSION_STR VALUE "InternalName", "openmsx\0" VALUE "LegalCopyright", "Copyright 2001-2014\0" VALUE "LegalTrademarks", "\0" VALUE "OriginalFilename", "openmsx.exe\0" VALUE "PrivateBuild", "\0" VALUE "ProductName", "openMSX\0" VALUE "ProductVersion", OPENMSX_VERSION_STR VALUE "SpecialBuild", "\0" END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x800, 1200 END END #endif // !_MAC #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // English (U.S.) (unknown sub-lang: 0xC) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, 0xC #pragma code_page(1252) #endif //_WIN32 #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED #endif // English (U.S.) (unknown sub-lang: 0xC) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED openMSX-RELEASE_0_12_0/src/security/000077500000000000000000000000001257557151200170755ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/src/security/.gitignore000066400000000000000000000000151257557151200210610ustar00rootroot00000000000000/GNUmakefile openMSX-RELEASE_0_12_0/src/security/SocketStreamWrapper.cc000066400000000000000000000011701257557151200233500ustar00rootroot00000000000000#ifdef _WIN32 #include "SocketStreamWrapper.hh" namespace openmsx { using namespace sspiutils; SocketStreamWrapper::SocketStreamWrapper(SOCKET userSock) : sock(userSock) { } uint32_t SocketStreamWrapper::Read(void* buffer, uint32_t cb) { int recvd = recv(sock, static_cast(buffer), cb, 0); if (recvd == 0 || recvd == SOCKET_ERROR) { return STREAM_ERROR; } return recvd; } uint32_t SocketStreamWrapper::Write(void* buffer, uint32_t cb) { int sent = send(sock, static_cast(buffer), cb, 0); if (sent == 0 || sent == SOCKET_ERROR) { return STREAM_ERROR; } return sent; } } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/security/SocketStreamWrapper.hh000066400000000000000000000007161257557151200233670ustar00rootroot00000000000000#ifndef SOCKET_STREAM_WRAPPER_HH #define SOCKET_STREAM_WRAPPER_HH #ifdef _WIN32 #include #include "SspiUtils.hh" namespace openmsx { class SocketStreamWrapper : public sspiutils::StreamWrapper { public: explicit SocketStreamWrapper(SOCKET userSock); uint32_t Read (void* buffer, uint32_t cb); uint32_t Write(void* buffer, uint32_t cb); private: SOCKET sock; }; } // namespace openmsx #endif // _WIN32 #endif // SOCKET_STREAM_WRAPPER_HH openMSX-RELEASE_0_12_0/src/security/SspiNegotiateServer.cc000066400000000000000000000070241257557151200233540ustar00rootroot00000000000000#ifdef _WIN32 #include "SspiNegotiateServer.hh" #include "MSXException.hh" #include "openmsx.hh" namespace openmsx { using namespace sspiutils; SspiNegotiateServer::SspiNegotiateServer(StreamWrapper& serverStream) : SspiPackageBase(serverStream, NEGOSSP_NAME_W) { // We should probably cache the security descriptor, but this // isn't exactly a performance-sensitive part of the code psd = CreateCurrentUserSecurityDescriptor(); if (!psd) { throw MSXException("CreateCurrentUserSecurityDescriptor failed"); } } SspiNegotiateServer::~SspiNegotiateServer() { LocalFree(psd); } bool SspiNegotiateServer::Authenticate() { TimeStamp tsCredsExpiry; SECURITY_STATUS ss = AcquireCredentialsHandleW( nullptr, const_cast(NEGOSSP_NAME_W), SECPKG_CRED_INBOUND, nullptr, nullptr, nullptr, nullptr, &hCreds, &tsCredsExpiry); DebugPrintSecurityStatus("AcquireCredentialsHandleW", ss); if (ss != SEC_E_OK) { return false; } SecBufferDesc secClientBufferDesc, secServerBufferDesc; SecBuffer secClientBuffer, secServerBuffer; InitTokenContextBuffer(&secClientBufferDesc, &secClientBuffer); InitTokenContextBuffer(&secServerBufferDesc, &secServerBuffer); std::vector buffer; PCtxtHandle phContext = nullptr; while (true) { // Receive another buffer from the client bool ret = RecvChunk(stream, buffer, cbMaxTokenSize); if (!ret) return false; secClientBuffer.cbBuffer = static_cast(buffer.size()); secClientBuffer.pvBuffer = &buffer[0]; ULONG fContextAttr; TimeStamp tsContextExpiry; ss = AcceptSecurityContext( &hCreds, phContext, &secClientBufferDesc, ASC_REQ_ALLOCATE_MEMORY | ASC_REQ_CONNECTION, SECURITY_NETWORK_DREP, &hContext, &secServerBufferDesc, &fContextAttr, &tsContextExpiry); DebugPrintSecurityStatus("AcceptSecurityContext", ss); if (ss != SEC_E_OK && ss != SEC_I_CONTINUE_NEEDED) { return false; } // If we have something for the client, send it if (secServerBuffer.cbBuffer) { bool ret = SendChunk(stream, secServerBuffer.pvBuffer, secServerBuffer.cbBuffer); ClearContextBuffers(&secServerBufferDesc); if (!ret) return false; } // SEC_E_OK means that we're done if (ss == SEC_E_OK) { DebugPrintSecurityPackageName(&hContext); DebugPrintSecurityPrincipalName(&hContext); return true; } // Another time around the loop phContext = &hContext; } } bool SspiNegotiateServer::Authorize() { #ifdef __GNUC__ // MinGW32's headers do not define QuerySecurityContextToken, // nor does its import library provide an export for it. // So when building with MinGW32, we load the export by hand. HMODULE secur32 = GetModuleHandleW(L"secur32.dll"); if (!secur32) { return false; } auto QuerySecurityContextToken = reinterpret_cast( GetProcAddress(secur32, "QuerySecurityContextToken")); if (!QuerySecurityContextToken) { return false; } #endif HANDLE hClientToken; SECURITY_STATUS ss = QuerySecurityContextToken(&hContext, &hClientToken); DebugPrintSecurityStatus("QuerySecurityContextToken", ss); if (ss != SEC_E_OK) { return false; } PRIVILEGE_SET privilegeSet; DWORD dwPrivSetSize = sizeof(privilegeSet); DWORD dwGranted; BOOL fAccess; BOOL ret = AccessCheck( psd, hClientToken, ACCESS_ALL, const_cast(&mapping), &privilegeSet, &dwPrivSetSize, &dwGranted, &fAccess); DebugPrintSecurityBool("AccessCheck", ret); DebugPrintSecurityPrincipalName(&hContext); CloseHandle(hClientToken); return ret && fAccess; } } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/security/SspiNegotiateServer.hh000066400000000000000000000007101257557151200233610ustar00rootroot00000000000000#ifndef SSPI_NEGOTIATE_SERVER_HH #define SSPI_NEGOTIATE_SERVER_HH #ifdef _WIN32 #include "SspiUtils.hh" namespace openmsx { class SspiNegotiateServer : public sspiutils::SspiPackageBase { public: explicit SspiNegotiateServer(sspiutils::StreamWrapper& serverStream); ~SspiNegotiateServer(); bool Authenticate(); bool Authorize(); private: PSECURITY_DESCRIPTOR psd; }; } // namespace openmsx #endif // _WIN32 #endif // SSPI_NEGOTIATE_SERVER_HH openMSX-RELEASE_0_12_0/src/security/SspiUtils.cc000066400000000000000000000165631257557151200213560ustar00rootroot00000000000000#ifdef _WIN32 #include "SspiUtils.hh" #include "MSXException.hh" #include #include #include // // NOTE: This file MUST be kept in sync between the openmsx and openmsx-debugger projects // namespace openmsx { namespace sspiutils { SspiPackageBase::SspiPackageBase(StreamWrapper& userStream, const SEC_WCHAR* securityPackage) : stream(userStream) , cbMaxTokenSize(GetPackageMaxTokenSize(securityPackage)) { memset(&hCreds, 0, sizeof(hCreds)); memset(&hContext, 0, sizeof(hContext)); if (!cbMaxTokenSize) { throw MSXException("GetPackageMaxTokenSize failed"); } } SspiPackageBase::~SspiPackageBase() { DeleteSecurityContext(&hContext); FreeCredentialsHandle(&hCreds); } void InitTokenContextBuffer(PSecBufferDesc pSecBufferDesc, PSecBuffer pSecBuffer) { pSecBuffer->BufferType = SECBUFFER_TOKEN; pSecBuffer->cbBuffer = 0; pSecBuffer->pvBuffer = nullptr; pSecBufferDesc->ulVersion = SECBUFFER_VERSION; pSecBufferDesc->cBuffers = 1; pSecBufferDesc->pBuffers = pSecBuffer; } void ClearContextBuffers(PSecBufferDesc pSecBufferDesc) { for (ULONG i = 0; i < pSecBufferDesc->cBuffers; i ++) { FreeContextBuffer(pSecBufferDesc->pBuffers[i].pvBuffer); pSecBufferDesc->pBuffers[i].cbBuffer = 0; pSecBufferDesc->pBuffers[i].pvBuffer = nullptr; } } void DebugPrintSecurityStatus(const char* context, SECURITY_STATUS ss) { (void)&context; (void)&ss; #if 0 switch (ss) { case SEC_E_OK: std::cerr << context << ": SEC_E_OK" << std::endl; break; case SEC_I_CONTINUE_NEEDED: std::cerr << context << ": SEC_I_CONTINUE_NEEDED" << std::endl; break; case SEC_E_INVALID_TOKEN: std::cerr << context << ": SEC_E_INVALID_TOKEN" << std::endl; break; case SEC_E_BUFFER_TOO_SMALL: std::cerr << context << ": SEC_E_BUFFER_TOO_SMALL" << std::endl; break; case SEC_E_INVALID_HANDLE: std::cerr << context << ": SEC_E_INVALID_HANDLE" << std::endl; break; case SEC_E_WRONG_PRINCIPAL: std::cerr << context << ": SEC_E_WRONG_PRINCIPAL" << std::endl; break; default: std::cerr << context << ": " << ss << std::endl; break; } #endif } void DebugPrintSecurityBool(const char* context, BOOL ret) { (void)&context; (void)&ret; #if 0 if (ret) { std::cerr << context << ": true" << std::endl; } else { std::cerr << context << ": false - " << GetLastError() << std::endl; } #endif } void DebugPrintSecurityPackageName(PCtxtHandle phContext) { (void)&phContext; #if 0 SecPkgContext_PackageInfoA package; SECURITY_STATUS ss = QueryContextAttributesA(phContext, SECPKG_ATTR_PACKAGE_INFO, &package); if (ss == SEC_E_OK) { std::cerr << "Using " << package.PackageInfo->Name << " package" << std::endl; } #endif } void DebugPrintSecurityPrincipalName(PCtxtHandle phContext) { (void)&phContext; #if 0 SecPkgContext_NamesA name; SECURITY_STATUS ss = QueryContextAttributesA(phContext, SECPKG_ATTR_NAMES, &name); if (ss == SEC_E_OK) { std::cerr << "Client principal " << name.sUserName << std::endl; } #endif } void DebugPrintSecurityDescriptor(PSECURITY_DESCRIPTOR psd) { (void)&psd; #if 0 char* sddl; BOOL ret = ConvertSecurityDescriptorToStringSecurityDescriptorA( psd, SDDL_REVISION, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | LABEL_SECURITY_INFORMATION, &sddl, nullptr); if (ret) { std::cerr << "SecurityDescriptor: " << sddl << std::endl; LocalFree(sddl); } #endif } // If successful, caller must free the results with LocalFree() // If unsuccessful, returns null static PTOKEN_USER GetProcessToken() { PTOKEN_USER pToken = nullptr; HANDLE hProcessToken; BOOL ret = OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &hProcessToken); DebugPrintSecurityBool("OpenProcessToken", ret); if (ret) { DWORD cbToken; ret = GetTokenInformation(hProcessToken, TokenUser, nullptr, 0, &cbToken); assert(!ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER && cbToken); pToken = static_cast(LocalAlloc(LMEM_ZEROINIT, cbToken)); if (pToken) { ret = GetTokenInformation(hProcessToken, TokenUser, pToken, cbToken, &cbToken); DebugPrintSecurityBool("GetTokenInformation", ret); if (!ret) { LocalFree(pToken); pToken = nullptr; } } CloseHandle(hProcessToken); } return pToken; } // If successful, caller must free the results with LocalFree() // If unsuccessful, returns null PSECURITY_DESCRIPTOR CreateCurrentUserSecurityDescriptor() { PSECURITY_DESCRIPTOR psd = nullptr; PTOKEN_USER pToken = GetProcessToken(); if (pToken) { PSID pUserSid = pToken->User.Sid; const DWORD cbEachAce = sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD); const DWORD cbACL = sizeof(ACL) + cbEachAce + GetLengthSid(pUserSid); // Allocate the SD and the ACL in one allocation, so we only have one buffer to manage // The SD structure ends with a pointer, so the start of the ACL will be well aligned BYTE* buffer = static_cast(LocalAlloc(LMEM_ZEROINIT, SECURITY_DESCRIPTOR_MIN_LENGTH + cbACL)); if (buffer) { psd = static_cast(buffer); PACL pacl = reinterpret_cast(buffer + SECURITY_DESCRIPTOR_MIN_LENGTH); PACCESS_ALLOWED_ACE pUserAce; if (InitializeSecurityDescriptor(psd, SECURITY_DESCRIPTOR_REVISION) && InitializeAcl(pacl, cbACL, ACL_REVISION) && AddAccessAllowedAce(pacl, ACL_REVISION, ACCESS_ALL, pUserSid) && SetSecurityDescriptorDacl(psd, TRUE, pacl, FALSE) && // Need to set the Group and Owner on the SD in order to use it with AccessCheck() GetAce(pacl, 0, reinterpret_cast(&pUserAce)) && SetSecurityDescriptorGroup(psd, &pUserAce->SidStart, FALSE) && SetSecurityDescriptorOwner(psd, &pUserAce->SidStart, FALSE)) { buffer = nullptr; } else { psd = nullptr; } LocalFree(buffer); } LocalFree(pToken); } if (psd) { assert(IsValidSecurityDescriptor(psd)); DebugPrintSecurityDescriptor(psd); } return psd; } unsigned long GetPackageMaxTokenSize(const SEC_WCHAR* package) { PSecPkgInfoW pkgInfo; SECURITY_STATUS ss = QuerySecurityPackageInfoW(const_cast(package), &pkgInfo); DebugPrintSecurityStatus("QuerySecurityPackageInfoW", ss); if (ss != SEC_E_OK) return 0; unsigned long cbMaxToken = pkgInfo->cbMaxToken; FreeContextBuffer(pkgInfo); return cbMaxToken; } static bool Send(StreamWrapper& stream, void* buffer, uint32_t cb) { uint32_t sent = 0; while (sent < cb) { uint32_t ret = stream.Write(static_cast(buffer) + sent, cb - sent); if (ret == STREAM_ERROR) return false; sent += ret; } return true; } bool SendChunk(StreamWrapper& stream, void* buffer, uint32_t cb) { uint32_t nl = htonl(cb); if (!Send(stream, &nl, sizeof(nl))) { return false; } return Send(stream, buffer, cb); } static bool Recv(StreamWrapper& stream, void* buffer, uint32_t cb) { uint32_t recvd = 0; while (recvd < cb) { uint32_t ret = stream.Read(static_cast(buffer) + recvd, cb - recvd); if (ret == STREAM_ERROR) return false; recvd += ret; } return true; } static bool RecvChunkSize(StreamWrapper& stream, uint32_t* pcb) { uint32_t cb; bool ret = Recv(stream, &cb, sizeof(cb)); if (ret) { *pcb = ntohl(cb); } return ret; } bool RecvChunk(StreamWrapper& stream, std::vector& buffer, uint32_t cbMaxSize) { uint32_t cb; if (!RecvChunkSize(stream, &cb) || cb > cbMaxSize) { return false; } buffer.resize(cb); if (!Recv(stream, &buffer[0], cb)) { return false; } return true; } } // namespace sspiutils } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/security/SspiUtils.hh000066400000000000000000000042311257557151200213550ustar00rootroot00000000000000#ifndef SSPI_UTILS_HH #define SSPI_UTILS_HH #ifdef _WIN32 #include #ifdef __GNUC__ // MinGW32 requires that subauth.h be included before security.h, in order to define several things // This differs from VC++, which only needs security.h #include // MinGW32 does not define NEGOSSP_NAME_W anywhere. It should. #define NEGOSSP_NAME_W L"Negotiate" #endif #ifndef SECURITY_WIN32 #define SECURITY_WIN32 #endif #include #include #include // // NOTE: This file MUST be kept in sync between the openmsx and openmsx-debugger projects // namespace openmsx { namespace sspiutils { const unsigned STREAM_ERROR = 0xffffffff; class StreamWrapper { public: virtual uint32_t Read (void* buffer, uint32_t cb) = 0; virtual uint32_t Write(void* buffer, uint32_t cb) = 0; }; class SspiPackageBase { protected: CredHandle hCreds; CtxtHandle hContext; StreamWrapper& stream; const unsigned int cbMaxTokenSize; SspiPackageBase(StreamWrapper& stream, const SEC_WCHAR* securityPackage); ~SspiPackageBase(); }; // Generic access control flags, used with AccessCheck const DWORD ACCESS_READ = 0x1; const DWORD ACCESS_WRITE = 0x2; const DWORD ACCESS_EXECUTE = 0x4; const DWORD ACCESS_ALL = ACCESS_READ | ACCESS_WRITE | ACCESS_EXECUTE; const GENERIC_MAPPING mapping = { ACCESS_READ, ACCESS_WRITE, ACCESS_EXECUTE, ACCESS_ALL }; void InitTokenContextBuffer(PSecBufferDesc pSecBufferDesc, PSecBuffer pSecBuffer); void ClearContextBuffers(PSecBufferDesc pSecBufferDesc); void DebugPrintSecurityStatus(const char* context, SECURITY_STATUS ss); void DebugPrintSecurityBool(const char* context, BOOL ret); void DebugPrintSecurityPackageName(PCtxtHandle phContext); void DebugPrintSecurityPrincipalName(PCtxtHandle phContext); void DebugPrintSecurityDescriptor(PSECURITY_DESCRIPTOR psd); PSECURITY_DESCRIPTOR CreateCurrentUserSecurityDescriptor(); unsigned long GetPackageMaxTokenSize(const SEC_WCHAR* package); bool SendChunk(StreamWrapper& stream, void* buffer, uint32_t cb); bool RecvChunk(StreamWrapper& stream, std::vector& buffer, uint32_t cbMaxSize); } // namespace sspiutils } // namespace openmsx #endif // _WIN32 #endif // SSPI_UTILS_HH openMSX-RELEASE_0_12_0/src/serial/000077500000000000000000000000001257557151200165055ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/src/serial/.gitignore000066400000000000000000000001761257557151200205010ustar00rootroot00000000000000/*.ROM /*.bb /*.bbg /*.da /*.dsk /*.prof /*.rom /*.rpo /*.swp /*.vim /.debug /.kdbgrc.openmsx /.xvpics /gmon.out /GNUmakefile openMSX-RELEASE_0_12_0/src/serial/ClockPin.cc000066400000000000000000000055101257557151200205170ustar00rootroot00000000000000#include "ClockPin.hh" #include "serialize.hh" #include using std::string; namespace openmsx { ClockPin::ClockPin(Scheduler& scheduler, ClockPinListener* listener_) : Schedulable(scheduler), listener(listener_) , referenceTime(EmuTime::zero) , periodic(false) , status(false), signalEdge(false) { } void ClockPin::setState(bool newStatus, EmuTime::param time) { periodic = false; if (signalEdge) { unschedule(); } if (signalEdge && !status && newStatus) { // pos edge status = newStatus; if (listener) { listener->signalPosEdge(*this, time); } } else { status = newStatus; } if (listener) { listener->signal(*this, time); } } void ClockPin::setPeriodicState(EmuDuration::param total, EmuDuration::param hi, EmuTime::param time) { referenceTime = time; totalDur = total; hiDur = hi; if (listener) { if (periodic) { unschedule(); } periodic = true; if (signalEdge) { executeUntil(time); } listener->signal(*this, time); } else { periodic = true; } } bool ClockPin::getState(EmuTime::param time) const { if (!periodic) { return status; } else { return ((time - referenceTime) % totalDur) < hiDur; } } EmuDuration::param ClockPin::getTotalDuration() const { assert(periodic); return totalDur; } EmuDuration::param ClockPin::getHighDuration() const { assert(periodic); return hiDur; } int ClockPin::getTicksBetween(EmuTime::param begin, EmuTime::param end) const { assert(begin <= end); if (!periodic) { return 0; } if (totalDur > EmuDuration::zero) { int a = (begin < referenceTime) ? 0 : (begin - referenceTime) / totalDur; int b = (end - referenceTime) / totalDur; return b - a; } else { return 0; } } void ClockPin::generateEdgeSignals(bool wanted, EmuTime::param time) { if (signalEdge != wanted) { signalEdge = wanted; if (periodic) { if (signalEdge) { EmuTime tmp(referenceTime); while (tmp < time) { tmp += totalDur; } if (listener) { schedule(tmp); } } else { unschedule(); } } } } void ClockPin::unschedule() { removeSyncPoint(); } void ClockPin::schedule(EmuTime::param time) { assert(signalEdge && periodic && listener); setSyncPoint(time); } void ClockPin::executeUntil(EmuTime::param time) { assert(signalEdge && periodic && listener); listener->signalPosEdge(*this, time); if (signalEdge && (totalDur > EmuDuration::zero)) { schedule(time + totalDur); } } template void ClockPin::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("totalDur", totalDur); ar.serialize("hiDur", hiDur); ar.serialize("referenceTime", referenceTime); ar.serialize("periodic", periodic); ar.serialize("status", status); ar.serialize("signalEdge", signalEdge); } INSTANTIATE_SERIALIZE_METHODS(ClockPin); } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/ClockPin.hh000066400000000000000000000026001257557151200205260ustar00rootroot00000000000000#ifndef CLOCKPIN_HH #define CLOCKPIN_HH #include "EmuTime.hh" #include "Schedulable.hh" namespace openmsx { class Scheduler; class ClockPin; class ClockPinListener { public: virtual void signal(ClockPin& pin, EmuTime::param time) = 0; virtual void signalPosEdge(ClockPin& pin, EmuTime::param time) = 0; protected: ~ClockPinListener() {} }; class ClockPin final : public Schedulable { public: explicit ClockPin(Scheduler& scheduler, ClockPinListener* listener = nullptr); // input side void setState(bool status, EmuTime::param time); void setPeriodicState(EmuDuration::param total, EmuDuration::param hi, EmuTime::param time); // output side bool getState(EmuTime::param time) const; bool isPeriodic() const { return periodic; } EmuDuration::param getTotalDuration() const; EmuDuration::param getHighDuration() const; int getTicksBetween(EmuTime::param begin, EmuTime::param end) const; // control void generateEdgeSignals(bool wanted, EmuTime::param time); template void serialize(Archive& ar, unsigned version); private: void unschedule(); void schedule(EmuTime::param time); void executeUntil(EmuTime::param time) override; ClockPinListener* const listener; EmuDuration totalDur; EmuDuration hiDur; EmuTime referenceTime; bool periodic; bool status; bool signalEdge; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serial/DummyMidiInDevice.cc000066400000000000000000000006341257557151200223240ustar00rootroot00000000000000#include "DummyMidiInDevice.hh" namespace openmsx { void DummyMidiInDevice::signal(EmuTime::param /*time*/) { // ignore } string_ref DummyMidiInDevice::getDescription() const { return ""; } void DummyMidiInDevice::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/) { } void DummyMidiInDevice::unplugHelper(EmuTime::param /*time*/) { } } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/DummyMidiInDevice.hh000066400000000000000000000006361257557151200223400ustar00rootroot00000000000000#ifndef DUMMYMIDIINDEVICE_HH #define DUMMYMIDIINDEVICE_HH #include "MidiInDevice.hh" namespace openmsx { class DummyMidiInDevice final : public MidiInDevice { public: void signal(EmuTime::param time) override; string_ref getDescription() const override; void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serial/DummyMidiOutDevice.cc000066400000000000000000000006231257557151200225230ustar00rootroot00000000000000#include "DummyMidiOutDevice.hh" namespace openmsx { void DummyMidiOutDevice::recvByte(byte /*value*/, EmuTime::param /*time*/) { // ignore } string_ref DummyMidiOutDevice::getDescription() const { return ""; } void DummyMidiOutDevice::plugHelper( Connector& /*connector*/, EmuTime::param /*time*/) { } void DummyMidiOutDevice::unplugHelper(EmuTime::param /*time*/) { } } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/DummyMidiOutDevice.hh000066400000000000000000000007201257557151200225330ustar00rootroot00000000000000#ifndef DUMMYMIDIOUTDEVICE_HH #define DUMMYMIDIOUTDEVICE_HH #include "MidiOutDevice.hh" namespace openmsx { class DummyMidiOutDevice final : public MidiOutDevice { public: // SerialDataInterface (part) void recvByte(byte value, EmuTime::param time) override; string_ref getDescription() const override; void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serial/DummyRS232Device.cc000066400000000000000000000007571257557151200217340ustar00rootroot00000000000000#include "DummyRS232Device.hh" namespace openmsx { void DummyRS232Device::signal(EmuTime::param /*time*/) { // ignore } string_ref DummyRS232Device::getDescription() const { return ""; } void DummyRS232Device::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/) { } void DummyRS232Device::unplugHelper(EmuTime::param /*time*/) { } void DummyRS232Device::recvByte(byte /*value*/, EmuTime::param /*time*/) { // ignore } } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/DummyRS232Device.hh000066400000000000000000000007631257557151200217430ustar00rootroot00000000000000#ifndef DUMMYRS232DEVICE_HH #define DUMMYRS232DEVICE_HH #include "RS232Device.hh" namespace openmsx { class DummyRS232Device final : public RS232Device { public: void signal(EmuTime::param time) override; string_ref getDescription() const override; void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; // SerialDataInterface (part) void recvByte(byte value, EmuTime::param time) override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serial/I8251.cc000066400000000000000000000224451257557151200175330ustar00rootroot00000000000000#include "I8251.hh" #include "serialize.hh" #include "unreachable.hh" #include using std::string; namespace openmsx { const byte STAT_TXRDY = 0x01; const byte STAT_RXRDY = 0x02; const byte STAT_TXEMPTY = 0x04; const byte STAT_PE = 0x08; const byte STAT_OE = 0x10; const byte STAT_FE = 0x20; const byte STAT_SYNBRK = 0x40; const byte STAT_DSR = 0x80; const byte MODE_BAUDRATE = 0x03; const byte MODE_SYNCHRONOUS = 0x00; const byte MODE_RATE1 = 0x01; const byte MODE_RATE16 = 0x02; const byte MODE_RATE64 = 0x03; const byte MODE_WORDLENGTH = 0x0C; const byte MODE_5BIT = 0x00; const byte MODE_6BIT = 0x04; const byte MODE_7BIT = 0x08; const byte MODE_8BIT = 0x0C; const byte MODE_PARITYEN = 0x10; const byte MODE_PARITODD = 0x00; const byte MODE_PARITEVEN = 0x20; const byte MODE_STOP_BITS = 0xC0; const byte MODE_STOP_INV = 0x00; const byte MODE_STOP_1 = 0x40; const byte MODE_STOP_15 = 0x80; const byte MODE_STOP_2 = 0xC0; const byte MODE_SINGLESYNC = 0x80; const byte CMD_TXEN = 0x01; const byte CMD_DTR = 0x02; const byte CMD_RXE = 0x04; const byte CMD_SBRK = 0x08; const byte CMD_RSTERR = 0x10; const byte CMD_RTS = 0x20; const byte CMD_RESET = 0x40; const byte CMD_HUNT = 0x80; I8251::I8251(Scheduler& scheduler, I8251Interface& interf_, EmuTime::param time) : syncRecv (scheduler) , syncTrans(scheduler) , interf(interf_), clock(scheduler) { reset(time); } void I8251::reset(EmuTime::param time) { // initialize these to avoid UMR on savestate // TODO investigate correct initial state after reset charLength = 0; recvDataBits = SerialDataInterface::DATA_8; recvStopBits = SerialDataInterface::STOP_1; recvParityBit = SerialDataInterface::EVEN; recvParityEnabled = false; recvBuf = 0; recvReady = false; sendByte = 0; sendBuffer = 0; mode = 0; sync1 = sync2 = 0; status = STAT_TXRDY | STAT_TXEMPTY; command = 0xFF; // make sure all bits change writeCommand(0, time); cmdFaze = FAZE_MODE; } byte I8251::readIO(word port, EmuTime::param time) { byte result; switch (port & 1) { case 0: result = readTrans(time); break; case 1: result = readStatus(time); break; default: UNREACHABLE; return 0; } return result; } byte I8251::peekIO(word port, EmuTime::param /*time*/) const { switch (port & 1) { case 0: return recvBuf; case 1: return status; // TODO peekStatus() default: UNREACHABLE; return 0; } } void I8251::writeIO(word port, byte value, EmuTime::param time) { switch (port & 1) { case 0: writeTrans(value, time); break; case 1: switch (cmdFaze) { case FAZE_MODE: setMode(value); if ((mode & MODE_BAUDRATE) == MODE_SYNCHRONOUS) { cmdFaze = FAZE_SYNC1; } else { cmdFaze = FAZE_CMD; } break; case FAZE_SYNC1: sync1 = value; if (mode & MODE_SINGLESYNC) { cmdFaze = FAZE_CMD; } else { cmdFaze = FAZE_SYNC2; } break; case FAZE_SYNC2: sync2 = value; cmdFaze = FAZE_CMD; break; case FAZE_CMD: if (value & CMD_RESET) { cmdFaze = FAZE_MODE; } else { writeCommand(value, time); } break; default: UNREACHABLE; } break; default: UNREACHABLE; } } void I8251::setMode(byte value) { mode = value; SerialDataInterface::DataBits dataBits; switch (mode & MODE_WORDLENGTH) { case MODE_5BIT: dataBits = SerialDataInterface::DATA_5; break; case MODE_6BIT: dataBits = SerialDataInterface::DATA_6; break; case MODE_7BIT: dataBits = SerialDataInterface::DATA_7; break; case MODE_8BIT: dataBits = SerialDataInterface::DATA_8; break; default: UNREACHABLE; dataBits = SerialDataInterface::DATA_8; } interf.setDataBits(dataBits); SerialDataInterface::StopBits stopBits; switch(mode & MODE_STOP_BITS) { case MODE_STOP_INV: stopBits = SerialDataInterface::STOP_INV; break; case MODE_STOP_1: stopBits = SerialDataInterface::STOP_1; break; case MODE_STOP_15: stopBits = SerialDataInterface::STOP_15; break; case MODE_STOP_2: stopBits = SerialDataInterface::STOP_2; break; default: UNREACHABLE; stopBits = SerialDataInterface::STOP_2; } interf.setStopBits(stopBits); bool parityEnable = (mode & MODE_PARITYEN) != 0; SerialDataInterface::ParityBit parity = (mode & MODE_PARITEVEN) ? SerialDataInterface::EVEN : SerialDataInterface::ODD; interf.setParityBit(parityEnable, parity); unsigned baudrate; switch (mode & MODE_BAUDRATE) { case MODE_SYNCHRONOUS: baudrate = 1; break; case MODE_RATE1: baudrate = 1; break; case MODE_RATE16: baudrate = 16; break; case MODE_RATE64: baudrate = 64; break; default: UNREACHABLE; baudrate = 1; } charLength = (((2 * (1 + unsigned(dataBits) + (parityEnable ? 1 : 0))) + unsigned(stopBits)) * baudrate) / 2; } void I8251::writeCommand(byte value, EmuTime::param time) { byte oldCommand = command; command = value; // CMD_RESET, CMD_TXEN, CMD_RXE handled in other routines interf.setRTS((command & CMD_RTS) != 0, time); interf.setDTR((command & CMD_DTR) != 0, time); if (!(command & CMD_TXEN)) { // disable transmitter syncTrans.removeSyncPoint(); status |= STAT_TXRDY | STAT_TXEMPTY; } if (command & CMD_RSTERR) { status &= ~(STAT_PE | STAT_OE | STAT_FE); } if (command & CMD_SBRK) { // TODO } if (command & CMD_HUNT) { // TODO } if ((command ^ oldCommand) & CMD_RXE) { if (command & CMD_RXE) { // enable receiver status &= ~(STAT_PE | STAT_OE | STAT_FE); // TODO recvReady = true; } else { // disable receiver syncRecv.removeSyncPoint(); status &= ~(STAT_PE | STAT_OE | STAT_FE); // TODO status &= ~STAT_RXRDY; } interf.signal(time); } } byte I8251::readStatus(EmuTime::param time) { byte result = status; if (interf.getDSR(time)) { result |= STAT_DSR; } return result; } byte I8251::readTrans(EmuTime::param time) { status &= ~STAT_RXRDY; interf.setRxRDY(false, time); return recvBuf; } void I8251::writeTrans(byte value, EmuTime::param time) { if (!(command & CMD_TXEN)) { return; } if (status & STAT_TXEMPTY) { // not sending send(value, time); } else { sendBuffer = value; status &= ~STAT_TXRDY; } } void I8251::setParityBit(bool enable, ParityBit parity) { recvParityEnabled = enable; recvParityBit = parity; } void I8251::recvByte(byte value, EmuTime::param time) { // TODO STAT_PE / STAT_FE / STAT_SYNBRK assert(recvReady && (command & CMD_RXE)); if (status & STAT_RXRDY) { status |= STAT_OE; } else { recvBuf = value; status |= STAT_RXRDY; interf.setRxRDY(true, time); } recvReady = false; if (clock.isPeriodic()) { EmuTime next = time + (clock.getTotalDuration() * charLength); syncRecv.setSyncPoint(next); } } bool I8251::isRecvEnabled() { return (command & CMD_RXE) != 0; } void I8251::send(byte value, EmuTime::param time) { status &= ~STAT_TXEMPTY; sendByte = value; if (clock.isPeriodic()) { EmuTime next = time + (clock.getTotalDuration() * charLength); syncTrans.setSyncPoint(next); } } void I8251::execRecv(EmuTime::param time) { assert(command & CMD_RXE); recvReady = true; interf.signal(time); } void I8251::execTrans(EmuTime::param time) { assert(!(status & STAT_TXEMPTY) && (command & CMD_TXEN)); interf.recvByte(sendByte, time); if (status & STAT_TXRDY) { status |= STAT_TXEMPTY; } else { status |= STAT_TXRDY; send(sendBuffer, time); } } static enum_string dataBitsInfo[] = { { "5", SerialDataInterface::DATA_5 }, { "6", SerialDataInterface::DATA_6 }, { "7", SerialDataInterface::DATA_7 }, { "8", SerialDataInterface::DATA_8 } }; SERIALIZE_ENUM(SerialDataInterface::DataBits, dataBitsInfo); static enum_string stopBitsInfo[] = { { "INVALID", SerialDataInterface::STOP_INV }, { "1", SerialDataInterface::STOP_1 }, { "1.5", SerialDataInterface::STOP_15 }, { "2", SerialDataInterface::STOP_2 } }; SERIALIZE_ENUM(SerialDataInterface::StopBits, stopBitsInfo); static enum_string parityBitInfo[] = { { "EVEN", SerialDataInterface::EVEN }, { "ODD", SerialDataInterface::ODD } }; SERIALIZE_ENUM(SerialDataInterface::ParityBit, parityBitInfo); static enum_string cmdFazeInfo[] = { { "MODE", I8251::FAZE_MODE }, { "SYNC1", I8251::FAZE_SYNC1 }, { "SYNC2", I8251::FAZE_SYNC2 }, { "CMD", I8251::FAZE_CMD } }; SERIALIZE_ENUM(I8251::CmdFaze, cmdFazeInfo); // version 1: initial version // version 2: removed 'userData' from Schedulable template void I8251::serialize(Archive& ar, unsigned version) { if (ar.versionAtLeast(version, 2)) { ar.serialize("syncRecv", syncRecv); ar.serialize("syncTrans", syncTrans); } else { Schedulable::restoreOld(ar, {&syncRecv, &syncTrans}); } ar.serialize("clock", clock); ar.serialize("charLength", charLength); ar.serialize("recvDataBits", recvDataBits); ar.serialize("recvStopBits", recvStopBits); ar.serialize("recvParityBit", recvParityBit); ar.serialize("recvParityEnabled", recvParityEnabled); ar.serialize("recvBuf", recvBuf); ar.serialize("recvReady", recvReady); ar.serialize("sendByte", sendByte); ar.serialize("sendBuffer", sendBuffer); ar.serialize("status", status); ar.serialize("command", command); ar.serialize("mode", mode); ar.serialize("sync1", sync1); ar.serialize("sync2", sync2); ar.serialize("cmdFaze", cmdFaze); } INSTANTIATE_SERIALIZE_METHODS(I8251); } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/I8251.hh000066400000000000000000000055611257557151200175450ustar00rootroot00000000000000// This class implements the Intel 8251 chip (UART) #ifndef I8251_HH #define I8251_HH #include "ClockPin.hh" #include "SerialDataInterface.hh" #include "Schedulable.hh" #include "serialize_meta.hh" #include "openmsx.hh" #include "outer.hh" namespace openmsx { class I8251Interface : public SerialDataInterface { public: virtual void setRxRDY(bool status, EmuTime::param time) = 0; virtual void setDTR(bool status, EmuTime::param time) = 0; virtual void setRTS(bool status, EmuTime::param time) = 0; virtual bool getDSR(EmuTime::param time) = 0; virtual bool getCTS(EmuTime::param time) = 0; // TODO use this virtual void signal(EmuTime::param time) = 0; protected: I8251Interface() {} ~I8251Interface() {} }; class I8251 final : public SerialDataInterface { public: I8251(Scheduler& scheduler, I8251Interface& interf, EmuTime::param time); void reset(EmuTime::param time); byte readIO(word port, EmuTime::param time); byte peekIO(word port, EmuTime::param time) const; void writeIO(word port, byte value, EmuTime::param time); ClockPin& getClockPin() { return clock; } bool isRecvReady() { return recvReady; } bool isRecvEnabled(); // SerialDataInterface void setDataBits(DataBits bits) override { recvDataBits = bits; } void setStopBits(StopBits bits) override { recvStopBits = bits; } void setParityBit(bool enable, ParityBit parity) override; void recvByte(byte value, EmuTime::param time) override; // Schedulable struct SyncRecv : Schedulable { friend class I8251; SyncRecv(Scheduler& s) : Schedulable(s) {} void executeUntil(EmuTime::param time) override { auto& i8251 = OUTER(I8251, syncRecv); i8251.execRecv(time); } } syncRecv; struct SyncTrans : Schedulable { friend class I8251; SyncTrans(Scheduler& s) : Schedulable(s) {} void executeUntil(EmuTime::param time) override { auto& i8251 = OUTER(I8251, syncTrans); i8251.execTrans(time); } } syncTrans; void execRecv(EmuTime::param time); void execTrans(EmuTime::param time); template void serialize(Archive& ar, unsigned version); // public for serialize enum CmdFaze { FAZE_MODE, FAZE_SYNC1, FAZE_SYNC2, FAZE_CMD }; private: void setMode(byte mode); void writeCommand(byte value, EmuTime::param time); byte readStatus(EmuTime::param time); byte readTrans(EmuTime::param time); void writeTrans(byte value, EmuTime::param time); void send(byte value, EmuTime::param time); I8251Interface& interf; ClockPin clock; unsigned charLength; CmdFaze cmdFaze; SerialDataInterface::DataBits recvDataBits; SerialDataInterface::StopBits recvStopBits; SerialDataInterface::ParityBit recvParityBit; bool recvParityEnabled; byte recvBuf; bool recvReady; byte sendByte; byte sendBuffer; byte status; byte command; byte mode; byte sync1, sync2; }; SERIALIZE_CLASS_VERSION(I8251, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serial/I8254.cc000066400000000000000000000273011257557151200175320ustar00rootroot00000000000000#include "I8254.hh" #include "EmuTime.hh" #include "ClockPin.hh" #include "serialize.hh" #include "unreachable.hh" #include "memory.hh" #include namespace openmsx { static const byte READ_BACK = 0xC0; static const byte RB_CNTR0 = 0x02; static const byte RB_CNTR1 = 0x04; static const byte RB_CNTR2 = 0x08; static const byte RB_STATUS = 0x10; static const byte RB_COUNT = 0x20; class Counter { public: Counter(Scheduler& scheduler, ClockPinListener* listener, EmuTime::param time); void reset(EmuTime::param time); byte readIO(EmuTime::param time); byte peekIO(EmuTime::param time) const; void writeIO(byte value, EmuTime::param time); void setGateStatus(bool status, EmuTime::param time); void writeControlWord(byte value, EmuTime::param time); void latchStatus(EmuTime::param time); void latchCounter(EmuTime::param time); template void serialize(Archive& ar, unsigned version); //private: enum ByteOrder {LOW, HIGH}; private: static const byte WRT_FRMT = 0x30; static const byte WF_LATCH = 0x00; static const byte WF_LOW = 0x10; static const byte WF_HIGH = 0x20; static const byte WF_BOTH = 0x30; static const byte CNTR_MODE = 0x0E; static const byte CNTR_M0 = 0x00; static const byte CNTR_M1 = 0x02; static const byte CNTR_M2 = 0x04; static const byte CNTR_M3 = 0x06; static const byte CNTR_M4 = 0x08; static const byte CNTR_M5 = 0x0A; static const byte CNTR_M2_ = 0x0C; static const byte CNTR_M3_ = 0x0E; void writeLoad(word value, EmuTime::param time); void advance(EmuTime::param time); ClockPin clock; ClockPin output; EmuTime currentTime; int counter; word latchedCounter, counterLoad; byte control, latchedControl; bool ltchCtrl, ltchCntr; ByteOrder readOrder, writeOrder; byte writeLatch; bool gate; bool active, triggered, counting; friend class I8254; }; // class I8254 I8254::I8254(Scheduler& scheduler, ClockPinListener* output0, ClockPinListener* output1, ClockPinListener* output2, EmuTime::param time) { counter[0] = make_unique(scheduler, output0, time); counter[1] = make_unique(scheduler, output1, time); counter[2] = make_unique(scheduler, output2, time); } I8254::~I8254() { } void I8254::reset(EmuTime::param time) { for (auto& c : counter) { c->reset(time); } } byte I8254::readIO(word port, EmuTime::param time) { port &= 3; switch (port) { case 0: case 1: case 2: // read counter 0, 1, 2 return counter[port]->readIO(time); case 3: // read from control word, illegal return 255; // TODO check value default: UNREACHABLE; return 0; } } byte I8254::peekIO(word port, EmuTime::param time) const { port &= 3; switch (port) { case 0: case 1: case 2:// read counter 0, 1, 2 return counter[port]->peekIO(time); case 3: // read from control word, illegal return 255; // TODO check value default: UNREACHABLE; return 0; } } void I8254::writeIO(word port, byte value, EmuTime::param time) { port &= 3; switch (port) { case 0: case 1: case 2: // write counter 0, 1, 2 counter[port]->writeIO(value, time); break; case 3: // write to control register if ((value & READ_BACK) != READ_BACK) { // set control word of a counter counter[value >> 6]->writeControlWord( value & 0x3F, time); } else { // Read-Back-Command if (value & RB_CNTR0) { readBackHelper(value, 0, time); } if (value & RB_CNTR1) { readBackHelper(value, 1, time); } if (value & RB_CNTR2) { readBackHelper(value, 2, time); } } break; default: UNREACHABLE; } } void I8254::readBackHelper(byte value, unsigned cntr, EmuTime::param time) { assert(cntr < 3); if (!(value & RB_STATUS)) { counter[cntr]->latchStatus(time); } if (!(value & RB_COUNT)) { counter[cntr]->latchCounter(time); } } void I8254::setGate(unsigned cntr, bool status, EmuTime::param time) { assert(cntr < 3); counter[cntr]->setGateStatus(status, time); } ClockPin& I8254::getClockPin(unsigned cntr) { assert(cntr < 3); return counter[cntr]->clock; } ClockPin& I8254::getOutputPin(unsigned cntr) { assert(cntr < 3); return counter[cntr]->output; } // class Counter Counter::Counter(Scheduler& scheduler, ClockPinListener* listener, EmuTime::param time) : clock(scheduler), output(scheduler, listener) , currentTime(time) { gate = true; counter = 0; counterLoad = 0; reset(time); } void Counter::reset(EmuTime::param time) { currentTime = time; ltchCtrl = false; ltchCntr = false; readOrder = LOW; writeOrder = LOW; control = 0x30; // Write BOTH / mode 0 / binary mode active = false; counting = true; triggered = false; // avoid UMR on savestate latchedCounter = 0; latchedControl = 0; writeLatch = 0; } byte Counter::readIO(EmuTime::param time) { if (ltchCtrl) { ltchCtrl = false; return latchedControl; } advance(time); word readData = ltchCntr ? latchedCounter : counter; switch (control & WRT_FRMT) { case WF_LATCH: UNREACHABLE; case WF_LOW: ltchCntr = false; return readData & 0x00FF; case WF_HIGH: ltchCntr = false; return readData >> 8; case WF_BOTH: if (readOrder == LOW) { readOrder = HIGH; return readData & 0x00FF; } else { readOrder = LOW; ltchCntr = false; return readData >> 8; } default: UNREACHABLE; return 0; } } byte Counter::peekIO(EmuTime::param time) const { if (ltchCtrl) { return latchedControl; } const_cast(this)->advance(time); word readData = ltchCntr ? latchedCounter : counter; switch (control & WRT_FRMT) { case WF_LATCH: UNREACHABLE; case WF_LOW: return readData & 0x00FF; case WF_HIGH: return readData >> 8; case WF_BOTH: if (readOrder == LOW) { return readData & 0x00FF; } else { return readData >> 8; } default: UNREACHABLE; return 0; } } void Counter::writeIO(byte value, EmuTime::param time) { advance(time); switch (control & WRT_FRMT) { case WF_LATCH: UNREACHABLE; case WF_LOW: writeLoad((counterLoad & 0xFF00) | value, time); break; case WF_HIGH: writeLoad((counterLoad & 0x00FF) | (value << 8), time); break; case WF_BOTH: if (writeOrder == LOW) { writeOrder = HIGH; writeLatch = value; if ((control & CNTR_MODE) == CNTR_M0) // pauze counting when in mode 0 counting = false; } else { writeOrder = LOW; counting = true; writeLoad((value << 8) | writeLatch, time); } break; default: UNREACHABLE; } } void Counter::writeLoad(word value, EmuTime::param time) { counterLoad = value; byte mode = control & CNTR_MODE; if ((mode==CNTR_M0) || (mode==CNTR_M4)) { counter = counterLoad; } if (!active && ((mode == CNTR_M2) || (mode == CNTR_M2_) || (mode == CNTR_M3) || (mode == CNTR_M3_))) { if (clock.isPeriodic()) { counter = counterLoad; EmuDuration::param high = clock.getTotalDuration(); EmuDuration total = high * counter; output.setPeriodicState(total, high, time); } else { // TODO ?? } } if (mode == CNTR_M0) { output.setState(false, time); } active = true; // counter is (re)armed after counter is initialized } void Counter::writeControlWord(byte value, EmuTime::param time) { advance(time); if ((value & WRT_FRMT) == 0) { // counter latch command latchCounter(time); return; } else { // new control mode control = value; writeOrder = LOW; counting = true; active = false; triggered = false; switch (control & CNTR_MODE) { case CNTR_M0: output.setState(false, time); break; case CNTR_M1: case CNTR_M2: case CNTR_M2_: case CNTR_M3: case CNTR_M3_: case CNTR_M4: case CNTR_M5: output.setState(true, time); break; default: UNREACHABLE; } } } void Counter::latchStatus(EmuTime::param time) { advance(time); if (!ltchCtrl) { ltchCtrl = true; byte out = output.getState(time) ? 0x80 : 0; latchedControl = out | control; // TODO bit 6 null-count } } void Counter::latchCounter(EmuTime::param time) { advance(time); if (!ltchCntr) { ltchCntr = true; readOrder = LOW; latchedCounter = counter; } } void Counter::setGateStatus(bool newStatus, EmuTime::param time) { advance(time); if (gate != newStatus) { gate = newStatus; switch (control & CNTR_MODE) { case CNTR_M0: case CNTR_M4: // Gate does not influence output, it just acts as a count enable // nothing needs to be done break; case CNTR_M1: if (gate && active) { // rising edge counter = counterLoad; output.setState(false, time); triggered = true; } break; case CNTR_M2: case CNTR_M2_: case CNTR_M3: case CNTR_M3_: if (gate) { if (clock.isPeriodic()) { counter = counterLoad; EmuDuration::param high = clock.getTotalDuration(); EmuDuration total = high * counter; output.setPeriodicState(total, high, time); } else { // TODO ??? } } else { output.setState(true, time); } break; case CNTR_M5: if (gate && active) { // rising edge counter = counterLoad; triggered = true; } break; default: UNREACHABLE; } } } void Counter::advance(EmuTime::param time) { // TODO !!!! Set SP !!!! // TODO BCD counting int ticks = int(clock.getTicksBetween(currentTime, time)); currentTime = time; switch (control & CNTR_MODE) { case CNTR_M0: if (gate && counting) { counter -= ticks; if (counter < 0) { counter &= 0xFFFF; if (active) { output.setState(false, time); active = false; // not periodic } } } break; case CNTR_M1: counter -= ticks; if (triggered) { if (counter < 0) { output.setState(true, time); triggered = false; // not periodic } } counter &= 0xFFFF; break; case CNTR_M2: case CNTR_M2_: if (gate) { counter -= ticks; if (active) { // TODO not completely correct if (counterLoad != 0) { counter %= counterLoad; } else { counter = 0; } } } break; case CNTR_M3: case CNTR_M3_: if (gate) { counter -= 2 * ticks; if (active) { // TODO not correct if (counterLoad != 0) { counter %= counterLoad; } else { counter = 0; } } } break; case CNTR_M4: if (gate) { counter -= ticks; if (active) { if (counter == 0) { output.setState(false, time); } else if (counter < 0) { output.setState(true, time); active = false; // not periodic } } counter &= 0xFFFF; } break; case CNTR_M5: counter -= ticks; if (triggered) { if (counter == 0) output.setState(false, time); if (counter < 0) { output.setState(true, time); triggered = false; // not periodic } } counter &= 0xFFFF; break; default: UNREACHABLE; } } static enum_string byteOrderInfo[] = { { "LOW", Counter::LOW }, { "HIGH", Counter::HIGH } }; SERIALIZE_ENUM(Counter::ByteOrder, byteOrderInfo); template void Counter::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("clock", clock); ar.serialize("output", output); ar.serialize("currentTime", currentTime); ar.serialize("counter", counter); ar.serialize("latchedCounter", latchedCounter); ar.serialize("counterLoad", counterLoad); ar.serialize("control", control); ar.serialize("latchedControl", latchedControl); ar.serialize("ltchCtrl", ltchCtrl); ar.serialize("ltchCntr", ltchCntr); ar.serialize("readOrder", readOrder); ar.serialize("writeOrder", writeOrder); ar.serialize("writeLatch", writeLatch); ar.serialize("gate", gate); ar.serialize("active", active); ar.serialize("triggered", triggered); ar.serialize("counting", counting); } template void I8254::serialize(Archive& ar, unsigned /*version*/) { char tag[9] = { 'c', 'o', 'u', 'n', 't', 'e', 'r', 'X', 0 }; for (int i = 0; i < 3; ++i) { tag[7] = char('0' + i); ar.serialize(tag, *counter[i]); } } INSTANTIATE_SERIALIZE_METHODS(I8254); } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/I8254.hh000066400000000000000000000022001257557151200175330ustar00rootroot00000000000000// This class implements the Intel 8254 chip (and 8253) // // * Only the 8254 is emulated, no surrounding hardware. // Use the class I8254Interface to do that. #ifndef I8254_HH #define I8254_HH #include "EmuTime.hh" #include "openmsx.hh" #include "noncopyable.hh" #include namespace openmsx { class Scheduler; class Counter; class ClockPin; class ClockPinListener; class I8254 : private noncopyable { public: I8254(Scheduler& scheduler, ClockPinListener* output0, ClockPinListener* output1, ClockPinListener* output2, EmuTime::param time); ~I8254(); void reset(EmuTime::param time); byte readIO(word port, EmuTime::param time); byte peekIO(word port, EmuTime::param time) const; void writeIO(word port, byte value, EmuTime::param time); void setGate(unsigned counter, bool status, EmuTime::param time); ClockPin& getClockPin(unsigned cntr); ClockPin& getOutputPin(unsigned cntr); template void serialize(Archive& ar, unsigned version); private: void readBackHelper(byte value, unsigned cntr, EmuTime::param time); std::unique_ptr counter[3]; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serial/MC6850.cc000066400000000000000000000261401257557151200176410ustar00rootroot00000000000000#include "MC6850.hh" #include "MidiInDevice.hh" #include "MSXMotherBoard.hh" #include "EmuTime.hh" #include "serialize.hh" #include "unreachable.hh" namespace openmsx { // MSX interface: // port R/W // 0 W Control Register // 1 W Transmit Data Register // 4 R Status Register // 5 R Receive Data Register // control register bits static const unsigned CR_CDS1 = 0x01; // Counter Divide Select 1 static const unsigned CR_CDS2 = 0x02; // Counter Divide Select 2 static const unsigned CR_CDS = CR_CDS1 | CR_CDS2; static const unsigned CR_MR = CR_CDS1 | CR_CDS2; // Master Reset // CDS2 CDS1 // 0 0 divide by 1 // 0 1 divide by 16 // 1 0 divide by 64 // 1 1 master reset (!) static const unsigned CR_WS1 = 0x04; // Word Select 1 (mostly parity) static const unsigned CR_WS2 = 0x08; // Word Select 2 (mostly nof stop bits) static const unsigned CR_WS3 = 0x10; // Word Select 3: 7/8 bits static const unsigned CR_WS = CR_WS1 | CR_WS2 | CR_WS3; // Word Select // WS3 WS2 WS1 // 0 0 0 7 bits - 2 stop bits - Even parity // 0 0 1 7 bits - 2 stop bits - Odd parity // 0 1 0 7 bits - 1 stop bit - Even parity // 0 1 1 7 bits - 1 stop bit - Odd Parity // 1 0 0 8 bits - 2 stop bits - No Parity // 1 0 1 8 bits - 1 stop bit - No Parity // 1 1 0 8 bits - 1 stop bit - Even parity // 1 1 1 8 bits - 1 stop bit - Odd parity static const unsigned CR_TC1 = 0x20; // Transmit Control 1 static const unsigned CR_TC2 = 0x40; // Transmit Control 2 static const unsigned CR_TC = CR_TC1 | CR_TC2; // Transmit Control // TC2 TC1 // 0 0 /RTS low, Transmitting Interrupt disabled // 0 1 /RTS low, Transmitting Interrupt enabled // 1 0 /RTS high, Transmitting Interrupt disabled // 1 1 /RTS low, Transmits a Break level on the Transmit Data Output. // Interrupt disabled static const unsigned CR_RIE = 0x80; // Receive Interrupt Enable: interrupt // at Receive Data Register Full, Overrun, low-to-high transition on the Data // Carrier Detect (/DCD) signal line // status register bits static const unsigned STAT_RDRF = 0x01; // Receive Data Register Full static const unsigned STAT_TDRE = 0x02; // Transmit Data Register Empty static const unsigned STAT_DCD = 0x04; // Data Carrier Detect (/DCD) static const unsigned STAT_CTS = 0x08; // Clear-to-Send (/CTS) static const unsigned STAT_FE = 0x10; // Framing Error static const unsigned STAT_OVRN = 0x20; // Receiver Overrun static const unsigned STAT_PE = 0x40; // Parity Error static const unsigned STAT_IRQ = 0x80; // Interrupt Request (/IRQ) // Some existing Music-Module detection routines: // - fac demo 5: does OUT 0,3 : OUT 0,21 : INP(4) and expects to read 2 // - tetris 2 special edition: does INP(4) and expects to read 0 // - Synthesix: does INP(4), expects 0; OUT 0,3 : OUT 0,21: INP(4) and expects // bit 1 to be 1 and bit 2, 3 and 7 to be 0. Then does OUT 5,0xFE : INP(4) // and expects bit 1 to be 0. // I did some _very_basic_ investigation and found the following: // - after a reset reading from port 4 returns 0 // - after initializing the control register, reading port 4 returns 0 (of // course this will change when you start to actually receive/transmit data) // - writing any value with the lower 2 bits set to 1 returns to the initial // state, and reading port 4 again returns 0. // - ?INP(4) : OUT0,3 : ?INP(4) : OUT0,21 : ? INP(4) : OUT0,3 : ?INP(4) // outputs: 0, 0, 2, 0 MC6850::MC6850(const DeviceConfig& config) : MSXDevice(config) , MidiInConnector(getMotherBoard().getPluggingController(), MSXDevice::getName() + "-in") , syncRecv (getMotherBoard().getScheduler()) , syncTrans(getMotherBoard().getScheduler()) , txClock(EmuTime::zero) , rxIRQ(getMotherBoard(), MSXDevice::getName() + "-rx-IRQ") , txIRQ(getMotherBoard(), MSXDevice::getName() + "-tx-IRQ") , txDataReg(0), txShiftReg(0) // avoid UMR , outConnector(getMotherBoard().getPluggingController(), MSXDevice::getName() + "-out") { reset(EmuTime::zero); setDataFormat(); } // (Re-)initialize chip to default values (Tx and Rx disabled) void MC6850::reset(EmuTime::param time) { syncRecv .removeSyncPoint(); syncTrans.removeSyncPoint(); txClock.reset(time); txClock.setFreq(500000); // 500kHz rxIRQ.reset(); txIRQ.reset(); rxReady = false; txShiftRegValid = false; pendingOVRN = false; controlReg = CR_MR; statusReg = 0; rxDataReg = 0; setDataFormat(); } byte MC6850::readIO(word port, EmuTime::param /*time*/) { switch (port & 0x1) { case 0: return readStatusReg(); case 1: return readDataReg(); } UNREACHABLE; return 0xFF; } byte MC6850::peekIO(word port, EmuTime::param /*time*/) const { switch (port & 0x1) { case 0: return peekStatusReg(); case 1: return peekDataReg(); } UNREACHABLE; return 0xFF; } void MC6850::writeIO(word port, byte value, EmuTime::param time) { switch (port & 0x01) { case 0: writeControlReg(value, time); break; case 1: writeDataReg(value, time); break; } } byte MC6850::readStatusReg() { return peekStatusReg(); } byte MC6850::peekStatusReg() const { byte result = statusReg; if (rxIRQ.getState() || txIRQ.getState()) result |= STAT_IRQ; return result; } byte MC6850::readDataReg() { byte result = peekDataReg(); statusReg &= ~(STAT_RDRF | STAT_OVRN); if (pendingOVRN) { pendingOVRN = false; statusReg |= STAT_OVRN; } rxIRQ.reset(); return result; } byte MC6850::peekDataReg() const { return rxDataReg; } void MC6850::writeControlReg(byte value, EmuTime::param time) { byte diff = value ^ controlReg; if (diff & CR_CDS) { if ((value & CR_CDS) == CR_MR) { reset(time); } else { // we got out of MR state rxReady = true; statusReg |= STAT_TDRE; txClock.reset(time); switch (value & CR_CDS) { case 0: txClock.setFreq(500000, 1); break; // 500kHz case 1: txClock.setFreq(500000, 16); break; // 31250Hz (MIDI) case 2: txClock.setFreq(500000, 64); break; // 7812.5Hz } } } controlReg = value; if (diff & CR_WS) setDataFormat(); // update IRQ status rxIRQ.set(( value & CR_RIE) && (statusReg & STAT_RDRF)); txIRQ.set(((value & CR_TC) == 0x20) && (statusReg & STAT_TDRE)); } // Sync data-format related parameters with the current value of controlReg void MC6850::setDataFormat() { outConnector.setDataBits(controlReg & CR_WS3 ? DATA_8 : DATA_7); StopBits stopBits[8] = { STOP_2, STOP_2, STOP_1, STOP_1, STOP_2, STOP_1, STOP_1, STOP_1, }; outConnector.setStopBits(stopBits[(controlReg & CR_WS) >> 2]); outConnector.setParityBit( (controlReg & (CR_WS3 | CR_WS2)) != 0x10, // enable (controlReg & CR_WS1) ? ODD : EVEN); // start-bits, data-bits, parity-bits, stop-bits byte len[8] = { 1 + 7 + 1 + 2, 1 + 7 + 1 + 2, 1 + 7 + 1 + 1, 1 + 7 + 1 + 1, 1 + 8 + 0 + 2, 1 + 8 + 0 + 1, 1 + 8 + 1 + 1, 1 + 8 + 1 + 1, }; charLen = len[(controlReg & CR_WS) >> 2]; } void MC6850::writeDataReg(byte value, EmuTime::param time) { if ((controlReg & CR_CDS) == CR_MR) return; txDataReg = value; statusReg &= ~STAT_TDRE; txIRQ.reset(); if (syncTrans.pendingSyncPoint()) { // We're still sending the previous character, only // buffer this one. Don't accept any further characters } else { // We were not yet sending. Start sending at the next txClock. // Important: till that time TDRE should go low // (MC6850 detection routine in Synthesix depends on this) txClock.advance(time); // clock edge before or at 'time' txClock += 1; // clock edge strictly after 'time' syncTrans.setSyncPoint(txClock.getTime()); } } // Triggered between transmitted characters, including before the first and // after the last character. void MC6850::execTrans(EmuTime::param time) { assert(txClock.getTime() == time); assert((controlReg & CR_CDS) != CR_MR); if (txShiftRegValid) { txShiftRegValid = false; outConnector.recvByte(txShiftReg, time); } if (statusReg & STAT_TDRE) { // No next character to send, we're done. } else { // There already is a next character, start sending that now // and accept a next one. statusReg |= STAT_TDRE; if (((controlReg & CR_TC) == 0x20)) txIRQ.set(); txShiftReg = txDataReg; txShiftRegValid = true; txClock += charLen; syncTrans.setSyncPoint(txClock.getTime()); } } // MidiInConnector sends a new character. void MC6850::recvByte(byte value, EmuTime::param time) { assert(acceptsData() && ready()); if (statusReg & STAT_RDRF) { // So, there is a byte that has to be read by the MSX still! // This happens when the MSX program doesn't // respond fast enough to an earlier received byte. // The STAT_OVRN flag only becomes active once the prior valid // character has been read from the data register. pendingOVRN = true; } else { rxDataReg = value; statusReg |= STAT_RDRF; } // both for OVRN and RDRF an IRQ is raised if (controlReg & CR_RIE) rxIRQ.set(); // Not ready now, but we will be in a while rxReady = false; // The MC6850 has separate TxCLK and RxCLK inputs, but both share a // common divider. This implementation hard-codes an input frequency of // 500kHz for both. Below we want the receive clock period, but it's OK // to calculate that as 'txClock.getPeriod()'. syncRecv.setSyncPoint(time + txClock.getPeriod() * charLen); } // Triggered when we're ready to receive the next character. void MC6850::execRecv(EmuTime::param time) { assert(acceptsData()); assert(!rxReady); rxReady = true; getPluggedMidiInDev().signal(time); // trigger (possible) send of next char } // MidiInDevice querries whether it can send a new character 'now'. bool MC6850::ready() { return rxReady; } // MidiInDevice queries whether it can send characters at all. bool MC6850::acceptsData() { return (controlReg & CR_CDS) != CR_MR; } // MidiInDevice informs us about the format of the data it will send // (MIDI is always 1 start-bit, 8 data-bits, 1 stop-bit, no parity-bits). void MC6850::setDataBits(DataBits /*bits*/) { // ignore } void MC6850::setStopBits(StopBits /*bits*/) { // ignore } void MC6850::setParityBit(bool /*enable*/, ParityBit /*parity*/) { // ignore } // version 1: initial version // version 2: added control // version 3: actually working MC6850 with many more member variables template void MC6850::serialize(Archive& ar, unsigned version) { ar.template serializeBase(*this); if (ar.versionAtLeast(version, 3)) { ar.template serializeBase(*this); ar.serialize("outConnector", outConnector); ar.serialize("syncRecv", syncRecv); ar.serialize("syncTrans", syncTrans); ar.serialize("txClock", txClock); ar.serialize("rxIRQ", rxIRQ); ar.serialize("txIRQ", txIRQ); ar.serialize("rxReady", rxReady); ar.serialize("txShiftRegValid", txShiftRegValid); ar.serialize("pendingOVRN", pendingOVRN); ar.serialize("rxDataReg", rxDataReg); ar.serialize("txDataReg", txDataReg); ar.serialize("txShiftReg", txShiftReg); ar.serialize("controlReg", controlReg); ar.serialize("statusReg", statusReg); } else if (ar.versionAtLeast(version, 2)) { ar.serialize("control", controlReg); } else { controlReg = 3; } if (ar.isLoader()) { setDataFormat(); } } INSTANTIATE_SERIALIZE_METHODS(MC6850); REGISTER_MSXDEVICE(MC6850, "MC6850"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/MC6850.hh000066400000000000000000000047541257557151200176620ustar00rootroot00000000000000#ifndef MC6850_HH #define MC6850_HH #include "MSXDevice.hh" #include "DynamicClock.hh" #include "IRQHelper.hh" #include "MidiInConnector.hh" #include "MidiOutConnector.hh" #include "Schedulable.hh" #include "openmsx.hh" #include "outer.hh" #include "serialize_meta.hh" class Scheduler; namespace openmsx { class MC6850 final : public MSXDevice, public MidiInConnector { public: explicit MC6850(const DeviceConfig& config); // MSXDevice void reset(EmuTime::param time) override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: byte readStatusReg(); byte peekStatusReg() const; byte readDataReg(); byte peekDataReg() const; void writeControlReg(byte value, EmuTime::param time); void writeDataReg (byte value, EmuTime::param time); void setDataFormat(); // MidiInConnector bool ready() override; bool acceptsData() override; void setDataBits(DataBits bits) override; void setStopBits(StopBits bits) override; void setParityBit(bool enable, ParityBit parity) override; void recvByte(byte value, EmuTime::param time) override; // Schedulable struct SyncRecv : Schedulable { friend class MC6850; SyncRecv(Scheduler& s) : Schedulable(s) {} void executeUntil(EmuTime::param time) override { auto& mc6850 = OUTER(MC6850, syncRecv); mc6850.execRecv(time); } } syncRecv; struct SyncTrans : Schedulable { friend class MC6850; SyncTrans(Scheduler& s) : Schedulable(s) {} void executeUntil(EmuTime::param time) override { auto& mc6850 = OUTER(MC6850, syncTrans); mc6850.execTrans(time); } } syncTrans; void execRecv (EmuTime::param time); void execTrans(EmuTime::param time); // External clock of 500kHz, divided by 1, 16 or 64. // Transmitted bits are synced to this clock DynamicClock txClock; IRQHelper rxIRQ; IRQHelper txIRQ; bool rxReady; bool txShiftRegValid; // UART data register // 1 -> UART status register return i8251.readIO(port & 1, time); } byte MSXFacMidiInterface::peekIO(word port, EmuTime::param time) const { return i8251.peekIO(port & 1, time); } void MSXFacMidiInterface::writeIO(word port, byte value, EmuTime::param time) { // 0 -> UART data register // 1 -> UART command register i8251.writeIO(port & 1, value, time); } // I8251Interface (pass calls from I8251 to outConnector) void MSXFacMidiInterface::I8251Interf::setRxRDY(bool /*status*/, EmuTime::param /*time*/) { } void MSXFacMidiInterface::I8251Interf::setDTR(bool /*status*/, EmuTime::param /*time*/) { } void MSXFacMidiInterface::I8251Interf::setRTS(bool /*status*/, EmuTime::param /*time*/) { } bool MSXFacMidiInterface::I8251Interf::getDSR(EmuTime::param /*time*/) { return true; } bool MSXFacMidiInterface::I8251Interf::getCTS(EmuTime::param /*time*/) { return true; } void MSXFacMidiInterface::I8251Interf::setDataBits(DataBits bits) { auto& midi = OUTER(MSXFacMidiInterface, interf); midi.outConnector.setDataBits(bits); } void MSXFacMidiInterface::I8251Interf::setStopBits(StopBits bits) { auto& midi = OUTER(MSXFacMidiInterface, interf); midi.outConnector.setStopBits(bits); } void MSXFacMidiInterface::I8251Interf::setParityBit(bool enable, ParityBit parity) { auto& midi = OUTER(MSXFacMidiInterface, interf); midi.outConnector.setParityBit(enable, parity); } void MSXFacMidiInterface::I8251Interf::recvByte(byte value, EmuTime::param time) { auto& midi = OUTER(MSXFacMidiInterface, interf); midi.outConnector.recvByte(value, time); } void MSXFacMidiInterface::I8251Interf::signal(EmuTime::param time) { auto& midi = OUTER(MSXFacMidiInterface, interf); midi.getPluggedMidiInDev().signal(time); } // MidiInConnector bool MSXFacMidiInterface::ready() { return i8251.isRecvReady(); } bool MSXFacMidiInterface::acceptsData() { return i8251.isRecvEnabled(); } void MSXFacMidiInterface::setDataBits(DataBits bits) { i8251.setDataBits(bits); } void MSXFacMidiInterface::setStopBits(StopBits bits) { i8251.setStopBits(bits); } void MSXFacMidiInterface::setParityBit(bool enable, ParityBit parity) { i8251.setParityBit(enable, parity); } void MSXFacMidiInterface::recvByte(byte value, EmuTime::param time) { i8251.recvByte(value, time); } template void MSXFacMidiInterface::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.template serializeBase(*this); ar.serialize("outConnector", outConnector); ar.serialize("I8251", i8251); } INSTANTIATE_SERIALIZE_METHODS(MSXFacMidiInterface); REGISTER_MSXDEVICE(MSXFacMidiInterface, "MSXFacMidiInterface"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/MSXFacMidiInterface.hh000066400000000000000000000031041257557151200225310ustar00rootroot00000000000000#ifndef MSXFACMIDIINTERFACE_HH #define MSXFACMIDIINTERFACE_HH #include "MSXDevice.hh" #include "MidiInConnector.hh" #include "MidiOutConnector.hh" #include "I8251.hh" namespace openmsx { class MSXFacMidiInterface final : public MSXDevice, public MidiInConnector { public: explicit MSXFacMidiInterface(const DeviceConfig& config); void reset(EmuTime::param time) override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; // MidiInConnector bool ready() override; bool acceptsData() override; void setDataBits(DataBits bits) override; void setStopBits(StopBits bits) override; void setParityBit(bool enable, ParityBit parity) override; void recvByte(byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: struct I8251Interf final : I8251Interface { void setRxRDY(bool status, EmuTime::param time) override; void setDTR(bool status, EmuTime::param time) override; void setRTS(bool status, EmuTime::param time) override; bool getDSR(EmuTime::param time) override; bool getCTS(EmuTime::param time) override; void setDataBits(DataBits bits) override; void setStopBits(StopBits bits) override; void setParityBit(bool enable, ParityBit parity) override; void recvByte(byte value, EmuTime::param time) override; void signal(EmuTime::param time) override; } interf; // must come last MidiOutConnector outConnector; I8251 i8251; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serial/MSXMidi.cc000066400000000000000000000255141257557151200202750ustar00rootroot00000000000000#include "MSXMidi.hh" #include "MidiInDevice.hh" #include "MSXCPUInterface.hh" #include "MSXException.hh" #include "memory.hh" #include "outer.hh" #include "serialize.hh" #include "unreachable.hh" #include namespace openmsx { // Documented in MSX-Datapack Vol. 3, section 4 (MSX-MIDI), from page 634 static const byte LIMITED_RANGE_VALUE = 0x01; // b0 = "E8" => determines port range static const byte DISABLED_VALUE = 0x80; // b7 = EN MSXMidi::MSXMidi(const DeviceConfig& config) : MSXDevice(config) , MidiInConnector(MSXDevice::getPluggingController(), "msx-midi-in") , timerIRQ(getMotherBoard(), MSXDevice::getName() + ".IRQtimer") , rxrdyIRQ(getMotherBoard(), MSXDevice::getName() + ".IRQrxrdy") , timerIRQlatch(false), timerIRQenabled(false) , rxrdyIRQlatch(false), rxrdyIRQenabled(false) , isExternalMSXMIDI(config.findChild("external") != nullptr) , isEnabled(!isExternalMSXMIDI) , isLimitedTo8251(true) , outConnector(MSXDevice::getPluggingController(), "msx-midi-out") , i8251(getScheduler(), interf, getCurrentTime()) , i8254(getScheduler(), &cntr0, nullptr, &cntr2, getCurrentTime()) { EmuDuration total(1.0 / 4e6); // 4MHz EmuDuration hi (1.0 / 8e6); // 8MHz half clock period EmuTime::param time = getCurrentTime(); i8254.getClockPin(0).setPeriodicState(total, hi, time); i8254.getClockPin(1).setState(false, time); i8254.getClockPin(2).setPeriodicState(total, hi, time); i8254.getOutputPin(2).generateEdgeSignals(true, time); reset(time); if (isExternalMSXMIDI) { // Ports are dynamically registered. if (config.findChild("io")) { throw MSXException( "Bad MSX-MIDI configuration, when using " "'external', you cannot specify I/O ports!"); } // Out-port 0xE2 is always enabled. getCPUInterface().register_IO_Out(0xE2, this); // Not enabled, so no other ports need to be registered yet. assert(!isEnabled); } else { // Ports are statically registered (via the config file). } } MSXMidi::~MSXMidi() { if (isExternalMSXMIDI) { registerIOports(DISABLED_VALUE | LIMITED_RANGE_VALUE); // disabled, unregisters ports getCPUInterface().unregister_IO_Out(0xE2, this); } } void MSXMidi::reset(EmuTime::param time) { timerIRQlatch = false; timerIRQenabled = false; timerIRQ.reset(); rxrdyIRQlatch = false; rxrdyIRQenabled = false; rxrdyIRQ.reset(); if (isExternalMSXMIDI) { registerIOports(DISABLED_VALUE | LIMITED_RANGE_VALUE); // also resets state } i8251.reset(time); } byte MSXMidi::readIO(word port, EmuTime::param time) { // If not enabled then no ports should have been registered. assert(isEnabled); // This handles both ports in range 0xE0-0xE1 and 0xE8-0xEF // Depending on the value written to 0xE2 they are active or not switch (port & 7) { case 0: // UART data register case 1: // UART status register return i8251.readIO(port & 1, time); case 2: // timer interrupt flag off case 3: // no function return 0xFF; case 4: // counter 0 data port case 5: // counter 1 data port case 6: // counter 2 data port case 7: // timer command register return i8254.readIO(port & 3, time); default: UNREACHABLE; return 0; } } byte MSXMidi::peekIO(word port, EmuTime::param time) const { // If not enabled then no ports should have been registered. assert(isEnabled); // This handles both ports in range 0xE0-0xE1 and 0xE8-0xEF // Depending on the value written to 0xE2 they are active or not switch (port & 7) { case 0: // UART data register case 1: // UART status register return i8251.peekIO(port & 1, time); case 2: // timer interrupt flag off case 3: // no function return 0xFF; case 4: // counter 0 data port case 5: // counter 1 data port case 6: // counter 2 data port case 7: // timer command register return i8254.peekIO(port & 3, time); default: UNREACHABLE; return 0; } } void MSXMidi::writeIO(word port, byte value, EmuTime::param time) { if (isExternalMSXMIDI && ((port & 0xFF) == 0xE2)) { // control register registerIOports(value); return; } assert(isEnabled); // This handles both ports in range 0xE0-0xE1 and 0xE8-0xEF switch (port & 7) { case 0: // UART data register case 1: // UART command register i8251.writeIO(port & 1, value, time); break; case 2: // timer interrupt flag off setTimerIRQ(false, time); break; case 3: // no function break; case 4: // counter 0 data port case 5: // counter 1 data port case 6: // counter 2 data port case 7: // timer command register i8254.writeIO(port & 3, value, time); break; } } void MSXMidi::registerIOports(byte value) { assert(isExternalMSXMIDI); bool newIsEnabled = (value & DISABLED_VALUE) == 0; bool newIsLimited = (value & LIMITED_RANGE_VALUE) != 0; if (newIsEnabled != isEnabled) { // Enable/disabled status changes, possibly limited status // changes as well but that doesn't matter, we anyway need // to (un)register the full port range. if (newIsEnabled) { // disabled -> enabled if (newIsLimited) { registerRange(0xE0, 2); } else { registerRange(0xE8, 8); } } else { // enabled -> disabled if (isLimitedTo8251) { // note: old isLimited status unregisterRange(0xE0, 2); } else { unregisterRange(0xE8, 8); } } } else if (isEnabled && (newIsLimited != isLimitedTo8251)) { // Remains enabled, and only 'isLimited' status changes. // Need to switch between the low/high range. if (newIsLimited) { // Switch high->low range. unregisterRange(0xE8, 8); registerRange (0xE0, 2); } else { // Switch low->high range. unregisterRange(0xE0, 2); registerRange (0xE8, 8); } } isEnabled = newIsEnabled; isLimitedTo8251 = newIsLimited; } void MSXMidi::registerRange(byte port, unsigned num) { for (unsigned i = 0; i < num; ++i) { getCPUInterface().register_IO_In (port + i, this); getCPUInterface().register_IO_Out(port + i, this); } } void MSXMidi::unregisterRange(byte port, unsigned num) { for (unsigned i = 0; i < num; ++i) { getCPUInterface().unregister_IO_In (port + i, this); getCPUInterface().unregister_IO_Out(port + i, this); } } void MSXMidi::setTimerIRQ(bool status, EmuTime::param time) { if (timerIRQlatch != status) { timerIRQlatch = status; if (timerIRQenabled) { timerIRQ.set(timerIRQlatch); } updateEdgeEvents(time); } } void MSXMidi::enableTimerIRQ(bool enabled, EmuTime::param time) { if (timerIRQenabled != enabled) { timerIRQenabled = enabled; if (timerIRQlatch) { timerIRQ.set(timerIRQenabled); } updateEdgeEvents(time); } } void MSXMidi::updateEdgeEvents(EmuTime::param time) { bool wantEdges = timerIRQenabled && !timerIRQlatch; i8254.getOutputPin(2).generateEdgeSignals(wantEdges, time); } void MSXMidi::setRxRDYIRQ(bool status) { if (rxrdyIRQlatch != status) { rxrdyIRQlatch = status; if (rxrdyIRQenabled) { rxrdyIRQ.set(rxrdyIRQlatch); } } } void MSXMidi::enableRxRDYIRQ(bool enabled) { if (rxrdyIRQenabled != enabled) { rxrdyIRQenabled = enabled; if (!rxrdyIRQenabled && rxrdyIRQlatch) { rxrdyIRQ.reset(); } } } // I8251Interface (pass calls from I8251 to outConnector) void MSXMidi::I8251Interf::setRxRDY(bool status, EmuTime::param /*time*/) { auto& midi = OUTER(MSXMidi, interf); midi.setRxRDYIRQ(status); } void MSXMidi::I8251Interf::setDTR(bool status, EmuTime::param time) { auto& midi = OUTER(MSXMidi, interf); midi.enableTimerIRQ(status, time); } void MSXMidi::I8251Interf::setRTS(bool status, EmuTime::param /*time*/) { auto& midi = OUTER(MSXMidi, interf); midi.enableRxRDYIRQ(status); } bool MSXMidi::I8251Interf::getDSR(EmuTime::param /*time*/) { auto& midi = OUTER(MSXMidi, interf); return midi.timerIRQ.getState(); } bool MSXMidi::I8251Interf::getCTS(EmuTime::param /*time*/) { return true; } void MSXMidi::I8251Interf::setDataBits(DataBits bits) { auto& midi = OUTER(MSXMidi, interf); midi.outConnector.setDataBits(bits); } void MSXMidi::I8251Interf::setStopBits(StopBits bits) { auto& midi = OUTER(MSXMidi, interf); midi.outConnector.setStopBits(bits); } void MSXMidi::I8251Interf::setParityBit(bool enable, ParityBit parity) { auto& midi = OUTER(MSXMidi, interf); midi.outConnector.setParityBit(enable, parity); } void MSXMidi::I8251Interf::recvByte(byte value, EmuTime::param time) { auto& midi = OUTER(MSXMidi, interf); midi.outConnector.recvByte(value, time); } void MSXMidi::I8251Interf::signal(EmuTime::param time) { auto& midi = OUTER(MSXMidi, interf); midi.getPluggedMidiInDev().signal(time); } // Counter 0 output void MSXMidi::Counter0::signal(ClockPin& pin, EmuTime::param time) { auto& midi = OUTER(MSXMidi, cntr0); ClockPin& clk = midi.i8251.getClockPin(); if (pin.isPeriodic()) { clk.setPeriodicState(pin.getTotalDuration(), pin.getHighDuration(), time); } else { clk.setState(pin.getState(time), time); } } void MSXMidi::Counter0::signalPosEdge(ClockPin& /*pin*/, EmuTime::param /*time*/) { UNREACHABLE; } // Counter 2 output void MSXMidi::Counter2::signal(ClockPin& pin, EmuTime::param time) { auto& midi = OUTER(MSXMidi, cntr2); ClockPin& clk = midi.i8254.getClockPin(1); if (pin.isPeriodic()) { clk.setPeriodicState(pin.getTotalDuration(), pin.getHighDuration(), time); } else { clk.setState(pin.getState(time), time); } } void MSXMidi::Counter2::signalPosEdge(ClockPin& /*pin*/, EmuTime::param time) { auto& midi = OUTER(MSXMidi, cntr2); midi.setTimerIRQ(true, time); } // MidiInConnector bool MSXMidi::ready() { return i8251.isRecvReady(); } bool MSXMidi::acceptsData() { return i8251.isRecvEnabled(); } void MSXMidi::setDataBits(DataBits bits) { i8251.setDataBits(bits); } void MSXMidi::setStopBits(StopBits bits) { i8251.setStopBits(bits); } void MSXMidi::setParityBit(bool enable, ParityBit parity) { i8251.setParityBit(enable, parity); } void MSXMidi::recvByte(byte value, EmuTime::param time) { i8251.recvByte(value, time); } template void MSXMidi::serialize(Archive& ar, unsigned version) { ar.template serializeBase(*this); ar.template serializeBase(*this); ar.serialize("outConnector", outConnector); ar.serialize("timerIRQ", timerIRQ); ar.serialize("rxrdyIRQ", rxrdyIRQ); ar.serialize("timerIRQlatch", timerIRQlatch); ar.serialize("timerIRQenabled", timerIRQenabled); ar.serialize("rxrdyIRQlatch", rxrdyIRQlatch); ar.serialize("rxrdyIRQenabled", rxrdyIRQenabled); ar.serialize("I8251", i8251); ar.serialize("I8254", i8254); if (ar.versionAtLeast(version, 2)) { bool newIsEnabled = isEnabled; // copy for saver bool newIsLimitedTo8251 = isLimitedTo8251; // copy for saver ar.serialize("isEnabled", newIsEnabled); ar.serialize("isLimitedTo8251", newIsLimitedTo8251); if (ar.isLoader() && isExternalMSXMIDI) { registerIOports((newIsEnabled ? 0x00 : DISABLED_VALUE) | (newIsLimitedTo8251 ? LIMITED_RANGE_VALUE : 0x00)); } } // don't serialize: cntr0, cntr2, interf } INSTANTIATE_SERIALIZE_METHODS(MSXMidi); REGISTER_MSXDEVICE(MSXMidi, "MSX-Midi"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/MSXMidi.hh000066400000000000000000000051541257557151200203050ustar00rootroot00000000000000#ifndef MSXMIDI_HH #define MSXMIDI_HH #include "MSXDevice.hh" #include "IRQHelper.hh" #include "MidiInConnector.hh" #include "MidiOutConnector.hh" #include "I8251.hh" #include "I8254.hh" namespace openmsx { class MSXMidi final : public MSXDevice, public MidiInConnector { public: explicit MSXMidi(const DeviceConfig& config); ~MSXMidi(); void reset(EmuTime::param time) override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; // MidiInConnector bool ready() override; bool acceptsData() override; void setDataBits(DataBits bits) override; void setStopBits(StopBits bits) override; void setParityBit(bool enable, ParityBit parity) override; void recvByte(byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: void setTimerIRQ(bool status, EmuTime::param time); void enableTimerIRQ(bool enabled, EmuTime::param time); void updateEdgeEvents(EmuTime::param time); void setRxRDYIRQ(bool status); void enableRxRDYIRQ(bool enabled); void registerIOports(byte value); void registerRange(byte port, unsigned num); void unregisterRange(byte port, unsigned num); struct Counter0 final : ClockPinListener { void signal(ClockPin& pin, EmuTime::param time) override; void signalPosEdge(ClockPin& pin, EmuTime::param time) override; } cntr0; // counter 0 clock pin struct Counter2 final : ClockPinListener { void signal(ClockPin& pin, EmuTime::param time) override; void signalPosEdge(ClockPin& pin, EmuTime::param time) override; } cntr2; // counter 2 clock pin struct I8251Interf final : I8251Interface { void setRxRDY(bool status, EmuTime::param time) override; void setDTR(bool status, EmuTime::param time) override; void setRTS(bool status, EmuTime::param time) override; bool getDSR(EmuTime::param time) override; bool getCTS(EmuTime::param time) override; void setDataBits(DataBits bits) override; void setStopBits(StopBits bits) override; void setParityBit(bool enable, ParityBit parity) override; void recvByte(byte value, EmuTime::param time) override; void signal(EmuTime::param time) override; } interf; IRQHelper timerIRQ; IRQHelper rxrdyIRQ; bool timerIRQlatch; bool timerIRQenabled; bool rxrdyIRQlatch; bool rxrdyIRQenabled; const bool isExternalMSXMIDI; bool isEnabled; /* EN bit */ bool isLimitedTo8251; /* inverse of E8 bit */ // must come last MidiOutConnector outConnector; I8251 i8251; I8254 i8254; }; SERIALIZE_CLASS_VERSION(MSXMidi, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serial/MSXRS232.cc000066400000000000000000000265321257557151200201670ustar00rootroot00000000000000#include "MSXRS232.hh" #include "RS232Device.hh" #include "CacheLine.hh" #include "Ram.hh" #include "Rom.hh" #include "BooleanSetting.hh" #include "MSXException.hh" #include "serialize.hh" #include "memory.hh" #include "outer.hh" #include "unreachable.hh" #include namespace openmsx { const unsigned RAM_OFFSET = 0x2000; const unsigned RAM_SIZE = 0x800; MSXRS232::MSXRS232(const DeviceConfig& config) : MSXDevice(config) , RS232Connector(MSXDevice::getPluggingController(), "msx-rs232") , i8254(getScheduler(), &cntr0, &cntr1, nullptr, getCurrentTime()) , i8251(getScheduler(), interf, getCurrentTime()) , rom(config.findChild("rom") ? make_unique( MSXDevice::getName() + " ROM", "rom", config) : nullptr) // when the ROM is already mapped, you don't want to specify it again here , ram(config.getChildDataAsBool("ram", false) ? make_unique( config, MSXDevice::getName() + " RAM", "RS232 RAM", RAM_SIZE) : nullptr) , rxrdyIRQ(getMotherBoard(), MSXDevice::getName() + ".IRQrxrdy") , rxrdyIRQlatch(false) , rxrdyIRQenabled(false) , hasMemoryBasedIo(config.getChildDataAsBool("memorybasedio", false)) , ioAccessEnabled(!hasMemoryBasedIo) , switchSetting(config.getChildDataAsBool("toshiba_rs232c_switch", false) ? make_unique(getCommandController(), "toshiba_rs232c_switch", "status of the RS-232C enable switch", true) : nullptr) { if (rom && rom->getSize() != 0x2000 && rom->getSize() != 0x4000) { throw MSXException("RS232C only supports 8kB or 16kB ROMs."); } EmuDuration total(1.0 / 1.8432e6); // 1.8432MHz EmuDuration hi (1.0 / 3.6864e6); // half clock period EmuTime::param time = getCurrentTime(); i8254.getClockPin(0).setPeriodicState(total, hi, time); i8254.getClockPin(1).setPeriodicState(total, hi, time); i8254.getClockPin(2).setPeriodicState(total, hi, time); powerUp(time); } MSXRS232::~MSXRS232() { } void MSXRS232::powerUp(EmuTime::param time) { if (ram) ram->clear(); reset(time); } void MSXRS232::reset(EmuTime::param /*time*/) { rxrdyIRQlatch = false; rxrdyIRQenabled = false; rxrdyIRQ.reset(); ioAccessEnabled = !hasMemoryBasedIo; if (ram) ram->clear(); } byte MSXRS232::readMem(word address, EmuTime::param time) { if (hasMemoryBasedIo && (0xBFF8 <= address) && (address <= 0xBFFF)) { return readIOImpl(address & 0x07, time); } word addr = address & 0x3FFF; if (ram && ((RAM_OFFSET <= addr) && (addr < (RAM_OFFSET + RAM_SIZE)))) { return (*ram)[addr - RAM_OFFSET]; } else if (rom && (0x4000 <= address) && (address < 0x8000)) { return (*rom)[addr & (rom->getSize() - 1)]; } else { return 0xFF; } } const byte* MSXRS232::getReadCacheLine(word start) const { if (hasMemoryBasedIo && (start == (0xBFF8 & CacheLine::HIGH))) { return nullptr; } word addr = start & 0x3FFF; if (ram && ((RAM_OFFSET <= addr) && (addr < (RAM_OFFSET + RAM_SIZE)))) { return &(*ram)[addr - RAM_OFFSET]; } else if (rom && (0x4000 <= start) && (start < 0x8000)) { return &(*rom)[addr & (rom->getSize() - 1)]; } else { return unmappedRead; } } void MSXRS232::writeMem(word address, byte value, EmuTime::param time) { if (hasMemoryBasedIo && (0xBFF8 <= address) && (address <= 0xBFFF)) { // when the interface has memory based I/O, the I/O port // based I/O is disabled, but it can be enabled by writing // bit 4 to 0xBFFA. It is disabled again at reset. // Source: Sony HB-G900P and Sony HB-G900AP service manuals. // We assume here you can also disable it by writing 0 to it. if (address == 0xBFFA) { ioAccessEnabled = (value & (1 << 4))!=0; } return writeIOImpl(address & 0x07, value, time); } word addr = address & 0x3FFF; if (ram && ((RAM_OFFSET <= addr) && (addr < (RAM_OFFSET + RAM_SIZE)))) { (*ram)[addr - RAM_OFFSET] = value; } } byte* MSXRS232::getWriteCacheLine(word start) const { if (hasMemoryBasedIo && (start == (0xBFF8 & CacheLine::HIGH))) { return nullptr; } word addr = start & 0x3FFF; if (ram && ((RAM_OFFSET <= addr) && (addr < (RAM_OFFSET + RAM_SIZE)))) { return &(*ram)[addr - RAM_OFFSET]; } else { return unmappedWrite; } } byte MSXRS232::readIO(word port, EmuTime::param time) { if (ioAccessEnabled) { return readIOImpl(port & 0x07, time); } return 0xFF; } byte MSXRS232::readIOImpl(word port, EmuTime::param time) { byte result; switch (port) { case 0: // UART data register case 1: // UART status register result = i8251.readIO(port, time); break; case 2: // Status sense port result = readStatus(time); break; case 3: // no function result = 0xFF; break; case 4: // counter 0 data port case 5: // counter 1 data port case 6: // counter 2 data port case 7: // timer command register result = i8254.readIO(port - 4, time); break; default: UNREACHABLE; return 0; } return result; } byte MSXRS232::peekIO(word port, EmuTime::param time) const { if (hasMemoryBasedIo && !ioAccessEnabled) return 0xFF; byte result; port &= 0x07; switch (port) { case 0: // UART data register case 1: // UART status register result = i8251.peekIO(port, time); break; case 2: // Status sense port result = 0; // TODO not implemented break; case 3: // no function result = 0xFF; break; case 4: // counter 0 data port case 5: // counter 1 data port case 6: // counter 2 data port case 7: // timer command register result = i8254.peekIO(port - 4, time); break; default: UNREACHABLE; return 0; } return result; } void MSXRS232::writeIO(word port, byte value, EmuTime::param time) { if (ioAccessEnabled) writeIOImpl(port & 0x07, value, time); } void MSXRS232::writeIOImpl(word port, byte value, EmuTime::param time) { switch (port) { case 0: // UART data register case 1: // UART command register i8251.writeIO(port, value, time); break; case 2: // interrupt mask register setIRQMask(value); break; case 3: // no function break; case 4: // counter 0 data port case 5: // counter 1 data port case 6: // counter 2 data port case 7: // timer command register i8254.writeIO(port - 4, value, time); break; } } byte MSXRS232::readStatus(EmuTime::param time) { // Info from http://nocash.emubase.de/portar.htm // // Bit Name Expl. // 0 CD Carrier Detect (0=Active, 1=Not active) // 1 RI Ring Indicator (0=Active, 1=Not active) (N/C in MSX) // 6 Timer Output from i8253 Counter 2 // 7 CTS Clear to Send (0=Active, 1=Not active) // // On Toshiba HX-22, see // http://www.msx.org/forum/msx-talk/hardware/toshiba-hx-22?page=3 // RetroTechie's post of 20-09-2012, 08:08 // ... The "RS-232 interrupt disable" bit can be read back via bit 3 // on this I/O port, if CN1 is open. If CN1 is closed, it always // reads back as "0". ... byte result = 0; // TODO check unused bits // TODO bit 0: carrier detect if (!rxrdyIRQenabled && switchSetting && switchSetting->getBoolean()) { result |= 0x08; } if (!interf.getCTS(time)) { result |= 0x80; } if (i8254.getOutputPin(2).getState(time)) { result |= 0x40; } return result; } void MSXRS232::setIRQMask(byte value) { enableRxRDYIRQ(!(value & 1)); } void MSXRS232::setRxRDYIRQ(bool status) { if (rxrdyIRQlatch != status) { rxrdyIRQlatch = status; if (rxrdyIRQenabled) { if (rxrdyIRQlatch) { rxrdyIRQ.set(); } else { rxrdyIRQ.reset(); } } } } void MSXRS232::enableRxRDYIRQ(bool enabled) { if (rxrdyIRQenabled != enabled) { rxrdyIRQenabled = enabled; if (!rxrdyIRQenabled && rxrdyIRQlatch) { rxrdyIRQ.reset(); } } } // I8251Interface (pass calls from I8251 to outConnector) void MSXRS232::I8251Interf::setRxRDY(bool status, EmuTime::param /*time*/) { auto& rs232 = OUTER(MSXRS232, interf); rs232.setRxRDYIRQ(status); } void MSXRS232::I8251Interf::setDTR(bool status, EmuTime::param time) { auto& rs232 = OUTER(MSXRS232, interf); rs232.getPluggedRS232Dev().setDTR(status, time); } void MSXRS232::I8251Interf::setRTS(bool status, EmuTime::param time) { auto& rs232 = OUTER(MSXRS232, interf); rs232.getPluggedRS232Dev().setRTS(status, time); } bool MSXRS232::I8251Interf::getDSR(EmuTime::param time) { auto& rs232 = OUTER(MSXRS232, interf); return rs232.getPluggedRS232Dev().getDSR(time); } bool MSXRS232::I8251Interf::getCTS(EmuTime::param time) { auto& rs232 = OUTER(MSXRS232, interf); return rs232.getPluggedRS232Dev().getCTS(time); } void MSXRS232::I8251Interf::setDataBits(DataBits bits) { auto& rs232 = OUTER(MSXRS232, interf); rs232.getPluggedRS232Dev().setDataBits(bits); } void MSXRS232::I8251Interf::setStopBits(StopBits bits) { auto& rs232 = OUTER(MSXRS232, interf); rs232.getPluggedRS232Dev().setStopBits(bits); } void MSXRS232::I8251Interf::setParityBit(bool enable, ParityBit parity) { auto& rs232 = OUTER(MSXRS232, interf); rs232.getPluggedRS232Dev().setParityBit(enable, parity); } void MSXRS232::I8251Interf::recvByte(byte value, EmuTime::param time) { auto& rs232 = OUTER(MSXRS232, interf); rs232.getPluggedRS232Dev().recvByte(value, time); } void MSXRS232::I8251Interf::signal(EmuTime::param time) { auto& rs232 = OUTER(MSXRS232, interf); rs232.getPluggedRS232Dev().signal(time); // for input } // Counter 0 output void MSXRS232::Counter0::signal(ClockPin& pin, EmuTime::param time) { auto& rs232 = OUTER(MSXRS232, cntr0); ClockPin& clk = rs232.i8251.getClockPin(); if (pin.isPeriodic()) { clk.setPeriodicState(pin.getTotalDuration(), pin.getHighDuration(), time); } else { clk.setState(pin.getState(time), time); } } void MSXRS232::Counter0::signalPosEdge(ClockPin& /*pin*/, EmuTime::param /*time*/) { UNREACHABLE; } // Counter 1 output // TODO split rx tx void MSXRS232::Counter1::signal(ClockPin& pin, EmuTime::param time) { auto& rs232 = OUTER(MSXRS232, cntr1); ClockPin& clk = rs232.i8251.getClockPin(); if (pin.isPeriodic()) { clk.setPeriodicState(pin.getTotalDuration(), pin.getHighDuration(), time); } else { clk.setState(pin.getState(time), time); } } void MSXRS232::Counter1::signalPosEdge(ClockPin& /*pin*/, EmuTime::param /*time*/) { UNREACHABLE; } // RS232Connector input bool MSXRS232::ready() { return i8251.isRecvReady(); } bool MSXRS232::acceptsData() { return i8251.isRecvEnabled(); } void MSXRS232::setDataBits(DataBits bits) { i8251.setDataBits(bits); } void MSXRS232::setStopBits(StopBits bits) { i8251.setStopBits(bits); } void MSXRS232::setParityBit(bool enable, ParityBit parity) { i8251.setParityBit(enable, parity); } void MSXRS232::recvByte(byte value, EmuTime::param time) { i8251.recvByte(value, time); } // version 1: initial version // version 2: added ioAccessEnabled // TODO: serialize switch status? template void MSXRS232::serialize(Archive& ar, unsigned version) { ar.template serializeBase(*this); ar.template serializeBase(*this); ar.serialize("I8254", i8254); ar.serialize("I8251", i8251); if (ram) ar.serialize("ram", *ram); ar.serialize("rxrdyIRQ", rxrdyIRQ); ar.serialize("rxrdyIRQlatch", rxrdyIRQlatch); ar.serialize("rxrdyIRQenabled", rxrdyIRQenabled); if (ar.versionAtLeast(version, 2)) { ar.serialize("ioAccessEnabled", ioAccessEnabled); } else { assert(ar.isLoader()); ioAccessEnabled = !hasMemoryBasedIo; // we can't know the // actual value, but this is probably // safest } // don't serialize cntr0, cntr1, interf } INSTANTIATE_SERIALIZE_METHODS(MSXRS232); REGISTER_MSXDEVICE(MSXRS232, "RS232"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/MSXRS232.hh000066400000000000000000000056371257557151200202040ustar00rootroot00000000000000#ifndef MSXRS232_HH #define MSXRS232_HH #include "MSXDevice.hh" #include "IRQHelper.hh" #include "RS232Connector.hh" #include "I8251.hh" #include "I8254.hh" #include namespace openmsx { class Ram; class Rom; class BooleanSetting; class MSXRS232 final : public MSXDevice, public RS232Connector { public: explicit MSXRS232(const DeviceConfig& config); ~MSXRS232(); void powerUp(EmuTime::param time) override; void reset(EmuTime::param time) override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; // TODO: implement peekMem, because the default isn't OK anymore const byte *getReadCacheLine(word start) const override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word start) const override; // RS232Connector (input) void setDataBits(DataBits bits) override; void setStopBits(StopBits bits) override; void setParityBit(bool enable, ParityBit parity) override; void recvByte(byte value, EmuTime::param time) override; bool ready() override; bool acceptsData() override; template void serialize(Archive& ar, unsigned version); private: byte readIOImpl(word port, EmuTime::param time); void writeIOImpl(word port, byte value, EmuTime::param time); byte readStatus(EmuTime::param time); void setIRQMask(byte value); void setRxRDYIRQ(bool status); void enableRxRDYIRQ(bool enabled); struct Counter0 final : ClockPinListener { void signal(ClockPin& pin, EmuTime::param time) override; void signalPosEdge(ClockPin& pin, EmuTime::param time) override; } cntr0; // counter 0 rx clock pin struct Counter1 final : ClockPinListener { void signal(ClockPin& pin, EmuTime::param time) override; void signalPosEdge(ClockPin& pin, EmuTime::param time) override; } cntr1; // counter 1 tx clock pin I8254 i8254; struct I8251Interf final : I8251Interface { void setRxRDY(bool status, EmuTime::param time) override; void setDTR(bool status, EmuTime::param time) override; void setRTS(bool status, EmuTime::param time) override; bool getDSR(EmuTime::param time) override; bool getCTS(EmuTime::param time) override; void setDataBits(DataBits bits) override; void setStopBits(StopBits bits) override; void setParityBit(bool enable, ParityBit parity) override; void recvByte(byte value, EmuTime::param time) override; void signal(EmuTime::param time) override; } interf; I8251 i8251; const std::unique_ptr rom; // can be nullptr const std::unique_ptr ram; // can be nullptr IRQHelper rxrdyIRQ; bool rxrdyIRQlatch; bool rxrdyIRQenabled; const bool hasMemoryBasedIo; bool ioAccessEnabled; const std::unique_ptr switchSetting; // can be nullptr }; SERIALIZE_CLASS_VERSION(MSXRS232, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serial/MidiInConnector.cc000066400000000000000000000016441257557151200220450ustar00rootroot00000000000000#include "MidiInConnector.hh" #include "MidiInDevice.hh" #include "DummyMidiInDevice.hh" #include "checked_cast.hh" #include "serialize.hh" #include "memory.hh" namespace openmsx { MidiInConnector::MidiInConnector(PluggingController& pluggingController, string_ref name) : Connector(pluggingController, name, make_unique()) { } MidiInConnector::~MidiInConnector() { } const std::string MidiInConnector::getDescription() const { return "MIDI-in connector"; } string_ref MidiInConnector::getClass() const { return "midi in"; } MidiInDevice& MidiInConnector::getPluggedMidiInDev() const { return *checked_cast(&getPlugged()); } template void MidiInConnector::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); } INSTANTIATE_SERIALIZE_METHODS(MidiInConnector); } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/MidiInConnector.hh000066400000000000000000000014121257557151200220500ustar00rootroot00000000000000#ifndef MIDIINCONNECTOR_HH #define MIDIINCONNECTOR_HH #include "Connector.hh" #include "SerialDataInterface.hh" #include "serialize_meta.hh" namespace openmsx { class MidiInDevice; class MidiInConnector : public Connector, public SerialDataInterface { public: MidiInDevice& getPluggedMidiInDev() const; // Connector const std::string getDescription() const final override; string_ref getClass() const final override; virtual bool ready() = 0; virtual bool acceptsData() = 0; template void serialize(Archive& ar, unsigned version); protected: MidiInConnector(PluggingController& pluggingController, string_ref name); ~MidiInConnector(); }; REGISTER_BASE_CLASS(MidiInConnector, "inConnector"); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serial/MidiInCoreMIDI.cc000066400000000000000000000202551257557151200214450ustar00rootroot00000000000000#if defined(__APPLE__) #include "MidiInCoreMIDI.hh" #include "MidiInConnector.hh" #include "PluggingController.hh" #include "PlugException.hh" #include "EventDistributor.hh" #include "Scheduler.hh" #include "serialize.hh" #include "memory.hh" #include namespace openmsx { // MidiInCoreMIDI =========================================================== void MidiInCoreMIDI::registerAll(EventDistributor& eventDistributor, Scheduler& scheduler, PluggingController& controller) { ItemCount numberOfEndpoints = MIDIGetNumberOfSources(); for (ItemCount i = 0; i < numberOfEndpoints; i++) { MIDIEndpointRef endpoint = MIDIGetSource(i); if (endpoint) { controller.registerPluggable(make_unique( eventDistributor, scheduler, endpoint)); } } } MidiInCoreMIDI::MidiInCoreMIDI(EventDistributor& eventDistributor_, Scheduler& scheduler_, MIDIEndpointRef endpoint_) : eventDistributor(eventDistributor_) , scheduler(scheduler_) , endpoint(endpoint_) { // Get a user-presentable name for the endpoint. CFStringRef midiDeviceName; OSStatus status = MIDIObjectGetStringProperty( endpoint, kMIDIPropertyDisplayName, &midiDeviceName); if (status) { status = MIDIObjectGetStringProperty( endpoint, kMIDIPropertyName, &midiDeviceName); } if (status) { name = "Nameless endpoint"; } else { name = StringOp::Builder() << StringOp::fromCFString(midiDeviceName) << " IN"; CFRelease(midiDeviceName); } eventDistributor.registerEventListener( OPENMSX_MIDI_IN_COREMIDI_EVENT, *this); } MidiInCoreMIDI::~MidiInCoreMIDI() { eventDistributor.unregisterEventListener( OPENMSX_MIDI_IN_COREMIDI_EVENT, *this); } void MidiInCoreMIDI::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/) { // Create client. if (OSStatus status = MIDIClientCreate( CFSTR("openMSX"), nullptr, nullptr, &client)) { throw PlugException(StringOp::Builder() << "Failed to create MIDI client (" << status << ")"); } // Create input port. if (OSStatus status = MIDIInputPortCreate( client, CFSTR("Input"), sendPacketList, this, &port)) { MIDIClientDispose(client); client = 0; throw PlugException(StringOp::Builder() << "Failed to create MIDI port (" << status << ")"); } MIDIPortConnectSource(port, endpoint, nullptr); } void MidiInCoreMIDI::unplugHelper(EmuTime::param /*time*/) { // Dispose of the client; this automatically disposes of the port as well. if (OSStatus status = MIDIClientDispose(client)) { fprintf(stderr, "Failed to dispose of MIDI client (%d)\n", (int)status); } port = 0; client = 0; } const std::string& MidiInCoreMIDI::getName() const { return name; } string_ref MidiInCoreMIDI::getDescription() const { return "Receives MIDI events from an existing CoreMIDI source."; } void MidiInCoreMIDI::sendPacketList(const MIDIPacketList *packetList, void *readProcRefCon, void *srcConnRefCon) { ((MidiInCoreMIDI*)readProcRefCon) ->sendPacketList(packetList, srcConnRefCon); } void MidiInCoreMIDI::sendPacketList(const MIDIPacketList *packetList, void * /*srcConnRefCon*/) { { std::lock_guard lock(mutex); const MIDIPacket *packet = &packetList->packet[0]; for (UInt32 i = 0; i < packetList->numPackets; i++) { for (UInt16 j = 0; j < packet->length; j++) { queue.push_back(packet->data[j]); } packet = MIDIPacketNext(packet); } } eventDistributor.distributeEvent( std::make_shared(OPENMSX_MIDI_IN_COREMIDI_EVENT)); } // MidiInDevice void MidiInCoreMIDI::signal(EmuTime::param time) { auto connector = static_cast(getConnector()); if (!connector->acceptsData()) { std::lock_guard lock(mutex); queue.clear(); return; } if (!connector->ready()) { return; } byte data; { std::lock_guard lock(mutex); if (queue.empty()) return; data = queue.pop_front(); } connector->recvByte(data, time); } // EventListener int MidiInCoreMIDI::signalEvent(const std::shared_ptr& /*event*/) { if (isPluggedIn()) { signal(scheduler.getCurrentTime()); } else { std::lock_guard lock(mutex); queue.clear(); } return 0; } template void MidiInCoreMIDI::serialize(Archive& /*ar*/, unsigned /*version*/) { } INSTANTIATE_SERIALIZE_METHODS(MidiInCoreMIDI); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, MidiInCoreMIDI, "MidiInCoreMIDI"); // MidiInCoreMIDIVirtual ==================================================== MidiInCoreMIDIVirtual::MidiInCoreMIDIVirtual(EventDistributor& eventDistributor_, Scheduler& scheduler_) : eventDistributor(eventDistributor_) , scheduler(scheduler_) , client(0) , endpoint(0) { eventDistributor.registerEventListener( OPENMSX_MIDI_IN_COREMIDI_VIRTUAL_EVENT, *this); } MidiInCoreMIDIVirtual::~MidiInCoreMIDIVirtual() { eventDistributor.unregisterEventListener( OPENMSX_MIDI_IN_COREMIDI_VIRTUAL_EVENT, *this); } void MidiInCoreMIDIVirtual::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/) { // Create client. if (OSStatus status = MIDIClientCreate(CFSTR("openMSX"), nullptr, nullptr, &client)) { throw PlugException(StringOp::Builder() << "Failed to create MIDI client (" << status << ")"); } // Create endpoint. if (OSStatus status = MIDIDestinationCreate(client, CFSTR("openMSX"), sendPacketList, this, &endpoint)) { MIDIClientDispose(client); throw PlugException(StringOp::Builder() << "Failed to create MIDI endpoint (" << status << ")"); } } void MidiInCoreMIDIVirtual::unplugHelper(EmuTime::param /*time*/) { if (OSStatus status = MIDIEndpointDispose(endpoint)) { fprintf(stderr, "Failed to dispose of MIDI port (%d)\n", (int)status); } endpoint = 0; if (OSStatus status = MIDIClientDispose(client)) { fprintf(stderr, "Failed to dispose of MIDI client (%d)\n", (int)status); } client = 0; } const std::string& MidiInCoreMIDIVirtual::getName() const { static const std::string name("Virtual IN"); return name; } string_ref MidiInCoreMIDIVirtual::getDescription() const { return "Sends MIDI events from a newly created CoreMIDI virtual source."; } void MidiInCoreMIDIVirtual::sendPacketList(const MIDIPacketList *packetList, void *readProcRefCon, void *srcConnRefCon) { ((MidiInCoreMIDIVirtual*)readProcRefCon) ->sendPacketList(packetList, srcConnRefCon); } void MidiInCoreMIDIVirtual::sendPacketList(const MIDIPacketList *packetList, void * /*srcConnRefCon*/) { { std::lock_guard lock(mutex); const MIDIPacket *packet = &packetList->packet[0]; for (UInt32 i = 0; i < packetList->numPackets; i++) { for (UInt16 j = 0; j < packet->length; j++) { queue.push_back(packet->data[j]); } packet = MIDIPacketNext(packet); } } eventDistributor.distributeEvent( std::make_shared(OPENMSX_MIDI_IN_COREMIDI_VIRTUAL_EVENT)); } // MidiInDevice void MidiInCoreMIDIVirtual::signal(EmuTime::param time) { auto connector = static_cast(getConnector()); if (!connector->acceptsData()) { std::lock_guard lock(mutex); queue.clear(); return; } if (!connector->ready()) { return; } byte data; { std::lock_guard lock(mutex); if (queue.empty()) return; data = queue.pop_front(); } connector->recvByte(data, time); } // EventListener int MidiInCoreMIDIVirtual::signalEvent( const std::shared_ptr& /*event*/) { if (isPluggedIn()) { signal(scheduler.getCurrentTime()); } else { std::lock_guard lock(mutex); queue.clear(); } return 0; } template void MidiInCoreMIDIVirtual::serialize(Archive& /*ar*/, unsigned /*version*/) { } INSTANTIATE_SERIALIZE_METHODS(MidiInCoreMIDIVirtual); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, MidiInCoreMIDIVirtual, "MidiInCoreMIDIVirtual"); } // namespace openmsx #endif // defined(__APPLE__) openMSX-RELEASE_0_12_0/src/serial/MidiInCoreMIDI.hh000066400000000000000000000062151257557151200214570ustar00rootroot00000000000000#ifndef MIDIINCOREMIDI_HH #define MIDIINCOREMIDI_HH #if defined(__APPLE__) #include "MidiInDevice.hh" #include "EventListener.hh" #include "openmsx.hh" #include "serialize_meta.hh" #include "circular_buffer.hh" #include #include namespace openmsx { class EventDistributor; class Scheduler; class PluggingController; /** Sends MIDI events to an existing CoreMIDI destination. */ class MidiInCoreMIDI final : public MidiInDevice, private EventListener { public: static void registerAll(EventDistributor& eventDistributor, Scheduler& scheduler, PluggingController& controller); /** Public for the sake of make_unique<>() - not intended for actual * public use. */ explicit MidiInCoreMIDI(EventDistributor& eventDistributor_, Scheduler& scheduler_, MIDIEndpointRef endpoint); ~MidiInCoreMIDI(); // Pluggable void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; const std::string& getName() const override; string_ref getDescription() const override; // MidiInDevice void signal(EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: // EventListener int signalEvent(const std::shared_ptr& event) override; static void sendPacketList(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon); void sendPacketList(const MIDIPacketList *pktlist, void *srcConnRefCon); EventDistributor& eventDistributor; Scheduler& scheduler; cb_queue queue; std::mutex mutex; // to protect queue MIDIClientRef client; MIDIPortRef port; MIDIEndpointRef endpoint; std::string name; }; /** Sends MIDI events from a newly created CoreMIDI virtual source. * This class acts as a MIDI input, unlike the other class that sends events * to a MIDI output. It is similar to using an IAC bus, but doesn't require * prior configuration to work. */ class MidiInCoreMIDIVirtual final : public MidiInDevice, private EventListener { public: explicit MidiInCoreMIDIVirtual(EventDistributor& eventDistributor_, Scheduler& scheduler_); ~MidiInCoreMIDIVirtual(); // Pluggable void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; const std::string& getName() const override; string_ref getDescription() const override; // MidiInDevice void signal(EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: // EventListener int signalEvent(const std::shared_ptr& event) override; static void sendPacketList(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon); void sendPacketList(const MIDIPacketList *pktlist, void *srcConnRefCon); EventDistributor& eventDistributor; Scheduler& scheduler; cb_queue queue; std::mutex mutex; // to protect queue MIDIClientRef client; MIDIEndpointRef endpoint; }; } // namespace openmsx #endif // defined(__APPLE__) #endif // MIDIINCOREMIDI_HH openMSX-RELEASE_0_12_0/src/serial/MidiInDevice.cc000066400000000000000000000002121257557151200213000ustar00rootroot00000000000000#include "MidiInDevice.hh" namespace openmsx { string_ref MidiInDevice::getClass() const { return "midi in"; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/MidiInDevice.hh000066400000000000000000000004441257557151200213210ustar00rootroot00000000000000#ifndef MIDIINDEVICE_HH #define MIDIINDEVICE_HH #include "Pluggable.hh" namespace openmsx { class MidiInDevice : public Pluggable { public: // Pluggable (part) string_ref getClass() const final override; virtual void signal(EmuTime::param time) = 0; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serial/MidiInReader.cc000066400000000000000000000071341257557151200213150ustar00rootroot00000000000000#include "MidiInReader.hh" #include "MidiInConnector.hh" #include "PlugException.hh" #include "EventDistributor.hh" #include "Scheduler.hh" #include "FileOperations.hh" #include "StringOp.hh" #include "serialize.hh" #include #include #include #include using std::string; namespace openmsx { // This only works for one simultaneous instance // TODO get rid of helper thread static std::atomic exitLoop(false); MidiInReader::MidiInReader(EventDistributor& eventDistributor_, Scheduler& scheduler_, CommandController& commandController) : eventDistributor(eventDistributor_), scheduler(scheduler_) , thread(this) , readFilenameSetting( commandController, "midi-in-readfilename", "filename of the file where the MIDI input is read from", "/dev/midi") { eventDistributor.registerEventListener(OPENMSX_MIDI_IN_READER_EVENT, *this); exitLoop = false; } MidiInReader::~MidiInReader() { exitLoop = true; eventDistributor.unregisterEventListener(OPENMSX_MIDI_IN_READER_EVENT, *this); } // Pluggable void MidiInReader::plugHelper(Connector& connector_, EmuTime::param /*time*/) { file = FileOperations::openFile(readFilenameSetting.getString().str(), "rb"); if (!file) { throw PlugException(StringOp::Builder() << "Failed to open input: " << strerror(errno)); } auto& midiConnector = static_cast(connector_); midiConnector.setDataBits(SerialDataInterface::DATA_8); // 8 data bits midiConnector.setStopBits(SerialDataInterface::STOP_1); // 1 stop bit midiConnector.setParityBit(false, SerialDataInterface::EVEN); // no parity setConnector(&connector_); // base class will do this in a moment, // but thread already needs it thread.start(); } void MidiInReader::unplugHelper(EmuTime::param /*time*/) { std::lock_guard lock(mutex); thread.stop(); file.reset(); } const string& MidiInReader::getName() const { static const string name("midi-in-reader"); return name; } string_ref MidiInReader::getDescription() const { return "MIDI in file reader. Sends data from an input file to the " "MIDI port it is connected to. The filename is set with " "the 'midi-in-readfilename' setting."; } // Runnable void MidiInReader::run() { byte buf; if (!file) return; while (true) { size_t num = fread(&buf, 1, 1, file.get()); if (exitLoop) break; if (num != 1) { continue; } assert(isPluggedIn()); { std::lock_guard lock(mutex); queue.push_back(buf); } eventDistributor.distributeEvent( std::make_shared(OPENMSX_MIDI_IN_READER_EVENT)); } } // MidiInDevice void MidiInReader::signal(EmuTime::param time) { auto connector = static_cast(getConnector()); if (!connector->acceptsData()) { std::lock_guard lock(mutex); queue.clear(); return; } if (!connector->ready()) { return; } byte data; { std::lock_guard lock(mutex); if (queue.empty()) return; data = queue.pop_front(); } connector->recvByte(data, time); } // EventListener int MidiInReader::signalEvent(const std::shared_ptr& /*event*/) { if (isPluggedIn()) { signal(scheduler.getCurrentTime()); } else { std::lock_guard lock(mutex); queue.clear(); } return 0; } template void MidiInReader::serialize(Archive& /*ar*/, unsigned /*version*/) { // don't try to resume a previous logfile (see PrinterPortLogger) } INSTANTIATE_SERIALIZE_METHODS(MidiInReader); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, MidiInReader, "MidiInReader"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/MidiInReader.hh000066400000000000000000000025311257557151200213230ustar00rootroot00000000000000#ifndef MIDIINREADER_HH #define MIDIINREADER_HH #include "MidiInDevice.hh" #include "Thread.hh" #include "EventListener.hh" #include "FilenameSetting.hh" #include "FileOperations.hh" #include "openmsx.hh" #include "circular_buffer.hh" #include #include namespace openmsx { class EventDistributor; class Scheduler; class CommandController; class MidiInReader final : public MidiInDevice, private Runnable , private EventListener { public: MidiInReader(EventDistributor& eventDistributor, Scheduler& scheduler, CommandController& commandController); ~MidiInReader(); // Pluggable void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; const std::string& getName() const override; string_ref getDescription() const override; // MidiInDevice void signal(EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: // Runnable void run() override; // EventListener int signalEvent(const std::shared_ptr& event) override; EventDistributor& eventDistributor; Scheduler& scheduler; Thread thread; FileOperations::FILE_t file; cb_queue queue; std::mutex mutex; // to protect queue FilenameSetting readFilenameSetting; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serial/MidiInWindows.cc000066400000000000000000000113061257557151200215410ustar00rootroot00000000000000#if defined(_WIN32) #include "MidiInWindows.hh" #include "MidiInConnector.hh" #include "PluggingController.hh" #include "PlugException.hh" #include "EventDistributor.hh" #include "Scheduler.hh" #include "serialize.hh" #include "memory.hh" #include #include #include "Midi_w32.hh" #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #include #include using std::string; namespace openmsx { void MidiInWindows::registerAll(EventDistributor& eventDistributor, Scheduler& scheduler, PluggingController& controller) { w32_midiInInit(); unsigned devnum = w32_midiInGetVFNsNum(); for (unsigned i = 0 ; i ( eventDistributor, scheduler, i)); } } MidiInWindows::MidiInWindows(EventDistributor& eventDistributor_, Scheduler& scheduler_, unsigned num) : eventDistributor(eventDistributor_), scheduler(scheduler_) , thread(this), devidx(unsigned(-1)) { name = w32_midiInGetVFN(num); desc = w32_midiInGetRDN(num); eventDistributor.registerEventListener(OPENMSX_MIDI_IN_WINDOWS_EVENT, *this); } MidiInWindows::~MidiInWindows() { eventDistributor.unregisterEventListener(OPENMSX_MIDI_IN_WINDOWS_EVENT, *this); //w32_midiInClean(); // TODO } // Pluggable void MidiInWindows::plugHelper(Connector& connector_, EmuTime::param /*time*/) { devidx = w32_midiInOpen(name.c_str(), thrdid); if (devidx == unsigned(-1)) { throw PlugException("Failed to open " + name); } auto& midiConnector = static_cast(connector_); midiConnector.setDataBits(SerialDataInterface::DATA_8); // 8 data bits midiConnector.setStopBits(SerialDataInterface::STOP_1); // 1 stop bit midiConnector.setParityBit(false, SerialDataInterface::EVEN); // no parity setConnector(&connector_); // base class will do this in a moment, // but thread already needs it thread.start(); } void MidiInWindows::unplugHelper(EmuTime::param /*time*/) { std::lock_guard lock(mutex); thread.stop(); if (devidx != unsigned(-1)) { w32_midiInClose(devidx); devidx = unsigned(-1); } } const string& MidiInWindows::getName() const { return name; } string_ref MidiInWindows::getDescription() const { return desc; } void MidiInWindows::procLongMsg(LPMIDIHDR p) { if (p->dwBytesRecorded) { { std::lock_guard lock(mutex); for (unsigned i = 0; i < p->dwBytesRecorded; ++i) { queue.push_back(p->lpData[i]); } } eventDistributor.distributeEvent( std::make_shared(OPENMSX_MIDI_IN_WINDOWS_EVENT)); } } void MidiInWindows::procShortMsg(DWORD param) { int num; switch (param & 0xF0) { case 0x80: case 0x90: case 0xA0: case 0xB0: case 0xE0: num = 3; break; case 0xC0: case 0xD0: num = 2; break; default: num = 1; break; } std::lock_guard lock(mutex); while (num--) { queue.push_back(param & 0xFF); param >>= 8; } eventDistributor.distributeEvent( std::make_shared(OPENMSX_MIDI_IN_WINDOWS_EVENT)); } // Runnable void MidiInWindows::run() { assert(isPluggedIn()); thrdid = GetCurrentThreadId(); MSG msg; bool fexit = false; int gmer; while (!fexit) { gmer = GetMessage(&msg, nullptr, 0, 0); if (gmer == 0 || gmer == -1) { break; } switch (msg.message) { case MM_MIM_OPEN: break; case MM_MIM_DATA: case MM_MIM_MOREDATA: procShortMsg(DWORD(msg.lParam)); break; case MM_MIM_LONGDATA: procLongMsg(reinterpret_cast(msg.lParam)); break; case MM_MIM_ERROR: case MM_MIM_LONGERROR: case MM_MIM_CLOSE: fexit = true; break; default: break; } } thrdid = 0; } // MidiInDevice void MidiInWindows::signal(EmuTime::param time) { auto connector = static_cast(getConnector()); if (!connector->acceptsData()) { std::lock_guard lock(mutex); queue.clear(); return; } if (!connector->ready()) return; byte data; { std::lock_guard lock(mutex); if (queue.empty()) return; data = queue.pop_front(); } connector->recvByte(data, time); } // EventListener int MidiInWindows::signalEvent(const std::shared_ptr& /*event*/) { if (isPluggedIn()) { signal(scheduler.getCurrentTime()); } else { std::lock_guard lock(mutex); queue.clear(); } return 0; } template void MidiInWindows::serialize(Archive& /*ar*/, unsigned /*version*/) { // don't restore this after loadstate } INSTANTIATE_SERIALIZE_METHODS(MidiInWindows); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, MidiInWindows, "MidiInWindows"); } // namespace openmsx #endif // defined(_WIN32) openMSX-RELEASE_0_12_0/src/serial/MidiInWindows.hh000066400000000000000000000033721257557151200215570ustar00rootroot00000000000000#ifndef MIDIINWINDOWS_HH #define MIDIINWINDOWS_HH #if defined(_WIN32) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include "openmsx.hh" #include "MidiInDevice.hh" #include "Thread.hh" #include "EventListener.hh" #include "serialize_meta.hh" #include "circular_buffer.hh" #include #include #include namespace openmsx { class EventDistributor; class Scheduler; class PluggingController; class MidiInWindows final : public MidiInDevice, private Runnable , private EventListener { public: /** Register all available native Windows midi in devices */ static void registerAll(EventDistributor& eventDistributor, Scheduler& scheduler, PluggingController& controller); MidiInWindows(EventDistributor& eventDistributor, Scheduler& scheduler, unsigned num); ~MidiInWindows(); // Pluggable void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; const std::string& getName() const override; string_ref getDescription() const override; // MidiInDevice void signal(EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: // Runnable void run() override; // EventListener int signalEvent(const std::shared_ptr& event) override; void procShortMsg(long unsigned param); void procLongMsg(LPMIDIHDR p); EventDistributor& eventDistributor; Scheduler& scheduler; Thread thread; unsigned devidx; DWORD thrdid; cb_queue queue; std::mutex mutex; // to protect queue std::string name; std::string desc; }; } // namespace openmsx #endif // defined(_WIN32) #endif // MIDIINWINDOWS_HH openMSX-RELEASE_0_12_0/src/serial/MidiOutConnector.cc000066400000000000000000000025231257557151200222430ustar00rootroot00000000000000#include "MidiOutConnector.hh" #include "MidiOutDevice.hh" #include "DummyMidiOutDevice.hh" #include "checked_cast.hh" #include "serialize.hh" #include "memory.hh" using std::string; namespace openmsx { MidiOutConnector::MidiOutConnector(PluggingController& pluggingController, string_ref name) : Connector(pluggingController, name, make_unique()) { } const string MidiOutConnector::getDescription() const { return "MIDI-out connector"; } string_ref MidiOutConnector::getClass() const { return "midi out"; } MidiOutDevice& MidiOutConnector::getPluggedMidiOutDev() const { return *checked_cast(&getPlugged()); } void MidiOutConnector::setDataBits(DataBits bits) { getPluggedMidiOutDev().setDataBits(bits); } void MidiOutConnector::setStopBits(StopBits bits) { getPluggedMidiOutDev().setStopBits(bits); } void MidiOutConnector::setParityBit(bool enable, ParityBit parity) { getPluggedMidiOutDev().setParityBit(enable, parity); } void MidiOutConnector::recvByte(byte value, EmuTime::param time) { getPluggedMidiOutDev().recvByte(value, time); } template void MidiOutConnector::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); } INSTANTIATE_SERIALIZE_METHODS(MidiOutConnector); } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/MidiOutConnector.hh000066400000000000000000000015151257557151200222550ustar00rootroot00000000000000#ifndef MIDIOUTCONNECTOR_HH #define MIDIOUTCONNECTOR_HH #include "Connector.hh" #include "SerialDataInterface.hh" namespace openmsx { class MidiOutDevice; class MidiOutConnector final : public Connector, public SerialDataInterface { public: MidiOutConnector(PluggingController& pluggingController, string_ref name); MidiOutDevice& getPluggedMidiOutDev() const; // Connector const std::string getDescription() const final override; string_ref getClass() const final override; // SerialDataInterface void setDataBits(DataBits bits) override; void setStopBits(StopBits bits) override; void setParityBit(bool enable, ParityBit parity) override; void recvByte(byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serial/MidiOutCoreMIDI.cc000066400000000000000000000177701257557151200216560ustar00rootroot00000000000000#if defined(__APPLE__) #include "MidiOutCoreMIDI.hh" #include "PluggingController.hh" #include "PlugException.hh" #include "serialize.hh" #include "memory.hh" #include "openmsx.hh" #include #include namespace openmsx { // MidiOutMessageBuffer ====================================================== static constexpr uint8_t MIDI_MSG_SYSEX = 0xF0; static constexpr uint8_t MIDI_MSG_SYSEX_END = 0xF7; static constexpr uint8_t MIDI_MSG_RESET = 0xFF; /** Returns the size in bytes of a message that starts with the given status. */ static size_t midiMessageLength(uint8_t status) { if (status < 0x80) { assert(false); return 0; } else if (status < 0xC0) { return 3; } else if (status < 0xE0) { return 2; } else if (status < 0xF0) { return 3; } else { switch (status) { case MIDI_MSG_SYSEX: // Limit to force sending large SysEx messages in chunks. // The limit for the amount of data in one MIDIPacket in CoreMIDI, // so our chunks could be rejected by CoreMIDI if we set this limit // larger than 256. return 256; case MIDI_MSG_SYSEX_END: assert(false); return 0; case 0xF1: case 0xF3: return 2; case 0xF2: return 3; case 0xF4: case 0xF5: case 0xF9: case 0xFD: // Data size unknown return 1; default: return 1; } } } MidiOutMessageBuffer::MidiOutMessageBuffer() : isSysEx(false) { } void MidiOutMessageBuffer::clearBuffer() { message.clear(); isSysEx = false; } // Note: Nothing in this method is specific to CoreMIDI; it could be moved to // generic MIDI code if any other platform also needs message rebuilding. void MidiOutMessageBuffer::recvByte(byte value, EmuTime::param time) { if (value & 0x80) { // status byte if (value == MIDI_MSG_SYSEX_END) { if (isSysEx) { message.push_back(value); messageComplete(time, message.data(), message.size()); } else { // Ignoring SysEx end without start } message.clear(); isSysEx = false; } else if (value >= 0xF8) { // Realtime message, send immediately. messageComplete(time, &value, 1); if (value == MIDI_MSG_RESET) { message.clear(); } return; } else { // Replace any message in progress. if (isSysEx) { // Discarding incomplete MIDI SysEx message } else if (message.size() >= 2) { #if 0 std::cerr << "Discarding incomplete MIDI message with status " "0x" << std::hex << int(message[0]) << std::dec << ", at " << message.size() << " of " << midiMessageLength(message[0]) << " bytes" << std::endl; #endif } message = { value }; isSysEx = value == MIDI_MSG_SYSEX; } } else { // data byte if (message.empty() && !isSysEx) { // Ignoring MIDI data without preceding status } else { message.push_back(value); } } // Is the message complete? if (!message.empty()) { uint8_t status = isSysEx ? MIDI_MSG_SYSEX : message[0]; size_t len = midiMessageLength(status); if (message.size() >= len) { messageComplete(time, message.data(), message.size()); if (status >= 0xF0 && status < 0xF8) { message.clear(); } else { // Keep last status, to support running status. message.resize(1); } } } } void MidiOutMessageBuffer::messageComplete(EmuTime::param /*time*/, const uint8_t *data, size_t size) { // TODO: It would be better to schedule events based on EmuTime. MIDITimeStamp abstime = mach_absolute_time(); MIDIPacketList packetList; MIDIPacket *curPacket = MIDIPacketListInit(&packetList); curPacket = MIDIPacketListAdd(&packetList, sizeof(packetList), curPacket, abstime, size, data); if (!curPacket) { fprintf(stderr, "Failed to package MIDI data\n"); } else if (OSStatus status = sendPacketList(&packetList)) { fprintf(stderr, "Failed to send MIDI data (%d)\n", (int)status); } else { //fprintf(stderr, "MIDI send OK: %02X\n", value); } } // MidiOutCoreMIDI =========================================================== void MidiOutCoreMIDI::registerAll(PluggingController& controller) { ItemCount numberOfEndpoints = MIDIGetNumberOfDestinations(); for (ItemCount i = 0; i < numberOfEndpoints; i++) { MIDIEndpointRef endpoint = MIDIGetDestination(i); if (endpoint) { controller.registerPluggable( make_unique(endpoint)); } } } MidiOutCoreMIDI::MidiOutCoreMIDI(MIDIEndpointRef endpoint_) : endpoint(endpoint_) { // Get a user-presentable name for the endpoint. CFStringRef midiDeviceName; OSStatus status = MIDIObjectGetStringProperty( endpoint, kMIDIPropertyDisplayName, &midiDeviceName); if (status) { status = MIDIObjectGetStringProperty( endpoint, kMIDIPropertyName, &midiDeviceName); } if (status) { name = "Nameless endpoint"; } else { name = StringOp::Builder() << StringOp::fromCFString(midiDeviceName) << " OUT"; CFRelease(midiDeviceName); } } void MidiOutCoreMIDI::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/) { // Create client. if (OSStatus status = MIDIClientCreate(CFSTR("openMSX"), nullptr, nullptr, &client)) { throw PlugException(StringOp::Builder() << "Failed to create MIDI client (" << status << ")"); } // Create output port. if (OSStatus status = MIDIOutputPortCreate(client, CFSTR("Output"), &port)) { MIDIClientDispose(client); client = 0; throw PlugException(StringOp::Builder() << "Failed to create MIDI port (" << status << ")"); } } void MidiOutCoreMIDI::unplugHelper(EmuTime::param /*time*/) { clearBuffer(); // Dispose of the client; this automatically disposes of the port as well. if (OSStatus status = MIDIClientDispose(client)) { fprintf(stderr, "Failed to dispose of MIDI client (%d)\n", (int)status); } port = 0; client = 0; } const std::string& MidiOutCoreMIDI::getName() const { return name; } string_ref MidiOutCoreMIDI::getDescription() const { return "Sends MIDI events to an existing CoreMIDI destination."; } OSStatus MidiOutCoreMIDI::sendPacketList(MIDIPacketList *myPacketList) { return MIDISend(port, endpoint, myPacketList); } template void MidiOutCoreMIDI::serialize(Archive& /*ar*/, unsigned /*version*/) { } INSTANTIATE_SERIALIZE_METHODS(MidiOutCoreMIDI); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, MidiOutCoreMIDI, "MidiOutCoreMIDI"); // MidiOutCoreMIDIVirtual ==================================================== MidiOutCoreMIDIVirtual:: MidiOutCoreMIDIVirtual() : client(0) , endpoint(0) { } void MidiOutCoreMIDIVirtual::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/) { // Create client. if (OSStatus status = MIDIClientCreate(CFSTR("openMSX"), nullptr, nullptr, &client)) { throw PlugException(StringOp::Builder() << "Failed to create MIDI client (" << status << ")"); } // Create endpoint. if (OSStatus status = MIDISourceCreate(client, CFSTR("openMSX"), &endpoint)) { MIDIClientDispose(client); throw PlugException(StringOp::Builder() << "Failed to create MIDI endpoint (" << status << ")"); } } void MidiOutCoreMIDIVirtual::unplugHelper(EmuTime::param /*time*/) { clearBuffer(); if (OSStatus status = MIDIEndpointDispose(endpoint)) { fprintf(stderr, "Failed to dispose of MIDI port (%d)\n", (int)status); } endpoint = 0; if (OSStatus status = MIDIClientDispose(client)) { fprintf(stderr, "Failed to dispose of MIDI client (%d)\n", (int)status); } client = 0; } const std::string& MidiOutCoreMIDIVirtual::getName() const { static const std::string name("Virtual OUT"); return name; } string_ref MidiOutCoreMIDIVirtual::getDescription() const { return "Sends MIDI events from a newly created CoreMIDI virtual source."; } OSStatus MidiOutCoreMIDIVirtual::sendPacketList(MIDIPacketList *myPacketList) { return MIDIReceived(endpoint, myPacketList); } template void MidiOutCoreMIDIVirtual::serialize(Archive& /*ar*/, unsigned /*version*/) { } INSTANTIATE_SERIALIZE_METHODS(MidiOutCoreMIDIVirtual); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, MidiOutCoreMIDIVirtual, "MidiOutCoreMIDIVirtual"); } // namespace openmsx #endif // defined(__APPLE__) openMSX-RELEASE_0_12_0/src/serial/MidiOutCoreMIDI.hh000066400000000000000000000046661257557151200216700ustar00rootroot00000000000000#ifndef MIDIOUTCOREMIDI_HH #define MIDIOUTCOREMIDI_HH #if defined(__APPLE__) #include "MidiOutDevice.hh" #include #include namespace openmsx { class PluggingController; /** Combines MIDI bytes into full MIDI messages. * CoreMIDI expects full messages in packet lists. */ class MidiOutMessageBuffer : public MidiOutDevice { public: // SerialDataInterface (part) void recvByte(byte value, EmuTime::param time) override; protected: explicit MidiOutMessageBuffer(); void clearBuffer(); virtual OSStatus sendPacketList(MIDIPacketList *myPacketList) = 0; private: void messageComplete(EmuTime::param time, const uint8_t *data, size_t size); std::vector message; bool isSysEx; }; /** Sends MIDI events to an existing CoreMIDI destination. */ class MidiOutCoreMIDI final : public MidiOutMessageBuffer { public: static void registerAll(PluggingController& controller); /** Public for the sake of make_unique<>() - not intended for actual * public use. */ explicit MidiOutCoreMIDI(MIDIEndpointRef endpoint); // Pluggable void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; const std::string& getName() const override; string_ref getDescription() const override; // MidiOutMessageBuffer OSStatus sendPacketList(MIDIPacketList *myPacketList) override; template void serialize(Archive& ar, unsigned version); private: MIDIClientRef client; MIDIPortRef port; MIDIEndpointRef endpoint; std::string name; }; /** Sends MIDI events from a newly created CoreMIDI virtual source. * This class acts as a MIDI input, unlike the other class that sends events * to a MIDI output. It is similar to using an IAC bus, but doesn't require * prior configuration to work. */ class MidiOutCoreMIDIVirtual final : public MidiOutMessageBuffer { public: explicit MidiOutCoreMIDIVirtual(); // Pluggable void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; const std::string& getName() const override; string_ref getDescription() const override; // MidiOutMessageBuffer OSStatus sendPacketList(MIDIPacketList *myPacketList) override; template void serialize(Archive& ar, unsigned version); private: MIDIClientRef client; MIDIEndpointRef endpoint; }; } // namespace openmsx #endif // defined(__APPLE__) #endif // MIDIOUTCOREMIDI_HH openMSX-RELEASE_0_12_0/src/serial/MidiOutDevice.cc000066400000000000000000000005531257557151200215110ustar00rootroot00000000000000#include "MidiOutDevice.hh" namespace openmsx { string_ref MidiOutDevice::getClass() const { return "midi out"; } void MidiOutDevice::setDataBits(DataBits /*bits*/) { // ignore } void MidiOutDevice::setStopBits(StopBits /*bits*/) { // ignore } void MidiOutDevice::setParityBit(bool /*enable*/, ParityBit /*parity*/) { // ignore } } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/MidiOutDevice.hh000066400000000000000000000007471257557151200215300ustar00rootroot00000000000000#ifndef MIDIOUTDEVICE_HH #define MIDIOUTDEVICE_HH #include "Pluggable.hh" #include "SerialDataInterface.hh" namespace openmsx { class MidiOutDevice : public Pluggable, public SerialDataInterface { public: // Pluggable (part) string_ref getClass() const final override; // SerialDataInterface (part) void setDataBits(DataBits bits) override; void setStopBits(StopBits bits) override; void setParityBit(bool enable, ParityBit parity) override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serial/MidiOutLogger.cc000066400000000000000000000027271257557151200215360ustar00rootroot00000000000000#include "MidiOutLogger.hh" #include "PlugException.hh" #include "FileOperations.hh" #include "serialize.hh" namespace openmsx { MidiOutLogger::MidiOutLogger(CommandController& commandController) : logFilenameSetting( commandController, "midi-out-logfilename", "filename of the file where the MIDI output is logged to", "/dev/midi") { } void MidiOutLogger::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/) { FileOperations::openofstream(file, logFilenameSetting.getString().str()); if (file.fail()) { file.clear(); throw PlugException("Error opening log file"); } } void MidiOutLogger::unplugHelper(EmuTime::param /*time*/) { file.close(); } const std::string& MidiOutLogger::getName() const { static const std::string name("midi-out-logger"); return name; } string_ref MidiOutLogger::getDescription() const { return "Midi output logger. Log all data that is sent to this " "pluggable to a file. The filename is set with the " "'midi-out-logfilename' setting."; } void MidiOutLogger::recvByte(byte value, EmuTime::param /*time*/) { if (file.is_open()) { file.put(value); file.flush(); } } template void MidiOutLogger::serialize(Archive& /*ar*/, unsigned /*version*/) { // don't try to resume a previous logfile (see PrinterPortLogger) } INSTANTIATE_SERIALIZE_METHODS(MidiOutLogger); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, MidiOutLogger, "MidiOutLogger"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/MidiOutLogger.hh000066400000000000000000000014321257557151200215400ustar00rootroot00000000000000#ifndef MIDIOUTLOGGER_HH #define MIDIOUTLOGGER_HH #include "MidiOutDevice.hh" #include "FilenameSetting.hh" #include namespace openmsx { class CommandController; class MidiOutLogger final : public MidiOutDevice { public: explicit MidiOutLogger(CommandController& commandController); // Pluggable void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; const std::string& getName() const override; string_ref getDescription() const override; // SerialDataInterface (part) void recvByte(byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: FilenameSetting logFilenameSetting; std::ofstream file; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serial/MidiOutWindows.cc000066400000000000000000000031311257557151200217370ustar00rootroot00000000000000#if defined(_WIN32) #include "Midi_w32.hh" #include "MidiOutWindows.hh" #include "PluggingController.hh" #include "PlugException.hh" #include "serialize.hh" #include "memory.hh" using std::string; namespace openmsx { void MidiOutWindows::registerAll(PluggingController& controller) { w32_midiOutInit(); unsigned devnum = w32_midiOutGetVFNsNum(); for (unsigned i = 0; i < devnum; ++i) { controller.registerPluggable(make_unique(i)); } } MidiOutWindows::MidiOutWindows(unsigned num) : devidx(unsigned(-1)) { name = w32_midiOutGetVFN(num); desc = w32_midiOutGetRDN(num); } MidiOutWindows::~MidiOutWindows() { //w32_midiOutClean(); // TODO } void MidiOutWindows::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/) { devidx = w32_midiOutOpen(name.c_str()); if (devidx == unsigned(-1)) { throw PlugException("Failed to open " + name); } } void MidiOutWindows::unplugHelper(EmuTime::param /*time*/) { if (devidx != unsigned(-1)) { w32_midiOutClose(devidx); devidx = unsigned(-1); } } const string& MidiOutWindows::getName() const { return name; } string_ref MidiOutWindows::getDescription() const { return desc; } void MidiOutWindows::recvByte(byte value, EmuTime::param /*time*/) { if (devidx != unsigned(-1)) { w32_midiOutPut(value, devidx); } } template void MidiOutWindows::serialize(Archive& /*ar*/, unsigned /*version*/) { // don't restore this after loadstate } INSTANTIATE_SERIALIZE_METHODS(MidiOutWindows); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, MidiOutWindows, "MidiOutWindows"); } // namespace openmsx #endif // defined(_WIN32) openMSX-RELEASE_0_12_0/src/serial/MidiOutWindows.hh000066400000000000000000000016031257557151200217530ustar00rootroot00000000000000#ifndef MIDIOUTWINDOWS_HH #define MIDIOUTWINDOWS_HH #if defined(_WIN32) #include "MidiOutDevice.hh" #include "serialize_meta.hh" namespace openmsx { class PluggingController; class MidiOutWindows final : public MidiOutDevice { public: static void registerAll(PluggingController& controller); explicit MidiOutWindows(unsigned num); ~MidiOutWindows(); // Pluggable void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; const std::string& getName() const override; string_ref getDescription() const override; // SerialDataInterface (part) void recvByte(byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: unsigned devidx; std::string name; std::string desc; }; } // namespace openmsx #endif // defined(_WIN32) #endif // MIDIOUTWINDOWS_HH openMSX-RELEASE_0_12_0/src/serial/Midi_w32.cc000066400000000000000000000254151257557151200204000ustar00rootroot00000000000000/* * Win32 MIDI utility routines for openMSX. * * Copyright (c) 2003 Reikan. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #ifdef _WIN32 #include "Midi_w32.hh" #include "MSXException.hh" #include "MemBuffer.hh" #include "StringOp.hh" #include "cstdiop.hh" #include #include #include #include #define MAXPATHLEN MAX_PATH using std::string; namespace openmsx { /* * MIDI I/O helper functions for Win32. */ // MIDI SYSTEM MESSAGES max length is not defined... // TODO: support variable length. #define OPENMSX_W32_MIDI_SYSMES_MAXLEN 4096 struct vfn_midi { unsigned idx; unsigned devid; HMIDI handle; char vfname[MAXPATHLEN + 1]; char devname[MAXPNAMELEN]; }; struct outbuf { DWORD shortmes; unsigned longmes_cnt; char longmes[OPENMSX_W32_MIDI_SYSMES_MAXLEN]; MIDIHDR header; }; static MemBuffer vfnt_midiout; static MemBuffer vfnt_midiin; static unsigned vfnt_midiout_num, vfnt_midiin_num; static MemBuffer state_out; static MemBuffer buf_out; static MIDIHDR inhdr; static char inlongmes[OPENMSX_W32_MIDI_SYSMES_MAXLEN]; static void w32_midiDevNameConv(char *dst, char *src) { size_t len = strlen(src); size_t i; for (i = 0; i < len; ++i) { if ((src[i] < '0') || (src[i] > 'z') || ((src[i] > '9') && (src[i] < 'A')) || ((src[i] > 'Z') && (src[i] < 'a'))) { dst[i] = '_'; } else { dst[i] = src[i]; } } dst[i] = '\0'; } // MIDI-OUT static int w32_midiOutFindDev(unsigned *idx, unsigned *dev, const char *vfn) { for (unsigned i = 0; i < vfnt_midiout_num; ++i) { if (!strcmp(vfnt_midiout[i].vfname, vfn)) { *idx = i; *dev = vfnt_midiout[i].devid; return 0; } } return -1; } int w32_midiOutInit() { vfnt_midiout_num = 0; unsigned num = midiOutGetNumDevs(); if (!num) return 0; state_out.resize(num + 1); memset(state_out.data(), 0, (num + 1) * sizeof(int)); buf_out.resize(num + 1); memset(buf_out.data(), 0, (num + 1) * sizeof(outbuf)); vfnt_midiout.resize(num + 1); // MIDI_MAPPER is #define's as ((UINT)-1) UINT OPENMSX_MIDI_MAPPER = static_cast(-1); MIDIOUTCAPSA cap; if (midiOutGetDevCapsA(OPENMSX_MIDI_MAPPER, &cap, sizeof(cap)) != MMSYSERR_NOERROR) { return 2; } vfnt_midiout[0].devid = OPENMSX_MIDI_MAPPER; w32_midiDevNameConv(vfnt_midiout[0].devname, cap.szPname); strncpy(vfnt_midiout[0].vfname, "midi-out", MAXPATHLEN + 1); vfnt_midiout_num ++; for (unsigned i = 0; i < num; ++i) { if (midiOutGetDevCapsA(i, &cap, sizeof(cap)) != MMSYSERR_NOERROR) { return 0; // atleast MIDI-MAPPER is available... } vfnt_midiout[i + 1].devid = i; w32_midiDevNameConv(vfnt_midiout[i + 1].devname, cap.szPname); snprintf(vfnt_midiout[i + 1].vfname, MAXPATHLEN + 1, "midi-out-%u", i); vfnt_midiout_num++; } return 0; } void w32_midiOutClean() { vfnt_midiout_num = 0; } unsigned w32_midiOutGetVFNsNum() { return vfnt_midiout_num; } string w32_midiOutGetVFN(unsigned nmb) { assert(nmb < vfnt_midiout_num); return vfnt_midiout[nmb].vfname; } string w32_midiOutGetRDN(unsigned nmb) { assert(nmb < vfnt_midiout_num); return vfnt_midiout[nmb].devname; } unsigned w32_midiOutOpen(const char *vfn) { unsigned idx, devid; if (w32_midiOutFindDev(&idx, &devid,vfn)) { return unsigned(-1); } if (midiOutOpen(reinterpret_cast(&vfnt_midiout[idx].handle), devid, 0, 0 ,0) != MMSYSERR_NOERROR) { return unsigned(-1); } return idx; } int w32_midiOutClose(unsigned idx) { midiOutReset(reinterpret_cast(vfnt_midiout[idx].handle)); if (midiOutClose(reinterpret_cast(vfnt_midiout[idx].handle)) == MMSYSERR_NOERROR) { return 0; } else { return -1; } } static int w32_midiOutFlushExclusiveMsg(unsigned idx) { int i; buf_out[idx].header.lpData = buf_out[idx].longmes; buf_out[idx].header.dwBufferLength = buf_out[idx].longmes_cnt; buf_out[idx].header.dwFlags = 0; if ((i = midiOutPrepareHeader(reinterpret_cast(vfnt_midiout[idx].handle), &buf_out[idx].header, sizeof(buf_out[idx].header))) != MMSYSERR_NOERROR) { throw FatalError(StringOp::Builder() << "midiOutPrepareHeader() returned " << i); } if ((i = midiOutLongMsg(reinterpret_cast(vfnt_midiout[idx].handle), &buf_out[idx].header, sizeof(buf_out[idx].header))) != MMSYSERR_NOERROR) { throw FatalError(StringOp::Builder() << "midiOutLongMsg() returned " << i); } // Wait sending in driver. // This may take long... while (!(buf_out[idx].header.dwFlags & MHDR_DONE)) { Sleep(1); } // Sending Exclusive done. if ((i = midiOutUnprepareHeader(reinterpret_cast(vfnt_midiout[idx].handle), &buf_out[idx].header, sizeof(buf_out[idx].header))) != MMSYSERR_NOERROR) { throw FatalError(StringOp::Builder() << "midiOutUnPrepareHeader() returned " << i); } buf_out[idx].longmes_cnt = 0; return 0; } int w32_midiOutPut(unsigned char value, unsigned idx) { if ((state_out[idx] & 0x1000) || ((value & 0x0ff) == 0x0f0)) { if (!(state_out[idx] & 0x1000)) { // SYSTEM MESSAGE Exclusive start state_out[idx] |= 0x1000; } if (buf_out[idx].longmes_cnt >= OPENMSX_W32_MIDI_SYSMES_MAXLEN) { return -1; } buf_out[idx].longmes[buf_out[idx].longmes_cnt++] = value; if (value == 0x0f7) { // SYSTEM MESSAGES Exclusive end w32_midiOutFlushExclusiveMsg(idx); state_out[idx] &= ~0x1000; } } else { switch (state_out[idx]) { case 0x0000: switch (value & 0x0f0) { case 0x080: // Note Off case 0x090: // Note On case 0x0a0: // Key Pressure case 0x0b0: // Control Change case 0x0e0: // Pitch Wheel state_out[idx] = 0x0082; buf_out[idx].shortmes = DWORD(value) & 0x0ff; break; case 0x0c0: // Program Change case 0x0d0: // After Touch state_out[idx] = 0x0041; buf_out[idx].shortmes = DWORD(value) & 0x0ff; break; case 0x0f0: // SYSTEM MESSAGE (other than "EXCLUSIVE") switch (value &0x0f) { case 0x02: // Song Position Pointer state_out[idx] = 0x0082; buf_out[idx].shortmes = DWORD(value) & 0x0ff; break; case 0x01: // Time Code case 0x03: // Song Select state_out[idx] = 0x0041; buf_out[idx].shortmes = DWORD(value) & 0x0ff; break; default: // Timing Clock, Sequencer Start, Sequencer Continue, // Sequencer Stop, Cable Check, System Reset, and Unknown... state_out[idx] = 0; buf_out[idx].shortmes = DWORD(value) & 0x0ff; midiOutShortMsg(reinterpret_cast(vfnt_midiout[idx].handle), buf_out[idx].shortmes); break; } break; default: state_out[idx] = 0; buf_out[idx].shortmes = DWORD(value) & 0x0ff; midiOutShortMsg(reinterpret_cast(vfnt_midiout[idx].handle), buf_out[idx].shortmes); break; } break; case 0x0041: buf_out[idx].shortmes |= (DWORD(value) & 0x0ff) << 8; midiOutShortMsg(reinterpret_cast(vfnt_midiout[idx].handle), buf_out[idx].shortmes); state_out[idx] = 0; break; case 0x0082: buf_out[idx].shortmes |= (DWORD(value) & 0x0ff) << 8; state_out[idx] = 0x0081; break; case 0x0081: buf_out[idx].shortmes |= (DWORD(value) & 0x0ff) << 16; midiOutShortMsg(reinterpret_cast(vfnt_midiout[idx].handle), buf_out[idx].shortmes); state_out[idx] = 0; break; default: // not reach... midiOutShortMsg(reinterpret_cast(vfnt_midiout[idx].handle), DWORD(value) & 0x0ff); break; } } return 0; } // MIDI-IN static int w32_midiInFindDev(unsigned *idx, unsigned *dev, const char *vfn) { for (unsigned i = 0; i < vfnt_midiin_num; ++i) { if (!strcmp(vfnt_midiin[i].vfname, vfn)) { *idx = i; *dev = vfnt_midiin[i].devid; return 0; } } return -1; } int w32_midiInInit() { vfnt_midiin_num = 0; unsigned num = midiInGetNumDevs(); if (!num) return 0; vfnt_midiin.resize(num + 1); for (unsigned i = 0; i < num; ++i) { MIDIINCAPSA cap; if (midiInGetDevCapsA(i, &cap, sizeof(cap)) != MMSYSERR_NOERROR) { return 1; } vfnt_midiin[i].devid = i; w32_midiDevNameConv(vfnt_midiin[i].devname, cap.szPname); snprintf(vfnt_midiin[i].vfname, MAXPATHLEN + 1, "midi-in-%u", i); vfnt_midiin_num++; } return 0; } void w32_midiInClean() { vfnt_midiin_num = 0; } unsigned w32_midiInGetVFNsNum() { return vfnt_midiin_num; } string w32_midiInGetVFN(unsigned nmb) { assert(nmb < vfnt_midiin_num); return vfnt_midiin[nmb].vfname; } string w32_midiInGetRDN(unsigned nmb) { assert(nmb < vfnt_midiin_num); return vfnt_midiin[nmb].devname; } unsigned w32_midiInOpen(const char *vfn, DWORD thrdid) { unsigned idx, devid; if (w32_midiInFindDev(&idx, &devid, vfn)) { return unsigned(-1); } if (midiInOpen(reinterpret_cast(&vfnt_midiin[idx].handle), devid, thrdid, 0, CALLBACK_THREAD) != MMSYSERR_NOERROR) { return unsigned(-1); } memset(&inhdr, 0, sizeof(inhdr)); inhdr.lpData = inlongmes; inhdr.dwBufferLength = OPENMSX_W32_MIDI_SYSMES_MAXLEN; if (midiInPrepareHeader(reinterpret_cast(vfnt_midiin[idx].handle), static_cast(&inhdr), sizeof(inhdr)) != MMSYSERR_NOERROR) { return unsigned(-1); } if (midiInAddBuffer(reinterpret_cast(vfnt_midiin[idx].handle), static_cast(&inhdr), sizeof(inhdr)) != MMSYSERR_NOERROR) { return unsigned(-1); } return idx; } int w32_midiInClose(unsigned idx) { midiInStop(reinterpret_cast(vfnt_midiin[idx].handle)); midiInReset(reinterpret_cast(vfnt_midiin[idx].handle)); midiInUnprepareHeader(reinterpret_cast(vfnt_midiin[idx].handle), static_cast(&inhdr), sizeof(inhdr)); if (midiInClose(reinterpret_cast(vfnt_midiin[idx].handle)) == MMSYSERR_NOERROR) { return 0; } else { return -1; } } } // namespace openmsx #endif // _WIN32 openMSX-RELEASE_0_12_0/src/serial/Midi_w32.hh000066400000000000000000000041541257557151200204070ustar00rootroot00000000000000/* * Win32 MIDI utility routines for openMSX. * * Copyright (c) 2003 Reikan. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #ifndef MIDI_W32_HH #define MIDI_W32_HH #ifdef _WIN32 #include #include #include #define MAXPATHLEN MAX_PATH namespace openmsx { int w32_midiOutInit(); void w32_midiOutClean(); unsigned w32_midiOutGetVFNsNum(); std::string w32_midiOutGetVFN(unsigned nmb); std::string w32_midiOutGetRDN(unsigned nmb); unsigned w32_midiOutOpen(const char* vfn); int w32_midiOutClose(unsigned idx); int w32_midiOutPut(unsigned char value, unsigned idx); int w32_midiInInit(); void w32_midiInClean(); unsigned w32_midiInGetVFNsNum(); std::string w32_midiInGetVFN(unsigned nmb); std::string w32_midiInGetRDN(unsigned nmb); unsigned w32_midiInOpen(const char* vfn, DWORD thrdid); int w32_midiInClose(unsigned idx); } // namespace openmsx #endif // _WIN32 #endif // MIDI_W32_HH openMSX-RELEASE_0_12_0/src/serial/RS232Connector.cc000066400000000000000000000016251257557151200214460ustar00rootroot00000000000000#include "RS232Connector.hh" #include "RS232Device.hh" #include "DummyRS232Device.hh" #include "checked_cast.hh" #include "serialize.hh" #include "memory.hh" namespace openmsx { RS232Connector::RS232Connector(PluggingController& pluggingController, string_ref name) : Connector(pluggingController, name, make_unique()) { } RS232Connector::~RS232Connector() { } const std::string RS232Connector::getDescription() const { return "Serial RS232 connector"; } string_ref RS232Connector::getClass() const { return "RS232"; } RS232Device& RS232Connector::getPluggedRS232Dev() const { return *checked_cast(&getPlugged()); } template void RS232Connector::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); } INSTANTIATE_SERIALIZE_METHODS(RS232Connector); } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/RS232Connector.hh000066400000000000000000000017771257557151200214700ustar00rootroot00000000000000#ifndef RS232CONNECTOR_HH #define RS232CONNECTOR_HH #include "Connector.hh" #include "SerialDataInterface.hh" #include "serialize_meta.hh" namespace openmsx { class RS232Device; class RS232Connector : public Connector, public SerialDataInterface { public: RS232Device& getPluggedRS232Dev() const; // Connector const std::string getDescription() const final override; string_ref getClass() const final override; // input (SerialDataInterface) void setDataBits(DataBits bits) override = 0; void setStopBits(StopBits bits) override = 0; void setParityBit(bool enable, ParityBit parity) override = 0; void recvByte(byte value, EmuTime::param time) override = 0; virtual bool ready() = 0; virtual bool acceptsData() = 0; template void serialize(Archive& ar, unsigned version); protected: RS232Connector(PluggingController& pluggingController, string_ref name); ~RS232Connector(); }; REGISTER_BASE_CLASS(RS232Connector, "rs232connector"); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serial/RS232Device.cc000066400000000000000000000012661257557151200207140ustar00rootroot00000000000000#include "RS232Device.hh" namespace openmsx { string_ref RS232Device::getClass() const { return "RS232"; } void RS232Device::setDataBits(DataBits /*bits*/) { // ignore } void RS232Device::setStopBits(StopBits /*bits*/) { // ignore } void RS232Device::setParityBit(bool /*enable*/, ParityBit /*parity*/) { // ignore } bool RS232Device::getCTS(EmuTime::param /*time*/) const { return true; // TODO check } bool RS232Device::getDSR(EmuTime::param /*time*/) const { return true; // TODO check } void RS232Device::setDTR(bool /*status*/, EmuTime::param /*time*/) { // ignore } void RS232Device::setRTS(bool /*status*/, EmuTime::param /*time*/) { // ignore } } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/RS232Device.hh000066400000000000000000000014031257557151200207170ustar00rootroot00000000000000#ifndef RS232DEVICE_HH #define RS232DEVICE_HH #include "Pluggable.hh" #include "SerialDataInterface.hh" namespace openmsx { class RS232Device : public Pluggable, public SerialDataInterface { public: // Pluggable (part) string_ref getClass() const final override; // input virtual void signal(EmuTime::param time) = 0; // SerialDataInterface (part) (output) void setDataBits(DataBits bits) override; void setStopBits(StopBits bits) override; void setParityBit(bool enable, ParityBit parity) override; // control virtual bool getCTS(EmuTime::param time) const; virtual bool getDSR(EmuTime::param time) const; virtual void setDTR(bool status, EmuTime::param time); virtual void setRTS(bool status, EmuTime::param time); }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serial/RS232Tester.cc000066400000000000000000000076541257557151200207720ustar00rootroot00000000000000#include "RS232Tester.hh" #include "RS232Connector.hh" #include "PlugException.hh" #include "EventDistributor.hh" #include "Scheduler.hh" #include "FileOperations.hh" #include "serialize.hh" namespace openmsx { RS232Tester::RS232Tester(EventDistributor& eventDistributor_, Scheduler& scheduler_, CommandController& commandController) : eventDistributor(eventDistributor_), scheduler(scheduler_) , thread(this) , rs232InputFilenameSetting( commandController, "rs232-inputfilename", "filename of the file where the RS232 input is read from", "rs232-input") , rs232OutputFilenameSetting( commandController, "rs232-outputfilename", "filename of the file where the RS232 output is written to", "rs232-output") { eventDistributor.registerEventListener(OPENMSX_RS232_TESTER_EVENT, *this); } RS232Tester::~RS232Tester() { eventDistributor.unregisterEventListener(OPENMSX_RS232_TESTER_EVENT, *this); } // Pluggable void RS232Tester::plugHelper(Connector& connector_, EmuTime::param /*time*/) { // output string_ref outName = rs232OutputFilenameSetting.getString(); FileOperations::openofstream(outFile, outName.str()); if (outFile.fail()) { outFile.clear(); throw PlugException("Error opening output file: " + outName); } // input string_ref inName = rs232InputFilenameSetting.getString(); inFile = FileOperations::openFile(inName.str(), "rb"); if (!inFile) { outFile.close(); throw PlugException("Error opening input file: " + inName); } auto& rs232Connector = static_cast(connector_); rs232Connector.setDataBits(SerialDataInterface::DATA_8); // 8 data bits rs232Connector.setStopBits(SerialDataInterface::STOP_1); // 1 stop bit rs232Connector.setParityBit(false, SerialDataInterface::EVEN); // no parity setConnector(&connector_); // base class will do this in a moment, // but thread already needs it thread.start(); } void RS232Tester::unplugHelper(EmuTime::param /*time*/) { // output outFile.close(); // input std::lock_guard lock(mutex); thread.stop(); inFile.reset(); } const std::string& RS232Tester::getName() const { static const std::string name("rs232-tester"); return name; } string_ref RS232Tester::getDescription() const { return "RS232 tester pluggable. Reads all data from file specified " "with the 'rs-232-inputfilename' setting. Writes all data " "to the file specified with the 'rs232-outputfilename' " "setting."; } // Runnable void RS232Tester::run() { byte buf; if (!inFile) return; while (!feof(inFile.get())) { size_t num = fread(&buf, 1, 1, inFile.get()); if (num != 1) { continue; } assert(isPluggedIn()); std::lock_guard lock(mutex); queue.push_back(buf); eventDistributor.distributeEvent( std::make_shared(OPENMSX_RS232_TESTER_EVENT)); } } // input void RS232Tester::signal(EmuTime::param time) { auto connector = static_cast(getConnector()); if (!connector->acceptsData()) { std::lock_guard lock(mutex); queue.clear(); return; } if (!connector->ready()) return; std::lock_guard lock(mutex); if (queue.empty()) return; connector->recvByte(queue.pop_front(), time); } // EventListener int RS232Tester::signalEvent(const std::shared_ptr& /*event*/) { if (isPluggedIn()) { signal(scheduler.getCurrentTime()); } else { std::lock_guard lock(mutex); queue.clear(); } return 0; } // output void RS232Tester::recvByte(byte value, EmuTime::param /*time*/) { if (outFile.is_open()) { outFile.put(value); outFile.flush(); } } template void RS232Tester::serialize(Archive& /*ar*/, unsigned /*version*/) { // don't try to resume a previous logfile (see PrinterPortLogger) } INSTANTIATE_SERIALIZE_METHODS(RS232Tester); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, RS232Tester, "RS232Tester"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/RS232Tester.hh000066400000000000000000000027601257557151200207750ustar00rootroot00000000000000#ifndef RS232TESTER_HH #define RS232TESTER_HH #include "RS232Device.hh" #include "Thread.hh" #include "EventListener.hh" #include "FilenameSetting.hh" #include "FileOperations.hh" #include "openmsx.hh" #include "circular_buffer.hh" #include #include #include namespace openmsx { class EventDistributor; class Scheduler; class CommandController; class RS232Tester final : public RS232Device, private Runnable , private EventListener { public: RS232Tester(EventDistributor& eventDistributor, Scheduler& scheduler, CommandController& commandController); ~RS232Tester(); // Pluggable void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; const std::string& getName() const override; string_ref getDescription() const override; // input void signal(EmuTime::param time) override; // output void recvByte(byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: // Runnable void run() override; // EventListener int signalEvent(const std::shared_ptr& event) override; EventDistributor& eventDistributor; Scheduler& scheduler; Thread thread; FileOperations::FILE_t inFile; cb_queue queue; std::mutex mutex; // to protect queue std::ofstream outFile; FilenameSetting rs232InputFilenameSetting; FilenameSetting rs232OutputFilenameSetting; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serial/SerialDataInterface.hh000066400000000000000000000012021257557151200226530ustar00rootroot00000000000000#ifndef SERIALDATAINTERFACE_HH #define SERIALDATAINTERFACE_HH #include "EmuTime.hh" #include "openmsx.hh" namespace openmsx { class SerialDataInterface { public: enum DataBits { DATA_5 = 5, DATA_6 = 6, DATA_7 = 7, DATA_8 = 8 }; enum StopBits { STOP_INV = 0, STOP_1 = 2, STOP_15 = 3, STOP_2 = 4 }; enum ParityBit { EVEN = 0, ODD = 1 }; virtual void setDataBits(DataBits bits) = 0; virtual void setStopBits(StopBits bits) = 0; virtual void setParityBit(bool enable, ParityBit parity) = 0; virtual void recvByte(byte value, EmuTime::param time) = 0; protected: ~SerialDataInterface() {} }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serial/YM2148.cc000066400000000000000000000151231257557151200176620ustar00rootroot00000000000000// implementation based on: // http://map.grauw.nl/resources/midi/ym2148.php #include "YM2148.hh" #include "MidiInDevice.hh" #include "MSXMotherBoard.hh" #include "serialize.hh" namespace openmsx { // status register flags static const unsigned STAT_TXRDY = 0x01; // Transmitter ready: no MIDI-out send is in progress static const unsigned STAT_RXRDY = 0x02; // Receiver ready: a MIDI-in byte is available for the MSX static const unsigned STAT_OE = 0x10; // Overrun error (incoming data) static const unsigned STAT_FE = 0x20; // Framing error (incoming data) // command register bits static const unsigned CMD_TXEN = 0x01; // Transmit enable static const unsigned CMD_TXIE = 0x02; // TxRDY interrupt enable static const unsigned CMD_RXEN = 0x04; // Receive enable static const unsigned CMD_RXIE = 0x08; // RxRDY interrupt enable static const unsigned CMD_ER = 0x10; // Error Reset static const unsigned CMD_IR = 0x80; // Internal Reset // The meaning of bits 5 and 6 are unknown (they are used by the CX5M // software). Some documentation *guesses* they are related to IM2 // IRQ handling. static const EmuDuration BIT_DURATION = EmuDuration::hz(31250); static const EmuDuration CHAR_DURATION = BIT_DURATION * 10; // 1 start-bit, 8 data-bits, 1 stop-bit YM2148::YM2148(const std::string& name, MSXMotherBoard& motherBoard) : MidiInConnector(motherBoard.getPluggingController(), name + "-MIDI-in") , syncRecv (motherBoard.getScheduler()) , syncTrans(motherBoard.getScheduler()) , rxIRQ(motherBoard, name + "-rx-IRQ") , txIRQ(motherBoard, name + "-tx-IRQ") , txBuffer1(0), txBuffer2(0) // avoid UMR , outConnector(motherBoard.getPluggingController(), name + "-MIDI-out") { reset(); } void YM2148::reset() { syncRecv .removeSyncPoint(); syncTrans.removeSyncPoint(); rxIRQ.reset(); txIRQ.reset(); rxReady = false; rxBuffer = 0; status = 0; commandReg = 0; } // MidiInConnector sends a new character. void YM2148::recvByte(byte value, EmuTime::param time) { assert(acceptsData() && ready()); if (status & STAT_RXRDY) { // So, there is a byte that has to be read by the MSX still! // This happens when the MSX program doesn't // respond fast enough to an earlier received byte. status |= STAT_OE; // TODO investigate: overwrite rxBuffer in case of overrun? } else { rxBuffer = value; status |= STAT_RXRDY; if (commandReg & CMD_RXIE) rxIRQ.set(); } // Not ready now, but we will be in a while rxReady = false; syncRecv.setSyncPoint(time + CHAR_DURATION); } // Triggered when we're ready to receive the next character. void YM2148::execRecv(EmuTime::param time) { assert(commandReg & CMD_RXEN); assert(!rxReady); rxReady = true; getPluggedMidiInDev().signal(time); // trigger (possible) send of next char } // MidiInDevice querries whether it can send a new character 'now'. bool YM2148::ready() { return rxReady; } // MidiInDevice querries whether it can send characters at all. bool YM2148::acceptsData() { return (commandReg & CMD_RXEN) != 0; } // MidiInDevice informs us about the format of the data it will send // (MIDI is always 1 start-bit, 8 data-bits, 1 stop-bit, no parity-bits). void YM2148::setDataBits(DataBits /*bits*/) { // ignore } void YM2148::setStopBits(StopBits /*bits*/) { // ignore } void YM2148::setParityBit(bool /*enable*/, ParityBit /*parity*/) { // ignore } // MSX program reads the status register. byte YM2148::readStatus(EmuTime::param /*time*/) { return status; } byte YM2148::peekStatus(EmuTime::param /*time*/) const { return status; } // MSX programs reads the data register. byte YM2148::readData(EmuTime::param /*time*/) { status &= ~STAT_RXRDY; rxIRQ.reset(); // no need to check CMD_RXIE return rxBuffer; } byte YM2148::peekData(EmuTime::param /*time*/) const { return rxBuffer; } // MSX program writes the command register. void YM2148::writeCommand(byte value) { if (value & CMD_IR) { reset(); return; // do not process any other commands } if (value & CMD_ER) { status &= ~(STAT_OE | STAT_FE); return; } byte diff = commandReg ^ value; commandReg = value; if (diff & CMD_RXEN) { if (commandReg & CMD_RXEN) { // disabled -> enabled rxReady = true; } else { // enabled -> disabled rxReady = false; syncRecv.removeSyncPoint(); status &= ~STAT_RXRDY; // IRQ is handled below } } if (diff & CMD_TXEN) { if (commandReg & CMD_TXEN) { // disabled -> enabled status |= STAT_TXRDY; // IRQ is handled below // TODO transmitter is ready at this point, does this immediately trigger an IRQ (when IRQs are enabled)? } else { // enabled -> disabled status &= ~STAT_TXRDY; // IRQ handled below syncTrans.removeSyncPoint(); } } // update IRQ status rxIRQ.set((value & CMD_RXIE) && (status & STAT_RXRDY)); txIRQ.set((value & CMD_TXIE) && (status & STAT_TXRDY)); } // MSX program writes the data register. void YM2148::writeData(byte value, EmuTime::param time) { if (!(commandReg & CMD_TXEN)) return; if (syncTrans.pendingSyncPoint()) { // We're still sending the previous character, only buffer // this one. Don't accept any further characters. txBuffer2 = value; status &= ~STAT_TXRDY; txIRQ.reset(); } else { // Immediately start sending this character. We're still // ready to accept a next character. send(value, time); } } // Start sending a character. It takes a while before it's finished sending. void YM2148::send(byte value, EmuTime::param time) { txBuffer1 = value; syncTrans.setSyncPoint(time + CHAR_DURATION); } // Triggered when a character has finished sending. void YM2148::execTrans(EmuTime::param time) { assert(commandReg & CMD_TXEN); outConnector.recvByte(txBuffer1, time); if (status & STAT_TXRDY) { // No next character to send. } else { // There already is a next character, start sending that now // and accept a next one. status |= STAT_TXRDY; if (commandReg & CMD_TXIE) txIRQ.set(); send(txBuffer2, time); } } // Any pending IRQs? bool YM2148::pendingIRQ() const { return rxIRQ.getState() || txIRQ.getState(); } template void YM2148::serialize(Archive& ar, unsigned version) { if (ar.versionAtLeast(version, 2)) { ar.template serializeBase(*this); ar.serialize("outConnector", outConnector); ar.serialize("syncRecv", syncRecv); ar.serialize("syncTrans", syncTrans); ar.serialize("rxIRQ", rxIRQ); ar.serialize("txIRQ", txIRQ); ar.serialize("rxReady", rxReady); ar.serialize("rxBuffer", rxBuffer); ar.serialize("txBuffer1", txBuffer1); ar.serialize("txBuffer2", txBuffer2); ar.serialize("status", status); ar.serialize("commandReg", commandReg); } } INSTANTIATE_SERIALIZE_METHODS(YM2148); } // namespace openmsx openMSX-RELEASE_0_12_0/src/serial/YM2148.hh000066400000000000000000000037041257557151200176760ustar00rootroot00000000000000#ifndef YM2148_HH #define YM2148_HH #include "IRQHelper.hh" #include "MidiInConnector.hh" #include "MidiOutConnector.hh" #include "Schedulable.hh" #include "openmsx.hh" #include "outer.hh" class MSXMotherBoard; class Scheduler; namespace openmsx { class YM2148 : public MidiInConnector { public: YM2148(const std::string& name, MSXMotherBoard& motherBoard); void reset(); void writeCommand(byte value); void writeData(byte value, EmuTime::param time); byte readStatus(EmuTime::param time); byte readData(EmuTime::param time); byte peekStatus(EmuTime::param time) const; byte peekData(EmuTime::param time) const; bool pendingIRQ() const; template void serialize(Archive& ar, unsigned version); private: // MidiInConnector bool ready() override; bool acceptsData() override; void setDataBits(DataBits bits) override; void setStopBits(StopBits bits) override; void setParityBit(bool enable, ParityBit parity) override; void recvByte(byte value, EmuTime::param time) override; // Schedulable struct SyncRecv : Schedulable { friend class YM2148; SyncRecv(Scheduler& s) : Schedulable(s) {} void executeUntil(EmuTime::param time) override { auto& ym2148 = OUTER(YM2148, syncRecv); ym2148.execRecv(time); } } syncRecv; struct SyncTrans : Schedulable { friend class YM2148; SyncTrans(Scheduler& s) : Schedulable(s) {} void executeUntil(EmuTime::param time) override { auto& ym2148 = OUTER(YM2148, syncTrans); ym2148.execTrans(time); } } syncTrans; void execRecv (EmuTime::param time); void execTrans(EmuTime::param time); void send(byte value, EmuTime::param time); IRQHelper rxIRQ; IRQHelper txIRQ; bool rxReady; byte rxBuffer; // #include using std::string; namespace openmsx { template void ArchiveBase::attribute(const char* name, const char* value) { string valueStr(value); self().attribute(name, valueStr); } template class ArchiveBase; template class ArchiveBase; //// OutputArchiveBase2::OutputArchiveBase2() : lastId(0) { } unsigned OutputArchiveBase2::generateID1(const void* p) { #ifdef linux assert("Can't serialize ID of object located on the stack" && !addressOnStack(p)); #endif ++lastId; assert(polyIdMap.find(p) == end(polyIdMap)); polyIdMap[p] = lastId; return lastId; } unsigned OutputArchiveBase2::generateID2( const void* p, const std::type_info& typeInfo) { #ifdef linux assert("Can't serialize ID of object located on the stack" && !addressOnStack(p)); #endif ++lastId; auto key = std::make_pair(p, std::type_index(typeInfo)); assert(idMap.find(key) == end(idMap)); idMap[key] = lastId; return lastId; } unsigned OutputArchiveBase2::getID1(const void* p) { auto it = polyIdMap.find(p); return it != end(polyIdMap) ? it->second : 0; } unsigned OutputArchiveBase2::getID2( const void* p, const std::type_info& typeInfo) { auto it = idMap.find({p, std::type_index(typeInfo)}); return it != end(idMap) ? it->second : 0; } template void OutputArchiveBase::serialize_blob( const char* tag, const void* data, size_t len) { string encoding; string tmp; if (false) { // useful for debugging encoding = "hex"; tmp = HexDump::encode(data, len); } else if (false) { encoding = "base64"; tmp = Base64::encode(data, len); } else { encoding = "gz-base64"; // TODO check for overflow? auto dstLen = uLongf(len + len / 1000 + 12 + 1); // worst-case MemBuffer buf(dstLen); if (compress2(buf.data(), &dstLen, reinterpret_cast(data), uLong(len), 9) != Z_OK) { throw MSXException("Error while compressing blob."); } tmp = Base64::encode(buf.data(), dstLen); } this->self().beginTag(tag); this->self().attribute("encoding", encoding); Saver saver; saver(this->self(), tmp, false); this->self().endTag(tag); } template class OutputArchiveBase; template class OutputArchiveBase; //// void* InputArchiveBase2::getPointer(unsigned id) { auto it = idMap.find(id); return it != end(idMap) ? it->second : nullptr; } void InputArchiveBase2::addPointer(unsigned id, const void* p) { assert(idMap.find(id) == end(idMap)); idMap[id] = const_cast(p); } template void InputArchiveBase::serialize_blob( const char* tag, void* data, size_t len) { this->self().beginTag(tag); string encoding; this->self().attribute("encoding", encoding); string tmp; Loader loader; loader(this->self(), tmp, std::make_tuple(), -1); this->self().endTag(tag); if (encoding == "gz-base64") { tmp = Base64::decode(tmp); auto dstLen = uLongf(len); // TODO check for overflow? if ((uncompress(reinterpret_cast(data), &dstLen, reinterpret_cast(tmp.data()), uLong(tmp.size())) != Z_OK) || (dstLen != len)) { throw MSXException("Error while decompressing blob."); } } else if ((encoding == "hex") || (encoding == "base64")) { if (encoding == "hex") { tmp = HexDump::decode(tmp); } else { tmp = Base64::decode(tmp); } if (tmp.size() != len) { throw XMLException(StringOp::Builder() << "Length of decoded blob: " << tmp.size() << " not identical to expected value: " << len); } memcpy(data, tmp.data(), len); } else { throw XMLException("Unsupported encoding \"" + encoding + "\" for blob"); } } template class InputArchiveBase; template class InputArchiveBase; //// void MemOutputArchive::save(const std::string& s) { auto size = s.size(); byte* buf = buffer.allocate(sizeof(size) + size); memcpy(buf, &size, sizeof(size)); memcpy(buf + sizeof(size), s.data(), size); } MemBuffer MemOutputArchive::releaseBuffer(size_t& size) { return buffer.release(size); } //// void MemInputArchive::load(std::string& s) { size_t length; load(length); s.resize(length); if (length) { get(&s[0], length); } } //// // Too small inputs don't compress very well (often the compressed size is even // bigger than the input). It also takes a relatively long time (because snappy // has a relatively large setup time). I choose this value semi-arbitrary. I // only made it >= 52 so that the (incompressible) RP5C01 registers won't be // compressed. static const size_t SMALL_SIZE = 100; void MemOutputArchive::serialize_blob(const char*, const void* data, size_t len) { // Compress in-memory blobs: // // This is a bit slower than memcpy, but it uses a lot less memory. // Memory usage is important for the reverse feature, where we keep a // lot of savestates in memory. // // I compared 'gzip level=1' (fastest version with lowest compression // ratio) with 'lzo'. lzo was considerably faster. Compression ratio // was about the same (maybe lzo was slightly better (OTOH on higher // levels gzip compresses better)). So I decided to go with lzo. // // Later I compared 'lzo' with 'snappy', lzo compresses 6-25% better, // but 'snappy' is about twice as fast. So I switched to 'snappy'. if (len >= SMALL_SIZE) { size_t dstLen = snappy::maxCompressedLength(len); byte* buf = buffer.allocate(sizeof(dstLen) + dstLen); snappy::compress(static_cast(data), len, reinterpret_cast(&buf[sizeof(dstLen)]), dstLen); memcpy(buf, &dstLen, sizeof(dstLen)); // fill-in actual size buffer.deallocate(&buf[sizeof(dstLen) + dstLen]); // dealloc unused portion } else { byte* buf = buffer.allocate(len); memcpy(buf, data, len); } } void MemInputArchive::serialize_blob(const char*, void* data, size_t len) { if (len >= SMALL_SIZE) { size_t srcLen; load(srcLen); snappy::uncompress(reinterpret_cast(buffer.getCurrentPos()), srcLen, reinterpret_cast(data), len); buffer.skip(srcLen); } else { memcpy(data, buffer.getCurrentPos(), len); buffer.skip(len); } } //// XmlOutputArchive::XmlOutputArchive(const string& filename) : root("serial") { root.addAttribute("openmsx_version", Version::full()); root.addAttribute("date_time", Date::toString(time(nullptr))); root.addAttribute("platform", TARGET_PLATFORM); auto f = FileOperations::openFile(filename, "wb"); if (!f) { throw XMLException("Could not open compressed file \"" + filename + "\""); } file = gzdopen(fileno(f.get()), "wb9"); if (!file) { throw XMLException("Could not open compressed file \"" + filename + "\""); } f.release(); current.push_back(&root); } XmlOutputArchive::~XmlOutputArchive() { assert(current.back() == &root); const char* header = "\n" "\n"; gzwrite(file, const_cast(header), unsigned(strlen(header))); string dump = root.dump(); gzwrite(file, const_cast(dump.data()), unsigned(dump.size())); gzclose(file); } void XmlOutputArchive::saveChar(char c) { save(string(1, c)); } void XmlOutputArchive::save(const string& str) { assert(!current.empty()); assert(current.back()->getData().empty()); current.back()->setData(str); } void XmlOutputArchive::save(bool b) { assert(!current.empty()); assert(current.back()->getData().empty()); current.back()->setData(b ? "true" : "false"); } void XmlOutputArchive::save(unsigned char b) { save(unsigned(b)); } void XmlOutputArchive::save(signed char c) { save(int(c)); } void XmlOutputArchive::save(char c) { save(int(c)); } void XmlOutputArchive::save(int i) { saveImpl(i); } void XmlOutputArchive::save(unsigned u) { saveImpl(u); } void XmlOutputArchive::save(unsigned long long ull) { saveImpl(ull); } void XmlOutputArchive::attribute(const char* name, const string& str) { assert(!current.empty()); assert(!current.back()->hasAttribute(name)); current.back()->addAttribute(name, str); } void XmlOutputArchive::attribute(const char* name, int i) { attributeImpl(name, i); } void XmlOutputArchive::attribute(const char* name, unsigned u) { attributeImpl(name, u); } void XmlOutputArchive::beginTag(const char* tag) { assert(!current.empty()); auto& elem = current.back()->addChild(tag); current.push_back(&elem); } void XmlOutputArchive::endTag(const char* tag) { assert(!current.empty()); assert(current.back()->getName() == tag); (void)tag; current.pop_back(); } //// XmlInputArchive::XmlInputArchive(const string& filename) : elem(XMLLoader::load(filename, "openmsx-serialize.dtd")) { elems.emplace_back(&elem, 0); } void XmlInputArchive::loadChar(char& c) { std::string str; load(str); std::istringstream is(str); is >> c; } void XmlInputArchive::load(string& t) { if (!elems.back().first->getChildren().empty()) { throw XMLException("No child tags expected for string types"); } t = elems.back().first->getData(); } void XmlInputArchive::load(bool& b) { if (!elems.back().first->getChildren().empty()) { throw XMLException("No child tags expected for boolean types"); } const auto& s = elems.back().first->getData(); if ((s == "true") || (s == "1")) { b = true; } else if ((s == "false") || (s == "0")) { b = false; } else { throw XMLException("Bad value found for boolean: " + s); } } void XmlInputArchive::load(unsigned char& b) { unsigned i; load(i); b = i; } void XmlInputArchive::load(signed char& c) { int i; load(i); c = i; } void XmlInputArchive::load(char& c) { int i; load(i); c = i; } // This function parses a number from a string. It's similar to the generic // templatized XmlInputArchive::load() method, but _much_ faster. It does // have some limitations though: // - it can't handle leading whitespace // - it can't handle extra characters at the end of the string // - it can only handle one base (only decimal, not octal or hexadecimal) // - it doesn't understand a leading '+' sign // - it doesn't detect overflow or underflow (The generic implementation sets // a 'bad' flag on the stream and clips the result to the min/max allowed // value. Though this 'bad' flag was ignored by the openMSX code). // This routine is only used to parse strings we've written ourselves (and the // savestate/replay XML files are not meant to be manually edited). So the // above limitations don't really matter. And we can use the speed gain. template struct ConditionalNegate; template<> struct ConditionalNegate { template void operator()(bool negate, T& t) { if (negate) t = -t; // ok to negate a signed type } }; template<> struct ConditionalNegate { template void operator()(bool negate, T& /*t*/) { assert(!negate); (void)negate; // can't negate unsigned type } }; template static inline void fastAtoi(const string& str, T& t) { t = 0; bool neg = false; size_t i = 0; size_t l = str.size(); static const bool IS_SIGNED = std::numeric_limits::is_signed; if (IS_SIGNED) { if (l == 0) return; if (str[0] == '-') { neg = true; i = 1; } } for (/**/; i < l; ++i) { unsigned d = str[i] - '0'; if (unlikely(d > 9)) { throw XMLException("Invalid integer: " + str); } t = 10 * t + d; } // The following stuff does the equivalent of: // if (neg) t = -t; // Though this expression triggers a warning on VC++ when T is an // unsigned type. This complex template stuff avoids the warning. ConditionalNegate negateFunctor; negateFunctor(neg, t); } void XmlInputArchive::load(int& i) { std::string str; load(str); fastAtoi(str, i); } void XmlInputArchive::load(unsigned& u) { std::string str; load(str); fastAtoi(str, u); } void XmlInputArchive::load(unsigned long long& ull) { std::string str; load(str); fastAtoi(str, ull); } void XmlInputArchive::beginTag(const char* tag) { auto* child = elems.back().first->findNextChild( tag, elems.back().second); if (!child) { string path; for (auto& e : elems) { path += e.first->getName() + '/'; } throw XMLException(StringOp::Builder() << "No child tag \"" << tag << "\" found at location \"" << path << '\"'); } elems.emplace_back(child, 0); } void XmlInputArchive::endTag(const char* tag) { const auto& elem = *elems.back().first; if (elem.getName() != tag) { throw XMLException("End tag \"" + elem.getName() + "\" not equal to begin tag \"" + tag + "\""); } auto& elem2 = const_cast(elem); elem2.clearName(); // mark this elem for later beginTag() calls elems.pop_back(); } void XmlInputArchive::attribute(const char* name, string& t) { try { t = elems.back().first->getAttribute(name); } catch (ConfigException& ex) { throw XMLException(ex.getMessage()); } } void XmlInputArchive::attribute(const char* name, int& i) { attributeImpl(name, i); } void XmlInputArchive::attribute(const char* name, unsigned& u) { attributeImpl(name, u); } bool XmlInputArchive::hasAttribute(const char* name) { return elems.back().first->hasAttribute(name); } bool XmlInputArchive::findAttribute(const char* name, unsigned& value) { return elems.back().first->findAttributeInt(name, value); } int XmlInputArchive::countChildren() const { return int(elems.back().first->getChildren().size()); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/serialize.hh000066400000000000000000000613421257557151200175440ustar00rootroot00000000000000#ifndef SERIALIZE_HH #define SERIALIZE_HH #include "serialize_core.hh" #include "SerializeBuffer.hh" #include "XMLElement.hh" #include "MemBuffer.hh" #include "StringOp.hh" #include "inline.hh" #include "unreachable.hh" #include #include #include #include #include #include #include #include #include namespace openmsx { template struct SerializeClassVersion; // In this section, the archive classes are defined. // // Archives can be categorized in two ways: // - backing stream they use // - input or output (each backing stream has exactly one input and one // output variant) // // ATM these backing streams implemented: // - Mem // Stores stream in memory. Is meant to be very compact and very fast. // It does not support versioning (it's not possible to load this stream // in a newer version of openMSX). It is also not platform independent // (e.g. integers are stored using native platform endianess). // The main use case for this archive format is regular in memory // snapshots, for example to support replay/rewind. // - XML // Stores the stream in a XML file. These files are meant to be portable // to different architectures (e.g. little/big endian, 32/64 bit system). // There is version information in the stream, so it should be possible // to load streams created with older openMSX versions a newer openMSX. // The XML files are meant to be human readable. Having editable XML files // is not a design goal (e.g. simply changing a value will probably work, // but swapping the position of two tag or adding or removing tags can // easily break the stream). // - Text // This stores to stream in a flat ascii file (one item per line). This // format is only written as a proof-of-concept to test the design. It's // not meant to be used in practice. // // Archive code is heavily templatized. It uses the CRTP (curiously recuring // template pattern ; a base class templatized on it's derived class). To // implement static polymorphism. This means there is practically no run-time // overhead of using this mechansim compared to 6 seperatly handcoded functions // (Mem/XML/Text x input/output). // TODO At least in theory, still need to verify this in practice. // Though my experience is that gcc is generally very good in this. template class ArchiveBase { public: /** Is this archive a loader or a saver. bool isLoader() const;*/ /** Serialize the base class of this classtype. * Should preferably be called as the first statement in the * implementation of a serialize() method of a class type. * See also serializeInlinedBase() below. */ template void serializeBase(T& t) { const char* tag = BaseClassName::getName(); self().serialize(tag, static_cast(t)); } /** Serialize the base class of this classtype. * Should preferably be called as the first statement in the * implementation of a serialize() method of a class type. * See also serializeBase() above. * * The differece between serializeBase() and serializeInlinedBase() * is only relevant for versioned archives (see needVersion(), e.g. * XML archives). In XML archives serializeBase() will put the base * class in a new subtag, serializeInlinedBase() puts the members * of the base class (inline) in the current tag. The advantage * of serializeBase() is that the base class can be versioned * seperatly from the subclass. The disadvantage is that it exposes * an internal implementation detail in the XML file, and thus makes * it harder to for example change the class hierarchy or move * members from base to subclass or vice-versa. */ template void serializeInlinedBase(T& t, unsigned version) { ::openmsx::serialize(self(), static_cast(t), version); } // Each concrete archive class also has the following methods: // Because of the implementation with static polymorphism, this // interface is not explictly visible in the base class. // // // template void serializeWithID(const char* tag, const T& t, ...) // // This is _the_most_important_ method of the serialization // framework. Depending on the concrete archive type (loader/saver) // this method will load or save the given type 't'. In case of an XML // archive the 'tag' parameter will be used as tagname. // // At the end there are still a number of optional parameters (in the // current implementation there can be between 0 and 3, but can be // extened when needed). These are 'global' constructor parameters, // constructor parameters that are not stored in the stream, but that // are needed to reconstruct the object (for example can be references // to structures that were already stored in the stream). So these // parameters are only actually used while loading. // TODO document this in more detail in some section where the // (polymorphic) constructors are also described. // // // void serialize_blob(const char* tag, const void* data, size_t len) // // Serialize the given data as a binary blob. // This cannot be part of the serialize() method above because we // cannot know whether a byte-array should be serialized as a blob // or as a collection of bytes (IOW we cannot decide it based on the // type). // // // template void serialize(const char* tag, const T& t) // // This is much like the serializeWithID() method above, but it doesn't // store an ID with this element. This means that it's not possible, // later on in the stream, to refer to this element. For many elements // you know this will not happen. This method results in a slightly // more compact stream. // // Note that for primitive types we already don't store an ID, because // pointers to primitive types are not supported (at least not ATM). // // // template void serializePointerID(const char* tag, const T& t) // // Serialize a pointer by storing the ID of the object it points to. // This only works if the object was already serialized. The only // reason to use this method instead of the more general serialize() // method is that this one does not instantiate the object // construction code. (So in some cases you can avoid having to // provide specializations of SerializeConstructorArgs.) // // // template void serializePolymorphic(const char* tag, const T& t) // // Serialize a value-type whose concrete type is not yet known at // compile-time (polymorphic pointers are already handled by the // generic serialize() method). // // The difference between pointer and value-types is that for // pointers, the de-serialize code also needs to construct the // object, while for value-types, the object (with the correct // concrete type) is already constructed, it only needs to be // initialized. // // // bool versionAtLeast(unsigned actual, unsigned required) const // bool versionBelow (unsigned actual, unsigned required) const // // Checks whether the actual version is respective 'bigger or equal' // or 'strictly lower' than the required version. So in fact these are // equivalent to respectively: // return actual >= required; // return actual < required; // Note that these two methods are the exact opposite of each other. // Though for memory-archives and output-archives we know that the // actual version is always equal to the latest class version and the // required version can never be bigger than this latest version, so // in these cases the methods can be optimized to respectively: // return true; // return false; // By using these methods instead of direct comparisons, the compiler // is able to perform more dead-code-elimination. /*internal*/ // These must be public for technical reasons, but they should only // be used by the serialization framework. /** Does this archive store version information. */ bool needVersion() const { return true; } /** Does this archive store enums as strings. * See also struct serialize_as_enum. */ bool translateEnumToString() const { return false; } /** Load/store an attribute from/in the archive. * Depending on the underlying concrete stream, attributes are either * stored like XML attributes or as regular values. Because of this * (and thus unlike XML attributes) the order of attributes matter. It * also matters whether an attribute is present or not. */ template void attribute(const char* name, T& t) { self().serialize(name, t); } void attribute(const char* name, const char* value); /** Some archives (like XML archives) can store optional attributes. * This method indicates whether that's the case or not. * This can be used to for example in XML files don't store attributes * with default values (thus to make the XML look prettier). */ bool canHaveOptionalAttributes() const { return false; } /** Check the presence of a (optional) attribute. * It's only allowed to call this method on archives that can have * optional attributes. */ bool hasAttribute(const char* /*name*/) { UNREACHABLE; return false; } /** Optimization: combination of hasAttribute() and getAttribute(). * Returns true if hasAttribute() and (if so) also fills in the value. */ bool findAttribute(const char* /*name*/, unsigned& /*value*/) { UNREACHABLE; return false; } /** Some archives (like XML archives) can count the number of subtags * that belong to the current tag. This method indicates whether that's * the case for this archive or not. * This can for example be used to make the XML files look prettier in * case of serialization of collections: in that case we don't need to * explictly store the size of the collection, it can be derived from * the number of subtags. */ bool canCountChildren() const { return false; } /** Count the number of child tags. * It's only allowed to call this method on archives that have support * for this operation. */ int countChildren() const { UNREACHABLE; return 0; } /** Indicate begin of a tag. * Only XML archives use this, other archives ignore it. * XML saver uses it as a name for the current tag, it doesn't * interpret the name in any way. * XML loader uses it only as a check: it checks whether the current * tag name matches the given name. So we will NOT search the tag * with the given name, the tags have to be in the correct order. */ void beginTag(const char* /*tag*/) { // nothing } /** Indicate begin of a tag. * Only XML archives use this, other archives ignore it. * Both XML loader and saver only use the given tag name to do some * internal checks (with checks disabled, the tag parameter has no * influence at all on loading or saving of the stream). */ void endTag(const char* /*tag*/) { // nothing } // These (internal) methods should be implemented in the concrete // archive classes. // // template void save(const T& t) // // Should only be implemented for OuputArchives. Is called to // store primitive types in the stream. In the end all structures // are broken down to primitive types, so all data that ends up // in the stream passes via this method (ok, depending on how // attribute() and serialize_blob() is implemented, that data may // not pass via save()). // // Often this method will be overloaded to handle certain types in a // specific way. // // // template void load(T& t) // // Should only be implemented for InputArchives. This is similar (but // opposite) to the save() method above. Loading of primitive types // is done via this method. // void beginSection() // void endSection() // void skipSection(bool skip) // The methods beginSection() and endSection() can only be used in // output archives. These mark the location of a section that can // later be skipped during loading. // The method skipSection() can only be used in input archives. It // optionally skips a section that was marked during saving. // For every beginSection() call in the output, there must be a // corresponding skipSection() call in the input (even if you don't // actually want to skip the section). protected: /** Returns a reference to the most derived class. * Helper function to implement static polymorphism. */ inline Derived& self() { return static_cast(*this); } }; // The part of OutputArchiveBase that doesn't depend on the template parameter class OutputArchiveBase2 { public: inline bool isLoader() const { return false; } inline bool versionAtLeast(unsigned /*actual*/, unsigned /*required*/) const { return true; } inline bool versionBelow(unsigned /*actual*/, unsigned /*required*/) const { return false; } void skipSection(bool /*skip*/) { UNREACHABLE; } /*internal*/ #ifdef linux // This routine is not portable, for example it breaks in // windows (mingw) because there the location of the stack // is _below_ the heap. // But this is anyway only used to check assertions. So for now // only do that in linux. static NEVER_INLINE bool addressOnStack(const void* p) { // This is not portable, it assumes: // - stack grows downwards // - heap is at lower address than stack // Also in c++ comparison between pointers is only defined when // the two pointers point to objects in the same array. int dummy; return &dummy < p; } #endif // Generate a new ID for the given pointer and store this association // for later (see getId()). template unsigned generateId(const T* p) { // For composed structures, for example // struct A { ... }; // struct B { A a; ... }; // The pointer to the outer and inner structure can be the // same while we still want a different ID to refer to these // two. That's why we use a std::pair as key // in the map. // For polymorphic types you do sometimes use a base pointer // to refer to a subtype. So there we only use the pointer // value as key in the map. if (std::is_polymorphic::value) { return generateID1(p); } else { return generateID2(p, typeid(T)); } } template unsigned getId(const T* p) { if (std::is_polymorphic::value) { return getID1(p); } else { return getID2(p, typeid(T)); } } protected: OutputArchiveBase2(); private: unsigned generateID1(const void* p); unsigned generateID2(const void* p, const std::type_info& typeInfo); unsigned getID1(const void* p); unsigned getID2(const void* p, const std::type_info& typeInfo); std::map, unsigned> idMap; std::map polyIdMap; unsigned lastId; }; template class OutputArchiveBase : public ArchiveBase, public OutputArchiveBase2 { public: template void serializeInlinedBase(T& t, unsigned version) { // same implementation as base class, but with extra check static_assert(SerializeClassVersion::value == SerializeClassVersion::value, "base and derived must have same version when " "using serializeInlinedBase()"); ArchiveBase::template serializeInlinedBase(t, version); } // Main saver method. Heavy lifting is done in the Saver class. // The 'global constructor arguments' parameters are ignored because // the saver archives also completely ignore those extra parameters. // But we need to provide them because the same (templatized) code path // is used both for saving and loading. template void serializeWithID(const char* tag, const T& t, Args... /*globalConstrArgs*/) { this->self().beginTag(tag); Saver saver; saver(this->self(), t, true); this->self().endTag(tag); } // Default implementation is to base64-encode the blob and serialize // the resulting string. But memory archives will memcpy the blob. void serialize_blob(const char* tag, const void* data, size_t len); template void serialize(const char* tag, const T& t) { this->self().beginTag(tag); Saver saver; saver(this->self(), t, false); this->self().endTag(tag); } template void serializePointerID(const char* tag, const T& t) { this->self().beginTag(tag); IDSaver saver; saver(this->self(), t); this->self().endTag(tag); } template void serializePolymorphic(const char* tag, const T& t) { static_assert(std::is_polymorphic::value, "must be a polymorphic type"); PolymorphicSaverRegistry::save(tag, this->self(), t); } // You shouldn't use this, it only exists for backwards compatibility void serializeChar(const char* tag, char c) { this->self().beginTag(tag); this->self().saveChar(c); this->self().endTag(tag); } protected: OutputArchiveBase() {} }; // Part of InputArchiveBase that doesn't depend on the template parameter class InputArchiveBase2 { public: inline bool isLoader() const { return true; } void beginSection() { UNREACHABLE; } void endSection() { UNREACHABLE; } /*internal*/ void* getPointer(unsigned id); void addPointer(unsigned id, const void* p); template void resetSharedPtr(std::shared_ptr& s, T* r) { if (!r) { s.reset(); return; } auto it = sharedPtrMap.find(r); if (it == end(sharedPtrMap)) { s.reset(r); sharedPtrMap[r] = s; } else { s = std::static_pointer_cast(it->second); } } protected: InputArchiveBase2() {} private: std::map idMap; std::map> sharedPtrMap; }; template class InputArchiveBase : public ArchiveBase, public InputArchiveBase2 { public: template void serializeWithID(const char* tag, T& t, Args... args) { doSerialize(tag, t, std::tuple(args...)); } void serialize_blob(const char* tag, void* data, size_t len); template void serialize(const char* tag, T& t) { this->self().beginTag(tag); using TNC = typename std::remove_const::type; auto& tnc = const_cast(t); Loader loader; loader(this->self(), tnc, std::make_tuple(), -1); // don't load id this->self().endTag(tag); } template void serializePointerID(const char* tag, const T& t) { this->self().beginTag(tag); using TNC = typename std::remove_const::type; auto& tnc = const_cast(t); IDLoader loader; loader(this->self(), tnc); this->self().endTag(tag); } template void serializePolymorphic(const char* tag, T& t) { static_assert(std::is_polymorphic::value, "must be a polymorphic type"); PolymorphicInitializerRegistry::init(tag, this->self(), &t); } // You shouldn't use this, it only exists for backwards compatibility void serializeChar(const char* tag, char& c) { this->self().beginTag(tag); this->self().loadChar(c); this->self().endTag(tag); } /*internal*/ // Actual loader method. Heavy lifting is done in the Loader class. template void doSerialize(const char* tag, T& t, TUPLE args, int id = 0) { this->self().beginTag(tag); using TNC = typename std::remove_const::type; auto& tnc = const_cast(t); Loader loader; loader(this->self(), tnc, args, id); this->self().endTag(tag); } protected: InputArchiveBase() {} }; class MemOutputArchive final : public OutputArchiveBase { public: MemOutputArchive() { } ~MemOutputArchive() { assert(openSections.empty()); } bool needVersion() const { return false; } template void save(const T& t) { put(&t, sizeof(t)); } inline void saveChar(char c) { save(c); } void save(const std::string& s); void serialize_blob(const char*, const void* data, size_t len); void beginSection() { size_t skip = 0; // filled in later save(skip); size_t beginPos = buffer.getPosition(); openSections.push_back(beginPos); } void endSection() { assert(!openSections.empty()); size_t endPos = buffer.getPosition(); size_t beginPos = openSections.back(); openSections.pop_back(); size_t skip = endPos - beginPos; buffer.insertAt(beginPos - sizeof(skip), &skip, sizeof(skip)); } MemBuffer releaseBuffer(size_t& size); private: void put(const void* data, size_t len) { if (len) { buffer.insert(data, len); } } OutputBuffer buffer; std::vector openSections; }; class MemInputArchive final : public InputArchiveBase { public: MemInputArchive(const byte* data, size_t size) : buffer(data, size) { } bool needVersion() const { return false; } inline bool versionAtLeast(unsigned /*actual*/, unsigned /*required*/) const { return true; } inline bool versionBelow(unsigned /*actual*/, unsigned /*required*/) const { return false; } template void load(T& t) { get(&t, sizeof(t)); } inline void loadChar(char& c) { load(c); } void load(std::string& s); void serialize_blob(const char*, void* data, size_t len); void skipSection(bool skip) { size_t num; load(num); if (skip) { buffer.skip(num); } } private: void get(void* data, size_t len) { if (len) { buffer.read(data, len); } } InputBuffer buffer; }; //// class XmlOutputArchive final : public OutputArchiveBase { public: XmlOutputArchive(const std::string& filename); ~XmlOutputArchive(); template void saveImpl(const T& t) { // TODO make sure floating point is printed with enough digits // maybe print as hex? save(StringOp::toString(t)); } template void save(const T& t) { saveImpl(t); } void saveChar(char c); void save(const std::string& str); void save(bool b); void save(unsigned char b); void save(signed char c); void save(char c); void save(int i); // these 3 are not strictly needed void save(unsigned u); // but having them non-inline void save(unsigned long long ull); // saves quite a bit of code void beginSection() { /*nothing*/ } void endSection() { /*nothing*/ } //internal: inline bool translateEnumToString() const { return true; } inline bool canHaveOptionalAttributes() const { return true; } inline bool canCountChildren() const { return true; } void beginTag(const char* tag); void endTag(const char* tag); template void attributeImpl(const char* name, const T& t) { attribute(name, StringOp::toString(t)); } template void attribute(const char* name, const T& t) { attributeImpl(name, t); } void attribute(const char* name, const std::string& str); void attribute(const char* name, int i); void attribute(const char* name, unsigned u); private: gzFile file; XMLElement root; std::vector current; }; class XmlInputArchive final : public InputArchiveBase { public: XmlInputArchive(const std::string& filename); inline bool versionAtLeast(unsigned actual, unsigned required) const { return actual >= required; } inline bool versionBelow(unsigned actual, unsigned required) const { return actual < required; } template void load(T& t) { std::string str; load(str); std::istringstream is(str); is >> t; } void loadChar(char& c); void load(std::string& t); void load(bool& b); void load(unsigned char& b); void load(signed char& c); void load(char& c); void load(int& i); // these 3 are not strictly needed void load(unsigned& u); // but having them non-inline void load(unsigned long long& ull); // saves quite a bit of code void skipSection(bool /*skip*/) { /*nothing*/ } //internal: inline bool translateEnumToString() const { return true; } inline bool canHaveOptionalAttributes() const { return true; } inline bool canCountChildren() const { return true; } void beginTag(const char* tag); void endTag(const char* tag); template void attributeImpl(const char* name, T& t) { std::string str; attribute(name, str); std::istringstream is(str); is >> t; } template void attribute(const char* name, T& t) { attributeImpl(name, t); } void attribute(const char* name, std::string& t); void attribute(const char* name, int& i); void attribute(const char* name, unsigned& u); bool hasAttribute(const char* name); bool findAttribute(const char* name, unsigned& value); int countChildren() const; private: XMLElement elem; std::vector> elems; }; #define INSTANTIATE_SERIALIZE_METHODS(CLASS) \ template void CLASS::serialize(MemInputArchive&, unsigned); \ template void CLASS::serialize(MemOutputArchive&, unsigned); \ template void CLASS::serialize(XmlInputArchive&, unsigned); \ template void CLASS::serialize(XmlOutputArchive&, unsigned); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serialize_constr.hh000066400000000000000000000027201257557151200211270ustar00rootroot00000000000000#ifndef SERIALIZE_CONSTR_HH #define SERIALIZE_CONSTR_HH #include namespace openmsx { /** Serialize (local) constructor arguments. * * Some classes don't have a default constructor. To be able to create new * instances of such classes, we need to invoke the constructor with some * parameters (see also Creator utility class above). * * SerializeConstructorArgs can be specialized for classes that need such extra * constructor arguments. * * This only stores the 'local' constructor arguments, this means the arguments * that are specific per instance. There can also be 'global' args, these are * known in the context where the class is being loaded (so these don't need to * be stored in the archive). See below for more details on global constr args. * * The serialize_as_enum class has the following members: * using type = tuple<...> * Tuple that holds the result of load() (see below) * void save(Archive& ar, const T& t) * This method should store the constructor args in the given archive * type load(Archive& ar, unsigned version) * This method should load the args from the given archive and return * them in a tuple. */ template struct SerializeConstructorArgs { using type = std::tuple<>; template void save(Archive& /*ar*/, const T& /*t*/) { } template type load(Archive& /*ar*/, unsigned /*version*/) { return std::make_tuple(); } }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serialize_core.cc000066400000000000000000000025611257557151200205400ustar00rootroot00000000000000#include "serialize_core.hh" #include "serialize.hh" #include "MSXException.hh" #include "StringOp.hh" #include "likely.hh" namespace openmsx { void enumError(const std::string& str) { throw MSXException("Invalid enum value: " + str); } void pointerError(unsigned id) { throw MSXException(StringOp::Builder() << "Couldn't find pointer in archive with id " << id); } static void versionError(const char* className, unsigned latestVersion, unsigned version) { // note: the result of type_info::name() is implementation defined // but should be ok to show in an error message throw MSXException(StringOp::Builder() << "your openMSX installation is too old (state contains type '" << className << "' with version " << version << ", while this openMSX installation only supports up to version " << latestVersion << ")."); } unsigned loadVersionHelper(MemInputArchive& /*ar*/, const char* /*className*/, unsigned /*latestVersion*/) { UNREACHABLE; return 0; } unsigned loadVersionHelper(XmlInputArchive& ar, const char* className, unsigned latestVersion) { assert(ar.canHaveOptionalAttributes()); unsigned version; if (!ar.findAttribute("version", version)) { return 1; } if (unlikely(version > latestVersion)) { versionError(className, latestVersion, version); } return version; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/serialize_core.hh000066400000000000000000000554761257557151200205670ustar00rootroot00000000000000#ifndef SERIALIZE_CORE_HH #define SERIALIZE_CORE_HH #include "serialize_constr.hh" #include "serialize_meta.hh" #include "unreachable.hh" #include #include #include #include namespace openmsx { // Type-queries for serialization framework // is_primitive template struct is_primitive : std::false_type {}; template<> struct is_primitive : std::true_type {}; template<> struct is_primitive : std::true_type {}; template<> struct is_primitive : std::true_type {}; template<> struct is_primitive : std::true_type {}; template<> struct is_primitive : std::true_type {}; template<> struct is_primitive : std::true_type {}; template<> struct is_primitive : std::true_type {}; template<> struct is_primitive : std::true_type {}; template<> struct is_primitive : std::true_type {}; template<> struct is_primitive : std::true_type {}; template<> struct is_primitive : std::true_type {}; template<> struct is_primitive : std::true_type {}; template<> struct is_primitive : std::true_type {}; template<> struct is_primitive : std::true_type {}; template<> struct is_primitive : std::true_type {}; template<> struct is_primitive : std::true_type {}; // Normally to make a class serializable, you have to implement a serialize() // method on the class. For some classes we cannot extend the source code. So // we need an alternative, non-intrusive, way to make those classes // serializable. template void serialize(Archive& ar, T& t, unsigned version) { // By default use the serialize() member. But this function can // be overloaded to serialize classes in a non-intrusive way. t.serialize(ar, version); } template void serialize(Archive& ar, std::pair& p, unsigned /*version*/) { ar.serialize("first", p.first); ar.serialize("second", p.second); } template struct SerializeClassVersion> { static const unsigned value = 0; }; /////////// /** serialize_as_enum * * For serialization of enums to work you have to specialize the * serialize_as_enum struct for that specific enum. This has a double purpose: * - let the framework know this type should be traited as an enum * - define a mapping between enum values and string representations * * The serialize_as_enum class has the following members: * - static bool value * True iff this type must be serialized as an enum. * The fields below are only valid (even only present) if this variable * is true. * - std::string toString(T t) * convert enum value to string * - T fromString(const std::string& str) * convert from string back to enum value * * If the enum has all consecutive values, starting from zero (as is the case * if you don't explicity mention the numeric values in the enum definition), * you can use the SERIALIZE_ENUM macro as a convenient way to define a * specialization of serialize_as_enum: * example: * enum MyEnum { FOO, BAR }; * enum_string myEnumInfo[] = { * { "FOO", FOO }, * { "BAR", BAR }, * { nullptr, FOO } // dummy enum value * }; * SERIALIZE_ENUM(MyEnum, myEnumInfo); * * Note: when an enum definition evolves it has impact on the serialization, * more specific on de-serialization of older version of the enum: adding * values or changing the numerical value is no problem. But be careful with * removing a value or changing the string representation. */ template struct serialize_as_enum : std::false_type {}; template struct enum_string { const char* str; T e; }; void enumError(const std::string& str); template struct serialize_as_enum_impl : std::true_type { serialize_as_enum_impl(enum_string* info_) : info(info_) {} std::string toString(T t) const { auto* p = info; while (p->str) { if (p->e == t) return p->str; ++p; } UNREACHABLE; return ""; } T fromString(const std::string& str) const { auto* p = info; while (p->str) { if (p->str == str) return p->e; ++p; } enumError(str); // does not return UNREACHABLE; return T(); // avoid warning } private: enum_string* info; }; #define SERIALIZE_ENUM(TYPE,INFO) \ template<> struct serialize_as_enum< TYPE > : serialize_as_enum_impl< TYPE > { \ serialize_as_enum() : serialize_as_enum_impl< TYPE >( INFO ) {} \ }; ///////////// // serialize_as_pointer // // Type-trait class that indicates whether a certain type should be serialized // as a pointer or not. There can be multiple pointers to the same object, // only the first such pointer is actually stored, the others are stored as // a reference to this first object. // // By default all pointer types are treated as pointer, but also smart pointer // can be traited as such. Though only unique_ptr is implemented ATM. // // The serialize_as_pointer class has the following members: // - static bool value // True iff this type must be serialized as a pointer. // The fields below are only valid (even only present) if this variable // is true. // - using type = T // The pointed-to type // - T* getPointer(X x) // Get an actual pointer from the abstract pointer x // (X can be a smart pointer type) // - void setPointer(X x, T* p, Archive& ar) // Copy the raw-pointer p to the abstract pointer x // The archive can be used to store per-archive data, this is for example // needed for shared_ptr. template struct serialize_as_pointer : std::false_type {}; template struct serialize_as_pointer_impl : std::true_type { // pointer to primitive types not supported static_assert(!is_primitive::value, "can't serialize ptr to primitive type"); using type = T; }; template struct serialize_as_pointer : serialize_as_pointer_impl { static inline T* getPointer(T* t) { return t; } template static inline void setPointer(T*& t, T* p, Archive& /*ar*/) { t = p; } }; template struct serialize_as_pointer> : serialize_as_pointer_impl { static inline T* getPointer(const std::unique_ptr& t) { return t.get(); } template static inline void setPointer(std::unique_ptr& t, T* p, Archive& /*ar*/) { t.reset(p); } }; template struct serialize_as_pointer> : serialize_as_pointer_impl { static T* getPointer(const std::shared_ptr& t) { return t.get(); } template static void setPointer(std::shared_ptr& t, T* p, Archive& ar) { ar.resetSharedPtr(t, p); } }; /////////// // serialize_as_collection // // Type-trait class that indicates whether a certain type should be serialized // as a collection or not. The serialization code 'knows' how to serialize // collections, so as a user of the serializer you only need to list the // collection to have it serialized (you don't have to iterate over it // manually). // // By default arrays, std::vector, std::list, std::set, std::deque and std::map // are recognized as collections. Though for STL collections you need to add // #include "serialize_stl.hh" // // The serialize_as_collection class has the following members: // // - static bool value // True iff this type must be serialized as a collection. // The fields below are only valid (even only present) if this variable // is true. // - int size // The size of the collection, -1 for variable sized collections. // Fixed sized collections can be serialized slightly more efficient // becuase we don't need to explicitly store the size. // - using value_type = ... // The type stored in the collection (only homogeneous collections are // supported). // - bool loadInPlace // Indicates whether we can directly load the elements in the correct // position in the container, otherwise there will be an extra assignment. // For this to be possible, the output iterator must support a dereference // operator that returns a 'regular' value_type. // - using const_iterator = ... // - const_iterator begin(...) // - const_iterator end(...) // Returns begin/end iterator for the given collection. Used for saving. // - using output_iterator = ... // - void prepare(..., int n) // - output_iterator output(...) // These are used for loading. The prepare() method should prepare the // collection to receive 'n' elements. The output() method returns an // output_iterator to the beginning of the collection. template struct serialize_as_collection : std::false_type {}; template struct serialize_as_collection : std::true_type { static const int size = N; // fixed size using value_type = T; // save using const_iterator = const T*; static const T* begin(const T (&array)[N]) { return &array[0]; } static const T* end (const T (&array)[N]) { return &array[N]; } // load static const bool loadInPlace = true; using output_iterator = T*; static void prepare(T (&/*array*/)[N], int /*n*/) { } static T* output(T (&array)[N]) { return &array[0]; } }; /////////// // Implementation of the different save-strategies. // // ATM we have // - PrimitiveSaver // Saves primitive values: int, bool, string, ... // Primitive values cannot be versioned. // - EnumSaver // Depending on the archive type, enums are either saved as strings (XML // archive) or as integers (memory archive). // This does not work automatically: it needs a specialization of // serialize_as_enum, see above. // - ClassSaver // From a serialization POV classes are a (heterogeneous) collection of // other to-be-serialized items. // Classes can have a version number, this allows to evolve the class // structure while still being able to load older versions (the load // method receive the version number as parameter, the user still needs // to keep the old loading code in place). // Optionally the name of the (concrete) class is saved in the stream. // This is used to support loading of polymorphic classes. // There is also an (optional) id saved in the stream. This used to // resolve possible multiple pointers to the same class. // - PointerSaver // Saves a pointer to a class (pointers to primitive types are not // supported). See also serialize_as_pointer // - IDSaver // Weaker version of PointerSaver: it can only save pointers to objects // that are already saved before (so it's will be saved by storing a // reference). To only reason to use IDSaver (iso PointerSaver) is that // it will not instantiate the object construction code. // - CollectionSaver // Saves a whole collection. See also serialize_as_collection // // All these strategies have a method: // template void operator()(Archive& ar, const T& t) // 'ar' is archive where the serialized stream will go // 't' is the to-be-saved object // 'saveId' Should ID be saved template struct PrimitiveSaver { template void operator()(Archive& ar, const T& t, bool /*saveId*/) { static_assert(is_primitive::value, "must be primitive type"); ar.save(t); } }; template struct EnumSaver { template void operator()(Archive& ar, const T& t, bool /*saveId*/) { if (ar.translateEnumToString()) { serialize_as_enum sae; std::string str = sae.toString(t); ar.save(str); } else { ar.save(int(t)); } } }; template struct ClassSaver { template void operator()( Archive& ar, const T& t, bool saveId, const char* type = nullptr, bool saveConstrArgs = false) { // Order is important (for non-xml archives). We use this order: // - id // - type // - version // - constructor args // Rational: // - 'id' must be first: it could be nullptr, in that // case the other fields are not even present. // - 'type' must be before version, because for some types we // might not need to store version (though it's unlikely) // - version must be before constructor args because the // constr args depend on the version if (saveId) { unsigned id = ar.generateId(&t); ar.attribute("id", id); } if (type) { ar.attribute("type", type); } unsigned version = SerializeClassVersion::value; if ((version != 0) && ar.needVersion()) { if (!ar.canHaveOptionalAttributes() || (version != 1)) { ar.attribute("version", version); } } if (saveConstrArgs) { // save local constructor args (if any) SerializeConstructorArgs constrArgs; constrArgs.save(ar, t); } using TNC = typename std::remove_const::type; auto& t2 = const_cast(t); serialize(ar, t2, version); } }; template struct PointerSaver { // note: we only support pointer to class template void operator()(Archive& ar, const TP& tp2, bool /*saveId*/) { static_assert(serialize_as_pointer::value, "must be serialized as pointer"); using T = typename serialize_as_pointer::type; const T* tp = serialize_as_pointer::getPointer(tp2); if (!tp) { unsigned id = 0; ar.attribute("id_ref", id); return; } if (unsigned id = ar.getId(tp)) { ar.attribute("id_ref", id); } else { if (std::is_polymorphic::value) { PolymorphicSaverRegistry::save(ar, tp); } else { ClassSaver saver; // don't store type // store id, constr-args saver(ar, *tp, true, nullptr, true); } } } }; template struct IDSaver { template void operator()(Archive& ar, const TP& tp2) { static_assert(serialize_as_pointer::value, "must be serialized as pointer"); auto tp = serialize_as_pointer::getPointer(tp2); unsigned id; if (!tp) { id = 0; } else { id = ar.getId(tp); assert(id); } ar.attribute("id_ref", id); } }; template struct CollectionSaver { template void operator()(Archive& ar, const TC& tc, bool saveId) { using sac = serialize_as_collection; static_assert(sac::value, "must be serialized as collection"); auto begin = sac::begin(tc); auto end = sac::end (tc); if ((sac::size < 0) && (!ar.canCountChildren())) { // variable size // e.g. in an XML archive the loader can look-ahead and // count the number of sub-tags, so no need to // explicitly store the size for such archives. int n = int(std::distance(begin, end)); ar.serialize("size", n); } for (/**/; begin != end; ++begin) { if (saveId) { ar.serializeWithID("item", *begin); } else { ar.serialize("item", *begin); } } } }; // Delegate to a specific Saver class // (implemented as inheriting from a specific baseclass). template struct Saver : if_, PrimitiveSaver, if_, EnumSaver, if_, PointerSaver, if_, CollectionSaver, ClassSaver>>>> {}; //// // Implementation of the different load-strategies. // // This matches very closly with the save-strategies above. // // All these strategies have a method: // template // void operator()(Archive& ar, const T& t, TUPLE args) // 'ar' Is archive where the serialized stream will go // 't' Is the object that has to be restored. // In case of a class (not a pointer to a class) the actual object // is already constructed, but it still needs to be filled in with // the correct data. // 'args' (Only used by PointerLoader) holds extra parameters used // to construct objects. // 'id' Used to skip loading an ID, see comment in ClassLoader template struct PrimitiveLoader { template void operator()(Archive& ar, T& t, TUPLE /*args*/, int /*id*/) { static_assert(std::tuple_size::value == 0, "can't have constructor arguments"); ar.load(t); } }; template struct EnumLoader { template void operator()(Archive& ar, T& t, TUPLE /*args*/, int /*id*/) { static_assert(std::tuple_size::value == 0, "can't have constructor arguments"); if (ar.translateEnumToString()) { std::string str; ar.load(str); serialize_as_enum sae; t = sae.fromString(str); } else { int i; ar.load(i); t = T(i); } } }; unsigned loadVersionHelper(MemInputArchive& ar, const char* className, unsigned latestVersion); unsigned loadVersionHelper(XmlInputArchive& ar, const char* className, unsigned latestVersion); template unsigned loadVersion(Archive& ar) { unsigned latestVersion = SerializeClassVersion::value; if ((latestVersion != 0) && ar.needVersion()) { return loadVersionHelper(ar, typeid(T).name(), latestVersion); } else { return latestVersion; } } template struct ClassLoader { template void operator()(Archive& ar, T& t, TUPLE /*args*/, int id = 0, int version = -1) { static_assert(std::tuple_size::value == 0, "can't have constructor arguments"); // id == -1: don't load id, don't addPointer // id == 0: load id from archive, addPointer // id == N: id already loaded, still addPointer if (id != -1) { if (id == 0) { ar.attribute("id", id); } ar.addPointer(id, &t); } // version == -1: load version // version == N: version already loaded if (version == -1) { version = loadVersion(ar); } using TNC = typename std::remove_const::type; auto& t2 = const_cast(t); serialize(ar, t2, version); } }; template struct NonPolymorphicPointerLoader { template T* operator()(Archive& ar, unsigned id, GlobalTuple globalArgs) { int version = loadVersion(ar); // load (local) constructor args (if any) using TNC = typename std::remove_const::type; using ConstrArgs = SerializeConstructorArgs; ConstrArgs constrArgs; auto localArgs = constrArgs.load(ar, version); // combine global and local constr args auto args = std::tuple_cat(globalArgs, localArgs); // TODO make combining global/local constr args configurable Creator creator; auto tp = creator(args); ClassLoader loader; loader(ar, *tp, std::make_tuple(), id, version); return tp.release(); } }; template struct PolymorphicPointerLoader { template T* operator()(Archive& ar, unsigned id, TUPLE args) { using ArgsType = typename PolymorphicConstructorArgs::type; static_assert(std::is_same::value, "constructor arguments types must match"); return static_cast( PolymorphicLoaderRegistry::load(ar, id, &args)); } }; template struct PointerLoader2 // extra indirection needed because inlining the body of // NonPolymorphicPointerLoader in PointerLoader does not compile // for abstract types : if_, PolymorphicPointerLoader, NonPolymorphicPointerLoader> {}; template struct PointerLoader { template void operator()(Archive& ar, TP& tp2, GlobalTuple globalArgs, int /*id*/) { static_assert(serialize_as_pointer::value, "must be serialized as a pointer"); // in XML archives we use 'id_ref' or 'id', in other archives // we don't care about the name unsigned id; if (ar.canHaveOptionalAttributes() && ar.findAttribute("id_ref", id)) { // nothing, 'id' already filled in } else { ar.attribute("id", id); } using T = typename serialize_as_pointer::type; T* tp; if (id == 0) { tp = nullptr; } else { if (void* p = ar.getPointer(id)) { tp = static_cast(p); } else { PointerLoader2 loader; tp = loader(ar, id, globalArgs); } } serialize_as_pointer::setPointer(tp2, tp, ar); } }; void pointerError(unsigned id); template struct IDLoader { template void operator()(Archive& ar, TP& tp2) { static_assert(serialize_as_pointer::value, "must be serialized as a pointer"); unsigned id; ar.attribute("id_ref", id); using T = typename serialize_as_pointer::type; T* tp; if (id == 0) { tp = nullptr; } else { void* p = ar.getPointer(id); if (!p) { pointerError(id); } tp = static_cast(p); } serialize_as_pointer::setPointer(tp2, tp, ar); } }; template struct CollectionLoaderHelper; template struct CollectionLoaderHelper { // used for array and vector template void operator()(Archive& ar, TUPLE args, OUT_ITER it, int id) { ar.doSerialize("item", *it, args, id); } }; template struct CollectionLoaderHelper { // We can't directly load the element in the correct position: // This screws-up id/pointer management because the element is still // copied after construction (and pointer value of initial object is // stored). template void operator()(Archive& ar, TUPLE args, OUT_ITER it, int id) { typename sac::value_type elem; ar.doSerialize("item", elem, args, id); *it = std::move(elem); } }; template struct CollectionLoader { template void operator()(Archive& ar, TC& tc, TUPLE args, int id = 0) { assert((id == 0) || (id == -1)); using sac = serialize_as_collection; static_assert(sac::value, "must be serialized as a collection"); int n = sac::size; if (n < 0) { // variable size if (ar.canCountChildren()) { n = ar.countChildren(); } else { ar.serialize("size", n); } } sac::prepare(tc, n); auto it = sac::output(tc); CollectionLoaderHelper loadOneElement; for (int i = 0; i < n; ++i, ++it) { loadOneElement(ar, args, it, id); } } }; template struct Loader : if_, PrimitiveLoader, if_, EnumLoader, if_, PointerLoader, if_, CollectionLoader, ClassLoader>>>> {}; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serialize_meta.cc000066400000000000000000000106171257557151200205370ustar00rootroot00000000000000#include "serialize_meta.hh" #include "serialize.hh" #include "MSXException.hh" #include "StringOp.hh" #include "stl.hh" #include #include #include namespace openmsx { template PolymorphicSaverRegistry::PolymorphicSaverRegistry() : initialized(false) { } template PolymorphicSaverRegistry::~PolymorphicSaverRegistry() { } template PolymorphicSaverRegistry& PolymorphicSaverRegistry::instance() { static PolymorphicSaverRegistry oneInstance; return oneInstance; } template void PolymorphicSaverRegistry::registerHelper( const std::type_info& type, std::unique_ptr> saver) { assert(!initialized); assert(none_of(begin(saverMap), end(saverMap), EqualTupleValue<0>(type))); saverMap.emplace_back(type, std::move(saver)); } template void PolymorphicSaverRegistry::save( Archive& ar, const void* t, const std::type_info& typeInfo) { auto& reg = PolymorphicSaverRegistry::instance(); if (unlikely(!reg.initialized)) { reg.initialized = true; sort(begin(reg.saverMap), end(reg.saverMap), LessTupleElement<0>()); } auto it = lower_bound(begin(reg.saverMap), end(reg.saverMap), typeInfo, LessTupleElement<0>()); if ((it == end(reg.saverMap)) || (it->first != typeInfo)) { std::cerr << "Trying to save an unregistered polymorphic type: " << typeInfo.name() << std::endl; assert(false); return; } it->second->save(ar, t); } template void PolymorphicSaverRegistry::save( const char* tag, Archive& ar, const void* t, const std::type_info& typeInfo) { ar.beginTag(tag); save(ar, t, typeInfo); ar.endTag(tag); } template class PolymorphicSaverRegistry; template class PolymorphicSaverRegistry; //// template PolymorphicLoaderRegistry::PolymorphicLoaderRegistry() { } template PolymorphicLoaderRegistry::~PolymorphicLoaderRegistry() { } template PolymorphicLoaderRegistry& PolymorphicLoaderRegistry::instance() { static PolymorphicLoaderRegistry oneInstance; return oneInstance; } template void PolymorphicLoaderRegistry::registerHelper( const char* name, std::unique_ptr> loader) { assert(!loaderMap.contains(name)); loaderMap.emplace_noDuplicateCheck(name, std::move(loader)); } template void* PolymorphicLoaderRegistry::load( Archive& ar, unsigned id, const void* args) { std::string type; ar.attribute("type", type); auto& reg = PolymorphicLoaderRegistry::instance(); auto it = reg.loaderMap.find(type); assert(it != end(reg.loaderMap)); return it->second->load(ar, id, args); } template class PolymorphicLoaderRegistry; template class PolymorphicLoaderRegistry; //// void polyInitError(const char* expected, const char* actual) { throw MSXException(StringOp::Builder() << "Expected type: " << expected << " but got: " << actual << '.'); } template PolymorphicInitializerRegistry::PolymorphicInitializerRegistry() { } template PolymorphicInitializerRegistry::~PolymorphicInitializerRegistry() { } template PolymorphicInitializerRegistry& PolymorphicInitializerRegistry::instance() { static PolymorphicInitializerRegistry oneInstance; return oneInstance; } template void PolymorphicInitializerRegistry::registerHelper( const char* name, std::unique_ptr> initializer) { assert(!initializerMap.contains(name)); initializerMap.emplace_noDuplicateCheck(name, std::move(initializer)); } template void PolymorphicInitializerRegistry::init( const char* tag, Archive& ar, void* t) { ar.beginTag(tag); unsigned id; ar.attribute("id", id); assert(id); std::string type; ar.attribute("type", type); auto& reg = PolymorphicInitializerRegistry::instance(); auto it = reg.initializerMap.find(type); assert(it != end(reg.initializerMap)); it->second->init(ar, t, id); ar.endTag(tag); } template class PolymorphicInitializerRegistry; template class PolymorphicInitializerRegistry; } // namespace openmsx openMSX-RELEASE_0_12_0/src/serialize_meta.hh000066400000000000000000000354031257557151200205510ustar00rootroot00000000000000#ifndef SERIALIZE_META_HH #define SERIALIZE_META_HH #include "hash_map.hh" #include "likely.hh" #include "memory.hh" #include "noncopyable.hh" #include "type_traits.hh" #include "xxhash.hh" #include #include #include #include namespace openmsx { /** Utility to do T* t = new T(...) * * The tricky part is that the constructor of T can have a variable number * of parameters. * * For example: * Creator creator; * tuple args = std::make_tuple(42, 3.14); * std::unique_ptr foo = creator(args); * This is equivalent to * auto foo = make_unique(42, 3.14); * But the former can be used in a generic context (where the number of * constructor parameters is unknown). */ template class Creator { public: template std::unique_ptr operator()(TUPLE args) { DoInstantiate::value, TUPLE> inst; return inst(args); } private: template struct DoInstantiate; template struct DoInstantiate<0, TUPLE> { std::unique_ptr operator()(TUPLE /*args*/) { return make_unique(); } }; template struct DoInstantiate<1, TUPLE> { std::unique_ptr operator()(TUPLE args) { return make_unique(std::get<0>(args)); } }; template struct DoInstantiate<2, TUPLE> { std::unique_ptr operator()(TUPLE args) { return make_unique( std::get<0>(args), std::get<1>(args)); } }; template struct DoInstantiate<3, TUPLE> { std::unique_ptr operator()(TUPLE args) { return make_unique( std::get<0>(args), std::get<1>(args), std::get<2>(args)); } }; }; /////////////////////////////// // Polymorphic class loader/saver // forward declarations // ClassSaver: used to save actually save a class. We also store the name of // the class so that the loader knows which concrete class it should load. template struct ClassSaver; // NonPolymorphicPointerLoader: once we know which concrete type to load, // we use the 'normal' class loader to load it. template struct NonPolymorphicPointerLoader; // Used by PolymorphicInitializer to initialize a concrete type. template struct ClassLoader; /** Store association between polymorphic class (base- or subclass) and * the list of constructor arguments. * Specializations of this class should store the constructor arguments * as a 'using type = tupple<...>'. */ template struct PolymorphicConstructorArgs; /** Store association between (polymorphic) sub- and baseclass. * Specialization of this class should provide a 'using type = '. */ template struct PolymorphicBaseClass; template struct MapConstrArgsEmpty { using TUPLEIn = typename PolymorphicConstructorArgs::type; std::tuple<> operator()(const TUPLEIn& /*t*/) { return std::make_tuple(); } }; template struct MapConstrArgsCopy { using TUPLEIn = typename PolymorphicConstructorArgs::type; using TUPLEOut = typename PolymorphicConstructorArgs::type; static_assert(std::is_same::value, "constructor argument types must match"); TUPLEOut operator()(const TUPLEIn& t) { return t; } }; /** Define mapping between constructor arg list of base- and subclass. * * When loading a polymorphic base class, the user must provide the union of * constructor arguments for all subclasses (because it's not yet known which * concrete subtype will be deserialized). This class defines the mapping * between this union of parameters and the subset used for a specific * subclass. * * In case the parameter list of the subclass is empty or if it is the same * as the base class, this mapping will be defined automatically. In the other * cases, the user must define a specialization of this class. */ template struct MapConstructorArguments : if_, typename PolymorphicConstructorArgs::type>, MapConstrArgsEmpty, MapConstrArgsCopy> {}; /** Stores the name of a base class. * This name is used as tag-name in XML archives. * * Specializations of this class should provide a function * static const char* getName() */ template struct BaseClassName; template class PolymorphicSaverBase { public: virtual ~PolymorphicSaverBase() {} virtual void save(Archive& ar, const void* p) const = 0; }; template class PolymorphicLoaderBase { public: virtual ~PolymorphicLoaderBase() {} virtual void* load(Archive& ar, unsigned id, const void* args) const = 0; }; template class PolymorphicInitializerBase { public: virtual ~PolymorphicInitializerBase() {} virtual void init(Archive& ar, void* t, unsigned id) const = 0; }; template class PolymorphicSaver : public PolymorphicSaverBase { public: PolymorphicSaver(const char* name_) : name(name_) { } void save(Archive& ar, const void* v) const override { using BaseType = typename PolymorphicBaseClass::type; auto base = static_cast(v); auto tp = static_cast(base); ClassSaver saver; saver(ar, *tp, true, name, true); // save id, type, constr-args } private: const char* name; }; template class PolymorphicLoader : public PolymorphicLoaderBase { public: void* load(Archive& ar, unsigned id, const void* args) const override { using BaseType = typename PolymorphicBaseClass::type; using TUPLEIn = typename PolymorphicConstructorArgs::type; using TUPLEOut = typename PolymorphicConstructorArgs::type; auto& argsIn = *static_cast(args); MapConstructorArguments mapArgs; TUPLEOut argsOut = mapArgs(argsIn); NonPolymorphicPointerLoader loader; return loader(ar, id, argsOut); } }; void polyInitError(const char* expected, const char* actual); template class PolymorphicInitializer : public PolymorphicInitializerBase { public: void init(Archive& ar, void* v, unsigned id) const override { using BaseType = typename PolymorphicBaseClass::type; auto base = static_cast(v); if (unlikely(dynamic_cast(base) != static_cast(base))) { polyInitError(typeid(T).name(), typeid(*base).name()); } auto t = static_cast(base); ClassLoader loader; loader(ar, *t, std::make_tuple(), id); } }; template class PolymorphicSaverRegistry : private noncopyable { public: static PolymorphicSaverRegistry& instance(); template void registerClass(const char* name) { static_assert(std::is_polymorphic::value, "must be a polymorphic type"); static_assert(!std::is_abstract::value, "can't be an abstract type"); registerHelper(typeid(T), make_unique>(name)); } template static void save(Archive& ar, T* t) { save(ar, t, typeid(*t)); } template static void save(const char* tag, Archive& ar, T& t) { save(tag, ar, &t, typeid(t)); } private: PolymorphicSaverRegistry(); ~PolymorphicSaverRegistry(); void registerHelper(const std::type_info& type, std::unique_ptr> saver); static void save(Archive& ar, const void* t, const std::type_info& typeInfo); static void save(const char* tag, Archive& ar, const void* t, const std::type_info& typeInfo); std::vector>>> saverMap; bool initialized; }; template class PolymorphicLoaderRegistry : private noncopyable { public: static PolymorphicLoaderRegistry& instance(); template void registerClass(const char* name) { static_assert(std::is_polymorphic::value, "must be a polymorphic type"); static_assert(!std::is_abstract::value, "can't be an abstract type"); registerHelper(name, make_unique>()); } static void* load(Archive& ar, unsigned id, const void* args); private: PolymorphicLoaderRegistry(); ~PolymorphicLoaderRegistry(); void registerHelper( const char* name, std::unique_ptr> loader); hash_map>, XXHasher> loaderMap; }; template class PolymorphicInitializerRegistry : private noncopyable { public: static PolymorphicInitializerRegistry& instance(); template void registerClass(const char* name) { static_assert(std::is_polymorphic::value, "must be a polymorphic type"); static_assert(!std::is_abstract::value, "can't be an abstract type"); registerHelper(name, make_unique>()); } static void init(const char* tag, Archive& ar, void* t); private: PolymorphicInitializerRegistry(); ~PolymorphicInitializerRegistry(); void registerHelper( const char* name, std::unique_ptr> initializer); hash_map>, XXHasher> initializerMap; }; template struct RegisterSaverHelper { RegisterSaverHelper(const char* name) { PolymorphicSaverRegistry::instance(). template registerClass(name); } }; template struct RegisterLoaderHelper { RegisterLoaderHelper(const char* name) { PolymorphicLoaderRegistry::instance(). template registerClass(name); } }; template struct RegisterInitializerHelper { RegisterInitializerHelper(const char* name) { PolymorphicInitializerRegistry::instance(). template registerClass(name); } }; #define REGISTER_CONSTRUCTOR_ARGS_0(C) \ template<> struct PolymorphicConstructorArgs \ { using type = std::tuple<>; }; #define REGISTER_CONSTRUCTOR_ARGS_1(C,T1) \ template<> struct PolymorphicConstructorArgs \ { using type = std::tuple; }; #define REGISTER_CONSTRUCTOR_ARGS_2(C,T1,T2) \ template<> struct PolymorphicConstructorArgs \ { using type = std::tuple; }; #define REGISTER_CONSTRUCTOR_ARGS_3(C,T1,T2,T3) \ template<> struct PolymorphicConstructorArgs \ { using type = std::tuple; }; class MemInputArchive; class MemOutputArchive; class XmlInputArchive; class XmlOutputArchive; /*#define REGISTER_POLYMORPHIC_CLASS_HELPER(B,C,N) \ static_assert(std::is_base_of::value, "must be base and sub class"); \ static RegisterLoaderHelper registerHelper1##C(N); \ static RegisterSaverHelper registerHelper2##C(N); \ static RegisterLoaderHelper registerHelper3##C(N); \ static RegisterSaverHelper registerHelper4##C(N); \ static RegisterLoaderHelper registerHelper5##C(N); \ static RegisterSaverHelper registerHelper6##C(N); \*/ #define REGISTER_POLYMORPHIC_CLASS_HELPER(B,C,N) \ static_assert(std::is_base_of::value, "must be base and sub class"); \ static RegisterLoaderHelper registerHelper3##C(N); \ static RegisterSaverHelper registerHelper4##C(N); \ static RegisterLoaderHelper registerHelper5##C(N); \ static RegisterSaverHelper registerHelper6##C(N); \ template<> struct PolymorphicBaseClass { using type = B; }; #define REGISTER_POLYMORPHIC_INITIALIZER_HELPER(B,C,N) \ static_assert(std::is_base_of::value, "must be base and sub class"); \ static RegisterInitializerHelper registerHelper3##C(N); \ static RegisterSaverHelper registerHelper4##C(N); \ static RegisterInitializerHelper registerHelper5##C(N); \ static RegisterSaverHelper registerHelper6##C(N); \ template<> struct PolymorphicBaseClass { using type = B; }; #define REGISTER_BASE_NAME_HELPER(B,N) \ template<> struct BaseClassName \ { static const char* getName() { static const char* name = N; return name; } }; // public macros // these are a more convenient way to define specializations of the // PolymorphicConstructorArgs and PolymorphicBaseClass classes #define REGISTER_POLYMORPHIC_CLASS(BASE,CLASS,NAME) \ REGISTER_POLYMORPHIC_CLASS_HELPER(BASE,CLASS,NAME) \ REGISTER_CONSTRUCTOR_ARGS_0(CLASS) #define REGISTER_POLYMORPHIC_CLASS_1(BASE,CLASS,NAME,TYPE1) \ REGISTER_POLYMORPHIC_CLASS_HELPER(BASE,CLASS,NAME) \ REGISTER_CONSTRUCTOR_ARGS_1(CLASS,TYPE1) #define REGISTER_POLYMORPHIC_CLASS_2(BASE,CLASS,NAME,TYPE1,TYPE2) \ REGISTER_POLYMORPHIC_CLASS_HELPER(BASE,CLASS,NAME) \ REGISTER_CONSTRUCTOR_ARGS_2(CLASS,TYPE1,TYPE2) #define REGISTER_POLYMORPHIC_CLASS_3(BASE,CLASS,NAME,TYPE1,TYPE2,TYPE3) \ REGISTER_POLYMORPHIC_CLASS_HELPER(BASE,CLASS,NAME) \ REGISTER_CONSTRUCTOR_ARGS_3(CLASS,TYPE1,TYPE2,TYPE3) #define REGISTER_BASE_CLASS(CLASS,NAME) \ REGISTER_BASE_NAME_HELPER(CLASS,NAME) \ REGISTER_CONSTRUCTOR_ARGS_0(CLASS) #define REGISTER_BASE_CLASS_1(CLASS,NAME,TYPE1) \ REGISTER_BASE_NAME_HELPER(CLASS,NAME) \ REGISTER_CONSTRUCTOR_ARGS_1(CLASS,TYPE1) #define REGISTER_BASE_CLASS_2(CLASS,NAME,TYPE1,TYPE2) \ REGISTER_BASE_NAME_HELPER(CLASS,NAME) \ REGISTER_CONSTRUCTOR_ARGS_2(CLASS,TYPE1,TYPE2) #define REGISTER_BASE_CLASS_3(CLASS,NAME,TYPE1,TYPE2,TYPE3) \ REGISTER_BASE_NAME_HELPER(CLASS,NAME) \ REGISTER_CONSTRUCTOR_ARGS_3(CLASS,TYPE1,TYPE2,TYPE3) #define REGISTER_POLYMORPHIC_INITIALIZER(BASE,CLASS,NAME) \ REGISTER_POLYMORPHIC_INITIALIZER_HELPER(BASE,CLASS,NAME) ////////////// /** Store serialization-version number of a class. * * Classes are individually versioned. Use the SERIALIZE_CLASS_VERSION * macro below as a convenient way to set the version of a class. * * The initial (=default) version is 1. When the layout of a class changes * in an incompatible way, you should increase the version number. But * remember: to be able to load older version the (de)serialize code for the * older version(s) must be kept. * * Version number 0 is special. It means the layout for this class will _NEVER_ * change. This can be a bit more efficient because the version number must * not be stored in the stream. Though be careful to only use version 0 for * _VERY_ stable classes, std::pair is a good example of a stable class. */ template struct SerializeClassVersion { static const unsigned value = 1; }; #define SERIALIZE_CLASS_VERSION(CLASS, VERSION) \ template<> struct SerializeClassVersion \ { \ static const unsigned value = VERSION; \ }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/serialize_stl.hh000066400000000000000000000045211257557151200204220ustar00rootroot00000000000000#ifndef SERIALIZE_STL_HH #define SERIALIZE_STL_HH #include "serialize_core.hh" #include "circular_buffer.hh" #include #include namespace openmsx { template struct serialize_as_stl_collection : std::true_type { static const int size = -1; // variable size using value_type = typename T::value_type; // save using const_iterator = typename T::const_iterator; static const_iterator begin(const T& t) { return t.begin(); } static const_iterator end (const T& t) { return t.end(); } // load static const bool loadInPlace = false; using output_iterator = typename std::insert_iterator; static void prepare(T& t, int /*n*/) { t.clear(); } static output_iterator output(T& t) { return std::inserter(t, begin(t)); } }; //template struct serialize_as_collection> // : serialize_as_stl_collection> {}; //template struct serialize_as_collection> // : serialize_as_stl_collection> {}; //template struct serialize_as_collection> // : serialize_as_stl_collection> {}; //template struct serialize_as_collection> // : serialize_as_stl_collection> {}; template struct serialize_as_collection> : serialize_as_stl_collection> { // Override load-part from base class. // Don't load vectors in-place, even though it's technically possible // and slightly more efficient. This is done to keep the correct vector // size at all intermediate steps. This may be important in case an // exception occurs during loading. static const bool loadInPlace = false; using output_iterator = typename std::back_insert_iterator>; static void prepare(std::vector& v, int n) { v.clear(); v.reserve(n); } static output_iterator output(std::vector& v) { return std::back_inserter(v); } }; template struct serialize_as_collection> : serialize_as_stl_collection> { using output_iterator = typename std::back_insert_iterator>; static void prepare(cb_queue& q, int n) { q.clear(); q.getBuffer().set_capacity(n); } static output_iterator output(cb_queue& q) { return std::back_inserter(q.getBuffer()); } }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/settings/000077500000000000000000000000001257557151200170665ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/src/settings/.gitignore000066400000000000000000000001761257557151200210620ustar00rootroot00000000000000/*.ROM /*.bb /*.bbg /*.da /*.dsk /*.prof /*.rom /*.rpo /*.swp /*.vim /.debug /.kdbgrc.openmsx /.xvpics /gmon.out /GNUmakefile openMSX-RELEASE_0_12_0/src/settings/BooleanSetting.cc000066400000000000000000000016741257557151200223220ustar00rootroot00000000000000#include "BooleanSetting.hh" #include "CommandController.hh" #include "Completer.hh" namespace openmsx { BooleanSetting::BooleanSetting( CommandController& commandController, string_ref name, string_ref description, bool initialValue, SaveSetting save) : Setting(commandController, name, description, TclObject(toString(initialValue)), save) { auto& interp = commandController.getInterpreter(); setChecker([this, &interp](TclObject& newValue) { // May throw. // Re-set the queried value to get a normalized value. newValue.setString(toString(newValue.getBoolean(interp))); }); init(); } string_ref BooleanSetting::getTypeString() const { return "boolean"; } void BooleanSetting::tabCompletion(std::vector& tokens) const { static const char* const values[] = { "true", "on", "yes", "false", "off", "no", }; Completer::completeString(tokens, values, false); // case insensitive } } // namespace openmsx openMSX-RELEASE_0_12_0/src/settings/BooleanSetting.hh000066400000000000000000000012641257557151200223270ustar00rootroot00000000000000#ifndef BOOLEANSETTING_HH #define BOOLEANSETTING_HH #include "Setting.hh" namespace openmsx { class BooleanSetting final : public Setting { public: BooleanSetting(CommandController& commandController, string_ref name, string_ref description, bool initialValue, SaveSetting save = SAVE); string_ref getTypeString() const override; void tabCompletion(std::vector& tokens) const override; bool getBoolean() const { return getValue().getBoolean(getInterpreter()); } void setBoolean(bool b) { setValue(TclObject(toString(b))); } private: static string_ref toString(bool b) { return b ? "true" : "false"; } }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/settings/EnumSetting.cc000066400000000000000000000026461257557151200216470ustar00rootroot00000000000000#include "EnumSetting.hh" #include "TclObject.hh" #include "Completer.hh" #include "CommandException.hh" #include "stringsp.hh" #include "unreachable.hh" #include "StringOp.hh" #include "stl.hh" #include namespace openmsx { using Comp = CmpTupleElement<0, StringOp::caseless>; EnumSettingBase::EnumSettingBase(BaseMap&& map) : baseMap(std::move(map)) { sort(begin(baseMap), end(baseMap), Comp()); } int EnumSettingBase::fromStringBase(string_ref str) const { auto it = lower_bound(begin(baseMap), end(baseMap), str, Comp()); StringOp::casecmp cmp; if ((it == end(baseMap)) || !cmp(it->first, str)) { throw CommandException("not a valid value: " + str); } return it->second; } string_ref EnumSettingBase::toStringBase(int value) const { for (auto& p : baseMap) { if (p.second == value) { return p.first; } } UNREACHABLE; return ""; } std::vector EnumSettingBase::getPossibleValues() const { std::vector result; for (auto& p : baseMap) { result.push_back(p.first); } return result; } void EnumSettingBase::additionalInfoBase(TclObject& result) const { TclObject valueList; valueList.addListElements(getPossibleValues()); result.addListElement(valueList); } void EnumSettingBase::tabCompletionBase(std::vector& tokens) const { Completer::completeString(tokens, getPossibleValues(), false); // case insensitive } } // namespace openmsx openMSX-RELEASE_0_12_0/src/settings/EnumSetting.hh000066400000000000000000000054441257557151200216600ustar00rootroot00000000000000#ifndef ENUMSETTING_HH #define ENUMSETTING_HH #include "Setting.hh" #include #include #include namespace openmsx { class TclObject; // non-templatized base class class EnumSettingBase { protected: // cannot be string_ref because of the 'default_machine' setting using BaseMap = std::vector>; EnumSettingBase(BaseMap&& m); int fromStringBase(string_ref str) const; string_ref toStringBase(int value) const; std::vector getPossibleValues() const; void additionalInfoBase(TclObject& result) const; void tabCompletionBase(std::vector& tokens) const; private: BaseMap baseMap; }; template class EnumSetting final : private EnumSettingBase, public Setting { public: using Map = std::vector>; EnumSetting(CommandController& commandController, string_ref name, string_ref description, T initialValue, Map&& map_, SaveSetting save = SAVE); string_ref getTypeString() const override; void additionalInfo(TclObject& result) const override; void tabCompletion(std::vector& tokens) const override; T getEnum() const; void setEnum(T value); string_ref getString() const; private: string_ref toString(T value) const; }; //------------- template EnumSetting::EnumSetting( CommandController& commandController, string_ref name, string_ref description, T initialValue, Map&& map, SaveSetting save) : EnumSettingBase(BaseMap(std::make_move_iterator(begin(map)), std::make_move_iterator(end(map)))) , Setting(commandController, name, description, TclObject(toString(initialValue)), save) { setChecker([this](TclObject& newValue) { fromStringBase(newValue.getString()); // may throw }); init(); } template string_ref EnumSetting::getTypeString() const { return "enumeration"; } template void EnumSetting::additionalInfo(TclObject& result) const { additionalInfoBase(result); } template void EnumSetting::tabCompletion(std::vector& tokens) const { tabCompletionBase(tokens); } template T EnumSetting::getEnum() const { return static_cast(fromStringBase(getValue().getString())); } template<> inline bool EnumSetting::getEnum() const { // _exactly_ the same functionality as above, but suppress VS warning return fromStringBase(getValue().getString()) != 0; } template void EnumSetting::setEnum(T value) { setValue(TclObject(toString(value))); } template string_ref EnumSetting::getString() const { return getValue().getString(); } template string_ref EnumSetting::toString(T value) const { return toStringBase(static_cast(value)); } } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/settings/FilenameSetting.cc000066400000000000000000000011261257557151200224530ustar00rootroot00000000000000#include "FilenameSetting.hh" #include "Completer.hh" #include "FileContext.hh" namespace openmsx { FilenameSetting::FilenameSetting( CommandController& commandController, string_ref name, string_ref description, string_ref initialValue) : Setting(commandController, name, description, TclObject(initialValue), Setting::SAVE) { init(); } string_ref FilenameSetting::getTypeString() const { return "filename"; } void FilenameSetting::tabCompletion(std::vector& tokens) const { Completer::completeFileName(tokens, systemFileContext()); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/settings/FilenameSetting.hh000066400000000000000000000011141257557151200224620ustar00rootroot00000000000000#ifndef FILENAMESETTING_HH #define FILENAMESETTING_HH #include "Setting.hh" namespace openmsx { class FilenameSetting final : public Setting { public: FilenameSetting(CommandController& commandController, string_ref name, string_ref description, string_ref initialValue); string_ref getTypeString() const override; void tabCompletion(std::vector& tokens) const override; string_ref getString() const { return getValue().getString(); } void setString(string_ref str) { setValue(TclObject(str)); } }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/settings/FloatSetting.cc000066400000000000000000000020671257557151200220050ustar00rootroot00000000000000#include "FloatSetting.hh" #include "CommandController.hh" namespace openmsx { FloatSetting::FloatSetting(CommandController& commandController, string_ref name, string_ref description, double initialValue, double minValue_, double maxValue_) : Setting(commandController, name, description, TclObject(initialValue), SAVE) , minValue(minValue_) , maxValue(maxValue_) { auto& interp = commandController.getInterpreter(); setChecker([this, &interp](TclObject& newValue) { double value = newValue.getDouble(interp); // may throw double clipped = std::min(std::max(value, minValue), maxValue); newValue.setDouble(clipped); }); init(); } string_ref FloatSetting::getTypeString() const { return "float"; } void FloatSetting::additionalInfo(TclObject& result) const { TclObject range; range.addListElement(minValue); range.addListElement(maxValue); result.addListElement(range); } void FloatSetting::setDouble(double d) { setValue(TclObject(d)); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/settings/FloatSetting.hh000066400000000000000000000012361257557151200220140ustar00rootroot00000000000000#ifndef FLOATSETTING_HH #define FLOATSETTING_HH #include "Setting.hh" namespace openmsx { /** A Setting with a floating point value. */ class FloatSetting final : public Setting { public: FloatSetting(CommandController& commandController, string_ref name, string_ref description, double initialValue, double minValue, double maxValue); string_ref getTypeString() const override; void additionalInfo(TclObject& result) const override; double getDouble() const { return getValue().getDouble(getInterpreter()); } void setDouble (double d); private: const double minValue; const double maxValue; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/settings/IntegerSetting.cc000066400000000000000000000020271257557151200223310ustar00rootroot00000000000000#include "IntegerSetting.hh" #include "CommandController.hh" namespace openmsx { IntegerSetting::IntegerSetting(CommandController& commandController, string_ref name, string_ref description, int initialValue, int minValue_, int maxValue_) : Setting(commandController, name, description, TclObject(initialValue), SAVE) , minValue(minValue_) , maxValue(maxValue_) { auto& interp = commandController.getInterpreter(); setChecker([this, &interp](TclObject& newValue) { int value = newValue.getInt(interp); // may throw int clipped = std::min(std::max(value, minValue), maxValue); newValue.setInt(clipped); }); init(); } string_ref IntegerSetting::getTypeString() const { return "integer"; } void IntegerSetting::additionalInfo(TclObject& result) const { TclObject range; range.addListElement(minValue); range.addListElement(maxValue); result.addListElement(range); } void IntegerSetting::setInt(int i) { setValue(TclObject(i)); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/settings/IntegerSetting.hh000066400000000000000000000012051257557151200223400ustar00rootroot00000000000000#ifndef INTEGERSETTING_HH #define INTEGERSETTING_HH #include "Setting.hh" namespace openmsx { /** A Setting with an integer value. */ class IntegerSetting final : public Setting { public: IntegerSetting(CommandController& commandController, string_ref name, string_ref description, int initialValue, int minValue, int maxValue); string_ref getTypeString() const override; void additionalInfo(TclObject& result) const override; int getInt() const { return getValue().getInt(getInterpreter()); } void setInt(int i); private: const int minValue; const int maxValue; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/settings/KeyCodeSetting.cc000066400000000000000000000014161257557151200222600ustar00rootroot00000000000000#include "KeyCodeSetting.hh" #include "CommandException.hh" namespace openmsx { KeyCodeSetting::KeyCodeSetting(CommandController& commandController, string_ref name, string_ref description, Keys::KeyCode initialValue) : Setting(commandController, name, description, TclObject(Keys::getName(initialValue)), SAVE) { setChecker([this](TclObject& newValue) { const auto& str = newValue.getString(); if (Keys::getCode(str) == Keys::K_NONE) { throw CommandException("Not a valid key: " + str); } }); init(); } string_ref KeyCodeSetting::getTypeString() const { return "key"; } Keys::KeyCode KeyCodeSetting::getKey() const { return Keys::getCode(getValue().getString()); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/settings/KeyCodeSetting.hh000066400000000000000000000006661257557151200223000ustar00rootroot00000000000000#ifndef KEYCODESETTING_HH #define KEYCODESETTING_HH #include "Setting.hh" #include "Keys.hh" namespace openmsx { class KeyCodeSetting final : public Setting { public: KeyCodeSetting(CommandController& commandController, string_ref name, string_ref description, Keys::KeyCode initialValue); string_ref getTypeString() const override; Keys::KeyCode getKey() const; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/settings/ProxySetting.cc000066400000000000000000000050561257557151200220620ustar00rootroot00000000000000#include "ProxySetting.hh" #include "MSXCommandController.hh" #include "Reactor.hh" #include "MSXMotherBoard.hh" #include "MSXException.hh" using std::string; using std::vector; namespace openmsx { ProxySetting::ProxySetting(Reactor& reactor_, string_ref name) : BaseSetting(name) , reactor(reactor_) { } BaseSetting* ProxySetting::getSetting() { auto* motherBoard = reactor.getMotherBoard(); if (!motherBoard) return nullptr; return motherBoard->getMSXCommandController().findSetting(getName()); } const BaseSetting* ProxySetting::getSetting() const { return const_cast(this)->getSetting(); } void ProxySetting::setValue(const TclObject& value) { if (auto* setting = getSetting()) { setting->setValue(value); } } string_ref ProxySetting::getTypeString() const { if (auto* setting = getSetting()) { return setting->getTypeString(); } else { return "proxy"; } } string_ref ProxySetting::getDescription() const { if (auto* setting = getSetting()) { return setting->getDescription(); } else { return "proxy"; } } const TclObject& ProxySetting::getValue() const { if (auto* setting = getSetting()) { return setting->getValue(); } else { throw MSXException("No setting '" + getName() + "' on current machine."); } } TclObject ProxySetting::getDefaultValue() const { if (auto* setting = getSetting()) { return setting->getDefaultValue(); } else { return TclObject("proxy"); } } TclObject ProxySetting::getRestoreValue() const { if (auto* setting = getSetting()) { return setting->getRestoreValue(); } else { return TclObject("proxy"); } } void ProxySetting::setValueDirect(const TclObject& value) { if (auto* setting = getSetting()) { // note: not setStringDirect() setting->setValue(value); } else { throw MSXException("No setting '" + getName() + "' on current machine."); } } void ProxySetting::tabCompletion(vector& tokens) const { if (auto* setting = getSetting()) { setting->tabCompletion(tokens); } } bool ProxySetting::needLoadSave() const { if (auto* setting = getSetting()) { return setting->needLoadSave(); } else { return false; } } bool ProxySetting::needTransfer() const { if (auto* setting = getSetting()) { return setting->needTransfer(); } else { return false; } } void ProxySetting::setDontSaveValue(const TclObject& dontSaveValue) { if (auto* setting = getSetting()) { setting->setDontSaveValue(dontSaveValue); } } void ProxySetting::additionalInfo(TclObject& result) const { if (auto* setting = getSetting()) { setting->additionalInfo(result); } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/settings/ProxySetting.hh000066400000000000000000000016561257557151200220760ustar00rootroot00000000000000#ifndef PROXYSETTING_HH #define PROXYSETTING_HH #include "Setting.hh" namespace openmsx { class Reactor; class ProxySetting final : public BaseSetting { public: ProxySetting(Reactor& reactor, string_ref name); void setValue(const TclObject& value) override; string_ref getTypeString() const override; string_ref getDescription() const override; const TclObject& getValue() const override; TclObject getDefaultValue() const override; TclObject getRestoreValue() const override; void setValueDirect(const TclObject& value) override; void tabCompletion(std::vector& tokens) const override; bool needLoadSave() const override; bool needTransfer() const override; void setDontSaveValue(const TclObject& dontSaveValue) override; void additionalInfo(TclObject& result) const override; private: BaseSetting* getSetting(); const BaseSetting* getSetting() const; Reactor& reactor; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/settings/ReadOnlySetting.cc000066400000000000000000000012651257557151200224540ustar00rootroot00000000000000#include "ReadOnlySetting.hh" #include "MSXException.hh" namespace openmsx { ReadOnlySetting::ReadOnlySetting( CommandController& commandController, string_ref name, string_ref description, const TclObject& initialValue) : Setting(commandController, name, description, initialValue, Setting::DONT_TRANSFER) , roValue(initialValue) { setChecker([this](TclObject& newValue) { if (newValue != roValue) { throw MSXException("Read-only setting"); } }); init(); } void ReadOnlySetting::setReadOnlyValue(const TclObject& value) { roValue = value; setValue(value); } string_ref ReadOnlySetting::getTypeString() const { return "read-only"; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/settings/ReadOnlySetting.hh000066400000000000000000000007331257557151200224650ustar00rootroot00000000000000#ifndef READONLYSETTING_HH #define READONLYSETTING_HH #include "Setting.hh" namespace openmsx { class ReadOnlySetting final : public Setting { public: ReadOnlySetting(CommandController& commandController, string_ref name, string_ref description, const TclObject& initialValue); void setReadOnlyValue(const TclObject& value); string_ref getTypeString() const override; private: TclObject roValue; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/settings/Setting.cc000066400000000000000000000116131257557151200210140ustar00rootroot00000000000000#include "Setting.hh" #include "CommandController.hh" #include "GlobalCommandController.hh" #include "MSXCommandController.hh" #include "SettingsConfig.hh" #include "TclObject.hh" #include "CliComm.hh" #include "XMLElement.hh" #include "MSXException.hh" #include "checked_cast.hh" using std::string; namespace openmsx { // class BaseSetting BaseSetting::BaseSetting(string_ref name_) : name(name_.str()) { } void BaseSetting::info(TclObject& result) const { result.addListElement(getTypeString()); result.addListElement(getDefaultValue()); additionalInfo(result); } // class Setting Setting::Setting(CommandController& commandController_, string_ref name_, string_ref desc_, const TclObject& initialValue, SaveSetting save_) : BaseSetting(name_) , commandController(commandController_) , description(desc_.str()) , value(initialValue) , defaultValue(initialValue) , restoreValue(initialValue) , save(save_) { checkFunc = [](TclObject&) { /* nothing */ }; } void Setting::init() { if (needLoadSave()) { auto& settingsConfig = getGlobalCommandController() .getSettingsConfig().getXMLElement(); if (auto* config = settingsConfig.findChild("settings")) { if (auto* elem = config->findChildWithAttribute( "setting", "id", getName())) { try { setValueDirect(TclObject(elem->getData())); } catch (MSXException&) { // saved value no longer valid, just keep default } } } } getCommandController().registerSetting(*this); // This is needed to for example inform catapult of the new setting // value when a setting was destroyed/recreated (by a machine switch // for example). notify(); } Setting::~Setting() { getCommandController().unregisterSetting(*this); } string_ref Setting::getDescription() const { return description; } void Setting::setValue(const TclObject& value) { getCommandController().changeSetting(*this, value); } void Setting::notify() const { // Notify all subsystems of a change in the setting value. There // are actually quite a few subsystems involved with the settings: // - the setting classes themselves // - the Tcl variables (and possibly traces on those variables) // - Subject/Observers // - CliComm setting-change events (for external GUIs) // - SettingsConfig (keeps values, also of not yet created settings) // This method takes care of the last 3 in this list. Subject::notify(); TclObject value = getValue(); commandController.getCliComm().update( CliComm::SETTING, getName(), value.getString()); // Always keep SettingsConfig in sync. auto& config = getGlobalCommandController().getSettingsConfig().getXMLElement(); auto& settings = config.getCreateChild("settings"); if (!needLoadSave() || (value == getDefaultValue())) { // remove setting if (auto* elem = settings.findChildWithAttribute( "setting", "id", getName())) { settings.removeChild(*elem); } } else { // add (or overwrite) setting auto& elem = settings.getCreateChildWithAttribute( "setting", "id", getName()); // check for non-saveable value // (mechanism can be generalize later when needed) if (value == dontSaveValue) value = getRestoreValue(); elem.setData(value.getString()); } } void Setting::notifyPropertyChange() const { TclObject result; info(result); commandController.getCliComm().update( CliComm::SETTINGINFO, getName(), result.getString()); } bool Setting::needLoadSave() const { return save == SAVE; } bool Setting::needTransfer() const { return save != DONT_TRANSFER; } void Setting::setDontSaveValue(const TclObject& dontSaveValue_) { dontSaveValue = dontSaveValue_; } GlobalCommandController& Setting::getGlobalCommandController() const { if (auto* globalCommandController = dynamic_cast(&commandController)) { return *globalCommandController; } else { return checked_cast(&commandController) ->getGlobalCommandController(); } } Interpreter& Setting::getInterpreter() const { return commandController.getInterpreter(); } void Setting::tabCompletion(std::vector& /*tokens*/) const { // nothing } void Setting::additionalInfo(TclObject& /*result*/) const { // nothing } void Setting::setValueDirect(const TclObject& newValue_) { TclObject newValue = newValue_; checkFunc(newValue); if (newValue != value) { value = newValue; notify(); } // synchronize proxy auto* controller = dynamic_cast( &getCommandController()); if (!controller) { // This is not a machine specific setting. return; } if (!controller->isActive()) { // This setting does not belong to the active machine. return; } auto& globalController = controller->getGlobalCommandController(); // Tcl already makes sure this doesn't result in an endless loop. try { globalController.changeSetting(getName(), getValue()); } catch (MSXException&) { // ignore } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/settings/Setting.hh000066400000000000000000000122161257557151200210260ustar00rootroot00000000000000#ifndef SETTING_HH #define SETTING_HH #include "Subject.hh" #include "TclObject.hh" #include "noncopyable.hh" #include "string_ref.hh" #include #include namespace openmsx { class CommandController; class GlobalCommandController; class Interpreter; class BaseSetting : private noncopyable { protected: BaseSetting(string_ref name); ~BaseSetting() {} public: /** Get the name of this setting. */ const std::string& getName() const { return name; } /** For SettingInfo */ void info(TclObject& result) const; /// pure virtual methods /// /** Get a description of this setting that can be presented to the user. */ virtual string_ref getDescription() const = 0; /** Returns a string describing the setting type (integer, string, ..) * Could be used in a GUI to pick an appropriate setting widget. */ virtual string_ref getTypeString() const = 0; /** Helper method for info(). */ virtual void additionalInfo(TclObject& result) const = 0; /** Complete a partly typed value. * Default implementation does not complete anything, * subclasses can override this to complete according to their * specific value type. */ virtual void tabCompletion(std::vector& tokens) const = 0; /** Get current value as a TclObject. */ virtual const TclObject& getValue() const = 0; /** Get the default value of this setting. * This is the initial value of the setting. Default values don't * get saved in 'settings.xml'. */ virtual TclObject getDefaultValue() const = 0; /** Get the value that will be set after a Tcl 'unset' command. * Usually this is the same as the default value. Though one * exception is 'renderer', see comments in RendererFactory.cc. */ virtual TclObject getRestoreValue() const = 0; /** Change the value of this setting to the given value. * This method will trigger Tcl traces. * This value still passes via the 'checker-callback' (see below), * so the value may be adjusted. Or in case of an invalid value * this method may throw. */ virtual void setValue(const TclObject& value) = 0; /** Similar to setValue(), but doesn't trigger Tcl traces. * Like setValue(), the given value may be adjusted or rejected. * Should only be used by the Interpreter class. */ virtual void setValueDirect(const TclObject& value) = 0; /** Does this setting need to be loaded or saved (settings.xml). */ virtual bool needLoadSave() const = 0; /** Does this setting need to be transfered on reverse. */ virtual bool needTransfer() const = 0; /** This value will never end up in the settings.xml file */ virtual void setDontSaveValue(const TclObject& dontSaveValue) = 0; private: /** The name of this setting. */ const std::string name; }; class Setting : public BaseSetting, public Subject { public: enum SaveSetting { SAVE, // save, transfer DONT_SAVE, // no-save, transfer DONT_TRANSFER, // no-save, no-transfer }; virtual ~Setting(); /** Gets the current value of this setting as a TclObject. */ const TclObject& getValue() const final override { return value; } /** Set restore value. See getDefaultValue() and getRestoreValue(). */ void setRestoreValue(const TclObject& value) { restoreValue = value; } /** Set value-check-callback. * The callback is called on each change of this settings value. * The callback has to posibility to * - change the value (modify the parameter) * - disallow the change (throw an exception) * The callback is only executed on each value change, even if the * new value is the same as the current value. However the callback * is not immediately executed once it's set (via this method). */ void setChecker(std::function checkFunc_) { checkFunc = checkFunc_; } // BaseSetting void setValue(const TclObject& value) final override; string_ref getDescription() const final override; TclObject getDefaultValue() const final override { return defaultValue; } TclObject getRestoreValue() const final override { return restoreValue; } void setValueDirect(const TclObject& value) final override; void tabCompletion(std::vector& tokens) const override; bool needLoadSave() const final override; void additionalInfo(TclObject& result) const override; bool needTransfer() const final override; void setDontSaveValue(const TclObject& dontSaveValue) final override; // convenience functions CommandController& getCommandController() const { return commandController; } Interpreter& getInterpreter() const; protected: Setting(CommandController& commandController, string_ref name, string_ref description, const TclObject& initialValue, SaveSetting save = SAVE); void init(); void notifyPropertyChange() const; private: GlobalCommandController& getGlobalCommandController() const; void notify() const; private: CommandController& commandController; const std::string description; std::function checkFunc; TclObject value; // TODO can we share the underlying Tcl var storage? const TclObject defaultValue; TclObject restoreValue; TclObject dontSaveValue; const SaveSetting save; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/settings/SettingsManager.cc000066400000000000000000000117611257557151200224760ustar00rootroot00000000000000#include "SettingsManager.hh" #include "GlobalCommandController.hh" #include "TclObject.hh" #include "Setting.hh" #include "CommandException.hh" #include "XMLElement.hh" #include "KeyRange.hh" #include "outer.hh" #include using std::string; using std::vector; namespace openmsx { // SettingsManager implementation: SettingsManager::SettingsManager(GlobalCommandController& commandController) : settingInfo (commandController.getOpenMSXInfoCommand()) , setCompleter (commandController) , incrCompleter (commandController, *this, "incr") , unsetCompleter(commandController, *this, "unset") { } SettingsManager::~SettingsManager() { assert(settingsMap.empty()); } void SettingsManager::registerSetting(BaseSetting& setting, string_ref name) { assert(!settingsMap.contains(name)); settingsMap.emplace_noDuplicateCheck(name.str(), &setting); } void SettingsManager::unregisterSetting(BaseSetting& /*setting*/, string_ref name) { assert(settingsMap.contains(name)); settingsMap.erase(name); } BaseSetting* SettingsManager::findSetting(string_ref name) const { auto it = settingsMap.find(name); return (it != end(settingsMap)) ? it->second : nullptr; } // Helper functions for setting commands BaseSetting& SettingsManager::getByName(string_ref cmd, string_ref name) const { if (auto* setting = findSetting(name)) { return *setting; } throw CommandException(cmd + ": " + name + ": no such setting"); } void SettingsManager::loadSettings(const XMLElement& config) { // restore default values for (auto* s : values(settingsMap)) { if (s->needLoadSave()) { s->setValue(s->getRestoreValue()); } } // load new values auto* settings = config.findChild("settings"); if (!settings) return; for (auto& p : settingsMap) { auto& name = p.first; auto& setting = *p.second; if (!setting.needLoadSave()) continue; if (auto* elem = settings->findChildWithAttribute( "setting", "id", name)) { try { setting.setValue(TclObject(elem->getData())); } catch (MSXException&) { // ignore, keep default value } } } } // class SettingInfo SettingsManager::SettingInfo::SettingInfo(InfoCommand& openMSXInfoCommand) : InfoTopic(openMSXInfoCommand, "setting") { } void SettingsManager::SettingInfo::execute( array_ref tokens, TclObject& result) const { auto& manager = OUTER(SettingsManager, settingInfo); auto& settingsMap = manager.settingsMap; switch (tokens.size()) { case 2: for (auto& p : settingsMap) { result.addListElement(p.first); } break; case 3: { const auto& name = tokens[2].getString(); auto it = settingsMap.find(name); if (it == end(settingsMap)) { throw CommandException("No such setting: " + name); } it->second->info(result); break; } default: throw CommandException("Too many parameters."); } } string SettingsManager::SettingInfo::help(const vector& /*tokens*/) const { return "openmsx_info setting : " "returns list of all settings\n" "openmsx_info setting : " "returns info on a specific setting\n"; } void SettingsManager::SettingInfo::tabCompletion(vector& tokens) const { if (tokens.size() == 3) { // complete setting name auto& manager = OUTER(SettingsManager, settingInfo); completeString(tokens, keys(manager.settingsMap)); } } // class SetCompleter SettingsManager::SetCompleter::SetCompleter( CommandController& commandController) : CommandCompleter(commandController, "set") { } string SettingsManager::SetCompleter::help(const vector& tokens) const { if (tokens.size() == 2) { auto& manager = OUTER(SettingsManager, setCompleter); return manager.getByName("set", tokens[1]).getDescription().str(); } return "Set or query the value of a openMSX setting or Tcl variable\n" " set shows current value\n" " set set a new value\n" "Use 'help set ' to get more info on a specific\n" "openMSX setting.\n"; } void SettingsManager::SetCompleter::tabCompletion(vector& tokens) const { auto& manager = OUTER(SettingsManager, setCompleter); switch (tokens.size()) { case 2: // complete setting name completeString(tokens, keys(manager.settingsMap), false); // case insensitive break; case 3: { // complete setting value auto it = manager.settingsMap.find(tokens[1]); if (it != end(manager.settingsMap)) { it->second->tabCompletion(tokens); } break; } } } // class SettingCompleter SettingsManager::SettingCompleter::SettingCompleter( CommandController& commandController, SettingsManager& manager_, const string& name) : CommandCompleter(commandController, name) , manager(manager_) { } string SettingsManager::SettingCompleter::help(const vector& /*tokens*/) const { return ""; // TODO } void SettingsManager::SettingCompleter::tabCompletion(vector& tokens) const { if (tokens.size() == 2) { // complete setting name completeString(tokens, keys(manager.settingsMap)); } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/settings/SettingsManager.hh000066400000000000000000000040071257557151200225030ustar00rootroot00000000000000#ifndef SETTINGSMANAGER_HH #define SETTINGSMANAGER_HH #include "Command.hh" #include "InfoTopic.hh" #include "hash_map.hh" #include "string_ref.hh" #include "noncopyable.hh" #include "xxhash.hh" namespace openmsx { class BaseSetting; class GlobalCommandController; class XMLElement; /** Manages all settings. */ class SettingsManager : private noncopyable { public: explicit SettingsManager(GlobalCommandController& commandController); ~SettingsManager(); /** Find the setting with given name. * @return The requested setting or nullptr. */ BaseSetting* findSetting(string_ref name) const; void loadSettings(const XMLElement& config); void registerSetting (BaseSetting& setting, string_ref name); void unregisterSetting(BaseSetting& setting, string_ref name); private: BaseSetting& getByName(string_ref cmd, string_ref name) const; struct SettingInfo final : InfoTopic { SettingInfo(InfoCommand& openMSXInfoCommand); void execute(array_ref tokens, TclObject& result) const override; std::string help(const std::vector& tokens) const override; void tabCompletion(std::vector& tokens) const override; } settingInfo; struct SetCompleter final : CommandCompleter { SetCompleter(CommandController& commandController); std::string help(const std::vector& tokens) const override; void tabCompletion(std::vector& tokens) const override; } setCompleter; class SettingCompleter final : public CommandCompleter { public: SettingCompleter(CommandController& commandController, SettingsManager& manager, const std::string& name); std::string help(const std::vector& tokens) const override; void tabCompletion(std::vector& tokens) const override; private: SettingsManager& manager; }; SettingCompleter incrCompleter; SettingCompleter unsetCompleter; // TODO refactor so that we can use Setting::getName() hash_map settingsMap; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/settings/StringSetting.cc000066400000000000000000000007121257557151200222010ustar00rootroot00000000000000#include "StringSetting.hh" namespace openmsx { StringSetting::StringSetting(CommandController& commandController, string_ref name, string_ref description, string_ref initialValue, SaveSetting save) : Setting(commandController, name, description, TclObject(initialValue), save) { init(); } string_ref StringSetting::getTypeString() const { return "string"; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/settings/StringSetting.hh000066400000000000000000000010231257557151200222070ustar00rootroot00000000000000#ifndef STRINGSETTING_HH #define STRINGSETTING_HH #include "Setting.hh" namespace openmsx { class StringSetting final : public Setting { public: StringSetting(CommandController& commandController, string_ref name, string_ref description, string_ref initialValue, SaveSetting save = SAVE); string_ref getTypeString() const override; string_ref getString() const { return getValue().getString(); } void setString(string_ref str) { setValue(TclObject(str)); } }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/settings/UserSettings.cc000066400000000000000000000172651257557151200220470ustar00rootroot00000000000000#include "UserSettings.hh" #include "CommandController.hh" #include "CommandException.hh" #include "TclObject.hh" #include "StringSetting.hh" #include "BooleanSetting.hh" #include "IntegerSetting.hh" #include "FloatSetting.hh" #include "memory.hh" #include "outer.hh" #include "stl.hh" #include using std::string; using std::vector; using std::unique_ptr; namespace openmsx { // class UserSettings UserSettings::UserSettings(CommandController& commandController_) : userSettingCommand(commandController_) { } void UserSettings::addSetting(unique_ptr setting) { assert(!findSetting(setting->getName())); settings.push_back(std::move(setting)); } void UserSettings::deleteSetting(Setting& setting) { settings.erase(find_if_unguarded(settings, [&](unique_ptr& p) { return p.get() == &setting; })); } Setting* UserSettings::findSetting(string_ref name) const { for (auto& s : settings) { if (s->getName() == name) { return s.get(); } } return nullptr; } // class UserSettings::Cmd UserSettings::Cmd::Cmd(CommandController& commandController) : Command(commandController, "user_setting") { } void UserSettings::Cmd::execute(array_ref tokens, TclObject& result) { if (tokens.size() < 2) { throw SyntaxError(); } const auto& subCommand = tokens[1].getString(); if (subCommand == "create") { create(tokens, result); } else if (subCommand == "destroy") { destroy(tokens, result); } else if (subCommand == "info") { info(tokens, result); } else { throw CommandException( "Invalid subcommand '" + subCommand + "', expected " "'create', 'destroy' or 'info'."); } } void UserSettings::Cmd::create(array_ref tokens, TclObject& result) { if (tokens.size() < 5) { throw SyntaxError(); } const auto& type = tokens[2].getString(); const auto& name = tokens[3].getString(); if (getCommandController().findSetting(name)) { throw CommandException( "There already exists a setting with this name: " + name); } unique_ptr setting; if (type == "string") { setting = createString(tokens); } else if (type == "boolean") { setting = createBoolean(tokens); } else if (type == "integer") { setting = createInteger(tokens); } else if (type == "float") { setting = createFloat(tokens); } else { throw CommandException( "Invalid setting type '" + type + "', expected " "'string', 'boolean', 'integer' or 'float'."); } auto& userSettings = OUTER(UserSettings, userSettingCommand); userSettings.addSetting(std::move(setting)); result.setString(tokens[3].getString()); // name } unique_ptr UserSettings::Cmd::createString(array_ref tokens) { if (tokens.size() != 6) { throw SyntaxError(); } const auto& name = tokens[3].getString(); const auto& desc = tokens[4].getString(); const auto& initVal = tokens[5].getString(); return make_unique( getCommandController(), name, desc, initVal); } unique_ptr UserSettings::Cmd::createBoolean(array_ref tokens) { if (tokens.size() != 6) { throw SyntaxError(); } const auto& name = tokens[3].getString(); const auto& desc = tokens[4].getString(); const auto& initVal = tokens[5].getBoolean(getInterpreter()); return make_unique( getCommandController(), name, desc, initVal); } unique_ptr UserSettings::Cmd::createInteger(array_ref tokens) { if (tokens.size() != 8) { throw SyntaxError(); } auto& interp = getInterpreter(); const auto& name = tokens[3].getString(); const auto& desc = tokens[4].getString(); const auto& initVal = tokens[5].getInt(interp); const auto& minVal = tokens[6].getInt(interp); const auto& maxVal = tokens[7].getInt(interp); return make_unique( getCommandController(), name, desc, initVal, minVal, maxVal); } unique_ptr UserSettings::Cmd::createFloat(array_ref tokens) { if (tokens.size() != 8) { throw SyntaxError(); } auto& interp = getInterpreter(); const auto& name = tokens[3].getString(); const auto& desc = tokens[4].getString(); const auto& initVal = tokens[5].getDouble(interp); const auto& minVal = tokens[6].getDouble(interp); const auto& maxVal = tokens[7].getDouble(interp); return make_unique( getCommandController(), name, desc, initVal, minVal, maxVal); } void UserSettings::Cmd::destroy(array_ref tokens, TclObject& /*result*/) { if (tokens.size() != 3) { throw SyntaxError(); } const auto& name = tokens[2].getString(); auto& userSettings = OUTER(UserSettings, userSettingCommand); auto* setting = userSettings.findSetting(name); if (!setting) { throw CommandException( "There is no user setting with this name: " + name); } userSettings.deleteSetting(*setting); } void UserSettings::Cmd::info(array_ref /*tokens*/, TclObject& result) { result.addListElements(getSettingNames()); } string UserSettings::Cmd::help(const vector& tokens) const { if (tokens.size() < 2) { return "Manage user-defined settings.\n" "\n" "User defined settings are mainly used in Tcl scripts " "to create variables (=settings) that are persistent over " "different openMSX sessions.\n" "\n" " user_setting create [ ]\n" " user_setting destroy \n" " user_setting info\n" "\n" "Use 'help user_setting ' to see more info " "on a specific subcommand."; } assert(tokens.size() >= 2); if (tokens[1] == "create") { return "user_setting create [ ]\n" "\n" "Create a user defined setting. The extra arguments have the following meaning:\n" " The type for the setting, must be 'string', 'boolean', 'integer' or 'float'.\n" " The name for the setting.\n" " A (short) description for this setting.\n" " This text can be queried via 'help set '.\n" " The initial value for the setting.\n" " This value is only used the very first time the setting is created, otherwise the value is taken from previous openMSX sessions.\n" " This parameter is only required for 'integer' and 'float' setting types.\n" " Together with max-value this parameter defines the range of valid values.\n" " See min-value."; } else if (tokens[1] == "destroy") { return "user_setting destroy \n" "\n" "Remove a previously defined user setting. This only " "removes the setting from the current openMSX session, " "the value of this setting is still preserved for " "future sessions."; } else if (tokens[1] == "info") { return "user_setting info\n" "\n" "Returns a list of all user defined settings that are " "active in this openMSX session."; } else { return "No such subcommand, see 'help user_setting'."; } } void UserSettings::Cmd::tabCompletion(vector& tokens) const { if (tokens.size() == 2) { static const char* const cmds[] = { "create", "destroy", "info" }; completeString(tokens, cmds); } else if ((tokens.size() == 3) && (tokens[1] == "create")) { static const char* const types[] = { "string", "boolean", "integer", "float" }; completeString(tokens, types); } else if ((tokens.size() == 3) && (tokens[1] == "destroy")) { completeString(tokens, getSettingNames()); } } vector UserSettings::Cmd::getSettingNames() const { vector result; auto& userSettings = OUTER(UserSettings, userSettingCommand); for (auto& s : userSettings.getSettings()) { result.push_back(s->getName()); } return result; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/settings/UserSettings.hh000066400000000000000000000027451257557151200220560ustar00rootroot00000000000000#ifndef USERSETTINGS_HH #define USERSETTINGS_HH #include "Command.hh" #include "noncopyable.hh" #include "string_ref.hh" #include #include namespace openmsx { class Setting; class UserSettings : private noncopyable { public: using Settings = std::vector>; explicit UserSettings(CommandController& commandController); void addSetting(std::unique_ptr setting); void deleteSetting(Setting& setting); Setting* findSetting(string_ref name) const; const Settings& getSettings() const { return settings; } private: class Cmd final : public Command { public: Cmd(CommandController& commandController); void execute(array_ref tokens, TclObject& result) override; std::string help(const std::vector& tokens) const override; void tabCompletion(std::vector& tokens) const override; private: void create (array_ref tokens, TclObject& result); void destroy(array_ref tokens, TclObject& result); void info (array_ref tokens, TclObject& result); std::unique_ptr createString (array_ref tokens); std::unique_ptr createBoolean(array_ref tokens); std::unique_ptr createInteger(array_ref tokens); std::unique_ptr createFloat (array_ref tokens); std::vector getSettingNames() const; } userSettingCommand; Settings settings; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/settings/VideoSourceSetting.cc000066400000000000000000000100051257557151200231560ustar00rootroot00000000000000#include "VideoSourceSetting.hh" #include "CommandException.hh" #include "Completer.hh" #include "KeyRange.hh" #include "StringOp.hh" #include "stl.hh" namespace openmsx { VideoSourceSetting::VideoSourceSetting(CommandController& commandController) : Setting(commandController, "videosource", "selects the video source to display on the screen", TclObject("none"), DONT_SAVE) { sources = { { "none", 0 } }; setChecker([this](TclObject& newValue) { checkSetValue(newValue.getString()); // may throw }); init(); } void VideoSourceSetting::checkSetValue(string_ref value) const { // Special case: in case there are no videosources registered (yet), // the only allowed value is "none". In case there is at least one // registered source, this special value "none" should be hidden. if (((value == "none") && (sources.size() > 1)) || ((value != "none") && !has(value))) { throw CommandException("video source not available"); } } int VideoSourceSetting::getSource() { // Always try to find a better value than "none". string_ref str = getValue().getString(); if (str != "none") { // If current value is allowed, then keep it. if (int id = has(str)) { return id; } } // Search the best value from the current set of allowed values. int id = 0; if (!id) { id = has("Video9000"); } // in if (!id) { id = has("MSX"); } // order if (!id) { id = has("GFX9000"); } // of if (!id) { id = has("Laserdisc"); } // preference if (!id) { // This handles the "none" case, but also stuff like // multiple V99x8/V9990 chips. Prefer the source with // highest id (=newest). for (auto& s : values(sources)) id = std::max(id, s); } setSource(id); // store new value return id; } void VideoSourceSetting::setSource(int id) { auto it = find_if_unguarded(sources, [&](const Sources::value_type& p) { return p.second == id; }); setValue(TclObject(it->first)); } string_ref VideoSourceSetting::getTypeString() const { return "enumeration"; } std::vector VideoSourceSetting::getPossibleValues() const { std::vector result; if (sources.size() == 1) { assert(sources.front().first == "none"); result.push_back("none"); } else { for (auto& p : sources) { if (p.second != 0) { result.push_back(p.first); } } } return result; } void VideoSourceSetting::additionalInfo(TclObject& result) const { TclObject valueList; valueList.addListElements(getPossibleValues()); result.addListElement(valueList); } void VideoSourceSetting::tabCompletion(std::vector& tokens) const { Completer::completeString(tokens, getPossibleValues(), false); // case insensitive } int VideoSourceSetting::registerVideoSource(const std::string& source) { static int counter = 0; // id's are globally unique assert(!has(source)); sources.emplace_back(source, ++counter); // First announce extended set of allowed values before announcing a // (possibly) different value. notifyPropertyChange(); setSource(getSource()); // via source to (possibly) adjust value return counter; } void VideoSourceSetting::unregisterVideoSource(int source) { sources.erase(find_if_unguarded(sources, [&](Sources::value_type& p) { return p.second == source; })); // First notify the (possibly) changed value before announcing the // shrinked set of values. setSource(getSource()); // via source to (possibly) adjust value notifyPropertyChange(); } bool VideoSourceSetting::has(int value) const { return contains(values(sources), value); } int VideoSourceSetting::has(string_ref value) const { auto it = find_if(begin(sources), end(sources), [&](const Sources::value_type& p) { StringOp::casecmp cmp; return cmp(p.first, value); }); return (it != end(sources)) ? it->second : 0; } VideoSourceActivator::VideoSourceActivator( VideoSourceSetting& setting_, const std::string& name) : setting(setting_) { id = setting.registerVideoSource(name); } VideoSourceActivator::~VideoSourceActivator() { setting.unregisterVideoSource(id); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/settings/VideoSourceSetting.hh000066400000000000000000000021001257557151200231650ustar00rootroot00000000000000#ifndef VIDEOSOURCESETTING_HH #define VIDEOSOURCESETTING_HH #include "Setting.hh" #include #include namespace openmsx { class VideoSourceSetting final : public Setting { public: explicit VideoSourceSetting(CommandController& commandController); string_ref getTypeString() const override; void additionalInfo(TclObject& result) const override; void tabCompletion(std::vector& tokens) const override; int registerVideoSource(const std::string& source); void unregisterVideoSource(int source); int getSource(); void setSource(int id); private: std::vector getPossibleValues() const; void checkSetValue(string_ref value) const; bool has(int value) const; int has(string_ref value) const; using Sources = std::vector>; Sources sources; }; class VideoSourceActivator { public: VideoSourceActivator( VideoSourceSetting& setting, const std::string& name); ~VideoSourceActivator(); int getID() const { return id; } private: VideoSourceSetting& setting; int id; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/000077500000000000000000000000001257557151200163565ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/src/sound/.gitignore000066400000000000000000000001141257557151200203420ustar00rootroot00000000000000/*.swp /*.rpo /*.rom /*.ROM /.xvpics /*.dsk /*.bb /*.bbg /*.da /GNUmakefile openMSX-RELEASE_0_12_0/src/sound/AY8910.cc000066400000000000000000000674461257557151200175410ustar00rootroot00000000000000/* * Emulation of the AY-3-8910 * * Original code taken from xmame-0.37b16.1 * Based on various code snippets by Ville Hallik, Michael Cuddy, * Tatsuyuki Satoh, Fabrice Frances, Nicola Salmoria. * Integrated into openMSX by ???. * Refactored in C++ style by Maarten ter Huurne. */ #include "AY8910.hh" #include "AY8910Periphery.hh" #include "DeviceConfig.hh" #include "GlobalSettings.hh" #include "MSXException.hh" #include "Math.hh" #include "StringOp.hh" #include "serialize.hh" #include "likely.hh" #include "outer.hh" #include "random.hh" #include #include #include using std::string; namespace openmsx { // The step clock for the tone and noise generators is the chip clock // divided by 8; for the envelope generator of the AY-3-8910, it is half // that much (clock/16). static const float NATIVE_FREQ_FLOAT = (3579545.0f / 2) / 8; static const int NATIVE_FREQ_INT = int(NATIVE_FREQ_FLOAT + 0.5f); static const int PORT_A_DIRECTION = 0x40; static const int PORT_B_DIRECTION = 0x80; enum Register { AY_AFINE = 0, AY_ACOARSE = 1, AY_BFINE = 2, AY_BCOARSE = 3, AY_CFINE = 4, AY_CCOARSE = 5, AY_NOISEPER = 6, AY_ENABLE = 7, AY_AVOL = 8, AY_BVOL = 9, AY_CVOL = 10, AY_EFINE = 11, AY_ECOARSE = 12, AY_ESHAPE = 13, AY_PORTA = 14, AY_PORTB = 15 }; // Perlin noise static float n[256 + 3]; static void initDetune() { auto& generator = global_urng(); // fast (non-cryptographic) random numbers std::uniform_real_distribution distribution(-1.0f, 1.0f); for (int i = 0; i < 256; ++i) { n[i] = distribution(generator); } n[256] = n[0]; n[257] = n[1]; n[258] = n[2]; } static float noiseValue(float x) { // cubic hermite spline interpolation assert(0.0f <= x); int xi = int(x); float xf = x - xi; xi &= 255; float n0 = n[xi + 0]; float n1 = n[xi + 1]; float n2 = n[xi + 2]; float n3 = n[xi + 3]; float a = n3 - n2 + n1 - n0; float b = n0 - n1 - a; float c = n2 - n0; float d = n1; return ((a * xf + b) * xf + c) * xf + d; } // Generator: AY8910::Generator::Generator() { reset(0); } inline void AY8910::Generator::reset(unsigned output) { count = 0; this->output = output; } inline void AY8910::Generator::setPeriod(int value) { // Careful studies of the chip output prove that it instead counts up from // 0 until the counter becomes greater or equal to the period. This is an // important difference when the program is rapidly changing the period to // modulate the sound. // Also, note that period = 0 is the same as period = 1. This is mentioned // in the YM2203 data sheets. However, this does NOT apply to the Envelope // period. In that case, period = 0 is half as period = 1. period = std::max(1, value); count = std::min(count, period - 1); } inline unsigned AY8910::Generator::getOutput() const { return output; } inline unsigned AY8910::Generator::getNextEventTime() const { assert(count < period); return period - count; } inline void AY8910::Generator::advanceFast(unsigned duration) { count += duration; assert(count < period); } // ToneGenerator: AY8910::ToneGenerator::ToneGenerator() : vibratoCount(0), detuneCount(0) { } int AY8910::ToneGenerator::getDetune(AY8910& ay8910) { int result = 0; float vibPerc = ay8910.vibratoPercent.getDouble(); if (vibPerc != 0.0f) { int vibratoPeriod = int( NATIVE_FREQ_FLOAT / float(ay8910.vibratoFrequency.getDouble())); vibratoCount += period; vibratoCount %= vibratoPeriod; result += int( sinf((float(2 * M_PI) * vibratoCount) / vibratoPeriod) * vibPerc * 0.01f * period); } float detunePerc = ay8910.detunePercent.getDouble(); if (detunePerc != 0.0f) { float detunePeriod = NATIVE_FREQ_FLOAT / float(ay8910.detuneFrequency.getDouble()); detuneCount += period; float noiseIdx = detuneCount / detunePeriod; float noise = noiseValue( noiseIdx) + noiseValue(2.0f * noiseIdx) / 2.0f; result += int(noise * detunePerc * 0.01f * period); } return std::min(result, period - 1); } inline void AY8910::ToneGenerator::advance(int duration) { assert(count < period); count += duration; if (count >= period) { // Calculate number of output transitions. int cycles = count / period; count -= period * cycles; // equivalent to count %= period; output ^= cycles & 1; } } inline void AY8910::ToneGenerator::doNextEvent(bool doDetune, AY8910& ay8910) { if (unlikely(doDetune)) { count = getDetune(ay8910); } else { count = 0; } output ^= 1; } // NoiseGenerator: AY8910::NoiseGenerator::NoiseGenerator() { reset(); } inline void AY8910::NoiseGenerator::reset() { Generator::reset(1); random = 1; } inline void AY8910::NoiseGenerator::doNextEvent() { count = 0; // noise output changes when (bit1 ^ bit0) == 1 output ^= ((random + 1) & 2) >> 1; // The Random Number Generator of the 8910 is a 17-bit shift register. // The input to the shift register is bit0 XOR bit2 (bit0 is the // output). // The following is a fast way to compute bit 17 = bit0^bit2. // Instead of doing all the logic operations, we only check bit 0, // relying on the fact that after two shifts of the register, what now // is bit 2 will become bit 0, and will invert, if necessary, bit 16, // which previously was bit 18. // Note: On Pentium 4, the "if" causes trouble in the pipeline. // After all this is pseudo-random and therefore a nightmare // for branch prediction. // A bit more calculation without a branch is faster. // Without the "if", the transformation described above still // speeds up the code, because the same "random & N" // subexpression appears twice (also when doing multiple cycles // in one go, see "advance" method). // TODO: Benchmark on other modern CPUs. //if (random & 1) random ^= 0x28000; //random >>= 1; random = (random >> 1) ^ ((random & 1) << 14) ^ ((random & 1) << 16); } inline void AY8910::NoiseGenerator::advance(int duration) { assert(count < period); count += duration; int cycles = count / period; count -= cycles * period; // equivalent to count %= period // See advanceToFlip for explanation of noise algorithm. for (; cycles >= 4405; cycles -= 4405) { random ^= (random >> 10) ^ ((random & 0x003FF) << 5) ^ ((random & 0x003FF) << 7); } for (; cycles >= 291; cycles -= 291) { random ^= (random >> 6) ^ ((random & 0x3F) << 9) ^ ((random & 0x3F) << 11); } for (; cycles >= 15; cycles -= 15) { random = (random & 0x07FFF) ^ (random >> 15) ^ ((random & 0x07FFF) << 2); } while (cycles--) { random = (random >> 1) ^ ((random & 1) << 14) ^ ((random & 1) << 16); } output = random & 1; } // Amplitude: static bool checkAY8910(const DeviceConfig& config) { string type = StringOp::toLower(config.getChildData("type", "ay8910")); if (type == "ay8910") { return true; } else if (type == "ym2149") { return false; } else { throw FatalError("Unknown PSG type: " + type); } } AY8910::Amplitude::Amplitude(const DeviceConfig& config) : isAY8910(checkAY8910(config)) { vol[0] = vol[1] = vol[2] = 0; envChan[0] = false; envChan[1] = false; envChan[2] = false; setMasterVolume(32768); } const unsigned* AY8910::Amplitude::getEnvVolTable() const { return envVolTable; } inline unsigned AY8910::Amplitude::getVolume(unsigned chan) const { assert(!followsEnvelope(chan)); return vol[chan]; } inline void AY8910::Amplitude::setChannelVolume(unsigned chan, unsigned value) { envChan[chan] = (value & 0x10) != 0; vol[chan] = volTable[value & 0x0F]; } inline void AY8910::Amplitude::setMasterVolume(int volume) { // Calculate the volume->voltage conversion table. // The AY-3-8910 has 16 levels, in a logarithmic scale (3dB per step). // YM2149 has 32 levels, the 16 extra levels are only used for envelope // volumes float out = volume; // avoid clipping float factor = powf(0.5f, 0.25f); // 1/sqrt(sqrt(2)) ~= 1/(1.5dB) for (int i = 31; i > 0; --i) { envVolTable[i] = unsigned(out + 0.5f); // round to nearest; out *= factor; } envVolTable[0] = 0; volTable[0] = 0; for (int i = 1; i < 16; ++i) { volTable[i] = envVolTable[2 * i + 1]; } if (isAY8910) { // only 16 envelope steps, duplicate every step envVolTable[1] = 0; for (int i = 2; i < 32; i += 2) { envVolTable[i] = envVolTable[i + 1]; } } } inline bool AY8910::Amplitude::followsEnvelope(unsigned chan) const { return envChan[chan]; } // Envelope: // AY8910 and YM2149 behave different here: // YM2149 envelope goes twice as fast and has twice as many levels. Here // we implement the YM2149 behaviour, but to get the AY8910 behaviour we // repeat every level twice in the envVolTable inline AY8910::Envelope::Envelope(const unsigned* envVolTable_) { envVolTable = envVolTable_; period = 1; count = 0; step = 0; attack = 0; hold = false; alternate = false; holding = false; } inline void AY8910::Envelope::reset() { count = 0; } inline void AY8910::Envelope::setPeriod(int value) { // twice as fast as AY8910 // see also Generator::setPeriod() period = std::max(1, 2 * value); count = std::min(count, period - 1); } inline unsigned AY8910::Envelope::getVolume() const { return envVolTable[step ^ attack]; } inline void AY8910::Envelope::setShape(unsigned shape) { // do 32 steps for both AY8910 and YM2149 /* envelope shapes: C AtAlH 0 0 x x \___ 0 1 x x /___ 1 0 0 0 \\\\ 1 0 0 1 \___ 1 0 1 0 \/\/ 1 0 1 1 \ 1 1 0 0 //// 1 1 0 1 / 1 1 1 0 /\/\ 1 1 1 1 /___ */ attack = (shape & 0x04) ? 0x1F : 0x00; if ((shape & 0x08) == 0) { // If Continue = 0, map the shape to the equivalent one // which has Continue = 1. hold = true; alternate = attack != 0; } else { hold = (shape & 0x01) != 0; alternate = (shape & 0x02) != 0; } count = 0; step = 0x1F; holding = false; } inline bool AY8910::Envelope::isChanging() const { return !holding; } inline void AY8910::Envelope::doSteps(int steps) { // For best performance callers should check upfront whether // isChanging() == true // Though we can't assert on it because the condition might change // in the inner loop(s) of generateChannels(). //assert(!holding); if (holding) return; step -= steps; // Check current envelope position. if (step < 0) { if (hold) { if (alternate) attack ^= 0x1F; holding = true; step = 0; } else { // If step has looped an odd number of times // (usually 1), invert the output. if (alternate && (step & 0x10)) { attack ^= 0x1F; } step &= 0x1F; } } } inline void AY8910::Envelope::advance(int duration) { assert(count < period); count += duration * 2; if (count >= period) { int steps = count / period; count -= steps * period; // equivalent to count %= period; doSteps(steps); } } inline void AY8910::Envelope::doNextEvent() { count = 0; doSteps(period == 1 ? 2 : 1); } inline unsigned AY8910::Envelope::getNextEventTime() const { assert(count < period); return (period - count + 1) / 2; } inline void AY8910::Envelope::advanceFast(unsigned duration) { count += 2 * duration; assert(count < period); } // AY8910 main class: AY8910::AY8910(const std::string& name, AY8910Periphery& periphery_, const DeviceConfig& config, EmuTime::param time) : ResampledSoundDevice(config.getMotherBoard(), name, "PSG", 3) , periphery(periphery_) , debuggable(config.getMotherBoard(), getName()) , vibratoPercent( config.getCommandController(), getName() + "_vibrato_percent", "controls strength of vibrato effect", 0.0, 0.0, 10.0) , vibratoFrequency( config.getCommandController(), getName() + "_vibrato_frequency", "frequency of vibrato effect in Hertz", 5, 1.0, 10.0) , detunePercent( config.getCommandController(), getName() + "_detune_percent", "controls strength of detune effect", 0.0, 0.0, 10.0) , detuneFrequency( config.getCommandController(), getName() + "_detune_frequency", "frequency of detune effect in Hertz", 5.0, 1.0, 100.0) , directionsCallback( config.getGlobalSettings().getInvalidPsgDirectionsSetting()) , amplitude(config) , envelope(amplitude.getEnvVolTable()) , isAY8910(checkAY8910(config)) { // (lazily) initialize detune stuff detuneInitialized = false; update(vibratoPercent); vibratoPercent.attach(*this); detunePercent .attach(*this); // make valgrind happy memset(regs, 0, sizeof(regs)); setInputRate(NATIVE_FREQ_INT); reset(time); registerSound(config); } AY8910::~AY8910() { unregisterSound(); vibratoPercent.detach(*this); detunePercent .detach(*this); } void AY8910::reset(EmuTime::param time) { // Reset generators and envelope. for (auto& t : tone) t.reset(0); noise.reset(); envelope.reset(); // Reset registers and values derived from them. for (unsigned reg = 0; reg <= 15; ++reg) { wrtReg(reg, 0, time); } } byte AY8910::readRegister(unsigned reg, EmuTime::param time) { assert(reg <= 15); switch (reg) { case AY_PORTA: if (!(regs[AY_ENABLE] & PORT_A_DIRECTION)) { // input regs[reg] = periphery.readA(time); } break; case AY_PORTB: if (!(regs[AY_ENABLE] & PORT_B_DIRECTION)) { // input regs[reg] = periphery.readB(time); } break; } // TODO some AY8910 models have 1F as mask for registers 1, 3, 5 static const byte regMask[16] = { 0xff, 0x0f, 0xff, 0x0f, 0xff, 0x0f, 0x1f, 0xff, 0x1f, 0x1f ,0x1f, 0xff, 0xff, 0x0f, 0xff, 0xff }; return isAY8910 ? regs[reg] & regMask[reg] : regs[reg]; } byte AY8910::peekRegister(unsigned reg, EmuTime::param time) const { assert(reg <= 15); switch (reg) { case AY_PORTA: if (!(regs[AY_ENABLE] & PORT_A_DIRECTION)) { // input return periphery.readA(time); } break; case AY_PORTB: if (!(regs[AY_ENABLE] & PORT_B_DIRECTION)) { // input return periphery.readB(time); } break; } return regs[reg]; } void AY8910::writeRegister(unsigned reg, byte value, EmuTime::param time) { assert(reg <= 15); if ((reg < AY_PORTA) && (reg == AY_ESHAPE || regs[reg] != value)) { // Update the output buffer before changing the register. updateStream(time); } wrtReg(reg, value, time); } void AY8910::wrtReg(unsigned reg, byte value, EmuTime::param time) { // Warn/force port directions if (reg == AY_ENABLE) { if (value & PORT_A_DIRECTION) { directionsCallback.execute(); } // portA -> input // portB -> output value = (value & ~PORT_A_DIRECTION) | PORT_B_DIRECTION; } // Note: unused bits are stored as well; they can be read back. byte oldValue = regs[reg]; regs[reg] = value; switch (reg) { case AY_AFINE: case AY_ACOARSE: case AY_BFINE: case AY_BCOARSE: case AY_CFINE: case AY_CCOARSE: tone[reg / 2].setPeriod(regs[reg & ~1] + 256 * (regs[reg | 1] & 0x0F)); break; case AY_NOISEPER: // half the frequency of tone generation noise.setPeriod(2 * (value & 0x1F)); break; case AY_AVOL: case AY_BVOL: case AY_CVOL: amplitude.setChannelVolume(reg - AY_AVOL, value); break; case AY_EFINE: case AY_ECOARSE: // also half the frequency of tone generation, but handled // inside Envelope::setPeriod() envelope.setPeriod(regs[AY_EFINE] + 256 * regs[AY_ECOARSE]); break; case AY_ESHAPE: envelope.setShape(value); break; case AY_ENABLE: if ((value & PORT_A_DIRECTION) && !(oldValue & PORT_A_DIRECTION)) { // Changed from input to output. periphery.writeA(regs[AY_PORTA], time); } if ((value & PORT_B_DIRECTION) && !(oldValue & PORT_B_DIRECTION)) { // Changed from input to output. periphery.writeB(regs[AY_PORTB], time); } break; case AY_PORTA: if (regs[AY_ENABLE] & PORT_A_DIRECTION) { // output periphery.writeA(value, time); } break; case AY_PORTB: if (regs[AY_ENABLE] & PORT_B_DIRECTION) { // output periphery.writeB(value, time); } break; } } static void addFill(int*& buf, int val, unsigned num) { // Note: in the past we tried to optimize this by always producing // a multiple of 4 output values. In the general case a sounddevice is // allowed to do this, but only at the end of the soundbuffer. This // method can also be called in the middle of a buffer (so multiple // times per buffer), in such case it does go wrong. assert(num > 0); #ifdef __arm__ asm volatile ( "subs %[num],%[num],#4\n\t" "bmi 1f\n" "0:\n\t" "ldmia %[buf],{r3-r6}\n\t" "add r3,r3,%[val]\n\t" "add r4,r4,%[val]\n\t" "add r5,r5,%[val]\n\t" "add r6,r6,%[val]\n\t" "stmia %[buf]!,{r3-r6}\n\t" "subs %[num],%[num],#4\n\t" "bpl 0b\n" "1:\n\t" "tst %[num],#2\n\t" "beq 2f\n\t" "ldmia %[buf],{r3-r4}\n\t" "add r3,r3,%[val]\n\t" "add r4,r4,%[val]\n\t" "stmia %[buf]!,{r3-r4}\n" "2:\n\t" "tst %[num],#1\n\t" "beq 3f\n\t" "ldr r3,[%[buf]]\n\t" "add r3,r3,%[val]\n\t" "str r3,[%[buf]],#4\n" "3:\n\t" : [buf] "=r" (buf) , [num] "=r" (num) : "[buf]" (buf) , [val] "r" (val) , "[num]" (num) : "memory", "r3","r4","r5","r6" ); return; #endif do { *buf++ += val; } while (--num); } void AY8910::generateChannels(int** bufs, unsigned length) { // Disable channels with volume 0: since the sample value doesn't matter, // we can use the fastest path. unsigned chanEnable = regs[AY_ENABLE]; for (unsigned chan = 0; chan < 3; ++chan) { if ((!amplitude.followsEnvelope(chan) && (amplitude.getVolume(chan) == 0)) || (amplitude.followsEnvelope(chan) && !envelope.isChanging() && (envelope.getVolume() == 0))) { bufs[chan] = nullptr; tone[chan].advance(length); chanEnable |= 0x09 << chan; } } // Noise disabled on all channels? if ((chanEnable & 0x38) == 0x38) { noise.advance(length); } // Calculate samples. // The 8910 has three outputs, each output is the mix of one of the // three tone generators and of the (single) noise generator. The two // are mixed BEFORE going into the DAC. The formula to mix each channel // is: // (ToneOn | ToneDisable) & (NoiseOn | NoiseDisable), // where ToneOn and NoiseOn are the current generator state // and ToneDisable and NoiseDisable come from the enable reg. // Note that this means that if both tone and noise are disabled, the // output is 1, not 0, and can be modulated by changing the volume. bool envelopeUpdated = false; Envelope initialEnvelope = envelope; NoiseGenerator initialNoise = noise; for (unsigned chan = 0; chan < 3; ++chan, chanEnable >>= 1) { int* buf = bufs[chan]; if (!buf) continue; ToneGenerator& t = tone[chan]; if (envelope.isChanging() && amplitude.followsEnvelope(chan)) { envelopeUpdated = true; envelope = initialEnvelope; if ((chanEnable & 0x09) == 0x08) { // no noise, square wave: alternating between 0 and 1. unsigned val = t.getOutput() * envelope.getVolume(); unsigned remaining = length; unsigned nextE = envelope.getNextEventTime(); unsigned nextT = t.getNextEventTime(); while ((nextT <= remaining) || (nextE <= remaining)) { if (nextT < nextE) { addFill(buf, val, nextT); remaining -= nextT; nextE -= nextT; envelope.advanceFast(nextT); t.doNextEvent(doDetune, *this); nextT = t.getNextEventTime(); } else if (nextE < nextT) { addFill(buf, val, nextE); remaining -= nextE; nextT -= nextE; t.advanceFast(nextE); envelope.doNextEvent(); nextE = envelope.getNextEventTime(); } else { assert(nextT == nextE); addFill(buf, val, nextT); remaining -= nextT; t.doNextEvent(doDetune, *this); nextT = t.getNextEventTime(); envelope.doNextEvent(); nextE = envelope.getNextEventTime(); } val = t.getOutput() * envelope.getVolume(); } if (remaining) { // last interval (without events) addFill(buf, val, remaining); t.advanceFast(remaining); envelope.advanceFast(remaining); } } else if ((chanEnable & 0x09) == 0x09) { // no noise, channel disabled: always 1. unsigned val = envelope.getVolume(); unsigned remaining = length; unsigned next = envelope.getNextEventTime(); while (next <= remaining) { addFill(buf, val, next); remaining -= next; envelope.doNextEvent(); val = envelope.getVolume(); next = envelope.getNextEventTime(); } if (remaining) { // last interval (without events) addFill(buf, val, remaining); envelope.advanceFast(remaining); } t.advance(length); } else if ((chanEnable & 0x09) == 0x00) { // noise enabled, tone enabled noise = initialNoise; unsigned val = noise.getOutput() * t.getOutput() * envelope.getVolume(); unsigned remaining = length; unsigned nextT = t.getNextEventTime(); unsigned nextN = noise.getNextEventTime(); unsigned nextE = envelope.getNextEventTime(); unsigned next = std::min(std::min(nextT, nextN), nextE); while (next <= remaining) { addFill(buf, val, next); remaining -= next; nextT -= next; nextN -= next; nextE -= next; if (nextT) { t.advanceFast(next); } else { t.doNextEvent(doDetune, *this); nextT = t.getNextEventTime(); } if (nextN) { noise.advanceFast(next); } else { noise.doNextEvent(); nextN = noise.getNextEventTime(); } if (nextE) { envelope.advanceFast(next); } else { envelope.doNextEvent(); nextE = envelope.getNextEventTime(); } next = std::min(std::min(nextT, nextN), nextE); val = noise.getOutput() * t.getOutput() * envelope.getVolume(); } if (remaining) { // last interval (without events) addFill(buf, val, remaining); t.advanceFast(remaining); noise.advanceFast(remaining); envelope.advanceFast(remaining); } } else { // noise enabled, tone disabled noise = initialNoise; unsigned val = noise.getOutput() * envelope.getVolume(); unsigned remaining = length; unsigned nextE = envelope.getNextEventTime(); unsigned nextN = noise.getNextEventTime(); while ((nextN <= remaining) || (nextE <= remaining)) { if (nextN < nextE) { addFill(buf, val, nextN); remaining -= nextN; nextE -= nextN; envelope.advanceFast(nextN); noise.doNextEvent(); nextN = noise.getNextEventTime(); } else if (nextE < nextN) { addFill(buf, val, nextE); remaining -= nextE; nextN -= nextE; noise.advanceFast(nextE); envelope.doNextEvent(); nextE = envelope.getNextEventTime(); } else { assert(nextN == nextE); addFill(buf, val, nextN); remaining -= nextN; noise.doNextEvent(); nextN = noise.getNextEventTime(); envelope.doNextEvent(); nextE = envelope.getNextEventTime(); } val = noise.getOutput() * envelope.getVolume(); } if (remaining) { // last interval (without events) addFill(buf, val, remaining); noise.advanceFast(remaining); envelope.advanceFast(remaining); } t.advance(length); } } else { // no (changing) envelope on this channel unsigned volume = amplitude.followsEnvelope(chan) ? envelope.getVolume() : amplitude.getVolume(chan); if ((chanEnable & 0x09) == 0x08) { // no noise, square wave: alternating between 0 and 1. unsigned val = t.getOutput() * volume; unsigned remaining = length; unsigned next = t.getNextEventTime(); while (next <= remaining) { addFill(buf, val, next); val ^= volume; remaining -= next; t.doNextEvent(doDetune, *this); next = t.getNextEventTime(); } if (remaining) { // last interval (without events) addFill(buf, val, remaining); t.advanceFast(remaining); } } else if ((chanEnable & 0x09) == 0x09) { // no noise, channel disabled: always 1. addFill(buf, volume, length); t.advance(length); } else if ((chanEnable & 0x09) == 0x00) { // noise enabled, tone enabled noise = initialNoise; unsigned val1 = t.getOutput() * volume; unsigned val2 = val1 * noise.getOutput(); unsigned remaining = length; unsigned nextN = noise.getNextEventTime(); unsigned nextT = t.getNextEventTime(); while ((nextN <= remaining) || (nextT <= remaining)) { if (nextT < nextN) { addFill(buf, val2, nextT); remaining -= nextT; nextN -= nextT; noise.advanceFast(nextT); t.doNextEvent(doDetune, *this); nextT = t.getNextEventTime(); val1 ^= volume; val2 = val1 * noise.getOutput(); } else if (nextN < nextT) { addFill(buf, val2, nextN); remaining -= nextN; nextT -= nextN; t.advanceFast(nextN); noise.doNextEvent(); nextN = noise.getNextEventTime(); val2 = val1 * noise.getOutput(); } else { assert(nextT == nextN); addFill(buf, val2, nextT); remaining -= nextT; t.doNextEvent(doDetune, *this); nextT = t.getNextEventTime(); noise.doNextEvent(); nextN = noise.getNextEventTime(); val1 ^= volume; val2 = val1 * noise.getOutput(); } } if (remaining) { // last interval (without events) addFill(buf, val2, remaining); t.advanceFast(remaining); noise.advanceFast(remaining); } } else { // noise enabled, tone disabled noise = initialNoise; unsigned remaining = length; unsigned val = noise.getOutput() * volume; unsigned next = noise.getNextEventTime(); while (next <= remaining) { addFill(buf, val, next); remaining -= next; noise.doNextEvent(); val = noise.getOutput() * volume; next = noise.getNextEventTime(); } if (remaining) { // last interval (without events) addFill(buf, val, remaining); noise.advanceFast(remaining); } t.advance(length); } } } // Envelope not yet updated? if (envelope.isChanging() && !envelopeUpdated) { envelope.advance(length); } } void AY8910::update(const Setting& setting) { if ((&setting == &vibratoPercent) || (&setting == &detunePercent)) { doDetune = (vibratoPercent.getDouble() != 0) || (detunePercent .getDouble() != 0); if (doDetune && !detuneInitialized) { detuneInitialized = true; initDetune(); } } else { ResampledSoundDevice::update(setting); } } // Debuggable AY8910::Debuggable::Debuggable(MSXMotherBoard& motherBoard, const string& name) : SimpleDebuggable(motherBoard, name + " regs", "PSG", 0x10) { } byte AY8910::Debuggable::read(unsigned address, EmuTime::param time) { auto& ay8910 = OUTER(AY8910, debuggable); return ay8910.readRegister(address, time); } void AY8910::Debuggable::write(unsigned address, byte value, EmuTime::param time) { auto& ay8910 = OUTER(AY8910, debuggable); return ay8910.writeRegister(address, value, time); } template void AY8910::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("toneGenerators", tone); ar.serialize("noiseGenerator", noise); ar.serialize("envelope", envelope); ar.serialize("registers", regs); // amplitude if (ar.isLoader()) { for (int i = 0; i < 3; ++i) { amplitude.setChannelVolume(i, regs[i + AY_AVOL]); } } } INSTANTIATE_SERIALIZE_METHODS(AY8910); template void AY8910::Generator::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("period", period); ar.serialize("count", count); ar.serialize("output", output); } INSTANTIATE_SERIALIZE_METHODS(AY8910::Generator); template void AY8910::ToneGenerator::serialize(Archive& ar, unsigned version) { ar.template serializeInlinedBase(*this, version); ar.serialize("vibratoCount", vibratoCount); ar.serialize("detuneCount", detuneCount); } INSTANTIATE_SERIALIZE_METHODS(AY8910::ToneGenerator); template void AY8910::NoiseGenerator::serialize(Archive& ar, unsigned version) { ar.template serializeInlinedBase(*this, version); ar.serialize("random", random); } INSTANTIATE_SERIALIZE_METHODS(AY8910::NoiseGenerator); template void AY8910::Envelope::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("period", period); ar.serialize("count", count); ar.serialize("step", step); ar.serialize("attack", attack); ar.serialize("hold", hold); ar.serialize("alternate", alternate); ar.serialize("holding", holding); } INSTANTIATE_SERIALIZE_METHODS(AY8910::Envelope); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/AY8910.hh000066400000000000000000000113551257557151200175370ustar00rootroot00000000000000#ifndef AY8910_HH #define AY8910_HH #include "ResampledSoundDevice.hh" #include "FloatSetting.hh" #include "SimpleDebuggable.hh" #include "TclCallback.hh" #include "openmsx.hh" namespace openmsx { class AY8910Periphery; class DeviceConfig; /** This class implements the AY-3-8910 sound chip. * Only the AY-3-8910 is emulated, no surrounding hardware, * use the class AY8910Periphery to connect peripherals. */ class AY8910 final : public ResampledSoundDevice { public: AY8910(const std::string& name, AY8910Periphery& periphery, const DeviceConfig& config, EmuTime::param time); ~AY8910(); byte readRegister(unsigned reg, EmuTime::param time); byte peekRegister(unsigned reg, EmuTime::param time) const; void writeRegister(unsigned reg, byte value, EmuTime::param time); void reset(EmuTime::param time); template void serialize(Archive& ar, unsigned version); private: class Generator { public: inline void reset(unsigned output); inline void setPeriod(int value); /** Gets the current output of this generator. */ inline unsigned getOutput() const; inline unsigned getNextEventTime() const; inline void advanceFast(unsigned duration); template void serialize(Archive& ar, unsigned version); protected: Generator(); /** Time between output steps. * For tones, this is half the period of the square wave. * For noise, this is the time before the random generator produces * its next output. */ int period; /** Time passed in this period. * Usually count will be smaller than period, but when the period * was recently changed this might not be the case. */ int count; /** Current state of the wave. * For tones, this is 0 or 1. */ unsigned output; }; class ToneGenerator : public Generator { public: ToneGenerator(); /** Advance tone generator several steps in time. * @param duration Length of interval to simulate. */ inline void advance(int duration); inline void doNextEvent(bool doDetune, AY8910& ay8910); template void serialize(Archive& ar, unsigned version); private: int getDetune(AY8910& ay8910); /** Time passed since start of vibrato cycle. */ unsigned vibratoCount; unsigned detuneCount; // disallow copy (can't use noncopyable utility for some reason) ToneGenerator(const ToneGenerator&); const ToneGenerator& operator=(const ToneGenerator&); }; class NoiseGenerator : public Generator { public: NoiseGenerator(); inline void reset(); /** Advance noise generator several steps in time. * @param duration Length of interval to simulate. */ inline void advance(int duration); inline void doNextEvent(); template void serialize(Archive& ar, unsigned version); private: int random; }; class Amplitude { public: explicit Amplitude(const DeviceConfig& config); const unsigned* getEnvVolTable() const; inline unsigned getVolume(unsigned chan) const; inline void setChannelVolume(unsigned chan, unsigned value); inline void setMasterVolume(int volume); inline bool followsEnvelope(unsigned chan) const; private: unsigned volTable[16]; unsigned envVolTable[32]; unsigned vol[3]; bool envChan[3]; const bool isAY8910; }; class Envelope { public: explicit inline Envelope(const unsigned* envVolTable); inline void reset(); inline void setPeriod(int value); inline void setShape(unsigned shape); inline bool isChanging() const; inline void advance(int duration); inline unsigned getVolume() const; inline unsigned getNextEventTime() const; inline void advanceFast(unsigned duration); inline void doNextEvent(); template void serialize(Archive& ar, unsigned version); private: inline void doSteps(int steps); const unsigned* envVolTable; int period; int count; int step; int attack; bool hold, alternate, holding; }; // SoundDevice void generateChannels(int** bufs, unsigned num) override; // Observer void update(const Setting& setting) override; void wrtReg(unsigned reg, byte value, EmuTime::param time); AY8910Periphery& periphery; struct Debuggable final : SimpleDebuggable { Debuggable(MSXMotherBoard& motherBoard, const std::string& name); byte read(unsigned address, EmuTime::param time) override; void write(unsigned address, byte value, EmuTime::param time) override; } debuggable; FloatSetting vibratoPercent; FloatSetting vibratoFrequency; FloatSetting detunePercent; FloatSetting detuneFrequency; TclCallback directionsCallback; ToneGenerator tone[3]; NoiseGenerator noise; Amplitude amplitude; Envelope envelope; byte regs[16]; const bool isAY8910; bool doDetune; bool detuneInitialized; }; } // namespace openmsx #endif // AY8910_HH openMSX-RELEASE_0_12_0/src/sound/AY8910Periphery.cc000066400000000000000000000007071257557151200214140ustar00rootroot00000000000000#include "AY8910Periphery.hh" namespace openmsx { byte AY8910Periphery::readA(EmuTime::param /*time*/) { return 0xFF; // unused bits are 1 } byte AY8910Periphery::readB(EmuTime::param /*time*/) { return 0xFF; // unused bits are 1 } void AY8910Periphery::writeA(byte /*value*/, EmuTime::param /*time*/) { // nothing connected } void AY8910Periphery::writeB(byte /*value*/, EmuTime::param /*time*/) { // nothing connected } } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/AY8910Periphery.hh000066400000000000000000000025671257557151200214340ustar00rootroot00000000000000#ifndef AY8910PERIPHERY_HH #define AY8910PERIPHERY_HH #include "EmuTime.hh" #include "openmsx.hh" namespace openmsx { /** Models the general purpose I/O ports of the AY8910. * The default implementation handles an empty periphery: * nothing is connected to the I/O ports. * This class can be overridden to connect peripherals. */ class AY8910Periphery { public: /** Reads the state of the peripheral on port A. * Since the AY8910 doesn't have control lines for the I/O ports, * a peripheral is not aware that it is read, which means that * "peek" and "read" are equivalent. * @param time The moment in time the peripheral's state is read. * On subsequent calls, the time will always be increasing. * @return the value read; unconnected bits should be 1 */ virtual byte readA(EmuTime::param time); /** Similar to readA, but reads port B. */ virtual byte readB(EmuTime::param time); /** Writes to the peripheral on port A. * @param value The value to write. * @param time The moment in time the value is written. * On subsequent calls, the time will always be increasing. */ virtual void writeA(byte value, EmuTime::param time); /** Similar to writeA, but writes port B. */ virtual void writeB(byte value, EmuTime::param time); protected: AY8910Periphery() {} ~AY8910Periphery() {} }; } // namespace openmsx #endif // AY8910PERIPHERY_HH openMSX-RELEASE_0_12_0/src/sound/AudioInputConnector.cc000066400000000000000000000020701257557151200226200ustar00rootroot00000000000000#include "AudioInputConnector.hh" #include "DummyAudioInputDevice.hh" #include "AudioInputDevice.hh" #include "checked_cast.hh" #include "serialize.hh" #include "memory.hh" namespace openmsx { AudioInputConnector::AudioInputConnector(PluggingController& pluggingController, string_ref name) : Connector(pluggingController, name, make_unique()) { } const std::string AudioInputConnector::getDescription() const { return "Audio input connector"; } string_ref AudioInputConnector::getClass() const { return "Audio Input Port"; } short AudioInputConnector::readSample(EmuTime::param time) const { return getPluggedAudioDev().readSample(time); } AudioInputDevice& AudioInputConnector::getPluggedAudioDev() const { return *checked_cast(&getPlugged()); } template void AudioInputConnector::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); } INSTANTIATE_SERIALIZE_METHODS(AudioInputConnector); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/AudioInputConnector.hh000066400000000000000000000011541257557151200226340ustar00rootroot00000000000000#ifndef AUDIOINPUTCONNECTOR_HH #define AUDIOINPUTCONNECTOR_HH #include "Connector.hh" namespace openmsx { class AudioInputDevice; class AudioInputConnector final : public Connector { public: AudioInputConnector(PluggingController& pluggingController, string_ref name); AudioInputDevice& getPluggedAudioDev() const; // Connector const std::string getDescription() const final override; string_ref getClass() const final override; short readSample(EmuTime::param time) const; template void serialize(Archive& ar, unsigned version); }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/AudioInputDevice.cc000066400000000000000000000002571257557151200220720ustar00rootroot00000000000000#include "AudioInputDevice.hh" using std::string; namespace openmsx { string_ref AudioInputDevice::getClass() const { return "Audio Input Port"; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/AudioInputDevice.hh000066400000000000000000000005131257557151200220770ustar00rootroot00000000000000#ifndef AUDIOINPUTDEVICE_HH #define AUDIOINPUTDEVICE_HH #include "Pluggable.hh" namespace openmsx { class AudioInputDevice : public Pluggable { public: /** * Read wave data */ virtual short readSample(EmuTime::param time) = 0; // Pluggable string_ref getClass() const final override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/BlipBuffer.cc000066400000000000000000000052231257557151200207070ustar00rootroot00000000000000#include "BlipBuffer.hh" #include "likely.hh" #include #include #include namespace openmsx { // This defines the table // static const int impulses[BLIP_RES][BLIP_IMPULSE_WIDTH] = { ... }; #include "BlipTable.ii" BlipBuffer::BlipBuffer() { offset = 0; accum = 0; availSamp = 0; memset(buffer, 0, sizeof(buffer)); } void BlipBuffer::addDelta(TimeIndex time, int delta) { unsigned tmp = time.toInt() + BLIP_IMPULSE_WIDTH; assert(tmp < BUFFER_SIZE); availSamp = std::max(availSamp, tmp); unsigned phase = time.fractAsInt(); unsigned ofst = time.toInt() + offset; if (likely((ofst + BLIP_IMPULSE_WIDTH) <= BUFFER_SIZE)) { for (int i = 0; i < BLIP_IMPULSE_WIDTH; ++i) { buffer[ofst + i] += impulses[phase][i] * delta; } } else { for (int i = 0; i < BLIP_IMPULSE_WIDTH; ++i) { buffer[(ofst + i) & BUFFER_MASK] += impulses[phase][i] * delta; } } } static const int SAMPLE_SHIFT = BLIP_SAMPLE_BITS - 16; static const int BASS_SHIFT = 9; template void BlipBuffer::readSamplesHelper(int* __restrict out, unsigned samples) __restrict { assert((offset + samples) <= BUFFER_SIZE); int acc = accum; unsigned ofst = offset; for (unsigned i = 0; i < samples; ++i) { out[i * PITCH] = acc >> SAMPLE_SHIFT; // Note: the following has different rounding behaviour // for positive and negative numbers! The original // code used 'acc / (1<< BASS_SHIFT)' to avoid this, // but it generates less efficient code. acc -= (acc >> BASS_SHIFT); acc += buffer[ofst]; buffer[ofst] = 0; ++ofst; } accum = acc; offset = ofst & BUFFER_MASK; } template bool BlipBuffer::readSamples(int* __restrict out, unsigned samples) { if (availSamp <= 0) { #ifdef DEBUG // buffer contains all zeros (only check this in debug mode) for (unsigned i = 0; i < BUFFER_SIZE; ++i) { assert(buffer[i] == 0); } #endif if (accum == 0) { // muted return false; } int acc = accum; for (unsigned i = 0; i < samples; ++i) { out[i * PITCH] = acc >> SAMPLE_SHIFT; // See note about rounding above. acc -= (acc >> BASS_SHIFT); acc -= (acc > 0) ? 1 : 0; // make sure acc eventually goes to zero } accum = acc; } else { availSamp -= samples; unsigned t1 = std::min(samples, BUFFER_SIZE - offset); readSamplesHelper(out, t1); if (t1 < samples) { assert(offset == 0); unsigned t2 = samples - t1; assert(t2 < BUFFER_SIZE); readSamplesHelper(&out[t1 * PITCH], t2); } assert(offset < BUFFER_SIZE); } return true; } template bool BlipBuffer::readSamples<1>(int*, unsigned); template bool BlipBuffer::readSamples<2>(int*, unsigned); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/BlipBuffer.hh000066400000000000000000000016731257557151200207260ustar00rootroot00000000000000// Heavily based on: // // Band-limited sound synthesis and buffering // Blip_Buffer 0.4.0 // http://www.slack.net/~ant/ #ifndef BLIPBUFFER_HH #define BLIPBUFFER_HH #include "FixedPoint.hh" namespace openmsx { #include "BlipConfig.hh" class BlipBuffer { public: using TimeIndex = FixedPoint; BlipBuffer(); // Update amplitude of waveform at given time. Time is in output sample // units and since the last time readSamples() was called. void addDelta(TimeIndex time, int delta); // Read the given amount of samples into destination buffer. template bool readSamples(int* dest, unsigned samples); private: template void readSamplesHelper(int* out, unsigned samples) __restrict; static const unsigned BUFFER_SIZE = 1 << 14; static const unsigned BUFFER_MASK = BUFFER_SIZE - 1; int buffer[BUFFER_SIZE]; unsigned offset; int accum; int availSamp; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/BlipConfig.hh000066400000000000000000000011721257557151200207140ustar00rootroot00000000000000#ifndef BLIPCONFIG_HH #define BLIPCONFIG_HH // Number of bits in phase offset. Fewer than 6 bits (64 phase offsets) results // in noticeable broadband noise when synthesizing high frequency square waves. static const int BLIP_PHASE_BITS = 10; // The input sample stream can only use this many bits out of the available 32 // bits. So 29 bits means the sample values must be in range [-256M, 256M]. static const int BLIP_SAMPLE_BITS = 29; // Number of samples in a (pre-calculated) impulse-response wave-form. static const int BLIP_IMPULSE_WIDTH = 16; // Derived constants static const int BLIP_RES = 1 << BLIP_PHASE_BITS; #endif openMSX-RELEASE_0_12_0/src/sound/BlipTable.ii000066400000000000000000002515631257557151200205530ustar00rootroot00000000000000// This is a generated file. DO NOT EDIT! // The table was generated for the following constants: static_assert(BLIP_PHASE_BITS == 10, "mismatch, regenerate"); static_assert(BLIP_SAMPLE_BITS == 29, "mismatch, regenerate"); static_assert(BLIP_IMPULSE_WIDTH == 16, "mismatch, regenerate"); static const int impulses[BLIP_RES][BLIP_IMPULSE_WIDTH] = { { 3, -25, 94, -229, 428, -688, 1310, 6406, 1310, -688, 428, -229, 94, -25, 3, 0 }, { 3, -25, 94, -229, 427, -686, 1304, 6406, 1316, -689, 428, -229, 94, -25, 3, 0 }, { 3, -25, 94, -229, 427, -684, 1298, 6404, 1323, -691, 428, -229, 94, -24, 3, 0 }, { 3, -25, 94, -229, 426, -682, 1292, 6405, 1329, -693, 429, -229, 93, -24, 3, 0 }, { 3, -25, 94, -229, 426, -681, 1285, 6406, 1335, -694, 429, -229, 93, -24, 3, 0 }, { 3, -25, 94, -229, 425, -679, 1279, 6406, 1341, -696, 430, -229, 93, -24, 3, 0 }, { 3, -25, 95, -229, 425, -677, 1273, 6404, 1348, -698, 430, -229, 93, -24, 3, 0 }, { 3, -25, 95, -229, 424, -676, 1267, 6404, 1354, -699, 431, -229, 93, -24, 3, 0 }, { 3, -26, 95, -229, 424, -674, 1261, 6405, 1360, -701, 431, -229, 93, -24, 3, 0 }, { 3, -26, 95, -229, 423, -672, 1255, 6405, 1366, -702, 432, -229, 93, -24, 2, 0 }, { 3, -26, 95, -229, 423, -671, 1249, 6406, 1373, -704, 432, -229, 92, -24, 2, 0 }, { 4, -26, 95, -229, 422, -669, 1242, 6406, 1379, -706, 432, -229, 92, -23, 2, 0 }, { 4, -26, 95, -229, 422, -667, 1236, 6404, 1385, -707, 433, -229, 92, -23, 2, 0 }, { 4, -26, 95, -229, 421, -666, 1230, 6406, 1391, -709, 433, -229, 92, -23, 2, 0 }, { 4, -26, 96, -229, 421, -664, 1224, 6403, 1398, -711, 434, -229, 92, -23, 2, 0 }, { 4, -26, 96, -229, 420, -662, 1218, 6403, 1404, -712, 434, -229, 92, -23, 2, 0 }, { 4, -26, 96, -229, 420, -660, 1212, 6402, 1410, -714, 435, -229, 92, -23, 2, 0 }, { 4, -26, 96, -229, 419, -659, 1206, 6405, 1416, -716, 435, -229, 91, -23, 2, 0 }, { 4, -27, 96, -229, 419, -657, 1200, 6404, 1423, -717, 435, -229, 91, -23, 2, 0 }, { 4, -27, 96, -229, 418, -655, 1193, 6403, 1429, -719, 436, -229, 91, -22, 2, 1 }, { 4, -27, 96, -229, 418, -654, 1187, 6403, 1435, -720, 436, -229, 91, -22, 2, 1 }, { 4, -27, 96, -229, 417, -652, 1181, 6402, 1442, -722, 437, -229, 91, -22, 2, 1 }, { 4, -27, 96, -229, 417, -650, 1175, 6401, 1448, -724, 437, -228, 91, -22, 2, 1 }, { 4, -27, 97, -229, 416, -648, 1169, 6400, 1454, -725, 438, -228, 91, -22, 1, 1 }, { 4, -27, 97, -229, 416, -647, 1163, 6401, 1461, -727, 438, -228, 90, -22, 1, 1 }, { 4, -27, 97, -228, 415, -645, 1157, 6400, 1467, -728, 438, -228, 90, -22, 1, 1 }, { 4, -27, 97, -228, 415, -643, 1151, 6399, 1473, -730, 439, -228, 90, -22, 1, 1 }, { 4, -27, 97, -228, 414, -641, 1145, 6399, 1480, -732, 439, -228, 90, -22, 1, 1 }, { 4, -28, 97, -228, 413, -640, 1139, 6399, 1486, -733, 440, -228, 90, -21, 1, 1 }, { 4, -28, 97, -228, 413, -638, 1133, 6399, 1492, -735, 440, -228, 90, -21, 1, 1 }, { 4, -28, 97, -228, 412, -636, 1127, 6398, 1499, -736, 440, -228, 90, -21, 1, 1 }, { 4, -28, 97, -228, 412, -634, 1121, 6398, 1505, -738, 441, -228, 89, -21, 1, 1 }, { 4, -28, 97, -228, 411, -633, 1115, 6399, 1511, -739, 441, -228, 89, -21, 1, 1 }, { 4, -28, 98, -228, 411, -631, 1109, 6397, 1518, -741, 441, -228, 89, -21, 1, 1 }, { 4, -28, 98, -228, 410, -629, 1103, 6397, 1524, -743, 442, -228, 89, -21, 1, 1 }, { 4, -28, 98, -228, 410, -627, 1097, 6395, 1531, -744, 442, -228, 89, -21, 1, 1 }, { 5, -28, 98, -228, 409, -626, 1091, 6394, 1537, -746, 443, -228, 89, -20, 1, 1 }, { 5, -28, 98, -228, 409, -624, 1085, 6395, 1543, -747, 443, -228, 88, -20, 0, 1 }, { 5, -29, 98, -228, 408, -622, 1079, 6396, 1550, -749, 443, -228, 88, -20, 0, 1 }, { 5, -29, 98, -228, 407, -620, 1073, 6395, 1556, -750, 444, -228, 88, -20, 0, 1 }, { 5, -29, 98, -228, 407, -618, 1067, 6393, 1563, -752, 444, -227, 88, -20, 0, 1 }, { 5, -29, 98, -228, 406, -617, 1061, 6394, 1569, -753, 444, -227, 88, -20, 0, 1 }, { 5, -29, 98, -228, 406, -615, 1055, 6393, 1575, -755, 445, -227, 88, -20, 0, 1 }, { 5, -29, 99, -228, 405, -613, 1049, 6393, 1582, -757, 445, -227, 87, -20, 0, 1 }, { 5, -29, 99, -227, 405, -611, 1043, 6390, 1588, -758, 445, -227, 87, -19, 0, 1 }, { 5, -29, 99, -227, 404, -610, 1037, 6390, 1595, -760, 446, -227, 87, -19, 0, 1 }, { 5, -29, 99, -227, 403, -608, 1031, 6390, 1601, -761, 446, -227, 87, -19, 0, 1 }, { 5, -29, 99, -227, 403, -606, 1025, 6389, 1608, -763, 446, -227, 87, -19, 0, 1 }, { 5, -29, 99, -227, 402, -604, 1019, 6388, 1614, -764, 447, -227, 87, -19, 0, 1 }, { 5, -30, 99, -227, 402, -602, 1014, 6388, 1621, -766, 447, -227, 86, -19, 0, 1 }, { 5, -30, 99, -227, 401, -601, 1008, 6389, 1627, -767, 447, -227, 86, -19, 0, 1 }, { 5, -30, 99, -227, 400, -599, 1002, 6390, 1633, -769, 448, -227, 86, -19, -1, 1 }, { 5, -30, 99, -227, 400, -597, 996, 6386, 1640, -770, 448, -226, 86, -18, -1, 1 }, { 5, -30, 99, -227, 399, -595, 990, 6387, 1646, -772, 448, -226, 86, -18, -1, 1 }, { 5, -30, 99, -227, 399, -593, 984, 6384, 1653, -773, 449, -226, 86, -18, -1, 1 }, { 5, -30, 100, -227, 398, -592, 978, 6386, 1659, -775, 449, -226, 85, -18, -1, 1 }, { 5, -30, 100, -226, 398, -590, 972, 6382, 1666, -776, 449, -226, 85, -18, -1, 2 }, { 5, -30, 100, -226, 397, -588, 967, 6381, 1672, -778, 450, -226, 85, -18, -1, 2 }, { 5, -30, 100, -226, 396, -586, 961, 6380, 1679, -779, 450, -226, 85, -18, -1, 2 }, { 5, -30, 100, -226, 396, -584, 955, 6380, 1685, -781, 450, -226, 85, -18, -1, 2 }, { 5, -31, 100, -226, 395, -582, 949, 6379, 1692, -782, 451, -226, 84, -17, -1, 2 }, { 5, -31, 100, -226, 394, -581, 943, 6380, 1698, -783, 451, -226, 84, -17, -1, 2 }, { 5, -31, 100, -226, 394, -579, 937, 6378, 1705, -785, 451, -225, 84, -17, -1, 2 }, { 6, -31, 100, -226, 393, -577, 932, 6376, 1711, -786, 451, -225, 84, -17, -1, 2 }, { 6, -31, 100, -226, 393, -575, 926, 6375, 1718, -788, 452, -225, 84, -17, -2, 2 }, { 6, -31, 100, -226, 392, -573, 920, 6375, 1724, -789, 452, -225, 84, -17, -2, 2 }, { 6, -31, 100, -225, 391, -571, 914, 6375, 1731, -791, 452, -225, 83, -17, -2, 2 }, { 6, -31, 100, -225, 391, -570, 908, 6373, 1737, -792, 453, -225, 83, -16, -2, 2 }, { 6, -31, 100, -225, 390, -568, 903, 6372, 1744, -794, 453, -225, 83, -16, -2, 2 }, { 6, -31, 101, -225, 389, -566, 897, 6370, 1751, -795, 453, -225, 83, -16, -2, 2 }, { 6, -31, 101, -225, 389, -564, 891, 6368, 1757, -796, 453, -224, 83, -16, -2, 2 }, { 6, -31, 101, -225, 388, -562, 885, 6368, 1764, -798, 454, -224, 82, -16, -2, 2 }, { 6, -32, 101, -225, 388, -560, 880, 6367, 1770, -799, 454, -224, 82, -16, -2, 2 }, { 6, -32, 101, -225, 387, -559, 874, 6368, 1777, -801, 454, -224, 82, -16, -2, 2 }, { 6, -32, 101, -225, 386, -557, 868, 6368, 1783, -802, 454, -224, 82, -16, -2, 2 }, { 6, -32, 101, -224, 386, -555, 862, 6364, 1790, -804, 455, -224, 82, -15, -2, 2 }, { 6, -32, 101, -224, 385, -553, 857, 6364, 1796, -805, 455, -224, 81, -15, -2, 2 }, { 6, -32, 101, -224, 384, -551, 851, 6363, 1803, -806, 455, -224, 81, -15, -2, 2 }, { 6, -32, 101, -224, 384, -549, 845, 6362, 1810, -808, 455, -223, 81, -15, -3, 2 }, { 6, -32, 101, -224, 383, -548, 840, 6361, 1816, -809, 456, -223, 81, -15, -3, 2 }, { 6, -32, 101, -224, 382, -546, 834, 6361, 1823, -811, 456, -223, 81, -15, -3, 2 }, { 6, -32, 101, -224, 382, -544, 828, 6361, 1829, -812, 456, -223, 80, -15, -3, 2 }, { 6, -32, 101, -224, 381, -542, 823, 6358, 1836, -813, 456, -223, 80, -14, -3, 2 }, { 6, -32, 101, -223, 380, -540, 817, 6356, 1843, -815, 457, -223, 80, -14, -3, 2 }, { 6, -33, 101, -223, 380, -538, 811, 6356, 1849, -816, 457, -223, 80, -14, -3, 2 }, { 6, -33, 102, -223, 379, -536, 806, 6352, 1856, -817, 457, -222, 80, -14, -3, 2 }, { 6, -33, 102, -223, 378, -535, 800, 6355, 1862, -819, 457, -222, 79, -14, -3, 2 }, { 6, -33, 102, -223, 378, -533, 794, 6353, 1869, -820, 457, -222, 79, -14, -3, 2 }, { 6, -33, 102, -223, 377, -531, 789, 6350, 1876, -821, 458, -222, 79, -14, -3, 2 }, { 6, -33, 102, -223, 376, -529, 783, 6350, 1882, -823, 458, -222, 79, -13, -3, 2 }, { 6, -33, 102, -223, 376, -527, 777, 6348, 1889, -824, 458, -222, 79, -13, -3, 2 }, { 6, -33, 102, -222, 375, -525, 772, 6346, 1896, -825, 458, -221, 78, -13, -4, 2 }, { 6, -33, 102, -222, 374, -523, 766, 6346, 1902, -827, 458, -221, 78, -13, -4, 3 }, { 7, -33, 102, -222, 374, -521, 761, 6341, 1909, -828, 459, -221, 78, -13, -4, 3 }, { 7, -33, 102, -222, 373, -520, 755, 6342, 1915, -829, 459, -221, 78, -13, -4, 3 }, { 7, -33, 102, -222, 372, -518, 749, 6342, 1922, -831, 459, -221, 78, -13, -4, 3 }, { 7, -33, 102, -222, 372, -516, 744, 6339, 1929, -832, 459, -221, 77, -12, -4, 3 }, { 7, -34, 102, -221, 371, -514, 738, 6338, 1935, -833, 459, -220, 77, -12, -4, 3 }, { 7, -34, 102, -221, 370, -512, 733, 6336, 1942, -835, 460, -220, 77, -12, -4, 3 }, { 7, -34, 102, -221, 370, -510, 727, 6334, 1949, -836, 460, -220, 77, -12, -4, 3 }, { 7, -34, 102, -221, 369, -508, 722, 6334, 1955, -837, 460, -220, 76, -12, -4, 3 }, { 7, -34, 102, -221, 368, -506, 716, 6334, 1962, -839, 460, -220, 76, -12, -4, 3 }, { 7, -34, 102, -221, 368, -504, 711, 6330, 1969, -840, 460, -219, 76, -12, -4, 3 }, { 7, -34, 102, -221, 367, -503, 705, 6330, 1975, -841, 460, -219, 76, -11, -4, 3 }, { 7, -34, 102, -220, 366, -501, 700, 6327, 1982, -842, 461, -219, 76, -11, -5, 3 }, { 7, -34, 102, -220, 365, -499, 694, 6328, 1989, -844, 461, -219, 75, -11, -5, 3 }, { 7, -34, 102, -220, 365, -497, 689, 6326, 1995, -845, 461, -219, 75, -11, -5, 3 }, { 7, -34, 103, -220, 364, -495, 683, 6324, 2002, -846, 461, -219, 75, -11, -5, 3 }, { 7, -34, 103, -220, 363, -493, 678, 6322, 2009, -848, 461, -218, 75, -11, -5, 3 }, { 7, -34, 103, -220, 363, -491, 672, 6321, 2016, -849, 461, -218, 74, -11, -5, 3 }, { 7, -34, 103, -219, 362, -489, 667, 6318, 2022, -850, 461, -218, 74, -10, -5, 3 }, { 7, -34, 103, -219, 361, -487, 661, 6316, 2029, -851, 462, -218, 74, -10, -5, 3 }, { 7, -35, 103, -219, 360, -486, 656, 6316, 2036, -853, 462, -217, 74, -10, -5, 3 }, { 7, -35, 103, -219, 360, -484, 650, 6315, 2042, -854, 462, -217, 74, -10, -5, 3 }, { 7, -35, 103, -219, 359, -482, 645, 6314, 2049, -855, 462, -217, 73, -10, -5, 3 }, { 7, -35, 103, -219, 358, -480, 639, 6313, 2056, -856, 462, -217, 73, -10, -5, 3 }, { 7, -35, 103, -218, 358, -478, 634, 6309, 2063, -857, 462, -217, 73, -10, -5, 3 }, { 7, -35, 103, -218, 357, -476, 629, 6308, 2069, -859, 462, -216, 73, -9, -6, 3 }, { 7, -35, 103, -218, 356, -474, 623, 6308, 2076, -860, 462, -216, 72, -9, -6, 3 }, { 7, -35, 103, -218, 355, -472, 618, 6305, 2083, -861, 463, -216, 72, -9, -6, 3 }, { 7, -35, 103, -218, 355, -470, 612, 6304, 2089, -862, 463, -216, 72, -9, -6, 3 }, { 7, -35, 103, -217, 354, -468, 607, 6301, 2096, -863, 463, -216, 72, -9, -6, 3 }, { 7, -35, 103, -217, 353, -466, 602, 6300, 2103, -865, 463, -215, 71, -9, -6, 3 }, { 7, -35, 103, -217, 352, -465, 596, 6300, 2110, -866, 463, -215, 71, -9, -6, 3 }, { 7, -35, 103, -217, 352, -463, 591, 6297, 2116, -867, 463, -215, 71, -8, -6, 3 }, { 8, -35, 103, -217, 351, -461, 586, 6294, 2123, -868, 463, -215, 71, -8, -6, 3 }, { 8, -35, 103, -216, 350, -459, 580, 6292, 2130, -869, 463, -214, 70, -8, -6, 3 }, { 8, -36, 103, -216, 349, -457, 575, 6292, 2137, -871, 463, -214, 70, -8, -6, 3 }, { 8, -36, 103, -216, 349, -455, 569, 6291, 2143, -872, 463, -214, 70, -8, -6, 3 }, { 8, -36, 103, -216, 348, -453, 564, 6288, 2150, -873, 463, -214, 70, -8, -6, 4 }, { 8, -36, 103, -216, 347, -451, 559, 6285, 2157, -874, 464, -214, 70, -7, -7, 4 }, { 8, -36, 103, -215, 346, -449, 554, 6282, 2164, -875, 464, -213, 69, -7, -7, 4 }, { 8, -36, 103, -215, 346, -447, 548, 6281, 2170, -876, 464, -213, 69, -7, -7, 4 }, { 8, -36, 103, -215, 345, -445, 543, 6279, 2177, -877, 464, -213, 69, -7, -7, 4 }, { 8, -36, 103, -215, 344, -443, 538, 6278, 2184, -879, 464, -213, 69, -7, -7, 4 }, { 8, -36, 103, -215, 343, -441, 532, 6277, 2191, -880, 464, -212, 68, -7, -7, 4 }, { 8, -36, 103, -214, 343, -440, 527, 6274, 2198, -881, 464, -212, 68, -7, -7, 4 }, { 8, -36, 103, -214, 342, -438, 522, 6272, 2204, -882, 464, -212, 68, -6, -7, 4 }, { 8, -36, 103, -214, 341, -436, 517, 6270, 2211, -883, 464, -212, 68, -6, -7, 4 }, { 8, -36, 103, -214, 340, -434, 511, 6269, 2218, -884, 464, -211, 67, -6, -7, 4 }, { 8, -36, 103, -214, 340, -432, 506, 6266, 2225, -885, 464, -211, 67, -6, -7, 4 }, { 8, -36, 103, -213, 339, -430, 501, 6264, 2231, -886, 464, -211, 67, -6, -7, 4 }, { 8, -36, 103, -213, 338, -428, 496, 6262, 2238, -887, 464, -210, 67, -6, -8, 4 }, { 8, -36, 103, -213, 337, -426, 490, 6262, 2245, -889, 464, -210, 66, -5, -8, 4 }, { 8, -37, 103, -213, 337, -424, 485, 6260, 2252, -890, 464, -210, 66, -5, -8, 4 }, { 8, -37, 103, -213, 336, -422, 480, 6258, 2259, -891, 464, -210, 66, -5, -8, 4 }, { 8, -37, 103, -212, 335, -420, 475, 6256, 2265, -892, 464, -209, 65, -5, -8, 4 }, { 8, -37, 103, -212, 334, -418, 470, 6254, 2272, -893, 464, -209, 65, -5, -8, 4 }, { 8, -37, 103, -212, 333, -416, 465, 6252, 2279, -894, 464, -209, 65, -5, -8, 4 }, { 8, -37, 103, -212, 333, -414, 459, 6249, 2286, -895, 464, -209, 65, -4, -8, 4 }, { 8, -37, 103, -211, 332, -413, 454, 6247, 2293, -896, 464, -208, 64, -4, -8, 4 }, { 8, -37, 103, -211, 331, -411, 449, 6245, 2300, -897, 464, -208, 64, -4, -8, 4 }, { 8, -37, 103, -211, 330, -409, 444, 6244, 2306, -898, 464, -208, 64, -4, -8, 4 }, { 8, -37, 103, -211, 330, -407, 439, 6241, 2313, -899, 464, -208, 64, -4, -8, 4 }, { 8, -37, 103, -211, 329, -405, 434, 6239, 2320, -900, 464, -207, 63, -4, -8, 4 }, { 8, -37, 103, -210, 328, -403, 429, 6237, 2327, -901, 464, -207, 63, -4, -9, 4 }, { 8, -37, 103, -210, 327, -401, 423, 6235, 2334, -902, 464, -207, 63, -3, -9, 4 }, { 8, -37, 103, -210, 326, -399, 418, 6233, 2340, -903, 464, -206, 63, -3, -9, 4 }, { 8, -37, 103, -210, 326, -397, 413, 6231, 2347, -904, 464, -206, 62, -3, -9, 4 }, { 8, -37, 103, -209, 325, -395, 408, 6228, 2354, -905, 464, -206, 62, -3, -9, 4 }, { 8, -37, 103, -209, 324, -393, 403, 6225, 2361, -906, 464, -205, 62, -3, -9, 4 }, { 9, -37, 103, -209, 323, -391, 398, 6222, 2368, -907, 464, -205, 62, -3, -9, 4 }, { 9, -37, 103, -209, 322, -389, 393, 6220, 2375, -908, 464, -205, 61, -2, -9, 4 }, { 9, -38, 103, -208, 322, -387, 388, 6217, 2382, -909, 464, -205, 61, -2, -9, 4 }, { 9, -38, 103, -208, 321, -385, 383, 6215, 2388, -910, 464, -204, 61, -2, -9, 4 }, { 9, -38, 103, -208, 320, -383, 378, 6214, 2395, -911, 464, -204, 60, -2, -9, 4 }, { 9, -38, 103, -208, 319, -382, 373, 6213, 2402, -912, 464, -204, 60, -2, -9, 4 }, { 9, -38, 103, -207, 318, -380, 368, 6209, 2409, -913, 464, -203, 60, -2, -10, 5 }, { 9, -38, 103, -207, 318, -378, 363, 6205, 2416, -914, 464, -203, 60, -1, -10, 5 }, { 9, -38, 103, -207, 317, -376, 358, 6204, 2423, -915, 464, -203, 59, -1, -10, 5 }, { 9, -38, 103, -207, 316, -374, 353, 6202, 2429, -916, 464, -202, 59, -1, -10, 5 }, { 9, -38, 103, -206, 315, -372, 348, 6199, 2436, -917, 464, -202, 59, -1, -10, 5 }, { 9, -38, 103, -206, 314, -370, 343, 6198, 2443, -918, 464, -202, 58, -1, -10, 5 }, { 9, -38, 103, -206, 314, -368, 338, 6195, 2450, -919, 463, -201, 58, -1, -10, 5 }, { 9, -38, 103, -206, 313, -366, 333, 6192, 2457, -920, 463, -201, 58, 0, -10, 5 }, { 9, -38, 103, -205, 312, -364, 328, 6188, 2464, -920, 463, -201, 58, 0, -10, 5 }, { 9, -38, 103, -205, 311, -362, 323, 6187, 2471, -921, 463, -201, 57, 0, -10, 5 }, { 9, -38, 103, -205, 310, -360, 318, 6184, 2478, -922, 463, -200, 57, 0, -10, 5 }, { 9, -38, 103, -205, 309, -358, 313, 6183, 2484, -923, 463, -200, 57, 0, -10, 5 }, { 9, -38, 103, -204, 309, -356, 308, 6179, 2491, -924, 463, -200, 57, 0, -10, 5 }, { 9, -38, 103, -204, 308, -354, 303, 6177, 2498, -925, 463, -199, 56, 1, -11, 5 }, { 9, -38, 103, -204, 307, -352, 298, 6175, 2505, -926, 463, -199, 56, 1, -11, 5 }, { 9, -38, 103, -204, 306, -350, 294, 6172, 2512, -927, 463, -199, 56, 1, -11, 5 }, { 9, -38, 103, -203, 305, -349, 289, 6171, 2519, -928, 462, -198, 55, 1, -11, 5 }, { 9, -38, 103, -203, 304, -347, 284, 6168, 2526, -928, 462, -198, 55, 1, -11, 5 }, { 9, -39, 103, -203, 304, -345, 279, 6165, 2533, -929, 462, -197, 55, 1, -11, 5 }, { 9, -39, 103, -202, 303, -343, 274, 6163, 2539, -930, 462, -197, 54, 2, -11, 5 }, { 9, -39, 103, -202, 302, -341, 269, 6161, 2546, -931, 462, -197, 54, 2, -11, 5 }, { 9, -39, 103, -202, 301, -339, 264, 6158, 2553, -932, 462, -196, 54, 2, -11, 5 }, { 9, -39, 103, -202, 300, -337, 260, 6155, 2560, -933, 462, -196, 54, 2, -11, 5 }, { 9, -39, 103, -201, 299, -335, 255, 6152, 2567, -933, 462, -196, 53, 2, -11, 5 }, { 9, -39, 103, -201, 299, -333, 250, 6149, 2574, -934, 461, -195, 53, 2, -11, 5 }, { 9, -39, 103, -201, 298, -331, 245, 6147, 2581, -935, 461, -195, 53, 3, -12, 5 }, { 9, -39, 103, -201, 297, -329, 240, 6146, 2588, -936, 461, -195, 52, 3, -12, 5 }, { 9, -39, 103, -200, 296, -327, 236, 6141, 2595, -937, 461, -194, 52, 3, -12, 5 }, { 9, -39, 103, -200, 295, -325, 231, 6139, 2601, -937, 461, -194, 52, 3, -12, 5 }, { 9, -39, 103, -200, 294, -323, 226, 6137, 2608, -938, 461, -194, 52, 3, -12, 5 }, { 9, -39, 103, -199, 294, -321, 221, 6133, 2615, -939, 461, -193, 51, 3, -12, 5 }, { 9, -39, 102, -199, 293, -319, 217, 6131, 2622, -940, 460, -193, 51, 4, -12, 5 }, { 9, -39, 102, -199, 292, -317, 212, 6128, 2629, -941, 460, -192, 51, 4, -12, 5 }, { 9, -39, 102, -199, 291, -315, 207, 6126, 2636, -941, 460, -192, 50, 4, -12, 5 }, { 9, -39, 102, -198, 290, -313, 202, 6123, 2643, -942, 460, -192, 50, 4, -12, 5 }, { 9, -39, 102, -198, 289, -312, 198, 6120, 2650, -943, 460, -191, 50, 4, -12, 5 }, { 9, -39, 102, -198, 289, -310, 193, 6118, 2657, -944, 459, -191, 49, 5, -12, 5 }, { 10, -39, 102, -197, 288, -308, 188, 6113, 2664, -944, 459, -191, 49, 5, -12, 5 }, { 10, -39, 102, -197, 287, -306, 184, 6110, 2671, -945, 459, -190, 49, 5, -13, 5 }, { 10, -39, 102, -197, 286, -304, 179, 6109, 2677, -946, 459, -190, 48, 5, -13, 6 }, { 10, -39, 102, -197, 285, -302, 174, 6105, 2684, -946, 459, -189, 48, 5, -13, 6 }, { 10, -39, 102, -196, 284, -300, 170, 6101, 2691, -947, 459, -189, 48, 5, -13, 6 }, { 10, -39, 102, -196, 283, -298, 165, 6099, 2698, -948, 458, -189, 48, 6, -13, 6 }, { 10, -39, 102, -196, 283, -296, 160, 6096, 2705, -949, 458, -188, 47, 6, -13, 6 }, { 10, -39, 102, -195, 282, -294, 156, 6091, 2712, -949, 458, -188, 47, 6, -13, 6 }, { 10, -39, 102, -195, 281, -292, 151, 6089, 2719, -950, 458, -188, 47, 6, -13, 6 }, { 10, -39, 102, -195, 280, -290, 146, 6088, 2726, -951, 457, -187, 46, 6, -13, 6 }, { 10, -40, 102, -194, 279, -288, 142, 6084, 2733, -951, 457, -187, 46, 6, -13, 6 }, { 10, -40, 102, -194, 278, -286, 137, 6080, 2740, -952, 457, -186, 46, 7, -13, 6 }, { 10, -40, 102, -194, 277, -284, 133, 6078, 2747, -953, 457, -186, 45, 7, -13, 6 }, { 10, -40, 102, -194, 276, -282, 128, 6077, 2753, -953, 457, -186, 45, 7, -14, 6 }, { 10, -40, 102, -193, 276, -280, 123, 6073, 2760, -954, 456, -185, 45, 7, -14, 6 }, { 10, -40, 102, -193, 275, -279, 119, 6072, 2767, -955, 456, -185, 44, 7, -14, 6 }, { 10, -40, 102, -193, 274, -277, 114, 6068, 2774, -955, 456, -184, 44, 7, -14, 6 }, { 10, -40, 101, -192, 273, -275, 110, 6064, 2781, -956, 456, -184, 44, 8, -14, 6 }, { 10, -40, 101, -192, 272, -273, 105, 6062, 2788, -956, 455, -183, 43, 8, -14, 6 }, { 10, -40, 101, -192, 271, -271, 101, 6059, 2795, -957, 455, -183, 43, 8, -14, 6 }, { 10, -40, 101, -191, 270, -269, 96, 6056, 2802, -958, 455, -183, 43, 8, -14, 6 }, { 10, -40, 101, -191, 269, -267, 92, 6051, 2809, -958, 455, -182, 43, 8, -14, 6 }, { 10, -40, 101, -191, 269, -265, 87, 6049, 2816, -959, 454, -182, 42, 9, -14, 6 }, { 10, -40, 101, -190, 268, -263, 83, 6044, 2823, -960, 454, -181, 42, 9, -14, 6 }, { 10, -40, 101, -190, 267, -261, 78, 6041, 2830, -960, 454, -181, 42, 9, -14, 6 }, { 10, -40, 101, -190, 266, -259, 74, 6040, 2837, -961, 453, -181, 41, 9, -14, 6 }, { 10, -40, 101, -190, 265, -257, 69, 6038, 2843, -961, 453, -180, 41, 9, -15, 6 }, { 10, -40, 101, -189, 264, -255, 65, 6034, 2850, -962, 453, -180, 41, 9, -15, 6 }, { 10, -40, 101, -189, 263, -253, 60, 6030, 2857, -962, 453, -179, 40, 10, -15, 6 }, { 10, -40, 101, -189, 262, -251, 56, 6028, 2864, -963, 452, -179, 40, 10, -15, 6 }, { 10, -40, 101, -188, 262, -250, 51, 6023, 2871, -963, 452, -178, 40, 10, -15, 6 }, { 10, -40, 101, -188, 261, -248, 47, 6021, 2878, -964, 452, -178, 39, 10, -15, 6 }, { 10, -40, 101, -188, 260, -246, 43, 6019, 2885, -965, 451, -178, 39, 10, -15, 6 }, { 10, -40, 101, -187, 259, -244, 38, 6013, 2892, -965, 451, -177, 39, 11, -15, 6 }, { 10, -40, 100, -187, 258, -242, 34, 6012, 2899, -966, 451, -177, 38, 11, -15, 6 }, { 10, -40, 100, -187, 257, -240, 29, 6009, 2906, -966, 450, -176, 38, 11, -15, 6 }, { 10, -40, 100, -186, 256, -238, 25, 6005, 2913, -967, 450, -176, 38, 11, -15, 6 }, { 10, -40, 100, -186, 255, -236, 21, 6001, 2920, -967, 450, -175, 37, 11, -15, 6 }, { 10, -40, 100, -186, 254, -234, 16, 6001, 2927, -968, 449, -175, 37, 11, -16, 6 }, { 10, -40, 100, -185, 254, -232, 12, 5993, 2934, -968, 449, -174, 37, 12, -16, 6 }, { 10, -40, 100, -185, 253, -230, 8, 5992, 2940, -969, 449, -174, 36, 12, -16, 6 }, { 10, -40, 100, -185, 252, -228, 3, 5989, 2947, -969, 448, -173, 36, 12, -16, 6 }, { 10, -40, 100, -184, 251, -226, -1, 5983, 2954, -969, 448, -173, 36, 12, -16, 7 }, { 10, -40, 100, -184, 250, -224, -5, 5981, 2961, -970, 448, -173, 35, 12, -16, 7 }, { 10, -40, 100, -184, 249, -223, -10, 5978, 2968, -970, 447, -172, 35, 13, -16, 7 }, { 10, -40, 100, -183, 248, -221, -14, 5974, 2975, -971, 447, -172, 35, 13, -16, 7 }, { 10, -40, 100, -183, 247, -219, -18, 5970, 2982, -971, 447, -171, 34, 13, -16, 7 }, { 10, -40, 100, -183, 246, -217, -23, 5969, 2989, -972, 446, -171, 34, 13, -16, 7 }, { 10, -40, 100, -182, 245, -215, -27, 5963, 2996, -972, 446, -170, 34, 13, -16, 7 }, { 10, -40, 99, -182, 245, -213, -31, 5961, 3003, -973, 446, -170, 33, 13, -16, 7 }, { 10, -40, 99, -182, 244, -211, -35, 5956, 3010, -973, 445, -169, 33, 14, -16, 7 }, { 10, -40, 99, -181, 243, -209, -40, 5954, 3017, -973, 445, -169, 32, 14, -17, 7 }, { 10, -40, 99, -181, 242, -207, -44, 5951, 3024, -974, 444, -168, 32, 14, -17, 7 }, { 10, -40, 99, -181, 241, -205, -48, 5947, 3031, -974, 444, -168, 32, 14, -17, 7 }, { 10, -41, 99, -180, 240, -203, -52, 5944, 3037, -974, 444, -167, 31, 14, -17, 7 }, { 10, -41, 99, -180, 239, -201, -56, 5941, 3044, -975, 443, -167, 31, 15, -17, 7 }, { 11, -41, 99, -180, 238, -200, -61, 5937, 3051, -975, 443, -166, 31, 15, -17, 7 }, { 11, -41, 99, -179, 237, -198, -65, 5935, 3058, -976, 442, -166, 30, 15, -17, 7 }, { 11, -41, 99, -179, 236, -196, -69, 5930, 3065, -976, 442, -165, 30, 15, -17, 7 }, { 11, -41, 99, -178, 236, -194, -73, 5924, 3072, -976, 442, -165, 30, 15, -17, 7 }, { 11, -41, 99, -178, 235, -192, -77, 5922, 3079, -977, 441, -164, 29, 15, -17, 7 }, { 11, -41, 99, -178, 234, -190, -82, 5918, 3086, -977, 441, -164, 29, 16, -17, 7 }, { 11, -41, 98, -177, 233, -188, -86, 5915, 3093, -977, 440, -164, 29, 16, -17, 7 }, { 11, -41, 98, -177, 232, -186, -90, 5913, 3100, -978, 440, -163, 28, 16, -18, 7 }, { 11, -41, 98, -177, 231, -184, -94, 5909, 3107, -978, 440, -163, 28, 16, -18, 7 }, { 11, -41, 98, -176, 230, -182, -98, 5904, 3114, -978, 439, -162, 28, 16, -18, 7 }, { 11, -41, 98, -176, 229, -180, -102, 5900, 3121, -978, 439, -162, 27, 17, -18, 7 }, { 11, -41, 98, -176, 228, -179, -106, 5899, 3127, -979, 438, -161, 27, 17, -18, 7 }, { 11, -41, 98, -175, 227, -177, -110, 5894, 3134, -979, 438, -161, 27, 17, -18, 7 }, { 11, -41, 98, -175, 226, -175, -114, 5891, 3141, -979, 437, -160, 26, 17, -18, 7 }, { 11, -41, 98, -175, 226, -173, -118, 5886, 3148, -980, 437, -159, 26, 17, -18, 7 }, { 11, -41, 98, -174, 225, -171, -122, 5882, 3155, -980, 436, -159, 25, 18, -18, 7 }, { 11, -41, 98, -174, 224, -169, -127, 5878, 3162, -980, 436, -158, 25, 18, -18, 7 }, { 11, -41, 98, -173, 223, -167, -131, 5873, 3169, -980, 436, -158, 25, 18, -18, 7 }, { 11, -41, 97, -173, 222, -165, -135, 5872, 3176, -981, 435, -157, 24, 18, -18, 7 }, { 11, -41, 97, -173, 221, -163, -139, 5868, 3183, -981, 435, -157, 24, 18, -18, 7 }, { 11, -41, 97, -172, 220, -162, -143, 5865, 3190, -981, 434, -156, 24, 18, -19, 7 }, { 11, -41, 97, -172, 219, -160, -147, 5861, 3197, -981, 434, -156, 23, 19, -19, 7 }, { 11, -41, 97, -172, 218, -158, -151, 5857, 3204, -981, 433, -155, 23, 19, -19, 7 }, { 11, -41, 97, -171, 217, -156, -155, 5854, 3210, -982, 433, -155, 23, 19, -19, 7 }, { 11, -41, 97, -171, 216, -154, -159, 5851, 3217, -982, 432, -154, 22, 19, -19, 7 }, { 11, -41, 97, -171, 215, -152, -163, 5847, 3224, -982, 432, -154, 22, 19, -19, 7 }, { 11, -41, 97, -170, 215, -150, -166, 5840, 3231, -982, 431, -153, 21, 20, -19, 7 }, { 11, -41, 97, -170, 214, -148, -170, 5836, 3238, -982, 431, -153, 21, 20, -19, 7 }, { 11, -41, 96, -169, 213, -146, -174, 5833, 3245, -983, 430, -152, 21, 20, -19, 7 }, { 11, -41, 96, -169, 212, -145, -178, 5830, 3252, -983, 430, -152, 20, 20, -19, 8 }, { 11, -41, 96, -169, 211, -143, -182, 5826, 3259, -983, 429, -151, 20, 20, -19, 8 }, { 11, -41, 96, -168, 210, -141, -186, 5820, 3266, -983, 429, -151, 20, 21, -19, 8 }, { 11, -41, 96, -168, 209, -139, -190, 5818, 3273, -983, 428, -150, 19, 21, -20, 8 }, { 11, -41, 96, -168, 208, -137, -194, 5813, 3280, -983, 428, -149, 19, 21, -20, 8 }, { 11, -41, 96, -167, 207, -135, -198, 5810, 3286, -983, 427, -149, 19, 21, -20, 8 }, { 11, -41, 96, -167, 206, -133, -202, 5807, 3293, -984, 427, -148, 18, 21, -20, 8 }, { 11, -41, 96, -166, 205, -131, -205, 5801, 3300, -984, 426, -148, 18, 22, -20, 8 }, { 11, -41, 96, -166, 204, -130, -209, 5799, 3307, -984, 425, -147, 17, 22, -20, 8 }, { 11, -41, 96, -166, 203, -128, -213, 5795, 3314, -984, 425, -147, 17, 22, -20, 8 }, { 11, -41, 95, -165, 203, -126, -217, 5790, 3321, -984, 424, -146, 17, 22, -20, 8 }, { 11, -41, 95, -165, 202, -124, -221, 5787, 3328, -984, 424, -146, 16, 22, -20, 8 }, { 11, -41, 95, -165, 201, -122, -225, 5783, 3335, -984, 423, -145, 16, 22, -20, 8 }, { 11, -41, 95, -164, 200, -120, -228, 5776, 3342, -984, 423, -145, 16, 23, -20, 8 }, { 11, -41, 95, -164, 199, -118, -232, 5774, 3348, -984, 422, -144, 15, 23, -20, 8 }, { 11, -41, 95, -163, 198, -117, -236, 5769, 3355, -984, 422, -143, 15, 23, -20, 8 }, { 11, -41, 95, -163, 197, -115, -240, 5768, 3362, -984, 421, -143, 14, 23, -21, 8 }, { 11, -41, 95, -163, 196, -113, -244, 5764, 3369, -984, 420, -142, 14, 23, -21, 8 }, { 11, -41, 95, -162, 195, -111, -247, 5757, 3376, -984, 420, -142, 14, 24, -21, 8 }, { 11, -41, 94, -162, 194, -109, -251, 5755, 3383, -984, 419, -141, 13, 24, -21, 8 }, { 11, -41, 94, -162, 193, -107, -255, 5751, 3390, -984, 419, -141, 13, 24, -21, 8 }, { 11, -41, 94, -161, 192, -105, -258, 5746, 3397, -984, 418, -140, 12, 24, -21, 8 }, { 11, -41, 94, -161, 191, -104, -262, 5743, 3403, -984, 418, -139, 12, 24, -21, 8 }, { 11, -41, 94, -160, 190, -102, -266, 5738, 3410, -984, 417, -139, 12, 25, -21, 8 }, { 11, -41, 94, -160, 189, -100, -270, 5735, 3417, -984, 416, -138, 11, 25, -21, 8 }, { 11, -41, 94, -160, 189, -98, -273, 5729, 3424, -984, 416, -138, 11, 25, -21, 8 }, { 11, -41, 94, -159, 188, -96, -277, 5724, 3431, -984, 415, -137, 11, 25, -21, 8 }, { 11, -41, 94, -159, 187, -94, -281, 5721, 3438, -984, 415, -137, 10, 25, -21, 8 }, { 11, -41, 93, -158, 186, -93, -284, 5716, 3445, -984, 414, -136, 10, 26, -21, 8 }, { 11, -41, 93, -158, 185, -91, -288, 5715, 3451, -984, 413, -135, 9, 26, -22, 8 }, { 11, -41, 93, -158, 184, -89, -292, 5711, 3458, -984, 413, -135, 9, 26, -22, 8 }, { 11, -41, 93, -157, 183, -87, -295, 5705, 3465, -984, 412, -134, 9, 26, -22, 8 }, { 11, -41, 93, -157, 182, -85, -299, 5703, 3472, -984, 411, -134, 8, 26, -22, 8 }, { 11, -41, 93, -156, 181, -83, -302, 5695, 3479, -984, 411, -133, 8, 27, -22, 8 }, { 11, -41, 93, -156, 180, -82, -306, 5693, 3486, -984, 410, -132, 7, 27, -22, 8 }, { 11, -41, 93, -156, 179, -80, -310, 5689, 3493, -983, 409, -132, 7, 27, -22, 8 }, { 11, -41, 93, -155, 178, -78, -313, 5683, 3499, -983, 409, -131, 7, 27, -22, 8 }, { 11, -41, 92, -155, 177, -76, -317, 5682, 3506, -983, 408, -131, 6, 27, -22, 8 }, { 11, -41, 92, -154, 176, -74, -320, 5675, 3513, -983, 408, -130, 6, 27, -22, 8 }, { 11, -41, 92, -154, 175, -72, -324, 5670, 3520, -983, 407, -129, 6, 28, -22, 8 }, { 11, -41, 92, -154, 175, -71, -327, 5667, 3527, -983, 406, -129, 5, 28, -22, 8 }, { 11, -41, 92, -153, 174, -69, -331, 5661, 3534, -983, 406, -128, 5, 28, -22, 8 }, { 11, -41, 92, -153, 173, -67, -334, 5660, 3540, -983, 405, -128, 4, 28, -23, 8 }, { 11, -41, 92, -152, 172, -65, -338, 5654, 3547, -982, 404, -127, 4, 28, -23, 8 }, { 11, -41, 92, -152, 171, -63, -342, 5648, 3554, -982, 404, -126, 4, 29, -23, 8 }, { 11, -41, 92, -152, 170, -62, -345, 5646, 3561, -982, 403, -126, 3, 29, -23, 8 }, { 11, -41, 91, -151, 169, -60, -348, 5641, 3568, -982, 402, -125, 3, 29, -23, 8 }, { 11, -41, 91, -151, 168, -58, -352, 5638, 3575, -982, 401, -125, 2, 29, -23, 9 }, { 11, -41, 91, -150, 167, -56, -355, 5631, 3581, -981, 401, -124, 2, 29, -23, 9 }, { 11, -41, 91, -150, 166, -54, -359, 5626, 3588, -981, 400, -123, 2, 30, -23, 9 }, { 11, -41, 91, -150, 165, -53, -362, 5624, 3595, -981, 399, -123, 1, 30, -23, 9 }, { 11, -41, 91, -149, 164, -51, -366, 5618, 3602, -981, 399, -122, 1, 30, -23, 9 }, { 11, -41, 91, -149, 163, -49, -369, 5613, 3609, -980, 398, -121, 0, 30, -23, 9 }, { 11, -41, 91, -148, 162, -47, -373, 5610, 3615, -980, 397, -121, 0, 30, -23, 9 }, { 11, -41, 90, -148, 161, -45, -376, 5604, 3622, -980, 397, -120, 0, 31, -23, 9 }, { 11, -41, 90, -148, 160, -44, -379, 5603, 3629, -980, 396, -120, -1, 31, -24, 9 }, { 11, -41, 90, -147, 160, -42, -383, 5596, 3636, -979, 395, -119, -1, 31, -24, 9 }, { 11, -41, 90, -147, 159, -40, -386, 5592, 3643, -979, 394, -118, -2, 31, -24, 9 }, { 11, -41, 90, -146, 158, -38, -390, 5588, 3649, -979, 394, -118, -2, 31, -24, 9 }, { 11, -41, 90, -146, 157, -36, -393, 5581, 3656, -978, 393, -117, -2, 32, -24, 9 }, { 11, -41, 90, -146, 156, -35, -396, 5578, 3663, -978, 392, -116, -3, 32, -24, 9 }, { 11, -40, 90, -145, 155, -33, -400, 5573, 3670, -978, 391, -116, -3, 32, -24, 9 }, { 11, -40, 89, -145, 154, -31, -403, 5569, 3677, -978, 391, -115, -4, 32, -24, 9 }, { 11, -40, 89, -144, 153, -29, -406, 5563, 3683, -977, 390, -114, -4, 32, -24, 9 }, { 11, -40, 89, -144, 152, -28, -410, 5560, 3690, -977, 389, -114, -4, 33, -24, 9 }, { 11, -40, 89, -143, 151, -26, -413, 5555, 3697, -977, 388, -113, -5, 33, -24, 9 }, { 11, -40, 89, -143, 150, -24, -416, 5548, 3704, -976, 388, -112, -5, 33, -24, 9 }, { 11, -40, 89, -143, 149, -22, -419, 5546, 3710, -976, 387, -112, -6, 33, -24, 9 }, { 11, -40, 89, -142, 148, -21, -423, 5542, 3717, -975, 386, -111, -6, 33, -25, 9 }, { 11, -40, 88, -142, 147, -19, -426, 5538, 3724, -975, 385, -111, -6, 34, -25, 9 }, { 11, -40, 88, -141, 146, -17, -429, 5532, 3731, -975, 385, -110, -7, 34, -25, 9 }, { 11, -40, 88, -141, 145, -15, -432, 5527, 3737, -974, 384, -109, -7, 34, -25, 9 }, { 11, -40, 88, -141, 145, -13, -436, 5524, 3744, -974, 383, -109, -8, 34, -25, 9 }, { 11, -40, 88, -140, 144, -12, -439, 5518, 3751, -973, 382, -108, -8, 34, -25, 9 }, { 12, -40, 88, -140, 143, -10, -442, 5512, 3758, -973, 381, -107, -9, 35, -25, 9 }, { 12, -40, 88, -139, 142, -8, -445, 5507, 3764, -973, 381, -107, -9, 35, -25, 9 }, { 12, -40, 88, -139, 141, -6, -449, 5502, 3771, -972, 380, -106, -9, 35, -25, 9 }, { 12, -40, 87, -138, 140, -5, -452, 5499, 3778, -972, 379, -105, -10, 35, -25, 9 }, { 12, -40, 87, -138, 139, -3, -455, 5494, 3785, -971, 378, -105, -10, 35, -25, 9 }, { 12, -40, 87, -138, 138, -1, -458, 5490, 3791, -971, 377, -104, -11, 36, -25, 9 }, { 12, -40, 87, -137, 137, 0, -461, 5483, 3798, -970, 377, -103, -11, 36, -25, 9 }, { 12, -40, 87, -137, 136, 2, -464, 5480, 3805, -970, 376, -103, -11, 36, -26, 9 }, { 12, -40, 87, -136, 135, 4, -467, 5474, 3812, -969, 375, -102, -12, 36, -26, 9 }, { 12, -40, 87, -136, 134, 6, -471, 5471, 3818, -969, 374, -101, -12, 36, -26, 9 }, { 12, -40, 86, -135, 133, 7, -474, 5467, 3825, -968, 373, -101, -13, 37, -26, 9 }, { 12, -40, 86, -135, 132, 9, -477, 5462, 3832, -968, 372, -100, -13, 37, -26, 9 }, { 12, -40, 86, -135, 131, 11, -480, 5457, 3838, -967, 372, -99, -14, 37, -26, 9 }, { 12, -40, 86, -134, 130, 13, -483, 5452, 3845, -967, 371, -99, -14, 37, -26, 9 }, { 12, -40, 86, -134, 130, 14, -486, 5446, 3852, -966, 370, -98, -14, 37, -26, 9 }, { 12, -40, 86, -133, 129, 16, -489, 5442, 3858, -966, 369, -97, -15, 37, -26, 9 }, { 12, -40, 86, -133, 128, 18, -492, 5435, 3865, -965, 368, -96, -15, 38, -26, 9 }, { 12, -40, 85, -133, 127, 19, -495, 5434, 3872, -965, 367, -96, -16, 38, -26, 9 }, { 12, -40, 85, -132, 126, 21, -498, 5426, 3879, -964, 367, -95, -16, 38, -26, 9 }, { 12, -40, 85, -132, 125, 23, -501, 5421, 3885, -963, 366, -94, -16, 38, -26, 9 }, { 12, -40, 85, -131, 124, 25, -504, 5417, 3892, -963, 365, -94, -17, 38, -26, 9 }, { 12, -40, 85, -131, 123, 26, -507, 5412, 3899, -962, 364, -93, -17, 39, -27, 9 }, { 12, -40, 85, -130, 122, 28, -510, 5408, 3905, -962, 363, -92, -18, 39, -27, 9 }, { 12, -40, 85, -130, 121, 30, -513, 5403, 3912, -961, 362, -92, -18, 39, -27, 9 }, { 12, -40, 84, -129, 120, 31, -516, 5399, 3919, -960, 361, -91, -19, 39, -27, 9 }, { 12, -40, 84, -129, 119, 33, -519, 5395, 3925, -960, 360, -90, -19, 39, -27, 9 }, { 12, -40, 84, -129, 118, 35, -522, 5387, 3932, -959, 360, -90, -19, 40, -27, 10 }, { 12, -40, 84, -128, 117, 36, -525, 5383, 3938, -958, 359, -89, -20, 40, -27, 10 }, { 12, -40, 84, -128, 116, 38, -528, 5378, 3945, -958, 358, -88, -20, 40, -27, 10 }, { 12, -40, 84, -127, 116, 40, -531, 5371, 3952, -957, 357, -87, -21, 40, -27, 10 }, { 12, -40, 84, -127, 115, 41, -534, 5368, 3958, -956, 356, -87, -21, 40, -27, 10 }, { 12, -40, 83, -126, 114, 43, -537, 5363, 3965, -956, 355, -86, -22, 41, -27, 10 }, { 12, -40, 83, -126, 113, 45, -540, 5357, 3972, -955, 354, -85, -22, 41, -27, 10 }, { 12, -40, 83, -126, 112, 47, -543, 5353, 3978, -954, 353, -85, -22, 41, -27, 10 }, { 12, -39, 83, -125, 111, 48, -546, 5348, 3985, -954, 352, -84, -23, 41, -27, 10 }, { 12, -39, 83, -125, 110, 50, -548, 5342, 3992, -953, 351, -83, -23, 41, -28, 10 }, { 12, -39, 83, -124, 109, 52, -551, 5336, 3998, -952, 350, -82, -24, 42, -28, 10 }, { 12, -39, 82, -124, 108, 53, -554, 5333, 4005, -951, 349, -82, -24, 42, -28, 10 }, { 12, -39, 82, -123, 107, 55, -557, 5327, 4011, -951, 349, -81, -24, 42, -28, 10 }, { 12, -39, 82, -123, 106, 57, -560, 5322, 4018, -950, 348, -80, -25, 42, -28, 10 }, { 12, -39, 82, -123, 105, 58, -563, 5318, 4025, -949, 347, -80, -25, 42, -28, 10 }, { 12, -39, 82, -122, 104, 60, -565, 5311, 4031, -948, 346, -79, -26, 43, -28, 10 }, { 12, -39, 82, -122, 103, 61, -568, 5307, 4038, -948, 345, -78, -26, 43, -28, 10 }, { 12, -39, 82, -121, 102, 63, -571, 5302, 4044, -947, 344, -77, -27, 43, -28, 10 }, { 12, -39, 81, -121, 102, 65, -574, 5297, 4051, -946, 343, -77, -27, 43, -28, 10 }, { 12, -39, 81, -120, 101, 66, -577, 5292, 4058, -945, 342, -76, -28, 43, -28, 10 }, { 12, -39, 81, -120, 100, 68, -579, 5285, 4064, -944, 341, -75, -28, 44, -28, 10 }, { 12, -39, 81, -119, 99, 70, -582, 5279, 4071, -944, 340, -74, -28, 44, -28, 10 }, { 12, -39, 81, -119, 98, 71, -585, 5277, 4077, -943, 339, -74, -29, 44, -28, 10 }, { 12, -39, 81, -119, 97, 73, -588, 5272, 4084, -942, 338, -73, -29, 44, -29, 10 }, { 12, -39, 80, -118, 96, 75, -590, 5267, 4090, -941, 337, -72, -30, 44, -29, 10 }, { 12, -39, 80, -118, 95, 76, -593, 5261, 4097, -940, 336, -71, -30, 45, -29, 10 }, { 12, -39, 80, -117, 94, 78, -596, 5257, 4103, -939, 335, -71, -31, 45, -29, 10 }, { 12, -39, 80, -117, 93, 79, -598, 5251, 4110, -938, 334, -70, -31, 45, -29, 10 }, { 12, -39, 80, -116, 92, 81, -601, 5246, 4116, -938, 333, -69, -31, 45, -29, 10 }, { 12, -39, 80, -116, 91, 83, -604, 5242, 4123, -937, 332, -69, -32, 45, -29, 10 }, { 12, -39, 80, -116, 90, 84, -606, 5235, 4130, -936, 331, -68, -32, 46, -29, 10 }, { 12, -39, 79, -115, 90, 86, -609, 5230, 4136, -935, 330, -67, -33, 46, -29, 10 }, { 12, -39, 79, -115, 89, 88, -612, 5224, 4143, -934, 329, -66, -33, 46, -29, 10 }, { 12, -39, 79, -114, 88, 89, -614, 5220, 4149, -933, 328, -66, -34, 46, -29, 10 }, { 12, -39, 79, -114, 87, 91, -617, 5214, 4156, -932, 327, -65, -34, 46, -29, 10 }, { 12, -39, 79, -113, 86, 92, -620, 5208, 4162, -931, 326, -64, -34, 47, -29, 10 }, { 12, -39, 79, -113, 85, 94, -622, 5202, 4169, -930, 325, -63, -35, 47, -29, 10 }, { 12, -39, 78, -112, 84, 96, -625, 5198, 4175, -929, 324, -63, -35, 47, -29, 10 }, { 12, -39, 78, -112, 83, 97, -627, 5194, 4182, -928, 323, -62, -36, 47, -30, 10 }, { 12, -39, 78, -112, 82, 99, -630, 5189, 4188, -927, 322, -61, -36, 47, -30, 10 }, { 12, -38, 78, -111, 81, 100, -633, 5182, 4195, -926, 321, -60, -37, 48, -30, 10 }, { 12, -38, 78, -111, 80, 102, -635, 5176, 4201, -925, 320, -59, -37, 48, -30, 10 }, { 12, -38, 78, -110, 79, 104, -638, 5171, 4207, -924, 319, -59, -37, 48, -30, 10 }, { 12, -38, 77, -110, 78, 105, -640, 5167, 4214, -923, 318, -58, -38, 48, -30, 10 }, { 12, -38, 77, -109, 78, 107, -643, 5160, 4220, -922, 317, -57, -38, 48, -30, 10 }, { 12, -38, 77, -109, 77, 108, -645, 5154, 4227, -921, 316, -56, -39, 49, -30, 10 }, { 12, -38, 77, -108, 76, 110, -648, 5150, 4233, -920, 314, -56, -39, 49, -30, 10 }, { 12, -38, 77, -108, 75, 111, -650, 5145, 4240, -919, 313, -55, -40, 49, -30, 10 }, { 12, -38, 77, -108, 74, 113, -653, 5140, 4246, -918, 312, -54, -40, 49, -30, 10 }, { 12, -38, 76, -107, 73, 115, -655, 5134, 4253, -917, 311, -53, -41, 49, -30, 10 }, { 12, -38, 76, -107, 72, 116, -658, 5131, 4259, -916, 310, -53, -41, 49, -30, 10 }, { 12, -38, 76, -106, 71, 118, -660, 5123, 4265, -915, 309, -52, -41, 50, -30, 10 }, { 12, -38, 76, -106, 70, 119, -663, 5119, 4272, -914, 308, -51, -42, 50, -30, 10 }, { 12, -38, 76, -105, 69, 121, -665, 5113, 4278, -913, 307, -50, -42, 50, -31, 10 }, { 12, -38, 76, -105, 68, 122, -668, 5108, 4285, -911, 306, -49, -43, 50, -31, 10 }, { 12, -38, 75, -104, 67, 124, -670, 5103, 4291, -910, 305, -49, -43, 50, -31, 10 }, { 12, -38, 75, -104, 67, 125, -672, 5096, 4298, -909, 304, -48, -44, 51, -31, 10 }, { 12, -38, 75, -104, 66, 127, -675, 5091, 4304, -908, 303, -47, -44, 51, -31, 10 }, { 12, -38, 75, -103, 65, 128, -677, 5087, 4310, -907, 301, -46, -45, 51, -31, 10 }, { 12, -38, 75, -103, 64, 130, -680, 5082, 4317, -906, 300, -46, -45, 51, -31, 10 }, { 12, -38, 75, -102, 63, 132, -682, 5075, 4323, -905, 299, -45, -45, 51, -31, 10 }, { 12, -38, 74, -102, 62, 133, -684, 5070, 4329, -903, 298, -44, -46, 52, -31, 10 }, { 12, -38, 74, -101, 61, 135, -687, 5063, 4336, -902, 297, -43, -46, 52, -31, 10 }, { 12, -38, 74, -101, 60, 136, -689, 5059, 4342, -901, 296, -42, -47, 52, -31, 10 }, { 12, -38, 74, -100, 59, 138, -691, 5052, 4349, -900, 295, -42, -47, 52, -31, 10 }, { 12, -38, 74, -100, 58, 139, -694, 5048, 4355, -899, 294, -41, -48, 52, -31, 11 }, { 12, -37, 74, -99, 57, 141, -696, 5039, 4361, -897, 292, -40, -48, 53, -31, 11 }, { 12, -37, 73, -99, 57, 142, -698, 5034, 4368, -896, 291, -39, -49, 53, -31, 11 }, { 12, -37, 73, -99, 56, 144, -701, 5030, 4374, -895, 290, -38, -49, 53, -32, 11 }, { 11, -37, 73, -98, 55, 145, -703, 5026, 4380, -894, 289, -38, -49, 53, -32, 11 }, { 11, -37, 73, -98, 54, 147, -705, 5019, 4387, -892, 288, -37, -50, 53, -32, 11 }, { 11, -37, 73, -97, 53, 148, -707, 5012, 4393, -891, 287, -36, -50, 54, -32, 11 }, { 11, -37, 73, -97, 52, 150, -710, 5008, 4399, -890, 286, -35, -51, 54, -32, 11 }, { 11, -37, 72, -96, 51, 151, -712, 5003, 4405, -888, 284, -34, -51, 54, -32, 11 }, { 11, -37, 72, -96, 50, 153, -714, 4998, 4412, -887, 283, -34, -52, 54, -32, 11 }, { 11, -37, 72, -95, 49, 154, -716, 4992, 4418, -886, 282, -33, -52, 54, -32, 11 }, { 11, -37, 72, -95, 48, 156, -719, 4987, 4424, -885, 281, -32, -53, 55, -32, 11 }, { 11, -37, 72, -95, 48, 157, -721, 4979, 4431, -883, 280, -31, -53, 55, -32, 11 }, { 11, -37, 72, -94, 47, 159, -723, 4972, 4437, -882, 279, -30, -53, 55, -32, 11 }, { 11, -37, 71, -94, 46, 160, -725, 4970, 4443, -880, 277, -30, -54, 55, -32, 11 }, { 11, -37, 71, -93, 45, 162, -727, 4963, 4449, -879, 276, -29, -54, 55, -32, 11 }, { 11, -37, 71, -93, 44, 163, -730, 4958, 4456, -878, 275, -28, -55, 56, -32, 11 }, { 11, -37, 71, -92, 43, 165, -732, 4950, 4462, -876, 274, -27, -55, 56, -32, 11 }, { 11, -37, 71, -92, 42, 166, -734, 4946, 4468, -875, 273, -26, -56, 56, -32, 11 }, { 11, -37, 70, -91, 41, 167, -736, 4943, 4474, -874, 271, -26, -56, 56, -32, 11 }, { 11, -37, 70, -91, 40, 169, -738, 4937, 4481, -872, 270, -25, -57, 56, -33, 11 }, { 11, -37, 70, -90, 40, 170, -740, 4930, 4487, -871, 269, -24, -57, 56, -33, 11 }, { 11, -37, 70, -90, 39, 172, -742, 4922, 4493, -869, 268, -23, -57, 57, -33, 11 }, { 11, -36, 70, -90, 38, 173, -744, 4917, 4499, -868, 267, -22, -58, 57, -33, 11 }, { 11, -36, 70, -89, 37, 175, -747, 4911, 4506, -867, 265, -21, -58, 57, -33, 11 }, { 11, -36, 69, -89, 36, 176, -749, 4908, 4512, -865, 264, -21, -59, 57, -33, 11 }, { 11, -36, 69, -88, 35, 178, -751, 4901, 4518, -864, 263, -20, -59, 57, -33, 11 }, { 11, -36, 69, -88, 34, 179, -753, 4895, 4524, -862, 262, -19, -60, 58, -33, 11 }, { 11, -36, 69, -87, 33, 180, -755, 4890, 4530, -861, 260, -18, -60, 58, -33, 11 }, { 11, -36, 69, -87, 32, 182, -757, 4884, 4536, -859, 259, -17, -61, 58, -33, 11 }, { 11, -36, 69, -86, 32, 183, -759, 4876, 4543, -858, 258, -16, -61, 58, -33, 11 }, { 11, -36, 68, -86, 31, 185, -761, 4872, 4549, -856, 257, -16, -62, 58, -33, 11 }, { 11, -36, 68, -85, 30, 186, -763, 4865, 4555, -855, 256, -15, -62, 59, -33, 11 }, { 11, -36, 68, -85, 29, 188, -765, 4859, 4561, -853, 254, -14, -62, 59, -33, 11 }, { 11, -36, 68, -85, 28, 189, -767, 4855, 4567, -852, 253, -13, -63, 59, -33, 11 }, { 11, -36, 68, -84, 27, 190, -769, 4848, 4573, -850, 252, -12, -63, 59, -33, 11 }, { 11, -36, 67, -84, 26, 192, -771, 4845, 4580, -849, 250, -11, -64, 59, -34, 11 }, { 11, -36, 67, -83, 25, 193, -773, 4838, 4586, -847, 249, -11, -64, 60, -34, 11 }, { 11, -36, 67, -83, 25, 195, -775, 4831, 4592, -845, 248, -10, -65, 60, -34, 11 }, { 11, -36, 67, -82, 24, 196, -777, 4825, 4598, -844, 247, -9, -65, 60, -34, 11 }, { 11, -36, 67, -82, 23, 197, -779, 4821, 4604, -842, 245, -8, -66, 60, -34, 11 }, { 11, -36, 67, -81, 22, 199, -781, 4814, 4610, -841, 244, -7, -66, 60, -34, 11 }, { 11, -36, 66, -81, 21, 200, -783, 4809, 4616, -839, 243, -6, -66, 60, -34, 11 }, { 11, -36, 66, -81, 20, 202, -785, 4803, 4622, -837, 242, -6, -67, 61, -34, 11 }, { 11, -35, 66, -80, 19, 203, -786, 4796, 4628, -836, 240, -5, -67, 61, -34, 11 }, { 11, -35, 66, -80, 18, 204, -788, 4791, 4634, -834, 239, -4, -68, 61, -34, 11 }, { 11, -35, 66, -79, 18, 206, -790, 4782, 4641, -833, 238, -3, -68, 61, -34, 11 }, { 11, -35, 65, -79, 17, 207, -792, 4779, 4647, -831, 236, -2, -69, 61, -34, 11 }, { 11, -35, 65, -78, 16, 208, -794, 4771, 4653, -829, 235, -1, -69, 62, -34, 11 }, { 11, -35, 65, -78, 15, 210, -796, 4767, 4659, -828, 234, -1, -70, 62, -34, 11 }, { 11, -35, 65, -77, 14, 211, -798, 4760, 4665, -826, 233, 0, -70, 62, -34, 11 }, { 11, -35, 65, -77, 13, 213, -799, 4754, 4671, -824, 231, 1, -71, 62, -34, 11 }, { 11, -35, 65, -76, 12, 214, -801, 4747, 4677, -822, 230, 2, -71, 62, -34, 11 }, { 11, -35, 64, -76, 11, 215, -803, 4742, 4683, -821, 229, 3, -71, 63, -34, 11 }, { 11, -35, 64, -76, 11, 217, -805, 4737, 4689, -819, 227, 4, -72, 63, -35, 11 }, { 11, -35, 64, -75, 10, 218, -807, 4730, 4695, -817, 226, 5, -72, 63, -35, 11 }, { 11, -35, 64, -75, 9, 219, -808, 4726, 4701, -816, 225, 5, -73, 63, -35, 11 }, { 11, -35, 64, -74, 8, 221, -810, 4719, 4707, -814, 223, 6, -73, 63, -35, 11 }, { 11, -35, 63, -74, 7, 222, -812, 4714, 4714, -812, 222, 7, -74, 63, -35, 11 }, { 11, -35, 63, -73, 6, 223, -814, 4707, 4719, -810, 221, 8, -74, 64, -35, 11 }, { 11, -35, 63, -73, 5, 225, -816, 4701, 4726, -808, 219, 9, -75, 64, -35, 11 }, { 11, -35, 63, -72, 5, 226, -817, 4695, 4730, -807, 218, 10, -75, 64, -35, 11 }, { 11, -35, 63, -72, 4, 227, -819, 4689, 4737, -805, 217, 11, -76, 64, -35, 11 }, { 11, -34, 63, -71, 3, 229, -821, 4683, 4742, -803, 215, 11, -76, 64, -35, 11 }, { 11, -34, 62, -71, 2, 230, -822, 4677, 4747, -801, 214, 12, -76, 65, -35, 11 }, { 11, -34, 62, -71, 1, 231, -824, 4671, 4754, -799, 213, 13, -77, 65, -35, 11 }, { 11, -34, 62, -70, 0, 233, -826, 4665, 4760, -798, 211, 14, -77, 65, -35, 11 }, { 11, -34, 62, -70, -1, 234, -828, 4659, 4767, -796, 210, 15, -78, 65, -35, 11 }, { 11, -34, 62, -69, -1, 235, -829, 4653, 4771, -794, 208, 16, -78, 65, -35, 11 }, { 11, -34, 61, -69, -2, 236, -831, 4647, 4779, -792, 207, 17, -79, 65, -35, 11 }, { 11, -34, 61, -68, -3, 238, -833, 4641, 4782, -790, 206, 18, -79, 66, -35, 11 }, { 11, -34, 61, -68, -4, 239, -834, 4634, 4791, -788, 204, 18, -80, 66, -35, 11 }, { 11, -34, 61, -67, -5, 240, -836, 4628, 4796, -786, 203, 19, -80, 66, -35, 11 }, { 11, -34, 61, -67, -6, 242, -837, 4622, 4803, -785, 202, 20, -81, 66, -36, 11 }, { 11, -34, 60, -66, -6, 243, -839, 4616, 4809, -783, 200, 21, -81, 66, -36, 11 }, { 11, -34, 60, -66, -7, 244, -841, 4610, 4814, -781, 199, 22, -81, 67, -36, 11 }, { 11, -34, 60, -66, -8, 245, -842, 4604, 4821, -779, 197, 23, -82, 67, -36, 11 }, { 11, -34, 60, -65, -9, 247, -844, 4598, 4825, -777, 196, 24, -82, 67, -36, 11 }, { 11, -34, 60, -65, -10, 248, -845, 4592, 4831, -775, 195, 25, -83, 67, -36, 11 }, { 11, -34, 60, -64, -11, 249, -847, 4586, 4838, -773, 193, 25, -83, 67, -36, 11 }, { 11, -34, 59, -64, -11, 250, -849, 4580, 4845, -771, 192, 26, -84, 67, -36, 11 }, { 11, -33, 59, -63, -12, 252, -850, 4573, 4848, -769, 190, 27, -84, 68, -36, 11 }, { 11, -33, 59, -63, -13, 253, -852, 4567, 4855, -767, 189, 28, -85, 68, -36, 11 }, { 11, -33, 59, -62, -14, 254, -853, 4561, 4859, -765, 188, 29, -85, 68, -36, 11 }, { 11, -33, 59, -62, -15, 256, -855, 4555, 4865, -763, 186, 30, -85, 68, -36, 11 }, { 11, -33, 58, -62, -16, 257, -856, 4549, 4872, -761, 185, 31, -86, 68, -36, 11 }, { 11, -33, 58, -61, -16, 258, -858, 4543, 4876, -759, 183, 32, -86, 69, -36, 11 }, { 11, -33, 58, -61, -17, 259, -859, 4536, 4884, -757, 182, 32, -87, 69, -36, 11 }, { 11, -33, 58, -60, -18, 260, -861, 4530, 4890, -755, 180, 33, -87, 69, -36, 11 }, { 11, -33, 58, -60, -19, 262, -862, 4524, 4895, -753, 179, 34, -88, 69, -36, 11 }, { 11, -33, 57, -59, -20, 263, -864, 4518, 4901, -751, 178, 35, -88, 69, -36, 11 }, { 11, -33, 57, -59, -21, 264, -865, 4512, 4908, -749, 176, 36, -89, 69, -36, 11 }, { 11, -33, 57, -58, -21, 265, -867, 4506, 4911, -747, 175, 37, -89, 70, -36, 11 }, { 11, -33, 57, -58, -22, 267, -868, 4499, 4917, -744, 173, 38, -90, 70, -36, 11 }, { 11, -33, 57, -57, -23, 268, -869, 4493, 4922, -742, 172, 39, -90, 70, -37, 11 }, { 11, -33, 56, -57, -24, 269, -871, 4487, 4930, -740, 170, 40, -90, 70, -37, 11 }, { 11, -33, 56, -57, -25, 270, -872, 4481, 4937, -738, 169, 40, -91, 70, -37, 11 }, { 11, -32, 56, -56, -26, 271, -874, 4474, 4943, -736, 167, 41, -91, 70, -37, 11 }, { 11, -32, 56, -56, -26, 273, -875, 4468, 4946, -734, 166, 42, -92, 71, -37, 11 }, { 11, -32, 56, -55, -27, 274, -876, 4462, 4950, -732, 165, 43, -92, 71, -37, 11 }, { 11, -32, 56, -55, -28, 275, -878, 4456, 4958, -730, 163, 44, -93, 71, -37, 11 }, { 11, -32, 55, -54, -29, 276, -879, 4449, 4963, -727, 162, 45, -93, 71, -37, 11 }, { 11, -32, 55, -54, -30, 277, -880, 4443, 4970, -725, 160, 46, -94, 71, -37, 11 }, { 11, -32, 55, -53, -30, 279, -882, 4437, 4972, -723, 159, 47, -94, 72, -37, 11 }, { 11, -32, 55, -53, -31, 280, -883, 4431, 4979, -721, 157, 48, -95, 72, -37, 11 }, { 11, -32, 55, -53, -32, 281, -885, 4424, 4987, -719, 156, 48, -95, 72, -37, 11 }, { 11, -32, 54, -52, -33, 282, -886, 4418, 4992, -716, 154, 49, -95, 72, -37, 11 }, { 11, -32, 54, -52, -34, 283, -887, 4412, 4998, -714, 153, 50, -96, 72, -37, 11 }, { 11, -32, 54, -51, -34, 284, -888, 4405, 5003, -712, 151, 51, -96, 72, -37, 11 }, { 11, -32, 54, -51, -35, 286, -890, 4399, 5008, -710, 150, 52, -97, 73, -37, 11 }, { 11, -32, 54, -50, -36, 287, -891, 4393, 5012, -707, 148, 53, -97, 73, -37, 11 }, { 11, -32, 53, -50, -37, 288, -892, 4387, 5019, -705, 147, 54, -98, 73, -37, 11 }, { 11, -32, 53, -49, -38, 289, -894, 4380, 5026, -703, 145, 55, -98, 73, -37, 11 }, { 11, -32, 53, -49, -38, 290, -895, 4374, 5030, -701, 144, 56, -99, 73, -37, 12 }, { 11, -31, 53, -49, -39, 291, -896, 4368, 5034, -698, 142, 57, -99, 73, -37, 12 }, { 11, -31, 53, -48, -40, 292, -897, 4361, 5039, -696, 141, 57, -99, 74, -37, 12 }, { 11, -31, 52, -48, -41, 294, -899, 4355, 5048, -694, 139, 58, -100, 74, -38, 12 }, { 10, -31, 52, -47, -42, 295, -900, 4349, 5052, -691, 138, 59, -100, 74, -38, 12 }, { 10, -31, 52, -47, -42, 296, -901, 4342, 5059, -689, 136, 60, -101, 74, -38, 12 }, { 10, -31, 52, -46, -43, 297, -902, 4336, 5063, -687, 135, 61, -101, 74, -38, 12 }, { 10, -31, 52, -46, -44, 298, -903, 4329, 5070, -684, 133, 62, -102, 74, -38, 12 }, { 10, -31, 51, -45, -45, 299, -905, 4323, 5075, -682, 132, 63, -102, 75, -38, 12 }, { 10, -31, 51, -45, -46, 300, -906, 4317, 5082, -680, 130, 64, -103, 75, -38, 12 }, { 10, -31, 51, -45, -46, 301, -907, 4310, 5087, -677, 128, 65, -103, 75, -38, 12 }, { 10, -31, 51, -44, -47, 303, -908, 4304, 5091, -675, 127, 66, -104, 75, -38, 12 }, { 10, -31, 51, -44, -48, 304, -909, 4298, 5096, -672, 125, 67, -104, 75, -38, 12 }, { 10, -31, 50, -43, -49, 305, -910, 4291, 5103, -670, 124, 67, -104, 75, -38, 12 }, { 10, -31, 50, -43, -49, 306, -911, 4285, 5108, -668, 122, 68, -105, 76, -38, 12 }, { 10, -31, 50, -42, -50, 307, -913, 4278, 5113, -665, 121, 69, -105, 76, -38, 12 }, { 10, -30, 50, -42, -51, 308, -914, 4272, 5119, -663, 119, 70, -106, 76, -38, 12 }, { 10, -30, 50, -41, -52, 309, -915, 4265, 5123, -660, 118, 71, -106, 76, -38, 12 }, { 10, -30, 49, -41, -53, 310, -916, 4259, 5131, -658, 116, 72, -107, 76, -38, 12 }, { 10, -30, 49, -41, -53, 311, -917, 4253, 5134, -655, 115, 73, -107, 76, -38, 12 }, { 10, -30, 49, -40, -54, 312, -918, 4246, 5140, -653, 113, 74, -108, 77, -38, 12 }, { 10, -30, 49, -40, -55, 313, -919, 4240, 5145, -650, 111, 75, -108, 77, -38, 12 }, { 10, -30, 49, -39, -56, 314, -920, 4233, 5150, -648, 110, 76, -108, 77, -38, 12 }, { 10, -30, 49, -39, -56, 316, -921, 4227, 5154, -645, 108, 77, -109, 77, -38, 12 }, { 10, -30, 48, -38, -57, 317, -922, 4220, 5160, -643, 107, 78, -109, 77, -38, 12 }, { 10, -30, 48, -38, -58, 318, -923, 4214, 5167, -640, 105, 78, -110, 77, -38, 12 }, { 10, -30, 48, -37, -59, 319, -924, 4207, 5171, -638, 104, 79, -110, 78, -38, 12 }, { 10, -30, 48, -37, -59, 320, -925, 4201, 5176, -635, 102, 80, -111, 78, -38, 12 }, { 10, -30, 48, -37, -60, 321, -926, 4195, 5182, -633, 100, 81, -111, 78, -38, 12 }, { 10, -30, 47, -36, -61, 322, -927, 4188, 5189, -630, 99, 82, -112, 78, -39, 12 }, { 10, -30, 47, -36, -62, 323, -928, 4182, 5194, -627, 97, 83, -112, 78, -39, 12 }, { 10, -29, 47, -35, -63, 324, -929, 4175, 5198, -625, 96, 84, -112, 78, -39, 12 }, { 10, -29, 47, -35, -63, 325, -930, 4169, 5202, -622, 94, 85, -113, 79, -39, 12 }, { 10, -29, 47, -34, -64, 326, -931, 4162, 5208, -620, 92, 86, -113, 79, -39, 12 }, { 10, -29, 46, -34, -65, 327, -932, 4156, 5214, -617, 91, 87, -114, 79, -39, 12 }, { 10, -29, 46, -34, -66, 328, -933, 4149, 5220, -614, 89, 88, -114, 79, -39, 12 }, { 10, -29, 46, -33, -66, 329, -934, 4143, 5224, -612, 88, 89, -115, 79, -39, 12 }, { 10, -29, 46, -33, -67, 330, -935, 4136, 5230, -609, 86, 90, -115, 79, -39, 12 }, { 10, -29, 46, -32, -68, 331, -936, 4130, 5235, -606, 84, 90, -116, 80, -39, 12 }, { 10, -29, 45, -32, -69, 332, -937, 4123, 5242, -604, 83, 91, -116, 80, -39, 12 }, { 10, -29, 45, -31, -69, 333, -938, 4116, 5246, -601, 81, 92, -116, 80, -39, 12 }, { 10, -29, 45, -31, -70, 334, -938, 4110, 5251, -598, 79, 93, -117, 80, -39, 12 }, { 10, -29, 45, -31, -71, 335, -939, 4103, 5257, -596, 78, 94, -117, 80, -39, 12 }, { 10, -29, 45, -30, -71, 336, -940, 4097, 5261, -593, 76, 95, -118, 80, -39, 12 }, { 10, -29, 44, -30, -72, 337, -941, 4090, 5267, -590, 75, 96, -118, 80, -39, 12 }, { 10, -29, 44, -29, -73, 338, -942, 4084, 5272, -588, 73, 97, -119, 81, -39, 12 }, { 10, -28, 44, -29, -74, 339, -943, 4077, 5277, -585, 71, 98, -119, 81, -39, 12 }, { 10, -28, 44, -28, -74, 340, -944, 4071, 5279, -582, 70, 99, -119, 81, -39, 12 }, { 10, -28, 44, -28, -75, 341, -944, 4064, 5285, -579, 68, 100, -120, 81, -39, 12 }, { 10, -28, 43, -28, -76, 342, -945, 4058, 5292, -577, 66, 101, -120, 81, -39, 12 }, { 10, -28, 43, -27, -77, 343, -946, 4051, 5297, -574, 65, 102, -121, 81, -39, 12 }, { 10, -28, 43, -27, -77, 344, -947, 4044, 5302, -571, 63, 102, -121, 82, -39, 12 }, { 10, -28, 43, -26, -78, 345, -948, 4038, 5307, -568, 61, 103, -122, 82, -39, 12 }, { 10, -28, 43, -26, -79, 346, -948, 4031, 5311, -565, 60, 104, -122, 82, -39, 12 }, { 10, -28, 42, -25, -80, 347, -949, 4025, 5318, -563, 58, 105, -123, 82, -39, 12 }, { 10, -28, 42, -25, -80, 348, -950, 4018, 5322, -560, 57, 106, -123, 82, -39, 12 }, { 10, -28, 42, -24, -81, 349, -951, 4011, 5327, -557, 55, 107, -123, 82, -39, 12 }, { 10, -28, 42, -24, -82, 349, -951, 4005, 5333, -554, 53, 108, -124, 82, -39, 12 }, { 10, -28, 42, -24, -82, 350, -952, 3998, 5336, -551, 52, 109, -124, 83, -39, 12 }, { 10, -28, 41, -23, -83, 351, -953, 3992, 5342, -548, 50, 110, -125, 83, -39, 12 }, { 10, -27, 41, -23, -84, 352, -954, 3985, 5348, -546, 48, 111, -125, 83, -39, 12 }, { 10, -27, 41, -22, -85, 353, -954, 3978, 5353, -543, 47, 112, -126, 83, -40, 12 }, { 10, -27, 41, -22, -85, 354, -955, 3972, 5357, -540, 45, 113, -126, 83, -40, 12 }, { 10, -27, 41, -22, -86, 355, -956, 3965, 5363, -537, 43, 114, -126, 83, -40, 12 }, { 10, -27, 40, -21, -87, 356, -956, 3958, 5368, -534, 41, 115, -127, 84, -40, 12 }, { 10, -27, 40, -21, -87, 357, -957, 3952, 5371, -531, 40, 116, -127, 84, -40, 12 }, { 10, -27, 40, -20, -88, 358, -958, 3945, 5378, -528, 38, 116, -128, 84, -40, 12 }, { 10, -27, 40, -20, -89, 359, -958, 3938, 5383, -525, 36, 117, -128, 84, -40, 12 }, { 10, -27, 40, -19, -90, 360, -959, 3932, 5387, -522, 35, 118, -129, 84, -40, 12 }, { 9, -27, 39, -19, -90, 360, -960, 3925, 5395, -519, 33, 119, -129, 84, -40, 12 }, { 9, -27, 39, -19, -91, 361, -960, 3919, 5399, -516, 31, 120, -129, 84, -40, 12 }, { 9, -27, 39, -18, -92, 362, -961, 3912, 5403, -513, 30, 121, -130, 85, -40, 12 }, { 9, -27, 39, -18, -92, 363, -962, 3905, 5408, -510, 28, 122, -130, 85, -40, 12 }, { 9, -27, 39, -17, -93, 364, -962, 3899, 5412, -507, 26, 123, -131, 85, -40, 12 }, { 9, -26, 38, -17, -94, 365, -963, 3892, 5417, -504, 25, 124, -131, 85, -40, 12 }, { 9, -26, 38, -16, -94, 366, -963, 3885, 5421, -501, 23, 125, -132, 85, -40, 12 }, { 9, -26, 38, -16, -95, 367, -964, 3879, 5426, -498, 21, 126, -132, 85, -40, 12 }, { 9, -26, 38, -16, -96, 367, -965, 3872, 5434, -495, 19, 127, -133, 85, -40, 12 }, { 9, -26, 38, -15, -96, 368, -965, 3865, 5435, -492, 18, 128, -133, 86, -40, 12 }, { 9, -26, 37, -15, -97, 369, -966, 3858, 5442, -489, 16, 129, -133, 86, -40, 12 }, { 9, -26, 37, -14, -98, 370, -966, 3852, 5446, -486, 14, 130, -134, 86, -40, 12 }, { 9, -26, 37, -14, -99, 371, -967, 3845, 5452, -483, 13, 130, -134, 86, -40, 12 }, { 9, -26, 37, -14, -99, 372, -967, 3838, 5457, -480, 11, 131, -135, 86, -40, 12 }, { 9, -26, 37, -13, -100, 372, -968, 3832, 5462, -477, 9, 132, -135, 86, -40, 12 }, { 9, -26, 37, -13, -101, 373, -968, 3825, 5467, -474, 7, 133, -135, 86, -40, 12 }, { 9, -26, 36, -12, -101, 374, -969, 3818, 5471, -471, 6, 134, -136, 87, -40, 12 }, { 9, -26, 36, -12, -102, 375, -969, 3812, 5474, -467, 4, 135, -136, 87, -40, 12 }, { 9, -26, 36, -11, -103, 376, -970, 3805, 5480, -464, 2, 136, -137, 87, -40, 12 }, { 9, -25, 36, -11, -103, 377, -970, 3798, 5483, -461, 0, 137, -137, 87, -40, 12 }, { 9, -25, 36, -11, -104, 377, -971, 3791, 5490, -458, -1, 138, -138, 87, -40, 12 }, { 9, -25, 35, -10, -105, 378, -971, 3785, 5494, -455, -3, 139, -138, 87, -40, 12 }, { 9, -25, 35, -10, -105, 379, -972, 3778, 5499, -452, -5, 140, -138, 87, -40, 12 }, { 9, -25, 35, -9, -106, 380, -972, 3771, 5502, -449, -6, 141, -139, 88, -40, 12 }, { 9, -25, 35, -9, -107, 381, -973, 3764, 5507, -445, -8, 142, -139, 88, -40, 12 }, { 9, -25, 35, -9, -107, 381, -973, 3758, 5512, -442, -10, 143, -140, 88, -40, 12 }, { 9, -25, 34, -8, -108, 382, -973, 3751, 5518, -439, -12, 144, -140, 88, -40, 11 }, { 9, -25, 34, -8, -109, 383, -974, 3744, 5524, -436, -13, 145, -141, 88, -40, 11 }, { 9, -25, 34, -7, -109, 384, -974, 3737, 5527, -432, -15, 145, -141, 88, -40, 11 }, { 9, -25, 34, -7, -110, 385, -975, 3731, 5532, -429, -17, 146, -141, 88, -40, 11 }, { 9, -25, 34, -6, -111, 385, -975, 3724, 5538, -426, -19, 147, -142, 88, -40, 11 }, { 9, -25, 33, -6, -111, 386, -975, 3717, 5542, -423, -21, 148, -142, 89, -40, 11 }, { 9, -24, 33, -6, -112, 387, -976, 3710, 5546, -419, -22, 149, -143, 89, -40, 11 }, { 9, -24, 33, -5, -112, 388, -976, 3704, 5548, -416, -24, 150, -143, 89, -40, 11 }, { 9, -24, 33, -5, -113, 388, -977, 3697, 5555, -413, -26, 151, -143, 89, -40, 11 }, { 9, -24, 33, -4, -114, 389, -977, 3690, 5560, -410, -28, 152, -144, 89, -40, 11 }, { 9, -24, 32, -4, -114, 390, -977, 3683, 5563, -406, -29, 153, -144, 89, -40, 11 }, { 9, -24, 32, -4, -115, 391, -978, 3677, 5569, -403, -31, 154, -145, 89, -40, 11 }, { 9, -24, 32, -3, -116, 391, -978, 3670, 5573, -400, -33, 155, -145, 90, -40, 11 }, { 9, -24, 32, -3, -116, 392, -978, 3663, 5578, -396, -35, 156, -146, 90, -41, 11 }, { 9, -24, 32, -2, -117, 393, -978, 3656, 5581, -393, -36, 157, -146, 90, -41, 11 }, { 9, -24, 31, -2, -118, 394, -979, 3649, 5588, -390, -38, 158, -146, 90, -41, 11 }, { 9, -24, 31, -2, -118, 394, -979, 3643, 5592, -386, -40, 159, -147, 90, -41, 11 }, { 9, -24, 31, -1, -119, 395, -979, 3636, 5596, -383, -42, 160, -147, 90, -41, 11 }, { 9, -24, 31, -1, -120, 396, -980, 3629, 5603, -379, -44, 160, -148, 90, -41, 11 }, { 9, -23, 31, 0, -120, 397, -980, 3622, 5604, -376, -45, 161, -148, 90, -41, 11 }, { 9, -23, 30, 0, -121, 397, -980, 3615, 5610, -373, -47, 162, -148, 91, -41, 11 }, { 9, -23, 30, 0, -121, 398, -980, 3609, 5613, -369, -49, 163, -149, 91, -41, 11 }, { 9, -23, 30, 1, -122, 399, -981, 3602, 5618, -366, -51, 164, -149, 91, -41, 11 }, { 9, -23, 30, 1, -123, 399, -981, 3595, 5624, -362, -53, 165, -150, 91, -41, 11 }, { 9, -23, 30, 2, -123, 400, -981, 3588, 5626, -359, -54, 166, -150, 91, -41, 11 }, { 9, -23, 29, 2, -124, 401, -981, 3581, 5631, -355, -56, 167, -150, 91, -41, 11 }, { 9, -23, 29, 2, -125, 401, -982, 3575, 5638, -352, -58, 168, -151, 91, -41, 11 }, { 8, -23, 29, 3, -125, 402, -982, 3568, 5641, -348, -60, 169, -151, 91, -41, 11 }, { 8, -23, 29, 3, -126, 403, -982, 3561, 5646, -345, -62, 170, -152, 92, -41, 11 }, { 8, -23, 29, 4, -126, 404, -982, 3554, 5648, -342, -63, 171, -152, 92, -41, 11 }, { 8, -23, 28, 4, -127, 404, -982, 3547, 5654, -338, -65, 172, -152, 92, -41, 11 }, { 8, -23, 28, 4, -128, 405, -983, 3540, 5660, -334, -67, 173, -153, 92, -41, 11 }, { 8, -22, 28, 5, -128, 406, -983, 3534, 5661, -331, -69, 174, -153, 92, -41, 11 }, { 8, -22, 28, 5, -129, 406, -983, 3527, 5667, -327, -71, 175, -154, 92, -41, 11 }, { 8, -22, 28, 6, -129, 407, -983, 3520, 5670, -324, -72, 175, -154, 92, -41, 11 }, { 8, -22, 27, 6, -130, 408, -983, 3513, 5675, -320, -74, 176, -154, 92, -41, 11 }, { 8, -22, 27, 6, -131, 408, -983, 3506, 5682, -317, -76, 177, -155, 92, -41, 11 }, { 8, -22, 27, 7, -131, 409, -983, 3499, 5683, -313, -78, 178, -155, 93, -41, 11 }, { 8, -22, 27, 7, -132, 409, -983, 3493, 5689, -310, -80, 179, -156, 93, -41, 11 }, { 8, -22, 27, 7, -132, 410, -984, 3486, 5693, -306, -82, 180, -156, 93, -41, 11 }, { 8, -22, 27, 8, -133, 411, -984, 3479, 5695, -302, -83, 181, -156, 93, -41, 11 }, { 8, -22, 26, 8, -134, 411, -984, 3472, 5703, -299, -85, 182, -157, 93, -41, 11 }, { 8, -22, 26, 9, -134, 412, -984, 3465, 5705, -295, -87, 183, -157, 93, -41, 11 }, { 8, -22, 26, 9, -135, 413, -984, 3458, 5711, -292, -89, 184, -158, 93, -41, 11 }, { 8, -22, 26, 9, -135, 413, -984, 3451, 5715, -288, -91, 185, -158, 93, -41, 11 }, { 8, -21, 26, 10, -136, 414, -984, 3445, 5716, -284, -93, 186, -158, 93, -41, 11 }, { 8, -21, 25, 10, -137, 415, -984, 3438, 5721, -281, -94, 187, -159, 94, -41, 11 }, { 8, -21, 25, 11, -137, 415, -984, 3431, 5724, -277, -96, 188, -159, 94, -41, 11 }, { 8, -21, 25, 11, -138, 416, -984, 3424, 5729, -273, -98, 189, -160, 94, -41, 11 }, { 8, -21, 25, 11, -138, 416, -984, 3417, 5735, -270, -100, 189, -160, 94, -41, 11 }, { 8, -21, 25, 12, -139, 417, -984, 3410, 5738, -266, -102, 190, -160, 94, -41, 11 }, { 8, -21, 24, 12, -139, 418, -984, 3403, 5743, -262, -104, 191, -161, 94, -41, 11 }, { 8, -21, 24, 12, -140, 418, -984, 3397, 5746, -258, -105, 192, -161, 94, -41, 11 }, { 8, -21, 24, 13, -141, 419, -984, 3390, 5751, -255, -107, 193, -162, 94, -41, 11 }, { 8, -21, 24, 13, -141, 419, -984, 3383, 5755, -251, -109, 194, -162, 94, -41, 11 }, { 8, -21, 24, 14, -142, 420, -984, 3376, 5757, -247, -111, 195, -162, 95, -41, 11 }, { 8, -21, 23, 14, -142, 420, -984, 3369, 5764, -244, -113, 196, -163, 95, -41, 11 }, { 8, -21, 23, 14, -143, 421, -984, 3362, 5768, -240, -115, 197, -163, 95, -41, 11 }, { 8, -20, 23, 15, -143, 422, -984, 3355, 5769, -236, -117, 198, -163, 95, -41, 11 }, { 8, -20, 23, 15, -144, 422, -984, 3348, 5774, -232, -118, 199, -164, 95, -41, 11 }, { 8, -20, 23, 16, -145, 423, -984, 3342, 5776, -228, -120, 200, -164, 95, -41, 11 }, { 8, -20, 22, 16, -145, 423, -984, 3335, 5783, -225, -122, 201, -165, 95, -41, 11 }, { 8, -20, 22, 16, -146, 424, -984, 3328, 5787, -221, -124, 202, -165, 95, -41, 11 }, { 8, -20, 22, 17, -146, 424, -984, 3321, 5790, -217, -126, 203, -165, 95, -41, 11 }, { 8, -20, 22, 17, -147, 425, -984, 3314, 5795, -213, -128, 203, -166, 96, -41, 11 }, { 8, -20, 22, 17, -147, 425, -984, 3307, 5799, -209, -130, 204, -166, 96, -41, 11 }, { 8, -20, 22, 18, -148, 426, -984, 3300, 5801, -205, -131, 205, -166, 96, -41, 11 }, { 8, -20, 21, 18, -148, 427, -984, 3293, 5807, -202, -133, 206, -167, 96, -41, 11 }, { 8, -20, 21, 19, -149, 427, -983, 3286, 5810, -198, -135, 207, -167, 96, -41, 11 }, { 8, -20, 21, 19, -149, 428, -983, 3280, 5813, -194, -137, 208, -168, 96, -41, 11 }, { 8, -20, 21, 19, -150, 428, -983, 3273, 5818, -190, -139, 209, -168, 96, -41, 11 }, { 8, -19, 21, 20, -151, 429, -983, 3266, 5820, -186, -141, 210, -168, 96, -41, 11 }, { 8, -19, 20, 20, -151, 429, -983, 3259, 5826, -182, -143, 211, -169, 96, -41, 11 }, { 8, -19, 20, 20, -152, 430, -983, 3252, 5830, -178, -145, 212, -169, 96, -41, 11 }, { 7, -19, 20, 21, -152, 430, -983, 3245, 5833, -174, -146, 213, -169, 96, -41, 11 }, { 7, -19, 20, 21, -153, 431, -982, 3238, 5836, -170, -148, 214, -170, 97, -41, 11 }, { 7, -19, 20, 21, -153, 431, -982, 3231, 5840, -166, -150, 215, -170, 97, -41, 11 }, { 7, -19, 19, 22, -154, 432, -982, 3224, 5847, -163, -152, 215, -171, 97, -41, 11 }, { 7, -19, 19, 22, -154, 432, -982, 3217, 5851, -159, -154, 216, -171, 97, -41, 11 }, { 7, -19, 19, 23, -155, 433, -982, 3210, 5854, -155, -156, 217, -171, 97, -41, 11 }, { 7, -19, 19, 23, -155, 433, -981, 3204, 5857, -151, -158, 218, -172, 97, -41, 11 }, { 7, -19, 19, 23, -156, 434, -981, 3197, 5861, -147, -160, 219, -172, 97, -41, 11 }, { 7, -19, 18, 24, -156, 434, -981, 3190, 5865, -143, -162, 220, -172, 97, -41, 11 }, { 7, -18, 18, 24, -157, 435, -981, 3183, 5868, -139, -163, 221, -173, 97, -41, 11 }, { 7, -18, 18, 24, -157, 435, -981, 3176, 5872, -135, -165, 222, -173, 97, -41, 11 }, { 7, -18, 18, 25, -158, 436, -980, 3169, 5873, -131, -167, 223, -173, 98, -41, 11 }, { 7, -18, 18, 25, -158, 436, -980, 3162, 5878, -127, -169, 224, -174, 98, -41, 11 }, { 7, -18, 18, 25, -159, 436, -980, 3155, 5882, -122, -171, 225, -174, 98, -41, 11 }, { 7, -18, 17, 26, -159, 437, -980, 3148, 5886, -118, -173, 226, -175, 98, -41, 11 }, { 7, -18, 17, 26, -160, 437, -979, 3141, 5891, -114, -175, 226, -175, 98, -41, 11 }, { 7, -18, 17, 27, -161, 438, -979, 3134, 5894, -110, -177, 227, -175, 98, -41, 11 }, { 7, -18, 17, 27, -161, 438, -979, 3127, 5899, -106, -179, 228, -176, 98, -41, 11 }, { 7, -18, 17, 27, -162, 439, -978, 3121, 5900, -102, -180, 229, -176, 98, -41, 11 }, { 7, -18, 16, 28, -162, 439, -978, 3114, 5904, -98, -182, 230, -176, 98, -41, 11 }, { 7, -18, 16, 28, -163, 440, -978, 3107, 5909, -94, -184, 231, -177, 98, -41, 11 }, { 7, -18, 16, 28, -163, 440, -978, 3100, 5913, -90, -186, 232, -177, 98, -41, 11 }, { 7, -17, 16, 29, -164, 440, -977, 3093, 5915, -86, -188, 233, -177, 98, -41, 11 }, { 7, -17, 16, 29, -164, 441, -977, 3086, 5918, -82, -190, 234, -178, 99, -41, 11 }, { 7, -17, 15, 29, -164, 441, -977, 3079, 5922, -77, -192, 235, -178, 99, -41, 11 }, { 7, -17, 15, 30, -165, 442, -976, 3072, 5924, -73, -194, 236, -178, 99, -41, 11 }, { 7, -17, 15, 30, -165, 442, -976, 3065, 5930, -69, -196, 236, -179, 99, -41, 11 }, { 7, -17, 15, 30, -166, 442, -976, 3058, 5935, -65, -198, 237, -179, 99, -41, 11 }, { 7, -17, 15, 31, -166, 443, -975, 3051, 5937, -61, -200, 238, -180, 99, -41, 11 }, { 7, -17, 15, 31, -167, 443, -975, 3044, 5941, -56, -201, 239, -180, 99, -41, 10 }, { 7, -17, 14, 31, -167, 444, -974, 3037, 5944, -52, -203, 240, -180, 99, -41, 10 }, { 7, -17, 14, 32, -168, 444, -974, 3031, 5947, -48, -205, 241, -181, 99, -40, 10 }, { 7, -17, 14, 32, -168, 444, -974, 3024, 5951, -44, -207, 242, -181, 99, -40, 10 }, { 7, -17, 14, 32, -169, 445, -973, 3017, 5954, -40, -209, 243, -181, 99, -40, 10 }, { 7, -16, 14, 33, -169, 445, -973, 3010, 5956, -35, -211, 244, -182, 99, -40, 10 }, { 7, -16, 13, 33, -170, 446, -973, 3003, 5961, -31, -213, 245, -182, 99, -40, 10 }, { 7, -16, 13, 34, -170, 446, -972, 2996, 5963, -27, -215, 245, -182, 100, -40, 10 }, { 7, -16, 13, 34, -171, 446, -972, 2989, 5969, -23, -217, 246, -183, 100, -40, 10 }, { 7, -16, 13, 34, -171, 447, -971, 2982, 5970, -18, -219, 247, -183, 100, -40, 10 }, { 7, -16, 13, 35, -172, 447, -971, 2975, 5974, -14, -221, 248, -183, 100, -40, 10 }, { 7, -16, 13, 35, -172, 447, -970, 2968, 5978, -10, -223, 249, -184, 100, -40, 10 }, { 7, -16, 12, 35, -173, 448, -970, 2961, 5981, -5, -224, 250, -184, 100, -40, 10 }, { 7, -16, 12, 36, -173, 448, -969, 2954, 5983, -1, -226, 251, -184, 100, -40, 10 }, { 6, -16, 12, 36, -173, 448, -969, 2947, 5989, 3, -228, 252, -185, 100, -40, 10 }, { 6, -16, 12, 36, -174, 449, -969, 2940, 5992, 8, -230, 253, -185, 100, -40, 10 }, { 6, -16, 12, 37, -174, 449, -968, 2934, 5993, 12, -232, 254, -185, 100, -40, 10 }, { 6, -16, 11, 37, -175, 449, -968, 2927, 6001, 16, -234, 254, -186, 100, -40, 10 }, { 6, -15, 11, 37, -175, 450, -967, 2920, 6001, 21, -236, 255, -186, 100, -40, 10 }, { 6, -15, 11, 38, -176, 450, -967, 2913, 6005, 25, -238, 256, -186, 100, -40, 10 }, { 6, -15, 11, 38, -176, 450, -966, 2906, 6009, 29, -240, 257, -187, 100, -40, 10 }, { 6, -15, 11, 38, -177, 451, -966, 2899, 6012, 34, -242, 258, -187, 100, -40, 10 }, { 6, -15, 11, 39, -177, 451, -965, 2892, 6013, 38, -244, 259, -187, 101, -40, 10 }, { 6, -15, 10, 39, -178, 451, -965, 2885, 6019, 43, -246, 260, -188, 101, -40, 10 }, { 6, -15, 10, 39, -178, 452, -964, 2878, 6021, 47, -248, 261, -188, 101, -40, 10 }, { 6, -15, 10, 40, -178, 452, -963, 2871, 6023, 51, -250, 262, -188, 101, -40, 10 }, { 6, -15, 10, 40, -179, 452, -963, 2864, 6028, 56, -251, 262, -189, 101, -40, 10 }, { 6, -15, 10, 40, -179, 453, -962, 2857, 6030, 60, -253, 263, -189, 101, -40, 10 }, { 6, -15, 9, 41, -180, 453, -962, 2850, 6034, 65, -255, 264, -189, 101, -40, 10 }, { 6, -15, 9, 41, -180, 453, -961, 2843, 6038, 69, -257, 265, -190, 101, -40, 10 }, { 6, -14, 9, 41, -181, 453, -961, 2837, 6040, 74, -259, 266, -190, 101, -40, 10 }, { 6, -14, 9, 42, -181, 454, -960, 2830, 6041, 78, -261, 267, -190, 101, -40, 10 }, { 6, -14, 9, 42, -181, 454, -960, 2823, 6044, 83, -263, 268, -190, 101, -40, 10 }, { 6, -14, 9, 42, -182, 454, -959, 2816, 6049, 87, -265, 269, -191, 101, -40, 10 }, { 6, -14, 8, 43, -182, 455, -958, 2809, 6051, 92, -267, 269, -191, 101, -40, 10 }, { 6, -14, 8, 43, -183, 455, -958, 2802, 6056, 96, -269, 270, -191, 101, -40, 10 }, { 6, -14, 8, 43, -183, 455, -957, 2795, 6059, 101, -271, 271, -192, 101, -40, 10 }, { 6, -14, 8, 43, -183, 455, -956, 2788, 6062, 105, -273, 272, -192, 101, -40, 10 }, { 6, -14, 8, 44, -184, 456, -956, 2781, 6064, 110, -275, 273, -192, 101, -40, 10 }, { 6, -14, 7, 44, -184, 456, -955, 2774, 6068, 114, -277, 274, -193, 102, -40, 10 }, { 6, -14, 7, 44, -185, 456, -955, 2767, 6072, 119, -279, 275, -193, 102, -40, 10 }, { 6, -14, 7, 45, -185, 456, -954, 2760, 6073, 123, -280, 276, -193, 102, -40, 10 }, { 6, -14, 7, 45, -186, 457, -953, 2753, 6077, 128, -282, 276, -194, 102, -40, 10 }, { 6, -13, 7, 45, -186, 457, -953, 2747, 6078, 133, -284, 277, -194, 102, -40, 10 }, { 6, -13, 7, 46, -186, 457, -952, 2740, 6080, 137, -286, 278, -194, 102, -40, 10 }, { 6, -13, 6, 46, -187, 457, -951, 2733, 6084, 142, -288, 279, -194, 102, -40, 10 }, { 6, -13, 6, 46, -187, 457, -951, 2726, 6088, 146, -290, 280, -195, 102, -39, 10 }, { 6, -13, 6, 47, -188, 458, -950, 2719, 6089, 151, -292, 281, -195, 102, -39, 10 }, { 6, -13, 6, 47, -188, 458, -949, 2712, 6091, 156, -294, 282, -195, 102, -39, 10 }, { 6, -13, 6, 47, -188, 458, -949, 2705, 6096, 160, -296, 283, -196, 102, -39, 10 }, { 6, -13, 6, 48, -189, 458, -948, 2698, 6099, 165, -298, 283, -196, 102, -39, 10 }, { 6, -13, 5, 48, -189, 459, -947, 2691, 6101, 170, -300, 284, -196, 102, -39, 10 }, { 6, -13, 5, 48, -189, 459, -946, 2684, 6105, 174, -302, 285, -197, 102, -39, 10 }, { 6, -13, 5, 48, -190, 459, -946, 2677, 6109, 179, -304, 286, -197, 102, -39, 10 }, { 5, -13, 5, 49, -190, 459, -945, 2671, 6110, 184, -306, 287, -197, 102, -39, 10 }, { 5, -12, 5, 49, -191, 459, -944, 2664, 6113, 188, -308, 288, -197, 102, -39, 10 }, { 5, -12, 5, 49, -191, 459, -944, 2657, 6118, 193, -310, 289, -198, 102, -39, 9 }, { 5, -12, 4, 50, -191, 460, -943, 2650, 6120, 198, -312, 289, -198, 102, -39, 9 }, { 5, -12, 4, 50, -192, 460, -942, 2643, 6123, 202, -313, 290, -198, 102, -39, 9 }, { 5, -12, 4, 50, -192, 460, -941, 2636, 6126, 207, -315, 291, -199, 102, -39, 9 }, { 5, -12, 4, 51, -192, 460, -941, 2629, 6128, 212, -317, 292, -199, 102, -39, 9 }, { 5, -12, 4, 51, -193, 460, -940, 2622, 6131, 217, -319, 293, -199, 102, -39, 9 }, { 5, -12, 3, 51, -193, 461, -939, 2615, 6133, 221, -321, 294, -199, 103, -39, 9 }, { 5, -12, 3, 52, -194, 461, -938, 2608, 6137, 226, -323, 294, -200, 103, -39, 9 }, { 5, -12, 3, 52, -194, 461, -937, 2601, 6139, 231, -325, 295, -200, 103, -39, 9 }, { 5, -12, 3, 52, -194, 461, -937, 2595, 6141, 236, -327, 296, -200, 103, -39, 9 }, { 5, -12, 3, 52, -195, 461, -936, 2588, 6146, 240, -329, 297, -201, 103, -39, 9 }, { 5, -12, 3, 53, -195, 461, -935, 2581, 6147, 245, -331, 298, -201, 103, -39, 9 }, { 5, -11, 2, 53, -195, 461, -934, 2574, 6149, 250, -333, 299, -201, 103, -39, 9 }, { 5, -11, 2, 53, -196, 462, -933, 2567, 6152, 255, -335, 299, -201, 103, -39, 9 }, { 5, -11, 2, 54, -196, 462, -933, 2560, 6155, 260, -337, 300, -202, 103, -39, 9 }, { 5, -11, 2, 54, -196, 462, -932, 2553, 6158, 264, -339, 301, -202, 103, -39, 9 }, { 5, -11, 2, 54, -197, 462, -931, 2546, 6161, 269, -341, 302, -202, 103, -39, 9 }, { 5, -11, 2, 54, -197, 462, -930, 2539, 6163, 274, -343, 303, -202, 103, -39, 9 }, { 5, -11, 1, 55, -197, 462, -929, 2533, 6165, 279, -345, 304, -203, 103, -39, 9 }, { 5, -11, 1, 55, -198, 462, -928, 2526, 6168, 284, -347, 304, -203, 103, -38, 9 }, { 5, -11, 1, 55, -198, 462, -928, 2519, 6171, 289, -349, 305, -203, 103, -38, 9 }, { 5, -11, 1, 56, -199, 463, -927, 2512, 6172, 294, -350, 306, -204, 103, -38, 9 }, { 5, -11, 1, 56, -199, 463, -926, 2505, 6175, 298, -352, 307, -204, 103, -38, 9 }, { 5, -11, 1, 56, -199, 463, -925, 2498, 6177, 303, -354, 308, -204, 103, -38, 9 }, { 5, -10, 0, 57, -200, 463, -924, 2491, 6179, 308, -356, 309, -204, 103, -38, 9 }, { 5, -10, 0, 57, -200, 463, -923, 2484, 6183, 313, -358, 309, -205, 103, -38, 9 }, { 5, -10, 0, 57, -200, 463, -922, 2478, 6184, 318, -360, 310, -205, 103, -38, 9 }, { 5, -10, 0, 57, -201, 463, -921, 2471, 6187, 323, -362, 311, -205, 103, -38, 9 }, { 5, -10, 0, 58, -201, 463, -920, 2464, 6188, 328, -364, 312, -205, 103, -38, 9 }, { 5, -10, 0, 58, -201, 463, -920, 2457, 6192, 333, -366, 313, -206, 103, -38, 9 }, { 5, -10, -1, 58, -201, 463, -919, 2450, 6195, 338, -368, 314, -206, 103, -38, 9 }, { 5, -10, -1, 58, -202, 464, -918, 2443, 6198, 343, -370, 314, -206, 103, -38, 9 }, { 5, -10, -1, 59, -202, 464, -917, 2436, 6199, 348, -372, 315, -206, 103, -38, 9 }, { 5, -10, -1, 59, -202, 464, -916, 2429, 6202, 353, -374, 316, -207, 103, -38, 9 }, { 5, -10, -1, 59, -203, 464, -915, 2423, 6204, 358, -376, 317, -207, 103, -38, 9 }, { 5, -10, -1, 60, -203, 464, -914, 2416, 6205, 363, -378, 318, -207, 103, -38, 9 }, { 5, -10, -2, 60, -203, 464, -913, 2409, 6209, 368, -380, 318, -207, 103, -38, 9 }, { 4, -9, -2, 60, -204, 464, -912, 2402, 6213, 373, -382, 319, -208, 103, -38, 9 }, { 4, -9, -2, 60, -204, 464, -911, 2395, 6214, 378, -383, 320, -208, 103, -38, 9 }, { 4, -9, -2, 61, -204, 464, -910, 2388, 6215, 383, -385, 321, -208, 103, -38, 9 }, { 4, -9, -2, 61, -205, 464, -909, 2382, 6217, 388, -387, 322, -208, 103, -38, 9 }, { 4, -9, -2, 61, -205, 464, -908, 2375, 6220, 393, -389, 322, -209, 103, -37, 9 }, { 4, -9, -3, 62, -205, 464, -907, 2368, 6222, 398, -391, 323, -209, 103, -37, 9 }, { 4, -9, -3, 62, -205, 464, -906, 2361, 6225, 403, -393, 324, -209, 103, -37, 8 }, { 4, -9, -3, 62, -206, 464, -905, 2354, 6228, 408, -395, 325, -209, 103, -37, 8 }, { 4, -9, -3, 62, -206, 464, -904, 2347, 6231, 413, -397, 326, -210, 103, -37, 8 }, { 4, -9, -3, 63, -206, 464, -903, 2340, 6233, 418, -399, 326, -210, 103, -37, 8 }, { 4, -9, -3, 63, -207, 464, -902, 2334, 6235, 423, -401, 327, -210, 103, -37, 8 }, { 4, -9, -4, 63, -207, 464, -901, 2327, 6237, 429, -403, 328, -210, 103, -37, 8 }, { 4, -8, -4, 63, -207, 464, -900, 2320, 6239, 434, -405, 329, -211, 103, -37, 8 }, { 4, -8, -4, 64, -208, 464, -899, 2313, 6241, 439, -407, 330, -211, 103, -37, 8 }, { 4, -8, -4, 64, -208, 464, -898, 2306, 6244, 444, -409, 330, -211, 103, -37, 8 }, { 4, -8, -4, 64, -208, 464, -897, 2300, 6245, 449, -411, 331, -211, 103, -37, 8 }, { 4, -8, -4, 64, -208, 464, -896, 2293, 6247, 454, -413, 332, -211, 103, -37, 8 }, { 4, -8, -4, 65, -209, 464, -895, 2286, 6249, 459, -414, 333, -212, 103, -37, 8 }, { 4, -8, -5, 65, -209, 464, -894, 2279, 6252, 465, -416, 333, -212, 103, -37, 8 }, { 4, -8, -5, 65, -209, 464, -893, 2272, 6254, 470, -418, 334, -212, 103, -37, 8 }, { 4, -8, -5, 65, -209, 464, -892, 2265, 6256, 475, -420, 335, -212, 103, -37, 8 }, { 4, -8, -5, 66, -210, 464, -891, 2259, 6258, 480, -422, 336, -213, 103, -37, 8 }, { 4, -8, -5, 66, -210, 464, -890, 2252, 6260, 485, -424, 337, -213, 103, -37, 8 }, { 4, -8, -5, 66, -210, 464, -889, 2245, 6262, 490, -426, 337, -213, 103, -36, 8 }, { 4, -8, -6, 67, -210, 464, -887, 2238, 6262, 496, -428, 338, -213, 103, -36, 8 }, { 4, -7, -6, 67, -211, 464, -886, 2231, 6264, 501, -430, 339, -213, 103, -36, 8 }, { 4, -7, -6, 67, -211, 464, -885, 2225, 6266, 506, -432, 340, -214, 103, -36, 8 }, { 4, -7, -6, 67, -211, 464, -884, 2218, 6269, 511, -434, 340, -214, 103, -36, 8 }, { 4, -7, -6, 68, -212, 464, -883, 2211, 6270, 517, -436, 341, -214, 103, -36, 8 }, { 4, -7, -6, 68, -212, 464, -882, 2204, 6272, 522, -438, 342, -214, 103, -36, 8 }, { 4, -7, -7, 68, -212, 464, -881, 2198, 6274, 527, -440, 343, -214, 103, -36, 8 }, { 4, -7, -7, 68, -212, 464, -880, 2191, 6277, 532, -441, 343, -215, 103, -36, 8 }, { 4, -7, -7, 69, -213, 464, -879, 2184, 6278, 538, -443, 344, -215, 103, -36, 8 }, { 4, -7, -7, 69, -213, 464, -877, 2177, 6279, 543, -445, 345, -215, 103, -36, 8 }, { 4, -7, -7, 69, -213, 464, -876, 2170, 6281, 548, -447, 346, -215, 103, -36, 8 }, { 4, -7, -7, 69, -213, 464, -875, 2164, 6282, 554, -449, 346, -215, 103, -36, 8 }, { 4, -7, -7, 70, -214, 464, -874, 2157, 6285, 559, -451, 347, -216, 103, -36, 8 }, { 4, -6, -8, 70, -214, 463, -873, 2150, 6288, 564, -453, 348, -216, 103, -36, 8 }, { 3, -6, -8, 70, -214, 463, -872, 2143, 6291, 569, -455, 349, -216, 103, -36, 8 }, { 3, -6, -8, 70, -214, 463, -871, 2137, 6292, 575, -457, 349, -216, 103, -36, 8 }, { 3, -6, -8, 70, -214, 463, -869, 2130, 6292, 580, -459, 350, -216, 103, -35, 8 }, { 3, -6, -8, 71, -215, 463, -868, 2123, 6294, 586, -461, 351, -217, 103, -35, 8 }, { 3, -6, -8, 71, -215, 463, -867, 2116, 6297, 591, -463, 352, -217, 103, -35, 7 }, { 3, -6, -9, 71, -215, 463, -866, 2110, 6300, 596, -465, 352, -217, 103, -35, 7 }, { 3, -6, -9, 71, -215, 463, -865, 2103, 6300, 602, -466, 353, -217, 103, -35, 7 }, { 3, -6, -9, 72, -216, 463, -863, 2096, 6301, 607, -468, 354, -217, 103, -35, 7 }, { 3, -6, -9, 72, -216, 463, -862, 2089, 6304, 612, -470, 355, -218, 103, -35, 7 }, { 3, -6, -9, 72, -216, 463, -861, 2083, 6305, 618, -472, 355, -218, 103, -35, 7 }, { 3, -6, -9, 72, -216, 462, -860, 2076, 6308, 623, -474, 356, -218, 103, -35, 7 }, { 3, -6, -9, 73, -216, 462, -859, 2069, 6308, 629, -476, 357, -218, 103, -35, 7 }, { 3, -5, -10, 73, -217, 462, -857, 2063, 6309, 634, -478, 358, -218, 103, -35, 7 }, { 3, -5, -10, 73, -217, 462, -856, 2056, 6313, 639, -480, 358, -219, 103, -35, 7 }, { 3, -5, -10, 73, -217, 462, -855, 2049, 6314, 645, -482, 359, -219, 103, -35, 7 }, { 3, -5, -10, 74, -217, 462, -854, 2042, 6315, 650, -484, 360, -219, 103, -35, 7 }, { 3, -5, -10, 74, -217, 462, -853, 2036, 6316, 656, -486, 360, -219, 103, -35, 7 }, { 3, -5, -10, 74, -218, 462, -851, 2029, 6316, 661, -487, 361, -219, 103, -34, 7 }, { 3, -5, -10, 74, -218, 461, -850, 2022, 6318, 667, -489, 362, -219, 103, -34, 7 }, { 3, -5, -11, 74, -218, 461, -849, 2016, 6321, 672, -491, 363, -220, 103, -34, 7 }, { 3, -5, -11, 75, -218, 461, -848, 2009, 6322, 678, -493, 363, -220, 103, -34, 7 }, { 3, -5, -11, 75, -219, 461, -846, 2002, 6324, 683, -495, 364, -220, 103, -34, 7 }, { 3, -5, -11, 75, -219, 461, -845, 1995, 6326, 689, -497, 365, -220, 102, -34, 7 }, { 3, -5, -11, 75, -219, 461, -844, 1989, 6328, 694, -499, 365, -220, 102, -34, 7 }, { 3, -5, -11, 76, -219, 461, -842, 1982, 6327, 700, -501, 366, -220, 102, -34, 7 }, { 3, -4, -11, 76, -219, 460, -841, 1975, 6330, 705, -503, 367, -221, 102, -34, 7 }, { 3, -4, -12, 76, -219, 460, -840, 1969, 6330, 711, -504, 368, -221, 102, -34, 7 }, { 3, -4, -12, 76, -220, 460, -839, 1962, 6334, 716, -506, 368, -221, 102, -34, 7 }, { 3, -4, -12, 76, -220, 460, -837, 1955, 6334, 722, -508, 369, -221, 102, -34, 7 }, { 3, -4, -12, 77, -220, 460, -836, 1949, 6334, 727, -510, 370, -221, 102, -34, 7 }, { 3, -4, -12, 77, -220, 460, -835, 1942, 6336, 733, -512, 370, -221, 102, -34, 7 }, { 3, -4, -12, 77, -220, 459, -833, 1935, 6338, 738, -514, 371, -221, 102, -34, 7 }, { 3, -4, -12, 77, -221, 459, -832, 1929, 6339, 744, -516, 372, -222, 102, -33, 7 }, { 3, -4, -13, 78, -221, 459, -831, 1922, 6342, 749, -518, 372, -222, 102, -33, 7 }, { 3, -4, -13, 78, -221, 459, -829, 1915, 6342, 755, -520, 373, -222, 102, -33, 7 }, { 3, -4, -13, 78, -221, 459, -828, 1909, 6341, 761, -521, 374, -222, 102, -33, 7 }, { 3, -4, -13, 78, -221, 458, -827, 1902, 6346, 766, -523, 374, -222, 102, -33, 6 }, { 2, -4, -13, 78, -221, 458, -825, 1896, 6346, 772, -525, 375, -222, 102, -33, 6 }, { 2, -3, -13, 79, -222, 458, -824, 1889, 6348, 777, -527, 376, -223, 102, -33, 6 }, { 2, -3, -13, 79, -222, 458, -823, 1882, 6350, 783, -529, 376, -223, 102, -33, 6 }, { 2, -3, -14, 79, -222, 458, -821, 1876, 6350, 789, -531, 377, -223, 102, -33, 6 }, { 2, -3, -14, 79, -222, 457, -820, 1869, 6353, 794, -533, 378, -223, 102, -33, 6 }, { 2, -3, -14, 79, -222, 457, -819, 1862, 6355, 800, -535, 378, -223, 102, -33, 6 }, { 2, -3, -14, 80, -222, 457, -817, 1856, 6352, 806, -536, 379, -223, 102, -33, 6 }, { 2, -3, -14, 80, -223, 457, -816, 1849, 6356, 811, -538, 380, -223, 101, -33, 6 }, { 2, -3, -14, 80, -223, 457, -815, 1843, 6356, 817, -540, 380, -223, 101, -32, 6 }, { 2, -3, -14, 80, -223, 456, -813, 1836, 6358, 823, -542, 381, -224, 101, -32, 6 }, { 2, -3, -15, 80, -223, 456, -812, 1829, 6361, 828, -544, 382, -224, 101, -32, 6 }, { 2, -3, -15, 81, -223, 456, -811, 1823, 6361, 834, -546, 382, -224, 101, -32, 6 }, { 2, -3, -15, 81, -223, 456, -809, 1816, 6361, 840, -548, 383, -224, 101, -32, 6 }, { 2, -3, -15, 81, -223, 455, -808, 1810, 6362, 845, -549, 384, -224, 101, -32, 6 }, { 2, -2, -15, 81, -224, 455, -806, 1803, 6363, 851, -551, 384, -224, 101, -32, 6 }, { 2, -2, -15, 81, -224, 455, -805, 1796, 6364, 857, -553, 385, -224, 101, -32, 6 }, { 2, -2, -15, 82, -224, 455, -804, 1790, 6364, 862, -555, 386, -224, 101, -32, 6 }, { 2, -2, -16, 82, -224, 454, -802, 1783, 6368, 868, -557, 386, -225, 101, -32, 6 }, { 2, -2, -16, 82, -224, 454, -801, 1777, 6368, 874, -559, 387, -225, 101, -32, 6 }, { 2, -2, -16, 82, -224, 454, -799, 1770, 6367, 880, -560, 388, -225, 101, -32, 6 }, { 2, -2, -16, 82, -224, 454, -798, 1764, 6368, 885, -562, 388, -225, 101, -31, 6 }, { 2, -2, -16, 83, -224, 453, -796, 1757, 6368, 891, -564, 389, -225, 101, -31, 6 }, { 2, -2, -16, 83, -225, 453, -795, 1751, 6370, 897, -566, 389, -225, 101, -31, 6 }, { 2, -2, -16, 83, -225, 453, -794, 1744, 6372, 903, -568, 390, -225, 100, -31, 6 }, { 2, -2, -16, 83, -225, 453, -792, 1737, 6373, 908, -570, 391, -225, 100, -31, 6 }, { 2, -2, -17, 83, -225, 452, -791, 1731, 6375, 914, -571, 391, -225, 100, -31, 6 }, { 2, -2, -17, 84, -225, 452, -789, 1724, 6375, 920, -573, 392, -226, 100, -31, 6 }, { 2, -2, -17, 84, -225, 452, -788, 1718, 6375, 926, -575, 393, -226, 100, -31, 6 }, { 2, -1, -17, 84, -225, 451, -786, 1711, 6376, 932, -577, 393, -226, 100, -31, 6 }, { 2, -1, -17, 84, -225, 451, -785, 1705, 6378, 937, -579, 394, -226, 100, -31, 5 }, { 2, -1, -17, 84, -226, 451, -783, 1698, 6380, 943, -581, 394, -226, 100, -31, 5 }, { 2, -1, -17, 84, -226, 451, -782, 1692, 6379, 949, -582, 395, -226, 100, -31, 5 }, { 2, -1, -18, 85, -226, 450, -781, 1685, 6380, 955, -584, 396, -226, 100, -30, 5 }, { 2, -1, -18, 85, -226, 450, -779, 1679, 6380, 961, -586, 396, -226, 100, -30, 5 }, { 2, -1, -18, 85, -226, 450, -778, 1672, 6381, 967, -588, 397, -226, 100, -30, 5 }, { 2, -1, -18, 85, -226, 449, -776, 1666, 6382, 972, -590, 398, -226, 100, -30, 5 }, { 1, -1, -18, 85, -226, 449, -775, 1659, 6386, 978, -592, 398, -227, 100, -30, 5 }, { 1, -1, -18, 86, -226, 449, -773, 1653, 6384, 984, -593, 399, -227, 99, -30, 5 }, { 1, -1, -18, 86, -226, 448, -772, 1646, 6387, 990, -595, 399, -227, 99, -30, 5 }, { 1, -1, -18, 86, -226, 448, -770, 1640, 6386, 996, -597, 400, -227, 99, -30, 5 }, { 1, -1, -19, 86, -227, 448, -769, 1633, 6390, 1002, -599, 400, -227, 99, -30, 5 }, { 1, 0, -19, 86, -227, 447, -767, 1627, 6389, 1008, -601, 401, -227, 99, -30, 5 }, { 1, 0, -19, 86, -227, 447, -766, 1621, 6388, 1014, -602, 402, -227, 99, -30, 5 }, { 1, 0, -19, 87, -227, 447, -764, 1614, 6388, 1019, -604, 402, -227, 99, -29, 5 }, { 1, 0, -19, 87, -227, 446, -763, 1608, 6389, 1025, -606, 403, -227, 99, -29, 5 }, { 1, 0, -19, 87, -227, 446, -761, 1601, 6390, 1031, -608, 403, -227, 99, -29, 5 }, { 1, 0, -19, 87, -227, 446, -760, 1595, 6390, 1037, -610, 404, -227, 99, -29, 5 }, { 1, 0, -19, 87, -227, 445, -758, 1588, 6390, 1043, -611, 405, -227, 99, -29, 5 }, { 1, 0, -20, 87, -227, 445, -757, 1582, 6393, 1049, -613, 405, -228, 99, -29, 5 }, { 1, 0, -20, 88, -227, 445, -755, 1575, 6393, 1055, -615, 406, -228, 98, -29, 5 }, { 1, 0, -20, 88, -227, 444, -753, 1569, 6394, 1061, -617, 406, -228, 98, -29, 5 }, { 1, 0, -20, 88, -227, 444, -752, 1563, 6393, 1067, -618, 407, -228, 98, -29, 5 }, { 1, 0, -20, 88, -228, 444, -750, 1556, 6395, 1073, -620, 407, -228, 98, -29, 5 }, { 1, 0, -20, 88, -228, 443, -749, 1550, 6396, 1079, -622, 408, -228, 98, -29, 5 }, { 1, 0, -20, 88, -228, 443, -747, 1543, 6395, 1085, -624, 409, -228, 98, -28, 5 }, { 1, 1, -20, 89, -228, 443, -746, 1537, 6394, 1091, -626, 409, -228, 98, -28, 5 }, { 1, 1, -21, 89, -228, 442, -744, 1531, 6395, 1097, -627, 410, -228, 98, -28, 4 }, { 1, 1, -21, 89, -228, 442, -743, 1524, 6397, 1103, -629, 410, -228, 98, -28, 4 }, { 1, 1, -21, 89, -228, 441, -741, 1518, 6397, 1109, -631, 411, -228, 98, -28, 4 }, { 1, 1, -21, 89, -228, 441, -739, 1511, 6399, 1115, -633, 411, -228, 97, -28, 4 }, { 1, 1, -21, 89, -228, 441, -738, 1505, 6398, 1121, -634, 412, -228, 97, -28, 4 }, { 1, 1, -21, 90, -228, 440, -736, 1499, 6398, 1127, -636, 412, -228, 97, -28, 4 }, { 1, 1, -21, 90, -228, 440, -735, 1492, 6399, 1133, -638, 413, -228, 97, -28, 4 }, { 1, 1, -21, 90, -228, 440, -733, 1486, 6399, 1139, -640, 413, -228, 97, -28, 4 }, { 1, 1, -22, 90, -228, 439, -732, 1480, 6399, 1145, -641, 414, -228, 97, -27, 4 }, { 1, 1, -22, 90, -228, 439, -730, 1473, 6399, 1151, -643, 415, -228, 97, -27, 4 }, { 1, 1, -22, 90, -228, 438, -728, 1467, 6400, 1157, -645, 415, -228, 97, -27, 4 }, { 1, 1, -22, 90, -228, 438, -727, 1461, 6401, 1163, -647, 416, -229, 97, -27, 4 }, { 1, 1, -22, 91, -228, 438, -725, 1454, 6400, 1169, -648, 416, -229, 97, -27, 4 }, { 1, 2, -22, 91, -228, 437, -724, 1448, 6401, 1175, -650, 417, -229, 96, -27, 4 }, { 1, 2, -22, 91, -229, 437, -722, 1442, 6402, 1181, -652, 417, -229, 96, -27, 4 }, { 1, 2, -22, 91, -229, 436, -720, 1435, 6403, 1187, -654, 418, -229, 96, -27, 4 }, { 1, 2, -22, 91, -229, 436, -719, 1429, 6403, 1193, -655, 418, -229, 96, -27, 4 }, { 0, 2, -23, 91, -229, 435, -717, 1423, 6404, 1200, -657, 419, -229, 96, -27, 4 }, { 0, 2, -23, 91, -229, 435, -716, 1416, 6405, 1206, -659, 419, -229, 96, -26, 4 }, { 0, 2, -23, 92, -229, 435, -714, 1410, 6402, 1212, -660, 420, -229, 96, -26, 4 }, { 0, 2, -23, 92, -229, 434, -712, 1404, 6403, 1218, -662, 420, -229, 96, -26, 4 }, { 0, 2, -23, 92, -229, 434, -711, 1398, 6403, 1224, -664, 421, -229, 96, -26, 4 }, { 0, 2, -23, 92, -229, 433, -709, 1391, 6406, 1230, -666, 421, -229, 95, -26, 4 }, { 0, 2, -23, 92, -229, 433, -707, 1385, 6404, 1236, -667, 422, -229, 95, -26, 4 }, { 0, 2, -23, 92, -229, 432, -706, 1379, 6406, 1242, -669, 422, -229, 95, -26, 4 }, { 0, 2, -24, 92, -229, 432, -704, 1373, 6406, 1249, -671, 423, -229, 95, -26, 3 }, { 0, 2, -24, 93, -229, 432, -702, 1366, 6405, 1255, -672, 423, -229, 95, -26, 3 }, { 0, 3, -24, 93, -229, 431, -701, 1360, 6405, 1261, -674, 424, -229, 95, -26, 3 }, { 0, 3, -24, 93, -229, 431, -699, 1354, 6404, 1267, -676, 424, -229, 95, -25, 3 }, { 0, 3, -24, 93, -229, 430, -698, 1348, 6404, 1273, -677, 425, -229, 95, -25, 3 }, { 0, 3, -24, 93, -229, 430, -696, 1341, 6406, 1279, -679, 425, -229, 94, -25, 3 }, { 0, 3, -24, 93, -229, 429, -694, 1335, 6406, 1285, -681, 426, -229, 94, -25, 3 }, { 0, 3, -24, 93, -229, 429, -693, 1329, 6405, 1292, -682, 426, -229, 94, -25, 3 }, { 0, 3, -24, 94, -229, 428, -691, 1323, 6404, 1298, -684, 427, -229, 94, -25, 3 }, { 0, 3, -25, 94, -229, 428, -689, 1316, 6406, 1304, -686, 427, -229, 94, -25, 3 }, }; openMSX-RELEASE_0_12_0/src/sound/DACSound16S.cc000066400000000000000000000034701257557151200205630ustar00rootroot00000000000000#include "DACSound16S.hh" #include "DeviceConfig.hh" #include "MSXMotherBoard.hh" #include "DynamicClock.hh" #include "serialize.hh" namespace openmsx { DACSound16S::DACSound16S(string_ref name, string_ref desc, const DeviceConfig& config) : SoundDevice(config.getMotherBoard().getMSXMixer(), name, desc, 1) , lastWrittenValue(0) { registerSound(config); } DACSound16S::~DACSound16S() { unregisterSound(); } void DACSound16S::setOutputRate(unsigned sampleRate) { setInputRate(sampleRate); } void DACSound16S::reset(EmuTime::param time) { writeDAC(0, time); } void DACSound16S::writeDAC(int16_t value, EmuTime::param time) { int delta = value - lastWrittenValue; if (delta == 0) return; lastWrittenValue = value; BlipBuffer::TimeIndex t; getHostSampleClock().getTicksTill(time, t); blip.addDelta(t, delta); } void DACSound16S::generateChannels(int** bufs, unsigned num) { // Note: readSamples() replaces the values in the buffer (it doesn't // add the new values to the existing values in the buffer). That's OK // because this is a single-channel SoundDevice. if (!blip.readSamples<1>(bufs[0], num)) { bufs[0] = nullptr; } } bool DACSound16S::updateBuffer(unsigned length, int* buffer, EmuTime::param /*time*/) { return mixChannels(buffer, length); } template void DACSound16S::serialize(Archive& ar, unsigned /*version*/) { // Note: It's ok to NOT serialize a DAC object if you call the // writeDAC() method in some other way during de-serialization. // This is for example done in MSXPPI/KeyClick. int16_t lastValue = lastWrittenValue; ar.serialize("lastValue", lastValue); if (ar.isLoader()) { writeDAC(lastValue, getHostSampleClock().getTime()); } } INSTANTIATE_SERIALIZE_METHODS(DACSound16S); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/DACSound16S.hh000066400000000000000000000014741257557151200205770ustar00rootroot00000000000000// This class implements a 16 bit signed DAC #ifndef DACSOUND16S_HH #define DACSOUND16S_HH #include "SoundDevice.hh" #include "BlipBuffer.hh" #include namespace openmsx { class DACSound16S : public SoundDevice { public: DACSound16S(string_ref name, string_ref desc, const DeviceConfig& config); virtual ~DACSound16S(); void reset(EmuTime::param time); void writeDAC(int16_t value, EmuTime::param time); template void serialize(Archive& ar, unsigned version); private: // SoundDevice void setOutputRate(unsigned sampleRate) override; void generateChannels(int** bufs, unsigned num) override; bool updateBuffer(unsigned length, int* buffer, EmuTime::param time) override; BlipBuffer blip; int16_t lastWrittenValue; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/DACSound8U.cc000066400000000000000000000005311257557151200205010ustar00rootroot00000000000000#include "DACSound8U.hh" namespace openmsx { DACSound8U::DACSound8U(string_ref name, string_ref desc, const DeviceConfig& config) : DACSound16S(name, desc, config) { } void DACSound8U::writeDAC(uint8_t value, EmuTime::param time) { DACSound16S::writeDAC((int16_t(value) - 0x80) << 8, time); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/DACSound8U.hh000066400000000000000000000005611257557151200205160ustar00rootroot00000000000000// This class implements a 8 bit unsigned DAC #ifndef DACSOUND8U_HH #define DACSOUND8U_HH #include "DACSound16S.hh" namespace openmsx { class DACSound8U final : public DACSound16S { public: DACSound8U(string_ref name, string_ref desc, const DeviceConfig& config); void writeDAC(uint8_t value, EmuTime::param time); }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/DirectXSoundDriver.cc000066400000000000000000000151241257557151200224170ustar00rootroot00000000000000#ifdef _WIN32 #include "DirectXSoundDriver.hh" #include "MSXException.hh" #include "openmsx.hh" #include "win32-windowhandle.hh" #include namespace openmsx { static const int BYTES_PER_SAMPLE = 2; static const int CHANNELS = 2; DirectXSoundDriver::DirectXSoundDriver(unsigned sampleRate, unsigned samples) { if (DirectSoundCreate(nullptr, &directSound, nullptr) != DS_OK) { throw MSXException("Couldn't initialize DirectSound driver"); } HWND hwnd = getWindowHandle(); if (IDirectSound_SetCooperativeLevel( directSound, hwnd, DSSCL_EXCLUSIVE) != DS_OK) { throw MSXException("Couldn't initialize DirectSound driver"); } DSCAPS capabilities; memset(&capabilities, 0, sizeof(capabilities)); capabilities.dwSize = sizeof(capabilities); IDirectSound_GetCaps(directSound, &capabilities); if (!((capabilities.dwFlags & DSCAPS_PRIMARY16BIT) || (capabilities.dwFlags & DSCAPS_SECONDARY16BIT))) { // no 16 bits per sample throw MSXException("Couldn't configure 16 bit per sample"); } if (!((capabilities.dwFlags & DSCAPS_PRIMARYSTEREO) || (capabilities.dwFlags & DSCAPS_SECONDARYSTEREO))) { // no stereo throw MSXException("Couldn't configure stereo mode"); } PCMWAVEFORMAT pcmwf; memset(&pcmwf, 0, sizeof(pcmwf)); pcmwf.wf.wFormatTag = WAVE_FORMAT_PCM; pcmwf.wf.nChannels = CHANNELS; pcmwf.wf.nSamplesPerSec = sampleRate; pcmwf.wBitsPerSample = 8 * BYTES_PER_SAMPLE; pcmwf.wf.nBlockAlign = CHANNELS * BYTES_PER_SAMPLE; pcmwf.wf.nAvgBytesPerSec = pcmwf.wf.nSamplesPerSec * pcmwf.wf.nBlockAlign; DSBUFFERDESC desc; memset(&desc, 0, sizeof(desc)); desc.dwSize = sizeof(desc); desc.dwFlags = DSBCAPS_PRIMARYBUFFER; bufferSize = 2 * samples * BYTES_PER_SAMPLE * CHANNELS; fragmentSize = 1; while (bufferSize / fragmentSize >= 32 || fragmentSize < 512) { fragmentSize <<= 1; } DWORD fragmentCount = 1 + bufferSize / fragmentSize; while (fragmentCount < 8) { fragmentCount *= 2; fragmentSize /= 2; } bufferSize = fragmentCount * fragmentSize; if (IDirectSound_CreateSoundBuffer( directSound, &desc, &secondaryBuffer, nullptr) != DS_OK) { throw MSXException("Couldn't initialize DirectSound driver"); } memset(&desc, 0, sizeof(desc)); desc.dwSize = sizeof(desc); desc.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLFREQUENCY | DSBCAPS_CTRLPAN | DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS; desc.dwBufferBytes = bufferSize; desc.lpwfxFormat = reinterpret_cast(&pcmwf); if (IDirectSound_CreateSoundBuffer( directSound, &desc, &primaryBuffer, nullptr) != DS_OK) { throw MSXException("Couldn't initialize DirectSound driver"); } WAVEFORMATEX wfex; memset(&wfex, 0, sizeof(wfex)); wfex.wFormatTag = WAVE_FORMAT_PCM; wfex.nChannels = CHANNELS; wfex.nSamplesPerSec = sampleRate; wfex.wBitsPerSample = 8 * BYTES_PER_SAMPLE; wfex.nBlockAlign = CHANNELS * BYTES_PER_SAMPLE; wfex.nAvgBytesPerSec = wfex.nSamplesPerSec * wfex.nBlockAlign; if (IDirectSoundBuffer_SetFormat(secondaryBuffer, &wfex) != DS_OK) { throw MSXException("Couldn't initialize DirectSound driver"); } bufferOffset = bufferSize; dxClear(); skipCount = 0; state = DX_SOUND_DISABLED; frequency = sampleRate; } DirectXSoundDriver::~DirectXSoundDriver() { IDirectSoundBuffer_Stop(primaryBuffer); IDirectSoundBuffer_Release(primaryBuffer); IDirectSound_Release(directSound); } void DirectXSoundDriver::mute() { dxClear(); IDirectSoundBuffer_Stop(primaryBuffer); state = DX_SOUND_DISABLED; } void DirectXSoundDriver::unmute() { state = DX_SOUND_ENABLED; } unsigned DirectXSoundDriver::getFrequency() const { return frequency; } unsigned DirectXSoundDriver::getSamples() const { return fragmentSize; } // #define MAKE_HRESULT(s,f,c) ((HRESULT)(((unsigned long)(s)<<31)|((unsigned long)(f)<<16)|((unsigned long)(c)))) // #define MAKE_DSHRESULT(code) MAKE_HRESULT(1, _FACDS, code) // #define DSERR_BUFFERLOST MAKE_DSHRESULT(150) static const HRESULT OPENMSX_DSERR_BUFFERLOST = (1u << 31) | (_FACDS << 16) | 150; void DirectXSoundDriver::dxClear() { void *audioBuffer1, *audioBuffer2; DWORD audioSize1, audioSize2; if (IDirectSoundBuffer_Lock( primaryBuffer, 0, bufferSize, &audioBuffer1, &audioSize1, &audioBuffer2, &audioSize2, 0) == OPENMSX_DSERR_BUFFERLOST) { IDirectSoundBuffer_Restore(primaryBuffer); } else { memset(audioBuffer1, 0, audioSize1); if (audioBuffer2) { memset(audioBuffer2, 0, audioSize2); } IDirectSoundBuffer_Unlock( primaryBuffer, audioBuffer1, audioSize1, audioBuffer2, audioSize2); } } int DirectXSoundDriver::dxCanWrite(unsigned start, unsigned size) { DWORD readPos, writePos; IDirectSoundBuffer_GetCurrentPosition( primaryBuffer, &readPos, &writePos); unsigned end = start + size; if (writePos < readPos) writePos += bufferSize; if (start < readPos) start += bufferSize; if (end < readPos) end += bufferSize; if ((start < writePos) || (end < writePos)) { return (bufferSize - (writePos - readPos)) / 2 - fragmentSize; } else { return 0; } } void DirectXSoundDriver::dxWriteOne(short* buffer, unsigned lockSize) { void *audioBuffer1, *audioBuffer2; DWORD audioSize1, audioSize2; do { if (IDirectSoundBuffer_Lock( primaryBuffer, bufferOffset, lockSize, &audioBuffer1, &audioSize1, &audioBuffer2, &audioSize2, 0) == OPENMSX_DSERR_BUFFERLOST) { IDirectSoundBuffer_Restore(primaryBuffer); IDirectSoundBuffer_Lock( primaryBuffer, bufferOffset, lockSize, &audioBuffer1, &audioSize1, &audioBuffer2, &audioSize2, 0); } } while ((audioSize1 + audioSize2) < lockSize); memcpy(audioBuffer1, buffer, audioSize1); if (audioBuffer2) { memcpy(audioBuffer2, reinterpret_cast(buffer) + audioSize1, audioSize2); } IDirectSoundBuffer_Unlock(primaryBuffer, audioBuffer1, audioSize1, audioBuffer2, audioSize2); bufferOffset += lockSize; bufferOffset %= bufferSize; } void DirectXSoundDriver::uploadBuffer(short* buffer, unsigned count) { if (state == DX_SOUND_DISABLED) return; if (state == DX_SOUND_ENABLED) { DWORD readPos, writePos; IDirectSoundBuffer_GetCurrentPosition( primaryBuffer, &readPos, &writePos); bufferOffset = (readPos + bufferSize / 2) % bufferSize; if (IDirectSoundBuffer_Play(primaryBuffer, 0, 0, DSBPLAY_LOOPING) == OPENMSX_DSERR_BUFFERLOST) { IDirectSoundBuffer_Play( primaryBuffer, 0, 0, DSBPLAY_LOOPING); } state = DX_SOUND_RUNNING; } count *= (CHANNELS * BYTES_PER_SAMPLE); if (skipCount > 0) { skipCount -= count; return; } skipCount = dxCanWrite(bufferOffset, count); if (skipCount <= 0) { dxWriteOne(buffer, count); } } } // namespace openmsx #endif // _WIN32 openMSX-RELEASE_0_12_0/src/sound/DirectXSoundDriver.hh000066400000000000000000000022261257557151200224300ustar00rootroot00000000000000#ifndef DIRECTXSOUNDDRIVER_HH #define DIRECTXSOUNDDRIVER_HH #ifdef _WIN32 #include "SoundDriver.hh" #include "noncopyable.hh" #ifdef WIN32_LEAN_AND_MEAN #undef WIN32_LEAN_AND_MEAN // Needed for #endif #define DIRECTSOUND_VERSION 0x0500 #include #include namespace openmsx { class DirectXSoundDriver final : public SoundDriver, private noncopyable { public: DirectXSoundDriver(unsigned sampleRate, unsigned bufferSize); ~DirectXSoundDriver(); void mute() override; void unmute() override; unsigned getFrequency() const override; unsigned getSamples() const override; void uploadBuffer(short* buffer, unsigned len) override; private: void dxClear(); int dxCanWrite(unsigned start, unsigned size); void dxWriteOne(short* buffer, unsigned lockSize); enum DxState { DX_SOUND_DISABLED, DX_SOUND_ENABLED, DX_SOUND_RUNNING }; DxState state; unsigned bufferOffset; unsigned bufferSize; unsigned fragmentSize; int skipCount; LPDIRECTSOUNDBUFFER primaryBuffer; LPDIRECTSOUNDBUFFER secondaryBuffer; LPDIRECTSOUND directSound; unsigned frequency; }; } // namespace openmsx #endif // _WIN32 #endif // DIRECTXSOUNDDRIVER_HH openMSX-RELEASE_0_12_0/src/sound/DummyAY8910Periphery.hh000066400000000000000000000012171257557151200224370ustar00rootroot00000000000000#ifndef DUMMYAY8910PERIPHERY_HH #define DUMMYAY8910PERIPHERY_HH #include "AY8910Periphery.hh" namespace openmsx { class DummyAY8910Periphery final : public AY8910Periphery { public: static DummyAY8910Periphery& instance() { static DummyAY8910Periphery oneInstance; return oneInstance; } byte readA(EmuTime::param /*time*/) override { return 255; } byte readB(EmuTime::param /*time*/) override { return 255; } void writeA(byte /*value*/, EmuTime::param /*time*/) override {} void writeB(byte /*value*/, EmuTime::param /*time*/) override {} private: DummyAY8910Periphery() {} ~DummyAY8910Periphery() {} }; }; // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/DummyAudioInputDevice.cc000066400000000000000000000007041257557151200231030ustar00rootroot00000000000000#include "DummyAudioInputDevice.hh" namespace openmsx { string_ref DummyAudioInputDevice::getDescription() const { return ""; } void DummyAudioInputDevice::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/) { } void DummyAudioInputDevice::unplugHelper(EmuTime::param /*time*/) { } short DummyAudioInputDevice::readSample(EmuTime::param /*time*/) { return 0; // silence } } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/DummyAudioInputDevice.hh000066400000000000000000000006671257557151200231250ustar00rootroot00000000000000#ifndef DUMMYAUDIOINPUTDEVICE_HH #define DUMMYAUDIOINPUTDEVICE_HH #include "AudioInputDevice.hh" namespace openmsx { class DummyAudioInputDevice final : public AudioInputDevice { public: string_ref getDescription() const override; void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; short readSample(EmuTime::param time) override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/DummyY8950KeyboardDevice.cc000066400000000000000000000010501257557151200232340ustar00rootroot00000000000000#include "DummyY8950KeyboardDevice.hh" namespace openmsx { void DummyY8950KeyboardDevice::write(byte /*data*/, EmuTime::param /*time*/) { // ignore data } byte DummyY8950KeyboardDevice::read(EmuTime::param /*time*/) { return 255; } string_ref DummyY8950KeyboardDevice::getDescription() const { return ""; } void DummyY8950KeyboardDevice::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/) { } void DummyY8950KeyboardDevice::unplugHelper(EmuTime::param /*time*/) { } } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/DummyY8950KeyboardDevice.hh000066400000000000000000000007661257557151200232630ustar00rootroot00000000000000#ifndef DUMMYY8950KEYBOARDDEVICE_HH #define DUMMYY8950KEYBOARDDEVICE_HH #include "Y8950KeyboardDevice.hh" namespace openmsx { class DummyY8950KeyboardDevice final : public Y8950KeyboardDevice { public: void write(byte data, EmuTime::param time) override; byte read(EmuTime::param time) override; string_ref getDescription() const override; void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/EmuTimer.cc000066400000000000000000000043771257557151200204270ustar00rootroot00000000000000#include "EmuTimer.hh" #include "serialize.hh" #include "memory.hh" using std::unique_ptr; namespace openmsx { unique_ptr EmuTimer::createOPM_1( Scheduler& scheduler, EmuTimerCallback& cb) { return make_unique( scheduler, cb, 0x40, 3579545, 64 * 2 , 1024); } unique_ptr EmuTimer::createOPM_2( Scheduler& scheduler, EmuTimerCallback& cb) { return make_unique( scheduler, cb, 0x20, 3579545, 64 * 2 * 16, 256); } unique_ptr EmuTimer::createOPL3_1( Scheduler& scheduler, EmuTimerCallback& cb) { return make_unique( scheduler, cb, 0x40, 3579545, 72 * 4 , 256); } unique_ptr EmuTimer::createOPL3_2( Scheduler& scheduler, EmuTimerCallback& cb) { return make_unique( scheduler, cb, 0x20, 3579545, 72 * 4 * 4, 256); } unique_ptr EmuTimer::createOPL4_1( Scheduler& scheduler, EmuTimerCallback& cb) { return make_unique( scheduler, cb, 0x40, 33868800, 72 * 38 , 256); } unique_ptr EmuTimer::createOPL4_2( Scheduler& scheduler, EmuTimerCallback& cb) { return make_unique( scheduler, cb, 0x20, 33868800, 72 * 38 * 4, 256); } EmuTimer::EmuTimer(Scheduler& scheduler, EmuTimerCallback& cb_, byte flag_, unsigned freq_num, unsigned freq_denom, unsigned maxval_) : Schedulable(scheduler), cb(cb_) , clock(EmuTime::dummy()) , maxval(maxval_), count(maxval_) , flag(flag_), counting(false) { clock.setFreq(freq_num, freq_denom); } void EmuTimer::setValue(int value) { count = maxval - value; } void EmuTimer::setStart(bool start, EmuTime::param time) { if (start != counting) { counting = start; if (start) { schedule(time); } else { unschedule(); } } } void EmuTimer::schedule(EmuTime::param time) { clock.reset(time); clock += count; setSyncPoint(clock.getTime()); } void EmuTimer::unschedule() { removeSyncPoint(); } void EmuTimer::executeUntil(EmuTime::param time) { cb.callback(flag); schedule(time); } template void EmuTimer::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("count", count); ar.serialize("counting", counting); } INSTANTIATE_SERIALIZE_METHODS(EmuTimer); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/EmuTimer.hh000066400000000000000000000026571257557151200204400ustar00rootroot00000000000000#ifndef EMUTIMER_HH #define EMUTIMER_HH #include "Schedulable.hh" #include "DynamicClock.hh" #include "openmsx.hh" #include namespace openmsx { class EmuTimerCallback { public: virtual void callback(byte value) = 0; protected: ~EmuTimerCallback() {} }; class EmuTimer final : public Schedulable { public: EmuTimer(Scheduler& scheduler, EmuTimerCallback& cb, byte flag, unsigned freq_num, unsigned freq_denom, unsigned maxval); static std::unique_ptr createOPM_1( Scheduler& scheduler, EmuTimerCallback& cb); static std::unique_ptr createOPM_2( Scheduler& scheduler, EmuTimerCallback& cb); static std::unique_ptr createOPL3_1( Scheduler& scheduler, EmuTimerCallback& cb); static std::unique_ptr createOPL3_2( Scheduler& scheduler, EmuTimerCallback& cb); static std::unique_ptr createOPL4_1( Scheduler& scheduler, EmuTimerCallback& cb); static std::unique_ptr createOPL4_2( Scheduler& scheduler, EmuTimerCallback& cb); void setValue(int value); void setStart(bool start, EmuTime::param time); template void serialize(Archive& ar, unsigned version); private: void executeUntil(EmuTime::param time) override; void schedule(EmuTime::param time); void unschedule(); EmuTimerCallback& cb; DynamicClock clock; const unsigned maxval; int count; const byte flag; bool counting; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/KeyClick.cc000066400000000000000000000010271257557151200203630ustar00rootroot00000000000000#include "KeyClick.hh" namespace openmsx { KeyClick::KeyClick(const DeviceConfig& config) : dac("keyclick", "1-bit click generator", config) , status(false) { } void KeyClick::reset(EmuTime::param time) { setClick(false, time); } void KeyClick::setClick(bool newStatus, EmuTime::param time) { if (newStatus != status) { status = newStatus; dac.writeDAC((status ? 0xff : 0x80), time); } } // We don't need a serialize() method, instead the setClick() method // gets called during de-serialization. } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/KeyClick.hh000066400000000000000000000006351257557151200204010ustar00rootroot00000000000000#ifndef KEYCLICK_HH #define KEYCLICK_HH #include "DACSound8U.hh" #include "EmuTime.hh" #include "noncopyable.hh" namespace openmsx { class DeviceConfig; class KeyClick : private noncopyable { public: explicit KeyClick(const DeviceConfig& config); void reset(EmuTime::param time); void setClick(bool status, EmuTime::param time); private: DACSound8U dac; bool status; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/MSXAudio.cc000066400000000000000000000064461257557151200203300ustar00rootroot00000000000000#include "MSXAudio.hh" #include "Y8950Periphery.hh" #include "DACSound8U.hh" #include "StringOp.hh" #include "serialize.hh" #include "memory.hh" using std::string; namespace openmsx { // MSXAudio MSXAudio::MSXAudio(const DeviceConfig& config) : MSXDevice(config) , y8950(getName(), config, config.getChildDataAsInt("sampleram", 256) * 1024, getCurrentTime(), *this) , dacValue(0x80), dacEnabled(false) { string type(StringOp::toLower(config.getChildData("type", "philips"))); if (type == "philips") { dac = make_unique( getName() + " 8-bit DAC", "MSX-AUDIO 8-bit DAC", config); } powerUp(getCurrentTime()); } MSXAudio::~MSXAudio() { // delete soon, because PanasonicAudioPeriphery still uses // this object in its destructor periphery.reset(); } Y8950Periphery& MSXAudio::createPeriphery(const string& soundDeviceName) { periphery = Y8950PeripheryFactory::create( *this, getDeviceConfig2(), soundDeviceName); return *periphery; } void MSXAudio::powerUp(EmuTime::param time) { y8950.clearRam(); reset(time); } void MSXAudio::reset(EmuTime::param time) { y8950.reset(time); periphery->reset(); registerLatch = 0; // TODO check } byte MSXAudio::readIO(word port, EmuTime::param time) { byte result; if ((port & 0xFF) == 0x0A) { // read DAC result = 255; } else { result = (port & 1) ? y8950.readReg(registerLatch, time) : y8950.readStatus(time); } return result; } byte MSXAudio::peekIO(word port, EmuTime::param time) const { if ((port & 0xFF) == 0x0A) { // read DAC return 255; // read always returns 255 } else { return (port & 1) ? y8950.peekReg(registerLatch, time) : y8950.peekStatus(time); } } void MSXAudio::writeIO(word port, byte value, EmuTime::param time) { if ((port & 0xFF) == 0x0A) { dacValue = value; if (dacEnabled) { assert(dac); dac->writeDAC(dacValue, time); } } else if ((port & 0x01) == 0) { // 0xC0 or 0xC2 registerLatch = value; } else { // 0xC1 or 0xC3 y8950.writeReg(registerLatch, value, time); } } byte MSXAudio::readMem(word address, EmuTime::param time) { return periphery->readMem(address, time); } byte MSXAudio::peekMem(word address, EmuTime::param time) const { return periphery->peekMem(address, time); } void MSXAudio::writeMem(word address, byte value, EmuTime::param time) { periphery->writeMem(address, value, time); } const byte* MSXAudio::getReadCacheLine(word start) const { return periphery->getReadCacheLine(start); } byte* MSXAudio::getWriteCacheLine(word start) const { return periphery->getWriteCacheLine(start); } void MSXAudio::enableDAC(bool enable, EmuTime::param time) { if ((dacEnabled != enable) && dac) { dacEnabled = enable; byte value = dacEnabled ? dacValue : 0x80; dac->writeDAC(value, time); } } template void MSXAudio::serialize(Archive& ar, unsigned /*version*/) { ar.serializePolymorphic("periphery", *periphery); ar.serialize("Y8950", y8950); ar.serialize("registerLatch", registerLatch); ar.serialize("dacValue", dacValue); ar.serialize("dacEnabled", dacEnabled); if (ar.isLoader()) { // restore dac status if (dacEnabled) { assert(dac); dac->writeDAC(dacValue, getCurrentTime()); } } } INSTANTIATE_SERIALIZE_METHODS(MSXAudio); REGISTER_MSXDEVICE(MSXAudio, "MSX-Audio"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/MSXAudio.hh000066400000000000000000000030151257557151200203270ustar00rootroot00000000000000#ifndef MSXAUDIO_HH #define MSXAUDIO_HH #include "MSXDevice.hh" #include "Y8950.hh" #include #include namespace openmsx { class Y8950Periphery; class DACSound8U; class MSXAudio final : public MSXDevice { public: explicit MSXAudio(const DeviceConfig& config); ~MSXAudio(); /** Creates a periphery object for this MSXAudio cartridge. * The ownership of the object remains with the MSXAudio instance. */ Y8950Periphery& createPeriphery(const std::string& soundDeviceName); void powerUp(EmuTime::param time) override; void reset(EmuTime::param time) override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte* getWriteCacheLine(word start) const override; template void serialize(Archive& ar, unsigned version); private: void enableDAC(bool enable, EmuTime::param time); std::unique_ptr periphery; // polymorphic Y8950 y8950; std::unique_ptr dac; // can be nullptr int registerLatch; byte dacValue; bool dacEnabled; friend class MusicModulePeriphery; friend class PanasonicAudioPeriphery; friend class ToshibaAudioPeriphery; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/MSXFmPac.cc000066400000000000000000000067031257557151200202510ustar00rootroot00000000000000#include "MSXFmPac.hh" #include "CacheLine.hh" #include "serialize.hh" namespace openmsx { static const char* const PAC_Header = "PAC2 BACKUP DATA"; MSXFmPac::MSXFmPac(const DeviceConfig& config) : MSXMusicBase(config) , sram(getName() + " SRAM", 0x1FFE, config, PAC_Header) , romBlockDebug(*this, &bank, 0x4000, 0x4000, 14) { reset(getCurrentTime()); } void MSXFmPac::reset(EmuTime::param time) { MSXMusicBase::reset(time); enable = 0; sramEnabled = false; bank = 0; r1ffe = r1fff = 0; // actual value doesn't matter as long // as it's not the magic combination } void MSXFmPac::writeIO(word port, byte value, EmuTime::param time) { if (enable & 1) { MSXMusicBase::writeIO(port, value, time); } } byte MSXFmPac::readMem(word address, EmuTime::param /*time*/) { address &= 0x3FFF; switch (address) { case 0x3FF6: return enable; case 0x3FF7: return bank; default: if (sramEnabled) { if (address < 0x1FFE) { return sram[address]; } else if (address == 0x1FFE) { return r1ffe; // always 0x4D } else if (address == 0x1FFF) { return r1fff; // always 0x69 } else { return 0xFF; } } else { return rom[bank * 0x4000 + address]; } } } const byte* MSXFmPac::getReadCacheLine(word address) const { address &= 0x3FFF; if (address == (0x3FF6 & CacheLine::HIGH)) { return nullptr; } if (sramEnabled) { if (address < (0x1FFE & CacheLine::HIGH)) { return &sram[address]; } else if (address == (0x1FFE & CacheLine::HIGH)) { return nullptr; } else { return unmappedRead; } } else { return &rom[bank * 0x4000 + address]; } } void MSXFmPac::writeMem(word address, byte value, EmuTime::param time) { // 'enable' has no effect for memory mapped access // (thanks to BiFiMSX for investigating this) address &= 0x3FFF; switch (address) { case 0x1FFE: if (!(enable & 0x10)) { r1ffe = value; checkSramEnable(); } break; case 0x1FFF: if (!(enable & 0x10)) { r1fff = value; checkSramEnable(); } break; case 0x3FF4: writeRegisterPort(value, time); break; case 0x3FF5: writeDataPort(value, time); break; case 0x3FF6: enable = value & 0x11; if (enable & 0x10) { r1ffe = r1fff = 0; // actual value not important checkSramEnable(); } break; case 0x3FF7: { byte newBank = value & 0x03; if (bank != newBank) { bank = newBank; invalidateMemCache(0x0000, 0x10000); } break; } default: if (sramEnabled && (address < 0x1FFE)) { sram.write(address, value); } } } byte* MSXFmPac::getWriteCacheLine(word address) const { address &= 0x3FFF; if (address == (0x1FFE & CacheLine::HIGH)) { return nullptr; } if (address == (0x3FF4 & CacheLine::HIGH)) { return nullptr; } if (sramEnabled && (address < 0x1FFE)) { return nullptr; } else { return unmappedWrite; } } void MSXFmPac::checkSramEnable() { bool newEnabled = (r1ffe == 0x4D) && (r1fff == 0x69); if (sramEnabled != newEnabled) { sramEnabled = newEnabled; invalidateMemCache(0x0000, 0x10000); } } template void MSXFmPac::serialize(Archive& ar, unsigned version) { ar.template serializeInlinedBase(*this, version); ar.serialize("sram", sram); ar.serialize("enable", enable); ar.serialize("bank", bank); ar.serialize("r1ffe", r1ffe); ar.serialize("r1fff", r1fff); if (ar.isLoader()) { // sramEnabled can be calculated checkSramEnable(); } } INSTANTIATE_SERIALIZE_METHODS(MSXFmPac); REGISTER_MSXDEVICE(MSXFmPac, "FM-PAC"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/MSXFmPac.hh000066400000000000000000000017011257557151200202540ustar00rootroot00000000000000#ifndef MSXFMPAC_HH #define MSXFMPAC_HH #include "MSXMusic.hh" #include "SRAM.hh" #include "RomBlockDebuggable.hh" #include "serialize_meta.hh" namespace openmsx { class MSXFmPac final : public MSXMusicBase { public: explicit MSXFmPac(const DeviceConfig& config); void reset(EmuTime::param time) override; void writeIO(word port, byte value, EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word address) const override; byte* getWriteCacheLine(word address) const override; template void serialize(Archive& ar, unsigned version); private: void checkSramEnable(); SRAM sram; RomBlockDebuggable romBlockDebug; byte enable; byte bank; byte r1ffe, r1fff; bool sramEnabled; }; SERIALIZE_CLASS_VERSION(MSXFmPac, 2); // must be in-sync with MSXMusicBase } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/MSXMixer.cc000066400000000000000000000575131257557151200203540ustar00rootroot00000000000000#include "MSXMixer.hh" #include "Mixer.hh" #include "SoundDevice.hh" #include "MSXMotherBoard.hh" #include "MSXCommandController.hh" #include "TclObject.hh" #include "ThrottleManager.hh" #include "GlobalSettings.hh" #include "IntegerSetting.hh" #include "StringSetting.hh" #include "BooleanSetting.hh" #include "CommandException.hh" #include "AviRecorder.hh" #include "Filename.hh" #include "CliComm.hh" #include "Math.hh" #include "StringOp.hh" #include "memory.hh" #include "stl.hh" #include "aligned.hh" #include "outer.hh" #include "unreachable.hh" #include "vla.hh" #include #include #include #include #include #ifdef __SSE2__ #include "emmintrin.h" #endif using std::remove; using std::string; using std::vector; namespace openmsx { MSXMixer::SoundDeviceInfo::SoundDeviceInfo() { } MSXMixer::SoundDeviceInfo::SoundDeviceInfo(SoundDeviceInfo&& rhs) : device (std::move(rhs.device)) , defaultVolume (std::move(rhs.defaultVolume)) , volumeSetting (std::move(rhs.volumeSetting)) , balanceSetting (std::move(rhs.balanceSetting)) , channelSettings(std::move(rhs.channelSettings)) , left1 (std::move(rhs.left1)) , right1 (std::move(rhs.right1)) , left2 (std::move(rhs.left2)) , right2 (std::move(rhs.right2)) { } MSXMixer::SoundDeviceInfo& MSXMixer::SoundDeviceInfo::operator=(SoundDeviceInfo&& rhs) { device = std::move(rhs.device); defaultVolume = std::move(rhs.defaultVolume); volumeSetting = std::move(rhs.volumeSetting); balanceSetting = std::move(rhs.balanceSetting); channelSettings = std::move(rhs.channelSettings); left1 = std::move(rhs.left1); right1 = std::move(rhs.right1); left2 = std::move(rhs.left2); right2 = std::move(rhs.right2); return *this; } MSXMixer::SoundDeviceInfo::ChannelSettings::ChannelSettings() { } MSXMixer::SoundDeviceInfo::ChannelSettings::ChannelSettings(ChannelSettings&& rhs) : recordSetting(std::move(rhs.recordSetting)) , muteSetting (std::move(rhs.muteSetting)) { } MSXMixer::SoundDeviceInfo::ChannelSettings& MSXMixer::SoundDeviceInfo::ChannelSettings::operator=(ChannelSettings&& rhs) { recordSetting = std::move(rhs.recordSetting); muteSetting = std::move(rhs.muteSetting); return *this; } MSXMixer::MSXMixer(Mixer& mixer_, MSXMotherBoard& motherBoard_, GlobalSettings& globalSettings) : Schedulable(motherBoard_.getScheduler()) , mixer(mixer_) , motherBoard(motherBoard_) , commandController(motherBoard.getMSXCommandController()) , masterVolume(mixer.getMasterVolume()) , speedSetting(globalSettings.getSpeedSetting()) , throttleManager(globalSettings.getThrottleManager()) , prevTime(getCurrentTime(), 44100) , soundDeviceInfo(commandController.getMachineInfoCommand()) , recorder(nullptr) , synchronousCounter(0) { hostSampleRate = 44100; fragmentSize = 0; muteCount = 1; unmute(); // calls Mixer::registerMixer() reschedule2(); masterVolume.attach(*this); speedSetting.attach(*this); throttleManager.attach(*this); } MSXMixer::~MSXMixer() { if (recorder) { recorder->stop(); } assert(infos.empty()); throttleManager.detach(*this); speedSetting.detach(*this); masterVolume.detach(*this); mute(); // calls Mixer::unregisterMixer() } void MSXMixer::registerSound(SoundDevice& device, float volume, int balance, unsigned numChannels) { // TODO read volume/balance(mode) from config file const string& name = device.getName(); SoundDeviceInfo info; info.device = &device; info.defaultVolume = volume; info.volumeSetting = make_unique( commandController, name + "_volume", "the volume of this sound chip", 75, 0, 100); info.balanceSetting = make_unique( commandController, name + "_balance", "the balance of this sound chip", balance, -100, 100); info.volumeSetting->attach(*this); info.balanceSetting->attach(*this); for (unsigned i = 0; i < numChannels; ++i) { SoundDeviceInfo::ChannelSettings channelSettings; string ch_name = StringOp::Builder() << name << "_ch" << i + 1; channelSettings.recordSetting = make_unique( commandController, ch_name + "_record", "filename to record this channel to", "", Setting::DONT_SAVE); channelSettings.recordSetting->attach(*this); channelSettings.muteSetting = make_unique( commandController, ch_name + "_mute", "sets mute-status of individual sound channels", false, Setting::DONT_SAVE); channelSettings.muteSetting->attach(*this); info.channelSettings.push_back(std::move(channelSettings)); } device.setOutputRate(getSampleRate()); infos.push_back(std::move(info)); updateVolumeParams(infos.back()); commandController.getCliComm().update(CliComm::SOUNDDEVICE, device.getName(), "add"); } void MSXMixer::unregisterSound(SoundDevice& device) { auto it = find_if_unguarded(infos, [&](const SoundDeviceInfo& i) { return i.device == &device; }); it->volumeSetting->detach(*this); it->balanceSetting->detach(*this); for (auto& s : it->channelSettings) { s.recordSetting->detach(*this); s.muteSetting->detach(*this); } if (it != (end(infos) - 1)) std::swap(*it, *(end(infos) - 1)); infos.pop_back(); commandController.getCliComm().update(CliComm::SOUNDDEVICE, device.getName(), "remove"); } void MSXMixer::setSynchronousMode(bool synchronous) { // TODO ATM synchronous is not used anymore if (synchronous) { ++synchronousCounter; if (synchronousCounter == 1) { setMixerParams(fragmentSize, hostSampleRate); } } else { assert(synchronousCounter > 0); --synchronousCounter; if (synchronousCounter == 0) { setMixerParams(fragmentSize, hostSampleRate); } } } double MSXMixer::getEffectiveSpeed() const { return synchronousCounter ? 1.0 : speedSetting.getInt() / 100.0; } void MSXMixer::updateStream(EmuTime::param time) { union { int16_t mixBuffer[8192 * 2]; int32_t dummy1; // make sure mixBuffer is also 32-bit aligned #ifdef __SSE2__ __m128i dummy2; // and optionally also 128-bit #endif }; unsigned count = prevTime.getTicksTill(time); assert(count <= 8192); // call generate() even if count==0 and even if muted generate(mixBuffer, time, count); if (!muteCount && fragmentSize) { mixer.uploadBuffer(*this, mixBuffer, count); } if (recorder) { recorder->addWave(count, mixBuffer); } prevTime += count; } // Various (inner) loops that multiply one buffer by a constant and add the // result to a second buffer. Either buffer can be mono or stereo, so if // necessary the mono buffer is expanded to stereo. It's possible the // accumulation buffer is still empty (as-if it contains zeros), in that case // we skip the accumulation step. // buf[0:n] *= f static inline void mul(int32_t* buf, int n, int f) { #ifdef __arm__ // ARM assembly version int32_t dummy1, dummy2; asm volatile ( "0:\n\t" "ldmia %[buf],{r3-r6}\n\t" "mul r3,%[f],r3\n\t" "mul r4,%[f],r4\n\t" "mul r5,%[f],r5\n\t" "mul r6,%[f],r6\n\t" "stmia %[buf]!,{r3-r6}\n\t" "subs %[n],%[n],#4\n\t" "bgt 0b\n\t" : [buf] "=r" (dummy1) , [n] "=r" (dummy2) : "[buf]" (buf) , "[n]" (n) , [f] "r" (f) : "memory", "r3","r4","r5","r6" ); return; #endif // C++ version, unrolled 4x, // this allows gcc/clang to do much better auto-vectorization // Note that this can process upto 3 samples too many, but that's OK. assume_SSE_aligned(buf); int i = 0; do { buf[i + 0] *= f; buf[i + 1] *= f; buf[i + 2] *= f; buf[i + 3] *= f; i += 4; } while (i < n); } // acc[0:n] += mul[0:n] * f static inline void mulAcc( int32_t* __restrict acc, const int32_t* __restrict mul, int n, int f) { #ifdef __arm__ // ARM assembly version int32_t dummy1, dummy2, dummy3; asm volatile ( "0:\n\t" "ldmia %[in]!,{r3,r4,r5,r6}\n\t" "ldmia %[out],{r8,r9,r10,r12}\n\t" "mla r3,%[f],r3,r8\n\t" "mla r4,%[f],r4,r9\n\t" "mla r5,%[f],r5,r10\n\t" "mla r6,%[f],r6,r12\n\t" "stmia %[out]!,{r3,r4,r5,r6}\n\t" "subs %[n],%[n],#4\n\t" "bgt 0b\n\t" : [in] "=r" (dummy1) , [out] "=r" (dummy2) , [n] "=r" (dummy3) : "[in]" (mul) , "[out]" (acc) , "[n]" (n) , [f] "r" (f) : "memory" , "r3","r4","r5","r6" , "r8","r9","r10","r12" ); return; #endif // C++ version, unrolled 4x, see comments above. assume_SSE_aligned(acc); assume_SSE_aligned(mul); int i = 0; do { acc[i + 0] += mul[i + 0] * f; acc[i + 1] += mul[i + 1] * f; acc[i + 2] += mul[i + 2] * f; acc[i + 3] += mul[i + 3] * f; i += 4; } while (i < n); } // buf[0:2n+0:2] = buf[0:n] * l // buf[1:2n+1:2] = buf[0:n] * r static inline void mulExpand(int32_t* buf, int n, int l, int r) { int i = n; do { --i; // back-to-front auto t = buf[i]; buf[2 * i + 0] = l * t; buf[2 * i + 1] = r * t; } while (i != 0); } // acc[0:2n+0:2] += mul[0:n] * l // acc[1:2n+1:2] += mul[0:n] * r static inline void mulExpandAcc( int32_t* __restrict acc, const int32_t* __restrict mul, int n, int l, int r) { int i = 0; do { auto t = mul[i]; acc[2 * i + 0] += l * t; acc[2 * i + 1] += r * t; } while (++i < n); } // buf[0:2n+0:2] = buf[0:2n+0:2] * l1 + buf[1:2n+1:2] * l2 // buf[1:2n+1:2] = buf[0:2n+0:2] * r1 + buf[1:2n+1:2] * r2 static inline void mulMix2(int32_t* buf, int n, int l1, int l2, int r1, int r2) { int i = 0; do { auto t1 = buf[2 * i + 0]; auto t2 = buf[2 * i + 1]; buf[2 * i + 0] = l1 * t1 + l2 * t2; buf[2 * i + 1] = r1 * t1 + r2 * t2; } while (++i < n); } // acc[0:2n+0:2] += mul[0:2n+0:2] * l1 + mul[1:2n+1:2] * l2 // acc[1:2n+1:2] += mul[0:2n+0:2] * r1 + mul[1:2n+1:2] * r2 static inline void mulMix2Acc( int32_t* __restrict acc, const int32_t* __restrict mul, int n, int l1, int l2, int r1, int r2) { int i = 0; do { auto t1 = mul[2 * i + 0]; auto t2 = mul[2 * i + 1]; acc[2 * i + 0] += l1 * t1 + l2 * t2; acc[2 * i + 1] += r1 * t1 + r2 * t2; } while (++i < n); } // DC removal filter routines: // // formula: // y(n) = x(n) - x(n-1) + R * y(n-1) // implemented as: // t1 = R * t0 + x(n) mathematically equivalent, has // y(n) = t1 - t0 the same number of operations but // t0 = t1 requires only one state variable // see: http://en.wikipedia.org/wiki/Digital_filter#Direct_Form_I // with: // R = 1 - (2*pi * cut-off-frequency / samplerate) // we take R = 511/512 // 44100Hz --> cutt-off freq = 14Hz // 22050Hz 7Hz // Note: the input still needs to be divided by 512 (because of balance- // multiplication), can be done together with the above division. // No new input, previous output was (non-zero) mono. static inline int32_t filterMonoNull(int32_t t0, int16_t* out, int n) { assert(n > 0); int i = 0; do { int32_t t1 = (511 * int64_t(t0)) >> 9; auto s = Math::clipIntToShort(t1 - t0); t0 = t1; out[2 * i + 0] = s; out[2 * i + 1] = s; } while (++i < n); return t0; } // No new input, previous output was (non-zero) stereo. static inline std::tuple filterStereoNull( int32_t tl0, int32_t tr0, int16_t* out, int n) { assert(n > 0); int i = 0; do { int32_t tl1 = (511 * int64_t(tl0)) >> 9; int32_t tr1 = (511 * int64_t(tr0)) >> 9; out[2 * i + 0] = Math::clipIntToShort(tl1 - tl0); out[2 * i + 1] = Math::clipIntToShort(tr1 - tr0); tl0 = tl1; tr0 = tr1; } while (++i < n); return std::make_tuple(tl0, tr0); } // New input is mono, previous output was also mono. static inline int32_t filterMonoMono(int32_t t0, void* buf, int n) { assert(n > 0); const auto* in = static_cast(buf); auto* out = static_cast< int16_t*>(buf); int i = 0; do { int32_t t1 = (511 * int64_t(t0) + in[i]) >> 9; auto s = Math::clipIntToShort(t1 - t0); t0 = t1; out[2 * i + 0] = s; out[2 * i + 1] = s; } while (++i < n); return t0; } // New input is mono, previous output was stereo static inline std::tuple filterStereoMono( int32_t tl0, int32_t tr0, void* buf, int n) { assert(n > 0); const auto* in = static_cast(buf); auto* out = static_cast< int16_t*>(buf); int i = 0; do { auto x = in[i]; int32_t tl1 = (511 * int64_t(tl0) + x) >> 9; int32_t tr1 = (511 * int64_t(tr0) + x) >> 9; out[2 * i + 0] = Math::clipIntToShort(tl1 - tl0); out[2 * i + 1] = Math::clipIntToShort(tr1 - tr0); tl0 = tl1; tr0 = tr1; } while (++i < n); return std::make_tuple(tl0, tr0); } // New input is stereo, (previous output either mono/stereo) static inline std::tuple filterStereoStereo( int32_t tl0, int32_t tr0, const int32_t* in, int16_t* out, int n) { assert(n > 0); int i = 0; do { int32_t tl1 = (511 * int64_t(tl0) + in[2 * i + 0]) >> 9; int32_t tr1 = (511 * int64_t(tr0) + in[2 * i + 1]) >> 9; out[2 * i + 0] = Math::clipIntToShort(tl1 - tl0); out[2 * i + 1] = Math::clipIntToShort(tr1 - tr0); tl0 = tl1; tr0 = tr1; } while (++i < n); return std::make_tuple(tl0, tr0); } // We have both mono and stereo input (and produce stereo output) static inline std::tuple filterBothStereo( int32_t tl0, int32_t tr0, const int32_t* inS, void* buf, int n) { assert(n > 0); const auto* inM = static_cast(buf); auto* out = static_cast< int16_t*>(buf); int i = 0; do { auto m = inM[i]; int32_t tl1 = (511 * int64_t(tl0) + inS[2 * i + 0] + m) >> 9; int32_t tr1 = (511 * int64_t(tr0) + inS[2 * i + 1] + m) >> 9; out[2 * i + 0] = Math::clipIntToShort(tl1 - tl0); out[2 * i + 1] = Math::clipIntToShort(tr1 - tr0); tl0 = tl1; tr0 = tr1; } while (++i < n); return std::make_tuple(tl0, tr0); } void MSXMixer::generate(int16_t* output, EmuTime::param time, unsigned samples) { // The code below is specialized for a lot of cases (before this // routine was _much_ shorter). This is done because this routine // ends up relatively high (top 5) in a profile run. // After these specialization this routine runs about two times // faster for the common cases (mono output or no sound at all). // In total emulation time this gave a speedup of about 2%. // When samples==0, call updateBuffer() but skip all further processing // (handling this as a special case allows to simply the code below). if (samples == 0) { int32_t dummyBuf[4]; for (auto& info : infos) { info.device->updateBuffer(0, dummyBuf, time); } return; } // +3 to allow processing samples in groups of 4 (and upto 3 samples // more than requested). VLA_SSE_ALIGNED(int32_t, stereoBuf, 2 * samples + 3); VLA_SSE_ALIGNED(int32_t, tmpBuf, 2 * samples + 3); // reuse 'output' as temporary storage auto* monoBuf = reinterpret_cast(output); static const unsigned HAS_MONO_FLAG = 1; static const unsigned HAS_STEREO_FLAG = 2; unsigned usedBuffers = 0; // FIXME: The Infos should be ordered such that all the mono // devices are handled first for (auto& info : infos) { SoundDevice& device = *info.device; int l1 = info.left1; int r1 = info.right1; if (!device.isStereo()) { if (l1 == r1) { if (!(usedBuffers & HAS_MONO_FLAG)) { if (device.updateBuffer(samples, monoBuf, time)) { usedBuffers |= HAS_MONO_FLAG; mul(monoBuf, samples, l1); } } else { if (device.updateBuffer(samples, tmpBuf, time)) { mulAcc(monoBuf, tmpBuf, samples, l1); } } } else { if (!(usedBuffers & HAS_STEREO_FLAG)) { if (device.updateBuffer(samples, stereoBuf, time)) { usedBuffers |= HAS_STEREO_FLAG; mulExpand(stereoBuf, samples, l1, r1); } } else { if (device.updateBuffer(samples, tmpBuf, time)) { mulExpandAcc(stereoBuf, tmpBuf, samples, l1, r1); } } } } else { int l2 = info.left2; int r2 = info.right2; if (l1 == r2) { assert(l2 == 0); assert(r1 == 0); if (!(usedBuffers & HAS_STEREO_FLAG)) { if (device.updateBuffer(samples, stereoBuf, time)) { usedBuffers |= HAS_STEREO_FLAG; mul(stereoBuf, 2 * samples, l1); } } else { if (device.updateBuffer(samples, tmpBuf, time)) { mulAcc(stereoBuf, tmpBuf, 2 * samples, l1); } } } else { if (!(usedBuffers & HAS_STEREO_FLAG)) { if (device.updateBuffer(samples, stereoBuf, time)) { usedBuffers |= HAS_STEREO_FLAG; mulMix2(stereoBuf, samples, l1, l2, r1, r2); } } else { if (device.updateBuffer(samples, tmpBuf, time)) { mulMix2Acc(stereoBuf, tmpBuf, samples, l1, l2, r1, r2); } } } } } // DC removal filter switch (usedBuffers) { case 0: // no new input if (tl0 == tr0) { if ((-511 <= tl0) && (tl0 <= 0)) { // Output was zero, new input is zero, // after DC-filter output will still be zero. memset(output, 0, 2 * samples * sizeof(int16_t)); tl0 = tr0 = 0; } else { // Output was not zero, but it was the same left and right. tl0 = filterMonoNull(tl0, output, samples); tr0 = tl0; } } else { std::tie(tl0, tr0) = filterStereoNull(tl0, tr0, output, samples); } break; case HAS_MONO_FLAG: // only mono assert(static_cast(monoBuf) == static_cast(output)); if (tl0 == tr0) { // previous output was also mono tl0 = filterMonoMono(tl0, output, samples); tr0 = tl0; } else { // previous output was stereo, rarely triggers but needed for correctness std::tie(tl0, tr0) = filterStereoMono(tl0, tr0, output, samples); } break; case HAS_STEREO_FLAG: // only stereo std::tie(tl0, tr0) = filterStereoStereo(tl0, tr0, stereoBuf, output, samples); break; default: // mono + stereo assert(static_cast(monoBuf) == static_cast(output)); std::tie(tl0, tr0) = filterBothStereo(tl0, tr0, stereoBuf, output, samples); } } bool MSXMixer::needStereoRecording() const { return any_of(begin(infos), end(infos), [](const SoundDeviceInfo& info) { return info.device->isStereo() || info.balanceSetting->getInt() != 0; }); } void MSXMixer::mute() { if (muteCount == 0) { mixer.unregisterMixer(*this); } ++muteCount; } void MSXMixer::unmute() { --muteCount; if (muteCount == 0) { tl0 = tr0 = 0; mixer.registerMixer(*this); } } void MSXMixer::reInit() { prevTime.reset(getCurrentTime()); prevTime.setFreq(hostSampleRate / getEffectiveSpeed()); reschedule(); } void MSXMixer::reschedule() { removeSyncPoints(); reschedule2(); } void MSXMixer::reschedule2() { unsigned size = (!muteCount && fragmentSize) ? fragmentSize : 512; setSyncPoint(prevTime.getFastAdd(size)); } void MSXMixer::setMixerParams(unsigned newFragmentSize, unsigned newSampleRate) { // TODO old code checked that values did actually change, // investigate if this optimization is worth it hostSampleRate = newSampleRate; fragmentSize = newFragmentSize; reInit(); // must come before call to setOutputRate() for (auto& info : infos) { info.device->setOutputRate(newSampleRate); } } void MSXMixer::setRecorder(AviRecorder* newRecorder) { if ((recorder != nullptr) != (newRecorder != nullptr)) { setSynchronousMode(newRecorder != nullptr); } recorder = newRecorder; } void MSXMixer::update(const Setting& setting) { if (&setting == &masterVolume) { updateMasterVolume(); } else if (&setting == &speedSetting) { if (synchronousCounter == 0) { setMixerParams(fragmentSize, hostSampleRate); } else { // Avoid calling reInit() while recording because // each call causes a small hiccup in the sound (and // while recording this call anyway has no effect). // This was noticable while sliding the speed slider // in catapult (becuase this causes many changes in // the speed setting). } } else if (dynamic_cast(&setting)) { auto it = find_if_unguarded(infos, [&](const SoundDeviceInfo& i) { return (i.volumeSetting .get() == &setting) || (i.balanceSetting.get() == &setting); }); updateVolumeParams(*it); } else if (dynamic_cast(&setting)) { changeRecordSetting(setting); } else if (dynamic_cast(&setting)) { changeMuteSetting(setting); } else { UNREACHABLE; } } void MSXMixer::changeRecordSetting(const Setting& setting) { for (auto& info : infos) { unsigned channel = 0; for (auto& s : info.channelSettings) { if (s.recordSetting.get() == &setting) { info.device->recordChannel( channel, Filename(s.recordSetting->getString().str())); return; } ++channel; } } UNREACHABLE; } void MSXMixer::changeMuteSetting(const Setting& setting) { for (auto& info : infos) { unsigned channel = 0; for (auto& s : info.channelSettings) { if (s.muteSetting.get() == &setting) { info.device->muteChannel( channel, s.muteSetting->getBoolean()); return; } ++channel; } } UNREACHABLE; } void MSXMixer::update(const ThrottleManager& /*throttleManager*/) { //reInit(); // TODO Should this be removed? } void MSXMixer::updateVolumeParams(SoundDeviceInfo& info) { int mVolume = masterVolume.getInt(); int dVolume = info.volumeSetting->getInt(); float volume = info.defaultVolume * mVolume * dVolume / (100.0f * 100.0f); int balance = info.balanceSetting->getInt(); float l1, r1, l2, r2; if (info.device->isStereo()) { if (balance < 0) { float b = (balance + 100.0f) / 100.0f; l1 = volume; r1 = 0.0f; l2 = volume * sqrtf(std::max(0.0f, 1.0f - b)); r2 = volume * sqrtf(std::max(0.0f, b)); } else { float b = balance / 100.0f; l1 = volume * sqrtf(std::max(0.0f, 1.0f - b)); r1 = volume * sqrtf(std::max(0.0f, b)); l2 = 0.0f; r2 = volume; } } else { // make sure that in case of rounding errors // we don't take sqrt() of negative numbers float b = (balance + 100.0f) / 200.0f; l1 = volume * sqrtf(std::max(0.0f, 1.0f - b)); r1 = volume * sqrtf(std::max(0.0f, b)); l2 = r2 = 0.0f; // dummy } // 512 (9 bits) because in the DC filter we also have a factor 512, and // using the same allows to fold both (later) divisions into one. int amp = 512 * info.device->getAmplificationFactor(); info.left1 = int(l1 * amp); info.right1 = int(r1 * amp); info.left2 = int(l2 * amp); info.right2 = int(r2 * amp); } void MSXMixer::updateMasterVolume() { for (auto& p : infos) { updateVolumeParams(p); } } void MSXMixer::executeUntil(EmuTime::param time) { updateStream(time); reschedule2(); // This method gets called very regularly, typically 44100/512 = 86x // per second (even if sound is muted and even with sound_driver=null). // This rate is constant in real-time (compared to e.g. the VDP sync // points that are constant in emutime). So we can use this to // regularly exit from the main CPU emulation loop. Without this there // were problems like described in 'bug#563 Console very slow when // setting speed to low values like 1'. motherBoard.exitCPULoopSync(); } // Sound device info SoundDevice* MSXMixer::findDevice(string_ref name) const { auto it = find_if(begin(infos), end(infos), [&](const SoundDeviceInfo& i) { return i.device->getName() == name; }); return (it != end(infos)) ? it->device : nullptr; } MSXMixer::SoundDeviceInfoTopic::SoundDeviceInfoTopic( InfoCommand& machineInfoCommand) : InfoTopic(machineInfoCommand, "sounddevice") { } void MSXMixer::SoundDeviceInfoTopic::execute( array_ref tokens, TclObject& result) const { auto& mixer = OUTER(MSXMixer, soundDeviceInfo); switch (tokens.size()) { case 2: for (auto& info : mixer.infos) { result.addListElement(info.device->getName()); } break; case 3: { SoundDevice* device = mixer.findDevice(tokens[2].getString()); if (!device) { throw CommandException("Unknown sound device"); } result.setString(device->getDescription()); break; } default: throw CommandException("Too many parameters"); } } string MSXMixer::SoundDeviceInfoTopic::help(const vector& /*tokens*/) const { return "Shows a list of available sound devices.\n"; } void MSXMixer::SoundDeviceInfoTopic::tabCompletion(vector& tokens) const { if (tokens.size() == 3) { vector devices; auto& mixer = OUTER(MSXMixer, soundDeviceInfo); for (auto& info : mixer.infos) { devices.push_back(info.device->getName()); } completeString(tokens, devices); } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/MSXMixer.hh000066400000000000000000000122221257557151200203520ustar00rootroot00000000000000#ifndef MSXMIXER_HH #define MSXMIXER_HH #include "Schedulable.hh" #include "Observer.hh" #include "InfoTopic.hh" #include "EmuTime.hh" #include "DynamicClock.hh" #include #include #include namespace openmsx { class SoundDevice; class Mixer; class MSXMotherBoard; class MSXCommandController; class GlobalSettings; class ThrottleManager; class IntegerSetting; class StringSetting; class BooleanSetting; class Setting; class AviRecorder; class MSXMixer final : private Schedulable, private Observer , private Observer { public: MSXMixer(Mixer& mixer, MSXMotherBoard& motherBoard, GlobalSettings& globalSettings); ~MSXMixer(); /** * Use this method to register a given sounddevice. * * While registering, the device its setSampleRate() method is * called (see SoundDevice for more info). * After registration the device its updateBuffer() method is * 'regularly' called (see SoundDevice for more info). */ void registerSound(SoundDevice& device, float volume, int balance, unsigned numChannels); /** * Every sounddevice must unregister before it is destructed */ void unregisterSound(SoundDevice& device); /** * Use this method to force an 'early' call to all * updateBuffer() methods. */ void updateStream(EmuTime::param time); /** Returns the ratio of emutime-speed per realtime-speed. * In other words how many times faster emutime goes compared to * realtime. This depends on the 'speed' setting but also on whether * we're recording or not (in case of recording we want to generate * sound as if realtime and emutime go at the same speed. */ double getEffectiveSpeed() const; /** If we're recording, we want to emulate sound at 100% emutime speed. * See alsoe getEffectiveSpeed(). */ void setSynchronousMode(bool synchronous); /** TODO * This methods (un)mute the sound. * These methods may be called multiple times, as long as * you never call unmute() more than mute() */ void mute(); void unmute(); // Called by Mixer or SoundDriver /** Set new fragment size and sample frequency. * A fragment size of zero means the Mixer is muted. */ void setMixerParams(unsigned fragmentSize, unsigned sampleRate); /** Clock that ticks at the exact moment(s) in time that a host sample * should be generated. The current time of this clock is the time of * the last generated sample. The rate of this clock is the same as * the host sample rate. * Note that this rate is not the same as the frequency set with the * 'frequency' setting. Either because the sound driver can't handle * the requested speed or because the 'speed' setting is different * from 100. */ const DynamicClock& getHostSampleClock() const { return prevTime; } // Called by AviRecorder bool needStereoRecording() const; void setRecorder(AviRecorder* recorder); // Returns the nominal host sample rate (not adjusted for speed setting) unsigned getSampleRate() const { return hostSampleRate; } SoundDevice* findDevice(string_ref name) const; void reInit(); private: struct SoundDeviceInfo { // TODO use compiler-generated versions once VS supports it SoundDeviceInfo(); SoundDeviceInfo(SoundDeviceInfo&& rhs); SoundDeviceInfo& operator=(SoundDeviceInfo&& rhs); SoundDevice* device; float defaultVolume; std::unique_ptr volumeSetting; std::unique_ptr balanceSetting; struct ChannelSettings { ChannelSettings(); ChannelSettings(ChannelSettings&& rhs); ChannelSettings& operator=(ChannelSettings&& rhs); std::unique_ptr recordSetting; std::unique_ptr muteSetting; }; std::vector channelSettings; int left1, right1, left2, right2; }; void updateVolumeParams(SoundDeviceInfo& info); void updateMasterVolume(); void reschedule(); void reschedule2(); void generate(int16_t* buffer, EmuTime::param time, unsigned samples); // Schedulable void executeUntil(EmuTime::param time) override; // Observer void update(const Setting& setting) override; // Observer void update(const ThrottleManager& throttleManager) override; void changeRecordSetting(const Setting& setting); void changeMuteSetting(const Setting& setting); unsigned fragmentSize; unsigned hostSampleRate; // requested freq by sound driver, // not compensated for speed std::vector infos; Mixer& mixer; MSXMotherBoard& motherBoard; MSXCommandController& commandController; IntegerSetting& masterVolume; IntegerSetting& speedSetting; ThrottleManager& throttleManager; DynamicClock prevTime; struct SoundDeviceInfoTopic final : InfoTopic { SoundDeviceInfoTopic(InfoCommand& machineInfoCommand); void execute(array_ref tokens, TclObject& result) const override; std::string help(const std::vector& tokens) const override; void tabCompletion(std::vector& tokens) const override; } soundDeviceInfo; AviRecorder* recorder; unsigned synchronousCounter; unsigned muteCount; int32_t tl0, tr0; // internal DC-filter state }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/MSXMoonSound.cc000066400000000000000000000205201257557151200211750ustar00rootroot00000000000000// ATM this class does several things: // - It connects the YMF278b chip to specific I/O ports in the MSX machine // - It glues the YMF262 (FM-part) and YMF278 (Wave-part) classes together in a // full model of a YMF278b chip. IOW part of the logic of the YM278b is // modeled here instead of in a chip-specific class. // TODO it would be nice to move the functionality of the 2nd point to a // different class, but until there's a 2nd user of this chip, this is // low priority. #include "MSXMoonSound.hh" #include "Clock.hh" #include "serialize.hh" #include "unreachable.hh" namespace openmsx { // The master clock, running at 33.8MHz. using MasterClock = Clock<33868800>; // Required delay between register select and register read/write. static const EmuDuration FM_REG_SELECT_DELAY = MasterClock::duration(56); // Required delay after register write. static const EmuDuration FM_REG_WRITE_DELAY = MasterClock::duration(56); // Datasheet doesn't mention any delay for reads from the FM registers. In fact // it says reads from FM registers are not possible while tests on a real // YMF278 show they do work (value of the NEW2 bit doesn't matter). // Required delay between register select and register read/write. static const EmuDuration WAVE_REG_SELECT_DELAY = MasterClock::duration(88); // Required delay after register write. static const EmuDuration WAVE_REG_WRITE_DELAY = MasterClock::duration(88); // Datasheet doesn't mention any delay for register reads (except for reads // from register 6, see below). I also couldn't measure any delay on a real // YMF278. // Required delay after memory read. static const EmuDuration MEM_READ_DELAY = MasterClock::duration(38); // Required delay after memory write (instead of register write delay). static const EmuDuration MEM_WRITE_DELAY = MasterClock::duration(28); // Required delay after instrument load. // We pick 10000 cycles, this is approximately 300us (the number given in the // datasheet). The exact number of cycles is unknown. But I did some (very // rough) tests on real HW, and this number is not too bad (slightly too high // but within 2%-4% of real value, needs more detailed tests). static const EmuDuration LOAD_DELAY = MasterClock::duration(10000); MSXMoonSound::MSXMoonSound(const DeviceConfig& config) : MSXDevice(config) , ymf262(getName() + " FM", config, true) , ymf278(getName() + " wave", config.getChildDataAsInt("sampleram", 512), // size in kb config) , ymf278LoadTime(getCurrentTime()) , ymf278BusyTime(getCurrentTime()) { powerUp(getCurrentTime()); } void MSXMoonSound::powerUp(EmuTime::param time) { ymf278.clearRam(); reset(time); } void MSXMoonSound::reset(EmuTime::param time) { ymf262.reset(time); ymf278.reset(time); opl4latch = 0; // TODO check opl3latch = 0; // TODO check alreadyReadID = false; ymf278BusyTime = time; ymf278LoadTime = time; } byte MSXMoonSound::readIO(word port, EmuTime::param time) { byte result; if ((port & 0xFF) < 0xC0) { // WAVE part 0x7E-0x7F switch (port & 0x01) { case 0: // read latch, not supported result = 255; break; case 1: // read wave register // Verified on real YMF278: // Even if NEW2=0 reads happen normally. Also reads // from sample memory (and thus the internal memory // pointer gets increased). if ((3 <= opl4latch) && (opl4latch <= 6)) { // This time is so small that on a MSX you can // never see BUSY=1. So I also couldn't test // whether this timing applies to registers 3-6 // (like for write) or only to register 6. I // also couldn't test how the other registers // behave. // TODO Should we comment out this code? It // doesn't have any measurable effect on MSX. ymf278BusyTime = time + MEM_READ_DELAY; } result = ymf278.readReg(opl4latch); break; default: // unreachable, avoid warning UNREACHABLE; result = 255; } } else { // FM part 0xC4-0xC7 switch (port & 0x03) { case 0: // read status case 2: result = ymf262.readStatus() | readYMF278Status(time); if (!alreadyReadID && getNew2()) { // Verified on real YMF278: // Only once after switching NEW2=1, reading // the status register returns '0x02'. This // behavior doesn't re-occur till after a // reset (datasheet confirms this behavior). // Also verified that only bit 1 changes (so // it's not the whole value that is forced to // 0x02, datasheet isn't clear about that). alreadyReadID = true; result |= 0x02; } break; case 1: case 3: // read fm register result = ymf262.readReg(opl3latch); break; default: // unreachable, avoid warning UNREACHABLE; result = 255; } } return result; } byte MSXMoonSound::peekIO(word port, EmuTime::param time) const { byte result; if ((port & 0xFF) < 0xC0) { // WAVE part 0x7E-0x7F switch (port & 0x01) { case 0: // read latch, not supported result = 255; break; case 1: // read wave register result = ymf278.peekReg(opl4latch); break; default: // unreachable, avoid warning UNREACHABLE; result = 255; } } else { // FM part 0xC4-0xC7 switch (port & 0x03) { case 0: // read status case 2: result = ymf262.peekStatus() | readYMF278Status(time); if (!alreadyReadID && getNew2()) { result |= 0x02; } break; case 1: case 3: // read fm register result = ymf262.peekReg(opl3latch); break; default: // unreachable, avoid warning UNREACHABLE; result = 255; } } return result; } void MSXMoonSound::writeIO(word port, byte value, EmuTime::param time) { if ((port & 0xFF) < 0xC0) { // WAVE part 0x7E-0x7F if (getNew2()) { switch (port & 0x01) { case 0: // select register ymf278BusyTime = time + WAVE_REG_SELECT_DELAY; opl4latch = value; break; case 1: if ((0x08 <= opl4latch) && (opl4latch <= 0x1F)) { ymf278LoadTime = time + LOAD_DELAY; } if ((3 <= opl4latch) && (opl4latch <= 6)) { // Note: this time is so small that on // MSX you never see BUSY=1 for these // registers. Confirmed on real HW that // also registers 3-5 are faster. ymf278BusyTime = time + MEM_WRITE_DELAY; } else { // For the other registers it is // possible to see BUSY=1, but only // very briefly and only on R800. ymf278BusyTime = time + WAVE_REG_WRITE_DELAY; } ymf278.writeReg(opl4latch, value, time); break; default: UNREACHABLE; } } else { // Verified on real YMF278: // Writes are ignored when NEW2=0 (both register select // and register write). } } else { // FM part 0xC4-0xC7 switch (port & 0x03) { case 0: // select register bank 0 opl3latch = value; ymf278BusyTime = time + FM_REG_SELECT_DELAY; break; case 2: // select register bank 1 opl3latch = value | 0x100; ymf278BusyTime = time + FM_REG_SELECT_DELAY; break; case 1: case 3: // write fm register ymf278BusyTime = time + FM_REG_WRITE_DELAY; ymf262.writeReg(opl3latch, value, time); break; default: UNREACHABLE; } } } bool MSXMoonSound::getNew2() const { return (ymf262.peekReg(0x105) & 0x02) != 0; } byte MSXMoonSound::readYMF278Status(EmuTime::param time) const { byte result = 0; if (time < ymf278BusyTime) result |= 0x01; if (time < ymf278LoadTime) result |= 0x02; return result; } // version 1: initial version // version 2: added alreadyReadID // version 3: moved loadTime and busyTime from YMF278 to here template void MSXMoonSound::serialize(Archive& ar, unsigned version) { ar.template serializeBase(*this); ar.serialize("ymf262", ymf262); ar.serialize("ymf278", ymf278); ar.serialize("opl3latch", opl3latch); ar.serialize("opl4latch", opl4latch); if (ar.versionAtLeast(version, 2)) { ar.serialize("alreadyReadID", alreadyReadID); } else { assert(ar.isLoader()); alreadyReadID = true; // we can't know the actual value, but // 'true' is the safest value } if (ar.versionAtLeast(version, 3)) { ar.serialize("loadTime", ymf278LoadTime); ar.serialize("busyTime", ymf278BusyTime); } else { assert(ar.isLoader()); // For 100% backwards compatibility we should restore these two // from the (old) YMF278 class. Though that's a lot of extra // work for very little gain. ymf278LoadTime = getCurrentTime(); ymf278BusyTime = getCurrentTime(); } } INSTANTIATE_SERIALIZE_METHODS(MSXMoonSound); REGISTER_MSXDEVICE(MSXMoonSound, "MoonSound"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/MSXMoonSound.hh000066400000000000000000000017751257557151200212220ustar00rootroot00000000000000#ifndef MSXMOONSOUND_HH #define MSXMOONSOUND_HH #include "MSXDevice.hh" #include "YMF262.hh" #include "YMF278.hh" #include "serialize_meta.hh" namespace openmsx { class MSXMoonSound final : public MSXDevice { public: explicit MSXMoonSound(const DeviceConfig& config); void powerUp(EmuTime::param time) override; void reset(EmuTime::param time) override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: bool getNew2() const; byte readYMF278Status(EmuTime::param time) const; YMF262 ymf262; YMF278 ymf278; /** Time at which instrument loading is finished. */ EmuTime ymf278LoadTime; /** Time until which the YMF278 is busy. */ EmuTime ymf278BusyTime; int opl3latch; byte opl4latch; bool alreadyReadID; }; SERIALIZE_CLASS_VERSION(MSXMoonSound, 3); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/MSXMusic.cc000066400000000000000000000101451257557151200203360ustar00rootroot00000000000000#include "MSXMusic.hh" #include "CacheLine.hh" #include "serialize.hh" namespace openmsx { // class MSXMusicBase MSXMusicBase::MSXMusicBase(const DeviceConfig& config) : MSXDevice(config) , rom(getName() + " ROM", "rom", config) , ym2413(getName(), config) { reset(getCurrentTime()); } void MSXMusicBase::reset(EmuTime::param time) { ym2413.reset(time); registerLatch = 0; // TODO check } void MSXMusicBase::writeIO(word port, byte value, EmuTime::param time) { switch (port & 0x01) { case 0: writeRegisterPort(value, time); break; case 1: writeDataPort(value, time); break; } } void MSXMusicBase::writeRegisterPort(byte value, EmuTime::param /*time*/) { registerLatch = value & 0x3F; } void MSXMusicBase::writeDataPort(byte value, EmuTime::param time) { ym2413.writeReg(registerLatch, value, time); } byte MSXMusicBase::peekMem(word address, EmuTime::param /*time*/) const { return *MSXMusicBase::getReadCacheLine(address); } byte MSXMusicBase::readMem(word address, EmuTime::param time) { return peekMem(address, time); } const byte* MSXMusicBase::getReadCacheLine(word start) const { return &rom[start & (rom.getSize() - 1)]; } // version 1: initial version // version 2: refactored YM2413 class structure template void MSXMusicBase::serialize(Archive& ar, unsigned version) { ar.template serializeBase(*this); if (ar.versionAtLeast(version, 2)) { ar.serialize("ym2413", ym2413); } else { // In older versions, the 'ym2413' level was missing, delegate // directly to YM2413 without emitting the 'ym2413' tag. ym2413.serialize(ar, version); } ar.serialize("registerLatch", registerLatch); } INSTANTIATE_SERIALIZE_METHODS(MSXMusicBase); // class MSXMusic MSXMusic::MSXMusic(const DeviceConfig& config) : MSXMusicBase(config) { } template void MSXMusic::serialize(Archive& ar, unsigned version) { ar.template serializeInlinedBase(*this, version); } INSTANTIATE_SERIALIZE_METHODS(MSXMusic); REGISTER_MSXDEVICE(MSXMusic, "MSX-Music"); // class MSXMusicWX // Thanks to NYYRIKKI for figuring this out: // - writes to 0x7ff0-0x7fff set a control register (mirrored 16x) // - writes to any other memory region have no effect // - bit 0 of this control register can be used to disable reading the ROM // (0=enable, 1=disabled), other bits seem to have no effect // - reading from 0x7ff0-0x7fff return the last written value OR 0xfc, IOW the // lower two bits are the last written value, higher bits always read 1 // - reading from 0x4000-0x7fef returns the content of the ROM if the ROM is // enabled (bit 0 of the control register = 0), when the ROM is disabled // reads return 0xff // - reading any other memory location returns 0xff MSXMusicWX::MSXMusicWX(const DeviceConfig& config) : MSXMusicBase(config) { } void MSXMusicWX::reset(EmuTime::param time) { MSXMusicBase::reset(time); control = 0; } byte MSXMusicWX::peekMem(word address, EmuTime::param time) const { if ((0x7FF0 <= address) && (address < 0x8000)) { return control | 0xFC; } else if ((control & 1) == 0) { return MSXMusicBase::peekMem(address, time); } else { return 0xFF; } } byte MSXMusicWX::readMem(word address, EmuTime::param time) { return peekMem(address, time); } const byte* MSXMusicWX::getReadCacheLine(word start) const { if ((0x7FF0 & CacheLine::HIGH) == start) { return nullptr; } else if ((control & 1) == 0) { return MSXMusicBase::getReadCacheLine(start); } else { return unmappedRead; } } void MSXMusicWX::writeMem(word address, byte value, EmuTime::param /*time*/) { if ((0x7FF0 <= address) && (address < 0x8000)) { control = value & 3; invalidateMemCache(0x0000, 0x10000); } } byte* MSXMusicWX::getWriteCacheLine(word start) const { if ((0x7FF0 & CacheLine::HIGH) == start) { return nullptr; } else { return unmappedWrite; } } template void MSXMusicWX::serialize(Archive& ar, unsigned version) { ar.template serializeInlinedBase(*this, version); ar.serialize("control", control); } INSTANTIATE_SERIALIZE_METHODS(MSXMusicWX); REGISTER_MSXDEVICE(MSXMusicWX, "MSX-Music-WX"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/MSXMusic.hh000066400000000000000000000034601257557151200203520ustar00rootroot00000000000000#ifndef MSXMUSIC_HH #define MSXMUSIC_HH #include "MSXDevice.hh" #include "Rom.hh" #include "YM2413.hh" #include "serialize_meta.hh" namespace openmsx { class MSXMusicBase : public MSXDevice { public: void reset(EmuTime::param time) override; void writeIO(word port, byte value, EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; byte readMem(word address, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; template void serialize(Archive& ar, unsigned version); protected: explicit MSXMusicBase(const DeviceConfig& config); ~MSXMusicBase() {} void writeRegisterPort(byte value, EmuTime::param time); void writeDataPort(byte value, EmuTime::param time); Rom rom; YM2413 ym2413; private: int registerLatch; }; SERIALIZE_CLASS_VERSION(MSXMusicBase, 2); class MSXMusic : public MSXMusicBase { public: explicit MSXMusic(const DeviceConfig& config); template void serialize(Archive& ar, unsigned version); }; SERIALIZE_CLASS_VERSION(MSXMusic, 2); // must be same as MSXMusicBase // Variant used in Panasonic_FS-A1WX and Panasonic_FS-A1WSX class MSXMusicWX : public MSXMusicBase { public: explicit MSXMusicWX(const DeviceConfig& config); void reset(EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; byte readMem(word address, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word start) const override; template void serialize(Archive& ar, unsigned version); private: byte control; }; SERIALIZE_CLASS_VERSION(MSXMusicWX, 2); // must be same as MSXMusicBase } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/MSXOPL3Cartridge.cc000066400000000000000000000035021257557151200216170ustar00rootroot00000000000000#include "MSXOPL3Cartridge.hh" #include "serialize.hh" #include "unreachable.hh" namespace openmsx { MSXOPL3Cartridge::MSXOPL3Cartridge(const DeviceConfig& config) : MSXDevice(config) , ymf262(getName(), config, false) { reset(getCurrentTime()); } void MSXOPL3Cartridge::reset(EmuTime::param time) { ymf262.reset(time); // TODO check opl3latch = 0; } byte MSXOPL3Cartridge::readIO(word port, EmuTime::param /*time*/) { byte result; // FM part 0xC4-0xC7 (in MoonSound) switch (port & 0x03) { case 0: // read status case 2: result = ymf262.readStatus(); break; case 1: case 3: // read fm register result = ymf262.readReg(opl3latch); break; default: // unreachable, avoid warning UNREACHABLE; result = 255; } return result; } byte MSXOPL3Cartridge::peekIO(word port, EmuTime::param /*time*/) const { byte result; switch (port & 0x03) { case 0: // read status case 2: result = ymf262.peekStatus(); break; case 1: case 3: // read fm register result = ymf262.peekReg(opl3latch); break; default: // unreachable, avoid warning UNREACHABLE; result = 255; } return result; } void MSXOPL3Cartridge::writeIO(word port, byte value, EmuTime::param time) { switch (port & 0x03) { case 0: // select register bank 0 opl3latch = value; break; case 2: // select register bank 1 opl3latch = value | 0x100; break; case 1: case 3: // write fm register ymf262.writeReg(opl3latch, value, time); break; default: UNREACHABLE; } } template void MSXOPL3Cartridge::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("ymf262", ymf262); ar.serialize("opl3latch", opl3latch); } INSTANTIATE_SERIALIZE_METHODS(MSXOPL3Cartridge); REGISTER_MSXDEVICE(MSXOPL3Cartridge, "OPL3Cartridge"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/MSXOPL3Cartridge.hh000066400000000000000000000011541257557151200216320ustar00rootroot00000000000000#ifndef MSXOPL3CARTRIDGE_HH #define MSXOPL3CARTRIDGE_HH #include "MSXDevice.hh" #include "YMF262.hh" namespace openmsx { class MSXOPL3Cartridge final : public MSXDevice { public: explicit MSXOPL3Cartridge(const DeviceConfig& config); void reset(EmuTime::param time) override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: YMF262 ymf262; int opl3latch; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/MSXPSG.cc000066400000000000000000000057661257557151200177240ustar00rootroot00000000000000#include "MSXPSG.hh" #include "LedStatus.hh" #include "CassettePort.hh" #include "MSXMotherBoard.hh" #include "JoystickPort.hh" #include "RenShaTurbo.hh" #include "serialize.hh" #include "checked_cast.hh" namespace openmsx { // MSXDevice MSXPSG::MSXPSG(const DeviceConfig& config) : MSXDevice(config) , cassette(getMotherBoard().getCassettePort()) , renShaTurbo(getMotherBoard().getRenShaTurbo()) , selectedPort(0) , prev(255) , keyLayoutBit(config.getChildData("keyboardlayout", "") == "JIS") { ports[0] = &getMotherBoard().getJoystickPort(0); ports[1] = &getMotherBoard().getJoystickPort(1); ay8910 = make_unique("PSG", *this, config, getCurrentTime()); reset(getCurrentTime()); } MSXPSG::~MSXPSG() { powerDown(EmuTime::dummy()); } void MSXPSG::reset(EmuTime::param time) { registerLatch = 0; ay8910->reset(time); } void MSXPSG::powerDown(EmuTime::param /*time*/) { getLedStatus().setLed(LedStatus::KANA, false); } byte MSXPSG::readIO(word /*port*/, EmuTime::param time) { return ay8910->readRegister(registerLatch, time); } byte MSXPSG::peekIO(word /*port*/, EmuTime::param time) const { return ay8910->peekRegister(registerLatch, time); } void MSXPSG::writeIO(word port, byte value, EmuTime::param time) { switch (port & 0x03) { case 0: registerLatch = value & 0x0F; break; case 1: ay8910->writeRegister(registerLatch, value, time); break; } } // AY8910Periphery byte MSXPSG::readA(EmuTime::param time) { byte joystick = ports[selectedPort]->read(time) | ((renShaTurbo.getSignal(time)) ? 0x10 : 0x00); // pin 6,7 input is ANDed with pin 6,7 output byte pin67 = prev << (4 - 2 * selectedPort); joystick &= (pin67| 0xCF); byte cassetteInput = cassette.cassetteIn(time) ? 0x80 : 0x00; byte keyLayout = keyLayoutBit ? 0x40 : 0x00; return joystick | keyLayout | cassetteInput; } void MSXPSG::writeB(byte value, EmuTime::param time) { byte val0 = (value & 0x03) | ((value & 0x10) >> 2); byte val1 = ((value & 0x0C) >> 2) | ((value & 0x20) >> 3); ports[0]->write(val0, time); ports[1]->write(val1, time); selectedPort = (value & 0x40) >> 6; if ((prev ^ value) & 0x80) { getLedStatus().setLed(LedStatus::KANA, !(value & 0x80)); } prev = value; } // version 1: initial version // version 2: joystickportA/B moved from here to MSXMotherBoard template void MSXPSG::serialize(Archive& ar, unsigned version) { ar.template serializeBase(*this); ar.serialize("ay8910", *ay8910); if (ar.versionBelow(version, 2)) { assert(ar.isLoader()); // in older versions there were always 2 real joytsick ports ar.serialize("joystickportA", *checked_cast(ports[0])); ar.serialize("joystickportB", *checked_cast(ports[1])); } ar.serialize("registerLatch", registerLatch); byte portB = prev; ar.serialize("portB", portB); if (ar.isLoader()) { writeB(portB, getCurrentTime()); } // selectedPort is derived from portB } INSTANTIATE_SERIALIZE_METHODS(MSXPSG); REGISTER_MSXDEVICE(MSXPSG, "PSG"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/MSXPSG.hh000066400000000000000000000025041257557151200177210ustar00rootroot00000000000000#ifndef MSXPSG_HH #define MSXPSG_HH #include "MSXDevice.hh" #include "AY8910.hh" #include "AY8910Periphery.hh" #include "serialize_meta.hh" #include namespace openmsx { class CassettePortInterface; class RenShaTurbo; class JoystickPortIf; class MSXPSG final : public MSXDevice, public AY8910Periphery { public: explicit MSXPSG(const DeviceConfig& config); ~MSXPSG(); void reset(EmuTime::param time) override; void powerDown(EmuTime::param time) override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: // AY8910Periphery: port A input, port B output byte readA(EmuTime::param time) override; void writeB(byte value, EmuTime::param time) override; CassettePortInterface& cassette; RenShaTurbo& renShaTurbo; JoystickPortIf* ports[2]; int selectedPort; int registerLatch; byte prev; const bool keyLayoutBit; // TODO could be by-value, but visual studio doesn't support // initialization of arrays (ports[2]) in the initializer list yet. std::unique_ptr ay8910; // must come after initialisation of most stuff above }; SERIALIZE_CLASS_VERSION(MSXPSG, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/MSXSCCPlusCart.cc000066400000000000000000000162231257557151200213470ustar00rootroot00000000000000// Note: this device is actually called SCC-I. But this would take a lot of // renaming, which isn't worth it right now. TODO rename this :) #include "MSXSCCPlusCart.hh" #include "File.hh" #include "FileContext.hh" #include "FileException.hh" #include "XMLElement.hh" #include "CacheLine.hh" #include "serialize.hh" namespace openmsx { MSXSCCPlusCart::MSXSCCPlusCart(const DeviceConfig& config) : MSXDevice(config) , ram(config, getName() + " RAM", "SCC+ RAM", 0x20000) , scc(getName(), config, getCurrentTime(), SCC::SCC_Compatible) , romBlockDebug(*this, mapper, 0x4000, 0x8000, 13) { if (const XMLElement* fileElem = config.findChild("filename")) { // read the rom file const std::string& filename = fileElem->getData(); try { File file(config.getFileContext().resolve(filename)); auto size = std::min(file.getSize(), ram.getSize()); file.read(&ram[0], size); } catch (FileException&) { throw MSXException("Error reading file: " + filename); } } string_ref subtype = config.getChildData("subtype", "expanded"); if (subtype == "Snatcher") { mapperMask = 0x0F; lowRAM = true; highRAM = false; } else if (subtype == "SD-Snatcher") { mapperMask = 0x0F; lowRAM = false; highRAM = true; } else if (subtype == "mirrored") { mapperMask = 0x07; lowRAM = true; highRAM = true; } else { // subtype "expanded", and all others mapperMask = 0x0F; lowRAM = true; highRAM = true; } // make valgrind happy for (int i = 0; i < 4; ++i) { isRamSegment[i] = true; mapper[i] = 0; } powerUp(getCurrentTime()); } void MSXSCCPlusCart::powerUp(EmuTime::param time) { scc.powerUp(time); reset(time); } void MSXSCCPlusCart::reset(EmuTime::param time) { setModeRegister(0); setMapper(0, 0); setMapper(1, 1); setMapper(2, 2); setMapper(3, 3); scc.reset(time); } byte MSXSCCPlusCart::readMem(word addr, EmuTime::param time) { byte result; if (((enable == EN_SCC) && (0x9800 <= addr) && (addr < 0xA000)) || ((enable == EN_SCCPLUS) && (0xB800 <= addr) && (addr < 0xC000))) { result = scc.readMem(addr & 0xFF, time); } else { result = MSXSCCPlusCart::peekMem(addr, time); } return result; } byte MSXSCCPlusCart::peekMem(word addr, EmuTime::param time) const { // modeRegister can not be read! if (((enable == EN_SCC) && (0x9800 <= addr) && (addr < 0xA000)) || ((enable == EN_SCCPLUS) && (0xB800 <= addr) && (addr < 0xC000))) { // SCC visible in 0x9800 - 0x9FFF // SCC+ visible in 0xB800 - 0xBFFF return scc.peekMem(addr & 0xFF, time); } else if ((0x4000 <= addr) && (addr < 0xC000)) { // SCC(+) enabled/disabled but not requested so memory stuff return internalMemoryBank[(addr >> 13) - 2][addr & 0x1FFF]; } else { // outside memory range return 0xFF; } } const byte* MSXSCCPlusCart::getReadCacheLine(word start) const { if (((enable == EN_SCC) && (0x9800 <= start) && (start < 0xA000)) || ((enable == EN_SCCPLUS) && (0xB800 <= start) && (start < 0xC000))) { // SCC visible in 0x9800 - 0x9FFF // SCC+ visible in 0xB800 - 0xBFFF return nullptr; } else if ((0x4000 <= start) && (start < 0xC000)) { // SCC(+) enabled/disabled but not requested so memory stuff return &internalMemoryBank[(start >> 13) - 2][start & 0x1FFF]; } else { // outside memory range return unmappedRead; } } void MSXSCCPlusCart::writeMem(word address, byte value, EmuTime::param time) { if ((address < 0x4000) || (0xC000 <= address)) { // outside memory range return; } // Mode register is mapped upon 0xBFFE and 0xBFFF if ((address | 0x0001) == 0xBFFF) { setModeRegister(value); return; } // Write to RAM int regio = (address >> 13) - 2; if (isRamSegment[regio]) { // According to Sean Young // when the regio's are in RAM mode you can read from // the SCC(+) but not write to them // => we assume a write to the memory but maybe // they are just discarded // TODO check this out => ask Sean... if (isMapped[regio]) { internalMemoryBank[regio][address & 0x1FFF] = value; } return; } /* Write to bankswitching registers * The address to change banks: * bank 1: 0x5000 - 0x57FF (0x5000 used) * bank 2: 0x7000 - 0x77FF (0x7000 used) * bank 3: 0x9000 - 0x97FF (0x9000 used) * bank 4: 0xB000 - 0xB7FF (0xB000 used) */ if ((address & 0x1800) == 0x1000) { setMapper(regio, value); return; } // call writeMemInterface of SCC if needed switch (enable) { case EN_NONE: // do nothing break; case EN_SCC: if ((0x9800 <= address) && (address < 0xA000)) { scc.writeMem(address & 0xFF, value, time); } break; case EN_SCCPLUS: if ((0xB800 <= address) && (address < 0xC000)) { scc.writeMem(address & 0xFF, value, time); } break; } } byte* MSXSCCPlusCart::getWriteCacheLine(word start) const { if ((0x4000 <= start) && (start < 0xC000)) { if (start == (0xBFFF & CacheLine::HIGH)) { return nullptr; } int regio = (start >> 13) - 2; if (isRamSegment[regio] && isMapped[regio]) { return &internalMemoryBank[regio][start & 0x1FFF]; } return nullptr; } return unmappedWrite; } void MSXSCCPlusCart::setMapper(int regio, byte value) { mapper[regio] = value; value &= mapperMask; byte* block; if ((!lowRAM && (value < 8)) || (!highRAM && (value >= 8))) { block = unmappedRead; isMapped[regio] = false; } else { block = &ram[0x2000 * value]; isMapped[regio] = true; } checkEnable(); // invalidateMemCache() done below internalMemoryBank[regio] = block; invalidateMemCache(0x4000 + regio * 0x2000, 0x2000); } void MSXSCCPlusCart::setModeRegister(byte value) { modeRegister = value; checkEnable(); // invalidateMemCache() done below if (modeRegister & 0x20) { scc.setChipMode(SCC::SCC_plusmode); } else { scc.setChipMode(SCC::SCC_Compatible); } if (modeRegister & 0x10) { isRamSegment[0] = true; isRamSegment[1] = true; isRamSegment[2] = true; isRamSegment[3] = true; } else { isRamSegment[0] = (modeRegister & 0x01) == 0x01; isRamSegment[1] = (modeRegister & 0x02) == 0x02; isRamSegment[2] = (modeRegister & 0x24) == 0x24; // extra requirement: SCC+ mode isRamSegment[3] = false; } invalidateMemCache(0x4000, 0x8000); } void MSXSCCPlusCart::checkEnable() { if ((modeRegister & 0x20) && (mapper[3] & 0x80)) { enable = EN_SCCPLUS; } else if ((!(modeRegister & 0x20)) && ((mapper[2] & 0x3F) == 0x3F)) { enable = EN_SCC; } else { enable = EN_NONE; } } template void MSXSCCPlusCart::serialize(Archive& ar, unsigned /*version*/) { // These are constants: // mapperMask, lowRAM, highRAM // only serialize that part of the Ram object that's actually // present in the cartridge unsigned ramSize = (lowRAM && highRAM && (mapperMask == 0xF)) ? 0x20000 : 0x10000; unsigned ramBase = lowRAM ? 0x00000 : 0x10000; ar.serialize_blob("ram", &ram[ramBase], ramSize); ar.serialize("scc", scc); ar.serialize("mapper", mapper); ar.serialize("mode", modeRegister); if (ar.isLoader()) { // recalculate: isMapped[4], internalMemoryBank[4] for (int i = 0; i < 4; ++i) { setMapper(i, mapper[i]); } // recalculate: enable, isRamSegment[4] setModeRegister(modeRegister); } } INSTANTIATE_SERIALIZE_METHODS(MSXSCCPlusCart); REGISTER_MSXDEVICE(MSXSCCPlusCart, "SCCPlus"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/MSXSCCPlusCart.hh000066400000000000000000000025071257557151200213610ustar00rootroot00000000000000// Note: this device is actually called SCC-I. But this would take a lot of // renaming, which isn't worth it right now. TODO rename this :) #ifndef MSXSCCPLUSCART_HH #define MSXSCCPLUSCART_HH #include "MSXDevice.hh" #include "SCC.hh" #include "Ram.hh" #include "RomBlockDebuggable.hh" namespace openmsx { class MSXSCCPlusCart final : public MSXDevice { public: explicit MSXSCCPlusCart(const DeviceConfig& config); void powerUp(EmuTime::param time) override; void reset(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte* getWriteCacheLine(word start) const override; template void serialize(Archive& ar, unsigned version); private: void setMapper(int regio, byte value); void setModeRegister(byte value); void checkEnable(); Ram ram; SCC scc; RomBlockDebuggable romBlockDebug; byte* internalMemoryBank[4]; // 4 blocks of 8kB starting at #4000 enum SCCEnable {EN_NONE, EN_SCC, EN_SCCPLUS} enable; byte modeRegister; bool isRamSegment[4]; bool isMapped[4]; byte mapper[4]; /*const*/ byte mapperMask; /*const*/ bool lowRAM, highRAM; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/MSXTurboRPCM.cc000066400000000000000000000066271257557151200210450ustar00rootroot00000000000000#include "MSXTurboRPCM.hh" #include "MSXMotherBoard.hh" #include "MSXMixer.hh" #include "serialize.hh" #include "unreachable.hh" namespace openmsx { MSXTurboRPCM::MSXTurboRPCM(const DeviceConfig& config) : MSXDevice(config) , mixer(getMotherBoard().getMSXMixer()) , connector(getPluggingController(), "pcminput") , dac("PCM", "Turbo-R PCM", config) , reference(getCurrentTime()) , hwMute(false) { reset(getCurrentTime()); } MSXTurboRPCM::~MSXTurboRPCM() { hardwareMute(false); } void MSXTurboRPCM::reset(EmuTime::param time) { reference.reset(time); status = 0; DValue = 0x80; // TODO correct initial value? hold = 0x80; // avoid UMR dac.reset(time); hardwareMute(false); } byte MSXTurboRPCM::readIO(word port, EmuTime::param time) { return peekIO(port, time); } byte MSXTurboRPCM::peekIO(word port, EmuTime::param time) const { byte result; switch (port & 0x01) { case 0: // bit 0-1 15.75kHz counter // bit 2-7 not used result = reference.getTicksTill(time) & 0x03; break; case 1: // bit 0 BUFF 0->D/A TODO check this bit // 1->A/D // bit 1 MUTE mute ALL sound 0->muted // bit 2 FILT filter 0->standard signal // 1->filtered signal // bit 3 SEL select 0->D/A // 1->Mic/Jack // bit 4 SMPL sample/hold 0->sample // 1->hold // bit 5-6 not used // bit 7 COMP comparator result 0->greater // 1->smaller result = (getComp(time) ? 0x80 : 0x00) | (status & 0x1F); break; default: // unreachable, avoid warning UNREACHABLE; result = 0; } return result; } void MSXTurboRPCM::writeIO(word port, byte value, EmuTime::param time) { switch (port & 0x01) { case 0: // While playing: sample value // recording: compare value // Resets counter reference.advance(time); DValue = value; if (status & 0x02) { dac.writeDAC(DValue, time); } break; case 1: // bit 0 BUFF // bit 1 MUTE mute _all_ sound 0->no sound // bit 2 FILT filter 1->filter on // bit 3 SEL select 1->Mic/Jack 0->D/A // bit 4 SMPL sample/hold 1->hold // bit 5-7 not used byte change = status ^ value; status = value; if ((change & 0x01) && ((status & 0x01) == 0)) { dac.writeDAC(DValue, time); } // TODO status & 0x08 if ((change & 0x10) && (status & 0x10)) { hold = getSample(time); } hardwareMute(!(status & 0x02)); break; } } byte MSXTurboRPCM::getSample(EmuTime::param time) const { return (status & 0x04) ? (connector.readSample(time) / 256) + 0x80 : 0x80; // TODO check } bool MSXTurboRPCM::getComp(EmuTime::param time) const { // TODO also when D/A ?? byte sample = (status & 0x10) ? hold : getSample(time); return sample >= DValue; } void MSXTurboRPCM::hardwareMute(bool mute) { if (mute == hwMute) return; hwMute = mute; if (hwMute) { mixer.mute(); } else { mixer.unmute(); } } template void MSXTurboRPCM::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("audioConnector", connector); ar.serialize("reference", reference); ar.serialize("status", status); ar.serialize("DValue", DValue); ar.serialize("hold", hold); ar.serialize("DAC", dac); hardwareMute(!(status & 0x02)); // restore hwMute } INSTANTIATE_SERIALIZE_METHODS(MSXTurboRPCM); REGISTER_MSXDEVICE(MSXTurboRPCM, "TurboR-PCM"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/MSXTurboRPCM.hh000066400000000000000000000016411257557151200210460ustar00rootroot00000000000000#ifndef MSXTURBORPCM_HH #define MSXTURBORPCM_HH #include "MSXDevice.hh" #include "AudioInputConnector.hh" #include "DACSound8U.hh" #include "Clock.hh" namespace openmsx { class MSXMixer; class MSXTurboRPCM final : public MSXDevice { public: explicit MSXTurboRPCM(const DeviceConfig& config); ~MSXTurboRPCM(); void reset(EmuTime::param time) override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: byte getSample(EmuTime::param time) const; bool getComp(EmuTime::param time) const; void hardwareMute(bool mute); MSXMixer& mixer; AudioInputConnector connector; DACSound8U dac; Clock<15750> reference; byte DValue; byte status; byte hold; bool hwMute; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/MSXYamahaSFG.cc000066400000000000000000000077131257557151200210250ustar00rootroot00000000000000#include "MSXYamahaSFG.hh" #include "CacheLine.hh" #include "serialize.hh" namespace openmsx { MSXYamahaSFG::MSXYamahaSFG(const DeviceConfig& config) : MSXDevice(config) , rom(getName() + " ROM", "rom", config) , ym2151(getName(), "Yamaha SFG-01/05", config, getCurrentTime()) , ym2148(getName(), getMotherBoard()) { reset(getCurrentTime()); } void MSXYamahaSFG::reset(EmuTime::param time) { ym2151.reset(time); ym2148.reset(); registerLatch = 0; // TODO check irqVector = 255; // TODO check irqVector2148 = 255; // TODO check } void MSXYamahaSFG::writeMem(word address, byte value, EmuTime::param time) { if (address < 0x3FF0 || address >= 0x3FF8) { return; } switch (address & 0x3FFF) { case 0x3FF0: // OPM ADDRESS REGISTER writeRegisterPort(value, time); break; case 0x3FF1: // OPM DATA REGISTER writeDataPort(value, time); break; case 0x3FF2: // Register for data latched to ST0 to ST7 output ports // TODO: keyboardLatch = value; //std::cerr << "TODO: keyboardLatch = " << (int)value << std::endl; break; case 0x3FF3: // MIDI IRQ VECTOR ADDRESS REGISTER irqVector2148 = value; break; case 0x3FF4: // EXTERNAL IRQ VECTOR ADDRESS REGISTER // IRQ vector for YM2151 (+ default vector ???) irqVector = value; break; case 0x3FF5: // MIDI standard UART DATA WRITE BUFFER ym2148.writeData(value, time); break; case 0x3FF6: // MIDI standard UART COMMAND REGISTER ym2148.writeCommand(value); break; } } byte* MSXYamahaSFG::getWriteCacheLine(word start) const { if ((start & CacheLine::HIGH) == (0x3FF0 & CacheLine::HIGH)) { return nullptr; } return unmappedWrite; } byte MSXYamahaSFG::readIRQVector() { return ym2148.pendingIRQ() ? irqVector2148 : irqVector; } void MSXYamahaSFG::writeRegisterPort(byte value, EmuTime::param /*time*/) { registerLatch = value; } void MSXYamahaSFG::writeDataPort(byte value, EmuTime::param time) { ym2151.writeReg(registerLatch, value, time); } byte MSXYamahaSFG::readMem(word address, EmuTime::param time) { if (address < 0x3FF0 || address >= 0x3FF8) { return peekMem(address, time); } switch (address & 0x3FFF) { case 0x3FF0: // (not used, it seems) case 0x3FF1: // OPM STATUS REGISTER case 0x3FF2: // Data buffer for SD0 to SD7 input ports return peekMem(address, time); break; case 0x3FF5: // MIDI standard UART DATA READ BUFFER return ym2148.readData(time); break; case 0x3FF6: // MIDI standard UART STATUS REGISTER return ym2148.readStatus(time); break; } return 0xFF; } byte MSXYamahaSFG::peekMem(word address, EmuTime::param time) const { if (address < 0x3FF0 || address >= 0x3FF8) { // size can also be 16kB for SFG-01 or 32kB for SFG-05 return rom[address & (rom.getSize() - 1)]; } switch (address & 0x3FFF) { case 0x3FF0: // (not used, it seems) return 0xFF; case 0x3FF1: // OPM STATUS REGISTER return ym2151.readStatus(); case 0x3FF2: // Data buffer for SD0 to SD7 input ports // TODO: return getKbdStatus(); break; case 0x3FF5: // MIDI standard UART DATA READ BUFFER return ym2148.peekData(time); break; case 0x3FF6: // MIDI standard UART STATUS REGISTER return ym2148.peekStatus(time); break; } return 0xFF; } const byte* MSXYamahaSFG::getReadCacheLine(word start) const { if ((start & CacheLine::HIGH) == (0x3FF0 & CacheLine::HIGH)) { return nullptr; } return &rom[start & (rom.getSize() - 1)]; } // version 1: initial version // version 2: moved irqVector2148 from ym2148 to here template void MSXYamahaSFG::serialize(Archive& ar, unsigned version) { ar.serialize("YM2151", ym2151); ar.serialize("YM2148", ym2148); ar.serialize("registerLatch", registerLatch); ar.serialize("irqVector", irqVector); if (ar.versionAtLeast(version, 2)) { ar.serialize("irqVector2148", irqVector2148); } else { irqVector2148 = 255; // could be retrieved from the old ym2148 // savestate data, but I didn't bother } } INSTANTIATE_SERIALIZE_METHODS(MSXYamahaSFG); REGISTER_MSXDEVICE(MSXYamahaSFG, "YamahaSFG"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/MSXYamahaSFG.hh000066400000000000000000000020321257557151200210240ustar00rootroot00000000000000#ifndef MSXYAMAHASFG_HH #define MSXYAMAHASFG_HH #include "MSXDevice.hh" #include "YM2151.hh" #include "YM2148.hh" #include "Rom.hh" #include "serialize_meta.hh" namespace openmsx { class MSXYamahaSFG final : public MSXDevice { public: explicit MSXYamahaSFG(const DeviceConfig& config); void reset(EmuTime::param time) override; byte readMem(word address, EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; const byte* getReadCacheLine(word start) const override; void writeMem(word address, byte value, EmuTime::param time) override; byte* getWriteCacheLine(word start) const override; byte readIRQVector() override; template void serialize(Archive& ar, unsigned version); private: void writeRegisterPort(byte value, EmuTime::param time); void writeDataPort(byte value, EmuTime::param time); Rom rom; YM2151 ym2151; YM2148 ym2148; int registerLatch; byte irqVector; byte irqVector2148; }; SERIALIZE_CLASS_VERSION(MSXYamahaSFG, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/Mixer.cc000066400000000000000000000101331257557151200177470ustar00rootroot00000000000000#include "Mixer.hh" #include "MSXMixer.hh" #include "NullSoundDriver.hh" #include "SDLSoundDriver.hh" #include "DirectXSoundDriver.hh" #include "CommandController.hh" #include "CliComm.hh" #include "MSXException.hh" #include "memory.hh" #include "stl.hh" #include "unreachable.hh" #include "components.hh" #include "build-info.hh" #include namespace openmsx { #if defined(_WIN32) static const int defaultsamples = 2048; #elif PLATFORM_ANDROID static const int defaultsamples = 2560; #else static const int defaultsamples = 1024; #endif static Mixer::SoundDriverType getDefaultSoundDriver() { #ifdef _WIN32 return Mixer::SND_DIRECTX; #else return Mixer::SND_SDL; #endif } static EnumSetting::Map getSoundDriverMap() { EnumSetting::Map soundDriverMap = { { "null", Mixer::SND_NULL }, { "sdl", Mixer::SND_SDL } }; #ifdef _WIN32 soundDriverMap.emplace_back("directx", Mixer::SND_DIRECTX); #endif return soundDriverMap; } Mixer::Mixer(Reactor& reactor_, CommandController& commandController_) : reactor(reactor_) , commandController(commandController_) , soundDriverSetting( commandController, "sound_driver", "select the sound output driver", getDefaultSoundDriver(), getSoundDriverMap()) , muteSetting( commandController, "mute", "(un)mute the emulation sound", false, Setting::DONT_SAVE) , masterVolume( commandController, "master_volume", "master volume", 75, 0, 100) , frequencySetting( commandController, "frequency", "mixer frequency", 44100, 11025, 48000) , samplesSetting( commandController, "samples", "mixer samples", defaultsamples, 64, 8192) , muteCount(0) { muteSetting .attach(*this); frequencySetting .attach(*this); samplesSetting .attach(*this); soundDriverSetting.attach(*this); // Set correct initial mute state. if (muteSetting.getBoolean()) ++muteCount; reloadDriver(); } Mixer::~Mixer() { assert(msxMixers.empty()); driver.reset(); soundDriverSetting.detach(*this); samplesSetting .detach(*this); frequencySetting .detach(*this); muteSetting .detach(*this); } void Mixer::reloadDriver() { // Destroy old driver before attempting to create a new one. Though // this means we end up without driver if creating the new one failed // for some reason. driver = make_unique(); try { switch (soundDriverSetting.getEnum()) { case SND_NULL: driver = make_unique(); break; case SND_SDL: driver = make_unique( reactor, frequencySetting.getInt(), samplesSetting.getInt()); break; #ifdef _WIN32 case SND_DIRECTX: driver = make_unique( frequencySetting.getInt(), samplesSetting.getInt()); break; #endif default: UNREACHABLE; } } catch (MSXException& e) { commandController.getCliComm().printWarning(e.getMessage()); } muteHelper(); } void Mixer::registerMixer(MSXMixer& mixer) { assert(!contains(msxMixers, &mixer)); msxMixers.push_back(&mixer); muteHelper(); } void Mixer::unregisterMixer(MSXMixer& mixer) { msxMixers.erase(find_unguarded(msxMixers, &mixer)); muteHelper(); } void Mixer::mute() { if (muteCount++ == 0) { muteHelper(); } } void Mixer::unmute() { assert(muteCount); if (--muteCount == 0) { muteHelper(); } } void Mixer::muteHelper() { bool mute = muteCount || msxMixers.empty(); unsigned samples = mute ? 0 : driver->getSamples(); unsigned frequency = driver->getFrequency(); for (auto& m : msxMixers) { m->setMixerParams(samples, frequency); } if (mute) { driver->mute(); } else { driver->unmute(); } } void Mixer::uploadBuffer(MSXMixer& /*msxMixer*/, short* buffer, unsigned len) { // can only handle one MSXMixer ATM assert(!msxMixers.empty()); driver->uploadBuffer(buffer, len); } void Mixer::update(const Setting& setting) { if (&setting == &muteSetting) { if (muteSetting.getBoolean()) { mute(); } else { unmute(); } } else if ((&setting == &samplesSetting) || (&setting == &soundDriverSetting) || (&setting == &frequencySetting)) { reloadDriver(); } else { UNREACHABLE; } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/Mixer.hh000066400000000000000000000027621257557151200177720ustar00rootroot00000000000000#ifndef MIXER_HH #define MIXER_HH #include "Observer.hh" #include "BooleanSetting.hh" #include "EnumSetting.hh" #include "IntegerSetting.hh" #include "noncopyable.hh" #include #include namespace openmsx { class SoundDriver; class Reactor; class CommandController; class MSXMixer; class Mixer final : private Observer, private noncopyable { public: enum SoundDriverType { SND_NULL, SND_SDL, SND_DIRECTX }; Mixer(Reactor& reactor, CommandController& commandController); ~Mixer(); /** Register per-machine mixer */ void registerMixer(MSXMixer& mixer); /** Unregister per-machine mixer */ void unregisterMixer(MSXMixer& mixer); /** * This methods (un)mute the sound. * These methods may be called multiple times, as long as * you never call unmute() more than mute() */ void mute(); void unmute(); // Called by MSXMixer /** Upload new sample data */ void uploadBuffer(MSXMixer& msxMixer, short* buffer, unsigned len); IntegerSetting& getMasterVolume() { return masterVolume; } private: void reloadDriver(); void muteHelper(); // Observer void update(const Setting& setting) override; std::vector msxMixers; std::unique_ptr driver; Reactor& reactor; CommandController& commandController; EnumSetting soundDriverSetting; BooleanSetting muteSetting; IntegerSetting masterVolume; IntegerSetting frequencySetting; IntegerSetting samplesSetting; int muteCount; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/NullSoundDriver.cc000066400000000000000000000005361257557151200217700ustar00rootroot00000000000000#include "NullSoundDriver.hh" namespace openmsx { void NullSoundDriver::mute() { } void NullSoundDriver::unmute() { } unsigned NullSoundDriver::getFrequency() const { return 44100; } unsigned NullSoundDriver::getSamples() const { return 0; } void NullSoundDriver::uploadBuffer(short* /*buffer*/, unsigned /*len*/) { } } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/NullSoundDriver.hh000066400000000000000000000006011257557151200217730ustar00rootroot00000000000000#ifndef NULLSOUNDDRIVER_HH #define NULLSOUNDDRIVER_HH #include "SoundDriver.hh" namespace openmsx { class NullSoundDriver final : public SoundDriver { public: void mute() override; void unmute() override; unsigned getFrequency() const override; unsigned getSamples() const override; void uploadBuffer(short* buffer, unsigned len) override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/ResampleAlgo.hh000066400000000000000000000004511257557151200212520ustar00rootroot00000000000000#ifndef RESAMPLEALGO_HH #define RESAMPLEALGO_HH #include "EmuTime.hh" namespace openmsx { class ResampleAlgo { public: virtual ~ResampleAlgo() {} virtual bool generateOutput(int* dataOut, unsigned num, EmuTime::param time) = 0; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/ResampleBlip.cc000066400000000000000000000062341257557151200212510ustar00rootroot00000000000000#include "ResampleBlip.hh" #include "ResampledSoundDevice.hh" #include "likely.hh" #include "vla.hh" #include #include namespace openmsx { template ResampleBlip::ResampleBlip( ResampledSoundDevice& input_, const DynamicClock& hostClock_, unsigned emuSampleRate) : input(input_) , hostClock(hostClock_) , emuClock(hostClock.getTime(), emuSampleRate) , step(FP::roundRatioDown(hostClock.getFreq(), emuSampleRate)) { for (auto& l : lastInput) l = 0; } template bool ResampleBlip::generateOutput(int* dataOut, unsigned hostNum, EmuTime::param time) { unsigned emuNum = emuClock.getTicksTill(time); if (emuNum > 0) { // 3 extra for padding, CHANNELS extra for sentinel // Clang will produce a link error if the length expression is put // inside the macro. const unsigned len = emuNum * CHANNELS + std::max(3u, CHANNELS); VLA_SSE_ALIGNED(int, buf, len); EmuTime emu1 = emuClock.getFastAdd(1); // time of 1st emu-sample assert(emu1 > hostClock.getTime()); if (input.generateInput(buf, emuNum)) { FP pos1; hostClock.getTicksTill(emu1, pos1); for (unsigned ch = 0; ch < CHANNELS; ++ch) { // In case of PSG (and to a lesser degree SCC) it happens // very often that two consecutive samples have the same // value. We can benefit from this by setting a sentinel // at the end of the buffer and move the end-of-loop test // into the 'samples differ' branch. assert(emuNum > 0); buf[CHANNELS * emuNum + ch] = buf[CHANNELS * (emuNum - 1) + ch] + 1; FP pos = pos1; int last = lastInput[ch]; // local var is slightly faster for (unsigned i = 0; /**/; ++i) { int delta = buf[CHANNELS * i + ch] - last; if (unlikely(delta != 0)) { if (i == emuNum) { break; } last = buf[CHANNELS * i + ch]; blip[ch].addDelta( BlipBuffer::TimeIndex(pos), delta); } pos += step; } lastInput[ch] = last; } } else { // input all zero BlipBuffer::TimeIndex pos; hostClock.getTicksTill(emu1, pos); for (unsigned ch = 0; ch < CHANNELS; ++ch) { if (lastInput[ch] != 0) { int delta = -lastInput[ch]; lastInput[ch] = 0; blip[ch].addDelta(pos, delta); } } } emuClock += emuNum; assert(emuClock.getTime() <= time); assert(emuClock.getFastAdd(1) > time); } bool results[CHANNELS]; for (unsigned ch = 0; ch < CHANNELS; ++ch) { results[ch] = blip[ch].template readSamples(dataOut + ch, hostNum); } static_assert((CHANNELS == 1) || (CHANNELS == 2), "either mono or stereo"); bool result; if (CHANNELS == 1) { result = results[0]; } else { if (results[0] == results[1]) { // Both muted or both unmuted result = results[0]; } else { // One channel muted, the other not. // We have to set the muted channel to all-zero. unsigned offset = results[0] ? 1 : 0; for (unsigned i = 0; i < hostNum; ++i) { dataOut[2 * i + offset] = 0; } result = true; } } return result; } // Force template instantiation. template class ResampleBlip<1>; template class ResampleBlip<2>; } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/ResampleBlip.hh000066400000000000000000000016171257557151200212630ustar00rootroot00000000000000#ifndef RESAMPLEBLIP_HH #define RESAMPLEBLIP_HH #include "ResampleAlgo.hh" #include "BlipBuffer.hh" #include "DynamicClock.hh" namespace openmsx { class ResampledSoundDevice; template class ResampleBlip final : public ResampleAlgo { public: ResampleBlip(ResampledSoundDevice& input, const DynamicClock& hostClock, unsigned emuSampleRate); bool generateOutput(int* dataOut, unsigned num, EmuTime::param time) override; private: BlipBuffer blip[CHANNELS]; ResampledSoundDevice& input; const DynamicClock& hostClock; // time of the last host-sample, // ticks once per host sample DynamicClock emuClock; // time of the last emu-sample, // ticks once per emu-sample using FP = FixedPoint<16>; const FP step; int lastInput[CHANNELS]; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/ResampleCoeffs.ii000066400000000000000000002207571257557151200216140ustar00rootroot00000000000000// Based on libsamplerate-0.1.2 (aka Secret Rabit Code) // see comments in Resample.cc // // f = make_filter(8f, 128f, 100.3); // Pass band width : 0.0039062 (should be 0.0039062) // Stop band atten. : 100.71 dB // -3dB band width : 0.484 // half length : 2463 // increment : 128 8.31472372954840555082e-01f, 8.31414005540308198583e-01f, 8.31238918266223869580e-01f, 8.30947156036480505392e-01f, 8.30538793675450581766e-01f, 8.30013935904800659316e-01f, 8.29372717311066987023e-01f, 8.28615302303967515840e-01f, 8.27741885065490623496e-01f, 8.26752689489751890761e-01f, 8.25647969113678215081e-01f, 8.24428007038499943704e-01f, 8.23093115842108757896e-01f, 8.21643637482293187624e-01f, 8.20079943190897053817e-01f, 8.18402433358933589780e-01f, 8.16611537412689103554e-01f, 8.14707713680854150873e-01f, 8.12691449252757824873e-01f, 8.10563259827706050764e-01f, 8.08323689555523805517e-01f, 8.05973310868314363198e-01f, 8.03512724303517833491e-01f, 8.00942558318331943035e-01f, 7.98263469095534694553e-01f, 7.95476140340800830231e-01f, 7.92581283071560838138e-01f, 7.89579635397499868255e-01f, 7.86471962292734527722e-01f, 7.83259055359786127148e-01f, 7.79941732585400893107e-01f, 7.76520838088307852054e-01f, 7.72997241859018080490e-01f, 7.69371839491718167992e-01f, 7.65645551908390675777e-01f, 7.61819325075220210586e-01f, 7.57894129711408459649e-01f, 7.53870960990470018181e-01f, 7.49750838234153449413e-01f, 7.45534804599028211314e-01f, 7.41223926755909090502e-01f, 7.36819294562192195208e-01f, 7.32322020727209643809e-01f, 7.27733240470738174110e-01f, 7.23054111174766811487e-01f, 7.18285812028632841830e-01f, 7.13429543667664534112e-01f, 7.08486527805442301009e-01f, 7.03458006859804640953e-01f, 6.98345243572719653891e-01f, 6.93149520624175785599e-01f, 6.87872140240182283755e-01f, 6.82514423795047564525e-01f, 6.77077711408058502407e-01f, 6.71563361534684655219e-01f, 6.65972750552474845875e-01f, 6.60307272341742135247e-01f, 6.54568337861228477514e-01f, 6.48757374718860524432e-01f, 6.42875826737744904271e-01f, 6.36925153517562181449e-01f, 6.30906829991492501541e-01f, 6.24822345978837789815e-01f, 6.18673205733470954470e-01f, 6.12460927488293727095e-01f, 6.06187042995817604307e-01f, 5.99853097065060292259e-01f, 5.93460647094893878339e-01f, 5.87011262603992944875e-01f, 5.80506524757569142281e-01f, 5.73948025891025337408e-01f, 5.67337369030688098981e-01f, 5.60676167411809700525e-01f, 5.53966043993961543279e-01f, 5.47208630974010734604e-01f, 5.40405569296826038261e-01f, 5.33558508163880174102e-01f, 5.26669104539922661168e-01f, 5.19739022657876970079e-01f, 5.12769933522119303326e-01f, 5.05763514410336290084e-01f, 4.98721448374081555155e-01f, 4.91645423738241937883e-01f, 4.84537133599546865348e-01f, 4.77398275324308896117e-01f, 4.70230550045545592219e-01f, 4.63035662159660077464e-01f, 4.55815318822846149427e-01f, 4.48571229447379538069e-01f, 4.41305105197960123586e-01f, 4.34018658488283970431e-01f, 4.26713602477997000495e-01f, 4.19391650570203500248e-01f, 4.12054515909689722530e-01f, 4.04703910882034223473e-01f, 3.97341546613763640927e-01f, 3.89969132473721613596e-01f, 3.82588375575806771689e-01f, 3.75200980283257823356e-01f, 3.67808647714624070701e-01f, 3.60413075251609871241e-01f, 3.53015956048925771960e-01f, 3.45618978546330835044e-01f, 3.38223825983006376461e-01f, 3.30832175914426429575e-01f, 3.23445699731881031180e-01f, 3.16066062184803764357e-01f, 3.08694920906066150312e-01f, 3.01333925940378832831e-01f, 2.93984719275965256102e-01f, 2.86648934379644393378e-01f, 2.79328195735489559492e-01f, 2.72024118387182545220e-01f, 2.64738307484245039003e-01f, 2.57472357832259801658e-01f, 2.50227853447243409057e-01f, 2.43006367114305704691e-01f, 2.35809459950733935063e-01f, 2.28638680973647728800e-01f, 2.21495566672345989279e-01f, 2.14381640585498134399e-01f, 2.07298412883298144305e-01f, 2.00247379954717363848e-01f, 1.93230023999986955108e-01f, 1.86247812628430653437e-01f, 1.79302198461779749294e-01f, 1.72394618743085786816e-01f, 1.65526494951356295537e-01f, 1.58699232422028796430e-01f, 1.51914219973401071195e-01f, 1.45172829539132269838e-01f, 1.38476415806921215879e-01f, 1.31826315863480453272e-01f, 1.25223848845901208904e-01f, 1.18670315599523901184e-01f, 1.12166998342411894374e-01f, 1.05715160336527447260e-01f, 9.93160455657086521652e-02f, 9.29708784205405536216e-02f, 8.66808633902153846673e-02f, 8.04471847614677826321e-02f, 7.42710063246745516574e-02f, 6.81534710872001986415e-02f, 6.20957009940759641076e-02f, 5.60987966560835549235e-02f, 5.01638370853247708703e-02f, 4.42918794383505357026e-02f, 3.84839587669171534490e-02f, 3.27410877764400740086e-02f, 2.70642565922108620236e-02f, 2.14544325334371267788e-02f, 1.59125598951669576520e-02f, 1.04395597381551803740e-02f, 5.03632968672305773861e-03f, -2.96256265336385191805e-04f, -5.55734794075828358179e-03f, -1.07461191566687631893e-02f, -1.58617678942645466689e-02f, -2.09035164602743607498e-02f, -2.58706116401622790435e-02f, -3.07623248430414844568e-02f, -3.55779522382659724178e-02f, -4.03168148836769782428e-02f, -4.49782588454727128013e-02f, -4.95616553096875425699e-02f, -5.40664006852556791594e-02f, -5.84919166986474642345e-02f, -6.28376504800633867154e-02f, -6.71030746411782619276e-02f, -7.12876873444269476554e-02f, -7.53910123638282386738e-02f, -7.94125991373483691715e-02f, -8.33520228108008270906e-02f, -8.72088842732959695914e-02f, -9.09828101842390379872e-02f, -9.46734529918955292072e-02f, -9.82804909435327500589e-02f, -1.01803628087157427284e-01f, -1.05242594264867719844e-01f, -1.08597145097841310535e-01f, -1.11867061962988789681e-01f, -1.15052151961296145188e-01f, -1.18152247877890054228e-01f, -1.21167208133862752684e-01f, -1.24096916729885473063e-01f, -1.26941283181660202750e-01f, -1.29700242447243679900e-01f, -1.32373754846295377252e-01f, -1.34961805971292009287e-01f, -1.37464406590764143257e-01f, -1.39881592544604443917e-01f, -1.42213424631507739937e-01f, -1.44459988488595730827e-01f, -1.46621394463294696386e-01f, -1.48697777477524800682e-01f, -1.50689296884269657850e-01f, -1.52596136316595465399e-01f, -1.54418503529190731527e-01f, -1.56156630232500315270e-01f, -1.57810771919529219121e-01f, -1.59381207685401427021e-01f, -1.60868240039743037872e-01f, -1.62272194711985145998e-01f, -1.63593420449666626659e-01f, -1.64832288809824062392e-01f, -1.65989193943563151379e-01f, -1.67064552373901109572e-01f, -1.68058802766975601273e-01f, -1.68972405696717037360e-01f, -1.69805843403086798027e-01f, -1.70559619543971530131e-01f, -1.71234258940853617537e-01f, -1.71830307318344255307e-01f, -1.72348331037702334756e-01f, -1.72788916824434257702e-01f, -1.73152671490098081231e-01f, -1.73440221648409775845e-01f, -1.73652213425782242506e-01f, -1.73789312166397952319e-01f, -1.73852202131942051855e-01f, -1.73841586196111674845e-01f, -1.73758185534021086793e-01f, -1.73602739306629005878e-01f, -1.73376004340306061335e-01f, -1.73078754801670009478e-01f, -1.72711781867818603420e-01f, -1.72275893392080048372e-01f, -1.71771913565416961545e-01f, -1.71200682573611373538e-01f, -1.70563056250360139954e-01f, -1.69859905726417126370e-01f, -1.69092117074913228514e-01f, -1.68260590952989147473e-01f, -1.67366242239875284703e-01f, -1.66409999671557895518e-01f, -1.65392805472166642966e-01f, -1.64315614982222552021e-01f, -1.63179396283883837437e-01f, -1.61985129823331186483e-01f, -1.60733808030429803360e-01f, -1.59426434935813571281e-01f, -1.58064025785527417778e-01f, -1.56647606653372045704e-01f, -1.55178214051094831571e-01f, -1.53656894536566474008e-01f, -1.52084704320088470730e-01f, -1.50462708868975059140e-01f, -1.48791982510548842500e-01f, -1.47073608033699704256e-01f, -1.45308676289147314931e-01f, -1.43498285788550977715e-01f, -1.41643542302611558092e-01f, -1.39745558458309881988e-01f, -1.37805453335422323224e-01f, -1.35824352062461073398e-01f, -1.33803385412180564362e-01f, -1.31743689396791985313e-01f, -1.29646404863030306753e-01f, -1.27512677087215337002e-01f, -1.25343655370452389253e-01f, -1.23140492634104758984e-01f, -1.20904345015691472298e-01f, -1.18636371465341922127e-01f, -1.16337733342949820048e-01f, -1.14009594016166518338e-01f, -1.11653118459372716065e-01f, -1.09269472853762789066e-01f, -1.06859824188683741331e-01f, -1.04425339864360325337e-01f, -1.01967187296145456177e-01f, -9.94865335204263567803e-02f, -9.69845448023236023083e-02f, -9.44623862453117940641e-02f, -9.19212214028948121358e-02f, -8.93622118924671249296e-02f, -8.67865170114848205607e-02f, -8.41952933560805999447e-02f, -8.15896944422443981537e-02f, -7.89708703296961439522e-02f, -7.63399672485739477779e-02f, -7.36981272290610500697e-02f, -7.10464877340710454501e-02f, -6.83861812951113146042e-02f, -6.57183351514422919859e-02f, -6.30440708926501142129e-02f, -6.03645041047437408421e-02f, -5.76807440198948140342e-02f, -5.49938931699267691267e-02f, -5.23050470436661057994e-02f, -4.96152937482609926456e-02f, -4.69257136745778041798e-02f, -4.42373791667729082677e-02f, -4.15513541961495605492e-02f, -3.88686940393953503370e-02f, -3.61904449613011935938e-02f, -3.35176439020573244121e-02f, -3.08513181692228674602e-02f, -2.81924851344595717162e-02f, -2.55421519351213023585e-02f, -2.29013151807887539724e-02f, -2.02709606648342685609e-02f, -1.76520630811025022733e-02f, -1.50455857457888787787e-02f, -1.24524803245954687053e-02f, -9.87368656524285036313e-03f, -7.31013203541311037958e-03f, -4.76273186619807602227e-03f, -2.23238850112297869746e-03f, 2.80008549183706099625e-04f, 2.77358294660976899965e-03f, 5.24747175940274562800e-03f, 7.70082569017439908660e-03f, 1.01328092980087648006e-02f, 1.25426012146140665460e-02f, 1.49293943544662570388e-02f, 1.72923961188884665885e-02f, 1.96308285940195309527e-02f, 2.19439287426209730936e-02f, 2.42309485896793734561e-02f, 2.64911554017603391442e-02f, 2.87238318600733545660e-02f, 3.09282762272103349532e-02f, 3.31038025075217068327e-02f, 3.52497406010981520486e-02f, 3.73654364513253609004e-02f, 3.94502521859858221176e-02f, 4.15035662518817155542e-02f, 4.35247735429537541130e-02f, 4.55132855218787699125e-02f, 4.74685303351244439196e-02f, 4.93899529214478216765e-02f, 5.12770151138242716304e-02f, 5.31291957347935772660e-02f, 5.49459906852194576721e-02f, 5.67269130264521220797e-02f, 5.84714930558940249039e-02f, 6.01792783759655322551e-02f, 6.18498339564735599705e-02f, 6.34827421903864652641e-02f, 6.50776029430226859995e-02f, 6.66340335946605799577e-02f, 6.81516690765814614483e-02f, 6.96301619005592065115e-02f, 7.10691821818139612965e-02f, 7.24684176554465098175e-02f, 7.38275736863740761340e-02f, 7.51463732727930683319e-02f, 7.64245570431912463194e-02f, 7.76618832469397474272e-02f, 7.88581277384926976337e-02f, 8.00130839552289779837e-02f, 8.11265628889681067459e-02f, 8.21983930512013155623e-02f, 8.32284204320703352442e-02f, 8.42165084531432683868e-02f, 8.51625379140240473808e-02f, 8.60664069328434949702e-02f, 8.69280308806818224898e-02f, 8.77473423099686122839e-02f, 8.85242908769151987114e-02f, 8.92588432580306151420e-02f, 8.99509830607803234637e-02f, 9.06007107284422380511e-02f, 9.12080434392217309636e-02f, 9.17730149996878741270e-02f, 9.22956757325926607782e-02f, 9.27760923591415126443e-02f, 9.32143478757788968014e-02f, 9.36105414255621187669e-02f, 9.39647881641913207407e-02f, 9.42772191207702781046e-02f, 9.45479810533706027664e-02f, 9.47772362994778183598e-02f, 9.49651626213951355338e-02f, 9.51119530466846413441e-02f, 9.52178157037280176178e-02f, 9.52829736524876819148e-02f, 9.53076647105531166160e-02f, 9.52921412745576373871e-02f, 9.52366701370536278271e-02f, 9.51415322989309503177e-02f, 9.50070227774735681647e-02f, 9.48334504101390751707e-02f, 9.46211376541590265532e-02f, 9.43704203820504156086e-02f, 9.40816476731309581094e-02f, 9.37551816011396865758e-02f, 9.33913970180541563870e-02f, 9.29906813342047527948e-02f, 9.25534342947849225647e-02f, 9.20800677528557931506e-02f, 9.15710054389489019888e-02f, 9.10266827273659706599e-02f, 9.04475463992783224043e-02f, 8.98340544027328158361e-02f, 8.91866756096650198371e-02f, 8.85058895700238101867e-02f, 8.77921862631190763615e-02f, 8.70460658462897246546e-02f, 8.62680384010083983748e-02f, 8.54586236765221690659e-02f, 8.46183508311429133375e-02f, 8.37477581712920277068e-02f, 8.28473928884114751980e-02f, 8.19178107938471483651e-02f, 8.09595760518180135312e-02f, 7.99732609105757996648e-02f, 7.89594454318716387764e-02f, 7.79187172188340326784e-02f, 7.68516711423724852015e-02f, 7.57589090662164482692e-02f, 7.46410395707000073884e-02f, 7.34986776754032733461e-02f, 7.23324445607601979047e-02f, 7.11429672887474440213e-02f, 6.99308785227581580779e-02f, 6.86968162467783832748e-02f, 6.74414234839716131287e-02f, 6.61653480147834510694e-02f, 6.48692420946761771905e-02f, 6.35537621716019962559e-02f, 6.22195686033254202751e-02f, 6.08673253747022482973e-02f, 5.94976998150253330588e-02f, 5.81113623155428762890e-02f, 5.67089860472591994478e-02f, 5.52912466791220663653e-02f, 5.38588220967053943333e-02f, 5.24123921214928872869e-02f, 5.09526382308646275110e-02f, 4.94802432788957607945e-02f, 4.79958912180662375380e-02f, 4.65002668219884549017e-02f, 4.49940554092515265783e-02f, 4.34779425684853407241e-02f, 4.19526138847447563340e-02f, 4.04187546673120054463e-02f, 3.88770496790168534895e-02f, 3.73281828671714888124e-02f, 3.57728370962169389680e-02f, 3.42116938821758476141e-02f, 3.26454331290065291604e-02f, 3.10747328669506231447e-02f, 2.95002689929673225788e-02f, 2.79227150133440210622e-02f, 2.63427417885741359249e-02f, 2.47610172805882329528e-02f, 2.31782063024293799591e-02f, 2.15949702704538760989e-02f, 2.00119669591453143431e-02f, 1.84298502586232419709e-02f, 1.68492699349288496680e-02f, 1.52708713931675090641e-02f, 1.36952954435869880129e-02f, 1.21231780706691841254e-02f, 1.05551502053105091677e-02f, 8.99183750016553651196e-03f, 7.43386010822696258193e-03f, 5.88183246471273707412e-03f, 4.33636307232945251988e-03f, 2.79805428998205086427e-03f, 1.26750212499337003291e-03f, -2.54703971099550386531e-04f, -1.76798130311027175757e-03f, -3.27175412906725469539e-03f, -4.76545385331804925710e-03f, -6.24851921581533794464e-03f, -7.72039647752874400727e-03f, -9.18053960192777122884e-03f, -1.06284104324833178490e-02f, -1.20634788661366718077e-02f, -1.34852230226875247771e-02f, -1.48931294100519973078e-02f, -1.62866930853476296615e-02f, -1.76654178117594401476e-02f, -1.90288162111466874205e-02f, -2.03764099123495759369e-02f, -2.17077296951579609696e-02f, -2.30223156299061669505e-02f, -2.43197172126588360974e-02f, -2.55994934959561624976e-02f, -2.68612132150869431513e-02f, -2.81044549098614510063e-02f, -2.93288070418574950415e-02f, -3.05338681071131295974e-02f, -3.17192467442452205595e-02f, -3.28845618379712614776e-02f, -3.40294426180154721551e-02f, -3.51535287533818185945e-02f, -3.62564704419792716017e-02f, -3.73379284955845242022e-02f, -3.83975744201309962533e-02f, -3.94350904913155775322e-02f, -4.04501698255130062720e-02f, -4.14425164459938585870e-02f, -4.24118453444415760556e-02f, -4.33578825377650758921e-02f, -4.42803651202084772032e-02f, -4.51790413107587551789e-02f, -4.60536704958539877541e-02f, -4.69040232673985507672e-02f, -4.77298814560914094751e-02f, -4.85310381600771723054e-02f, -4.93072977689298017068e-02f, -5.00584759829825892696e-02f, -5.07843998280173986037e-02f, -5.14849076653303427964e-02f, -5.21598491971914657306e-02f, -5.28090854677170859488e-02f, -5.34324888591782357072e-02f, -5.40299430837655400572e-02f, -5.46013431708381041796e-02f, -5.51465954496810906171e-02f, -5.56656175277993395256e-02f, -5.61583382647804357779e-02f, -5.66246977417538960298e-02f, -5.70646472264832865795e-02f, -5.74781491341238848225e-02f, -5.78651769836829588112e-02f, -5.82257153502198851469e-02f, -5.85597598128258789441e-02f, -5.88673168984241990120e-02f, -5.91484040214318093631e-02f, -5.94030494193287308957e-02f, -5.96312920841784027681e-02f, -5.98331816901454746627e-02f, -6.00087785170606569096e-02f, -6.01581533700810480725e-02f, -6.02813874954959694197e-02f, -6.03785724927326447609e-02f, -6.04498102226119424230e-02f, -6.04952127119116611631e-02f, -6.05149020542914278797e-02f, -6.05090103076376881197e-02f, -6.04776793878847099273e-02f, -6.04210609593744951695e-02f, -6.03393163218124903291e-02f, -6.02326162938837256222e-02f, -6.01011410935896536745e-02f, -5.99450802153716350018e-02f, -5.97646323040843391317e-02f, -5.95600050258849322837e-02f, -5.93314149361059764431e-02f, -5.90790873441773764507e-02f, -5.88032561756684640786e-02f, -5.85041638315173181950e-02f, -5.81820610445198463379e-02f, -5.78372067331465664064e-02f, -5.74698678527617162759e-02f, -5.70803192443151696800e-02f, -5.66688434805820984153e-02f, -5.62357307100216502471e-02f, -5.57812784983319834287e-02f, -5.53057916677746758127e-02f, -5.48095821343453915020e-02f, -5.42929687428649263015e-02f, -5.37562771000702349644e-02f, -5.31998394057807341695e-02f, -5.26239942822169029513e-02f, -5.20290866015511582754e-02f, -5.14154673117670768523e-02f, -5.07834932609073572141e-02f, -5.01335270197884388943e-02f, -4.94659367032617980353e-02f, -4.87810957901005926018e-02f, -4.80793829415919610204e-02f, -4.73611818189140221236e-02f, -4.66268808993793651418e-02f, -4.58768732916221277929e-02f, -4.51115565498113532672e-02f, -4.43313324869706107401e-02f, -4.35366069874822472774e-02f, -4.27277898188581847783e-02f, -4.19052944428566706558e-02f, -4.10695378260253277092e-02f, -4.02209402497498702544e-02f, -3.93599251198885058400e-02f, -3.84869187760717781921e-02f, -3.76023503007467674308e-02f, -3.67066513280452297319e-02f, -3.58002558525536487832e-02f, -3.48836000380640318119e-02f, -3.39571220263849699039e-02f, -3.30212617462878818553e-02f, -3.20764607226682249563e-02f, -3.11231618859974003277e-02f, -3.01618093821427596390e-02f, -2.91928483826300218251e-02f, -2.82167248954252464221e-02f, -2.72338855763107207109e-02f, -2.62447775409285488646e-02f, -2.52498481775659533444e-02f, -2.42495449607560524530e-02f, -2.32443152657647901516e-02f, -2.22346061840382018537e-02f, -2.12208643396787077773e-02f, -2.02035357070221716080e-02f, -1.91830654293842946256e-02f, -1.81598976390459701524e-02f, -1.71344752785447841659e-02f, -1.61072399233397958729e-02f, -1.50786316059164128556e-02f, -1.40490886413957953571e-02f, -1.30190474547137412242e-02f, -1.19889424094323342185e-02f, -1.09592056382471266657e-02f, -9.93026687525074697183e-03f, -8.90255329001433948211e-03f, -7.87648932354562125724e-03f, -6.85249652618241146540e-03f, -5.83099339747908569642e-03f, -4.81239522814202146106e-03f, -3.79711394406930576734e-03f, -2.78555795254968683455e-03f, -1.77813199067227692071e-03f, -7.75236976000132386663e-04f, 2.22730140442126654798e-04f, 1.21537651881706244492e-03f, 2.20231357271108733539e-03f, 3.18315710891246220898e-03f, 4.15752746468348553799e-03f, 5.12504964248380791986e-03f, 6.08535344210042478813e-03f, 7.03807359014245199208e-03f, 7.98284986685961206465e-03f, 8.91932723024580452476e-03f, 9.84715593738785290034e-03f, 1.07659916630240357766e-02f, 1.16754956152756248638e-02f, 1.25753346485176220604e-02f, 1.34651813733560731662e-02f, 1.43447142636787781933e-02f, 1.52136177607511777904e-02f, 1.60715823743268690360e-02f, 1.69183047807457617728e-02f, 1.77534879179936204430e-02f, 1.85768410776981605925e-02f, 1.93880799940382604618e-02f, 2.01869269295435888045e-02f, 2.09731107577651766649e-02f, 2.17463670427963037812e-02f, 2.25064381156266125894e-02f, 2.32530731473125917841e-02f, 2.39860282189490944815e-02f, 2.47050663884288181082e-02f, 2.54099577539762186418e-02f, 2.61004795144461655687e-02f, 2.67764160263764816605e-02f, 2.74375588577874841845e-02f, 2.80837068387202806741e-02f, 2.87146661085097808230e-02f, 2.93302501597869115513e-02f, 2.99302798792087168533e-02f, 3.05145835849139068774e-02f, 3.10829970607048658437e-02f, 3.16353635869560598226e-02f, 3.21715339682534032240e-02f, 3.26913665577675052742e-02f, 3.31947272783659833029e-02f, 3.36814896404726560331e-02f, 3.41515347566807569990e-02f, 3.46047513531298478462e-02f, 3.50410357776568884280e-02f, 3.54602920047340924858e-02f, 3.58624316372060172875e-02f, 3.62473739048404727803e-02f, 3.66150456597097023748e-02f, 3.69653813684179058385e-02f, 3.72983231011940682964e-02f, 3.76138205178691634178e-02f, 3.79118308507581658340e-02f, 3.81923188844700278732e-02f, 3.84552569326661666804e-02f, 3.87006248117945095277e-02f, 3.89284098118221136287e-02f, 3.91386066639944005252e-02f, 3.93312175056476295842e-02f, 3.95062518421033306848e-02f, 3.96637265056755394799e-02f, 3.98036656118202977761e-02f, 3.99261005124597820326e-02f, 4.00310697465144360585e-02f, 4.01186189876763035778e-02f, 4.01888009894591641258e-02f, 4.02416755275608953313e-02f, 4.02773093395744422041e-02f, 4.02957760620868618573e-02f, 4.02971561652026855072e-02f, 4.02815368845340013304e-02f, 4.02490121506946865737e-02f, 4.01996825163432602857e-02f, 4.01336550808131173329e-02f, 4.00510434123766412284e-02f, 3.99519674681838021790e-02f, 3.98365535119223901361e-02f, 3.97049340292425986809e-02f, 3.95572476409943238340e-02f, 3.93936390143226622396e-02f, 3.92142587716682866628e-02f, 3.90192633977227906761e-02f, 3.88088151443859719070e-02f, 3.85830819337740632546e-02f, 3.83422372593309676581e-02f, 3.80864600850902706997e-02f, 3.78159347431409609275e-02f, 3.75308508293468318096e-02f, 3.72314030973733209318e-02f, 3.69177913510723085255e-02f, 3.65902203352790472701e-02f, 3.62488996250740352911e-02f, 3.58940435135636018438e-02f, 3.55258708982338911042e-02f, 3.51446051659309519066e-02f, 3.47504740765239503175e-02f, 3.43437096453047957523e-02f, 3.39245480241803926136e-02f, 3.34932293817127510471e-02f, 3.30499977820627663383e-02f, 3.25951010628938789293e-02f, 3.21287907122915217251e-02f, 3.16513217447548164674e-02f, 3.11629525763171093267e-02f, 3.06639448988514501382e-02f, 3.01545635536184866710e-02f, 2.96350764041116987446e-02f, 2.91057542082603579181e-02f, 2.85668704900414009706e-02f, 2.80187014105628129368e-02f, 2.74615256386703497637e-02f, 2.68956242211381771345e-02f, 2.63212804524964143205e-02f, 2.57387797445546746833e-02f, 2.51484094956766456030e-02f, 2.45504589598617914414e-02f, 2.39452191156906725455e-02f, 2.33329825351894608321e-02f, 2.27140432526683408443e-02f, 2.20886966335908999093e-02f, 2.14572392435271874778e-02f, 2.08199687172471933905e-02f, 2.01771836280079629178e-02f, 1.95291833570884962312e-02f, 1.88762679636269269101e-02f, 1.82187380548123403767e-02f, 1.75568946564845403124e-02f, 1.68910390841945853846e-02f, 1.62214728147774996103e-02f, 1.55484973584896369464e-02f, 1.48724141317607399387e-02f, 1.41935243306124080076e-02f, 1.35121288047925294795e-02f, 1.28285279326754275003e-02f, 1.21430214969758445281e-02f, 1.14559085613274869858e-02f, 1.07674873477713456404e-02f, 1.00780551152029641815e-02f, 9.38790803882408146641e-03f, 8.69734109064560119429e-03f, 8.00664792108640895052e-03f, 7.31612074171312902482e-03f, 6.62605020916498532735e-03f, 5.93672531030635993593e-03f, 5.24843324865020312286e-03f, 4.56145933209378684481e-03f, 3.87608686200798923521e-03f, 3.19259702372048361982e-03f, 2.51126877843176705626e-03f, 1.83237875660391988202e-03f, 1.15620115285868549186e-03f, 4.83007622422852007059e-04f, -1.86932820843070034112e-04f, -8.53353904797455329115e-04f, -1.51599219771675255281e-03f, -2.17458720530792556924e-03f, -2.82888146600037857989e-03f, -3.47862064448672828401e-03f, -4.12355362347965707925e-03f, -4.76343259365718217635e-03f, -5.39801314176371720144e-03f, -6.02705433684159932323e-03f, -6.65031881456398799024e-03f, -7.26757285964317947813e-03f, -7.87858648628854928153e-03f, -8.48313351669007821576e-03f, -9.08099165750268083608e-03f, -9.67194257431004678072e-03f, -1.02557719640449674509e-02f, -1.08322696253466653482e-02f, -1.14012295268339416271e-02f, -1.19624498732761111452e-02f, -1.25157331696445651287e-02f, -1.30608862830260651078e-02f, -1.35977205023845738180e-02f, -1.41260515961539080687e-02f, -1.46456998676501564532e-02f, -1.51564902082884610246e-02f, -1.56582521485937077588e-02f, -1.61508199069943896020e-02f, -1.66340324363880263936e-02f, -1.71077334684716746149e-02f, -1.75717715558275228149e-02f, -1.80260001117568194329e-02f, -1.84702774478586080609e-02f, -1.89044668093441003975e-02f, -1.93284364080869922042e-02f, -1.97420594534034529732e-02f, -2.01452141805614354242e-02f, -2.05377838770183090977e-02f, -2.09196569063852221004e-02f, -2.12907267301215390176e-02f, -2.16508919269584217127e-02f, -2.20000562100566773860e-02f, -2.23381284419012192399e-02f, -2.26650226469371808558e-02f, -2.29806580219539050014e-02f, -2.32849589442222955349e-02f, -2.35778549773940013234e-02f, -2.38592808751701725145e-02f, -2.41291765827496146324e-02f, -2.43874872360661625048e-02f, -2.46341631588262027774e-02f, -2.48691598573592027865e-02f, -2.50924380132932847709e-02f, -2.53039634740697960691e-02f, -2.55037072413113186098e-02f, -2.56916454570593408291e-02f, -2.58677593878966008423e-02f, -2.60320354069717534162e-02f, -2.61844649739453247395e-02f, -2.63250446128731642459e-02f, -2.64537758880496950975e-02f, -2.65706653778289558776e-02f, -2.66757246464459155111e-02f, -2.67689702138592805492e-02f, -2.68504235236379437679e-02f, -2.69201109089152179621e-02f, -2.69780635564342181898e-02f, -2.70243174687087896191e-02f, -2.70589134243261995871e-02f, -2.70818969364167577707e-02f, -2.70933182093176481986e-02f, -2.70932320934577017257e-02f, -2.70816980384915410862e-02f, -2.70587800447114543156e-02f, -2.70245466127663376554e-02f, -2.69790706917171427270e-02f, -2.69224296254590607369e-02f, -2.68547050975419879237e-02f, -2.67759830744198866481e-02f, -2.66863537471611969587e-02f, -2.65859114716531889921e-02f, -2.64747547073322930800e-02f, -2.63529859544745573285e-02f, -2.62207116900796607939e-02f, -2.60780423023825730366e-02f, -2.59250920240284947471e-02f, -2.57619788639449828760e-02f, -2.55888245379471308827e-02f, -2.54057543981124761556e-02f, -2.52128973609604678519e-02f, -2.50103858344739478359e-02f, -2.47983556439997539222e-02f, -2.45769459570643403201e-02f, -2.43462992071435090080e-02f, -2.41065610164222128564e-02f, -2.38578801175844575078e-02f, -2.36004082746693114037e-02f, -2.33343002030331689300e-02f, -2.30597134884559483436e-02f, -2.27768085054302904524e-02f, -2.24857483346725776918e-02f, -2.21866986798954189675e-02f, -2.18798277838799307138e-02f, -2.15653063438876642366e-02f, -2.12433074264517691987e-02f, -2.09140063815867055519e-02f, -2.05775807564556566243e-02f, -2.02342102085360346642e-02f, -1.98840764183222142025e-02f, -1.95273630016047500257e-02f, -1.91642554213670816832e-02f, -1.87949408993371563925e-02f, -1.84196083272362247374e-02f, -1.80384481777610752862e-02f, -1.76516524153425696797e-02f, -1.72594144067167720724e-02f, -1.68619288313498413845e-02f, -1.64593915917550098760e-02f, -1.60519997237402040069e-02f, -1.56399513066264282679e-02f, -1.52234453734734331148e-02f, -1.48026818213531103502e-02f, -1.43778613217079923037e-02f, -1.39491852308316760523e-02f, -1.35168555005115483686e-02f, -1.30810745888681710658e-02f, -1.26420453714316226301e-02f, -1.21999710524887047813e-02f, -1.17550550767402828961e-02f, -1.13075010413035727252e-02f, -1.08575126080952908542e-02f, -1.04052934166326063736e-02f, -9.95104699728536351566e-03f, -9.49497668501652312967e-03f, -9.03728553364356763933e-03f, -8.57817623065582068875e-03f, -8.11785101262214349449e-03f, -7.65651158122056946231e-03f, -7.19435901992488725798e-03f, -6.73159371137851351291e-03f, -6.26841525548942068990e-03f, -5.80502238827697216589e-03f, -5.34161290150089295564e-03f, -4.87838356310490647849e-03f, -4.41553003850264462471e-03f, -3.95324681273798422126e-03f, -3.49172711354636287548e-03f, -3.03116283534747218975e-03f, -2.57174446419663202748e-03f, -2.11366100372138449731e-03f, -1.65709990207213789248e-03f, -1.20224697991074881177e-03f, -7.49286359465203312402e-04f, -2.98400394673150758020e-04f, 1.50230397559290287587e-04f, 5.96427404960260163468e-04f, 1.04001398633389997676e-03f, 1.48081553681653948010e-03f, 1.91865955192711671630e-03f, 2.35337569038958404136e-03f, 2.78479583570576333731e-03f, 3.21275415646031688166e-03f, 3.63708716533605539573e-03f, 4.05763377682291995208e-03f, 4.47423536360066955581e-03f, 4.88673581157838838457e-03f, 5.29498157357465894235e-03f, 5.69882172162047926506e-03f, 6.09810799787139853900e-03f, 6.49269486411187517899e-03f, 6.88243954983998491859e-03f, 7.26720209891677272618e-03f, 7.64684541476874993227e-03f, 8.02123530413159993580e-03f, 8.39024051932213063565e-03f, 8.75373279902990839019e-03f, 9.11158690761618844656e-03f, 9.46368067291306243327e-03f, 9.80989502251233651264e-03f, 1.01501140185368699670e-02f, 1.04842248908878447194e-02f, 1.08121180689596009528e-02f, 1.11336872118183785596e-02f, 1.14488292368375710328e-02f, 1.17574443467867335855e-02f, 1.20594360553697797084e-02f, 1.23547112112087492664e-02f, 1.26431800202723137322e-02f, 1.29247560667452802280e-02f, 1.31993563323394361153e-02f, 1.34669012140451026943e-02f, 1.37273145403230718842e-02f, 1.39805235857388930609e-02f, 1.42264590840399576116e-02f, 1.44650552396788801418e-02f, 1.46962497377853603536e-02f, 1.49199837525900817770e-02f, 1.51362019543059365262e-02f, 1.53448525144697818512e-02f, 1.55458871097522988158e-02f, 1.57392609242401407266e-02f, 1.59249326501989980909e-02f, 1.61028644873237487822e-02f, 1.62730221404839558996e-02f, 1.64353748159745995105e-02f, 1.65898952162792344411e-02f, 1.67365595333573702330e-02f, 1.68753474404654685292e-02f, 1.70062420825228405308e-02f, 1.71292300650343690127e-02f, 1.72443014415816948948e-02f, 1.73514496998961910423e-02f, 1.74506717465267233158e-02f, 1.75419678901157470585e-02f, 1.76253418232991503067e-02f, 1.77008006032431768062e-02f, 1.77683546308354950449e-02f, 1.78280176285450023266e-02f, 1.78798066169677284665e-02f, 1.79237418900749095885e-02f, 1.79598469891815541721e-02f, 1.79881486756524357207e-02f, 1.80086769023645003329e-02f, 1.80214647839439801036e-02f, 1.80265485657978320744e-02f, 1.80239675919585257136e-02f, 1.80137642717629609113e-02f, 1.79959840453853894826e-02f, 1.79706753482452019632e-02f, 1.79378895743111561878e-02f, 1.78976810383233188306e-02f, 1.78501069369546815080e-02f, 1.77952273089348571300e-02f, 1.77331049941585293384e-02f, 1.76638055918014250101e-02f, 1.75873974174670689996e-02f, 1.75039514593883366311e-02f, 1.74135413337067820883e-02f, 1.73162432388551425222e-02f, 1.72121359090659648006e-02f, 1.71013005670323306462e-02f, 1.69838208757447130248e-02f, 1.68597828895295613616e-02f, 1.67292750043147309125e-02f, 1.65923879071472879509e-02f, 1.64492145249898746862e-02f, 1.62998499728209574056e-02f, 1.61443915010654574782e-02f, 1.59829384423819872985e-02f, 1.58155921578329479449e-02f, 1.56424559824643004402e-02f, 1.54636351703211580993e-02f, 1.52792368389266484952e-02f, 1.50893699132506348831e-02f, 1.48941450691946284529e-02f, 1.46936746766213478105e-02f, 1.44880727419542387757e-02f, 1.42774548503756936596e-02f, 1.40619381076500047506e-02f, 1.38416410815988405458e-02f, 1.36166837432563775367e-02f, 1.33871874077307433104e-02f, 1.31532746747999255282e-02f, 1.29150693692685249875e-02f, 1.26726964811125480254e-02f, 1.24262821054400597609e-02f, 1.21759533822933443264e-02f, 1.19218384363212748234e-02f, 1.16640663163469111840e-02f, 1.14027669348586990772e-02f, 1.11380710074510391738e-02f, 1.08701099922405512027e-02f, 1.05990160292857588803e-02f, 1.03249218800347264402e-02f, 1.00479608668283364181e-02f, 9.76826681248407595326e-03f, 9.48597397998680001707e-03f, 9.20121701231205180171e-03f, 8.91413087240663405686e-03f, 8.62485078335300560382e-03f, 8.33351216874106057175e-03f, 8.04025059327335284154e-03f, 7.74520170362733365033e-03f, 7.44850116959968472363e-03f, 7.15028462555652392224e-03f, 6.85068761221313375642e-03f, 6.54984551876693164157e-03f, 6.24789352540736173808e-03f, 5.94496654622468298501e-03f, 5.64119917254172174859e-03f, 5.33672561668945780872e-03f, 5.03167965625017643561e-03f, 4.72619457878821046942e-03f, 4.42040312709122713147e-03f, 4.11443744494245557813e-03f, 3.80842902344421868274e-03f, 3.50250864791438413365e-03f, 3.19680634537424174582e-03f, 2.89145133264915015631e-03f, 2.58657196509964968506e-03f, 2.28229568600325869593e-03f, 1.97874897660506266980e-03f, 1.67605730685465247574e-03f, 1.37434508684857771554e-03f, 1.07373561899400072825e-03f, 7.74351050912206037222e-04f, 4.76312329096932108620e-04f, 1.79739153344913828647e-04f, -1.15250068026150436743e-04f, -4.08538262157430215240e-04f, -7.00009734810518881830e-04f, -9.89550212697529359140e-04f, -1.27704688496522110984e-03f, -1.56238844381914230262e-03f, -1.84546512427596291067e-03f, -2.12616874302977649017e-03f, -2.40439273642179809562e-03f, -2.68003219750039467159e-03f, -2.95298391216083011210e-03f, -3.22314639435426720723e-03f, -3.49041992035452591087e-03f, -3.75470656207426648626e-03f, -4.01591021941965966441e-03f, -4.27393665167596914500e-03f, -4.52869350791463860101e-03f, -4.78009035641408387002e-03f, -5.02803871308742881402e-03f, -5.27245206890878791856e-03f, -5.51324591633307794364e-03f, -5.75033777470175880286e-03f, -5.98364721463032038506e-03f, -6.21309588137129026331e-03f, -6.43860751714846711591e-03f, -6.66010798245885143193e-03f, -6.87752527633716734257e-03f, -7.09078955558135361203e-03f, -7.29983315293484570641e-03f, -7.50459059422442856246e-03f, -7.70499861445137022159e-03f, -7.90099617283428028169e-03f, -8.09252446680348673513e-03f, -8.27952694494581836748e-03f, -8.46194931890021165288e-03f, -8.63973957420479179992e-03f, -8.81284798009584514900e-03f, -8.98122709826090423468e-03f, -9.14483179054685624276e-03f, -9.30361922562642808254e-03f, -9.45754888462495800494e-03f, -9.60658256571109842037e-03f, -9.75068438765514661215e-03f, -9.88982079235872779677e-03f, -1.00239605463608785763e-02f, -1.01530747413246837108e-02f, -1.02771367935108499936e-02f, -1.03961224422430293518e-02f, -1.05100097473716045521e-02f, -1.06187790857425311958e-02f, -1.07224131466778661165e-02f, -1.08208969264758890494e-02f, -1.09142177219381259629e-02f, -1.10023651229317290939e-02f, -1.10853310039956218930e-02f, -1.11631095149994884197e-02f, -1.12356970708646971419e-02f, -1.13030923403568215463e-02f, -1.13652962339602110059e-02f, -1.14223118908440956359e-02f, -1.14741446649318026840e-02f, -1.15208021100836454503e-02f, -1.15622939644049946284e-02f, -1.15986321336910645080e-02f, -1.16298306740207010868e-02f, -1.16559057735113307669e-02f, -1.16768757332475214827e-02f, -1.16927609473963332182e-02f, -1.17035838825226608945e-02f, -1.17093690561177760784e-02f, -1.17101430143551586693e-02f, -1.17059343090872795129e-02f, -1.16967734740980097013e-02f, -1.16826930006248379257e-02f, -1.16637273121658596037e-02f, -1.16399127385864407935e-02f, -1.16112874895409699111e-02f, -1.15778916272246922003e-02f, -1.15397670384720374415e-02f, -1.14969574062164479888e-02f, -1.14495081803284975280e-02f, -1.13974665478479546959e-02f, -1.13408814026266253211e-02f, -1.12798033143984600957e-02f, -1.12142844972935168402e-02f, -1.11443787778127377519e-02f, -1.10701415622809114236e-02f, -1.09916298037944538957e-02f, -1.09089019686816925125e-02f, -1.08220180024931385970e-02f, -1.07310392955389764802e-02f, -1.06360286479915983754e-02f, -1.05370502345710423397e-02f, -1.04341695688310136247e-02f, -1.03274534670632443106e-02f, -1.02169700118386209270e-02f, -1.01027885152025192345e-02f, -9.98497948154308812008e-03f, -9.86361457015006402871e-03f, -9.73876655748246930488e-03f, -9.61050929916365190286e-03f, -9.47891769172138146105e-03f, -9.34406763409175583623e-03f, -9.20603598890469380922e-03f, -9.06490054356958417647e-03f, -8.92073997117914622990e-03f, -8.77363379124968326139e-03f, -8.62366233031589164704e-03f, -8.47090668239862398803e-03f, -8.31544866936306283078e-03f, -8.15737080118616487978e-03f, -7.99675623615058242533e-03f, -7.83368874098351944402e-03f, -7.66825265095798756787e-03f, -7.50053282997436773782e-03f, -7.33061463064018075525e-03f, -7.15858385436481461928e-03f, -6.98452671148786126409e-03f, -6.80852978145714965441e-03f, -6.63067997307481386826e-03f, -6.45106448482760802543e-03f, -6.26977076531890029770e-03f, -6.08688647381931853542e-03f, -5.90249944095203298716e-03f, -5.71669762953000513278e-03f, -5.52956909556100162373e-03f, -5.34120194943696596085e-03f, -5.15168431732329797079e-03f, -4.96110430276443595266e-03f, -4.76954994852103134756e-03f, -4.57710919865432410564e-03f, -4.38386986087277181340e-03f, -4.18991956915663876782e-03f, -3.99534574667439676410e-03f, -3.80023556900675307108e-03f, -3.60467592769156538676e-03f, -3.40875339410503987864e-03f, -3.21255418369197943973e-03f, -3.01616412055992575564e-03f, -2.81966860245005685598e-03f, -2.62315256609809257030e-03f, -2.42670045299875130826e-03f, -2.23039617558575898118e-03f, -2.03432308384080993632e-03f, -1.83856393234277533909e-03f, -1.64320084776991355742e-03f, -1.44831529686655904529e-03f, -1.25398805488530435195e-03f, -1.06029917451672204415e-03f, -8.67327955316482155854e-04f, -6.75152913641518712638e-04f, -4.83851753104545291573e-04f, -2.93501335557769932588e-04f, -1.04177652615230481180e-04f, 8.40442022771478958144e-05f, 2.71090061213828637746e-04f, 4.56886708636217294885e-04f, 6.41361907564611910364e-04f, 8.24444425246958221068e-04f, 1.00606405821750295726e-03f, 1.18615165675600578790e-03f, 1.36463914874257485378e-03f, 1.54145956289825905236e-03f, 1.71654705140769636706e-03f, 1.88983691191461173828e-03f, 2.06126560888645086675e-03f, 2.23077079434063144103e-03f, 2.39829132792830895110e-03f, 2.56376729636941056573e-03f, 2.72714003223500402184e-03f, 2.88835213207216685155e-03f, 3.04734747386685260462e-03f, 3.20407123384176817371e-03f, 3.35846990258462183704e-03f, 3.51049130050470068257e-03f, 3.66008459261367522647e-03f, 3.80720030262936314294e-03f, 3.95179032639856198800e-03f, 4.09380794463911311387e-03f, 4.23320783499702736619e-03f, 4.36994608342004212803e-03f, 4.50398019484403704799e-03f, 4.63526910319382156461e-03f, 4.76377318069614620610e-03f, 4.88945424650618146178e-03f, 5.01227557464674778470e-03f, 5.13220190126144337750e-03f, 5.24919943118207308480e-03f, 5.36323584381190321402e-03f, 5.47428029832571112767e-03f, 5.58230343818897148389e-03f, 5.68727739499729628703e-03f, 5.78917579163970574818e-03f, 5.88797374478673089110e-03f, 5.98364786670789981782e-03f, 6.07617626642060343345e-03f, 6.16553855017385084303e-03f, 6.25171582127166582804e-03f, 6.33469067923863194541e-03f, 6.41444721833308011821e-03f, 6.49097102541174898749e-03f, 6.56424917715103632687e-03f, 6.63427023662958338657e-03f, 6.70102424927795491810e-03f, 6.76450273820044644529e-03f, 6.82469869887525251023e-03f, 6.88160659323871527759e-03f, 6.93522234316026366108e-03f, 6.98554332331408935064e-03f, 7.03256835345506155222e-03f, 7.07629769010476809138e-03f, 7.11673301765615093362e-03f, 7.15387743890304877992e-03f, 7.18773546500291789924e-03f, 7.21831300488032408247e-03f, 7.24561735407938580650e-03f, 7.26965718307318129604e-03f, 7.29044252503875406940e-03f, 7.30798476310635155423e-03f, 7.32229661709144288850e-03f, 7.33339212971884264747e-03f, 7.34128665234775375920e-03f, 7.34599683020745793799e-03f, 7.34754058715258225737e-03f, 7.34593710994830336597e-03f, 7.34120683209452638829e-03f, 7.33337141719967496728e-03f, 7.32245374191355016119e-03f, 7.30847787843014878861e-03f, 7.29146907657012011139e-03f, 7.27145374545387114529e-03f, 7.24845943477565521351e-03f, 7.22251481568945107037e-03f, 7.19364966131744686118e-03f, 7.16189482689201083881e-03f, 7.12728222954231872138e-03f, 7.08984482773655864257e-03f, 7.04961660039112210374e-03f, 7.00663252565801673161e-03f, 6.96092855940177307472e-03f, 6.91254161337735619636e-03f, 6.86150953312070904788e-03f, 6.80787107556324582597e-03f, 6.75166588638215301593e-03f, 6.69293447709806265528e-03f, 6.63171820193170571955e-03f, 6.56805923443159328512e-03f, 6.50200054388410785683e-03f, 6.43358587151825807998e-03f, 6.36285970651646794888e-03f, 6.28986726184373092646e-03f, 6.21465444990643503531e-03f, 6.13726785805332464285e-03f, 6.05775472392990760317e-03f, 5.97616291069856791357e-03f, 5.89254088213594148099e-03f, 5.80693767761965816410e-03f, 5.71940288701587758180e-03f, 5.62998662548002196115e-03f, 5.53873950818146131014e-03f, 5.44571262496510149348e-03f, 5.35095751496040238082e-03f, 5.25452614115022934027e-03f, 5.15647086491062122543e-03f, 5.05684442053339500839e-03f, 4.95569988974256699088e-03f, 4.85309067621648645985e-03f, 4.74907048012647350216e-03f, 4.64369327270371719946e-03f, 4.53701327084515566163e-03f, 4.42908491176951992635e-03f, 4.31996282773485212186e-03f, 4.20970182082771107734e-03f, 4.09835683783572966160e-03f, 3.98598294521311340144e-03f, 3.87263530415101094040e-03f, 3.75836914576165720403e-03f, 3.64323974638825019007e-03f, 3.52730240304928790995e-03f, 3.41061240902878646739e-03f, 3.29322502962129748730e-03f, 3.17519547804233142826e-03f, 3.05657889151338601694e-03f, 2.93743030753160130203e-03f, 2.81780464033296821486e-03f, 2.69775665755896121301e-03f, 2.57734095713514719389e-03f, 2.45661194437134461702e-03f, 2.33562380929147129019e-03f, 2.21443050420279223534e-03f, 2.09308572151161147862e-03f, 1.97164287179554201940e-03f, 1.85015506213867531038e-03f, 1.72867507473943343883e-03f, 1.60725534579748128607e-03f, 1.48594794468843234732e-03f, 1.36480455343317803527e-03f, 1.24387644646943291808e-03f, 1.12321447073277739387e-03f, 1.00286902605367005473e-03f, 8.82890045877847201225e-04f, 7.63326978315998568199e-04f, 6.44228767529792380013e-04f, 5.25643835459782418976e-04f, 4.07620063901896608968e-04f, 2.90204776937506045247e-04f, 1.73444723723805766706e-04f, 5.73860616484244659592e-05f, -5.79256601447129809831e-05f, -1.72445516855139978872e-04f, -2.86129223744603178401e-04f, -3.98933151471683798521e-04f, -5.10814341036775051638e-04f, -6.21730518333987194034e-04f, -7.31640108305292775383e-04f, -8.40502248693749103720e-04f, -9.48276803391555388537e-04f, -1.05492437538016840815e-03f, -1.16040631925863960139e-03f, -1.26468475335793167046e-03f, -1.36772257143744795961e-03f, -1.46948345396231690001e-03f, -1.56993187895820992227e-03f, -1.66903313244173671925e-03f, -1.76675331842487395514e-03f, -1.86305936849075486246e-03f, -1.95791905094055886799e-03f, -2.05130097950870097026e-03f, -2.14317462164644121497e-03f, -2.23351030637205421117e-03f, -2.32227923168745655630e-03f, -2.40945347156019864382e-03f, -2.49500598247101752769e-03f, -2.57891060952624437755e-03f, -2.66114209213571661222e-03f, -2.74167606925580006200e-03f, -2.82048908419860059130e-03f, -2.89755858900738426376e-03f, -2.97286294839969704451e-03f, -3.04638144327833843006e-03f, -3.11809427381250833106e-03f, -3.18798256208930414976e-03f, -3.25602835433819615824e-03f, -3.32221462272949338845e-03f, -3.38652526674922864716e-03f, -3.44894511415224942069e-03f, -3.50945992149601917673e-03f, -3.56805637425754813494e-03f, -3.62472208653601482564e-03f, -3.67944560034401835918e-03f, -3.73221638449032109761e-03f, -3.78302483305743104600e-03f, -3.83186226347697993233e-03f, -3.87872091420681094562e-03f, -3.92359394201303914723e-03f, -3.96647541886111301701e-03f, -4.00736032841956212047e-03f, -4.04624456218095809173e-03f, -4.08312491520368761599e-03f, -4.11799908147960237043e-03f, -4.15086564893147550587e-03f, -4.18172409404541419592e-03f, -4.21057477614258761356e-03f, -4.23741893129556731340e-03f, -4.26225866589410255086e-03f, -4.28509694986558761776e-03f, -4.30593760955555893838e-03f, -4.32478532027361865092e-03f, -4.34164559851025946141e-03f, -4.35652479383043678834e-03f, -4.36943008044940375129e-03f, -4.38036944849687272935e-03f, -4.38935169497548655082e-03f, -4.39638641441941844384e-03f, -4.40148398925969356471e-03f, -4.40465557990201740657e-03f, -4.40591311452397972614e-03f, -4.40526927859759102890e-03f, -4.40273750414399336200e-03f, -4.39833195872687317957e-03f, -4.39206753419116562726e-03f, -4.38395983515392842489e-03f, -4.37402516725410826781e-03f, -4.36228052516814471251e-03f, -4.34874358039834783829e-03f, -4.33343266884099378305e-03f, -4.31636677814128347924e-03f, -4.29756553484226828249e-03f, -4.27704919133478460302e-03f, -4.25483861261575103258e-03f, -4.23095526286197242544e-03f, -4.20542119182673330285e-03f, -4.17825902106652539991e-03f, -4.14949193000528453179e-03f, -4.11914364184333848390e-03f, -4.08723840931864319803e-03f, -4.05380100032764340012e-03f, -4.01885668341308427420e-03f, -3.98243121312639048598e-03f, -3.94455081527187324114e-03f, -3.90524217204034265055e-03f, -3.86453240703949710971e-03f, -3.82244907022857112119e-03f, -3.77902012276458334344e-03f, -3.73427392176791782957e-03f, -3.68823920501411533363e-03f, -3.64094507555994461798e-03f, -3.59242098631046497328e-03f, -3.54269672453505143905e-03f, -3.49180239633924225512e-03f, -3.43976841109999795926e-03f, -3.38662546587152699790e-03f, -3.33240452976901182711e-03f, -3.27713682833726460339e-03f, -3.22085382791172753977e-03f, -3.16358721997866921757e-03f, -3.10536890554190866259e-03f, -3.04623097950279270868e-03f, -2.98620571506079835605e-03f, -2.92532554814109750294e-03f, -2.86362306185669341502e-03f, -2.80113097101106212072e-03f, -2.73788210664884781517e-03f, -2.67390940066071719841e-03f, -2.60924587044905360173e-03f, -2.54392460366138209102e-03f, -2.47797874299740972931e-03f, -2.41144147109654920225e-03f, -2.34434599551184728525e-03f, -2.27672553377682423265e-03f, -2.20861329857108918892e-03f, -2.14004248299099744321e-03f, -2.07104624593109319652e-03f, -2.00165769758243626206e-03f, -1.93190988505322330110e-03f, -1.86183577811795423693e-03f, -1.79146825509988766485e-03f, -1.72084008889326943795e-03f, -1.64998393312966343087e-03f, -1.57893230849462818527e-03f, -1.50771758919912264932e-03f, -1.43637198961127387532e-03f, -1.36492755105304883201e-03f, -1.29341612876705033125e-03f, -1.22186937905778840625e-03f, -1.15031874661246304344e-03f, -1.07879545200534983015e-03f, -1.00733047939059738862e-03f, -9.35954564387392199190e-04f, -8.64698182161781326270e-04f, -7.93591535709335898878e-04f, -7.22664544342259342231e-04f, -6.51946832385152269008e-04f, -5.81467718082645003419e-04f, -5.11256202723101406751e-04f, -4.41340959980976682391e-04f, -3.71750325482115011495e-04f, -3.02512286594312090893e-04f, -2.33654472446860898751e-04f, -1.65204144181543805632e-04f, -9.71881854382950145007e-05f, -2.96330930778333087529e-05f, 3.74350318557888840534e-05f, 1.03990492930665522531e-04f, 1.70008006877860913661e-04f, 2.35462711895771908792e-04f, 3.00330175705594570618e-04f, 3.64586403356421419591e-04f, 4.28207844777551787079e-04f, 4.91171402077186748311e-04f, 5.53454436585029511952e-04f, 6.15034775638214118408e-04f, 6.75890719108685204992e-04f, 7.36001045671158494674e-04f, 7.95345018810736208159e-04f, 8.53902392568785988461e-04f, 9.11653417026953884206e-04f, 9.68578843528107927274e-04f, 1.02465992963409239201e-03f, 1.07987844381954517861e-03f, 1.13421666990184857697e-03f, 1.18765741120669863183e-03f, 1.24018399446962118877e-03f, 1.29178027347317181048e-03f, 1.34243063242041153760e-03f, 1.39211998904448393775e-03f, 1.44083379745542361494e-03f, 1.48855805072399061464e-03f, 1.53527928320389963832e-03f, 1.58098457259274222282e-03f, 1.62566154173287799080e-03f, 1.66929836015300469068e-03f, 1.71188374535185877309e-03f, 1.75340696382495094506e-03f, 1.79385783183615881223e-03f, 1.83322671593499951850e-03f, 1.87150453322187351919e-03f, 1.90868275136241117781e-03f, 1.94475338835288632730e-03f, 1.97970901203875114180e-03f, 2.01354273938795765367e-03f, 2.04624823552140497340e-03f, 2.07781971250251560127e-03f, 2.10825192788842950831e-03f, 2.13754018304492627786e-03f, 2.16568032122775241954e-03f, 2.19266872543273649843e-03f, 2.21850231601753312277e-03f, 2.24317854809751244041e-03f, 2.26669540871874157159e-03f, 2.28905141381085521987e-03f, 2.31024560492300908704e-03f, 2.33027754574556848419e-03f, 2.34914731842113299123e-03f, 2.36685551964774167771e-03f, 2.38340325657774047483e-03f, 2.39879214251553785076e-03f, 2.41302429241780800814e-03f, 2.42610231819949092105e-03f, 2.43802932384921434983e-03f, 2.44880890035772524199e-03f, 2.45844512046309356806e-03f, 2.46694253321619750424e-03f, 2.47430615837057783779e-03f, 2.48054148060029584083e-03f, 2.48565444354972896537e-03f, 2.48965144371932805070e-03f, 2.49253932419119233338e-03f, 2.49432536819870756192e-03f, 2.49501729254406187306e-03f, 2.49462324086802481049e-03f, 2.49315177677595679884e-03f, 2.49061187682437026880e-03f, 2.48701292337218105022e-03f, 2.48236469730101589823e-03f, 2.47667737060877201499e-03f, 2.46996149888082565729e-03f, 2.46222801364318543901e-03f, 2.45348821460201170497e-03f, 2.44375376177385136151e-03f, 2.43303666751100867299e-03f, 2.42134928842652587602e-03f, 2.40870431722312039469e-03f, 2.39511477443066006163e-03f, 2.38059400005651662299e-03f, 2.36515564515335941637e-03f, 2.34881366330883401689e-03f, 2.33158230206161314751e-03f, 2.31347609424828503169e-03f, 2.29450984928561332182e-03f, 2.27469864439260342423e-03f, 2.25405781575686164908e-03f, 2.23260294964972971082e-03f, 2.21034987349466626252e-03f, 2.18731464689328946802e-03f, 2.16351355261355186160e-03f, 2.13896308754443764677e-03f, 2.11367995362164717843e-03f, 2.08768104872860673846e-03f, 2.06098345757712827012e-03f, 2.03360444257223585765e-03f, 2.00556143466523338278e-03f, 1.97687202419953838434e-03f, 1.94755395175337346625e-03f, 1.91762509898370150235e-03f, 1.88710347947544372464e-03f, 1.85600722960030926380e-03f, 1.82435459938927956557e-03f, 1.79216394342290039413e-03f, 1.75945371174334418141e-03f, 1.72624244079242210488e-03f, 1.69254874437936271916e-03f, 1.65839130468247841071e-03f, 1.62378886328829906069e-03f, 1.58876021227249563002e-03f, 1.55332418532590395277e-03f, 1.51749964892962602506e-03f, 1.48130549358308134279e-03f, 1.44476062508820820261e-03f, 1.40788395589385340219e-03f, 1.37069439650360901800e-03f, 1.33321084695073701146e-03f, 1.29545218834344489506e-03f, 1.25743727448406820806e-03f, 1.21918492356532437090e-03f, 1.18071390994699786103e-03f, 1.14204295601606406994e-03f, 1.10319072413361980722e-03f, 1.06417580867146756643e-03f, 1.02501672814153568618e-03f, 9.85731917420752321024e-04f, 9.46339720074761028661e-04f, 9.06858380782653099826e-04f, 8.67306037865940180828e-04f, 8.27700715924154601863e-04f, 7.88060318579825755218e-04f, 7.48402621335140681366e-04f, 7.08745264542995573667e-04f, 6.69105746494616255432e-04f, 6.29501416626196083105e-04f, 5.89949468846577432593e-04f, 5.50466934988384673337e-04f, 5.11070678384569518186e-04f, 4.71777387572266644969e-04f, 4.32603570126133903009e-04f, 3.93565546622840108093e-04f, 3.54679444738640930936e-04f, 3.15961193481461178213e-04f, 2.77426517559644606348e-04f, 2.39090931888283949464e-04f, 2.00969736235134577339e-04f, 1.63078010007227617185e-04f, 1.25430607179671989044e-04f, 8.80421513676910678318e-05f, 5.09270310433270975443e-05f, 1.40993948977051206995e-05f, -2.24268526499689963992e-05f, -5.86380557959355767418e-05f, -9.45208115468090933828e-05f, -1.30061973765318488264e-04f, -1.65248657065980185041e-04f, -2.00068240560348736649e-04f, -2.34508371450769772170e-04f, -2.68556968472767403858e-04f, -3.02202225185153135114e-04f, -3.35432613107714699702e-04f, -3.68236884706315019053e-04f, -4.00604076224976460123e-04f, -4.32523510365022737217e-04f, -4.63984798811095579957e-04f, -4.94977844604242624843e-04f, -5.25492844361963827582e-04f, -5.55520290345540247376e-04f, -5.85050972374773735341e-04f, -6.14075979590610184985e-04f, -6.42586702065795259929e-04f, -6.70574832264203037069e-04f, -6.98032366349193725621e-04f, -7.24951605341828033552e-04f, -7.51325156129156322866e-04f, -7.77145932323820387393e-04f, -8.02407154975299293648e-04f, -8.27102353133903219450e-04f, -8.51225364268258727699e-04f, -8.74770334537431802288e-04f, -8.97731718918501797773e-04f, -9.20104281190827605862e-04f, -9.41883093778050233848e-04f, -9.63063537449165905750e-04f, -9.83641300879760935652e-04f, -1.00361238007481306557e-03f, -1.02297307765449128208e-03f, -1.04172000200423238610e-03f, -1.05985006629069969386e-03f, -1.07736048734502422482e-03f, -1.09424878441514257174e-03f, -1.11051277778848088776e-03f, -1.12615058728708443934e-03f, -1.14116063063659394818e-03f, -1.15554162171100749217e-03f, -1.16929256865495575027e-03f, -1.18241277188544584124e-03f, -1.19490182197486941203e-03f, -1.20675959741726535404e-03f, -1.21798626227982297217e-03f, -1.22858226374166065384e-03f, -1.23854832952187280184e-03f, -1.24788546519901061629e-03f, -1.25659495142406364872e-03f, -1.26467834102919943727e-03f, -1.27213745603428412742e-03f, -1.27897438455362244883e-03f, -1.28519147760499177768e-03f, -1.29079134582336268501e-03f, -1.29577685608154116216e-03f, -1.30015112802015094483e-03f, -1.30391753048923449490e-03f, -1.30707967790388737521e-03f, -1.30964142651636395419e-03f, -1.31160687060702277167e-03f, -1.31298033859659005196e-03f, -1.31376638908218385492e-03f, -1.31396980679961724973e-03f, -1.31359559851441281067e-03f, -1.31264898884408520024e-03f, -1.31113541601419526274e-03f, -1.30906052755070225271e-03f, -1.30643017591116055581e-03f, -1.30325041405731484843e-03f, -1.29952749097163636725e-03f, -1.29526784712039586857e-03f, -1.29047810986578645812e-03f, -1.28516508882973415524e-03f, -1.27933577121191956356e-03f, -1.27299731706460124078e-03f, -1.26615705452684046764e-03f, -1.25882247502064623448e-03f, -1.25100122841167137044e-03f, -1.24270111813698665680e-03f, -1.23393009630251375940e-03f, -1.22469625875265661526e-03f, -1.21500784011470538623e-03f, -1.20487320882053006339e-03f, -1.19430086210808427544e-03f, -1.18329942100529298017e-03f, -1.17187762529875026674e-03f, -1.16004432848981092231e-03f, -1.14780849274044756403e-03f, -1.13517918381147498753e-03f, -1.12216556599544479457e-03f, -1.10877689704675665670e-03f, -1.09502252311132830878e-03f, -1.08091187365826806380e-03f, -1.06645445641585442931e-03f, -1.05165985231426076241e-03f, -1.03653771043727446592e-03f, -1.02109774298538512859e-03f, -1.00534972025243104707e-03f, -9.89303465618205854493e-04f, -9.72968850559070386023e-04f, -9.56355789678956110487e-04f, -9.39474235762757228438e-04f, -9.22334174854466432199e-04f, -9.04945621361946477522e-04f, -8.87318613190598128385e-04f, -8.69463206907894578697e-04f, -8.51389472940857513876e-04f, -8.33107490808391681729e-04f, -8.14627344390523211622e-04f, -7.95959117236444131487e-04f, -7.77112887913182157074e-04f, -7.58098725396869001399e-04f, -7.38926684508335205812e-04f, -7.19606801394884719458e-04f, -7.00149089059903611230e-04f, -6.80563532942129469604e-04f, -6.60860086546161931871e-04f, -6.41048667125920334890e-04f, -6.21139151422514545521e-04f, -6.01141371458319205383e-04f, -5.81065110388481308873e-04f, -5.60920098411564179222e-04f, -5.40716008740645473485e-04f, -5.20462453636329636791e-04f, -5.00168980502928035153e-04f, -4.79845068049252442000e-04f, -4.59500122515179752151e-04f, -4.39143473965308969373e-04f, -4.18784372650737749064e-04f, -3.98431985440370181519e-04f, -3.78095392322528038760e-04f, -3.57783582978218623466e-04f, -3.37505453426840712917e-04f, -3.17269802745462131330e-04f, -2.97085329862539673351e-04f, -2.76960630426857884294e-04f, -2.56904193752789059696e-04f, -2.36924399842341755311e-04f, -2.17029516485020210204e-04f, -1.97227696436049984184e-04f, -1.77526974673721080374e-04f, -1.57935265736364822877e-04f, -1.38460361139671443579e-04f, -1.19109926874780676581e-04f, -9.98915009877096604120e-05f, -8.08124912404658787314e-05f, -6.18801728543761411219e-05f, -4.31016863358901726119e-05f, -2.44840353852607290751e-05f, -6.03408488822807525549e-06f, 1.22414410087942923482e-05f, 3.03359607401630166279e-05f, 4.82430370786643698930e-05f, 6.59563788094303331657e-05f, 8.34698423199615796228e-05f, 1.00777433106333784039e-04f, 1.17873307195481496850e-04f, 1.34751772483674884346e-04f, 1.51407289991337552218e-04f, 1.67834475034225882872e-04f, 1.84028098311239825778e-04f, 1.99983086909011169297e-04f, 2.15694525223608961004e-04f, 2.31157655799539194473e-04f, 2.46367880086458945071e-04f, 2.61320759113890506518e-04f, 2.76012014084471178285e-04f, 2.90437526885940853344e-04f, 3.04593340522622661477e-04f, 3.18475659466672001308e-04f, 3.32080849929764377465e-04f, 3.45405440055728798956e-04f, 3.58446120034815369788e-04f, 3.71199742140139986053e-04f, 3.83663320687053425832e-04f, 3.95834031916090828447e-04f, 4.07709213800306149479e-04f, 4.19286365777668133727e-04f, 4.30563148409382728376e-04f, 4.41537382964913725693e-04f, 4.52207050934656925797e-04f, 4.62570293470961546242e-04f, 4.72625410758635230730e-04f, 4.82370861315684551149e-04f, 4.91805261225328150740e-04f, 5.00927383300294468485e-04f, 5.09736156180340470138e-04f, 5.18230663364083014855e-04f, 5.26410142176160977465e-04f, 5.34273982670858136115e-04f, 5.41821726473215733132e-04f, 5.49053065558819199977e-04f, 5.55967840973343140065e-04f, 5.62566041493084596781e-04f, 5.68847802227587348751e-04f, 5.74813403165606124842e-04f, 5.80463267665585255090e-04f, 5.85797960891953992807e-04f, 5.90818188198352634011e-04f, 5.95524793459205619046e-04f, 5.99918757350793364382e-04f, 6.04001195583171897330e-04f, 6.07773357084216074402e-04f, 6.11236622137123529822e-04f, 6.14392500472671181100e-04f, 6.17242629317575641247e-04f, 6.19788771400296852575e-04f, 6.22032812915646266330e-04f, 6.23976761449542352556e-04f, 6.25622743865282679096e-04f, 6.26973004152734866845e-04f, 6.28029901241785953013e-04f, 6.28795906781460817864e-04f, 6.29273602886090081127e-04f, 6.29465679849935955126e-04f, 6.29374933831637162034e-04f, 6.29004264509917614644e-04f, 6.28356672711923979618e-04f, 6.27435258015602670238e-04f, 6.26243216327530610385e-04f, 6.24783837437580067548e-04f, 6.23060502551831571320e-04f, 6.21076681805128117189e-04f, 6.18835931754663313292e-04f, 6.16341892855999597296e-04f, 6.13598286922899423257e-04f, 6.10608914572360016171e-04f, 6.07377652656219848941e-04f, 6.03908451680720608987e-04f, 6.00205333215381750360e-04f, 5.96272387292547546150e-04f, 5.92113769798962869678e-04f, 5.87733699860716260477e-04f, 5.83136457222883410056e-04f, 5.78326379625196070923e-04f, 5.73307860175058681823e-04f, 5.68085344719199331932e-04f, 5.62663329215264671791e-04f, 5.57046357104629347515e-04f, 5.51239016687691835844e-04f, 5.45245938502918582949e-04f, 5.39071792710859535452e-04f, 5.32721286484389919269e-04f, 5.26199161406370888652e-04f, 5.19510190875949965511e-04f, 5.12659177524647379116e-04f, 5.05650950643461694588e-04f, 4.98490363622070692395e-04f, 4.91182291401325650700e-04f, 4.83731627940122419548e-04f, 4.76143283697776802978e-04f, 4.68422183132952726740e-04f, 4.60573262220249240485e-04f, 4.52601465985464203410e-04f, 4.44511746060588389465e-04f, 4.36309058259498746754e-04f, 4.27998360175392566791e-04f, 4.19584608800889610671e-04f, 4.11072758171783069880e-04f, 4.02467757035333936928e-04f, 3.93774546544049656484e-04f, 3.84998057975832470683e-04f, 3.76143210481313700240e-04f, 3.67214908859312392543e-04f, 3.58218041361136620669e-04f, 3.49157477524604336044e-04f, 3.40038066038525694287e-04f, 3.30864632638427479810e-04f, 3.21641978034209344382e-04f, 3.12374875870502510550e-04f, 3.03068070720368721877e-04f, 2.93726276113044930307e-04f, 2.84354172596324122988e-04f, 2.74956405834251703824e-04f, 2.65537584740686090270e-04f, 2.56102279649332684359e-04f, 2.46655020520732712157e-04f, 2.37200295186851598233e-04f, 2.27742547633623577692e-04f, 2.18286176322046336163e-04f, 2.08835532548216017655e-04f, 1.99394918842769620351e-04f, 1.89968587410093434423e-04f, 1.80560738607737464008e-04f, 1.71175519466368710516e-04f, 1.61817022250571095152e-04f, 1.52489283060860649410e-04f, 1.43196280477158617963e-04f, 1.33941934244020521144e-04f, 1.24730103997818122337e-04f, 1.15564588036148766046e-04f, 1.06449122129624024050e-04f, 9.73873783762444747338e-05f, 8.83829640984437603628e-05f, 7.94394207830501368397e-05f, 7.05602230641164422306e-05f, 6.17487777488439845697e-05f, 5.30084228865277819307e-05f, 4.43424268807018922721e-05f, 3.57539876443504868529e-05f, 2.72462317982963840894e-05f, 1.88222139126731892130e-05f, 1.04849157914673770899e-05f, 2.23724580001398245952e-06f, -5.91796176458841909860e-06f, -1.39779472603022615363e-05f, -2.19400262443108254274e-05f, -2.98015899747312779436e-05f, -3.75601058699388188874e-05f, -4.52131179258346812058e-05f, -5.27582470911579511194e-05f, -6.01931916011169823111e-05f, -6.75157272696155086192e-05f, -7.47237077402686759994e-05f, -8.18150646965978259242e-05f, -8.87878080315813176041e-05f, -9.56400259770174112402e-05f, -1.02369885192932295447e-04f, -1.08975630817447491838e-04f, -1.15455586477445233109e-04f, -1.21808154260462635915e-04f, -1.28031814648165977134e-04f, -1.34125126411867404445e-04f, -1.40086726470483718130e-04f, -1.45915329711446663599e-04f, -1.51609728774925367391e-04f, -1.57168793801964925671e-04f, -1.62591472146915872604e-04f, -1.67876788054762332352e-04f, -1.73023842303760092562e-04f, -1.78031811814023872061e-04f, -1.82899949222519527144e-04f, -1.87627582425063416539e-04f, -1.92214114085866635134e-04f, -1.96659021115234305434e-04f, -2.00961854115964548605e-04f, -2.05122236799067813760e-04f, -2.09139865369424642018e-04f, -2.13014507881963919287e-04f, -2.16746003569016719827e-04f, -2.20334262139455170628e-04f, -2.23779263050288623272e-04f, -2.27081054751326671731e-04f, -2.30239753903598189230e-04f, -2.33255544572162539776e-04f, -2.36128677394024743099e-04f, -2.38859468721759089912e-04f, -2.41448299743606627134e-04f, -2.43895615580664562507e-04f, -2.46201924361897988517e-04f, -2.48367796277648787261e-04f, -2.50393862612362249915e-04f, -2.52280814757210175570e-04f, -2.54029403203337271477e-04f, -2.55640436516424906718e-04f, -2.57114780293308988878e-04f, -2.58453356101321892260e-04f, -2.59657140401130335366e-04f, -2.60727163453730896234e-04f, -2.61664508212373382850e-04f, -2.62470309200077975623e-04f, -2.63145751373508769710e-04f, -2.63692068973905575490e-04f, -2.64110544365773612825e-04f, -2.64402506864084673854e-04f, -2.64569331550674807438e-04f, -2.64612438080565591254e-04f, -2.64533289478916812908e-04f, -2.64333390929326621406e-04f, -2.64014288554176887285e-04f, -2.63577568187730454374e-04f, -2.63024854142685936189e-04f, -2.62357807970878796362e-04f, -2.61578127218826830911e-04f, -2.60687544178804346540e-04f, -2.59687824636131118095e-04f, -2.58580766613355161020e-04f, -2.57368199111998000473e-04f, -2.56051980852535184582e-04f, -2.54633999013272917245e-04f, -2.53116167968770410229e-04f, -2.51500428028467203686e-04f, -2.49788744176148520739e-04f, -2.47983104810888498049e-04f, -2.46085520490098991209e-04f, -2.44098022675305284133e-04f, -2.42022662481256641692e-04f, -2.39861509428983846149e-04f, -2.37616650203396260988e-04f, -2.35290187416002671578e-04f, -2.32884238373345356290e-04f, -2.30400933851706944807e-04f, -2.27842416878660760553e-04f, -2.25210841522004905167e-04f, -2.22508371686640725982e-04f, -2.19737179919906623887e-04f, -2.16899446225907947382e-04f, -2.13997356889345798732e-04f, -2.11033103309352538255e-04f, -2.08008880843819519632e-04f, -2.04926887664708660573e-04f, -2.01789323624807442155e-04f, -1.98598389136399967663e-04f, -1.95356284062287273917e-04f, -1.92065206619610498147e-04f, -1.88727352296894345393e-04f, -1.85344912784731391314e-04f, -1.81920074920499755819e-04f, -1.78455019647530454055e-04f, -1.74951920989073971512e-04f, -1.71412945037461607323e-04f, -1.67840248958807175192e-04f, -1.64235980013589989322e-04f, -1.60602274593463965847e-04f, -1.56941257274607583660e-04f, -1.53255039887926981963e-04f, -1.49545720606400077326e-04f, -1.45815383049865629223e-04f, -1.42066095407511174204e-04f, -1.38299909578337953649e-04f, -1.34518860329830815736e-04f, -1.30724964475093245915e-04f, -1.26920220068654472992e-04f, -1.23106605621173758290e-04f, -1.19286079333219134950e-04f, -1.15460578348350841244e-04f, -1.11632018025632952099e-04f, -1.07802291231779830637e-04f, -1.03973267653065606659e-04f, -1.00146793127148565233e-04f, -9.63246889949230804435e-05f, -9.25087514725346873358e-05f, -8.87007510436452450671e-05f, -8.49024318720545515026e-05f, -8.11155112347393549661e-05f, -7.73416789754019609908e-05f, -7.35825969785674924512e-05f, -6.98398986642887023612e-05f, -6.61151885034750767074e-05f, -6.24100415538899748772e-05f, -5.87260030168200428450e-05f, -5.50645878143974986741e-05f, -5.14272801876128782695e-05f, -4.78155333149380714058e-05f, -4.42307689515766361233e-05f, -4.06743770892715146635e-05f, -3.71477156366324749162e-05f, -3.36521101199027279356e-05f, -3.01888534041130525139e-05f, -2.67592054345224943665e-05f, -2.33643929982665948157e-05f, -2.00056095060906951114e-05f, -1.66840147940797201324e-05f, -1.34007349452439661363e-05f, -1.01568621308437851693e-05f, -6.95345447128813701427e-06f, -3.79153591650250810366e-06f, -6.72096145553293655593e-07f, 2.40390951459279597616e-06f, 5.43556015137456058426e-06f, 8.42196931017640123499e-06f, 1.13622850193110927676e-05f, 1.42556897966238257746e-05f, 1.71014006379341248229e-05f, 1.98986689875390831126e-05f, 2.26467806909793172563e-05f, 2.53450559303131682314e-05f, 2.79928491421220740245e-05f, 3.05895489185053302676e-05f, 3.31345778912925430443e-05f, 3.56273925997451632237e-05f, 3.80674833419947510364e-05f, 4.04543740105068599521e-05f, 4.27876219118044463113e-05f, 4.50668175707753267513e-05f, 4.72915845198089502860e-05f, 4.94615790730713200283e-05f, 5.15764900862005805371e-05f, 5.36360387017334339240e-05f, 5.56399780805481871900e-05f, 5.75880931196438774090e-05f, 5.94802001565561985590e-05f, 6.13161466607366493955e-05f, 6.30958109121965409621e-05f, 6.48191016677504891876e-05f, 6.64859578151715343739e-05f, 6.80963480156043240521e-05f, 6.96502703345347587470e-05f, 7.11477518616810702089e-05f, 7.25888483201263885920e-05f, 7.39736436650163245096e-05f, 7.53022496721903708853e-05f, 7.65748055170595645635e-05f, 7.77914773440878826866e-05f, 7.89524578272120806021e-05f, 8.00579657215541123884e-05f, 8.11082454067584148567e-05f, 8.21035664223097817662e-05f, 8.30442229951717527346e-05f, 8.39305335600990616227e-05f, 8.47628402729624154692e-05f, 8.55415085174379832356e-05f, 8.62669264054029689666e-05f, 8.69395042713942022743e-05f, 8.75596741614536786501e-05f, 8.81278893167311284636e-05f, 8.86446236521661512422e-05f, 8.91103712306026604075e-05f, 8.95256457326717546962e-05f, 8.98909799227875892661e-05f, 9.02069251115862640782e-05f, 9.04740506151490363976e-05f, 9.06929432113401867103e-05f, 9.08642065935940279782e-05f, 9.09884608224771455898e-05f, 9.10663417753510066833e-05f, 9.10985005944656069544e-05f, 9.10856031337957275460e-05f, 9.10283294049456589850e-05f, 9.09273730224310103125e-05f, 9.07834406486567112131e-05f, 9.05972514388884682737e-05f, 9.03695364865346147717e-05f, 9.01010382690298824038e-05f, 8.97925100946232214449e-05f, 8.94447155503604388049e-05f, 8.90584279515538464710e-05f, 8.86344297930203114553e-05f, 8.81735122023717546660e-05f, 8.76764743956322256668e-05f, 8.71441231354561004216e-05f, 8.65772721922116491981e-05f, 8.59767418081955635050e-05f, 8.53433581652348287718e-05f, 8.46779528559294970011e-05f, 8.39813623587842351167e-05f, 8.32544275174723505482e-05f, 8.24979930244696123580e-05f, 8.17129069092911340059e-05f, 8.09000200315580515319e-05f, 8.00601855791177764851e-05f, 7.91942585714318722121e-05f, 7.83030953684469709260e-05f, 7.73875531851490379041e-05f, 7.64484896120066966278e-05f, 7.54867621414943666810e-05f, 7.45032277008856680736e-05f, 7.34987421914997258445e-05f, 7.24741600345781298914e-05f, 7.14303337239639759421e-05f, 7.03681133857495075701e-05f, 6.92883463450485250238e-05f, 6.81918767000556897513e-05f, 6.70795449035298084342e-05f, 6.59521873518545653477e-05f, 6.48106359818019012913e-05f, 6.36557178751403541801e-05f, 6.24882548712000070136e-05f, 6.13090631875229413088e-05f, 6.01189530487050480105e-05f, 5.89187283235398522627e-05f, 5.77091861705600261078e-05f, 5.64911166920768371556e-05f, 5.52653025968012133018e-05f, 5.40325188711331935503e-05f, 5.27935324591918643858e-05f, 5.15491019516612150597e-05f, 5.02999772835162253098e-05f, 4.90468994406823114189e-05f, 4.77906001756943685883e-05f, 4.65318017323872241855e-05f, 4.52712165796745444508e-05f, 4.40095471544385224527e-05f, 4.27474856135762158195e-05f, 4.14857135952114382585e-05f, 4.02249019891050019768e-05f, 3.89657107162695045101e-05f, 3.77087885178019617646e-05f, 3.64547727529321324196e-05f, 3.52042892062907349744e-05f, 3.39579519043854685862e-05f, 3.27163629412759540162e-05f, 3.14801123134230239524e-05f, 3.02497777637023744978e-05f, 2.90259246345387218443e-05f, 2.78091057301432964695e-05f, 2.65998611878025348972e-05f, 2.53987183581919854364e-05f, 2.42061916946511448772e-05f, 2.30227826513813061706e-05f, 2.18489795905047869758e-05f, 2.06852576979222047276e-05f, 1.95320789079097866306e-05f, 1.83898918363816234168e-05f, 1.72591317227478167385e-05f, 1.61402203802856600485e-05f, 1.50335661549492530784e-05f, 1.39395638925266073790e-05f, 1.28585949140593193966e-05f, 1.17910269994264146578e-05f, 1.07372143790027249535e-05f, 9.69749773328629209753e-06f, 8.67220420039572420566e-06f, 7.66164739132314657512e-06f, 6.66612741284622437347e-06f, 5.68593089796976951548e-06f, 4.72133104379410701050e-06f, 3.77258765668374723169e-06f, 2.83994720461857994069e-06f, 1.92364287659795633360e-06f, 1.02389464897677183691e-06f, 1.40909358598783243636e-07f, -7.25119217400925882488e-07f, -1.57401027526573751796e-06f, -2.40559589188401177682e-06f, -3.21972092580435969379e-06f, -4.01624291213239929447e-06f, -4.79503195145223449727e-06f, -5.55597059291959898474e-06f, -6.29895371166915398527e-06f, -7.02388838068995278969e-06f, -7.73069373730662337714e-06f, -8.41930084443043260571e-06f, -9.08965254672049638874e-06f, -9.74170332181420186798e-06f, -1.03754191267770145960e-05f, -1.09907772399306088372e-05f, -1.15877660982095854786e-05f, -1.21663851302071818753e-05f, -1.27266445850638015879e-05f, -1.32685653573596831328e-05f, -1.37921788081646564831e-05f, -1.42975265824070264185e-05f, -1.47846604227169150120e-05f, -1.52536419799077777845e-05f, -1.57045426202454864190e-05f, -1.61374432296745794440e-05f, -1.65524340151509907705e-05f, -1.69496143032443776877e-05f, -1.73290923361651281922e-05f, -1.76909850653765942752e-05f, -1.80354179429455134585e-05f, -1.83625247107888849940e-05f, -1.86724471879742999741e-05f, -1.89653350562250020523e-05f, -1.92413456437866339777e-05f, -1.95006437078064384194e-05f, -1.97434012153796016272e-05f, -1.99697971234102591856e-05f, -2.01800171574394671614e-05f, -2.03742535895865872536e-05f, -2.05527050157544749519e-05f, -2.07155761322383869926e-05f, -2.08630775118898238230e-05f, -2.09954253799722451970e-05f, -2.11128413898529763743e-05f, -2.12155523986690494219e-05f, -2.13037902431062623279e-05f, -2.13777915154254429865e-05f, -2.14377973398712400380e-05f, -2.14840531495946086989e-05f, -2.15168084642204138245e-05f, -2.15363166681862903517e-05f, -2.15428347899803886579e-05f, -2.15366232824005027408e-05f, -2.15179458039577262574e-05f, -2.14870690015422825217e-05f, -2.14442622944694083432e-05f, -2.13897976600211963056e-05f, -2.13239494205938423077e-05f, -2.12469940325636042692e-05f, -2.11592098769767039874e-05f, -2.10608770521689021838e-05f, -2.09522771684173563140e-05f, -2.08336931447246940868e-05f, -2.07054090078323760555e-05f, -2.05677096935581607638e-05f, -2.04208808505498906150e-05f, -2.02652086465450858026e-05f, -2.01009795772225878085e-05f, -1.99284802777307897448e-05f, -1.97479973369733553508e-05f, -1.95598171147313057406e-05f, -1.93642255616965921713e-05f, -1.91615080424908975211e-05f, -1.89519491617392180451e-05f, -1.87358325932659067312e-05f, -1.85134409124775290410e-05f, -1.82850554319945745078e-05f, -1.80509560405904254164e-05f, -1.78114210454942761765e-05f, -1.75667270181107023892e-05f, -1.73171486432067083623e-05f, -1.70629585716134836285e-05f, -1.68044272764881711800e-05f, -1.65418229131769521309e-05f, -1.62754111827191523261e-05f, -1.60054551990284544996e-05f, -1.57322153597849753270e-05f, -1.54559492210684566130e-05f, -1.51769113757617390406e-05f, -1.48953533357483810943e-05f, -1.46115234179280960335e-05f, -1.43256666340692667526e-05f, -1.40380245845161517285e-05f, -1.37488353557644599467e-05f, -1.34583334219182035850e-05f, -1.31667495500361276861e-05f, -1.28743107093750131593e-05f, -1.25812399845332226092e-05f, -1.22877564924967183814e-05f, -1.19940753035859587103e-05f, -1.17004073663007361519e-05f, -1.14069594360560160192e-05f, -1.11139340078022052344e-05f, -1.08215292525168970740e-05f, -1.05299389575568482923e-05f, -1.02393524708539662493e-05f, -9.94995464893742348120e-06f, -9.66192580876271611343e-06f, -9.37544168332491989210e-06f, -9.09067338103246418157e-06f, -8.80778734881422739372e-06f, -8.52694533893248795902e-06f, -8.24830437947059051977e-06f, -7.97201674846333152320e-06f, -7.69822995163491404110e-06f, -7.42708670370925303302e-06f, -7.15872491325360909230e-06f, -6.89327767101633304933e-06f, -6.63087324171573705124e-06f, -6.37163505923899323223e-06f, -6.11568172520321447157e-06f, -5.86312701083429459631e-06f, -5.61407986211404469277e-06f, -5.36864440814655013182e-06f, -5.12691997269171846217e-06f, -4.88900108881444233564e-06f, -4.65497751659457443244e-06f, -4.42493426384305196255e-06f, -4.19895160976678087265e-06f, -3.97710513152554464493e-06f, -3.75946573362158312445e-06f, -3.54609968006129461300e-06f, -3.33706862922910230812e-06f, -3.13242967141053148128e-06f, -2.93223536890220278037e-06f, -2.73653379864365903313e-06f, -2.54536859730841838172e-06f, -2.35877900878616331091e-06f, -2.17679993399191934131e-06f, -1.99946198293410626841e-06f, -1.82679152897453787459e-06f, -1.65881076521146377169e-06f, -1.49553776291791911430e-06f, -1.33698653196547020166e-06f, -1.18316708316432299485e-06f, -1.03408549244911473199e-06f, -8.89743966841013678409e-07f, -7.50140912114827378233e-07f, -6.15271002100918799020e-07f, -4.85125249549975873449e-07f, -3.59691078491283933177e-07f, -2.38952398011216803052e-07f, -1.22889677382464548894e-07f, 0 // Need a final zero coefficient openMSX-RELEASE_0_12_0/src/sound/ResampleHQ.cc000066400000000000000000000317341257557151200206760ustar00rootroot00000000000000// Based on libsamplerate-0.1.2 (aka Secret Rabit Code) // // simplified code in several ways: // - resample algorithm is no longer switchable, we took this variant: // Band limited sinc interpolation, fastest, 97dB SNR, 80% BW // - don't allow to change sample rate on-the-fly // - assume input (and thus also output) signals have infinte length, so // there is no special code to handle the ending of the signal // - changed/simplified API to better match openmsx use model // (e.g. remove all error checking) #include "ResampleHQ.hh" #include "ResampledSoundDevice.hh" #include "FixedPoint.hh" #include "MemBuffer.hh" #include "countof.hh" #include "likely.hh" #include "noncopyable.hh" #include "stl.hh" #include "vla.hh" #include "build-info.hh" #include #include #include #include #include #ifdef __SSE2__ #include #endif namespace openmsx { // Note: without appending 'f' to the values in ResampleCoeffs.ii, // this will generate thousands of C4305 warnings in VC++ // E.g. warning C4305: 'initializing' : truncation from 'double' to 'const float' static const float coeffs[] = { #include "ResampleCoeffs.ii" }; static const int INDEX_INC = 128; static const int COEFF_LEN = countof(coeffs); static const int COEFF_HALF_LEN = COEFF_LEN - 1; static const unsigned TAB_LEN = 4096; class ResampleCoeffs : private noncopyable { public: static ResampleCoeffs& instance(); void getCoeffs(double ratio, float*& table, unsigned& filterLen); void releaseCoeffs(double ratio); private: using FilterIndex = FixedPoint<16>; using Table = MemBuffer; ResampleCoeffs(); ~ResampleCoeffs(); double getCoeff(FilterIndex index); Table calcTable(double ratio, unsigned& filterLen); struct Element { double ratio; Table table; unsigned filterLen; unsigned count; // workaround for vs013: normally these are auto-generated Element(double ratio_, Table&& table_, unsigned filterLen_, unsigned count_) : ratio(ratio_), table(std::move(table_)) , filterLen(filterLen_), count(count_) {} Element(Element&& e) : ratio (std::move(e.ratio)) , table (std::move(e.table)) , filterLen(std::move(e.filterLen)) , count (std::move(e.count)) {} Element& operator=(Element&& e) { ratio = std::move(e.ratio); table = std::move(e.table); filterLen = std::move(e.filterLen); count = std::move(e.count); return *this; } }; std::vector cache; // typically 1-4 entries -> unsorted vector }; ResampleCoeffs::ResampleCoeffs() { } ResampleCoeffs::~ResampleCoeffs() { assert(cache.empty()); } ResampleCoeffs& ResampleCoeffs::instance() { static ResampleCoeffs resampleCoeffs; return resampleCoeffs; } void ResampleCoeffs::getCoeffs( double ratio, float*& table, unsigned& filterLen) { auto it = find_if(begin(cache), end(cache), [=](const Element& e) { return e.ratio == ratio; }); if (it != end(cache)) { table = it->table.data(); filterLen = it->filterLen; it->count++; return; } Table tab = calcTable(ratio, filterLen); table = tab.data(); cache.push_back({ratio, std::move(tab), filterLen, 1}); } void ResampleCoeffs::releaseCoeffs(double ratio) { auto it = find_if_unguarded(cache, [=](const Element& e) { return e.ratio == ratio; }); it->count--; if (it->count == 0) { if (it != (end(cache) - 1)) { *it = std::move(cache.back()); // move last element here } cache.pop_back(); // and erase last } } double ResampleCoeffs::getCoeff(FilterIndex index) { double fraction = index.fractionAsDouble(); int indx = index.toInt(); return double(coeffs[indx]) + fraction * (double(coeffs[indx + 1]) - double(coeffs[indx])); } ResampleCoeffs::Table ResampleCoeffs::calcTable(double ratio, unsigned& filterLen) { double floatIncr = (ratio > 1.0) ? INDEX_INC / ratio : INDEX_INC; double normFactor = floatIncr / INDEX_INC; FilterIndex increment = FilterIndex(floatIncr); FilterIndex maxFilterIndex(COEFF_HALF_LEN); int min_idx = -maxFilterIndex.divAsInt(increment); int max_idx = 1 + (maxFilterIndex - (increment - FilterIndex(floatIncr))).divAsInt(increment); int idx_cnt = max_idx - min_idx + 1; filterLen = (idx_cnt + 3) & ~3; // round up to multiple of 4 min_idx -= (filterLen - idx_cnt); Table table(TAB_LEN * filterLen); memset(table.data(), 0, TAB_LEN * filterLen * sizeof(float)); for (unsigned t = 0; t < TAB_LEN; ++t) { double lastPos = double(t) / TAB_LEN; FilterIndex startFilterIndex(lastPos * floatIncr); FilterIndex filterIndex(startFilterIndex); int coeffCount = (maxFilterIndex - filterIndex).divAsInt(increment); filterIndex += increment * coeffCount; int bufIndex = -coeffCount; do { table[t * filterLen + bufIndex - min_idx] = float(getCoeff(filterIndex) * normFactor); filterIndex -= increment; bufIndex += 1; } while (filterIndex >= FilterIndex(0)); filterIndex = increment - startFilterIndex; coeffCount = (maxFilterIndex - filterIndex).divAsInt(increment); filterIndex += increment * coeffCount; bufIndex = 1 + coeffCount; do { table[t * filterLen + bufIndex - min_idx] = float(getCoeff(filterIndex) * normFactor); filterIndex -= increment; bufIndex -= 1; } while (filterIndex > FilterIndex(0)); } return table; } template ResampleHQ::ResampleHQ( ResampledSoundDevice& input_, const DynamicClock& hostClock_, unsigned emuSampleRate) : input(input_) , hostClock(hostClock_) , emuClock(hostClock.getTime(), emuSampleRate) , ratio(float(emuSampleRate) / hostClock.getFreq()) { ResampleCoeffs::instance().getCoeffs(ratio, table, filterLen); // fill buffer with 'enough' zero's unsigned extra = int(filterLen + 1 + ratio + 1); bufStart = 0; bufEnd = extra; nonzeroSamples = 0; unsigned initialSize = 4000; // buffer grows dynamically if this is too small buffer.resize((initialSize + extra) * CHANNELS); // zero-initialized } template ResampleHQ::~ResampleHQ() { ResampleCoeffs::instance().releaseCoeffs(ratio); } #ifdef __SSE2__ static inline void calcSseMono(const float* buf_, const float* tab_, long len, int* out) { assert((len % 4) == 0); assert((uintptr_t(tab_) % 16) == 0); long x = (len & ~7) * sizeof(float); assert((x % 32) == 0); const char* buf = reinterpret_cast(buf_) + x; const char* tab = reinterpret_cast(tab_) + x; x = -x; __m128 a0 = _mm_setzero_ps(); __m128 a1 = _mm_setzero_ps(); do { __m128 b0 = _mm_loadu_ps(reinterpret_cast(buf + x + 0)); __m128 b1 = _mm_loadu_ps(reinterpret_cast(buf + x + 16)); __m128 t0 = _mm_load_ps (reinterpret_cast(tab + x + 0)); __m128 t1 = _mm_load_ps (reinterpret_cast(tab + x + 16)); __m128 m0 = _mm_mul_ps(b0, t0); __m128 m1 = _mm_mul_ps(b1, t1); a0 = _mm_add_ps(a0, m0); a1 = _mm_add_ps(a1, m1); x += 2 * sizeof(__m128); } while (x < 0); if (len & 4) { __m128 b0 = _mm_loadu_ps(reinterpret_cast(buf)); __m128 t0 = _mm_load_ps (reinterpret_cast(tab)); __m128 m0 = _mm_mul_ps(b0, t0); a0 = _mm_add_ps(a0, m0); } __m128 a = _mm_add_ps(a0, a1); // The following can be _slighly_ faster by using the SSE3 _mm_hadd_ps() // intrinsic, but not worth the trouble. __m128 t = _mm_add_ps(a, _mm_movehl_ps(a, a)); __m128 s = _mm_add_ss(t, _mm_shuffle_ps(t, t, 1)); *out = _mm_cvtss_si32(s); } template static inline __m128 shuffle(__m128 x) { return _mm_castsi128_ps(_mm_shuffle_epi32(_mm_castps_si128(x), N)); } static inline void calcSseStereo(const float* buf_, const float* tab_, long len, int* out) { assert((len % 4) == 0); assert((uintptr_t(tab_) % 16) == 0); long x = (len & ~7) * sizeof(float); const char* buf = reinterpret_cast(buf_) + 2*x; const char* tab = reinterpret_cast(tab_) + x; x = -x; __m128 a0 = _mm_setzero_ps(); __m128 a1 = _mm_setzero_ps(); __m128 a2 = _mm_setzero_ps(); __m128 a3 = _mm_setzero_ps(); do { __m128 b0 = _mm_loadu_ps(reinterpret_cast(buf + 2*x + 0)); __m128 b1 = _mm_loadu_ps(reinterpret_cast(buf + 2*x + 16)); __m128 b2 = _mm_loadu_ps(reinterpret_cast(buf + 2*x + 32)); __m128 b3 = _mm_loadu_ps(reinterpret_cast(buf + 2*x + 48)); __m128 ta = _mm_load_ps (reinterpret_cast(tab + x + 0)); __m128 tb = _mm_load_ps (reinterpret_cast(tab + x + 16)); __m128 t0 = shuffle<0x50>(ta); __m128 t1 = shuffle<0xFA>(ta); __m128 t2 = shuffle<0x50>(tb); __m128 t3 = shuffle<0xFA>(tb); __m128 m0 = _mm_mul_ps(b0, t0); __m128 m1 = _mm_mul_ps(b1, t1); __m128 m2 = _mm_mul_ps(b2, t2); __m128 m3 = _mm_mul_ps(b3, t3); a0 = _mm_add_ps(a0, m0); a1 = _mm_add_ps(a1, m1); a2 = _mm_add_ps(a2, m2); a3 = _mm_add_ps(a3, m3); x += 2 * sizeof(__m128); } while (x < 0); if (len & 4) { __m128 b0 = _mm_loadu_ps(reinterpret_cast(buf + 0)); __m128 b1 = _mm_loadu_ps(reinterpret_cast(buf + 16)); __m128 ta = _mm_load_ps (reinterpret_cast(tab)); __m128 t0 = shuffle<0x50>(ta); __m128 t1 = shuffle<0xFA>(ta); __m128 m0 = _mm_mul_ps(b0, t0); __m128 m1 = _mm_mul_ps(b1, t1); a0 = _mm_add_ps(a0, m0); a1 = _mm_add_ps(a1, m1); } __m128 a01 = _mm_add_ps(a0, a1); __m128 a23 = _mm_add_ps(a2, a3); __m128 a = _mm_add_ps(a01, a23); // Can faster with SSE3, but (like above) not worth the trouble. __m128 s = _mm_add_ps(a, _mm_movehl_ps(a, a)); __m128i si = _mm_cvtps_epi32(s); #if ASM_X86_64 *reinterpret_cast(out) = _mm_cvtsi128_si64(si); #else out[0] = _mm_cvtsi128_si32(si); out[1] = _mm_cvtsi128_si32(_mm_shuffle_epi32(si, 0x55)); #endif } #endif template void ResampleHQ::calcOutput( float pos, int* __restrict output) { assert((filterLen & 3) == 0); int t = int(pos * TAB_LEN + 0.5f) % TAB_LEN; const float* tab = &table[t * filterLen]; int bufIdx = int(pos) + bufStart; assert((bufIdx + filterLen) <= bufEnd); bufIdx *= CHANNELS; const float* buf = &buffer[bufIdx]; #ifdef __SSE2__ if (CHANNELS == 1) { calcSseMono (buf, tab, filterLen, output); } else { calcSseStereo(buf, tab, filterLen, output); } return; #endif // c++ version, both mono and stereo for (unsigned ch = 0; ch < CHANNELS; ++ch) { float r0 = 0.0f; float r1 = 0.0f; float r2 = 0.0f; float r3 = 0.0f; for (unsigned i = 0; i < filterLen; i += 4) { r0 += tab[i + 0] * buf[CHANNELS * (i + 0)]; r1 += tab[i + 1] * buf[CHANNELS * (i + 1)]; r2 += tab[i + 2] * buf[CHANNELS * (i + 2)]; r3 += tab[i + 3] * buf[CHANNELS * (i + 3)]; } output[ch] = lrint(r0 + r1 + r2 + r3); ++buf; } } template void ResampleHQ::prepareData(unsigned emuNum) { // Still enough free space at end of buffer? unsigned free = unsigned(buffer.size() / CHANNELS) - bufEnd; if (free < emuNum) { // No, then move everything to the start // (data needs to be in a contiguous memory block) unsigned available = bufEnd - bufStart; memmove(&buffer[0], &buffer[bufStart * CHANNELS], available * CHANNELS * sizeof(float)); bufStart = 0; bufEnd = available; free = unsigned(buffer.size() / CHANNELS) - bufEnd; int missing = emuNum - free; if (unlikely(missing > 0)) { // Still not enough room: grow the buffer. // TODO an alternative is to instead of using a large // buffer, chop the work in multiple smaller pieces. // That may have the advantage that the data fits in // the CPU's data cache. OTOH too small chunks have // more overhead. (Not yet implemented because it's // more complex). buffer.resize(buffer.size() + missing * CHANNELS); } } VLA_SSE_ALIGNED(int, tmpBuf, emuNum * CHANNELS + 3); if (input.generateInput(tmpBuf, emuNum)) { for (unsigned i = 0; i < emuNum * CHANNELS; ++i) { buffer[bufEnd * CHANNELS + i] = float(tmpBuf[i]); } bufEnd += emuNum; nonzeroSamples = bufEnd - bufStart; } else { memset(&buffer[bufEnd * CHANNELS], 0, emuNum * CHANNELS * sizeof(float)); bufEnd += emuNum; } assert(bufStart <= bufEnd); assert(bufEnd <= (buffer.size() / CHANNELS)); } template bool ResampleHQ::generateOutput( int* __restrict dataOut, unsigned hostNum, EmuTime::param time) { unsigned emuNum = emuClock.getTicksTill(time); if (emuNum > 0) { prepareData(emuNum); } bool notMuted = nonzeroSamples > 0; if (notMuted) { // main processing loop EmuTime host1 = hostClock.getFastAdd(1); assert(host1 > emuClock.getTime()); float pos = emuClock.getTicksTillDouble(host1); assert(pos <= (ratio + 2)); for (unsigned i = 0; i < hostNum; ++i) { calcOutput(pos, &dataOut[i * CHANNELS]); pos += ratio; } } emuClock += emuNum; bufStart += emuNum; nonzeroSamples = std::max(0, nonzeroSamples - emuNum); assert(bufStart <= bufEnd); unsigned available = bufEnd - bufStart; unsigned extra = int(filterLen + 1 + ratio + 1); assert(available == extra); (void)available; (void)extra; return notMuted; } // Force template instantiation. template class ResampleHQ<1>; template class ResampleHQ<2>; } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/ResampleHQ.hh000066400000000000000000000015061257557151200207020ustar00rootroot00000000000000#ifndef RESAMPLEHQ_HH #define RESAMPLEHQ_HH #include "ResampleAlgo.hh" #include "DynamicClock.hh" #include namespace openmsx { class ResampledSoundDevice; template class ResampleHQ final : public ResampleAlgo { public: ResampleHQ(ResampledSoundDevice& input, const DynamicClock& hostClock, unsigned emuSampleRate); ~ResampleHQ(); bool generateOutput(int* dataOut, unsigned num, EmuTime::param time) override; private: void calcOutput(float pos, int* output); void prepareData(unsigned emuNum); ResampledSoundDevice& input; const DynamicClock& hostClock; DynamicClock emuClock; const float ratio; unsigned bufStart; unsigned bufEnd; unsigned nonzeroSamples; unsigned filterLen; std::vector buffer; float* table; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/ResampleLQ.cc000066400000000000000000000167311257557151200207020ustar00rootroot00000000000000#include "ResampleLQ.hh" #include "ResampledSoundDevice.hh" #include "likely.hh" #include "memory.hh" #include #include #include namespace openmsx { // 16-byte aligned buffer of ints (shared among all instances of this resampler) static std::vector bufferStorage; // (possibly) unaligned storage static unsigned bufferSize = 0; // usable buffer size (aligned portion) static int* bufferInt = nullptr; // pointer to aligned sub-buffer //// template std::unique_ptr> ResampleLQ::create( ResampledSoundDevice& input, const DynamicClock& hostClock, unsigned emuSampleRate) { std::unique_ptr> result; unsigned hostSampleRate = hostClock.getFreq(); if (emuSampleRate < hostSampleRate) { result = make_unique>( input, hostClock, emuSampleRate); } else { result = make_unique>( input, hostClock, emuSampleRate); } return result; } template ResampleLQ::ResampleLQ( ResampledSoundDevice& input_, const DynamicClock& hostClock_, unsigned emuSampleRate) : input(input_) , hostClock(hostClock_) , emuClock(hostClock.getTime(), emuSampleRate) , step(FP::roundRatioDown(emuSampleRate, hostClock.getFreq())) { for (auto& l : lastInput) l = 0; } template bool ResampleLQ::fetchData(EmuTime::param time, unsigned& valid) { unsigned emuNum = emuClock.getTicksTill(time); valid = 2 + emuNum; unsigned required = emuNum + 4; if (unlikely(required > bufferSize)) { // grow buffer (3 extra to be able to align) bufferStorage.resize(required + 3); // align at 16-byte boundary auto p = reinterpret_cast(bufferStorage.data()); bufferInt = reinterpret_cast((p + 15) & ~15); // calculate actual usable size (the aligned portion) bufferSize = (bufferStorage.data() + bufferStorage.size()) - bufferInt; assert(bufferSize >= required); } emuClock += emuNum; assert(emuClock.getTime() <= time); assert(emuClock.getFastAdd(1) > time); int* buffer = &bufferInt[4 - 2 * CHANNELS]; assert((uintptr_t(&buffer[2 * CHANNELS]) & 15) == 0); if (!input.generateInput(&buffer[2 * CHANNELS], emuNum)) { // New input is all zero int last = 0; for (auto& l : lastInput) last |= l; if (last == 0) { // Old input was also all zero, then the resampled // output will be all zero as well. return false; } memset(&buffer[CHANNELS], 0, emuNum * CHANNELS * sizeof(int)); } for (unsigned j = 0; j < 2 * CHANNELS; ++j) { buffer[j] = lastInput[j]; lastInput[j] = buffer[emuNum * CHANNELS + j]; } return true; } //// template ResampleLQUp::ResampleLQUp( ResampledSoundDevice& input, const DynamicClock& hostClock, unsigned emuSampleRate) : ResampleLQ(input, hostClock, emuSampleRate) { assert(emuSampleRate < hostClock.getFreq()); // only upsampling } template bool ResampleLQUp::generateOutput( int* __restrict dataOut, unsigned hostNum, EmuTime::param time) { EmuTime host1 = this->hostClock.getFastAdd(1); assert(host1 > this->emuClock.getTime()); FP pos; this->emuClock.getTicksTill(host1, pos); assert(pos.toInt() < 2); unsigned valid; // only indices smaller than this number are valid if (!this->fetchData(time, valid)) return false; // this is currently only used to upsample cassette player sound, // sound quality is not so important here, so use 0-th order // interpolation (instead of 1st-order). int* buffer = &bufferInt[4 - 2 * CHANNELS]; for (unsigned i = 0; i < hostNum; ++i) { unsigned p = pos.toInt(); assert(p < valid); for (unsigned j = 0; j < CHANNELS; ++j) { dataOut[i * CHANNELS + j] = buffer[p * CHANNELS + j]; } pos += this->step; } return true; } //// template ResampleLQDown::ResampleLQDown( ResampledSoundDevice& input, const DynamicClock& hostClock, unsigned emuSampleRate) : ResampleLQ(input, hostClock, emuSampleRate) { assert(emuSampleRate > hostClock.getFreq()); // can only do downsampling } template bool ResampleLQDown::generateOutput( int* __restrict dataOut, unsigned hostNum, EmuTime::param time) { EmuTime host1 = this->hostClock.getFastAdd(1); assert(host1 > this->emuClock.getTime()); FP pos; this->emuClock.getTicksTill(host1, pos); unsigned valid; if (!this->fetchData(time, valid)) return false; int* buffer = &bufferInt[4 - 2 * CHANNELS]; #ifdef __arm__ if (CHANNELS == 1) { unsigned dummy; // This asm code is equivalent to the c++ code below (does // 1st order interpolation). It's still a bit slow, so we // use 0th order interpolation. Sound quality is still good // especially on portable devices with only medium quality // speakers. /*asm volatile ( "0:\n\t" "mov r7,%[p],LSR #14\n\t" "add r7,%[buf],r7,LSL #2\n\t" "ldmia r7,{r7,r8}\n\t" "sub r8,r8,r7\n\t" "and %[t],%[p],%[m]\n\t" "mul %[t],r8,%[t]\n\t" "add %[t],r7,%[t],ASR #14\n\t" "str %[t],[%[out]],#4\n\t" "add %[p],%[p],%[s]\n\t" "mov r7,%[p],LSR #14\n\t" "add r7,%[buf],r7,LSL #2\n\t" "ldmia r7,{r7,r8}\n\t" "sub r8,r8,r7\n\t" "and %[t],%[p],%[m]\n\t" "mul %[t],r8,%[t]\n\t" "add %[t],r7,%[t],ASR #14\n\t" "str %[t],[%[out]],#4\n\t" "add %[p],%[p],%[s]\n\t" "mov r7,%[p],LSR #14\n\t" "add r7,%[buf],r7,LSL #2\n\t" "ldmia r7,{r7,r8}\n\t" "sub r8,r8,r7\n\t" "and %[t],%[p],%[m]\n\t" "mul %[t],r8,%[t]\n\t" "add %[t],r7,%[t],ASR #14\n\t" "str %[t],[%[out]],#4\n\t" "add %[p],%[p],%[s]\n\t" "mov r7,%[p],LSR #14\n\t" "add r7,%[buf],r7,LSL #2\n\t" "ldmia r7,{r7,r8}\n\t" "sub r8,r8,r7\n\t" "and %[t],%[p],%[m]\n\t" "mul %[t],r8,%[t]\n\t" "add %[t],r7,%[t],ASR #14\n\t" "str %[t],[%[out]],#4\n\t" "add %[p],%[p],%[s]\n\t" "subs %[n],%[n],#4\n\t" "bgt 0b\n\t" : [p] "=r" (pos) , [t] "=&r" (dummy) : "[p]" (pos) , [buf] "r" (buffer) , [out] "r" (dataOut) , [s] "r" (step) , [n] "r" (hostNum) , [m] "r" (0x3FFF) // mask 14 bits : "r7","r8" );*/ // 0th order interpolation asm volatile ( "0:\n\t" "lsrs %[t],%[p],#14\n\t" "ldr %[t],[%[buf],%[t],LSL #2]\n\t" "str %[t],[%[out]],#4\n\t" "add %[p],%[p],%[s]\n\t" "lsrs %[t],%[p],#14\n\t" "ldr %[t],[%[buf],%[t],LSL #2]\n\t" "str %[t],[%[out]],#4\n\t" "add %[p],%[p],%[s]\n\t" "lsrs %[t],%[p],#14\n\t" "ldr %[t],[%[buf],%[t],LSL #2]\n\t" "str %[t],[%[out]],#4\n\t" "add %[p],%[p],%[s]\n\t" "lsrs %[t],%[p],#14\n\t" "ldr %[t],[%[buf],%[t],LSL #2]\n\t" "str %[t],[%[out]],#4\n\t" "add %[p],%[p],%[s]\n\t" "subs %[n],%[n],#4\n\t" "bgt 0b\n\t" : [p] "=r" (pos) , [out] "=r" (dataOut) , [n] "=r" (hostNum) , [t] "=&r" (dummy) : "[p]" (pos) , "[out]" (dataOut) , "[n]" (hostNum) , [buf] "r" (buffer) , [s] "r" (this->step) : "memory" ); } else { #endif for (unsigned i = 0; i < hostNum; ++i) { unsigned p = pos.toInt(); assert((p + 1) < valid); FP fract = pos.fract(); for (unsigned j = 0; j < CHANNELS; ++j) { int s0 = buffer[(p + 0) * CHANNELS + j]; int s1 = buffer[(p + 1) * CHANNELS + j]; int out = s0 + (fract * (s1 - s0)).toInt(); dataOut[i * CHANNELS + j] = out; } pos += this->step; } #ifdef __arm__ } #endif return true; } // Force template instantiation. template class ResampleLQ<1>; template class ResampleLQ<2>; } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/ResampleLQ.hh000066400000000000000000000027701257557151200207120ustar00rootroot00000000000000#ifndef RESAMPLELQ_HH #define RESAMPLELQ_HH #include "ResampleAlgo.hh" #include "DynamicClock.hh" #include "FixedPoint.hh" #include namespace openmsx { class ResampledSoundDevice; template class ResampleLQ : public ResampleAlgo { public: static std::unique_ptr> create( ResampledSoundDevice& input, const DynamicClock& hostClock, unsigned emuSampleRate); protected: ResampleLQ(ResampledSoundDevice& input, const DynamicClock& hostClock, unsigned emuSampleRate); bool fetchData(EmuTime::param time, unsigned& valid); ResampledSoundDevice& input; const DynamicClock& hostClock; DynamicClock emuClock; using FP = FixedPoint<14>; const FP step; int lastInput[2 * CHANNELS]; }; template class ResampleLQDown final : public ResampleLQ { public: ResampleLQDown(ResampledSoundDevice& input, const DynamicClock& hostClock, unsigned emuSampleRate); private: bool generateOutput(int* dataOut, unsigned num, EmuTime::param time) override; using FP = typename ResampleLQ::FP; }; template class ResampleLQUp final : public ResampleLQ { public: ResampleLQUp(ResampledSoundDevice& input, const DynamicClock& hostClock, unsigned emuSampleRate); private: bool generateOutput(int* dataOut, unsigned num, EmuTime::param time) override; using FP = typename ResampleLQ::FP; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/ResampleTrivial.cc000066400000000000000000000007401257557151200217710ustar00rootroot00000000000000#include "ResampleTrivial.hh" #include "ResampledSoundDevice.hh" #include namespace openmsx { ResampleTrivial::ResampleTrivial(ResampledSoundDevice& input_) : input(input_) { } bool ResampleTrivial::generateOutput(int* dataOut, unsigned num, EmuTime::param /*time*/) { #ifdef __SSE2__ assert((uintptr_t(dataOut) & 15) == 0); // must be 16-byte aligned #endif return input.generateInput(dataOut, num); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/ResampleTrivial.hh000066400000000000000000000006401257557151200220020ustar00rootroot00000000000000#ifndef RESAMPLETRIVIAL_HH #define RESAMPLETRIVIAL_HH #include "ResampleAlgo.hh" namespace openmsx { class ResampledSoundDevice; class ResampleTrivial final : public ResampleAlgo { public: ResampleTrivial(ResampledSoundDevice& input); bool generateOutput(int* dataOut, unsigned num, EmuTime::param time) override; private: ResampledSoundDevice& input; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/ResampledSoundDevice.cc000066400000000000000000000044451257557151200227410ustar00rootroot00000000000000#include "ResampledSoundDevice.hh" #include "ResampleTrivial.hh" #include "ResampleHQ.hh" #include "ResampleLQ.hh" #include "ResampleBlip.hh" #include "MSXMotherBoard.hh" #include "Reactor.hh" #include "GlobalSettings.hh" #include "EnumSetting.hh" #include "unreachable.hh" #include "memory.hh" #include namespace openmsx { ResampledSoundDevice::ResampledSoundDevice( MSXMotherBoard& motherBoard, string_ref name, string_ref description, unsigned channels, bool stereo) : SoundDevice(motherBoard.getMSXMixer(), name, description, channels, stereo) , resampleSetting(motherBoard.getReactor().getGlobalSettings().getResampleSetting()) { resampleSetting.attach(*this); } ResampledSoundDevice::~ResampledSoundDevice() { resampleSetting.detach(*this); } void ResampledSoundDevice::setOutputRate(unsigned /*sampleRate*/) { createResampler(); } bool ResampledSoundDevice::updateBuffer(unsigned length, int* buffer, EmuTime::param time) { return algo->generateOutput(buffer, length, time); } bool ResampledSoundDevice::generateInput(int* buffer, unsigned num) { return mixChannels(buffer, num); } void ResampledSoundDevice::update(const Setting& setting) { (void)setting; assert(&setting == &resampleSetting); createResampler(); } void ResampledSoundDevice::createResampler() { const DynamicClock& hostClock = getHostSampleClock(); unsigned outputRate = hostClock.getFreq(); unsigned inputRate = getInputRate() / getEffectiveSpeed(); if (outputRate == inputRate) { algo = make_unique(*this); } else { switch (resampleSetting.getEnum()) { case RESAMPLE_HQ: if (!isStereo()) { algo = make_unique>( *this, hostClock, inputRate); } else { algo = make_unique>( *this, hostClock, inputRate); } break; case RESAMPLE_LQ: if (!isStereo()) { algo = ResampleLQ<1>::create( *this, hostClock, inputRate); } else { algo = ResampleLQ<2>::create( *this, hostClock, inputRate); } break; case RESAMPLE_BLIP: if (!isStereo()) { algo = make_unique>( *this, hostClock, inputRate); } else { algo = make_unique>( *this, hostClock, inputRate); } break; default: UNREACHABLE; } } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/ResampledSoundDevice.hh000066400000000000000000000023021257557151200227410ustar00rootroot00000000000000#ifndef RESAMPLEDSOUNDDEVICE_HH #define RESAMPLEDSOUNDDEVICE_HH #include "SoundDevice.hh" #include "Observer.hh" #include namespace openmsx { class MSXMotherBoard; class ResampleAlgo; class Setting; template class EnumSetting; class ResampledSoundDevice : public SoundDevice, protected Observer { public: enum ResampleType { RESAMPLE_HQ, RESAMPLE_LQ, RESAMPLE_BLIP }; /** Note: To enable various optimizations (like SSE), this method is * allowed to generate up to 3 extra sample. * @see SoundDevice::updateBuffer() */ bool generateInput(int* buffer, unsigned num); protected: ResampledSoundDevice(MSXMotherBoard& motherBoard, string_ref name, string_ref description, unsigned channels, bool stereo = false); ~ResampledSoundDevice(); // SoundDevice void setOutputRate(unsigned sampleRate) override; bool updateBuffer(unsigned length, int* buffer, EmuTime::param time) override; // Observer void update(const Setting& setting) override; void createResampler(); private: EnumSetting& resampleSetting; std::unique_ptr algo; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/SCC.cc000066400000000000000000000430261257557151200173020ustar00rootroot00000000000000//----------------------------------------------------------------------------- // // On Mon, 24 Feb 2003, Jon De Schrijder wrote: // // I've done some measurements with the scope on the output of the SCC. // I didn't do timing tests, only amplitude checks: // // I know now for sure, the amplitude calculation works as follows: // // AmpOut=640+AmpA+AmpB+AmpC+AmpD+AmpE // // range AmpOut (11 bits positive number=SCC digital output): [+40...+1235] // // AmpA="((SampleValue*VolA) AND #7FF0) div 16" // AmpB="((SampleValue*VolB) AND #7FF0) div 16" // AmpC="((SampleValue*VolC) AND #7FF0) div 16" // AmpD="((SampleValue*VolD) AND #7FF0) div 16" // AmpE="((SampleValue*VolE) AND #7FF0) div 16" // // Setting the enablebit to zero, corresponds with VolX=0. // // SampleValue range [-128...+127] // VolX range [0..15] // // Notes: // * SampleValue*VolX is calculated (signed multiplication) and the lower 4 // bits are dropped (both in case the value is positive or negative), before // the addition of the 5 values is done. This was tested by setting // SampleValue=+1 and VolX=15 of different channels. The resulting AmpOut=640, // indicating that the 4 lower bits were dropped *before* the addition. // //----------------------------------------------------------------------------- // // On Mon, 14 Apr 2003, Manuel Pazos wrote // // I have some info about SCC/SCC+ that I hope you find useful. It is about // "Mode Setting Register", also called "Deformation Register" Here it goes: // // bit0: 4 bits frequency (%XXXX00000000). Equivalent to // (normal frequency >> 8) bits0-7 are ignored // bit1: 8 bits frequency (%0000XXXXXXXX) bits8-11 are ignored // bit2: // bit3: // bit4: // bit5: wave data is played from begining when frequency is changed // bit6: rotate all waves data. You can't write to them. Rotation speed // =3.58Mhz / (channel i frequency + 1) // bit7: rotate channel 4 wave data. You can't write to that channel // data.ONLY works in MegaROM SCC (not in SCC+) // // If bit7 and bit6 are set, only channel 1-3 wave data rotates . You can't // write to ANY wave data. And there is a weird behaviour in this setting. It // seems SCC sound is corrupted in anyway with MSX databus or so. Try to // activate them (with proper waves, freqs, and vol.) and execute DIR command // on DOS. You will hear "noise" This seems to be fixed in SCC+ // // Reading Mode Setting Register, is equivalent to write #FF to it. // // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // // Additions: // - Setting both bit0 and bit1 is equivalent to setting only bit1 // - A rotation goes like this: // wavedata[0:31] = wavedata[1:31].wavedata[0] // - Channel 4-5 rotation speed is set by channel 5 freq (channel 4 freq // is ignored for rotation) // // Also see this MRC thread: // http://www.msx.org/forumtopicl7875.html // //----------------------------------------------------------------------------- // // On Sat, 09 Sep 2005, NYYRIKKI wrote (MRC post) // // ... // // One important thing to know is that change of volume is not implemented // immediately in SCC. Normally it is changed when next byte from sample memory // is played, but writing value to frequency causes current byte to be started // again. As in this example we write values very quickly to frequency registers // the internal sample counter does not actually move at all. // // Third method is a variation of first method. As we don't know where SCC is // playing, let's update the whole sample memory with one and same new value. // To make sample rate not variable in low sample rates we first stop SCC from // reading sample memory. This can be done by writing value less than 9 to // frequency. Now we can update sample RAM so, that output does not change. // After sample RAM has been updated, we start SCC internal counter so that // value (where ever the counter was) is sent to output. This routine can be // found below as example 3. // // ... // // // // Something completely different: the SCC+ is actually called SCC-I. //----------------------------------------------------------------------------- #include "SCC.hh" #include "DeviceConfig.hh" #include "serialize.hh" #include "likely.hh" #include "outer.hh" #include "unreachable.hh" using std::string; namespace openmsx { static string calcDescription(SCC::ChipMode mode) { return (mode == SCC::SCC_Real) ? "Konami SCC" : "Konami SCC+"; } SCC::SCC(const string& name, const DeviceConfig& config, EmuTime::param time, ChipMode mode) : ResampledSoundDevice( config.getMotherBoard(), name, calcDescription(mode), 5) , debuggable(config.getMotherBoard(), getName()) , deformTimer(time) , currentChipMode(mode) { // Make valgrind happy for (auto& op : orgPeriod) op = 0; float input = 3579545.0f / 32; setInputRate(int(input + 0.5f)); powerUp(time); registerSound(config); } SCC::~SCC() { unregisterSound(); } void SCC::powerUp(EmuTime::param time) { // Power on values, tested by enen (log from IRC #openmsx): // // wouter_: i did an scc poweron values test, deform=0, // amplitude=full, channelenable=0, period=under 8 // ... // did you test the value of the waveforms as well? // ... // filled with $FF, some bits cleared but that seems random // Initialize ch_enable, deform (initialize this before period) reset(time); // Initialize waveform (initialize before volumes) for (unsigned i = 0; i < 5; ++i) { for (unsigned j = 0; j < 32; ++j) { wave[i][j] = ~0; } } // Initialize volume (initialize this before period) for (int i = 0; i < 5; ++i) { setFreqVol(i + 10, 15, time); } // Actual initial value is difficult to measure, assume zero // (initialize before period) for (auto& p : pos) p = 0; // Initialize period (sets members orgPeriod, period, incr, count, out) for (int i = 0; i < 2 * 5; ++i) { setFreqVol(i, 0, time); } } void SCC::reset(EmuTime::param /*time*/) { if (currentChipMode != SCC_Real) { setChipMode(SCC_Compatible); } setDeformRegHelper(0); ch_enable = 0; } void SCC::setChipMode(ChipMode newMode) { if (currentChipMode == SCC_Real) { assert(newMode == SCC_Real); } else { assert(newMode != SCC_Real); } currentChipMode = newMode; } byte SCC::readMem(byte addr, EmuTime::param time) { // Deform-register locations: // SCC_Real: 0xE0..0xFF // SCC_Compatible: 0xC0..0xDF // SCC_plusmode: 0xC0..0xDF if (((currentChipMode == SCC_Real) && (addr >= 0xE0)) || ((currentChipMode != SCC_Real) && (0xC0 <= addr) && (addr < 0xE0))) { setDeformReg(0xFF, time); } return peekMem(addr, time); } byte SCC::peekMem(byte address, EmuTime::param time) const { byte result; switch (currentChipMode) { case SCC_Real: if (address < 0x80) { // 0x00..0x7F : read wave form 1..4 result = readWave(address >> 5, address, time); } else { // 0x80..0x9F : freq volume block, write only // 0xA0..0xDF : no function // 0xE0..0xFF : deformation register result = 0xFF; } break; case SCC_Compatible: if (address < 0x80) { // 0x00..0x7F : read wave form 1..4 result = readWave(address >> 5, address, time); } else if (address < 0xA0) { // 0x80..0x9F : freq volume block result = 0xFF; } else if (address < 0xC0) { // 0xA0..0xBF : read wave form 5 result = readWave(4, address, time); } else { // 0xC0..0xDF : deformation register // 0xE0..0xFF : no function result = 0xFF; } break; case SCC_plusmode: if (address < 0xA0) { // 0x00..0x9F : read wave form 1..5 result = readWave(address >> 5, address, time); } else { // 0xA0..0xBF : freq volume block // 0xC0..0xDF : deformation register // 0xE0..0xFF : no function result = 0xFF; } break; default: UNREACHABLE; return 0; } return result; } byte SCC::readWave(unsigned channel, unsigned address, EmuTime::param time) const { if (!rotate[channel]) { return wave[channel][address & 0x1F]; } else { unsigned ticks = deformTimer.getTicksTill(time); unsigned periodCh = ((channel == 3) && (currentChipMode != SCC_plusmode) && ((deformValue & 0xC0) == 0x40)) ? 4 : channel; unsigned shift = ticks / (period[periodCh] + 1); return wave[channel][(address + shift) & 0x1F]; } } byte SCC::getFreqVol(unsigned address) const { address &= 0x0F; if (address < 0x0A) { // get frequency unsigned channel = address / 2; if (address & 1) { return orgPeriod[channel] >> 8; } else { return orgPeriod[channel] & 0xFF; } } else if (address < 0x0F) { // get volume return volume[address - 0xA]; } else { // get enable-bits return ch_enable; } } void SCC::writeMem(byte address, byte value, EmuTime::param time) { updateStream(time); switch (currentChipMode) { case SCC_Real: if (address < 0x80) { // 0x00..0x7F : write wave form 1..4 writeWave(address >> 5, address, value); } else if (address < 0xA0) { // 0x80..0x9F : freq volume block setFreqVol(address, value, time); } else if (address < 0xE0) { // 0xA0..0xDF : no function } else { // 0xE0..0xFF : deformation register setDeformReg(value, time); } break; case SCC_Compatible: if (address < 0x80) { // 0x00..0x7F : write wave form 1..4 writeWave(address >> 5, address, value); } else if (address < 0xA0) { // 0x80..0x9F : freq volume block setFreqVol(address, value, time); } else if (address < 0xC0) { // 0xA0..0xBF : ignore write wave form 5 } else if (address < 0xE0) { // 0xC0..0xDF : deformation register setDeformReg(value, time); } else { // 0xE0..0xFF : no function } break; case SCC_plusmode: if (address < 0xA0) { // 0x00..0x9F : write wave form 1..5 writeWave(address >> 5, address, value); } else if (address < 0xC0) { // 0xA0..0xBF : freq volume block setFreqVol(address, value, time); } else if (address < 0xE0) { // 0xC0..0xDF : deformation register setDeformReg(value, time); } else { // 0xE0..0xFF : no function } break; default: UNREACHABLE; } } int SCC::getAmplificationFactor() const { return 256; } inline int SCC::adjust(signed char wav, byte vol) { return (int(wav) * vol) >> 4; } void SCC::writeWave(unsigned channel, unsigned address, byte value) { // write to channel 5 only possible in SCC+ mode assert(channel < 5); assert((channel != 4) || (currentChipMode == SCC_plusmode)); if (!readOnly[channel]) { unsigned pos = address & 0x1F; wave[channel][pos] = value; volAdjustedWave[channel][pos] = adjust(value, volume[channel]); if ((currentChipMode != SCC_plusmode) && (channel == 3)) { // copy waveform 4 -> waveform 5 wave[4][pos] = wave[3][pos]; volAdjustedWave[4][pos] = adjust(value, volume[4]); } } } void SCC::setFreqVol(unsigned address, byte value, EmuTime::param time) { address &= 0x0F; // region is visible twice if (address < 0x0A) { // change frequency unsigned channel = address / 2; unsigned per = (address & 1) ? ((value & 0xF) << 8) | (orgPeriod[channel] & 0xFF) : (orgPeriod[channel] & 0xF00) | (value & 0xFF); orgPeriod[channel] = per; if (deformValue & 2) { // 8 bit frequency per &= 0xFF; } else if (deformValue & 1) { // 4 bit frequency per >>= 8; } period[channel] = per; incr[channel] = (per <= 8) ? 0 : 32; count[channel] = 0; // reset to begin of byte if (deformValue & 0x20) { pos[channel] = 0; // reset to begin of waveform // also 'rotation' mode (confirmed by test based on // Artag's SCC sample player) deformTimer.advance(time); } // after a freq change, update the output out[channel] = volAdjustedWave[channel][pos[channel]]; } else if (address < 0x0F) { // change volume unsigned channel = address - 0x0A; volume[channel] = value & 0xF; for (unsigned i = 0; i < 32; ++i) { volAdjustedWave[channel][i] = adjust(wave[channel][i], volume[channel]); } } else { // change enable-bits ch_enable = value; } } void SCC::setDeformReg(byte value, EmuTime::param time) { if (value == deformValue) { return; } deformTimer.advance(time); setDeformRegHelper(value); } void SCC::setDeformRegHelper(byte value) { deformValue = value; if (currentChipMode != SCC_Real) { value &= ~0x80; } switch (value & 0xC0) { case 0x00: for (unsigned i = 0; i < 5; ++i) { rotate[i] = false; readOnly[i] = false; } break; case 0x40: for (unsigned i = 0; i < 5; ++i) { rotate[i] = true; readOnly[i] = true; } break; case 0x80: for (unsigned i = 0; i < 3; ++i) { rotate[i] = false; readOnly[i] = false; } for (unsigned i = 3; i < 5; ++i) { rotate[i] = true; readOnly[i] = true; } break; case 0xC0: for (unsigned i = 0; i < 3; ++i) { rotate[i] = true; readOnly[i] = true; } for (unsigned i = 3; i < 5; ++i) { rotate[i] = false; readOnly[i] = true; } break; default: UNREACHABLE; } } void SCC::generateChannels(int** bufs, unsigned num) { unsigned enable = ch_enable; for (unsigned i = 0; i < 5; ++i, enable >>= 1) { if ((enable & 1) && (volume[i] || out[i])) { #ifdef __arm__ unsigned dummy; int* buf = bufs[i]; asm volatile ( "0:\n\t" "ldr %[T],[%[B]]\n\t" "add %[T],%[T],%[O]\n\t" "add %[C],%[C],%[I]\n\t" "str %[T],[%[B]],#4\n\t" "subs %[T],%[C],%[PE]\n\t" "bpl 2f\n" "1:\n\t" "cmp %[B],%[E]\n\t" "bne 0b\n\t" "b 3f\n" "2:\n\t" "adds %[PO],%[PO],#1\n\t" "subs %[T],%[T],%[PE]\n\t" "bpl 2b\n\t" "and %[PO],%[PO],#31\n\t" "add %[C],%[T],%[PE]\n\t" "ldr %[O],[%[V],%[PO],LSL #2]\n\t" "b 1b\n" "3:\n\t" : [T] "=&r" (dummy) , [B] "=r" (buf) , [O] "=r" (out[i]) , [C] "=r" (count[i]) , [PO] "=r" (pos[i]) : "[B]" (buf) , "[O]" (out[i]) , "[C]" (count[i]) , [I] "r" (incr[i]) , [PE] "r" (period[i] + 1) , [E] "r" (&buf[num]) , "[PO]" (pos[i]) , [V] "r" (volAdjustedWave[i]) : "memory", "cc" ); #else int out2 = out[i]; unsigned count2 = count[i]; unsigned pos2 = pos[i]; unsigned incr2 = incr[i]; unsigned period2 = period[i] + 1; for (unsigned j = 0; j < num; ++j) { bufs[i][j] += out2; count2 += incr2; // Note: only for very small periods // this will take more than 1 iteration while (unlikely(count2 >= period2)) { count2 -= period2; pos2 = (pos2 + 1) % 32; out2 = volAdjustedWave[i][pos2]; } } out[i] = out2; count[i] = count2; pos[i] = pos2; #endif } else { bufs[i] = nullptr; // channel muted // Update phase counter. unsigned newCount = count[i] + num * incr[i]; count[i] = newCount % (period[i] + 1); pos[i] = (pos[i] + newCount / (period[i] + 1)) % 32; // Channel stays off until next waveform index. out[i] = 0; } } } // Debuggable SCC::Debuggable::Debuggable(MSXMotherBoard& motherBoard, const string& name) : SimpleDebuggable(motherBoard, name + " SCC", "SCC registers in SCC+ format", 0x100) { } byte SCC::Debuggable::read(unsigned address, EmuTime::param time) { auto& scc = OUTER(SCC, debuggable); if (address < 0xA0) { // read wave form 1..5 return scc.readWave(address >> 5, address, time); } else if (address < 0xC0) { // freq volume block return scc.getFreqVol(address); } else if (address < 0xE0) { // peek deformation register return scc.deformValue; } else { return 0xFF; } } void SCC::Debuggable::write(unsigned address, byte value, EmuTime::param time) { auto& scc = OUTER(SCC, debuggable); if (address < 0xA0) { // read wave form 1..5 scc.writeWave(address >> 5, address, value); } else if (address < 0xC0) { // freq volume block scc.setFreqVol(address, value, time); } else if (address < 0xE0) { // deformation register scc.setDeformReg(value, time); } else { // ignore } } static enum_string chipModeInfo[] = { { "Real", SCC::SCC_Real }, { "Compatible", SCC::SCC_Compatible }, { "Plus", SCC::SCC_plusmode }, }; SERIALIZE_ENUM(SCC::ChipMode, chipModeInfo); template void SCC::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("mode", currentChipMode); ar.serialize("period", orgPeriod); ar.serialize("volume", volume); ar.serialize("ch_enable", ch_enable); ar.serialize("deformTimer", deformTimer); ar.serialize("deform", deformValue); // multi-dimensional arrays are not directly support by the // serialization framework, maybe in the future. So for now // manually loop over the channels. char tag[6] = { 'w', 'a', 'v', 'e', 'X', 0 }; for (int channel = 0; channel < 5; ++channel) { tag[4] = char('1' + channel); ar.serialize(tag, wave[channel]); // signed char } if (ar.isLoader()) { // recalculate volAdjustedWave for (int channel = 0; channel < 5; ++channel) { for (int pos = 0; pos < 32; ++pos) { volAdjustedWave[channel][pos] = adjust(wave[channel][pos], volume[channel]); } } // recalculate rotate[5] and readOnly[5] setDeformRegHelper(deformValue); // recalculate incr[5] and period[5] // this also (possibly) changes count[5], pos[5] and out[5] // as an unwanted side-effect, so (de)serialize those later // Don't use current time, but instead use deformTimer, to // avoid changing the value of deformTimer. EmuTime::param time = deformTimer.getTime(); for (int channel = 0; channel < 5; ++channel) { unsigned per = orgPeriod[channel]; setFreqVol(2 * channel + 0, (per & 0x0FF) >> 0, time); setFreqVol(2 * channel + 1, (per & 0xF00) >> 8, time); } } // call to setFreqVol() modifies these variables, see above ar.serialize("count", count); ar.serialize("pos", pos); ar.serialize("out", out); } INSTANTIATE_SERIALIZE_METHODS(SCC); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/SCC.hh000066400000000000000000000036601257557151200173140ustar00rootroot00000000000000#ifndef SCC_HH #define SCC_HH #include "ResampledSoundDevice.hh" #include "SimpleDebuggable.hh" #include "Clock.hh" #include "openmsx.hh" namespace openmsx { class SCC final : public ResampledSoundDevice { public: enum ChipMode {SCC_Real, SCC_Compatible, SCC_plusmode}; SCC(const std::string& name, const DeviceConfig& config, EmuTime::param time, ChipMode mode = SCC_Real); ~SCC(); // interaction with realCartridge void powerUp(EmuTime::param time); void reset(EmuTime::param time); byte readMem(byte address,EmuTime::param time); byte peekMem(byte address,EmuTime::param time) const; void writeMem(byte address, byte value, EmuTime::param time); void setChipMode(ChipMode newMode); template void serialize(Archive& ar, unsigned version); private: // SoundDevice int getAmplificationFactor() const override; void generateChannels(int** bufs, unsigned num) override; inline int adjust(signed char wav, byte vol); byte readWave(unsigned channel, unsigned address, EmuTime::param time) const; void writeWave(unsigned channel, unsigned offset, byte value); void setDeformReg(byte value, EmuTime::param time); void setDeformRegHelper(byte value); void setFreqVol(unsigned address, byte value, EmuTime::param time); byte getFreqVol(unsigned address) const; static const int CLOCK_FREQ = 3579545; struct Debuggable final : SimpleDebuggable { Debuggable(MSXMotherBoard& motherBoard, const std::string& name); byte read(unsigned address, EmuTime::param time) override; void write(unsigned address, byte value, EmuTime::param time) override; } debuggable; Clock deformTimer; ChipMode currentChipMode; signed char wave[5][32]; int volAdjustedWave[5][32]; unsigned incr[5]; unsigned count[5]; unsigned pos[5]; unsigned period[5]; unsigned orgPeriod[5]; int out[5]; byte volume[5]; byte ch_enable; byte deformValue; bool rotate[5]; bool readOnly[5]; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/SDLSoundDriver.cc000066400000000000000000000104571257557151200215030ustar00rootroot00000000000000#include "SDLSoundDriver.hh" #include "Reactor.hh" #include "MSXMotherBoard.hh" #include "RealTime.hh" #include "GlobalSettings.hh" #include "ThrottleManager.hh" #include "MSXException.hh" #include "Math.hh" #include "StringOp.hh" #include "Timer.hh" #include "build-info.hh" #include #include #include #include namespace openmsx { SDLSoundDriver::SDLSoundDriver(Reactor& reactor_, unsigned wantedFreq, unsigned wantedSamples) : reactor(reactor_) , muted(true) { SDL_AudioSpec desired; desired.freq = wantedFreq; desired.samples = Math::powerOfTwo(wantedSamples); desired.channels = 2; // stereo desired.format = OPENMSX_BIGENDIAN ? AUDIO_S16MSB : AUDIO_S16LSB; desired.callback = audioCallbackHelper; // must be a static method desired.userdata = this; if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0) { throw MSXException(StringOp::Builder() << "Unable to initialize SDL audio subsystem: " << SDL_GetError()); } SDL_AudioSpec audioSpec; if (SDL_OpenAudio(&desired, &audioSpec) != 0) { SDL_QuitSubSystem(SDL_INIT_AUDIO); throw MSXException(StringOp::Builder() << "Unable to open SDL audio: " << SDL_GetError()); } frequency = audioSpec.freq; fragmentSize = audioSpec.samples; mixBufferSize = 3 * (audioSpec.size / sizeof(short)) + 2; mixBuffer.resize(mixBufferSize); reInit(); } SDLSoundDriver::~SDLSoundDriver() { SDL_CloseAudio(); SDL_QuitSubSystem(SDL_INIT_AUDIO); } void SDLSoundDriver::reInit() { SDL_LockAudio(); readIdx = 0; writeIdx = 0; SDL_UnlockAudio(); } void SDLSoundDriver::mute() { if (!muted) { muted = true; SDL_PauseAudio(1); } } void SDLSoundDriver::unmute() { if (muted) { muted = false; reInit(); SDL_PauseAudio(0); } } unsigned SDLSoundDriver::getFrequency() const { return frequency; } unsigned SDLSoundDriver::getSamples() const { return fragmentSize; } void SDLSoundDriver::audioCallbackHelper(void* userdata, byte* strm, int len) { assert((len & 3) == 0); // stereo, 16-bit static_cast(userdata)-> audioCallback(reinterpret_cast(strm), len / sizeof(short)); } unsigned SDLSoundDriver::getBufferFilled() const { int result = writeIdx - readIdx; if (result < 0) result += mixBufferSize; assert((0 <= result) && (unsigned(result) < mixBufferSize)); return result; } unsigned SDLSoundDriver::getBufferFree() const { // we can't distinguish completely filled from completely empty // (in both cases readIx would be equal to writeIdx), so instead // we define full as '(writeIdx + 2) == readIdx' (note that index // increases in steps of 2 (stereo)). int result = mixBufferSize - 2 - getBufferFilled(); assert((0 <= result) && (unsigned(result) < mixBufferSize)); return result; } void SDLSoundDriver::audioCallback(short* stream, unsigned len) { assert((len & 1) == 0); // stereo unsigned available = getBufferFilled(); unsigned num = std::min(len, available); if ((readIdx + num) < mixBufferSize) { memcpy(stream, &mixBuffer[readIdx], num * sizeof(short)); readIdx += num; } else { unsigned len1 = mixBufferSize - readIdx; memcpy(stream, &mixBuffer[readIdx], len1 * sizeof(short)); unsigned len2 = num - len1; memcpy(&stream[len1], &mixBuffer[0], len2 * sizeof(short)); readIdx = len2; } int missing = len - available; if (missing > 0) { // buffer underrun memset(&stream[available], 0, missing * sizeof(short)); } } void SDLSoundDriver::uploadBuffer(short* buffer, unsigned len) { SDL_LockAudio(); len *= 2; // stereo unsigned free = getBufferFree(); if (len > free) { if (reactor.getGlobalSettings().getThrottleManager().isThrottled()) { do { SDL_UnlockAudio(); Timer::sleep(5000); // 5ms SDL_LockAudio(); if (MSXMotherBoard* board = reactor.getMotherBoard()) { board->getRealTime().resync(); } free = getBufferFree(); } while (len > free); } else { // drop excess samples len = free; } } assert(len <= free); if ((writeIdx + len) < mixBufferSize) { memcpy(&mixBuffer[writeIdx], buffer, len * sizeof(short)); writeIdx += len; } else { unsigned len1 = mixBufferSize - writeIdx; memcpy(&mixBuffer[writeIdx], buffer, len1 * sizeof(short)); unsigned len2 = len - len1; memcpy(&mixBuffer[0], &buffer[len1], len2 * sizeof(short)); writeIdx = len2; } SDL_UnlockAudio(); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/SDLSoundDriver.hh000066400000000000000000000017211257557151200215070ustar00rootroot00000000000000#ifndef SDLSOUNDDRIVER_HH #define SDLSOUNDDRIVER_HH #include "SoundDriver.hh" #include "MemBuffer.hh" #include "openmsx.hh" #include "noncopyable.hh" namespace openmsx { class Reactor; class SDLSoundDriver final : public SoundDriver, private noncopyable { public: SDLSoundDriver(Reactor& reactor, unsigned frequency, unsigned samples); ~SDLSoundDriver(); void mute() override; void unmute() override; unsigned getFrequency() const override; unsigned getSamples() const override; void uploadBuffer(short* buffer, unsigned len) override; private: void reInit(); unsigned getBufferFilled() const; unsigned getBufferFree() const; static void audioCallbackHelper(void* userdata, byte* strm, int len); void audioCallback(short* stream, unsigned len); Reactor& reactor; MemBuffer mixBuffer; unsigned mixBufferSize; unsigned frequency; unsigned fragmentSize; unsigned readIdx, writeIdx; bool muted; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/SamplePlayer.cc000066400000000000000000000072261257557151200212720ustar00rootroot00000000000000#include "SamplePlayer.hh" #include "DeviceConfig.hh" #include "CliComm.hh" #include "FileContext.hh" #include "StringOp.hh" #include "MSXException.hh" #include "serialize.hh" #include namespace openmsx { SamplePlayer::SamplePlayer(const std::string& name, const std::string& desc, const DeviceConfig& config, const std::string& samplesBaseName, unsigned numSamples, const std::string& alternativeName) : ResampledSoundDevice(config.getMotherBoard(), name, desc, 1) { setInputRate(44100); // Initialize with dummy value bool alreadyWarned = false; samples.resize(numSamples); // initialize with empty wavs auto context = systemFileContext(); for (unsigned i = 0; i < numSamples; ++i) { try { std::string filename = StringOp::Builder() << samplesBaseName << i << ".wav"; samples[i] = WavData(context.resolve(filename)); } catch (MSXException& e1) { try { if (alternativeName.empty()) throw; std::string filename = StringOp::Builder() << alternativeName << i << ".wav"; samples[i] = WavData(context.resolve(filename)); } catch (MSXException& /*e2*/) { if (!alreadyWarned) { alreadyWarned = true; // print message from the 1st error config.getCliComm().printWarning( "Couldn't read " + name + " sample data: " + e1.getMessage() + ". Continuing without sample data."); } } } } registerSound(config); reset(); // avoid UMR on serialize index = 0; } SamplePlayer::~SamplePlayer() { unregisterSound(); } void SamplePlayer::reset() { currentSampleNum = unsigned(-1); stopRepeat(); } void SamplePlayer::play(unsigned sampleNum) { assert(sampleNum < samples.size()); currentSampleNum = sampleNum; index = 0; setWavParams(); } void SamplePlayer::setWavParams() { if ((currentSampleNum < samples.size()) && samples[currentSampleNum].getSize()) { auto& wav = samples[currentSampleNum]; sampBuf = wav.getData(); bufferSize = wav.getSize(); unsigned bits = wav.getBits(); assert((bits == 8) || (bits == 16)); bits8 = (bits == 8); unsigned freq = wav.getFreq(); if (freq != getInputRate()) { // this potentially switches resampler, so there might be // some dropped samples if this is done in the middle of // playing, though this shouldn't happen often (or at all) setInputRate(freq); createResampler(); } } else { reset(); } } void SamplePlayer::repeat(unsigned sampleNum) { assert(sampleNum < samples.size()); nextSampleNum = sampleNum; if (!isPlaying()) { doRepeat(); } } inline int SamplePlayer::getSample(unsigned index) { return bits8 ? (static_cast(sampBuf)[index] - 0x80) * 256 : static_cast(sampBuf)[index]; } void SamplePlayer::generateChannels(int** bufs, unsigned num) { // Single channel device: replace content of bufs[0] (not add to it). if (!isPlaying()) { bufs[0] = nullptr; return; } for (unsigned i = 0; i < num; ++i) { if (index >= bufferSize) { if (nextSampleNum != unsigned(-1)) { doRepeat(); } else { currentSampleNum = unsigned(-1); // fill remaining buffer with zeros do { bufs[0][i++] = 0; } while (i < num); break; } } bufs[0][i] = 3 * getSample(index++); } } void SamplePlayer::doRepeat() { play(nextSampleNum); } template void SamplePlayer::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("index", index); ar.serialize("currentSampleNum", currentSampleNum); ar.serialize("nextSampleNum", nextSampleNum); if (ar.isLoader()) { setWavParams(); } } INSTANTIATE_SERIALIZE_METHODS(SamplePlayer); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/SamplePlayer.hh000066400000000000000000000031051257557151200212740ustar00rootroot00000000000000#ifndef SAMPLEPLAYER_HH #define SAMPLEPLAYER_HH #include "ResampledSoundDevice.hh" #include "WavData.hh" #include namespace openmsx { class SamplePlayer final : public ResampledSoundDevice { public: SamplePlayer(const std::string& name, const std::string& desc, const DeviceConfig& config, const std::string& samplesBaseName, unsigned numSamples, const std::string& alternativeName = ""); ~SamplePlayer(); void reset(); /** Start playing a (new) sample. */ void play(unsigned sampleNum); /** Keep on repeating the given sample data. * If there is already a sample playing, that sample is still * finished. If there was no sample playing, the given sample * immediatly starts playing. * Parameters are the same as for the play() method. * @see stopRepeat() */ void repeat(unsigned sampleNum); /** Stop repeat mode. * The currently playing sample will still be finished, but won't * be started. * @see repeat() */ void stopRepeat() { nextSampleNum = unsigned(-1); } /** Is there currently playing a sample. */ bool isPlaying() const { return currentSampleNum != unsigned(-1); } template void serialize(Archive& ar, unsigned version); private: inline int getSample(unsigned index); void setWavParams(); void doRepeat(); // SoundDevice void generateChannels(int** bufs, unsigned num) override; std::vector samples; const void* sampBuf; unsigned index; unsigned bufferSize; unsigned currentSampleNum; unsigned nextSampleNum; bool bits8; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/SoundDevice.cc000066400000000000000000000173251257557151200211050ustar00rootroot00000000000000#include "SoundDevice.hh" #include "MSXMixer.hh" #include "DeviceConfig.hh" #include "XMLElement.hh" #include "WavWriter.hh" #include "Filename.hh" #include "StringOp.hh" #include "MemoryOps.hh" #include "MemBuffer.hh" #include "MSXException.hh" #include "likely.hh" #include "vla.hh" #include "memory.hh" #include using std::string; namespace openmsx { static MemBuffer mixBuffer; static unsigned mixBufferSize = 0; static void allocateMixBuffer(unsigned size) { if (unlikely(mixBufferSize < size)) { mixBufferSize = size; mixBuffer.resize(mixBufferSize); } } static string makeUnique(MSXMixer& mixer, string_ref name) { string result = name.str(); if (mixer.findDevice(result)) { unsigned n = 0; do { result = StringOp::Builder() << name << " (" << ++n << ')'; } while (mixer.findDevice(result)); } return result; } SoundDevice::SoundDevice(MSXMixer& mixer_, string_ref name_, string_ref description_, unsigned numChannels_, bool stereo_) : mixer(mixer_) , name(makeUnique(mixer, name_)) , description(description_.str()) , numChannels(numChannels_) , stereo(stereo_ ? 2 : 1) , numRecordChannels(0) , balanceCenter(true) { assert(numChannels <= MAX_CHANNELS); assert(stereo == 1 || stereo == 2); // initially no channels are muted for (unsigned i = 0; i < numChannels; ++i) { channelMuted[i] = false; channelBalance[i] = 0; } } SoundDevice::~SoundDevice() { } bool SoundDevice::isStereo() const { return stereo == 2 || !balanceCenter; } int SoundDevice::getAmplificationFactor() const { return 1; } void SoundDevice::registerSound(const DeviceConfig& config) { const XMLElement& soundConfig = config.getChild("sound"); float volume = soundConfig.getChildDataAsInt("volume") / 32767.0f; int devBalance = 0; string_ref mode = soundConfig.getChildData("mode", "mono"); if (mode == "mono") { devBalance = 0; } else if (mode == "left") { devBalance = -100; } else if (mode == "right") { devBalance = 100; } else { throw MSXException("balance \"" + mode + "\" illegal"); } for (auto& b : soundConfig.getChildren("balance")) { int balance = StringOp::stringToInt(b->getData()); if (!b->hasAttribute("channel")) { devBalance = balance; continue; } // TODO Support other balances if (balance != 0 && balance != -100 && balance != 100) { throw MSXException(StringOp::Builder() << "balance " << balance << " illegal"); } if (balance != 0) { balanceCenter = false; } const string& range = b->getAttribute("channel"); for (unsigned c : StringOp::parseRange(range, 1, numChannels)) { channelBalance[c - 1] = balance; } } mixer.registerSound(*this, volume, devBalance, numChannels); } void SoundDevice::unregisterSound() { mixer.unregisterSound(*this); } void SoundDevice::updateStream(EmuTime::param time) { mixer.updateStream(time); } void SoundDevice::recordChannel(unsigned channel, const Filename& filename) { assert(channel < numChannels); bool wasRecording = writer[channel] != nullptr; if (!filename.empty()) { writer[channel] = make_unique( filename, stereo, inputSampleRate); } else { writer[channel].reset(); } bool recording = writer[channel] != nullptr; if (recording != wasRecording) { if (recording) { if (numRecordChannels == 0) { mixer.setSynchronousMode(true); } ++numRecordChannels; assert(numRecordChannels <= numChannels); } else { assert(numRecordChannels > 0); --numRecordChannels; if (numRecordChannels == 0) { mixer.setSynchronousMode(false); } } } } void SoundDevice::muteChannel(unsigned channel, bool muted) { assert(channel < numChannels); channelMuted[channel] = muted; } bool SoundDevice::mixChannels(int* dataOut, unsigned samples) { #ifdef __SSE2__ assert((uintptr_t(dataOut) & 15) == 0); // must be 16-byte aligned #endif if (samples == 0) return true; unsigned outputStereo = isStereo() ? 2 : 1; MemoryOps::MemSet mset; if (numChannels != 1) { // The generateChannels() method of SoundDevices with more than // one channel will _add_ the generated channel data in the // provided buffers. Those with only one channel will directly // replace the content of the buffer. For the former we must // start from a buffer containing all zeros. mset(reinterpret_cast(dataOut), outputStereo * samples, 0); } VLA(int*, bufs, numChannels); unsigned separateChannels = 0; unsigned pitch = (samples * stereo + 3) & ~3; // align for SSE access // TODO optimization: All channels with the same balance (according to // channelBalance[]) could use the same buffer when balanceCenter is // false for (unsigned i = 0; i < numChannels; ++i) { if (!channelMuted[i] && !writer[i] && balanceCenter) { // no need to keep this channel separate bufs[i] = dataOut; } else { // muted or recorded channels must go separate // cannot yet fill in bufs[i] here ++separateChannels; } } if (separateChannels) { allocateMixBuffer(pitch * separateChannels); mset(reinterpret_cast(mixBuffer.data()), pitch * separateChannels, 0); // still need to fill in (some) bufs[i] pointers unsigned count = 0; for (unsigned i = 0; i < numChannels; ++i) { if (!(!channelMuted[i] && !writer[i] && balanceCenter)) { bufs[i] = &mixBuffer[pitch * count++]; } } assert(count == separateChannels); } generateChannels(bufs, samples); if (separateChannels == 0) { for (unsigned i = 0; i < numChannels; ++i) { if (bufs[i]) { return true; } } return false; } // record channels for (unsigned i = 0; i < numChannels; ++i) { if (writer[i]) { assert(bufs[i] != dataOut); if (bufs[i]) { writer[i]->write( bufs[i], stereo, samples, getAmplificationFactor()); } else { writer[i]->writeSilence(stereo, samples); } } } // remove muted channels (explictly by user or by device itself) bool anyUnmuted = false; unsigned numMix = 0; VLA(int, mixBalance, numChannels); for (unsigned i = 0; i < numChannels; ++i) { if (bufs[i] && !channelMuted[i]) { anyUnmuted = true; if (bufs[i] != dataOut) { bufs[numMix] = bufs[i]; mixBalance[numMix] = channelBalance[i]; ++numMix; } } } if (numMix == 0) { // all extra channels muted return anyUnmuted; } // actually mix channels if (!balanceCenter) { unsigned i = 0; do { int left0 = 0; int right0 = 0; int left1 = 0; int right1 = 0; unsigned j = 0; do { if (mixBalance[j] <= 0) { left0 += bufs[j][i + 0]; left1 += bufs[j][i + 1]; } if (mixBalance[j] >= 0) { right0 += bufs[j][i + 0]; right1 += bufs[j][i + 1]; } j++; } while (j < numMix); dataOut[i * 2 + 0] = left0; dataOut[i * 2 + 1] = right0; dataOut[i * 2 + 2] = left1; dataOut[i * 2 + 3] = right1; i += 2; } while (i < samples); return true; } // In the past we had ARM and x86-SSE2 optimized assembly routines for // the stuff below. Currently this code is only rarely used anymore // (only when recording or muting individual soundchip channels), so // it's not worth the extra complexity anymore. unsigned num = samples * stereo; unsigned i = 0; do { int out0 = dataOut[i + 0]; int out1 = dataOut[i + 1]; int out2 = dataOut[i + 2]; int out3 = dataOut[i + 3]; unsigned j = 0; do { out0 += bufs[j][i + 0]; out1 += bufs[j][i + 1]; out2 += bufs[j][i + 2]; out3 += bufs[j][i + 3]; ++j; } while (j < numMix); dataOut[i + 0] = out0; dataOut[i + 1] = out1; dataOut[i + 2] = out2; dataOut[i + 3] = out3; i += 4; } while (i < num); return true; } const DynamicClock& SoundDevice::getHostSampleClock() const { return mixer.getHostSampleClock(); } double SoundDevice::getEffectiveSpeed() const { return mixer.getEffectiveSpeed(); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/SoundDevice.hh000066400000000000000000000126251257557151200211150ustar00rootroot00000000000000#ifndef SOUNDDEVICE_HH #define SOUNDDEVICE_HH #include "EmuTime.hh" #include "noncopyable.hh" #include "string_ref.hh" #include namespace openmsx { class MSXMixer; class DeviceConfig; class Wav16Writer; class Filename; class DynamicClock; class SoundDevice : private noncopyable { public: static const unsigned MAX_CHANNELS = 24; /** Get the unique name that identifies this sound device. * Used to create setting names. */ const std::string& getName() const { return name; } /** Gets a description of this sound device, * to be presented to the user. */ const std::string& getDescription() const { return description; } /** Is this a stereo device? * This is set in the constructor and cannot be changed anymore */ bool isStereo() const; /** Get extra amplification factor for this device. * Normally the outputBuffer() method should scale the output to * the range [-32768,32768]. Some devices can be emulated slightly * faster to produce another output range. In later stages the output * is anyway still multiplied by some factor. This method tells which * factor should be used to scale the output to the correct range. * The default implementation returns 1. */ virtual int getAmplificationFactor() const; void recordChannel(unsigned channel, const Filename& filename); void muteChannel (unsigned channel, bool muted); protected: /** Constructor. * @param mixer The Mixer object * @param name Name for this device, will be made unique * @param description Description for this sound device * @param numChannels The number of channels for this device * @param stereo Is this a stereo device */ SoundDevice(MSXMixer& mixer, string_ref name, string_ref description, unsigned numChannels, bool stereo = false); ~SoundDevice(); /** * Registers this sound device with the Mixer. * Call this method when the sound device is ready to start receiving * calls to updateBuffer, so after all initialisation is done. * @param config Configuration data for this sound device. */ void registerSound(const DeviceConfig& config); /** * Unregisters this sound device with the Mixer. * Call this method before any deallocation is done. */ void unregisterSound(); /** @see Mixer::updateStream */ void updateStream(EmuTime::param time); void setInputRate(unsigned sampleRate) { inputSampleRate = sampleRate; } unsigned getInputRate() const { return inputSampleRate; } public: // Will be called by Mixer: /** * When a SoundDevice registers itself with the Mixer, the Mixer sets * the required sampleRate through this method. All sound devices share * a common sampleRate. */ virtual void setOutputRate(unsigned sampleRate) = 0; /** Generate sample data * @param length The number of required samples * @param buffer This buffer should be filled * @param time current time * @result false iff the output is empty. IOW filling the buffer with * zeros or returning false has the same effect, but the latter * can be more efficient * * This method is regularly called from the Mixer, it should return a * pointer to a buffer filled with the required number of samples. * Samples are always ints, later they are converted to the systems * native format (e.g. 16-bit signed). * * Note: To enable various optimizations (like SSE), this method can * fill the output buffer with up to 3 extra samples. Those extra * samples should be ignored, though the caller must make sure the * buffer has enough space to hold them. */ virtual bool updateBuffer(unsigned length, int* buffer, EmuTime::param time) = 0; protected: /** Abstract method to generate the actual sound data. * @param buffers An array of pointer to buffers. Each buffer must * be big enough to hold 'num' samples. * @param num The number of samples. * * This method should fill each buffer with sound data that * corresponds to one channel of the sound device. The same channel * should each time be written to the same buffer (needed for record). * * If a certain channel is muted it is allowed to set the buffer * pointer to nullptr. This has exactly the same effect as filling the * buffer completely with zeros, but it can be more efficient. */ virtual void generateChannels(int** buffers, unsigned num) = 0; /** Calls generateChannels() and combines the output to a single * channel. * @param dataOut Output buffer, must be big enough to hold * 'num' samples * @param num The number of samples * @result true iff at least one channel was unmuted * * Note: To enable various optimizations (like SSE), this method can * fill the output buffer with up to 3 extra samples. Those extra * samples should be ignored, though the caller must make sure the * buffer has enough space to hold them. */ bool mixChannels(int* dataOut, unsigned num); /** See MSXMixer::getHostSampleClock(). */ const DynamicClock& getHostSampleClock() const; double getEffectiveSpeed() const; private: MSXMixer& mixer; const std::string name; const std::string description; std::unique_ptr writer[MAX_CHANNELS]; unsigned inputSampleRate; const unsigned numChannels; const unsigned stereo; unsigned numRecordChannels; int channelBalance[MAX_CHANNELS]; bool channelMuted[MAX_CHANNELS]; bool balanceCenter; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/SoundDriver.hh000066400000000000000000000013231257557151200211420ustar00rootroot00000000000000#ifndef SOUNDDRIVER_HH #define SOUNDDRIVER_HH namespace openmsx { class SoundDriver { public: virtual ~SoundDriver() {} /** Mute the sound system */ virtual void mute() = 0; /** Unmute the sound system */ virtual void unmute() = 0; /** Returns the actual sample frequency. This might be different * from the requested frequency ('frequency' setting). */ virtual unsigned getFrequency() const = 0; /** Get the number of samples that should be created 'per fragment'. * This is not the same value as the 'samples setting'. */ virtual unsigned getSamples() const = 0; virtual void uploadBuffer(short* buffer, unsigned len) = 0; protected: SoundDriver() {} }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/VLM5030.cc000066400000000000000000000364271257557151200176470ustar00rootroot00000000000000/* vlm5030.c VLM5030 emulator Written by Tatsuyuki Satoh Based on TMS5220 simulator (tms5220.c) note: memory read cycle(==sampling rate) = 122.9u(440clock) interpolator (LC8109 = 2.5ms) = 20 * samples(125us) frame time (20ms) = 4 * interpolator 9bit DAC is composed of 5bit Physical and 3bitPWM. todo: Noise Generator circuit without 'rand()' function. ----------- command format (Analytical result) ---------- 1)end of speech (8bit) :00000011: 2)silent some frame (8bit) :????SS01: SS : number of silent frames 00 = 2 frame 01 = 4 frame 10 = 6 frame 11 = 8 frame 3)-speech frame (48bit) function: 6th : 5th : 4th : 3rd : 2nd : 1st : end : --- : --- : --- : --- : --- :00000011: silent : --- : --- : --- : --- : --- :0000SS01: speech :11111122:22233334:44455566:67778889:99AAAEEE:EEPPPPP0: EEEEE : energy : volume 0=off,0x1f=max PPPPP : pitch : 0=noize , 1=fast,0x1f=slow 111111 : K1 : 48=off 22222 : K2 : 0=off,1=+min,0x0f=+max,0x10=off,0x11=+max,0x1f=-min : 16 == special function?? 3333 : K3 : 0=off,1=+min,0x07=+max,0x08=-max,0x0f=-min 4444 : K4 : 555 : K5 : 0=off,1=+min,0x03=+max,0x04=-max,0x07=-min 666 : K6 : 777 : K7 : 888 : K8 : 999 : K9 : AAA : K10 : ---------- chirp table information ---------- DAC PWM cycle == 88system clock , (11clock x 8 pattern) = 40.6KHz one chirp == 5 x PWM cycle == 440systemclock(8,136Hz) chirp 0 : volume 10- 8 : with filter chirp 1 : volume 8- 6 : with filter chirp 2 : volume 6- 4 : with filter chirp 3 : volume 4 : no filter ?? chirp 4- 5: volume 4- 2 : with filter chirp 6-11: volume 2- 0 : with filter chirp 12-..: vokume 0 : silent ---------- digial output information ---------- when ME pin = high , some status output to A0..15 pins A0..8 : DAC output value (abs) A9 : DAC sign flag , L=minus,H=Plus A10 : energy reload flag (pitch pulse) A11..15 : unknown [DAC output value(signed 6bit)] = A9 ? A0..8 : -(A0..8) */ #include "VLM5030.hh" #include "DeviceConfig.hh" #include "XMLElement.hh" #include "FileOperations.hh" #include "Math.hh" #include "serialize.hh" #include "random.hh" #include #include namespace openmsx { // interpolator per frame static const int FR_SIZE = 4; // samples per interpolator static const int IP_SIZE_SLOWER = 240 / FR_SIZE; static const int IP_SIZE_SLOW = 200 / FR_SIZE; static const int IP_SIZE_NORMAL = 160 / FR_SIZE; static const int IP_SIZE_FAST = 120 / FR_SIZE; static const int IP_SIZE_FASTER = 80 / FR_SIZE; // phase value enum { PH_RESET, PH_IDLE, PH_SETUP, PH_WAIT, PH_RUN, PH_STOP, PH_END }; // speed parameter // SPC SPB SPA // 1 0 1 more slow (05h) : 42ms (150%) : 60sample // 1 1 x slow (06h,07h) : 34ms (125%) : 50sample // x 0 0 normal (00h,04h) : 25.6ms (100%) : 40samplme // 0 0 1 fast (01h) : 20.2ms (75%) : 30sample // 0 1 x more fast (02h,03h) : 12.2ms (50%) : 20sample static const int VLM5030_speed_table[8] = { IP_SIZE_NORMAL, IP_SIZE_FAST, IP_SIZE_FASTER, IP_SIZE_FASTER, IP_SIZE_NORMAL, IP_SIZE_SLOWER, IP_SIZE_SLOW, IP_SIZE_SLOW }; // ROM Tables // This is the energy lookup table // sampled from real chip static word energytable[0x20] = { 0, 2, 4, 6, 10, 12, 14, 18, // 0-7 22, 26, 30, 34, 38, 44, 48, 54, // 8-15 62, 68, 76, 84, 94,102,114,124, // 16-23 136,150,164,178,196,214,232,254 // 24-31 }; // This is the pitch lookup table static const byte pitchtable [0x20] = { 1, // 0 : random mode 22, // 1 : start=22 23, 24, 25, 26, 27, 28, 29, 30, // 2- 9 : 1step 32, 34, 36, 38, 40, 42, 44, 46, // 10-17 : 2step 50, 54, 58, 62, 66, 70, 74, 78, // 18-25 : 4step 86, 94, 102,110,118,126 // 26-31 : 8step }; static const int16_t K1_table[] = { -24898, -25672, -26446, -27091, -27736, -28252, -28768, -29155, -29542, -29929, -30316, -30574, -30832, -30961, -31219, -31348, -31606, -31735, -31864, -31864, -31993, -32122, -32122, -32251, -32251, -32380, -32380, -32380, -32509, -32509, -32509, -32509, 24898, 23995, 22963, 21931, 20770, 19480, 18061, 16642, 15093, 13416, 11610, 9804, 7998, 6063, 3999, 1935, 0, -1935, -3999, -6063, -7998, -9804, -11610, -13416, -15093, -16642, -18061, -19480, -20770, -21931, -22963, -23995 }; static const int16_t K2_table[] = { 0, -3096, -6321, -9417, -12513, -15351, -18061, -20770, -23092, -25285, -27220, -28897, -30187, -31348, -32122, -32638, 0, 32638, 32122, 31348, 30187, 28897, 27220, 25285, 23092, 20770, 18061, 15351, 12513, 9417, 6321, 3096 }; static const int16_t K3_table[] = { 0, -3999, -8127, -12255, -16384, -20383, -24511, -28639, 32638, 28639, 24511, 20383, 16254, 12255, 8127, 3999 }; static const int16_t K5_table[] = { 0, -8127, -16384, -24511, 32638, 24511, 16254, 8127 }; int VLM5030::getBits(unsigned sbit, unsigned bits) { unsigned offset = address + (sbit / 8); unsigned data = rom[(offset + 0) & address_mask] + rom[(offset + 1) & address_mask] * 256; data >>= (sbit & 7); data &= (0xFF >> (8 - bits)); return data; } // get next frame int VLM5030::parseFrame() { // remember previous frame old_energy = new_energy; old_pitch = new_pitch; for (int i = 0; i <= 9; ++i) { old_k[i] = new_k[i]; } // command byte check byte cmd = rom[address & address_mask]; if (cmd & 0x01) { // extend frame new_energy = new_pitch = 0; for (int i = 0; i <= 9; ++i) { new_k[i] = 0; } ++address; if (cmd & 0x02) { // end of speech return 0; } else { // silent frame int nums = ((cmd >> 2) + 1) * 2; return nums * FR_SIZE; } } // pitch new_pitch = (pitchtable[getBits(1, 5)] + pitch_offset) & 0xff; // energy new_energy = energytable[getBits(6, 5)]; // 10 K's new_k[9] = K5_table[getBits(11, 3)]; new_k[8] = K5_table[getBits(14, 3)]; new_k[7] = K5_table[getBits(17, 3)]; new_k[6] = K5_table[getBits(20, 3)]; new_k[5] = K5_table[getBits(23, 3)]; new_k[4] = K5_table[getBits(26, 3)]; new_k[3] = K3_table[getBits(29, 4)]; new_k[2] = K3_table[getBits(33, 4)]; new_k[1] = K2_table[getBits(37, 5)]; new_k[0] = K1_table[getBits(42, 6)]; address += 6; return FR_SIZE; } // decode and buffering data void VLM5030::generateChannels(int** bufs, unsigned length) { // Single channel device: replace content of bufs[0] (not add to it). if (phase == PH_IDLE) { bufs[0] = nullptr; return; } int buf_count = 0; // running if (phase == PH_RUN || phase == PH_STOP) { // playing speech while (length > 0) { int current_val; // check new interpolator or new frame if (sample_count == 0) { if (phase == PH_STOP) { phase = PH_END; sample_count = 1; goto phase_stop; // continue to end phase } sample_count = frame_size; // interpolator changes if (interp_count == 0) { // change to new frame interp_count = parseFrame(); // with change phase if (interp_count == 0 ) { // end mark found interp_count = FR_SIZE; sample_count = frame_size; // end -> stop time phase = PH_STOP; } // Set old target as new start of frame current_energy = old_energy; current_pitch = old_pitch; for (int i = 0; i <= 9; ++i) { current_k[i] = old_k[i]; } // is this a zero energy frame? if (current_energy == 0) { target_energy = 0; target_pitch = current_pitch; for (int i = 0; i <= 9; ++i) { target_k[i] = current_k[i]; } } else { // normal frame target_energy = new_energy; target_pitch = new_pitch; for (int i = 0; i <= 9; ++i) { target_k[i] = new_k[i]; } } } // next interpolator // Update values based on step values 25%, 50%, 75%, 100% interp_count -= interp_step; // 3,2,1,0 -> 1,2,3,4 int interp_effect = FR_SIZE - (interp_count % FR_SIZE); current_energy = old_energy + (target_energy - old_energy) * interp_effect / FR_SIZE; if (old_pitch > 1) { current_pitch = old_pitch + (target_pitch - old_pitch) * interp_effect / FR_SIZE; } for (int i = 0; i <= 9; ++i) current_k[i] = old_k[i] + (target_k[i] - old_k[i]) * interp_effect / FR_SIZE; } // calcrate digital filter if (old_energy == 0) { // generate silent samples here current_val = 0x00; } else if (old_pitch <= 1) { // generate unvoiced samples here current_val = random_bool() ? int(current_energy) : -int(current_energy); } else { // generate voiced samples here current_val = (pitch_count == 0) ? current_energy : 0; } // Lattice filter here int u[11]; u[10] = current_val; for (int i = 9; i >= 0; --i) { u[i] = u[i + 1] - ((current_k[i] * x[i]) / 32768); } for (int i = 9; i >= 1; --i) { x[i] = x[i - 1] + ((current_k[i - 1] * u[i - 1]) / 32768); } x[0] = u[0]; // clipping, buffering bufs[0][buf_count] = Math::clip<-511, 511>(u[0]); ++buf_count; --sample_count; ++pitch_count; if (pitch_count >= current_pitch) { pitch_count = 0; } --length; } // return; } phase_stop: switch (phase) { case PH_SETUP: if (sample_count <= length) { sample_count = 0; // pin_BSY = true; phase = PH_WAIT; } else { sample_count -= length; } break; case PH_END: if (sample_count <= length) { sample_count = 0; pin_BSY = false; phase = PH_IDLE; } else { sample_count -= length; } } // silent buffering while (length > 0) { bufs[0][buf_count++] = 0; --length; } } int VLM5030::getAmplificationFactor() const { return 1 << (15 - 9); } // setup parameteroption when RST=H void VLM5030::setupParameter(byte param) { // latch parameter value parameter = param; // bit 0,1 : 4800bps / 9600bps , interporator step if (param & 2) { // bit 1 = 1 , 9600bps interp_step = 4; // 9600bps : no interporator } else if (param & 1) { // bit1 = 0 & bit0 = 1 , 4800bps interp_step = 2; // 4800bps : 2 interporator } else { // bit1 = bit0 = 0 : 2400bps interp_step = 1; // 2400bps : 4 interporator } // bit 3,4,5 : speed (frame size) frame_size = VLM5030_speed_table[(param >> 3) & 7]; // bit 6,7 : low / high pitch if (param & 0x80) { // bit7=1 , high pitch pitch_offset = -8; } else if (param & 0x40) { // bit6=1 , low pitch pitch_offset = 8; } else { pitch_offset = 0; } } void VLM5030::reset() { phase = PH_RESET; address = 0; vcu_addr_h = 0; pin_BSY = false; old_energy = old_pitch = 0; new_energy = new_pitch = 0; current_energy = current_pitch = 0; target_energy = target_pitch = 0; memset(old_k, 0, sizeof(old_k)); memset(new_k, 0, sizeof(new_k)); memset(current_k, 0, sizeof(current_k)); memset(target_k, 0, sizeof(target_k)); interp_count = sample_count = pitch_count = 0; memset(x, 0, sizeof(x)); // reset parameters setupParameter(0x00); } // get BSY pin level bool VLM5030::getBSY(EmuTime::param time) const { const_cast(this)->updateStream(time); return pin_BSY; } // latch control data void VLM5030::writeData(byte data) { latch_data = data; } void VLM5030::writeControl(byte data, EmuTime::param time) { updateStream(time); setRST((data & 0x01) != 0); setVCU((data & 0x04) != 0); setST ((data & 0x02) != 0); } // set RST pin level : reset / set table address A8-A15 void VLM5030::setRST(bool pin) { if (pin_RST) { if (!pin) { // H -> L : latch parameters pin_RST = false; setupParameter(latch_data); } } else { if (pin) { // L -> H : reset chip pin_RST = true; if (pin_BSY) { reset(); } } } } // set VCU pin level : ?? unknown void VLM5030::setVCU(bool pin) { // direct mode / indirect mode pin_VCU = pin; } // set ST pin level : set table address A0-A7 / start speech void VLM5030::setST(bool pin) { if (pin_ST == pin) { // pin level unchanged return; } if (!pin) { // H -> L pin_ST = false; if (pin_VCU) { // direct access mode & address High vcu_addr_h = (int(latch_data) << 8) + 0x01; } else { // check access mode if (vcu_addr_h) { // direct access mode address = (vcu_addr_h & 0xff00) + latch_data; vcu_addr_h = 0; } else { // indirect access mode int table = (latch_data & 0xfe) + ((int(latch_data) & 1) << 8); address = ((rom[(table + 0) & address_mask]) << 8) | rom[(table + 1) & address_mask]; } // reset process status sample_count = frame_size; interp_count = FR_SIZE; // clear filter // start after 3 sampling cycle phase = PH_RUN; } } else { // L -> H pin_ST = true; // setup speech, BSY on after 30ms? phase = PH_SETUP; sample_count = 1; // wait time for busy on pin_BSY = true; } } static XMLElement getRomConfig(const std::string& name, const std::string& romFilename) { XMLElement voiceROMconfig(name); voiceROMconfig.addAttribute("id", "name"); auto& romElement = voiceROMconfig.addChild("rom"); romElement.addChild( // load by sha1sum "sha1", "4f36d139ee4baa7d5980f765de9895570ee05f40"); romElement.addChild( // load by predefined filename in software rom's dir "filename", FileOperations::stripExtension(romFilename) + "_voice.rom"); romElement.addChild( // or hardcoded filename in ditto dir "filename", "keyboardmaster/voice.rom"); return voiceROMconfig; } VLM5030::VLM5030(const std::string& name, const std::string& desc, const std::string& romFilename, const DeviceConfig& config) : ResampledSoundDevice(config.getMotherBoard(), name, desc, 1) , rom(name + " ROM", "rom", DeviceConfig(config, getRomConfig(name, romFilename))) { // reset input pins pin_RST = pin_ST = pin_VCU = false; latch_data = 0; reset(); phase = PH_IDLE; address_mask = rom.getSize() - 1; const int CLOCK_FREQ = 3579545; float input = CLOCK_FREQ / 440.0f; setInputRate(int(input + 0.5f)); registerSound(config); } VLM5030::~VLM5030() { unregisterSound(); } template void VLM5030::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("address_mask", address_mask); ar.serialize("frame_size", frame_size); ar.serialize("pitch_offset", pitch_offset); ar.serialize("current_energy", current_energy); ar.serialize("current_pitch", current_pitch); ar.serialize("current_k", current_k); ar.serialize("x", x); ar.serialize("address", address); ar.serialize("vcu_addr_h", vcu_addr_h); ar.serialize("old_k", old_k); ar.serialize("new_k", new_k); ar.serialize("target_k", target_k); ar.serialize("old_energy", old_energy); ar.serialize("new_energy", new_energy); ar.serialize("target_energy", target_energy); ar.serialize("old_pitch", old_pitch); ar.serialize("new_pitch", new_pitch); ar.serialize("target_pitch", target_pitch); ar.serialize("interp_step", interp_step); ar.serialize("interp_count", interp_count); ar.serialize("sample_count", sample_count); ar.serialize("pitch_count", pitch_count); ar.serialize("latch_data", latch_data); ar.serialize("parameter", parameter); ar.serialize("phase", phase); ar.serialize("pin_BSY", pin_BSY); ar.serialize("pin_ST", pin_ST); ar.serialize("pin_VCU", pin_VCU); ar.serialize("pin_RST", pin_RST); } INSTANTIATE_SERIALIZE_METHODS(VLM5030); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/VLM5030.hh000066400000000000000000000034341257557151200176510ustar00rootroot00000000000000#ifndef VLM5030_HH #define VLM5030_HH #include "ResampledSoundDevice.hh" #include "Rom.hh" #include "EmuTime.hh" #include "openmsx.hh" #include namespace openmsx { class DeviceConfig; class VLM5030 final : public ResampledSoundDevice { public: VLM5030(const std::string& name, const std::string& desc, const std::string& romFilename, const DeviceConfig& config); ~VLM5030(); void reset(); /** latch control data */ void writeData(byte data); /** set RST / VCU / ST pins */ void writeControl(byte data, EmuTime::param time); /** get BSY pin level */ bool getBSY(EmuTime::param time) const; template void serialize(Archive& ar, unsigned version); private: void setRST(bool pin); void setVCU(bool pin); void setST (bool pin); // SoundDevice void generateChannels(int** bufs, unsigned num) override; int getAmplificationFactor() const override; void setupParameter(byte param); int getBits(unsigned sbit, unsigned bits); int parseFrame(); Rom rom; int address_mask; // state of option paramter int frame_size; int pitch_offset; // these contain data describing the current and previous voice frames // these are all used to contain the current state of the sound generation unsigned current_energy; unsigned current_pitch; int current_k[10]; int x[10]; word address; word vcu_addr_h; int16_t old_k[10]; int16_t new_k[10]; int16_t target_k[10]; word old_energy; word new_energy; word target_energy; byte old_pitch; byte new_pitch; byte target_pitch; byte interp_step; byte interp_count; // number of interp periods byte sample_count; // sample number within interp byte pitch_count; byte latch_data; byte parameter; byte phase; bool pin_BSY; bool pin_ST; bool pin_VCU; bool pin_RST; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/WavAudioInput.cc000066400000000000000000000042551257557151200214320ustar00rootroot00000000000000#include "WavAudioInput.hh" #include "CommandController.hh" #include "PlugException.hh" #include "CliComm.hh" #include "serialize.hh" using std::string; namespace openmsx { WavAudioInput::WavAudioInput(CommandController& commandController) : audioInputFilenameSetting( commandController, "audio-inputfilename", "filename of the file where the sampler reads data from", "audio-input.wav") , reference(EmuTime::zero) { audioInputFilenameSetting.attach(*this); } WavAudioInput::~WavAudioInput() { audioInputFilenameSetting.detach(*this); } void WavAudioInput::loadWave() { wav = WavData(audioInputFilenameSetting.getString().str(), 16, 0); } const string& WavAudioInput::getName() const { static const string name("wavinput"); return name; } string_ref WavAudioInput::getDescription() const { return "Read .wav files. Can for example be used as input for " "samplers."; } void WavAudioInput::plugHelper(Connector& /*connector*/, EmuTime::param time) { try { if (wav.getSize() == 0) { loadWave(); } } catch (MSXException& e) { throw PlugException("Load of wave file failed: " + e.getMessage()); } reference = time; } void WavAudioInput::unplugHelper(EmuTime::param /*time*/) { } void WavAudioInput::update(const Setting& setting) { (void)setting; assert(&setting == &audioInputFilenameSetting); try { if (isPluggedIn()) { loadWave(); } } catch (MSXException& e) { // TODO proper error handling, message should go to console setting.getCommandController().getCliComm().printWarning( "Load of wave file failed: " + e.getMessage()); } } short WavAudioInput::readSample(EmuTime::param time) { if (wav.getSize()) { unsigned pos = (time - reference).getTicksAt(wav.getFreq()); if (pos < wav.getSize()) { auto buf = static_cast(wav.getData()); return buf[pos]; } } return 0; } template void WavAudioInput::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("reference", reference); if (ar.isLoader()) { update(audioInputFilenameSetting); } } INSTANTIATE_SERIALIZE_METHODS(WavAudioInput); REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, WavAudioInput, "WavAudioInput"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/WavAudioInput.hh000066400000000000000000000016621257557151200214430ustar00rootroot00000000000000#ifndef WAVAUDIOINPUT_HH #define WAVAUDIOINPUT_HH #include "AudioInputDevice.hh" #include "FilenameSetting.hh" #include "WavData.hh" #include "Observer.hh" #include "EmuTime.hh" namespace openmsx { class CommandController; class WavAudioInput final : public AudioInputDevice, private Observer { public: explicit WavAudioInput(CommandController& commandController); ~WavAudioInput(); // AudioInputDevice const std::string& getName() const override; string_ref getDescription() const override; void plugHelper(Connector& connector, EmuTime::param time) override; void unplugHelper(EmuTime::param time) override; short readSample(EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: void loadWave(); void update(const Setting& setting) override; FilenameSetting audioInputFilenameSetting; WavData wav; EmuTime reference; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/WavData.cc000066400000000000000000000026261257557151200202220ustar00rootroot00000000000000#include "WavData.hh" #include "MSXException.hh" #include "StringOp.hh" #include #include using std::string; namespace openmsx { static inline bool is8Bit(Uint16 format) { return (format == AUDIO_U8) || (format == AUDIO_S8); } WavData::WavData(const string& filename, unsigned wantedBits, unsigned wantedFreq) { SDL_AudioSpec wavSpec; Uint8* wavBuf; Uint32 wavLen; if (!SDL_LoadWAV(filename.c_str(), &wavSpec, &wavBuf, &wavLen)) { throw MSXException(StringOp::Builder() << "WavData error: " << SDL_GetError()); } freq = (wantedFreq == 0) ? unsigned(wavSpec.freq) : wantedFreq; bits = (wantedBits == 0) ? (is8Bit(wavSpec.format) ? 8 : 16) : wantedBits; channels = wavSpec.channels; assert((bits == 8) || (bits == 16)); Uint16 format = (bits == 8) ? AUDIO_U8 : AUDIO_S16SYS; SDL_AudioCVT audioCVT; if (SDL_BuildAudioCVT(&audioCVT, wavSpec.format, wavSpec.channels, wavSpec.freq, format, 1, freq) == -1) { SDL_FreeWAV(wavBuf); throw MSXException("Couldn't build wav converter"); } buffer.resize(wavLen * audioCVT.len_mult); memcpy(buffer.data(), wavBuf, wavLen); SDL_FreeWAV(wavBuf); audioCVT.buf = buffer.data(); audioCVT.len = wavLen; if (SDL_ConvertAudio(&audioCVT) == -1) { throw MSXException("Couldn't convert wav file to internal format"); } length = unsigned(audioCVT.len * audioCVT.len_ratio) / 2; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/WavData.hh000066400000000000000000000026161257557151200202330ustar00rootroot00000000000000#ifndef WAVDATA_HH #define WAVDATA_HH #include "MemBuffer.hh" #include "openmsx.hh" #include namespace openmsx { class WavData { public: /** Construct empty wav. */ WavData() : length(0) {} /** Construct from .wav file, optionally convert to a specific * bit-depth and sample rate. */ WavData(const std::string& filename, unsigned bits = 0, unsigned freq = 0); // Move constructor/assignment // Normally these are auto-generated, but vs2013 isn't fully c++11 // compliant yet. WavData(WavData&& other) : buffer(std::move(other.buffer)) , bits(other.bits) , freq(other.freq) , length(other.length) , channels(other.channels) {} WavData& operator=(WavData&& other) { buffer = std::move(other.buffer); bits = other.bits; freq = other.freq; length = other.length; channels = other.channels; return *this; } unsigned getFreq() const { return freq; } unsigned getBits() const { return bits; } unsigned getSize() const { return length; } unsigned getChannels() const { return channels; } const void* getData() const { return buffer.data(); } private: #if defined(_MSC_VER) // Make non-copyable/assignable // work around limitation in vs2013, see comment in MemBuffer. WavData(const WavData&); WavData& operator=(const WavData&); #endif MemBuffer buffer; unsigned bits; unsigned freq; unsigned length; unsigned channels; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/WavWriter.cc000066400000000000000000000073341257557151200206260ustar00rootroot00000000000000#include "WavWriter.hh" #include "MSXException.hh" #include "Math.hh" #include "vla.hh" #include "endian.hh" #include #include namespace openmsx { WavWriter::WavWriter(const Filename& filename, unsigned channels, unsigned bits, unsigned frequency) : file(filename, "wb") , bytes(0) { // write wav header struct WavHeader { char chunkID[4]; // + 0 'RIFF' Endian::L32 chunkSize; // + 4 total size char format[4]; // + 8 'WAVE' char subChunk1ID[4]; // +12 'fmt ' Endian::L32 subChunk1Size; // +16 = 16 (fixed) Endian::L16 audioFormat; // +20 = 1 (fixed) Endian::L16 numChannels; // +22 Endian::L32 sampleRate; // +24 Endian::L32 byteRate; // +28 Endian::L16 blockAlign; // +32 Endian::L16 bitsPerSample; // +34 char subChunk2ID[4]; // +36 'data' Endian::L32 subChunk2Size; // +40 } header; memcpy(header.chunkID, "RIFF", sizeof(header.chunkID)); header.chunkSize = 0; // actual value filled in later memcpy(header.format, "WAVE", sizeof(header.format)); memcpy(header.subChunk1ID, "fmt ", sizeof(header.subChunk1ID)); header.subChunk1Size = 16; header.audioFormat = 1; header.numChannels = channels; header.sampleRate = frequency; header.byteRate = (channels * frequency * bits) / 8; header.blockAlign = (channels * bits) / 8; header.bitsPerSample = bits; memcpy(header.subChunk2ID, "data", sizeof(header.subChunk2ID)); header.subChunk2Size = 0; // actaul value filled in later file.write(&header, sizeof(header)); } WavWriter::~WavWriter() { try { // data chunk must have an even number of bytes if (bytes & 1) { uint8_t pad = 0; file.write(&pad, 1); } flush(); // write header } catch (MSXException&) { // ignore, can't throw from destructor } } void WavWriter::flush() { // TODO For now (before C++11) this needs separate definition and // initialization. See comments in Endian::EndianT for details. Endian::L32 totalSize, wavSize; totalSize = (bytes + 44 - 8 + 1) & ~1; // round up to even number wavSize = bytes; file.seek(4); file.write(&totalSize, 4); file.seek(40); file.write(&wavSize, 4); file.seek(file.getSize()); // SEEK_END file.flush(); } void Wav8Writer::write(const uint8_t* buffer, unsigned samples) { file.write(buffer, samples); bytes += samples; } void Wav16Writer::write(const int16_t* buffer, unsigned samples) { unsigned size = sizeof(int16_t) * samples; if (OPENMSX_BIGENDIAN) { // Variable length arrays (VLA) are part of c99 but not of c++ // (not even c++11). Some compilers (like gcc) do support VLA // in c++ mode, others (like VC++) don't. Still other compilers // (like clang) only support VLA for POD types. // To side-step this issue we simply use a std::vector, this // code is anyway not performance critical. //VLA(Endian::L16, buf, samples); // doesn't work in clang //std::vector buf(buffer, buffer + samples); // this needs c++11 std::vector buf(samples); for (unsigned i = 0; i < samples; ++i) { buf[i] = buffer[i]; } file.write(buf.data(), size); } else { file.write(buffer, size); } bytes += size; } void Wav16Writer::write(const int* buffer, unsigned samples, int amp) { //VLA(Endian::L16, buf, samples); // doesn't work in clang std::vector buf(samples); for (unsigned i = 0; i < samples; ++i) { buf[i] = Math::clipIntToShort(buffer[i] * amp); } unsigned size = sizeof(int16_t) * samples; file.write(buf.data(), size); bytes += size; } void Wav16Writer::writeSilence(unsigned samples) { VLA(int16_t, buf, samples); unsigned size = sizeof(int16_t) * samples; memset(buf, 0, size); file.write(buf, size); bytes += size; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/WavWriter.hh000066400000000000000000000036641257557151200206420ustar00rootroot00000000000000#ifndef WAVWRITER_HH #define WAVWRITER_HH #include "File.hh" #include "noncopyable.hh" #include #include namespace openmsx { class Filename; /** Base class for writing WAV files. */ class WavWriter : private noncopyable { public: /** Returns false if there has been data written to the wav image. */ bool isEmpty() const { return bytes == 0; } /** Flush data to file and update header. Try to make (possibly) * incomplete file already usable for external programs. */ void flush(); protected: WavWriter(const Filename& filename, unsigned channels, unsigned bits, unsigned frequency); ~WavWriter(); File file; unsigned bytes; }; /** Writes 8-bit WAV files. */ class Wav8Writer : public WavWriter { public: Wav8Writer(const Filename& filename, unsigned channels, unsigned frequency) : WavWriter(filename, channels, 8, frequency) {} void write(const uint8_t* buffer, unsigned stereo, unsigned samples) { assert(stereo == 1 || stereo == 2); write(buffer, stereo * samples); } private: void write(const uint8_t* buffer, unsigned samples); }; /** Writes 16-bit WAV files. */ class Wav16Writer : public WavWriter { public: Wav16Writer(const Filename& filename, unsigned channels, unsigned frequency) : WavWriter(filename, channels, 16, frequency) {} void write(const int16_t* buffer, unsigned stereo, unsigned samples) { assert(stereo == 1 || stereo == 2); write(buffer, stereo * samples); } void write(const int* buffer, unsigned stereo, unsigned samples, int amp = 1) { assert(stereo == 1 || stereo == 2); write(buffer, stereo * samples, amp); } void writeSilence(unsigned stereo, unsigned samples) { assert(stereo == 1 || stereo == 2); writeSilence(stereo * samples); } private: void write(const int16_t* buffer, unsigned samples); void write(const int* buffer, unsigned samples, int amp); void writeSilence(unsigned samples); }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/Y8950.cc000066400000000000000000001007511257557151200174270ustar00rootroot00000000000000/* * Based on: * emu8950.c -- Y8950 emulator written by Mitsutaka Okazaki 2001 * heavily rewritten to fit openMSX structure */ #include "Y8950.hh" #include "Y8950Periphery.hh" #include "MSXAudio.hh" #include "DeviceConfig.hh" #include "MSXMotherBoard.hh" #include "Math.hh" #include "outer.hh" #include "serialize.hh" #include #include namespace openmsx { static const unsigned EG_MUTE = 1 << Y8950::EG_BITS; static const Y8950::EnvPhaseIndex EG_DP_MAX = Y8950::EnvPhaseIndex(EG_MUTE); static const unsigned MOD = 0; static const unsigned CAR = 1; static const float EG_STEP = 0.1875f; // 3/16 static const float SL_STEP = 3.0f; static const float TL_STEP = 0.75f; // 12/16 static const float DB_STEP = 0.1875f; // 3/16 static const unsigned SL_PER_EG = 16; // SL_STEP / EG_STEP static const unsigned TL_PER_EG = 4; // TL_STEP / EG_STEP static const unsigned EG_PER_DB = 1; // EG_STEP / DB_STEP // PM speed(Hz) and depth(cent) static const float PM_SPEED = 6.4f; static const float PM_DEPTH = 13.75f / 2; static const float PM_DEPTH2 = 13.75f; // Dynamic range of sustine level static const int SL_BITS = 4; static const int SL_MUTE = 1 << SL_BITS; // Size of Sintable ( 1 -- 18 can be used, but 7 -- 14 recommended.) static const int PG_BITS = 10; static const int PG_WIDTH = 1 << PG_BITS; static const int PG_MASK = PG_WIDTH - 1; // Phase increment counter static const int DP_BITS = 19; static const int DP_BASE_BITS = DP_BITS - PG_BITS; // WaveTable for each envelope amp. // values are in range[ 0, DB_MUTE) (for positive values) // or [2*DB_MUTE, 3*DB_MUTE) (for negative values) static unsigned sintable[PG_WIDTH]; // Phase incr table for Attack. static Y8950::EnvPhaseIndex dphaseARTable[16][16]; // Phase incr table for Decay and Release. static Y8950::EnvPhaseIndex dphaseDRTable[16][16]; // TL Table. static int tllTable[16 * 8][4]; // Linear to Log curve conversion table (for Attack rate) and vice versa. // values are in the range [0 .. EG_MUTE] static unsigned AR_ADJUST_TABLE[EG_MUTE]; static unsigned RA_ADJUST_TABLE[EG_MUTE + 1]; // Dynamic range static const int DB_BITS = 9; static const int DB_MUTE = 1 << DB_BITS; // PM table is calcurated by PM_AMP * exp2(PM_DEPTH * sin(x) / 1200) static const int PM_AMP_BITS = 8; static const int PM_AMP = 1 << PM_AMP_BITS; // Bits for liner value static const int DB2LIN_AMP_BITS = 11; static const int SLOT_AMP_BITS = DB2LIN_AMP_BITS; // Bits for Pitch and Amp modulator static const int PM_PG_BITS = 8; static const int PM_PG_WIDTH = 1 << PM_PG_BITS; static const int PM_DP_BITS = 16; static const int PM_DP_WIDTH = 1 << PM_DP_BITS; static const int AM_PG_BITS = 8; static const int AM_PG_WIDTH = 1 << AM_PG_BITS; static const int AM_DP_BITS = 16; static const int AM_DP_WIDTH = 1 << AM_DP_BITS; // LFO Table static const unsigned PM_DPHASE = unsigned(PM_SPEED * PM_DP_WIDTH / (Y8950::CLOCK_FREQ / float(Y8950::CLOCK_FREQ_DIV))); static int pmtable[2][PM_PG_WIDTH]; // dB to Liner table static int dB2LinTab[(2 * DB_MUTE) * 2]; // LFO Amplitude Modulation table (verified on real YM3812) // 27 output levels (triangle waveform); // 1 level takes one of: 192, 256 or 448 samples // // Length: 210 elements. // Each of the elements has to be repeated // exactly 64 times (on 64 consecutive samples). // The whole table takes: 64 * 210 = 13440 samples. // // Verified on real YM3812 (OPL2), but I believe it's the same for Y8950 // because it closely matches the Y8950 AM parameters: // speed = 3.7Hz // depth = 4.875dB // Also this approch can be easily implemented in HW, the previous one (see SVN // history) could not. static const unsigned LFO_AM_TAB_ELEMENTS = 210; static const byte lfo_am_table[LFO_AM_TAB_ELEMENTS] = { 0,0,0,0,0,0,0, 1,1,1,1, 2,2,2,2, 3,3,3,3, 4,4,4,4, 5,5,5,5, 6,6,6,6, 7,7,7,7, 8,8,8,8, 9,9,9,9, 10,10,10,10, 11,11,11,11, 12,12,12,12, 13,13,13,13, 14,14,14,14, 15,15,15,15, 16,16,16,16, 17,17,17,17, 18,18,18,18, 19,19,19,19, 20,20,20,20, 21,21,21,21, 22,22,22,22, 23,23,23,23, 24,24,24,24, 25,25,25,25, 26,26,26, 25,25,25,25, 24,24,24,24, 23,23,23,23, 22,22,22,22, 21,21,21,21, 20,20,20,20, 19,19,19,19, 18,18,18,18, 17,17,17,17, 16,16,16,16, 15,15,15,15, 14,14,14,14, 13,13,13,13, 12,12,12,12, 11,11,11,11, 10,10,10,10, 9,9,9,9, 8,8,8,8, 7,7,7,7, 6,6,6,6, 5,5,5,5, 4,4,4,4, 3,3,3,3, 2,2,2,2, 1,1,1,1 }; //**************************************************// // // // Helper functions // // // //**************************************************// static inline unsigned DB_POS(int x) { int result = int(x / DB_STEP); assert(result < DB_MUTE); assert(result >= 0); return result; } static inline unsigned DB_NEG(int x) { return 2 * DB_MUTE + DB_POS(x); } //**************************************************// // // // Create tables // // // //**************************************************// // Return log_BASE(x) IOW the value y so that pow(BASE, y) == x. template static inline float log(float x) { return ::logf(x) / ::logf(float(BASE)); } // Table for AR to LogCurve and vice versa. static void makeAdjustTable() { AR_ADJUST_TABLE[0] = EG_MUTE; RA_ADJUST_TABLE[0] = EG_MUTE; for (int i = 1; i < int(EG_MUTE); ++i) { AR_ADJUST_TABLE[i] = (EG_MUTE - 1 - EG_MUTE * log(i)) / 2; RA_ADJUST_TABLE[i] = powf(EG_MUTE, float(EG_MUTE - 1 - 2 * i) / EG_MUTE); assert(0 <= int(AR_ADJUST_TABLE[i])); assert(0 <= int(RA_ADJUST_TABLE[i])); assert(AR_ADJUST_TABLE[i] <= EG_MUTE); assert(RA_ADJUST_TABLE[i] <= EG_MUTE); } RA_ADJUST_TABLE[EG_MUTE] = 0; // AR_ADJUST_TABLE[] and RA_ADJUST_TABLE[] are each others inverse, IOW // RA_ADJUST_TABLE[AR_ADJUST_TABLE[x]] == x // (except for rounding errors). } // Table for dB(0 -- (1<= 0); assert(result <= DB_MUTE - 1); return result; } // Sin Table static void makeSinTable() { for (int i = 0; i < PG_WIDTH / 4; ++i) { sintable[i] = lin2db(sinf(float(2.0 * M_PI) * i / PG_WIDTH)); } for (int i = 0; i < PG_WIDTH / 4; i++) { sintable[PG_WIDTH / 2 - 1 - i] = sintable[i]; } for (int i = 0; i < PG_WIDTH / 2; i++) { sintable[PG_WIDTH / 2 + i] = 2 * DB_MUTE + sintable[i]; } } // Table for Pitch Modulator static void makePmTable() { for (int i = 0; i < PM_PG_WIDTH; ++i) { pmtable[0][i] = int(float(PM_AMP) * exp2f(float(PM_DEPTH) * sinf(float(2.0 * M_PI) * i / PM_PG_WIDTH) / 1200)); pmtable[1][i] = int(float(PM_AMP) * exp2f(float(PM_DEPTH2) * sinf(float(2.0 * M_PI) * i / PM_PG_WIDTH) / 1200)); } } static void makeTllTable() { // Processed version of Table 3.5 from the Application Manual static const unsigned kltable[16] = { 0, 24, 32, 37, 40, 43, 45, 47, 48, 50, 51, 52, 53, 54, 55, 56 }; // This is indeed {0.0, 3.0, 1.5, 6.0} dB/oct, verified on real Y8950. // Note the illogical order of 2nd and 3rd element. static const unsigned shift[4] = { 31, 1, 2, 0 }; for (unsigned freq = 0; freq < 16 * 8; ++freq) { unsigned fnum = freq % 16; unsigned block = freq / 16; int tmp = 4 * kltable[fnum] - 32 * (7 - block); for (unsigned KL = 0; KL < 4; ++KL) { tllTable[freq][KL] = (tmp <= 0) ? 0 : (tmp >> shift[KL]); } } } // Rate Table for Attack static void makeDphaseARTable() { for (unsigned Rks = 0; Rks < 16; ++Rks) { dphaseARTable[Rks][0] = Y8950::EnvPhaseIndex(0); for (unsigned AR = 1; AR < 15; ++AR) { unsigned RM = std::min(AR + (Rks >> 2), 15u); unsigned RL = Rks & 3; dphaseARTable[Rks][AR] = Y8950::EnvPhaseIndex(12 * (RL + 4)) >> (15 - RM); } dphaseARTable[Rks][15] = EG_DP_MAX; } } // Rate Table for Decay static void makeDphaseDRTable() { for (unsigned Rks = 0; Rks < 16; ++Rks) { dphaseDRTable[Rks][0] = Y8950::EnvPhaseIndex(0); for (unsigned DR = 1; DR < 16; ++DR) { unsigned RM = std::min(DR + (Rks >> 2), 15u); unsigned RL = Rks & 3; dphaseDRTable[Rks][DR] = Y8950::EnvPhaseIndex(RL + 4) >> (15 - RM); } } } // class Y8950::Patch Y8950::Patch::Patch() { reset(); } void Y8950::Patch::reset() { AM = false; PM = false; EG = false; ML = 0; KL = 0; TL = 0; AR = 0; DR = 0; SL = 0; RR = 0; setKeyScaleRate(false); setFeedbackShift(0); } // class Y8950::Slot void Y8950::Slot::reset() { phase = 0; output = 0; feedback = 0; eg_mode = FINISH; eg_phase = EG_DP_MAX; key = 0; patch.reset(); // this initializes: // dphase, tll, dphaseARTableRks, dphaseDRTableRks, eg_dphase updateAll(0); } void Y8950::Slot::updatePG(unsigned freq) { static const int mltable[16] = { 1, 1*2, 2*2, 3*2, 4*2, 5*2, 6*2 , 7*2, 8*2, 9*2, 10*2, 10*2, 12*2, 12*2, 15*2, 15*2 }; unsigned fnum = freq % 1024; unsigned block = freq / 1024; dphase = ((fnum * mltable[patch.ML]) << block) >> (21 - DP_BITS); } void Y8950::Slot::updateTLL(unsigned freq) { tll = tllTable[freq >> 6][patch.KL] + patch.TL * TL_PER_EG; } void Y8950::Slot::updateRKS(unsigned freq) { unsigned rks = freq >> patch.KR; assert(rks < 16); dphaseARTableRks = dphaseARTable[rks]; dphaseDRTableRks = dphaseDRTable[rks]; } void Y8950::Slot::updateEG() { switch (eg_mode) { case ATTACK: eg_dphase = dphaseARTableRks[patch.AR]; break; case DECAY: eg_dphase = dphaseDRTableRks[patch.DR]; break; case SUSTAIN: case RELEASE: eg_dphase = dphaseDRTableRks[patch.RR]; break; case FINISH: eg_dphase = Y8950::EnvPhaseIndex(0); break; } } void Y8950::Slot::updateAll(unsigned freq) { updatePG(freq); updateTLL(freq); updateRKS(freq); updateEG(); // EG should be last } bool Y8950::Slot::isActive() const { return eg_mode != FINISH; } // Slot key on void Y8950::Slot::slotOn(KeyPart part) { if (!key) { eg_mode = ATTACK; phase = 0; eg_phase = Y8950::EnvPhaseIndex(RA_ADJUST_TABLE[eg_phase.toInt()]); } key |= part; } // Slot key off void Y8950::Slot::slotOff(KeyPart part) { if (key) { key &= ~part; if (!key) { if (eg_mode == ATTACK) { eg_phase = Y8950::EnvPhaseIndex(AR_ADJUST_TABLE[eg_phase.toInt()]); } eg_mode = RELEASE; } } } // class Y8950::Channel Y8950::Channel::Channel() { reset(); } void Y8950::Channel::reset() { setFreq(0); slot[MOD].reset(); slot[CAR].reset(); alg = false; } // Set frequency (combined F-Number (10bit) and Block (3bit)) void Y8950::Channel::setFreq(unsigned freq_) { freq = freq_; } void Y8950::Channel::keyOn(KeyPart part) { slot[MOD].slotOn(part); slot[CAR].slotOn(part); } void Y8950::Channel::keyOff(KeyPart part) { slot[MOD].slotOff(part); slot[CAR].slotOff(part); } Y8950::Y8950(const std::string& name, const DeviceConfig& config, unsigned sampleRam, EmuTime::param time, MSXAudio& audio) : ResampledSoundDevice(config.getMotherBoard(), name, "MSX-AUDIO", 9 + 5 + 1) , motherBoard(config.getMotherBoard()) , periphery(audio.createPeriphery(getName())) , adpcm(*this, config, name, sampleRam) , connector(motherBoard.getPluggingController()) , dac13(name + " DAC", "MSX-AUDIO 13-bit DAC", config) , debuggable(motherBoard, getName()) , timer1(EmuTimer::createOPL3_1(motherBoard.getScheduler(), *this)) , timer2(EmuTimer::createOPL3_2(motherBoard.getScheduler(), *this)) , irq(motherBoard, getName() + ".IRQ") , enabled(true) { makePmTable(); makeAdjustTable(); makeDB2LinTable(); makeTllTable(); makeSinTable(); makeDphaseARTable(); makeDphaseDRTable(); float input = Y8950::CLOCK_FREQ / float(Y8950::CLOCK_FREQ_DIV); setInputRate(int(input + 0.5f)); reset(time); registerSound(config); } Y8950::~Y8950() { unregisterSound(); } void Y8950::clearRam() { adpcm.clearRam(); } // Reset whole of opl except patch datas. void Y8950::reset(EmuTime::param time) { for (auto& c : ch) c.reset(); rythm_mode = false; am_mode = false; pm_mode = false; pm_phase = 0; am_phase = 0; noise_seed = 0xffff; noiseA_phase = 0; noiseB_phase = 0; noiseA_dphase = 0; noiseB_dphase = 0; // update the output buffer before changing the register updateStream(time); for (auto& r : reg) r = 0x00; reg[0x04] = 0x18; reg[0x19] = 0x0F; // fixes 'Thunderbirds are Go' status = 0x00; statusMask = 0; irq.reset(); adpcm.reset(time); } // Drum key on void Y8950::keyOn_BD() { ch[6].keyOn(KEY_RHYTHM); } void Y8950::keyOn_HH() { ch[7].slot[MOD].slotOn(KEY_RHYTHM); } void Y8950::keyOn_SD() { ch[7].slot[CAR].slotOn(KEY_RHYTHM); } void Y8950::keyOn_TOM() { ch[8].slot[MOD].slotOn(KEY_RHYTHM); } void Y8950::keyOn_CYM() { ch[8].slot[CAR].slotOn(KEY_RHYTHM); } // Drum key off void Y8950::keyOff_BD() { ch[6].keyOff(KEY_RHYTHM); } void Y8950::keyOff_HH() { ch[7].slot[MOD].slotOff(KEY_RHYTHM); } void Y8950::keyOff_SD() { ch[7].slot[CAR].slotOff(KEY_RHYTHM); } void Y8950::keyOff_TOM(){ ch[8].slot[MOD].slotOff(KEY_RHYTHM); } void Y8950::keyOff_CYM(){ ch[8].slot[CAR].slotOff(KEY_RHYTHM); } // Change Rhythm Mode void Y8950::setRythmMode(int data) { bool newMode = (data & 32) != 0; if (rythm_mode != newMode) { rythm_mode = newMode; if (!rythm_mode) { // ON->OFF keyOff_BD(); // TODO keyOff() or immediately to FINISH? keyOff_HH(); // other variants use keyOff(), but keyOff_SD(); // verify on real HW keyOff_TOM(); keyOff_CYM(); } } } // recalculate 'key' from register settings void Y8950::update_key_status() { for (unsigned i = 0; i < 9; ++i) { int main = (reg[0xb0 + i] & 0x20) ? KEY_MAIN : 0; ch[i].slot[MOD].key = main; ch[i].slot[CAR].key = main; } if (rythm_mode) { ch[6].slot[MOD].key |= (reg[0xbd] & 0x10) ? KEY_RHYTHM : 0; // BD1 ch[6].slot[CAR].key |= (reg[0xbd] & 0x10) ? KEY_RHYTHM : 0; // BD2 ch[7].slot[MOD].key |= (reg[0xbd] & 0x01) ? KEY_RHYTHM : 0; // HH ch[7].slot[CAR].key |= (reg[0xbd] & 0x08) ? KEY_RHYTHM : 0; // SD ch[8].slot[MOD].key |= (reg[0xbd] & 0x04) ? KEY_RHYTHM : 0; // TOM ch[8].slot[CAR].key |= (reg[0xbd] & 0x02) ? KEY_RHYTHM : 0; // CYM } } // // Generate wave data // // Convert Amp(0 to EG_HEIGHT) to Phase(0 to 8PI). static inline int wave2_8pi(int e) { int shift = SLOT_AMP_BITS - PG_BITS - 2; return (shift > 0) ? (e >> shift) : (e << -shift); } unsigned Y8950::Slot::calc_phase(int lfo_pm) { if (patch.PM) { phase += (dphase * lfo_pm) >> PM_AMP_BITS; } else { phase += dphase; } return phase >> DP_BASE_BITS; } #define S2E(x) Y8950::EnvPhaseIndex(int(x / EG_STEP)) static const Y8950::EnvPhaseIndex SL[16] = { S2E( 0), S2E( 3), S2E( 6), S2E( 9), S2E(12), S2E(15), S2E(18), S2E(21), S2E(24), S2E(27), S2E(30), S2E(33), S2E(36), S2E(39), S2E(42), S2E(93) }; unsigned Y8950::Slot::calc_envelope(int lfo_am) { unsigned egout = 0; switch (eg_mode) { case ATTACK: eg_phase += eg_dphase; if (eg_phase >= EG_DP_MAX) { egout = 0; eg_phase = Y8950::EnvPhaseIndex(0); eg_mode = DECAY; updateEG(); } else { egout = AR_ADJUST_TABLE[eg_phase.toInt()]; } break; case DECAY: eg_phase += eg_dphase; if (eg_phase >= SL[patch.SL]) { eg_phase = SL[patch.SL]; eg_mode = SUSTAIN; updateEG(); } egout = eg_phase.toInt(); break; case SUSTAIN: if (!patch.EG) { eg_phase += eg_dphase; } egout = eg_phase.toInt(); if (egout >= EG_MUTE) { eg_phase = EG_DP_MAX; eg_mode = FINISH; egout = EG_MUTE - 1; } break; case RELEASE: eg_phase += eg_dphase; egout = eg_phase.toInt(); if (egout >= EG_MUTE) { eg_phase = EG_DP_MAX; eg_mode = FINISH; egout = EG_MUTE - 1; } break; case FINISH: egout = EG_MUTE - 1; break; } egout = ((egout + tll) * EG_PER_DB); if (patch.AM) { egout += lfo_am; } return std::min(egout, DB_MUTE - 1); } int Y8950::Slot::calc_slot_car(int lfo_pm, int lfo_am, int fm) { unsigned egout = calc_envelope(lfo_am); int pgout = calc_phase(lfo_pm) + wave2_8pi(fm); return dB2LinTab[sintable[pgout & PG_MASK] + egout]; } int Y8950::Slot::calc_slot_mod(int lfo_pm, int lfo_am) { unsigned egout = calc_envelope(lfo_am); unsigned pgout = calc_phase(lfo_pm); if (patch.FB != 0) { pgout += wave2_8pi(feedback) >> patch.FB; } int newOutput = dB2LinTab[sintable[pgout & PG_MASK] + egout]; feedback = (output + newOutput) >> 1; output = newOutput; return feedback; } int Y8950::Slot::calc_slot_tom(int lfo_pm, int lfo_am) { unsigned egout = calc_envelope(lfo_am); unsigned pgout = calc_phase(lfo_pm); return dB2LinTab[sintable[pgout & PG_MASK] + egout]; } int Y8950::Slot::calc_slot_snare(int lfo_pm, int lfo_am, int whitenoise) { unsigned egout = calc_envelope(lfo_am); unsigned pgout = calc_phase(lfo_pm); unsigned tmp = (pgout & (1 << (PG_BITS - 1))) ? 0 : 2 * DB_MUTE; return (dB2LinTab[tmp + egout] + dB2LinTab[egout + whitenoise]) >> 1; } int Y8950::Slot::calc_slot_cym(int lfo_am, int a, int b) { unsigned egout = calc_envelope(lfo_am); return (dB2LinTab[egout + a] + dB2LinTab[egout + b]) >> 1; } // HI-HAT int Y8950::Slot::calc_slot_hat(int lfo_am, int a, int b, int whitenoise) { unsigned egout = calc_envelope(lfo_am); return (dB2LinTab[egout + whitenoise] + dB2LinTab[egout + a] + dB2LinTab[egout + b]) >> 2; } int Y8950::getAmplificationFactor() const { return 1 << (15 - DB2LIN_AMP_BITS); } void Y8950::setEnabled(bool enabled_, EmuTime::param time) { updateStream(time); enabled = enabled_; } bool Y8950::checkMuteHelper() { if (!enabled) { return true; } for (int i = 0; i < 6; ++i) { if (ch[i].slot[CAR].isActive()) return false; } if (!rythm_mode) { for(int i = 6; i < 9; ++i) { if (ch[i].slot[CAR].isActive()) return false; } } else { if (ch[6].slot[CAR].isActive()) return false; if (ch[7].slot[MOD].isActive()) return false; if (ch[7].slot[CAR].isActive()) return false; if (ch[8].slot[MOD].isActive()) return false; if (ch[8].slot[CAR].isActive()) return false; } return adpcm.isMuted(); } void Y8950::generateChannels(int** bufs, unsigned num) { // TODO implement per-channel mute (instead of all-or-nothing) if (checkMuteHelper()) { // TODO update internal state even when muted // during mute pm_phase, am_phase, noiseA_phase, noiseB_phase // and noise_seed aren't updated, probably ok for (int i = 0; i < 9 + 5 + 1; ++i) { bufs[i] = nullptr; } return; } for (unsigned sample = 0; sample < num; ++sample) { // Amplitude modulation: 27 output levels (triangle waveform); // 1 level takes one of: 192, 256 or 448 samples // One entry from LFO_AM_TABLE lasts for 64 samples // lfo_am_table is 210 elements long ++am_phase; if (am_phase == (LFO_AM_TAB_ELEMENTS * 64)) am_phase = 0; unsigned tmp = lfo_am_table[am_phase / 64]; int lfo_am = am_mode ? tmp : tmp / 4; pm_phase = (pm_phase + PM_DPHASE) & (PM_DP_WIDTH - 1); int lfo_pm = pmtable[pm_mode][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; if (noise_seed & 1) { noise_seed ^= 0x24000; } noise_seed >>= 1; int whitenoise = noise_seed & 1 ? DB_POS(6) : DB_NEG(6); noiseA_phase += noiseA_dphase; noiseA_phase &= (0x40 << 11) - 1; if ((noiseA_phase >> 11) == 0x3f) { noiseA_phase = 0; } int noiseA = noiseA_phase & (0x03 << 11) ? DB_POS(6) : DB_NEG(6); noiseB_phase += noiseB_dphase; noiseB_phase &= (0x10 << 11) - 1; int noiseB = noiseB_phase & (0x0A << 11) ? DB_POS(6) : DB_NEG(6); int m = rythm_mode ? 6 : 9; for (int i = 0; i < m; ++i) { if (ch[i].slot[CAR].isActive()) { bufs[i][sample] += ch[i].alg ? ch[i].slot[CAR].calc_slot_car(lfo_pm, lfo_am, 0) + ch[i].slot[MOD].calc_slot_mod(lfo_pm, lfo_am) : ch[i].slot[CAR].calc_slot_car(lfo_pm, lfo_am, ch[i].slot[MOD].calc_slot_mod(lfo_pm, lfo_am)); } else { //bufs[i][sample] += 0; } } if (rythm_mode) { //bufs[6][sample] += 0; //bufs[7][sample] += 0; //bufs[8][sample] += 0; // TODO wasn't in original source either ch[7].slot[MOD].calc_phase(lfo_pm); ch[8].slot[CAR].calc_phase(lfo_pm); bufs[ 9][sample] += (ch[6].slot[CAR].isActive()) ? 2 * ch[6].slot[CAR].calc_slot_car(lfo_pm, lfo_am, ch[6].slot[MOD].calc_slot_mod(lfo_pm, lfo_am)) : 0; bufs[10][sample] += (ch[7].slot[CAR].isActive()) ? 2 * ch[7].slot[CAR].calc_slot_snare(lfo_pm, lfo_am, whitenoise) : 0; bufs[11][sample] += (ch[8].slot[CAR].isActive()) ? 2 * ch[8].slot[CAR].calc_slot_cym(lfo_am, noiseA, noiseB) : 0; bufs[12][sample] += (ch[7].slot[MOD].isActive()) ? 2 * ch[7].slot[MOD].calc_slot_hat(lfo_am, noiseA, noiseB, whitenoise) : 0; bufs[13][sample] += (ch[8].slot[MOD].isActive()) ? 2 * ch[8].slot[MOD].calc_slot_tom(lfo_pm, lfo_am) : 0; } else { //bufs[ 9] += 0; //bufs[10] += 0; //bufs[11] += 0; //bufs[12] += 0; //bufs[13] += 0; } bufs[14][sample] += adpcm.calcSample(); } } // // I/O Ctrl // void Y8950::writeReg(byte rg, byte data, EmuTime::param time) { int stbl[32] = { 0, 2, 4, 1, 3, 5, -1, -1, 6, 8, 10, 7, 9, 11, -1, -1, 12, 14, 16, 13, 15, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; // TODO only for registers that influence sound // TODO also ADPCM //if (rg >= 0x20) { // update the output buffer before changing the register updateStream(time); //} switch (rg & 0xe0) { case 0x00: { switch (rg) { case 0x01: // TEST // TODO // Y8950 MSX-AUDIO Test register $01 (write only) // // Bit Description // // 7 Reset LFOs - seems to force the LFOs to their initial // values (eg. maximum amplitude, zero phase deviation) // // 6 something to do with ADPCM - bit 0 of the status // register is affected by setting this bit (PCM BSY) // // 5 No effect? - Waveform select enable in YM3812 OPL2 so seems // reasonable that this bit wouldn't have been used in OPL // // 4 No effect? // // 3 Faster LFOs - increases the frequencies of the LFOs and // (maybe) the timers (cf. YM2151 test register) // // 2 Reset phase generators - No phase generator output, but // envelope generators still work (can hear a transient // when they are gated) // // 1 No effect? // // 0 Reset envelopes - Envelope generator outputs forced // to maximum, so all enabled voices sound at maximum reg[rg] = data; break; case 0x02: // TIMER1 (reso. 80us) timer1->setValue(data); reg[rg] = data; break; case 0x03: // TIMER2 (reso. 320us) timer2->setValue(data); reg[rg] = data; break; case 0x04: // FLAG CONTROL if (data & Y8950::R04_IRQ_RESET) { resetStatus(0x78); // reset all flags } else { changeStatusMask((~data) & 0x78); timer1->setStart((data & Y8950::R04_ST1) != 0, time); timer2->setStart((data & Y8950::R04_ST2) != 0, time); reg[rg] = data; } adpcm.resetStatus(); break; case 0x06: // (KEYBOARD OUT) connector.write(data, time); reg[rg] = data; break; case 0x07: // START/REC/MEM DATA/REPEAT/SP-OFF/-/-/RESET periphery.setSPOFF((data & 8) != 0, time); // bit 3 // fall-through case 0x08: // CSM/KEY BOARD SPLIT/-/-/SAMPLE/DA AD/64K/ROM case 0x09: // START ADDRESS (L) case 0x0A: // START ADDRESS (H) case 0x0B: // STOP ADDRESS (L) case 0x0C: // STOP ADDRESS (H) case 0x0D: // PRESCALE (L) case 0x0E: // PRESCALE (H) case 0x0F: // ADPCM-DATA case 0x10: // DELTA-N (L) case 0x11: // DELTA-N (H) case 0x12: // ENVELOP CONTROL case 0x1A: // PCM-DATA reg[rg] = data; adpcm.writeReg(rg, data, time); break; case 0x15: // DAC-DATA (bit9-2) reg[rg] = data; if (reg[0x08] & 0x04) { int tmp = static_cast(reg[0x15]) * 256 + reg[0x16]; tmp = (tmp * 4) >> (7 - reg[0x17]); tmp = Math::clipIntToShort(tmp); dac13.writeDAC(tmp, time); } break; case 0x16: // (bit1-0) reg[rg] = data & 0xC0; break; case 0x17: // (exponent) reg[rg] = data & 0x07; break; case 0x18: // I/O-CONTROL (bit3-0) // 0 -> input // 1 -> output reg[rg] = data; periphery.write(reg[0x18], reg[0x19], time); break; case 0x19: // I/O-DATA (bit3-0) reg[rg] = data; periphery.write(reg[0x18], reg[0x19], time); break; } break; } case 0x20: { int s = stbl[rg & 0x1f]; if (s >= 0) { auto& chan = ch[s / 2]; auto& slot = chan.slot[s & 1]; slot.patch.AM = (data >> 7) & 1; slot.patch.PM = (data >> 6) & 1; slot.patch.EG = (data >> 5) & 1; slot.patch.setKeyScaleRate((data & 0x10) != 0); slot.patch.ML = (data >> 0) & 15; slot.updateAll(chan.freq); } reg[rg] = data; break; } case 0x40: { int s = stbl[rg & 0x1f]; if (s >= 0) { auto& chan = ch[s / 2]; auto& slot = chan.slot[s & 1]; slot.patch.KL = (data >> 6) & 3; slot.patch.TL = (data >> 0) & 63; slot.updateAll(chan.freq); } reg[rg] = data; break; } case 0x60: { int s = stbl[rg & 0x1f]; if (s >= 0) { auto& slot = ch[s / 2].slot[s & 1]; slot.patch.AR = (data >> 4) & 15; slot.patch.DR = (data >> 0) & 15; slot.updateEG(); } reg[rg] = data; break; } case 0x80: { int s = stbl[rg & 0x1f]; if (s >= 0) { auto& slot = ch[s / 2].slot[s & 1]; slot.patch.SL = (data >> 4) & 15; slot.patch.RR = (data >> 0) & 15; slot.updateEG(); } reg[rg] = data; break; } case 0xa0: { if (rg == 0xbd) { am_mode = (data & 0x80) != 0; pm_mode = (data & 0x40) != 0; setRythmMode(data); if (rythm_mode) { if (data & 0x10) keyOn_BD(); else keyOff_BD(); if (data & 0x08) keyOn_SD(); else keyOff_SD(); if (data & 0x04) keyOn_TOM(); else keyOff_TOM(); if (data & 0x02) keyOn_CYM(); else keyOff_CYM(); if (data & 0x01) keyOn_HH(); else keyOff_HH(); } ch[6].slot[MOD].updateAll(ch[6].freq); ch[6].slot[CAR].updateAll(ch[6].freq); ch[7].slot[MOD].updateAll(ch[7].freq); ch[7].slot[CAR].updateAll(ch[7].freq); ch[8].slot[MOD].updateAll(ch[8].freq); ch[8].slot[CAR].updateAll(ch[8].freq); reg[rg] = data; break; } unsigned c = rg & 0x0f; if (c > 8) { // 0xa9-0xaf 0xb9-0xbf break; } unsigned freq; if (!(rg & 0x10)) { // 0xa0-0xa8 freq = data | ((reg[rg + 0x10] & 0x1F) << 8); } else { // 0xb0-0xb8 if (data & 0x20) { ch[c].keyOn (KEY_MAIN); } else { ch[c].keyOff(KEY_MAIN); } freq = reg[rg - 0x10] | ((data & 0x1F) << 8); } ch[c].setFreq(freq); unsigned fNum = freq % 1024; unsigned block = freq / 1024; switch (c) { case 7: noiseA_dphase = fNum << block; break; case 8: noiseB_dphase = fNum << block; break; } ch[c].slot[CAR].updateAll(freq); ch[c].slot[MOD].updateAll(freq); reg[rg] = data; break; } case 0xc0: { if (rg > 0xc8) break; int c = rg - 0xC0; ch[c].slot[MOD].patch.setFeedbackShift((data >> 1) & 7); ch[c].alg = data & 1; reg[rg] = data; } } } byte Y8950::readReg(byte rg, EmuTime::param time) { updateStream(time); // TODO only when necessary byte result; switch (rg) { case 0x0F: // ADPCM-DATA case 0x13: // ??? case 0x14: // ??? case 0x1A: // PCM-DATA result = adpcm.readReg(rg, time); break; default: result = peekReg(rg, time); } return result; } byte Y8950::peekReg(byte rg, EmuTime::param time) const { switch (rg) { case 0x05: // (KEYBOARD IN) return connector.peek(time); case 0x0F: // ADPCM-DATA case 0x13: // ??? case 0x14: // ??? case 0x1A: // PCM-DATA return adpcm.peekReg(rg, time); case 0x19: { // I/O DATA byte input = periphery.read(time); byte output = reg[0x19]; byte enable = reg[0x18]; return (output & enable) | (input & ~enable) | 0xF0; } default: return reg[rg]; } } byte Y8950::readStatus(EmuTime::param time) { byte result = peekStatus(time); //std::cout << "status: " << (int)result << std::endl; return result; } byte Y8950::peekStatus(EmuTime::param time) const { const_cast(adpcm).sync(time); return (status & (0x80 | statusMask)) | 0x06; // bit 1 and 2 are always 1 } void Y8950::callback(byte flag) { setStatus(flag); } void Y8950::setStatus(byte flags) { status |= flags; if (status & statusMask) { status |= 0x80; irq.set(); } } void Y8950::resetStatus(byte flags) { status &= ~flags; if (!(status & statusMask)) { status &= 0x7f; irq.reset(); } } byte Y8950::peekRawStatus() const { return status; } void Y8950::changeStatusMask(byte newMask) { statusMask = newMask; status &= statusMask; if (status) { status |= 0x80; irq.set(); } else { status &= 0x7f; irq.reset(); } } template void Y8950::Patch::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("AM", AM); ar.serialize("PM", PM); ar.serialize("EG", EG); ar.serialize("KR", KR); ar.serialize("ML", ML); ar.serialize("KL", KL); ar.serialize("TL", TL); ar.serialize("FB", FB); ar.serialize("AR", AR); ar.serialize("DR", DR); ar.serialize("SL", SL); ar.serialize("RR", RR); } static enum_string envelopeStateInfo[]= { { "ATTACK", Y8950::ATTACK }, { "DECAY", Y8950::DECAY }, { "SUSTAIN", Y8950::SUSTAIN }, { "RELEASE", Y8950::RELEASE }, { "FINISH", Y8950::FINISH } }; SERIALIZE_ENUM(Y8950::EnvelopeState, envelopeStateInfo); // version 1: initial version // version 2: 'slotStatus' is replaced with 'key' and no longer serialized // instead it's recalculated via update_key_status() // version 3: serialize 'eg_mode' as an enum instead of an int, also merged // the 2 enum values SUSHOLD and SUSTINE into SUSTAIN template void Y8950::Slot::serialize(Archive& ar, unsigned version) { ar.serialize("feedback", feedback); ar.serialize("output", output); ar.serialize("phase", phase); ar.serialize("eg_phase", eg_phase); ar.serialize("patch", patch); if (ar.versionAtLeast(version, 3)) { ar.serialize("eg_mode", eg_mode); } else { assert(ar.isLoader()); int tmp = 0; // dummy init to avoid warning ar.serialize("eg_mode", tmp); switch (tmp) { case 0: eg_mode = ATTACK; break; case 1: eg_mode = DECAY; break; case 2: eg_mode = SUSTAIN; break; // was SUSHOLD case 3: eg_mode = SUSTAIN; break; // was SUSTINE case 4: eg_mode = RELEASE; break; default: eg_mode = FINISH; break; } } // These are restored by call to updateAll() in Y8950::Channel::serialize() // dphase, tll, dphaseARTableRks, dphaseDRTableRks, eg_dphase // These are restored by update_key_status(): // key } template void Y8950::Channel::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("mod", slot[MOD]); ar.serialize("car", slot[CAR]); ar.serialize("freq", freq); ar.serialize("alg", alg); if (ar.isLoader()) { slot[MOD].updateAll(freq); slot[CAR].updateAll(freq); } } template void Y8950::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("keyboardConnector", connector); ar.serialize("adpcm", adpcm); ar.serialize("timer1", *timer1); ar.serialize("timer2", *timer2); ar.serialize("irq", irq); ar.serialize_blob("registers", reg, sizeof(reg)); ar.serialize("pm_phase", pm_phase); ar.serialize("am_phase", am_phase); ar.serialize("noise_seed", noise_seed); ar.serialize("noiseA_phase", noiseA_phase); ar.serialize("noiseB_phase", noiseB_phase); ar.serialize("noiseA_dphase", noiseA_dphase); ar.serialize("noiseB_dphase", noiseB_dphase); ar.serialize("channels", ch); ar.serialize("status", status); ar.serialize("statusMask", statusMask); ar.serialize("rythm_mode", rythm_mode); ar.serialize("am_mode", am_mode); ar.serialize("pm_mode", pm_mode); ar.serialize("enabled", enabled); // TODO restore more state from registers static const byte rewriteRegs[] = { 6, // connector 15, // dac13 }; if (ar.isLoader()) { update_key_status(); EmuTime::param time = motherBoard.getCurrentTime(); for (auto r : rewriteRegs) { writeReg(r, reg[r], time); } } } // SimpleDebuggable Y8950::Debuggable::Debuggable(MSXMotherBoard& motherBoard, const std::string& name) : SimpleDebuggable(motherBoard, name + " regs", "MSX-AUDIO", 0x100) { } byte Y8950::Debuggable::read(unsigned address, EmuTime::param time) { auto& y8950 = OUTER(Y8950, debuggable); return y8950.peekReg(address, time); } void Y8950::Debuggable::write(unsigned address, byte value, EmuTime::param time) { auto& y8950 = OUTER(Y8950, debuggable); y8950.writeReg(address, value, time); } INSTANTIATE_SERIALIZE_METHODS(Y8950); SERIALIZE_CLASS_VERSION(Y8950::Slot, 3); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/Y8950.hh000066400000000000000000000136621257557151200174450ustar00rootroot00000000000000#ifndef Y8950_HH #define Y8950_HH #include "Y8950Adpcm.hh" #include "Y8950KeyboardConnector.hh" #include "ResampledSoundDevice.hh" #include "DACSound16S.hh" #include "SimpleDebuggable.hh" #include "IRQHelper.hh" #include "EmuTimer.hh" #include "EmuTime.hh" #include "FixedPoint.hh" #include "openmsx.hh" #include #include namespace openmsx { class MSXAudio; class DeviceConfig; class Y8950Periphery; class Y8950 final : private ResampledSoundDevice, private EmuTimerCallback { public: static const int CLOCK_FREQ = 3579545; static const int CLOCK_FREQ_DIV = 72; // Bitmask for register 0x04 // Timer1 Start. static const int R04_ST1 = 0x01; // Timer2 Start. static const int R04_ST2 = 0x02; // not used //static const int R04 = 0x04; // Mask 'Buffer Ready'. static const int R04_MASK_BUF_RDY = 0x08; // Mask 'End of sequence'. static const int R04_MASK_EOS = 0x10; // Mask Timer2 flag. static const int R04_MASK_T2 = 0x20; // Mask Timer1 flag. static const int R04_MASK_T1 = 0x40; // IRQ RESET. static const int R04_IRQ_RESET = 0x80; // Bitmask for status register static const int STATUS_EOS = R04_MASK_EOS; static const int STATUS_BUF_RDY = R04_MASK_BUF_RDY; static const int STATUS_T2 = R04_MASK_T2; static const int STATUS_T1 = R04_MASK_T1; Y8950(const std::string& name, const DeviceConfig& config, unsigned sampleRam, EmuTime::param time, MSXAudio& audio); ~Y8950(); void setEnabled(bool enabled, EmuTime::param time); void clearRam(); void reset(EmuTime::param time); void writeReg(byte reg, byte data, EmuTime::param time); byte readReg(byte reg, EmuTime::param time); byte peekReg(byte reg, EmuTime::param time) const; byte readStatus(EmuTime::param time); byte peekStatus(EmuTime::param time) const; // for ADPCM void setStatus(byte flags); void resetStatus(byte flags); byte peekRawStatus() const; template void serialize(Archive& ar, unsigned version); private: // SoundDevice int getAmplificationFactor() const override; void generateChannels(int** bufs, unsigned num) override; inline void keyOn_BD(); inline void keyOn_SD(); inline void keyOn_TOM(); inline void keyOn_HH(); inline void keyOn_CYM(); inline void keyOff_BD(); inline void keyOff_SD(); inline void keyOff_TOM(); inline void keyOff_HH(); inline void keyOff_CYM(); inline void setRythmMode(int data); void update_key_status(); bool checkMuteHelper(); void changeStatusMask(byte newMask); void callback(byte flag) override; public: // Dynamic range of envelope static const int EG_BITS = 9; // Bits for envelope phase incremental counter static const int EG_DP_BITS = 23; using EnvPhaseIndex = FixedPoint; enum EnvelopeState { ATTACK, DECAY, SUSTAIN, RELEASE, FINISH }; private: enum KeyPart { KEY_MAIN = 1, KEY_RHYTHM = 2 }; class Patch { public: Patch(); void reset(); void setKeyScaleRate(bool value) { KR = value ? 9 : 11; } void setFeedbackShift(byte value) { FB = value ? 8 - value : 0; } template void serialize(Archive& ar, unsigned version); bool AM, PM, EG; byte KR; // 0,1 transformed to 9,11 byte ML; // 0-15 byte KL; // 0-3 byte TL; // 0-63 byte FB; // 0,1-7 transformed to 0,7-1 byte AR; // 0-15 byte DR; // 0-15 byte SL; // 0-15 byte RR; // 0-15 }; class Slot { public: void reset(); inline bool isActive() const; inline void slotOn (KeyPart part); inline void slotOff(KeyPart part); inline unsigned calc_phase(int lfo_pm); inline unsigned calc_envelope(int lfo_am); inline int calc_slot_car(int lfo_pm, int lfo_am, int fm); inline int calc_slot_mod(int lfo_pm, int lfo_am); inline int calc_slot_tom(int lfo_pm, int lfo_am); inline int calc_slot_snare(int lfo_pm, int lfo_am, int whitenoise); inline int calc_slot_cym(int lfo_am, int a, int b); inline int calc_slot_hat(int lfo_am, int a, int b, int whitenoise); inline void updateAll(unsigned freq); inline void updatePG(unsigned freq); inline void updateTLL(unsigned freq); inline void updateRKS(unsigned freq); inline void updateEG(); template void serialize(Archive& ar, unsigned version); // OUTPUT int feedback; int output; // Output value of slot // for Phase Generator (PG) unsigned phase; // Phase unsigned dphase; // Phase increment amount // for Envelope Generator (EG) EnvPhaseIndex* dphaseARTableRks; EnvPhaseIndex* dphaseDRTableRks; int tll; // Total Level + Key scale level EnvelopeState eg_mode; // Current state EnvPhaseIndex eg_phase; // Phase EnvPhaseIndex eg_dphase;// Phase increment amount Patch patch; byte key; }; class Channel { public: Channel(); void reset(); inline void setFreq(unsigned freq); inline void keyOn (KeyPart part); inline void keyOff(KeyPart part); template void serialize(Archive& ar, unsigned version); Slot slot[2]; unsigned freq; // combined F-Number and Block bool alg; }; MSXMotherBoard& motherBoard; Y8950Periphery& periphery; Y8950Adpcm adpcm; Y8950KeyboardConnector connector; DACSound16S dac13; // 13-bit (exponential) DAC struct Debuggable final : SimpleDebuggable { Debuggable(MSXMotherBoard& motherBoard, const std::string& name); byte read(unsigned address, EmuTime::param time) override; void write(unsigned address, byte value, EmuTime::param time) override; } debuggable; const std::unique_ptr timer1; // 80us timer const std::unique_ptr timer2; // 320us timer IRQHelper irq; byte reg[0x100]; Channel ch[9]; unsigned pm_phase; // Pitch Modulator unsigned am_phase; // Amp Modulator // Noise Generator int noise_seed; unsigned noiseA_phase; unsigned noiseB_phase; unsigned noiseA_dphase; unsigned noiseB_dphase; byte status; // STATUS Register byte statusMask; // bit=0 -> masked bool rythm_mode; bool am_mode; bool pm_mode; bool enabled; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/Y8950Adpcm.cc000066400000000000000000000345761257557151200204070ustar00rootroot00000000000000// The actual sample playing part is duplicated for the 'emu' domain and the // 'audio' domain. The emu part is responsible for cycle accurate sample // readback (see peekReg() register 0x13 and 0x14) and for cycle accurate // status register updates (the status bits related to playback, e.g. // end-of-sample). The audio part is responsible for the actual sound // generation. This split up allows for the two parts to be out-of-sync. So for // example when emulation is running faster or slower than 100% realtime speed, // we both get cycle accurate emulation behaviour and still sound generation at // 100% realtime speed (which is most of the time better for sound quality). #include "Y8950Adpcm.hh" #include "Y8950.hh" #include "Clock.hh" #include "DeviceConfig.hh" #include "MSXMotherBoard.hh" #include "Math.hh" #include "serialize.hh" namespace openmsx { // Bitmask for register 0x07 static const int R07_RESET = 0x01; static const int R07_SP_OFF = 0x08; static const int R07_REPEAT = 0x10; static const int R07_MEMORY_DATA = 0x20; static const int R07_REC = 0x40; static const int R07_START = 0x80; static const int R07_MODE = 0xE0; // Bitmask for register 0x08 static const int R08_ROM = 0x01; static const int R08_64K = 0x02; static const int R08_DA_AD = 0x04; static const int R08_SAMPL = 0x08; static const int R08_NOTE_SET = 0x40; static const int R08_CSM = 0x80; static const int DMAX = 0x6000; static const int DMIN = 0x7F; static const int DDEF = 0x7F; static const int STEP_BITS = 16; static const int STEP_MASK = (1 << STEP_BITS) -1; Y8950Adpcm::Y8950Adpcm(Y8950& y8950_, const DeviceConfig& config, const std::string& name, unsigned sampleRam) : Schedulable(config.getScheduler()) , y8950(y8950_) , ram(config, name + " RAM", "Y8950 sample RAM", sampleRam) , clock(config.getMotherBoard().getCurrentTime()) , volume(0) { clearRam(); } Y8950Adpcm::~Y8950Adpcm() { } void Y8950Adpcm::clearRam() { ram.clear(0xFF); } void Y8950Adpcm::reset(EmuTime::param time) { removeSyncPoint(); clock.reset(time); startAddr = 0; stopAddr = 7; delta = 0; addrMask = (1 << 18) - 1; reg7 = 0; reg15 = 0; readDelay = 0; romBank = false; writeReg(0x12, 255, time); // volume restart(emu); restart(aud); y8950.setStatus(Y8950::STATUS_BUF_RDY); } bool Y8950Adpcm::isPlaying() const { return (reg7 & 0xC0) == 0x80; } bool Y8950Adpcm::isMuted() const { return !isPlaying() || (reg7 & R07_SP_OFF); } void Y8950Adpcm::restart(PlayData& pd) { pd.memPntr = startAddr; pd.nowStep = (1 << STEP_BITS) - delta; pd.out = 0; pd.output = 0; pd.diff = DDEF; pd.nextLeveling = 0; pd.sampleStep = 0; pd.adpcm_data = 0; // dummy, avoid UMR in serialize } void Y8950Adpcm::sync(EmuTime::param time) { if (isPlaying()) { // optimization, also correct without this test unsigned ticks = clock.getTicksTill(time); for (unsigned i = 0; isPlaying() && (i < ticks); ++i) { calcSample(true); // ignore result } } clock.advance(time); } void Y8950Adpcm::schedule() { assert(isPlaying()); if ((stopAddr > startAddr) && (delta != 0)) { // TODO possible optimization, no need to set sync points if // the corresponding bit is masked in the interupt enable // register if (reg7 & R07_MEMORY_DATA) { // we already did a sync(time), so clock is up-to-date Clock stop(clock); uint64_t samples = stopAddr - emu.memPntr + 1; uint64_t length = (samples << STEP_BITS) + ((1 << STEP_BITS) - emu.nowStep) + (delta - 1); stop += unsigned(length / delta); setSyncPoint(stop.getTime()); } else { // TODO we should also set a syncpoint in this case // because this mode sets the STATUS_BUF_RDY bit // which also triggers an IRQ } } } void Y8950Adpcm::executeUntil(EmuTime::param time) { assert(isPlaying()); sync(time); // should set STATUS_EOS assert(y8950.peekRawStatus() & Y8950::STATUS_EOS); if (isPlaying() && (reg7 & R07_REPEAT)) { schedule(); } } void Y8950Adpcm::writeReg(byte rg, byte data, EmuTime::param time) { sync(time); // TODO only when needed switch (rg) { case 0x07: // START/REC/MEM DATA/REPEAT/SP-OFF/-/-/RESET reg7 = data; if (reg7 & R07_RESET) { reg7 = 0; } if (reg7 & R07_START) { // start ADPCM restart(emu); restart(aud); } if (reg7 & R07_MEMORY_DATA) { // access external memory? emu.memPntr = startAddr; aud.memPntr = startAddr; readDelay = 2; // two dummy reads if ((reg7 & 0xA0) == 0x20) { // Memory read or write y8950.setStatus(Y8950::STATUS_BUF_RDY); } } else { // access via CPU emu.memPntr = 0; aud.memPntr = 0; } removeSyncPoint(); if (isPlaying()) { schedule(); } break; case 0x08: // CSM/KEY BOARD SPLIT/-/-/SAMPLE/DA AD/64K/ROM romBank = data & R08_ROM; addrMask = data & R08_64K ? (1 << 16) - 1 : (1 << 18) - 1; break; case 0x09: // START ADDRESS (L) startAddr = (startAddr & 0x7F807) | (data << 3); break; case 0x0A: // START ADDRESS (H) startAddr = (startAddr & 0x007FF) | (data << 11); break; case 0x0B: // STOP ADDRESS (L) stopAddr = (stopAddr & 0x7F807) | (data << 3); if (isPlaying()) { removeSyncPoint(); schedule(); } break; case 0x0C: // STOP ADDRESS (H) stopAddr = (stopAddr & 0x007FF) | (data << 11); if (isPlaying()) { removeSyncPoint(); schedule(); } break; case 0x0F: // ADPCM-DATA writeData(data); break; case 0x10: // DELTA-N (L) delta = (delta & 0xFF00) | data; volumeWStep = (volume * delta) >> STEP_BITS; if (isPlaying()) { removeSyncPoint(); schedule(); } break; case 0x11: // DELTA-N (H) delta = (delta & 0x00FF) | (data << 8); volumeWStep = (volume * delta) >> STEP_BITS; if (isPlaying()) { removeSyncPoint(); schedule(); } break; case 0x12: { // ENVELOP CONTROL volume = data; volumeWStep = (volume * delta) >> STEP_BITS; break; } case 0x0D: // PRESCALE (L) case 0x0E: // PRESCALE (H) case 0x15: // DAC-DATA (bit9-2) case 0x16: // (bit1-0) case 0x17: // (exponent) case 0x1A: // PCM-DATA // not implemented break; } } void Y8950Adpcm::writeData(byte data) { reg15 = data; if ((reg7 & R07_MODE) == 0x60) { // external memory write assert(!isPlaying()); // no need to update the 'aud' data if (readDelay) { emu.memPntr = startAddr; readDelay = 0; } if (emu.memPntr <= stopAddr) { writeMemory(emu.memPntr, data); emu.memPntr += 2; // two nibbles at a time // reset BRDY bit in status register, // which means we are processing the write y8950.resetStatus(Y8950::STATUS_BUF_RDY); // setup a timer that will callback us in 10 // master clock cycles for Y8950. In the // callback set the BRDY flag to 1 , which // means we have written the data. For now, we // don't really do this; we simply reset and // set the flag in zero time, so that the IRQ // will work. // set BRDY bit in status register y8950.setStatus(Y8950::STATUS_BUF_RDY); } else { // set EOS bit in status register y8950.setStatus(Y8950::STATUS_EOS); } } else if ((reg7 & R07_MODE) == 0x80) { // ADPCM synthesis from CPU // Reset BRDY bit in status register, which means we // are full of data y8950.resetStatus(Y8950::STATUS_BUF_RDY); } } byte Y8950Adpcm::readReg(byte rg, EmuTime::param time) { sync(time); // TODO only when needed byte result = (rg == 0x0F) ? readData() // ADPCM-DATA : peekReg(rg); // other return result; } byte Y8950Adpcm::peekReg(byte rg, EmuTime::param time) const { const_cast(this)->sync(time); // TODO only when needed return peekReg(rg); } byte Y8950Adpcm::peekReg(byte rg) const { switch (rg) { case 0x0F: // ADPCM-DATA return peekData(); case 0x13: // TODO check: is this before or after // volume is applied // filtering is performed return (emu.output >> 8) & 0xFF; case 0x14: return emu.output >> 16; default: return 255; } } void Y8950Adpcm::resetStatus() { // If the BUF_RDY mask is cleared (e.g. by writing the value 0x80 to // register R#4). Reading the status register still has the BUF_RDY // bit set. Without this behavior demos like 'NOP Unknown reality' // hang when testing the amount of sample ram or when uploading data // to the sample ram. // // Before this code was added, those demos also worked but only // because we had a hack that always kept bit BUF_RDY set. // // When the ADPCM unit is not performing any function (e.g. after a // reset), the BUF_RDY bit should still be set. The AUDIO detection // routine in 'MSX-Audio BIOS v1.3' depends on this. See // [3533002] Y8950 not being detected by MSX-Audio v1.3 // https://sourceforge.net/tracker/?func=detail&aid=3533002&group_id=38274&atid=421861 // TODO I've implemented this as '(reg7 & R07_MODE) == 0', is this // correct/complete? if (((reg7 & R07_MODE & ~R07_REC) == R07_MEMORY_DATA) || ((reg7 & R07_MODE) == 0)){ // transfer to or from sample ram, or no function y8950.setStatus(Y8950::STATUS_BUF_RDY); } } byte Y8950Adpcm::readData() { if ((reg7 & R07_MODE) == R07_MEMORY_DATA) { // external memory read assert(!isPlaying()); // no need to update the 'aud' data if (readDelay) { emu.memPntr = startAddr; } } byte result = peekData(); if ((reg7 & R07_MODE) == R07_MEMORY_DATA) { assert(!isPlaying()); // no need to update the 'aud' data if (readDelay) { // two dummy reads --readDelay; y8950.setStatus(Y8950::STATUS_BUF_RDY); } else if (emu.memPntr > stopAddr) { // set EOS bit in status register y8950.setStatus(Y8950::STATUS_EOS); } else { emu.memPntr += 2; // two nibbles at a time // reset BRDY bit in status register, which means we // are reading the memory now y8950.resetStatus(Y8950::STATUS_BUF_RDY); // setup a timer that will callback us in 10 master // clock cycles for Y8950. In the callback set the BRDY // flag to 1, which means we have another data ready. // For now, we don't really do this; we simply reset and // set the flag in zero time, so that the IRQ will work. // set BRDY bit in status register y8950.setStatus(Y8950::STATUS_BUF_RDY); } } return result; } byte Y8950Adpcm::peekData() const { if ((reg7 & R07_MODE) == R07_MEMORY_DATA) { // external memory read assert(!isPlaying()); // no need to update the 'aud' data if (readDelay) { return reg15; } else if (emu.memPntr > stopAddr) { return 0; } else { return readMemory(emu.memPntr); } } else { return 0; // TODO check } } void Y8950Adpcm::writeMemory(unsigned memPntr, byte value) { unsigned addr = (memPntr / 2) & addrMask; if ((addr < ram.getSize()) && !romBank) { ram[addr] = value; } } byte Y8950Adpcm::readMemory(unsigned memPntr) const { unsigned addr = (memPntr / 2) & addrMask; if (romBank || (addr >= ram.getSize())) { return 0; // checked on a real machine } else { return ram[addr]; } } int Y8950Adpcm::calcSample() { // called by audio thread if (!isPlaying()) return 0; int output = calcSample(false); return (reg7 & R07_SP_OFF) ? 0 : output; } int Y8950Adpcm::calcSample(bool doEmu) { // values taken from ymdelta.c by Tatsuyuki Satoh. static const int F1[16] = { 1, 3, 5, 7, 9, 11, 13, 15, -1, -3, -5, -7, -9, -11, -13, -15 }; static const int F2[16] = { 57, 57, 57, 57, 77, 102, 128, 153, 57, 57, 57, 57, 77, 102, 128, 153 }; assert(isPlaying()); PlayData& pd = doEmu ? emu : aud; pd.nowStep += delta; if (pd.nowStep & ~STEP_MASK) { pd.nowStep &= STEP_MASK; byte val; if (!(pd.memPntr & 1)) { // even nibble if (reg7 & R07_MEMORY_DATA) { pd.adpcm_data = readMemory(pd.memPntr); } else { pd.adpcm_data = reg15; // set BRDY bit, ready to accept new data if (doEmu) { y8950.setStatus(Y8950::STATUS_BUF_RDY); } } val = pd.adpcm_data >> 4; } else { // odd nibble val = pd.adpcm_data & 0x0F; } int prevOut = pd.out; pd.out = Math::clipIntToShort(pd.out + (pd.diff * F1[val]) / 8); pd.diff = Math::clip((pd.diff * F2[val]) / 64); int prevLeveling = pd.nextLeveling; pd.nextLeveling = (prevOut + pd.out) / 2; int deltaLeveling = pd.nextLeveling - prevLeveling; pd.sampleStep = deltaLeveling * volumeWStep; int tmp = deltaLeveling * ((volume * pd.nowStep) >> STEP_BITS); pd.output = prevLeveling * volume + tmp; ++pd.memPntr; if ((reg7 & R07_MEMORY_DATA) && (pd.memPntr > stopAddr)) { // On 2003/06/21 I commited a patch with comment: // generate end-of-sample interrupt at every sample // end, including loops // Unfortunatly it doesn't give any reason why and now // I can't remember it :-( // This is different from e.g. the MAME implementation. if (doEmu) { y8950.setStatus(Y8950::STATUS_EOS); } if (reg7 & R07_REPEAT) { restart(pd); } else { if (doEmu) { removeSyncPoint(); reg7 = 0; } } } } else { pd.output += pd.sampleStep; } return pd.output >> 12; } // version 1: // Initial verson // version 2: // - Split PlayData in emu and audio part (though this doesn't add new state // to the savestate). // - Added clock object. template void Y8950Adpcm::serialize(Archive& ar, unsigned version) { ar.template serializeBase(*this); ar.serialize("ram", ram); ar.serialize("startAddr", startAddr); ar.serialize("stopAddr", stopAddr); ar.serialize("addrMask", addrMask); ar.serialize("volume", volume); ar.serialize("volumeWStep", volumeWStep); ar.serialize("readDelay", readDelay); ar.serialize("delta", delta); ar.serialize("reg7", reg7); ar.serialize("reg15", reg15); ar.serialize("romBank", romBank); ar.serialize("memPntr", emu.memPntr); ar.serialize("nowStep", emu.nowStep); ar.serialize("out", emu.out); ar.serialize("output", emu.output); ar.serialize("diff", emu.diff); ar.serialize("nextLeveling", emu.nextLeveling); ar.serialize("sampleStep", emu.sampleStep); ar.serialize("adpcm_data", emu.adpcm_data); if (ar.isLoader()) { // ignore aud part for saving, // for loading we make it the same as the emu part aud = emu; } if (ar.versionBelow(version, 2)) { clock.reset(getCurrentTime()); // reschedule, because automatically deserialized sync-point // can be off, because clock.getTime() != getCurrentTime() removeSyncPoint(); if (isPlaying()) { schedule(); } } else { ar.serialize("clock", clock); } } INSTANTIATE_SERIALIZE_METHODS(Y8950Adpcm); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/Y8950Adpcm.hh000066400000000000000000000035361257557151200204110ustar00rootroot00000000000000#ifndef Y8950ADPCM_HH #define Y8950ADPCM_HH #include "Ram.hh" #include "Schedulable.hh" #include "Clock.hh" #include "serialize_meta.hh" #include "openmsx.hh" namespace openmsx { class DeviceConfig; class Y8950; class Y8950Adpcm final : public Schedulable { public: Y8950Adpcm(Y8950& y8950, const DeviceConfig& config, const std::string& name, unsigned sampleRam); ~Y8950Adpcm(); void clearRam(); void reset(EmuTime::param time); bool isMuted() const; void writeReg(byte rg, byte data, EmuTime::param time); byte readReg(byte rg, EmuTime::param time); byte peekReg(byte rg, EmuTime::param time) const; int calcSample(); void sync(EmuTime::param time); void resetStatus(); template void serialize(Archive& ar, unsigned version); private: // This data is updated while playing struct PlayData { unsigned memPntr; unsigned nowStep; int out; int output; int diff; int nextLeveling; int sampleStep; byte adpcm_data; }; // Schedulable void executeUntil(EmuTime::param time) override; void schedule(); void restart(PlayData& pd); bool isPlaying() const; void writeData(byte data); byte peekReg(byte rg) const; byte readData(); byte peekData() const; void writeMemory(unsigned memPntr, byte value); byte readMemory(unsigned memPntr) const; int calcSample(bool doEmu); Y8950& y8950; Ram ram; // copy/pasted from Y8950.hh static const int CLOCK_FREQ = 3579545; static const int CLOCK_FREQ_DIV = 72; Clock clock; PlayData emu; // used for emulator behaviour (read back of sample data) PlayData aud; // used by audio generation thread unsigned startAddr; unsigned stopAddr; unsigned addrMask; int volume; int volumeWStep; int readDelay; int delta; byte reg7; byte reg15; bool romBank; }; SERIALIZE_CLASS_VERSION(Y8950Adpcm, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/Y8950KeyboardConnector.cc000066400000000000000000000030531257557151200227600ustar00rootroot00000000000000#include "Y8950KeyboardConnector.hh" #include "Y8950KeyboardDevice.hh" #include "DummyY8950KeyboardDevice.hh" #include "checked_cast.hh" #include "serialize.hh" #include "memory.hh" namespace openmsx { Y8950KeyboardConnector::Y8950KeyboardConnector( PluggingController& pluggingController) : Connector(pluggingController, "audiokeyboardport", make_unique()) , data(255) { } void Y8950KeyboardConnector::write(byte newData, EmuTime::param time) { if (newData != data) { data = newData; getPluggedKeyb().write(data, time); } } byte Y8950KeyboardConnector::read(EmuTime::param time) { return getPluggedKeyb().read(time); } byte Y8950KeyboardConnector::peek(EmuTime::param time) const { // TODO implement proper peek return const_cast(this)->read(time); } const std::string Y8950KeyboardConnector::getDescription() const { return "MSX-AUDIO keyboard connector"; } string_ref Y8950KeyboardConnector::getClass() const { return "Y8950 Keyboard Port"; } void Y8950KeyboardConnector::plug(Pluggable& dev, EmuTime::param time) { Connector::plug(dev, time); getPluggedKeyb().write(data, time); } Y8950KeyboardDevice& Y8950KeyboardConnector::getPluggedKeyb() const { return *checked_cast(&getPlugged()); } template void Y8950KeyboardConnector::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); // don't serialize 'data', done in Y8950 } INSTANTIATE_SERIALIZE_METHODS(Y8950KeyboardConnector); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/Y8950KeyboardConnector.hh000066400000000000000000000014111257557151200227660ustar00rootroot00000000000000#ifndef Y8950KEYBOARDCONNECTOR_HH #define Y8950KEYBOARDCONNECTOR_HH #include "Connector.hh" #include "openmsx.hh" namespace openmsx { class Y8950KeyboardDevice; class Y8950KeyboardConnector final : public Connector { public: explicit Y8950KeyboardConnector(PluggingController& pluggingController); void write(byte data, EmuTime::param time); byte read(EmuTime::param time); byte peek(EmuTime::param time) const; Y8950KeyboardDevice& getPluggedKeyb() const; // Connector const std::string getDescription() const final override; string_ref getClass() const final override; void plug(Pluggable& dev, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: byte data; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/Y8950KeyboardDevice.cc000066400000000000000000000002441257557151200222240ustar00rootroot00000000000000#include "Y8950KeyboardDevice.hh" namespace openmsx { string_ref Y8950KeyboardDevice::getClass() const { return "Y8950 Keyboard Port"; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/Y8950KeyboardDevice.hh000066400000000000000000000015721257557151200222430ustar00rootroot00000000000000#ifndef Y8950KEYBOARDDEVICE_HH #define Y8950KEYBOARDDEVICE_HH #include "Pluggable.hh" #include "openmsx.hh" namespace openmsx { class Y8950KeyboardDevice : public Pluggable { public: /** * Send data to the device. * Normally this is used to select a certain row from the * keyboard but you might also connect a non-keyboard device. * A 1-bit means corresponding row is selected ( 0V) * 0-bit not selected (+5V) */ virtual void write(byte data, EmuTime::param time) = 0; /** * Read data from the device. * Normally this are the keys that are pressed but you might * also connect a non-keyboard device. * A 0-bit means corresponding key is pressed * 1-bit not pressed */ virtual byte read(EmuTime::param time) = 0; // pluggable string_ref getClass() const final override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/Y8950Periphery.cc000066400000000000000000000212651257557151200213210ustar00rootroot00000000000000#include "Y8950Periphery.hh" #include "Y8950.hh" #include "MSXAudio.hh" #include "MSXCPU.hh" #include "MSXCPUInterface.hh" #include "MSXDevice.hh" #include "CacheLine.hh" #include "Ram.hh" #include "Rom.hh" #include "BooleanSetting.hh" #include "MSXException.hh" #include "StringOp.hh" #include "DeviceConfig.hh" #include "serialize.hh" #include "memory.hh" #include using std::string; namespace openmsx { // Subclass declarations: class MusicModulePeriphery final : public Y8950Periphery { public: explicit MusicModulePeriphery(MSXAudio& audio); void write(nibble outputs, nibble values, EmuTime::param time) override; nibble read(EmuTime::param time) override; template void serialize(Archive& /*ar*/, unsigned /*version*/) { // nothing } private: MSXAudio& audio; }; REGISTER_POLYMORPHIC_INITIALIZER(Y8950Periphery, MusicModulePeriphery, "MusicModule"); class PanasonicAudioPeriphery final : public Y8950Periphery { public: PanasonicAudioPeriphery( MSXAudio& audio, const DeviceConfig& config, const string& soundDeviceName); ~PanasonicAudioPeriphery(); void reset() override; void write(nibble outputs, nibble values, EmuTime::param time) override; nibble read(EmuTime::param time) override; byte peekMem(word address, EmuTime::param time) const override; void writeMem(word address, byte value, EmuTime::param time) override; const byte* getReadCacheLine(word start) const override; byte* getWriteCacheLine(word start) const override; template void serialize(Archive& ar, unsigned version); private: void setBank(byte value); void setIOPorts(byte value); void setIOPortsHelper(unsigned base, bool enable); MSXAudio& audio; BooleanSetting swSwitch; Ram ram; Rom rom; byte bankSelect; byte ioPorts; }; REGISTER_POLYMORPHIC_INITIALIZER(Y8950Periphery, PanasonicAudioPeriphery, "Panasonic"); class ToshibaAudioPeriphery final : public Y8950Periphery { public: explicit ToshibaAudioPeriphery(MSXAudio& audio); void write(nibble outputs, nibble values, EmuTime::param time) override; nibble read(EmuTime::param time) override; void setSPOFF(bool value, EmuTime::param time) override; template void serialize(Archive& /*ar*/, unsigned /*version*/) { // nothing } private: MSXAudio& audio; }; REGISTER_POLYMORPHIC_INITIALIZER(Y8950Periphery, ToshibaAudioPeriphery, "Toshiba"); // Base class implementation: void Y8950Periphery::reset() { // nothing } void Y8950Periphery::setSPOFF(bool /*value*/, EmuTime::param /*time*/) { // nothing } byte Y8950Periphery::readMem(word address, EmuTime::param time) { // by default do same as peekMem() return peekMem(address, time); } byte Y8950Periphery::peekMem(word /*address*/, EmuTime::param /*time*/) const { return 0xFF; } void Y8950Periphery::writeMem(word /*address*/, byte /*value*/, EmuTime::param /*time*/) { // nothing } const byte* Y8950Periphery::getReadCacheLine(word /*start*/) const { return MSXDevice::unmappedRead; } byte* Y8950Periphery::getWriteCacheLine(word /*start*/) const { return MSXDevice::unmappedWrite; } // MusicModulePeriphery implementation: MusicModulePeriphery::MusicModulePeriphery(MSXAudio& audio_) : audio(audio_) { } void MusicModulePeriphery::write(nibble outputs, nibble values, EmuTime::param time) { nibble actual = (outputs & values) | (~outputs & read(time)); audio.y8950.setEnabled((actual & 8) != 0, time); audio.enableDAC((actual & 1) != 0, time); } nibble MusicModulePeriphery::read(EmuTime::param /*time*/) { // IO2-IO1 are unconnected, reading them initially returns the last // written value, but after some seconds it falls back to '0' // IO3 and IO0 are output pins, but reading them return respectively // '1' and '0' return 8; } // PanasonicAudioPeriphery implementation: PanasonicAudioPeriphery::PanasonicAudioPeriphery( MSXAudio& audio_, const DeviceConfig& config, const string& soundDeviceName) : audio(audio_) , swSwitch(audio.getCommandController(), soundDeviceName + "_firmware", "This setting controls the switch on the Panasonic " "MSX-AUDIO module. The switch controls whether the internal " "software of this module must be started or not.", false) // note: name + " RAM" already taken by sample RAM , ram(config, audio.getName() + " mapped RAM", "MSX-AUDIO mapped RAM", 0x1000) , rom(audio.getName() + " ROM", "MSX-AUDIO ROM", config) , ioPorts(0) { reset(); } PanasonicAudioPeriphery::~PanasonicAudioPeriphery() { setIOPorts(0); // unregister IO ports } void PanasonicAudioPeriphery::reset() { ram.clear(); // TODO check setBank(0); setIOPorts(0); // TODO check: neither IO port ranges active } void PanasonicAudioPeriphery::write(nibble outputs, nibble values, EmuTime::param time) { nibble actual = (outputs & values) | (~outputs & read(time)); audio.y8950.setEnabled(!(actual & 8), time); } nibble PanasonicAudioPeriphery::read(EmuTime::param /*time*/) { // verified bit 0,1,3 read as zero return swSwitch.getBoolean() ? 0x4 : 0x0; // bit2 } byte PanasonicAudioPeriphery::peekMem(word address, EmuTime::param /*time*/) const { if ((bankSelect == 0) && ((address & 0x3FFF) >= 0x3000)) { return ram[(address & 0x3FFF) - 0x3000]; } else { return rom[0x8000 * bankSelect + (address & 0x7FFF)]; } } const byte* PanasonicAudioPeriphery::getReadCacheLine(word address) const { if ((bankSelect == 0) && ((address & 0x3FFF) >= 0x3000)) { return &ram[(address & 0x3FFF) - 0x3000]; } else { return &rom[0x8000 * bankSelect + (address & 0x7FFF)]; } } void PanasonicAudioPeriphery::writeMem(word address, byte value, EmuTime::param /*time*/) { address &= 0x7FFF; if (address == 0x7FFE) { setBank(value); } else if (address == 0x7FFF) { setIOPorts(value); } address &= 0x3FFF; if ((bankSelect == 0) && (address >= 0x3000)) { ram[address - 0x3000] = value; } } byte* PanasonicAudioPeriphery::getWriteCacheLine(word address) const { address &= 0x7FFF; if (address == (0x7FFE & CacheLine::HIGH)) { return nullptr; } address &= 0x3FFF; if ((bankSelect == 0) && (address >= 0x3000)) { return const_cast(&ram[address - 0x3000]); } else { return MSXDevice::unmappedWrite; } } void PanasonicAudioPeriphery::setBank(byte value) { bankSelect = value & 3; audio.getCPU().invalidateMemCache(0x0000, 0x10000); } void PanasonicAudioPeriphery::setIOPorts(byte value) { byte diff = ioPorts ^ value; if (diff & 1) { setIOPortsHelper(0xC0, (value & 1) != 0); } if (diff & 2) { setIOPortsHelper(0xC2, (value & 2) != 0); } ioPorts = value; } void PanasonicAudioPeriphery::setIOPortsHelper(unsigned base, bool enable) { MSXCPUInterface& cpu = audio.getCPUInterface(); if (enable) { cpu.register_IO_In (base + 0, &audio); cpu.register_IO_In (base + 1, &audio); cpu.register_IO_Out(base + 0, &audio); cpu.register_IO_Out(base + 1, &audio); } else { cpu.unregister_IO_In (base + 0, &audio); cpu.unregister_IO_In (base + 1, &audio); cpu.unregister_IO_Out(base + 0, &audio); cpu.unregister_IO_Out(base + 1, &audio); } } template void PanasonicAudioPeriphery::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("ram", ram); ar.serialize("bankSelect", bankSelect); byte tmpIoPorts = ioPorts; ar.serialize("ioPorts", tmpIoPorts); if (ar.isLoader()) { setIOPorts(tmpIoPorts); } } // ToshibaAudioPeriphery implementation: ToshibaAudioPeriphery::ToshibaAudioPeriphery(MSXAudio& audio_) : audio(audio_) { } void ToshibaAudioPeriphery::write(nibble /*outputs*/, nibble /*values*/, EmuTime::param /*time*/) { // TODO IO1-IO0 are programmed as output by HX-MU900 software rom // and it writes periodically the values 1/1/2/2/0/0 to // these pins, but I have no idea what function they have } nibble ToshibaAudioPeriphery::read(EmuTime::param /*time*/) { // IO3-IO2 are unconnected (see also comment in MusicModulePeriphery) // IO1-IO0 are output pins, but reading them returns '1' return 0x3; } void ToshibaAudioPeriphery::setSPOFF(bool value, EmuTime::param time) { audio.y8950.setEnabled(!value, time); } // Y8950PeripheryFactory implementation: std::unique_ptr Y8950PeripheryFactory::create( MSXAudio& audio, const DeviceConfig& config, const std::string& soundDeviceName) { string type(StringOp::toLower(config.getChildData("type", "philips"))); if (type == "philips") { return make_unique(audio); } else if (type == "panasonic") { return make_unique( audio, config, soundDeviceName); } else if (type == "toshiba") { return make_unique(audio); } else { throw MSXException("Unknown MSX-AUDIO type: " + type); } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/Y8950Periphery.hh000066400000000000000000000033701257557151200213300ustar00rootroot00000000000000#ifndef Y8950PERIPHERY_HH #define Y8950PERIPHERY_HH #include "EmuTime.hh" #include "openmsx.hh" #include #include namespace openmsx { /** Models the 4 general purpose I/O pins on the Y8950 * (controlled by registers r#18 and r#19) */ class Y8950Periphery { public: virtual ~Y8950Periphery() {} virtual void reset(); /** Write to (some of) the pins * @param outputs A '1' bit indicates the corresponding bit is * programmed as output. * @param values The actual value that is written, only bits for * which the corresponding bit in the 'outputs' * parameter is set are meaningful. * @param time The moment in time the write occurs */ virtual void write(nibble outputs, nibble values, EmuTime::param time) = 0; /** Read from (some of) the pins * Some of the pins might be programmed as output, but this method * doesn't care about that, it should return the value of all pins * as-if they were all programmed as input. * @param time The moment in time the read occurs */ virtual nibble read(EmuTime::param time) = 0; /** SP-OFF bit (bit 3 in Y8950 register 7) */ virtual void setSPOFF(bool value, EmuTime::param time); virtual byte readMem(word address, EmuTime::param time); virtual byte peekMem(word address, EmuTime::param time) const; virtual void writeMem(word address, byte value, EmuTime::param time); virtual const byte* getReadCacheLine(word start) const; virtual byte* getWriteCacheLine(word start) const; }; class MSXAudio; class DeviceConfig; class Y8950PeripheryFactory { public: static std::unique_ptr create( MSXAudio& audio, const DeviceConfig& config, const std::string& soundDeviceName); }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/YM2151.cc000066400000000000000000001311361257557151200175300ustar00rootroot00000000000000/***************************************************************************** * * Yamaha YM2151 driver (version 2.150 final beta) * ******************************************************************************/ #include "YM2151.hh" #include "DeviceConfig.hh" #include "Math.hh" #include "serialize.hh" #include #include namespace openmsx { // TODO void ym2151WritePortCallback(void* ref, unsigned port, byte value); static const int FREQ_SH = 16; // 16.16 fixed point (frequency calculations) static const int FREQ_MASK = (1 << FREQ_SH) - 1; static const int ENV_BITS = 10; static const int ENV_LEN = 1 << ENV_BITS; static const float ENV_STEP = 128.0f / ENV_LEN; static const int MAX_ATT_INDEX = ENV_LEN - 1; // 1023 static const int MIN_ATT_INDEX = 0; static const unsigned EG_ATT = 4; static const unsigned EG_DEC = 3; static const unsigned EG_SUS = 2; static const unsigned EG_REL = 1; static const unsigned EG_OFF = 0; static const int SIN_BITS = 10; static const int SIN_LEN = 1 << SIN_BITS; static const int SIN_MASK = SIN_LEN - 1; static const int TL_RES_LEN = 256; // 8 bits addressing (real chip) // TL_TAB_LEN is calculated as: // 13 - sinus amplitude bits (Y axis) // 2 - sinus sign bit (Y axis) // TL_RES_LEN - sinus resolution (X axis) static const unsigned TL_TAB_LEN = 13 * 2 * TL_RES_LEN; static int tl_tab[TL_TAB_LEN]; static const unsigned ENV_QUIET = TL_TAB_LEN >> 3; // sin waveform table in 'decibel' scale static unsigned sin_tab[SIN_LEN]; // translate from D1L to volume index (16 D1L levels) static unsigned d1l_tab[16]; static const unsigned RATE_STEPS = 8; static byte eg_inc[19 * RATE_STEPS] = { //cycle:0 1 2 3 4 5 6 7 /* 0 */ 0,1, 0,1, 0,1, 0,1, // rates 00..11 0 (increment by 0 or 1) /* 1 */ 0,1, 0,1, 1,1, 0,1, // rates 00..11 1 /* 2 */ 0,1, 1,1, 0,1, 1,1, // rates 00..11 2 /* 3 */ 0,1, 1,1, 1,1, 1,1, // rates 00..11 3 /* 4 */ 1,1, 1,1, 1,1, 1,1, // rate 12 0 (increment by 1) /* 5 */ 1,1, 1,2, 1,1, 1,2, // rate 12 1 /* 6 */ 1,2, 1,2, 1,2, 1,2, // rate 12 2 /* 7 */ 1,2, 2,2, 1,2, 2,2, // rate 12 3 /* 8 */ 2,2, 2,2, 2,2, 2,2, // rate 13 0 (increment by 2) /* 9 */ 2,2, 2,4, 2,2, 2,4, // rate 13 1 /*10 */ 2,4, 2,4, 2,4, 2,4, // rate 13 2 /*11 */ 2,4, 4,4, 2,4, 4,4, // rate 13 3 /*12 */ 4,4, 4,4, 4,4, 4,4, // rate 14 0 (increment by 4) /*13 */ 4,4, 4,8, 4,4, 4,8, // rate 14 1 /*14 */ 4,8, 4,8, 4,8, 4,8, // rate 14 2 /*15 */ 4,8, 8,8, 4,8, 8,8, // rate 14 3 /*16 */ 8,8, 8,8, 8,8, 8,8, // rates 15 0, 15 1, 15 2, 15 3 (increment by 8) /*17 */ 16,16,16,16,16,16,16,16, // rates 15 2, 15 3 for attack /*18 */ 0,0, 0,0, 0,0, 0,0, // infinity rates for attack and decay(s) }; #define O(a) (a*RATE_STEPS) // note that there is no O(17) in this table - it's directly in the code static byte eg_rate_select[32 + 64 + 32] = { // Envelope Generator rates (32 + 64 rates + 32 RKS) // 32 dummy (infinite time) rates O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), // rates 00-11 O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), // rate 12 O( 4),O( 5),O( 6),O( 7), // rate 13 O( 8),O( 9),O(10),O(11), // rate 14 O(12),O(13),O(14),O(15), // rate 15 O(16),O(16),O(16),O(16), // 32 dummy rates (same as 15 3) O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16) }; #undef O // rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 // shift 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0 // mask 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0, 0 #define O(a) (a*1) static byte eg_rate_shift[32 + 64 + 32] = { // Envelope Generator counter shifts (32 + 64 rates + 32 RKS) // 32 infinite time rates O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), // rates 00-11 O(11),O(11),O(11),O(11), O(10),O(10),O(10),O(10), O( 9),O( 9),O( 9),O( 9), O( 8),O( 8),O( 8),O( 8), O( 7),O( 7),O( 7),O( 7), O( 6),O( 6),O( 6),O( 6), O( 5),O( 5),O( 5),O( 5), O( 4),O( 4),O( 4),O( 4), O( 3),O( 3),O( 3),O( 3), O( 2),O( 2),O( 2),O( 2), O( 1),O( 1),O( 1),O( 1), O( 0),O( 0),O( 0),O( 0), // rate 12 O( 0),O( 0),O( 0),O( 0), // rate 13 O( 0),O( 0),O( 0),O( 0), // rate 14 O( 0),O( 0),O( 0),O( 0), // rate 15 O( 0),O( 0),O( 0),O( 0), // 32 dummy rates (same as 15 3) O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0) }; #undef O // DT2 defines offset in cents from base note // // This table defines offset in frequency-deltas table. // User's Manual page 22 // // Values below were calculated using formula: value = orig.val / 1.5625 // // DT2=0 DT2=1 DT2=2 DT2=3 // 0 600 781 950 static unsigned dt2_tab[4] = { 0, 384, 500, 608 }; // DT1 defines offset in Hertz from base note // This table is converted while initialization... // Detune table shown in YM2151 User's Manual is wrong (verified on the real chip) static byte dt1_tab[4 * 32] = { // DT1 = 0 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // DT1 = 1 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 8, 8, // DT1 = 2 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 9,10,11,12,13,14,16,16,16,16, // DT1 = 3 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 9,10,11,12,13,14,16,17,19,20,22,22,22,22 }; static word phaseinc_rom[768] = { 1299,1300,1301,1302,1303,1304,1305,1306,1308,1309,1310,1311,1313,1314,1315,1316, 1318,1319,1320,1321,1322,1323,1324,1325,1327,1328,1329,1330,1332,1333,1334,1335, 1337,1338,1339,1340,1341,1342,1343,1344,1346,1347,1348,1349,1351,1352,1353,1354, 1356,1357,1358,1359,1361,1362,1363,1364,1366,1367,1368,1369,1371,1372,1373,1374, 1376,1377,1378,1379,1381,1382,1383,1384,1386,1387,1388,1389,1391,1392,1393,1394, 1396,1397,1398,1399,1401,1402,1403,1404,1406,1407,1408,1409,1411,1412,1413,1414, 1416,1417,1418,1419,1421,1422,1423,1424,1426,1427,1429,1430,1431,1432,1434,1435, 1437,1438,1439,1440,1442,1443,1444,1445,1447,1448,1449,1450,1452,1453,1454,1455, 1458,1459,1460,1461,1463,1464,1465,1466,1468,1469,1471,1472,1473,1474,1476,1477, 1479,1480,1481,1482,1484,1485,1486,1487,1489,1490,1492,1493,1494,1495,1497,1498, 1501,1502,1503,1504,1506,1507,1509,1510,1512,1513,1514,1515,1517,1518,1520,1521, 1523,1524,1525,1526,1528,1529,1531,1532,1534,1535,1536,1537,1539,1540,1542,1543, 1545,1546,1547,1548,1550,1551,1553,1554,1556,1557,1558,1559,1561,1562,1564,1565, 1567,1568,1569,1570,1572,1573,1575,1576,1578,1579,1580,1581,1583,1584,1586,1587, 1590,1591,1592,1593,1595,1596,1598,1599,1601,1602,1604,1605,1607,1608,1609,1610, 1613,1614,1615,1616,1618,1619,1621,1622,1624,1625,1627,1628,1630,1631,1632,1633, 1637,1638,1639,1640,1642,1643,1645,1646,1648,1649,1651,1652,1654,1655,1656,1657, 1660,1661,1663,1664,1666,1667,1669,1670,1672,1673,1675,1676,1678,1679,1681,1682, 1685,1686,1688,1689,1691,1692,1694,1695,1697,1698,1700,1701,1703,1704,1706,1707, 1709,1710,1712,1713,1715,1716,1718,1719,1721,1722,1724,1725,1727,1728,1730,1731, 1734,1735,1737,1738,1740,1741,1743,1744,1746,1748,1749,1751,1752,1754,1755,1757, 1759,1760,1762,1763,1765,1766,1768,1769,1771,1773,1774,1776,1777,1779,1780,1782, 1785,1786,1788,1789,1791,1793,1794,1796,1798,1799,1801,1802,1804,1806,1807,1809, 1811,1812,1814,1815,1817,1819,1820,1822,1824,1825,1827,1828,1830,1832,1833,1835, 1837,1838,1840,1841,1843,1845,1846,1848,1850,1851,1853,1854,1856,1858,1859,1861, 1864,1865,1867,1868,1870,1872,1873,1875,1877,1879,1880,1882,1884,1885,1887,1888, 1891,1892,1894,1895,1897,1899,1900,1902,1904,1906,1907,1909,1911,1912,1914,1915, 1918,1919,1921,1923,1925,1926,1928,1930,1932,1933,1935,1937,1939,1940,1942,1944, 1946,1947,1949,1951,1953,1954,1956,1958,1960,1961,1963,1965,1967,1968,1970,1972, 1975,1976,1978,1980,1982,1983,1985,1987,1989,1990,1992,1994,1996,1997,1999,2001, 2003,2004,2006,2008,2010,2011,2013,2015,2017,2019,2021,2022,2024,2026,2028,2029, 2032,2033,2035,2037,2039,2041,2043,2044,2047,2048,2050,2052,2054,2056,2058,2059, 2062,2063,2065,2067,2069,2071,2073,2074,2077,2078,2080,2082,2084,2086,2088,2089, 2092,2093,2095,2097,2099,2101,2103,2104,2107,2108,2110,2112,2114,2116,2118,2119, 2122,2123,2125,2127,2129,2131,2133,2134,2137,2139,2141,2142,2145,2146,2148,2150, 2153,2154,2156,2158,2160,2162,2164,2165,2168,2170,2172,2173,2176,2177,2179,2181, 2185,2186,2188,2190,2192,2194,2196,2197,2200,2202,2204,2205,2208,2209,2211,2213, 2216,2218,2220,2222,2223,2226,2227,2230,2232,2234,2236,2238,2239,2242,2243,2246, 2249,2251,2253,2255,2256,2259,2260,2263,2265,2267,2269,2271,2272,2275,2276,2279, 2281,2283,2285,2287,2288,2291,2292,2295,2297,2299,2301,2303,2304,2307,2308,2311, 2315,2317,2319,2321,2322,2325,2326,2329,2331,2333,2335,2337,2338,2341,2342,2345, 2348,2350,2352,2354,2355,2358,2359,2362,2364,2366,2368,2370,2371,2374,2375,2378, 2382,2384,2386,2388,2389,2392,2393,2396,2398,2400,2402,2404,2407,2410,2411,2414, 2417,2419,2421,2423,2424,2427,2428,2431,2433,2435,2437,2439,2442,2445,2446,2449, 2452,2454,2456,2458,2459,2462,2463,2466,2468,2470,2472,2474,2477,2480,2481,2484, 2488,2490,2492,2494,2495,2498,2499,2502,2504,2506,2508,2510,2513,2516,2517,2520, 2524,2526,2528,2530,2531,2534,2535,2538,2540,2542,2544,2546,2549,2552,2553,2556, 2561,2563,2565,2567,2568,2571,2572,2575,2577,2579,2581,2583,2586,2589,2590,2593 }; // Noise LFO waveform. // // Here are just 256 samples out of much longer data. // // It does NOT repeat every 256 samples on real chip and I wasnt able to find // the point where it repeats (even in strings as long as 131072 samples). // // I only put it here because its better than nothing and perhaps // someone might be able to figure out the real algorithm. // // Note that (due to the way the LFO output is calculated) it is quite // possible that two values: 0x80 and 0x00 might be wrong in this table. // To be exact: // some 0x80 could be 0x81 as well as some 0x00 could be 0x01. static byte lfo_noise_waveform[256] = { 0xFF,0xEE,0xD3,0x80,0x58,0xDA,0x7F,0x94,0x9E,0xE3,0xFA,0x00,0x4D,0xFA,0xFF,0x6A, 0x7A,0xDE,0x49,0xF6,0x00,0x33,0xBB,0x63,0x91,0x60,0x51,0xFF,0x00,0xD8,0x7F,0xDE, 0xDC,0x73,0x21,0x85,0xB2,0x9C,0x5D,0x24,0xCD,0x91,0x9E,0x76,0x7F,0x20,0xFB,0xF3, 0x00,0xA6,0x3E,0x42,0x27,0x69,0xAE,0x33,0x45,0x44,0x11,0x41,0x72,0x73,0xDF,0xA2, 0x32,0xBD,0x7E,0xA8,0x13,0xEB,0xD3,0x15,0xDD,0xFB,0xC9,0x9D,0x61,0x2F,0xBE,0x9D, 0x23,0x65,0x51,0x6A,0x84,0xF9,0xC9,0xD7,0x23,0xBF,0x65,0x19,0xDC,0x03,0xF3,0x24, 0x33,0xB6,0x1E,0x57,0x5C,0xAC,0x25,0x89,0x4D,0xC5,0x9C,0x99,0x15,0x07,0xCF,0xBA, 0xC5,0x9B,0x15,0x4D,0x8D,0x2A,0x1E,0x1F,0xEA,0x2B,0x2F,0x64,0xA9,0x50,0x3D,0xAB, 0x50,0x77,0xE9,0xC0,0xAC,0x6D,0x3F,0xCA,0xCF,0x71,0x7D,0x80,0xA6,0xFD,0xFF,0xB5, 0xBD,0x6F,0x24,0x7B,0x00,0x99,0x5D,0xB1,0x48,0xB0,0x28,0x7F,0x80,0xEC,0xBF,0x6F, 0x6E,0x39,0x90,0x42,0xD9,0x4E,0x2E,0x12,0x66,0xC8,0xCF,0x3B,0x3F,0x10,0x7D,0x79, 0x00,0xD3,0x1F,0x21,0x93,0x34,0xD7,0x19,0x22,0xA2,0x08,0x20,0xB9,0xB9,0xEF,0x51, 0x99,0xDE,0xBF,0xD4,0x09,0x75,0xE9,0x8A,0xEE,0xFD,0xE4,0x4E,0x30,0x17,0xDF,0xCE, 0x11,0xB2,0x28,0x35,0xC2,0x7C,0x64,0xEB,0x91,0x5F,0x32,0x0C,0x6E,0x00,0xF9,0x92, 0x19,0xDB,0x8F,0xAB,0xAE,0xD6,0x12,0xC4,0x26,0x62,0xCE,0xCC,0x0A,0x03,0xE7,0xDD, 0xE2,0x4D,0x8A,0xA6,0x46,0x95,0x0F,0x8F,0xF5,0x15,0x97,0x32,0xD4,0x28,0x1E,0x55 }; void YM2151::initTables() { for (int x = 0; x < TL_RES_LEN; ++x) { float m = (1 << 16) / exp2f((x + 1) * (ENV_STEP / 4.0f) / 8.0f); m = floorf(m); // we never reach (1 << 16) here due to the (x + 1) // result fits within 16 bits at maximum int n = int(m); // 16 bits here n >>= 4; // 12 bits here if (n & 1) { // round to closest n = (n >> 1) + 1; } else { n = n >> 1; } // 11 bits here (rounded) n <<= 2; // 13 bits here (as in real chip) tl_tab[x * 2 + 0] = n; tl_tab[x * 2 + 1] = -tl_tab[x * 2 + 0]; for (int i = 1; i < 13; ++i) { tl_tab[x * 2 + 0 + i * 2 * TL_RES_LEN] = tl_tab[x * 2 + 0] >> i; tl_tab[x * 2 + 1 + i * 2 * TL_RES_LEN] = -tl_tab[x * 2 + 0 + i * 2 * TL_RES_LEN]; } } static const float LOG2 = log(2.0); for (int i = 0; i < SIN_LEN; ++i) { // non-standard sinus float m = sinf((i * 2 + 1) * M_PI / SIN_LEN); // verified on the real chip // we never reach zero here due to (i * 2 + 1) float o = -8.0f * logf(std::abs(m)) / LOG2; // convert to decibels o = o / (ENV_STEP / 4); int n = int(2.0f * o); if (n & 1) { // round to closest n = (n >> 1) + 1; } else { n = n >> 1; } sin_tab[i] = n * 2 + (m >= 0.0f ? 0 : 1); } // calculate d1l_tab table for (int i = 0; i < 16; ++i) { // every 3 'dB' except for all bits = 1 = 45+48 'dB' d1l_tab[i] = unsigned((i != 15 ? i : i + 16) * (4.0f / ENV_STEP)); } } void YM2151::initChipTables() { // this loop calculates Hertz values for notes from c-0 to b-7 // including 64 'cents' (100/64 that is 1.5625 of real cent) per note // i*100/64/1200 is equal to i/768 // real chip works with 10 bits fixed point values (10.10) // -10 because phaseinc_rom table values are already in 10.10 format float mult = 1 << (FREQ_SH - 10); for (int i = 0; i < 768; ++i) { float phaseinc = phaseinc_rom[i]; // real chip phase increment // octave 2 - reference octave // adjust to X.10 fixed point freq[768 + 2 * 768 + i] = int(phaseinc * mult) & 0xffffffc0; // octave 0 and octave 1 for (int j = 0; j < 2; ++j) { // adjust to X.10 fixed point freq[768 + j * 768 + i] = (freq[768 + 2 * 768 + i] >> (2 - j)) & 0xffffffc0; } // octave 3 to 7 for (int j = 3; j < 8; ++j) { freq[768 + j * 768 + i] = freq[768 + 2 * 768 + i] << (j - 2); } } // octave -1 (all equal to: oct 0, _KC_00_, _KF_00_) for (int i = 0; i < 768; ++i) { freq[0 * 768 + i] = freq[1 * 768 + 0]; } // octave 8 and 9 (all equal to: oct 7, _KC_14_, _KF_63_) for (int j = 8; j < 10; ++j) { for (int i = 0; i < 768; ++i) { freq[768 + j * 768 + i] = freq[768 + 8 * 768 - 1]; } } mult = 1 << FREQ_SH; for (int j = 0; j < 4; ++j) { for (int i = 0; i < 32; ++i) { // calculate phase increment float phaseinc = float(dt1_tab[j * 32 + i]) / (1 << 20) * (SIN_LEN); // positive and negative values dt1_freq[(j + 0) * 32 + i] = int(phaseinc * mult); dt1_freq[(j + 4) * 32 + i] = -dt1_freq[(j + 0) * 32 + i]; } } timer_A_val = 0; // calculate noise periods table // this table tells how many cycles/samples it takes before noise is recalculated. // 2/2 means every cycle/sample, 2/5 means 2 out of 5 cycles/samples, etc. for (int i = 0; i < 32; ++i) { noise_tab[i] = 32 - (i != 31 ? i : 30); // rate 30 and 31 are the same } } void YM2151::keyOn(YM2151Operator* op, unsigned keySet) { if (!op->key) { op->phase = 0; /* clear phase */ op->state = EG_ATT; /* KEY ON = attack */ op->volume += (~op->volume * (eg_inc[op->eg_sel_ar + ((eg_cnt >> op->eg_sh_ar)&7)]) ) >>4; if (op->volume <= MIN_ATT_INDEX) { op->volume = MIN_ATT_INDEX; op->state = EG_DEC; } } op->key |= keySet; } void YM2151::keyOff(YM2151Operator* op, unsigned keyClear) { if (op->key) { op->key &= keyClear; if (!op->key) { if (op->state > EG_REL) { op->state = EG_REL; /* KEY OFF = release */ } } } } void YM2151::envelopeKONKOFF(YM2151Operator* op, int v) { if (v & 0x08) { // M1 keyOn (op + 0, 1); } else { keyOff(op + 0,unsigned(~1)); } if (v & 0x20) { // M2 keyOn (op + 1, 1); } else { keyOff(op + 1,unsigned(~1)); } if (v & 0x10) { // C1 keyOn (op + 2, 1); } else { keyOff(op + 2,unsigned(~1)); } if (v & 0x40) { // C2 keyOn (op + 3, 1); } else { keyOff(op + 3,unsigned(~1)); } } void YM2151::setConnect(YM2151Operator* om1, int cha, int v) { YM2151Operator* om2 = om1 + 1; YM2151Operator* oc1 = om1 + 2; // set connect algorithm // MEM is simply one sample delay switch (v & 7) { case 0: // M1---C1---MEM---M2---C2---OUT om1->connect = &c1; oc1->connect = &mem; om2->connect = &c2; om1->mem_connect = &m2; break; case 1: // M1------+-MEM---M2---C2---OUT // C1-+ om1->connect = &mem; oc1->connect = &mem; om2->connect = &c2; om1->mem_connect = &m2; break; case 2: // M1-----------------+-C2---OUT // C1---MEM---M2-+ om1->connect = &c2; oc1->connect = &mem; om2->connect = &c2; om1->mem_connect = &m2; break; case 3: // M1---C1---MEM------+-C2---OUT // M2-+ om1->connect = &c1; oc1->connect = &mem; om2->connect = &c2; om1->mem_connect = &c2; break; case 4: // M1---C1-+-OUT // M2---C2-+ // MEM: not used om1->connect = &c1; oc1->connect = &chanout[cha]; om2->connect = &c2; om1->mem_connect = &mem; // store it anywhere where it will not be used break; case 5: // +----C1----+ // M1-+-MEM---M2-+-OUT // +----C2----+ om1->connect = nullptr; // special mark oc1->connect = &chanout[cha]; om2->connect = &chanout[cha]; om1->mem_connect = &m2; break; case 6: // M1---C1-+ // M2-+-OUT // C2-+ // MEM: not used om1->connect = &c1; oc1->connect = &chanout[cha]; om2->connect = &chanout[cha]; om1->mem_connect = &mem; // store it anywhere where it will not be used break; case 7: // M1-+ // C1-+-OUT // M2-+ // C2-+ // MEM: not used om1->connect = &chanout[cha]; oc1->connect = &chanout[cha]; om2->connect = &chanout[cha]; om1->mem_connect = &mem; // store it anywhere where it will not be used break; } } void YM2151::refreshEG(YM2151Operator* op) { unsigned kc = op->kc; // v = 32 + 2*RATE + RKS = max 126 unsigned v = kc >> op->ks; if ((op->ar + v) < 32 + 62) { op->eg_sh_ar = eg_rate_shift [op->ar + v]; op->eg_sel_ar = eg_rate_select[op->ar + v]; } else { op->eg_sh_ar = 0; op->eg_sel_ar = 17 * RATE_STEPS; } op->eg_sh_d1r = eg_rate_shift [op->d1r + v]; op->eg_sel_d1r = eg_rate_select[op->d1r + v]; op->eg_sh_d2r = eg_rate_shift [op->d2r + v]; op->eg_sel_d2r = eg_rate_select[op->d2r + v]; op->eg_sh_rr = eg_rate_shift [op->rr + v]; op->eg_sel_rr = eg_rate_select[op->rr + v]; op += 1; v = kc >> op->ks; if ((op->ar + v) < 32 + 62) { op->eg_sh_ar = eg_rate_shift [op->ar + v]; op->eg_sel_ar = eg_rate_select[op->ar + v]; } else { op->eg_sh_ar = 0; op->eg_sel_ar = 17 * RATE_STEPS; } op->eg_sh_d1r = eg_rate_shift [op->d1r + v]; op->eg_sel_d1r = eg_rate_select[op->d1r + v]; op->eg_sh_d2r = eg_rate_shift [op->d2r + v]; op->eg_sel_d2r = eg_rate_select[op->d2r + v]; op->eg_sh_rr = eg_rate_shift [op->rr + v]; op->eg_sel_rr = eg_rate_select[op->rr + v]; op += 1; v = kc >> op->ks; if ((op->ar + v) < 32 + 62) { op->eg_sh_ar = eg_rate_shift [op->ar + v]; op->eg_sel_ar = eg_rate_select[op->ar + v]; } else { op->eg_sh_ar = 0; op->eg_sel_ar = 17 * RATE_STEPS; } op->eg_sh_d1r = eg_rate_shift [op->d1r + v]; op->eg_sel_d1r = eg_rate_select[op->d1r + v]; op->eg_sh_d2r = eg_rate_shift [op->d2r + v]; op->eg_sel_d2r = eg_rate_select[op->d2r + v]; op->eg_sh_rr = eg_rate_shift [op->rr + v]; op->eg_sel_rr = eg_rate_select[op->rr + v]; op += 1; v = kc >> op->ks; if ((op->ar + v) < 32 + 62) { op->eg_sh_ar = eg_rate_shift [op->ar + v]; op->eg_sel_ar = eg_rate_select[op->ar + v]; } else { op->eg_sh_ar = 0; op->eg_sel_ar = 17 * RATE_STEPS; } op->eg_sh_d1r = eg_rate_shift [op->d1r + v]; op->eg_sel_d1r = eg_rate_select[op->d1r + v]; op->eg_sh_d2r = eg_rate_shift [op->d2r + v]; op->eg_sel_d2r = eg_rate_select[op->d2r + v]; op->eg_sh_rr = eg_rate_shift [op->rr + v]; op->eg_sel_rr = eg_rate_select[op->rr + v]; } void YM2151::writeReg(byte r, byte v, EmuTime::param time) { updateStream(time); YM2151Operator* op = &oper[(r & 0x07) * 4 + ((r & 0x18) >> 3)]; regs[r] = v; switch (r & 0xe0) { case 0x00: switch (r) { case 0x01: // LFO reset(bit 1), Test Register (other bits) test = v; if (v & 2) lfo_phase = 0; break; case 0x08: envelopeKONKOFF(&oper[(v & 7) * 4], v); break; case 0x0f: // noise mode enable, noise period noise = v; noise_f = noise_tab[v & 0x1f]; noise_p = 0; break; case 0x10: timer_A_val &= 0x03; timer_A_val |= v << 2; timer1->setValue(timer_A_val); break; case 0x11: timer_A_val &= 0x03fc; timer_A_val |= v & 3; timer1->setValue(timer_A_val); break; case 0x12: timer2->setValue(v); break; case 0x14: // CSM, irq flag reset, irq enable, timer start/stop irq_enable = v; // bit 3-timer B, bit 2-timer A, bit 7 - CSM if (v & 0x10) { // reset timer A irq flag resetStatus(1); } if (v & 0x20) { // reset timer B irq flag resetStatus(2); } timer1->setStart((v & 4) != 0, time); timer2->setStart((v & 8) != 0, time); break; case 0x18: // LFO frequency lfo_overflow = (1 << ((15 - (v >> 4)) + 3)); lfo_counter_add = 0x10 + (v & 0x0f); break; case 0x19: // PMD (bit 7==1) or AMD (bit 7==0) if (v & 0x80) { pmd = v & 0x7f; } else { amd = v & 0x7f; } break; case 0x1b: // CT2, CT1, LFO waveform ct = v >> 6; lfo_wsel = v & 3; // TODO ym2151WritePortCallback(0 , ct); break; default: break; } break; case 0x20: op = &oper[(r & 7) * 4]; switch (r & 0x18) { case 0x00: // RL enable, Feedback, Connection op->fb_shift = ((v >> 3) & 7) ? ((v >> 3) & 7) + 6 : 0; pan[(r & 7) * 2 + 0] = (v & 0x40) ? ~0 : 0; pan[(r & 7) * 2 + 1] = (v & 0x80) ? ~0 : 0; setConnect(op, r & 7, v & 7); break; case 0x08: // Key Code v &= 0x7f; if (v != op->kc) { unsigned kc_channel = (v - (v>>2))*64; kc_channel += 768; kc_channel |= (op->kc_i & 63); (op + 0)->kc = v; (op + 0)->kc_i = kc_channel; (op + 1)->kc = v; (op + 1)->kc_i = kc_channel; (op + 2)->kc = v; (op + 2)->kc_i = kc_channel; (op + 3)->kc = v; (op + 3)->kc_i = kc_channel; unsigned kc = v>>2; (op + 0)->dt1 = dt1_freq[(op + 0)->dt1_i + kc]; (op + 0)->freq = ((freq[kc_channel + (op + 0)->dt2] + (op + 0)->dt1) * (op + 0)->mul) >> 1; (op + 1)->dt1 = dt1_freq[(op + 1)->dt1_i + kc]; (op + 1)->freq = ((freq[kc_channel + (op + 1)->dt2] + (op + 1)->dt1) * (op + 1)->mul) >> 1; (op + 2)->dt1 = dt1_freq[(op + 2)->dt1_i + kc]; (op + 2)->freq = ((freq[kc_channel + (op + 2)->dt2] + (op + 2)->dt1) * (op + 2)->mul) >> 1; (op + 3)->dt1 = dt1_freq[(op + 3)->dt1_i + kc]; (op + 3)->freq = ((freq[kc_channel + (op + 3)->dt2] + (op + 3)->dt1) * (op + 3)->mul) >> 1; refreshEG( op ); } break; case 0x10: // Key Fraction v >>= 2; if (v != (op->kc_i & 63)) { unsigned kc_channel = v; kc_channel |= (op->kc_i & ~63); (op + 0)->kc_i = kc_channel; (op + 1)->kc_i = kc_channel; (op + 2)->kc_i = kc_channel; (op + 3)->kc_i = kc_channel; (op + 0)->freq = ((freq[kc_channel + (op + 0)->dt2] + (op + 0)->dt1) * (op + 0)->mul) >> 1; (op + 1)->freq = ((freq[kc_channel + (op + 1)->dt2] + (op + 1)->dt1) * (op + 1)->mul) >> 1; (op + 2)->freq = ((freq[kc_channel + (op + 2)->dt2] + (op + 2)->dt1) * (op + 2)->mul) >> 1; (op + 3)->freq = ((freq[kc_channel + (op + 3)->dt2] + (op + 3)->dt1) * (op + 3)->mul) >> 1; } break; case 0x18: // PMS, AMS op->pms = (v >> 4) & 7; op->ams = (v & 3); break; } break; case 0x40: { // DT1, MUL unsigned olddt1_i = op->dt1_i; unsigned oldmul = op->mul; op->dt1_i = (v & 0x70) << 1; op->mul = (v & 0x0f) ? (v & 0x0f) << 1 : 1; if (olddt1_i != op->dt1_i) { op->dt1 = dt1_freq[ op->dt1_i + (op->kc>>2) ]; } if ((olddt1_i != op->dt1_i) || (oldmul != op->mul)) { op->freq = ((freq[op->kc_i + op->dt2] + op->dt1) * op->mul) >> 1; } break; } case 0x60: // TL op->tl = (v & 0x7f) << (ENV_BITS - 7); // 7bit TL break; case 0x80: { // KS, AR unsigned oldks = op->ks; unsigned oldar = op->ar; op->ks = 5 - (v >> 6); op->ar = (v & 0x1f) ? 32 + ((v & 0x1f) << 1) : 0; if ((op->ar != oldar) || (op->ks != oldks)) { if ((op->ar + (op->kc >> op->ks)) < 32 + 62) { op->eg_sh_ar = eg_rate_shift [op->ar + (op->kc>>op->ks)]; op->eg_sel_ar = eg_rate_select[op->ar + (op->kc>>op->ks)]; } else { op->eg_sh_ar = 0; op->eg_sel_ar = 17 * RATE_STEPS; } } if (op->ks != oldks) { op->eg_sh_d1r = eg_rate_shift [op->d1r + (op->kc >> op->ks)]; op->eg_sel_d1r = eg_rate_select[op->d1r + (op->kc >> op->ks)]; op->eg_sh_d2r = eg_rate_shift [op->d2r + (op->kc >> op->ks)]; op->eg_sel_d2r = eg_rate_select[op->d2r + (op->kc >> op->ks)]; op->eg_sh_rr = eg_rate_shift [op->rr + (op->kc >> op->ks)]; op->eg_sel_rr = eg_rate_select[op->rr + (op->kc >> op->ks)]; } break; } case 0xa0: // LFO AM enable, D1R op->AMmask = (v & 0x80) ? ~0 : 0; op->d1r = (v & 0x1f) ? 32 + ((v & 0x1f) << 1) : 0; op->eg_sh_d1r = eg_rate_shift [op->d1r + (op->kc >> op->ks)]; op->eg_sel_d1r = eg_rate_select[op->d1r + (op->kc >> op->ks)]; break; case 0xc0: { // DT2, D2R unsigned olddt2 = op->dt2; op->dt2 = dt2_tab[v >> 6]; if (op->dt2 != olddt2) { op->freq = ((freq[op->kc_i + op->dt2] + op->dt1) * op->mul) >> 1; } op->d2r = (v & 0x1f) ? 32 + ((v & 0x1f) << 1) : 0; op->eg_sh_d2r = eg_rate_shift [op->d2r + (op->kc >> op->ks)]; op->eg_sel_d2r = eg_rate_select[op->d2r + (op->kc >> op->ks)]; break; } case 0xe0: // D1L, RR op->d1l = d1l_tab[v >> 4]; op->rr = 34 + ((v & 0x0f) << 2); op->eg_sh_rr = eg_rate_shift [op->rr + (op->kc >> op->ks)]; op->eg_sel_rr = eg_rate_select[op->rr + (op->kc >> op->ks)]; break; } } YM2151::YM2151(const std::string& name, const std::string& desc, const DeviceConfig& config, EmuTime::param time) : ResampledSoundDevice(config.getMotherBoard(), name, desc, 8, true) , irq(config.getMotherBoard(), getName() + ".IRQ") , timer1(EmuTimer::createOPM_1(config.getScheduler(), *this)) , timer2(EmuTimer::createOPM_2(config.getScheduler(), *this)) { // Avoid UMR on savestate // TODO Registers 0x20-0xFF are cleared on reset. // Should we do the same for registers 0x00-0x1F? memset(regs, 0, sizeof(regs)); initTables(); initChipTables(); static const int CLCK_FREQ = 3579545; float input = CLCK_FREQ / 64.0f; setInputRate(int(input + 0.5f)); reset(time); registerSound(config); } YM2151::~YM2151() { unregisterSound(); } bool YM2151::checkMuteHelper() { for (auto& op : oper) { if (op.state != EG_OFF) return false; } return true; } void YM2151::reset(EmuTime::param time) { // initialize hardware registers for (auto& op : oper) { memset(&op, '\0', sizeof(op)); op.volume = MAX_ATT_INDEX; op.kc_i = 768; // min kc_i value } eg_timer = 0; eg_cnt = 0; lfo_timer = 0; lfo_counter = 0; lfo_phase = 0; lfo_wsel = 0; pmd = 0; amd = 0; lfa = 0; lfp = 0; test = 0; irq_enable = 0; timer1->setStart(0, time); timer2->setStart(0, time); noise = 0; noise_rng = 0; noise_p = 0; noise_f = noise_tab[0]; csm_req = 0; status = 0; writeReg(0x1b, 0, time); // only because of CT1, CT2 output pins writeReg(0x18, 0, time); // set LFO frequency for (int i = 0x20; i < 0x100; ++i) { // set the operators writeReg(i, 0, time); } irq.reset(); } int YM2151::opCalc(YM2151Operator* OP, unsigned env, int pm) { unsigned p = (env << 3) + sin_tab[(int((OP->phase & ~FREQ_MASK) + (pm << 15)) >> FREQ_SH) & SIN_MASK]; if (p >= TL_TAB_LEN) { return 0; } return tl_tab[p]; } int YM2151::opCalc1(YM2151Operator* OP, unsigned env, int pm) { int i = (OP->phase & ~FREQ_MASK) + pm; unsigned p = (env << 3) + sin_tab[(i >> FREQ_SH) & SIN_MASK]; if (p >= TL_TAB_LEN) { return 0; } return tl_tab[p]; } unsigned YM2151::volumeCalc(YM2151Operator* OP, unsigned AM) { return OP->tl + unsigned(OP->volume) + (AM & OP->AMmask); } void YM2151::chanCalc(unsigned chan) { m2 = c1 = c2 = mem = 0; YM2151Operator* op = &oper[chan*4]; // M1 *op->mem_connect = op->mem_value; // restore delayed sample (MEM) value to m2 or c2 unsigned AM = 0; if (op->ams) { AM = lfa << (op->ams-1); } unsigned env = volumeCalc(op, AM); { int out = op->fb_out_prev + op->fb_out_curr; op->fb_out_prev = op->fb_out_curr; if (!op->connect) { // algorithm 5 mem = c1 = c2 = op->fb_out_prev; } else { *op->connect = op->fb_out_prev; } op->fb_out_curr = 0; if (env < ENV_QUIET) { if (!op->fb_shift) { out = 0; } op->fb_out_curr = opCalc1(op, env, (out << op->fb_shift)); } } env = volumeCalc(op + 1, AM); // M2 if (env < ENV_QUIET) { *(op + 1)->connect += opCalc(op + 1, env, m2); } env = volumeCalc(op + 2, AM); // C1 if (env < ENV_QUIET) { *(op + 2)->connect += opCalc(op + 2, env, c1); } env = volumeCalc(op + 3, AM); // C2 if (env < ENV_QUIET) { chanout[chan] += opCalc(op + 3, env, c2); } // M1 op->mem_value = mem; } void YM2151::chan7Calc() { m2 = c1 = c2 = mem = 0; YM2151Operator* op = &oper[7 * 4]; // M1 *op->mem_connect = op->mem_value; // restore delayed sample (MEM) value to m2 or c2 unsigned AM = 0; if (op->ams) { AM = lfa << (op->ams - 1); } unsigned env = volumeCalc(op, AM); { int out = op->fb_out_prev + op->fb_out_curr; op->fb_out_prev = op->fb_out_curr; if (!op->connect) { // algorithm 5 mem = c1 = c2 = op->fb_out_prev; } else { // other algorithms *op->connect = op->fb_out_prev; } op->fb_out_curr = 0; if (env < ENV_QUIET) { if (!op->fb_shift) { out = 0; } op->fb_out_curr = opCalc1(op, env, (out << op->fb_shift)); } } env = volumeCalc(op + 1, AM); // M2 if (env < ENV_QUIET) { *(op + 1)->connect += opCalc(op + 1, env, m2); } env = volumeCalc(op + 2, AM); // C1 if (env < ENV_QUIET) { *(op + 2)->connect += opCalc(op + 2, env, c1); } env = volumeCalc(op + 3, AM); // C2 if (noise & 0x80) { unsigned noiseout = 0; if (env < 0x3ff) { noiseout = (env ^ 0x3ff) * 2; // range of the YM2151 noise output is -2044 to 2040 } chanout[7] += (noise_rng & 0x10000) ? noiseout : unsigned(-int(noiseout)); // bit 16 -> output } else { if (env < ENV_QUIET) { chanout[7] += opCalc(op + 3, env, c2); } } // M1 op->mem_value = mem; } /* The 'rate' is calculated from following formula (example on decay rate): rks = notecode after key scaling (a value from 0 to 31) DR = value written to the chip register rate = 2*DR + rks; (max rate = 2*31+31 = 93) Four MSBs of the 'rate' above are the 'main' rate (from 00 to 15) Two LSBs of the 'rate' above are the value 'x' (the shape type). (eg. '11 2' means that 'rate' is 11*4+2=46) NOTE: A 'sample' in the description below is actually 3 output samples, thats because the Envelope Generator clock is equal to internal_clock/3. Single '-' (minus) character in the diagrams below represents one sample on the output; this is for rates 11 x (11 0, 11 1, 11 2 and 11 3) these 'main' rates: 00 x: single '-' = 2048 samples; (ie. level can change every 2048 samples) 01 x: single '-' = 1024 samples; 02 x: single '-' = 512 samples; 03 x: single '-' = 256 samples; 04 x: single '-' = 128 samples; 05 x: single '-' = 64 samples; 06 x: single '-' = 32 samples; 07 x: single '-' = 16 samples; 08 x: single '-' = 8 samples; 09 x: single '-' = 4 samples; 10 x: single '-' = 2 samples; 11 x: single '-' = 1 sample; (ie. level can change every 1 sample) Shapes for rates 11 x look like this: rate: step: 11 0 01234567 level: 0 -- 1 -- 2 -- 3 -- rate: step: 11 1 01234567 level: 0 -- 1 -- 2 - 3 - 4 -- rate: step: 11 2 01234567 level: 0 -- 1 - 2 - 3 -- 4 - 5 - rate: step: 11 3 01234567 level: 0 -- 1 - 2 - 3 - 4 - 5 - 6 - For rates 12 x, 13 x, 14 x and 15 x output level changes on every sample - this means that the waveform looks like this: (but the level changes by different values on different steps) 12 3 01234567 0 - 2 - 4 - 8 - 10 - 12 - 14 - 18 - 20 - Notes about the timing: ---------------------- 1. Synchronism Output level of each two (or more) voices running at the same 'main' rate (eg 11 0 and 11 1 in the diagram below) will always be changing in sync, even if there're started with some delay. Note that, in the diagram below, the decay phase in channel 0 starts at sample #2, while in channel 1 it starts at sample #6. Anyway, both channels will always change their levels at exactly the same (following) samples. (S - start point of this channel, A-attack phase, D-decay phase): step: 01234567012345670123456 channel 0: -- | -- | - | - | -- | -- | -- | - | - | -- AADDDDDDDDDDDDDDDD S 01234567012345670123456 channel 1: - | - | -- | -- | -- | - | - | -- | -- | -- AADDDDDDDDDDDDDDDD S 01234567012345670123456 2. Shifted (delayed) synchronism Output of each two (or more) voices running at different 'main' rate (9 1, 10 1 and 11 1 in the diagrams below) will always be changing in 'delayed-sync' (even if there're started with some delay as in "1.") Note that the shapes are delayed by exactly one sample per one 'main' rate increment. (Normally one would expect them to start at the same samples.) See diagram below (* - start point of the shape). cycle: 0123456701234567012345670123456701234567012345670123456701234567 rate 09 1 *------- -------- ---- ---- -------- *------- | -------- | ---- | ---- | -------- rate 10 1 | -- | *--- | ---- | -- | -- | ---- | *--- | | ---- | | -- | | <- one step (two samples) delay between 9 1 and 10 1 | -- | | | ----| | *--- | ---- | -- | -- | ---- rate 11 1 | - | -- | *- | -- | - | - | -- | *- | -- | - || <- one step (one sample) delay between 10 1 and 11 1 - || --| *- -- - - -- *- -- - - -- */ void YM2151::advanceEG() { if (eg_timer++ != 3) { // envelope generator timer overlfows every 3 samples (on real chip) return; } eg_timer = 0; eg_cnt++; // envelope generator for (auto& op : oper) { switch (op.state) { case EG_ATT: // attack phase if (!(eg_cnt & ((1 << op.eg_sh_ar) - 1))) { op.volume += (~op.volume * (eg_inc[op.eg_sel_ar + ((eg_cnt >> op.eg_sh_ar) & 7)]) ) >> 4; if (op.volume <= MIN_ATT_INDEX) { op.volume = MIN_ATT_INDEX; op.state = EG_DEC; } } break; case EG_DEC: // decay phase if (!(eg_cnt & ((1 << op.eg_sh_d1r) - 1))) { op.volume += eg_inc[op.eg_sel_d1r + ((eg_cnt >> op.eg_sh_d1r) & 7)]; if (unsigned(op.volume) >= op.d1l) { op.state = EG_SUS; } } break; case EG_SUS: // sustain phase if (!(eg_cnt & ((1 << op.eg_sh_d2r) - 1))) { op.volume += eg_inc[op.eg_sel_d2r + ((eg_cnt >> op.eg_sh_d2r) & 7)]; if (op.volume >= MAX_ATT_INDEX) { op.volume = MAX_ATT_INDEX; op.state = EG_OFF; } } break; case EG_REL: // release phase if (!(eg_cnt & ((1 << op.eg_sh_rr) - 1))) { op.volume += eg_inc[op.eg_sel_rr + ((eg_cnt >> op.eg_sh_rr) & 7)]; if (op.volume >= MAX_ATT_INDEX) { op.volume = MAX_ATT_INDEX; op.state = EG_OFF; } } break; } } } void YM2151::advance() { // LFO if (test & 2) { lfo_phase = 0; } else { if (lfo_timer++ >= lfo_overflow) { lfo_timer = 0; lfo_counter += lfo_counter_add; lfo_phase += (lfo_counter >> 4); lfo_phase &= 255; lfo_counter &= 15; } } unsigned i = lfo_phase; // calculate LFO AM and PM waveform value (all verified on real chip, // except for noise algorithm which is impossible to analyse) int a, p; switch (lfo_wsel) { case 0: // saw // AM: 255 down to 0 // PM: 0 to 127, -127 to 0 (at PMD=127: LFP = 0 to 126, -126 to 0) a = 255 - i; if (i < 128) { p = i; } else { p = i - 255; } break; case 1: // square // AM: 255, 0 // PM: 128,-128 (LFP = exactly +PMD, -PMD) if (i < 128) { a = 255; p = 128; } else { a = 0; p = -128; } break; case 2: // triangle // AM: 255 down to 1 step -2; 0 up to 254 step +2 // PM: 0 to 126 step +2, 127 to 1 step -2, // 0 to -126 step -2, -127 to -1 step +2 if (i < 128) { a = 255 - (i * 2); } else { a = (i * 2) - 256; } if (i < 64) { // i = 0..63 p = i * 2; // 0 to 126 step +2 } else if (i < 128) { // i = 64..127 p = 255 - i * 2; // 127 to 1 step -2 } else if (i < 192) { // i = 128..191 p = 256 - i*2; // 0 to -126 step -2 } else { // i = 192..255 p = i*2 - 511; // -127 to -1 step +2 } break; case 3: default: // keep the compiler happy // Random. The real algorithm is unknown !!! // We just use a snapshot of data from real chip // AM: range 0 to 255 // PM: range -128 to 127 a = lfo_noise_waveform[i]; p = a - 128; break; } lfa = a * amd / 128; lfp = p * pmd / 128; // The Noise Generator of the YM2151 is 17-bit shift register. // Input to the bit16 is negated (bit0 XOR bit3) (EXNOR). // Output of the register is negated (bit0 XOR bit3). // Simply use bit16 as the noise output. // noise changes depending on the index in noise_tab (noise_f = noise_tab[x]) // noise_tab contains how many cycles/samples (x2) the noise should change. // so, when it contains 29, noise should change every 14.5 cycles (2 out of 29). // if you read this code well, you'll see that is what happens here :) noise_p -= 2; if (noise_p < 0) { noise_p += noise_f; unsigned j = ((noise_rng ^ (noise_rng >> 3)) & 1) ^ 1; noise_rng = (j << 16) | (noise_rng >> 1); } // phase generator YM2151Operator* op = &oper[0]; // CH 0 M1 i = 8; do { // only when phase modulation from LFO is enabled for this channel if (op->pms) { int mod_ind = lfp; // -128..+127 (8bits signed) if (op->pms < 6) { mod_ind >>= (6 - op->pms); } else { mod_ind <<= (op->pms - 5); } if (mod_ind) { unsigned kc_channel = op->kc_i + mod_ind; (op + 0)->phase += ((freq[kc_channel + (op + 0)->dt2] + (op + 0)->dt1) * (op + 0)->mul) >> 1; (op + 1)->phase += ((freq[kc_channel + (op + 1)->dt2] + (op + 1)->dt1) * (op + 1)->mul) >> 1; (op + 2)->phase += ((freq[kc_channel + (op + 2)->dt2] + (op + 2)->dt1) * (op + 2)->mul) >> 1; (op + 3)->phase += ((freq[kc_channel + (op + 3)->dt2] + (op + 3)->dt1) * (op + 3)->mul) >> 1; } else { // phase modulation from LFO is equal to zero (op + 0)->phase += (op + 0)->freq; (op + 1)->phase += (op + 1)->freq; (op + 2)->phase += (op + 2)->freq; (op + 3)->phase += (op + 3)->freq; } } else { // phase modulation from LFO is disabled (op + 0)->phase += (op + 0)->freq; (op + 1)->phase += (op + 1)->freq; (op + 2)->phase += (op + 2)->freq; (op + 3)->phase += (op + 3)->freq; } op += 4; i--; } while (i); // CSM is calculated *after* the phase generator calculations (verified // on real chip) // CSM keyon line seems to be ORed with the KO line inside of the chip. // The result is that it only works when KO (register 0x08) is off, ie. 0 // // Interesting effect is that when timer A is set to 1023, the KEY ON happens // on every sample, so there is no KEY OFF at all - the result is that // the sound played is the same as after normal KEY ON. if (csm_req) { // CSM KEYON/KEYOFF seqeunce request if (csm_req == 2) { // KEY ON op = &oper[0]; // CH 0 M1 i = 32; do { keyOn(op, 2); op++; i--; } while (i); csm_req = 1; } else { // KEY OFF op = &oper[0]; // CH 0 M1 i = 32; do { keyOff(op,unsigned(~2)); op++; i--; } while (i); csm_req = 0; } } } void YM2151::generateChannels(int** bufs, unsigned num) { if (checkMuteHelper()) { // TODO update internal state, even if muted for (int i = 0; i < 8; ++i) { bufs[i] = nullptr; } return; } for (unsigned i = 0; i < num; ++i) { advanceEG(); for (int j = 0; j < 8-1; ++j) { chanout[j] = 0; chanCalc(j); } chanout[7] = 0; chan7Calc(); // special case for channel 7 for (int j = 0; j < 8; ++j) { bufs[j][2 * i + 0] += chanout[j] & pan[2 * j + 0]; bufs[j][2 * i + 1] += chanout[j] & pan[2 * j + 1]; } advance(); } } void YM2151::callback(byte flag) { if (flag & 0x20) { // Timer 1 if (irq_enable & 0x04) { setStatus(1); } if (irq_enable & 0x80) { csm_req = 2; // request KEY ON / KEY OFF sequence } } if (flag & 0x40) { // Timer 2 if (irq_enable & 0x08) { setStatus(2); } } } byte YM2151::readStatus() const { return status; } void YM2151::setStatus(byte flags) { status |= flags; if (status) { irq.set(); } } void YM2151::resetStatus(byte flags) { status &= ~flags; if (!status) { irq.reset(); } } template void YM2151::YM2151Operator::serialize(Archive& ar, unsigned /*version*/) { //int* connect; // recalculated from regs[0x20-0x27] //int* mem_connect; // recalculated from regs[0x20-0x27] ar.serialize("phase", phase); ar.serialize("freq", freq); ar.serialize("dt1", dt1); ar.serialize("mul", mul); ar.serialize("dt1_i", dt1_i); ar.serialize("dt2", dt2); ar.serialize("mem_value", mem_value); //ar.serialize("fb_shift", fb_shift); // recalculated from regs[0x20-0x27] ar.serialize("fb_out_curr", fb_out_curr); ar.serialize("fb_out_prev", fb_out_prev); ar.serialize("kc", kc); ar.serialize("kc_i", kc_i); ar.serialize("pms", pms); ar.serialize("ams", ams); ar.serialize("AMmask", AMmask); ar.serialize("state", state); ar.serialize("tl", tl); ar.serialize("volume", volume); ar.serialize("d1l", d1l); ar.serialize("key", key); ar.serialize("ks", ks); ar.serialize("ar", this->ar); ar.serialize("d1r", d1r); ar.serialize("d2r", d2r); ar.serialize("rr", rr); ar.serialize("eg_sh_ar", eg_sh_ar); ar.serialize("eg_sel_ar", eg_sel_ar); ar.serialize("eg_sh_d1r", eg_sh_d1r); ar.serialize("eg_sel_d1r", eg_sel_d1r); ar.serialize("eg_sh_d2r", eg_sh_d2r); ar.serialize("eg_sel_d2r", eg_sel_d2r); ar.serialize("eg_sh_rr", eg_sh_rr); ar.serialize("eg_sel_rr", eg_sel_rr); }; template void YM2151::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("irq", irq); ar.serialize("timer1", *timer1); ar.serialize("timer2", *timer2); ar.serialize("operators", oper); //ar.serialize("pan", pan); // recalculated from regs[0x20-0x27] ar.serialize("eg_cnt", eg_cnt); ar.serialize("eg_timer", eg_timer); ar.serialize("lfo_phase", lfo_phase); ar.serialize("lfo_timer", lfo_timer); ar.serialize("lfo_overflow", lfo_overflow); ar.serialize("lfo_counter", lfo_counter); ar.serialize("lfo_counter_add", lfo_counter_add); ar.serialize("lfa", lfa); ar.serialize("lfp", lfp); ar.serialize("noise", noise); ar.serialize("noise_rng", noise_rng); ar.serialize("noise_p", noise_p); ar.serialize("noise_f", noise_f); ar.serialize("csm_req", csm_req); ar.serialize("irq_enable", irq_enable); ar.serialize("status", status); ar.serialize("chanout", chanout); ar.serialize("m2", m2); ar.serialize("c1", c1); ar.serialize("c2", c2); ar.serialize("mem", mem); ar.serialize("timer_A_val", timer_A_val); ar.serialize("lfo_wsel", lfo_wsel); ar.serialize("amd", amd); ar.serialize("pmd", pmd); ar.serialize("test", test); ar.serialize("ct", ct); ar.serialize_blob("registers", regs, sizeof(regs)); if (ar.isLoader()) { // TODO restore more state from registers EmuTime::param time = timer1->getCurrentTime(); for (int r = 0x20; r < 0x28; ++r) { writeReg(r , regs[r], time); } } } INSTANTIATE_SERIALIZE_METHODS(YM2151); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/YM2151.hh000066400000000000000000000175601257557151200175460ustar00rootroot00000000000000/* ** ** File: ym2151.h - header file for software implementation of YM2151 ** FM Operator Type-M(OPM) ** ** (c) 1997-2002 Jarek Burczynski (s0246@poczta.onet.pl, bujar@mame.net) ** Some of the optimizing ideas by Tatsuyuki Satoh ** ** Version 2.150 final beta May, 11th 2002 ** ** ** I would like to thank following people for making this project possible: ** ** Beauty Planets - for making a lot of real YM2151 samples and providing ** additional informations about the chip. Also for the time spent making ** the samples and the speed of replying to my endless requests. ** ** Shigeharu Isoda - for general help, for taking time to scan his YM2151 ** Japanese Manual first of all, and answering MANY of my questions. ** ** Nao - for giving me some info about YM2151 and pointing me to Shigeharu. ** Also for creating fmemu (which I still use to test the emulator). ** ** Aaron Giles and Chris Hardy - they made some samples of one of my favourite ** arcade games so I could compare it to my emulator. ** ** Bryan McPhail and Tim (powerjaw) - for making some samples. ** ** Ishmair - for the datasheet and motivation. */ #ifndef YM2151_HH #define YM2151_HH #include "ResampledSoundDevice.hh" #include "EmuTimer.hh" #include "EmuTime.hh" #include "IRQHelper.hh" #include "openmsx.hh" #include #include namespace openmsx { class DeviceConfig; class YM2151 final : public ResampledSoundDevice, private EmuTimerCallback { public: YM2151(const std::string& name, const std::string& desc, const DeviceConfig& config, EmuTime::param time); ~YM2151(); void reset(EmuTime::param time); void writeReg(byte r, byte v, EmuTime::param time); byte readStatus() const; template void serialize(Archive& ar, unsigned version); private: // a single operator struct YM2151Operator { template void serialize(Archive& ar, unsigned version); int* connect; // operator output 'direction' int* mem_connect; // where to put the delayed sample (MEM) unsigned phase; // accumulated operator phase unsigned freq; // operator frequency count int dt1; // current DT1 (detune 1 phase inc/decrement) value unsigned mul; // frequency count multiply unsigned dt1_i; // DT1 index * 32 unsigned dt2; // current DT2 (detune 2) value int mem_value; // delayed sample (MEM) value // channel specific data // note: each operator number 0 contains channel specific data unsigned fb_shift; // feedback shift value for operators 0 in each channel int fb_out_curr; // operator feedback value (used only by operators 0) int fb_out_prev; // previous feedback value (used only by operators 0) unsigned kc; // channel KC (copied to all operators) unsigned kc_i; // just for speedup unsigned pms; // channel PMS unsigned ams; // channel AMS unsigned AMmask; // LFO Amplitude Modulation enable mask unsigned state; // Envelope state: 4-attack(AR) // 3-decay(D1R) // 2-sustain(D2R) // 1-release(RR) // 0-off unsigned tl; // Total attenuation Level int volume; // current envelope attenuation level unsigned d1l; // envelope switches to sustain state after unsigned key; // 0=last key was KEY OFF, 1=last key was KEY ON unsigned ks; // key scale unsigned ar; // attack rate unsigned d1r; // decay rate unsigned d2r; // sustain rate unsigned rr; // release rate byte eg_sh_ar; // (attack state) byte eg_sel_ar; // (attack state) byte eg_sh_d1r; // (decay state) byte eg_sel_d1r; // (decay state) // reaching this level byte eg_sh_d2r; // (sustain state) byte eg_sel_d2r; // (sustain state) byte eg_sh_rr; // (release state) byte eg_sel_rr; // (release state) }; void setConnect(YM2151Operator* om1, int cha, int v); // SoundDevice void generateChannels(int** bufs, unsigned num) override; void callback(byte flag) override; void setStatus(byte flags); void resetStatus(byte flags); void initTables(); void initChipTables(); // operator methods void envelopeKONKOFF(YM2151Operator* op, int v); static void refreshEG(YM2151Operator* op); int opCalc(YM2151Operator* op, unsigned env, int pm); int opCalc1(YM2151Operator* op, unsigned env, int pm); inline unsigned volumeCalc(YM2151Operator* op, unsigned AM); inline void keyOn(YM2151Operator* op, unsigned keySet); inline void keyOff(YM2151Operator* op, unsigned keyClear); // general chip mehods void chanCalc(unsigned chan); void chan7Calc(); void advanceEG(); void advance(); bool checkMuteHelper(); IRQHelper irq; // Timers (see EmuTimer class for details about timing) const std::unique_ptr timer1; const std::unique_ptr timer2; YM2151Operator oper[32]; // the 32 operators unsigned pan[16]; // channels output masks (0xffffffff = enable) unsigned eg_cnt; // global envelope generator counter unsigned eg_timer; // global envelope generator counter // works at frequency = chipclock/64/3 unsigned lfo_phase; // accumulated LFO phase (0 to 255) unsigned lfo_timer; // LFO timer unsigned lfo_overflow; // LFO generates new output when lfo_timer // reaches this value unsigned lfo_counter; // LFO phase increment counter unsigned lfo_counter_add;// step of lfo_counter unsigned lfa; // LFO current AM output int lfp; // LFO current PM output unsigned noise; // noise enable/period register // bit 7 - noise enable, bits 4-0 - noise period unsigned noise_rng; // 17 bit noise shift register int noise_p; // current noise 'phase' unsigned noise_f; // current noise period unsigned csm_req; // CSM KEY ON / KEY OFF sequence request unsigned irq_enable; // IRQ enable for timer B (bit 3) and timer A // (bit 2); bit 7 - CSM mode (keyon to all // slots, everytime timer A overflows) unsigned status; // chip status (BUSY, IRQ Flags) // Frequency-deltas to get the closest frequency possible. // There are 11 octaves because of DT2 (max 950 cents over base frequency) // and LFO phase modulation (max 800 cents below AND over base frequency) // Summary: octave explanation // 0 note code - LFO PM // 1 note code // 2 note code // 3 note code // 4 note code // 5 note code // 6 note code // 7 note code // 8 note code // 9 note code + DT2 + LFO PM // 10 note code + DT2 + LFO PM unsigned freq[11 * 768]; // 11 octaves, 768 'cents' per octave // No Save // Frequency deltas for DT1. These deltas alter operator frequency // after it has been taken from frequency-deltas table. int dt1_freq[8 * 32]; // 8 DT1 levels, 32 KC values // No Save unsigned noise_tab[32]; // 17bit Noise Generator periods // No Save int chanout[8]; int m2, c1, c2; // Phase Modulation input for operators 2,3,4 int mem; // one sample delay memory word timer_A_val; byte lfo_wsel; // LFO waveform (0-saw, 1-square, 2-triangle, // 3-random noise) byte amd; // LFO Amplitude Modulation Depth signed char pmd; // LFO Phase Modulation Depth byte test; // TEST register byte ct; // output control pins (bit1-CT2, bit0-CT1) byte regs[256]; // only used for serialization ATM }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/YM2413.cc000066400000000000000000000035421257557151200175300ustar00rootroot00000000000000#include "YM2413.hh" #include "YM2413Okazaki.hh" #include "YM2413Burczynski.hh" #include "DeviceConfig.hh" #include "serialize.hh" #include "memory.hh" #include "outer.hh" namespace openmsx { // Debuggable YM2413::Debuggable::Debuggable( MSXMotherBoard& motherBoard, const std::string& name) : SimpleDebuggable(motherBoard, name + " regs", "MSX-MUSIC", 0x40) { } byte YM2413::Debuggable::read(unsigned address) { auto& ym2413 = OUTER(YM2413, debuggable); return ym2413.core->peekReg(address); } void YM2413::Debuggable::write(unsigned address, byte value, EmuTime::param time) { auto& ym2413 = OUTER(YM2413, debuggable); ym2413.writeReg(address, value, time); } // YM2413 static std::unique_ptr createCore(const DeviceConfig& config) { if (config.getChildDataAsBool("alternative", false)) { return make_unique(); } else { return make_unique(); } } YM2413::YM2413(const std::string& name, const DeviceConfig& config) : ResampledSoundDevice(config.getMotherBoard(), name, "MSX-MUSIC", 9 + 5) , core(createCore(config)) , debuggable(config.getMotherBoard(), getName()) { float input = YM2413Core::CLOCK_FREQ / 72.0f; setInputRate(int(input + 0.5f)); registerSound(config); } YM2413::~YM2413() { unregisterSound(); } void YM2413::reset(EmuTime::param time) { updateStream(time); core->reset(); } void YM2413::writeReg(byte reg, byte value, EmuTime::param time) { updateStream(time); core->writeReg(reg, value); } void YM2413::generateChannels(int** bufs, unsigned num) { core->generateChannels(bufs, num); } int YM2413::getAmplificationFactor() const { return core->getAmplificationFactor(); } template void YM2413::serialize(Archive& ar, unsigned /*version*/) { ar.serializePolymorphic("ym2413", *core); } INSTANTIATE_SERIALIZE_METHODS(YM2413); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/YM2413.hh000066400000000000000000000017241257557151200175420ustar00rootroot00000000000000#ifndef YM2413_HH #define YM2413_HH #include "ResampledSoundDevice.hh" #include "SimpleDebuggable.hh" #include "EmuTime.hh" #include "openmsx.hh" #include #include namespace openmsx { class YM2413Core; class YM2413 final : public ResampledSoundDevice { public: YM2413(const std::string& name, const DeviceConfig& config); ~YM2413(); void reset(EmuTime::param time); void writeReg(byte reg, byte value, EmuTime::param time); template void serialize(Archive& ar, unsigned version); private: // SoundDevice void generateChannels(int** bufs, unsigned num) override; int getAmplificationFactor() const override; const std::unique_ptr core; struct Debuggable final : SimpleDebuggable { Debuggable(MSXMotherBoard& motherBoard, const std::string& name); byte read(unsigned address) override; void write(unsigned address, byte value, EmuTime::param time) override; } debuggable; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/YM2413Burczynski.cc000066400000000000000000001131121257557151200216070ustar00rootroot00000000000000/* * * File: ym2413.c - software implementation of YM2413 * FM sound generator type OPLL * * Copyright (C) 2002 Jarek Burczynski * * Version 1.0 * * * TODO: * - make sure of the sinus amplitude bits * - make sure of the EG resolution bits (looks like the biggest * modulation index generated by the modulator is 123, 124 = no modulation) * - find proper algorithm for attack phase of EG * - tune up instruments ROM * - support sample replay in test mode (it is NOT as simple as setting bit 0 * in register 0x0f and using register 0x10 for sample data). * Which games use this feature ? */ #include "YM2413Burczynski.hh" #include "Math.hh" #include "serialize.hh" #include #include namespace openmsx { namespace YM2413Burczynski { // envelope output entries static const int ENV_BITS = 10; static const float ENV_STEP = 128.0f / (1 << ENV_BITS); static const int MAX_ATT_INDEX = (1 << (ENV_BITS - 2)) - 1; // 255 static const int MIN_ATT_INDEX = 0; // sinwave entries static const int SIN_BITS = 10; static const int SIN_LEN = 1 << SIN_BITS; static const int SIN_MASK = SIN_LEN - 1; static const int TL_RES_LEN = 256; // 8 bits addressing (real chip) // key scale level // table is 3dB/octave, DV converts this into 6dB/octave // 0.1875 is bit 0 weight of the envelope counter (volume) expressed // in the 'decibel' scale #define DV(x) int(x / 0.1875) static const int ksl_tab[8 * 16] = { // OCT 0 DV( 0.000),DV( 0.000),DV( 0.000),DV( 0.000), DV( 0.000),DV( 0.000),DV( 0.000),DV( 0.000), DV( 0.000),DV( 0.000),DV( 0.000),DV( 0.000), DV( 0.000),DV( 0.000),DV( 0.000),DV( 0.000), // OCT 1 DV( 0.000),DV( 0.000),DV( 0.000),DV( 0.000), DV( 0.000),DV( 0.000),DV( 0.000),DV( 0.000), DV( 0.000),DV( 0.750),DV( 1.125),DV( 1.500), DV( 1.875),DV( 2.250),DV( 2.625),DV( 3.000), // OCT 2 DV( 0.000),DV( 0.000),DV( 0.000),DV( 0.000), DV( 0.000),DV( 1.125),DV( 1.875),DV( 2.625), DV( 3.000),DV( 3.750),DV( 4.125),DV( 4.500), DV( 4.875),DV( 5.250),DV( 5.625),DV( 6.000), // OCT 3 DV( 0.000),DV( 0.000),DV( 0.000),DV( 1.875), DV( 3.000),DV( 4.125),DV( 4.875),DV( 5.625), DV( 6.000),DV( 6.750),DV( 7.125),DV( 7.500), DV( 7.875),DV( 8.250),DV( 8.625),DV( 9.000), // OCT 4 DV( 0.000),DV( 0.000),DV( 3.000),DV( 4.875), DV( 6.000),DV( 7.125),DV( 7.875),DV( 8.625), DV( 9.000),DV( 9.750),DV(10.125),DV(10.500), DV(10.875),DV(11.250),DV(11.625),DV(12.000), // OCT 5 DV( 0.000),DV( 3.000),DV( 6.000),DV( 7.875), DV( 9.000),DV(10.125),DV(10.875),DV(11.625), DV(12.000),DV(12.750),DV(13.125),DV(13.500), DV(13.875),DV(14.250),DV(14.625),DV(15.000), // OCT 6 DV( 0.000),DV( 6.000),DV( 9.000),DV(10.875), DV(12.000),DV(13.125),DV(13.875),DV(14.625), DV(15.000),DV(15.750),DV(16.125),DV(16.500), DV(16.875),DV(17.250),DV(17.625),DV(18.000), // OCT 7 DV( 0.000),DV( 9.000),DV(12.000),DV(13.875), DV(15.000),DV(16.125),DV(16.875),DV(17.625), DV(18.000),DV(18.750),DV(19.125),DV(19.500), DV(19.875),DV(20.250),DV(20.625),DV(21.000) }; #undef DV // sustain level table (3dB per step) // 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,45 (dB) #define SC(db) int((float(db)) / ENV_STEP) static const int sl_tab[16] = { SC( 0),SC( 1),SC( 2),SC(3 ),SC(4 ),SC(5 ),SC(6 ),SC( 7), SC( 8),SC( 9),SC(10),SC(11),SC(12),SC(13),SC(14),SC(15) }; #undef SC static const byte eg_inc[15][8] = { // cycle: 0 1 2 3 4 5 6 7 /* 0 */ { 0,1, 0,1, 0,1, 0,1, }, // rates 00..12 0 (increment by 0 or 1) /* 1 */ { 0,1, 0,1, 1,1, 0,1, }, // rates 00..12 1 /* 2 */ { 0,1, 1,1, 0,1, 1,1, }, // rates 00..12 2 /* 3 */ { 0,1, 1,1, 1,1, 1,1, }, // rates 00..12 3 /* 4 */ { 1,1, 1,1, 1,1, 1,1, }, // rate 13 0 (increment by 1) /* 5 */ { 1,1, 1,2, 1,1, 1,2, }, // rate 13 1 /* 6 */ { 1,2, 1,2, 1,2, 1,2, }, // rate 13 2 /* 7 */ { 1,2, 2,2, 1,2, 2,2, }, // rate 13 3 /* 8 */ { 2,2, 2,2, 2,2, 2,2, }, // rate 14 0 (increment by 2) /* 9 */ { 2,2, 2,4, 2,2, 2,4, }, // rate 14 1 /*10 */ { 2,4, 2,4, 2,4, 2,4, }, // rate 14 2 /*11 */ { 2,4, 4,4, 2,4, 4,4, }, // rate 14 3 /*12 */ { 4,4, 4,4, 4,4, 4,4, }, // rates 15 0, 15 1, 15 2, 15 3 (incr by 4) /*13 */ { 8,8, 8,8, 8,8, 8,8, }, // rates 15 2, 15 3 for attack /*14 */ { 0,0, 0,0, 0,0, 0,0, }, // infinity rates for attack and decay(s) }; // note that there is no value 13 in this table - it's directly in the code static const byte eg_rate_select[16 + 64 + 16] = { // Envelope Generator rates (16 + 64 rates + 16 RKS) // 16 infinite time rates 14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14, // rates 00-12 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, // rate 13 4, 5, 6, 7, // rate 14 8, 9,10,11, // rate 15 12,12,12,12, // 16 dummy rates (same as 15 3) 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, }; // rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 // shift 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0 // mask 8191, 4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0 static const byte eg_rate_shift[16 + 64 + 16] = { // Envelope Generator counter shifts (16 + 64 rates + 16 RKS) // 16 infinite time rates 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // rates 00-12 13,13,13,13, 12,12,12,12, 11,11,11,11, 10,10,10,10, 9, 9, 9, 9, 8, 8, 8, 8, 7, 7, 7, 7, 6, 6, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, // rate 13 0, 0, 0, 0, // rate 14 0, 0, 0, 0, // rate 15 0, 0, 0, 0, // 16 dummy rates (same as 15 3) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; // multiple table #define ML(x) byte(2 * x) static const byte mul_tab[16] = { ML( 0.50), ML( 1.00), ML( 2.00), ML( 3.00), ML( 4.00), ML( 5.00), ML( 6.00), ML( 7.00), ML( 8.00), ML( 9.00), ML(10.00), ML(10.00), ML(12.00), ML(12.00), ML(15.00), ML(15.00), }; #undef ML // TL_TAB_LEN is calculated as: // 11 - sinus amplitude bits (Y axis) // 2 - sinus sign bit (Y axis) // TL_RES_LEN - sinus resolution (X axis) const int TL_TAB_LEN = 11 * 2 * TL_RES_LEN; static int tl_tab[TL_TAB_LEN]; // sin waveform table in 'decibel' scale // two waveforms on OPLL type chips static unsigned sin_tab[SIN_LEN * 2]; // LFO Amplitude Modulation table (verified on real YM3812) // 27 output levels (triangle waveform); // 1 level takes one of: 192, 256 or 448 samples // // Length: 210 elements. // // Each of the elements has to be repeated // exactly 64 times (on 64 consecutive samples). // The whole table takes: 64 * 210 = 13440 samples. // // We use data>>1, until we find what it really is on real chip... static const int LFO_AM_TAB_ELEMENTS = 210; static const byte lfo_am_table[LFO_AM_TAB_ELEMENTS] = { 0,0,0,0,0,0,0, 1,1,1,1, 2,2,2,2, 3,3,3,3, 4,4,4,4, 5,5,5,5, 6,6,6,6, 7,7,7,7, 8,8,8,8, 9,9,9,9, 10,10,10,10, 11,11,11,11, 12,12,12,12, 13,13,13,13, 14,14,14,14, 15,15,15,15, 16,16,16,16, 17,17,17,17, 18,18,18,18, 19,19,19,19, 20,20,20,20, 21,21,21,21, 22,22,22,22, 23,23,23,23, 24,24,24,24, 25,25,25,25, 26,26,26, 25,25,25,25, 24,24,24,24, 23,23,23,23, 22,22,22,22, 21,21,21,21, 20,20,20,20, 19,19,19,19, 18,18,18,18, 17,17,17,17, 16,16,16,16, 15,15,15,15, 14,14,14,14, 13,13,13,13, 12,12,12,12, 11,11,11,11, 10,10,10,10, 9,9,9,9, 8,8,8,8, 7,7,7,7, 6,6,6,6, 5,5,5,5, 4,4,4,4, 3,3,3,3, 2,2,2,2, 1,1,1,1 }; // LFO Phase Modulation table (verified on real YM2413) static const signed char lfo_pm_table[8][8] = { // FNUM2/FNUM = 0 00xxxxxx (0x0000) { 0, 0, 0, 0, 0, 0, 0, 0, }, // FNUM2/FNUM = 0 01xxxxxx (0x0040) { 1, 0, 0, 0,-1, 0, 0, 0, }, // FNUM2/FNUM = 0 10xxxxxx (0x0080) { 2, 1, 0,-1,-2,-1, 0, 1, }, // FNUM2/FNUM = 0 11xxxxxx (0x00C0) { 3, 1, 0,-1,-3,-1, 0, 1, }, // FNUM2/FNUM = 1 00xxxxxx (0x0100) { 4, 2, 0,-2,-4,-2, 0, 2, }, // FNUM2/FNUM = 1 01xxxxxx (0x0140) { 5, 2, 0,-2,-5,-2, 0, 2, }, // FNUM2/FNUM = 1 10xxxxxx (0x0180) { 6, 3, 0,-3,-6,-3, 0, 3, }, // FNUM2/FNUM = 1 11xxxxxx (0x01C0) { 7, 3, 0,-3,-7,-3, 0, 3, }, }; // This is not 100% perfect yet but very close // // - multi parameters are 100% correct (instruments and drums) // - LFO PM and AM enable are 100% correct // - waveform DC and DM select are 100% correct static const byte table[16 + 3][8] = { // MULT MULT modTL DcDmFb AR/DR AR/DR SL/RR SL/RR // 0 1 2 3 4 5 6 7 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // user instrument { 0x61, 0x61, 0x1e, 0x17, 0xf0, 0x7f, 0x00, 0x17 }, // violin { 0x13, 0x41, 0x16, 0x0e, 0xfd, 0xf4, 0x23, 0x23 }, // guitar { 0x03, 0x01, 0x9a, 0x04, 0xf3, 0xf3, 0x13, 0xf3 }, // piano { 0x11, 0x61, 0x0e, 0x07, 0xfa, 0x64, 0x70, 0x17 }, // flute { 0x22, 0x21, 0x1e, 0x06, 0xf0, 0x76, 0x00, 0x28 }, // clarinet { 0x21, 0x22, 0x16, 0x05, 0xf0, 0x71, 0x00, 0x18 }, // oboe { 0x21, 0x61, 0x1d, 0x07, 0x82, 0x80, 0x17, 0x17 }, // trumpet { 0x23, 0x21, 0x2d, 0x16, 0x90, 0x90, 0x00, 0x07 }, // organ { 0x21, 0x21, 0x1b, 0x06, 0x64, 0x65, 0x10, 0x17 }, // horn { 0x21, 0x21, 0x0b, 0x1a, 0x85, 0xa0, 0x70, 0x07 }, // synthesizer { 0x23, 0x01, 0x83, 0x10, 0xff, 0xb4, 0x10, 0xf4 }, // harpsichord { 0x97, 0xc1, 0x20, 0x07, 0xff, 0xf4, 0x22, 0x22 }, // vibraphone { 0x61, 0x00, 0x0c, 0x05, 0xc2, 0xf6, 0x40, 0x44 }, // synthesizer bass { 0x01, 0x01, 0x56, 0x03, 0x94, 0xc2, 0x03, 0x12 }, // acoustic bass { 0x21, 0x01, 0x89, 0x03, 0xf1, 0xe4, 0xf0, 0x23 }, // electric guitar // drum instruments definitions // MULTI MULTI modTL xxx AR/DR AR/DR SL/RR SL/RR // 0 1 2 3 4 5 6 7 //{ 0x07, 0x21, 0x14, 0x00, 0xee, 0xf8, 0xff, 0xf8 }, //{ 0x01, 0x31, 0x00, 0x00, 0xf8, 0xf7, 0xf8, 0xf7 }, //{ 0x25, 0x11, 0x00, 0x00, 0xf8, 0xfa, 0xf8, 0x55 } { 0x01, 0x01, 0x16, 0x00, 0xfd, 0xf8, 0x2f, 0x6d },// BD(multi verified, modTL verified, mod env - verified(close), carr. env verifed) { 0x01, 0x01, 0x00, 0x00, 0xd8, 0xd8, 0xf9, 0xf8 },// HH(multi verified), SD(multi not used) { 0x05, 0x01, 0x00, 0x00, 0xf8, 0xba, 0x49, 0x55 },// TOM(multi,env verified), TOP CYM(multi verified, env verified) }; static inline FreqIndex fnumToIncrement(int block_fnum) { // OPLL (YM2413) phase increment counter = 18bit // Chip works with 10.10 fixed point, while we use 16.16. const int block = (block_fnum & 0x1C00) >> 10; return FreqIndex(block_fnum & 0x03FF) >> (11 - block); } inline int Slot::calc_envelope(Channel& channel, unsigned eg_cnt, bool carrier) { switch (state) { case EG_DUMP: // Dump phase is performed by both operators in each channel. // When CARRIER envelope gets down to zero level, phases in BOTH // operators are reset (at the same time?). // TODO: That sounds logical, but it does not match the implementation. if (!(eg_cnt & eg_mask_dp)) { egout += eg_sel_dp[(eg_cnt >> eg_sh_dp) & 7]; if (egout >= MAX_ATT_INDEX) { egout = MAX_ATT_INDEX; setEnvelopeState(EG_ATTACK); phase = FreqIndex(0); // restart Phase Generator } } break; case EG_ATTACK: if (!(eg_cnt & eg_mask_ar)) { egout += (~egout * eg_sel_ar[(eg_cnt >> eg_sh_ar) & 7]) >> 2; if (egout <= MIN_ATT_INDEX) { egout = MIN_ATT_INDEX; setEnvelopeState(EG_DECAY); } } break; case EG_DECAY: if (!(eg_cnt & eg_mask_dr)) { egout += eg_sel_dr[(eg_cnt >> eg_sh_dr) & 7]; if (egout >= sl) { setEnvelopeState(EG_SUSTAIN); } } break; case EG_SUSTAIN: // this is important behaviour: // one can change percusive/non-percussive modes on the fly and // the chip will remain in sustain phase // - verified on real YM3812 if (eg_sustain) { // non-percussive mode (sustained tone) // do nothing } else { // percussive mode // during sustain phase chip adds Release Rate (in // percussive mode) if (!(eg_cnt & eg_mask_rr)) { egout += eg_sel_rr[(eg_cnt >> eg_sh_rr) & 7]; if (egout >= MAX_ATT_INDEX) { egout = MAX_ATT_INDEX; } } // else do nothing in sustain phase } break; case EG_RELEASE: // Exclude modulators in melody channels from performing anything in // this mode. if (carrier) { const bool sustain = !eg_sustain || channel.isSustained(); const unsigned mask = sustain ? eg_mask_rs : eg_mask_rr; if (!(eg_cnt & mask)) { const byte shift = sustain ? eg_sh_rs : eg_sh_rr; const byte* sel = sustain ? eg_sel_rs : eg_sel_rr; egout += sel[(eg_cnt >> shift) & 7]; if (egout >= MAX_ATT_INDEX) { egout = MAX_ATT_INDEX; setEnvelopeState(EG_OFF); } } } break; case EG_OFF: break; } return egout; } inline int Slot::calc_phase(Channel& channel, unsigned lfo_pm) { if (vib) { const int lfo_fn_table_index_offset = lfo_pm_table [(channel.getBlockFNum() & 0x01FF) >> 6][lfo_pm]; phase += fnumToIncrement( channel.getBlockFNum() * 2 + lfo_fn_table_index_offset ) * mul; } else { // LFO phase modulation disabled for this operator phase += freq; } return phase.toInt(); } inline void Slot::updateTotalLevel(Channel& channel) { TLL = TL + (channel.getKeyScaleLevelBase() >> ksl); } inline void Slot::updateAttackRate(int kcodeScaled) { if ((ar + kcodeScaled) < (16 + 62)) { eg_sh_ar = eg_rate_shift[ar + kcodeScaled]; eg_sel_ar = eg_inc[eg_rate_select[ar + kcodeScaled]]; } else { eg_sh_ar = 0; eg_sel_ar = eg_inc[13]; } eg_mask_ar = (1 << eg_sh_ar) - 1; } inline void Slot::updateDecayRate(int kcodeScaled) { eg_sh_dr = eg_rate_shift[dr + kcodeScaled]; eg_sel_dr = eg_inc[eg_rate_select[dr + kcodeScaled]]; eg_mask_dr = (1 << eg_sh_dr) - 1; } inline void Slot::updateReleaseRate(int kcodeScaled) { eg_sh_rr = eg_rate_shift[rr + kcodeScaled]; eg_sel_rr = eg_inc[eg_rate_select[rr + kcodeScaled]]; eg_mask_rr = (1 << eg_sh_rr) - 1; } inline int Slot::calcOutput(Channel& channel, unsigned eg_cnt, bool carrier, unsigned lfo_am, int phase) { int egout = calc_envelope(channel, eg_cnt, carrier); int env = (TLL + egout + (lfo_am & AMmask)) << 5; int p = env + wavetable[phase & SIN_MASK]; return p < TL_TAB_LEN ? tl_tab[p] : 0; } inline int Slot::calc_slot_mod(Channel& channel, unsigned eg_cnt, bool carrier, unsigned lfo_pm, unsigned lfo_am) { // Compute phase. int phase = calc_phase(channel, lfo_pm); if (fb_shift) { phase += (op1_out[0] + op1_out[1]) >> fb_shift; } // Shift output in 2-place buffer. op1_out[0] = op1_out[1]; // Calculate operator output. op1_out[1] = calcOutput(channel, eg_cnt, carrier, lfo_am, phase); return op1_out[0] << 1; } inline int Channel::calcOutput(unsigned eg_cnt, unsigned lfo_pm, unsigned lfo_am, int fm) { int phase = car.calc_phase(*this, lfo_pm) + fm; return car.calcOutput(*this, eg_cnt, true, lfo_am, phase); } // Operators used in the rhythm sounds generation process: // // Envelope Generator: // // channel operator register number Bass High Snare Tom Top // / slot number TL ARDR SLRR Wave Drum Hat Drum Tom Cymbal // 6 / 0 12 50 70 90 f0 + // 6 / 1 15 53 73 93 f3 + // 7 / 0 13 51 71 91 f1 + // 7 / 1 16 54 74 94 f4 + // 8 / 0 14 52 72 92 f2 + // 8 / 1 17 55 75 95 f5 + // // Phase Generator: // // channel operator register number Bass High Snare Tom Top // / slot number MULTIPLE Drum Hat Drum Tom Cymbal // 6 / 0 12 30 + // 6 / 1 15 33 + // 7 / 0 13 31 + + + // 7 / 1 16 34 ----- n o t u s e d ----- // 8 / 0 14 32 + // 8 / 1 17 35 + + // // channel operator register number Bass High Snare Tom Top // number number BLK/FNUM2 FNUM Drum Hat Drum Tom Cymbal // 6 12,15 B6 A6 + // 7 13,16 B7 A7 + + + // 8 14,17 B8 A8 + + + // Phase generation is based on: // HH (13) channel 7->slot 1 combined with channel 8->slot 2 // (same combination as TOP CYMBAL but different output phases) // SD (16) channel 7->slot 1 // TOM (14) channel 8->slot 1 // TOP (17) channel 7->slot 1 combined with channel 8->slot 2 // (same combination as HIGH HAT but different output phases) static inline int genPhaseHighHat(int phaseM7, int phaseC8, int noise_rng) { // hi == phase >= 0x200 bool hi; // enable gate based on frequency of operator 2 in channel 8 if (phaseC8 & 0x28) { hi = true; } else { // base frequency derived from operator 1 in channel 7 // VC++ requires explicit conversion to bool. Compiler bug?? const bool bit7 = (phaseM7 & 0x80) != 0; const bool bit3 = (phaseM7 & 0x08) != 0; const bool bit2 = (phaseM7 & 0x04) != 0; hi = (bit2 ^ bit7) | bit3; } if (noise_rng & 1) { return hi ? (0x200 | 0xD0) : (0xD0 >> 2); } else { return hi ? (0x200 | (0xD0 >> 2)) : 0xD0; } } static inline int genPhaseSnare(int phaseM7, int noise_rng) { // base frequency derived from operator 1 in channel 7 // noise bit XOR'es phase by 0x100 return ((phaseM7 & 0x100) + 0x100) ^ ((noise_rng & 1) << 8); } static inline int genPhaseCymbal(int phaseM7, int phaseC8) { // enable gate based on frequency of operator 2 in channel 8 if (phaseC8 & 0x28) { return 0x300; } else { // base frequency derived from operator 1 in channel 7 // VC++ requires explicit conversion to bool. Compiler bug?? const bool bit7 = (phaseM7 & 0x80) != 0; const bool bit3 = (phaseM7 & 0x08) != 0; const bool bit2 = (phaseM7 & 0x04) != 0; return ((bit2 != bit7) || bit3) ? 0x300 : 0x100; } } static void initTables() { static bool alreadyInit = false; if (alreadyInit) return; alreadyInit = true; for (int x = 0; x < TL_RES_LEN; ++x) { float m = (1 << 16) / exp2f((x + 1) * (ENV_STEP / 4.0f) / 8.0f); m = floorf(m); // we never reach (1 << 16) here due to the (x + 1) // result fits within 16 bits at maximum int n = int(m); // 16 bits here n >>= 4; // 12 bits here n = (n >> 1) + (n & 1); // round to nearest // 11 bits here (rounded) for (int i = 0; i < 11; ++i) { tl_tab[x * 2 + 0 + i * 2 * TL_RES_LEN] = n >> i; tl_tab[x * 2 + 1 + i * 2 * TL_RES_LEN] = -(n >> i); } } unsigned* full = &sin_tab[0 * SIN_LEN]; // waveform 0: standard sinus unsigned* half = &sin_tab[1 * SIN_LEN]; // waveform 1: positive part of sinus static const float LOG2 = logf(2.0f); for (int i = 0; i < SIN_LEN / 4; ++i) { // checked on real hardware, see also // http://docs.google.com/Doc?id=dd8kqn9f_13cqjkf4gp float m = sinf(((i * 2) + 1) * M_PI / SIN_LEN); int n = int(roundf(logf(m) / LOG2 * -256.0f)); full[i] = half[i] = 2 * n; } for (int i = 0; i < SIN_LEN / 4; ++i) { full[SIN_LEN / 4 + i] = half[SIN_LEN / 4 + i] = full[SIN_LEN / 4 - 1 - i]; } for (int i = 0; i < SIN_LEN / 2; ++i) { full[SIN_LEN / 2 + i] = full[i] | 1; half[SIN_LEN / 2 + i] = TL_TAB_LEN; } } Slot::Slot() : phase(0), freq(0) { ar = dr = rr = KSR = ksl = mul = 0; fb_shift = op1_out[0] = op1_out[1] = 0; TL = TLL = egout = sl = 0; eg_sh_dp = eg_sh_ar = eg_sh_dr = eg_sh_rr = eg_sh_rs = 0; eg_sel_dp = eg_sel_ar = eg_sel_dr = eg_sel_rr = eg_sel_rs = eg_inc[0]; eg_mask_dp = eg_mask_ar = eg_mask_dr = eg_mask_rr = eg_mask_rs = 0; eg_sustain = false; setEnvelopeState(EG_OFF); key = AMmask = vib = 0; wavetable = &sin_tab[0 * SIN_LEN]; } void Slot::setKeyOn(KeyPart part) { if (!key) { // do NOT restart Phase Generator (verified on real YM2413) setEnvelopeState(EG_DUMP); } key |= part; } void Slot::setKeyOff(KeyPart part) { if (key) { key &= ~part; if (!key) { if (isActive()) { setEnvelopeState(EG_RELEASE); } } } } void Slot::setKeyOnOff(KeyPart part, bool enabled) { if (enabled) { setKeyOn(part); } else { setKeyOff(part); } } bool Slot::isActive() const { return state != EG_OFF; } void Slot::setEnvelopeState(EnvelopeState state_) { state = state_; } void Slot::setFrequencyMultiplier(byte value) { mul = mul_tab[value]; } void Slot::setKeyScaleRate(bool value) { KSR = value ? 0 : 2; } void Slot::setEnvelopeSustained(bool value) { eg_sustain = value; } void Slot::setVibrato(bool value) { vib = value; } void Slot::setAmplitudeModulation(bool value) { AMmask = value ? ~0 : 0; } void Slot::setTotalLevel(Channel& channel, byte value) { TL = value << (ENV_BITS - 2 - 7); // 7 bits TL (bit 6 = always 0) updateTotalLevel(channel); } void Slot::setKeyScaleLevel(Channel& channel, byte value) { ksl = value ? (3 - value) : 31; updateTotalLevel(channel); } void Slot::setWaveform(byte value) { wavetable = &sin_tab[value * SIN_LEN]; } void Slot::setFeedbackShift(byte value) { fb_shift = value ? 8 - value : 0; } void Slot::setAttackRate(const Channel& channel, byte value) { int kcodeScaled = channel.getKeyCode() >> KSR; ar = value ? 16 + (value << 2) : 0; updateAttackRate(kcodeScaled); } void Slot::setDecayRate(const Channel& channel, byte value) { int kcodeScaled = channel.getKeyCode() >> KSR; dr = value ? 16 + (value << 2) : 0; updateDecayRate(kcodeScaled); } void Slot::setReleaseRate(const Channel& channel, byte value) { int kcodeScaled = channel.getKeyCode() >> KSR; rr = value ? 16 + (value << 2) : 0; updateReleaseRate(kcodeScaled); } void Slot::setSustainLevel(byte value) { sl = sl_tab[value]; } void Slot::updateFrequency(Channel& channel) { updateTotalLevel(channel); updateGenerators(channel); } void Slot::resetOperators() { wavetable = &sin_tab[0 * SIN_LEN]; setEnvelopeState(EG_OFF); egout = MAX_ATT_INDEX; } void Slot::updateGenerators(Channel& channel) { // (frequency) phase increment counter freq = channel.getFrequencyIncrement() * mul; // calculate envelope generator rates const int kcodeScaled = channel.getKeyCode() >> KSR; updateAttackRate(kcodeScaled); updateDecayRate(kcodeScaled); updateReleaseRate(kcodeScaled); const int rs = channel.isSustained() ? 16 + (5 << 2) : 16 + (7 << 2); eg_sh_rs = eg_rate_shift[rs + kcodeScaled]; eg_sel_rs = eg_inc[eg_rate_select[rs + kcodeScaled]]; const int dp = 16 + (13 << 2); eg_sh_dp = eg_rate_shift[dp + kcodeScaled]; eg_sel_dp = eg_inc[eg_rate_select[dp + kcodeScaled]]; eg_mask_rs = (1 << eg_sh_rs) - 1; eg_mask_dp = (1 << eg_sh_dp) - 1; } Channel::Channel() : fc(0) { block_fnum = ksl_base = 0; sus = false; } void Channel::setFrequency(int block_fnum_) { if (block_fnum == block_fnum_) return; block_fnum = block_fnum_; ksl_base = ksl_tab[block_fnum >> 5]; fc = fnumToIncrement(block_fnum * 2); // Refresh Total Level and frequency counter in both SLOTs of this channel. mod.updateFrequency(*this); car.updateFrequency(*this); } void Channel::setFrequencyLow(byte value) { setFrequency((block_fnum & 0x0F00) | value); } void Channel::setFrequencyHigh(byte value) { setFrequency((value << 8) | (block_fnum & 0x00FF)); } int Channel::getBlockFNum() const { return block_fnum; } FreqIndex Channel::getFrequencyIncrement() const { return fc; } int Channel::getKeyScaleLevelBase() const { return ksl_base; } byte Channel::getKeyCode() const { // BLK 2,1,0 bits -> bits 3,2,1 of kcode, FNUM MSB -> kcode LSB return (block_fnum & 0x0F00) >> 8; } bool Channel::isSustained() const { return sus; } void Channel::setSustain(bool sustained) { sus = sustained; } void Channel::updateInstrumentPart(int part, byte value) { switch (part) { case 0: mod.setFrequencyMultiplier(value & 0x0F); mod.setKeyScaleRate((value & 0x10) != 0); mod.setEnvelopeSustained((value & 0x20) != 0); mod.setVibrato((value & 0x40) != 0); mod.setAmplitudeModulation((value & 0x80) != 0); mod.updateGenerators(*this); break; case 1: car.setFrequencyMultiplier(value & 0x0F); car.setKeyScaleRate((value & 0x10) != 0); car.setEnvelopeSustained((value & 0x20) != 0); car.setVibrato((value & 0x40) != 0); car.setAmplitudeModulation((value & 0x80) != 0); car.updateGenerators(*this); break; case 2: mod.setKeyScaleLevel(*this, value >> 6); mod.setTotalLevel(*this, value & 0x3F); break; case 3: mod.setWaveform((value & 0x08) >> 3); mod.setFeedbackShift(value & 0x07); car.setKeyScaleLevel(*this, value >> 6); car.setWaveform((value & 0x10) >> 4); break; case 4: mod.setAttackRate(*this, value >> 4); mod.setDecayRate(*this, value & 0x0F); break; case 5: car.setAttackRate(*this, value >> 4); car.setDecayRate(*this, value & 0x0F); break; case 6: mod.setSustainLevel(value >> 4); mod.setReleaseRate(*this, value & 0x0F); break; case 7: car.setSustainLevel(value >> 4); car.setReleaseRate(*this, value & 0x0F); break; } } void Channel::updateInstrument(const byte* inst) { for (int part = 0; part < 8; ++part) { updateInstrumentPart(part, inst[part]); } } YM2413::YM2413() : lfo_am_cnt(0), lfo_pm_cnt(0) { initTables(); memset(reg, 0, sizeof(reg)); // avoid UMR eg_cnt = 0; noise_rng = 0; reset(); } void YM2413::updateCustomInstrument(int part, byte value) { // Update instrument definition. inst_tab[0][part] = value; // Update every channel that has instrument 0 selected. const int numMelodicChannels = isRhythm() ? 6 : 9; for (int ch = 0; ch < numMelodicChannels; ++ch) { Channel& channel = channels[ch]; if ((reg[0x30 + ch] & 0xF0) == 0) { channel.updateInstrumentPart(part, value); } } } void YM2413::setRhythmFlags(byte old) { Channel& ch6 = channels[6]; Channel& ch7 = channels[7]; Channel& ch8 = channels[8]; // flags = X | X | mode | BD | SD | TOM | TC | HH byte flags = reg[0x0E]; if ((flags ^ old) & 0x20) { if (flags & 0x20) { // OFF -> ON // Bass drum. ch6.updateInstrument(inst_tab[16]); // High hat and snare drum. ch7.updateInstrument(inst_tab[17]); ch7.mod.setTotalLevel(ch7, (reg[0x37] >> 4) << 2); // High hat // Tom-tom and top cymbal. ch8.updateInstrument(inst_tab[18]); ch8.mod.setTotalLevel(ch8, (reg[0x38] >> 4) << 2); // Tom-tom } else { // ON -> OFF ch6.updateInstrument(inst_tab[reg[0x36] >> 4]); ch7.updateInstrument(inst_tab[reg[0x37] >> 4]); ch8.updateInstrument(inst_tab[reg[0x38] >> 4]); // BD key off ch6.mod.setKeyOff(Slot::KEY_RHYTHM); ch6.car.setKeyOff(Slot::KEY_RHYTHM); // HH key off ch7.mod.setKeyOff(Slot::KEY_RHYTHM); // SD key off ch7.car.setKeyOff(Slot::KEY_RHYTHM); // TOM key off ch8.mod.setKeyOff(Slot::KEY_RHYTHM); // TOP-CY off ch8.car.setKeyOff(Slot::KEY_RHYTHM); } } if (flags & 0x20) { // BD key on/off ch6.mod.setKeyOnOff(Slot::KEY_RHYTHM, (flags & 0x10) != 0); ch6.car.setKeyOnOff(Slot::KEY_RHYTHM, (flags & 0x10) != 0); // HH key on/off ch7.mod.setKeyOnOff(Slot::KEY_RHYTHM, (flags & 0x01) != 0); // SD key on/off ch7.car.setKeyOnOff(Slot::KEY_RHYTHM, (flags & 0x08) != 0); // TOM key on/off ch8.mod.setKeyOnOff(Slot::KEY_RHYTHM, (flags & 0x04) != 0); // TOP-CY key on/off ch8.car.setKeyOnOff(Slot::KEY_RHYTHM, (flags & 0x02) != 0); } } void YM2413::reset() { eg_cnt = 0; noise_rng = 1; // noise shift register idleSamples = 0; // setup instruments table for (int instrument = 0; instrument < 19; ++instrument) { for (int part = 0; part < 8; ++part) { inst_tab[instrument][part] = table[instrument][part]; } } // reset with register write writeReg(0x0F, 0); // test reg for (int i = 0x3F; i >= 0x10; --i) { writeReg(i, 0); } resetOperators(); } void YM2413::resetOperators() { for (auto& ch : channels) { ch.mod.resetOperators(); ch.car.resetOperators(); } } bool YM2413::isRhythm() const { return (reg[0x0E] & 0x20) != 0; } Channel& YM2413::getChannelForReg(byte reg) { byte chan = (reg & 0x0F) % 9; // verified on real YM2413 return channels[chan]; } int YM2413::getAmplificationFactor() const { return 1 << 4; } void YM2413::generateChannels(int* bufs[9 + 5], unsigned num) { // TODO make channelActiveBits a member and // keep it up-to-date all the time // bits 0-8 -> ch[0-8].car // bits 9-17 -> ch[0-8].mod (only ch7 and ch8 used) unsigned channelActiveBits = 0; const int numMelodicChannels = isRhythm() ? 6 : 9; for (int ch = 0; ch < numMelodicChannels; ++ch) { if (channels[ch].car.isActive()) { channelActiveBits |= 1 << ch; } else { bufs[ch] = nullptr; } } if (isRhythm()) { bufs[6] = nullptr; bufs[7] = nullptr; bufs[8] = nullptr; for (int ch = 6; ch < 9; ++ch) { if (channels[ch].car.isActive()) { channelActiveBits |= 1 << ch; } else { bufs[ch + 3] = nullptr; } } if (channels[7].mod.isActive()) { channelActiveBits |= 1 << (7 + 9); } else { bufs[12] = nullptr; } if (channels[8].mod.isActive()) { channelActiveBits |= 1 << (8 + 9); } else { bufs[13] = nullptr; } } else { bufs[ 9] = nullptr; bufs[10] = nullptr; bufs[11] = nullptr; bufs[12] = nullptr; bufs[13] = nullptr; } if (channelActiveBits) { idleSamples = 0; } else { if (idleSamples > (CLOCK_FREQ / (72 * 5))) { // Optimization: // idle for over 1/5s = 200ms // we don't care that noise / AM / PM isn't exactly // in sync with the real HW when music resumes // Alternative: // implement an efficient advance(n) method return; } idleSamples += num; } for (unsigned i = 0; i < num; ++i) { // Amplitude modulation: 27 output levels (triangle waveform) // 1 level takes one of: 192, 256 or 448 samples // One entry from LFO_AM_TABLE lasts for 64 samples lfo_am_cnt.addQuantum(); if (lfo_am_cnt == LFOAMIndex(LFO_AM_TAB_ELEMENTS)) { // lfo_am_table is 210 elements long lfo_am_cnt = LFOAMIndex(0); } unsigned lfo_am = lfo_am_table[lfo_am_cnt.toInt()] >> 1; unsigned lfo_pm = lfo_pm_cnt.toInt() & 7; for (int ch = 0; ch < numMelodicChannels; ++ch) { Channel& channel = channels[ch]; int fm = channel.mod.calc_slot_mod(channel, eg_cnt, false, lfo_pm, lfo_am); if ((channelActiveBits >> ch) & 1) { bufs[ch][i] += channel.calcOutput(eg_cnt, lfo_pm, lfo_am, fm); } } if (isRhythm()) { // Bass Drum (verified on real YM3812): // - depends on the channel 6 'connect' register: // when connect = 0 it works the same as in normal (non-rhythm) mode // (op1->op2->out) // when connect = 1 _only_ operator 2 is present on output (op2->out), // operator 1 is ignored // - output sample always is multiplied by 2 Channel& channel6 = channels[6]; int fm = channel6.mod.calc_slot_mod(channels[6], eg_cnt, true, lfo_pm, lfo_am); if (channelActiveBits & (1 << 6)) { bufs[ 9][i] += 2 * channel6.calcOutput(eg_cnt, lfo_pm, lfo_am, fm); } // TODO: Skip phase generation if output will 0 anyway. // Possible by passing phase generator as a template parameter to // calcOutput. /* phaseC7 */channels[7].car.calc_phase(channels[7], lfo_pm); int phaseM7 = channels[7].mod.calc_phase(channels[7], lfo_pm); int phaseC8 = channels[8].car.calc_phase(channels[8], lfo_pm); int phaseM8 = channels[8].mod.calc_phase(channels[8], lfo_pm); // Snare Drum (verified on real YM3812) if (channelActiveBits & (1 << 7)) { Slot& SLOT7_2 = channels[7].car; bufs[10][i] += 2 * SLOT7_2.calcOutput(channels[7], eg_cnt, true, lfo_am, genPhaseSnare(phaseM7, noise_rng)); } // Top Cymbal (verified on real YM2413) if (channelActiveBits & (1 << 8)) { Slot& SLOT8_2 = channels[8].car; bufs[11][i] += 2 * SLOT8_2.calcOutput(channels[8], eg_cnt, true, lfo_am, genPhaseCymbal(phaseM7, phaseC8)); } // High Hat (verified on real YM3812) if (channelActiveBits & (1 << (7 + 9))) { Slot& SLOT7_1 = channels[7].mod; bufs[12][i] += 2 * SLOT7_1.calcOutput(channels[7], eg_cnt, true, lfo_am, genPhaseHighHat(phaseM7, phaseC8, noise_rng)); } // Tom Tom (verified on real YM3812) if (channelActiveBits & (1 << (8 + 9))) { Slot& SLOT8_1 = channels[8].mod; bufs[13][i] += 2 * SLOT8_1.calcOutput(channels[8], eg_cnt, true, lfo_am, phaseM8); } } // Vibrato: 8 output levels (triangle waveform) // 1 level takes 1024 samples lfo_pm_cnt.addQuantum(); ++eg_cnt; // The Noise Generator of the YM3812 is 23-bit shift register. // Period is equal to 2^23-2 samples. // Register works at sampling frequency of the chip, so output // can change on every sample. // // Output of the register and input to the bit 22 is: // bit0 XOR bit14 XOR bit15 XOR bit22 // // Simply use bit 22 as the noise output. // int j = ((noise_rng >> 0) ^ (noise_rng >> 14) ^ // (noise_rng >> 15) ^ (noise_rng >> 22)) & 1; // noise_rng = (j << 22) | (noise_rng >> 1); // // Instead of doing all the logic operations above, we // use a trick here (and use bit 0 as the noise output). // The difference is only that the noise bit changes one // step ahead. This doesn't matter since we don't know // what is real state of the noise_rng after the reset. if (noise_rng & 1) { noise_rng ^= 0x800302; } noise_rng >>= 1; } } void YM2413::writeReg(byte r, byte v) { byte old = reg[r]; reg[r] = v; switch (r & 0xF0) { case 0x00: { // 00-0F: control switch (r & 0x0F) { case 0x00: // AM/VIB/EGTYP/KSR/MULTI (modulator) case 0x01: // AM/VIB/EGTYP/KSR/MULTI (carrier) case 0x02: // Key Scale Level, Total Level (modulator) case 0x03: // Key Scale Level, carrier waveform, modulator waveform, // Feedback case 0x04: // Attack, Decay (modulator) case 0x05: // Attack, Decay (carrier) case 0x06: // Sustain, Release (modulator) case 0x07: // Sustain, Release (carrier) updateCustomInstrument(r, v); break; case 0x0E: setRhythmFlags(old); break; } break; } case 0x10: { // 10-18: FNUM 0-7 Channel& ch = getChannelForReg(r); ch.setFrequencyLow(v); break; } case 0x20: { // 20-28: suson, keyon, block, FNUM 8 Channel& ch = getChannelForReg(r); ch.mod.setKeyOnOff(Slot::KEY_MAIN, (v & 0x10) != 0); ch.car.setKeyOnOff(Slot::KEY_MAIN, (v & 0x10) != 0); ch.setSustain((v & 0x20) != 0); // Note: When changing the frequency, a new value for RS is // computed using the sustain value, so make sure the new // sustain value is committed first. ch.setFrequencyHigh(v & 0x0F); break; } case 0x30: { // inst 4 MSBs, VOL 4 LSBs Channel& ch = getChannelForReg(r); ch.car.setTotalLevel(ch, (v & 0x0F) << 2); // Check wether we are in rhythm mode and handle instrument/volume // register accordingly. byte chan = (r & 0x0F) % 9; // verified on real YM2413 if (isRhythm() && (chan >= 6)) { if (chan > 6) { // channel 7 or 8 in ryhthm mode // modulator envelope is HH(chan=7) or TOM(chan=8). ch.mod.setTotalLevel(ch, (v >> 4) << 2); } } else { if ((old & 0xF0) != (v & 0xF0)) { ch.updateInstrument(inst_tab[v >> 4]); } } break; } default: break; } } byte YM2413::peekReg(byte r) const { return reg[r]; } } // namespace Burczynsk static enum_string envelopeStateInfo[] = { { "DUMP", YM2413Burczynski::Slot::EG_DUMP }, { "ATTACK", YM2413Burczynski::Slot::EG_ATTACK }, { "DECAY", YM2413Burczynski::Slot::EG_DECAY }, { "SUSTAIN", YM2413Burczynski::Slot::EG_SUSTAIN }, { "RELEASE", YM2413Burczynski::Slot::EG_RELEASE }, { "OFF", YM2413Burczynski::Slot::EG_OFF } }; SERIALIZE_ENUM(YM2413Burczynski::Slot::EnvelopeState, envelopeStateInfo); namespace YM2413Burczynski { // version 1: initial version // version 2: - removed kcodeScaled // - calculated more members from other state // (TLL, freq, eg_sel_*, eg_sh_*) template void Slot::serialize(Archive& ar, unsigned /*version*/) { // TODO some of the serialized members here could be calculated from // other members int waveform = (wavetable == &sin_tab[0]) ? 0 : 1; ar.serialize("waveform", waveform); if (ar.isLoader()) { setWaveform(waveform); } ar.serialize("phase", phase); ar.serialize("TL", TL); ar.serialize("volume", egout); ar.serialize("sl", sl); ar.serialize("state", state); ar.serialize("op1_out", op1_out); ar.serialize("eg_sustain", eg_sustain); ar.serialize("fb_shift", fb_shift); ar.serialize("key", key); ar.serialize("ar", this->ar); ar.serialize("dr", dr); ar.serialize("rr", rr); ar.serialize("KSR", KSR); ar.serialize("ksl", ksl); ar.serialize("mul", mul); ar.serialize("AMmask", AMmask); ar.serialize("vib", vib); // These are calculated by updateTotalLevel() // TLL // These are calculated by updateGenerators() // freq, eg_sh_ar, eg_sel_ar, eg_sh_dr, eg_sel_dr, eg_sh_rr, eg_sel_rr // eg_sh_rs, eg_sel_rs, eg_sh_dp, eg_sel_dp } // version 1: original version // version 2: removed kcode // version 3: removed instvol_r template void Channel::serialize(Archive& ar, unsigned /*version*/) { // mod/car were originally an array, keep serializing as such for bwc Slot slots[2] = { mod, car }; ar.serialize("slots", slots); if (ar.isLoader()) { mod = slots[0]; car = slots[1]; } ar.serialize("block_fnum", block_fnum); ar.serialize("fc", fc); ar.serialize("ksl_base", ksl_base); ar.serialize("sus", sus); if (ar.isLoader()) { mod.updateFrequency(*this); car.updateFrequency(*this); } } // version 1: initial version // version 2: 'registers' are moved here (no longer serialized in base class) // version 3: removed 'rhythm' variable template void YM2413::serialize(Archive& ar, unsigned version) { if (ar.versionBelow(version, 2)) ar.beginTag("YM2413Core"); ar.serialize("registers", reg); if (ar.versionBelow(version, 2)) ar.endTag("YM2413Core"); // only serialize user instrument ar.serialize_blob("user_instrument", inst_tab[0], 8); ar.serialize("channels", channels); ar.serialize("eg_cnt", eg_cnt); ar.serialize("noise_rng", noise_rng); ar.serialize("lfo_am_cnt", lfo_am_cnt); ar.serialize("lfo_pm_cnt", lfo_pm_cnt); // don't serialize idleSamples, it's only an optimization } } // namespace Burczynsk using YM2413Burczynski::YM2413; INSTANTIATE_SERIALIZE_METHODS(YM2413); REGISTER_POLYMORPHIC_INITIALIZER(YM2413Core, YM2413, "YM2413-Jarek-Burczynski"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/YM2413Burczynski.hh000066400000000000000000000165261257557151200216340ustar00rootroot00000000000000#ifndef YM2413BURCZYNSKI_HH #define YM2413BURCZYNSKI_HH #include "YM2413Core.hh" #include "FixedPoint.hh" #include "serialize_meta.hh" namespace openmsx { namespace YM2413Burczynski { class Channel; /** 16.16 fixed point type for frequency calculations. */ using FreqIndex = FixedPoint<16>; class Slot { public: Slot(); void resetOperators(); /** Update phase increment counter of operator. * Also updates the EG rates if necessary. */ void updateGenerators(Channel& channel); inline int calcOutput(Channel& channel, unsigned eg_cnt, bool carrier, unsigned lfo_am, int phase); inline int calc_slot_mod(Channel& channel, unsigned eg_cnt, bool carrier, unsigned lfo_pm, unsigned lfo_am); inline int calc_envelope(Channel& channel, unsigned eg_cnt, bool carrier); inline int calc_phase(Channel& channel, unsigned lfo_pm); enum KeyPart { KEY_MAIN = 1, KEY_RHYTHM = 2 }; void setKeyOn(KeyPart part); void setKeyOff(KeyPart part); void setKeyOnOff(KeyPart part, bool enabled); /** Does this slot currently produce an output signal? */ bool isActive() const; /** Sets the frequency multiplier [0..15]. */ void setFrequencyMultiplier(byte value); /** Sets the key scale rate: true->0, false->2. */ void setKeyScaleRate(bool value); /** Sets the envelope type of the current instrument. * @param value true->sustained, false->percussive. */ void setEnvelopeSustained(bool value); /** Enables (true) or disables (false) vibrato. */ void setVibrato(bool value); /** Enables (true) or disables (false) amplitude modulation. */ void setAmplitudeModulation(bool value); /** Sets the total level: [0..63]. */ void setTotalLevel(Channel& channel, byte value); /** Sets the key scale level: 0->0 / 1->1.5 / 2->3.0 / 3->6.0 dB/OCT. */ void setKeyScaleLevel(Channel& channel, byte value); /** Sets the waveform: 0 = sinus, 1 = half sinus, half silence. */ void setWaveform(byte value); /** Sets the amount of feedback [0..7]. */ void setFeedbackShift(byte value); /** Sets the attack rate [0..15]. */ void setAttackRate(const Channel& channel, byte value); /** Sets the decay rate [0..15]. */ void setDecayRate(const Channel& channel, byte value); /** Sets the release rate [0..15]. */ void setReleaseRate(const Channel& channel, byte value); /** Sets the sustain level [0..15]. */ void setSustainLevel(byte value); /** Called by Channel when block_fnum changes. */ void updateFrequency(Channel& channel); template void serialize(Archive& ar, unsigned version); public: // public for serialization, otherwise could be private /** Envelope Generator phases * Note: These are ordered: phase constants are compared in the code. */ enum EnvelopeState { EG_DUMP, EG_ATTACK, EG_DECAY, EG_SUSTAIN, EG_RELEASE, EG_OFF }; private: /** Change envelope state */ void setEnvelopeState(EnvelopeState state); inline void updateTotalLevel(Channel& channel); inline void updateAttackRate(int kcodeScaled); inline void updateDecayRate(int kcodeScaled); inline void updateReleaseRate(int kcodeScaled); unsigned* wavetable; // waveform select // Phase Generator FreqIndex phase; // frequency counter FreqIndex freq; // frequency counter step // Envelope Generator int TL; // total level: TL << 2 int TLL; // adjusted now TL int egout; // envelope counter int sl; // sustain level: sl_tab[SL] EnvelopeState state; int op1_out[2]; // MOD output for feedback bool eg_sustain;// percussive/nonpercussive mode byte fb_shift; // feedback shift value byte key; // 0 = KEY OFF, >0 = KEY ON const byte* eg_sel_dp; const byte* eg_sel_ar; const byte* eg_sel_dr; const byte* eg_sel_rr; const byte* eg_sel_rs; unsigned eg_mask_dp; // == (1 << eg_sh_dp) - 1 unsigned eg_mask_ar; // == (1 << eg_sh_ar) - 1 unsigned eg_mask_dr; // == (1 << eg_sh_dr) - 1 unsigned eg_mask_rr; // == (1 << eg_sh_rr) - 1 unsigned eg_mask_rs; // == (1 << eg_sh_rs) - 1 byte eg_sh_dp; // (dump state) byte eg_sh_ar; // (attack state) byte eg_sh_dr; // (decay state) byte eg_sh_rr; // (release state for non-perc.) byte eg_sh_rs; // (release state for perc.mode) byte ar; // attack rate: AR<<2 byte dr; // decay rate: DR<<2 byte rr; // release rate:RR<<2 byte KSR; // key scale rate byte ksl; // keyscale level byte mul; // multiple: mul_tab[ML] // LFO byte AMmask; // LFO Amplitude Modulation enable mask byte vib; // LFO Phase Modulation enable flag (active high) }; class Channel { public: Channel(); /** Calculate the value of the current sample produced by this channel. */ inline int calcOutput(unsigned eg_cnt, unsigned lfo_pm, unsigned lfo_am, int fm); /** Sets the frequency for this channel. */ void setFrequency(int block_fnum); /** Changes the lower 8 bits of the frequency for this channel. */ void setFrequencyLow(byte value); /** Changes the higher 4 bits of the frequency for this channel. */ void setFrequencyHigh(byte value); /** Sets some synthesis parameters as specified by the instrument. * @param part Part [0..7] of the instrument. * @param value New value for this part. */ void updateInstrumentPart(int part, byte value); /** Sets all synthesis parameters as specified by the instrument. * @param inst Instrument data. */ void updateInstrument(const byte* inst); int getBlockFNum() const; FreqIndex getFrequencyIncrement() const; int getKeyScaleLevelBase() const; byte getKeyCode() const; bool isSustained() const; void setSustain(bool sustained); template void serialize(Archive& ar, unsigned version); Slot mod; Slot car; private: // phase generator state int block_fnum; // block+fnum FreqIndex fc; // Freq. freqement base int ksl_base; // KeyScaleLevel Base step bool sus; // sus on/off (release speed in percussive mode) }; class YM2413 final : public YM2413Core { public: YM2413(); template void serialize(Archive& ar, unsigned version); private: // YM2413Core void reset() override; void writeReg(byte reg, byte value) override; byte peekReg(byte reg) const override; void generateChannels(int* bufs[9 + 5], unsigned num) override; int getAmplificationFactor() const override; /** Reset operator parameters. */ void resetOperators(); inline bool isRhythm() const; Channel& getChannelForReg(byte reg); /** Called when the custom instrument (instrument 0) has changed. * @param part Part [0..7] of the instrument. * @param value The new value. */ void updateCustomInstrument(int part, byte value); void setRhythmFlags(byte old); /** OPLL chips have 9 channels. */ Channel channels[9]; /** Global envelope generator counter. */ unsigned eg_cnt; /** Random generator for noise: 23 bit shift register. */ int noise_rng; /** Number of samples the output was completely silent. */ unsigned idleSamples; using LFOAMIndex = FixedPoint< 6>; using LFOPMIndex = FixedPoint<10>; LFOAMIndex lfo_am_cnt; LFOPMIndex lfo_pm_cnt; /** Instrument settings: * 0 - user instrument * 1-15 - fixed instruments * 16 - bass drum settings * 17-18 - other percussion instruments */ byte inst_tab[19][8]; /** Registers */ byte reg[0x40]; }; } // namespace YM2413Burczynski SERIALIZE_CLASS_VERSION(YM2413Burczynski::YM2413, 3); SERIALIZE_CLASS_VERSION(YM2413Burczynski::Channel, 3); SERIALIZE_CLASS_VERSION(YM2413Burczynski::Slot, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/YM2413Core.hh000066400000000000000000000115271257557151200203550ustar00rootroot00000000000000#ifndef YM2413CORE_HH #define YM2413CORE_HH #include "openmsx.hh" namespace openmsx { /** Abstract interface for the YM2413 core. * * We currently have two concrete implementations: * - YM2413Okazaki * - YM2413Burczynski * The long term goal is to study the difference between these two * implementations and merge the best parts of either core into a single core. * * This interface separates the actual YM2413 emulation from the rest of the * (sound) emulation details. This allows to more easily share this * implementation between different emulators. It also allows to more easily * test the YM2413 emulation in isolation. * * There are two main functions in this interface: write to registers and get * output samples. All timing information is implicit in the order of the calls * to these functions (e.g. write some register, generate 10 output samples, * write another register, ...). */ class YM2413Core { public: /** Input clock frequency. * An output sample is generated every 72 cycles. So the output sample * frequency is effectively 49716Hz. If you need the output at e.g. * 44100Hz you need to resample the data. Generating the output at the * native YM2413 frequency allows for quite some simplifications in the * implementation of the cores. It also generally results in better * sound quality. At least if the resampler step itself doesn't degrade * the quality again (Certainly don't simply skip some of the samples * to get to 44kHz, at the very least do linear interpolation. But * preferably use a better resample algorithm). */ static const int CLOCK_FREQ = 3579545; virtual ~YM2413Core() {} /** Reset this YM2413 core. */ virtual void reset() = 0; /** Write to a YM2413 register. */ virtual void writeReg(byte reg, byte value) = 0; /** Read from a YM2413 register. * Note that the real YM2413 chip doesn't allow to read the registers. * This returns the last written value or the default value if this * register hasn't been written to since the last reset() call. * Reading registers has no influence on the generated sound. */ virtual byte peekReg(byte reg) const = 0; /** Generate the sound output. * @param bufs Pointers to output buffers. * @param num The number of required output samples. * * The requested number of samples must be strictly bigger than zero. * * The output of the different channels is put in separate output * buffers. This makes it possible to e.g. record individual channels * or to pan, mute or adjust volume per channel. The YM2413 can operate * in two modes: 9 channels or 6 channels + 5 drum channels. The latter * mode requires a total of 11 output buffers. In the first mode, the * last two output buffers are filled with silence (this is very * efficient, see below). Each output buffer should be big enough to * hold at least 'num' number of ints. * * The output is not simply stored in the buffer, but added to the * existing data in the buffer. So you'll have to zero the content * of the buffer before passing it to this function. OTOH if you want * to combine (some of) the channels, you can pass the same buffer for * all those channels. This approach may have a little overhead when * you're interested in all channels, but it is very efficient if * you're only interested in the combined channels (the most common * case). You can even directly combine this output with other chips * with the same native frequency (like Y8950 or YMF262). * * When the core detects that some channel is silent, it will assign * nullptr to the buffer pointer (so the content of the buffer is left * unchanged, but the pointer to that buffer is set to zero). When all * the channels you're interested in are silent you can even skip all * subsequent audio processing for this channel (e.g. skip resampling to * 44kHz). It is very common that some or even all of the channels are * silent, so it's definitely worth it to implement this optimization. * Also the cores internally try to detect silent channels very early, * so an idle YM2413 core generally requires very little emulation * time. */ virtual void generateChannels(int* bufs[11], unsigned num) = 0; /** Returns normalization factor. * The output of the generateChannels() method should still be * amplified (=multiplied) with this factor to get a consistent volume * level across the different implementations of the YM2413 core. This * allows to internally calculate with native volume levels, and * possibly results in slightly simpler and/or faster code. It's very * likely that subsequent audio processing steps (like resampler, * filters or volume adjustments) must anyway still multiply the output * sample values, so this factor can be folded-in for free. */ virtual int getAmplificationFactor() const = 0; protected: YM2413Core() {} }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/YM2413Okazaki.cc000066400000000000000000001147461257557151200210530ustar00rootroot00000000000000/* * Based on: * emu2413.c -- YM2413 emulator written by Mitsutaka Okazaki 2001 * heavily rewritten to fit openMSX structure */ #include "YM2413Okazaki.hh" #include "serialize.hh" #include "inline.hh" #include "unreachable.hh" #include #include namespace openmsx { namespace YM2413Okazaki { // This defines the tables: // - signed char pmTable[8][8] // - int dB2LinTab[DBTABLEN * 2] // - unsigned AR_ADJUST_TABLE[1 << EG_BITS] // - byte tllTable[4][16 * 8] // - unsigned* waveform[2] // - int dphaseDRTable[16][16] // - byte lfo_am_table[LFO_AM_TAB_ELEMENTS] // - unsigned slTable[16] // - byte mlTable[16] #include "YM2413OkazakiTable.ii" // Extra (derived) constants static const EnvPhaseIndex EG_DP_MAX = EnvPhaseIndex(1 << 7); static const EnvPhaseIndex EG_DP_INF = EnvPhaseIndex(1 << 8); // as long as it's bigger // // Helper functions // static inline int EG2DB(int d) { return d * int(EG_STEP / DB_STEP); } static inline unsigned TL2EG(unsigned d) { assert(d < 64); // input is in range [0..63] return d * int(TL_STEP / EG_STEP); } static inline unsigned DB_POS(float x) { int result = int(x / DB_STEP); assert(0 <= result); assert(result < DB_MUTE); return result; } static inline unsigned DB_NEG(float x) { return DBTABLEN + DB_POS(x); } static inline bool BIT(unsigned s, unsigned b) { return (s >> b) & 1; } // // Patch // Patch::Patch() : AMPM(0), EG(false), AR(0), DR(0), RR(0) { setKR(0); setML(0); setKL(0); setTL(0); setWF(0); setFB(0); setSL(0); } void Patch::initModulator(const byte* data) { AMPM = (data[0] >> 6) & 3; EG = (data[0] >> 5) & 1; setKR ((data[0] >> 4) & 1); setML ((data[0] >> 0) & 15); setKL ((data[2] >> 6) & 3); setTL ((data[2] >> 0) & 63); setWF ((data[3] >> 3) & 1); setFB ((data[3] >> 0) & 7); AR = (data[4] >> 4) & 15; DR = (data[4] >> 0) & 15; setSL ((data[6] >> 4) & 15); RR = (data[6] >> 0) & 15; } void Patch::initCarrier(const byte* data) { AMPM = (data[1] >> 6) & 3; EG = (data[1] >> 5) & 1; setKR ((data[1] >> 4) & 1); setML ((data[1] >> 0) & 15); setKL ((data[3] >> 6) & 3); setTL (0); setWF ((data[3] >> 4) & 1); setFB (0); AR = (data[5] >> 4) & 15; DR = (data[5] >> 0) & 15; setSL ((data[7] >> 4) & 15); RR = (data[7] >> 0) & 15; } void Patch::setKR(byte value) { KR = value ? 8 : 10; } void Patch::setML(byte value) { ML = mlTable[value]; } void Patch::setKL(byte value) { KL = tllTable[value]; } void Patch::setTL(byte value) { assert(value < 64); assert(TL2EG(value) < 256); TL = TL2EG(value); } void Patch::setWF(byte value) { WF = waveform[value]; } void Patch::setFB(byte value) { FB = value ? 8 - value : 0; } void Patch::setSL(byte value) { SL = slTable[value]; } // // Slot // void Slot::reset() { cphase = 0; for (auto& dp : dphase) dp = 0; output = 0; feedback = 0; setEnvelopeState(FINISH); dphaseDRTableRks = dphaseDRTable[0]; tll = 0; sustain = false; volume = TL2EG(0); slot_on_flag = 0; } void Slot::updatePG(unsigned freq) { // Pre-calculate all phase-increments. The 8 different values are for // the 8 steps of the PM stuff (for mod and car phase calculation). // When PM isn't used then dphase[0] is used (pmTable[.][0] == 0). // The original Okazaki core calculated the PM stuff in a different // way. This algorithm was copied from the Burczynski core because it // is much more suited for a (cheap) hardware calculation. unsigned fnum = freq & 511; unsigned block = freq / 512; for (int pm = 0; pm < 8; ++pm) { unsigned tmp = ((2 * fnum + pmTable[fnum >> 6][pm]) * patch.ML) << block; dphase[pm] = tmp >> (21 - DP_BITS); } } void Slot::updateTLL(unsigned freq, bool actAsCarrier) { tll = patch.KL[freq >> 5] + (actAsCarrier ? volume : patch.TL); } void Slot::updateRKS(unsigned freq) { unsigned rks = freq >> patch.KR; assert(rks < 16); dphaseDRTableRks = dphaseDRTable[rks]; } void Slot::updateEG() { switch (state) { case ATTACK: // Original code had separate table for AR, the values in // this table were 12 times bigger than the values in the // dphaseDRTableRks table, expect for the value for AR=15. // But when AR=15, the value doesn't matter. // // This factor 12 can also be seen in the attack/decay rates // table in the ym2413 application manual (table III-7, page // 13). For other chips like OPL1, OPL3 this ratio seems to be // different. eg_dphase = EnvPhaseIndex::create( dphaseDRTableRks[patch.AR] * 12); break; case DECAY: eg_dphase = EnvPhaseIndex::create(dphaseDRTableRks[patch.DR]); break; case SUSTAIN: eg_dphase = EnvPhaseIndex::create(dphaseDRTableRks[patch.RR]); break; case RELEASE: { unsigned idx = sustain ? 5 : (patch.EG ? patch.RR : 7); eg_dphase = EnvPhaseIndex::create(dphaseDRTableRks[idx]); break; } case SETTLE: // Value based on ym2413 application manual: // - p10: (envelope graph) // DP: 10ms // - p13: (table III-7 attack and decay rates) // Rate 12-0 -> 10.22ms // Rate 12-1 -> 8.21ms // Rate 12-2 -> 6.84ms // Rate 12-3 -> 5.87ms // The datasheet doesn't say anything about key-scaling for // this state (in fact it doesn't say much at all about this // state). Experiments showed that the with key-scaling the // output matches closer the real HW. Also all other states use // key-scaling. eg_dphase = EnvPhaseIndex::create(dphaseDRTableRks[12]); break; case SUSHOLD: case FINISH: eg_dphase = EnvPhaseIndex(0); break; } } void Slot::updateAll(unsigned freq, bool actAsCarrier) { updatePG(freq); updateTLL(freq, actAsCarrier); updateRKS(freq); updateEG(); // EG should be updated last } void Slot::setEnvelopeState(EnvelopeState state_) { state = state_; switch (state) { case ATTACK: eg_phase_max = (patch.AR == 15) ? EnvPhaseIndex(0) : EG_DP_MAX; break; case DECAY: eg_phase_max = EnvPhaseIndex::create(patch.SL); break; case SUSHOLD: eg_phase_max = EG_DP_INF; break; case SETTLE: case SUSTAIN: case RELEASE: eg_phase_max = EG_DP_MAX; break; case FINISH: eg_phase = EG_DP_MAX; eg_phase_max = EG_DP_INF; break; } updateEG(); } bool Slot::isActive() const { return state != FINISH; } // Slot key on void Slot::slotOn() { setEnvelopeState(ATTACK); eg_phase = EnvPhaseIndex(0); cphase = 0; } // Slot key on, without resetting the phase void Slot::slotOn2() { setEnvelopeState(ATTACK); eg_phase = EnvPhaseIndex(0); } // Slot key off void Slot::slotOff() { if (state == FINISH) return; // already in off state if (state == ATTACK) { eg_phase = EnvPhaseIndex(AR_ADJUST_TABLE[eg_phase.toInt()]); } setEnvelopeState(RELEASE); } // Change a rhythm voice void Slot::setPatch(Patch& patch) { this->patch = patch; // copy data if ((state == SUSHOLD) && (patch.EG == 0)) { setEnvelopeState(SUSTAIN); } setEnvelopeState(state); // recalc eg_phase_max } // Set new volume based on 4-bit register value (0-15). void Slot::setVolume(unsigned value) { // '<< 2' to transform 4 bits to the same range as the 6 bits TL field volume = TL2EG(value << 2); } // // Channel // Channel::Channel() { car.sibling = &mod; // car needs a pointer to its sibling mod.sibling = nullptr; // mod doesn't need this pointer } void Channel::reset(YM2413& ym2413) { mod.reset(); car.reset(); setPatch(0, ym2413); } // Change a voice void Channel::setPatch(unsigned num, YM2413& ym2413) { mod.setPatch(ym2413.getPatch(num, false)); car.setPatch(ym2413.getPatch(num, true)); } // Set sustain parameter void Channel::setSustain(bool sustain, bool modActAsCarrier) { car.sustain = sustain; if (modActAsCarrier) { mod.sustain = sustain; } } // Channel key on void Channel::keyOn() { // TODO Should we also test mod.slot_on_flag? // Should we set mod.slot_on_flag? // Can make a difference for channel 7/8 in ryhthm mode. if (!car.slot_on_flag) { car.setEnvelopeState(SETTLE); // this will shortly set both car and mod to ATTACK state } car.slot_on_flag |= 1; mod.slot_on_flag |= 1; } // Channel key off void Channel::keyOff() { // Note: no mod.slotOff() in original code!!! if (car.slot_on_flag) { car.slot_on_flag &= ~1; mod.slot_on_flag &= ~1; if (!car.slot_on_flag) { car.slotOff(); } } } // // YM2413 // static byte inst_data[16 + 3][8] = { { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, // user instrument { 0x61,0x61,0x1e,0x17,0xf0,0x7f,0x00,0x17 }, // violin { 0x13,0x41,0x16,0x0e,0xfd,0xf4,0x23,0x23 }, // guitar { 0x03,0x01,0x9a,0x04,0xf3,0xf3,0x13,0xf3 }, // piano { 0x11,0x61,0x0e,0x07,0xfa,0x64,0x70,0x17 }, // flute { 0x22,0x21,0x1e,0x06,0xf0,0x76,0x00,0x28 }, // clarinet { 0x21,0x22,0x16,0x05,0xf0,0x71,0x00,0x18 }, // oboe { 0x21,0x61,0x1d,0x07,0x82,0x80,0x17,0x17 }, // trumpet { 0x23,0x21,0x2d,0x16,0x90,0x90,0x00,0x07 }, // organ { 0x21,0x21,0x1b,0x06,0x64,0x65,0x10,0x17 }, // horn { 0x21,0x21,0x0b,0x1a,0x85,0xa0,0x70,0x07 }, // synthesizer { 0x23,0x01,0x83,0x10,0xff,0xb4,0x10,0xf4 }, // harpsichord { 0x97,0xc1,0x20,0x07,0xff,0xf4,0x22,0x22 }, // vibraphone { 0x61,0x00,0x0c,0x05,0xc2,0xf6,0x40,0x44 }, // synthesizer bass { 0x01,0x01,0x56,0x03,0x94,0xc2,0x03,0x12 }, // acoustic bass { 0x21,0x01,0x89,0x03,0xf1,0xe4,0xf0,0x23 }, // electric guitar { 0x07,0x21,0x14,0x00,0xee,0xf8,0xff,0xf8 }, { 0x01,0x31,0x00,0x00,0xf8,0xf7,0xf8,0xf7 }, { 0x25,0x11,0x00,0x00,0xf8,0xfa,0xf8,0x55 } }; YM2413::YM2413() { memset(reg, 0, sizeof(reg)); // avoid UMR for (unsigned i = 0; i < 16 + 3; ++i) { patches[i][0].initModulator(inst_data[i]); patches[i][1].initCarrier (inst_data[i]); } reset(); } // Reset whole of OPLL except patch datas void YM2413::reset() { pm_phase = 0; am_phase = 0; noise_seed = 0xFFFF; for (auto& ch : channels) { ch.reset(*this); } for (unsigned i = 0; i < 0x40; ++i) { writeReg(i, 0); } } // Drum key on void YM2413::keyOn_BD() { Channel& ch6 = channels[6]; if (!ch6.car.slot_on_flag) { ch6.car.setEnvelopeState(SETTLE); // this will shortly set both car and mod to ATTACK state } ch6.car.slot_on_flag |= 2; ch6.mod.slot_on_flag |= 2; } void YM2413::keyOn_HH() { // TODO do these also use the SETTLE stuff? Channel& ch7 = channels[7]; if (!ch7.mod.slot_on_flag) { ch7.mod.slotOn2(); } ch7.mod.slot_on_flag |= 2; } void YM2413::keyOn_SD() { Channel& ch7 = channels[7]; if (!ch7.car.slot_on_flag) { ch7.car.slotOn(); } ch7.car.slot_on_flag |= 2; } void YM2413::keyOn_TOM() { Channel& ch8 = channels[8]; if (!ch8.mod.slot_on_flag) { ch8.mod.slotOn(); } ch8.mod.slot_on_flag |= 2; } void YM2413::keyOn_CYM() { Channel& ch8 = channels[8]; if (!ch8.car.slot_on_flag) { ch8.car.slotOn2(); } ch8.car.slot_on_flag |= 2; } // Drum key off void YM2413::keyOff_BD() { Channel& ch6 = channels[6]; if (ch6.car.slot_on_flag) { ch6.car.slot_on_flag &= ~2; ch6.mod.slot_on_flag &= ~2; if (!ch6.car.slot_on_flag) { ch6.car.slotOff(); } } } void YM2413::keyOff_HH() { Channel& ch7 = channels[7]; if (ch7.mod.slot_on_flag) { ch7.mod.slot_on_flag &= ~2; if (!ch7.mod.slot_on_flag) { ch7.mod.slotOff(); } } } void YM2413::keyOff_SD() { Channel& ch7 = channels[7]; if (ch7.car.slot_on_flag) { ch7.car.slot_on_flag &= ~2; if (!ch7.car.slot_on_flag) { ch7.car.slotOff(); } } } void YM2413::keyOff_TOM() { Channel& ch8 = channels[8]; if (ch8.mod.slot_on_flag) { ch8.mod.slot_on_flag &= ~2; if (!ch8.mod.slot_on_flag) { ch8.mod.slotOff(); } } } void YM2413::keyOff_CYM() { Channel& ch8 = channels[8]; if (ch8.car.slot_on_flag) { ch8.car.slot_on_flag &= ~2; if (!ch8.car.slot_on_flag) { ch8.car.slotOff(); } } } void YM2413::setRhythmFlags(byte old) { Channel& ch6 = channels[6]; Channel& ch7 = channels[7]; Channel& ch8 = channels[8]; // flags = X | X | mode | BD | SD | TOM | TC | HH byte flags = reg[0x0E]; if ((flags ^ old) & 0x20) { if (flags & 0x20) { // OFF -> ON ch6.setPatch(16, *this); ch7.setPatch(17, *this); ch7.mod.setVolume(reg[0x37] >> 4); ch8.setPatch(18, *this); ch8.mod.setVolume(reg[0x38] >> 4); } else { // ON -> OFF ch6.setPatch(reg[0x36] >> 4, *this); keyOff_BD(); ch7.setPatch(reg[0x37] >> 4, *this); keyOff_SD(); keyOff_HH(); ch8.setPatch(reg[0x38] >> 4, *this); keyOff_TOM(); keyOff_CYM(); } } if (flags & 0x20) { if (flags & 0x10) keyOn_BD(); else keyOff_BD(); if (flags & 0x08) keyOn_SD(); else keyOff_SD(); if (flags & 0x04) keyOn_TOM(); else keyOff_TOM(); if (flags & 0x02) keyOn_CYM(); else keyOff_CYM(); if (flags & 0x01) keyOn_HH(); else keyOff_HH(); } unsigned freq6 = getFreq(6); ch6.mod.updateAll(freq6, false); ch6.car.updateAll(freq6, true); unsigned freq7 = getFreq(7); ch7.mod.updateAll(freq7, isRhythm()); ch7.car.updateAll(freq7, true); unsigned freq8 = getFreq(8); ch8.mod.updateAll(freq8, isRhythm()); ch8.car.updateAll(freq8, true); } void YM2413::update_key_status() { for (unsigned i = 0; i < 9; ++i) { int slot_on = (reg[0x20 + i] & 0x10) ? 1 : 0; Channel& ch = channels[i]; ch.mod.slot_on_flag = slot_on; ch.car.slot_on_flag = slot_on; } if (isRhythm()) { Channel& ch6 = channels[6]; ch6.mod.slot_on_flag |= (reg[0x0e] & 0x10) ? 2 : 0; // BD1 ch6.car.slot_on_flag |= (reg[0x0e] & 0x10) ? 2 : 0; // BD2 Channel& ch7 = channels[7]; ch7.mod.slot_on_flag |= (reg[0x0e] & 0x01) ? 2 : 0; // HH ch7.car.slot_on_flag |= (reg[0x0e] & 0x08) ? 2 : 0; // SD Channel& ch8 = channels[8]; ch8.mod.slot_on_flag |= (reg[0x0e] & 0x04) ? 2 : 0; // TOM ch8.car.slot_on_flag |= (reg[0x0e] & 0x02) ? 2 : 0; // CYM } } // Convert Amp(0 to EG_HEIGHT) to Phase(0 to 8PI) static inline int wave2_8pi(int e) { int shift = SLOT_AMP_BITS - PG_BITS - 2; if (shift > 0) { return e >> shift; } else { return e << -shift; } } // PG ALWAYS_INLINE unsigned Slot::calc_phase(unsigned lfo_pm) { cphase += dphase[lfo_pm]; return cphase >> DP_BASE_BITS; } // EG void Slot::calc_envelope_outline(unsigned& out) { switch (state) { case ATTACK: out = 0; eg_phase = EnvPhaseIndex(0); setEnvelopeState(DECAY); break; case DECAY: eg_phase = eg_phase_max; setEnvelopeState(patch.EG ? SUSHOLD : SUSTAIN); break; case SUSTAIN: case RELEASE: setEnvelopeState(FINISH); break; case SETTLE: // Comment copied from Burczynski code (didn't verify myself): // When CARRIER envelope gets down to zero level, phases in // BOTH operators are reset (at the same time?). slotOn(); sibling->slotOn(); break; case SUSHOLD: case FINISH: default: UNREACHABLE; break; } } template ALWAYS_INLINE unsigned Slot::calc_envelope(int lfo_am, unsigned fixed_env) { assert(!FIXED_ENV || (state == SUSHOLD) || (state == FINISH)); if (FIXED_ENV) { unsigned out = fixed_env; if (HAS_AM) { out += lfo_am; // [0, 512) out |= 3; } else { // out |= 3 is already done in calc_fixed_env() } return out; } else { unsigned out = eg_phase.toInt(); // in range [0, 128) if (state == ATTACK) { out = AR_ADJUST_TABLE[out]; // [0, 128) } eg_phase += eg_dphase; if (eg_phase >= eg_phase_max) { calc_envelope_outline(out); } out = EG2DB(out + tll); // [0, 480) if (HAS_AM) { out += lfo_am; // [0, 512) } return out | 3; } } template unsigned Slot::calc_fixed_env() const { assert((state == SUSHOLD) || (state == FINISH)); assert(eg_dphase == EnvPhaseIndex(0)); unsigned out = eg_phase.toInt(); // in range [0, 128) out = EG2DB(out + tll); // [0, 480) if (!HAS_AM) { out |= 3; } return out; } // CARRIER template ALWAYS_INLINE int Slot::calc_slot_car(unsigned lfo_pm, int lfo_am, int fm, unsigned fixed_env) { int phase = calc_phase(lfo_pm) + wave2_8pi(fm); unsigned egout = calc_envelope(lfo_am, fixed_env); int newOutput = dB2LinTab[patch.WF[phase & PG_MASK] + egout]; output = (output + newOutput) >> 1; return output; } // MODULATOR template ALWAYS_INLINE int Slot::calc_slot_mod(unsigned lfo_pm, int lfo_am, unsigned fixed_env) { assert((patch.FB != 0) == HAS_FB); unsigned phase = calc_phase(lfo_pm); unsigned egout = calc_envelope(lfo_am, fixed_env); if (HAS_FB) { phase += wave2_8pi(feedback) >> patch.FB; } int newOutput = dB2LinTab[patch.WF[phase & PG_MASK] + egout]; feedback = (output + newOutput) >> 1; output = newOutput; return feedback; } // TOM (ch8 mod) ALWAYS_INLINE int Slot::calc_slot_tom() { unsigned phase = calc_phase(0); unsigned egout = calc_envelope(0, 0); return dB2LinTab[patch.WF[phase & PG_MASK] + egout]; } // SNARE (ch7 car) ALWAYS_INLINE int Slot::calc_slot_snare(bool noise) { unsigned phase = calc_phase(0); unsigned egout = calc_envelope(0, 0); return BIT(phase, 7) ? dB2LinTab[(noise ? DB_POS(0.0f) : DB_POS(15.0f)) + egout] : dB2LinTab[(noise ? DB_NEG(0.0f) : DB_NEG(15.0f)) + egout]; } // TOP-CYM (ch8 car) ALWAYS_INLINE int Slot::calc_slot_cym(unsigned phase7, unsigned phase8) { unsigned egout = calc_envelope(0, 0); unsigned dbout = (((BIT(phase7, PG_BITS - 8) ^ BIT(phase7, PG_BITS - 1)) | BIT(phase7, PG_BITS - 7)) ^ ( BIT(phase8, PG_BITS - 7) & !BIT(phase8, PG_BITS - 5))) ? DB_NEG(3.0f) : DB_POS(3.0f); return dB2LinTab[dbout + egout]; } // HI-HAT (ch7 mod) ALWAYS_INLINE int Slot::calc_slot_hat(unsigned phase7, unsigned phase8, bool noise) { unsigned egout = calc_envelope(0, 0); unsigned dbout = (((BIT(phase7, PG_BITS - 8) ^ BIT(phase7, PG_BITS - 1)) | BIT(phase7, PG_BITS - 7)) ^ ( BIT(phase8, PG_BITS - 7) & !BIT(phase8, PG_BITS - 5))) ? (noise ? DB_NEG(12.0f) : DB_NEG(24.0f)) : (noise ? DB_POS(12.0f) : DB_POS(24.0f)); return dB2LinTab[dbout + egout]; } int YM2413::getAmplificationFactor() const { return 1 << (15 - DB2LIN_AMP_BITS); } bool YM2413::isRhythm() const { return (reg[0x0E] & 0x20) != 0; } unsigned YM2413::getFreq(unsigned channel) const { // combined fnum (=9bit) and block (=3bit) assert(channel < 9); return reg[0x10 + channel] | ((reg[0x20 + channel] & 0x0F) << 8); } Patch& YM2413::getPatch(unsigned instrument, bool carrier) { return patches[instrument][carrier]; } template ALWAYS_INLINE void YM2413::calcChannel(Channel& ch, int* buf, unsigned num) { // VC++ requires explicit conversion to bool. Compiler bug?? const bool HAS_CAR_PM = (FLAGS & 1) != 0; const bool HAS_CAR_AM = (FLAGS & 2) != 0; const bool HAS_MOD_PM = (FLAGS & 4) != 0; const bool HAS_MOD_AM = (FLAGS & 8) != 0; const bool HAS_MOD_FB = (FLAGS & 16) != 0; const bool HAS_CAR_FIXED_ENV = (FLAGS & 32) != 0; const bool HAS_MOD_FIXED_ENV = (FLAGS & 64) != 0; assert(((ch.car.patch.AMPM & 1) != 0) == HAS_CAR_PM); assert(((ch.car.patch.AMPM & 2) != 0) == HAS_CAR_AM); assert(((ch.mod.patch.AMPM & 1) != 0) == HAS_MOD_PM); assert(((ch.mod.patch.AMPM & 2) != 0) == HAS_MOD_AM); unsigned tmp_pm_phase = pm_phase; unsigned tmp_am_phase = am_phase; unsigned car_fixed_env = 0; // dummy unsigned mod_fixed_env = 0; // dummy if (HAS_CAR_FIXED_ENV) { car_fixed_env = ch.car.calc_fixed_env(); } if (HAS_MOD_FIXED_ENV) { mod_fixed_env = ch.mod.calc_fixed_env(); } unsigned sample = 0; do { unsigned lfo_pm = 0; if (HAS_CAR_PM || HAS_MOD_PM) { // Copied from Burczynski: // There are only 8 different steps for PM, and each // step lasts for 1024 samples. This results in a PM // freq of 6.1Hz (but datasheet says it's 6.4Hz). ++tmp_pm_phase; lfo_pm = (tmp_pm_phase >> 10) & 7; } int lfo_am = 0; // avoid warning if (HAS_CAR_AM || HAS_MOD_AM) { ++tmp_am_phase; if (tmp_am_phase == (LFO_AM_TAB_ELEMENTS * 64)) { tmp_am_phase = 0; } lfo_am = lfo_am_table[tmp_am_phase / 64]; } int fm = ch.mod.calc_slot_mod( lfo_pm, lfo_am, mod_fixed_env); buf[sample] += ch.car.calc_slot_car( lfo_pm, lfo_am, fm, car_fixed_env); ++sample; } while (sample < num); } void YM2413::generateChannels(int* bufs[9 + 5], unsigned num) { assert(num != 0); unsigned m = isRhythm() ? 6 : 9; for (unsigned i = 0; i < m; ++i) { Channel& ch = channels[i]; if (ch.car.isActive()) { // Below we choose between 128 specialized versions of // calcChannel(). This allows to move a lot of // conditional code out of the inner-loop. bool carFixedEnv = (ch.car.state == SUSHOLD) || (ch.car.state == FINISH); bool modFixedEnv = (ch.mod.state == SUSHOLD) || (ch.mod.state == FINISH); if (ch.car.state == SETTLE) { modFixedEnv = false; } unsigned flags = ( ch.car.patch.AMPM << 0) | ( ch.mod.patch.AMPM << 2) | ((ch.mod.patch.FB != 0) << 4) | ( carFixedEnv << 5) | ( modFixedEnv << 6); switch (flags) { case 0: calcChannel< 0>(ch, bufs[i], num); break; case 1: calcChannel< 1>(ch, bufs[i], num); break; case 2: calcChannel< 2>(ch, bufs[i], num); break; case 3: calcChannel< 3>(ch, bufs[i], num); break; case 4: calcChannel< 4>(ch, bufs[i], num); break; case 5: calcChannel< 5>(ch, bufs[i], num); break; case 6: calcChannel< 6>(ch, bufs[i], num); break; case 7: calcChannel< 7>(ch, bufs[i], num); break; case 8: calcChannel< 8>(ch, bufs[i], num); break; case 9: calcChannel< 9>(ch, bufs[i], num); break; case 10: calcChannel< 10>(ch, bufs[i], num); break; case 11: calcChannel< 11>(ch, bufs[i], num); break; case 12: calcChannel< 12>(ch, bufs[i], num); break; case 13: calcChannel< 13>(ch, bufs[i], num); break; case 14: calcChannel< 14>(ch, bufs[i], num); break; case 15: calcChannel< 15>(ch, bufs[i], num); break; case 16: calcChannel< 16>(ch, bufs[i], num); break; case 17: calcChannel< 17>(ch, bufs[i], num); break; case 18: calcChannel< 18>(ch, bufs[i], num); break; case 19: calcChannel< 19>(ch, bufs[i], num); break; case 20: calcChannel< 20>(ch, bufs[i], num); break; case 21: calcChannel< 21>(ch, bufs[i], num); break; case 22: calcChannel< 22>(ch, bufs[i], num); break; case 23: calcChannel< 23>(ch, bufs[i], num); break; case 24: calcChannel< 24>(ch, bufs[i], num); break; case 25: calcChannel< 25>(ch, bufs[i], num); break; case 26: calcChannel< 26>(ch, bufs[i], num); break; case 27: calcChannel< 27>(ch, bufs[i], num); break; case 28: calcChannel< 28>(ch, bufs[i], num); break; case 29: calcChannel< 29>(ch, bufs[i], num); break; case 30: calcChannel< 30>(ch, bufs[i], num); break; case 31: calcChannel< 31>(ch, bufs[i], num); break; case 32: calcChannel< 32>(ch, bufs[i], num); break; case 33: calcChannel< 33>(ch, bufs[i], num); break; case 34: calcChannel< 34>(ch, bufs[i], num); break; case 35: calcChannel< 35>(ch, bufs[i], num); break; case 36: calcChannel< 36>(ch, bufs[i], num); break; case 37: calcChannel< 37>(ch, bufs[i], num); break; case 38: calcChannel< 38>(ch, bufs[i], num); break; case 39: calcChannel< 39>(ch, bufs[i], num); break; case 40: calcChannel< 40>(ch, bufs[i], num); break; case 41: calcChannel< 41>(ch, bufs[i], num); break; case 42: calcChannel< 42>(ch, bufs[i], num); break; case 43: calcChannel< 43>(ch, bufs[i], num); break; case 44: calcChannel< 44>(ch, bufs[i], num); break; case 45: calcChannel< 45>(ch, bufs[i], num); break; case 46: calcChannel< 46>(ch, bufs[i], num); break; case 47: calcChannel< 47>(ch, bufs[i], num); break; case 48: calcChannel< 48>(ch, bufs[i], num); break; case 49: calcChannel< 49>(ch, bufs[i], num); break; case 50: calcChannel< 50>(ch, bufs[i], num); break; case 51: calcChannel< 51>(ch, bufs[i], num); break; case 52: calcChannel< 52>(ch, bufs[i], num); break; case 53: calcChannel< 53>(ch, bufs[i], num); break; case 54: calcChannel< 54>(ch, bufs[i], num); break; case 55: calcChannel< 55>(ch, bufs[i], num); break; case 56: calcChannel< 56>(ch, bufs[i], num); break; case 57: calcChannel< 57>(ch, bufs[i], num); break; case 58: calcChannel< 58>(ch, bufs[i], num); break; case 59: calcChannel< 59>(ch, bufs[i], num); break; case 60: calcChannel< 60>(ch, bufs[i], num); break; case 61: calcChannel< 61>(ch, bufs[i], num); break; case 62: calcChannel< 62>(ch, bufs[i], num); break; case 63: calcChannel< 63>(ch, bufs[i], num); break; case 64: calcChannel< 64>(ch, bufs[i], num); break; case 65: calcChannel< 65>(ch, bufs[i], num); break; case 66: calcChannel< 66>(ch, bufs[i], num); break; case 67: calcChannel< 67>(ch, bufs[i], num); break; case 68: calcChannel< 68>(ch, bufs[i], num); break; case 69: calcChannel< 69>(ch, bufs[i], num); break; case 70: calcChannel< 70>(ch, bufs[i], num); break; case 71: calcChannel< 71>(ch, bufs[i], num); break; case 72: calcChannel< 72>(ch, bufs[i], num); break; case 73: calcChannel< 73>(ch, bufs[i], num); break; case 74: calcChannel< 74>(ch, bufs[i], num); break; case 75: calcChannel< 75>(ch, bufs[i], num); break; case 76: calcChannel< 76>(ch, bufs[i], num); break; case 77: calcChannel< 77>(ch, bufs[i], num); break; case 78: calcChannel< 78>(ch, bufs[i], num); break; case 79: calcChannel< 79>(ch, bufs[i], num); break; case 80: calcChannel< 80>(ch, bufs[i], num); break; case 81: calcChannel< 81>(ch, bufs[i], num); break; case 82: calcChannel< 82>(ch, bufs[i], num); break; case 83: calcChannel< 83>(ch, bufs[i], num); break; case 84: calcChannel< 84>(ch, bufs[i], num); break; case 85: calcChannel< 85>(ch, bufs[i], num); break; case 86: calcChannel< 86>(ch, bufs[i], num); break; case 87: calcChannel< 87>(ch, bufs[i], num); break; case 88: calcChannel< 88>(ch, bufs[i], num); break; case 89: calcChannel< 89>(ch, bufs[i], num); break; case 90: calcChannel< 90>(ch, bufs[i], num); break; case 91: calcChannel< 91>(ch, bufs[i], num); break; case 92: calcChannel< 92>(ch, bufs[i], num); break; case 93: calcChannel< 93>(ch, bufs[i], num); break; case 94: calcChannel< 94>(ch, bufs[i], num); break; case 95: calcChannel< 95>(ch, bufs[i], num); break; case 96: calcChannel< 96>(ch, bufs[i], num); break; case 97: calcChannel< 97>(ch, bufs[i], num); break; case 98: calcChannel< 98>(ch, bufs[i], num); break; case 99: calcChannel< 99>(ch, bufs[i], num); break; case 100: calcChannel<100>(ch, bufs[i], num); break; case 101: calcChannel<101>(ch, bufs[i], num); break; case 102: calcChannel<102>(ch, bufs[i], num); break; case 103: calcChannel<103>(ch, bufs[i], num); break; case 104: calcChannel<104>(ch, bufs[i], num); break; case 105: calcChannel<105>(ch, bufs[i], num); break; case 106: calcChannel<106>(ch, bufs[i], num); break; case 107: calcChannel<107>(ch, bufs[i], num); break; case 108: calcChannel<108>(ch, bufs[i], num); break; case 109: calcChannel<109>(ch, bufs[i], num); break; case 110: calcChannel<110>(ch, bufs[i], num); break; case 111: calcChannel<111>(ch, bufs[i], num); break; case 112: calcChannel<112>(ch, bufs[i], num); break; case 113: calcChannel<113>(ch, bufs[i], num); break; case 114: calcChannel<114>(ch, bufs[i], num); break; case 115: calcChannel<115>(ch, bufs[i], num); break; case 116: calcChannel<116>(ch, bufs[i], num); break; case 117: calcChannel<117>(ch, bufs[i], num); break; case 118: calcChannel<118>(ch, bufs[i], num); break; case 119: calcChannel<119>(ch, bufs[i], num); break; case 120: calcChannel<120>(ch, bufs[i], num); break; case 121: calcChannel<121>(ch, bufs[i], num); break; case 122: calcChannel<122>(ch, bufs[i], num); break; case 123: calcChannel<123>(ch, bufs[i], num); break; case 124: calcChannel<124>(ch, bufs[i], num); break; case 125: calcChannel<125>(ch, bufs[i], num); break; case 126: calcChannel<126>(ch, bufs[i], num); break; case 127: calcChannel<127>(ch, bufs[i], num); break; default: UNREACHABLE; } } else { bufs[i] = nullptr; } } // update AM, PM unit pm_phase += num; am_phase = (am_phase + num) % (LFO_AM_TAB_ELEMENTS * 64); if (isRhythm()) { bufs[6] = nullptr; bufs[7] = nullptr; bufs[8] = nullptr; Channel& ch6 = channels[6]; Channel& ch7 = channels[7]; Channel& ch8 = channels[8]; unsigned old_noise = noise_seed; unsigned old_cphase7 = ch7.mod.cphase; unsigned old_cphase8 = ch8.car.cphase; if (ch6.car.isActive()) { for (unsigned sample = 0; sample < num; ++sample) { bufs[ 9][sample] += 2 * ch6.car.calc_slot_car( 0, 0, ch6.mod.calc_slot_mod< false, false, false>(0, 0, 0), 0); } } else { bufs[9] = nullptr; } if (ch7.car.isActive()) { for (unsigned sample = 0; sample < num; ++sample) { noise_seed >>= 1; bool noise_bit = noise_seed & 1; if (noise_bit) noise_seed ^= 0x8003020; bufs[10][sample] += -2 * ch7.car.calc_slot_snare(noise_bit); } } else { bufs[10] = nullptr; } if (ch8.car.isActive()) { for (unsigned sample = 0; sample < num; ++sample) { unsigned phase7 = ch7.mod.calc_phase(0); unsigned phase8 = ch8.car.calc_phase(0); bufs[11][sample] += -2 * ch8.car.calc_slot_cym(phase7, phase8); } } else { bufs[11] = nullptr; } if (ch7.mod.isActive()) { // restore noise, ch7/8 cphase noise_seed = old_noise; ch7.mod.cphase = old_cphase7; ch8.car.cphase = old_cphase8; for (unsigned sample = 0; sample < num; ++sample) { noise_seed >>= 1; bool noise_bit = noise_seed & 1; if (noise_bit) noise_seed ^= 0x8003020; unsigned phase7 = ch7.mod.calc_phase(0); unsigned phase8 = ch8.car.calc_phase(0); bufs[12][sample] += 2 * ch7.mod.calc_slot_hat(phase7, phase8, noise_bit); } } else { bufs[12] = nullptr; } if (ch8.mod.isActive()) { for (unsigned sample = 0; sample < num; ++sample) { bufs[13][sample] += 2 * ch8.mod.calc_slot_tom(); } } else { bufs[13] = nullptr; } } else { bufs[ 9] = nullptr; bufs[10] = nullptr; bufs[11] = nullptr; bufs[12] = nullptr; bufs[13] = nullptr; } } void YM2413::writeReg(byte r, byte data) { assert(r < 0x40); switch (r) { case 0x00: { reg[r] = data; patches[0][0].AMPM = (data >> 6) & 3; patches[0][0].EG = (data >> 5) & 1; patches[0][0].setKR ((data >> 4) & 1); patches[0][0].setML ((data >> 0) & 15); unsigned m = isRhythm() ? 6 : 9; for (unsigned i = 0; i < m; ++i) { if ((reg[0x30 + i] & 0xF0) == 0) { Channel& ch = channels[i]; ch.setPatch(0, *this); // TODO optimize unsigned freq = getFreq(i); ch.mod.updatePG (freq); ch.mod.updateRKS(freq); ch.mod.updateEG(); } } break; } case 0x01: { reg[r] = data; patches[0][1].AMPM = (data >> 6) & 3; patches[0][1].EG = (data >> 5) & 1; patches[0][1].setKR ((data >> 4) & 1); patches[0][1].setML ((data >> 0) & 15); unsigned m = isRhythm() ? 6 : 9; for (unsigned i = 0; i < m; ++i) { if ((reg[0x30 + i] & 0xF0) == 0) { Channel& ch = channels[i]; ch.setPatch(0, *this); // TODO optimize unsigned freq = getFreq(i); ch.car.updatePG (freq); ch.car.updateRKS(freq); ch.car.updateEG(); } } break; } case 0x02: { reg[r] = data; patches[0][0].setKL((data >> 6) & 3); patches[0][0].setTL((data >> 0) & 63); unsigned m = isRhythm() ? 6 : 9; for (unsigned i = 0; i < m; ++i) { if ((reg[0x30 + i] & 0xF0) == 0) { Channel& ch = channels[i]; ch.setPatch(0, *this); // TODO optimize bool actAsCarrier = (i >= 7) && isRhythm(); assert(!actAsCarrier); (void)actAsCarrier; ch.mod.updateTLL(getFreq(i), false); } } break; } case 0x03: { reg[r] = data; patches[0][1].setKL((data >> 6) & 3); patches[0][1].setWF((data >> 4) & 1); patches[0][0].setWF((data >> 3) & 1); patches[0][0].setFB((data >> 0) & 7); unsigned m = isRhythm() ? 6 : 9; for (unsigned i = 0; i < m; ++i) { if ((reg[0x30 + i] & 0xF0) == 0) { Channel& ch = channels[i]; ch.setPatch(0, *this); // TODO optimize } } break; } case 0x04: { reg[r] = data; patches[0][0].AR = (data >> 4) & 15; patches[0][0].DR = (data >> 0) & 15; unsigned m = isRhythm() ? 6 : 9; for (unsigned i = 0; i < m; ++i) { if ((reg[0x30 + i] & 0xF0) == 0) { Channel& ch = channels[i]; ch.setPatch(0, *this); // TODO optimize ch.mod.updateEG(); if (ch.mod.state == ATTACK) { ch.mod.setEnvelopeState(ATTACK); } } } break; } case 0x05: { reg[r] = data; patches[0][1].AR = (data >> 4) & 15; patches[0][1].DR = (data >> 0) & 15; unsigned m = isRhythm() ? 6 : 9; for (unsigned i = 0; i < m; ++i) { if ((reg[0x30 + i] & 0xF0) == 0) { Channel& ch = channels[i]; ch.setPatch(0, *this); // TODO optimize ch.car.updateEG(); if (ch.car.state == ATTACK) { ch.car.setEnvelopeState(ATTACK); } } } break; } case 0x06: { reg[r] = data; patches[0][0].setSL((data >> 4) & 15); patches[0][0].RR = (data >> 0) & 15; unsigned m = isRhythm() ? 6 : 9; for (unsigned i = 0; i < m; ++i) { if ((reg[0x30 + i] & 0xF0) == 0) { Channel& ch = channels[i]; ch.setPatch(0, *this); // TODO optimize ch.mod.updateEG(); if (ch.mod.state == DECAY) { ch.mod.setEnvelopeState(DECAY); } } } break; } case 0x07: { reg[r] = data; patches[0][1].setSL((data >> 4) & 15); patches[0][1].RR = (data >> 0) & 15; unsigned m = isRhythm() ? 6 : 9; for (unsigned i = 0; i < m; i++) { if ((reg[0x30 + i] & 0xF0) == 0) { Channel& ch = channels[i]; ch.setPatch(0, *this); // TODO optimize ch.car.updateEG(); if (ch.car.state == DECAY) { ch.car.setEnvelopeState(DECAY); } } } break; } case 0x0E: { byte old = reg[r]; reg[r] = data; setRhythmFlags(old); break; } case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F: r -= 9; // verified on real YM2413 // fall-through case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x18: { reg[r] = data; unsigned cha = r & 0x0F; assert(cha < 9); Channel& ch = channels[cha]; bool actAsCarrier = (cha >= 7) && isRhythm(); unsigned freq = getFreq(cha); ch.mod.updateAll(freq, actAsCarrier); ch.car.updateAll(freq, true); break; } case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: r -= 9; // verified on real YM2413 // fall-through case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: { reg[r] = data; unsigned cha = r & 0x0F; assert(cha < 9); Channel& ch = channels[cha]; bool modActAsCarrier = (cha >= 7) && isRhythm(); ch.setSustain((data >> 5) & 1, modActAsCarrier); if (data & 0x10) { ch.keyOn(); } else { ch.keyOff(); } unsigned freq = getFreq(cha); ch.mod.updateAll(freq, modActAsCarrier); ch.car.updateAll(freq, true); break; } case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: r -= 9; // verified on real YM2413 // fall-through case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37: case 0x38: { reg[r] = data; unsigned cha = r & 0x0F; assert(cha < 9); Channel& ch = channels[cha]; if (isRhythm() && (cha >= 6)) { if (cha > 6) { // channel 7 or 8 in ryhthm mode ch.mod.setVolume(data >> 4); } } else { ch.setPatch(data >> 4, *this); } ch.car.setVolume(data & 15); bool actAsCarrier = (cha >= 7) && isRhythm(); unsigned freq = getFreq(cha); ch.mod.updateAll(freq, actAsCarrier); ch.car.updateAll(freq, true); break; } default: break; } } byte YM2413::peekReg(byte r) const { return reg[r]; } } // namespace YM2413Okazaki static enum_string envelopeStateInfo[] = { { "ATTACK", YM2413Okazaki::ATTACK }, { "DECAY", YM2413Okazaki::DECAY }, { "SUSHOLD", YM2413Okazaki::SUSHOLD }, { "SUSTAIN", YM2413Okazaki::SUSTAIN }, { "RELEASE", YM2413Okazaki::RELEASE }, { "SETTLE", YM2413Okazaki::SETTLE }, { "FINISH", YM2413Okazaki::FINISH } }; SERIALIZE_ENUM(YM2413Okazaki::EnvelopeState, envelopeStateInfo); namespace YM2413Okazaki { // version 1: initial version // version 2: don't serialize "type / actAsCarrier" anymore, it's now // a calculated value // version 3: don't serialize slot_on_flag anymore // version 4: don't serialize volume anymore template void Slot::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("feedback", feedback); ar.serialize("output", output); ar.serialize("cphase", cphase); ar.serialize("state", state); ar.serialize("eg_phase", eg_phase); ar.serialize("sustain", sustain); // These are restored by calls to // updateAll(): eg_dphase, dphaseDRTableRks, tll, dphase // setEnvelopeState(): eg_phase_max // setPatch(): patch // setVolume(): volume // update_key_status(): slot_on_flag } // version 1: initial version // version 2: removed patch_number, freq template void Channel::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("mod", mod); ar.serialize("car", car); } // version 1: initial version // version 2: 'registers' are moved here (no longer serialized in base class) // version 3: no longer serialize 'user_patch_mod' and 'user_patch_car' template void YM2413::serialize(Archive& ar, unsigned version) { if (ar.versionBelow(version, 2)) ar.beginTag("YM2413Core"); ar.serialize("registers", reg); if (ar.versionBelow(version, 2)) ar.endTag("YM2413Core"); // no need to serialize patches[] // patches[0] is restored from registers, the others are read-only ar.serialize("channels", channels); ar.serialize("pm_phase", pm_phase); ar.serialize("am_phase", am_phase); ar.serialize("noise_seed", noise_seed); if (ar.isLoader()) { patches[0][0].initModulator(®[0]); patches[0][1].initCarrier (®[0]); for (int i = 0; i < 9; ++i) { Channel& ch = channels[i]; // restore patch unsigned p = ((i >= 6) && isRhythm()) ? (16 + (i - 6)) : (reg[0x30 + i] >> 4); ch.setPatch(p, *this); // before updateAll() // restore volume ch.car.setVolume(reg[0x30 + i] & 15); if (isRhythm() && (i >= 7)) { // ch 7/8 ryhthm ch.mod.setVolume(reg[0x30 + i] >> 4); } // sync various variables bool actAsCarrier = (i >= 7) && isRhythm(); unsigned freq = getFreq(i); ch.mod.updateAll(freq, actAsCarrier); ch.car.updateAll(freq, true); ch.mod.setEnvelopeState(ch.mod.state); ch.car.setEnvelopeState(ch.car.state); } update_key_status(); } } } // namespace YM2413Okazaki using YM2413Okazaki::YM2413; INSTANTIATE_SERIALIZE_METHODS(YM2413); REGISTER_POLYMORPHIC_INITIALIZER(YM2413Core, YM2413, "YM2413-Okazaki"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/YM2413Okazaki.hh000066400000000000000000000123441257557151200210540ustar00rootroot00000000000000#ifndef YM2413OKAZAKI_HH #define YM2413OKAZAKI_HH #include "YM2413Core.hh" #include "FixedPoint.hh" #include "serialize_meta.hh" namespace openmsx { namespace YM2413Okazaki { // Constants (shared between this class and table generator) #include "YM2413OkazakiConfig.hh" class YM2413; using EnvPhaseIndex = FixedPoint; enum EnvelopeState { ATTACK, DECAY, SUSHOLD, SUSTAIN, RELEASE, SETTLE, FINISH }; class Patch { public: /** Creates an uninitialized Patch object; call initXXX() before use. * This approach makes it possible to create an array of Patches. */ Patch(); void initModulator(const byte* data); void initCarrier (const byte* data); /** Sets the Key Scale of Rate (0 or 1). */ inline void setKR(byte value); /** Sets the frequency multiplier factor [0..15]. */ inline void setML(byte value); /** Sets Key scale level [0..3]. */ inline void setKL(byte value); /** Set volume (total level) [0..63]. */ inline void setTL(byte value); /** Set waveform [0..1]. */ inline void setWF(byte value); /** Sets the amount of feedback [0..7]. */ inline void setFB(byte value); /** Sets sustain level [0..15]. */ inline void setSL(byte value); unsigned* WF; // 0-1 transformed to waveform[0-1] byte* KL; // 0-3 transformed to tllTable[0-3] unsigned SL; // 0-15 transformed to slTable[0-15] byte AMPM; // 0-3 2 packed booleans bool EG; // 0-1 byte KR; // 0-1 transformed to 10,8 byte ML; // 0-15 transformed to mlTable[0-15] byte TL; // 0-63 transformed to TL2EG(0..63) == [0..252] byte FB; // 0,1-7 transformed to 0,7-1 byte AR; // 0-15 byte DR; // 0-15 byte RR; // 0-15 }; class Slot { public: void reset(); inline void setEnvelopeState(EnvelopeState state); inline bool isActive() const; inline void slotOn(); inline void slotOn2(); inline void slotOff(); inline void setPatch(Patch& patch); inline void setVolume(unsigned volume); inline unsigned calc_phase(unsigned lfo_pm); template inline unsigned calc_envelope(int lfo_am, unsigned fixed_env); template unsigned calc_fixed_env() const; void calc_envelope_outline(unsigned& out); template inline int calc_slot_car(unsigned lfo_pm, int lfo_am, int fm, unsigned fixed_env); template inline int calc_slot_mod(unsigned lfo_pm, int lfo_am, unsigned fixed_env); inline int calc_slot_tom(); inline int calc_slot_snare(bool noise); inline int calc_slot_cym(unsigned phase7, unsigned phase8); inline int calc_slot_hat(unsigned phase7, unsigned phase8, bool noise); inline void updatePG(unsigned freq); inline void updateTLL(unsigned freq, bool actAsCarrier); inline void updateRKS(unsigned freq); inline void updateEG(); inline void updateAll(unsigned freq, bool actAsCarrier); template void serialize(Archive& ar, unsigned version); // OUTPUT int feedback; int output; // Output value of slot // for Phase Generator (PG) unsigned cphase; // Phase counter unsigned dphase[8]; // Phase increment // for Envelope Generator (EG) unsigned volume; // Current volume unsigned tll; // Total Level + Key scale level int* dphaseDRTableRks; // (converted to EnvPhaseIndex) EnvelopeState state; // Current state EnvPhaseIndex eg_phase; // Phase EnvPhaseIndex eg_dphase;// Phase increment amount EnvPhaseIndex eg_phase_max; byte slot_on_flag; bool sustain; // Sustain Patch patch; Slot* sibling; // pointer to sibling slot (only valid for car -> mod) }; class Channel { public: Channel(); void reset(YM2413& global); inline void setPatch(unsigned num, YM2413& global); inline void setSustain(bool sustain, bool modActAsCarrier); inline void keyOn(); inline void keyOff(); Slot mod, car; template void serialize(Archive& ar, unsigned version); }; class YM2413 final : public YM2413Core { public: YM2413(); inline void keyOn_BD(); inline void keyOn_SD(); inline void keyOn_TOM(); inline void keyOn_HH(); inline void keyOn_CYM(); inline void keyOff_BD(); inline void keyOff_SD(); inline void keyOff_TOM(); inline void keyOff_HH(); inline void keyOff_CYM(); inline void setRhythmFlags(byte old); inline void update_key_status(); inline bool isRhythm() const; inline unsigned getFreq(unsigned channel) const; Patch& getPatch(unsigned instrument, bool carrier); template inline void calcChannel(Channel& ch, int* buf, unsigned num); template void serialize(Archive& ar, unsigned version); private: // YM2413Core void reset() override; void writeReg(byte reg, byte value) override; byte peekReg(byte reg) const override; void generateChannels(int* bufs[9 + 5], unsigned num) override; int getAmplificationFactor() const override; /** Channel & Slot */ Channel channels[9]; /** Pitch Modulator */ unsigned pm_phase; /** Amp Modulator */ unsigned am_phase; /** Noise Generator */ unsigned noise_seed; /** Voice Data */ Patch patches[19][2]; /** Registers */ byte reg[0x40]; }; } // namespace YM2413Okazaki SERIALIZE_CLASS_VERSION(YM2413Okazaki::Slot, 4); SERIALIZE_CLASS_VERSION(YM2413Okazaki::Channel, 2); SERIALIZE_CLASS_VERSION(YM2413Okazaki::YM2413, 3); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/YM2413OkazakiConfig.hh000066400000000000000000000035331257557151200222020ustar00rootroot00000000000000#ifndef YM2413OKAZAKICONFIG_HH #define YM2413OKAZAKICONFIG_HH // Number of bits in 'PhaseModulation' and 'EnvPhaseIndex' fixed point types. static const int PM_FP_BITS = 8; static const int EP_FP_BITS = 15; // Dynamic range (Accuracy of sin table) static const int DB_BITS = 8; static const int DB_MUTE = 1 << DB_BITS; static const int DBTABLEN = 3 * DB_MUTE; // enough to not have to check for overflow static const float DB_STEP = 48.0 / DB_MUTE; static const float EG_STEP = 0.375; static const float TL_STEP = 0.75; // Size of Sintable ( 8 -- 18 can be used, but 9 recommended.) static const int PG_BITS = 9; static const int PG_WIDTH = 1 << PG_BITS; static const int PG_MASK = PG_WIDTH - 1; // Phase increment counter static const int DP_BITS = 18; static const int DP_BASE_BITS = DP_BITS - PG_BITS; // Dynamic range of envelope static const int EG_BITS = 7; // Bits for linear value static const int DB2LIN_AMP_BITS = 8; static const int SLOT_AMP_BITS = DB2LIN_AMP_BITS; // Bits for Amp modulator static const int AM_PG_BITS = 8; static const int AM_PG_WIDTH = 1 << AM_PG_BITS; static const int AM_DP_BITS = 16; static const int AM_DP_WIDTH = 1 << AM_DP_BITS; static const int AM_DP_MASK = AM_DP_WIDTH - 1; // LFO Amplitude Modulation table (verified on real YM3812) // 27 output levels (triangle waveform); // 1 level takes one of: 192, 256 or 448 samples // // Length: 210 elements. // Each of the elements has to be repeated // exactly 64 times (on 64 consecutive samples). // The whole table takes: 64 * 210 = 13440 samples. // // Verified on real YM3812 (OPL2), but I believe it's the same for YM2413 // because it closely matches the YM2413 AM parameters: // speed = 3.7Hz // depth = 4.875dB // Also this approch can be easily implemented in HW, the previous one (see SVN // history) could not. static const unsigned LFO_AM_TAB_ELEMENTS = 210; #endif openMSX-RELEASE_0_12_0/src/sound/YM2413OkazakiTable.ii000066400000000000000000000574651257557151200220430ustar00rootroot00000000000000// This is a generated file. DO NOT EDIT! // These tables were generated for the following constants: static_assert(PM_FP_BITS == 8, "mismatch, regenerate"); static_assert(EP_FP_BITS == 15, "mismatch, regenerate"); static_assert(DB_BITS == 8, "mismatch, regenerate"); static_assert(DB_MUTE == 256, "mismatch, regenerate"); static_assert(DBTABLEN == 768, "mismatch, regenerate"); //static_assert(DB_STEP == 0.1875, "mismatch, regenerate"); //static_assert(EG_STEP == 0.375, "mismatch, regenerate"); static_assert(PG_BITS == 9, "mismatch, regenerate"); static_assert(PG_WIDTH == 512, "mismatch, regenerate"); static_assert(EG_BITS == 7, "mismatch, regenerate"); static_assert(DB2LIN_AMP_BITS == 8, "mismatch, regenerate"); static_assert(LFO_AM_TAB_ELEMENTS == 210, "mismatch, regenerate"); // LFO Phase Modulation table (copied from Burczynski core) static const signed char pmTable[8][8] = { { 0, 0, 0, 0, 0, 0, 0, 0, }, // FNUM = 000xxxxxx { 0, 0, 1, 0, 0, 0,-1, 0, }, // FNUM = 001xxxxxx { 0, 1, 2, 1, 0,-1,-2,-1, }, // FNUM = 010xxxxxx { 0, 1, 3, 1, 0,-1,-3,-1, }, // FNUM = 011xxxxxx { 0, 2, 4, 2, 0,-2,-4,-2, }, // FNUM = 100xxxxxx { 0, 2, 5, 2, 0,-2,-5,-2, }, // FNUM = 101xxxxxx { 0, 3, 6, 3, 0,-3,-6,-3, }, // FNUM = 110xxxxxx { 0, 3, 7, 3, 0,-3,-7,-3, }, // FNUM = 111xxxxxx }; // dB to linear table (used by Slot) // dB(0 .. DB_MUTE-1) -> linear(0 .. DB2LIN_AMP_WIDTH) // indices in range: // [0, DB_MUTE ) actual values, from max to min // [DB_MUTE, DBTABLEN) filled with min val (to allow some overflow in index) // [DBTABLEN, 2*DBTABLEN) as above but for negative output values static int dB2LinTab[2 * DBTABLEN] = { 255, 249, 244, 239, 233, 228, 224, 219, 214, 209, 205, 201, 196, 192, 188, 184, 180, 176, 172, 169, 165, 162, 158, 155, 151, 148, 145, 142, 139, 136, 133, 130, 127, 125, 122, 119, 117, 114, 112, 109, 107, 105, 102, 100, 98, 96, 94, 92, 90, 88, 86, 84, 82, 81, 79, 77, 76, 74, 72, 71, 69, 68, 66, 65, 64, 62, 61, 60, 58, 57, 56, 55, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 38, 37, 36, 35, 34, 34, 33, 32, 32, 31, 30, 30, 29, 28, 28, 27, 27, 26, 25, 25, 24, 24, 23, 23, 22, 22, 21, 21, 20, 20, 19, 19, 19, 18, 18, 17, 17, 17, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, 12, 12, 12, 12, 11, 11, 11, 11, 10, 10, 10, 10, 10, 9, 9, 9, 9, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -255, -249, -244, -239, -233, -228, -224, -219, -214, -209, -205, -201, -196, -192, -188, -184, -180, -176, -172, -169, -165, -162, -158, -155, -151, -148, -145, -142, -139, -136, -133, -130, -127, -125, -122, -119, -117, -114, -112, -109, -107, -105, -102, -100, -98, -96, -94, -92, -90, -88, -86, -84, -82, -81, -79, -77, -76, -74, -72, -71, -69, -68, -66, -65, -64, -62, -61, -60, -58, -57, -56, -55, -53, -52, -51, -50, -49, -48, -47, -46, -45, -44, -43, -42, -41, -40, -39, -38, -38, -37, -36, -35, -34, -34, -33, -32, -32, -31, -30, -30, -29, -28, -28, -27, -27, -26, -25, -25, -24, -24, -23, -23, -22, -22, -21, -21, -20, -20, -19, -19, -19, -18, -18, -17, -17, -17, -16, -16, -16, -15, -15, -15, -14, -14, -14, -13, -13, -13, -12, -12, -12, -12, -11, -11, -11, -11, -10, -10, -10, -10, -10, -9, -9, -9, -9, -8, -8, -8, -8, -8, -8, -7, -7, -7, -7, -7, -7, -6, -6, -6, -6, -6, -6, -6, -5, -5, -5, -5, -5, -5, -5, -5, -5, -4, -4, -4, -4, -4, -4, -4, -4, -4, -4, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; // Linear to Log curve conversion table (for Attack rate) static unsigned AR_ADJUST_TABLE[1 << EG_BITS] = { 127, 127, 108, 98, 90, 84, 80, 75, 72, 69, 66, 64, 61, 59, 57, 56, 54, 52, 51, 49, 48, 47, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 36, 35, 34, 33, 33, 32, 31, 30, 30, 29, 29, 28, 27, 27, 26, 26, 25, 24, 24, 23, 23, 22, 22, 21, 21, 21, 20, 20, 19, 19, 18, 18, 17, 17, 17, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 9, 9, 8, 8, 8, 7, 7, 7, 7, 6, 6, 6, 6, 5, 5, 5, 4, 4, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, }; // KSL + TL Table values are in range [0, 112] static byte tllTable[4][16 * 8] = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 7, 7, 8, 0, 0, 0, 2, 4, 5, 6, 7, 8, 9, 9, 10, 10, 11, 11, 12, 0, 0, 4, 6, 8, 9, 10, 11, 12, 13, 13, 14, 14, 15, 15, 16, 0, 4, 8, 10, 12, 13, 14, 15, 16, 17, 17, 18, 18, 19, 19, 20, 0, 8, 12, 14, 16, 17, 18, 19, 20, 21, 21, 22, 22, 23, 23, 24, 0, 12, 16, 18, 20, 21, 22, 23, 24, 25, 25, 26, 26, 27, 27, 28 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, 3, 5, 7, 8, 10, 11, 12, 13, 14, 15, 16, 0, 0, 0, 5, 8, 11, 13, 15, 16, 18, 19, 20, 21, 22, 23, 24, 0, 0, 8, 13, 16, 19, 21, 23, 24, 26, 27, 28, 29, 30, 31, 32, 0, 8, 16, 21, 24, 27, 29, 31, 32, 34, 35, 36, 37, 38, 39, 40, 0, 16, 24, 29, 32, 35, 37, 39, 40, 42, 43, 44, 45, 46, 47, 48, 0, 24, 32, 37, 40, 43, 45, 47, 48, 50, 51, 52, 53, 54, 55, 56 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 6, 8, 10, 12, 14, 16, 0, 0, 0, 0, 0, 6, 10, 14, 16, 20, 22, 24, 26, 28, 30, 32, 0, 0, 0, 10, 16, 22, 26, 30, 32, 36, 38, 40, 42, 44, 46, 48, 0, 0, 16, 26, 32, 38, 42, 46, 48, 52, 54, 56, 58, 60, 62, 64, 0, 16, 32, 42, 48, 54, 58, 62, 64, 68, 70, 72, 74, 76, 78, 80, 0, 32, 48, 58, 64, 70, 74, 78, 80, 84, 86, 88, 90, 92, 94, 96, 0, 48, 64, 74, 80, 86, 90, 94, 96,100,102,104,106,108,110,112 }, }; // WaveTable for each envelope amp // values are in range [0, DB_MUTE) (for positive values) // or [0, DB_MUTE) + DBTABLEN (for negative values) static unsigned fullsintable[PG_WIDTH] = { 255, 203, 171, 152, 139, 129, 120, 113, 107, 102, 97, 92, 88, 85, 81, 78, 75, 72, 70, 67, 65, 63, 61, 59, 57, 55, 53, 52, 50, 48, 47, 45, 44, 43, 41, 40, 39, 38, 37, 35, 34, 33, 32, 31, 30, 29, 28, 28, 27, 26, 25, 24, 23, 23, 22, 21, 21, 20, 19, 19, 18, 17, 17, 16, 16, 15, 14, 14, 13, 13, 12, 12, 11, 11, 11, 10, 10, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 10, 10, 11, 11, 11, 12, 12, 13, 13, 14, 14, 15, 16, 16, 17, 17, 18, 19, 19, 20, 21, 21, 22, 23, 23, 24, 25, 26, 27, 28, 28, 29, 30, 31, 32, 33, 34, 35, 37, 38, 39, 40, 41, 43, 44, 45, 47, 48, 50, 52, 53, 55, 57, 59, 61, 63, 65, 67, 70, 72, 75, 78, 81, 85, 88, 92, 97, 102, 107, 113, 120, 129, 139, 152, 171, 203, 255, 1023, 971, 939, 920, 907, 897, 888, 881, 875, 870, 865, 860, 856, 853, 849, 846, 843, 840, 838, 835, 833, 831, 829, 827, 825, 823, 821, 820, 818, 816, 815, 813, 812, 811, 809, 808, 807, 806, 805, 803, 802, 801, 800, 799, 798, 797, 796, 796, 795, 794, 793, 792, 791, 791, 790, 789, 789, 788, 787, 787, 786, 785, 785, 784, 784, 783, 782, 782, 781, 781, 780, 780, 779, 779, 779, 778, 778, 777, 777, 776, 776, 776, 775, 775, 775, 774, 774, 774, 773, 773, 773, 772, 772, 772, 772, 771, 771, 771, 771, 770, 770, 770, 770, 770, 770, 769, 769, 769, 769, 769, 769, 769, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 768, 769, 769, 769, 769, 769, 769, 769, 770, 770, 770, 770, 770, 770, 771, 771, 771, 771, 772, 772, 772, 772, 773, 773, 773, 774, 774, 774, 775, 775, 775, 776, 776, 776, 777, 777, 778, 778, 779, 779, 779, 780, 780, 781, 781, 782, 782, 783, 784, 784, 785, 785, 786, 787, 787, 788, 789, 789, 790, 791, 791, 792, 793, 794, 795, 796, 796, 797, 798, 799, 800, 801, 802, 803, 805, 806, 807, 808, 809, 811, 812, 813, 815, 816, 818, 820, 821, 823, 825, 827, 829, 831, 833, 835, 838, 840, 843, 846, 849, 853, 856, 860, 865, 870, 875, 881, 888, 897, 907, 920, 939, 971, 1023, }; static unsigned halfsintable[PG_WIDTH] = { 255, 203, 171, 152, 139, 129, 120, 113, 107, 102, 97, 92, 88, 85, 81, 78, 75, 72, 70, 67, 65, 63, 61, 59, 57, 55, 53, 52, 50, 48, 47, 45, 44, 43, 41, 40, 39, 38, 37, 35, 34, 33, 32, 31, 30, 29, 28, 28, 27, 26, 25, 24, 23, 23, 22, 21, 21, 20, 19, 19, 18, 17, 17, 16, 16, 15, 14, 14, 13, 13, 12, 12, 11, 11, 11, 10, 10, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 10, 10, 11, 11, 11, 12, 12, 13, 13, 14, 14, 15, 16, 16, 17, 17, 18, 19, 19, 20, 21, 21, 22, 23, 23, 24, 25, 26, 27, 28, 28, 29, 30, 31, 32, 33, 34, 35, 37, 38, 39, 40, 41, 43, 44, 45, 47, 48, 50, 52, 53, 55, 57, 59, 61, 63, 65, 67, 70, 72, 75, 78, 81, 85, 88, 92, 97, 102, 107, 113, 120, 129, 139, 152, 171, 203, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, }; static unsigned* waveform[2] = {fullsintable, halfsintable}; // Phase incr table for attack, decay and release // note: original code had indices swapped. It also had // a separate table for attack // 17.15 fixed point static int dphaseDRTable[16][16] = { { 0, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536 }, { 0, 5, 10, 20, 40, 80, 160, 320, 640, 1280, 2560, 5120, 10240, 20480, 40960, 81920 }, { 0, 6, 12, 24, 48, 96, 192, 384, 768, 1536, 3072, 6144, 12288, 24576, 49152, 98304 }, { 0, 7, 14, 28, 56, 112, 224, 448, 896, 1792, 3584, 7168, 14336, 28672, 57344,114688 }, { 0, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 65536 }, { 0, 10, 20, 40, 80, 160, 320, 640, 1280, 2560, 5120, 10240, 20480, 40960, 81920, 81920 }, { 0, 12, 24, 48, 96, 192, 384, 768, 1536, 3072, 6144, 12288, 24576, 49152, 98304, 98304 }, { 0, 14, 28, 56, 112, 224, 448, 896, 1792, 3584, 7168, 14336, 28672, 57344,114688,114688 }, { 0, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 65536, 65536 }, { 0, 20, 40, 80, 160, 320, 640, 1280, 2560, 5120, 10240, 20480, 40960, 81920, 81920, 81920 }, { 0, 24, 48, 96, 192, 384, 768, 1536, 3072, 6144, 12288, 24576, 49152, 98304, 98304, 98304 }, { 0, 28, 56, 112, 224, 448, 896, 1792, 3584, 7168, 14336, 28672, 57344,114688,114688,114688 }, { 0, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 65536, 65536, 65536 }, { 0, 40, 80, 160, 320, 640, 1280, 2560, 5120, 10240, 20480, 40960, 81920, 81920, 81920, 81920 }, { 0, 48, 96, 192, 384, 768, 1536, 3072, 6144, 12288, 24576, 49152, 98304, 98304, 98304, 98304 }, { 0, 56, 112, 224, 448, 896, 1792, 3584, 7168, 14336, 28672, 57344,114688,114688,114688,114688 }, }; // LFO Amplitude Modulation table (verified on real YM3812) static const unsigned char lfo_am_table[LFO_AM_TAB_ELEMENTS] = { 0,0,0,0,0,0,0, 1,1,1,1, 2,2,2,2, 3,3,3,3, 4,4,4,4, 5,5,5,5, 6,6,6,6, 7,7,7,7, 8,8,8,8, 9,9,9,9, 10,10,10,10, 11,11,11,11, 12,12,12,12, 13,13,13,13, 14,14,14,14, 15,15,15,15, 16,16,16,16, 17,17,17,17, 18,18,18,18, 19,19,19,19, 20,20,20,20, 21,21,21,21, 22,22,22,22, 23,23,23,23, 24,24,24,24, 25,25,25,25, 26,26,26, 25,25,25,25, 24,24,24,24, 23,23,23,23, 22,22,22,22, 21,21,21,21, 20,20,20,20, 19,19,19,19, 18,18,18,18, 17,17,17,17, 16,16,16,16, 15,15,15,15, 14,14,14,14, 13,13,13,13, 12,12,12,12, 11,11,11,11, 10,10,10,10, 9,9,9,9, 8,8,8,8, 7,7,7,7, 6,6,6,6, 5,5,5,5, 4,4,4,4, 3,3,3,3, 2,2,2,2, 1,1,1,1, }; // Sustain level (17.15 fixed point) static const unsigned slTable[16] = { 0, 262144, 524288, 786432, 1048576, 1310720, 1572864, 1835008, 2097152, 2359296, 2621440, 2883584, 3145728, 3407872, 3670016, 4194304, }; // ML-table static const byte mlTable[16] = { 1, 1*2, 2*2, 3*2, 4*2, 5*2, 6*2, 7*2, 8*2, 9*2, 10*2, 10*2, 12*2, 12*2, 15*2, 15*2 }; openMSX-RELEASE_0_12_0/src/sound/YM2413Test.cc000066400000000000000000000117751257557151200203770ustar00rootroot00000000000000#include "YM2413Okazaki.hh" #include "YM2413Burczynski.hh" #include "WavWriter.hh" #include "WavData.hh" #include "Filename.hh" #include "StringOp.hh" #include #include #include using namespace std; using namespace openmsx; // global vars string coreName; string testName; static const unsigned CHANNELS = 11; struct RegWrite { RegWrite(byte reg_, byte val_) : reg(reg_), val(val_) {} byte reg; byte val; }; using RegWrites = vector; struct LogEvent { vector regWrites; unsigned samples; // number of samples between this and next event }; using Log = vector; using Samples = vector; static void error(const string& message) { cout << message << endl; } static void saveWav(const string& filename, const Samples& data) { WavWriter writer(Filename(filename), 1, 16, 3579545 / 72); writer.write16mono(&data[0], data.size()); } static void loadWav(const string& filename, Samples& data) { WavData wav(filename); assert(wav.getFreq() == 3579545 / 72); assert(wav.getBits() == 16); assert(wav.getChannels() == 1); auto rawData = reinterpret_cast(wav.getData()); data.assign(rawData, rawData + wav.getSize()); } static void loadWav(Samples& data) { string filename = coreName + '-' + testName + ".wav"; loadWav(filename, data); } static void createSilence(const Log& log, Samples& result) { unsigned size = 0; for (auto& l : log) { size += l.samples; } result.resize(size); } static void test(YM2413Core& core, const Log& log, const Samples* expectedSamples[CHANNELS]) { cout << " test " << testName << " ..." << endl; Samples generatedSamples[CHANNELS]; for (auto& l : log) { // write registers for (auto& w : l.regWrites) { core.writeReg(w.reg, w.val); } unsigned samples = l.samples; // setup buffers int* bufs[CHANNELS]; unsigned oldSize = generatedSamples[0].size(); for (unsigned i = 0; i < CHANNELS; ++i) { generatedSamples[i].resize(oldSize + samples); bufs[i] = &generatedSamples[i][oldSize]; } // actually generate samples core.generateChannels(bufs, samples); } // amplify generated data // (makes comparison between different cores easier) unsigned factor = core.getAmplificationFactor(); for (unsigned i = 0; i < CHANNELS; ++i) { for (unsigned j = 0; j < generatedSamples[i].size(); ++j) { int s = generatedSamples[i][j]; s *= factor; assert(s == short(s)); // shouldn't overflow 16-bit generatedSamples[i][j] = s; } } // verify generated samples for (unsigned i = 0; i < CHANNELS; ++i) { StringOp::Builder msg; msg << "Error in channel " << i << ": "; bool err = false; if (generatedSamples[i].size() != expectedSamples[i]->size()) { msg << "wrong size, expected " << expectedSamples[i]->size() << " but got " << generatedSamples[i].size(); err = true; } else if (generatedSamples[i] != *expectedSamples[i]) { msg << "Wrong data"; err = true; } if (err) { StringOp::Builder filename; filename << "bad-" << coreName << '-' << testName << "-ch" << i << ".wav"; msg << " writing data to " << std::string(filename); error(msg); saveWav(filename, generatedSamples[i]); } } } static void testSingleChannel(YM2413Core& core, const Log& log, const Samples& channelData, unsigned channelNum) { Samples silence; createSilence(log, silence); const Samples* samples[CHANNELS]; for (unsigned i = 0; i < CHANNELS; ++i) { if (i == channelNum) { samples[i] = &channelData; } else { samples[i] = &silence; } } test(core, log, samples); } static void testSilence(YM2413Core& core) { testName = "silence"; Log log; { LogEvent event; // no register writes event.samples = 1000; log.push_back(event); } Samples silence; createSilence(log, silence); const Samples* samples[CHANNELS]; for (unsigned i = 0; i < CHANNELS; ++i) { samples[i] = &silence; } test(core, log, samples); } static void testViolin(YM2413Core& core) { testName = "violin"; Log log; { LogEvent event; event.regWrites.emplace_back(0x30, 0x10); // instrument / volume event.regWrites.emplace_back(0x10, 0xAD); // frequency event.regWrites.emplace_back(0x20, 0x14); // key-on / frequency event.samples = 11000; log.push_back(event); } { LogEvent event; event.regWrites.emplace_back(0x20, 0x16); // change freq event.samples = 11000; log.push_back(event); } { LogEvent event; event.regWrites.emplace_back(0x20, 0x06); // key-off event.samples = 11000; log.push_back(event); } Samples gold; loadWav(gold); testSingleChannel(core, log, gold, 0); } template void testOnCore(FUNC f) { CORE core; f(core); } template static void testAll(const string& coreName_) { coreName = coreName_; cout << "Testing YM2413 core " << coreName << endl; testOnCore(testSilence); testOnCore(testViolin); cout << endl; } int main() { testAll("Okazaki"); testAll("Burczynski"); return 0; } openMSX-RELEASE_0_12_0/src/sound/YMF262.cc000066400000000000000000001410261257557151200175560ustar00rootroot00000000000000/* * * File: ymf262.c - software implementation of YMF262 * FM sound generator type OPL3 * * Copyright (C) 2003 Jarek Burczynski * * Version 0.2 * * * Revision History: * * 03-03-2003: initial release * - thanks to Olivier Galibert and Chris Hardy for YMF262 and YAC512 chips * - thanks to Stiletto for the datasheets * * * * differences between OPL2 and OPL3 not documented in Yamaha datahasheets: * - sinus table is a little different: the negative part is off by one... * * - in order to enable selection of four different waveforms on OPL2 * one must set bit 5 in register 0x01(test). * on OPL3 this bit is ignored and 4-waveform select works *always*. * (Don't confuse this with OPL3's 8-waveform select.) * * - Envelope Generator: all 15 x rates take zero time on OPL3 * (on OPL2 15 0 and 15 1 rates take some time while 15 2 and 15 3 rates * take zero time) * * - channel calculations: output of operator 1 is in perfect sync with * output of operator 2 on OPL3; on OPL and OPL2 output of operator 1 * is always delayed by one sample compared to output of operator 2 * * * differences between OPL2 and OPL3 shown in datasheets: * - YMF262 does not support CSM mode */ #include "YMF262.hh" #include "DeviceConfig.hh" #include "MSXMotherBoard.hh" #include "Math.hh" #include "outer.hh" #include "serialize.hh" #include #include namespace openmsx { static inline YMF262::FreqIndex fnumToIncrement(unsigned block_fnum) { // opn phase increment counter = 20bit // chip works with 10.10 fixed point, while we use 16.16 unsigned block = (block_fnum & 0x1C00) >> 10; return YMF262::FreqIndex(block_fnum & 0x03FF) >> (11 - block); } // envelope output entries static const int ENV_BITS = 10; static const int ENV_LEN = 1 << ENV_BITS; static const float ENV_STEP = 128.0 / ENV_LEN; static const int MAX_ATT_INDEX = (1 << (ENV_BITS - 1)) - 1; // 511 static const int MIN_ATT_INDEX = 0; // sinwave entries static const int SIN_BITS = 10; static const int SIN_LEN = 1 << SIN_BITS; static const int SIN_MASK = SIN_LEN - 1; static const int TL_RES_LEN = 256; // 8 bits addressing (real chip) // register number to channel number , slot offset static const byte MOD = 0; static const byte CAR = 1; // mapping of register number (offset) to slot number used by the emulator static const int slot_array[32] = { 0, 2, 4, 1, 3, 5, -1, -1, 6, 8, 10, 7, 9, 11, -1, -1, 12, 14, 16, 13, 15, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; // key scale level // table is 3dB/octave , DV converts this into 6dB/octave // 0.1875 is bit 0 weight of the envelope counter (volume) expressed // in the 'decibel' scale #define DV(x) int(x / (0.1875 / 2.0)) static const unsigned ksl_tab[8 * 16] = { // OCT 0 DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), // OCT 1 DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.750), DV( 1.125), DV( 1.500), DV( 1.875), DV( 2.250), DV( 2.625), DV( 3.000), // OCT 2 DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 0.000), DV( 1.125), DV( 1.875), DV( 2.625), DV( 3.000), DV( 3.750), DV( 4.125), DV( 4.500), DV( 4.875), DV( 5.250), DV( 5.625), DV( 6.000), // OCT 3 DV( 0.000), DV( 0.000), DV( 0.000), DV( 1.875), DV( 3.000), DV( 4.125), DV( 4.875), DV( 5.625), DV( 6.000), DV( 6.750), DV( 7.125), DV( 7.500), DV( 7.875), DV( 8.250), DV( 8.625), DV( 9.000), // OCT 4 DV( 0.000), DV( 0.000), DV( 3.000), DV( 4.875), DV( 6.000), DV( 7.125), DV( 7.875), DV( 8.625), DV( 9.000), DV( 9.750), DV(10.125), DV(10.500), DV(10.875), DV(11.250), DV(11.625), DV(12.000), // OCT 5 DV( 0.000), DV( 3.000), DV( 6.000), DV( 7.875), DV( 9.000), DV(10.125), DV(10.875), DV(11.625), DV(12.000), DV(12.750), DV(13.125), DV(13.500), DV(13.875), DV(14.250), DV(14.625), DV(15.000), // OCT 6 DV( 0.000), DV( 6.000), DV( 9.000), DV(10.875), DV(12.000), DV(13.125), DV(13.875), DV(14.625), DV(15.000), DV(15.750), DV(16.125), DV(16.500), DV(16.875), DV(17.250), DV(17.625), DV(18.000), // OCT 7 DV( 0.000), DV( 9.000), DV(12.000), DV(13.875), DV(15.000), DV(16.125), DV(16.875), DV(17.625), DV(18.000), DV(18.750), DV(19.125), DV(19.500), DV(19.875), DV(20.250), DV(20.625), DV(21.000) }; #undef DV // sustain level table (3dB per step) // 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB) #define SC(db) unsigned(db * (2.0f / ENV_STEP)) static const unsigned sl_tab[16] = { SC( 0), SC( 1), SC( 2), SC(3 ), SC(4 ), SC(5 ), SC(6 ), SC( 7), SC( 8), SC( 9), SC(10), SC(11), SC(12), SC(13), SC(14), SC(31) }; #undef SC static const byte RATE_STEPS = 8; static const byte eg_inc[15 * RATE_STEPS] = { //cycle:0 1 2 3 4 5 6 7 0,1, 0,1, 0,1, 0,1, // 0 rates 00..12 0 (increment by 0 or 1) 0,1, 0,1, 1,1, 0,1, // 1 rates 00..12 1 0,1, 1,1, 0,1, 1,1, // 2 rates 00..12 2 0,1, 1,1, 1,1, 1,1, // 3 rates 00..12 3 1,1, 1,1, 1,1, 1,1, // 4 rate 13 0 (increment by 1) 1,1, 1,2, 1,1, 1,2, // 5 rate 13 1 1,2, 1,2, 1,2, 1,2, // 6 rate 13 2 1,2, 2,2, 1,2, 2,2, // 7 rate 13 3 2,2, 2,2, 2,2, 2,2, // 8 rate 14 0 (increment by 2) 2,2, 2,4, 2,2, 2,4, // 9 rate 14 1 2,4, 2,4, 2,4, 2,4, // 10 rate 14 2 2,4, 4,4, 2,4, 4,4, // 11 rate 14 3 4,4, 4,4, 4,4, 4,4, // 12 rates 15 0, 15 1, 15 2, 15 3 for decay 8,8, 8,8, 8,8, 8,8, // 13 rates 15 0, 15 1, 15 2, 15 3 for attack (zero time) 0,0, 0,0, 0,0, 0,0, // 14 infinity rates for attack and decay(s) }; #define O(a) (a * RATE_STEPS) // note that there is no O(13) in this table - it's directly in the code static const byte eg_rate_select[16 + 64 + 16] = { // Envelope Generator rates (16 + 64 rates + 16 RKS) // 16 infinite time rates O(14), O(14), O(14), O(14), O(14), O(14), O(14), O(14), O(14), O(14), O(14), O(14), O(14), O(14), O(14), O(14), // rates 00-12 O( 0), O( 1), O( 2), O( 3), O( 0), O( 1), O( 2), O( 3), O( 0), O( 1), O( 2), O( 3), O( 0), O( 1), O( 2), O( 3), O( 0), O( 1), O( 2), O( 3), O( 0), O( 1), O( 2), O( 3), O( 0), O( 1), O( 2), O( 3), O( 0), O( 1), O( 2), O( 3), O( 0), O( 1), O( 2), O( 3), O( 0), O( 1), O( 2), O( 3), O( 0), O( 1), O( 2), O( 3), O( 0), O( 1), O( 2), O( 3), O( 0), O( 1), O( 2), O( 3), // rate 13 O( 4), O( 5), O( 6), O( 7), // rate 14 O( 8), O( 9), O(10), O(11), // rate 15 O(12), O(12), O(12), O(12), // 16 dummy rates (same as 15 3) O(12), O(12), O(12), O(12), O(12), O(12), O(12), O(12), O(12), O(12), O(12), O(12), O(12), O(12), O(12), O(12), }; #undef O // rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 // shift 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0 // mask 4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0 #define O(a) (a * 1) static const byte eg_rate_shift[16 + 64 + 16] = { // Envelope Generator counter shifts (16 + 64 rates + 16 RKS) // 16 infinite time rates O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), // rates 00-15 O(12), O(12), O(12), O(12), O(11), O(11), O(11), O(11), O(10), O(10), O(10), O(10), O( 9), O( 9), O( 9), O( 9), O( 8), O( 8), O( 8), O( 8), O( 7), O( 7), O( 7), O( 7), O( 6), O( 6), O( 6), O( 6), O( 5), O( 5), O( 5), O( 5), O( 4), O( 4), O( 4), O( 4), O( 3), O( 3), O( 3), O( 3), O( 2), O( 2), O( 2), O( 2), O( 1), O( 1), O( 1), O( 1), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), // 16 dummy rates (same as 15 3) O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), O( 0), }; #undef O // multiple table #define ML(x) byte(2 * x) static const byte mul_tab[16] = { // 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,10,12,12,15,15 ML( 0.5), ML( 1.0), ML( 2.0), ML( 3.0), ML( 4.0), ML( 5.0), ML( 6.0), ML( 7.0), ML( 8.0), ML( 9.0), ML(10.0), ML(10.0), ML(12.0), ML(12.0), ML(15.0), ML(15.0) }; #undef ML // TL_TAB_LEN is calculated as: // (12+1)=13 - sinus amplitude bits (Y axis) // additional 1: to compensate for calculations of negative part of waveform // (if we don't add it then the greatest possible _negative_ value would be -2 // and we really need -1 for waveform #7) // 2 - sinus sign bit (Y axis) // TL_RES_LEN - sinus resolution (X axis) static const int TL_TAB_LEN = 13 * 2 * TL_RES_LEN; static int tl_tab[TL_TAB_LEN]; static const int ENV_QUIET = TL_TAB_LEN >> 4; // sin waveform table in 'decibel' scale // there are eight waveforms on OPL3 chips static unsigned sin_tab[SIN_LEN * 8]; // LFO Amplitude Modulation table (verified on real YM3812) // 27 output levels (triangle waveform); 1 level takes one of: 192, 256 or 448 samples // // Length: 210 elements // // Each of the elements has to be repeated // exactly 64 times (on 64 consecutive samples). // The whole table takes: 64 * 210 = 13440 samples. // // When AM = 1 data is used directly // When AM = 0 data is divided by 4 before being used (loosing precision is important) static const unsigned LFO_AM_TAB_ELEMENTS = 210; static const byte lfo_am_table[LFO_AM_TAB_ELEMENTS] = { 0, 0, 0, /**/ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, /**/ 25, 25, 25, 25, 24, 24, 24, 24, 23, 23, 23, 23, 22, 22, 22, 22, 21, 21, 21, 21, 20, 20, 20, 20, 19, 19, 19, 19, 18, 18, 18, 18, 17, 17, 17, 17, 16, 16, 16, 16, 15, 15, 15, 15, 14, 14, 14, 14, 13, 13, 13, 13, 12, 12, 12, 12, 11, 11, 11, 11, 10, 10, 10, 10, 9, 9, 9, 9, 8, 8, 8, 8, 7, 7, 7, 7, 6, 6, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1 }; // LFO Phase Modulation table (verified on real YM3812) static const signed char lfo_pm_table[8 * 8 * 2] = { // FNUM2/FNUM = 00 0xxxxxxx (0x0000) 0, 0, 0, 0, 0, 0, 0, 0, // LFO PM depth = 0 0, 0, 0, 0, 0, 0, 0, 0, // LFO PM depth = 1 // FNUM2/FNUM = 00 1xxxxxxx (0x0080) 0, 0, 0, 0, 0, 0, 0, 0, // LFO PM depth = 0 1, 0, 0, 0,-1, 0, 0, 0, // LFO PM depth = 1 // FNUM2/FNUM = 01 0xxxxxxx (0x0100) 1, 0, 0, 0,-1, 0, 0, 0, // LFO PM depth = 0 2, 1, 0,-1,-2,-1, 0, 1, // LFO PM depth = 1 // FNUM2/FNUM = 01 1xxxxxxx (0x0180) 1, 0, 0, 0,-1, 0, 0, 0, // LFO PM depth = 0 3, 1, 0,-1,-3,-1, 0, 1, // LFO PM depth = 1 // FNUM2/FNUM = 10 0xxxxxxx (0x0200) 2, 1, 0,-1,-2,-1, 0, 1, // LFO PM depth = 0 4, 2, 0,-2,-4,-2, 0, 2, // LFO PM depth = 1 // FNUM2/FNUM = 10 1xxxxxxx (0x0280) 2, 1, 0,-1,-2,-1, 0, 1, // LFO PM depth = 0 5, 2, 0,-2,-5,-2, 0, 2, // LFO PM depth = 1 // FNUM2/FNUM = 11 0xxxxxxx (0x0300) 3, 1, 0,-1,-3,-1, 0, 1, // LFO PM depth = 0 6, 3, 0,-3,-6,-3, 0, 3, // LFO PM depth = 1 // FNUM2/FNUM = 11 1xxxxxxx (0x0380) 3, 1, 0,-1,-3,-1, 0, 1, // LFO PM depth = 0 7, 3, 0,-3,-7,-3, 0, 3 // LFO PM depth = 1 }; // TODO clean this up static int phase_modulation; // phase modulation input (SLOT 2) static int phase_modulation2; // phase modulation input (SLOT 3 // in 4 operator channels) YMF262::Slot::Slot() : Cnt(0), Incr(0) { ar = dr = rr = KSR = ksl = ksr = mul = 0; fb_shift = op1_out[0] = op1_out[1] = 0; CON = eg_type = vib = false; connect = nullptr; TL = TLL = volume = sl = 0; state = EG_OFF; eg_m_ar = eg_sh_ar = eg_sel_ar = eg_m_dr = eg_sh_dr = 0; eg_sel_dr = eg_m_rr = eg_sh_rr = eg_sel_rr = 0; key = AMmask = 0; wavetable = &sin_tab[0 * SIN_LEN]; } YMF262::Channel::Channel() { block_fnum = ksl_base = kcode = 0; extended = false; fc = FreqIndex(0); } void YMF262::callback(byte flag) { setStatus(flag); } // status set and IRQ handling void YMF262::setStatus(byte flag) { // set status flag masking out disabled IRQs status |= flag; if (status & statusMask) { status |= 0x80; irq.set(); } } // status reset and IRQ handling void YMF262::resetStatus(byte flag) { // reset status flag status &= ~flag; if (!(status & statusMask)) { status &= 0x7F; irq.reset(); } } // IRQ mask set void YMF262::changeStatusMask(byte flag) { statusMask = flag; status &= statusMask; if (status) { status |= 0x80; irq.set(); } else { status &= 0x7F; irq.reset(); } } void YMF262::Slot::advanceEnvelopeGenerator(unsigned eg_cnt) { switch (state) { case EG_ATTACK: if (!(eg_cnt & eg_m_ar)) { volume += (~volume * eg_inc[eg_sel_ar + ((eg_cnt >> eg_sh_ar) & 7)]) >> 3; if (volume <= MIN_ATT_INDEX) { volume = MIN_ATT_INDEX; state = EG_DECAY; } } break; case EG_DECAY: if (!(eg_cnt & eg_m_dr)) { volume += eg_inc[eg_sel_dr + ((eg_cnt >> eg_sh_dr) & 7)]; if (volume >= sl) { state = EG_SUSTAIN; } } break; case EG_SUSTAIN: // this is important behaviour: // one can change percusive/non-percussive // modes on the fly and the chip will remain // in sustain phase - verified on real YM3812 if (eg_type) { // non-percussive mode // do nothing } else { // percussive mode // during sustain phase chip adds Release Rate (in percussive mode) if (!(eg_cnt & eg_m_rr)) { volume += eg_inc[eg_sel_rr + ((eg_cnt >> eg_sh_rr) & 7)]; if (volume >= MAX_ATT_INDEX) { volume = MAX_ATT_INDEX; } } else { // do nothing in sustain phase } } break; case EG_RELEASE: if (!(eg_cnt & eg_m_rr)) { volume += eg_inc[eg_sel_rr + ((eg_cnt >> eg_sh_rr) & 7)]; if (volume >= MAX_ATT_INDEX) { volume = MAX_ATT_INDEX; state = EG_OFF; } } break; default: break; } } void YMF262::Slot::advancePhaseGenerator(Channel& ch, unsigned lfo_pm) { if (vib) { // LFO phase modulation active unsigned block_fnum = ch.block_fnum; unsigned fnum_lfo = (block_fnum & 0x0380) >> 7; int lfo_fn_table_index_offset = lfo_pm_table[lfo_pm + 16 * fnum_lfo]; Cnt += fnumToIncrement(block_fnum + lfo_fn_table_index_offset) * mul; } else { // LFO phase modulation disabled for this operator Cnt += Incr; } } // advance to next sample void YMF262::advance() { // Vibrato: 8 output levels (triangle waveform); // 1 level takes 1024 samples lfo_pm_cnt.addQuantum(); unsigned lfo_pm = (lfo_pm_cnt.toInt() & 7) | lfo_pm_depth_range; ++eg_cnt; for (auto& ch : channel) { for (int s = 0; s < 2; ++s) { auto& op = ch.slot[s]; op.advanceEnvelopeGenerator(eg_cnt); op.advancePhaseGenerator(ch, lfo_pm); } } // The Noise Generator of the YM3812 is 23-bit shift register. // Period is equal to 2^23-2 samples. // Register works at sampling frequency of the chip, so output // can change on every sample. // // Output of the register and input to the bit 22 is: // bit0 XOR bit14 XOR bit15 XOR bit22 // // Simply use bit 22 as the noise output. // // unsigned j = ((noise_rng >> 0) ^ (noise_rng >> 14) ^ // (noise_rng >> 15) ^ (noise_rng >> 22)) & 1; // noise_rng = (j << 22) | (noise_rng >> 1); // // Instead of doing all the logic operations above, we // use a trick here (and use bit 0 as the noise output). // The difference is only that the noise bit changes one // step ahead. This doesn't matter since we don't know // what is real state of the noise_rng after the reset. if (noise_rng & 1) { noise_rng ^= 0x800302; } noise_rng >>= 1; } inline int YMF262::Slot::op_calc(unsigned phase, unsigned lfo_am) const { unsigned env = (TLL + volume + (lfo_am & AMmask)) << 4; int p = env + wavetable[phase & SIN_MASK]; return (p < TL_TAB_LEN) ? tl_tab[p] : 0; } // calculate output of a standard 2 operator channel // (or 1st part of a 4-op channel) void YMF262::Channel::chan_calc(unsigned lfo_am) { // !! something is wrong with this, it caused bug // !! [2823673] moonsound 4 operator FM fail // !! optimization disabled for now // !! TODO investigate // !! maybe this micro optimization isn't worth the trouble/risk // !! // - mod.connect can point to 'phase_modulation' or 'ch0-output' // - car.connect can point to 'phase_modulation2' or 'ch0-output' // (see register #C0-#C8 writes) // - phase_modulation2 is only used in 4op mode // - mod.connect and car.connect can point to the same thing, so we need // an addition for car.connect (and initialize phase_modulation2 to // zero). For mod.connect we can directly assign the value. // ?? is this paragraph correct ?? // phase_modulation should be initialized to zero here. But there seems // to be an optimization bug in gcc-4.2: it *seems* that when we // initialize phase_modulation to zero in this function, the optimizer // assumes it still has value zero at the end of this function (where // it's used to calculate car.connect). As a workaround we initialize // phase_modulation each time before calling this function. phase_modulation = 0; phase_modulation2 = 0; auto& mod = slot[MOD]; int out = mod.fb_shift ? mod.op1_out[0] + mod.op1_out[1] : 0; mod.op1_out[0] = mod.op1_out[1]; mod.op1_out[1] = mod.op_calc(mod.Cnt.toInt() + (out >> mod.fb_shift), lfo_am); *mod.connect += mod.op1_out[1]; auto& car = slot[CAR]; *car.connect += car.op_calc(car.Cnt.toInt() + phase_modulation, lfo_am); } // calculate output of a 2nd part of 4-op channel void YMF262::Channel::chan_calc_ext(unsigned lfo_am) { // !! see remark in chan_cal(), something is wrong with this // !! optimization disabled for now // !! // - mod.connect can point to 'phase_modulation' or 'ch3-output' // - car.connect always points to 'ch3-output' (always 4op-mode) // (see register #C0-#C8 writes) // - mod.connect and car.connect can point to the same thing, so we need // an addition for car.connect. For mod.connect we can directly assign // the value. phase_modulation = 0; auto& mod = slot[MOD]; *mod.connect += mod.op_calc(mod.Cnt.toInt() + phase_modulation2, lfo_am); auto& car = slot[CAR]; *car.connect += car.op_calc(car.Cnt.toInt() + phase_modulation, lfo_am); } // operators used in the rhythm sounds generation process: // // Envelope Generator: // // channel operator register number Bass High Snare Tom Top // / slot number TL ARDR SLRR Wave Drum Hat Drum Tom Cymbal // 6 / 0 12 50 70 90 f0 + // 6 / 1 15 53 73 93 f3 + // 7 / 0 13 51 71 91 f1 + // 7 / 1 16 54 74 94 f4 + // 8 / 0 14 52 72 92 f2 + // 8 / 1 17 55 75 95 f5 + // // Phase Generator: // // channel operator register number Bass High Snare Tom Top // / slot number MULTIPLE Drum Hat Drum Tom Cymbal // 6 / 0 12 30 + // 6 / 1 15 33 + // 7 / 0 13 31 + + + // 7 / 1 16 34 ----- n o t u s e d ----- // 8 / 0 14 32 + // 8 / 1 17 35 + + // // channel operator register number Bass High Snare Tom Top // number number BLK/FNUM2 FNUM Drum Hat Drum Tom Cymbal // 6 12,15 B6 A6 + // // 7 13,16 B7 A7 + + + // // 8 14,17 B8 A8 + + + // The following formulas can be well optimized. // I leave them in direct form for now (in case I've missed something). inline int YMF262::genPhaseHighHat() { // high hat phase generation (verified on real YM3812): // phase = d0 or 234 (based on frequency only) // phase = 34 or 2d0 (based on noise) // base frequency derived from operator 1 in channel 7 int op71phase = channel[7].slot[MOD].Cnt.toInt(); bool bit7 = (op71phase & 0x80) != 0; bool bit3 = (op71phase & 0x08) != 0; bool bit2 = (op71phase & 0x04) != 0; bool res1 = (bit2 ^ bit7) | bit3; // when res1 = 0 phase = 0x000 | 0xd0; // when res1 = 1 phase = 0x200 | (0xd0>>2); unsigned phase = res1 ? (0x200 | (0xd0 >> 2)) : 0xd0; // enable gate based on frequency of operator 2 in channel 8 int op82phase = channel[8].slot[CAR].Cnt.toInt(); bool bit5e= (op82phase & 0x20) != 0; bool bit3e= (op82phase & 0x08) != 0; bool res2 = (bit3e ^ bit5e); // when res2 = 0 pass the phase from calculation above (res1); // when res2 = 1 phase = 0x200 | (0xd0>>2); if (res2) { phase = (0x200 | (0xd0 >> 2)); } // when phase & 0x200 is set and noise=1 then phase = 0x200|0xd0 // when phase & 0x200 is set and noise=0 then phase = 0x200|(0xd0>>2), ie no change if (phase & 0x200) { if (noise_rng & 1) { phase = 0x200 | 0xd0; } } else { // when phase & 0x200 is clear and noise=1 then phase = 0xd0>>2 // when phase & 0x200 is clear and noise=0 then phase = 0xd0, ie no change if (noise_rng & 1) { phase = 0xd0 >> 2; } } return phase; } inline int YMF262::genPhaseSnare() { // verified on real YM3812 // base frequency derived from operator 1 in channel 7 // noise bit XOR'es phase by 0x100 return ((channel[7].slot[MOD].Cnt.toInt() & 0x100) + 0x100) ^ ((noise_rng & 1) << 8); } inline int YMF262::genPhaseCymbal() { // verified on real YM3812 // enable gate based on frequency of operator 2 in channel 8 // NOTE: YM2413_2 uses bit5 | bit3, this core uses bit5 ^ bit3 // most likely only one of the two is correct int op82phase = channel[8].slot[CAR].Cnt.toInt(); if ((op82phase ^ (op82phase << 2)) & 0x20) { // bit5 ^ bit3 return 0x300; } else { // base frequency derived from operator 1 in channel 7 int op71phase = channel[7].slot[MOD].Cnt.toInt(); bool bit7 = (op71phase & 0x80) != 0; bool bit3 = (op71phase & 0x08) != 0; bool bit2 = (op71phase & 0x04) != 0; return ((bit2 != bit7) || bit3) ? 0x300 : 0x100; } } // calculate rhythm void YMF262::chan_calc_rhythm(unsigned lfo_am) { // Bass Drum (verified on real YM3812): // - depends on the channel 6 'connect' register: // when connect = 0 it works the same as in normal (non-rhythm) // mode (op1->op2->out) // when connect = 1 _only_ operator 2 is present on output // (op2->out), operator 1 is ignored // - output sample always is multiplied by 2 auto& mod6 = channel[6].slot[MOD]; int out = mod6.fb_shift ? mod6.op1_out[0] + mod6.op1_out[1] : 0; mod6.op1_out[0] = mod6.op1_out[1]; int pm = mod6.CON ? 0 : mod6.op1_out[0]; mod6.op1_out[1] = mod6.op_calc(mod6.Cnt.toInt() + (out >> mod6.fb_shift), lfo_am); auto& car6 = channel[6].slot[CAR]; chanout[6] += 2 * car6.op_calc(car6.Cnt.toInt() + pm, lfo_am); // Phase generation is based on: // HH (13) channel 7->slot 1 combined with channel 8->slot 2 // (same combination as TOP CYMBAL but different output phases) // SD (16) channel 7->slot 1 // TOM (14) channel 8->slot 1 // TOP (17) channel 7->slot 1 combined with channel 8->slot 2 // (same combination as HIGH HAT but different output phases) // // Envelope generation based on: // HH channel 7->slot1 // SD channel 7->slot2 // TOM channel 8->slot1 // TOP channel 8->slot2 auto& mod7 = channel[7].slot[MOD]; chanout[7] += 2 * mod7.op_calc(genPhaseHighHat(), lfo_am); auto& car7 = channel[7].slot[CAR]; chanout[7] += 2 * car7.op_calc(genPhaseSnare(), lfo_am); auto& mod8 = channel[8].slot[MOD]; chanout[8] += 2 * mod8.op_calc(mod8.Cnt.toInt(), lfo_am); auto& car8 = channel[8].slot[CAR]; chanout[8] += 2 * car8.op_calc(genPhaseCymbal(), lfo_am); } // generic table initialize void YMF262::init_tables() { static bool alreadyInit = false; if (alreadyInit) return; alreadyInit = true; for (int x = 0; x < TL_RES_LEN; x++) { float m = (1 << 16) / exp2f((x + 1) * (ENV_STEP / 4.0f) / 8.0f); m = floorf(m); // we never reach (1<<16) here due to the (x+1) // result fits within 16 bits at maximum int n = int(m); // 16 bits here n >>= 4; // 12 bits here n = (n >> 1) + (n & 1); // round to nearest // 11 bits here (rounded) n <<= 1; // 12 bits here (as in real chip) tl_tab[x * 2 + 0] = n; tl_tab[x * 2 + 1] = ~tl_tab[x * 2 + 0]; // this _is_ different from OPL2 (verified on real YMF262) for (int i = 1; i < 13; i++) { tl_tab[x * 2 + 0 + i * 2 * TL_RES_LEN] = tl_tab[x * 2 + 0] >> i; tl_tab[x * 2 + 1 + i * 2 * TL_RES_LEN] = ~tl_tab[x * 2 + 0 + i * 2 * TL_RES_LEN]; // this _is_ different from OPL2 (verified on real YMF262) } } static const float LOG2 = log(2.0); for (int i = 0; i < SIN_LEN; i++) { // non-standard sinus float m = sinf(((i * 2) + 1) * M_PI / SIN_LEN); // checked against the real chip // we never reach zero here due to ((i * 2) + 1) float o = -8.0f * logf(std::abs(m)) / LOG2; // convert to 'decibels' o = o / (ENV_STEP / 4); int n = int(2 * o); n = (n >> 1) + (n & 1); // round to nearest sin_tab[i] = n * 2 + (m >= 0.0f ? 0 : 1); } for (int i = 0; i < SIN_LEN; ++i) { // these 'pictures' represent _two_ cycles // waveform 1: __ __ // / \____/ \____ // output only first half of the sinus waveform (positive one) sin_tab[1 * SIN_LEN + i] = (i & (1 << (SIN_BITS - 1))) ? TL_TAB_LEN : sin_tab[i]; // waveform 2: __ __ __ __ // / \/ \/ \/ \. // abs(sin) sin_tab[2 * SIN_LEN + i] = sin_tab[i & (SIN_MASK >> 1)]; // waveform 3: _ _ _ _ // / |_/ |_/ |_/ |_ // abs(output only first quarter of the sinus waveform) sin_tab[3 * SIN_LEN + i] = (i & (1 << (SIN_BITS - 2))) ? TL_TAB_LEN : sin_tab[i & (SIN_MASK>>2)]; // waveform 4: /\ ____/\ ____ // \/ \/ // output whole sinus waveform in half the cycle(step=2) // and output 0 on the other half of cycle sin_tab[4 * SIN_LEN + i] = (i & (1 << (SIN_BITS - 1))) ? TL_TAB_LEN : sin_tab[i * 2]; // waveform 5: /\/\____/\/\____ // // output abs(whole sinus) waveform in half the cycle(step=2) // and output 0 on the other half of cycle sin_tab[5 * SIN_LEN + i] = (i & (1 << (SIN_BITS - 1))) ? TL_TAB_LEN : sin_tab[(i * 2) & (SIN_MASK >> 1)]; // waveform 6: ____ ____ // ____ ____ // output maximum in half the cycle and output minimum // on the other half of cycle sin_tab[6 * SIN_LEN + i] = (i & (1 << (SIN_BITS - 1))) ? 1 // negative : 0; // positive // waveform 7:|\____ |\____ // \| \| // output sawtooth waveform int x = (i & (1 << (SIN_BITS - 1))) ? ((SIN_LEN - 1) - i) * 16 + 1 // negative: from 8177 to 1 : i * 16; // positive: from 0 to 8176 x = std::min(x, TL_TAB_LEN); // clip to the allowed range sin_tab[7 * SIN_LEN + i] = x; } } void YMF262::Slot::FM_KEYON(byte key_set) { if (!key) { // restart Phase Generator Cnt = FreqIndex(0); // phase -> Attack state = EG_ATTACK; } key |= key_set; } void YMF262::Slot::FM_KEYOFF(byte key_clr) { if (key) { key &= ~key_clr; if (!key) { // phase -> Release if (state != EG_OFF) { state = EG_RELEASE; } } } } void YMF262::Slot::update_ar_dr() { if ((ar + ksr) < 16 + 60) { // verified on real YMF262 - all 15 x rates take "zero" time eg_sh_ar = eg_rate_shift [ar + ksr]; eg_sel_ar = eg_rate_select[ar + ksr]; } else { eg_sh_ar = 0; eg_sel_ar = 13 * RATE_STEPS; } eg_m_ar = (1 << eg_sh_ar) - 1; eg_sh_dr = eg_rate_shift [dr + ksr]; eg_sel_dr = eg_rate_select[dr + ksr]; eg_m_dr = (1 << eg_sh_dr) - 1; } void YMF262::Slot::update_rr() { eg_sh_rr = eg_rate_shift [rr + ksr]; eg_sel_rr = eg_rate_select[rr + ksr]; eg_m_rr = (1 << eg_sh_rr) - 1; } // update phase increment counter of operator (also update the EG rates if necessary) void YMF262::Slot::calc_fc(const Channel& ch) { // (frequency) phase increment counter Incr = ch.fc * mul; int newKsr = ch.kcode >> KSR; if (ksr == newKsr) return; ksr = newKsr; // calculate envelope generator rates update_ar_dr(); update_rr(); } static const unsigned channelPairTab[18] = { 0, 1, 2, 0, 1, 2, unsigned(~0), unsigned(~0), unsigned(~0), 9, 10, 11, 9, 10, 11, unsigned(~0), unsigned(~0), unsigned(~0), }; inline bool YMF262::isExtended(unsigned ch) const { assert(ch < 18); if (!OPL3_mode) return false; if (channelPairTab[ch] == unsigned(~0)) return false; return channel[channelPairTab[ch]].extended; } static inline unsigned getFirstOfPairNum(unsigned ch) { assert((ch < 18) && (channelPairTab[ch] != unsigned(~0))); return channelPairTab[ch]; } inline YMF262::Channel& YMF262::getFirstOfPair(unsigned ch) { return channel[getFirstOfPairNum(ch) + 0]; } inline YMF262::Channel& YMF262::getSecondOfPair(unsigned ch) { return channel[getFirstOfPairNum(ch) + 3]; } // set multi,am,vib,EG-TYP,KSR,mul void YMF262::set_mul(unsigned sl, byte v) { unsigned chan_no = sl / 2; auto& ch = channel[chan_no]; auto& slot = ch.slot[sl & 1]; slot.mul = mul_tab[v & 0x0f]; slot.KSR = (v & 0x10) ? 0 : 2; slot.eg_type = (v & 0x20) != 0; slot.vib = (v & 0x40) != 0; slot.AMmask = (v & 0x80) ? ~0 : 0; if (isExtended(chan_no)) { // 4op mode // update this slot using frequency data for 1st channel of a pair slot.calc_fc(getFirstOfPair(chan_no)); } else { // normal (OPL2 mode or 2op mode) slot.calc_fc(ch); } } // set ksl & tl void YMF262::set_ksl_tl(unsigned sl, byte v) { unsigned chan_no = sl / 2; auto& ch = channel[chan_no]; auto& slot = ch.slot[sl & 1]; // This is indeed {0.0, 3.0, 1.5, 6.0} dB/oct, verified on real YMF262. // Note the illogical order of 2nd and 3rd element. static const unsigned ksl_shift[4] = { 31, 1, 2, 0 }; slot.ksl = ksl_shift[v >> 6]; slot.TL = (v & 0x3F) << (ENV_BITS - 1 - 7); // 7 bits TL (bit 6 = always 0) if (isExtended(chan_no)) { // update this slot using frequency data for 1st channel of a pair auto& ch0 = getFirstOfPair(chan_no); slot.TLL = slot.TL + (ch0.ksl_base >> slot.ksl); } else { // normal slot.TLL = slot.TL + (ch.ksl_base >> slot.ksl); } } // set attack rate & decay rate void YMF262::set_ar_dr(unsigned sl, byte v) { auto& ch = channel[sl / 2]; auto& slot = ch.slot[sl & 1]; slot.ar = (v >> 4) ? 16 + ((v >> 4) << 2) : 0; slot.dr = (v & 0x0F) ? 16 + ((v & 0x0F) << 2) : 0; slot.update_ar_dr(); } // set sustain level & release rate void YMF262::set_sl_rr(unsigned sl, byte v) { auto& ch = channel[sl / 2]; auto& slot = ch.slot[sl & 1]; slot.sl = sl_tab[v >> 4]; slot.rr = (v & 0x0F) ? 16 + ((v & 0x0F) << 2) : 0; slot.update_rr(); } byte YMF262::readReg(unsigned r) { // no need to call updateStream(time) return peekReg(r); } byte YMF262::peekReg(unsigned r) const { return reg[r]; } void YMF262::writeReg(unsigned r, byte v, EmuTime::param time) { if (!OPL3_mode && (r != 0x105)) { // in OPL2 mode the only accessible in set #2 is register 0x05 r &= ~0x100; } writeReg512(r, v, time); } void YMF262::writeReg512(unsigned r, byte v, EmuTime::param time) { updateStream(time); // TODO optimize only for regs that directly influence sound writeRegDirect(r, v, time); } void YMF262::writeRegDirect(unsigned r, byte v, EmuTime::param time) { reg[r] = v; switch (r) { case 0x104: // 6 channels enable channel[ 0].extended = (v & 0x01) != 0; channel[ 1].extended = (v & 0x02) != 0; channel[ 2].extended = (v & 0x04) != 0; channel[ 9].extended = (v & 0x08) != 0; channel[10].extended = (v & 0x10) != 0; channel[11].extended = (v & 0x20) != 0; return; case 0x105: // OPL3 mode when bit0=1 otherwise it is OPL2 mode OPL3_mode = v & 0x01; // When NEW2 bit is first set, a read from the status register // (once) returns bit 1 set (0x02). This only happens once after // reset, so clearing NEW2 and setting it again doesn't cause // another change in the status register. // This seems strange behaviour to me, but it is what I saw on // a real YMF278. Also see page 10 in the 'OPL4 YMF278B // Application Manual' (though it's not clear on the details). if ((v & 0x02) && !alreadySignaledNEW2 && isYMF278) { status2 = 0x02; alreadySignaledNEW2 = true; } // following behaviour was tested on real YMF262, // switching OPL3/OPL2 modes on the fly: // - does not change the waveform previously selected // (unless when ....) // - does not update CH.A, CH.B, CH.C and CH.D output // selectors (registers c0-c8) (unless when ....) // - does not disable channels 9-17 on OPL3->OPL2 switch // - does not switch 4 operator channels back to 2 // operator channels return; } unsigned ch_offset = (r & 0x100) ? 9 : 0; switch (r & 0xE0) { case 0x00: // 00-1F:control switch (r & 0x1F) { case 0x01: // test register break; case 0x02: // Timer 1 timer1->setValue(v); break; case 0x03: // Timer 2 timer2->setValue(v); break; case 0x04: // IRQ clear / mask and Timer enable if (v & 0x80) { // IRQ flags clear resetStatus(0x60); } else { changeStatusMask((~v) & 0x60); timer1->setStart((v & R04_ST1) != 0, time); timer2->setStart((v & R04_ST2) != 0, time); } break; case 0x08: // x,NTS,x,x, x,x,x,x nts = (v & 0x40) != 0; break; default: break; } break; case 0x20: { // am ON, vib ON, ksr, eg_type, mul int slot = slot_array[r & 0x1F]; if (slot < 0) return; set_mul(slot + ch_offset * 2, v); break; } case 0x40: { int slot = slot_array[r & 0x1F]; if (slot < 0) return; set_ksl_tl(slot + ch_offset * 2, v); break; } case 0x60: { int slot = slot_array[r & 0x1F]; if (slot < 0) return; set_ar_dr(slot + ch_offset * 2, v); break; } case 0x80: { int slot = slot_array[r & 0x1F]; if (slot < 0) return; set_sl_rr(slot + ch_offset * 2, v); break; } case 0xA0: { // note: not r != 0x1BD, only first register block if (r == 0xBD) { // am depth, vibrato depth, r,bd,sd,tom,tc,hh lfo_am_depth = (v & 0x80) != 0; lfo_pm_depth_range = (v & 0x40) ? 8 : 0; rhythm = v & 0x3F; if (rhythm & 0x20) { // BD key on/off if (v & 0x10) { channel[6].slot[MOD].FM_KEYON (2); channel[6].slot[CAR].FM_KEYON (2); } else { channel[6].slot[MOD].FM_KEYOFF(2); channel[6].slot[CAR].FM_KEYOFF(2); } // HH key on/off if (v & 0x01) { channel[7].slot[MOD].FM_KEYON (2); } else { channel[7].slot[MOD].FM_KEYOFF(2); } // SD key on/off if (v & 0x08) { channel[7].slot[CAR].FM_KEYON (2); } else { channel[7].slot[CAR].FM_KEYOFF(2); } // TOM key on/off if (v & 0x04) { channel[8].slot[MOD].FM_KEYON (2); } else { channel[8].slot[MOD].FM_KEYOFF(2); } // TOP-CY key on/off if (v & 0x02) { channel[8].slot[CAR].FM_KEYON (2); } else { channel[8].slot[CAR].FM_KEYOFF(2); } } else { // BD key off channel[6].slot[MOD].FM_KEYOFF(2); channel[6].slot[CAR].FM_KEYOFF(2); // HH key off channel[7].slot[MOD].FM_KEYOFF(2); // SD key off channel[7].slot[CAR].FM_KEYOFF(2); // TOM key off channel[8].slot[MOD].FM_KEYOFF(2); // TOP-CY off channel[8].slot[CAR].FM_KEYOFF(2); } return; } // keyon,block,fnum if ((r & 0x0F) > 8) { return; } unsigned chan_no = (r & 0x0F) + ch_offset; auto& ch = channel[chan_no]; int block_fnum; if (!(r & 0x10)) { // a0-a8 block_fnum = (ch.block_fnum & 0x1F00) | v; } else { // b0-b8 block_fnum = ((v & 0x1F) << 8) | (ch.block_fnum & 0xFF); if (isExtended(chan_no)) { if (getFirstOfPairNum(chan_no) == chan_no) { // keyon/off slots of both channels // forming a 4-op channel auto& ch0 = getFirstOfPair(chan_no); auto& ch3 = getSecondOfPair(chan_no); if (v & 0x20) { ch0.slot[MOD].FM_KEYON(1); ch0.slot[CAR].FM_KEYON(1); ch3.slot[MOD].FM_KEYON(1); ch3.slot[CAR].FM_KEYON(1); } else { ch0.slot[MOD].FM_KEYOFF(1); ch0.slot[CAR].FM_KEYOFF(1); ch3.slot[MOD].FM_KEYOFF(1); ch3.slot[CAR].FM_KEYOFF(1); } } else { // do nothing } } else { // 2 operator function keyon/off if (v & 0x20) { ch.slot[MOD].FM_KEYON (1); ch.slot[CAR].FM_KEYON (1); } else { ch.slot[MOD].FM_KEYOFF(1); ch.slot[CAR].FM_KEYOFF(1); } } } // update if (ch.block_fnum != block_fnum) { ch.block_fnum = block_fnum; ch.ksl_base = ksl_tab[block_fnum >> 6]; ch.fc = fnumToIncrement(block_fnum); // BLK 2,1,0 bits -> bits 3,2,1 of kcode ch.kcode = (ch.block_fnum & 0x1C00) >> 9; // the info below is actually opposite to what is stated // in the Manuals (verifed on real YMF262) // if notesel == 0 -> lsb of kcode is bit 10 (MSB) of fnum // if notesel == 1 -> lsb of kcode is bit 9 (MSB-1) of fnum if (nts) { ch.kcode |= (ch.block_fnum & 0x100) >> 8; // notesel == 1 } else { ch.kcode |= (ch.block_fnum & 0x200) >> 9; // notesel == 0 } if (isExtended(chan_no)) { if (getFirstOfPairNum(chan_no) == chan_no) { // update slots of both channels // forming up 4-op channel // refresh Total Level auto& ch0 = getFirstOfPair(chan_no); auto& ch3 = getSecondOfPair(chan_no); ch0.slot[MOD].TLL = ch0.slot[MOD].TL + (ch.ksl_base >> ch0.slot[MOD].ksl); ch0.slot[CAR].TLL = ch0.slot[CAR].TL + (ch.ksl_base >> ch0.slot[CAR].ksl); ch3.slot[MOD].TLL = ch3.slot[MOD].TL + (ch.ksl_base >> ch3.slot[MOD].ksl); ch3.slot[CAR].TLL = ch3.slot[CAR].TL + (ch.ksl_base >> ch3.slot[CAR].ksl); // refresh frequency counter ch0.slot[MOD].calc_fc(ch); ch0.slot[CAR].calc_fc(ch); ch3.slot[MOD].calc_fc(ch); ch3.slot[CAR].calc_fc(ch); } else { // nothing } } else { // refresh Total Level in both SLOTs of this channel ch.slot[MOD].TLL = ch.slot[MOD].TL + (ch.ksl_base >> ch.slot[MOD].ksl); ch.slot[CAR].TLL = ch.slot[CAR].TL + (ch.ksl_base >> ch.slot[CAR].ksl); // refresh frequency counter in both SLOTs of this channel ch.slot[MOD].calc_fc(ch); ch.slot[CAR].calc_fc(ch); } } break; } case 0xC0: { // CH.D, CH.C, CH.B, CH.A, FB(3bits), C if ((r & 0xF) > 8) { return; } unsigned chan_no = (r & 0x0F) + ch_offset; auto& ch = channel[chan_no]; unsigned base = chan_no * 4; if (OPL3_mode) { // OPL3 mode pan[base + 0] = (v & 0x10) ? unsigned(~0) : 0; // ch.A pan[base + 1] = (v & 0x20) ? unsigned(~0) : 0; // ch.B pan[base + 2] = (v & 0x40) ? unsigned(~0) : 0; // ch.C pan[base + 3] = (v & 0x80) ? unsigned(~0) : 0; // ch.D } else { // OPL2 mode - always enabled pan[base + 0] = unsigned(~0); // ch.A pan[base + 1] = unsigned(~0); // ch.B pan[base + 2] = unsigned(~0); // ch.C pan[base + 3] = unsigned(~0); // ch.D } ch.slot[MOD].setFeedbackShift((v >> 1) & 7); ch.slot[MOD].CON = v & 1; if (isExtended(chan_no)) { unsigned chan_no0 = getFirstOfPairNum(chan_no); unsigned chan_no3 = chan_no0 + 3; auto& ch0 = getFirstOfPair(chan_no); auto& ch3 = getSecondOfPair(chan_no); switch ((ch0.slot[MOD].CON ? 2:0) | (ch3.slot[MOD].CON ? 1:0)) { case 0: // 1 -> 2 -> 3 -> 4 -> out ch0.slot[MOD].connect = &phase_modulation; ch0.slot[CAR].connect = &phase_modulation2; ch3.slot[MOD].connect = &phase_modulation; ch3.slot[CAR].connect = &chanout[chan_no3]; break; case 1: // 1 -> 2 -\. // 3 -> 4 --+-> out ch0.slot[MOD].connect = &phase_modulation; ch0.slot[CAR].connect = &chanout[chan_no0]; ch3.slot[MOD].connect = &phase_modulation; ch3.slot[CAR].connect = &chanout[chan_no3]; break; case 2: // 1 ----------\. // 2 -> 3 -> 4 -+-> out ch0.slot[MOD].connect = &chanout[chan_no0]; ch0.slot[CAR].connect = &phase_modulation2; ch3.slot[MOD].connect = &phase_modulation; ch3.slot[CAR].connect = &chanout[chan_no3]; break; case 3: // 1 -----\. // 2 -> 3 -+-> out // 4 -----/ ch0.slot[MOD].connect = &chanout[chan_no0]; ch0.slot[CAR].connect = &phase_modulation2; ch3.slot[MOD].connect = &chanout[chan_no3]; ch3.slot[CAR].connect = &chanout[chan_no3]; break; } } else { // 2 operators mode ch.slot[MOD].connect = ch.slot[MOD].CON ? &chanout[chan_no] : &phase_modulation; ch.slot[CAR].connect = &chanout[chan_no]; } break; } case 0xE0: { // waveform select int slot = slot_array[r & 0x1f]; if (slot < 0) return; slot += ch_offset * 2; auto& ch = channel[slot / 2]; // store 3-bit value written regardless of current OPL2 or OPL3 // mode... (verified on real YMF262) v &= 7; // ... but select only waveforms 0-3 in OPL2 mode if (!OPL3_mode) { v &= 3; } ch.slot[slot & 1].wavetable = &sin_tab[v * SIN_LEN]; break; } } } void YMF262::reset(EmuTime::param time) { eg_cnt = 0; noise_rng = 1; // noise shift register nts = false; // note split alreadySignaledNEW2 = false; resetStatus(0x60); // reset with register write writeRegDirect(0x01, 0, time); // test register writeRegDirect(0x02, 0, time); // Timer1 writeRegDirect(0x03, 0, time); // Timer2 writeRegDirect(0x04, 0, time); // IRQ mask clear // FIX IT registers 101, 104 and 105 // FIX IT (dont change CH.D, CH.C, CH.B and CH.A in C0-C8 registers) for (int c = 0xFF; c >= 0x20; c--) { writeRegDirect(c, 0, time); } // FIX IT (dont change CH.D, CH.C, CH.B and CH.A in C0-C8 registers) for (int c = 0x1FF; c >= 0x120; c--) { writeRegDirect(c, 0, time); } // reset operator parameters for (auto& ch : channel) { for (int s = 0; s < 2; s++) { ch.slot[s].state = EG_OFF; ch.slot[s].volume = MAX_ATT_INDEX; } } } YMF262::YMF262(const std::string& name, const DeviceConfig& config, bool isYMF278_) : ResampledSoundDevice(config.getMotherBoard(), name, "MoonSound FM-part", 18, true) , debuggable(config.getMotherBoard(), getName()) , timer1(isYMF278_ ? EmuTimer::createOPL4_1(config.getScheduler(), *this) : EmuTimer::createOPL3_1(config.getScheduler(), *this)) , timer2(isYMF278_ ? EmuTimer::createOPL4_2(config.getScheduler(), *this) : EmuTimer::createOPL3_2(config.getScheduler(), *this)) , irq(config.getMotherBoard(), getName() + ".IRQ") , lfo_am_cnt(0), lfo_pm_cnt(0) , isYMF278(isYMF278_) { lfo_am_depth = false; lfo_pm_depth_range = 0; rhythm = 0; OPL3_mode = false; status = status2 = statusMask = 0; // avoid (harmless) UMR in serialize() memset(chanout, 0, sizeof(chanout)); memset(reg, 0, sizeof(reg)); init_tables(); float input = isYMF278 ? 33868800.0f / (19 * 36) : 4 * 3579545.0f / ( 8 * 36); setInputRate(int(input + 0.5f)); reset(config.getMotherBoard().getCurrentTime()); registerSound(config); } YMF262::~YMF262() { unregisterSound(); } byte YMF262::readStatus() { // no need to call updateStream(time) byte result = status | status2; status2 = 0; return result; } byte YMF262::peekStatus() const { return status | status2; } bool YMF262::checkMuteHelper() { // TODO this doesn't always mute when possible for (auto& ch : channel) { for (int j = 0; j < 2; j++) { auto& sl = ch.slot[j]; if (!((sl.state == EG_OFF) || ((sl.state == EG_RELEASE) && ((sl.TLL + sl.volume) >= ENV_QUIET)))) { return false; } } } return true; } int YMF262::getAmplificationFactor() const { return 1 << 2; } void YMF262::generateChannels(int** bufs, unsigned num) { // TODO implement per-channel mute (instead of all-or-nothing) // TODO output rhythm on separate channels? if (checkMuteHelper()) { // TODO update internal state, even if muted for (int i = 0; i < 18; ++i) { bufs[i] = nullptr; } return; } bool rhythmEnabled = (rhythm & 0x20) != 0; for (unsigned j = 0; j < num; ++j) { // Amplitude modulation: 27 output levels (triangle waveform); // 1 level takes one of: 192, 256 or 448 samples // One entry from LFO_AM_TABLE lasts for 64 samples lfo_am_cnt.addQuantum(); if (lfo_am_cnt == LFOAMIndex(LFO_AM_TAB_ELEMENTS)) { // lfo_am_table is 210 elements long lfo_am_cnt = LFOAMIndex(0); } unsigned tmp = lfo_am_table[lfo_am_cnt.toInt()]; unsigned lfo_am = lfo_am_depth ? tmp : tmp / 4; // clear channel outputs memset(chanout, 0, sizeof(chanout)); // channels 0,3 1,4 2,5 9,12 10,13 11,14 // in either 2op or 4op mode for (int k = 0; k <= 9; k += 9) { for (int i = 0; i < 3; ++i) { auto& ch0 = channel[k + i + 0]; auto& ch3 = channel[k + i + 3]; // extended 4op ch#0 part 1 or 2op ch#0 ch0.chan_calc(lfo_am); if (ch0.extended) { // extended 4op ch#0 part 2 ch3.chan_calc_ext(lfo_am); } else { // standard 2op ch#3 ch3.chan_calc(lfo_am); } } } // channels 6,7,8 rhythm or 2op mode if (!rhythmEnabled) { channel[6].chan_calc(lfo_am); channel[7].chan_calc(lfo_am); channel[8].chan_calc(lfo_am); } else { // Rhythm part chan_calc_rhythm(lfo_am); } // channels 15,16,17 are fixed 2-operator channels only channel[15].chan_calc(lfo_am); channel[16].chan_calc(lfo_am); channel[17].chan_calc(lfo_am); for (int i = 0; i < 18; ++i) { bufs[i][2 * j + 0] += chanout[i] & pan[4 * i + 0]; bufs[i][2 * j + 1] += chanout[i] & pan[4 * i + 1]; // unused c += chanout[i] & pan[4 * i + 2]; // unused d += chanout[i] & pan[4 * i + 3]; } advance(); } } static enum_string envelopeStateInfo[]= { { "ATTACK", YMF262::EG_ATTACK }, { "DECAY", YMF262::EG_DECAY }, { "SUSTAIN", YMF262::EG_SUSTAIN }, { "RELEASE", YMF262::EG_RELEASE }, { "OFF", YMF262::EG_OFF } }; SERIALIZE_ENUM(YMF262::EnvelopeState, envelopeStateInfo); template void YMF262::Slot::serialize(Archive& ar, unsigned /*version*/) { // wavetable unsigned waveform = unsigned((wavetable - sin_tab) / SIN_LEN); ar.serialize("waveform", waveform); if (ar.isLoader()) { wavetable = &sin_tab[waveform * SIN_LEN]; } // done by rewriting registers: // connect, fb_shift, CON // TODO handle more state like this ar.serialize("Cnt", Cnt); ar.serialize("Incr", Incr); ar.serialize("op1_out", op1_out); ar.serialize("TL", TL); ar.serialize("TLL", TLL); ar.serialize("volume", volume); ar.serialize("sl", sl); ar.serialize("state", state); ar.serialize("eg_m_ar", eg_m_ar); ar.serialize("eg_m_dr", eg_m_dr); ar.serialize("eg_m_rr", eg_m_rr); ar.serialize("eg_sh_ar", eg_sh_ar); ar.serialize("eg_sel_ar", eg_sel_ar); ar.serialize("eg_sh_dr", eg_sh_dr); ar.serialize("eg_sel_dr", eg_sel_dr); ar.serialize("eg_sh_rr", eg_sh_rr); ar.serialize("eg_sel_rr", eg_sel_rr); ar.serialize("key", key); ar.serialize("eg_type", eg_type); ar.serialize("AMmask", AMmask); ar.serialize("vib", vib); ar.serialize("ar", this->ar); ar.serialize("dr", dr); ar.serialize("rr", rr); ar.serialize("KSR", KSR); ar.serialize("ksl", ksl); ar.serialize("ksr", ksr); ar.serialize("mul", mul); } template void YMF262::Channel::serialize(Archive& ar, unsigned /*version*/) { ar.serialize("slots", slot); ar.serialize("block_fnum", block_fnum); ar.serialize("fc", fc); ar.serialize("ksl_base", ksl_base); ar.serialize("kcode", kcode); ar.serialize("extended", extended); } // version 1: initial version // version 2: added alreadySignaledNEW2 template void YMF262::serialize(Archive& ar, unsigned version) { ar.serialize("timer1", *timer1); ar.serialize("timer2", *timer2); ar.serialize("irq", irq); ar.serialize("chanout", chanout); ar.serialize_blob("registers", reg, sizeof(reg)); ar.serialize("channels", channel); ar.serialize("eg_cnt", eg_cnt); ar.serialize("noise_rng", noise_rng); ar.serialize("lfo_am_cnt", lfo_am_cnt); ar.serialize("lfo_pm_cnt", lfo_pm_cnt); ar.serialize("lfo_am_depth", lfo_am_depth); ar.serialize("lfo_pm_depth_range", lfo_pm_depth_range); ar.serialize("rhythm", rhythm); ar.serialize("nts", nts); ar.serialize("OPL3_mode", OPL3_mode); ar.serialize("status", status); ar.serialize("status2", status2); ar.serialize("statusMask", statusMask); if (ar.versionAtLeast(version, 2)) { ar.serialize("alreadySignaledNEW2", alreadySignaledNEW2); } // TODO restore more state by rewriting register values // this handles pan EmuTime::param time = timer1->getCurrentTime(); for (int i = 0xC0; i <= 0xC8; ++i) { writeRegDirect(i + 0x000, reg[i + 0x000], time); writeRegDirect(i + 0x100, reg[i + 0x100], time); } } INSTANTIATE_SERIALIZE_METHODS(YMF262); // YMF262::Debuggable YMF262::Debuggable::Debuggable(MSXMotherBoard& motherBoard, const std::string& name) : SimpleDebuggable(motherBoard, name + " regs", "MoonSound FM-part registers", 0x200) { } byte YMF262::Debuggable::read(unsigned address) { auto& ymf262 = OUTER(YMF262, debuggable); return ymf262.peekReg(address); } void YMF262::Debuggable::write(unsigned address, byte value, EmuTime::param time) { auto& ymf262 = OUTER(YMF262, debuggable); ymf262.writeReg512(address, value, time); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/YMF262.hh000066400000000000000000000143001257557151200175620ustar00rootroot00000000000000#ifndef YMF262_HH #define YMF262_HH #include "ResampledSoundDevice.hh" #include "SimpleDebuggable.hh" #include "EmuTimer.hh" #include "EmuTime.hh" #include "FixedPoint.hh" #include "IRQHelper.hh" #include "openmsx.hh" #include "serialize_meta.hh" #include #include namespace openmsx { class DeviceConfig; class YMF262 final : private ResampledSoundDevice, private EmuTimerCallback { public: YMF262(const std::string& name, const DeviceConfig& config, bool isYMF278); ~YMF262(); void reset(EmuTime::param time); void writeReg (unsigned r, byte v, EmuTime::param time); void writeReg512(unsigned r, byte v, EmuTime::param time); byte readReg(unsigned reg); byte peekReg(unsigned reg) const; byte readStatus(); byte peekStatus() const; template void serialize(Archive& ar, unsigned version); public: /** 16.16 fixed point type for frequency calculations */ using FreqIndex = FixedPoint<16>; enum EnvelopeState { EG_ATTACK, EG_DECAY, EG_SUSTAIN, EG_RELEASE, EG_OFF }; private: class Channel; class Slot { public: Slot(); inline int op_calc(unsigned phase, unsigned lfo_am) const; inline void FM_KEYON(byte key_set); inline void FM_KEYOFF(byte key_clr); inline void advanceEnvelopeGenerator(unsigned eg_cnt); inline void advancePhaseGenerator(Channel& ch, unsigned lfo_pm); void update_ar_dr(); void update_rr(); void calc_fc(const Channel& ch); /** Sets the amount of feedback [0..7] */ void setFeedbackShift(byte value) { fb_shift = value ? 9 - value : 0; } template void serialize(Archive& ar, unsigned version); // Phase Generator FreqIndex Cnt; // frequency counter FreqIndex Incr; // frequency counter step int* connect; // slot output pointer int op1_out[2]; // slot1 output for feedback // Envelope Generator unsigned TL; // total level: TL << 2 int TLL; // adjusted now TL int volume; // envelope counter int sl; // sustain level: sl_tab[SL] unsigned* wavetable; // waveform select EnvelopeState state; // EG: phase type unsigned eg_m_ar;// (attack state) unsigned eg_m_dr;// (decay state) unsigned eg_m_rr;// (release state) byte eg_sh_ar; // (attack state) byte eg_sel_ar; // (attack state) byte eg_sh_dr; // (decay state) byte eg_sel_dr; // (decay state) byte eg_sh_rr; // (release state) byte eg_sel_rr; // (release state) byte key; // 0 = KEY OFF, >0 = KEY ON byte fb_shift; // PG: feedback shift value bool CON; // PG: connection (algorithm) type bool eg_type; // EG: percussive/non-percussive mode // LFO byte AMmask; // LFO Amplitude Modulation enable mask bool vib; // LFO Phase Modulation enable flag (active high) byte ar; // attack rate: AR<<2 byte dr; // decay rate: DR<<2 byte rr; // release rate:RR<<2 byte KSR; // key scale rate byte ksl; // keyscale level byte ksr; // key scale rate: kcode>>KSR byte mul; // multiple: mul_tab[ML] }; class Channel { public: Channel(); void chan_calc(unsigned lfo_am); void chan_calc_ext(unsigned lfo_am); template void serialize(Archive& ar, unsigned version); Slot slot[2]; int block_fnum; // block+fnum FreqIndex fc; // Freq. Increment base int ksl_base; // KeyScaleLevel Base step byte kcode; // key code (for key scaling) // there are 12 2-operator channels which can be combined in pairs // to form six 4-operator channel, they are: // 0 and 3, // 1 and 4, // 2 and 5, // 9 and 12, // 10 and 13, // 11 and 14 bool extended; // set if this channel forms up a 4op channel with // another channel (only used by first of pair of // channels, ie 0,1,2 and 9,10,11) }; // SoundDevice int getAmplificationFactor() const override; void generateChannels(int** bufs, unsigned num) override; void callback(byte flag) override; void writeRegDirect(unsigned r, byte v, EmuTime::param time); void init_tables(); void setStatus(byte flag); void resetStatus(byte flag); void changeStatusMask(byte flag); void advance(); inline int genPhaseHighHat(); inline int genPhaseSnare(); inline int genPhaseCymbal(); void chan_calc_rhythm(unsigned lfo_am); void set_mul(unsigned sl, byte v); void set_ksl_tl(unsigned sl, byte v); void set_ar_dr(unsigned sl, byte v); void set_sl_rr(unsigned sl, byte v); bool checkMuteHelper(); inline bool isExtended(unsigned ch) const; inline Channel& getFirstOfPair(unsigned ch); inline Channel& getSecondOfPair(unsigned ch); struct Debuggable final : SimpleDebuggable { Debuggable(MSXMotherBoard& motherBoard, const std::string& name); byte read(unsigned address) override; void write(unsigned address, byte value, EmuTime::param time) override; } debuggable; // Bitmask for register 0x04 static const int R04_ST1 = 0x01; // Timer1 Start static const int R04_ST2 = 0x02; // Timer2 Start static const int R04_MASK_T2 = 0x20; // Mask Timer2 flag static const int R04_MASK_T1 = 0x40; // Mask Timer1 flag static const int R04_IRQ_RESET = 0x80; // IRQ RESET // Bitmask for status register static const int STATUS_T2 = R04_MASK_T2; static const int STATUS_T1 = R04_MASK_T1; // Timers (see EmuTimer class for details about timing) const std::unique_ptr timer1; // 80.8us OPL4 ( 80.5us OPL3) const std::unique_ptr timer2; // 323.1us OPL4 (321.8us OPL3) IRQHelper irq; int chanout[18]; // 18 channels byte reg[512]; Channel channel[18]; // OPL3 chips have 18 channels unsigned pan[18 * 4]; // channels output masks 4 per channel // 0xffffffff = enable unsigned eg_cnt; // global envelope generator counter unsigned noise_rng; // 23 bit noise shift register // LFO using LFOAMIndex = FixedPoint< 6>; using LFOPMIndex = FixedPoint<10>; LFOAMIndex lfo_am_cnt; LFOPMIndex lfo_pm_cnt; bool lfo_am_depth; byte lfo_pm_depth_range; byte rhythm; // Rhythm mode bool nts; // NTS (note select) bool OPL3_mode; // OPL3 extension enable flag byte status; // status flag byte status2; byte statusMask; // status mask bool alreadySignaledNEW2; const bool isYMF278; // true iff this is actually a YMF278 // ATM only used for NEW2 bit }; SERIALIZE_CLASS_VERSION(YMF262, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/YMF278.cc000066400000000000000000000703231257557151200175660ustar00rootroot00000000000000// Based on ymf278b.c written by R. Belmont and O. Galibert // This class doesn't model a full YMF278b chip. Instead it only models the // wave part. The FM part in modeled in YMF262 (it's almost 100% compatible, // the small differences are handled in YMF262). The status register and // interaction with the FM registers (e.g. the NEW2 bit) is currently handled // in the MSXMoonSound class. #include "YMF278.hh" #include "DeviceConfig.hh" #include "MSXMotherBoard.hh" #include "MSXException.hh" #include "StringOp.hh" #include "serialize.hh" #include "likely.hh" #include "outer.hh" #include #include #include namespace openmsx { static const int EG_SH = 16; // 16.16 fixed point (EG timing) static const unsigned EG_TIMER_OVERFLOW = 1 << EG_SH; // envelope output entries static const int ENV_BITS = 10; static const int ENV_LEN = 1 << ENV_BITS; static const float ENV_STEP = 128.0f / ENV_LEN; static const int MAX_ATT_INDEX = (1 << (ENV_BITS - 1)) - 1; // 511 static const int MIN_ATT_INDEX = 0; // Envelope Generator phases static const int EG_ATT = 4; static const int EG_DEC = 3; static const int EG_SUS = 2; static const int EG_REL = 1; static const int EG_OFF = 0; static const int EG_REV = 5; // pseudo reverb static const int EG_DMP = 6; // damp // Pan values, units are -3dB, i.e. 8. static const int pan_left[16] = { 0, 8, 16, 24, 32, 40, 48, 256, 256, 0, 0, 0, 0, 0, 0, 0 }; static const int pan_right[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 256, 256, 48, 40, 32, 24, 16, 8 }; // Mixing levels, units are -3dB, and add some marging to avoid clipping static const int mix_level[8] = { 8, 16, 24, 32, 40, 48, 56, 256 }; // decay level table (3dB per step) // 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB) #define SC(db) unsigned(db * (2.0f / ENV_STEP)) static const unsigned dl_tab[16] = { SC( 0), SC( 1), SC( 2), SC(3 ), SC(4 ), SC(5 ), SC(6 ), SC( 7), SC( 8), SC( 9), SC(10), SC(11), SC(12), SC(13), SC(14), SC(31) }; #undef SC static const byte RATE_STEPS = 8; static const byte eg_inc[15 * RATE_STEPS] = { //cycle:0 1 2 3 4 5 6 7 0, 1, 0, 1, 0, 1, 0, 1, // 0 rates 00..12 0 (increment by 0 or 1) 0, 1, 0, 1, 1, 1, 0, 1, // 1 rates 00..12 1 0, 1, 1, 1, 0, 1, 1, 1, // 2 rates 00..12 2 0, 1, 1, 1, 1, 1, 1, 1, // 3 rates 00..12 3 1, 1, 1, 1, 1, 1, 1, 1, // 4 rate 13 0 (increment by 1) 1, 1, 1, 2, 1, 1, 1, 2, // 5 rate 13 1 1, 2, 1, 2, 1, 2, 1, 2, // 6 rate 13 2 1, 2, 2, 2, 1, 2, 2, 2, // 7 rate 13 3 2, 2, 2, 2, 2, 2, 2, 2, // 8 rate 14 0 (increment by 2) 2, 2, 2, 4, 2, 2, 2, 4, // 9 rate 14 1 2, 4, 2, 4, 2, 4, 2, 4, // 10 rate 14 2 2, 4, 4, 4, 2, 4, 4, 4, // 11 rate 14 3 4, 4, 4, 4, 4, 4, 4, 4, // 12 rates 15 0, 15 1, 15 2, 15 3 for decay 8, 8, 8, 8, 8, 8, 8, 8, // 13 rates 15 0, 15 1, 15 2, 15 3 for attack (zero time) 0, 0, 0, 0, 0, 0, 0, 0, // 14 infinity rates for attack and decay(s) }; #define O(a) (a * RATE_STEPS) static const byte eg_rate_select[64] = { O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 0),O( 1),O( 2),O( 3), O( 4),O( 5),O( 6),O( 7), O( 8),O( 9),O(10),O(11), O(12),O(12),O(12),O(12), }; #undef O // rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 // shift 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0 // mask 4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0 #define O(a) (a) static const byte eg_rate_shift[64] = { O(12),O(12),O(12),O(12), O(11),O(11),O(11),O(11), O(10),O(10),O(10),O(10), O( 9),O( 9),O( 9),O( 9), O( 8),O( 8),O( 8),O( 8), O( 7),O( 7),O( 7),O( 7), O( 6),O( 6),O( 6),O( 6), O( 5),O( 5),O( 5),O( 5), O( 4),O( 4),O( 4),O( 4), O( 3),O( 3),O( 3),O( 3), O( 2),O( 2),O( 2),O( 2), O( 1),O( 1),O( 1),O( 1), O( 0),O( 0),O( 0),O( 0), O( 0),O( 0),O( 0),O( 0), O( 0),O( 0),O( 0),O( 0), O( 0),O( 0),O( 0),O( 0), }; #undef O // number of steps to take in quarter of lfo frequency // TODO check if frequency matches real chip #define O(a) int((EG_TIMER_OVERFLOW / a) / 6) static const int lfo_period[8] = { O(0.168f), O(2.019f), O(3.196f), O(4.206f), O(5.215f), O(5.888f), O(6.224f), O(7.066f) }; #undef O #define O(a) int(a * 65536) static const int vib_depth[8] = { O( 0.0f ), O( 3.378f), O( 5.065f), O( 6.750f), O(10.114f), O(20.170f), O(40.106f), O(79.307f) }; #undef O #define SC(db) int(db * (2.0f / ENV_STEP)) static const int am_depth[8] = { SC(0.0f ), SC(1.781f), SC(2.906f), SC( 3.656f), SC(4.406f), SC(5.906f), SC(7.406f), SC(11.91f ) }; #undef SC YMF278::Slot::Slot() { reset(); } // Sign extend a 4-bit value to int (32-bit) // require: x in range [0..15] static inline int sign_extend_4(int x) { return (x ^ 8) - 8; } // Params: oct in [0 .. 15] // fn in [0 .. 1023] // We want to interpret oct as a signed 4-bit number and calculate // ((fn | 1024) + vib) << (5 + sign_extend_4(oct)) // Though in this formula the shift can go over a negative distance (in that // case we should shift in the other direction). static inline unsigned calcStep(unsigned oct, unsigned fn, unsigned vib = 0) { oct ^= 8; // [0..15] -> [8..15][0..7] == sign_extend_4(x) + 8 unsigned t = (fn + 1024 + vib) << oct; // use '+' iso '|' (generates slightly better code) return t >> 3; // was shifted 3 positions too far } void YMF278::Slot::reset() { wave = FN = OCT = PRVB = LD = TL = pan = lfo = vib = AM = 0; AR = D1R = DL = D2R = RC = RR = 0; stepptr = 0; step = calcStep(OCT, FN); bits = startaddr = loopaddr = endaddr = 0; env_vol = MAX_ATT_INDEX; lfo_active = false; lfo_cnt = lfo_step = 0; lfo_max = lfo_period[0]; state = EG_OFF; active = false; // not strictly needed, but avoid UMR on savestate pos = sample1 = sample2 = 0; } int YMF278::Slot::compute_rate(int val) const { if (val == 0) { return 0; } else if (val == 15) { return 63; } int res; if (RC != 15) { // TODO it may be faster to store 'OCT' sign extended int oct = sign_extend_4(OCT); res = (oct + RC) * 2 + (FN & 0x200 ? 1 : 0) + val * 4; } else { res = val * 4; } if (res < 0) { res = 0; } else if (res > 63) { res = 63; } return res; } int YMF278::Slot::compute_vib() const { return (((lfo_step << 8) / lfo_max) * vib_depth[int(vib)]) >> 24; } int YMF278::Slot::compute_am() const { if (lfo_active && AM) { return (((lfo_step << 8) / lfo_max) * am_depth[int(AM)]) >> 12; } else { return 0; } } void YMF278::Slot::set_lfo(int newlfo) { lfo_step = (((lfo_step << 8) / lfo_max) * newlfo) >> 8; lfo_cnt = (((lfo_cnt << 8) / lfo_max) * newlfo) >> 8; lfo = newlfo; lfo_max = lfo_period[int(lfo)]; } void YMF278::advance() { eg_cnt++; for (auto& op : slots) { if (op.lfo_active) { op.lfo_cnt++; if (op.lfo_cnt < op.lfo_max) { op.lfo_step++; } else if (op.lfo_cnt < (op.lfo_max * 3)) { op.lfo_step--; } else { op.lfo_step++; if (op.lfo_cnt == (op.lfo_max * 4)) { op.lfo_cnt = 0; } } } // Envelope Generator switch(op.state) { case EG_ATT: { // attack phase byte rate = op.compute_rate(op.AR); if (rate < 4) { break; } byte shift = eg_rate_shift[rate]; if (!(eg_cnt & ((1 << shift) -1))) { byte select = eg_rate_select[rate]; op.env_vol += (~op.env_vol * eg_inc[select + ((eg_cnt >> shift) & 7)]) >> 3; if (op.env_vol <= MIN_ATT_INDEX) { op.env_vol = MIN_ATT_INDEX; if (op.DL) { op.state = EG_DEC; } else { op.state = EG_SUS; } } } break; } case EG_DEC: { // decay phase byte rate = op.compute_rate(op.D1R); if (rate < 4) { break; } byte shift = eg_rate_shift[rate]; if (!(eg_cnt & ((1 << shift) -1))) { byte select = eg_rate_select[rate]; op.env_vol += eg_inc[select + ((eg_cnt >> shift) & 7)]; if ((unsigned(op.env_vol) > dl_tab[6]) && op.PRVB) { op.state = EG_REV; } else { if (op.env_vol >= op.DL) { op.state = EG_SUS; } } } break; } case EG_SUS: { // sustain phase byte rate = op.compute_rate(op.D2R); if (rate < 4) { break; } byte shift = eg_rate_shift[rate]; if (!(eg_cnt & ((1 << shift) -1))) { byte select = eg_rate_select[rate]; op.env_vol += eg_inc[select + ((eg_cnt >> shift) & 7)]; if ((unsigned(op.env_vol) > dl_tab[6]) && op.PRVB) { op.state = EG_REV; } else { if (op.env_vol >= MAX_ATT_INDEX) { op.env_vol = MAX_ATT_INDEX; op.active = false; } } } break; } case EG_REL: { // release phase byte rate = op.compute_rate(op.RR); if (rate < 4) { break; } byte shift = eg_rate_shift[rate]; if (!(eg_cnt & ((1 << shift) -1))) { byte select = eg_rate_select[rate]; op.env_vol += eg_inc[select + ((eg_cnt >> shift) & 7)]; if ((unsigned(op.env_vol) > dl_tab[6]) && op.PRVB) { op.state = EG_REV; } else { if (op.env_vol >= MAX_ATT_INDEX) { op.env_vol = MAX_ATT_INDEX; op.active = false; } } } break; } case EG_REV: { // pseudo reverb // TODO improve env_vol update byte rate = op.compute_rate(5); //if (rate < 4) { // break; //} byte shift = eg_rate_shift[rate]; if (!(eg_cnt & ((1 << shift) - 1))) { byte select = eg_rate_select[rate]; op.env_vol += eg_inc[select + ((eg_cnt >> shift) & 7)]; if (op.env_vol >= MAX_ATT_INDEX) { op.env_vol = MAX_ATT_INDEX; op.active = false; } } break; } case EG_DMP: { // damping // TODO improve env_vol update, damp is just fastest decay now byte rate = 56; byte shift = eg_rate_shift[rate]; if (!(eg_cnt & ((1 << shift) - 1))) { byte select = eg_rate_select[rate]; op.env_vol += eg_inc[select + ((eg_cnt >> shift) & 7)]; if (op.env_vol >= MAX_ATT_INDEX) { op.env_vol = MAX_ATT_INDEX; op.active = false; } } break; } case EG_OFF: // nothing break; default: UNREACHABLE; } } } short YMF278::getSample(Slot& op) { // TODO How does this behave when R#2 bit 0 = 1? // As-if read returns 0xff? (Like for CPU memory reads.) Or is // sound generation blocked at some higher level? short sample; switch (op.bits) { case 0: { // 8 bit sample = readMem(op.startaddr + op.pos) << 8; break; } case 1: { // 12 bit unsigned addr = op.startaddr + ((op.pos / 2) * 3); if (op.pos & 1) { sample = readMem(addr + 2) << 8 | ((readMem(addr + 1) << 4) & 0xF0); } else { sample = readMem(addr + 0) << 8 | (readMem(addr + 1) & 0xF0); } break; } case 2: { // 16 bit unsigned addr = op.startaddr + (op.pos * 2); sample = (readMem(addr + 0) << 8) | (readMem(addr + 1)); break; } default: // TODO unspecified sample = 0; } return sample; } bool YMF278::anyActive() { for (auto& op : slots) { if (op.active) return true; } return false; } void YMF278::generateChannels(int** bufs, unsigned num) { if (!anyActive()) { // TODO update internal state, even if muted // TODO also mute individual channels for (int i = 0; i < 24; ++i) { bufs[i] = nullptr; } return; } int vl = mix_level[pcm_l]; int vr = mix_level[pcm_r]; for (unsigned j = 0; j < num; ++j) { for (int i = 0; i < 24; ++i) { auto& sl = slots[i]; if (!sl.active) { //bufs[i][2 * j + 0] += 0; //bufs[i][2 * j + 1] += 0; continue; } short sample = (sl.sample1 * (0x10000 - sl.stepptr) + sl.sample2 * sl.stepptr) >> 16; int vol = sl.TL + (sl.env_vol >> 2) + sl.compute_am(); int volLeft = vol + pan_left [int(sl.pan)] + vl; int volRight = vol + pan_right[int(sl.pan)] + vr; // TODO prob doesn't happen in real chip volLeft = std::max(0, volLeft); volRight = std::max(0, volRight); bufs[i][2 * j + 0] += (sample * volume[volLeft] ) >> 14; bufs[i][2 * j + 1] += (sample * volume[volRight]) >> 14; unsigned step = (sl.lfo_active && sl.vib) ? calcStep(sl.OCT, sl.FN, sl.compute_vib()) : sl.step; sl.stepptr += step; while (sl.stepptr >= 0x10000) { sl.stepptr -= 0x10000; sl.sample1 = sl.sample2; sl.pos++; if (sl.pos >= sl.endaddr) { sl.pos = sl.loopaddr; } sl.sample2 = getSample(sl); } } advance(); } } void YMF278::keyOnHelper(YMF278::Slot& slot) { slot.active = true; slot.state = EG_ATT; slot.stepptr = 0; slot.pos = 0; slot.sample1 = getSample(slot); slot.pos = 1; slot.sample2 = getSample(slot); } void YMF278::writeReg(byte reg, byte data, EmuTime::param time) { updateStream(time); // TODO optimize only for regs that directly influence sound writeRegDirect(reg, data, time); } void YMF278::writeRegDirect(byte reg, byte data, EmuTime::param time) { // Handle slot registers specifically if (reg >= 0x08 && reg <= 0xF7) { int snum = (reg - 8) % 24; auto& slot = slots[snum]; switch ((reg - 8) / 24) { case 0: { slot.wave = (slot.wave & 0x100) | data; int wavetblhdr = (regs[2] >> 2) & 0x7; int base = (slot.wave < 384 || !wavetblhdr) ? (slot.wave * 12) : (wavetblhdr * 0x80000 + ((slot.wave - 384) * 12)); byte buf[12]; for (int i = 0; i < 12; ++i) { // TODO What if R#2 bit 0 = 1? // See also getSample() buf[i] = readMem(base + i); } slot.bits = (buf[0] & 0xC0) >> 6; slot.startaddr = buf[2] | (buf[1] << 8) | ((buf[0] & 0x3F) << 16); slot.loopaddr = buf[4] + (buf[3] << 8); slot.endaddr = (((buf[6] + (buf[5] << 8)) ^ 0xFFFF) + 1); for (int i = 7; i < 12; ++i) { // Verified on real YMF278: // After tone loading, if you read these // registers, their value actually has changed. writeRegDirect(8 + snum + (i - 2) * 24, buf[i], time); } if ((regs[reg + 4] & 0x080)) { keyOnHelper(slot); } break; } case 1: { slot.wave = (slot.wave & 0xFF) | ((data & 0x1) << 8); slot.FN = (slot.FN & 0x380) | (data >> 1); slot.step = calcStep(slot.OCT, slot.FN); break; } case 2: { slot.FN = (slot.FN & 0x07F) | ((data & 0x07) << 7); slot.PRVB = ((data & 0x08) >> 3); slot.OCT = ((data & 0xF0) >> 4); slot.step = calcStep(slot.OCT, slot.FN); break; } case 3: slot.TL = data >> 1; slot.LD = data & 0x1; // TODO if (slot.LD) { // directly change volume } else { // interpolate volume } break; case 4: if (data & 0x10) { // output to DO1 pin: // this pin is not used in moonsound // we emulate this by muting the sound slot.pan = 8; // both left/right -inf dB } else { slot.pan = data & 0x0F; } if (data & 0x020) { // LFO reset slot.lfo_active = false; slot.lfo_cnt = 0; slot.lfo_max = lfo_period[int(slot.vib)]; slot.lfo_step = 0; } else { // LFO activate slot.lfo_active = true; } switch (data >> 6) { case 0: // tone off, no damp if (slot.active && (slot.state != EG_REV) ) { slot.state = EG_REL; } break; case 2: // tone on, no damp if (!(regs[reg] & 0x080)) { keyOnHelper(slot); } break; case 1: // tone off, damp case 3: // tone on, damp slot.state = EG_DMP; break; } break; case 5: slot.vib = data & 0x7; slot.set_lfo((data >> 3) & 0x7); break; case 6: slot.AR = data >> 4; slot.D1R = data & 0xF; break; case 7: slot.DL = dl_tab[data >> 4]; slot.D2R = data & 0xF; break; case 8: slot.RC = data >> 4; slot.RR = data & 0xF; break; case 9: slot.AM = data & 0x7; break; } } else { // All non-slot registers switch (reg) { case 0x00: // TEST case 0x01: break; case 0x02: // wave-table-header / memory-type / memory-access-mode // Simply store in regs[2] break; case 0x03: // Verified on real YMF278: // * Don't update the 'memadr' variable on writes to // reg 3 and 4. Only store the value in the 'regs' // array for later use. // * The upper 2 bits are not used to address the // external memories (so from a HW pov they don't // matter). But if you read back this register, the // upper 2 bits always read as '0' (even if you wrote // '1'). So we mask the bits here already. data &= 0x3F; break; case 0x04: // See reg 3. break; case 0x05: // Verified on real YMF278: (see above) // Only writes to reg 5 change the (full) 'memadr'. memadr = (regs[3] << 16) | (regs[4] << 8) | data; break; case 0x06: // memory data if (regs[2] & 1) { writeMem(memadr, data); ++memadr; // no need to mask (again) here } else { // Verified on real YMF278: // - writes are ignored // - memadr is NOT increased } break; case 0xF8: // TODO use these fm_l = data & 0x7; fm_r = (data >> 3) & 0x7; break; case 0xF9: pcm_l = data & 0x7; pcm_r = (data >> 3) & 0x7; break; } } regs[reg] = data; } byte YMF278::readReg(byte reg) { // no need to call updateStream(time) byte result = peekReg(reg); if (reg == 6) { // Memory Data Register if (regs[2] & 1) { // Verified on real YMF278: // memadr is only increased when 'regs[2] & 1' ++memadr; // no need to mask (again) here } } return result; } byte YMF278::peekReg(byte reg) const { byte result; switch (reg) { case 2: // 3 upper bits are device ID result = (regs[2] & 0x1F) | 0x20; break; case 6: // Memory Data Register if (regs[2] & 1) { result = readMem(memadr); } else { // Verified on real YMF278 result = 0xff; } break; default: result = regs[reg]; break; } return result; } YMF278::YMF278(const std::string& name, int ramSize_, const DeviceConfig& config) : ResampledSoundDevice(config.getMotherBoard(), name, "MoonSound wave-part", 24, true) , motherBoard(config.getMotherBoard()) , debugRegisters(motherBoard, getName()) , debugMemory (motherBoard, getName()) , rom(name + " ROM", "rom", config) , ramSize(ramSize_ * 1024) // in kB , ram(ramSize) { if (rom.getSize() != 0x200000) { // 2MB throw MSXException( "Wrong ROM for MoonSound (YMF278). The ROM (usually " "called yrw801.rom) should have a size of exactly 2MB."); } if ((ramSize_ != 0) && // - - (ramSize_ != 128) && // 128kB - (ramSize_ != 256) && // 128kB 128kB (ramSize_ != 512) && // 512kB - (ramSize_ != 640) && // 512kB 128kB (ramSize_ != 1024) && // 512kB 512kB (ramSize_ != 2048)) { // 512kB 512kB 512kB 512kB throw MSXException(StringOp::Builder() << "Wrong sampleram size for MoonSound (YMF278). " "Got " << ramSize_ << ", but must be one of " "0, 128, 256, 512, 640, 1024 or 2048."); } memadr = 0; // avoid UMR setInputRate(44100); reset(motherBoard.getCurrentTime()); registerSound(config); // Volume table, 1 = -0.375dB, 8 = -3dB, 256 = -96dB for (int i = 0; i < 256; ++i) { volume[i] = int(32768.0 * exp2((-0.375 / 6) * i)); } for (int i = 256; i < 256 * 4; ++i) { volume[i] = 0; } } YMF278::~YMF278() { unregisterSound(); } void YMF278::clearRam() { memset(ram.data(), 0, ramSize); } void YMF278::reset(EmuTime::param time) { updateStream(time); eg_cnt = 0; for (auto& op : slots) { op.reset(); } regs[2] = 0; // avoid UMR for (int i = 255; i >= 0; --i) { // reverse order to avoid UMR writeRegDirect(i, 0, time); } memadr = 0; fm_l = fm_r = pcm_l = pcm_r = 0; } // This routine translates an address from the (upper) MoonSound address space // to an address inside the (linearized) SRAM address space. // // The following info is based on measurements on a real MoonSound (v2.0) // PCB. This PCB can have several possible SRAM configurations: // 128kB: // 1 SRAM chip of 128kB, chip enable (/CE) of this SRAM chip is connected to // the 1Y0 output of a 74LS139 (2-to-4 decoder). The enable input of the // 74LS139 is connected to YMF278 pin /MCS6 and the 74LS139 1B:1A inputs are // connected to YMF278 pins MA18:MA17. So the SRAM is selected when /MC6 is // active and MA18:MA17 == 0:0. // 256kB: // 2 SRAM chips of 128kB. First one connected as above. Second one has /CE // connected to 74LS139 pin 1Y1. So SRAM2 is selected when /MSC6 is active // and MA18:MA17 == 0:1. // 512kB: // 1 SRAM chip of 512kB, /CE connected to /MCS6 // 640kB: // 1 SRAM chip of 512kB, /CE connected to /MCS6 // 1 SRAM chip of 128kB, /CE connected to /MCS7. // (This means SRAM2 is potentially mirrored over a 512kB region) // 1024kB: // 1 SRAM chip of 512kB, /CE connected to /MCS6 // 1 SRAM chip of 512kB, /CE connected to /MCS7 // 2048kB: // 1 SRAM chip of 512kB, /CE connected to /MCS6 // 1 SRAM chip of 512kB, /CE connected to /MCS7 // 1 SRAM chip of 512kB, /CE connected to /MCS8 // 1 SRAM chip of 512kB, /CE connected to /MCS9 // This configuration is not so easy to create on the v2.0 PCB. So it's // very rare. // // So the /MCS6 and /MCS7 (and /MCS8 and /MCS9 in case of 2048kB) signals are // used to select the different SRAM chips. The meaning of these signals // depends on the 'memory access mode'. This mode can be changed at run-time // via bit 1 in register 2. The following table indicates for which regions // these signals are active (normally MoonSound should be used with mode=0): // mode=0 mode=1 // /MCS6 0x200000-0x27FFFF 0x380000-0x39FFFF // /MCS7 0x280000-0x2FFFFF 0x3A0000-0x3BFFFF // /MCS8 0x300000-0x37FFFF 0x3C0000-0x3DFFFF // /MCS9 0x380000-0x3FFFFF 0x3E0000-0x3FFFFF // // (For completeness) MoonSound also has 2MB ROM (YRW801), /CE of this ROM is // connected to YMF278 /MCS0. In both mode=0 and mode=1 this signal is active // for the region 0x000000-0x1FFFFF. (But this routine does not handle ROM). unsigned YMF278::getRamAddress(unsigned addr) const { addr -= 0x200000; // RAM starts at 0x200000 if (unlikely(regs[2] & 2)) { // Normally MoonSound is used in 'memory access mode = 0'. But // in the rare case that mode=1 we adjust the address. if ((0x180000 <= addr) && (addr <= 0x1FFFFF)) { addr -= 0x180000; switch (addr & 0x060000) { case 0x000000: // [0x380000-0x39FFFF] // 1st 128kB of SRAM1 break; case 0x020000: // [0x3A0000-0x3BFFFF] if (ramSize == 256 * 1024) { // 2nd 128kB SRAM chip } else { // 2nd block of 128kB in SRAM2 // In case of 512+128, we use mirroring addr += 0x080000; } break; case 0x040000: // [0x3C0000-0x3DFFFF] // 3rd 128kB block in SRAM3 addr += 0x100000; break; case 0x060000: // [0x3EFFFF-0x3FFFFF] // 4th 128kB block in SRAM4 addr += 0x180000; break; } } else { addr = unsigned(-1); // unmapped } } if (ramSize == 640 * 1024) { // Verified on real MoonSound cartridge (v2.0): In case of // 640kB (1x512kB + 1x128kB), the 128kB SRAM chip is 4 times // visible. None of the other SRAM configurations show similar // mirroring (because the others are powers of two). if (addr > 0x080000) { addr &= ~0x060000; } } return addr; } byte YMF278::readMem(unsigned address) const { // Verified on real YMF278: address space wraps at 4MB. address &= 0x3FFFFF; if (address < 0x200000) { // ROM connected to /MCS0 return rom[address]; } else { unsigned ramAddr = getRamAddress(address); if (ramAddr < ramSize) { return ram[ramAddr]; } else { // unmapped region return 255; // TODO check } } } void YMF278::writeMem(unsigned address, byte value) { address &= 0x3FFFFF; if (address < 0x200000) { // can't write to ROM } else { unsigned ramAddr = getRamAddress(address); if (ramAddr < ramSize) { ram[ramAddr] = value; } else { // can't write to unmapped memory } } } // version 1: initial version, some variables were saved as char // version 2: serialization framework was fixed to save/load chars as numbers // but for backwards compatibility we still load old savestates as // characters // version 3: 'step' is no longer stored (it is recalculated) template void YMF278::Slot::serialize(Archive& ar, unsigned version) { // TODO restore more state from registers ar.serialize("startaddr", startaddr); ar.serialize("loopaddr", loopaddr); ar.serialize("endaddr", endaddr); ar.serialize("stepptr", stepptr); ar.serialize("pos", pos); ar.serialize("sample1", sample1); ar.serialize("sample2", sample2); ar.serialize("env_vol", env_vol); ar.serialize("lfo_cnt", lfo_cnt); ar.serialize("lfo_step", lfo_step); ar.serialize("lfo_max", lfo_max); ar.serialize("DL", DL); ar.serialize("wave", wave); ar.serialize("FN", FN); if (ar.versionAtLeast(version, 2)) { ar.serialize("OCT", OCT); ar.serialize("PRVB", PRVB); ar.serialize("LD", LD); ar.serialize("TL", TL); ar.serialize("pan", pan); ar.serialize("lfo", lfo); ar.serialize("vib", vib); ar.serialize("AM", AM); ar.serialize("AR", AR); ar.serialize("D1R", D1R); ar.serialize("D2R", D2R); ar.serialize("RC", RC); ar.serialize("RR", RR); } else { ar.serializeChar("OCT", OCT); ar.serializeChar("PRVB", PRVB); ar.serializeChar("LD", LD); ar.serializeChar("TL", TL); ar.serializeChar("pan", pan); ar.serializeChar("lfo", lfo); ar.serializeChar("vib", vib); ar.serializeChar("AM", AM); ar.serializeChar("AR", AR); ar.serializeChar("D1R", D1R); ar.serializeChar("D2R", D2R); ar.serializeChar("RC", RC); ar.serializeChar("RR", RR); } ar.serialize("bits", bits); ar.serialize("active", active); ar.serialize("state", state); ar.serialize("lfo_active", lfo_active); // Recalculate redundant state if (ar.isLoader()) { step = calcStep(OCT, FN); } // This old comment is NOT completely true: // Older version also had "env_vol_step" and "env_vol_lim" but those // members were nowhere used, so removed those in the current // version (it's ok to remove members from the savestate without // updating the version number). // When you remove member variables without increasing the version // number, new openMSX executables can still read old savestates. And // if you try to load a new savestate in an old openMSX version you do // get a (cryptic) error message. But if the version number is // increased the error message is much clearer. } // version 1: initial version // version 2: loadTime and busyTime moved to MSXMoonSound class // version 3: memadr cannot be restored from register values template void YMF278::serialize(Archive& ar, unsigned version) { ar.serialize("slots", slots); ar.serialize("eg_cnt", eg_cnt); ar.serialize_blob("ram", ram.data(), ramSize); ar.serialize_blob("registers", regs, sizeof(regs)); if (ar.versionAtLeast(version, 3)) { // must come after 'regs' ar.serialize("memadr", memadr); } else { assert(ar.isLoader()); // Old formats didn't store 'memadr' so we also can't magically // restore the correct value. The best we can do is restore the // last set address. regs[3] &= 0x3F; // mask upper two bits memadr = (regs[3] << 16) | (regs[4] << 8) | regs[5]; } // TODO restore more state from registers static const byte rewriteRegs[] = { 0xf8, // fm_l, fm_r 0xf9, // pcm_l, pcm_r }; if (ar.isLoader()) { EmuTime::param time = motherBoard.getCurrentTime(); for (auto r : rewriteRegs) { writeRegDirect(r, regs[r], time); } } } INSTANTIATE_SERIALIZE_METHODS(YMF278); // class DebugRegisters YMF278::DebugRegisters::DebugRegisters(MSXMotherBoard& motherBoard, const std::string& name) : SimpleDebuggable(motherBoard, name + " regs", "OPL4 registers", 0x100) { } byte YMF278::DebugRegisters::read(unsigned address) { auto& ymf278 = OUTER(YMF278, debugRegisters); return ymf278.peekReg(address); } void YMF278::DebugRegisters::write(unsigned address, byte value, EmuTime::param time) { auto& ymf278 = OUTER(YMF278, debugRegisters); ymf278.writeReg(address, value, time); } // class DebugMemory YMF278::DebugMemory::DebugMemory(MSXMotherBoard& motherBoard, const std::string& name) : SimpleDebuggable(motherBoard, name + " mem", "OPL4 memory (includes both ROM and RAM)", 0x400000) // 4MB { } byte YMF278::DebugMemory::read(unsigned address) { auto& ymf278 = OUTER(YMF278, debugMemory); return ymf278.readMem(address); } void YMF278::DebugMemory::write(unsigned address, byte value) { auto& ymf278 = OUTER(YMF278, debugMemory); ymf278.writeMem(address, value); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/sound/YMF278.hh000066400000000000000000000062121257557151200175740ustar00rootroot00000000000000#ifndef YMF278_HH #define YMF278_HH #include "ResampledSoundDevice.hh" #include "SimpleDebuggable.hh" #include "Rom.hh" #include "MemBuffer.hh" #include "EmuTime.hh" #include "openmsx.hh" #include "serialize_meta.hh" #include namespace openmsx { class DeviceConfig; class YMF278 final : public ResampledSoundDevice { public: YMF278(const std::string& name, int ramSize, const DeviceConfig& config); ~YMF278(); void clearRam(); void reset(EmuTime::param time); void writeReg(byte reg, byte data, EmuTime::param time); byte readReg(byte reg); byte peekReg(byte reg) const; byte readMem(unsigned address) const; void writeMem(unsigned address, byte value); template void serialize(Archive& ar, unsigned version); private: class Slot { public: Slot(); void reset(); int compute_rate(int val) const; unsigned decay_rate(int num, int sample_rate); void envelope_next(int sample_rate); inline int compute_vib() const; inline int compute_am() const; void set_lfo(int newlfo); template void serialize(Archive& ar, unsigned version); unsigned startaddr; unsigned loopaddr; unsigned endaddr; unsigned step; // fixed-point frequency step // invariant: step == calcStep(OCT, FN) unsigned stepptr; // fixed-point pointer into the sample unsigned pos; short sample1, sample2; int env_vol; int lfo_cnt; int lfo_step; int lfo_max; int DL; short wave; // wavetable number short FN; // f-number TODO store 'FN | 1024'? char OCT; // octave [0..15] TODO store sign-extended? char PRVB; // pseudo-reverb char LD; // level direct char TL; // total level char pan; // panpot char lfo; // LFO char vib; // vibrato char AM; // AM level char AR; char D1R; char D2R; char RC; // rate correction char RR; byte bits; // width of the samples bool active; // slot keyed on byte state; bool lfo_active; }; // SoundDevice void generateChannels(int** bufs, unsigned num) override; void writeRegDirect(byte reg, byte data, EmuTime::param time); unsigned getRamAddress(unsigned addr) const; short getSample(Slot& op); void advance(); bool anyActive(); void keyOnHelper(Slot& slot); MSXMotherBoard& motherBoard; struct DebugRegisters final : SimpleDebuggable { DebugRegisters(MSXMotherBoard& motherBoard, const std::string& name); byte read(unsigned address) override; void write(unsigned address, byte value, EmuTime::param time) override; } debugRegisters; struct DebugMemory final : SimpleDebuggable { DebugMemory(MSXMotherBoard& motherBoard, const std::string& name); byte read(unsigned address) override; void write(unsigned address, byte value) override; } debugMemory; Slot slots[24]; /** Global envelope generator counter. */ unsigned eg_cnt; int memadr; int fm_l, fm_r; int pcm_l, pcm_r; Rom rom; const unsigned ramSize; MemBuffer ram; /** Precalculated attenuation values with some margin for * envelope and pan levels. */ int volume[256 * 4]; byte regs[256]; }; SERIALIZE_CLASS_VERSION(YMF278::Slot, 3); SERIALIZE_CLASS_VERSION(YMF278, 3); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/sound/generateBlipTable.cc000066400000000000000000000062561257557151200222470ustar00rootroot00000000000000#include #include // This defines the constants // BLIP_SAMPLE_BITS, BLIP_PHASE_BITS, BLIP_IMPULSE_WIDTH #include "BlipConfig.hh" int main() { static const int HALF_SIZE = BLIP_RES / 2 * (BLIP_IMPULSE_WIDTH - 1); double fimpulse[HALF_SIZE + 2 * BLIP_RES]; double* out = &fimpulse[BLIP_RES]; // generate sinc, apply hamming window double oversample = ((4.5 / (BLIP_IMPULSE_WIDTH - 1)) + 0.85); double to_angle = M_PI / (2.0 * oversample * BLIP_RES); double to_fraction = M_PI / (2 * (HALF_SIZE - 1)); for (int i = 0; i < HALF_SIZE; ++i) { double angle = ((i - HALF_SIZE) * 2 + 1) * to_angle; out[i] = sin(angle) / angle; out[i] *= 0.54 - 0.46 * cos((2 * i + 1) * to_fraction); } // need mirror slightly past center for calculation for (int i = 0; i < BLIP_RES; ++i) { out[HALF_SIZE + i] = out[HALF_SIZE - 1 - i]; } // starts at 0 for (int i = 0; i < BLIP_RES; ++i) { fimpulse[i] = 0.0; } // find rescale factor double total = 0.0; for (int i = 0; i < HALF_SIZE; ++i) { total += out[i]; } int kernelUnit = 1 << (BLIP_SAMPLE_BITS - 16); double rescale = kernelUnit / (2.0 * total); // integrate, first difference, rescale, convert to int static const int IMPULSES_SIZE = BLIP_RES * (BLIP_IMPULSE_WIDTH / 2) + 1; static int imp[IMPULSES_SIZE]; double sum = 0.0; double next = 0.0; for (int i = 0; i < IMPULSES_SIZE; ++i) { imp[i] = int(floor((next - sum) * rescale + 0.5)); sum += fimpulse[i]; next += fimpulse[i + BLIP_RES]; } // sum pairs for each phase and add error correction to end of first half for (int p = BLIP_RES; p-- >= BLIP_RES / 2; /* */) { int p2 = BLIP_RES - 2 - p; int error = kernelUnit; for (int i = 1; i < IMPULSES_SIZE; i += BLIP_RES) { error -= imp[i + p ]; error -= imp[i + p2]; } if (p == p2) { // phase = 0.5 impulse uses same half for both sides error /= 2; } imp[IMPULSES_SIZE - BLIP_RES + p] += error; } // dump header plus checking code std::cout << "// This is a generated file. DO NOT EDIT!\n"; std::cout << "\n"; std::cout << "// The table was generated for the following constants:\n"; std::cout << "static_assert(BLIP_PHASE_BITS == " << BLIP_PHASE_BITS << ", \"mismatch, regenerate\");\n"; std::cout << "static_assert(BLIP_SAMPLE_BITS == " << BLIP_SAMPLE_BITS << ", \"mismatch, regenerate\");\n"; std::cout << "static_assert(BLIP_IMPULSE_WIDTH == " << BLIP_IMPULSE_WIDTH << ", \"mismatch, regenerate\");\n"; std::cout << "\n"; // dump actual table: reshuffle values to a more cache friendly order std::cout << "static const int impulses[BLIP_RES][BLIP_IMPULSE_WIDTH] = {\n"; for (int phase = 0; phase < BLIP_RES; ++phase) { const int* imp_fwd = &imp[BLIP_RES - phase]; const int* imp_rev = &imp[phase]; std::cout << "\t{ "; for (int i = 0; i < BLIP_IMPULSE_WIDTH / 2; ++i) { std::cout << imp_fwd[BLIP_RES * i] << ", "; } for (int i = BLIP_IMPULSE_WIDTH / 2 - 1; i > 0; --i) { std::cout << imp_rev[BLIP_RES * i] << ", "; } std::cout << imp_rev[BLIP_RES * 0] << " },\n"; } std::cout << "};\n"; } openMSX-RELEASE_0_12_0/src/sound/generateYM2413OkazakiTable.cc000066400000000000000000000227261257557151200235320ustar00rootroot00000000000000#include #include #include #include #include using namespace std; // Constants #include "YM2413OkazakiConfig.hh" static void makeChecks() { cout << "// This is a generated file. DO NOT EDIT!\n" "\n" "// These tables were generated for the following constants:\n" "static_assert(PM_FP_BITS == " << PM_FP_BITS << ", \"mismatch, regenerate\");\n" "static_assert(EP_FP_BITS == " << EP_FP_BITS << ", \"mismatch, regenerate\");\n" "static_assert(DB_BITS == " << DB_BITS << ", \"mismatch, regenerate\");\n" "static_assert(DB_MUTE == " << DB_MUTE << ", \"mismatch, regenerate\");\n" "static_assert(DBTABLEN == " << DBTABLEN << ", \"mismatch, regenerate\");\n" "//static_assert(DB_STEP == " << DB_STEP << ", \"mismatch, regenerate\");\n" "//static_assert(EG_STEP == " << EG_STEP << ", \"mismatch, regenerate\");\n" "static_assert(PG_BITS == " << PG_BITS << ", \"mismatch, regenerate\");\n" "static_assert(PG_WIDTH == " << PG_WIDTH << ", \"mismatch, regenerate\");\n" "static_assert(EG_BITS == " << EG_BITS << ", \"mismatch, regenerate\");\n" "static_assert(DB2LIN_AMP_BITS == " << DB2LIN_AMP_BITS << ", \"mismatch, regenerate\");\n" "static_assert(LFO_AM_TAB_ELEMENTS == " << LFO_AM_TAB_ELEMENTS << ", \"mismatch, regenerate\");\n" "\n"; } template static void formatTable(const T (&tab)[N], unsigned columns, int width) { assert((N % columns) == 0); for (unsigned i = 0; i < N; ++i) { if ((i % columns) == 0) cout << '\t'; cout << setw(width) << tab[i] << (((i % columns) != (columns - 1)) ? "," : ",\n"); } cout << "};\n" "\n"; } template static void formatTable2(const T (&tab)[M][N], int width, int newLines, int columns) { assert((N % columns) == 0); for (unsigned i = 0; i < M; ++i) { if (i && ((i % newLines) == 0)) cout << '\n'; cout << "\t{ "; for (unsigned j = 0; j < N; ++j) { if (j && ((j % columns) == 0)) cout << "\n\t "; cout << setw(width) << tab[i][j]; if (j != (N - 1)) cout << ','; } cout << " },\n"; } cout << "};\n" "\n"; } // Sawtooth function with amplitude 1 and period 1. static inline float saw(float phase) { if (phase < 0.25f) { return phase * 4.0f; } else if (phase < 0.75f) { return 2.0f - (phase * 4.0f); } else { return -4.0f + (phase * 4.0f); } } static void makePmTable() { cout << "// LFO Phase Modulation table (copied from Burczynski core)\n" "static const signed char pmTable[8][8] =\n" "{\n" " { 0, 0, 0, 0, 0, 0, 0, 0, }, // FNUM = 000xxxxxx\n" " { 0, 0, 1, 0, 0, 0,-1, 0, }, // FNUM = 001xxxxxx\n" " { 0, 1, 2, 1, 0,-1,-2,-1, }, // FNUM = 010xxxxxx\n" " { 0, 1, 3, 1, 0,-1,-3,-1, }, // FNUM = 011xxxxxx\n" " { 0, 2, 4, 2, 0,-2,-4,-2, }, // FNUM = 100xxxxxx\n" " { 0, 2, 5, 2, 0,-2,-5,-2, }, // FNUM = 101xxxxxx\n" " { 0, 3, 6, 3, 0,-3,-6,-3, }, // FNUM = 110xxxxxx\n" " { 0, 3, 7, 3, 0,-3,-7,-3, }, // FNUM = 111xxxxxx\n" "};\n" "\n"; } static void makeDB2LinTable() { int dB2LinTab[2 * DBTABLEN]; for (int i = 0; i < DB_MUTE; ++i) { dB2LinTab[i] = int(float((1 << DB2LIN_AMP_BITS) - 1) * powf(10, -float(i) * DB_STEP / 20)); } dB2LinTab[DB_MUTE - 1] = 0; for (int i = DB_MUTE; i < DBTABLEN; ++i) { dB2LinTab[i] = 0; } for (int i = 0; i < DBTABLEN; ++i) { dB2LinTab[i + DBTABLEN] = -dB2LinTab[i]; } cout << "// dB to linear table (used by Slot)\n" "// dB(0 .. DB_MUTE-1) -> linear(0 .. DB2LIN_AMP_WIDTH)\n" "// indices in range:\n" "// [0, DB_MUTE ) actual values, from max to min\n" "// [DB_MUTE, DBTABLEN) filled with min val (to allow some overflow in index)\n" "// [DBTABLEN, 2*DBTABLEN) as above but for negative output values\n" "static int dB2LinTab[2 * DBTABLEN] = {\n"; formatTable(dB2LinTab, 8, 5); } static void makeAdjustTable() { unsigned AR_ADJUST_TABLE[1 << EG_BITS]; AR_ADJUST_TABLE[0] = (1 << EG_BITS) - 1; for (int i = 1; i < (1 << EG_BITS); ++i) { AR_ADJUST_TABLE[i] = unsigned(float(1 << EG_BITS) - 1 - ((1 << EG_BITS) - 1) * ::logf(float(i)) / ::logf(127.0f)); } cout << "// Linear to Log curve conversion table (for Attack rate)\n" "static unsigned AR_ADJUST_TABLE[1 << EG_BITS] = {\n"; formatTable(AR_ADJUST_TABLE, 8, 4); } static void makeTllTable() { // Processed version of Table III-5 from the Application Manual. static const unsigned kltable[16] = { 0, 24, 32, 37, 40, 43, 45, 47, 48, 50, 51, 52, 53, 54, 55, 56 }; // Note: KL [0..3] results in {0.0, 1.5, 3.0, 6.0} dB/oct. // This is different from Y8950 and YMF262 which have {0, 3, 1.5, 6}. // (2nd and 3rd elements are swapped). Verified on real YM2413. unsigned tllTable[4][16 * 8]; for (unsigned freq = 0; freq < 16 * 8; ++freq) { unsigned fnum = freq & 15; unsigned block = freq / 16; int tmp = 2 * kltable[fnum] - 16 * (7 - block); for (unsigned KL = 0; KL < 4; ++KL) { tllTable[KL][freq] = (tmp <= 0 || KL == 0) ? 0 : (tmp >> (3 - KL)); assert(tllTable[KL][freq] <= 112); } } cout << "// KSL + TL Table values are in range [0, 112]\n" << "static byte tllTable[4][16 * 8] = {\n"; formatTable2(tllTable, 3, 99, 16); } // lin(+0.0 .. +1.0) to dB(DB_MUTE-1 .. 0) static int lin2db(float d) { return (d == 0) ? DB_MUTE - 1 : std::min(-int(20.0f * log10f(d) / DB_STEP), DB_MUTE - 1); // 0 - 127 } // Sin Table static void makeSinTable() { unsigned fullsintable[PG_WIDTH]; unsigned halfsintable[PG_WIDTH]; for (int i = 0; i < PG_WIDTH / 4; ++i) { fullsintable[i] = lin2db(sinf(float(2.0 * M_PI) * i / PG_WIDTH)); } for (int i = 0; i < PG_WIDTH / 4; ++i) { fullsintable[PG_WIDTH / 2 - 1 - i] = fullsintable[i]; } for (int i = 0; i < PG_WIDTH / 2; ++i) { fullsintable[PG_WIDTH / 2 + i] = DBTABLEN + fullsintable[i]; } for (int i = 0; i < PG_WIDTH / 2; ++i) { halfsintable[i] = fullsintable[i]; } for (int i = PG_WIDTH / 2; i < PG_WIDTH; ++i) { halfsintable[i] = fullsintable[0]; } cout << "// WaveTable for each envelope amp\n" "// values are in range [0, DB_MUTE) (for positive values)\n" "// or [0, DB_MUTE) + DBTABLEN (for negative values)\n" "static unsigned fullsintable[PG_WIDTH] = {\n"; formatTable(fullsintable, 8, 5); cout << "static unsigned halfsintable[PG_WIDTH] = {\n"; formatTable(halfsintable, 8, 5); cout << "static unsigned* waveform[2] = {fullsintable, halfsintable};\n" "\n"; } static void makeDphaseDRTable() { int dphaseDRTable[16][16]; for (unsigned Rks = 0; Rks < 16; ++Rks) { dphaseDRTable[Rks][0] = 0; for (unsigned DR = 1; DR < 16; ++DR) { unsigned RM = std::min(DR + (Rks >> 2), 15u); unsigned RL = Rks & 3; dphaseDRTable[Rks][DR] = ((RL + 4) << EP_FP_BITS) >> (16 - RM); } } cout << "// Phase incr table for attack, decay and release\n" "// note: original code had indices swapped. It also had\n" "// a separate table for attack\n" "// 17.15 fixed point\n" "static int dphaseDRTable[16][16] = {\n"; formatTable2(dphaseDRTable, 6, 999, 8); } static void makeLfoAmTable() { cout << "// LFO Amplitude Modulation table (verified on real YM3812)\n" "static const unsigned char lfo_am_table[LFO_AM_TAB_ELEMENTS] = {\n" " 0,0,0,0,0,0,0,\n" " 1,1,1,1,\n" " 2,2,2,2,\n" " 3,3,3,3,\n" " 4,4,4,4,\n" " 5,5,5,5,\n" " 6,6,6,6,\n" " 7,7,7,7,\n" " 8,8,8,8,\n" " 9,9,9,9,\n" " 10,10,10,10,\n" " 11,11,11,11,\n" " 12,12,12,12,\n" " 13,13,13,13,\n" " 14,14,14,14,\n" " 15,15,15,15,\n" " 16,16,16,16,\n" " 17,17,17,17,\n" " 18,18,18,18,\n" " 19,19,19,19,\n" " 20,20,20,20,\n" " 21,21,21,21,\n" " 22,22,22,22,\n" " 23,23,23,23,\n" " 24,24,24,24,\n" " 25,25,25,25,\n" " 26,26,26,\n" " 25,25,25,25,\n" " 24,24,24,24,\n" " 23,23,23,23,\n" " 22,22,22,22,\n" " 21,21,21,21,\n" " 20,20,20,20,\n" " 19,19,19,19,\n" " 18,18,18,18,\n" " 17,17,17,17,\n" " 16,16,16,16,\n" " 15,15,15,15,\n" " 14,14,14,14,\n" " 13,13,13,13,\n" " 12,12,12,12,\n" " 11,11,11,11,\n" " 10,10,10,10,\n" " 9,9,9,9,\n" " 8,8,8,8,\n" " 7,7,7,7,\n" " 6,6,6,6,\n" " 5,5,5,5,\n" " 4,4,4,4,\n" " 3,3,3,3,\n" " 2,2,2,2,\n" " 1,1,1,1,\n" "};\n" "\n"; } static void makeSusLevTable() { unsigned slTable[16]; for (int i = 0; i < 16; ++i) { float x = (i == 15) ? 48.0f : (3.0f * i); slTable[i] = int(x / EG_STEP) << EP_FP_BITS; } cout << "// Sustain level (17.15 fixed point)\n" "static const unsigned slTable[16] = {\n"; formatTable(slTable, 4, 8); } static void makeMLTable() { cout << "// ML-table\n" "static const byte mlTable[16] = {\n" " 1, 1*2, 2*2, 3*2, 4*2, 5*2, 6*2, 7*2,\n" " 8*2, 9*2, 10*2, 10*2, 12*2, 12*2, 15*2, 15*2\n" "};\n" "\n"; } int main() { makeChecks(); makePmTable(); makeDB2LinTable(); makeAdjustTable(); makeTllTable(); makeSinTable(); makeDphaseDRTable(); makeLfoAmTable(); makeSusLevTable(); makeMLTable(); } openMSX-RELEASE_0_12_0/src/thread/000077500000000000000000000000001257557151200164755ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/src/thread/.gitignore000066400000000000000000000001761257557151200204710ustar00rootroot00000000000000/*.ROM /*.bb /*.bbg /*.da /*.dsk /*.prof /*.rom /*.rpo /*.swp /*.vim /.debug /.kdbgrc.openmsx /.xvpics /gmon.out /GNUmakefile openMSX-RELEASE_0_12_0/src/thread/Thread.cc000066400000000000000000000031351257557151200202150ustar00rootroot00000000000000#include "Thread.hh" #include "MSXException.hh" #include "StringOp.hh" #include "unreachable.hh" #include #include #include namespace openmsx { static unsigned mainThreadId = unsigned(-1); void Thread::setMainThread() { assert(mainThreadId == unsigned(-1)); mainThreadId = SDL_ThreadID(); } bool Thread::isMainThread() { assert(mainThreadId != unsigned(-1)); return mainThreadId == SDL_ThreadID(); } Thread::Thread(Runnable* runnable_) : runnable(runnable_) , thread(nullptr) { } Thread::~Thread() { stop(); } void Thread::start() { assert(!thread); thread = SDL_CreateThread(startThread, runnable); if (!thread) { throw FatalError(StringOp::Builder() << "Unable to create thread: " << SDL_GetError()); } } void Thread::stop() { if (thread) { SDL_KillThread(thread); thread = nullptr; } } // TODO: A version with timeout would be useful. // After the timeout expires, the method would return false. // Alternatively, stop() is called if the thread does not end // within the given timeout. void Thread::join() { if (thread) { SDL_WaitThread(thread, nullptr); thread = nullptr; } } int Thread::startThread(void* runnable) { try { static_cast(runnable)->run(); } catch (FatalError& e) { std::cerr << "Fatal error in subthread: " << e.getMessage() << std::endl; UNREACHABLE; } catch (MSXException& e) { std::cerr << "Uncaught exception in subthread: " << e.getMessage() << std::endl; UNREACHABLE; } // don't catch(..), thread cancelation seems to depend on it return 0; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/thread/Thread.hh000066400000000000000000000025171257557151200202320ustar00rootroot00000000000000#ifndef THREAD_HH #define THREAD_HH #include "noncopyable.hh" struct SDL_Thread; namespace openmsx { class Runnable : private noncopyable { public: virtual void run() = 0; protected: ~Runnable() {} }; class Thread : private noncopyable { public: /** Create a new thread. * @param runnable Object those run() method will be invoked by * the created thread when it starts running. */ explicit Thread(Runnable* runnable); ~Thread(); /** Start this thread. * It is not allowed to call this method on a running thread. */ void start(); /** Destroys this thread. * Only use this method as a last resort, because it kills the thread * with no regard for locks or resources the thread may be holding, * I/O it is performing etc. * If this method is called on a stopped thread, nothing happens. */ void stop(); /** Waits for this thread to terminate. */ void join(); // For debugging only /** Store ID of the main thread, should be called exactly once from * the main thread. */ static void setMainThread(); /** Returns true when called from the main thread. */ static bool isMainThread(); private: /** Helper function to start a thread (SDL is plain C). */ static int startThread(void* runnable); Runnable* runnable; SDL_Thread* thread; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/thread/Timer.cc000066400000000000000000000042711257557151200200700ustar00rootroot00000000000000#include "Timer.hh" #include "systemfuncs.hh" #if HAVE_USLEEP #include #endif #include #include namespace openmsx { namespace Timer { // non-static to avoid unused function warning inline uint64_t getSDLTicks() { return static_cast(SDL_GetTicks()) * 1000; } uint64_t getTime() { static uint64_t lastTime = 0; uint64_t now; #ifndef _MSC_VER using namespace std::chrono; now = duration_cast( steady_clock::now().time_since_epoch()).count(); #else // Visual studio 2012 does offer std::chrono, but unfortunately it's // buggy and low resolution. So for now we still use SDL. See also: // http://stackoverflow.com/questions/11488075/vs11-is-steady-clock-steady // https://connect.microsoft.com/VisualStudio/feedback/details/753115/ now = static_cast(SDL_GetTicks()) * 1000; #endif // Other parts of openMSX may crash if this function ever returns a // value that is less than a previously returned value. Hence this // extra check. // SDL_GetTicks() is not guaranteed to return monotonic values. // steady_clock OTOH should be monotonic. It's implemented in terms of // clock_gettime(CLOCK_MONOTONIC). Unfortunately in older linux // versions we've seen buggy implementation that once in a while did // return time points slightly in the past. if (now < lastTime) return lastTime; lastTime = now; return now; } /*#if defined _WIN32 static void CALLBACK timerCallback(unsigned int, unsigned int, unsigned long eventHandle, unsigned long, unsigned long) { SetEvent((HANDLE)eventHandle); } #endif*/ void sleep(uint64_t us) { /*#if defined _WIN32 us /= 1000; if (us > 0) { static HANDLE timerEvent = nullptr; if (!timerEvent) { timerEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); } UINT id = timeSetEvent(us, 1, timerCallback, (DWORD)timerEvent, TIME_ONESHOT); WaitForSingleObject(timerEvent, INFINITE); timeKillEvent(id); } */ #if HAVE_USLEEP usleep(us); #else SDL_Delay(unsigned(us / 1000)); #endif } } // namespace Timer } // namespace openmsx openMSX-RELEASE_0_12_0/src/thread/Timer.hh000066400000000000000000000006361257557151200201030ustar00rootroot00000000000000#ifndef TIMER_HH #define TIMER_HH #include namespace openmsx { namespace Timer { /** Get current (real) time in us. Absolute value has no meaning. */ uint64_t getTime(); /** Sleep for the specified amount of time (in us). It is possible * that this method sleeps longer or shorter than the requested time. */ void sleep(uint64_t us); } // namespace Timer } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/utils/000077500000000000000000000000001257557151200163665ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/src/utils/.gitignore000066400000000000000000000000151257557151200203520ustar00rootroot00000000000000/GNUmakefile openMSX-RELEASE_0_12_0/src/utils/AlignedBuffer.hh000066400000000000000000000071701257557151200214110ustar00rootroot00000000000000#ifndef ALIGNEDBUFFER_HH #define ALIGNEDBUFFER_HH #include "alignof.hh" #include #include #include #include namespace openmsx { // Hack for visual studio, remove when we can use c++11 alignas/alignof #ifdef _MSC_VER #ifdef _WIN64 #define MAX_ALIGN_V 16 #else #define MAX_ALIGN_V 8 #endif #else // This only works starting from gcc-4-9 // #define MAX_ALIGN_V ALIGNOF(MAX_ALIGN_T) #ifdef __x86_64 #define MAX_ALIGN_V 16 #else #define MAX_ALIGN_V 8 #endif #endif // Interface for an aligned buffer. // This type doesn't itself provide any storage, it only 'refers' to some // storage. In that sense it is very similar in usage to a 'uint8_t*'. // // For example: // void f1(uint8_t* buf) { // ... uint8_t x = buf[7]; // ... buf[4] = 10; // ... uint8_t* begin = buf; // ... uint8_t* end = buf + 12; // } // void f2(AlignedBuffer& buf) { // // The exact same syntax as above, the only difference is that here // // the compiler knows at compile-time the alignment of 'buf', while // // above the compiler must assume worst case alignment. // } #ifdef _MSC_VER // TODO in the future use the c++11 'alignas' feature __declspec (align(MAX_ALIGN_V)) #endif class AlignedBuffer { public: static const size_t ALIGNMENT = MAX_ALIGN_V; operator uint8_t*() { return p(); } operator const uint8_t*() const { return p(); } uint8_t* operator+(ptrdiff_t i) { return p() + i; }; const uint8_t* operator+(ptrdiff_t i) const { return p() + i; }; uint8_t& operator[](int i) { return *(p() + i); } const uint8_t& operator[](int i) const { return *(p() + i); } uint8_t& operator[](unsigned int i) { return *(p() + i); } const uint8_t& operator[](unsigned int i) const { return *(p() + i); } uint8_t& operator[](long i) { return *(p() + i); } const uint8_t& operator[](long i) const { return *(p() + i); } uint8_t& operator[](unsigned long i) { return *(p() + i); } const uint8_t& operator[](unsigned long i) const { return *(p() + i); } private: uint8_t* p() { return reinterpret_cast< uint8_t*>(this); } const uint8_t* p() const { return reinterpret_cast(this); } } #ifndef _MSC_VER __attribute__((__aligned__((MAX_ALIGN_V)))) #endif ; static_assert(ALIGNOF(AlignedBuffer) == AlignedBuffer::ALIGNMENT, "must be aligned"); // Provide actual storage for the AlignedBuffer // A possible alternative is to use a union. template class AlignedByteArray : public AlignedBuffer { public: size_t size() const { return N; } uint8_t* data() { return dat; } const uint8_t* data() const { return dat; } private: uint8_t dat[N]; } // Repeat alignment because Clang 3.2svn does not inherit it from an empty // base class. #ifndef _MSC_VER __attribute__((__aligned__((MAX_ALIGN_V)))) #endif ; static_assert(ALIGNOF(AlignedByteArray<13>) == AlignedBuffer::ALIGNMENT, "must be aligned"); static_assert(sizeof(AlignedByteArray<32>) == 32, "we rely on the empty-base optimization"); /** Cast one pointer type to another pointer type. * When asserts are enabled this checks whether the original pointer is * properly aligned to point to the destination type. */ template static inline T aligned_cast(void* p) { static_assert(std::is_pointer::value, "can only perform aligned_cast on pointers"); assert((reinterpret_cast(p) % ALIGNOF(typename std::remove_pointer::type)) == 0); return reinterpret_cast(p); } } //namespace openmsx #endif openMSX-RELEASE_0_12_0/src/utils/AltSpaceSuppressor.cc000066400000000000000000000103331257557151200224770ustar00rootroot00000000000000#ifdef _WIN32 #include "AltSpaceSuppressor.hh" #include "MSXException.hh" #include "openmsx.hh" #include // This module exists to fix the following bug: // [ 1206036 ] Windows Control Menu while using ALT // http://sourceforge.net/tracker/index.php?func=detail&aid=1206036&group_id=38274&atid=421861 // // The Windows control menu will pop up by default in a window when the user // presses LALT+SPACE or RALT+SPACE. Programmatically, this shows up to the // application as a series of Windows messages: the respective WM_SYSKEYDOWN // events for ALT and SPACE, followed by a WM_SYSCOMMAND message whose default // processing by the OS results in the control menu itself appearing. // // The fix, at the application level, is to handle that WM_SYSCOMMAND message // (effectively swallowing it) and thus to prevent it from being forwarded to // Win32's DefWindowProc. // // We first attempted to fix this by interacting with SDL's messaging // subsystem. The hope was that by calling SDL_SetEventFilter with a custom // event filter, we could intercept and silence the offending WM_SYSCOMMAND // message. Unfortunately, SDL allows an application listening for // SDL_SYSWMEVENT events to see those messages, but it does _not_ allow the // application to indicate that the event was handled and should not be // processed further. // // More concretely, the SDL code as of 1.2.13 in SDL_dibevents.c (circa line // 250) ignores the return value from the event filter for WM events and // ultimately forwards the message to DefWindowProc anyway. Further, in // SDL_dx5events.c line 532, a comment implies that this is by design: // // It would be better to allow the application to // decide whether or not to blow these off, but the // semantics of SDL_PrivateSysWMEvent() don't allow // the application that choice. // // We filed bug 686 (http://bugzilla.libsdl.org/show_bug.cgi?id=686) on the SDL // developers. If they respond positively, we may want to return to a solution // along those lines. // // Our second attempt at a fix, the one represented by the code below, involves // registering our own Windows proc, filtering out the offending WM_SYSCOMMAND // event, and letting the rest go through. // // This is currently integrated into openmsx inside the SDLVideoSystem class. namespace openmsx { WindowLongPtrStacker::WindowLongPtrStacker(int index, LONG_PTR value) : hWnd(nullptr) , nIndex(index) , newValue(value) { } void WindowLongPtrStacker::Push(HWND hWndArg) { assert(hWndArg); hWnd = hWndArg; SetLastError(0); oldValue = SetWindowLongPtr(hWnd, nIndex, newValue); if (!oldValue && GetLastError()) { throw MSXException("SetWindowLongPtr failed"); } } void WindowLongPtrStacker::Pop() { assert(hWnd); if (oldValue) { SetLastError(0); LONG_PTR removedValue = SetWindowLongPtr(hWnd, nIndex, oldValue); if (!removedValue && GetLastError()) { throw MSXException("SetWindowLongPtr failed"); } assert(removedValue == newValue); } } LONG_PTR WindowLongPtrStacker::GetOldValue() { return oldValue; } WindowLongPtrStacker AltSpaceSuppressor::procStacker( GWLP_WNDPROC, reinterpret_cast(InterceptorWndProc)); void AltSpaceSuppressor::Start(HWND hWnd) { procStacker.Push(hWnd); } void AltSpaceSuppressor::Stop() { procStacker.Pop(); } LRESULT AltSpaceSuppressor::InterceptorWndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { LRESULT lResult = 0; if (SuppressAltSpace(hWnd, message, wParam, lParam, &lResult)) { // Message processed return lResult; } // Message not processed, use default handling auto nextWndProc = reinterpret_cast(procStacker.GetOldValue()); if (nextWndProc) { // Forward to the window proc we replaced return CallWindowProc(nextWndProc, hWnd, message, wParam, lParam); } // Let the system default window proc handle the message return DefWindowProc(hWnd, message, wParam, lParam); } bool AltSpaceSuppressor::SuppressAltSpace( HWND /*hWnd*/, UINT message, WPARAM wParam, LPARAM lParam, LRESULT* outResult) { if (message == WM_SYSCOMMAND && wParam == SC_KEYMENU && lParam == LPARAM(' ')) { // Suppressed ALT+SPACE message *outResult = 0; return true; // processed } return false; // not processed } } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/utils/AltSpaceSuppressor.hh000066400000000000000000000015101257557151200225060ustar00rootroot00000000000000#ifndef ALTSPACESUPPRESSOR_HH #define ALTSPACESUPPRESSOR_HH #ifdef _WIN32 #include namespace openmsx { // Utility class that stacks a WindowLongPtr value class WindowLongPtrStacker { public: WindowLongPtrStacker(int index, LONG_PTR value); void Push(HWND hWndArg); void Pop(); LONG_PTR GetOldValue(); private: HWND hWnd; int nIndex; LONG_PTR oldValue, newValue; }; // Suppressor of ALT+SPACE windows messages class AltSpaceSuppressor { public: static void Start(HWND hWnd); static void Stop(); private: static WindowLongPtrStacker procStacker; static LRESULT CALLBACK InterceptorWndProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); static bool SuppressAltSpace( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT* outResult); }; } // namespace openmsx #endif #endif openMSX-RELEASE_0_12_0/src/utils/Base64.cc000066400000000000000000000061431257557151200177250ustar00rootroot00000000000000#include "Base64.hh" #include "xrange.hh" #include #include #include namespace Base64 { using std::string; static inline char encode(uint8_t c) { static const char* const base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; assert(c < 64); return base64_chars[c]; } static inline uint8_t decode(unsigned char c) { if ('A' <= c && c <= 'Z') { return c - 'A'; } else if ('a' <= c && c <= 'z') { return c - 'a' + 26; } else if ('0' <= c && c <= '9') { return c - '0' + 52; } else if (c == '+') { return 62; } else if (c == '/') { return 63; } else { return uint8_t(-1); } } string encode(const void* input_, size_t inSize) { static const int CHUNKS = 19; static const int IN_CHUNKS = 3 * CHUNKS; static const int OUT_CHUNKS = 4 * CHUNKS; // 76 chars per line auto input = static_cast(input_); auto outSize = ((inSize + (IN_CHUNKS - 1)) / IN_CHUNKS) * (OUT_CHUNKS + 1); // overestimation string ret(outSize, 0); // too big size_t out = 0; while (inSize) { if (out) ret[out++] = '\n'; auto n2 = std::min(IN_CHUNKS, inSize); auto n = unsigned(n2); for (/**/; n >= 3; n -= 3) { ret[out++] = encode( (input[0] & 0xfc) >> 2); ret[out++] = encode(((input[0] & 0x03) << 4) + ((input[1] & 0xf0) >> 4)); ret[out++] = encode(((input[1] & 0x0f) << 2) + ((input[2] & 0xc0) >> 6)); ret[out++] = encode( (input[2] & 0x3f) >> 0); input += 3; } if (n) { uint8_t buf3[3] = { 0, 0, 0 }; for (unsigned i = 0; i < n; ++i) { buf3[i] = input[i]; } uint8_t buf4[4]; buf4[0] = (buf3[0] & 0xfc) >> 2; buf4[1] = ((buf3[0] & 0x03) << 4) + ((buf3[1] & 0xf0) >> 4); buf4[2] = ((buf3[1] & 0x0f) << 2) + ((buf3[2] & 0xc0) >> 6); buf4[3] = (buf3[2] & 0x3f) >> 0; for (unsigned j = 0; (j < n + 1); ++j) { ret[out++] = encode(buf4[j]); } for (/**/; n < 3; ++n) { ret[out++] = '='; } } inSize -= n2; } assert(outSize >= out); ret.resize(out); // shrink to correct size return ret; } string decode(const string& input) { auto inSize = input.size(); auto outSize = (inSize * 3 + 3) / 4; // overestimation string ret(outSize, 0); // too big unsigned i = 0; size_t out = 0; uint8_t buf4[4]; for (auto in : xrange(inSize)) { uint8_t d = decode(input[in]); if (d == uint8_t(-1)) continue; buf4[i++] = d; if (i == 4) { i = 0; ret[out++] = char(((buf4[0] & 0xff) << 2) + ((buf4[1] & 0x30) >> 4)); ret[out++] = char(((buf4[1] & 0x0f) << 4) + ((buf4[2] & 0x3c) >> 2)); ret[out++] = char(((buf4[2] & 0x03) << 6) + ((buf4[3] & 0xff) >> 0)); } } if (i) { for (unsigned j = i; j < 4; ++j) { buf4[j] = 0; } uint8_t buf3[3]; buf3[0] = ((buf4[0] & 0xff) << 2) + ((buf4[1] & 0x30) >> 4); buf3[1] = ((buf4[1] & 0x0f) << 4) + ((buf4[2] & 0x3c) >> 2); buf3[2] = ((buf4[2] & 0x03) << 6) + ((buf4[3] & 0xff) >> 0); for (unsigned j = 0; (j < i - 1); ++j) { ret[out++] = buf3[j]; } } assert(outSize >= out); ret.resize(out); // shrink to correct size return ret; } } // namespace Base64 openMSX-RELEASE_0_12_0/src/utils/Base64.hh000066400000000000000000000002701257557151200177320ustar00rootroot00000000000000#ifndef BASE64_HH #define BASE64_HH #include namespace Base64 { std::string encode(const void* input, size_t len); std::string decode(const std::string& input); } #endif openMSX-RELEASE_0_12_0/src/utils/CRC16.cc000066400000000000000000000432541257557151200174630ustar00rootroot00000000000000#include "CRC16.hh" namespace openmsx { // Accelerator table to compute the CRC (upto) 64 bits at a time // (total table size is 4kB) const uint16_t CRC16::tab[8][256] = { { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0, }, { 0x0000, 0x3331, 0x6662, 0x5553, 0xCCC4, 0xFFF5, 0xAAA6, 0x9997, 0x89A9, 0xBA98, 0xEFCB, 0xDCFA, 0x456D, 0x765C, 0x230F, 0x103E, 0x0373, 0x3042, 0x6511, 0x5620, 0xCFB7, 0xFC86, 0xA9D5, 0x9AE4, 0x8ADA, 0xB9EB, 0xECB8, 0xDF89, 0x461E, 0x752F, 0x207C, 0x134D, 0x06E6, 0x35D7, 0x6084, 0x53B5, 0xCA22, 0xF913, 0xAC40, 0x9F71, 0x8F4F, 0xBC7E, 0xE92D, 0xDA1C, 0x438B, 0x70BA, 0x25E9, 0x16D8, 0x0595, 0x36A4, 0x63F7, 0x50C6, 0xC951, 0xFA60, 0xAF33, 0x9C02, 0x8C3C, 0xBF0D, 0xEA5E, 0xD96F, 0x40F8, 0x73C9, 0x269A, 0x15AB, 0x0DCC, 0x3EFD, 0x6BAE, 0x589F, 0xC108, 0xF239, 0xA76A, 0x945B, 0x8465, 0xB754, 0xE207, 0xD136, 0x48A1, 0x7B90, 0x2EC3, 0x1DF2, 0x0EBF, 0x3D8E, 0x68DD, 0x5BEC, 0xC27B, 0xF14A, 0xA419, 0x9728, 0x8716, 0xB427, 0xE174, 0xD245, 0x4BD2, 0x78E3, 0x2DB0, 0x1E81, 0x0B2A, 0x381B, 0x6D48, 0x5E79, 0xC7EE, 0xF4DF, 0xA18C, 0x92BD, 0x8283, 0xB1B2, 0xE4E1, 0xD7D0, 0x4E47, 0x7D76, 0x2825, 0x1B14, 0x0859, 0x3B68, 0x6E3B, 0x5D0A, 0xC49D, 0xF7AC, 0xA2FF, 0x91CE, 0x81F0, 0xB2C1, 0xE792, 0xD4A3, 0x4D34, 0x7E05, 0x2B56, 0x1867, 0x1B98, 0x28A9, 0x7DFA, 0x4ECB, 0xD75C, 0xE46D, 0xB13E, 0x820F, 0x9231, 0xA100, 0xF453, 0xC762, 0x5EF5, 0x6DC4, 0x3897, 0x0BA6, 0x18EB, 0x2BDA, 0x7E89, 0x4DB8, 0xD42F, 0xE71E, 0xB24D, 0x817C, 0x9142, 0xA273, 0xF720, 0xC411, 0x5D86, 0x6EB7, 0x3BE4, 0x08D5, 0x1D7E, 0x2E4F, 0x7B1C, 0x482D, 0xD1BA, 0xE28B, 0xB7D8, 0x84E9, 0x94D7, 0xA7E6, 0xF2B5, 0xC184, 0x5813, 0x6B22, 0x3E71, 0x0D40, 0x1E0D, 0x2D3C, 0x786F, 0x4B5E, 0xD2C9, 0xE1F8, 0xB4AB, 0x879A, 0x97A4, 0xA495, 0xF1C6, 0xC2F7, 0x5B60, 0x6851, 0x3D02, 0x0E33, 0x1654, 0x2565, 0x7036, 0x4307, 0xDA90, 0xE9A1, 0xBCF2, 0x8FC3, 0x9FFD, 0xACCC, 0xF99F, 0xCAAE, 0x5339, 0x6008, 0x355B, 0x066A, 0x1527, 0x2616, 0x7345, 0x4074, 0xD9E3, 0xEAD2, 0xBF81, 0x8CB0, 0x9C8E, 0xAFBF, 0xFAEC, 0xC9DD, 0x504A, 0x637B, 0x3628, 0x0519, 0x10B2, 0x2383, 0x76D0, 0x45E1, 0xDC76, 0xEF47, 0xBA14, 0x8925, 0x991B, 0xAA2A, 0xFF79, 0xCC48, 0x55DF, 0x66EE, 0x33BD, 0x008C, 0x13C1, 0x20F0, 0x75A3, 0x4692, 0xDF05, 0xEC34, 0xB967, 0x8A56, 0x9A68, 0xA959, 0xFC0A, 0xCF3B, 0x56AC, 0x659D, 0x30CE, 0x03FF, }, { 0x0000, 0x3730, 0x6E60, 0x5950, 0xDCC0, 0xEBF0, 0xB2A0, 0x8590, 0xA9A1, 0x9E91, 0xC7C1, 0xF0F1, 0x7561, 0x4251, 0x1B01, 0x2C31, 0x4363, 0x7453, 0x2D03, 0x1A33, 0x9FA3, 0xA893, 0xF1C3, 0xC6F3, 0xEAC2, 0xDDF2, 0x84A2, 0xB392, 0x3602, 0x0132, 0x5862, 0x6F52, 0x86C6, 0xB1F6, 0xE8A6, 0xDF96, 0x5A06, 0x6D36, 0x3466, 0x0356, 0x2F67, 0x1857, 0x4107, 0x7637, 0xF3A7, 0xC497, 0x9DC7, 0xAAF7, 0xC5A5, 0xF295, 0xABC5, 0x9CF5, 0x1965, 0x2E55, 0x7705, 0x4035, 0x6C04, 0x5B34, 0x0264, 0x3554, 0xB0C4, 0x87F4, 0xDEA4, 0xE994, 0x1DAD, 0x2A9D, 0x73CD, 0x44FD, 0xC16D, 0xF65D, 0xAF0D, 0x983D, 0xB40C, 0x833C, 0xDA6C, 0xED5C, 0x68CC, 0x5FFC, 0x06AC, 0x319C, 0x5ECE, 0x69FE, 0x30AE, 0x079E, 0x820E, 0xB53E, 0xEC6E, 0xDB5E, 0xF76F, 0xC05F, 0x990F, 0xAE3F, 0x2BAF, 0x1C9F, 0x45CF, 0x72FF, 0x9B6B, 0xAC5B, 0xF50B, 0xC23B, 0x47AB, 0x709B, 0x29CB, 0x1EFB, 0x32CA, 0x05FA, 0x5CAA, 0x6B9A, 0xEE0A, 0xD93A, 0x806A, 0xB75A, 0xD808, 0xEF38, 0xB668, 0x8158, 0x04C8, 0x33F8, 0x6AA8, 0x5D98, 0x71A9, 0x4699, 0x1FC9, 0x28F9, 0xAD69, 0x9A59, 0xC309, 0xF439, 0x3B5A, 0x0C6A, 0x553A, 0x620A, 0xE79A, 0xD0AA, 0x89FA, 0xBECA, 0x92FB, 0xA5CB, 0xFC9B, 0xCBAB, 0x4E3B, 0x790B, 0x205B, 0x176B, 0x7839, 0x4F09, 0x1659, 0x2169, 0xA4F9, 0x93C9, 0xCA99, 0xFDA9, 0xD198, 0xE6A8, 0xBFF8, 0x88C8, 0x0D58, 0x3A68, 0x6338, 0x5408, 0xBD9C, 0x8AAC, 0xD3FC, 0xE4CC, 0x615C, 0x566C, 0x0F3C, 0x380C, 0x143D, 0x230D, 0x7A5D, 0x4D6D, 0xC8FD, 0xFFCD, 0xA69D, 0x91AD, 0xFEFF, 0xC9CF, 0x909F, 0xA7AF, 0x223F, 0x150F, 0x4C5F, 0x7B6F, 0x575E, 0x606E, 0x393E, 0x0E0E, 0x8B9E, 0xBCAE, 0xE5FE, 0xD2CE, 0x26F7, 0x11C7, 0x4897, 0x7FA7, 0xFA37, 0xCD07, 0x9457, 0xA367, 0x8F56, 0xB866, 0xE136, 0xD606, 0x5396, 0x64A6, 0x3DF6, 0x0AC6, 0x6594, 0x52A4, 0x0BF4, 0x3CC4, 0xB954, 0x8E64, 0xD734, 0xE004, 0xCC35, 0xFB05, 0xA255, 0x9565, 0x10F5, 0x27C5, 0x7E95, 0x49A5, 0xA031, 0x9701, 0xCE51, 0xF961, 0x7CF1, 0x4BC1, 0x1291, 0x25A1, 0x0990, 0x3EA0, 0x67F0, 0x50C0, 0xD550, 0xE260, 0xBB30, 0x8C00, 0xE352, 0xD462, 0x8D32, 0xBA02, 0x3F92, 0x08A2, 0x51F2, 0x66C2, 0x4AF3, 0x7DC3, 0x2493, 0x13A3, 0x9633, 0xA103, 0xF853, 0xCF63, }, { 0x0000, 0x76B4, 0xED68, 0x9BDC, 0xCAF1, 0xBC45, 0x2799, 0x512D, 0x85C3, 0xF377, 0x68AB, 0x1E1F, 0x4F32, 0x3986, 0xA25A, 0xD4EE, 0x1BA7, 0x6D13, 0xF6CF, 0x807B, 0xD156, 0xA7E2, 0x3C3E, 0x4A8A, 0x9E64, 0xE8D0, 0x730C, 0x05B8, 0x5495, 0x2221, 0xB9FD, 0xCF49, 0x374E, 0x41FA, 0xDA26, 0xAC92, 0xFDBF, 0x8B0B, 0x10D7, 0x6663, 0xB28D, 0xC439, 0x5FE5, 0x2951, 0x787C, 0x0EC8, 0x9514, 0xE3A0, 0x2CE9, 0x5A5D, 0xC181, 0xB735, 0xE618, 0x90AC, 0x0B70, 0x7DC4, 0xA92A, 0xDF9E, 0x4442, 0x32F6, 0x63DB, 0x156F, 0x8EB3, 0xF807, 0x6E9C, 0x1828, 0x83F4, 0xF540, 0xA46D, 0xD2D9, 0x4905, 0x3FB1, 0xEB5F, 0x9DEB, 0x0637, 0x7083, 0x21AE, 0x571A, 0xCCC6, 0xBA72, 0x753B, 0x038F, 0x9853, 0xEEE7, 0xBFCA, 0xC97E, 0x52A2, 0x2416, 0xF0F8, 0x864C, 0x1D90, 0x6B24, 0x3A09, 0x4CBD, 0xD761, 0xA1D5, 0x59D2, 0x2F66, 0xB4BA, 0xC20E, 0x9323, 0xE597, 0x7E4B, 0x08FF, 0xDC11, 0xAAA5, 0x3179, 0x47CD, 0x16E0, 0x6054, 0xFB88, 0x8D3C, 0x4275, 0x34C1, 0xAF1D, 0xD9A9, 0x8884, 0xFE30, 0x65EC, 0x1358, 0xC7B6, 0xB102, 0x2ADE, 0x5C6A, 0x0D47, 0x7BF3, 0xE02F, 0x969B, 0xDD38, 0xAB8C, 0x3050, 0x46E4, 0x17C9, 0x617D, 0xFAA1, 0x8C15, 0x58FB, 0x2E4F, 0xB593, 0xC327, 0x920A, 0xE4BE, 0x7F62, 0x09D6, 0xC69F, 0xB02B, 0x2BF7, 0x5D43, 0x0C6E, 0x7ADA, 0xE106, 0x97B2, 0x435C, 0x35E8, 0xAE34, 0xD880, 0x89AD, 0xFF19, 0x64C5, 0x1271, 0xEA76, 0x9CC2, 0x071E, 0x71AA, 0x2087, 0x5633, 0xCDEF, 0xBB5B, 0x6FB5, 0x1901, 0x82DD, 0xF469, 0xA544, 0xD3F0, 0x482C, 0x3E98, 0xF1D1, 0x8765, 0x1CB9, 0x6A0D, 0x3B20, 0x4D94, 0xD648, 0xA0FC, 0x7412, 0x02A6, 0x997A, 0xEFCE, 0xBEE3, 0xC857, 0x538B, 0x253F, 0xB3A4, 0xC510, 0x5ECC, 0x2878, 0x7955, 0x0FE1, 0x943D, 0xE289, 0x3667, 0x40D3, 0xDB0F, 0xADBB, 0xFC96, 0x8A22, 0x11FE, 0x674A, 0xA803, 0xDEB7, 0x456B, 0x33DF, 0x62F2, 0x1446, 0x8F9A, 0xF92E, 0x2DC0, 0x5B74, 0xC0A8, 0xB61C, 0xE731, 0x9185, 0x0A59, 0x7CED, 0x84EA, 0xF25E, 0x6982, 0x1F36, 0x4E1B, 0x38AF, 0xA373, 0xD5C7, 0x0129, 0x779D, 0xEC41, 0x9AF5, 0xCBD8, 0xBD6C, 0x26B0, 0x5004, 0x9F4D, 0xE9F9, 0x7225, 0x0491, 0x55BC, 0x2308, 0xB8D4, 0xCE60, 0x1A8E, 0x6C3A, 0xF7E6, 0x8152, 0xD07F, 0xA6CB, 0x3D17, 0x4BA3, }, { 0x0000, 0xAA51, 0x4483, 0xEED2, 0x8906, 0x2357, 0xCD85, 0x67D4, 0x022D, 0xA87C, 0x46AE, 0xECFF, 0x8B2B, 0x217A, 0xCFA8, 0x65F9, 0x045A, 0xAE0B, 0x40D9, 0xEA88, 0x8D5C, 0x270D, 0xC9DF, 0x638E, 0x0677, 0xAC26, 0x42F4, 0xE8A5, 0x8F71, 0x2520, 0xCBF2, 0x61A3, 0x08B4, 0xA2E5, 0x4C37, 0xE666, 0x81B2, 0x2BE3, 0xC531, 0x6F60, 0x0A99, 0xA0C8, 0x4E1A, 0xE44B, 0x839F, 0x29CE, 0xC71C, 0x6D4D, 0x0CEE, 0xA6BF, 0x486D, 0xE23C, 0x85E8, 0x2FB9, 0xC16B, 0x6B3A, 0x0EC3, 0xA492, 0x4A40, 0xE011, 0x87C5, 0x2D94, 0xC346, 0x6917, 0x1168, 0xBB39, 0x55EB, 0xFFBA, 0x986E, 0x323F, 0xDCED, 0x76BC, 0x1345, 0xB914, 0x57C6, 0xFD97, 0x9A43, 0x3012, 0xDEC0, 0x7491, 0x1532, 0xBF63, 0x51B1, 0xFBE0, 0x9C34, 0x3665, 0xD8B7, 0x72E6, 0x171F, 0xBD4E, 0x539C, 0xF9CD, 0x9E19, 0x3448, 0xDA9A, 0x70CB, 0x19DC, 0xB38D, 0x5D5F, 0xF70E, 0x90DA, 0x3A8B, 0xD459, 0x7E08, 0x1BF1, 0xB1A0, 0x5F72, 0xF523, 0x92F7, 0x38A6, 0xD674, 0x7C25, 0x1D86, 0xB7D7, 0x5905, 0xF354, 0x9480, 0x3ED1, 0xD003, 0x7A52, 0x1FAB, 0xB5FA, 0x5B28, 0xF179, 0x96AD, 0x3CFC, 0xD22E, 0x787F, 0x22D0, 0x8881, 0x6653, 0xCC02, 0xABD6, 0x0187, 0xEF55, 0x4504, 0x20FD, 0x8AAC, 0x647E, 0xCE2F, 0xA9FB, 0x03AA, 0xED78, 0x4729, 0x268A, 0x8CDB, 0x6209, 0xC858, 0xAF8C, 0x05DD, 0xEB0F, 0x415E, 0x24A7, 0x8EF6, 0x6024, 0xCA75, 0xADA1, 0x07F0, 0xE922, 0x4373, 0x2A64, 0x8035, 0x6EE7, 0xC4B6, 0xA362, 0x0933, 0xE7E1, 0x4DB0, 0x2849, 0x8218, 0x6CCA, 0xC69B, 0xA14F, 0x0B1E, 0xE5CC, 0x4F9D, 0x2E3E, 0x846F, 0x6ABD, 0xC0EC, 0xA738, 0x0D69, 0xE3BB, 0x49EA, 0x2C13, 0x8642, 0x6890, 0xC2C1, 0xA515, 0x0F44, 0xE196, 0x4BC7, 0x33B8, 0x99E9, 0x773B, 0xDD6A, 0xBABE, 0x10EF, 0xFE3D, 0x546C, 0x3195, 0x9BC4, 0x7516, 0xDF47, 0xB893, 0x12C2, 0xFC10, 0x5641, 0x37E2, 0x9DB3, 0x7361, 0xD930, 0xBEE4, 0x14B5, 0xFA67, 0x5036, 0x35CF, 0x9F9E, 0x714C, 0xDB1D, 0xBCC9, 0x1698, 0xF84A, 0x521B, 0x3B0C, 0x915D, 0x7F8F, 0xD5DE, 0xB20A, 0x185B, 0xF689, 0x5CD8, 0x3921, 0x9370, 0x7DA2, 0xD7F3, 0xB027, 0x1A76, 0xF4A4, 0x5EF5, 0x3F56, 0x9507, 0x7BD5, 0xD184, 0xB650, 0x1C01, 0xF2D3, 0x5882, 0x3D7B, 0x972A, 0x79F8, 0xD3A9, 0xB47D, 0x1E2C, 0xF0FE, 0x5AAF, }, { 0x0000, 0x45A0, 0x8B40, 0xCEE0, 0x06A1, 0x4301, 0x8DE1, 0xC841, 0x0D42, 0x48E2, 0x8602, 0xC3A2, 0x0BE3, 0x4E43, 0x80A3, 0xC503, 0x1A84, 0x5F24, 0x91C4, 0xD464, 0x1C25, 0x5985, 0x9765, 0xD2C5, 0x17C6, 0x5266, 0x9C86, 0xD926, 0x1167, 0x54C7, 0x9A27, 0xDF87, 0x3508, 0x70A8, 0xBE48, 0xFBE8, 0x33A9, 0x7609, 0xB8E9, 0xFD49, 0x384A, 0x7DEA, 0xB30A, 0xF6AA, 0x3EEB, 0x7B4B, 0xB5AB, 0xF00B, 0x2F8C, 0x6A2C, 0xA4CC, 0xE16C, 0x292D, 0x6C8D, 0xA26D, 0xE7CD, 0x22CE, 0x676E, 0xA98E, 0xEC2E, 0x246F, 0x61CF, 0xAF2F, 0xEA8F, 0x6A10, 0x2FB0, 0xE150, 0xA4F0, 0x6CB1, 0x2911, 0xE7F1, 0xA251, 0x6752, 0x22F2, 0xEC12, 0xA9B2, 0x61F3, 0x2453, 0xEAB3, 0xAF13, 0x7094, 0x3534, 0xFBD4, 0xBE74, 0x7635, 0x3395, 0xFD75, 0xB8D5, 0x7DD6, 0x3876, 0xF696, 0xB336, 0x7B77, 0x3ED7, 0xF037, 0xB597, 0x5F18, 0x1AB8, 0xD458, 0x91F8, 0x59B9, 0x1C19, 0xD2F9, 0x9759, 0x525A, 0x17FA, 0xD91A, 0x9CBA, 0x54FB, 0x115B, 0xDFBB, 0x9A1B, 0x459C, 0x003C, 0xCEDC, 0x8B7C, 0x433D, 0x069D, 0xC87D, 0x8DDD, 0x48DE, 0x0D7E, 0xC39E, 0x863E, 0x4E7F, 0x0BDF, 0xC53F, 0x809F, 0xD420, 0x9180, 0x5F60, 0x1AC0, 0xD281, 0x9721, 0x59C1, 0x1C61, 0xD962, 0x9CC2, 0x5222, 0x1782, 0xDFC3, 0x9A63, 0x5483, 0x1123, 0xCEA4, 0x8B04, 0x45E4, 0x0044, 0xC805, 0x8DA5, 0x4345, 0x06E5, 0xC3E6, 0x8646, 0x48A6, 0x0D06, 0xC547, 0x80E7, 0x4E07, 0x0BA7, 0xE128, 0xA488, 0x6A68, 0x2FC8, 0xE789, 0xA229, 0x6CC9, 0x2969, 0xEC6A, 0xA9CA, 0x672A, 0x228A, 0xEACB, 0xAF6B, 0x618B, 0x242B, 0xFBAC, 0xBE0C, 0x70EC, 0x354C, 0xFD0D, 0xB8AD, 0x764D, 0x33ED, 0xF6EE, 0xB34E, 0x7DAE, 0x380E, 0xF04F, 0xB5EF, 0x7B0F, 0x3EAF, 0xBE30, 0xFB90, 0x3570, 0x70D0, 0xB891, 0xFD31, 0x33D1, 0x7671, 0xB372, 0xF6D2, 0x3832, 0x7D92, 0xB5D3, 0xF073, 0x3E93, 0x7B33, 0xA4B4, 0xE114, 0x2FF4, 0x6A54, 0xA215, 0xE7B5, 0x2955, 0x6CF5, 0xA9F6, 0xEC56, 0x22B6, 0x6716, 0xAF57, 0xEAF7, 0x2417, 0x61B7, 0x8B38, 0xCE98, 0x0078, 0x45D8, 0x8D99, 0xC839, 0x06D9, 0x4379, 0x867A, 0xC3DA, 0x0D3A, 0x489A, 0x80DB, 0xC57B, 0x0B9B, 0x4E3B, 0x91BC, 0xD41C, 0x1AFC, 0x5F5C, 0x971D, 0xD2BD, 0x1C5D, 0x59FD, 0x9CFE, 0xD95E, 0x17BE, 0x521E, 0x9A5F, 0xDFFF, 0x111F, 0x54BF, }, { 0x0000, 0xB861, 0x60E3, 0xD882, 0xC1C6, 0x79A7, 0xA125, 0x1944, 0x93AD, 0x2BCC, 0xF34E, 0x4B2F, 0x526B, 0xEA0A, 0x3288, 0x8AE9, 0x377B, 0x8F1A, 0x5798, 0xEFF9, 0xF6BD, 0x4EDC, 0x965E, 0x2E3F, 0xA4D6, 0x1CB7, 0xC435, 0x7C54, 0x6510, 0xDD71, 0x05F3, 0xBD92, 0x6EF6, 0xD697, 0x0E15, 0xB674, 0xAF30, 0x1751, 0xCFD3, 0x77B2, 0xFD5B, 0x453A, 0x9DB8, 0x25D9, 0x3C9D, 0x84FC, 0x5C7E, 0xE41F, 0x598D, 0xE1EC, 0x396E, 0x810F, 0x984B, 0x202A, 0xF8A8, 0x40C9, 0xCA20, 0x7241, 0xAAC3, 0x12A2, 0x0BE6, 0xB387, 0x6B05, 0xD364, 0xDDEC, 0x658D, 0xBD0F, 0x056E, 0x1C2A, 0xA44B, 0x7CC9, 0xC4A8, 0x4E41, 0xF620, 0x2EA2, 0x96C3, 0x8F87, 0x37E6, 0xEF64, 0x5705, 0xEA97, 0x52F6, 0x8A74, 0x3215, 0x2B51, 0x9330, 0x4BB2, 0xF3D3, 0x793A, 0xC15B, 0x19D9, 0xA1B8, 0xB8FC, 0x009D, 0xD81F, 0x607E, 0xB31A, 0x0B7B, 0xD3F9, 0x6B98, 0x72DC, 0xCABD, 0x123F, 0xAA5E, 0x20B7, 0x98D6, 0x4054, 0xF835, 0xE171, 0x5910, 0x8192, 0x39F3, 0x8461, 0x3C00, 0xE482, 0x5CE3, 0x45A7, 0xFDC6, 0x2544, 0x9D25, 0x17CC, 0xAFAD, 0x772F, 0xCF4E, 0xD60A, 0x6E6B, 0xB6E9, 0x0E88, 0xABF9, 0x1398, 0xCB1A, 0x737B, 0x6A3F, 0xD25E, 0x0ADC, 0xB2BD, 0x3854, 0x8035, 0x58B7, 0xE0D6, 0xF992, 0x41F3, 0x9971, 0x2110, 0x9C82, 0x24E3, 0xFC61, 0x4400, 0x5D44, 0xE525, 0x3DA7, 0x85C6, 0x0F2F, 0xB74E, 0x6FCC, 0xD7AD, 0xCEE9, 0x7688, 0xAE0A, 0x166B, 0xC50F, 0x7D6E, 0xA5EC, 0x1D8D, 0x04C9, 0xBCA8, 0x642A, 0xDC4B, 0x56A2, 0xEEC3, 0x3641, 0x8E20, 0x9764, 0x2F05, 0xF787, 0x4FE6, 0xF274, 0x4A15, 0x9297, 0x2AF6, 0x33B2, 0x8BD3, 0x5351, 0xEB30, 0x61D9, 0xD9B8, 0x013A, 0xB95B, 0xA01F, 0x187E, 0xC0FC, 0x789D, 0x7615, 0xCE74, 0x16F6, 0xAE97, 0xB7D3, 0x0FB2, 0xD730, 0x6F51, 0xE5B8, 0x5DD9, 0x855B, 0x3D3A, 0x247E, 0x9C1F, 0x449D, 0xFCFC, 0x416E, 0xF90F, 0x218D, 0x99EC, 0x80A8, 0x38C9, 0xE04B, 0x582A, 0xD2C3, 0x6AA2, 0xB220, 0x0A41, 0x1305, 0xAB64, 0x73E6, 0xCB87, 0x18E3, 0xA082, 0x7800, 0xC061, 0xD925, 0x6144, 0xB9C6, 0x01A7, 0x8B4E, 0x332F, 0xEBAD, 0x53CC, 0x4A88, 0xF2E9, 0x2A6B, 0x920A, 0x2F98, 0x97F9, 0x4F7B, 0xF71A, 0xEE5E, 0x563F, 0x8EBD, 0x36DC, 0xBC35, 0x0454, 0xDCD6, 0x64B7, 0x7DF3, 0xC592, 0x1D10, 0xA571, }, { 0x0000, 0x47D3, 0x8FA6, 0xC875, 0x0F6D, 0x48BE, 0x80CB, 0xC718, 0x1EDA, 0x5909, 0x917C, 0xD6AF, 0x11B7, 0x5664, 0x9E11, 0xD9C2, 0x3DB4, 0x7A67, 0xB212, 0xF5C1, 0x32D9, 0x750A, 0xBD7F, 0xFAAC, 0x236E, 0x64BD, 0xACC8, 0xEB1B, 0x2C03, 0x6BD0, 0xA3A5, 0xE476, 0x7B68, 0x3CBB, 0xF4CE, 0xB31D, 0x7405, 0x33D6, 0xFBA3, 0xBC70, 0x65B2, 0x2261, 0xEA14, 0xADC7, 0x6ADF, 0x2D0C, 0xE579, 0xA2AA, 0x46DC, 0x010F, 0xC97A, 0x8EA9, 0x49B1, 0x0E62, 0xC617, 0x81C4, 0x5806, 0x1FD5, 0xD7A0, 0x9073, 0x576B, 0x10B8, 0xD8CD, 0x9F1E, 0xF6D0, 0xB103, 0x7976, 0x3EA5, 0xF9BD, 0xBE6E, 0x761B, 0x31C8, 0xE80A, 0xAFD9, 0x67AC, 0x207F, 0xE767, 0xA0B4, 0x68C1, 0x2F12, 0xCB64, 0x8CB7, 0x44C2, 0x0311, 0xC409, 0x83DA, 0x4BAF, 0x0C7C, 0xD5BE, 0x926D, 0x5A18, 0x1DCB, 0xDAD3, 0x9D00, 0x5575, 0x12A6, 0x8DB8, 0xCA6B, 0x021E, 0x45CD, 0x82D5, 0xC506, 0x0D73, 0x4AA0, 0x9362, 0xD4B1, 0x1CC4, 0x5B17, 0x9C0F, 0xDBDC, 0x13A9, 0x547A, 0xB00C, 0xF7DF, 0x3FAA, 0x7879, 0xBF61, 0xF8B2, 0x30C7, 0x7714, 0xAED6, 0xE905, 0x2170, 0x66A3, 0xA1BB, 0xE668, 0x2E1D, 0x69CE, 0xFD81, 0xBA52, 0x7227, 0x35F4, 0xF2EC, 0xB53F, 0x7D4A, 0x3A99, 0xE35B, 0xA488, 0x6CFD, 0x2B2E, 0xEC36, 0xABE5, 0x6390, 0x2443, 0xC035, 0x87E6, 0x4F93, 0x0840, 0xCF58, 0x888B, 0x40FE, 0x072D, 0xDEEF, 0x993C, 0x5149, 0x169A, 0xD182, 0x9651, 0x5E24, 0x19F7, 0x86E9, 0xC13A, 0x094F, 0x4E9C, 0x8984, 0xCE57, 0x0622, 0x41F1, 0x9833, 0xDFE0, 0x1795, 0x5046, 0x975E, 0xD08D, 0x18F8, 0x5F2B, 0xBB5D, 0xFC8E, 0x34FB, 0x7328, 0xB430, 0xF3E3, 0x3B96, 0x7C45, 0xA587, 0xE254, 0x2A21, 0x6DF2, 0xAAEA, 0xED39, 0x254C, 0x629F, 0x0B51, 0x4C82, 0x84F7, 0xC324, 0x043C, 0x43EF, 0x8B9A, 0xCC49, 0x158B, 0x5258, 0x9A2D, 0xDDFE, 0x1AE6, 0x5D35, 0x9540, 0xD293, 0x36E5, 0x7136, 0xB943, 0xFE90, 0x3988, 0x7E5B, 0xB62E, 0xF1FD, 0x283F, 0x6FEC, 0xA799, 0xE04A, 0x2752, 0x6081, 0xA8F4, 0xEF27, 0x7039, 0x37EA, 0xFF9F, 0xB84C, 0x7F54, 0x3887, 0xF0F2, 0xB721, 0x6EE3, 0x2930, 0xE145, 0xA696, 0x618E, 0x265D, 0xEE28, 0xA9FB, 0x4D8D, 0x0A5E, 0xC22B, 0x85F8, 0x42E0, 0x0533, 0xCD46, 0x8A95, 0x5357, 0x1484, 0xDCF1, 0x9B22, 0x5C3A, 0x1BE9, 0xD39C, 0x944F, }, }; } // namespace openmsx #if 0 #include int main() { openmsx::CRC16 crc; assert(crc.getValue() == 0xFFFF); crc.update(0xA1); crc.update(0xA1); crc.update(0xA1); assert(crc.getValue() == 0xCDB4); } #endif #if 0 // Generator for the table above #include using namespace openmsx; int main() { uint16_t tab[8][256]; for (unsigned i = 0; i < 0x100; ++i) { uint16_t x = i << 8; for (int j = 0; j < 8; ++j) { x = (x << 1) ^ ((x & 0x8000) ? 0x1021 : 0); } tab[0][i] = x; } for (unsigned i = 0; i < 0x100; ++i) { uint16_t c = tab[0][i]; for (unsigned j = 1; j < 8; ++j) { c = tab[0][c >> 8] ^ (c << 8); tab[j][i] = c; } } printf("const uint16_t CRC16::tab[8][256] = {\n"); for (int i = 0; i < 8; ++i) { printf("\t{\n"); for (int j = 0; j < 0x100; ++j) { printf((j & 7) ? " " : "\t\t"); printf("0x%04X,", tab[i][j]); if ((j & 7) == 7) printf("\n"); } printf("\t},\n"); } printf("};\n"); } #endif openMSX-RELEASE_0_12_0/src/utils/CRC16.hh000066400000000000000000000073601257557151200174730ustar00rootroot00000000000000#ifndef CRC16_HH #define CRC16_HH #include #include namespace openmsx { /** * This class calculates CRC numbers for the polygon * x^16 + x^12 + x^5 + 1 */ class CRC16 { public: /** Create CRC16 with an optional initial value */ explicit CRC16(uint16_t initialCRC = 0xFFFF) { init(initialCRC); } /** (Re)initialize the current value */ void init(uint16_t initialCRC) { crc = initialCRC; } /** (Re)initialize with a short initial sequence. * The initial value is guaranteed to be computed at compile time. */ template void init() { static const uint16_t T0 = 0xffff; static const uint16_t T1 = CT_CRC16::value; init(T1); } template void init() { static const uint16_t T0 = 0xffff; static const uint16_t T1 = CT_CRC16::value; static const uint16_t T2 = CT_CRC16::value; init(T2); } template void init() { static const uint16_t T0 = 0xffff; static const uint16_t T1 = CT_CRC16::value; static const uint16_t T2 = CT_CRC16::value; static const uint16_t T3 = CT_CRC16::value; init(T3); } template void init() { static const uint16_t T0 = 0xffff; static const uint16_t T1 = CT_CRC16::value; static const uint16_t T2 = CT_CRC16::value; static const uint16_t T3 = CT_CRC16::value; static const uint16_t T4 = CT_CRC16::value; init(T4); } /** Update CRC with one byte */ void update(uint8_t value) { // Classical byte-at-a-time algorithm by Dilip V. Sarwate crc = (crc << 8) ^ tab[0][(crc >> 8) ^ value]; } /** For large blocks (e.g. 512 bytes) this routine is approx 5x faster * than calling the method above in a loop. */ void update(const uint8_t* data, size_t size) { // Based on: // Slicing-by-4 and slicing-by-8 algorithms by Michael E. // Kounavis and Frank L. Berry from Intel Corp. // http://www.intel.com/technology/comms/perfnet/download/CRC_generators.pdf // and the implementation by Peter Kankowski found on: // http://www.strchr.com/crc32_popcnt // I transformed the code from CRC32 to CRC16 (this was not // trivial because both CRCs use a different convention about // bit order). I also made the code work on bigendian hosts. unsigned c = crc; // 32-bit are faster than 16-bit calculations // on x86 and many other modern architectures // calculate the bulk of the data 8 bytes at a time for (auto n = size / 8; n; --n) { c = tab[7][data[0] ^ (c >> 8)] ^ tab[6][data[1] ^ (c & 255)] ^ tab[5][data[2]] ^ tab[4][data[3]] ^ tab[3][data[4]] ^ tab[2][data[5]] ^ tab[1][data[6]] ^ tab[0][data[7]]; data += 8; } // calculate the remaining bytes in the usual way for (size &= 7; size; --size) { c = uint16_t(c << 8) ^ tab[0][(c >> 8) ^ *data++]; } crc = c; // store back in a 16-bit result } /** Get current CRC value */ uint16_t getValue() const { return crc; } private: uint16_t crc; static const uint16_t tab[8][256]; // The Stuff below is template magic to perform the following // computation at compile-time: // for (int i = 8; i < 16; ++i) { // crc = (crc << 1) ^ ((((crc ^ (data << i)) & 0x8000) ? 0x1021 : 0)); // } template struct CT_H { static const uint16_t D = uint16_t(C << 1) ^ (((C ^ V) & 0x8000) ? 0x1021 : 0); static const uint16_t value = CT_H::value; }; template struct CT_H { static const uint16_t value = C; }; template struct CT_CRC16 : CT_H {}; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/utils/CircularBuffer.cc000066400000000000000000000014621257557151200215760ustar00rootroot00000000000000#if 0 #include "CircularBuffer.hh" #include int main() { openmsx::CircularBuffer buf; assert(buf.isEmpty()); assert(!buf.isFull()); assert(buf.size() == 0); buf.addBack(15); assert(!buf.isEmpty()); assert(!buf.isFull()); assert(buf.size() == 1); assert(buf[0] == 15); buf[0] = 25; assert(buf[0] == 25); buf.addFront(17); assert(!buf.isEmpty()); assert(buf.isFull()); assert(buf.size() == 2); assert(buf[0] == 17); assert(buf[1] == 25); buf[1] = 35; assert(buf[0] == 17); assert(buf[1] == 35); buf[0] = 27; assert(buf[0] == 27); assert(buf[1] == 35); int a = buf.removeBack(); assert(a == 35); assert(buf.size() == 1); assert(buf[0] == 27); int b = buf.removeFront(); assert(b == 27); assert(buf.isEmpty()); std::cout << "Test passed!" << std::endl; } #endif openMSX-RELEASE_0_12_0/src/utils/CircularBuffer.hh000066400000000000000000000025761257557151200216170ustar00rootroot00000000000000#ifndef CIRCULARBUFFER_HH #define CIRCULARBUFFER_HH #include namespace openmsx { template class CircularBuffer { public: CircularBuffer() : first(0), last(0) { } void addFront(const T& element) { assert(!isFull()); first = prev(first); buffer[first] = element; } void addBack(const T& element) { assert(!isFull()); buffer[last] = element; last = next(last); } T& removeFront() { assert(!isEmpty()); auto tmp = first; first = next(first); return buffer[tmp]; } T& removeBack() { assert(!isEmpty()); last = prev(last); return buffer[last]; } T& operator[](size_t pos) { assert(pos < MAXSIZE); auto tmp = first + pos; if (tmp > MAXSIZE) { tmp -= (MAXSIZE + 1); } return buffer[tmp]; } const T& operator[](size_t pos) const { return const_cast(*this)[pos]; } bool isEmpty() const { return (first == last); } bool isFull() const { return (first == next(last)); } size_t size() const { if (first > last) { return MAXSIZE + 1 - first + last; } else { return last - first; } } private: inline size_t next(size_t a) const { return (a != MAXSIZE) ? a + 1 : 0; } inline size_t prev(size_t a) const { return (a != 0) ? a - 1 : MAXSIZE; } size_t first, last; // one extra to be able to distinguish full and empty T buffer[MAXSIZE + 1]; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/utils/Date.cc000066400000000000000000000077141257557151200175630ustar00rootroot00000000000000#include "Date.hh" #include #include namespace openmsx { namespace Date { const char* const days[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; const char* const months[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; template static inline bool parseDigit(unsigned c, T& t) { c -= '0'; if (c > 9) return false; if (FIRST) { t = c * MUL; } else { t += c * MUL; } return true; } time_t fromString(const char* p) { struct tm tm; // skip day p += 3; // space if (*p++ != ' ') return time_t(-1); // Parse month switch (*p++) { case 'J': // Jan Jun Jul switch (*p++) { case 'a': // Jan if (*p++ != 'n') return time_t(-1); tm.tm_mon = 0; break; case 'u': // Jun Jul switch (*p++) { case 'n': tm.tm_mon = 5; break; case 'l': tm.tm_mon = 6; break; default: return time_t(-1); } break; default: return time_t(-1); } break; case 'F': // Feb if (*p++ != 'e') return time_t(-1); if (*p++ != 'b') return time_t(-1); tm.tm_mon = 1; break; case 'M': // Mar May if (*p++ != 'a') return time_t(-1); switch (*p++) { case 'r': tm.tm_mon = 2; break; case 'y': tm.tm_mon = 4; break; default: return time_t(-1); } break; case 'A': // Apr Aug switch (*p++) { case 'p': // Apr if (*p++ != 'r') return time_t(-1); tm.tm_mon = 3; break; case 'u': // Aug if (*p++ != 'g') return time_t(-1); tm.tm_mon = 7; break; default: return time_t(-1); } break; case 'S': // Sep if (*p++ != 'e') return time_t(-1); if (*p++ != 'p') return time_t(-1); tm.tm_mon = 8; break; case 'O': // Oct if (*p++ != 'c') return time_t(-1); if (*p++ != 't') return time_t(-1); tm.tm_mon = 9; break; case 'N': // Nov if (*p++ != 'o') return time_t(-1); if (*p++ != 'v') return time_t(-1); tm.tm_mon = 10; break; case 'D': // Dec if (*p++ != 'e') return time_t(-1); if (*p++ != 'c') return time_t(-1); tm.tm_mon = 11; break; default: return time_t(-1); } // space if (*p++ != ' ') return time_t(-1); // parse mday if (!parseDigit(*p++, tm.tm_mday)) return time_t(-1); if (!parseDigit(*p++, tm.tm_mday)) return time_t(-1); if ((tm.tm_mday < 1) || (31 < tm.tm_mday) ) return time_t(-1); // space if (*p++ != ' ') return time_t(-1); // parse hour if (!parseDigit(*p++, tm.tm_hour)) return time_t(-1); if (!parseDigit(*p++, tm.tm_hour)) return time_t(-1); if ((tm.tm_hour < 0) || (23 < tm.tm_hour)) return time_t(-1); // colon if (*p++ != ':') return time_t(-1); // parse minute if (!parseDigit(*p++, tm.tm_min)) return time_t(-1); if (!parseDigit(*p++, tm.tm_min)) return time_t(-1); if ((tm.tm_min < 0) || (59 < tm.tm_min)) return time_t(-1); // colon if (*p++ != ':') return time_t(-1); // parse second if (!parseDigit(*p++, tm.tm_sec)) return time_t(-1); if (!parseDigit(*p++, tm.tm_sec)) return time_t(-1); if ((tm.tm_sec < 0) || (59 < tm.tm_sec)) return time_t(-1); // space if (*p++ != ' ') return time_t(-1); // parse year if (!parseDigit(*p++, tm.tm_year)) return time_t(-1); if (!parseDigit(*p++, tm.tm_year)) return time_t(-1); if (!parseDigit(*p++, tm.tm_year)) return time_t(-1); if (!parseDigit(*p++, tm.tm_year)) return time_t(-1); tm.tm_year -= 1900; if (tm.tm_year < 0) return time_t(-1); tm.tm_isdst = -1; return mktime(&tm); } std::string toString(time_t time) { if (time < 0) time = 0; struct tm* tm; tm = localtime(&time); std::ostringstream sstr; sstr << std::setfill('0') << days [tm->tm_wday] << ' ' << months[tm->tm_mon] << ' ' << std::setw(2) << tm->tm_mday << ' ' << std::setw(2) << tm->tm_hour << ':' << std::setw(2) << tm->tm_min << ':' << std::setw(2) << tm->tm_sec << ' ' << std::setw(4) << (tm->tm_year + 1900); return sstr.str(); } } // namespace Date } // namespace openmsx openMSX-RELEASE_0_12_0/src/utils/Date.hh000066400000000000000000000004601257557151200175640ustar00rootroot00000000000000#ifndef DATE_HH #define DATE_HH #include #include namespace openmsx { namespace Date { // 'line' must point to a buffer that is at least 24 characters long time_t fromString(const char* line); std::string toString(time_t time); } // namespace Date } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/utils/DivModByConst.hh000066400000000000000000000310421257557151200213730ustar00rootroot00000000000000#ifndef DIVMODBYCONST #define DIVMODBYCONST #include "build-info.hh" #include "type_traits.hh" #include #include /** Utility class to optimize 64-bit divide/module by a 32-bit constant. * For 32-bit by 32-bit gcc already does this optimiztion (on 64-bit * CPUs gcc also does it for 64-bit operands). This optimization especially * helps on CPU without a HW division instruction (like ARM). * * Usage: * DivModByConst<123> dm; * uint32_t d = dm.div(x); // equivalent to d = x / 123; * uint32_t m = dm.mod(x); // equivalent to d = x % 123; */ namespace DivModByConstPrivate { template struct log2 : if_c, log2> {}; // Utility class to perform 128-bit by 128-bit division at compilation time template struct Div128_helper { static const uint64_t QL2 = (QL << 1); static const uint64_t QH2 = (QH << 1) + (QL2 < QL); static const uint64_t RL2 = (RL << 1) + (QH2 < QH); static const uint64_t RH2 = (RH << 1) + (RL2 < RL); static const bool C = (RH2 != DH) ? (RH2 < DH) : (RL2 < DL); static const uint64_t RL3 = C ? RL2 : RL2 - DL; static const uint64_t RH3 = C ? RH2 : RH2 - DH - (RL3 > RL2); static const uint64_t QL3 = C ? QL2 : QL2 + 1; static const uint64_t QH3 = C ? QH2 : ((QL3 != 0) ? QH2 : QH2 + 1); using Div = Div128_helper; static const uint64_t quotientLow = Div::quotientLow; static const uint64_t quotientHigh = Div::quotientHigh; static const uint64_t remainderLow = Div::remainderLow; static const uint64_t remainderHigh = Div::remainderHigh; }; template struct Div128_helper { static const uint64_t quotientLow = QL; static const uint64_t quotientHigh = QH; static const uint64_t remainderLow = RL; static const uint64_t remainderHigh = RH; }; template struct Div128 : Div128_helper<0, 0, DividendH, DividendL, DividerH, DividerL, 128> {}; // equivalent to the following run-time loop: // while (!(M & 1)) { // M >>= 1; // --S; // } template struct DBCReduce { using R2 = DBCReduce; static const uint64_t M2 = R2::M2; static const uint32_t S2 = R2::S2; }; template struct DBCReduce { static const uint64_t M2 = M; static const uint32_t S2 = S; }; // equivalent to the following run-tim loop: // while (((m_low >> 1) < (m_high >> 1)) && (l > 0)) { // m_low >>= 1; // m_high >>= 1; // --l; // } template struct DBCReduce2Shift { static const uint64_t AH2 = AH / 2; static const uint64_t AL2 = AL / 2 + ((AH2 * 2 != AH) ? (1ull << 63) : 0); static const uint64_t BH2 = BH / 2; static const uint64_t BL2 = BL / 2 + ((BH2 * 2 != BH) ? (1ull << 63) : 0); }; template struct DBCReduce2Test { using S = DBCReduce2Shift; static const bool C = (S::AH2 != S::BH2) ? (S::AH2 < S::BH2) : (S::AL2 < S::BL2); static const bool value = C && (L > 0); }; template struct DBCReduce2Loop { using S = DBCReduce2Shift; using T = DBCReduce2Test; using R = DBCReduce2Loop; static const uint64_t MLH = R::MLH; static const uint64_t MLL = R::MLL; static const uint64_t MHH = R::MHH; static const uint64_t MHL = R::MHL; static const uint32_t L = R::L; }; template struct DBCReduce2Loop { static const uint64_t MLH = AH; static const uint64_t MLL = AL; static const uint64_t MHH = BH; static const uint64_t MHL = BL; static const uint32_t L = LL; }; template struct DBCReduce2 { using T = DBCReduce2Test; using R = DBCReduce2Loop; static const uint64_t MLH = R::MLH; static const uint64_t MLL = R::MLL; static const uint64_t MHH = R::MHH; static const uint64_t MHL = R::MHL; static const uint32_t L = R::L; }; template struct DBCAlgo1 { // division possible by only shifting uint32_t operator()(uint64_t dividend) const { return dividend >> S; } }; static inline uint64_t mla64(uint64_t a, uint64_t b, uint64_t c) { // equivalent to this: // return (__uint128_t(a) * b + c) >> 64; uint64_t t1 = uint64_t(uint32_t(a)) * uint32_t(b); uint64_t t2 = (a >> 32) * uint32_t(b); uint64_t t3 = uint32_t(a) * (b >> 32); uint64_t t4 = (a >> 32) * (b >> 32); uint64_t s1 = uint64_t(uint32_t(c)) + uint32_t(t1); uint64_t s2 = (s1 >> 32) + (c >> 32) + (t1 >> 32) + t2; uint64_t s3 = uint64_t(uint32_t(s2)) + uint32_t(t3); uint64_t s4 = (s3 >> 32) + (s2 >> 32) + (t3 >> 32) + t4; return s4; } template struct DBCAlgo2 { // division possible by multiplication and shift uint32_t operator()(uint64_t dividend) const { using R = DBCReduce; #if ASM_X86_32 || defined(__arm__) const uint32_t _ah_ = R::M2 >> 32; const uint32_t _al_ = uint32_t((R::M2 << 32) >> 32); // Suppress VC++ C4310 warning const uint32_t _bh_ = dividend >> 32; const uint32_t _bl_ = uint32_t(dividend); #endif #if ASM_X86_32 #ifdef _MSC_VER uint32_t _tl_; register uint32_t result; __asm { // It's worth noting that simple benchmarks show this to be // no faster than straight division on an Intel E8400 // // eax and edx are used with mul // ecx = bl // esi = ah mov ecx,_bl_ mov esi,_ah_ // ebx is th mov eax,esi mul ecx mov _tl_,eax mov ebx,edx mov eax,_al_ mul ecx add _tl_,edx adc ebx,0 // ecx = bh now // edi is cl mov ecx,_bh_ mov eax,esi mul ecx mov edi,eax // esi is ch now mov esi,edx mov eax,_al_ mul ecx add _tl_,eax adc ebx,edx adc esi,0 add edi,ebx adc esi,0 // Sadly, no way to make this an immediate in VC++ mov cl,byte ptr [R::S2] shrd edi,esi,cl mov result,edi } #ifdef DEBUG uint32_t realResult = uint32_t(mla64(dividend, R::M2, 0) >> R::S2); assert(realResult == result); #endif return result; #else uint32_t th, tl, ch, cl; asm ( "movl %[AH],%%eax\n\t" "mull %[BL]\n\t" "movl %%eax,%[TL]\n\t" "movl %%edx,%[TH]\n\t" "movl %[AL],%%eax\n\t" "mull %[BL]\n\t" "addl %%edx,%[TL]\n\t" "adcl $0,%[TH]\n\t" "movl %[AH],%%eax\n\t" "mull %[BH]\n\t" "movl %%eax,%[CL]\n\t" "movl %%edx,%[CH]\n\t" "movl %[AL],%%eax\n\t" "mull %[BH]\n\t" "addl %%eax,%[TL]\n\t" "adcl %%edx,%[TH]\n\t" "adcl $0,%[CH]\n\t" "addl %[TH],%[CL]\n\t" "adcl $0,%[CH]\n\t" : [CH] "=&rm" (ch) , [TH] "=&r" (th) , [CL] "=rm" (cl) , [TL] "=&rm" (tl) : [AH] "g" (_ah_) , [AL] "g" (_al_) , [BH] "rm" (_bh_) , [BL] "[CL]" (_bl_) : "eax","edx" ); asm ( "shrd %[SH],%[CH],%[CL]\n\t" : [CL] "=rm" (cl) : [CH] "r" (ch) , "[CL]" (cl) , [SH] "i" (R::S2) ); return cl; #endif #elif defined(__arm__) uint32_t res; uint32_t th,tl; asm volatile ( "umull %[TH],%[TL],%[AL],%[BL]\n\t" "eors %[TH],%[TH]\n\t" "umlal %[TL],%[TH],%[AH],%[BL]\n\t" "umull %[BL],%[AL],%[BH],%[AL]\n\t" "adds %[TL],%[TL],%[BL]\n\t" "adcs %[TH],%[TH],%[AL]\n\t" "mov %[TL],#0\n\t" "adc %[TL],%[TL],%[TL]\n\t" "umlal %[TH],%[TL],%[AH],%[BH]\n\t" "lsr %[RES],%[TH],%[S]\n\t" //"orr %[RES],%[RES],%[TL],LSL %[S32]\n\t" // not thumb2 "lsls %[TL],%[TL],%[S32]\n\t" "orrs %[RES],%[RES],%[TL]\n\t" : [RES] "=r" (res) , [TH] "=&r" (th) , [TL] "=&r" (tl) : [AH] "r" (_ah_) , [AL] "r" (_al_) , [BH] "r" (_bh_) , [BL] "[RES]" (_bl_) , [S] "M" (R::S2) , [S32] "M" (32 - R::S2) ); return res; #else uint64_t h = mla64(dividend, R::M2, 0); uint64_t result = h >> R::S2; #ifdef DEBUG // we don't even want this overhead in devel builds assert(result == uint32_t(result)); #endif return uint32_t(result); #endif } }; template struct DBCAlgo3 { // division possible by multiplication, addition and shift static const uint32_t S = log2::value - 1; using D = Div128<1 << S, 0, 0, DIVISOR>; static const uint64_t M = D::quotientLow + (D::remainderLow > (DIVISOR / 2)); uint32_t operator()(uint64_t dividend) const { using R = DBCReduce; #if ASM_X86_32 || defined(__arm__) const uint32_t ah = R::M2 >> 32; const uint32_t al = uint32_t(R::M2); const uint32_t bh = dividend >> 32; const uint32_t bl = dividend; #endif #if ASM_X86_32 uint32_t th, tl, ch, cl; asm ( "mov %[AH],%%eax\n\t" "mull %[BL]\n\t" "mov %%eax,%[TL]\n\t" "mov %%edx,%[TH]\n\t" "mov %[AL],%%eax\n\t" "mull %[BL]\n\t" "add %[AL],%%eax\n\t" "adc %[AH],%%edx\n\t" "adc $0,%[TH]\n\t" "add %%edx,%[TL]\n\t" "adc $0,%[TH]\n\t" "mov %[AH],%%eax\n\t" "mull %[BH]\n\t" "mov %%eax,%[CL]\n\t" "mov %%edx,%[CH]\n\t" "mov %[AL],%%eax\n\t" "mull %[BH]\n\t" "add %%eax,%[TL]\n\t" "adc %%edx,%[TH]\n\t" "adc $0,%[CH]\n\t" "add %[TH],%[CL]\n\t" "adc $0,%[CH]\n\t" : [CH] "=&rm" (ch) , [TH] "=&r" (th) , [CL] "=rm" (cl) , [TL] "=&rm" (tl) : [AH] "g" (ah) , [AL] "g" (al) , [BH] "rm" (bh) , [BL] "[CL]" (bl) : "eax","edx" ); asm ( "shrd %[SH],%[CH],%[CL]\n\t" : [CL] "=rm" (cl) : [CH] "r" (ch) , "[CL]" (cl) , [SH] "i" (R::S2) ); return cl; #elif defined(__arm__) uint32_t res; uint32_t th,tl; asm volatile ( "umull %[TH],%[TL],%[AL],%[BL]\n\t" "adds %[TH],%[TH],%[AL]\n\t" "adcs %[TL],%[TL],%[AH]\n\t" "mov %[TH],#0\n\t" "adc %[TH],%[TH],%[TH]\n\t" "umlal %[TL],%[TH],%[AH],%[BL]\n\t" "umull %[BL],%[AL],%[BH],%[AL]\n\t" "adds %[TL],%[TL],%[BL]\n\t" "adcs %[TH],%[TH],%[AL]\n\t" "mov %[TL],#0\n\t" "adc %[TL],%[TL],%[TL]\n\t" "umlal %[TH],%[TL],%[AH],%[BH]\n\t" "lsr %[RES],%[TH],%[S]\n\t" //"orr %[RES],%[RES],%[TL],LSL %[S32]\n\t" // not thumb2 "lsls %[TL],%[TL],%[S32]\n\t" "orrs %[RES],%[RES],%[TL]\n\t" : [RES] "=r" (res) , [TH] "=&r" (th) , [TL] "=&r" (tl) : [AH] "r" (ah) , [AL] "r" (al) , [BH] "r" (bh) , [BL] "[RES]" (bl) , [S] "M" (R::S2) , [S32] "M" (32 - R::S2) ); return res; #else uint64_t h = mla64(dividend, R::M2, R::M2); return h >> R::S2; #endif } }; template struct DBCHelper3 : if_c , DBCAlgo3> {}; template struct DBCHelper2 { static const uint32_t L = log2::value; static const uint64_t J = 0xffffffffffffffffull % DIVISOR; using K = Div128<1 << L, 0, 0, 0xffffffffffffffffull - J>; using M_LOW = Div128< 1 << L, 0, 0, DIVISOR>; using M_HIGH = Div128<(1 << L) + K::quotientHigh, K::quotientLow, 0, DIVISOR>; using R = DBCReduce2; uint32_t operator()(uint64_t dividend) const { DBCHelper3 dbc; return dbc(dividend); } }; template struct DBCHelper1 : if_c, if_c , DBCHelper1>> {}; } // namespace DivModByConstPrivate template struct DivModByConst { uint32_t div(uint64_t dividend) const { #ifdef __x86_64 // on 64-bit CPUs gcc already does this // optimization (and better) return uint32_t(dividend / DIVISOR); #else DivModByConstPrivate::DBCHelper1 dbc; return dbc(dividend); #endif } uint32_t mod(uint64_t dividend) const { uint64_t result; #ifdef __x86_64 result = dividend % DIVISOR; #else result = dividend - DIVISOR * div(dividend); #endif #ifdef DEBUG // we don't even want this overhead in devel builds assert(result == uint32_t(result)); #endif return uint32_t(result); } }; #endif // DIVMODBYCONST openMSX-RELEASE_0_12_0/src/utils/DivModBySame.cc000066400000000000000000000033311257557151200211600ustar00rootroot00000000000000#include "DivModBySame.hh" #include "uint128.hh" namespace openmsx { static uint32_t log2(uint64_t i) { uint32_t t = 0; i >>= 1; while (i) { i >>= 1; ++t; } return t; } void DivModBySame::setDivisor(uint32_t divisor_) { //assert(divisor_ < 0x8000000000000000ull); // when divisor is uint64_t divisor = divisor_; // reduce divisor until it becomes odd uint32_t n = 0; uint64_t t = divisor; while (!(t & 1)) { t >>= 1; ++n; } if (t == 1) { m = 0xffffffffffffffffull; a = m; s = 0; } else { // Generate m, s for algorithm 0. Based on: Granlund, T.; Montgomery, // P.L.: "Division by Invariant Integers using Multiplication". // SIGPLAN Notices, Vol. 29, June 1994, page 61. uint32_t l = log2(t) + 1; uint64_t j = 0xffffffffffffffffull % t; uint128 k = (uint128(1) << (64 + l)) / (0xffffffffffffffffull - j); uint128 m_low = (uint128(1) << (64 + l)) / t; uint128 m_high = ((uint128(1) << (64 + l)) + k) / t; while (((m_low >> 1) < (m_high >> 1)) && (l > 0)) { m_low >>= 1; m_high >>= 1; --l; } if ((m_high >> 64) == 0) { m = toUint64(m_high); s = l; a = 0; } else { // Generate m, s for algorithm 1. Based on: Magenheimer, D.J.; et al: // "Integer Multiplication and Division on the HP Precision Architecture". // IEEE Transactions on Computers, Vol 37, No. 8, August 1988, page 980. s = log2(t); uint128 m_low = (uint128(1) << (64 + s)) / t; uint64_t r = toUint64((uint128(1) << (64 + s)) % t); m = toUint64(m_low + ((r <= (t >> 1)) ? 0 : 1)); a = m; } // reduce multiplier to smallest possible while (!(m & 1)) { m >>= 1; a >>= 1; s--; } } // adjust multiplier for reduction of even divisors s += n; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/utils/DivModBySame.hh000066400000000000000000000164121257557151200211760ustar00rootroot00000000000000#ifndef DIVISIONBYCONST_HH #define DIVISIONBYCONST_HH #include "build-info.hh" #include #include namespace openmsx { /** Helper class to divide multiple times by the same number. * Binary division can be performed by: * - multiplication by a magic number * - followed by an addition of a magic number * - follwed by a right shift over some magic number of bits * These magic constants only depend on the divisor, but they are quite * expensive to calculate. * However if you know you will divide many times by the same number this * algorithm does make sense and can result in a big speedup, especially on * CPUs which lack a hardware division instruction (like ARM). * * If the divisor is a compile-time constant, it's even faster to use * the DivModByConst utility class. */ class DivModBySame { public: void setDivisor(uint32_t divisor); inline uint32_t getDivisor() const { return divisor; } uint32_t div(uint64_t dividend) const { #if defined __x86_64 && !defined _MSC_VER uint64_t t = (__uint128_t(dividend) * m + a) >> 64; return t >> s; #elif ASM_X86_32 uint32_t _ch_ = a >> 32; uint32_t _cl_ = uint32_t(a); const uint32_t _ah_ = dividend >> 32; const uint32_t _al_ = uint32_t(dividend); const uint32_t _bh_ = m >> 32; const uint32_t _bl_ = uint32_t(m); #ifdef _MSC_VER uint32_t _s_ = s, result; __asm { // It's worth noting that simple benchmarks show this to be // no faster than straight division on an Intel E8400 // // eax and edx are clobbered by mul // eax = _ah_ // ebx is _cl_ // ecx is _tl_ - not initialized // edi is _th_ - not initialized // esi is _ch_ mov eax,_ah_ mov esi,_ch_ mov ebx,_cl_ mul _bl_ mov ecx,eax mov edi,edx mov eax,_al_ mul _bl_ add ebx,eax adc esi,edx adc edi,0 add ecx,esi adc edi,0 // eax = _ah_ // ecx is now free - use it for _bh_ mov ecx,_bh_ mov eax,_ah_ mul ecx mov ebx,eax mov esi,edx mov eax,_al_ mul ecx add ecx,eax adc edi,edx adc esi,0 add ebx,edi adc esi,0 mov cl,byte ptr [_s_] shrd ebx,esi,cl mov result,ebx } #ifdef DEBUG uint32_t checkResult = divinC(dividend); assert(checkResult == result); #endif return result; #else // Split in 3 asm sections to be able to satisfy operand // constraints: between two sections gcc can reassign operands // to different registers or memory locations. Apparently there // are only 3 free registers available on OSX (devel flavour). uint32_t _th_, _tl_; uint32_t dummy; asm ( "mull %[BL]\n\t" // eax = [AH] "movl %%eax,%[TL]\n\t" "movl %%edx,%[TH]\n\t" "movl %[AL],%%eax\n\t" "mull %[BL]\n\t" "addl %%eax,%[CL]\n\t" "adcl %%edx,%[CH]\n\t" "adcl $0,%[TH]\n\t" "addl %[CH],%[TL]\n\t" "adcl $0,%[TH]\n\t" : [TH] "=&rm" (_th_) , [TL] "=&r" (_tl_) , [CH] "=rm" (_ch_) , [CL] "=rm" (_cl_) , [EAX] "=&a" (dummy) : "[CH]" (_ch_) , "[CL]" (_cl_) , "[EAX]" (_ah_) , [AL] "m" (_al_) , [BL] "m" (_bl_) : "edx" ); asm ( "mull %[BH]\n\t" // eax = [AH] "movl %%eax,%[CL]\n\t" "movl %%edx,%[CH]\n\t" "movl %[AL],%%eax\n\t" "mull %[BH]\n\t" "addl %%eax,%[TL]\n\t" "adcl %%edx,%[TH]\n\t" "adcl $0,%[CH]\n\t" "addl %[TH],%[CL]\n\t" "adcl $0,%[CH]\n\t" : [CH] "=rm" (_ch_) , [CL] "=r" (_cl_) , [TH] "=&rm" (_th_) , [TL] "=&rm" (_tl_) , [EAX] "=&a" (dummy) : "[TH]" (_th_) , "[TL]" (_tl_) , "[EAX]" (_ah_) , [AL] "m" (_al_) , [BH] "m" (_bh_) : "edx" ); asm ( "shrd %%cl,%[CH],%[CL]\n\t" // SH = ecx : [CL] "=rm" (_cl_) : [CH] "r" (_ch_) , "[CL]" (_cl_) , [SH] "c" (s) ); return _cl_; #endif #elif defined(__arm__) uint32_t res; uint32_t dummy; asm volatile ( "ldmia %[RES],{r3,r4,r5,r6,r8}\n\t" "umull %[T],%[RES],%[AL],r3\n\t" // RES:T = AL * BL "adds %[T],%[T],r5\n\t" // T += CL "adcs %[RES],%[RES],r6\n\t" // RES += CH "mov r5,#0\n\t" "adc %[T],r5,r5\n\t" // T = carry "umlal %[RES],%[T],%[AH],r3\n\t" // T:RES = AH:AL * BL + CH:CL = TH:TL "umull r3,r6,%[AL],r4\n\t" // r6:r3 = AL * BH "adds r3,r3,%[RES]\n\t" // r3 += TL "adcs r6,r6,%[T]\n\t" // r6 += TH "adc r3,r5,r5\n\t" // r3 = carry "umlal r6,r3,%[AH],r4\n\t" // r3:r6 = AH:AL * BH:BL + CH:CL "rsb %[T],r8,#32\n\t" "lsr %[RES],r6,r8\n\t" //"orr %[RES],%[RES],r3,LSL %[T]\n\t" // not thumb2 "lsls r3,r3,%[T]\n\t" "orrs %[RES],%[RES],r3\n\t" //"mov %[AL],r3,LSR r8\n\t" // high word of result, should be 0 : [RES] "=r" (res) , [T] "=&r" (dummy) : "[RES]" (this) , [AL] "r" (uint32_t(dividend)) , [AH] "r" (dividend >> 32) : "r3","r4","r5","r6","r8" ); return res; #else return divinC(dividend); #endif } inline uint32_t divinC(uint64_t dividend) const { uint64_t t1 = uint64_t(uint32_t(dividend)) * uint32_t(m); uint64_t t2 = (dividend >> 32) * uint32_t(m); uint64_t t3 = uint32_t(dividend) * (m >> 32); uint64_t t4 = (dividend >> 32) * (m >> 32); uint64_t s1 = uint64_t(uint32_t(a)) + uint32_t(t1); uint64_t s2 = (s1 >> 32) + (a >> 32) + (t1 >> 32) + t2; uint64_t s3 = uint64_t(uint32_t(s2)) + uint32_t(t3); uint64_t s4 = (s3 >> 32) + (s2 >> 32) + (t3 >> 32) + t4; uint64_t result = s4 >> s; #ifdef DEBUG // we don't even want this overhead in devel builds assert(result == uint32_t(result)); #endif return uint32_t(result); } uint32_t mod(uint64_t dividend) const { #ifdef __arm__ uint32_t res; uint32_t dummy; asm volatile ( "ldmia %[RES],{r3,r4,r5,r6,r8,r9}\n\t" "umull %[T],%[RES],%[AL],r3\n\t" // RES:T = AL * BL "adds %[T],%[T],r5\n\t" // T += CL "adcs %[RES],%[RES],r6\n\t" // RES += CH "mov r5,#0\n\t" "adc %[T],r5,r5\n\t" // T = carry "umlal %[RES],%[T],%[AH],r3\n\t" // T:RES = AH:AL * BL + CH:CL = TH:TL "umull r3,r6,%[AL],r4\n\t" // r6:r3 = AL * BH "adds r3,r3,%[RES]\n\t" // r3 += TL "adcs r6,r6,%[T]\n\t" // r6 += TH "adc r3,r5,r5\n\t" // r3 = carry "umlal r6,r3,%[AH],r4\n\t" // r3:r6 = AH:AL * BH:BL + CH:CL "rsb %[T],r8,#32\n\t" "lsr %[RES],r6,r8\n\t" //"orr %[RES],%[RES],r3,LSL %[T]\n\t" // not thumb2 "lsls r3,r3,%[T]\n\t" "orrs %[RES],%[RES],r3\n\t" // RES = quotient (must fit in 32-bit) "mul %[AH],%[RES],r9\n\t" // AH = q * divisor "sub %[RES],%[AL],%[AH]\n\t" // RES = D - q*d : [RES] "=r" (res) , [T] "=&r" (dummy) : "[RES]" (this) , [AL] "r" (uint32_t(dividend)) , [AH] "r" (dividend >> 32) : "r3","r4","r5","r6","r8","r9" ); return res; #else assert(uint32_t(divisor) == divisor); // must fit in 32-bit uint64_t q = div(dividend); assert(uint32_t(q) == q); // must fit in 32 bit // result fits in 32-bit, so no 64-bit calculations required return uint32_t(dividend) - uint32_t(q) * uint32_t(divisor); #endif } private: // note: order is important for ARM asm routines uint64_t m; uint64_t a; uint32_t s; uint32_t divisor; // only used by mod() and getDivisor() }; } // namespace openmsx #endif // DIVISIONBYCONST_HH openMSX-RELEASE_0_12_0/src/utils/FixedPoint.hh000066400000000000000000000146221257557151200207650ustar00rootroot00000000000000#ifndef FIXEDPOINT_HH #define FIXEDPOINT_HH #include #include namespace openmsx { /** A fixed point number, implemented by a 32-bit signed integer. * The FRACTION_BITS template argument selects the position of the "binary * point" (base 2 equivalent to decimal point). */ template class FixedPoint { public: /** Number of fractional bits (export template parameter as a constant * so that external code can use it more easily). */ static const unsigned FRACTION_BITS = FRACTION_BITS_; private: /** Fixed point representation of 1. */ static const int ONE = 1 << FRACTION_BITS; /** Precalculated float value of 1 / ONE. */ static const float INV_ONE_F; /** Precalculated double value of 1 / ONE. */ static const double INV_ONE_D; /** Bitmask to filter out the fractional part of a fixed point * representation. */ static const int FRACTION_MASK = ONE - 1; public: /** Create new fixed point object from given representation. * Used by the overloaded operators. * @param value the internal representation. */ static inline FixedPoint create(const int value) { FixedPoint ret; ret.value = value; return ret; } /** Creates an uninitialized fixed point object. * This must be public to allow arrays of FixedPoint objects. */ explicit FixedPoint() {} // Conversion to fixed point: explicit FixedPoint(const int i) : value(i << FRACTION_BITS) {} explicit FixedPoint(const unsigned i) : value(i << FRACTION_BITS) {} explicit FixedPoint(const float f) : value(lrintf(f * ONE)) {} explicit FixedPoint(const double d) : value(lrint(d * ONE)) {} static FixedPoint roundRatioDown(unsigned n, unsigned d) { return create((static_cast(n) << FRACTION_BITS) / d); } static inline int shiftHelper(int x, int s) { return (s >= 0) ? (x >> s) : (x << -s); } template explicit FixedPoint(FixedPoint other) : value(shiftHelper(other.getRawValue(), BITS2 - FRACTION_BITS)) {} // Conversion from fixed point: /** * Returns the integer part (rounded down) of this fixed point number. * Note that for negative numbers, rounding occurs away from zero. */ int toInt() const { return value >> FRACTION_BITS; } /** * Returns the float value that corresponds to this fixed point number. */ float toFloat() const { return value * INV_ONE_F; } /** * Returns the double value that corresponds to this fixed point number. */ double toDouble() const { return value * INV_ONE_D; } /** * Returns the fractional part of this fixed point number as a float. * The fractional part is never negative, even for negative fixed point * numbers. * x.toInt() + x.fractionAsFloat() is approximately equal to x.toFloat() */ float fractionAsFloat() const { return (value & FRACTION_MASK) * INV_ONE_F; } /** * Returns the fractional part of this fixed point number as a double. * The fractional part is never negative, even for negative fixed point * numbers. * x.toInt() + x.fractionAsDouble() is approximately equal to x.toDouble() */ double fractionAsDouble() const { return (value & FRACTION_MASK) * INV_ONE_D; } // Various arithmetic: /** * Returns the result of a division between this fixed point number and * another, rounded towards zero. */ int divAsInt(const FixedPoint other) const { return value / other.value; } /** * Returns this value rounded down. * The result is equal to FixedPoint(fp.toInt()). */ FixedPoint floor() const { return create(value & ~FRACTION_MASK); } /** * Returns the fractional part of this value. * The result is equal to fp - floor(fp). */ FixedPoint fract() const { return create(value & FRACTION_MASK); } /** * Returns the fractional part of this value as an integer. * The result is equal to (fract() * (1 << FRACTION_BITS)).toInt() */ unsigned fractAsInt() const { return value & FRACTION_MASK; } // Arithmetic operators: FixedPoint operator+(const FixedPoint other) const { return create(value + other.value); } FixedPoint operator-(const FixedPoint other) const { return create(value - other.value); } FixedPoint operator*(const FixedPoint other) const { return create(int( (static_cast(value) * other.value) >> FRACTION_BITS)); } FixedPoint operator*(const int i) const { return create(value * i); } /** * Divides two fixed point numbers. * The fractional part is rounded down. */ FixedPoint operator/(const FixedPoint other) const { return create(int( (static_cast(value) << FRACTION_BITS) / other.value)); } FixedPoint operator/(const int i) const { return create(value / i); } FixedPoint operator<<(const int b) const { return create(value << b); } FixedPoint operator>>(const int b) const { return create(value >> b); } // Comparison operators: bool operator==(const FixedPoint other) const { return value == other.value; } bool operator!=(const FixedPoint other) const { return value != other.value; } bool operator<(const FixedPoint other) const { return value < other.value; } bool operator<=(const FixedPoint other) const { return value <= other.value; } bool operator>(const FixedPoint other) const { return value > other.value; } bool operator>=(const FixedPoint other) const { return value >= other.value; } // Arithmetic operators that modify this object: void operator+=(const FixedPoint other) { value += other.value; } void operator-=(const FixedPoint other) { value -= other.value; } /** Increase this value with the smallest possible amount. Typically * used to implement counters at the resolution of this datatype. */ void addQuantum() { value += 1; } // Should only be used by other instances of this class // templatized friend declarations are not possible in c++ int getRawValue() const { return value; } template void serialize(Archive& ar, unsigned /*version*/) { ar.serialize("value", value); } private: int value; }; // Force all constants being defined, some compilers need this: template const int FixedPoint::ONE; template const float FixedPoint::INV_ONE_F = 1.0f / ONE; template const double FixedPoint::INV_ONE_D = 1.0 / ONE; template const int FixedPoint::FRACTION_MASK; } // namespace openmsx #endif // FIXEDPOINT_HH openMSX-RELEASE_0_12_0/src/utils/HexDump.cc000066400000000000000000000023331257557151200202500ustar00rootroot00000000000000#include "HexDump.hh" #include #include namespace HexDump { using std::string; static char encode2(uint8_t x) { return (x < 10) ? (x + '0') : (x - 10 + 'A'); } static string encode(uint8_t x) { string result; result += encode2(x >> 4); result += encode2(x & 15); return result; } string encode(const void* input_, size_t len, bool newlines) { auto input = static_cast(input_); string ret; while (len) { if (newlines && !ret.empty()) ret += '\n'; int t = int(std::min(16, len)); for (int i = 0; i < t; ++i) { ret += encode(*input++); if (i != (t - 1)) ret += ' '; } len -= t; } return ret; } static int decode(char x) { if (('0' <= x) && (x <= '9')) { return x - '0'; } else if (('A' <= x) && (x <= 'F')) { return x - 'A' + 10; } else if (('a' <= x) && (x <= 'f')) { return x - 'a' + 10; } else { return -1; } } string decode(const string& input) { string ret; const size_t len = input.size(); bool flip = true; char tmp = 0; for (size_t in = 0; in < len; ++in) { int d = decode(input[in]); if (d == -1) continue; if (flip) { tmp = d << 4; } else { tmp |= d; ret += tmp; } flip = !flip; } return ret; } } // namespace HexDump openMSX-RELEASE_0_12_0/src/utils/HexDump.hh000066400000000000000000000003211257557151200202550ustar00rootroot00000000000000#ifndef HEXDUMP_HH #define HEXDUMP_HH #include namespace HexDump { std::string encode(const void* input, size_t len, bool newlines = true); std::string decode(const std::string& input); } #endif openMSX-RELEASE_0_12_0/src/utils/KeyRange.hh000066400000000000000000000032741257557151200204220ustar00rootroot00000000000000#ifndef KEYRANGE_HH #define KEYRANGE_HH #include #include namespace detail { template class KeyIterator { using map_iter = typename MAP::const_iterator; using pair_type = typename std::iterator_traits::value_type; public: using value_type = const typename std::tuple_element::type; using pointer = value_type*; using reference = value_type&; using difference_type = typename std::iterator_traits::difference_type; using iterator_category = std::forward_iterator_tag; KeyIterator(map_iter it_) : it(it_) {} reference operator*() const { return std::get(*it); } KeyIterator& operator++() { ++it; return *this; } bool operator==(KeyIterator& other) const { return it == other.it; } bool operator!=(KeyIterator& other) const { return it != other.it; } private: map_iter it; }; template class KeyRange { public: KeyRange(const MAP& map_) : map(map_) {} KeyIterator begin() const { return map.begin(); } KeyIterator end() const { return map.end(); } private: const MAP& map; }; } // namespace detail // Input: a collection that contains key-value pairs or tuples. // Output: a range that represents (only) the keys, the values or the N-th // elements of the items in the input. template detail::KeyRange keys(const MAP& map) { return detail::KeyRange(map); } template detail::KeyRange values(const MAP& map) { return detail::KeyRange(map); } template detail::KeyRange elements(const MAP& map) { return detail::KeyRange(map); } #endif openMSX-RELEASE_0_12_0/src/utils/Math.cc000066400000000000000000000004721257557151200175710ustar00rootroot00000000000000#include "Math.hh" namespace Math { unsigned powerOfTwo(unsigned a) { // classical implementation: // unsigned res = 1; // while (a > res) res <<= 1; // return res; // optimized version a += (a == 0); // can be removed if argument is never zero return floodRight(a - 1) + 1; } } // namespace Math openMSX-RELEASE_0_12_0/src/utils/Math.hh000066400000000000000000000140401257557151200175770ustar00rootroot00000000000000#ifndef MATH_HH #define MATH_HH #include "likely.hh" #include #include #include // M_PI is a very common extension, but not guaranteed to be defined by // when compiling in a strict standards compliant mode. #ifndef M_PI #define M_PI 3.14159265358979323846 #endif namespace Math { /** Is the given number an integer power of 2? * Not correct for zero (according to this test 0 is a power of 2). */ inline bool isPowerOfTwo(unsigned a) { return (a & (a - 1)) == 0; } /** Returns the smallest number that is both >=a and a power of two. */ unsigned powerOfTwo(unsigned a); /** Clips x to the range [LO,HI]. * Slightly faster than std::min(HI, std::max(LO, x)) * especially when no clipping is required. */ template inline int clip(int x) { static_assert(LO <= HI, "invalid clip range"); return unsigned(x - LO) <= unsigned(HI - LO) ? x : (x < HI ? LO : HI); } /** Clip x to range [-32768,32767]. Special case of the version above. * Optimized for the case when no clipping is needed. */ inline int16_t clipIntToShort(int x) { static_assert((-1 >> 1) == -1, "right-shift must preserve sign"); return likely(int16_t(x) == x) ? x : (0x7FFF - (x >> 31)); } /** Clip x to range [0,255]. * Optimized for the case when no clipping is needed. */ inline uint8_t clipIntToByte(int x) { static_assert((-1 >> 1) == -1, "right-shift must preserve sign"); return likely(uint8_t(x) == x) ? x : ~(x >> 31); } /** Calculate greatest common divider of two strictly positive integers. * Classical implementation is like this: * while (unsigned t = b % a) { b = a; a = t; } * return a; * The following implementation avoids the costly modulo operation. It * is about 40% faster on my machine. * * require: a != 0 && b != 0 */ inline unsigned gcd(unsigned a, unsigned b) { unsigned k = 0; while (((a & 1) == 0) && ((b & 1) == 0)) { a >>= 1; b >>= 1; ++k; } // either a or b (or both) is odd while ((a & 1) == 0) a >>= 1; while ((b & 1) == 0) b >>= 1; // both a and b odd while (a != b) { if (a >= b) { a -= b; do { a >>= 1; } while ((a & 1) == 0); } else { b -= a; do { b >>= 1; } while ((b & 1) == 0); } } return b << k; } /** Reverse the lower N bits of a given value. * The upper 32-N bits from the input are ignored and will be returned as 0. * For example reverseNBits('xxxabcde', 5) returns '000edcba' (binary notation). */ inline unsigned reverseNBits(unsigned x, unsigned bits) { unsigned ret = 0; while (bits--) { ret = (ret << 1) | (x & 1); x >>= 1; } return ret; /* Just for fun I tried the asm version below (the carry-flag trick * cannot be described in plain C). It's correct and generates shorter * code (both less instructions and less bytes). But it doesn't * actually run faster on the machine I tested on, or only a tiny bit * (possibly because of dependency chains and processor stalls???). * However a big disadvantage of this asm version is that when called * with compile-time constant arguments, this version performs exactly * the same, while the version above can be further optimized by the * compiler (constant-propagation, loop unrolling, ...). unsigned ret = 0; if (bits) { asm ( "1: shr %[VAL]\n" " adc %[RET],%[RET]\n" " dec %[BITS]\n" " jne 1b\n" : [VAL] "+r" (val) , [BITS] "+r" (bits) , [RET] "+r" (ret) ); } return ret; */ /* Maarten suggested the following approach with O(lg(N)) time * complexity (the version above is O(N)). * - reverse full (32-bit) word: O(lg(N)) * - shift right over 32-N bits: O(1) * Note: In some lower end CPU the shift-over-N-bits instruction itself * is O(N), in that case this whole algorithm is O(N) * Note2: Instead of '32' it's also possible to use a lower power of 2, * as long as it's bigger than or equal to N. * This algorithm may or may not be faster than the version above, I * didn't try it yet. Also because this routine is _NOT_ performance * critical _AT_ALL_ currently. */ } /** Reverse the bits in a byte. * This is equivalent to (but faster than) reverseNBits(x, 8); */ inline uint8_t reverseByte(uint8_t a) { // Classical implementation (can be extended to 16 and 32 bits) // a = ((a & 0xF0) >> 4) | ((a & 0x0F) << 4); // a = ((a & 0xCC) >> 2) | ((a & 0x33) << 2); // a = ((a & 0xAA) >> 1) | ((a & 0x55) << 1); // return a; // The versions below are specific to reverse a single byte (can't // easily be extended to wider types). Found these tricks on: // http://graphics.stanford.edu/~seander/bithacks.html #ifdef __x86_64 // on 64-bit systems this is slightly faster return (((a * 0x80200802ULL) & 0x0884422110ULL) * 0x0101010101ULL) >> 32; #else // on 32-bit systems this is faster return (((a * 0x0802 & 0x22110) | (a * 0x8020 & 0x88440)) * 0x10101) >> 16; #endif } /** Returns the smallest number of the form 2^n-1 that is greater or equal * to the given number. * The resulting number has the same number of leading zeros as the input, * but starting from the first 1-bit in the input all bits more to the right * are also 1. */ template inline T floodRight(T x) { x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> ((sizeof(x) >= 2) ? 8 : 0); // Written in a weird way to x |= x >> ((sizeof(x) >= 4) ? 16 : 0); // suppress compiler warnings. x |= x >> ((sizeof(x) >= 8) ? 32 : 0); // Generates equally efficient return x; // code. } /** Count the number of leading zero-bits in the given word. * The result is undefined when the input is zero (all bits are zero). */ inline unsigned countLeadingZeros(unsigned x) { #ifdef __GNUC__ // actually this only exists starting from gcc-3.4.x return __builtin_clz(x); // undefined when x==0 #else // gives incorrect result for x==0, but that doesn't matter here unsigned lz = 0; if (x <= 0x0000ffff) { lz += 16; x <<= 16; } if (x <= 0x00ffffff) { lz += 8; x <<= 8; } if (x <= 0x0fffffff) { lz += 4; x <<= 4; } lz += (0x55ac >> ((x >> 27) & 0x1e)) & 0x3; return lz; #endif } } // namespace Math #endif // MATH_HH openMSX-RELEASE_0_12_0/src/utils/MemBuffer.hh000066400000000000000000000122741257557151200205650ustar00rootroot00000000000000#ifndef MEMBUFFER_HH #define MEMBUFFER_HH #include "noncopyable.hh" #include "MemoryOps.hh" #include "alignof.hh" #include #include // for bad_alloc #include #include namespace openmsx { // When SSE2 is enabled (some) buffers need to be 16-bytes aligned. If not // then don't enforce stricter than default alignment. #ifdef __SSE2__ static const size_t SSE2_ALIGNMENT = 16; #else static const size_t SSE2_ALIGNMENT = 0; #endif /** This class manages the lifetime of a block of memory. * * Its two main use cases are: * 1) As a safer alternative for new[] / delete[]. * Using this class makes sure the memory block is always properly * cleaned up. Also in case of exceptions. * 2) As an alternative for vector ('byte' or other primitive types). * The main difference with vector is that the allocated block is left * uninitialized. This can be a bit more efficient if the block will * anyway soon be overwritten. * * Like vector this buffer can dynamically grow or shrink. But it's not * optimized for this case (it doesn't keep track of extra capacity). If you * need frequent resizing prefer to use vector instead of this class. */ template class MemBuffer //: private noncopyable { public: /** Construct an empty MemBuffer, no memory is allocated. */ MemBuffer() : dat(nullptr) #ifdef DEBUG , sz(0) #endif { } /** Construct a (uninitialized) memory buffer of given size. */ explicit MemBuffer(size_t size) : dat(static_cast(my_malloc(size * sizeof(T)))) #ifdef DEBUG , sz(size) #endif { } /** Move constructor. */ MemBuffer(MemBuffer&& other) : dat(other.dat) #ifdef DEBUG , sz(other.sz) #endif { other.dat = nullptr; } /** Move assignment. */ MemBuffer& operator=(MemBuffer&& other) { std::swap(dat, other.dat); #ifdef DEBUG std::swap(sz , other.sz); #endif return *this; } /** Free the memory buffer. */ ~MemBuffer() { my_free(dat); } /** Returns pointer to the start of the memory buffer. * This method can be called even when there's no buffer allocated. */ const T* data() const { return dat; } T* data() { return dat; } /** Access elements in the memory buffer. */ const T& operator[](size_t i) const { #ifdef DEBUG assert(i < sz); #endif return dat[i]; } T& operator[](size_t i) { #ifdef DEBUG assert(i < sz); #endif return dat[i]; } /** No memory allocated? */ bool empty() const { return !dat; } /** Grow or shrink the memory block. * In case of growing, the extra space is left uninitialized. * It is possible (even likely) that the memory buffer is copied * to a new location after this call, so data() will return a * different pointer value. */ void resize(size_t size) { if (size) { dat = static_cast(my_realloc(dat, size * sizeof(T))); #ifdef DEBUG sz = size; #endif } else { clear(); } } /** Free the allocated memory block and set the current size to 0. */ void clear() { my_free(dat); dat = nullptr; #ifdef DEBUG sz = 0; #endif } /** Swap the managed memory block of two MemBuffers. */ void swap(MemBuffer& other) { std::swap(dat, other.dat); #ifdef DEBUG std::swap(sz , other.sz ); #endif } private: #if defined(_MSC_VER) // Make non-copyable/assignable. // Strictly according to the c++11 standard this is not needed because // there's a user-defined move-constructor/assignment. Though visual // studio 2013 isn't fully standard compliant yet (it still provides // an auto-generated copy-constructor/assignment). Visual studio 2013 // also doesn't support the '=delete' syntax yet. MemBuffer(const MemBuffer&); MemBuffer& operator=(const MemBuffer&); #endif // If the requested alignment is less or equally strict than the // guaranteed alignment by the standard malloc()-like functions // we use those. Otherwise we use platform specific functions to // request aligned memory. // A valid alternative would be to always use the platform specific // functions. The only disadvantage is that we cannot use realloc() // in that case (there are no, not even platform specific, functions // to realloc memory with bigger than default alignment). static const bool SIMPLE_MALLOC = ALIGNMENT <= ALIGNOF(MAX_ALIGN_T); void* my_malloc(size_t bytes) { void* result; if (SIMPLE_MALLOC) { result = malloc(bytes); if (!result && bytes) throw std::bad_alloc(); } else { // already throws bad_alloc in case of error result = MemoryOps::mallocAligned(ALIGNMENT, bytes); } return result; } void my_free(void* p) { if (SIMPLE_MALLOC) { free(p); } else { MemoryOps::freeAligned(p); } } void* my_realloc(void* old, size_t bytes) { void* result; if (SIMPLE_MALLOC) { result = realloc(old, bytes); if (!result && bytes) throw std::bad_alloc(); } else { result = MemoryOps::mallocAligned(ALIGNMENT, bytes); if (!result && bytes) throw std::bad_alloc(); MemoryOps::freeAligned(old); } return result; } private: T* dat; #ifdef DEBUG size_t sz; #endif }; } // namespace openmsx namespace std { template void swap(openmsx::MemBuffer& l, openmsx::MemBuffer& r) { l.swap(r); } } #endif openMSX-RELEASE_0_12_0/src/utils/MemoryOps.cc000066400000000000000000000204131257557151200206270ustar00rootroot00000000000000#include "MemoryOps.hh" #include "likely.hh" #include "build-info.hh" #include "systemfuncs.hh" #include "Math.hh" #include "stl.hh" #include "unreachable.hh" #include #include #include #include #include #include // for std::bad_alloc #if ASM_X86 && defined _MSC_VER #include // for __stosd intrinsic #endif #ifdef __SSE2__ #include #endif namespace openmsx { namespace MemoryOps { #ifdef __SSE2__ #if ASM_X86_32 && defined _MSC_VER // Gcc has the _mm_set1_epi64x() function for both 32 and 64 bit. Visual studio // only has it for 64 bit. So we add it ourselves for vc++/32-bit. An // alternative would be to always use this routine, but this generates worse // code than the real _mm_set1_epi64x() function for gcc (both 32 and 64 bit). static inline __m128i _mm_set1_epi64x(uint64_t val) { uint32_t low = val >> 32; uint32_t high = val >> 0; return _mm_set_epi32(low, high, low, high); } #endif static inline void memset_64_SSE( uint64_t* dest, size_t num64, uint64_t val64) { if (unlikely(num64 == 0)) return; // Align at 16-byte boundary. if (unlikely(size_t(dest) & 8)) { dest[0] = val64; ++dest; --num64; } __m128i val128 = _mm_set1_epi64x(val64); uint64_t* e = dest + num64 - 3; for (/**/; dest < e; dest += 4) { _mm_store_si128(reinterpret_cast<__m128i*>(dest + 0), val128); _mm_store_si128(reinterpret_cast<__m128i*>(dest + 2), val128); } if (unlikely(num64 & 2)) { _mm_store_si128(reinterpret_cast<__m128i*>(dest), val128); dest += 2; } if (unlikely(num64 & 1)) { dest[0] = val64; } } #endif static inline void memset_64( uint64_t* dest, size_t num64, uint64_t val64) { assert((size_t(dest) % 8) == 0); // must be 8-byte aligned #ifdef __SSE2__ memset_64_SSE(dest, num64, val64); return; #endif uint64_t* e = dest + num64 - 3; for (/**/; dest < e; dest += 4) { dest[0] = val64; dest[1] = val64; dest[2] = val64; dest[3] = val64; } if (unlikely(num64 & 2)) { dest[0] = val64; dest[1] = val64; dest += 2; } if (unlikely(num64 & 1)) { dest[0] = val64; } } static inline void memset_32_2( uint32_t* dest, size_t num32, uint32_t val0, uint32_t val1) { assert((size_t(dest) % 4) == 0); // must be 4-byte aligned if (unlikely(num32 == 0)) return; // Align at 8-byte boundary. if (unlikely(size_t(dest) & 4)) { dest[0] = val1; // start at odd pixel ++dest; --num32; } uint64_t val64 = OPENMSX_BIGENDIAN ? (uint64_t(val0) << 32) | val1 : val0 | (uint64_t(val1) << 32); memset_64(reinterpret_cast(dest), num32 / 2, val64); if (unlikely(num32 & 1)) { dest[num32 - 1] = val0; } } static inline void memset_32(uint32_t* dest, size_t num32, uint32_t val32) { assert((size_t(dest) % 4) == 0); // must be 4-byte aligned #if ASM_X86 #if defined _MSC_VER // VC++'s __stosd intrinsic results in emulator benchmarks // running about 7% faster than with memset_32_2, streaming or not, // and about 3% faster than the C code below. __stosd(reinterpret_cast(dest), val32, num32); #else memset_32_2(dest, num32, val32, val32); #endif #elif defined __arm__ // Ideally the first mov(*) instruction could be omitted (and then // replace 'r3' with '%[val]'. But this can cause problems in the // 'stm' instructions when the compiler chooses a register // 'bigger' than r4 for [val]. See commit message for LOTS more // details. asm volatile ( "mov r3, %[val]\n\t" // (*) should not be needed "mov r4, r3\n\t" "mov r5, r3\n\t" "mov r6, r3\n\t" "subs %[num],%[num],#8\n\t" "bmi 1f\n" "mov r8, r3\n\t" "mov r9, r3\n\t" "mov r10,r3\n\t" "mov r12,r3\n\t" "0:\n\t" "stmia %[dest]!,{r3,r4,r5,r6,r8,r9,r10,r12}\n\t" "subs %[num],%[num],#8\n\t" "bpl 0b\n\t" "1:\n\t" "tst %[num],#4\n\t" "it ne\n\t" "stmne %[dest]!,{r3,r4,r5,r6}\n\t" "tst %[num],#2\n\t" "it ne\n\t" "stmne %[dest]!,{r3,r4}\n\t" "tst %[num],#1\n\t" "it ne\n\t" "strne r3,[%[dest]]\n\t" : [dest] "=r" (dest) , [num] "=r" (num32) : "[dest]" (dest) , "[num]" (num32) , [val] "r" (val32) : "memory" , "r3","r4","r5","r6","r8","r9","r10","r12" ); return; #else uint32_t* e = dest + num32 - 7; for (/**/; dest < e; dest += 8) { dest[0] = val32; dest[1] = val32; dest[2] = val32; dest[3] = val32; dest[4] = val32; dest[5] = val32; dest[6] = val32; dest[7] = val32; } if (unlikely(num32 & 4)) { dest[0] = val32; dest[1] = val32; dest[2] = val32; dest[3] = val32; dest += 4; } if (unlikely(num32 & 2)) { dest[0] = val32; dest[1] = val32; dest += 2; } if (unlikely(num32 & 1)) { dest[0] = val32; } #endif } static inline void memset_16_2( uint16_t* dest, size_t num16, uint16_t val0, uint16_t val1) { if (unlikely(num16 == 0)) return; // Align at 4-byte boundary. if (unlikely(size_t(dest) & 2)) { dest[0] = val1; // start at odd pixel ++dest; --num16; } uint32_t val32 = OPENMSX_BIGENDIAN ? (uint32_t(val0) << 16) | val1 : val0 | (uint32_t(val1) << 16); memset_32(reinterpret_cast(dest), num16 / 2, val32); if (unlikely(num16 & 1)) { dest[num16 - 1] = val0; } } static inline void memset_16(uint16_t* dest, size_t num16, uint16_t val16) { memset_16_2(dest, num16, val16, val16); } template void MemSet::operator()( Pixel* dest, size_t num, Pixel val) const { if (sizeof(Pixel) == 2) { memset_16(reinterpret_cast(dest), num, val); } else if (sizeof(Pixel) == 4) { memset_32(reinterpret_cast(dest), num, val); } else { UNREACHABLE; } } template void MemSet2::operator()( Pixel* dest, size_t num, Pixel val0, Pixel val1) const { if (sizeof(Pixel) == 2) { memset_16_2(reinterpret_cast(dest), num, val0, val1); } else if (sizeof(Pixel) == 4) { memset_32_2(reinterpret_cast(dest), num, val0, val1); } else { UNREACHABLE; } } // Force template instantiation template struct MemSet ; template struct MemSet ; template struct MemSet2; template struct MemSet2; /** Aligned memory (de)allocation */ // Helper class to keep track of aligned/unaligned pointer pairs class AllocMap { public: static AllocMap& instance() { static AllocMap oneInstance; return oneInstance; } void insert(void* aligned, void* unaligned) { if (!aligned) return; assert(none_of(begin(allocMap), end(allocMap), EqualTupleValue<0>(aligned))); allocMap.emplace_back(aligned, unaligned); } void* remove(void* aligned) { if (!aligned) return nullptr; // LIFO order is more likely than FIFO -> search backwards auto it = find_if_unguarded(allocMap.rbegin(), allocMap.rend(), EqualTupleValue<0>(aligned)); // return the associated unaligned value void* unaligned = it->second; // instead of vector::erase(), swap with back and drop that *it = allocMap.back(); allocMap.pop_back(); return unaligned; } private: AllocMap() {} ~AllocMap() { assert(allocMap.empty()); } // typically contains 5-10 items, so (unsorted) vector is fine std::vector> allocMap; }; void* mallocAligned(size_t alignment, size_t size) { assert("must be a power of 2" && Math::isPowerOfTwo(alignment)); assert(alignment >= sizeof(void*)); #if HAVE_POSIX_MEMALIGN void* aligned; if (posix_memalign(&aligned, alignment, size)) { throw std::bad_alloc(); } #if defined DEBUG AllocMap::instance().insert(aligned, aligned); #endif return aligned; #elif defined _MSC_VER void* result = _aligned_malloc(size, alignment); if (!result && size) throw std::bad_alloc(); return result; #else auto t = alignment - 1; void* unaligned = malloc(size + t); if (!unaligned) { throw std::bad_alloc(); } auto aligned = reinterpret_cast( (reinterpret_cast(unaligned) + t) & ~t); AllocMap::instance().insert(aligned, unaligned); return aligned; #endif } void freeAligned(void* aligned) { #if HAVE_POSIX_MEMALIGN #if defined DEBUG AllocMap::instance().remove(aligned); #endif free(aligned); #elif defined _MSC_VER return _aligned_free(aligned); #else void* unaligned = AllocMap::instance().remove(aligned); free(unaligned); #endif } } // namespace MemoryOps } // namespace openmsx openMSX-RELEASE_0_12_0/src/utils/MemoryOps.hh000066400000000000000000000007771257557151200206540ustar00rootroot00000000000000#ifndef MEMORYOPS_HH #define MEMORYOPS_HH #include namespace openmsx { namespace MemoryOps { template struct MemSet { void operator()(Pixel* out, size_t num, Pixel val) const; }; template struct MemSet2 { void operator()(Pixel* out, size_t num, Pixel val0, Pixel val1) const; }; void* mallocAligned(size_t alignment, size_t size); void freeAligned(void* ptr); } // namespace MemoryOps } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/utils/Observer.hh000066400000000000000000000005401257557151200204750ustar00rootroot00000000000000#ifndef OBSERVER_HH #define OBSERVER_HH namespace openmsx { /** * Generic Gang-of-Four Observer class, templatized edition. */ template class Observer { public: virtual void update(const T& subject) = 0; virtual void subjectDeleted(const T& /*subject*/) { /*nothing*/ } protected: ~Observer() {} }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/utils/ScopedAssign.hh000066400000000000000000000006041257557151200212710ustar00rootroot00000000000000#ifndef SCOPEDASSIGN_HH #define SCOPEDASSIGN_HH /** Assign new value to some variable and restore the original value * when this object goes out of scope. */ template class ScopedAssign { public: ScopedAssign(T& var_, T newValue) : var(var_) { oldValue = var; var = newValue; } ~ScopedAssign() { var = oldValue; } private: T& var; T oldValue; }; #endif openMSX-RELEASE_0_12_0/src/utils/SerializeBuffer.cc000066400000000000000000000050251257557151200217600ustar00rootroot00000000000000#include "SerializeBuffer.hh" #include "likely.hh" #include // for bad_alloc #include namespace openmsx { // class OutputBuffer size_t OutputBuffer::lastSize = 50000; // initial estimate OutputBuffer::OutputBuffer() : buf(lastSize) , end(buf.data()) , finish(buf.data() + lastSize) { // We've allocated a buffer with an estimated initial size. This // estimate is based on the largest intermediate size of the previously // required buffers. // For correctness this initial size doesn't matter (we could even not // allocate any initial buffer at all). But it can make a difference in // performance. If later we discover the buffer is too small, we have // to reallocate (and thus make a copy). In profiling this reallocation // step was noticable. // Slowly drop the estimated required size. This makes sure that when // we've overestimated the size once, we don't forever keep this too // high value. For performance an overestimation is less bad than an // underestimation. lastSize -= lastSize >> 7; } #ifdef __GNUC__ template void OutputBuffer::insertN(const void* __restrict data) { byte* newEnd = end + LEN; if (likely(newEnd <= finish)) { memcpy(end, data, LEN); end = newEnd; } else { insertGrow(data, LEN); } } // Force template instantiation template void OutputBuffer::insertN<1>(const void* __restrict data); template void OutputBuffer::insertN<2>(const void* __restrict data); template void OutputBuffer::insertN<4>(const void* __restrict data); template void OutputBuffer::insertN<8>(const void* __restrict data); #endif void OutputBuffer::insertN(const void* __restrict data, size_t len) { byte* newEnd = end + len; if (likely(newEnd <= finish)) { memcpy(end, data, len); end = newEnd; } else { insertGrow(data, len); } } MemBuffer OutputBuffer::release(size_t& size) { size = end - buf.data(); // Deallocate unused buffer space. buf.resize(size); end = finish = nullptr; return std::move(buf); } void OutputBuffer::insertGrow(const void* __restrict data, size_t len) { byte* pos = allocateGrow(len); memcpy(pos, data, len); } byte* OutputBuffer::allocateGrow(size_t len) { size_t oldSize = end - buf.data(); size_t newSize = std::max(oldSize + len, oldSize + oldSize / 2); buf.resize(newSize); end = buf.data() + oldSize + len; finish = buf.data() + newSize; return buf.data() + oldSize; } // class InputBuffer InputBuffer::InputBuffer(const byte* data, size_t size) : buf(data) #ifndef NDEBUG , finish(buf + size) #endif { (void)size; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/utils/SerializeBuffer.hh000066400000000000000000000122601257557151200217710ustar00rootroot00000000000000#ifndef SERIALIZEBUFFER_HH #define SERIALIZEBUFFER_HH #include "MemBuffer.hh" #include "openmsx.hh" #include "noncopyable.hh" #include #include #include namespace openmsx { /** Memory output buffer * * Acts as a replacement for std::vector. You can insert data in the * buffer and the buffer will automatically grow. Like std::vector it manages * an internal memory buffer that will automatically reallocate and grow * exponentially. * * This class is much less general than std::vector and optimized for the case * of lots of (small) inserts at the end of the buffer (the main use case of * in-memory savestates). This makes it more efficient than std::vector. * std::vector is far from inefficient, but for savestates this is used A LOT, * so even small improvements matter a lot. */ class OutputBuffer : private noncopyable { public: /** Create an empty output buffer. */ OutputBuffer(); /** Insert data at the end of this buffer. * This will automatically grow this buffer. */ void insert(const void* __restrict data, size_t len) { #ifdef __GNUC__ if (__builtin_constant_p(len)) { if (len == 1) { insertN<1>(data); return; } else if (len == 2) { insertN<2>(data); return; } else if (len == 4) { insertN<4>(data); return; } else if (len == 8) { insertN<8>(data); return; } } #endif insertN(data, len); } #ifdef __GNUC__ template void insertN(const void* __restrict data); #endif void insertN(const void* __restrict data, size_t len); /** Insert data at a given position. This will overwrite the old data. * It's not possible to grow the buffer via this method (so the buffer * must already be big enough to hold the new data). */ void insertAt(size_t pos, const void* __restrict data, size_t len) { assert(buf.data() + pos + len <= finish); memcpy(buf.data() + pos, data, len); } /** Reserve space to insert the given number of bytes. * The returned pointer is only valid until the next internal * reallocate, so till the next call to insert() or allocate(). * * If you don't know yet exactly how much memory to allocate (e.g. * when the buffer will be used for gzip output data), you can request * the maximum size and deallocate the unused space later. */ byte* allocate(size_t len) { byte* newEnd = end + len; // Make sure the next OutputBuffer will start with an initial size // that can hold this much space plus some slack. size_t newSize = newEnd - buf.data(); lastSize = std::max(lastSize, newSize + 1000); if (newEnd <= finish) { byte* result = end; end = newEnd; return result; } else { return allocateGrow(len); } } /** Free part of a previously allocated buffer. * * The parameter must point right after the last byte of the used * portion of the buffer. So it must be in the range [buf, buf+num] * with buf and num respectively the return value and the parameter * of the last allocate() call. * * See comment in allocate(). This call must be done right after the * allocate() call, there cannot be any other (non-const) call to this * object in between. */ void deallocate(byte* pos) { assert(buf.data() <= pos); assert(pos <= end); end = pos; } /** Get the current size of the buffer. */ size_t getPosition() const { return end - buf.data(); } /** Release ownership of the buffer. * Returns both the buffer and its size. */ MemBuffer release(size_t& size); private: void insertGrow(const void* __restrict data, size_t len); byte* allocateGrow(size_t len); MemBuffer buf; // begin of allocated memory byte* end; // points right after the last used byte // so end - buf == size byte* finish; // points right after the last allocated byte // so finish - buf == capacity static size_t lastSize; }; /** This class is complementary to the OutputBuffer class. * Instead of filling an initially empty buffer it starts from a filled buffer * and allows to retrieve items starting from the start of the buffer. */ class InputBuffer : private noncopyable { public: /** Construct new InputBuffer, typically the data and size parameters * will come from a MemBuffer object. */ InputBuffer(const byte* data, size_t size); /** Read the given number of bytes. * This 'consumes' the read bytes, so a future read() will continue * where this read stopped. */ void read(void* __restrict result, size_t len) __restrict { memcpy(result, buf, len); buf += len; assert(buf <= finish); } /** Skip the given number of bytes. * This is similar to a read(), but it will only consume the data, not * copy it. */ void skip(size_t len) { buf += len; assert(buf <= finish); } /** Return a pointer to the current position in the buffer. * This is useful if you don't want to copy the data, but e.g. use it * as input for an uncompress algorithm. You can later use skip() to * actually consume the data. */ const byte* getCurrentPos() const { return buf; } private: const byte* buf; #ifndef NDEBUG const byte* finish; // only used to check asserts #endif }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/utils/String32.hh000066400000000000000000000027511257557151200203270ustar00rootroot00000000000000#ifndef STRING32_HH #define STRING32_HH #include #include #include #include // Given a buffer of max size 4GB, 32 bits are enough to address each position // in that buffer. // - On 32-bit systems we can use a pointer to a location in the buffer. // - On 64-bit systems a pointer is too large (64 bit), but an index in the // buffer works fine. (An index works fine on 32-bit systems as well, but is // slightly less efficient). // The String32 helper abstracts the difference between the two above // approaches. In both cases it is a 32-bit type (hence the name). And on // both 32/64-bit systems it uses the more effient implementation. using String32 = std::conditional< (sizeof(char*) > sizeof(uint32_t)), // is a pointer bigger than an uint32_t uint32_t, // yes -> use uint32_t const char*>::type; // no -> directly use pointer // convert string in buffer to String32 inline void toString32(const char* buffer, const char* str, uint32_t& result) { assert(buffer <= str); assert(str < (buffer + std::numeric_limits::max())); result = str - buffer; } inline void toString32(const char* /*buffer*/, const char* str, const char*& result) { result = str; } // convert String32 back to string in buffer inline const char* fromString32(const char* buffer, uint32_t str32) { return buffer + str32; } inline const char* fromString32(const char* /*buffer*/, const char* str32) { return str32; } #endif openMSX-RELEASE_0_12_0/src/utils/StringOp.cc000066400000000000000000000262641257557151200204540ustar00rootroot00000000000000#include "StringOp.hh" #include "MSXException.hh" #include #include #include #include #include using std::advance; using std::equal; using std::string; using std::transform; using std::vector; using std::set; namespace StringOp { // class Builder Builder::Builder() { } Builder::~Builder() { } Builder& Builder::operator<<(const std::string& t) { buf += t; return *this; } Builder& Builder::operator<<(string_ref t) { buf.append(t.data(), t.size()); return *this; } Builder& Builder::operator<<(const char* t) { buf += t; return *this; } Builder& Builder::operator<<(unsigned char t) { return operator<<(unsigned(t)); } Builder& Builder::operator<<(unsigned short t) { buf += toString(t); return *this; } Builder& Builder::operator<<(unsigned t) { buf += toString(t); return *this; } Builder& Builder::operator<<(unsigned long t) { buf += toString(t); return *this; } Builder& Builder::operator<<(unsigned long long t) { buf += toString(t); return *this; } Builder& Builder::operator<<(char t) { buf += t; return *this; } Builder& Builder::operator<<(short t) { buf += toString(t); return *this; } Builder& Builder::operator<<(int t) { buf += toString(t); return *this; } Builder& Builder::operator<<(long t) { buf += toString(t); return *this; } Builder& Builder::operator<<(long long t) { buf += toString(t); return *this; } Builder& Builder::operator<<(float t) { buf += toString(t); return *this; } Builder& Builder::operator<<(double t) { buf += toString(t); return *this; } // Returns a fast type that is (at least) big enough to hold the absolute value // of values of the given type. (It always returns 'unsigned' except for 64-bit // integers it returns unsigned long long). template struct FastUnsigned { using type = unsigned; }; template<> struct FastUnsigned { using type = unsigned long long; }; template<> struct FastUnsigned { using type = unsigned long long; }; template<> struct FastUnsigned { using type = unsigned long; }; template<> struct FastUnsigned { using type = unsigned long; }; // This does the equivalent of // unsigned u = (t < 0) ? -t : t; // but it avoids a compiler warning on the operations // 't < 0' and '-t' // when 't' is actually an unsigned type. template struct AbsHelper; template<> struct AbsHelper { template inline typename FastUnsigned::type operator()(T t) const { return (t < 0) ? -t : t; } }; template<> struct AbsHelper { template inline typename FastUnsigned::type operator()(T t) const { return t; } }; // Does the equivalent of if (t < 0) *--p = '-'; // but it avoids a compiler warning on 't < 0' when 't' is an unsigned type. template struct PutSign; template<> struct PutSign { template inline void operator()(T t, char*& p) const { if (t < 0) *--p = '-'; } }; template<> struct PutSign { template inline void operator()(T /*t*/, char*& /*p*/) const { // nothing } }; // This routine is inspired by boost::lexical_cast. It's much faster than a // generic version using std::stringstream. See this page for some numbers: // http://www.boost.org/doc/libs/1_47_0/libs/conversion/lexical_cast.htm#performance template static inline string toStringImpl(T t) { static const bool IS_SIGNED = std::numeric_limits::is_signed; static const unsigned BUF_SIZE = 1 + std::numeric_limits::digits10 + (IS_SIGNED ? 1 : 0); char buf[BUF_SIZE]; char* p = &buf[BUF_SIZE]; AbsHelper absHelper; typename FastUnsigned::type a = absHelper(t); do { *--p = '0' + (a % 10); a /= 10; } while (a); PutSign putSign; putSign(t, p); return string(p, &buf[BUF_SIZE] - p); } string toString(long long a) { return toStringImpl(a); } string toString(unsigned long long a) { return toStringImpl(a); } string toString(long a) { return toStringImpl(a); } string toString(unsigned long a) { return toStringImpl(a); } string toString(int a) { return toStringImpl(a); } string toString(unsigned a) { return toStringImpl(a); } string toString(short a) { return toStringImpl(a); } string toString(unsigned short a) { return toStringImpl(a); } string toString(char a) { return string(1, a); } string toString(signed char a) { return string(1, a); } string toString(unsigned char a) { return string(1, a); } string toString(bool a) { return string(1, '0' + a); } static inline char hexDigit(unsigned x) { return (x < 10) ? ('0' + x) : ('a' + x - 10); } string toHexString(unsigned x, unsigned width) { assert((0 < width) && (width <= 8)); char buf[8]; char* p = &buf[8]; int i = width; do { *--p = hexDigit(x & 15); x >>= 4; } while (--i); return string(p, width); } int stringToInt(const string& str) { return strtol(str.c_str(), nullptr, 0); } bool stringToInt(const string& str, int& result) { char* endptr; result = strtol(str.c_str(), &endptr, 0); return *endptr == '\0'; } unsigned stringToUint(const string& str) { return strtoul(str.c_str(), nullptr, 0); } bool stringToUint(const string& str, unsigned& result) { char* endptr; result = strtoul(str.c_str(), &endptr, 0); return *endptr == '\0'; } uint64_t stringToUint64(const string& str) { return strtoull(str.c_str(), nullptr, 0); } bool stringToBool(string_ref str) { if (str == "1") return true; if ((str.size() == 4) && (strncasecmp(str.data(), "true", 4) == 0)) return true; if ((str.size() == 3) && (strncasecmp(str.data(), "yes", 3) == 0)) return true; return false; } double stringToDouble(const string& str) { return strtod(str.c_str(), nullptr); } bool stringToDouble(const string& str, double& result) { char* endptr; result = strtod(str.c_str(), &endptr); return *endptr == '\0'; } string toLower(string_ref str) { string result = str.str(); transform(begin(result), end(result), begin(result), ::tolower); return result; } bool startsWith(string_ref total, string_ref part) { return total.starts_with(part); } bool startsWith(string_ref total, char part) { return !total.empty() && (total.front() == part); } bool endsWith(string_ref total, string_ref part) { return total.ends_with(part); } bool endsWith(string_ref total, char part) { return !total.empty() && (total.back() == part); } void trimRight(string& str, const char* chars) { auto pos = str.find_last_not_of(chars); if (pos != string::npos) { str.erase(pos + 1); } else { str.clear(); } } void trimRight(string& str, char chars) { auto pos = str.find_last_not_of(chars); if (pos != string::npos) { str.erase(pos + 1); } else { str.clear(); } } void trimRight(string_ref& str, string_ref chars) { while (!str.empty() && (chars.find(str.back()) != string_ref::npos)) { str.pop_back(); } } void trimRight(string_ref& str, char chars) { while (!str.empty() && (str.back() == chars)) { str.pop_back(); } } void trimLeft(string& str, const char* chars) { str.erase(0, str.find_first_not_of(chars)); } void trimLeft(string& str, char chars) { str.erase(0, str.find_first_not_of(chars)); } void trimLeft(string_ref& str, string_ref chars) { while (!str.empty() && (chars.find(str.front()) != string_ref::npos)) { str.pop_front(); } } void trimLeft(string_ref& str, char chars) { while (!str.empty() && (str.front() == chars)) { str.pop_front(); } } void trim(string_ref& str, string_ref chars) { trimRight(str, chars); trimLeft (str, chars); } void trim(string_ref& str, char chars) { trimRight(str, chars); trimLeft (str, chars); } void splitOnFirst(string_ref str, string_ref chars, string_ref& first, string_ref& last) { auto pos = str.find_first_of(chars); if (pos == string_ref::npos) { first = str; last.clear(); } else { first = str.substr(0, pos); last = str.substr(pos + 1); } } void splitOnFirst(string_ref str, char chars, string_ref& first, string_ref& last) { auto pos = str.find_first_of(chars); if (pos == string_ref::npos) { first = str; last.clear(); } else { first = str.substr(0, pos); last = str.substr(pos + 1); } } void splitOnLast(string_ref str, string_ref chars, string_ref& first, string_ref& last) { auto pos = str.find_last_of(chars); if (pos == string_ref::npos) { first.clear(); last = str; } else { first = str.substr(0, pos); last = str.substr(pos + 1); } } void splitOnLast(string_ref str, char chars, string_ref& first, string_ref& last) { auto pos = str.find_last_of(chars); if (pos == string_ref::npos) { first.clear(); last = str; } else { first = str.substr(0, pos); last = str.substr(pos + 1); } } vector split(string_ref str, char chars) { vector result; while (!str.empty()) { string_ref first, last; splitOnFirst(str, chars, first, last); result.push_back(first); str = last; } return result; } string join(const vector& elems, char separator) { if (elems.empty()) return string(); auto it = begin(elems); Builder result; result << *it; for (++it; it != end(elems); ++it) { result << separator; result << *it; } return result; } static unsigned parseNumber(string_ref str) { trim(str, " \t"); if (!str.empty()) { try { return fast_stou(str); } catch (std::invalid_argument&) { // parse error } } throw openmsx::MSXException("Invalid integer: " + str); } static void insert(unsigned x, set& result, unsigned min, unsigned max) { if ((x < min) || (x > max)) { throw openmsx::MSXException("Out of range"); } result.insert(x); } static void parseRange2(string_ref str, set& result, unsigned min, unsigned max) { // trimRight only: here we only care about all spaces trimRight(str, " \t"); if (str.empty()) return; auto pos = str.find('-'); if (pos == string_ref::npos) { insert(parseNumber(str), result, min, max); } else { unsigned begin = parseNumber(str.substr(0, pos)); unsigned end = parseNumber(str.substr(pos + 1)); if (end < begin) { std::swap(begin, end); } for (unsigned i = begin; i <= end; ++i) { insert(i, result, min, max); } } } set parseRange(string_ref str, unsigned min, unsigned max) { set result; while (true) { auto next = str.find(','); string_ref sub = (next == string_ref::npos) ? str : str.substr(0, next++); parseRange2(sub, result, min, max); if (next == string_ref::npos) break; str = str.substr(next); } return result; } #if defined(__APPLE__) std::string fromCFString(CFStringRef str) { // Try the quick route first. const char *cstr = CFStringGetCStringPtr(str, kCFStringEncodingUTF8); if (cstr) { // String was already in UTF8 encoding. return std::string(cstr); } // Convert to UTF8 encoding. CFIndex len = CFStringGetLength(str); CFRange range = CFRangeMake(0, len); CFIndex usedBufLen = 0; CFStringGetBytes( str, range, kCFStringEncodingUTF8, '?', false, nullptr, len, &usedBufLen); UInt8 buffer[usedBufLen]; CFStringGetBytes( str, range, kCFStringEncodingUTF8, '?', false, buffer, len, &usedBufLen); return std::string(reinterpret_cast(buffer), usedBufLen); } #endif } // namespace StringOp openMSX-RELEASE_0_12_0/src/utils/StringOp.hh000066400000000000000000000116741257557151200204650ustar00rootroot00000000000000#ifndef STRINGOP_HH #define STRINGOP_HH #include "string_ref.hh" #include "stringsp.hh" #include #include #include #include #include #include #include #if defined(__APPLE__) #include #endif namespace StringOp { class Builder { public: Builder(); ~Builder(); // Overloaded for the most common types, to avoid having to use // the templatized version below (expect for being implicitly // inlined, the template is just fine). Builder& operator<<(const std::string& t); Builder& operator<<(string_ref t); Builder& operator<<(const char* t); Builder& operator<<(unsigned char t); Builder& operator<<(unsigned short t); Builder& operator<<(unsigned t); Builder& operator<<(unsigned long t); Builder& operator<<(unsigned long long t); Builder& operator<<(char t); Builder& operator<<(short t); Builder& operator<<(int t); Builder& operator<<(long t); Builder& operator<<(long long t); Builder& operator<<(float t); Builder& operator<<(double t); // Templatized version is commented out. There's no problem in // enabling it, but the code works ATM without, and having it // disabled helps to catch future missing overloads in the // list above. /*template Builder& operator<<(const T& t) { buf += toString(t); return *this; }*/ //operator std::string() const; operator std::string() const { return buf; } operator string_ref() const { return buf; } private: std::string buf; }; // Generic toString implementation, works for all 'streamable' types. template std::string toString(const T& t) { std::ostringstream s; s << t; return s.str(); } // Overloads for specific types. These are much faster than the generic // version. Having them non-inline also reduces size of executable. std::string toString(long long a); std::string toString(unsigned long long a); std::string toString(long a); std::string toString(unsigned long a); std::string toString(int a); std::string toString(unsigned a); std::string toString(short a); std::string toString(unsigned short a); std::string toString(char a); std::string toString(signed char a); std::string toString(unsigned char a); std::string toString(bool a); // These are simple enough to inline. inline std::string toString(const char* s) { return s; } inline const std::string& toString(const std::string& s) { return s; } // Print the lower 'width' number of digits of 'a' in hex format // (without leading '0x' and with a-f in lower case). std::string toHexString(unsigned a, unsigned width); int stringToInt(const std::string& str); bool stringToInt(const std::string& str, int& result); unsigned stringToUint(const std::string& str); bool stringToUint(const std::string& str, unsigned& result); uint64_t stringToUint64(const std::string& str); bool stringToBool(string_ref str); double stringToDouble(const std::string& str); bool stringToDouble(const std::string& str, double& result); std::string toLower(string_ref str); bool startsWith(string_ref total, string_ref part); bool startsWith(string_ref total, char part); bool endsWith (string_ref total, string_ref part); bool endsWith (string_ref total, char part); void trimRight(std::string& str, const char* chars); void trimRight(std::string& str, char chars); void trimRight(string_ref& str, string_ref chars); void trimRight(string_ref& str, char chars); void trimLeft (std::string& str, const char* chars); void trimLeft (std::string& str, char chars); void trimLeft (string_ref& str, string_ref chars); void trimLeft (string_ref& str, char chars); void trim (string_ref& str, string_ref chars); void trim (string_ref& str, char chars); void splitOnFirst(string_ref str, string_ref chars, string_ref& first, string_ref& last); void splitOnFirst(string_ref str, char chars, string_ref& first, string_ref& last); void splitOnLast (string_ref str, string_ref chars, string_ref& first, string_ref& last); void splitOnLast (string_ref str, char chars, string_ref& first, string_ref& last); std::vector split(string_ref str, char chars); std::string join(const std::vector& elems, char separator); std::set parseRange(string_ref str, unsigned min, unsigned max); // case insensitive less-than operator struct caseless { bool operator()(string_ref s1, string_ref s2) const { auto m = std::min(s1.size(), s2.size()); int r = strncasecmp(s1.data(), s2.data(), m); return (r != 0) ? (r < 0) : (s1.size() < s2.size()); } }; struct casecmp { bool operator()(string_ref s1, string_ref s2) const { if (s1.size() != s2.size()) return false; return strncasecmp(s1.data(), s2.data(), s1.size()) == 0; } }; #if defined(__APPLE__) std::string fromCFString(CFStringRef str); #endif } #endif openMSX-RELEASE_0_12_0/src/utils/Subject.hh000066400000000000000000000026261257557151200203140ustar00rootroot00000000000000#ifndef SUBJECT_HH #define SUBJECT_HH #include "Observer.hh" #include "ScopedAssign.hh" #include "stl.hh" #include #include #include namespace openmsx { /** * Generic Gang-of-Four Subject class of the Observer pattern, templatized * edition. */ template class Subject { public: void attach(Observer& observer); void detach(Observer& observer); protected: Subject(); ~Subject(); void notify() const; private: std::vector*> observers; #ifndef NDEBUG mutable bool notifyInProgress; #endif }; template Subject::Subject() #ifndef NDEBUG : notifyInProgress(false) #endif { } template Subject::~Subject() { assert(!notifyInProgress); auto copy = observers; for (auto& o : copy) { o->subjectDeleted(*static_cast(this)); } assert(observers.empty()); } template void Subject::attach(Observer& observer) { assert(!notifyInProgress); observers.push_back(&observer); } template void Subject::detach(Observer& observer) { assert(!notifyInProgress); observers.erase(find_unguarded(observers, &observer)); } template void Subject::notify() const { #ifndef NDEBUG assert(!notifyInProgress); ScopedAssign sa(notifyInProgress, true); #endif for (auto& o : observers) { o->update(*static_cast(this)); } } } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/utils/TigerTree.cc000066400000000000000000000153141257557151200205730ustar00rootroot00000000000000#include "TigerTree.hh" #include "Math.hh" #include #include #include namespace openmsx { static const size_t BLOCK_SIZE = 1024; struct TTCacheEntry { TTCacheEntry() : time(-1) {} // TODO use compiler generated versions once VS supports that TTCacheEntry(TTCacheEntry&& other) : hash (std::move(other.hash )) , valid(std::move(other.valid)) , time (std::move(other.time )) {} TTCacheEntry& operator=(TTCacheEntry&& other) { hash = std::move(other.hash ); valid = std::move(other.valid); time = std::move(other.time ); return *this; } MemBuffer hash; MemBuffer valid; size_t numNodes; time_t time; }; // Typically contains 0 or 1 element, and only rarely 2 or more. But we need // the address of existing elements to remain stable when new elements are // inserted. So still use std::map instead of std::vector. static std::map, TTCacheEntry> ttCache; static size_t calcNumNodes(size_t dataSize) { auto numBlocks = (dataSize + BLOCK_SIZE - 1) / BLOCK_SIZE; return (numBlocks == 0) ? 1 : 2 * numBlocks - 1; } static TTCacheEntry& getCacheEntry( TTData& data, size_t dataSize, const std::string& name) { size_t numNodes = calcNumNodes(dataSize); auto& result = ttCache[std::make_pair(dataSize, name)]; if ((numNodes != result.numNodes) || !data.isCacheStillValid(result.time)) { result.hash .resize(numNodes); result.valid.resize(numNodes); result.numNodes = numNodes; memset(result.valid.data(), 0, numNodes); // all invalid } return result; } TigerTree::TigerTree(TTData& data_, size_t dataSize_, const std::string& name) : data(data_) , dataSize(dataSize_) , entry(getCacheEntry(data, dataSize, name)) { } const TigerHash& TigerTree::calcHash() { return calcHash(getTop()); } void TigerTree::notifyChange(size_t offset, size_t len, time_t time) { entry.time = time; assert((offset + len) <= dataSize); if (len == 0) return; entry.valid[getTop().n] = false; // set sentinel auto first = offset / BLOCK_SIZE; auto last = (offset + len - 1) / BLOCK_SIZE; assert(first <= last); // requires len != 0 do { auto node = getLeaf(first); while (entry.valid[node.n]) { entry.valid[node.n] = false; node = getParent(node); } } while (++first <= last); } const TigerHash& TigerTree::calcHash(Node node) { auto n = node.n; if (!entry.valid[n]) { if (n & 1) { // interior node auto left = getLeftChild (node); auto right = getRightChild(node); auto& h1 = calcHash(left); auto& h2 = calcHash(right); tiger_int(h1, h2, entry.hash[n]); } else { // leaf node size_t b = n * (BLOCK_SIZE / 2); size_t l = dataSize - b; if (l >= BLOCK_SIZE) { auto* d = data.getData(b, BLOCK_SIZE); tiger_leaf(d, entry.hash[n]); } else { // partial last block auto* d = data.getData(b, l); auto backup = d[-1]; d[-1] = 0; tiger(d - 1, l + 1, entry.hash[n]); d[-1] = backup; } } entry.valid[n] = true; } return entry.hash[n]; } // The TigerTree::nodes member variable stores a linearized binary tree. The // linearization is done like in this example: // // 7 (level = 8) // ----/ \---- . // 3 11 (level = 4) // / \ / \ . // 1 5 9 | (level = 2) // / \ / \ / \ | . // 0 2 4 6 8 10 12 (level = 1) // // All leaf nodes are given even node values (0, 2, 4, .., 12). Leaf nodes have // level=1. At the next level (level=2) leaf nodes are pair-wise combined in // internal nodes. So nodes 0 and 2 are combined in node 1, 4+6->5 and 8+10->9. // Leaf-node 12 cannot be paired (there is no leaf-node 14), so there's no // corresponding node at level=2. The next level is level=4 (level values // double for each higher level). At level=4 node 3 is the combination of 1 and // 5 and 9+12->11. Note that 11 is a combination of two nodes from a different // (lower) level. And finally, node 7 at level=8 combines 3+11. // // The following methods navigate in this tree. TigerTree::Node TigerTree::getTop() const { auto n = Math::floodRight(entry.numNodes / 2); return Node(n, n + 1); } TigerTree::Node TigerTree::getLeaf(size_t block) const { assert((2 * block) < entry.numNodes); return Node(2 * block, 1); } TigerTree::Node TigerTree::getParent(Node node) const { assert(node.n < entry.numNodes); do { node.n = (node.n & ~(2 * node.l)) + node.l; node.l *= 2; } while (node.n >= entry.numNodes); return node; } TigerTree::Node TigerTree::getLeftChild(Node node) const { assert(node.n < entry.numNodes); assert(node.l > 1); node.l /= 2; node.n -= node.l; return node; } TigerTree::Node TigerTree::getRightChild(Node node) const { assert(node.n < entry.numNodes); while (1) { assert(node.l > 1); node.l /= 2; auto r = node.n + node.l; if (r < entry.numNodes) return Node(r, node.l); } } } // namespace openmsx #if 0 // Unittest class TTTestData : public openmsx::TTData { public: uint8_t* getData(size_t offset, size_t size) override { return buffer + offset; } uint8_t* buffer; }; int main() { uint8_t buffer_[8192 + 1]; uint8_t* buffer = buffer_ + 1; TTTestData data; data.buffer = buffer; // zero sized buffer openmsx::TigerTree tt0(data, 0); assert(tt0.calcHash().toString() == "LWPNACQDBZRYXW3VHJVCJ64QBZNGHOHHHZWCLNQ"); // size less than one block openmsx::TigerTree tt1(data, 100); memset(buffer, 0, 100); assert(tt1.calcHash().toString() == "EOIEKIQO6BSNCNRX2UB2MB466INV6LICZ6MPUWQ"); memset(buffer + 20, 1, 10); tt1.notifyChange(20, 10); assert(tt1.calcHash().toString() == "GOTZVYW3WIE37XFCDOY66PLLXWGP6DPN3CQRHWA"); // 3 full and one partial block openmsx::TigerTree tt2(data, 4000); memset(buffer, 0, 4000); assert(tt2.calcHash().toString() == "YC44NFWFCN3QWFRSS6ICGUJDLH7F654RCKVT7VY"); memset(buffer + 1500, 1, 10); tt2.notifyChange(1500, 10); // change a single block assert(tt2.calcHash().toString() == "JU5RYR446PVZSPMOJML4IQ2FXLDDKE522CEYIBA"); memset(buffer + 2000, 1, 100); tt2.notifyChange(2000, 100); // change two blocks assert(tt2.calcHash().toString() == "IPV53CDVB2I63HXIXVK2OUPNS26YB7V7G2Y7XIA"); // 7 full blocks (unbalanced internal binary tree) openmsx::TigerTree tt3(data, 7 * 1024); memset(buffer, 0, 7 * 1024); assert(tt3.calcHash().toString() == "FPSZ35773WS4WGBVXM255KWNETQZXMTEJGFMLTA"); memset(buffer + 512, 1, 512); tt3.notifyChange(512, 512); // part of block-0 assert(tt3.calcHash().toString() == "Z32BC2WSHPW5DYUSNSZGLDIFTEIP3DBFJ7MG2MQ"); memset(buffer + 3*1024, 1, 4*1024); tt3.notifyChange(3*1024, 4*1024); // blocks 3-6 assert(tt3.calcHash().toString() == "SJUYB3QVIJXNKZMSQZGIMHA7GA2MYU2UECDA26A"); } #endif openMSX-RELEASE_0_12_0/src/utils/TigerTree.hh000066400000000000000000000065761257557151200206170ustar00rootroot00000000000000/** Incremental Tiger-tree-hash (tth) calculation. * * This code is based on code from the tigertree project * https://sourceforge.net/projects/tigertree/ * * The main difference is that the original code can only perform a one-shot * tth calculation while this code (also) allows incremental tth calculations. * When the input data changes, incremental calculation allows to only redo * part of the full calculation, so it can be much faster. * * Here's the copyright notice of the original code (even though the code is * almost completely rewritten): * * Copyright (C) 2001 Bitzi (aka Bitcollider) Inc. and Gordon Mohr * Released into the public domain by same; permission is explicitly * granted to copy, modify, and use freely. * * THE WORK IS PROVIDED "AS IS," AND COMES WITH ABSOLUTELY NO WARRANTY, * EXPRESS OR IMPLIED, TO THE EXTENT PERMITTED BY APPLICABLE LAW, * INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY * OR FITNESS FOR A PARTICULAR PURPOSE. * * (PD) 2001 The Bitzi Corporation * Please see file COPYING or http://bitzi.com/publicdomain * for more info. */ #ifndef TIGERTREE_HH #define TIGERTREE_HH #include "tiger.hh" #include "MemBuffer.hh" #include #include #include namespace openmsx { /** The TigerTree class will query the to-be-hashed data via this abstract * interface. This allows to e.g. fetch the data from a file. */ class TTData { public: /** Return the requested portion of the to-be-hashed data block. * Special requirement: it should be allowed to temporarily overwrite * the byte one position before the returned pointer. */ virtual uint8_t* getData(size_t offset, size_t size) = 0; /** Because TTH calculation of a large file takes some time (a few * 1/10s for a harddisk image) we try to cache previous calculations. * This method makes sure we don't wrongly reuse the data. E.g. after * it has been modified (by openmsx or even externally). * * Note that the current implementation of the caching is only * suited for files. Refactor this if we ever need some different. */ virtual bool isCacheStillValid(time_t& time) = 0; protected: ~TTData() {} }; struct TTCacheEntry; /** Calculate a tiger-tree-hash. * Calculation can be done incrementally, so recalculating the hash after a * (small) modification of the input is efficient. */ class TigerTree { public: /** Create TigerTree calculator for the given (abstract) data block * of given size. */ TigerTree(TTData& data, size_t dataSize, const std::string& name); /** Calculate the hash value. */ const TigerHash& calcHash(); /** Inform this calculator about changes in the input data. This is * used to (not) skip re-calculations on future calcHash() calls. So * it's crucial this calculator is informed about _all_ changes in * the input. */ void notifyChange(size_t offset, size_t len, time_t time); private: // functions to navigate in binary tree struct Node { Node(size_t n_, size_t l_) : n(n_), l(l_) {} size_t n; // node number size_t l; // level number }; Node getTop() const; Node getLeaf(size_t block) const; Node getParent(Node node) const; Node getLeftChild(Node node) const; Node getRightChild(Node node) const; const TigerHash& calcHash(Node node); TTData& data; const size_t dataSize; TTCacheEntry& entry; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/utils/aligned.hh000066400000000000000000000051151257557151200203140ustar00rootroot00000000000000#ifndef ALIGNED_HH #define ALIGNED_HH #include "build-info.hh" #include #include #include #ifdef _MSC_VER #define ALIGNED(EXPRESSION, ALIGNMENT) __declspec (align(ALIGNMENT)) EXPRESSION #else // GCC style #define ALIGNED(EXPRESSION, ALIGNMENT) EXPRESSION __attribute__((__aligned__((ALIGNMENT)))); #endif // Only need to (more strictly) align when SSE is actually enabled. #ifdef __SSE2__ #define SSE_ALIGNED(EXPRESSION) ALIGNED(EXPRESSION, 16) #else #define SSE_ALIGNED(EXPRESSION) EXPRESSION #endif // Unaligned loads and stores. template static inline T unalignedLoad(const void* p) { if (openmsx::OPENMSX_UNALIGNED_MEMORY_ACCESS) { return *reinterpret_cast(p); } else { T t; memcpy(&t, p, sizeof(t)); return t; } } template static inline void unalignedStore(void* p, T t) { if (openmsx::OPENMSX_UNALIGNED_MEMORY_ACCESS) { *reinterpret_cast(p) = t; } else { memcpy(p, &t, sizeof(t)); } } static inline uint32_t unalignedLoad32(const void* p) { return unalignedLoad(p); } static inline uint64_t unalignedLoad64(const void* p) { return unalignedLoad(p); } static inline void unalignedStore32(void* p, uint32_t v) { unalignedStore(p, v); } static inline void unalignedStore64(void* p, uint64_t v) { unalignedStore(p, v); } // assume_aligned, assume_SSE_aligned // // Let the compiler know a pointer is more strictly aligned than guaranteed by // the C++ language rules. Sometimes this allows the compiler to generate more // efficient code (typically when auto-vectorization is performed). // // Example: // int* p = ... // assume_SSE_aligned(p); // compiler can now assume 16-byte alignment for p // clang offers __has_builtin, fallback for other compilers #ifndef __has_builtin # define __has_builtin(x) 0 #endif // gcc-version check macro #if defined(__GNUC__) && defined(__GNUC_MINOR__) # define GNUC_PREREQ(maj, min) \ (((maj) * 100 + (min)) <= (__GNUC__ * 100 + __GNUC_MINOR__)) #else # define GNUC_PREREQ(maj, min) 0 #endif template static inline void assume_aligned(T* __restrict & ptr) { #ifdef DEBUG // only check in debug build assert((reinterpret_cast(ptr) % A) == 0); #endif #if __has_builtin(__builtin_assume_aligned) || GNUC_PREREQ(4, 7) ptr = static_cast(__builtin_assume_aligned(ptr, A)); #else (void)ptr; // nothing #endif } template static inline void assume_SSE_aligned( #ifdef __SSE2__ T* __restrict & ptr) { assume_aligned<16>(ptr); } #else T* __restrict & /*ptr*/) { /* nothing */ } #endif #endif // ALIGNED_HH openMSX-RELEASE_0_12_0/src/utils/alignof.hh000066400000000000000000000021651257557151200203320ustar00rootroot00000000000000#ifndef ALIGNOF_HH #define ALIGNOF_HH // Portable alignof operator // // C++11 has a new alignof operator (much like the sizeof operator). Many // compilers already offered a similar feature in non-c++11 mode as an // extension (e.g. gcc has the __alignof__ operator). This file implements the // same functionality for pre-c++11 compilers in a portable way. Once we switch // to c++11 we can drop this header. // // Usage: // c++11: alignof(SomeType) // this: ALIGNOF(SomeType) template struct AlignOf { struct S { char c; T t; }; static const unsigned value = sizeof(S) - sizeof(T); }; #define ALIGNOF(T) AlignOf::value // c++11 offers std::max_align_t, a type whose alignment is at least as great // as that of any standard type. The malloc() function guarantees to return // memory with at least this alignment. // gcc-4.7, gcc-4.8 : only offer ::max_align_t but not std::max_align_t // gcc-4.9 : offers both ::max_align_t and std::max_align_t // visual studio 2013: offers only std::max_align_t (as per the c++ standard) struct MAX_ALIGN_T { long long ll; long double ld; }; #endif openMSX-RELEASE_0_12_0/src/utils/array_ref.hh000066400000000000000000000061201257557151200206600ustar00rootroot00000000000000#ifndef ARRAY_REF_HH #define ARRAY_REF_HH #include #include #include #include #include /** This class implements a subset of the proposal for std::array_ref * (proposed for the next c++ standard (c++1y)). * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3334.html#classstd_1_1array__ref * * It has an interface that is close to std::vector, but it does not own the * memory of the array. Basically it's just a wrapper around a pointer plus * length. */ template class array_ref { public: // types using value_type = T; using pointer = const T*; using reference = const T&; using const_reference = const T&; using const_iterator = const T*; using iterator = const_iterator; using const_reverse_iterator = std::reverse_iterator; using reverse_iterator = const_reverse_iterator; using size_type = size_t; using difference_type = ptrdiff_t; // construct/copy/assign array_ref() : dat(nullptr), siz(0) {} array_ref(const array_ref& r) : dat(r.data()), siz(r.size()) {} array_ref(const T* array, size_t length) : dat(array), siz(length) {} template array_ref(ITER begin, ITER end) : dat(&*begin), siz(std::distance(begin, end)) {} array_ref(const std::vector& v) : dat(v.data()), siz(v.size()) {} template array_ref(const T(&a)[N]) : dat(a), siz(N) {} array_ref& operator=(const array_ref& rhs) { dat = rhs.data(); siz = rhs.size(); return *this; } // iterators const_iterator begin() const { return dat; } const_iterator end() const { return dat + siz; } const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } // capacity size_type size() const { return siz; } bool empty() const { return siz == 0; } // element access const T& operator[](size_t i) const { assert(i < siz); return dat[i]; } const T& front() const { return (*this)[0]; } const T& back() const { return (*this)[siz - 1]; } const T* data() const { return dat; } // mutators void clear() { siz = 0; } // no need to change 'dat' void remove_prefix(size_type n) { if (n <= siz) { dat += n; siz -= n; } else { clear(); } } void remove_suffix(size_type n) { if (n <= siz) { siz -= n; } else { clear(); } } void pop_back() { remove_suffix(1); } void pop_front() { remove_prefix(1); } array_ref substr(size_type pos, size_type n = size_type(-1)) const { if (pos >= siz) return array_ref(); return array_ref(dat + pos, std::min(n, siz - pos)); } private: const T* dat; size_type siz; }; // deducing constructor wrappers template inline array_ref make_array_ref(const T* array, size_t length) { return array_ref(array, length); } template inline array_ref make_array_ref(const T(&a)[N]) { return array_ref(a); } template inline array_ref make_array_ref(const std::vector& v) { return array_ref(v); } #endif openMSX-RELEASE_0_12_0/src/utils/checked_cast.hh000066400000000000000000000013171257557151200213110ustar00rootroot00000000000000#ifndef CHECKED_CAST_HH #define CHECKED_CAST_HH /** * Based on checked_cast implementation from the book: * C++ Coding Standard * item 93: Avoid using static_cast on pointers */ #include #include template static TO checked_cast(FROM* from) { assert(dynamic_cast(from) == static_cast(from)); return static_cast(from); } template static TO checked_cast(FROM& from) { using TO_PTR = typename std::remove_reference::type*; TO_PTR* suppress_warning = nullptr; (void)suppress_warning; assert(dynamic_cast(&from) == static_cast(&from)); return static_cast(from); } #endif openMSX-RELEASE_0_12_0/src/utils/circular_buffer.hh000066400000000000000000000260711257557151200220520ustar00rootroot00000000000000/** * This code is heavily based on boost circular_buffer: * http://www.boost.org/doc/libs/1_55_0/doc/html/circular_buffer.html * The interface of this version and the original boost version is (as much as * possible identical). I did leave out some of the more fancy methods like * insert/remove in the middle of the buffer or linearize the buffer. If we * ever need those we can add them of course. * So (for now) this class represents a dynamically sized circular buffer with * (efficient) {push,pop}_{front,back} and operator[] methods. It also offers * random access iterators. For much more details check the boost * documentation. */ #ifndef CIRCULAR_BUFFER_HH #define CIRCULAR_BUFFER_HH #include #include #include #include #include /** Random access iterator for circular_buffer. */ template class cb_iterator { public: using value_type = T; using pointer = T*; using reference = T&; using difference_type = ptrdiff_t; using iterator_category = std::random_access_iterator_tag; cb_iterator() : buf(nullptr), p(nullptr) {} cb_iterator(const cb_iterator& it) : buf(it.buf), p(it.p) {} cb_iterator(const BUF* buf_, T* p_) : buf(buf_), p(p_) {} cb_iterator& operator=(const cb_iterator& it) { buf = it.buf; p = it.p; return *this; } T& operator*() const { return *p; } T* operator->() const { return p; } difference_type operator-(const cb_iterator& it) const { return index(p) - index(it.p); } cb_iterator& operator++() { buf->increment(p); if (p == buf->last) p = nullptr; return *this; } cb_iterator& operator--() { if (p == nullptr) p = buf->last; buf->decrement(p); return *this; } cb_iterator operator++(int) { auto tmp = *this; ++*this; return tmp; } cb_iterator operator--(int) { auto tmp = *this; --*this; return tmp; } cb_iterator& operator+=(difference_type n) { if (n > 0) { p = buf->add(p, n); if (p == buf->last) p = nullptr; } else if (n < 0) { if (p == nullptr) p = buf->last; p = buf->sub(p, -n); } else { // nothing, but _must_ be handled separately } return *this; } cb_iterator& operator-=(difference_type n) { *this += -n; return *this; } cb_iterator operator+(difference_type n) { return cb_iterator(*this) += n; } cb_iterator operator-(difference_type n) { return cb_iterator(*this) -= n; } T& operator[](difference_type n) const { return *(*this + n); } bool operator==(const cb_iterator& it) const { return p == it.p; } bool operator!=(const cb_iterator& it) const { return p != it.p; } bool operator<(const cb_iterator& it) const { return index(p) < index(it.p); } bool operator> (const cb_iterator& it) const { return it < *this; } bool operator<=(const cb_iterator& it) const { return !(it < *this); } bool operator>=(const cb_iterator& it) const { return !(*this < it); } private: size_t index(const T* q) const { return q ? buf->index(q) : buf->size(); } const BUF* buf; T* p; // invariant: end-iterator -> nullptr, // other iterators -> pointer to element }; /** Circular buffer class, based on boost::circular_buffer/ */ template class circular_buffer { public: using iterator = cb_iterator, T>; using const_iterator = cb_iterator, const T>; using reverse_iterator = std::reverse_iterator< iterator>; using const_reverse_iterator = std::reverse_iterator; using value_type = T; using pointer = T*; using reference = T&; using difference_type = ptrdiff_t; using size_type = size_t; circular_buffer() : buf(nullptr), stop(nullptr) , first(nullptr), last(nullptr), siz(0) {} explicit circular_buffer(size_t buffer_capacity) : siz(0) { buf = allocate(buffer_capacity); stop = buf + buffer_capacity; first = last = buf; } circular_buffer(const circular_buffer& cb) : siz(cb.size()) { buf = allocate(cb.capacity()); stop = buf + cb.capacity(); first = buf; try { last = uninitialized_copy(cb.begin(), cb.end(), buf); } catch (...) { free(buf); throw; } if (last == stop) last = buf; } circular_buffer(circular_buffer&& cb) : buf(nullptr), stop(nullptr) , first(nullptr), last(nullptr), siz(0) { cb.swap(*this); } ~circular_buffer() { destroy(); } circular_buffer& operator=(const circular_buffer& cb) { if (this == &cb) return *this; T* buff = allocate(cb.capacity()); try { reset(buff, uninitialized_copy(cb.begin(), cb.end(), buff), cb.capacity()); } catch (...) { free(buff); throw; } return *this; } circular_buffer& operator=(circular_buffer&& cb) { cb.swap(*this); circular_buffer().swap(cb); return *this; } void swap(circular_buffer& cb) { std::swap(buf, cb.buf); std::swap(stop, cb.stop); std::swap(first, cb.first); std::swap(last, cb.last); std::swap(siz, cb.siz); } iterator begin() { return iterator(this, empty() ? nullptr : first); } const_iterator begin() const { return const_iterator(this, empty() ? nullptr : first); } iterator end() { return iterator (this, nullptr); } const_iterator end() const { return const_iterator(this, nullptr); } reverse_iterator rbegin() { return reverse_iterator(end()); } const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } reverse_iterator rend() { return reverse_iterator(begin()); } const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } T& operator[](size_t i) { return *add(first, i); } const T& operator[](size_t i) const { return *add(first, i); } T& front() { return *first; } const T& front() const { return *first; } T& back() { return *((last == buf ? stop : last) - 1); } const T& back() const { return *((last == buf ? stop : last) - 1); } size_t size() const { return siz; } bool empty() const { return size() == 0; } bool full() const { return capacity() == size(); } size_t reserve() const { return capacity() - size(); } size_t capacity() const { return stop - buf; } void set_capacity(size_t new_capacity) { if (new_capacity == capacity()) return; T* new_buf = allocate(new_capacity); iterator b = begin(); try { reset(new_buf, uninitialized_move_n(b, std::min(new_capacity, size()), new_buf), new_capacity); } catch (...) { free(new_buf); throw; } } void push_back (const T& t) { push_back_impl ( t ); } void push_back ( T&& t) { push_back_impl < T&&>(std::move(t)); } void push_front(const T& t) { push_front_impl( t ); } void push_front( T&& t) { push_front_impl< T&&>(std::move(t)); } void push_back(std::initializer_list list) { for (auto& e : list) push_back(e); } void pop_back() { decrement(last); last->~T(); --siz; } void pop_front() { first->~T(); increment(first); --siz; } void clear() { for (size_t i = 0; i < size(); ++i, increment(first)) { first->~T(); } siz = 0; } private: T* uninitialized_copy(const_iterator b, const_iterator e, T* dst) { T* next = dst; try { while (b != e) { ::new (dst) T(*b); ++b; ++dst; } } catch (...) { while (next != dst) { next->~T(); ++next; } throw; } return dst; } T* uninitialized_move_n(iterator src, size_t n, T* dst) { while (n) { ::new (dst) T(std::move(*src)); ++src; ++dst; --n; } return dst; } template void push_back_impl(ValT t) { ::new (last) T(static_cast(t)); increment(last); ++siz; } template void push_front_impl(ValT t) { try { decrement(first); ::new (first) T(static_cast(t)); ++siz; } catch (...) { increment(first); throw; } } template void increment(Pointer& p) const { if (++p == stop) p = buf; } template void decrement(Pointer& p) const { if (p == buf) p = stop; --p; } template Pointer add(Pointer p, difference_type n) const { p += n; if (p >= stop) p -= capacity(); return p; } template Pointer sub(Pointer p, difference_type n) const { p -= n; if (p < buf) p += capacity(); return p; } size_t index(const T* p) const { return (p >= first) ? (p - first) : (stop - first) + (p - buf); } T* allocate(size_t n) { return (n == 0) ? nullptr : static_cast(malloc(n * sizeof(T))); } void destroy() { clear(); free(buf); } void reset(T* new_buf, T* new_last, size_t new_capacity) { destroy(); siz = new_last - new_buf; first = buf = new_buf; stop = buf + new_capacity; last = new_last == stop ? buf : new_last; } private: T* buf; // start of allocated area T* stop; // end of allocated area (exclusive) T* first; // position of the 1st element in the buffer T* last; // position past the last element // note: both for a full or empty buffer first==last size_t siz; // number of elements in the buffer template friend class cb_iterator; }; /** This implements a queue on top of circular_buffer (not part of boost). * It will automatically grow the buffer when its capacity is too small * while inserting new elements. */ template class cb_queue { public: using value_type = typename circular_buffer::value_type; using iterator = typename circular_buffer::iterator; using const_iterator = typename circular_buffer::const_iterator; using reverse_iterator = typename circular_buffer::reverse_iterator; using const_reverse_iterator = typename circular_buffer::const_reverse_iterator; cb_queue() {} explicit cb_queue(size_t capacity) : buf(capacity) {} template void push_back(U&& u) { checkGrow(); buf.push_back(std::forward(u)); } template void push_back(std::initializer_list list) { for (auto& e : list) push_back(e); } T pop_front() { T t = std::move(buf.front()); buf.pop_front(); return t; } const T& front() const { return buf.front(); } const T& back() const { return buf.back(); } const T& operator[](size_t i) const { return buf[i]; } iterator begin() { return buf.begin(); } iterator end() { return buf.end(); } const_iterator begin() const { return buf.begin(); } const_iterator end() const { return buf.end(); } reverse_iterator rbegin() { return buf.rbegin(); } const_reverse_iterator rbegin() const { return buf.rbegin(); } reverse_iterator rend() { return buf.rend(); } const_reverse_iterator rend() const { return buf.rend(); } size_t size() const { return buf.size(); } bool empty() const { return buf.empty(); } void clear() { buf.clear(); } circular_buffer& getBuffer() { return buf; } const circular_buffer& getBuffer() const { return buf; } private: void checkGrow() { if (buf.full()) { buf.set_capacity(std::max(size_t(4), buf.capacity() * 2)); } } circular_buffer buf; }; #endif openMSX-RELEASE_0_12_0/src/utils/countof.hh000066400000000000000000000027221257557151200203670ustar00rootroot00000000000000// countof() returns the number of elements in a statically // allocated array. For example: // // int a[10]; // f(a, countof(a)); // f will be called with value 10 as 2nd parameter // // // A naive implementation is this: // // #define countof(array) (sizeof(array) / sizeof(array[0])) // // This implementation has a problem. The following example compiles fine, but // it gives the wrong result: // // int a[10]; // int* p = a; // countof(p); // wrong result when called with pointer argument // // // A better implementation is this: // // template // unsigned countof(T(&)[N]) // { // return N; // } // // The above example now results in a compilation error (which is good). // Though in this implementation, the result of countof() is no longer a // compile-time constant. So now this example fails to compile: // // int a[10]; // int b[countof(a)]; // error: array bound is not a constant // // // The implementation below fixes both problems. #ifndef COUNTOF_HH #define COUNTOF_HH // The following line uses a very obscure syntax: // It declares a templatized function 'countofHelper'. // It has one (unnamed) parameter: a reference to an array T[N]. // The return type is a reference to an array char[N] // Note that this function does not need an implementation. template char (&countofHelper(T(&)[N]))[N]; #define countof(array) (sizeof(countofHelper(array))) #endif openMSX-RELEASE_0_12_0/src/utils/cstdiop.hh000066400000000000000000000002511257557151200203520ustar00rootroot00000000000000#ifndef CSTDIOP_HH #define CSTDIOP_HH #include #ifdef _MSC_VER #include #define STDIN_FILENO _fileno(stdin) #define snprintf _snprintf #endif #endif openMSX-RELEASE_0_12_0/src/utils/direntp.hh000066400000000000000000000013061257557151200203540ustar00rootroot00000000000000#ifndef DIRENTP_HH #define DIRENTP_HH #ifndef _WIN32 #include #else // When building with MinGW, we use our own wrapper instead of the // native dirent.h provided by the MinGW C runtime. We do this because // we need an implementation of dirent that returns UTF8-encoded strings // as chars. // Unfortunately, MinGW's implementation of dirent provides either // wchar_t-based versions that support UTF16 (when UNICODE is defined), // or char-based versions containing what appear to be ANSI strings, // certainly not UTF8. // While this behavior is relatively consistent with Win32, it's not // what we need here. Consequently, we use our wrapper instead. #include "win32-dirent.hh" #endif #endif openMSX-RELEASE_0_12_0/src/utils/endian.hh000066400000000000000000000303231257557151200201460ustar00rootroot00000000000000#ifndef ENDIAN_HH #define ENDIAN_HH #include "alignof.hh" #include "build-info.hh" #include #include namespace Endian { // Revese bytes in a 16-bit number: 0x1234 becomes 0x3412 static inline uint16_t bswap16(uint16_t x) { // This sequence generates 'optimal' code on a wide range of gcc/clang // versions (a single rotate instruction on x86). The newer compiler // versions also do 'the right thing' for the simpler expression below. // Those newer compilers also support __builtin_bswap16() but that // doesn't generate better code (and is less portable). return ((x & 0x00FF) << 8) | ((x & 0xFF00) >> 8); //return (x << 8) | (x >> 8); } // Revese bytes in a 32-bit number: 0x12345678 becomes 0x78563412 static inline uint32_t bswap32(uint32_t x) { #if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 3)) // Starting from gcc-4.3 there's a builtin function for this. // E.g. on x86 this is translated to a single 'bswap' instruction. return __builtin_bswap32(x); #else return (x << 24) | ((x << 8) & 0x00ff0000) | ((x >> 8) & 0x0000ff00) | (x >> 24); #endif } // Revese bytes in a 64-bit value: 0x1122334455667788 becomes 0x8877665544332211 static inline uint64_t bswap64(uint64_t x) { #if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 3)) // Starting from gcc-4.3 there's a builtin function for this. // E.g. on x86 this is translated to a single 'bswap' instruction. return __builtin_bswap64(x); #else return (uint64_t(bswap32(x >> 0)) << 32) | (uint64_t(bswap32(x >> 32)) << 0); #endif } // Use overloading to get a (statically) polymorphic bswap() function. static inline uint16_t bswap(uint16_t x) { return bswap16(x); } static inline uint32_t bswap(uint32_t x) { return bswap32(x); } static inline uint64_t bswap(uint64_t x) { return bswap64(x); } // Identity operator, simply returns the given value. struct Ident { template inline T operator()(T t) const { return t; } }; // Byte-swap operator, swap bytes in the given value (16 or 32 bit). struct BSwap { template inline T operator()(T t) const { return bswap(t); } }; // Helper class that stores a value and allows to read/write that value. Though // right before it is loaded/stored the value is transformed by a configurable // operation. // TODO If needed this can be extended with stuff like operator+= .... template class EndianT { public: // TODO These constructors are useful, but they prevent this type from // being used in a union. This limitation is removed in C++11. //EndianT() { /* leave uninitialized */ } //EndianT(T t_) { Op op; t = op(t_); } inline operator T() const { Op op; return op(t); } inline EndianT& operator=(T a) { Op op; t = op(a); return *this; } private: T t; }; // Define the types B16, B32, L16, L32. // // Typically these types are used to define the layout of external structures // For example: // // struct FATDirectoryEntry { // char filename[8]; // char extension[3]; // ... // Endian::L32 size; // 32-bit little endian value // }; // ... // unsigned s = myDirEntry.size; // Possibly performs endianess conversion. // yourDirEntry.size = s; // If native endianess is already correct // // this has no extra overhead. // // You can assign and read values in native endianess to values of these types. // So basically in a single location define the structure with the correct // endianess and in all other places use the value as-if it were a native type. // // Note that these types should still be correctly aligned (e.g. L32 should be // 4-byte aligned). For unaligned access use the functions below. // template struct ConvBig; template<> struct ConvBig : Ident {}; template<> struct ConvBig : BSwap {}; template struct ConvLittle; template<> struct ConvLittle : BSwap {}; template<> struct ConvLittle : Ident {}; using B16 = EndianT>; using L16 = EndianT>; using B32 = EndianT>; using L32 = EndianT>; static_assert(sizeof(B16) == 2, "must have size 2"); static_assert(sizeof(L16) == 2, "must have size 2"); static_assert(sizeof(B32) == 4, "must have size 4"); static_assert(sizeof(L32) == 4, "must have size 4"); static_assert(ALIGNOF(B16) <= 2, "may have alignment 2"); static_assert(ALIGNOF(L16) <= 2, "may have alignment 2"); static_assert(ALIGNOF(B32) <= 4, "may have alignment 4"); static_assert(ALIGNOF(L32) <= 4, "may have alignment 4"); // Helper functions to read/write aligned 16/32 bit values. static inline void writeB16(void* p, uint16_t x) { *reinterpret_cast(p) = x; } static inline void writeL16(void* p, uint16_t x) { *reinterpret_cast(p) = x; } static inline void writeB32(void* p, uint32_t x) { *reinterpret_cast(p) = x; } static inline void writeL32(void* p, uint32_t x) { *reinterpret_cast(p) = x; } static inline uint16_t readB16(const void* p) { return *reinterpret_cast(p); } static inline uint16_t readL16(const void* p) { return *reinterpret_cast(p); } static inline uint32_t readB32(const void* p) { return *reinterpret_cast(p); } static inline uint32_t readL32(const void* p) { return *reinterpret_cast(p); } // Read/write big/little 16/32/64-bit values to/from a (possibly) unaligned // memory location. If the host architecture supports unaligned load/stores // (e.g. x86), these functions perform a single load/store (with possibly an // adjust operation on the value if the endianess is different from the host // endianess). If the architecture does not support unaligned memory operations // (e.g. early ARM architectures), the operation is split into byte accesses. template static inline void write_UA(void* p, T x) { if (SWAP) x = bswap(x); memcpy(p, &x, sizeof(x)); } static inline void write_UA_B16(void* p, uint16_t x) { write_UA(p, x); } static inline void write_UA_L16(void* p, uint16_t x) { write_UA< openmsx::OPENMSX_BIGENDIAN>(p, x); } static inline void write_UA_B32(void* p, uint32_t x) { write_UA(p, x); } static inline void write_UA_L32(void* p, uint32_t x) { write_UA< openmsx::OPENMSX_BIGENDIAN>(p, x); } static inline void write_UA_B64(void* p, uint64_t x) { write_UA(p, x); } static inline void write_UA_L64(void* p, uint64_t x) { write_UA< openmsx::OPENMSX_BIGENDIAN>(p, x); } template static inline T read_UA(const void* p) { T x; memcpy(&x, p, sizeof(x)); if (SWAP) x = bswap(x); return x; } static inline uint16_t read_UA_B16(const void* p) { return read_UA(p); } static inline uint16_t read_UA_L16(const void* p) { return read_UA< openmsx::OPENMSX_BIGENDIAN, uint16_t>(p); } static inline uint32_t read_UA_B32(const void* p) { return read_UA(p); } static inline uint32_t read_UA_L32(const void* p) { return read_UA< openmsx::OPENMSX_BIGENDIAN, uint32_t>(p); } static inline uint64_t read_UA_B64(const void* p) { return read_UA(p); } static inline uint64_t read_UA_L64(const void* p) { return read_UA< openmsx::OPENMSX_BIGENDIAN, uint64_t>(p); } // Like the types above, but these don't need to be aligned. class UA_B16 { public: inline operator uint16_t() const { return read_UA_B16(x); } inline UA_B16& operator=(uint16_t a) { write_UA_B16(x, a); return *this; } private: uint8_t x[2]; }; class UA_L16 { public: inline operator uint16_t() const { return read_UA_L16(x); } inline UA_L16& operator=(uint16_t a) { write_UA_L16(x, a); return *this; } private: uint8_t x[2]; }; class UA_B32 { public: inline operator uint32_t() const { return read_UA_B32(x); } inline UA_B32& operator=(uint32_t a) { write_UA_B32(x, a); return *this; } private: uint8_t x[4]; }; class UA_L32 { public: inline operator uint32_t() const { return read_UA_L32(x); } inline UA_L32& operator=(uint32_t a) { write_UA_L32(x, a); return *this; } private: uint8_t x[4]; }; static_assert(sizeof(UA_B16) == 2, "must have size 2"); static_assert(sizeof(UA_L16) == 2, "must have size 2"); static_assert(sizeof(UA_B32) == 4, "must have size 4"); static_assert(sizeof(UA_L32) == 4, "must have size 4"); static_assert(ALIGNOF(UA_B16) == 1, "must have alignment 1"); static_assert(ALIGNOF(UA_L16) == 1, "must have alignment 1"); static_assert(ALIGNOF(UA_B32) == 1, "must have alignment 1"); static_assert(ALIGNOF(UA_L32) == 1, "must have alignment 1"); // Template meta-programming. // Get a type of the same size of the given type that stores the value in a // specific endianess. Typically used in template functions that can work on // either 16 or 32 bit values. // usage: // using LE_T = typename Endian::Little::type; // The type LE_T is now a type that stores values of the same size as 'T' // in little endian format (independent of host endianess). template struct Little; template<> struct Little { using type = uint8_t; }; template<> struct Little { using type = L16; }; template<> struct Little { using type = L32; }; template struct Big; template<> struct Big { using type = uint8_t; }; template<> struct Big { using type = B16; }; template<> struct Big { using type = B32; }; } // namespace Endian #endif #if 0 /////////////////////////////////////////// // Small functions to inspect code quality using namespace Endian; uint16_t testSwap16(uint16_t x) { return bswap16(x); } uint16_t testSwap16() { return bswap16(0x1234); } uint32_t testSwap32(uint32_t x) { return bswap32(x); } uint32_t testSwap32() { return bswap32(0x12345678); } union T16 { B16 be; L16 le; }; void test1(T16& t, uint16_t x) { t.le = x; } void test2(T16& t, uint16_t x) { t.be = x; } uint16_t test3(T16& t) { return t.le; } uint16_t test4(T16& t) { return t.be; } void testA(uint16_t& s, uint16_t x) { write_UA_L16(&s, x); } void testB(uint16_t& s, uint16_t x) { write_UA_B16(&s, x); } uint16_t testC(uint16_t& s) { return read_UA_L16(&s); } uint16_t testD(uint16_t& s) { return read_UA_B16(&s); } union T32 { B32 be; L32 le; }; void test1(T32& t, uint32_t x) { t.le = x; } void test2(T32& t, uint32_t x) { t.be = x; } uint32_t test3(T32& t) { return t.le; } uint32_t test4(T32& t) { return t.be; } void testA(uint32_t& s, uint32_t x) { write_UA_L32(&s, x); } void testB(uint32_t& s, uint32_t x) { write_UA_B32(&s, x); } uint32_t testC(uint32_t& s) { return read_UA_L32(&s); } uint32_t testD(uint32_t& s) { return read_UA_B32(&s); } /////////////////////////////// // Simple unit test #include int main() { T16 t16; assert(sizeof(t16) == 2); t16.le = 0x1234; assert(t16.le == 0x1234); assert(t16.be == 0x3412); assert(read_UA_L16(&t16) == 0x1234); assert(read_UA_B16(&t16) == 0x3412); t16.be = 0x1234; assert(t16.le == 0x3412); assert(t16.be == 0x1234); assert(read_UA_L16(&t16) == 0x3412); assert(read_UA_B16(&t16) == 0x1234); write_UA_L16(&t16, 0xaabb); assert(t16.le == 0xaabb); assert(t16.be == 0xbbaa); assert(read_UA_L16(&t16) == 0xaabb); assert(read_UA_B16(&t16) == 0xbbaa); write_UA_B16(&t16, 0xaabb); assert(t16.le == 0xbbaa); assert(t16.be == 0xaabb); assert(read_UA_L16(&t16) == 0xbbaa); assert(read_UA_B16(&t16) == 0xaabb); T32 t32; assert(sizeof(t32) == 4); t32.le = 0x12345678; assert(t32.le == 0x12345678); assert(t32.be == 0x78563412); assert(read_UA_L32(&t32) == 0x12345678); assert(read_UA_B32(&t32) == 0x78563412); t32.be = 0x12345678; assert(t32.le == 0x78563412); assert(t32.be == 0x12345678); assert(read_UA_L32(&t32) == 0x78563412); assert(read_UA_B32(&t32) == 0x12345678); write_UA_L32(&t32, 0xaabbccdd); assert(t32.le == 0xaabbccdd); assert(t32.be == 0xddccbbaa); assert(read_UA_L32(&t32) == 0xaabbccdd); assert(read_UA_B32(&t32) == 0xddccbbaa); write_UA_B32(&t32, 0xaabbccdd); assert(t32.le == 0xddccbbaa); assert(t32.be == 0xaabbccdd); assert(read_UA_L32(&t32) == 0xddccbbaa); assert(read_UA_B32(&t32) == 0xaabbccdd); } #endif openMSX-RELEASE_0_12_0/src/utils/hash_map.hh000066400000000000000000000046411257557151200204740ustar00rootroot00000000000000// hash_map // // Written by Wouter Vermaelen, based upon HashSet/HashMap // example code and ideas provided by Jacques Van Damme. #ifndef HASH_MAP_HH #define HASH_MAP_HH #include "hash_set.hh" namespace hash_set_impl { // Takes any (const or non-const) pair reference and returns a reference to // the first element of the pair. struct ExtractFirst { // c++14: template auto& operator()(Pair&& p) const { return p.first; } template inline First& operator()( std::pair& p) const { return p.first; } template inline const First& operator()(const std::pair& p) const { return p.first; } }; } // namespace hash_set_impl // hash_map // // A hash-map implementation with an STL-like interface. // // It builds upon hash-set, see there for more details. template, typename Equal = EqualTo> class hash_map : public hash_set, hash_set_impl::ExtractFirst, Hasher, Equal> { using BaseType = hash_set, hash_set_impl::ExtractFirst, Hasher, Equal>; public: using key_type = Key; using mapped_type = Value; using value_type = std::pair; using iterator = typename BaseType:: iterator; using const_iterator = typename BaseType::const_iterator; hash_map(unsigned initialSize = 0, Hasher hasher_ = Hasher(), Equal equal_ = Equal()) : BaseType(initialSize, hash_set_impl::ExtractFirst(), hasher_, equal_) { } template Value& operator[](K&& key) { auto it = this->find(key); if (it == this->end()) { auto p = this->insert(value_type(std::forward(key), Value())); it = p.first; } return it->second; } // Proposed for C++17. Is equivalent to 'operator[](key) = value', but: // - also works for non-default-constructible types // - returns more information // - is slightly more efficient template std::pair insert_or_assign(K&& key, V&& value) { auto it = this->find(key); if (it == this->end()) { // insert, return pair return this->insert(value_type(std::forward(key), std::forward(value))); } else { // assign, return pair it->second = std::forward(value); return std::make_pair(it, false); } } }; #endif openMSX-RELEASE_0_12_0/src/utils/hash_set.hh000066400000000000000000000473731257557151200205230ustar00rootroot00000000000000// hash_set // // Written by Wouter Vermaelen, based upon HashSet/HashMap // example code and ideas provided by Jacques Van Damme. #ifndef HASH_SET_HH #define HASH_SET_HH #include "stl.hh" #include "unreachable.hh" #include #include #include #include #include #include #include #include namespace hash_set_impl { // Identity operation: accepts any type (by const or non-const reference) and // returns the exact same (reference) value. struct Identity { template inline T& operator()(T&& t) const { return t; } }; // Holds the data that will be stored in the hash_set plus some extra // administrative data. // - value: the actual to be stored data // - hash: always equal to hasher(extract(value)) // - nextIdx: index of next element in a single-linked-list. This is either // the list of colliding buckets in the hash, or the list of free objects // in the Pool (see below). template struct Element { Value value; unsigned hash; unsigned nextIdx; template Element(V&& value_, unsigned hash_, unsigned nextIdx_) : value(std::forward(value_)) , hash(hash_) , nextIdx(nextIdx_) { } template Element(Args&&... args) : value(std::forward(args)...) // hash left uninitialized // nextIdx left uninitialized { } // Move constructor should be auto-generated, but visual studio 2013 // is not fully c++11 compliant :( // TODO remove once we switch to vs2015 Element(Element&& source) : value(std::move(source.value)) , hash(source.hash), nextIdx(source.nextIdx) { } }; // Holds 'Element' objects. These objects can be in 2 states: // - 'free': In that case 'nextIdx' forms a single-linked list of all // free objects. 'freeIdx_' is the head of this list. Index 0 // indicates the end of the list. This also means that valid // indices start at 1 instead of 0. // - 'in-use': The Element object is in use by some other data structure, // the Pool class doesn't interpret any of the Element fields. template class Pool { using Elem = Element; public: Pool() : buf1_(adjust(nullptr)), freeIdx_(0), capacity_(0) { } Pool(Pool&& source) : buf1_ (source.buf1_) , freeIdx_ (source.freeIdx_) , capacity_(source.capacity_) { source.buf1_ = adjust(nullptr); source.freeIdx_ = 0; source.capacity_ = 0; } Pool& operator=(Pool&& source) { buf1_ = source.buf1_; freeIdx_ = source.freeIdx_; capacity_ = source.capacity_; source.buf1_ = adjust(nullptr); source.freeIdx_ = 0; source.capacity_ = 0; return *this; } ~Pool() { free(buf1_ + 1); } // Lookup Element by index. Valid indices start at 1 instead of 0! // (External code) is only allowed to call this method with indices // that were earlier returned from create() and have not yet been // passed to destroy(). Elem& get(unsigned idx) { assert(idx != 0); assert(idx <= capacity_); return buf1_[idx]; } const Elem& get(unsigned idx) const { return const_cast(*this).get(idx); } // - Insert a new Element in the pool (will be created with the given // Element constructor parameters). // - Returns an index that can be used with the get() method. Never // returns 0 (so 0 can be used as a 'special' value). // - Internally Pool keeps a list of pre-allocated objects, when this // list runs out Pool will automatically allocate more objects. template unsigned create(V&& value, unsigned hash, unsigned nextIdx) { if (freeIdx_ == 0) grow(); unsigned idx = freeIdx_; auto& elem = get(idx); freeIdx_ = elem.nextIdx; new (&elem) Elem(std::forward(value), hash, nextIdx); return idx; } // Destroys a previously created object (given by index). Index must // have been returned by an earlier create() call, and the same index // can only be destroyed once. It's not allowed to destroy index 0. void destroy(unsigned idx) { auto& elem = get(idx); elem.~Elem(); elem.nextIdx = freeIdx_; freeIdx_ = idx; } // Leaves 'hash' and 'nextIdx' members uninitialized! template unsigned emplace(Args&&... args) { if (freeIdx_ == 0) grow(); unsigned idx = freeIdx_; auto& elem = get(idx); freeIdx_ = elem.nextIdx; new (&elem) Elem(std::forward(args)...); return idx; } unsigned capacity() const { return capacity_; } void reserve(unsigned count) { // note: not required to be a power-of-2 if (capacity_ >= count) return; if (capacity_ != 0) growMore(count); else growInitial(count); } friend void swap(Pool& x, Pool& y) { using std::swap; swap(x.buf1_, y.buf1_); swap(x.freeIdx_, y.freeIdx_); swap(x.capacity_, y.capacity_); } private: static inline Elem* adjust(Elem* p) { return p - 1; } void grow() { if (capacity_ != 0) growMore(2 * capacity_); else growInitial(4); // initial capacity = 4 } void growMore(unsigned newCapacity) { auto* oldBuf = buf1_ + 1; Elem* newBuf; #if __GNUC__ <= 4 // only implemented starting from gcc-5 bool trivial = false; #else bool trivial = std::is_trivially_move_constructible::value; #endif if (trivial) { newBuf = static_cast(realloc(oldBuf, newCapacity * sizeof(Elem))); if (!newBuf) throw std::bad_alloc(); } else { newBuf = static_cast(malloc(newCapacity * sizeof(Elem))); if (!newBuf) throw std::bad_alloc(); for (unsigned i = 0; i < capacity_; ++i) { new (&newBuf[i]) Elem(std::move(oldBuf[i])); oldBuf[i].~Elem(); } free(oldBuf); } for (unsigned i = capacity_; i < newCapacity - 1; ++i) { newBuf[i].nextIdx = i + 1 + 1; } newBuf[newCapacity - 1].nextIdx = 0; buf1_ = adjust(newBuf); freeIdx_ = capacity_ + 1; capacity_ = newCapacity; } void growInitial(unsigned newCapacity) { auto* newBuf = static_cast(malloc(newCapacity * sizeof(Elem))); if (!newBuf) throw std::bad_alloc(); for (unsigned i = 0; i < newCapacity - 1; ++i) { newBuf[i].nextIdx = i + 1 + 1; } newBuf[newCapacity - 1].nextIdx = 0; buf1_ = adjust(newBuf); freeIdx_ = 1; capacity_ = newCapacity; } private: Elem* buf1_; // 1 before start of buffer -> valid indices start at 1 unsigned freeIdx_; // index of a free block, 0 means nothing is free unsigned capacity_; }; // Type-alias for the resulting type of applying Extractor on Value. template using ExtractedType = typename std::remove_cv< typename std::remove_reference< decltype(std::declval()(std::declval()))> ::type> ::type; } // namespace hash_set_impl // hash_set // // A hash-set implementation with an STL-like interface. // // One important property of this implementation is that stored values are not // the same as the keys that are used for hashing the values or looking them // up. // // By default (when not specifying a 2nd template argument) the values are the // same as the keys (so you get a classic hash-set). but it's possible to // specify an extractor functor that extracts the key-part from the value (e.g. // extract one member variable out of a bigger struct). // // If required it's also possible to specify custom hash and equality functors. template>, typename Equal = EqualTo> class hash_set { public: using value_type = Value; template class Iter { public: using value_type = IValue; using difference_type = int; using pointer = IValue*; using reference = IValue&; using iterator_category = std::forward_iterator_tag; Iter() : hashSet(nullptr), elemIdx(0) {} template Iter(const Iter& other) : hashSet(other.hashSet), elemIdx(other.elemIdx) {} template Iter& operator=(const Iter& rhs) { hashSet = rhs.hashSet; elemIdx = rhs.elemIdx; return *this; } bool operator==(const Iter& rhs) const { assert((hashSet == rhs.hashSet) || !hashSet || !rhs.hashSet); return elemIdx == rhs.elemIdx; } bool operator!=(const Iter& rhs) const { return !(*this == rhs); } Iter& operator++() { auto& oldElem = hashSet->pool.get(elemIdx); elemIdx = oldElem.nextIdx; if (!elemIdx) { unsigned tableIdx = oldElem.hash & hashSet->allocMask; do { if (tableIdx == hashSet->allocMask) break; elemIdx = hashSet->table[++tableIdx]; } while (!elemIdx); } return *this; } Iter operator++(int) { Iter tmp = *this; ++(*this); return tmp; } IValue& operator*() const { return hashSet->pool.get(elemIdx).value; } IValue* operator->() const { return &hashSet->pool.get(elemIdx).value; } private: friend class hash_set; Iter(HashSet* m, unsigned idx) : hashSet(m), elemIdx(idx) {} unsigned getElementIdx() const { return elemIdx; } private: HashSet* hashSet; unsigned elemIdx; }; using iterator = Iter< hash_set, Value>; using const_iterator = Iter; public: hash_set(unsigned initialSize = 0, Extractor extract_ = Extractor(), Hasher hasher_ = Hasher(), Equal equal_ = Equal()) : table(nullptr), allocMask(-1), elemCount(0) , extract(extract_), hasher(hasher_), equal(equal_) { reserve(initialSize); // optimized away if initialSize==0 } hash_set(const hash_set& source) : table(nullptr), allocMask(-1), elemCount(0) , extract(source.extract), hasher(source.hasher) , equal(source.equal) { if (source.elemCount == 0) return; reserve(source.elemCount); for (unsigned i = 0; i <= source.allocMask; ++i) { for (auto idx = source.table[i]; idx; /**/) { const auto& elem = source.pool.get(idx); insert_noCapacityCheck_noDuplicateCheck(elem.value); idx = elem.nextIdx; } } } hash_set(hash_set&& source) : table(source.table) , pool(std::move(source.pool)) , allocMask(source.allocMask) , elemCount(source.elemCount) , extract(std::move(source.extract)) , hasher (std::move(source.hasher)) , equal (std::move(source.equal)) { source.table = nullptr; source.allocMask = -1; source.elemCount = 0; } hash_set(std::initializer_list args) : table(nullptr), allocMask(-1), elemCount(0) { reserve(args.size()); for (auto a : args) insert_noCapacityCheck(a); // need duplicate check?? } ~hash_set() { clear(); free(table); } hash_set& operator=(const hash_set& source) { clear(); if (source.elemCount == 0) return *this; reserve(source.elemCount); for (unsigned i = 0; i <= source.allocMask; ++i) { for (auto idx = source.table[i]; idx; /**/) { const auto& elem = source.pool.get(idx); insert_noCapacityCheck_noDuplicateCheck(elem.value); idx = elem.nextIdx; } } extract = source.extract; hasher = source.hasher; equal = source.equal; return *this; } hash_set& operator=(hash_set&& source) { table = source.table; pool = std::move(source.pool); allocMask = source.allocMask; elemCount = source.elemCount; extract = std::move(source.extract); hasher = std::move(source.hasher); equal = std::move(source.equal); source.table = nullptr; source.allocMask = -1; source.elemCount = 0; return *this; } template bool contains(const K& key) const { return locateElement(key); } template std::pair insert(V&& value) { return insert_impl(std::forward(value)); } template std::pair insert_noCapacityCheck(V&& value) { return insert_impl(std::forward(value)); } template iterator insert_noDuplicateCheck(V&& value) { return insert_impl(std::forward(value)).first; } template iterator insert_noCapacityCheck_noDuplicateCheck(V&& value) { return insert_impl(std::forward(value)).first; } template std::pair emplace(Args&&... args) { return emplace_impl(std::forward(args)...); } template std::pair emplace_noCapacityCheck(Args&&... args) { return emplace_impl(std::forward(args)...); } template iterator emplace_noDuplicateCheck(Args&&... args) { return emplace_impl(std::forward(args)...).first; } template iterator emplace_noCapacityCheck_noDuplicateCheck(Args&&... args) { return emplace_impl(std::forward(args)...).first; } template bool erase(const K& key) { if (elemCount == 0) return false; unsigned hash = hasher(key); unsigned tableIdx = hash & allocMask; for (auto* prev = &table[tableIdx]; *prev; prev = &(pool.get(*prev).nextIdx)) { unsigned elemIdx = *prev; auto& elem = pool.get(elemIdx); if (elem.hash != hash) continue; if (!equal(extract(elem.value), key)) continue; *prev = elem.nextIdx; pool.destroy(elemIdx); --elemCount; return true; } return false; } void erase(iterator it) { auto elemIdx = it.getElementIdx(); if (!elemIdx) { UNREACHABLE; // not allowed to call erase(end()) return; } auto& elem = pool.get(elemIdx); unsigned tableIdx = pool.get(elemIdx).hash & allocMask; auto* prev = &table[tableIdx]; assert(*prev); while (*prev != elemIdx) { prev = &(pool.get(*prev).nextIdx); assert(*prev); } *prev = elem.nextIdx; pool.destroy(elemIdx); --elemCount; } bool empty() const { return elemCount == 0; } unsigned size() const { return elemCount; } void clear() { if (elemCount == 0) return; for (unsigned i = 0; i <= allocMask; ++i) { for (auto elemIdx = table[i]; elemIdx; /**/) { auto nextIdx = pool.get(elemIdx).nextIdx; pool.destroy(elemIdx); elemIdx = nextIdx; } table[i] = 0; } elemCount = 0; } template iterator find(const K& key) { return iterator(this, locateElement(key)); } template const_iterator find(const K& key) const { return const_iterator(this, locateElement(key)); } iterator begin() { if (elemCount == 0) return end(); for (unsigned idx = 0; idx <= allocMask; ++idx) { if (table[idx]) { return iterator(this, table[idx]); } } UNREACHABLE; return end(); // avoid warning } const_iterator begin() const { if (elemCount == 0) return end(); for (unsigned idx = 0; idx <= allocMask; ++idx) { if (table[idx]) { return const_iterator(this, table[idx]); } } UNREACHABLE; return end(); // avoid warning } iterator end() { return iterator(); } const_iterator end() const { return const_iterator(); } unsigned capacity() const { unsigned poolCapacity = pool.capacity(); unsigned tableCapacity = (allocMask + 1) / 4 * 3; return std::min(poolCapacity, tableCapacity); } // After this call, the hash_set can at least contain 'count' number of // elements without requiring any additional memory allocation or rehash. void reserve(unsigned count) { pool.reserve(count); unsigned oldCount = allocMask + 1; unsigned newCount = nextPowerOf2((count + 2) / 3 * 4); if (oldCount >= newCount) return; allocMask = newCount - 1; if (oldCount == 0) { table = static_cast(calloc(newCount, sizeof(unsigned))); } else { table = static_cast(realloc(table, newCount * sizeof(unsigned))); do { rehash(oldCount); oldCount *= 2; } while (oldCount < newCount); } } friend void swap(hash_set& x, hash_set& y) { using std::swap; swap(x.table, y.table); swap(x.pool, y.pool); swap(x.allocMask, y.allocMask); swap(x.elemCount, y.elemCount); swap(x.extract , y.extract); swap(x.hasher , y.hasher); swap(x.equal , y.equal); } friend iterator begin( hash_set& s) { return s.begin(); } friend const_iterator begin(const hash_set& s) { return s.begin(); } friend iterator end ( hash_set& s) { return s.end(); } friend const_iterator end (const hash_set& s) { return s.end(); } private: // Returns the smallest value that is >= x that is also a power of 2. // (for x=0 it returns 0) static inline unsigned nextPowerOf2(unsigned x) { static_assert(sizeof(unsigned) == sizeof(uint32_t), "only works for exactly 32 bit"); x -= 1; x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16; return x + 1; } template std::pair insert_impl(V&& value) { unsigned hash = hasher(extract(value)); unsigned tableIdx = hash & allocMask; unsigned primary = 0; if (!CHECK_CAPACITY || (elemCount > 0)) { primary = table[tableIdx]; if (CHECK_DUPLICATE) { for (auto elemIdx = primary; elemIdx; /**/) { auto& elem = pool.get(elemIdx); if ((elem.hash == hash) && equal(extract(elem.value), extract(value))) { // already exists return std::make_pair(iterator(this, elemIdx), false); } elemIdx = elem.nextIdx; } } } if (CHECK_CAPACITY && (elemCount >= ((allocMask + 1) / 4 * 3))) { grow(); tableIdx = hash & allocMask; primary = table[tableIdx]; } elemCount++; unsigned idx = pool.create(std::forward(value), hash, primary); table[tableIdx] = idx; return std::make_pair(iterator(this, idx), true); } template std::pair emplace_impl(Args&&... args) { unsigned poolIdx = pool.emplace(std::forward(args)...); auto& poolElem = pool.get(poolIdx); unsigned hash = hasher(extract(poolElem.value)); unsigned tableIdx = hash & allocMask; unsigned primary = 0; if (!CHECK_CAPACITY || (elemCount > 0)) { primary = table[tableIdx]; if (CHECK_DUPLICATE) { for (auto elemIdx = primary; elemIdx; ) { auto& elem = pool.get(elemIdx); if ((elem.hash == hash) && equal(extract(elem.value), extract(poolElem.value))) { // already exists pool.destroy(poolIdx); return std::make_pair(iterator(this, elemIdx), false); } elemIdx = elem.nextIdx; } } } if (CHECK_CAPACITY && (elemCount >= ((allocMask + 1) / 4 * 3))) { grow(); tableIdx = hash & allocMask; primary = table[tableIdx]; } elemCount++; poolElem.hash = hash; poolElem.nextIdx = primary; table[tableIdx] = poolIdx; return std::make_pair(iterator(this, poolIdx), true); } void grow() { unsigned oldCount = allocMask + 1; if (oldCount == 0) { allocMask = 4 - 1; // initial size table = static_cast(calloc(4, sizeof(unsigned))); } else { unsigned newCount = 2 * oldCount; allocMask = newCount - 1; table = static_cast(realloc(table, newCount * sizeof(unsigned))); rehash(oldCount); } } void rehash(unsigned oldCount) { assert((oldCount & (oldCount - 1)) == 0); // must be a power-of-2 for (unsigned i = 0; i < oldCount; i++) { auto* p0 = &table[i]; auto* p1 = &table[i + oldCount]; for (auto p = *p0; p; p = pool.get(p).nextIdx) { auto& elem = pool.get(p); if ((elem.hash & oldCount) == 0) { *p0 = p; p0 = &elem.nextIdx; } else { *p1 = p; p1 = &elem.nextIdx; } } *p0 = 0; *p1 = 0; } } template unsigned locateElement(const K& key) const { if (elemCount == 0) return 0; unsigned hash = hasher(key); unsigned tableIdx = hash & allocMask; for (unsigned elemIdx = table[tableIdx]; elemIdx; /**/) { auto& elem = pool.get(elemIdx); if ((elem.hash == hash) && equal(extract(elem.value), key)) { return elemIdx; } elemIdx = elem.nextIdx; } return 0; } private: unsigned* table; hash_set_impl::Pool pool; unsigned allocMask; unsigned elemCount; Extractor extract; Hasher hasher; Equal equal; }; #endif openMSX-RELEASE_0_12_0/src/utils/inline.hh000066400000000000000000000007411257557151200201670ustar00rootroot00000000000000#ifndef INLINE_HH #define INLINE_HH #ifdef __GNUC__ #define ALWAYS_INLINE inline __attribute__((always_inline)) #define NEVER_INLINE __attribute__((noinline)) #elif defined _MSC_VER && 0 // Enabling these macros appears to make openmsx about 5% slower // when compiled with VC++ for x64. Hence we leave the defaults #define ALWAYS_INLINE __forceinline #define NEVER_INLINE __declspec(noinline) #else // Fallback #define ALWAYS_INLINE inline #define NEVER_INLINE #endif #endif openMSX-RELEASE_0_12_0/src/utils/likely.hh000066400000000000000000000007421257557151200202030ustar00rootroot00000000000000#ifndef LIKELY_HH #define LIKELY_HH /* Somewhere in the middle of the GCC 2.96 development cycle, we implemented * a mechanism by which the user can annotate likely branch directions and * expect the blocks to be reordered appropriately. Define __builtin_expect * to nothing for earlier compilers. */ #if __GNUC__ > 2 #define likely(x) __builtin_expect((x),1) #define unlikely(x) __builtin_expect((x),0) #else #define likely(x) (x) #define unlikely(x) (x) #endif #endif openMSX-RELEASE_0_12_0/src/utils/memory.hh000066400000000000000000000154701257557151200202260ustar00rootroot00000000000000#ifndef MEMORY_HH #define MEMORY_HH #include #include // See this blog for a motivation for the make_unique() function: // http://herbsutter.com/gotw/_102/ // This function will almost certainly be added in the next revision of the // c++ language. // The above blog also gives an implementation for make_unique(). Unfortunately // vs2012 doesn't support variadic templates yet, so for now we have to use // the longer (and less general) version below. #if 0 template std::unique_ptr make_unique(Args&& ...args) { return std::unique_ptr(new T(std::forward(args)...)); } #else // Emulate variadic templates for upto 13 arguments (yes we need a version // with 13 parameters!). template std::unique_ptr make_unique() { return std::unique_ptr(new T()); } template std::unique_ptr make_unique(P1&& p1) { return std::unique_ptr(new T( std::forward(p1))); } template std::unique_ptr make_unique(P1&& p1, P2&& p2) { return std::unique_ptr(new T( std::forward(p1), std::forward(p2))); } template std::unique_ptr make_unique(P1&& p1, P2&& p2, P3&& p3) { return std::unique_ptr(new T( std::forward(p1), std::forward(p2), std::forward(p3))); } template std::unique_ptr make_unique(P1&& p1, P2&& p2, P3&& p3, P4&& p4) { return std::unique_ptr(new T( std::forward(p1), std::forward(p2), std::forward(p3), std::forward(p4))); } template std::unique_ptr make_unique(P1&& p1, P2&& p2, P3&& p3, P4&& p4, P5&& p5) { return std::unique_ptr(new T( std::forward(p1), std::forward(p2), std::forward(p3), std::forward(p4), std::forward(p5))); } template std::unique_ptr make_unique(P1&& p1, P2&& p2, P3&& p3, P4&& p4, P5&& p5, P6&& p6) { return std::unique_ptr(new T( std::forward(p1), std::forward(p2), std::forward(p3), std::forward(p4), std::forward(p5), std::forward(p6))); } template std::unique_ptr make_unique(P1&& p1, P2&& p2, P3&& p3, P4&& p4, P5&& p5, P6&& p6, P7&& p7) { return std::unique_ptr(new T( std::forward(p1), std::forward(p2), std::forward(p3), std::forward(p4), std::forward(p5), std::forward(p6), std::forward(p7))); } template std::unique_ptr make_unique(P1&& p1, P2&& p2, P3&& p3, P4&& p4, P5&& p5, P6&& p6, P7&& p7, P8&& p8) { return std::unique_ptr(new T( std::forward(p1), std::forward(p2), std::forward(p3), std::forward(p4), std::forward(p5), std::forward(p6), std::forward(p7), std::forward(p8))); } template std::unique_ptr make_unique(P1&& p1, P2&& p2, P3&& p3, P4&& p4, P5&& p5, P6&& p6, P7&& p7, P8&& p8, P9&& p9) { return std::unique_ptr(new T( std::forward(p1), std::forward(p2), std::forward(p3), std::forward(p4), std::forward(p5), std::forward(p6), std::forward(p7), std::forward(p8), std::forward(p9))); } template std::unique_ptr make_unique(P1&& p1, P2&& p2, P3&& p3, P4&& p4, P5&& p5, P6&& p6, P7&& p7, P8&& p8, P9&& p9, P10&& p10) { return std::unique_ptr(new T( std::forward(p1), std::forward(p2 ), std::forward(p3), std::forward(p4 ), std::forward(p5), std::forward(p6 ), std::forward(p7), std::forward(p8 ), std::forward(p9), std::forward(p10))); } template std::unique_ptr make_unique(P1&& p1, P2&& p2, P3&& p3, P4&& p4, P5&& p5, P6&& p6, P7&& p7, P8&& p8, P9&& p9, P10&& p10, P11&& p11) { return std::unique_ptr(new T( std::forward(p1 ), std::forward(p2 ), std::forward(p3 ), std::forward(p4 ), std::forward(p5 ), std::forward(p6 ), std::forward(p7 ), std::forward(p8 ), std::forward(p9 ), std::forward(p10), std::forward(p11))); } template std::unique_ptr make_unique(P1&& p1, P2&& p2, P3&& p3, P4&& p4, P5&& p5, P6&& p6, P7&& p7, P8&& p8, P9&& p9, P10&& p10, P11&& p11, P12&& p12) { return std::unique_ptr(new T( std::forward(p1 ), std::forward(p2 ), std::forward(p3 ), std::forward(p4 ), std::forward(p5 ), std::forward(p6 ), std::forward(p7 ), std::forward(p8 ), std::forward(p9 ), std::forward(p10), std::forward(p11), std::forward(p12))); } template std::unique_ptr make_unique(P1&& p1, P2&& p2, P3&& p3, P4&& p4, P5&& p5, P6&& p6, P7&& p7, P8&& p8, P9&& p9, P10&& p10, P11&& p11, P12&& p12, P13&& p13) { return std::unique_ptr(new T( std::forward(p1 ), std::forward(p2 ), std::forward(p3 ), std::forward(p4 ), std::forward(p5 ), std::forward(p6 ), std::forward(p7 ), std::forward(p8 ), std::forward(p9 ), std::forward(p10), std::forward(p11), std::forward(p12), std::forward(p13))); } #endif #endif openMSX-RELEASE_0_12_0/src/utils/noncopyable.hh000066400000000000000000000007461257557151200212270ustar00rootroot00000000000000#ifndef NONCOPYABLE_HH #define NONCOPYABLE_HH /** * Based on boost::noncopyable, see boost documentation: * http://www.boost.org/libs/utility * * Summary: * Class noncopyable is a base class. Derive your own class from noncopyable * when you want to prohibit copy construction and copy assignment. */ class noncopyable { protected: noncopyable() {} ~noncopyable() {} private: noncopyable(const noncopyable&); const noncopyable& operator=(const noncopyable&); }; #endif openMSX-RELEASE_0_12_0/src/utils/outer.hh000066400000000000000000000030011257557151200200370ustar00rootroot00000000000000#ifndef OUTER_HH #define OUTER_HH #include // Example usage: // class Foo { // ... // struct Bar : public SomeBaseClass { // ... // void f() override { // // transform the 'this' pointer to the 'bar' inner object // // into a reference to the outer object of type Foo. // Foo& foo = OUTER(Foo, bar); // } // } bar; // }; // // This avoids having to store a back-pointer from the inner 'bar' object to // the outer 'Foo' object. // // The 'bar' object lives at a fixed offset inside the 'Foo' object, so // changing the bar-this-pointer into a Foo-reference is a matter of // subtracting this (compile-time-constant) offset. // // At least the offset is constant in the absence of virtual inheritance (we // don't use this c++ feature in openMSX). Also strictly speaking the c++ // standard doesn't guarantee the offset is constant in the presence of virtual // functions, though it is in all practical implementations. See here for more // background info: // http://stackoverflow.com/questions/1129894/why-cant-you-use-offsetof-on-non-pod-structures-in-c // Adjust the current 'this' pointer (pointer to the inner object) to a // reference to the outer object. The first parameter is the type of the outer // object, the second parameter is the name of the 'this' member variable in // the outer object. #define OUTER(type, member) *reinterpret_cast(reinterpret_cast(this) - offsetof(type, member)); #endif openMSX-RELEASE_0_12_0/src/utils/random.hh000066400000000000000000000044111257557151200201670ustar00rootroot00000000000000#ifndef RANDOM_HH #define RANDOM_HH #include /** Return reference to a (shared) global random number generator. */ inline std::minstd_rand0& global_urng() { static std::minstd_rand0 u; return u; } /** Seed the (shared) random number generator. */ inline void randomize() { static std::random_device rd; global_urng().seed(rd()); } /** Return a random boolean value. */ inline bool random_bool() { // Note: this is only 100% uniform if 'generator.max() - // generator().min() + 1' is even. This is the case for // std::minstd_rand0. auto& generator = global_urng(); return generator() & 1; } /** Return a random integer in the range [from, thru] (note: closed interval). * This function is convenient if you only need a few random values. If you * need a large amount it's a bit faster to create a local distribution * object and reuse that for all your values. */ inline int random_int(int from, int thru) { static std::uniform_int_distribution d; using parm_t = decltype(d)::param_type; return d(global_urng(), parm_t{from, thru}); } /** Return a random float in the range [from, upto) (note: half-open interval). * This function is convenient if you only need a few random values. If you * need a large amount it's a bit faster to create a local distribution * object and reuse that for all your values. */ inline float random_float(float from, float upto) { static std::uniform_real_distribution d; using parm_t = decltype(d)::param_type; return d(global_urng(), parm_t{from, upto}); } /** Return a random 32-bit value. * This function should rarely be used. It should NOT be used if you actually * need random values in a smaller range than [0 .. 0xffffffff]. For example: * 'random_32bit % N' with * - N not a power-of-2: is NOT uniform distribution anymore (higher values * have a slighly lower probability than the lower values) * - N a power-of-2: does more work than needed (typically has to call the * underlying generator more than once to make sure all 32 bits are * random, but then those upper bits are discarded). */ inline uint32_t random_32bit() { static std::uniform_int_distribution d; using parm_t = decltype(d)::param_type; return d(global_urng(), parm_t{0, 0xffffffff}); } #endif openMSX-RELEASE_0_12_0/src/utils/rapidsax.cc000066400000000000000000000060711257557151200205140ustar00rootroot00000000000000#include "rapidsax.hh" namespace rapidsax { namespace internal { // Character class lookup table // bit 0: \0 // bit 1: \t \r \r space // bit 2: < // bit 3: & // bit 4: ' // bit 5: " // bit 6: / > ? // bit 7: ! = const uint8_t lutChar[256] = { // 0 1 2 3 4 5 6 7 8 9 A B C D E F 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x02,0x00,0x00,0x02,0x00,0x00, // 0 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 1 0x02,0x80,0x20,0x00,0x00,0x00,0x08,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40, // 2 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x80,0x40,0x40, // 3 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 4 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 5 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 6 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 7 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 8 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 9 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // A 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // B 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // C 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // D 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // E 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // F }; // Digits (dec and hex, 255 denotes end of numeric character reference) const uint8_t lutDigits[256] = { // 0 1 2 3 4 5 6 7 8 9 A B C D E F 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 0 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 1 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 2 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,255,255,255,255,255,255, // 3 255, 10, 11, 12, 13, 14, 15,255,255,255,255,255,255,255,255,255, // 4 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 5 255, 10, 11, 12, 13, 14, 15,255,255,255,255,255,255,255,255,255, // 6 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 7 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 8 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // 9 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // A 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // B 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // C 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // D 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, // E 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255 // F }; } // namespace internal } // namespace rapidsax openMSX-RELEASE_0_12_0/src/utils/rapidsax.hh000066400000000000000000000456171257557151200205370ustar00rootroot00000000000000#ifndef RAPIDSAX_HH #define RAPIDSAX_HH // This code is _heavily_ based on RapidXml 1.13 // http://rapidxml.sourceforge.net/ // // RapidXml is a very fast XML parser. // http://xmlbench.sourceforge.net/results/benchmark200910/index.html // One of the main reasons it can be this fast is that doesn't do any string // copies. Instead the XML input data is modified in-place (e.g. for stuff like // < replacements). Though this also means the output produced by the parser // is tied to the lifetime of the XML input data. // // RapidXml produces a DOM-like output. This parser has a SAX-like interface. #include "string_ref.hh" #include #include namespace rapidsax { // Parse given XML text and call callback functions in the given handler. // - XML text must be zero-terminated // - Handler must implement the methods defined in NullHandler (below). An // easy way to do this is to inherit from NullHandler and only reimplement // the methods that you need. // - The behavior of the parser can be fine-tuned with the FLAGS parameter, // see below for more details. // - When a parse error is encounter, an instance of ParseError is thrown. // - The lifetime of the string_ref's in the callback handler is the same as // the lifetime of the input XML data (no string copies are made, instead // the XML file is modified in-place and references to this data are passed). template void parse(HANDLER& handler, char* xml); // Flags that influence parsing behavior. The flags can be OR'ed together. // Should XML entities like < be expanded or not? static const int noEntityTranslation = 0x1; // Should leading and trailing whitespace be trimmed? static const int trimWhitespace = 0x2; // Should sequences of whitespace characters be replaced with a single // space character? static const int normalizeWhitespace = 0x4; // Callback handler with all empty implementations (can be used as a base // class in case you only need to reimplement a few of the methods). class NullHandler { public: // Called when an opening XML tag is encountered. // 'name' is the name of the XML tag. void start(string_ref /*name*/) {} // Called when a XML tag is closed. // Note: the parser does currently not check whether the name of the // opening nd closing tags matches. void stop() {} // Called when text inside a tag is parsed. // XML entities are replaced (optional) // Whitespace is (optionally) trimmed or normalized. // This method is not called for an empty text string. // (Unlike other SAX parsers) the whole text string is always // passed in a single chunk (so no need to concatenate this text // with previous chunks in the callback). void text(string_ref /*text*/) {} // Called for each parsed attribute. // Attributes can occur inside xml tags or inside XML declarations. void attribute(string_ref /*name*/, string_ref /*value*/) {} // Called for parsed CDATA sections. void cdata(string_ref /*value*/) {} // Called when a XML comment () is parsed. void comment(string_ref /*value*/) {} // Called when XML declaration () is parsed. // Inside a XML declaration there can be attributes. void declarationStart() {} void declAttribute(string_ref /*name*/, string_ref /*value*/) {} void declarationStop() {} // Called when the is parsed. void doctype(string_ref /*text*/) {} // Called when XML processing instructions () are parsed. void procInstr(string_ref /*target*/, string_ref /*instr*/) {} }; class ParseError { public: ParseError(const char* what, char* where) : m_what(what) , m_where(where) { } const char* what() const { return m_what; } char* where() const { return m_where; } private: const char* m_what; char* m_where; }; namespace internal { extern const uint8_t lutChar [256]; // Character class extern const uint8_t lutDigits[256]; // Digits // Detect whitespace character (space \n \r \t) struct WhitespacePred { static bool test(char ch) { return (lutChar[uint8_t(ch)] & 0x02) != 0; } }; // Detect node name character (anything but space \n \r \t / > ? \0) struct NodeNamePred { static bool test(char ch) { return !(lutChar[uint8_t(ch)] & 0x43); } }; // Detect attribute name character (anything but space \n \r \t / < > = ? ! \0) struct AttributeNamePred { static bool test(char ch) { return !(lutChar[uint8_t(ch)] & 0xC7); } }; // Detect text character (PCDATA) (anything but < \0) struct TextPred { static bool test(char ch) { return !(lutChar[uint8_t(ch)] & 0x05); } }; // Detect text character (PCDATA) that does not require processing when ws // normalization is disabled (anything but < \0 &) struct TextPureNoWsPred { static bool test(char ch) { return !(lutChar[uint8_t(ch)] & 0x0D); } }; // Detect text character (PCDATA) that does not require processing when ws // normalizationis is enabled (anything but < \0 & space \n \r \t) struct TextPureWithWsPred { static bool test(char ch) { return !(lutChar[uint8_t(ch)] & 0x0F); } }; // Detect attribute value character, single quote (anything but ' \0) struct AttPred1 { static bool test(char ch) { return !(lutChar[uint8_t(ch)] & 0x11); } }; // Detect attribute value character, double quote (anything but " \0) struct AttPred2 { static bool test(char ch) { return !(lutChar[uint8_t(ch)] & 0x21); } }; // Detect attribute value character, single quote, that does not require // processing (anything but ' \0 &) struct AttPurePred1 { static bool test(char ch) { return !(lutChar[uint8_t(ch)] & 0x19); } }; // Detect attribute value character, double quote, that does not require // processing (anything but " \0 &) struct AttPurePred2 { static bool test(char ch) { return !(lutChar[uint8_t(ch)] & 0x29); } }; // Insert coded character, using UTF8 static inline void insertUTF8char(char*& text, unsigned long code) { if (code < 0x80) { // 1 byte sequence text[0] = char(code); text += 1; } else if (code < 0x800) {// 2 byte sequence text[1] = char((code | 0x80) & 0xBF); code >>= 6; text[0] = char (code | 0xC0); text += 2; } else if (code < 0x10000) { // 3 byte sequence text[2] = char((code | 0x80) & 0xBF); code >>= 6; text[1] = char((code | 0x80) & 0xBF); code >>= 6; text[0] = char (code | 0xE0); text += 3; } else if (code < 0x110000) { // 4 byte sequence text[3] = char((code | 0x80) & 0xBF); code >>= 6; text[2] = char((code | 0x80) & 0xBF); code >>= 6; text[1] = char((code | 0x80) & 0xBF); code >>= 6; text[0] = char (code | 0xF0); text += 4; } else { // Invalid, only codes up to 0x10FFFF are allowed in Unicode throw ParseError("invalid numeric character entity", text); } } template static inline bool next(const char* p) { return (p[0] == C0) && (p[1] == C1); } template static inline bool next(const char* p) { return (p[0] == C0) && (p[1] == C1) && (p[2] == C2); } template static inline bool next(const char* p) { return (p[0] == C0) && (p[1] == C1) && (p[2] == C2) && (p[3] == C3); } template static inline bool next(const char* p) { return (p[0] == C0) && (p[1] == C1) && (p[2] == C2) && (p[3] == C3) && (p[4] == C4) && (p[5] == C5); } // Skip characters until predicate evaluates to true template static inline void skip(char*& text) { char* tmp = text; while (StopPred::test(*tmp)) ++tmp; text = tmp; } // Skip characters until predicate evaluates to true while doing the following: // - replacing XML character entity references with proper characters // (' & " < > &#...;) // - condensing whitespace sequences to single space character template static inline char* skipAndExpand(char*& text) { // If entity translation, whitespace condense and whitespace // trimming is disabled, use plain skip. if ( (FLAGS & noEntityTranslation) && !(FLAGS & normalizeWhitespace) && !(FLAGS & trimWhitespace)) { skip(text); return text; } // Use simple skip until first modification is detected skip(text); // Use translation skip char* src = text; char* dest = src; while (StopPred::test(*src)) { // Test if replacement is needed if (!(FLAGS & noEntityTranslation) && (src[0] == '&')) { switch (src[1]) { case 'a': // & ' if (next<'m','p',';'>(&src[2])) { *dest = '&'; ++dest; src += 5; continue; } if (next<'p','o','s',';'>(&src[2])) { *dest = '\''; ++dest; src += 6; continue; } break; case 'q': // " if (next<'u','o','t',';'>(&src[2])) { *dest = '"'; ++dest; src += 6; continue; } break; case 'g': // > if (next<'t',';'>(&src[2])) { *dest = '>'; ++dest; src += 4; continue; } break; case 'l': // < if (next<'t',';'>(&src[2])) { *dest = '<'; ++dest; src += 4; continue; } break; case '#': // &#...; - assumes ASCII if (src[2] == 'x') { unsigned long code = 0; src += 3; // skip &#x while (true) { uint8_t digit = lutDigits[uint8_t(*src)]; if (digit == 0xFF) break; code = code * 16 + digit; ++src; } insertUTF8char(dest, code); } else { unsigned long code = 0; src += 2; // skip &# while (1) { uint8_t digit = lutDigits[uint8_t(*src)]; if (digit == 0xFF) break; code = code * 10 + digit; ++src; } insertUTF8char(dest, code); } if (*src != ';') { throw ParseError("expected ;", src); } ++src; continue; default: // Something else, ignore, just copy '&' verbatim break; } } // Test if condensing is needed if ((FLAGS & normalizeWhitespace) && (WhitespacePred::test(*src))) { *dest++ = ' '; // single space in dest ++src; // skip first whitespace char // Skip remaining whitespace chars while (WhitespacePred::test(*src)) ++src; continue; } // No replacement, only copy character *dest++ = *src++; } // Return new end text = src; return dest; } static inline void skipBOM(char*& text) { if (next(text)) { text += 3; // skip utf-8 bom } } template class Parser { HANDLER& handler; public: Parser(HANDLER& handler_, char* text) : handler(handler_) { skipBOM(text); while (true) { // Skip whitespace before node skip(text); if (*text == 0) break; if (*text != '<') { throw ParseError("expected <", text); } ++text; // skip '<' parseNode(text); } } private: // Parse XML declaration ((text); // skip ws before attributes or ?> parseAttributes(text, true); handler.declarationStop(); // skip ?> if (!next<'?','>'>(text)) { throw ParseError("expected ?>", text); } text += 2; } // Parse XML comment (' } void parseDoctype(char*& text) { char* value = text; // remember value start // skip to > while (*text != '>') { switch (*text) { case '[': { // If '[' encountered, scan for matching ending // ']' using naive algorithm with depth. This // works for all W3C test files except for 2 // most wicked. ++text; // skip '[' int depth = 1; while (depth > 0) { switch (*text) { case char('['): ++depth; break; case char(']'): --depth; break; case 0: throw ParseError( "unexpected end of data", text); } ++text; } break; } case '\0': throw ParseError("unexpected end of data", text); default: ++text; } } handler.doctype(string_ref(value, text)); text += 1; // skip '>' } void parsePI(char*& text) { // Extract PI target name char* name = text; skip(text); char* nameEnd = text; if (name == nameEnd) { throw ParseError("expected PI target", text); } // Skip whitespace between pi target and pi skip(text); // Skip to '?>' char* value = text; // Remember start of pi while (!next<'?','>'>(text)) { if (*text == 0) { throw ParseError("unexpected end of data", text); } ++text; } // Set pi value (verbatim, no entity expansion or ws normalization) handler.procInstr(string_ref(name, nameEnd), string_ref(value, text)); text += 2; // skip '?>' } void parseText(char*& text, char* contentsStart) { // Backup to contents start if whitespace trimming is disabled if (!(FLAGS & trimWhitespace)) { text = contentsStart; } // Skip until end of data char* value = text; char* end = (FLAGS & normalizeWhitespace) ? skipAndExpand(text) : skipAndExpand(text); // Trim trailing whitespace; leading was already trimmed by // whitespace skip after > if (FLAGS & trimWhitespace) { if (FLAGS & normalizeWhitespace) { // Whitespace is already condensed to single // space characters by skipping function, so // just trim 1 char off the end. if (end[-1] == ' ') { --end; } } else { // Backup until non-whitespace character is found while (WhitespacePred::test(end[-1])) { --end; } } } // check next char before calling handler.text() if (*text == '\0') { throw ParseError("unexpected end of data", text); } else { assert(*text == '<'); } // Handle text, but only if non-empty. auto len = end - value; if (len) handler.text(string_ref(value, len)); } void parseCdata(char*& text) { // Skip until end of cdata char* value = text; while (!next<']',']','>'>(text)) { if (text[0] == 0) { throw ParseError("unexpected end of data", text); } ++text; } handler.cdata(string_ref(value, text)); text += 3; // skip ]]> } void parseElement(char*& text) { // Extract element name char* name = text; skip(text); char* nameEnd = text; if (name == nameEnd) { throw ParseError("expected element name", text); } handler.start(string_ref(name, nameEnd)); skip(text); // skip ws before attributes or > parseAttributes(text, false); // Determine ending type if (*text == '>') { ++text; parseNodeContents(text); } else if (*text == '/') { handler.stop(); ++text; if (*text != '>') { throw ParseError("expected >", text); } ++text; } else { throw ParseError("expected >", text); } } // Determine node type, and parse it void parseNode(char*& text) { switch (text[0]) { case '?': // (text) || next<'X','M','L'>(text)) && WhitespacePred::test(text[3])) { // '(&text[2])) { // '(&text[2]) && WhitespacePred::test(text[8])) { // '') { if (*text == 0) { throw ParseError( "unexpected end of data", text); } ++text; } ++text; // skip '>' break; default: // <... parseElement(text); break; } } // Parse contents of the node - children, data etc. void parseNodeContents(char*& text) { while (true) { char* contentsStart = text; // start before ws is skipped skip(text); // Skip ws between > and contents switch (*text) { case '<': // Node closing or child node afterText: // After parseText() jump here instead of continuing // the loop, because skipping whitespace is unnecessary. if (text[1] == '/') { // Node closing text += 2; // skip '(text); // TODO validate closing tag?? handler.stop(); // Skip remaining whitespace after node name skip(text); if (*text != '>') { throw ParseError("expected >", text); } ++text; // skip '>' return; } else { // Child node ++text; // skip '<' parseNode(text); } break; case '\0': throw ParseError("unexpected end of data", text); default: parseText(text, contentsStart); goto afterText; } } } // Parse XML attributes of the node void parseAttributes(char*& text, bool declaration) { // For all attributes while (AttributeNamePred::test(*text)) { // Extract attribute name char* name = text; ++text; // Skip first character of attribute name skip(text); char* nameEnd = text; if (name == nameEnd) { throw ParseError("expected attribute name", name); } skip(text); // skip ws after name if (*text != '=') { throw ParseError("expected =", text); } ++text; // skip = skip(text); // skip ws after = // Skip quote and remember if it was ' or " char quote = *text; if (quote != '\'' && quote != '"') { throw ParseError("expected ' or \"", text); } ++text; // Extract attribute value and expand char refs in it // No whitespace normalization in attributes static const int FLAGS2 = FLAGS & ~normalizeWhitespace; char* value = text; char* valueEnd = (quote == '\'') ? skipAndExpand(text) : skipAndExpand(text); // Make sure that end quote is present // check before calling handler.xxx() if (*text != quote) { throw ParseError("expected ' or \"", text); } ++text; // skip quote if (!declaration) { handler.attribute(string_ref(name, nameEnd), string_ref(value, valueEnd)); } else { handler.declAttribute(string_ref(name, nameEnd), string_ref(value, valueEnd)); } skip(text); // skip ws after value } } }; } // namespace internal template inline void parse(HANDLER& handler, char* xml) { internal::Parser parser(handler, xml); } } // namespace rapidsax #endif openMSX-RELEASE_0_12_0/src/utils/sha1.cc000066400000000000000000000155471257557151200175450ustar00rootroot00000000000000/* Based on: 100% free public domain implementation of the SHA-1 algorithm by Dominik Reichl Refactored in C++ style as part of openMSX by Maarten ter Huurne and Wouter Vermaelen. === Test Vectors (from FIPS PUB 180-1) === "abc" A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 A million repetitions of "a" 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F */ #include "sha1.hh" #include "MSXException.hh" #include "endian.hh" #include #include using std::string; namespace openmsx { // Rotate x bits to the left inline static uint32_t rol32(uint32_t value, int bits) { return (value << bits) | (value >> (32 - bits)); } class WorkspaceBlock { private: uint32_t data[16]; uint32_t next0(int i) { data[i] = Endian::readB32(&data[i]); return data[i]; } uint32_t next(int i) { return data[i & 15] = rol32( data[(i + 13) & 15] ^ data[(i + 8) & 15] ^ data[(i + 2) & 15] ^ data[ i & 15] , 1); } public: explicit WorkspaceBlock(const uint8_t buffer[64]); // SHA-1 rounds void r0(uint32_t v, uint32_t& w, uint32_t x, uint32_t y, uint32_t& z, int i) { z += ((w & (x ^ y)) ^ y) + next0(i) + 0x5A827999 + rol32(v, 5); w = rol32(w, 30); } void r1(uint32_t v, uint32_t& w, uint32_t x, uint32_t y, uint32_t& z, int i) { z += ((w & (x ^ y)) ^ y) + next(i) + 0x5A827999 + rol32(v, 5); w = rol32(w, 30); } void r2(uint32_t v, uint32_t& w, uint32_t x, uint32_t y, uint32_t& z, int i) { z += (w ^ x ^ y) + next(i) + 0x6ED9EBA1 + rol32(v, 5); w = rol32(w, 30); } void r3(uint32_t v, uint32_t& w, uint32_t x, uint32_t y, uint32_t& z, int i) { z += (((w | x) & y) | (w & x)) + next(i) + 0x8F1BBCDC + rol32(v, 5); w = rol32(w, 30); } void r4(uint32_t v, uint32_t& w, uint32_t x, uint32_t y, uint32_t& z, int i) { z += (w ^ x ^ y) + next(i) + 0xCA62C1D6 + rol32(v, 5); w = rol32(w, 30); } }; WorkspaceBlock::WorkspaceBlock(const uint8_t buffer[64]) { memcpy(data, buffer, sizeof(data)); } // class Sha1Sum Sha1Sum::Sha1Sum() { clear(); } Sha1Sum::Sha1Sum(string_ref str) { if (str.size() != 40) { throw MSXException("Invalid sha1, should be exactly 40 digits long: " + str); } parse40(str.data()); } static inline unsigned hex(char x, const char* str) { if (('0' <= x) && (x <= '9')) return x - '0'; if (('a' <= x) && (x <= 'f')) return x - 'a' + 10; if (('A' <= x) && (x <= 'F')) return x - 'A' + 10; throw MSXException("Invalid sha1, digits should be 0-9, a-f: " + string(str, 40)); } void Sha1Sum::parse40(const char* str) { const char* p = str; for (int i = 0; i < 5; ++i) { unsigned t = 0; for (int j = 0; j < 8; ++j) { t <<= 4; t |= hex(*p++, str); } a[i] = t; } } static inline char digit(unsigned x) { return (x < 10) ? (x + '0') : (x - 10 + 'a'); } std::string Sha1Sum::toString() const { char buf[40]; char* p = buf; for (int i = 0; i < 5; ++i) { for (int j = 28; j >= 0; j -= 4) { *p++ = digit((a[i] >> j) & 0xf); } } return string(buf, 40); } bool Sha1Sum::empty() const { for (int i = 0; i < 5; ++i) { if (a[i] != 0) return false; } return true; } void Sha1Sum::clear() { for (int i = 0; i < 5; ++i) { a[i] = 0; } } // class SHA1 SHA1::SHA1() { // SHA1 initialization constants m_state.a[0] = 0x67452301; m_state.a[1] = 0xEFCDAB89; m_state.a[2] = 0x98BADCFE; m_state.a[3] = 0x10325476; m_state.a[4] = 0xC3D2E1F0; m_count = 0; m_finalized = false; } void SHA1::transform(const uint8_t buffer[64]) { WorkspaceBlock block(buffer); // Copy m_state[] to working vars uint32_t a = m_state.a[0]; uint32_t b = m_state.a[1]; uint32_t c = m_state.a[2]; uint32_t d = m_state.a[3]; uint32_t e = m_state.a[4]; // 4 rounds of 20 operations each. Loop unrolled block.r0(a,b,c,d,e, 0); block.r0(e,a,b,c,d, 1); block.r0(d,e,a,b,c, 2); block.r0(c,d,e,a,b, 3); block.r0(b,c,d,e,a, 4); block.r0(a,b,c,d,e, 5); block.r0(e,a,b,c,d, 6); block.r0(d,e,a,b,c, 7); block.r0(c,d,e,a,b, 8); block.r0(b,c,d,e,a, 9); block.r0(a,b,c,d,e,10); block.r0(e,a,b,c,d,11); block.r0(d,e,a,b,c,12); block.r0(c,d,e,a,b,13); block.r0(b,c,d,e,a,14); block.r0(a,b,c,d,e,15); block.r1(e,a,b,c,d,16); block.r1(d,e,a,b,c,17); block.r1(c,d,e,a,b,18); block.r1(b,c,d,e,a,19); block.r2(a,b,c,d,e,20); block.r2(e,a,b,c,d,21); block.r2(d,e,a,b,c,22); block.r2(c,d,e,a,b,23); block.r2(b,c,d,e,a,24); block.r2(a,b,c,d,e,25); block.r2(e,a,b,c,d,26); block.r2(d,e,a,b,c,27); block.r2(c,d,e,a,b,28); block.r2(b,c,d,e,a,29); block.r2(a,b,c,d,e,30); block.r2(e,a,b,c,d,31); block.r2(d,e,a,b,c,32); block.r2(c,d,e,a,b,33); block.r2(b,c,d,e,a,34); block.r2(a,b,c,d,e,35); block.r2(e,a,b,c,d,36); block.r2(d,e,a,b,c,37); block.r2(c,d,e,a,b,38); block.r2(b,c,d,e,a,39); block.r3(a,b,c,d,e,40); block.r3(e,a,b,c,d,41); block.r3(d,e,a,b,c,42); block.r3(c,d,e,a,b,43); block.r3(b,c,d,e,a,44); block.r3(a,b,c,d,e,45); block.r3(e,a,b,c,d,46); block.r3(d,e,a,b,c,47); block.r3(c,d,e,a,b,48); block.r3(b,c,d,e,a,49); block.r3(a,b,c,d,e,50); block.r3(e,a,b,c,d,51); block.r3(d,e,a,b,c,52); block.r3(c,d,e,a,b,53); block.r3(b,c,d,e,a,54); block.r3(a,b,c,d,e,55); block.r3(e,a,b,c,d,56); block.r3(d,e,a,b,c,57); block.r3(c,d,e,a,b,58); block.r3(b,c,d,e,a,59); block.r4(a,b,c,d,e,60); block.r4(e,a,b,c,d,61); block.r4(d,e,a,b,c,62); block.r4(c,d,e,a,b,63); block.r4(b,c,d,e,a,64); block.r4(a,b,c,d,e,65); block.r4(e,a,b,c,d,66); block.r4(d,e,a,b,c,67); block.r4(c,d,e,a,b,68); block.r4(b,c,d,e,a,69); block.r4(a,b,c,d,e,70); block.r4(e,a,b,c,d,71); block.r4(d,e,a,b,c,72); block.r4(c,d,e,a,b,73); block.r4(b,c,d,e,a,74); block.r4(a,b,c,d,e,75); block.r4(e,a,b,c,d,76); block.r4(d,e,a,b,c,77); block.r4(c,d,e,a,b,78); block.r4(b,c,d,e,a,79); // Add the working vars back into m_state[] m_state.a[0] += a; m_state.a[1] += b; m_state.a[2] += c; m_state.a[3] += d; m_state.a[4] += e; } // Use this function to hash in binary data and strings void SHA1::update(const uint8_t* data, size_t len) { assert(!m_finalized); uint32_t j = (m_count >> 3) & 63; m_count += uint64_t(len) << 3; uint32_t i; if ((j + len) > 63) { memcpy(&m_buffer[j], data, (i = 64 - j)); transform(m_buffer); for (; i + 63 < len; i += 64) { transform(&data[i]); } j = 0; } else { i = 0; } memcpy(&m_buffer[j], &data[i], len - i); } void SHA1::finalize() { assert(!m_finalized); uint8_t finalcount[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; for (int i = 0; i < 8; i++) { finalcount[i] = uint8_t(m_count >> ((7 - i) * 8)); } update(reinterpret_cast("\200"), 1); while ((m_count & 504) != 448) { update(reinterpret_cast("\0"), 1); } update(finalcount, 8); // cause a transform() m_finalized = true; } Sha1Sum SHA1::digest() { if (!m_finalized) finalize(); return m_state; } Sha1Sum SHA1::calc(const uint8_t* data, size_t len) { SHA1 sha1; sha1.update(data, len); return sha1.digest(); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/utils/sha1.hh000066400000000000000000000050111257557151200175400ustar00rootroot00000000000000#ifndef SHA1_HH #define SHA1_HH #include "string_ref.hh" #include #include #include namespace openmsx { /** This class represents the result of a sha1 calculation (a 160-bit value). * Objects of this class can be constructed from/converted to 40-digit hex * string. * We treat the value '000...00' (all zeros) special. This value can be used * to indicate a null-sha1sum value (e.g. sha1 not yet calculated, or not * meaningful). In theory it's possible this special value is the result of an * actual sha1 calculation, but this has an _extremely_ low probability. */ class Sha1Sum { public: // note: default copy and assign are ok Sha1Sum(); /** Construct from string, throws when string is malformed. */ explicit Sha1Sum(string_ref hex); void parse40(const char* str); std::string toString() const; // Test or set 'null' value. bool empty() const; void clear(); bool operator==(const Sha1Sum& other) const { for (int i = 0; i < 5; ++i) { if (a[i] != other.a[i]) return false; } return true; } bool operator!=(const Sha1Sum& other) const { return !(*this == other); } bool operator< (const Sha1Sum& other) const { for (int i = 0; i < 5-1; ++i) { if (a[i] != other.a[i]) return a[i] < other.a[i]; } return a[5-1] < other.a[5-1]; } bool operator<=(const Sha1Sum& other) const { return !(other < *this); } bool operator> (const Sha1Sum& other) const { return (other < *this); } bool operator>=(const Sha1Sum& other) const { return !(*this < other); } friend std::ostream& operator<<(std::ostream& os, const Sha1Sum& sum) { os << sum.toString(); return os; } private: uint32_t a[5]; friend class SHA1; }; /** Helper class to perform a sha1 calculation. * Basic usage: * - construct a SHA1 object * - repeatedly call update() * - call digest() to get the result * Alternatively, use calc() if all data can be passed at once (IOW when there * would be exactly one call to update() in the recipe above). */ class SHA1 { public: SHA1(); /** Incrementally calculate the hash value. */ void update(const uint8_t* data, size_t len); /** Get the final hash. After this method is called, calls to update() * are invalid. */ Sha1Sum digest(); /** Easier to use interface, if you can pass all data in one go. */ static Sha1Sum calc(const uint8_t* data, size_t len); private: void transform(const uint8_t buffer[64]); void finalize(); uint64_t m_count; Sha1Sum m_state; uint8_t m_buffer[64]; bool m_finalized; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/utils/snappy.cc000066400000000000000000000507121257557151200202140ustar00rootroot00000000000000#include "snappy.hh" #include "aligned.hh" #include "likely.hh" #include "endian.hh" #include "build-info.hh" #include #include #include #include using namespace openmsx; namespace snappy { enum { LITERAL = 0, COPY_1_BYTE_OFFSET = 1, // 3 bit length + 3 bits of offset in opcode COPY_2_BYTE_OFFSET = 2, COPY_4_BYTE_OFFSET = 3 }; static inline void unalignedCopy64(const char* src, char* dst) { if (sizeof(void*) == 8) { unalignedStore64(dst + 0, unalignedLoad64(src + 0)); } else { // This can be more efficient than unalignedLoad64 + // unalignedStore64 on some platforms, in particular ARM. unalignedStore32(dst + 0, unalignedLoad32(src + 0)); unalignedStore32(dst + 4, unalignedLoad32(src + 4)); } } static inline void unalignedCopy128(const char* src, char* dst) { unalignedCopy64(src + 0, dst + 0); unalignedCopy64(src + 8, dst + 8); } ///////////////// // Copy "len" bytes from "src" to "op", one byte at a time. Used for // handling COPY operations where the input and output regions may // overlap. For example, suppose: // src == "ab" // op == src + 2 // len == 20 // After incrementalCopy(src, op, len), the result will have // eleven copies of "ab" // ababababababababababab // Note that this does not match the semantics of either memcpy() // or memmove(). static inline void incrementalCopy(const char* src, char* op, ptrdiff_t len) { assert(len > 0); do { *op++ = *src++; } while (--len); } // Equivalent to incrementalCopy() except that it can write up to ten extra // bytes after the end of the copy, and that it is faster. // // The main part of this loop is a simple copy of eight bytes at a time until // we've copied (at least) the requested amount of bytes. However, if op and // src are less than eight bytes apart (indicating a repeating pattern of // length < 8), we first need to expand the pattern in order to get the correct // results. For instance, if the buffer looks like this, with the eight-byte // and patterns marked as intervals: // // abxxxxxxxxxxxx // [------] src // [------] op // // a single eight-byte copy from to will repeat the pattern once, // after which we can move two bytes without moving : // // ababxxxxxxxxxx // [------] src // [------] op // // and repeat the exercise until the two no longer overlap. // // This allows us to do very well in the special case of one single byte // repeated many times, without taking a big hit for more general cases. // // The worst case of extra writing past the end of the match occurs when // op - src == 1 and len == 1; the last copy will read from byte positions // [0..7] and write to [4..11], whereas it was only supposed to write to // position 1. Thus, ten excess bytes. static const int MAX_INCR_COPY_OVERFLOW = 10; static inline void incrementalCopyFast(const char* src, char* op, ptrdiff_t len) { while (op - src < 8) { unalignedCopy64(src, op); len -= op - src; op += op - src; } while (len > 0) { unalignedCopy64(src, op); src += 8; op += 8; len -= 8; } } static inline uint32_t loadNBytes(const void* p, unsigned n) { // Mapping from i in range [0,4] to a mask to extract the bottom i bytes static const uint32_t wordmask[] = { 0, 0xff, 0xffff, 0xffffff, 0xffffffff }; return Endian::read_UA_L32(p) & wordmask[n]; } // Data stored per entry in lookup table: // Range Bits-used Description // ------------------------------------ // 1..64 0..7 Literal/copy length encoded in opcode byte // 0..7 8..10 Copy offset encoded in opcode byte / 256 // 0..4 11..13 Extra bytes after opcode // // We use eight bits for the length even though 7 would have sufficed // because of efficiency reasons: // (1) Extracting a byte is faster than a bit-field // (2) It properly aligns copy offset so we do not need a <<8 // See original code for a generator for this table. static const uint16_t charTable[256] = { 0x0001, 0x0804, 0x1001, 0x2001, 0x0002, 0x0805, 0x1002, 0x2002, 0x0003, 0x0806, 0x1003, 0x2003, 0x0004, 0x0807, 0x1004, 0x2004, 0x0005, 0x0808, 0x1005, 0x2005, 0x0006, 0x0809, 0x1006, 0x2006, 0x0007, 0x080a, 0x1007, 0x2007, 0x0008, 0x080b, 0x1008, 0x2008, 0x0009, 0x0904, 0x1009, 0x2009, 0x000a, 0x0905, 0x100a, 0x200a, 0x000b, 0x0906, 0x100b, 0x200b, 0x000c, 0x0907, 0x100c, 0x200c, 0x000d, 0x0908, 0x100d, 0x200d, 0x000e, 0x0909, 0x100e, 0x200e, 0x000f, 0x090a, 0x100f, 0x200f, 0x0010, 0x090b, 0x1010, 0x2010, 0x0011, 0x0a04, 0x1011, 0x2011, 0x0012, 0x0a05, 0x1012, 0x2012, 0x0013, 0x0a06, 0x1013, 0x2013, 0x0014, 0x0a07, 0x1014, 0x2014, 0x0015, 0x0a08, 0x1015, 0x2015, 0x0016, 0x0a09, 0x1016, 0x2016, 0x0017, 0x0a0a, 0x1017, 0x2017, 0x0018, 0x0a0b, 0x1018, 0x2018, 0x0019, 0x0b04, 0x1019, 0x2019, 0x001a, 0x0b05, 0x101a, 0x201a, 0x001b, 0x0b06, 0x101b, 0x201b, 0x001c, 0x0b07, 0x101c, 0x201c, 0x001d, 0x0b08, 0x101d, 0x201d, 0x001e, 0x0b09, 0x101e, 0x201e, 0x001f, 0x0b0a, 0x101f, 0x201f, 0x0020, 0x0b0b, 0x1020, 0x2020, 0x0021, 0x0c04, 0x1021, 0x2021, 0x0022, 0x0c05, 0x1022, 0x2022, 0x0023, 0x0c06, 0x1023, 0x2023, 0x0024, 0x0c07, 0x1024, 0x2024, 0x0025, 0x0c08, 0x1025, 0x2025, 0x0026, 0x0c09, 0x1026, 0x2026, 0x0027, 0x0c0a, 0x1027, 0x2027, 0x0028, 0x0c0b, 0x1028, 0x2028, 0x0029, 0x0d04, 0x1029, 0x2029, 0x002a, 0x0d05, 0x102a, 0x202a, 0x002b, 0x0d06, 0x102b, 0x202b, 0x002c, 0x0d07, 0x102c, 0x202c, 0x002d, 0x0d08, 0x102d, 0x202d, 0x002e, 0x0d09, 0x102e, 0x202e, 0x002f, 0x0d0a, 0x102f, 0x202f, 0x0030, 0x0d0b, 0x1030, 0x2030, 0x0031, 0x0e04, 0x1031, 0x2031, 0x0032, 0x0e05, 0x1032, 0x2032, 0x0033, 0x0e06, 0x1033, 0x2033, 0x0034, 0x0e07, 0x1034, 0x2034, 0x0035, 0x0e08, 0x1035, 0x2035, 0x0036, 0x0e09, 0x1036, 0x2036, 0x0037, 0x0e0a, 0x1037, 0x2037, 0x0038, 0x0e0b, 0x1038, 0x2038, 0x0039, 0x0f04, 0x1039, 0x2039, 0x003a, 0x0f05, 0x103a, 0x203a, 0x003b, 0x0f06, 0x103b, 0x203b, 0x003c, 0x0f07, 0x103c, 0x203c, 0x0801, 0x0f08, 0x103d, 0x203d, 0x1001, 0x0f09, 0x103e, 0x203e, 0x1801, 0x0f0a, 0x103f, 0x203f, 0x2001, 0x0f0b, 0x1040, 0x2040 }; static const size_t SCRATCH_SIZE = 16; void uncompress(const char* input, size_t inLen, char* output, size_t outLen) { const char* ip = input; const char* ipLimit = input + inLen - SCRATCH_SIZE; char* op = output;; char* opLimit = output + outLen; while (ip != ipLimit) { unsigned char c = *ip++; if ((c & 0x3) == LITERAL) { size_t literalLen = (c >> 2) + 1; size_t outLeft = opLimit - op; if (literalLen <= 16 && outLeft >= 16) { // Fast path, used for the majority (about 95%) // of invocations. unalignedCopy128(ip, op); op += literalLen; ip += literalLen; continue; } if (unlikely(literalLen >= 61)) { // Long literal. size_t literalLenLen = literalLen - 60; literalLen = loadNBytes(ip, unsigned(literalLenLen)) + 1; ip += literalLenLen; } memcpy(op, ip, literalLen); op += literalLen; ip += literalLen; } else { uint32_t entry = charTable[c]; uint32_t trailer = loadNBytes(ip, entry >> 11); uint32_t length = entry & 0xff; ip += entry >> 11; // offset/256 is encoded in bits 8..10. By just // fetching those bits, we get copyOffset (since the // bit-field starts at bit 8). size_t offset = (entry & 0x700) + trailer; size_t outLeft = opLimit - op; const char* src = op - offset; if (length <= 16 && offset >= 8 && outLeft >= 16) { // Fast path, used for the majority (70-80%) of // dynamic invocations. unalignedCopy128(src, op); } else { if (outLeft >= length + MAX_INCR_COPY_OVERFLOW) { incrementalCopyFast(src, op, length); } else { incrementalCopy(src, op, length); } } op += length; } } } ///////////////// // Any hash function will produce a valid compressed bitstream, but a good hash // function reduces the number of collisions and thus yields better compression // for compressible input, and more speed for incompressible input. Of course, // it doesn't hurt if the hash function is reasonably fast either, as it gets // called a lot. template static inline uint32_t hashBytes(uint32_t bytes) { return (bytes * 0x1e35a7bd) >> SHIFT; } template static inline uint32_t hash(const char* p) { return hashBytes(unalignedLoad32(p)); } // For 0 <= offset <= 4, getUint32AtOffset(getEightBytesAt(p), offset) will // equal unalignedLoad32(p + offset). Motivation: On x86-64 hardware we have // empirically found that overlapping loads such as // unalignedLoad32(p) ... unalignedLoad32(p+1) ... unalignedLoad32(p+2) // are slower than unalignedLoad64(p) followed by shifts and casts to uint32_t. // // We have different versions for 64- and 32-bit; ideally we would avoid the // two functions and just inline the unalignedLoad64 call into // getUint32AtOffset, but GCC (at least not as of 4.6) is seemingly not clever // enough to avoid loading the value multiple times then. For 64-bit, the load // is done when getEightBytesAt() is called, whereas for 32-bit, the load is // done at getUint32AtOffset() time. template class EightBytesReference; template<> class EightBytesReference<8> { public: void setAddress(const char* ptr) { data = unalignedLoad64(ptr); } template uint32_t getUint32AtOffset() const { static_assert((OFFSET >= 0) && (OFFSET <= 4), "must be in [0..4]"); int shift = OPENMSX_BIGENDIAN ? (32 - 8 * OFFSET) : ( 8 * OFFSET); return data >> shift; } private: uint64_t data; }; template<> class EightBytesReference<4> { public: void setAddress(const char* ptr_) { ptr = ptr_; } template uint32_t getUint32AtOffset() const { static_assert((OFFSET >= 0) && (OFFSET <= 4), "must be in [0..4]"); return unalignedLoad32(ptr + OFFSET); } private: const char* ptr; }; // Count the number of trailing zero bytes (=trailing zero bits divided by 8). // Returns an undefined value if n == 0. template static inline unsigned ctzDiv8(T n) { static const unsigned DIV = 8; #if (defined(__i386__) || defined(__x86_64__)) && \ ((__GNUC__ >= 4) || ((__GNUC__ == 3) && (__GNUC_MINOR >= 3))) // Gcc-3.4 or above have the following builtins. Though we only want to // use them if they are very fast. E.g. on x86 they compile to a single // 'bsf' instruction. If they have to be emulated in software then the // fallback code below is likely faster (because it skips part of the // full ctz calculation). // TODO on vc++ we could use // _BitScanForward and _BitScanForward64 // and possibly other compilers have something similar. if (sizeof(T) <= 4) { return __builtin_ctz(n) / DIV; } else { return __builtin_ctzll(n) / DIV; } #else // Classical ctz routine, but skip the last 3 (for DIV=8) iterations. unsigned bits = 8 * sizeof(T); unsigned r = (bits - 1) / DIV; for (unsigned shift = bits / 2; shift >= DIV; shift /= 2) { if (T x = n << shift) { n = x; r -= shift / DIV; } } return r; #endif } // Return the largest n such that // // s1[0,n-1] == s2[0,n-1] // and n <= (s2Limit - s2). // // Does not read *s2Limit or beyond. // Does not read *(s1 + (s2Limit - s2)) or beyond. // Requires that s2Limit >= s2. // // This implementation is tuned for 64-bit, little-endian machines. But it // also works fine on 32-bit and/or big-endian machines. // search either 4 or 8 bytes at-a-time template struct FindMatchUnit; template<> struct FindMatchUnit<4> { using type = uint32_t; }; template<> struct FindMatchUnit<8> { using type = uint64_t; }; static inline int findMatchLength(const char* s1, const char* s2, const char* s2Limit) { assert(s2Limit >= s2); int matched = 0; // Find out how long the match is. We loop over the data N bits at a // time until we find a N-bit block that doesn't match, Then (only on // little endian machines) we find the first non-matching bit and use // that to calculate the total length of the match. using T = FindMatchUnit::type; while (likely(s2 <= s2Limit - sizeof(T))) { T l2 = unalignedLoad(s2); T l1 = unalignedLoad(s1 + matched); if (unlikely(l2 == l1)) { s2 += sizeof(T); matched += sizeof(T); } else { if (OPENMSX_BIGENDIAN) break; // On current (mid-2008) Opteron models there is a 3% // more efficient code sequence to find the first // non-matching byte. However, what follows is ~10% // better on Intel Core 2 and newer, and we expect // AMD's bsf instruction to improve. return matched + ctzDiv8(l2 ^ l1); } } while ((s2 < s2Limit) && (s1[matched] == *s2)) { ++s2; ++matched; } return matched; } template static inline char* emitLiteral(char* op, const char* literal, int len) { int n = len - 1; // Zero-length literals are disallowed if (n < 60) { // Fits in tag byte *op++ = LITERAL | (n << 2); // The vast majority of copies are below 16 bytes, for which a // call to memcpy is overkill. This fast path can sometimes // copy up to 15 bytes too much, but that is okay in the // main loop, since we have a bit to go on for both sides: // // - The input will always have INPUT_MARGIN_BYTES = 15 extra // available bytes, as long as we're in the main loop, and // if not, ALLOW_FAST_PATH = false. // - The output will always have 32 spare bytes (see // maxCompressedLength()). if (ALLOW_FAST_PATH && len <= 16) { unalignedCopy128(literal, op); return op + len; } } else { // Encode in upcoming bytes char* base = op; op++; int count = 0; do { *op++ = n & 0xff; n >>= 8; count++; } while (n > 0); assert(count >= 1); assert(count <= 4); *base = LITERAL | ((59 + count) << 2); } memcpy(op, literal, len); return op + len; } static inline char* emitCopyLessThan64(char* op, size_t offset, int len) { assert(len <= 64); assert(len >= 4); assert(offset < 65536); if ((len < 12) && (offset < 2048)) { size_t lenMinus4 = len - 4; assert(lenMinus4 < 8); // Must fit in 3 bits *op++ = char(COPY_1_BYTE_OFFSET + ((lenMinus4) << 2) + ((offset >> 8) << 5)); *op++ = offset & 0xff; } else { *op++ = COPY_2_BYTE_OFFSET + ((len - 1) << 2); Endian::write_UA_L16(op, uint16_t(offset)); op += 2; } return op; } static inline char* emitCopy(char* op, size_t offset, int len) { // Emit 64 byte copies but make sure to keep at least four bytes reserved while (len >= 68) { op = emitCopyLessThan64(op, offset, 64); len -= 64; } // Emit an extra 60 byte copy if have too much data to fit in one copy if (len > 64) { op = emitCopyLessThan64(op, offset, 60); len -= 60; } // Emit remainder (at least 4 bytes) op = emitCopyLessThan64(op, offset, len); return op; } // The size of a compression block. Note that many parts of the compression // code assumes that kBlockSize <= 65536; in particular, the hash table // can only store 16-bit offsets, and EmitCopy() also assumes the offset // is 65535 bytes or less. static const size_t BLOCK_SIZE = 1 << 16; // Compresses "input" string to the "*op" buffer. // // REQUIRES: "input" is at most "BLOCK_SIZE" bytes long. // REQUIRES: "op" points to an array of memory that is at least // "maxCompressedLength(input.size())" in size. // // Returns an "end" pointer into "op" buffer. // "end - op" is the compressed size of "input". static char* compressFragment(const char* input, size_t inputSize, char* op) { static const int HASH_TABLE_BITS = 14; static const int SHIFT = 32 - HASH_TABLE_BITS; uint16_t table[1 << HASH_TABLE_BITS]; // 32KB memset(table, 0, sizeof(table)); // "ip" is the input pointer, and "op" is the output pointer. const char* ip = input; const char* ipEnd = input + inputSize; assert(inputSize <= BLOCK_SIZE); // Bytes in [nextEmit, ip) will be emitted as literal bytes. Or // [nextEmit, ipEnd) after the main loop. const char* nextEmit = ip; static const size_t INPUT_MARGIN_BYTES = 15; if (likely(inputSize >= INPUT_MARGIN_BYTES)) { const char* ipLimit = ipEnd - INPUT_MARGIN_BYTES; uint32_t nextHash = hash(++ip); while (true) { assert(nextEmit < ip); // The body of this loop calls emitLiteral() once and // then emitCopy one or more times. (The exception is // that when we're close to exhausting the input we // goto emitRemainder.) // // In the first iteration of this loop we're just // starting, so there's nothing to copy, so calling // emitLiteral() once is necessary. And we only start // a new iteration when the current iteration has // determined that a call to emitLiteral() will precede // the next call to emitCopy (if any). // // Step 1: Scan forward in the input looking for a // 4-byte-long match. If we get close to exhausting the // input then goto emitRemainder. // // Heuristic match skipping: If 32 bytes are scanned // with no matches found, start looking only at every // other byte. If 32 more bytes are scanned, look at // every third byte, etc.. When a match is found, // immediately go back to looking at every byte. This // is a small loss (~5% performance, ~0.1% density) for // compressible data due to more bookkeeping, but for // non-compressible data (such as JPEG) it's a huge win // since the compressor quickly "realizes" the data is // incompressible and doesn't bother looking for // matches everywhere. // // The "skip" variable keeps track of how many bytes // there are since the last match; dividing it by 32 // (ie. right-shifting by five) gives the number of // bytes to move ahead for each iteration. uint32_t skip = 32; const char* nextIp = ip; const char* candidate; do { ip = nextIp; uint32_t h = nextHash; assert(h == hash(ip)); uint32_t bytesBetweenHashLookups = skip++ >> 5; nextIp = ip + bytesBetweenHashLookups; if (unlikely(nextIp > ipLimit)) { goto emitRemainder; } nextHash = hash(nextIp); candidate = input + table[h]; assert(candidate >= input); assert(candidate < ip); table[h] = ip - input; } while (likely(unalignedLoad32(ip) != unalignedLoad32(candidate))); // Step 2: A 4-byte match has been found. We'll later // see if more than 4 bytes match. But, prior to the // match, input bytes [nextEmit, ip) are unmatched. // Emit them as "literal bytes." assert(nextEmit + 16 <= ipEnd); op = emitLiteral(op, nextEmit, ip - nextEmit); // Step 3: Call emitCopy, and then see if another // emitCopy could be our next move. Repeat until we // find no match for the input immediately after what // was consumed by the last emitCopy call. // // If we exit this loop normally then we need to call // emitLiteral() next, though we don't yet know how big // the literal will be. We handle that by proceeding // to the next iteration of the main loop. We also can // exit this loop via goto if we get close to // exhausting the input. EightBytesReference inputBytes; uint32_t candidateBytes = 0; do { // We have a 4-byte match at ip, and no need to // emit any "literal bytes" prior to ip. int matched = 4 + findMatchLength(candidate + 4, ip + 4, ipEnd); size_t offset = ip - candidate; assert(0 == memcmp(ip, candidate, matched)); ip += matched; op = emitCopy(op, offset, matched); // We could immediately start working at ip // now, but to improve compression we first // update table[hash(ip - 1, ...)]. const char* insertTail = ip - 1; nextEmit = ip; if (unlikely(ip >= ipLimit)) { goto emitRemainder; } inputBytes.setAddress(insertTail); uint32_t prevHash = hashBytes(inputBytes.getUint32AtOffset<0>()); table[prevHash] = ip - input - 1; uint32_t curHash = hashBytes(inputBytes.getUint32AtOffset<1>()); candidate = input + table[curHash]; candidateBytes = unalignedLoad32(candidate); table[curHash] = ip - input; } while (inputBytes.getUint32AtOffset<1>() == candidateBytes); nextHash = hashBytes(inputBytes.getUint32AtOffset<2>()); ++ip; } } emitRemainder: // Emit the remaining bytes as a literal if (nextEmit < ipEnd) { op = emitLiteral(op, nextEmit, ipEnd - nextEmit); } return op; } void compress(const char* input, size_t inLen, char* output, size_t& outLen) { char* out = output; while (inLen > 0) { size_t numToRead = std::min(inLen, BLOCK_SIZE); out = compressFragment(input, numToRead, out); inLen -= numToRead; input += numToRead; } outLen = out - output + SCRATCH_SIZE; } size_t maxCompressedLength(size_t inLen) { return 32 + SCRATCH_SIZE + inLen + inLen / 6; } } openMSX-RELEASE_0_12_0/src/utils/snappy.hh000066400000000000000000000066201257557151200202250ustar00rootroot00000000000000///////////////////////////////////////////////////////////////////////// // // Copyright 2005 and onwards Google Inc. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // A light-weight compression algorithm. It is designed for speed of // compression and decompression, rather than for the utmost in space // savings. // // For getting better compression ratios when you are compressing data // with long repeated sequences or compressing data that is similar to // other data, while still compressing fast, you might look at first // using BMDiff and then compressing the output of BMDiff with // Snappy. // ///////////////////////////////////////////////////////////////////////// // // The snappy code is modified quite a bit for openMSX. The original // verion can be obtained here: // http://code.google.com/p/snappy/ // // The main difference are: // - Rewritten to reuse existing openMSX helper functions and style. // - Removed possibility to operate on chunks of data, the current code // requires the full to-be-(de)compressed memory block in one go. // - Removed all safety checks during decompression. The original code // would return an error on invalid input, this code will crash on // such input (but that shouldn't happen because we only feed input // that was previously produced by the compression routine (and always // keeping that compressed block in memory). // The motivation for this rewrite is to: // - Reduce code duplication between the snappy code and the rest of // openMSX. // - Gain some extra speed at the expense of flexibility (which we don't // need in openMSX). // ///////////////////////////////////////////////////////////////////////// #ifndef SNAPPY_HH #define SNAPPY_HH #include namespace snappy { void compress(const char* input, size_t inLen, char* output, size_t& outLen); void uncompress(const char* input, size_t inLen, char* output, size_t outLen); size_t maxCompressedLength(size_t inLen); } #endif openMSX-RELEASE_0_12_0/src/utils/statp.hh000066400000000000000000000003031257557151200200360ustar00rootroot00000000000000#ifndef STATP_HH #define STATP_HH #include #ifdef _MSC_VER #define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) #define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) #endif #endif openMSX-RELEASE_0_12_0/src/utils/stl.hh000066400000000000000000000112201257557151200175050ustar00rootroot00000000000000#ifndef STL_HH #define STL_HH #include #include #include #include // Dereference the two given (pointer-like) parameters and then compare // them with the less-than operator. struct LessDeref { template bool operator()(PTR p1, PTR p2) const { return *p1 < *p2; } }; // C++14 has an std::equal_to functor that is very much like this version. // TODO remove this version once we switch to C++14. struct EqualTo { template bool operator()(const T1& t1, const T2& t2) const { return t1 == t2; } }; // Note: LessTupleElement and CmpTupleElement can be made a lot more general // (and uniform). This can be done relatively easily with variadic templates. // Unfortunately vc++ doesn't support this yet. So for now these classes are // 'just enough' to make the current users of these utilities happy. // Compare the N-th element of a tuple using the less-than operator. Also // provides overloads to compare the N-the element with a single value of the // same type. template struct LessTupleElement { template bool operator()(const TUPLE& x, const TUPLE& y) const { return std::get(x) < std::get(y); } template bool operator()(const typename std::tuple_element::type& x, const TUPLE& y) const { return x < std::get(y); } template bool operator()(const TUPLE& x, const typename std::tuple_element::type& y) const { return std::get(x) < y; } }; // Similar to LessTupleElement, but with a custom comparison functor. ATM the // functor cannot take constructor arguments, possibly refactor this in the // future. template struct CmpTupleElement { template bool operator()(const std::pair& x, const std::pair& y) const { return cmp(std::get(x), std::get(y)); } template bool operator()(const T& x, const std::pair& y) const { return cmp(x, std::get(y)); } template bool operator()(const std::pair& x, const T& y) const { return cmp(std::get(x), y); } private: CMP cmp; }; // Check whether the N-the element of a tuple is equal to the given value. template struct EqualTupleValueImpl { EqualTupleValueImpl(const T& t_) : t(t_) {} template bool operator()(const TUPLE& tup) const { return std::get(tup) == t; } private: const T& t; }; template EqualTupleValueImpl EqualTupleValue(const T& t) { return EqualTupleValueImpl(t); } /** Check if a range contains a given value, using linear search. * Equivalent to 'find(first, last, val) != last', though this algorithm * is more convenient to use. * Note: we don't need a variant that uses 'find_if' instead of 'find' because * STL already has the 'any_of' algorithm. */ template inline bool contains(ITER first, ITER last, const VAL& val) { return std::find(first, last, val) != last; } template inline bool contains(const RANGE& range, const VAL& val) { return contains(std::begin(range), std::end(range), val); } /** Faster alternative to 'find' when it's guaranteed that the value will be * found (if not the behavior is undefined). * When asserts are enabled we check whether we really don't move beyond the * end of the range. And this check is the only reason why you need to pass * the 'last' parameter. Sometimes you see 'find_unguarded' without a 'last' * parameter, we could consider providing such an overload as well. */ template inline ITER find_unguarded(ITER first, ITER last, const VAL& val) { (void)last; while (1) { assert(first != last); if (*first == val) return first; ++first; } } template inline auto find_unguarded(RANGE& range, const VAL& val) -> decltype(std::begin(range)) { return find_unguarded(std::begin(range), std::end(range), val); } /** Faster alternative to 'find_if' when it's guaranteed that the predicate * will be true for at least one element in the given range. * See also 'find_unguarded'. */ template inline ITER find_if_unguarded(ITER first, ITER last, PRED pred) { (void)last; while (1) { assert(first != last); if (pred(*first)) return first; ++first; } } template inline auto find_if_unguarded(RANGE& range, PRED pred) -> decltype(std::begin(range)) { return find_if_unguarded(std::begin(range), std::end(range), pred); } #endif openMSX-RELEASE_0_12_0/src/utils/string_ref.cc000066400000000000000000000106511257557151200210420ustar00rootroot00000000000000#include "string_ref.hh" #include "likely.hh" #include #include #include #include using std::string; // Outgoing conversion operators string string_ref::str() const { return siz ? string(dat, siz) : string(); } // string operations with the same semantics as std::string int string_ref::compare(string_ref rhs) const { // Check prefix. if (int r = memcmp(dat, rhs.dat, std::min(siz, rhs.siz))) { return r; } // Prefixes match, check length. return int(siz - rhs.siz); // Note: this overflows for very large strings. } string_ref string_ref::substr(size_type pos, size_type n) const { if (pos >= siz) return string_ref(); return string_ref(dat + pos, std::min(n, siz - pos)); } string_ref::size_type string_ref::find(string_ref s) const { // Simple string search algorithm O(size() * s.size()). An algorithm // like Boyer–Moore has better time complexity and will run a lot // faster on large strings. Though when the strings are relatively // short (the typically case?) this very simple algorithm may run // faster (because it has no setup-time). The implementation of // std::string::find() in gcc uses a similar simple algorithm. if (s.empty()) return 0; if (s.size() <= siz) { auto m = siz - s.size(); for (size_type pos = 0; pos <= m; ++pos) { if ((dat[pos] == s[0]) && std::equal(s.begin() + 1, s.end(), dat + pos + 1)) { return pos; } } } return npos; } string_ref::size_type string_ref::find(char c) const { auto it = std::find(begin(), end(), c); return (it == end()) ? npos : it - begin(); } string_ref::size_type string_ref::rfind(string_ref s) const { // see comment in find() if (s.empty()) return siz; if (s.size() <= siz) { auto m = siz - s.size(); for (auto pos = m; pos != size_type(-1); --pos) { if ((dat[pos] == s[0]) && std::equal(s.begin() + 1, s.end(), dat + pos + 1)) { return pos; } } } return npos; } string_ref::size_type string_ref::rfind(char c) const { auto it = std::find(rbegin(), rend(), c); return (it == rend()) ? npos : (it.base() - begin() - 1); } string_ref::size_type string_ref::find_first_of(string_ref s) const { auto it = std::find_first_of(begin(), end(), s.begin(), s.end()); return (it == end()) ? npos : it - begin(); } string_ref::size_type string_ref::find_first_of(char c) const { return find(c); } //string_ref::size_type string_ref::find_first_not_of(string_ref s) const; //string_ref::size_type string_ref::find_first_not_of(char c) const; string_ref::size_type string_ref::find_last_of(string_ref s) const { auto it = std::find_first_of(rbegin(), rend(), s.begin(), s.end()); return (it == rend()) ? npos : (it.base() - begin() - 1); } string_ref::size_type string_ref::find_last_of(char c) const { return rfind(c); } //string_ref::size_type string_ref::find_last_not_of(string_ref s) const; //string_ref::size_type string_ref::find_last_not_of(char c) const; // new string operations (not part of std::string) bool string_ref::starts_with(string_ref x) const { return (siz >= x.size()) && (memcmp(dat, x.data(), x.size()) == 0); } bool string_ref::starts_with(char x) const { return !empty() && (front() == x); } bool string_ref::ends_with(string_ref x) const { return (siz >= x.size()) && (memcmp(dat + siz - x.size(), x.data(), x.size()) == 0); } bool string_ref::ends_with(char x) const { return !empty() && (back() == x); } // Comparison operators bool operator< (string_ref x, string_ref y) { return x.compare(y) < 0; } // numeric conversions unsigned fast_stou(string_ref str) { unsigned result = 0; for (char c : str) { unsigned d = c - '0'; if (unlikely(d > 9)) { throw std::invalid_argument("fast_stoi"); } result *= 10; result += d; } return result; } // concatenation // TODO make s1 + s2 + s3 also efficient string operator+(string_ref x, string_ref y) { string result; result.reserve(x.size() + y.size()); result.append(x.data(), x.size()); result.append(y.data(), y.size()); return result; } std::string operator+(char x, string_ref y) { string result; result.reserve(1 + y.size()); result.append(&x, 1); result.append(y.data(), y.size()); return result; } std::string operator+(string_ref x, char y) { string result; result.reserve(x.size() + 1); result.append(x.data(), x.size()); result.append(&y, 1); return result; } std::ostream& operator<<(std::ostream& os, string_ref str) { os.write(str.data(), str.size()); return os; } openMSX-RELEASE_0_12_0/src/utils/string_ref.hh000066400000000000000000000132511257557151200210530ustar00rootroot00000000000000#ifndef STRING_REF_HH #define STRING_REF_HH #include #include #include #include #include /** This class implements a subset of the proposal for std::string_ref * (proposed for the next c++ standard (c++1y)). * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3334.html#classstd_1_1basic__string__ref_1ab23a4885309a116e8e67349fe0950290 * * It has an interface that is close to std::string, but it does not own * the memory for the string. Basically it's just a wrapper around: * const char* + length. */ class string_ref { public: using size_type = size_t; using difference_type = std::ptrdiff_t; using const_iterator = const char*; using const_reverse_iterator = std::reverse_iterator; static const size_type npos = size_type(-1); // construct/copy/assign string_ref() : dat(nullptr), siz(0) {} string_ref(const string_ref& str) : dat(str.dat), siz(str.siz) {} string_ref(const char* str) : dat(str), siz(str ? size_type(strlen(str)) : 0) {} string_ref(const char* str, size_type len) : dat(str), siz(len) { if (!dat) assert(siz == 0); } string_ref(const char* begin, const char* end) : dat(begin), siz(end - begin) { if (!dat) assert(siz == 0); } string_ref(const std::string& str) : dat(str.data()), siz(str.size()) {} string_ref& operator=(const string_ref& rhs) { dat = rhs.data(); siz = rhs.size(); return *this; } // iterators const_iterator begin() const { return dat; } const_iterator end() const { return dat + siz; } const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } // capacity size_type size() const { return siz; } bool empty() const { return siz == 0; } //size_type max_size() const; //size_type length() const; // element access char operator[](size_type i) const { assert(i < siz); return dat[i]; } //const char& at(size_type i) const; char front() const { return *dat; } char back() const { return *(dat + siz - 1); } const char* data() const { return dat; } // Outgoing conversion operators //explicit operator std::string() const; // c++11 std::string str() const; // mutators void clear() { siz = 0; } // no need to change 'dat' void remove_prefix(size_type n) { if (n <= siz) { dat += n; siz -= n; } else { clear(); } } void remove_suffix(size_type n) { if (n <= siz) { siz -= n; } else { clear(); } } void pop_back() { remove_suffix(1); } void pop_front() { remove_prefix(1); } // string operations with the same semantics as std::string int compare(string_ref x) const; string_ref substr(size_type pos, size_type n = npos) const; //size_type copy(char* buf) const; size_type find(string_ref s) const; size_type find(char c) const; size_type rfind(string_ref s) const; size_type rfind(char c) const; size_type find_first_of(string_ref s) const; size_type find_first_of(char c) const; //size_type find_first_not_of(string_ref s) const; //size_type find_first_not_of(char c) const; size_type find_last_of(string_ref s) const; size_type find_last_of(char c) const; //size_type find_last_not_of(string_ref s) const; //size_type find_last_not_of(char c) const; // new string operations (not part of std::string) bool starts_with(string_ref x) const; bool starts_with(char x) const; bool ends_with(string_ref x) const; bool ends_with(char x) const; private: const char* dat; size_type siz; }; // Comparison operators inline bool operator==(string_ref x, string_ref y) { return (x.size() == y.size()) && (memcmp(x.data(), y.data(), x.size()) == 0); } bool operator< (string_ref x, string_ref y); inline bool operator!=(string_ref x, string_ref y) { return !(x == y); } inline bool operator> (string_ref x, string_ref y) { return (y < x); } inline bool operator<=(string_ref x, string_ref y) { return !(y < x); } inline bool operator>=(string_ref x, string_ref y) { return !(x < y); } // numeric conversions //int stoi (string_ref str, string_ref::size_type* idx = nullptr, int base = 0); //long stol (string_ref str, string_ref::size_type* idx = nullptr, int base = 0); //unsigned long stoul (string_ref str, string_ref::size_type* idx = nullptr, int base = 0); //long long stoll (string_ref str, string_ref::size_type* idx = nullptr, int base = 0); //unsigned long long stoull(string_ref str, string_ref::size_type* idx = nullptr, int base = 0); //float stof (string_ref str, string_ref::size_type* idx = nullptr); //double stod (string_ref str, string_ref::size_type* idx = nullptr); //long double stold (string_ref str, string_ref::size_type* idx = nullptr); // Faster than the above, but less general (not part of the std proposal): // - Only handles decimal. // - No leading + or - sign (and thus only positive values). // - No leading whitespace. // - No trailing non-digit characters. // - No out-of-range check (so undetected overflow on e.g. 9999999999). // - Empty string parses as zero. // Throws std::invalid_argument if any character is different from [0-9], // similar to the error reporting in the std::stoi() (and related) functions. unsigned fast_stou(string_ref str); // concatenation (this is not part of the std::string_ref proposal) std::string operator+(string_ref x, string_ref y); std::string operator+(char x, string_ref y); std::string operator+(string_ref x, char y); std::ostream& operator<<(std::ostream& os, string_ref str); // begin, end inline string_ref::const_iterator begin(const string_ref& x) { return x.begin(); } inline string_ref::const_iterator end (const string_ref& x) { return x.end(); } #endif openMSX-RELEASE_0_12_0/src/utils/stringsp.hh000066400000000000000000000002651257557151200205630ustar00rootroot00000000000000#ifndef STRINGSP_HH #define STRINGSP_HH #ifndef _MSC_VER #include #else #include #define strcasecmp _stricmp #define strncasecmp _strnicmp #endif #endif openMSX-RELEASE_0_12_0/src/utils/tiger.cc000066400000000000000000001024431257557151200200130ustar00rootroot00000000000000#include "tiger.hh" #include "endian.hh" #include "build-info.hh" #include #include namespace openmsx { std::string TigerHash::toString() const { static const char* const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; std::string result; const uint8_t* src = h8; const uint8_t* end = src + 24; unsigned bit = 0; uint8_t tmp; while (src != end) { if (bit > 3) { tmp = *src & (0xFF >> bit); bit = (bit + 5) % 8; tmp <<= bit; ++src; if (src != end) { tmp |= *src >> (8 - bit); } } else { tmp = (*src >> (3 - bit)) & 0x1F; bit = (bit + 5) % 8; if (bit == 0) ++src; } assert(tmp < 32); result += chars[tmp]; } return result; } // Tiger S boxes static const uint64_t table[4 * 256] = { 0x02AAB17CF7E90C5EULL, 0xAC424B03E243A8ECULL, // 0 0x72CD5BE30DD5FCD3ULL, 0x6D019B93F6F97F3AULL, // 2 0xCD9978FFD21F9193ULL, 0x7573A1C9708029E2ULL, // 4 0xB164326B922A83C3ULL, 0x46883EEE04915870ULL, // 6 0xEAACE3057103ECE6ULL, 0xC54169B808A3535CULL, // 8 0x4CE754918DDEC47CULL, 0x0AA2F4DFDC0DF40CULL, // 10 0x10B76F18A74DBEFAULL, 0xC6CCB6235AD1AB6AULL, // 12 0x13726121572FE2FFULL, 0x1A488C6F199D921EULL, // 14 0x4BC9F9F4DA0007CAULL, 0x26F5E6F6E85241C7ULL, // 16 0x859079DBEA5947B6ULL, 0x4F1885C5C99E8C92ULL, // 18 0xD78E761EA96F864BULL, 0x8E36428C52B5C17DULL, // 20 0x69CF6827373063C1ULL, 0xB607C93D9BB4C56EULL, // 22 0x7D820E760E76B5EAULL, 0x645C9CC6F07FDC42ULL, // 24 0xBF38A078243342E0ULL, 0x5F6B343C9D2E7D04ULL, // 26 0xF2C28AEB600B0EC6ULL, 0x6C0ED85F7254BCACULL, // 28 0x71592281A4DB4FE5ULL, 0x1967FA69CE0FED9FULL, // 30 0xFD5293F8B96545DBULL, 0xC879E9D7F2A7600BULL, // 32 0x860248920193194EULL, 0xA4F9533B2D9CC0B3ULL, // 34 0x9053836C15957613ULL, 0xDB6DCF8AFC357BF1ULL, // 36 0x18BEEA7A7A370F57ULL, 0x037117CA50B99066ULL, // 38 0x6AB30A9774424A35ULL, 0xF4E92F02E325249BULL, // 40 0x7739DB07061CCAE1ULL, 0xD8F3B49CECA42A05ULL, // 42 0xBD56BE3F51382F73ULL, 0x45FAED5843B0BB28ULL, // 44 0x1C813D5C11BF1F83ULL, 0x8AF0E4B6D75FA169ULL, // 46 0x33EE18A487AD9999ULL, 0x3C26E8EAB1C94410ULL, // 48 0xB510102BC0A822F9ULL, 0x141EEF310CE6123BULL, // 50 0xFC65B90059DDB154ULL, 0xE0158640C5E0E607ULL, // 52 0x884E079826C3A3CFULL, 0x930D0D9523C535FDULL, // 54 0x35638D754E9A2B00ULL, 0x4085FCCF40469DD5ULL, // 56 0xC4B17AD28BE23A4CULL, 0xCAB2F0FC6A3E6A2EULL, // 58 0x2860971A6B943FCDULL, 0x3DDE6EE212E30446ULL, // 60 0x6222F32AE01765AEULL, 0x5D550BB5478308FEULL, // 62 0xA9EFA98DA0EDA22AULL, 0xC351A71686C40DA7ULL, // 64 0x1105586D9C867C84ULL, 0xDCFFEE85FDA22853ULL, // 66 0xCCFBD0262C5EEF76ULL, 0xBAF294CB8990D201ULL, // 68 0xE69464F52AFAD975ULL, 0x94B013AFDF133E14ULL, // 70 0x06A7D1A32823C958ULL, 0x6F95FE5130F61119ULL, // 72 0xD92AB34E462C06C0ULL, 0xED7BDE33887C71D2ULL, // 74 0x79746D6E6518393EULL, 0x5BA419385D713329ULL, // 76 0x7C1BA6B948A97564ULL, 0x31987C197BFDAC67ULL, // 78 0xDE6C23C44B053D02ULL, 0x581C49FED002D64DULL, // 80 0xDD474D6338261571ULL, 0xAA4546C3E473D062ULL, // 82 0x928FCE349455F860ULL, 0x48161BBACAAB94D9ULL, // 84 0x63912430770E6F68ULL, 0x6EC8A5E602C6641CULL, // 86 0x87282515337DDD2BULL, 0x2CDA6B42034B701BULL, // 88 0xB03D37C181CB096DULL, 0xE108438266C71C6FULL, // 90 0x2B3180C7EB51B255ULL, 0xDF92B82F96C08BBCULL, // 92 0x5C68C8C0A632F3BAULL, 0x5504CC861C3D0556ULL, // 94 0xABBFA4E55FB26B8FULL, 0x41848B0AB3BACEB4ULL, // 96 0xB334A273AA445D32ULL, 0xBCA696F0A85AD881ULL, // 98 0x24F6EC65B528D56CULL, 0x0CE1512E90F4524AULL, // 100 0x4E9DD79D5506D35AULL, 0x258905FAC6CE9779ULL, // 102 0x2019295B3E109B33ULL, 0xF8A9478B73A054CCULL, // 104 0x2924F2F934417EB0ULL, 0x3993357D536D1BC4ULL, // 106 0x38A81AC21DB6FF8BULL, 0x47C4FBF17D6016BFULL, // 108 0x1E0FAADD7667E3F5ULL, 0x7ABCFF62938BEB96ULL, // 110 0xA78DAD948FC179C9ULL, 0x8F1F98B72911E50DULL, // 112 0x61E48EAE27121A91ULL, 0x4D62F7AD31859808ULL, // 114 0xECEBA345EF5CEAEBULL, 0xF5CEB25EBC9684CEULL, // 116 0xF633E20CB7F76221ULL, 0xA32CDF06AB8293E4ULL, // 118 0x985A202CA5EE2CA4ULL, 0xCF0B8447CC8A8FB1ULL, // 120 0x9F765244979859A3ULL, 0xA8D516B1A1240017ULL, // 122 0x0BD7BA3EBB5DC726ULL, 0xE54BCA55B86ADB39ULL, // 124 0x1D7A3AFD6C478063ULL, 0x519EC608E7669EDDULL, // 126 0x0E5715A2D149AA23ULL, 0x177D4571848FF194ULL, // 128 0xEEB55F3241014C22ULL, 0x0F5E5CA13A6E2EC2ULL, // 130 0x8029927B75F5C361ULL, 0xAD139FABC3D6E436ULL, // 132 0x0D5DF1A94CCF402FULL, 0x3E8BD948BEA5DFC8ULL, // 134 0xA5A0D357BD3FF77EULL, 0xA2D12E251F74F645ULL, // 136 0x66FD9E525E81A082ULL, 0x2E0C90CE7F687A49ULL, // 138 0xC2E8BCBEBA973BC5ULL, 0x000001BCE509745FULL, // 140 0x423777BBE6DAB3D6ULL, 0xD1661C7EAEF06EB5ULL, // 142 0xA1781F354DAACFD8ULL, 0x2D11284A2B16AFFCULL, // 144 0xF1FC4F67FA891D1FULL, 0x73ECC25DCB920ADAULL, // 146 0xAE610C22C2A12651ULL, 0x96E0A810D356B78AULL, // 148 0x5A9A381F2FE7870FULL, 0xD5AD62EDE94E5530ULL, // 150 0xD225E5E8368D1427ULL, 0x65977B70C7AF4631ULL, // 152 0x99F889B2DE39D74FULL, 0x233F30BF54E1D143ULL, // 154 0x9A9675D3D9A63C97ULL, 0x5470554FF334F9A8ULL, // 156 0x166ACB744A4F5688ULL, 0x70C74CAAB2E4AEADULL, // 158 0xF0D091646F294D12ULL, 0x57B82A89684031D1ULL, // 160 0xEFD95A5A61BE0B6BULL, 0x2FBD12E969F2F29AULL, // 162 0x9BD37013FEFF9FE8ULL, 0x3F9B0404D6085A06ULL, // 164 0x4940C1F3166CFE15ULL, 0x09542C4DCDF3DEFBULL, // 166 0xB4C5218385CD5CE3ULL, 0xC935B7DC4462A641ULL, // 168 0x3417F8A68ED3B63FULL, 0xB80959295B215B40ULL, // 170 0xF99CDAEF3B8C8572ULL, 0x018C0614F8FCB95DULL, // 172 0x1B14ACCD1A3ACDF3ULL, 0x84D471F200BB732DULL, // 174 0xC1A3110E95E8DA16ULL, 0x430A7220BF1A82B8ULL, // 176 0xB77E090D39DF210EULL, 0x5EF4BD9F3CD05E9DULL, // 178 0x9D4FF6DA7E57A444ULL, 0xDA1D60E183D4A5F8ULL, // 180 0xB287C38417998E47ULL, 0xFE3EDC121BB31886ULL, // 182 0xC7FE3CCC980CCBEFULL, 0xE46FB590189BFD03ULL, // 184 0x3732FD469A4C57DCULL, 0x7EF700A07CF1AD65ULL, // 186 0x59C64468A31D8859ULL, 0x762FB0B4D45B61F6ULL, // 188 0x155BAED099047718ULL, 0x68755E4C3D50BAA6ULL, // 190 0xE9214E7F22D8B4DFULL, 0x2ADDBF532EAC95F4ULL, // 192 0x32AE3909B4BD0109ULL, 0x834DF537B08E3450ULL, // 194 0xFA209DA84220728DULL, 0x9E691D9B9EFE23F7ULL, // 196 0x0446D288C4AE8D7FULL, 0x7B4CC524E169785BULL, // 198 0x21D87F0135CA1385ULL, 0xCEBB400F137B8AA5ULL, // 200 0x272E2B66580796BEULL, 0x3612264125C2B0DEULL, // 202 0x057702BDAD1EFBB2ULL, 0xD4BABB8EACF84BE9ULL, // 204 0x91583139641BC67BULL, 0x8BDC2DE08036E024ULL, // 206 0x603C8156F49F68EDULL, 0xF7D236F7DBEF5111ULL, // 208 0x9727C4598AD21E80ULL, 0xA08A0896670A5FD7ULL, // 210 0xCB4A8F4309EBA9CBULL, 0x81AF564B0F7036A1ULL, // 212 0xC0B99AA778199ABDULL, 0x959F1EC83FC8E952ULL, // 214 0x8C505077794A81B9ULL, 0x3ACAAF8F056338F0ULL, // 216 0x07B43F50627A6778ULL, 0x4A44AB49F5ECCC77ULL, // 218 0x3BC3D6E4B679EE98ULL, 0x9CC0D4D1CF14108CULL, // 220 0x4406C00B206BC8A0ULL, 0x82A18854C8D72D89ULL, // 222 0x67E366B35C3C432CULL, 0xB923DD61102B37F2ULL, // 224 0x56AB2779D884271DULL, 0xBE83E1B0FF1525AFULL, // 226 0xFB7C65D4217E49A9ULL, 0x6BDBE0E76D48E7D4ULL, // 228 0x08DF828745D9179EULL, 0x22EA6A9ADD53BD34ULL, // 230 0xE36E141C5622200AULL, 0x7F805D1B8CB750EEULL, // 232 0xAFE5C7A59F58E837ULL, 0xE27F996A4FB1C23CULL, // 234 0xD3867DFB0775F0D0ULL, 0xD0E673DE6E88891AULL, // 236 0x123AEB9EAFB86C25ULL, 0x30F1D5D5C145B895ULL, // 238 0xBB434A2DEE7269E7ULL, 0x78CB67ECF931FA38ULL, // 240 0xF33B0372323BBF9CULL, 0x52D66336FB279C74ULL, // 242 0x505F33AC0AFB4EAAULL, 0xE8A5CD99A2CCE187ULL, // 244 0x534974801E2D30BBULL, 0x8D2D5711D5876D90ULL, // 246 0x1F1A412891BC038EULL, 0xD6E2E71D82E56648ULL, // 248 0x74036C3A497732B7ULL, 0x89B67ED96361F5ABULL, // 250 0xFFED95D8F1EA02A2ULL, 0xE72B3BD61464D43DULL, // 252 0xA6300F170BDC4820ULL, 0xEBC18760ED78A77AULL, // 254 0xE6A6BE5A05A12138ULL, 0xB5A122A5B4F87C98ULL, // 256 0x563C6089140B6990ULL, 0x4C46CB2E391F5DD5ULL, // 258 0xD932ADDBC9B79434ULL, 0x08EA70E42015AFF5ULL, // 260 0xD765A6673E478CF1ULL, 0xC4FB757EAB278D99ULL, // 262 0xDF11C6862D6E0692ULL, 0xDDEB84F10D7F3B16ULL, // 264 0x6F2EF604A665EA04ULL, 0x4A8E0F0FF0E0DFB3ULL, // 266 0xA5EDEEF83DBCBA51ULL, 0xFC4F0A2A0EA4371EULL, // 268 0xE83E1DA85CB38429ULL, 0xDC8FF882BA1B1CE2ULL, // 270 0xCD45505E8353E80DULL, 0x18D19A00D4DB0717ULL, // 272 0x34A0CFEDA5F38101ULL, 0x0BE77E518887CAF2ULL, // 274 0x1E341438B3C45136ULL, 0xE05797F49089CCF9ULL, // 276 0xFFD23F9DF2591D14ULL, 0x543DDA228595C5CDULL, // 278 0x661F81FD99052A33ULL, 0x8736E641DB0F7B76ULL, // 280 0x15227725418E5307ULL, 0xE25F7F46162EB2FAULL, // 282 0x48A8B2126C13D9FEULL, 0xAFDC541792E76EEAULL, // 284 0x03D912BFC6D1898FULL, 0x31B1AAFA1B83F51BULL, // 286 0xF1AC2796E42AB7D9ULL, 0x40A3A7D7FCD2EBACULL, // 288 0x1056136D0AFBBCC5ULL, 0x7889E1DD9A6D0C85ULL, // 290 0xD33525782A7974AAULL, 0xA7E25D09078AC09BULL, // 292 0xBD4138B3EAC6EDD0ULL, 0x920ABFBE71EB9E70ULL, // 294 0xA2A5D0F54FC2625CULL, 0xC054E36B0B1290A3ULL, // 296 0xF6DD59FF62FE932BULL, 0x3537354511A8AC7DULL, // 298 0xCA845E9172FADCD4ULL, 0x84F82B60329D20DCULL, // 300 0x79C62CE1CD672F18ULL, 0x8B09A2ADD124642CULL, // 302 0xD0C1E96A19D9E726ULL, 0x5A786A9B4BA9500CULL, // 304 0x0E020336634C43F3ULL, 0xC17B474AEB66D822ULL, // 306 0x6A731AE3EC9BAAC2ULL, 0x8226667AE0840258ULL, // 308 0x67D4567691CAECA5ULL, 0x1D94155C4875ADB5ULL, // 310 0x6D00FD985B813FDFULL, 0x51286EFCB774CD06ULL, // 312 0x5E8834471FA744AFULL, 0xF72CA0AEE761AE2EULL, // 314 0xBE40E4CDAEE8E09AULL, 0xE9970BBB5118F665ULL, // 316 0x726E4BEB33DF1964ULL, 0x703B000729199762ULL, // 318 0x4631D816F5EF30A7ULL, 0xB880B5B51504A6BEULL, // 320 0x641793C37ED84B6CULL, 0x7B21ED77F6E97D96ULL, // 322 0x776306312EF96B73ULL, 0xAE528948E86FF3F4ULL, // 324 0x53DBD7F286A3F8F8ULL, 0x16CADCE74CFC1063ULL, // 326 0x005C19BDFA52C6DDULL, 0x68868F5D64D46AD3ULL, // 328 0x3A9D512CCF1E186AULL, 0x367E62C2385660AEULL, // 330 0xE359E7EA77DCB1D7ULL, 0x526C0773749ABE6EULL, // 332 0x735AE5F9D09F734BULL, 0x493FC7CC8A558BA8ULL, // 334 0xB0B9C1533041AB45ULL, 0x321958BA470A59BDULL, // 336 0x852DB00B5F46C393ULL, 0x91209B2BD336B0E5ULL, // 338 0x6E604F7D659EF19FULL, 0xB99A8AE2782CCB24ULL, // 340 0xCCF52AB6C814C4C7ULL, 0x4727D9AFBE11727BULL, // 342 0x7E950D0C0121B34DULL, 0x756F435670AD471FULL, // 344 0xF5ADD442615A6849ULL, 0x4E87E09980B9957AULL, // 346 0x2ACFA1DF50AEE355ULL, 0xD898263AFD2FD556ULL, // 348 0xC8F4924DD80C8FD6ULL, 0xCF99CA3D754A173AULL, // 350 0xFE477BACAF91BF3CULL, 0xED5371F6D690C12DULL, // 352 0x831A5C285E687094ULL, 0xC5D3C90A3708A0A4ULL, // 354 0x0F7F903717D06580ULL, 0x19F9BB13B8FDF27FULL, // 356 0xB1BD6F1B4D502843ULL, 0x1C761BA38FFF4012ULL, // 358 0x0D1530C4E2E21F3BULL, 0x8943CE69A7372C8AULL, // 360 0xE5184E11FEB5CE66ULL, 0x618BDB80BD736621ULL, // 362 0x7D29BAD68B574D0BULL, 0x81BB613E25E6FE5BULL, // 364 0x071C9C10BC07913FULL, 0xC7BEEB7909AC2D97ULL, // 366 0xC3E58D353BC5D757ULL, 0xEB017892F38F61E8ULL, // 368 0xD4EFFB9C9B1CC21AULL, 0x99727D26F494F7ABULL, // 370 0xA3E063A2956B3E03ULL, 0x9D4A8B9A4AA09C30ULL, // 372 0x3F6AB7D500090FB4ULL, 0x9CC0F2A057268AC0ULL, // 374 0x3DEE9D2DEDBF42D1ULL, 0x330F49C87960A972ULL, // 376 0xC6B2720287421B41ULL, 0x0AC59EC07C00369CULL, // 378 0xEF4EAC49CB353425ULL, 0xF450244EEF0129D8ULL, // 380 0x8ACC46E5CAF4DEB6ULL, 0x2FFEAB63989263F7ULL, // 382 0x8F7CB9FE5D7A4578ULL, 0x5BD8F7644E634635ULL, // 384 0x427A7315BF2DC900ULL, 0x17D0C4AA2125261CULL, // 386 0x3992486C93518E50ULL, 0xB4CBFEE0A2D7D4C3ULL, // 388 0x7C75D6202C5DDD8DULL, 0xDBC295D8E35B6C61ULL, // 390 0x60B369D302032B19ULL, 0xCE42685FDCE44132ULL, // 392 0x06F3DDB9DDF65610ULL, 0x8EA4D21DB5E148F0ULL, // 394 0x20B0FCE62FCD496FULL, 0x2C1B912358B0EE31ULL, // 396 0xB28317B818F5A308ULL, 0xA89C1E189CA6D2CFULL, // 398 0x0C6B18576AAADBC8ULL, 0xB65DEAA91299FAE3ULL, // 400 0xFB2B794B7F1027E7ULL, 0x04E4317F443B5BEBULL, // 402 0x4B852D325939D0A6ULL, 0xD5AE6BEEFB207FFCULL, // 404 0x309682B281C7D374ULL, 0xBAE309A194C3B475ULL, // 406 0x8CC3F97B13B49F05ULL, 0x98A9422FF8293967ULL, // 408 0x244B16B01076FF7CULL, 0xF8BF571C663D67EEULL, // 410 0x1F0D6758EEE30DA1ULL, 0xC9B611D97ADEB9B7ULL, // 412 0xB7AFD5887B6C57A2ULL, 0x6290AE846B984FE1ULL, // 414 0x94DF4CDEACC1A5FDULL, 0x058A5BD1C5483AFFULL, // 416 0x63166CC142BA3C37ULL, 0x8DB8526EB2F76F40ULL, // 418 0xE10880036F0D6D4EULL, 0x9E0523C9971D311DULL, // 420 0x45EC2824CC7CD691ULL, 0x575B8359E62382C9ULL, // 422 0xFA9E400DC4889995ULL, 0xD1823ECB45721568ULL, // 424 0xDAFD983B8206082FULL, 0xAA7D29082386A8CBULL, // 426 0x269FCD4403B87588ULL, 0x1B91F5F728BDD1E0ULL, // 428 0xE4669F39040201F6ULL, 0x7A1D7C218CF04ADEULL, // 430 0x65623C29D79CE5CEULL, 0x2368449096C00BB1ULL, // 432 0xAB9BF1879DA503BAULL, 0xBC23ECB1A458058EULL, // 434 0x9A58DF01BB401ECCULL, 0xA070E868A85F143DULL, // 436 0x4FF188307DF2239EULL, 0x14D565B41A641183ULL, // 438 0xEE13337452701602ULL, 0x950E3DCF3F285E09ULL, // 440 0x59930254B9C80953ULL, 0x3BF299408930DA6DULL, // 442 0xA955943F53691387ULL, 0xA15EDECAA9CB8784ULL, // 444 0x29142127352BE9A0ULL, 0x76F0371FFF4E7AFBULL, // 446 0x0239F450274F2228ULL, 0xBB073AF01D5E868BULL, // 448 0xBFC80571C10E96C1ULL, 0xD267088568222E23ULL, // 450 0x9671A3D48E80B5B0ULL, 0x55B5D38AE193BB81ULL, // 452 0x693AE2D0A18B04B8ULL, 0x5C48B4ECADD5335FULL, // 454 0xFD743B194916A1CAULL, 0x2577018134BE98C4ULL, // 456 0xE77987E83C54A4ADULL, 0x28E11014DA33E1B9ULL, // 458 0x270CC59E226AA213ULL, 0x71495F756D1A5F60ULL, // 460 0x9BE853FB60AFEF77ULL, 0xADC786A7F7443DBFULL, // 462 0x0904456173B29A82ULL, 0x58BC7A66C232BD5EULL, // 464 0xF306558C673AC8B2ULL, 0x41F639C6B6C9772AULL, // 466 0x216DEFE99FDA35DAULL, 0x11640CC71C7BE615ULL, // 468 0x93C43694565C5527ULL, 0xEA038E6246777839ULL, // 470 0xF9ABF3CE5A3E2469ULL, 0x741E768D0FD312D2ULL, // 472 0x0144B883CED652C6ULL, 0xC20B5A5BA33F8552ULL, // 474 0x1AE69633C3435A9DULL, 0x97A28CA4088CFDECULL, // 476 0x8824A43C1E96F420ULL, 0x37612FA66EEEA746ULL, // 478 0x6B4CB165F9CF0E5AULL, 0x43AA1C06A0ABFB4AULL, // 480 0x7F4DC26FF162796BULL, 0x6CBACC8E54ED9B0FULL, // 482 0xA6B7FFEFD2BB253EULL, 0x2E25BC95B0A29D4FULL, // 484 0x86D6A58BDEF1388CULL, 0xDED74AC576B6F054ULL, // 486 0x8030BDBC2B45805DULL, 0x3C81AF70E94D9289ULL, // 488 0x3EFF6DDA9E3100DBULL, 0xB38DC39FDFCC8847ULL, // 490 0x123885528D17B87EULL, 0xF2DA0ED240B1B642ULL, // 492 0x44CEFADCD54BF9A9ULL, 0x1312200E433C7EE6ULL, // 494 0x9FFCC84F3A78C748ULL, 0xF0CD1F72248576BBULL, // 496 0xEC6974053638CFE4ULL, 0x2BA7B67C0CEC4E4CULL, // 498 0xAC2F4DF3E5CE32EDULL, 0xCB33D14326EA4C11ULL, // 500 0xA4E9044CC77E58BCULL, 0x5F513293D934FCEFULL, // 502 0x5DC9645506E55444ULL, 0x50DE418F317DE40AULL, // 504 0x388CB31A69DDE259ULL, 0x2DB4A83455820A86ULL, // 506 0x9010A91E84711AE9ULL, 0x4DF7F0B7B1498371ULL, // 508 0xD62A2EABC0977179ULL, 0x22FAC097AA8D5C0EULL, // 510 0xF49FCC2FF1DAF39BULL, 0x487FD5C66FF29281ULL, // 512 0xE8A30667FCDCA83FULL, 0x2C9B4BE3D2FCCE63ULL, // 514 0xDA3FF74B93FBBBC2ULL, 0x2FA165D2FE70BA66ULL, // 516 0xA103E279970E93D4ULL, 0xBECDEC77B0E45E71ULL, // 518 0xCFB41E723985E497ULL, 0xB70AAA025EF75017ULL, // 520 0xD42309F03840B8E0ULL, 0x8EFC1AD035898579ULL, // 522 0x96C6920BE2B2ABC5ULL, 0x66AF4163375A9172ULL, // 524 0x2174ABDCCA7127FBULL, 0xB33CCEA64A72FF41ULL, // 526 0xF04A4933083066A5ULL, 0x8D970ACDD7289AF5ULL, // 528 0x8F96E8E031C8C25EULL, 0xF3FEC02276875D47ULL, // 530 0xEC7BF310056190DDULL, 0xF5ADB0AEBB0F1491ULL, // 532 0x9B50F8850FD58892ULL, 0x4975488358B74DE8ULL, // 534 0xA3354FF691531C61ULL, 0x0702BBE481D2C6EEULL, // 536 0x89FB24057DEDED98ULL, 0xAC3075138596E902ULL, // 538 0x1D2D3580172772EDULL, 0xEB738FC28E6BC30DULL, // 540 0x5854EF8F63044326ULL, 0x9E5C52325ADD3BBEULL, // 542 0x90AA53CF325C4623ULL, 0xC1D24D51349DD067ULL, // 544 0x2051CFEEA69EA624ULL, 0x13220F0A862E7E4FULL, // 546 0xCE39399404E04864ULL, 0xD9C42CA47086FCB7ULL, // 548 0x685AD2238A03E7CCULL, 0x066484B2AB2FF1DBULL, // 550 0xFE9D5D70EFBF79ECULL, 0x5B13B9DD9C481854ULL, // 552 0x15F0D475ED1509ADULL, 0x0BEBCD060EC79851ULL, // 554 0xD58C6791183AB7F8ULL, 0xD1187C5052F3EEE4ULL, // 556 0xC95D1192E54E82FFULL, 0x86EEA14CB9AC6CA2ULL, // 558 0x3485BEB153677D5DULL, 0xDD191D781F8C492AULL, // 560 0xF60866BAA784EBF9ULL, 0x518F643BA2D08C74ULL, // 562 0x8852E956E1087C22ULL, 0xA768CB8DC410AE8DULL, // 564 0x38047726BFEC8E1AULL, 0xA67738B4CD3B45AAULL, // 566 0xAD16691CEC0DDE19ULL, 0xC6D4319380462E07ULL, // 568 0xC5A5876D0BA61938ULL, 0x16B9FA1FA58FD840ULL, // 570 0x188AB1173CA74F18ULL, 0xABDA2F98C99C021FULL, // 572 0x3E0580AB134AE816ULL, 0x5F3B05B773645ABBULL, // 574 0x2501A2BE5575F2F6ULL, 0x1B2F74004E7E8BA9ULL, // 576 0x1CD7580371E8D953ULL, 0x7F6ED89562764E30ULL, // 578 0xB15926FF596F003DULL, 0x9F65293DA8C5D6B9ULL, // 580 0x6ECEF04DD690F84CULL, 0x4782275FFF33AF88ULL, // 582 0xE41433083F820801ULL, 0xFD0DFE409A1AF9B5ULL, // 584 0x4325A3342CDB396BULL, 0x8AE77E62B301B252ULL, // 586 0xC36F9E9F6655615AULL, 0x85455A2D92D32C09ULL, // 588 0xF2C7DEA949477485ULL, 0x63CFB4C133A39EBAULL, // 590 0x83B040CC6EBC5462ULL, 0x3B9454C8FDB326B0ULL, // 592 0x56F56A9E87FFD78CULL, 0x2DC2940D99F42BC6ULL, // 594 0x98F7DF096B096E2DULL, 0x19A6E01E3AD852BFULL, // 596 0x42A99CCBDBD4B40BULL, 0xA59998AF45E9C559ULL, // 598 0x366295E807D93186ULL, 0x6B48181BFAA1F773ULL, // 600 0x1FEC57E2157A0A1DULL, 0x4667446AF6201AD5ULL, // 602 0xE615EBCACFB0F075ULL, 0xB8F31F4F68290778ULL, // 604 0x22713ED6CE22D11EULL, 0x3057C1A72EC3C93BULL, // 606 0xCB46ACC37C3F1F2FULL, 0xDBB893FD02AAF50EULL, // 608 0x331FD92E600B9FCFULL, 0xA498F96148EA3AD6ULL, // 610 0xA8D8426E8B6A83EAULL, 0xA089B274B7735CDCULL, // 612 0x87F6B3731E524A11ULL, 0x118808E5CBC96749ULL, // 614 0x9906E4C7B19BD394ULL, 0xAFED7F7E9B24A20CULL, // 616 0x6509EADEEB3644A7ULL, 0x6C1EF1D3E8EF0EDEULL, // 618 0xB9C97D43E9798FB4ULL, 0xA2F2D784740C28A3ULL, // 620 0x7B8496476197566FULL, 0x7A5BE3E6B65F069DULL, // 622 0xF96330ED78BE6F10ULL, 0xEEE60DE77A076A15ULL, // 624 0x2B4BEE4AA08B9BD0ULL, 0x6A56A63EC7B8894EULL, // 626 0x02121359BA34FEF4ULL, 0x4CBF99F8283703FCULL, // 628 0x398071350CAF30C8ULL, 0xD0A77A89F017687AULL, // 630 0xF1C1A9EB9E423569ULL, 0x8C7976282DEE8199ULL, // 632 0x5D1737A5DD1F7ABDULL, 0x4F53433C09A9FA80ULL, // 634 0xFA8B0C53DF7CA1D9ULL, 0x3FD9DCBC886CCB77ULL, // 636 0xC040917CA91B4720ULL, 0x7DD00142F9D1DCDFULL, // 638 0x8476FC1D4F387B58ULL, 0x23F8E7C5F3316503ULL, // 640 0x032A2244E7E37339ULL, 0x5C87A5D750F5A74BULL, // 642 0x082B4CC43698992EULL, 0xDF917BECB858F63CULL, // 644 0x3270B8FC5BF86DDAULL, 0x10AE72BB29B5DD76ULL, // 646 0x576AC94E7700362BULL, 0x1AD112DAC61EFB8FULL, // 648 0x691BC30EC5FAA427ULL, 0xFF246311CC327143ULL, // 650 0x3142368E30E53206ULL, 0x71380E31E02CA396ULL, // 652 0x958D5C960AAD76F1ULL, 0xF8D6F430C16DA536ULL, // 654 0xC8FFD13F1BE7E1D2ULL, 0x7578AE66004DDBE1ULL, // 656 0x05833F01067BE646ULL, 0xBB34B5AD3BFE586DULL, // 658 0x095F34C9A12B97F0ULL, 0x247AB64525D60CA8ULL, // 660 0xDCDBC6F3017477D1ULL, 0x4A2E14D4DECAD24DULL, // 662 0xBDB5E6D9BE0A1EEBULL, 0x2A7E70F7794301ABULL, // 664 0xDEF42D8A270540FDULL, 0x01078EC0A34C22C1ULL, // 666 0xE5DE511AF4C16387ULL, 0x7EBB3A52BD9A330AULL, // 668 0x77697857AA7D6435ULL, 0x004E831603AE4C32ULL, // 670 0xE7A21020AD78E312ULL, 0x9D41A70C6AB420F2ULL, // 672 0x28E06C18EA1141E6ULL, 0xD2B28CBD984F6B28ULL, // 674 0x26B75F6C446E9D83ULL, 0xBA47568C4D418D7FULL, // 676 0xD80BADBFE6183D8EULL, 0x0E206D7F5F166044ULL, // 678 0xE258A43911CBCA3EULL, 0x723A1746B21DC0BCULL, // 680 0xC7CAA854F5D7CDD3ULL, 0x7CAC32883D261D9CULL, // 682 0x7690C26423BA942CULL, 0x17E55524478042B8ULL, // 684 0xE0BE477656A2389FULL, 0x4D289B5E67AB2DA0ULL, // 686 0x44862B9C8FBBFD31ULL, 0xB47CC8049D141365ULL, // 688 0x822C1B362B91C793ULL, 0x4EB14655FB13DFD8ULL, // 690 0x1ECBBA0714E2A97BULL, 0x6143459D5CDE5F14ULL, // 692 0x53A8FBF1D5F0AC89ULL, 0x97EA04D81C5E5B00ULL, // 694 0x622181A8D4FDB3F3ULL, 0xE9BCD341572A1208ULL, // 696 0x1411258643CCE58AULL, 0x9144C5FEA4C6E0A4ULL, // 698 0x0D33D06565CF620FULL, 0x54A48D489F219CA1ULL, // 700 0xC43E5EAC6D63C821ULL, 0xA9728B3A72770DAFULL, // 702 0xD7934E7B20DF87EFULL, 0xE35503B61A3E86E5ULL, // 704 0xCAE321FBC819D504ULL, 0x129A50B3AC60BFA6ULL, // 706 0xCD5E68EA7E9FB6C3ULL, 0xB01C90199483B1C7ULL, // 708 0x3DE93CD5C295376CULL, 0xAED52EDF2AB9AD13ULL, // 710 0x2E60F512C0A07884ULL, 0xBC3D86A3E36210C9ULL, // 712 0x35269D9B163951CEULL, 0x0C7D6E2AD0CDB5FAULL, // 714 0x59E86297D87F5733ULL, 0x298EF221898DB0E7ULL, // 716 0x55000029D1A5AA7EULL, 0x8BC08AE1B5061B45ULL, // 718 0xC2C31C2B6C92703AULL, 0x94CC596BAF25EF42ULL, // 720 0x0A1D73DB22540456ULL, 0x04B6A0F9D9C4179AULL, // 722 0xEFFDAFA2AE3D3C60ULL, 0xF7C8075BB49496C4ULL, // 724 0x9CC5C7141D1CD4E3ULL, 0x78BD1638218E5534ULL, // 726 0xB2F11568F850246AULL, 0xEDFABCFA9502BC29ULL, // 728 0x796CE5F2DA23051BULL, 0xAAE128B0DC93537CULL, // 730 0x3A493DA0EE4B29AEULL, 0xB5DF6B2C416895D7ULL, // 732 0xFCABBD25122D7F37ULL, 0x70810B58105DC4B1ULL, // 734 0xE10FDD37F7882A90ULL, 0x524DCAB5518A3F5CULL, // 736 0x3C9E85878451255BULL, 0x4029828119BD34E2ULL, // 738 0x74A05B6F5D3CECCBULL, 0xB610021542E13ECAULL, // 740 0x0FF979D12F59E2ACULL, 0x6037DA27E4F9CC50ULL, // 742 0x5E92975A0DF1847DULL, 0xD66DE190D3E623FEULL, // 744 0x5032D6B87B568048ULL, 0x9A36B7CE8235216EULL, // 746 0x80272A7A24F64B4AULL, 0x93EFED8B8C6916F7ULL, // 748 0x37DDBFF44CCE1555ULL, 0x4B95DB5D4B99BD25ULL, // 750 0x92D3FDA169812FC0ULL, 0xFB1A4A9A90660BB6ULL, // 752 0x730C196946A4B9B2ULL, 0x81E289AA7F49DA68ULL, // 754 0x64669A0F83B1A05FULL, 0x27B3FF7D9644F48BULL, // 756 0xCC6B615C8DB675B3ULL, 0x674F20B9BCEBBE95ULL, // 758 0x6F31238275655982ULL, 0x5AE488713E45CF05ULL, // 760 0xBF619F9954C21157ULL, 0xEABAC46040A8EAE9ULL, // 762 0x454C6FE9F2C0C1CDULL, 0x419CF6496412691CULL, // 764 0xD3DC3BEF265B0F70ULL, 0x6D0E60F5C3578A9EULL, // 766 0x5B0E608526323C55ULL, 0x1A46C1A9FA1B59F5ULL, // 768 0xA9E245A17C4C8FFAULL, 0x65CA5159DB2955D7ULL, // 770 0x05DB0A76CE35AFC2ULL, 0x81EAC77EA9113D45ULL, // 772 0x528EF88AB6AC0A0DULL, 0xA09EA253597BE3FFULL, // 774 0x430DDFB3AC48CD56ULL, 0xC4B3A67AF45CE46FULL, // 776 0x4ECECFD8FBE2D05EULL, 0x3EF56F10B39935F0ULL, // 778 0x0B22D6829CD619C6ULL, 0x17FD460A74DF2069ULL, // 780 0x6CF8CC8E8510ED40ULL, 0xD6C824BF3A6ECAA7ULL, // 782 0x61243D581A817049ULL, 0x048BACB6BBC163A2ULL, // 784 0xD9A38AC27D44CC32ULL, 0x7FDDFF5BAAF410ABULL, // 786 0xAD6D495AA804824BULL, 0xE1A6A74F2D8C9F94ULL, // 788 0xD4F7851235DEE8E3ULL, 0xFD4B7F886540D893ULL, // 790 0x247C20042AA4BFDAULL, 0x096EA1C517D1327CULL, // 792 0xD56966B4361A6685ULL, 0x277DA5C31221057DULL, // 794 0x94D59893A43ACFF7ULL, 0x64F0C51CCDC02281ULL, // 796 0x3D33BCC4FF6189DBULL, 0xE005CB184CE66AF1ULL, // 798 0xFF5CCD1D1DB99BEAULL, 0xB0B854A7FE42980FULL, // 800 0x7BD46A6A718D4B9FULL, 0xD10FA8CC22A5FD8CULL, // 802 0xD31484952BE4BD31ULL, 0xC7FA975FCB243847ULL, // 804 0x4886ED1E5846C407ULL, 0x28CDDB791EB70B04ULL, // 806 0xC2B00BE2F573417FULL, 0x5C9590452180F877ULL, // 808 0x7A6BDDFFF370EB00ULL, 0xCE509E38D6D9D6A4ULL, // 810 0xEBEB0F00647FA702ULL, 0x1DCC06CF76606F06ULL, // 812 0xE4D9F28BA286FF0AULL, 0xD85A305DC918C262ULL, // 814 0x475B1D8732225F54ULL, 0x2D4FB51668CCB5FEULL, // 816 0xA679B9D9D72BBA20ULL, 0x53841C0D912D43A5ULL, // 818 0x3B7EAA48BF12A4E8ULL, 0x781E0E47F22F1DDFULL, // 820 0xEFF20CE60AB50973ULL, 0x20D261D19DFFB742ULL, // 822 0x16A12B03062A2E39ULL, 0x1960EB2239650495ULL, // 824 0x251C16FED50EB8B8ULL, 0x9AC0C330F826016EULL, // 826 0xED152665953E7671ULL, 0x02D63194A6369570ULL, // 828 0x5074F08394B1C987ULL, 0x70BA598C90B25CE1ULL, // 830 0x794A15810B9742F6ULL, 0x0D5925E9FCAF8C6CULL, // 832 0x3067716CD868744EULL, 0x910AB077E8D7731BULL, // 834 0x6A61BBDB5AC42F61ULL, 0x93513EFBF0851567ULL, // 836 0xF494724B9E83E9D5ULL, 0xE887E1985C09648DULL, // 838 0x34B1D3C675370CFDULL, 0xDC35E433BC0D255DULL, // 840 0xD0AAB84234131BE0ULL, 0x08042A50B48B7EAFULL, // 842 0x9997C4EE44A3AB35ULL, 0x829A7B49201799D0ULL, // 844 0x263B8307B7C54441ULL, 0x752F95F4FD6A6CA6ULL, // 846 0x927217402C08C6E5ULL, 0x2A8AB754A795D9EEULL, // 848 0xA442F7552F72943DULL, 0x2C31334E19781208ULL, // 850 0x4FA98D7CEAEE6291ULL, 0x55C3862F665DB309ULL, // 852 0xBD0610175D53B1F3ULL, 0x46FE6CB840413F27ULL, // 854 0x3FE03792DF0CFA59ULL, 0xCFE700372EB85E8FULL, // 856 0xA7BE29E7ADBCE118ULL, 0xE544EE5CDE8431DDULL, // 858 0x8A781B1B41F1873EULL, 0xA5C94C78A0D2F0E7ULL, // 860 0x39412E2877B60728ULL, 0xA1265EF3AFC9A62CULL, // 862 0xBCC2770C6A2506C5ULL, 0x3AB66DD5DCE1CE12ULL, // 864 0xE65499D04A675B37ULL, 0x7D8F523481BFD216ULL, // 866 0x0F6F64FCEC15F389ULL, 0x74EFBE618B5B13C8ULL, // 868 0xACDC82B714273E1DULL, 0xDD40BFE003199D17ULL, // 870 0x37E99257E7E061F8ULL, 0xFA52626904775AAAULL, // 872 0x8BBBF63A463D56F9ULL, 0xF0013F1543A26E64ULL, // 874 0xA8307E9F879EC898ULL, 0xCC4C27A4150177CCULL, // 876 0x1B432F2CCA1D3348ULL, 0xDE1D1F8F9F6FA013ULL, // 878 0x606602A047A7DDD6ULL, 0xD237AB64CC1CB2C7ULL, // 880 0x9B938E7225FCD1D3ULL, 0xEC4E03708E0FF476ULL, // 882 0xFEB2FBDA3D03C12DULL, 0xAE0BCED2EE43889AULL, // 884 0x22CB8923EBFB4F43ULL, 0x69360D013CF7396DULL, // 886 0x855E3602D2D4E022ULL, 0x073805BAD01F784CULL, // 888 0x33E17A133852F546ULL, 0xDF4874058AC7B638ULL, // 890 0xBA92B29C678AA14AULL, 0x0CE89FC76CFAADCDULL, // 892 0x5F9D4E0908339E34ULL, 0xF1AFE9291F5923B9ULL, // 894 0x6E3480F60F4A265FULL, 0xEEBF3A2AB29B841CULL, // 896 0xE21938A88F91B4ADULL, 0x57DFEFF845C6D3C3ULL, // 898 0x2F006B0BF62CAAF2ULL, 0x62F479EF6F75EE78ULL, // 900 0x11A55AD41C8916A9ULL, 0xF229D29084FED453ULL, // 902 0x42F1C27B16B000E6ULL, 0x2B1F76749823C074ULL, // 904 0x4B76ECA3C2745360ULL, 0x8C98F463B91691BDULL, // 906 0x14BCC93CF1ADE66AULL, 0x8885213E6D458397ULL, // 908 0x8E177DF0274D4711ULL, 0xB49B73B5503F2951ULL, // 910 0x10168168C3F96B6BULL, 0x0E3D963B63CAB0AEULL, // 912 0x8DFC4B5655A1DB14ULL, 0xF789F1356E14DE5CULL, // 914 0x683E68AF4E51DAC1ULL, 0xC9A84F9D8D4B0FD9ULL, // 916 0x3691E03F52A0F9D1ULL, 0x5ED86E46E1878E80ULL, // 918 0x3C711A0E99D07150ULL, 0x5A0865B20C4E9310ULL, // 920 0x56FBFC1FE4F0682EULL, 0xEA8D5DE3105EDF9BULL, // 922 0x71ABFDB12379187AULL, 0x2EB99DE1BEE77B9CULL, // 924 0x21ECC0EA33CF4523ULL, 0x59A4D7521805C7A1ULL, // 926 0x3896F5EB56AE7C72ULL, 0xAA638F3DB18F75DCULL, // 928 0x9F39358DABE9808EULL, 0xB7DEFA91C00B72ACULL, // 930 0x6B5541FD62492D92ULL, 0x6DC6DEE8F92E4D5BULL, // 932 0x353F57ABC4BEEA7EULL, 0x735769D6DA5690CEULL, // 934 0x0A234AA642391484ULL, 0xF6F9508028F80D9DULL, // 936 0xB8E319A27AB3F215ULL, 0x31AD9C1151341A4DULL, // 938 0x773C22A57BEF5805ULL, 0x45C7561A07968633ULL, // 940 0xF913DA9E249DBE36ULL, 0xDA652D9B78A64C68ULL, // 942 0x4C27A97F3BC334EFULL, 0x76621220E66B17F4ULL, // 944 0x967743899ACD7D0BULL, 0xF3EE5BCAE0ED6782ULL, // 946 0x409F753600C879FCULL, 0x06D09A39B5926DB6ULL, // 948 0x6F83AEB0317AC588ULL, 0x01E6CA4A86381F21ULL, // 950 0x66FF3462D19F3025ULL, 0x72207C24DDFD3BFBULL, // 952 0x4AF6B6D3E2ECE2EBULL, 0x9C994DBEC7EA08DEULL, // 954 0x49ACE597B09A8BC4ULL, 0xB38C4766CF0797BAULL, // 956 0x131B9373C57C2A75ULL, 0xB1822CCE61931E58ULL, // 958 0x9D7555B909BA1C0CULL, 0x127FAFDD937D11D2ULL, // 960 0x29DA3BADC66D92E4ULL, 0xA2C1D57154C2ECBCULL, // 962 0x58C5134D82F6FE24ULL, 0x1C3AE3515B62274FULL, // 964 0xE907C82E01CB8126ULL, 0xF8ED091913E37FCBULL, // 966 0x3249D8F9C80046C9ULL, 0x80CF9BEDE388FB63ULL, // 968 0x1881539A116CF19EULL, 0x5103F3F76BD52457ULL, // 970 0x15B7E6F5AE47F7A8ULL, 0xDBD7C6DED47E9CCFULL, // 972 0x44E55C410228BB1AULL, 0xB647D4255EDB4E99ULL, // 974 0x5D11882BB8AAFC30ULL, 0xF5098BBB29D3212AULL, // 976 0x8FB5EA14E90296B3ULL, 0x677B942157DD025AULL, // 978 0xFB58E7C0A390ACB5ULL, 0x89D3674C83BD4A01ULL, // 980 0x9E2DA4DF4BF3B93BULL, 0xFCC41E328CAB4829ULL, // 982 0x03F38C96BA582C52ULL, 0xCAD1BDBD7FD85DB2ULL, // 984 0xBBB442C16082AE83ULL, 0xB95FE86BA5DA9AB0ULL, // 986 0xB22E04673771A93FULL, 0x845358C9493152D8ULL, // 988 0xBE2A488697B4541EULL, 0x95A2DC2DD38E6966ULL, // 990 0xC02C11AC923C852BULL, 0x2388B1990DF2A87BULL, // 992 0x7C8008FA1B4F37BEULL, 0x1F70D0C84D54E503ULL, // 994 0x5490ADEC7ECE57D4ULL, 0x002B3C27D9063A3AULL, // 996 0x7EAEA3848030A2BFULL, 0xC602326DED2003C0ULL, // 998 0x83A7287D69A94086ULL, 0xC57A5FCB30F57A8AULL, // 1000 0xB56844E479EBE779ULL, 0xA373B40F05DCBCE9ULL, // 1002 0xD71A786E88570EE2ULL, 0x879CBACDBDE8F6A0ULL, // 1004 0x976AD1BCC164A32FULL, 0xAB21E25E9666D78BULL, // 1006 0x901063AAE5E5C33CULL, 0x9818B34448698D90ULL, // 1008 0xE36487AE3E1E8ABBULL, 0xAFBDF931893BDCB4ULL, // 1010 0x6345A0DC5FBBD519ULL, 0x8628FE269B9465CAULL, // 1012 0x1E5D01603F9C51ECULL, 0x4DE44006A15049B7ULL, // 1014 0xBF6C70E5F776CBB1ULL, 0x411218F2EF552BEDULL, // 1016 0xCB0C0708705A36A3ULL, 0xE74D14754F986044ULL, // 1018 0xCD56D9430EA8280EULL, 0xC12591D7535F5065ULL, // 1020 0xC83223F1720AEF96ULL, 0xC3A0396F7363A51FULL, // 1022 }; static inline void round(uint64_t& a, uint64_t& b, uint64_t& c, uint64_t x, int mul) { c ^= x; a -= table[uint8_t(c >> 0) + 0 * 256] ^ table[uint8_t(c >> 16) + 1 * 256] ^ table[uint8_t(c >> 32) + 2 * 256] ^ table[uint8_t(c >> 48) + 3 * 256]; b += table[uint8_t(c >> 8) + 3 * 256] ^ table[uint8_t(c >> 24) + 2 * 256] ^ table[uint8_t(c >> 40) + 1 * 256] ^ table[uint8_t(c >> 56) + 0 * 256]; b *= mul; } static void tiger_compress(const uint8_t* str, uint64_t state[3]) { uint64_t a = state[0]; uint64_t b = state[1]; uint64_t c = state[2]; uint64_t x0 = Endian::read_UA_L64(str + 0); uint64_t x1 = Endian::read_UA_L64(str + 8); uint64_t x2 = Endian::read_UA_L64(str + 16); uint64_t x3 = Endian::read_UA_L64(str + 24); uint64_t x4 = Endian::read_UA_L64(str + 32); uint64_t x5 = Endian::read_UA_L64(str + 40); uint64_t x6 = Endian::read_UA_L64(str + 48); uint64_t x7 = Endian::read_UA_L64(str + 56); uint64_t aa = a; uint64_t bb = b; uint64_t cc = c; round(a, b, c, x0, 5); round(b, c, a, x1, 5); round(c, a, b, x2, 5); round(a, b, c, x3, 5); round(b, c, a, x4, 5); round(c, a, b, x5, 5); round(a, b, c, x6, 5); round(b, c, a, x7, 5); x0 -= x7 ^ 0xA5A5A5A5A5A5A5A5LL; x1 ^= x0; x2 += x1; x3 -= x2 ^ ((~x1) << 19); x4 ^= x3; x5 += x4; x6 -= x5 ^ ((~x4) >> 23); x7 ^= x6; x0 += x7; x1 -= x0 ^ ((~x7) << 19); x2 ^= x1; x3 += x2; x4 -= x3 ^ ((~x2) >> 23); x5 ^= x4; x6 += x5; x7 -= x6 ^ 0x0123456789ABCDEFLL; round(c, a, b, x0, 7); round(a, b, c, x1, 7); round(b, c, a, x2, 7); round(c, a, b, x3, 7); round(a, b, c, x4, 7); round(b, c, a, x5, 7); round(c, a, b, x6, 7); round(a, b, c, x7, 7); x0 -= x7 ^ 0xA5A5A5A5A5A5A5A5LL; x1 ^= x0; x2 += x1; x3 -= x2 ^ ((~x1) << 19); x4 ^= x3; x5 += x4; x6 -= x5 ^ ((~x4) >> 23); x7 ^= x6; x0 += x7; x1 -= x0 ^ ((~x7) << 19); x2 ^= x1; x3 += x2; x4 -= x3 ^ ((~x2) >> 23); x5 ^= x4; x6 += x5; x7 -= x6 ^ 0x0123456789ABCDEFLL; round(b, c, a, x0, 9); round(c, a, b, x1, 9); round(a, b, c, x2, 9); round(b, c, a, x3, 9); round(c, a, b, x4, 9); round(a, b, c, x5, 9); round(b, c, a, x6, 9); round(c, a, b, x7, 9); state[0] = a ^ aa; state[1] = b - bb; state[2] = c + cc; } static inline void initState(uint64_t state[3]) { state[0] = 0x0123456789ABCDEFULL; state[1] = 0xFEDCBA9876543210ULL; state[2] = 0xF096A5B4C3B2E187ULL; } static inline void returnState(uint64_t state[3]) { if (OPENMSX_BIGENDIAN) { state[0] = Endian::bswap64(state[0]); state[1] = Endian::bswap64(state[1]); state[2] = Endian::bswap64(state[2]); } } void tiger(const uint8_t* str, size_t length, TigerHash& result) { uint8_t temp[64]; initState(result.h64); size_t i; for (i = length; i >= 64; i -= 64) { tiger_compress(str, result.h64); str += 64; } size_t j; for (j = 0; j < i; ++j) { temp[j] = str[j]; } temp[j++] = 0x01; for (/**/; j & 7; ++j) { temp[j] = 0; } if (j > 56) { for (/**/; j < 64; ++j) { temp[j] = 0; } tiger_compress(temp, result.h64); j = 0; } for (/**/; j < 56; ++j) { temp[j] = 0; } Endian::write_UA_L64(temp + 56, uint64_t(length) << 3); tiger_compress(temp, result.h64); returnState(result.h64); } void tiger_int(const TigerHash& h0, const TigerHash& h1, TigerHash& result) { static uint8_t buf[64] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; memcpy(buf + 1, h0.h64, 24); memcpy(buf + 1 + 24, h1.h64, 24); initState(result.h64); tiger_compress(buf, result.h64); returnState(result.h64); } void tiger_leaf(/*const*/ uint8_t data[1024], TigerHash& result) { static uint8_t last[64] = { 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; initState(result.h64); auto backup = data[-1]; data[-1] = 0; for (int i = 0; i < 16; ++i) { tiger_compress(data - 1 + i * 64, result.h64); } data[-1] = backup; last[0] = data[1023]; tiger_compress(last, result.h64); returnState(result.h64); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/utils/tiger.hh000066400000000000000000000042751257557151200200310ustar00rootroot00000000000000/** * The implementation of the tiger() function is based on the corresponding * function in the tigertree project: * https://sourceforge.net/projects/tigertree/ * Which in turn states that the code is almost a literal copy from the * original Tiger authors code. It can be found at: * http://www.cs.technion.ac.il/~biham/Reports/Tiger * * The functions tiger_int() and tiger_leaf() are implemented by me. These are * built on top of the (internal) tiger_compress() function. These 2 functions * are faster than the generic tiger() function for the specific case of * tiger-tree calculations. */ #ifndef TIGER_H #define TIGER_H #include #include namespace openmsx { /** This struct represents the result of a tiger-hash. * This is a 192-bit value. It can be formatted as a 39-char base32-encoded * string. Similar to the output of the unix 'tthsum' tool. */ struct TigerHash { std::string toString() const; union { uint64_t h64[3]; uint8_t h8[24]; }; }; /** Generic function to calculate a tiger-hash. * input: start-address and size of the block * output: 192-bit hash value * The result is endianness-neutral (on big-endian systems the result is * converted so that, when interpreted as a byte-array, it is identical to * the result obtained on a little-endian system). */ void tiger(const uint8_t* str, size_t length, TigerHash& result); /** Use for tiger-tree internal node hash calculations. * Combine two earlier calculated tiger hash values in a specific way (add * marker/padding/length bytes before/after) and calculate a new hash value. * This function is not reentrant. */ void tiger_int(const TigerHash& h0, const TigerHash& h1, TigerHash& result); /** Use for tiger-tree leaf node hash calculations. * Take a 1024-byte input block, add some marker/padding/length bytes * before/after and calculate a tiger-hash. * This function is not reentrant. * This function requires that data[-1] can be (temporarily) overridden (so * after the function returns the data buffer is unchanged, but temporarily * it is changed, hence the parameter cannot be const). */ void tiger_leaf(/*const*/ uint8_t data[1024], TigerHash& result); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/utils/type_traits.hh000066400000000000000000000004401257557151200212540ustar00rootroot00000000000000#ifndef TYPE_TRAITS_HH #define TYPE_TRAITS_HH // if_ template struct if_c : F {}; template< class T, class F> struct if_c : T {}; template struct if_ : if_c {}; #endif openMSX-RELEASE_0_12_0/src/utils/uint128.cc000066400000000000000000000017551257557151200201170ustar00rootroot00000000000000#if defined __x86_64 && !defined _MSC_VER // nothing #else // __x86_64 && !_MSC_VER #include "uint128.hh" uint128& uint128::operator*=(const uint128& b) { uint128 a = *this; uint128 t = b; lo = 0; hi = 0; while (t != 0) { if (t.lo & 1) { *this += a; } a <<= 1; t >>= 1; } return *this; } uint128 uint128::div(const uint128& ds, uint128& remainder) const { uint128 dd = *this; uint128 r = 0; uint128 q = 0; unsigned b = 127; while (r < ds) { r <<= 1; if (dd.bit(b--)) { r.lo |= 1; } } ++b; while (true) { if (r < ds) { if (!(b--)) break; r <<= 1; if (dd.bit(b)) { r.lo |= 1; } } else { r -= ds; q.setBit(b); } } remainder = r; return q; } bool uint128::bit(unsigned n) const { if (n < 64) { return (lo & (1ull << n)) != 0; } else { return (hi & (1ull << (n - 64))) != 0; } } void uint128::setBit(unsigned n) { if (n < 64) { lo |= (1ull << n); } else { hi |= (1ull << (n - 64)); } } #endif // __x86_64 && !_MSC_VER openMSX-RELEASE_0_12_0/src/utils/uint128.hh000066400000000000000000000106541257557151200201270ustar00rootroot00000000000000#ifndef UINT128_HH #define UINT128_HH #include #if defined __x86_64 && !defined _MSC_VER // On 64-bit CPUs gcc already provides a 128-bit type, // use that type because it's most likely much more efficient. // VC++ 2008 does not provide a 128-bit integer type using uint128 = __uint128_t; inline uint64_t toUint64(uint128 a) { return a; } #else // __x86_64 && !_MSC_VER /** Unsigned 128-bit integer type. * Very simple implementation, not optimized for speed. * * Loosely based on the 128-bit utility classes written by * Jan Ringos, http://Tringi.Mx-3.cz */ class uint128 { public: uint128(const uint128& a) : lo(a.lo), hi(a.hi) {} uint128(uint64_t a) : lo(a), hi(0) {} bool operator!() const { return !(hi || lo); } uint128 operator~() const { return uint128(~lo, ~hi); } uint128 operator-() const { uint128 result = ~*this; ++result; return result; } uint128& operator++() { ++lo; if (lo == 0) ++hi; return *this; } uint128& operator--() { if (lo == 0) --hi; --lo; return *this; } uint128 operator++(int) { uint128 b = *this; ++*this; return b; } uint128 operator--(int) { uint128 b = *this; --*this; return b; } uint128& operator+=(const uint128& b) { uint64_t old_lo = lo; lo += b.lo; hi += b.hi + (lo < old_lo); return *this; } uint128& operator-=(const uint128& b) { return *this += (-b); } uint128& operator>>=(unsigned n) { n &= 0x7F; if (n < 64) { lo = (hi << (64 - n)) | (lo >> n); hi >>= n; } else { lo = hi >> (n - 64); hi = 0; } return *this; } uint128& operator<<=(unsigned n) { n &= 0x7F; if (n < 64) { hi = (hi << n) | (lo >> (64 - n)); lo <<= n; } else { hi = lo << (n - 64); lo = 0; } return *this; } uint128& operator|=(const uint128& b) { hi |= b.hi; lo |= b.lo; return *this; } uint128& operator&=(const uint128& b) { hi &= b.hi; lo &= b.lo; return *this; } uint128& operator^=(const uint128& b) { hi ^= b.hi; lo ^= b.lo; return *this; } uint128& operator/=(const uint128& b) { uint128 dummy; *this = div(b, dummy); return *this; } uint128& operator%=(const uint128& b) { div(b, *this); return *this; } uint128& operator*=(const uint128& b); private: uint128() {} uint128(uint64_t a, uint64_t b) : lo(a), hi(b) {} uint128 div(const uint128& ds, uint128& remainder) const; bool bit(unsigned n) const; void setBit(unsigned n); uint64_t lo; uint64_t hi; friend bool operator< (const uint128&, const uint128&); friend bool operator==(const uint128&, const uint128&); friend bool operator||(const uint128&, const uint128&); friend bool operator&&(const uint128&, const uint128&); friend uint64_t toUint64(const uint128&); }; inline uint128 operator+(const uint128& a, const uint128& b) { return uint128(a) += b; } inline uint128 operator-(const uint128& a, const uint128& b) { return uint128(a) -= b; } inline uint128 operator*(const uint128& a, const uint128& b) { return uint128(a) *= b; } inline uint128 operator/(const uint128& a, const uint128& b) { return uint128(a) /= b; } inline uint128 operator%(const uint128& a, const uint128& b) { return uint128(a) %= b; } inline uint128 operator>>(const uint128& a, unsigned n) { return uint128(a) >>= n; } inline uint128 operator<<(const uint128 & a, unsigned n) { return uint128(a) <<= n; } inline uint128 operator&(const uint128& a, const uint128& b) { return uint128(a) &= b; } inline uint128 operator|(const uint128& a, const uint128& b) { return uint128(a) |= b; } inline uint128 operator^(const uint128& a, const uint128& b) { return uint128(a) ^= b; } inline bool operator<(const uint128& a, const uint128& b) { return (a.hi == b.hi) ? (a.lo < b.lo) : (a.hi < b.hi); } inline bool operator>(const uint128& a, const uint128& b) { return b < a; } inline bool operator<=(const uint128& a, const uint128& b) { return !(b < a); } inline bool operator>=(const uint128& a, const uint128& b) { return !(a < b); } inline bool operator==(const uint128& a, const uint128& b) { return (a.hi == b.hi) && (a.lo == b.lo); } inline bool operator!=(const uint128& a, const uint128& b) { return !(a == b); } inline bool operator&&(const uint128& a, const uint128& b) { return (a.hi || a.lo) && (b.hi || b.lo); } inline bool operator||(const uint128& a, const uint128& b) { return a.hi || a.lo || b.hi || b.lo; } inline uint64_t toUint64(const uint128& a) { return a.lo; } #endif // __x86_64 && !_MSC_VER #endif // UINT128_HH openMSX-RELEASE_0_12_0/src/utils/unistdp.hh000066400000000000000000000003111257557151200203700ustar00rootroot00000000000000#ifndef UNISTDP_HH #define UNISTDP_HH #ifndef _MSC_VER #include #else #include #include #define getpid _getpid using mode_t = int; #endif #endif // UNISTDP_HH openMSX-RELEASE_0_12_0/src/utils/unreachable.hh000066400000000000000000000017321257557151200211630ustar00rootroot00000000000000#ifndef UNREACHABLE_HH #define UNREACHABLE_HH // Clang has a very convenient way of testing features, unfortunately (for now) // it's clang-only so add a fallback for non-clang compilers. #ifndef __has_builtin #define __has_builtin(x) 0 #endif #if defined(NDEBUG) // clang #if __has_builtin(__builtin_unreachable) #define UNREACHABLE __builtin_unreachable() // __builtin_unreachable() was introduced in gcc-4.5, but 4.5 and 4.6 contain // bugs on architectures with delay slots (e.g. MIPS). Look at the git // history of this file for more details. #elif ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 7))) // gcc-4.7 or above #define UNREACHABLE __builtin_unreachable() #elif defined(_MSC_VER) // visual studio #define UNREACHABLE __assume(0) #else // fall-back #define UNREACHABLE /*nothing*/ #endif #else // asserts enabled #include #define UNREACHABLE assert(false) #endif #endif // UNREACHABLE_HH openMSX-RELEASE_0_12_0/src/utils/utf8_checked.cc000066400000000000000000000036601257557151200212360ustar00rootroot00000000000000#ifdef _WIN32 #include "utf8_checked.hh" #include "vla.hh" #include "StringOp.hh" #include "MSXException.hh" #include namespace utf8 { static bool multibytetoutf16(const std::string& multibyte, UINT cp, DWORD dwFlags, std::wstring& utf16) { const char* multibyteA = multibyte.c_str(); int len = MultiByteToWideChar(cp, dwFlags, multibyteA, -1, nullptr, 0); if (len) { VLA(wchar_t, utf16W, len); len = MultiByteToWideChar(cp, dwFlags, multibyteA, -1, utf16W, len); if (len) { utf16 = utf16W; return true; } } return false; } static bool utf16tomultibyte(const std::wstring& utf16, UINT cp, std::string& multibyte) { const wchar_t* utf16W = utf16.c_str(); int len = WideCharToMultiByte(cp, 0, utf16W, -1, nullptr, 0, nullptr, nullptr); if (len) { VLA(char, multibyteA, len); len = WideCharToMultiByte(cp, 0, utf16W, -1, multibyteA, len, nullptr, nullptr); if (len) { multibyte = multibyteA; return true; } } return false; } std::string utf8toansi(const std::string& utf8) { std::wstring utf16; if (!multibytetoutf16(utf8, CP_UTF8, MB_ERR_INVALID_CHARS, utf16)) { throw openmsx::FatalError(StringOp::Builder() << "MultiByteToWideChar failed: " << GetLastError()); } std::string ansi; if (!utf16tomultibyte(utf16, CP_ACP, ansi)) { throw openmsx::FatalError(StringOp::Builder() << "MultiByteToWideChar failed: " << GetLastError()); } return ansi; } std::wstring utf8to16(const std::string& utf8) { std::wstring utf16; if (!multibytetoutf16(utf8, CP_UTF8, MB_ERR_INVALID_CHARS, utf16)) { throw openmsx::FatalError(StringOp::Builder() << "MultiByteToWideChar failed: " << GetLastError()); } return utf16; } std::string utf16to8(const std::wstring& utf16) { std::string utf8; if (!utf16tomultibyte(utf16, CP_UTF8, utf8)) { throw openmsx::FatalError(StringOp::Builder() << "MultiByteToWideChar failed: " << GetLastError()); } return utf8; } } // namespace utf8 #endif // _WIN32 openMSX-RELEASE_0_12_0/src/utils/utf8_checked.hh000066400000000000000000000220321257557151200212420ustar00rootroot00000000000000// UTF8-CPP http://utfcpp.sourceforge.net/ // Slightly simplified (and reformatted) to fit openMSX coding style. // Copyright 2006 Nemanja Trifunovic /* Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef UTF8_CHECKED_HH #define UTF8_CHECKED_HH #include "utf8_core.hh" #include namespace utf8 { // Exceptions that may be thrown from the library functions. class invalid_code_point : public std::exception { uint32_t cp; public: explicit invalid_code_point(uint32_t cp_) : cp(cp_) {} const char* what() const throw() override { return "Invalid code point"; } uint32_t code_point() const { return cp; } }; class invalid_utf8 : public std::exception { uint8_t u8; public: explicit invalid_utf8(uint8_t u) : u8(u) {} const char* what() const throw() override { return "Invalid UTF-8"; } uint8_t utf8_octet() const { return u8; } }; class invalid_utf16 : public std::exception { uint16_t u16; public: explicit invalid_utf16(uint16_t u) : u16(u) {} const char* what() const throw() override { return "Invalid UTF-16"; } uint16_t utf16_word() const { return u16; } }; class not_enough_room : public std::exception { public: const char* what() const throw() override { return "Not enough space"; } }; // The library API - functions intended to be called by the users template output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement) { while (start != end) { auto sequence_start = start; internal::utf_error err_code = internal::validate_next(start, end); switch (err_code) { case internal::OK: for (auto it = sequence_start; it != start; ++it) { *out++ = *it; } break; case internal::NOT_ENOUGH_ROOM: throw not_enough_room(); case internal::INVALID_LEAD: append(replacement, out); ++start; break; case internal::INCOMPLETE_SEQUENCE: case internal::OVERLONG_SEQUENCE: case internal::INVALID_CODE_POINT: append(replacement, out); ++start; // just one replacement mark for the sequence while (internal::is_trail(*start) && start != end) { ++start; } break; } } return out; } template inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) { return replace_invalid(start, end, out, 0xfffd); } template octet_iterator append(uint32_t cp, octet_iterator result) { if (!internal::is_code_point_valid(cp)) { throw invalid_code_point(cp); } if (cp < 0x80) { // one octet *result++ = cp; } else if (cp < 0x800) { // two octets *result++ = ((cp >> 6) ) | 0xc0; *result++ = ((cp >> 0) & 0x3f) | 0x80; } else if (cp < 0x10000) { // three octets *result++ = ((cp >> 12) ) | 0xe0; *result++ = ((cp >> 6) & 0x3f) | 0x80; *result++ = ((cp >> 0) & 0x3f) | 0x80; } else if (cp <= internal::CODE_POINT_MAX) { // four octets *result++ = ((cp >> 18) ) | 0xf0; *result++ = ((cp >> 12) & 0x3f) | 0x80; *result++ = ((cp >> 6) & 0x3f) | 0x80; *result++ = ((cp >> 0) & 0x3f) | 0x80; } else { throw invalid_code_point(cp); } return result; } template uint32_t next(octet_iterator& it, octet_iterator end) { uint32_t cp = 0; internal::utf_error err_code = internal::validate_next(it, end, &cp); switch (err_code) { case internal::OK : break; case internal::NOT_ENOUGH_ROOM: throw not_enough_room(); case internal::INVALID_LEAD: case internal::INCOMPLETE_SEQUENCE: case internal::OVERLONG_SEQUENCE: throw invalid_utf8(*it); case internal::INVALID_CODE_POINT: throw invalid_code_point(cp); } return cp; } template uint32_t peek_next(octet_iterator it, octet_iterator end) { return next(it, end); } template uint32_t prior(octet_iterator& it, octet_iterator start) { auto end = it; while (internal::is_trail(*(--it))) { if (it < start) { // error - no lead byte in the sequence throw invalid_utf8(*it); } } auto temp = it; return next(temp, end); } template void advance(octet_iterator& it, distance_type n, octet_iterator end) { for (distance_type i = 0; i < n; ++i) { next(it, end); } } template typename std::iterator_traits::difference_type distance(octet_iterator first, octet_iterator last) { typename std::iterator_traits::difference_type dist; for (dist = 0; first < last; ++dist) { next(first, last); } return dist; } template octet_iterator utf16to8(u16bit_iterator start, u16bit_iterator end, octet_iterator result) { while (start != end) { uint32_t cp = *start++; // Take care of surrogate pairs first if (internal::is_surrogate(cp)) { if (start == end) { throw invalid_utf16(*start); } uint32_t trail_surrogate = *start++; if (trail_surrogate < internal::TRAIL_SURROGATE_MIN || trail_surrogate > internal::TRAIL_SURROGATE_MAX) { throw invalid_utf16(trail_surrogate); } cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; } result = append(cp, result); } return result; } template u16bit_iterator utf8to16(octet_iterator start, octet_iterator end, u16bit_iterator result) { while (start != end) { uint32_t cp = next(start, end); if (cp > 0xffff) { // make a surrogate pair *result++ = (cp >> 10) + internal::LEAD_OFFSET; *result++ = (cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN; } else { *result++ = cp; } } return result; } template octet_iterator utf32to8(u32bit_iterator start, u32bit_iterator end, octet_iterator result) { while (start != end) { result = append(*start++, result); } return result; } template u32bit_iterator utf8to32(octet_iterator start, octet_iterator end, u32bit_iterator result) { while (start < end) { *result++ = next(start, end); } return result; } // The iterator class template class iterator : public std::iterator { octet_iterator it; octet_iterator range_start; octet_iterator range_end; public: iterator() {}; iterator(const octet_iterator& octet_it, const octet_iterator& range_start, const octet_iterator& range_end) : it(octet_it) , range_start(range_start) , range_end(range_end) { if (it < range_start || it > range_end) { throw std::out_of_range("Invalid utf-8 iterator position"); } } // the default "big three" are OK octet_iterator base() const { return it; } uint32_t operator*() const { auto temp = it; return next(temp, range_end); } bool operator==(const iterator& rhs) const { if ((range_start != rhs.range_start) || (range_end != rhs.range_end)) { throw std::logic_error( "Comparing utf-8 iterators defined with different ranges"); } return it == rhs.it; } bool operator!=(const iterator& rhs) const { return !(operator==(rhs)); } iterator& operator++() { next(it, range_end); return *this; } iterator operator++(int) { auto temp = *this; next(it, range_end); return temp; } iterator& operator--() { prior(it, range_start); return *this; } iterator operator--(int) { auto temp = *this; prior(it, range_start); return temp; } }; #ifdef _WIN32 std::string unknowntoutf8(const std::string& unknown); std::string utf8toansi(const std::string& utf8); std::wstring utf8to16(const std::string& utf8); std::string utf16to8(const std::wstring& utf16); #endif } // namespace utf8 #endif openMSX-RELEASE_0_12_0/src/utils/utf8_core.hh000066400000000000000000000140601257557151200206060ustar00rootroot00000000000000// UTF8-CPP http://utfcpp.sourceforge.net/ // Slightly simplified (and reformatted) to fit openMSX coding style. // Copyright 2006 Nemanja Trifunovic /* Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef UTF8_CORE_HH #define UTF8_CORE_HH #include #include namespace utf8 { // Helper code - not intended to be directly called by the library users. // May be changed at any time namespace internal { // Unicode constants // Leading (high) surrogates: 0xd800 - 0xdbff // Trailing (low) surrogates: 0xdc00 - 0xdfff const uint16_t LEAD_SURROGATE_MIN = 0xd800u; const uint16_t LEAD_SURROGATE_MAX = 0xdbffu; const uint16_t TRAIL_SURROGATE_MIN = 0xdc00u; const uint16_t TRAIL_SURROGATE_MAX = 0xdfffu; const uint16_t LEAD_OFFSET = LEAD_SURROGATE_MIN - (0x10000 >> 10); const uint32_t SURROGATE_OFFSET = 0x10000u - (LEAD_SURROGATE_MIN << 10) - TRAIL_SURROGATE_MIN; // Maximum valid value for a Unicode code point const uint32_t CODE_POINT_MAX = 0x0010ffffu; inline bool is_trail(uint8_t oc) { return (oc >> 6) == 0x2; } inline bool is_surrogate(uint16_t cp) { return (cp >= LEAD_SURROGATE_MIN) && (cp <= TRAIL_SURROGATE_MAX); } inline bool is_code_point_valid(uint32_t cp) { return (cp <= CODE_POINT_MAX) && !is_surrogate(cp) && (cp != 0xfffe) && (cp != 0xffff); } inline unsigned sequence_length(uint8_t lead) { if (lead < 0x80) { return 1; } else if ((lead >> 5) == 0x06) { return 2; } else if ((lead >> 4) == 0x0e) { return 3; } else if ((lead >> 3) == 0x1e) { return 4; } else { return 0; } } enum utf_error { OK, NOT_ENOUGH_ROOM, INVALID_LEAD, INCOMPLETE_SEQUENCE, OVERLONG_SEQUENCE, INVALID_CODE_POINT }; template utf_error validate_next(octet_iterator& it, octet_iterator end, uint32_t* code_point) { uint32_t cp = *it; // Check the lead octet int length = sequence_length(*it); // "Shortcut" for ASCII characters if (length == 1) { if (end - it <= 0) { return NOT_ENOUGH_ROOM; } if (code_point) { *code_point = cp; } ++it; return OK; } // Do we have enough memory? if (std::distance(it, end) < length) { return NOT_ENOUGH_ROOM; } // Check trail octets and calculate the code point switch (length) { case 0: return INVALID_LEAD; case 2: if (is_trail(*(++it))) { cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f); } else { --it; return INCOMPLETE_SEQUENCE; } break; case 3: if (is_trail(*(++it))) { cp = ((cp << 12) & 0xffff) + ((*it << 6) & 0xfff); if (is_trail(*(++it))) { cp += (*it) & 0x3f; } else { std::advance(it, -2); return INCOMPLETE_SEQUENCE; } } else { --it; return INCOMPLETE_SEQUENCE; } break; case 4: if (is_trail(*(++it))) { cp = ((cp << 18) & 0x1fffff) + ((*it << 12) & 0x3ffff); if (is_trail(*(++it))) { cp += (*it << 6) & 0xfff; if (is_trail(*(++it))) { cp += (*it) & 0x3f; } else { std::advance(it, -3); return INCOMPLETE_SEQUENCE; } } else { std::advance(it, -2); return INCOMPLETE_SEQUENCE; } } else { --it; return INCOMPLETE_SEQUENCE; } break; } // Is the code point valid? if (!is_code_point_valid(cp)) { for (int i = 0; i < length - 1; ++i) { --it; } return INVALID_CODE_POINT; } if (code_point) { *code_point = cp; } if (cp < 0x80) { if (length != 1) { std::advance(it, -(length-1)); return OVERLONG_SEQUENCE; } } else if (cp < 0x800) { if (length != 2) { std::advance(it, -(length-1)); return OVERLONG_SEQUENCE; } } else if (cp < 0x10000) { if (length != 3) { std::advance(it, -(length-1)); return OVERLONG_SEQUENCE; } } ++it; return OK; } template inline utf_error validate_next(octet_iterator& it, octet_iterator end) { return validate_next(it, end, nullptr); } } // namespace internal /// The library API - functions intended to be called by the users // Byte order mark const uint8_t bom[] = { 0xef, 0xbb, 0xbf }; template octet_iterator find_invalid(octet_iterator start, octet_iterator end) { auto result = start; while (result != end) { internal::utf_error err_code = internal::validate_next(result, end); if (err_code != internal::OK) { return result; } } return result; } template inline bool is_valid(octet_iterator start, octet_iterator end) { return find_invalid(start, end) == end; } template inline bool is_bom(octet_iterator it) { return ((*it++ == bom[0]) && (*it++ == bom[1]) && (*it == bom[2])); } template inline octet_iterator sync_forward(octet_iterator it) { while (internal::is_trail(*it)) ++it; return it; } template inline octet_iterator sync_backward(octet_iterator it) { while (internal::is_trail(*it)) --it; return it; } } // namespace utf8 #endif openMSX-RELEASE_0_12_0/src/utils/utf8_unchecked.hh000066400000000000000000000144531257557151200216150ustar00rootroot00000000000000// UTF8-CPP http://utfcpp.sourceforge.net/ // Slightly simplified (and reformatted) to fit openMSX coding style. // Copyright 2006 Nemanja Trifunovic /* Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef UTF8_UNCHECKED_HH #define UTF8_UNCHECKED_HH #include "utf8_core.hh" #include "string_ref.hh" namespace utf8 { namespace unchecked { template octet_iterator append(uint32_t cp, octet_iterator result) { if (cp < 0x80) { // one octet *result++ = cp; } else if (cp < 0x800) { // two octets *result++ = ((cp >> 6) ) | 0xc0; *result++ = ((cp >> 0) & 0x3f) | 0x80; } else if (cp < 0x10000) { // three octets *result++ = ((cp >> 12) ) | 0xe0; *result++ = ((cp >> 6) & 0x3f) | 0x80; *result++ = ((cp >> 0) & 0x3f) | 0x80; } else { // four octets *result++ = ((cp >> 18) ) | 0xf0; *result++ = ((cp >> 12) & 0x3f) | 0x80; *result++ = ((cp >> 6) & 0x3f) | 0x80; *result++ = ((cp >> 0) & 0x3f) | 0x80; } return result; } template uint32_t next(octet_iterator& it) { uint32_t cp = *it; switch (utf8::internal::sequence_length(cp)) { case 1: break; case 2: ++it; cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f); break; case 3: ++it; cp = ((cp << 12) & 0xffff) + ((*it << 6) & 0xfff); ++it; cp += (*it) & 0x3f; break; case 4: ++it; cp = ((cp << 18) & 0x1fffff) + ((*it << 12) & 0x3ffff); ++it; cp += (*it << 6) & 0xfff; ++it; cp += (*it) & 0x3f; break; } ++it; return cp; } template uint32_t peek_next(octet_iterator it) { return next(it); } template uint32_t prior(octet_iterator& it) { while (internal::is_trail(*(--it))) ; auto temp = it; return next(temp); } template void advance(octet_iterator& it, distance_type n) { for (distance_type i = 0; i < n; ++i) { unchecked::next(it); } } template typename std::iterator_traits::difference_type distance(octet_iterator first, octet_iterator last) { typename std::iterator_traits::difference_type dist; for (dist = 0; first < last; ++dist) { unchecked::next(first); } return dist; } template octet_iterator utf16to8(u16bit_iterator start, u16bit_iterator end, octet_iterator result) { while (start != end) { uint32_t cp = *start++; // Take care of surrogate pairs first if (internal::is_surrogate(cp)) { uint32_t trail_surrogate = *start++; cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; } result = append(cp, result); } return result; } template u16bit_iterator utf8to16(octet_iterator start, octet_iterator end, u16bit_iterator result) { while (start != end) { uint32_t cp = next(start); if (cp > 0xffff) { // make a surrogate pair *result++ = (cp >> 10) + internal::LEAD_OFFSET; *result++ = (cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN; } else { *result++ = cp; } } return result; } template octet_iterator utf32to8(u32bit_iterator start, u32bit_iterator end, octet_iterator result) { while (start != end) { result = append(*start++, result); } return result; } template u32bit_iterator utf8to32(octet_iterator start, octet_iterator end, u32bit_iterator result) { while (start < end) { *result++ = next(start); } return result; } // The iterator class template class iterator : public std::iterator { octet_iterator it; public: iterator() {}; explicit iterator(const octet_iterator& octet_it) : it(octet_it) {} // the default "big three" are OK octet_iterator base() const { return it; } uint32_t operator*() const { octet_iterator temp = it; return next(temp); } bool operator==(const iterator& rhs) const { return it == rhs.it; } bool operator!=(const iterator& rhs) const { return !(operator==(rhs)); } iterator& operator++() { std::advance(it, internal::sequence_length(*it)); return *this; } iterator operator++(int) { auto temp = *this; std::advance(it, internal::sequence_length(*it)); return temp; } iterator& operator--() { prior(it); return *this; } iterator operator--(int) { auto temp = *this; prior(it); return temp; } }; // convenience functions inline size_t size(string_ref utf8) { return utf8::unchecked::distance(begin(utf8), end(utf8)); } inline string_ref substr(string_ref utf8, string_ref::size_type first = 0, string_ref::size_type len = string_ref::npos) { auto b = begin(utf8); utf8::unchecked::advance(b, first); string_ref::const_iterator e; if (len != string_ref::npos) { e = b; while (len && (e != end(utf8))) { unchecked::next(e); --len; } } else { e = end(utf8); } return string_ref(b, e); } } // namespace unchecked } // namespace utf8 #endif openMSX-RELEASE_0_12_0/src/utils/vla.hh000066400000000000000000000030431257557151200174710ustar00rootroot00000000000000#ifndef VLA_HH #define VLA_HH // VLAs (Variable Length Array's) are part of C99, but not of C++98. // Though gcc does support VLAs as an extension. For VC++ we have to emulate // them via alloca. #ifndef _MSC_VER #define VLA(TYPE, NAME, LENGTH) \ TYPE NAME[(LENGTH)] #if defined __i386 || defined __x86_64 #define VLA_ALIGNED(TYPE, NAME, LENGTH, ALIGNMENT) \ TYPE NAME[(LENGTH)] __attribute__((__aligned__((ALIGNMENT)))) #else // Except on x86/x86-64, GCC can align VLAs within a stack frame, but it makes // no guarantees if the requested alignment is larger than the alignment of the // stack frame itself. // http://gcc.gnu.org/bugzilla/show_bug.cgi?id=16660 #define VLA_ALIGNED(TYPE, NAME, LENGTH, ALIGNMENT) \ UNABLE_TO_GUARANTEE_VLA_ALIGNMENT_ON_THIS_ARCHITECTURE #endif #else #define VLA(TYPE, NAME, LENGTH) \ auto NAME = static_cast(_alloca(sizeof(TYPE) * (LENGTH))) // mfeingol: evil hack alert #define VLA_ALIGNED(TYPE, NAME, LENGTH, ALIGNMENT) \ size_t cbAlign##NAME = (ALIGNMENT); \ void* palloc##NAME = _alloca(sizeof(TYPE) * (LENGTH) + cbAlign##NAME); \ palloc##NAME = (void*)((size_t(palloc##NAME) + cbAlign##NAME - 1UL) & ~(cbAlign##NAME - 1UL)); \ auto NAME = static_cast(palloc##NAME); \ #endif // Macro to align a buffer that might be used by SSE instructions. // On platforms without SSE no (extra) alignment is performed. #ifdef __SSE2__ #define VLA_SSE_ALIGNED(TYPE, NAME, LENGTH) VLA_ALIGNED(TYPE, NAME, LENGTH, 16) #else #define VLA_SSE_ALIGNED(TYPE, NAME, LENGTH) VLA(TYPE, NAME, LENGTH) #endif #endif // VLA_HH openMSX-RELEASE_0_12_0/src/utils/win32-arggen.cc000066400000000000000000000013121257557151200210750ustar00rootroot00000000000000#ifdef _WIN32 #include "win32-arggen.hh" #include "MSXException.hh" #include "utf8_checked.hh" #include #include namespace openmsx { ArgumentGenerator::~ArgumentGenerator() { for (int i = 0; i < argc; ++i) { free(argv[i]); } } char** ArgumentGenerator::GetArguments(int& argc_) { if (argv.empty()) { LPWSTR* pszArglist = CommandLineToArgvW(GetCommandLineW(), &argc); if (!pszArglist) { throw MSXException("Failed to obtain command line arguments"); } argv.resize(argc); for (int i = 0; i < argc; ++i) { argv[i] = strdup(utf8::utf16to8(pszArglist[i]).c_str()); } LocalFree(pszArglist); } argc_ = argc; return argv.data(); } } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/utils/win32-arggen.hh000066400000000000000000000004661257557151200211200ustar00rootroot00000000000000#ifndef WIN32_ARG_GEN_HH #define WIN32_ARG_GEN_HH #ifdef _WIN32 #include "MemBuffer.hh" namespace openmsx { class ArgumentGenerator { public: ~ArgumentGenerator(); char** GetArguments(int& argc); private: MemBuffer argv; int argc; }; #endif } // namespace openmsx #endif // WIN32_ARG_GEN_HH openMSX-RELEASE_0_12_0/src/utils/win32-dirent.cc000066400000000000000000000064601257557151200211300ustar00rootroot00000000000000/* Copyright (C) 2001, 2006 Free Software Foundation, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // NB: Taken from http://www.koders.com/c/fidB38A2E97F60B8A0C903631F8C60078DE8C6C8433.aspx // Also modified to use Unicode calls explicitly // Slightly reformatted/simplified to fit openMSX coding style. #ifdef _WIN32 #include "win32-dirent.hh" #include "utf8_checked.hh" #include "MSXException.hh" #include "StringOp.hh" #include "countof.hh" #include #include #include #include namespace openmsx { DIR* opendir(const char* name) { if (!name || !*name) return nullptr; std::wstring nameW = utf8::utf8to16(name); if (!StringOp::endsWith(name, '/') && !StringOp::endsWith(name, "\\")) { nameW += L"\\*"; } else { nameW += L"*"; } HANDLE hnd; WIN32_FIND_DATAW find; if ((hnd = FindFirstFileW(nameW.c_str(), &find)) == INVALID_HANDLE_VALUE) { return nullptr; } DIR* dir = new DIR; dir->mask = nameW; dir->fd = reinterpret_cast(hnd); dir->data = new WIN32_FIND_DATAW; dir->filepos = 0; memcpy(dir->data, &find, sizeof(find)); return dir; } dirent* readdir(DIR* dir) { static dirent entry; entry.d_ino = 0; entry.d_type = 0; auto find = static_cast(dir->data); if (dir->filepos) { if (!FindNextFileW(reinterpret_cast(dir->fd), find)) { return nullptr; } } std::string d_name = utf8::utf16to8(find->cFileName); strncpy(entry.d_name, d_name.c_str(), countof(entry.d_name)); entry.d_off = dir->filepos; entry.d_reclen = static_cast(strlen(entry.d_name)); dir->filepos++; return &entry; } int closedir(DIR* dir) { auto hnd = reinterpret_cast(dir->fd); delete static_cast(dir->data); delete dir; return FindClose(hnd) ? 0 : -1; } void rewinddir(DIR* dir) { auto hnd = reinterpret_cast(dir->fd); auto find = static_cast(dir->data); FindClose(hnd); hnd = FindFirstFileW(dir->mask.c_str(), find); dir->fd = reinterpret_cast(hnd); dir->filepos = 0; } void seekdir(DIR* dir, off_t offset) { rewinddir(dir); for (off_t n = 0; n < offset; ++n) { if (FindNextFileW(reinterpret_cast(dir->fd), static_cast(dir->data))) { dir->filepos++; } } } off_t telldir(DIR* dir) { return dir->filepos; } // For correctness on 64-bit Windows, this function would need to maintain an // internal map of ints to HANDLEs, since HANDLEs are sizeof(void*). It's not // used at the moment in the openMSX sources, so let's not bother for now. /*int dirfd(DIR* dir) { return dir->fd; }*/ } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/utils/win32-dirent.hh000066400000000000000000000037651257557151200211470ustar00rootroot00000000000000#ifndef WIN32_DIRENT_HH #define WIN32_DIRENT_HH /* Copyright (C) 2001, 2006 Free Software Foundation, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* Directory stream type. The miscellaneous Unix `readdir' implementations read directory data into a buffer and return `struct dirent *' pointers into it. */ // NB: Taken from http://www.koders.com/c/fid5F00BC983E0F005E15E8E590E461D363F6146CEB.aspx // Slightly reformatted/simplified to fit openMSX coding style. #ifdef _WIN32 #include #include // for INT_PTR #include namespace openmsx { struct dirstream { INT_PTR fd; // File descriptor. void* data; // Directory block. off_t filepos; // Position of next entry to read. std::wstring mask; // Initial file mask. }; struct dirent { long d_ino; off_t d_off; unsigned short d_reclen; unsigned char d_type; char d_name[256]; }; #define d_fileno d_ino // Backwards compatibility. // This is the data type of directory stream objects. // The actual structure is opaque to users. using DIR = struct dirstream; DIR* opendir(const char* name); struct dirent* readdir(DIR* dir); int closedir(DIR* dir); void rewinddir(DIR* dir); void seekdir(DIR* dir, off_t offset); off_t telldir(DIR* dir); //int dirfd(DIR* dir); } // namespace openmsx #endif #endif // WIN32_DIRENT_HH openMSX-RELEASE_0_12_0/src/utils/win32-windowhandle.cc000066400000000000000000000012561257557151200223240ustar00rootroot00000000000000#ifdef _WIN32 #include "win32-windowhandle.hh" #include "MSXException.hh" #include "StringOp.hh" #include #ifdef WIN32_LEAN_AND_MEAN #undef WIN32_LEAN_AND_MEAN // For , which defines it carelessly #endif #include namespace openmsx { HWND getWindowHandle() { // There is no window handle until the video subsystem has been initialized. if (!SDL_WasInit(SDL_INIT_VIDEO)) { SDL_InitSubSystem(SDL_INIT_VIDEO); } SDL_SysWMinfo info; SDL_VERSION(&info.version); if (SDL_GetWMInfo(&info) != 1) { throw MSXException(StringOp::Builder() << "SDL_GetWMInfo failed: " << SDL_GetError()); } return info.window; } } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/utils/win32-windowhandle.hh000066400000000000000000000003231257557151200223300ustar00rootroot00000000000000#ifndef WIN32_WINDOW_HANDLE_HH #define WIN32_WINDOW_HANDLE_HH #ifdef _WIN32 #include namespace openmsx { HWND getWindowHandle(); } // namespace openmsx #endif #endif // WIN32_WINDOW_HANDLE_HH openMSX-RELEASE_0_12_0/src/utils/xrange.hh000066400000000000000000000040551257557151200201770ustar00rootroot00000000000000#ifndef XRANGE_HH #define XRANGE_HH // Utility to iterate over a range of numbers, // modeled after python's xrange() function. // // Typically used as a more compact notation for some for-loop patterns: // // a) loop over [0, N) // // auto num = expensiveFunctionCall(); // for (decltype(num) i = 0; i < num; ++i) { ... } // // for (auto i : xrange(expensiveFunctionCall()) { ... } // // b) loop over [B, E) // // auto i = func1(); // auto end = func2(); // for (/**/; i < end; ++i) { ... } // // for (auto i : xrange(func1(), func2()) { ... } // // Note that when using the xrange utility you also don't need to worry about // the correct type of the induction variable. // // Gcc is able to optimize the xrange() based loops to the same code as the // equivalent 'old-style' for loop. // // In an earlier version of this utility I also implemented the possibility to // specify a step size (currently the step size is always +1). Although I // believe that code was correct I still removed it because it was quite tricky // (getting the stop condition correct is not trivial) and we don't need it // currently. template class XRange { public: class XRangeIter { public: using difference_type = size_t; using value_type = T; using pointer = T*; using reference = T&; using iterator_category = std::forward_iterator_tag; XRangeIter(T x_) : x(x_) { } T operator*() const { return x; } XRangeIter& operator++() { ++x; return *this; } bool operator==(const XRangeIter& other) const { return x == other.x; } bool operator!=(const XRangeIter& other) const { return x != other.x; } private: T x; }; XRange(T e_) : b(0), e(e_) { } XRange(T b_, T e_) : b(b_), e(e_) { } XRangeIter begin() const { return XRangeIter(b); } XRangeIter end() const { return XRangeIter(e); } private: const T b; const T e; }; template inline XRange xrange(T e) { return XRange(e); } template inline XRange xrange(T b, T e) { return XRange(b, e); } #endif openMSX-RELEASE_0_12_0/src/utils/xxhash.hh000066400000000000000000000112561257557151200202170ustar00rootroot00000000000000/* This code is a based on xxhash, the main modifications are: - allow to produce case-insensitive (for ascii) hash values - tweak interface to fit openMSX (e.g. directly work on string_ref) - - - - - - - - - - - - - - - - - - - - original header: xxHash - Fast Hash algorithm Header File Copyright (C) 2012-2014, Yann Collet. BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. You can contact the author at : - xxHash source repository : http://code.google.com/p/xxhash/ */ #ifndef XXHASH_HH #define XXHASH_HH #include "string_ref.hh" #include "likely.hh" #include "build-info.hh" #include #include static const uint32_t PRIME32_1 = 2654435761; static const uint32_t PRIME32_2 = 2246822519; static const uint32_t PRIME32_3 = 3266489917; static const uint32_t PRIME32_4 = 668265263; static const uint32_t PRIME32_5 = 374761393; template static inline uint32_t rotl(uint32_t x) { return (x << R) | (x >> (32 - R)); } template static inline uint32_t read32(const uint8_t* ptr) { if (ALIGNED) { return *reinterpret_cast(ptr); } else { uint32_t result; memcpy(&result, ptr, sizeof(result)); return result; } } template static inline uint32_t xxhash_impl(const uint8_t* p, size_t size) { static const uint32_t MASK32 = MASK8 * 0x01010101U; const uint8_t* const bEnd = p + size; uint32_t h32; if (unlikely(size >= 16)) { const uint8_t* const limit = bEnd - 16; uint32_t v1 = SEED + PRIME32_1 + PRIME32_2; uint32_t v2 = SEED + PRIME32_2; uint32_t v3 = SEED + 0; uint32_t v4 = SEED - PRIME32_1; do { uint32_t r1 = (read32(p + 0) & MASK32) * PRIME32_2; uint32_t r2 = (read32(p + 4) & MASK32) * PRIME32_2; uint32_t r3 = (read32(p + 8) & MASK32) * PRIME32_2; uint32_t r4 = (read32(p + 12) & MASK32) * PRIME32_2; p += 16; v1 = rotl<13>(v1 + r1) * PRIME32_1; v2 = rotl<13>(v2 + r2) * PRIME32_1; v3 = rotl<13>(v3 + r3) * PRIME32_1; v4 = rotl<13>(v4 + r4) * PRIME32_1; } while (p <= limit); h32 = rotl<1>(v1) + rotl<7>(v2) + rotl<12>(v3) + rotl<18>(v4); } else { h32 = SEED + PRIME32_5; } h32 += uint32_t(size); while ((p + 4) <= bEnd) { uint32_t r = (read32(p) & MASK32) * PRIME32_3; h32 = rotl<17>(h32 + r) * PRIME32_4; p += 4; } while (p < bEnd) { uint32_t r = (*p & MASK8) * PRIME32_5; h32 = rotl<11>(h32 + r) * PRIME32_1; p += 1; } h32 = (h32 ^ (h32 >> 15)) * PRIME32_2; h32 = (h32 ^ (h32 >> 13)) * PRIME32_3; return h32 ^ (h32 >> 16); } template static inline uint32_t xxhash_impl(string_ref key) { auto* data = reinterpret_cast(key.data()); auto size = key.size(); if (!openmsx::OPENMSX_UNALIGNED_MEMORY_ACCESS && (reinterpret_cast(data) & 3)) { return xxhash_impl(data, size); } else { // Input is aligned, let's leverage the speed advantage return xxhash_impl(data, size); } } inline uint32_t xxhash(string_ref key) { return xxhash_impl<0xFF>(key); } inline uint32_t xxhash_case(string_ref key) { return xxhash_impl(~('a' - 'A'))>(key); } struct XXHasher { uint32_t operator()(string_ref key) const { return xxhash(key); } }; struct XXHasher_IgnoreCase { uint32_t operator()(string_ref key) const { return xxhash_case(key); } }; #endif openMSX-RELEASE_0_12_0/src/video/000077500000000000000000000000001257557151200163345ustar00rootroot00000000000000openMSX-RELEASE_0_12_0/src/video/.gitignore000066400000000000000000000000151257557151200203200ustar00rootroot00000000000000/GNUmakefile openMSX-RELEASE_0_12_0/src/video/ADVram.cc000066400000000000000000000042171257557151200177610ustar00rootroot00000000000000#include "ADVram.hh" #include "VDP.hh" #include "VDPVRAM.hh" #include "MSXException.hh" #include "serialize.hh" #include namespace openmsx { ADVram::ADVram(const DeviceConfig& config) : MSXDevice(config) , vdp(nullptr) , vram(nullptr) , hasEnable(config.getChildDataAsBool("hasEnable", true)) { reset(EmuTime::dummy()); } void ADVram::init() { MSXDevice::init(); const MSXDevice::Devices& references = getReferences(); if (references.size() != 1) { throw MSXException("Invalid ADVRAM configuration: " "need reference to VDP device."); } vdp = dynamic_cast(references[0]); if (!vdp) { throw MSXException("Invalid ADVRAM configuration: device '" + references[0]->getName() + "' is not a VDP device."); } vram = &vdp->getVRAM(); mask = std::min(vram->getSize(), 128u * 1024) - 1; } void ADVram::reset(EmuTime::param /*time*/) { // TODO figure out exactly what happens during reset baseAddr = 0; planar = false; enabled = !hasEnable; } byte ADVram::readIO(word port, EmuTime::param /*time*/) { // ADVram only gets 'read's from 0x9A if (hasEnable) { enabled = ((port & 0x8000) != 0); planar = ((port & 0x4000) != 0); } else { planar = ((port & 0x0100) != 0); } return 0xFF; } void ADVram::writeIO(word /*port*/, byte value, EmuTime::param /*time*/) { // set mapper register baseAddr = (value & 0x07) << 14; } unsigned ADVram::calcAddress(word address) const { unsigned addr = (address & 0x3FFF) | baseAddr; if (planar) { addr = ((addr & 1) << 16) | (addr >> 1); } return addr & mask; } byte ADVram::readMem(word address, EmuTime::param time) { return enabled ? vram->cpuRead(calcAddress(address), time) : 0xFF; } void ADVram::writeMem(word address, byte value, EmuTime::param time) { if (enabled) { vram->cpuWrite(calcAddress(address), value, time); } } template void ADVram::serialize(Archive& ar, unsigned /*version*/) { ar.template serializeBase(*this); ar.serialize("baseAddr", baseAddr); ar.serialize("enabled", enabled); ar.serialize("planar", planar); } INSTANTIATE_SERIALIZE_METHODS(ADVram); REGISTER_MSXDEVICE(ADVram, "ADVRAM"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/ADVram.hh000066400000000000000000000043011257557151200177650ustar00rootroot00000000000000#ifndef ADVRAM_HH #define ADVRAM_HH #include "MSXDevice.hh" namespace openmsx { class VDP; class VDPVRAM; /** Implementation of direct cpu access to VRAM. ADVram (Accesso * Direito à Vram is a rare hardware modification that allows the * CPU to access the video ram in the same way as ordinary ram. */ class ADVram final : public MSXDevice { public: explicit ADVram(const DeviceConfig& config); /** This method is called on reset. Reset the mapper register and * the planar bit, if the device is configured with an enable bit * then that bit is reset as well. */ void reset(EmuTime::param time) override; /** Read a byte from an IO port, change mode bits. The planar bit * and possibly the enable bit are set according to address lines * that are normally ignored for IO reads. Returns 255. */ byte readIO(word port, EmuTime::param time) override; // default peekIO() implementation is ok. /** Write a byte to a given IO port, set mapper register. */ void writeIO(word port, byte value, EmuTime::param time) override; /** Read a byte from a location in the video ram at a certain * time. If the device is enabled then the value returned comes * from the video ram, otherwise it returns 255. */ byte readMem(word address, EmuTime::param time) override; /** Write a given byte at a certain time to a given location in * the video ram. If the device is enabled then the write is * redirected to the video ram, if it is not, nothing happens. */ void writeMem(word address, byte value, EmuTime::param time) override; template void serialize(Archive& ar, unsigned version); private: void init() override; inline unsigned calcAddress(word address) const; VDP* vdp; VDPVRAM* vram; /** Bit mask applied to logical addresses, reflects vram size. */ /*const*/ unsigned mask; /** Base address of selected page in vram. */ unsigned baseAddr; /** True if ADVram device is enabled. */ bool enabled; /** True if ADVram device can be switched on or off by performing IO-reads. */ const bool hasEnable; /** True if address bits are shuffled as is appropriate for screen 7 and higher. */ bool planar; }; } // namespace openmsx #endif // ADVRAM_HH openMSX-RELEASE_0_12_0/src/video/AviRecorder.cc000066400000000000000000000233051257557151200210530ustar00rootroot00000000000000#include "AviRecorder.hh" #include "AviWriter.hh" #include "WavWriter.hh" #include "Reactor.hh" #include "MSXMotherBoard.hh" #include "FileContext.hh" #include "CommandException.hh" #include "Display.hh" #include "PostProcessor.hh" #include "MSXMixer.hh" #include "Filename.hh" #include "CliComm.hh" #include "FileOperations.hh" #include "TclObject.hh" #include "memory.hh" #include "outer.hh" #include "vla.hh" #include using std::string; using std::vector; namespace openmsx { AviRecorder::AviRecorder(Reactor& reactor_) : reactor(reactor_) , recordCommand(reactor.getCommandController()) , mixer(nullptr) , duration(EmuDuration::infinity) , prevTime(EmuTime::infinity) , frameHeight(0) { } AviRecorder::~AviRecorder() { assert(!aviWriter); assert(!wavWriter); } void AviRecorder::start(bool recordAudio, bool recordVideo, bool recordMono, bool recordStereo, const Filename& filename) { stop(); MSXMotherBoard* motherBoard = reactor.getMotherBoard(); if (!motherBoard) { throw CommandException("No active MSX machine."); } if (recordAudio) { mixer = &motherBoard->getMSXMixer(); warnedStereo = false; if (recordStereo) { stereo = true; } else if (recordMono) { stereo = false; warnedStereo = true; // no warning if data is actually stereo } else { stereo = mixer->needStereoRecording(); } sampleRate = mixer->getSampleRate(); warnedSampleRate = false; } if (recordVideo) { // Set V99x8, V9990, Laserdisc, ... in record mode (when // present). Only the active one will actually send frames to // the video. This also works for Video9000. postProcessors.clear(); for (auto* l : reactor.getDisplay().getAllLayers()) { if (auto* pp = dynamic_cast(l)) { postProcessors.push_back(pp); } } if (postProcessors.empty()) { throw CommandException( "Current renderer doesn't support video recording."); } // any source is fine because they all have the same bpp unsigned bpp = postProcessors.front()->getBpp(); warnedFps = false; duration = EmuDuration::infinity; prevTime = EmuTime::infinity; try { aviWriter = make_unique( filename, frameWidth, frameHeight, bpp, (recordAudio && stereo) ? 2 : 1, sampleRate); } catch (MSXException& e) { throw CommandException("Can't start recording: " + e.getMessage()); } } else { assert(recordAudio); wavWriter = make_unique( filename, stereo ? 2 : 1, sampleRate); } // only set recorders when all errors are checked for for (auto* pp : postProcessors) { pp->setRecorder(this); } if (mixer) mixer->setRecorder(this); } void AviRecorder::stop() { for (auto* pp : postProcessors) { pp->setRecorder(nullptr); } postProcessors.clear(); if (mixer) { mixer->setRecorder(nullptr); mixer = nullptr; } sampleRate = 0; aviWriter.reset(); wavWriter.reset(); } void AviRecorder::addWave(unsigned num, short* data) { if (!warnedSampleRate && (mixer->getSampleRate() != sampleRate)) { warnedSampleRate = true; reactor.getCliComm().printWarning( "Detected audio sample frequency change during " "avi recording. Audio/video might get out of sync " "because of this."); } if (stereo) { if (wavWriter) { wavWriter->write(data, 2, num); } else { assert(aviWriter); audioBuf.insert(end(audioBuf), data, data + 2 * num); } } else { VLA(short, buf, num); unsigned i = 0; for (/**/; !warnedStereo && i < num; ++i) { if (data[2 * i + 0] != data[2 * i + 1]) { reactor.getCliComm().printWarning( "Detected stereo sound during mono recording. " "Channels will be mixed down to mono. To " "avoid this warning you can explicity pass the " "-mono or -stereo flag to the record command."); warnedStereo = true; break; } buf[i] = data[2 * i]; } for (/**/; i < num; ++i) { buf[i] = (int(data[2 * i + 0]) + int(data[2 * i + 1])) / 2; } if (wavWriter) { wavWriter->write(buf, 1, num); } else { assert(aviWriter); audioBuf.insert(end(audioBuf), buf, buf + num); } } } void AviRecorder::addImage(FrameSource* frame, EmuTime::param time) { assert(!wavWriter); if (duration != EmuDuration::infinity) { if (!warnedFps && ((time - prevTime) != duration)) { warnedFps = true; reactor.getCliComm().printWarning( "Detected frame rate change (PAL/NTSC or frameskip) " "during avi recording. Audio/video might get out of " "sync because of this."); } } else if (prevTime != EmuTime::infinity) { duration = time - prevTime; aviWriter->setFps(1.0 / duration.toDouble()); } prevTime = time; if (mixer) { mixer->updateStream(time); } aviWriter->addFrame(frame, unsigned(audioBuf.size()), audioBuf.data()); audioBuf.clear(); } // TODO: Can this be dropped? unsigned AviRecorder::getFrameHeight() const { assert (frameHeight != 0); // someone uses the getter too early? return frameHeight; } void AviRecorder::processStart(array_ref tokens, TclObject& result) { string filename; string prefix = "openmsx"; bool recordAudio = true; bool recordVideo = true; bool recordMono = false; bool recordStereo = false; frameWidth = 320; frameHeight = 240; vector arguments; for (unsigned i = 2; i < tokens.size(); ++i) { string_ref token = tokens[i].getString(); if (token.starts_with("-")) { if (token == "--") { for (auto it = std::begin(tokens) + i + 1; it != std::end(tokens); ++it) { arguments.push_back(it->getString().str()); } break; } if (token == "-prefix") { if (++i == tokens.size()) { throw CommandException("Missing argument"); } prefix = tokens[i].getString().str(); } else if (token == "-audioonly") { recordVideo = false; } else if (token == "-mono") { recordMono = true; } else if (token == "-stereo") { recordStereo = true; } else if (token == "-videoonly") { recordAudio = false; } else if (token == "-doublesize") { frameWidth = 640; frameHeight = 480; } else if (token == "-triplesize") { frameWidth = 960; frameHeight = 720; } else { throw CommandException("Invalid option: " + token); } } else { arguments.push_back(token.str()); } } if (!recordAudio && !recordVideo) { throw CommandException("Can't have both -videoonly and -audioonly."); } if (recordStereo && recordMono) { throw CommandException("Can't have both -mono and -stereo."); } if (!recordAudio && (recordStereo || recordMono)) { throw CommandException("Can't have both -videoonly and -stereo or -mono."); } switch (arguments.size()) { case 0: // nothing break; case 1: filename = arguments[0]; break; default: throw SyntaxError(); } string directory = recordVideo ? "videos" : "soundlogs"; string extension = recordVideo ? ".avi" : ".wav"; filename = FileOperations::parseCommandFileArgument( filename, directory, prefix, extension); if (aviWriter || wavWriter) { result.setString("Already recording."); } else { start(recordAudio, recordVideo, recordMono, recordStereo, Filename(filename)); result.setString("Recording to " + filename); } } void AviRecorder::processStop(array_ref tokens) { if (tokens.size() != 2) { throw SyntaxError(); } stop(); } void AviRecorder::processToggle(array_ref tokens, TclObject& result) { if (aviWriter || wavWriter) { // drop extra tokens processStop(make_array_ref(tokens.data(), 2)); } else { processStart(tokens, result); } } void AviRecorder::status(array_ref tokens, TclObject& result) const { if (tokens.size() != 2) { throw SyntaxError(); } result.addListElement("status"); if (aviWriter || wavWriter) { result.addListElement("recording"); } else { result.addListElement("idle"); } } // class AviRecorder::Cmd AviRecorder::Cmd::Cmd(CommandController& commandController) : Command(commandController, "record") { } void AviRecorder::Cmd::execute(array_ref tokens, TclObject& result) { if (tokens.size() < 2) { throw CommandException("Missing argument"); } auto& recorder = OUTER(AviRecorder, recordCommand); const string_ref subcommand = tokens[1].getString(); if (subcommand == "start") { recorder.processStart(tokens, result); } else if (subcommand == "stop") { recorder.processStop(tokens); } else if (subcommand == "toggle") { recorder.processToggle(tokens, result); } else if (subcommand == "status") { recorder.status(tokens, result); } else { throw SyntaxError(); } } string AviRecorder::Cmd::help(const vector& /*tokens*/) const { return "Controls video recording: Write openMSX audio/video to a .avi file.\n" "record start Record to file 'openmsxNNNN.avi'\n" "record start Record to given file\n" "record start -prefix foo Record to file 'fooNNNN.avi'\n" "record stop Stop recording\n" "record toggle Toggle recording (useful as keybinding)\n" "record status Query recording state\n" "\n" "The start subcommand also accepts an optional -audioonly, -videoonly, " " -mono, -stereo, -doublesize flag.\n" "Videos are recorded in a 320x240 size by default, at 640x480 when the " "-doublesize flag is used and at 960x720 when the -triplesize flag is used."; } void AviRecorder::Cmd::tabCompletion(vector& tokens) const { if (tokens.size() == 2) { static const char* const cmds[] = { "start", "stop", "toggle", "status", }; completeString(tokens, cmds); } else if ((tokens.size() >= 3) && (tokens[1] == "start")) { static const char* const options[] = { "-prefix", "-videoonly", "-audioonly", "-doublesize", "-triplesize", "-mono", "-stereo", }; completeFileName(tokens, userFileContext(), options); } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/AviRecorder.hh000066400000000000000000000033171257557151200210660ustar00rootroot00000000000000#ifndef AVIRECORDER_HH #define AVIRECORDER_HH #include "Command.hh" #include "EmuTime.hh" #include "array_ref.hh" #include "noncopyable.hh" #include #include namespace openmsx { class Reactor; class AviWriter; class Wav16Writer; class Filename; class PostProcessor; class FrameSource; class MSXMixer; class TclObject; class AviRecorder : private noncopyable { public: explicit AviRecorder(Reactor& reactor); ~AviRecorder(); void addWave(unsigned num, short* data); void addImage(FrameSource* frame, EmuTime::param time); void stop(); unsigned getFrameHeight() const; private: void start(bool recordAudio, bool recordVideo, bool recordMono, bool recordStereo, const Filename& filename); void status(array_ref tokens, TclObject& result) const; void processStart (array_ref tokens, TclObject& result); void processStop (array_ref tokens); void processToggle(array_ref tokens, TclObject& result); Reactor& reactor; struct Cmd final : Command { Cmd(CommandController& commandController); void execute(array_ref tokens, TclObject& result) override; std::string help(const std::vector& tokens) const override; void tabCompletion(std::vector& tokens) const override; } recordCommand; std::vector audioBuf; std::unique_ptr aviWriter; // can be nullptr std::unique_ptr wavWriter; // can be nullptr std::vector postProcessors; MSXMixer* mixer; EmuDuration duration; EmuTime prevTime; unsigned sampleRate; unsigned frameWidth; unsigned frameHeight; bool warnedFps; bool warnedSampleRate; bool warnedStereo; bool stereo; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/AviWriter.cc000066400000000000000000000231251257557151200205620ustar00rootroot00000000000000// Code based on DOSBox-0.65 #include "AviWriter.hh" #include "FileOperations.hh" #include "MSXException.hh" #include "memory.hh" #include "build-info.hh" #include "Version.hh" #include "cstdiop.hh" // for snprintf #include #include #include namespace openmsx { static const unsigned AVI_HEADER_SIZE = 500; AviWriter::AviWriter(const Filename& filename, unsigned width_, unsigned height_, unsigned bpp, unsigned channels_, unsigned freq_) : file(filename, "wb") , codec(width_, height_, bpp) , fps(0.0f) // will be filled in later , width(width_) , height(height_) , channels(channels_) , audiorate(freq_) { char dummy[AVI_HEADER_SIZE]; memset(dummy, 0, sizeof(dummy)); file.write(dummy, sizeof(dummy)); index.resize(2); frames = 0; written = 0; audiowritten = 0; } AviWriter::~AviWriter() { if (written == 0) { // no data written yet (a recording less than one video frame) std::string filename = file.getURL(); file.close(); // close file (needed for windows?) FileOperations::unlink(filename); return; } assert(fps != 0.0f); // a decent fps should have been set // Possible cleanup: use structs for the different headers, that // also allows to use the aligned versions of the Endian routines. unsigned char avi_header[AVI_HEADER_SIZE]; memset(&avi_header, 0, sizeof(avi_header)); unsigned header_pos = 0; #define AVIOUT4(_S_) memcpy(&avi_header[header_pos],_S_,4);header_pos+=4; #define AVIOUTw(_S_) Endian::write_UA_L16(&avi_header[header_pos], _S_);header_pos+=2; #define AVIOUTd(_S_) Endian::write_UA_L32(&avi_header[header_pos], _S_);header_pos+=4; #define AVIOUTs(_S_) memcpy(&avi_header[header_pos],_S_,strlen(_S_)+1);header_pos+=(strlen(_S_)+1 + 1) & ~1; bool hasAudio = audiorate != 0; // write avi header AVIOUT4("RIFF"); // Riff header AVIOUTd(AVI_HEADER_SIZE + written - 8 + unsigned(index.size() * sizeof(Endian::L32))); AVIOUT4("AVI "); AVIOUT4("LIST"); // List header unsigned main_list = header_pos; AVIOUTd(0); // size of list, filled in later AVIOUT4("hdrl"); AVIOUT4("avih"); AVIOUTd(56); // # of bytes to follow AVIOUTd(unsigned(1000000 / fps)); // Microseconds per frame AVIOUTd(0); AVIOUTd(0); // PaddingGranularity (whatever that might be) AVIOUTd(0x110); // Flags,0x10 has index, 0x100 interleaved AVIOUTd(frames); // TotalFrames AVIOUTd(0); // InitialFrames AVIOUTd(hasAudio? 2 : 1); // Stream count AVIOUTd(0); // SuggestedBufferSize AVIOUTd(width); // Width AVIOUTd(height); // Height AVIOUTd(0); // TimeScale: Unit used to measure time AVIOUTd(0); // DataRate: Data rate of playback AVIOUTd(0); // StartTime: Starting time of AVI data AVIOUTd(0); // DataLength: Size of AVI data chunk // Video stream list AVIOUT4("LIST"); AVIOUTd(4 + 8 + 56 + 8 + 40); // Size of the list AVIOUT4("strl"); // video stream header AVIOUT4("strh"); AVIOUTd(56); // # of bytes to follow AVIOUT4("vids"); // Type AVIOUT4(ZMBVEncoder::CODEC_4CC); // Handler AVIOUTd(0); // Flags AVIOUTd(0); // Reserved, MS says: wPriority, wLanguage AVIOUTd(0); // InitialFrames AVIOUTd(1000000); // Scale AVIOUTd(unsigned(1000000 * fps)); // Rate: Rate/Scale == samples/second AVIOUTd(0); // Start AVIOUTd(frames); // Length AVIOUTd(0); // SuggestedBufferSize AVIOUTd(unsigned(~0)); // Quality AVIOUTd(0); // SampleSize AVIOUTd(0); // Frame AVIOUTd(0); // Frame // The video stream format AVIOUT4("strf"); AVIOUTd(40); // # of bytes to follow AVIOUTd(40); // Size AVIOUTd(width); // Width AVIOUTd(height); // Height AVIOUTd(0); AVIOUT4(ZMBVEncoder::CODEC_4CC); // Compression AVIOUTd(width * height * 4); // SizeImage (in bytes?) AVIOUTd(0); // XPelsPerMeter AVIOUTd(0); // YPelsPerMeter AVIOUTd(0); // ClrUsed: Number of colors used AVIOUTd(0); // ClrImportant: Number of colors important if (hasAudio) { // 1 fragment is 1 (for mono) or 2 (for stereo) samples unsigned bitsPerSample = 16; unsigned bytesPerSample = bitsPerSample / 8; unsigned bytesPerFragment = bytesPerSample * channels; unsigned bytesPerSecond = audiorate * bytesPerFragment; unsigned fragments = audiowritten / channels; // Audio stream list AVIOUT4("LIST"); AVIOUTd(4 + 8 + 56 + 8 + 16);// Length of list in bytes AVIOUT4("strl"); // The audio stream header AVIOUT4("strh"); AVIOUTd(56); // # of bytes to follow AVIOUT4("auds"); AVIOUTd(0); // Format (Optionally) AVIOUTd(0); // Flags AVIOUTd(0); // Reserved, MS says: wPriority, wLanguage AVIOUTd(0); // InitialFrames // Rate/Scale should be number of samples per second! AVIOUTd(bytesPerFragment); // Scale AVIOUTd(bytesPerSecond); // Rate, actual rate is scale/rate AVIOUTd(0); // Start AVIOUTd(fragments); // Length AVIOUTd(0); // SuggestedBufferSize AVIOUTd(unsigned(~0)); // Quality AVIOUTd(bytesPerFragment); // SampleSize (should be the same as BlockAlign) AVIOUTd(0); // Frame AVIOUTd(0); // Frame // The audio stream format AVIOUT4("strf"); AVIOUTd(16); // # of bytes to follow AVIOUTw(1); // Format, WAVE_ZMBV_FORMAT_PCM AVIOUTw(channels); // Number of channels AVIOUTd(audiorate); // SamplesPerSec AVIOUTd(bytesPerSecond); // AvgBytesPerSec AVIOUTw(bytesPerFragment); // BlockAlign: for PCM: nChannels * BitsPerSaple / 8 AVIOUTw(bitsPerSample); // BitsPerSample } std::string versionStr = Version::full(); // The standard snprintf() function does always zero-terminate the // output it writes. Though windows doesn't have a snprintf() function, // instead we #define snprintf to _snprintf and the latter does NOT // properly zero-terminate. See also // http://stackoverflow.com/questions/7706936/is-snprintf-always-null-terminating char dateStr[11]; time_t t = time(nullptr); struct tm *tm = localtime(&t); snprintf(dateStr, 11, "%04d-%02d-%02d", 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday); dateStr[10] = 0; // only required on windows AVIOUT4("LIST"); AVIOUTd(4 // list type + (4 + 4 + ((versionStr.size() + 1 + 1) & ~1)) // 1st chunk + (4 + 4 + ((strlen(dateStr ) + 1 + 1) & ~1)) // 2nd chunk ); // size of the list AVIOUT4("INFO"); AVIOUT4("ISFT"); AVIOUTd(unsigned(versionStr.size()) + 1); // # of bytes to follow AVIOUTs(versionStr.c_str()); AVIOUT4("ICRD"); AVIOUTd(unsigned(strlen(dateStr)) + 1); // # of bytes to follow AVIOUTs(dateStr); // TODO: add artist (IART), comments (ICMT), name (INAM), etc. // use a loop over chunks (type + string) to create the above bytes in // a much nicer way // Finish stream list, i.e. put number of bytes in the list to proper pos int nmain = header_pos - main_list - 4; int njunk = AVI_HEADER_SIZE - 8 - 12 - header_pos; assert(njunk > 0); // increase AVI_HEADER_SIZE if this occurs AVIOUT4("JUNK"); AVIOUTd(njunk); // Fix the size of the main list header_pos = main_list; AVIOUTd(nmain); header_pos = AVI_HEADER_SIZE - 12; AVIOUT4("LIST"); AVIOUTd(written + 4); // Length of list in bytes AVIOUT4("movi"); try { // First add the index table to the end unsigned idxSize = unsigned(index.size()) * sizeof(Endian::L32); memcpy(&index[0], "idx1", 4); index[1] = idxSize - 8; file.write(&index[0], idxSize); file.seek(0); file.write(&avi_header, AVI_HEADER_SIZE); } catch (MSXException&) { // can't throw from destructor } } void AviWriter::addAviChunk(const char* tag, unsigned size, void* data, unsigned flags) { struct { char t[4]; Endian::L32 s; } chunk; memcpy(chunk.t, tag, sizeof(chunk.t)); chunk.s = size; file.write(&chunk, sizeof(chunk)); unsigned writesize = (size + 1) & ~1; file.write(data, writesize); unsigned pos = written + 4; written += writesize + 8; size_t idxSize = index.size(); index.resize(idxSize + 4); memcpy(&index[idxSize], tag, sizeof(Endian::L32)); index[idxSize + 1] = flags; index[idxSize + 2] = pos; index[idxSize + 3] = size; } void AviWriter::addFrame(FrameSource* frame, unsigned samples, short* sampleData) { bool keyFrame = (frames++ % 300 == 0); void* buffer; unsigned size; codec.compressFrame(keyFrame, frame, buffer, size); addAviChunk("00dc", size, buffer, keyFrame ? 0x10 : 0x0); if (samples) { assert((samples % channels) == 0); assert(audiorate != 0); if (OPENMSX_BIGENDIAN) { // See comment in WavWriter::write() //VLA(Endian::L16, buf, samples); // doesn't work in clang //std::vector buf(sampleData, sampleData + samples); // needs c++11 std::vector buf(samples); for (unsigned i = 0; i < samples; ++i) { buf[i] = sampleData[i]; } addAviChunk("01wb", samples * sizeof(short), buf.data(), 0); } else { addAviChunk("01wb", samples * sizeof(short), sampleData, 0); } audiowritten += samples; } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/AviWriter.hh000066400000000000000000000015631257557151200205760ustar00rootroot00000000000000// Code based on DOSBox-0.65 #ifndef AVIWRITER_HH #define AVIWRITER_HH #include "ZMBVEncoder.hh" #include "File.hh" #include "endian.hh" #include #include namespace openmsx { class Filename; class FrameSource; class AviWriter { public: AviWriter(const Filename& filename, unsigned width, unsigned height, unsigned bpp, unsigned channels, unsigned freq); ~AviWriter(); void addFrame(FrameSource* frame, unsigned samples, short* sampleData); void setFps(float fps_) { fps = fps_; } private: void addAviChunk(const char* tag, unsigned size, void* data, unsigned flags); File file; ZMBVEncoder codec; std::vector index; float fps; const unsigned width; const unsigned height; const unsigned channels; const unsigned audiorate; unsigned frames; unsigned audiowritten; unsigned written; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/BaseImage.cc000066400000000000000000000011001257557151200204500ustar00rootroot00000000000000#include "BaseImage.hh" #include "MSXException.hh" #include "StringOp.hh" namespace openmsx { static const int MAX_SIZE = 2048; using StringOp::Builder; void BaseImage::checkSize(gl::ivec2 size) { if (size[0] < -MAX_SIZE || size[0] > MAX_SIZE) { throw MSXException( Builder() << "Image width too large: " << size[0] << " (max " << MAX_SIZE << ')'); } if (size[1] < -MAX_SIZE || size[1] > MAX_SIZE) { throw MSXException( Builder() << "Image height too large: " << size[1] << " (max " << MAX_SIZE << ')'); } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/BaseImage.hh000066400000000000000000000013571257557151200205000ustar00rootroot00000000000000#ifndef BASEIMAGE_HH #define BASEIMAGE_HH #include "gl_vec.hh" #include "openmsx.hh" #include "noncopyable.hh" namespace openmsx { class OutputSurface; class BaseImage : private noncopyable { public: /** * Performs a sanity check on image size. * Throws MSXException if width or height is excessively large. * Negative image sizes are valid and flip the image. */ static void checkSize(gl::ivec2 size); virtual ~BaseImage() {} virtual void draw(OutputSurface& output, gl::ivec2 pos, byte r, byte g, byte b, byte alpha) = 0; virtual gl::ivec2 getSize() const = 0; void draw(OutputSurface& output, gl::ivec2 pos, byte alpha = 255) { draw(output, pos, 255, 255, 255, alpha); } }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/BitmapConverter.cc000066400000000000000000000302551257557151200217540ustar00rootroot00000000000000#include "BitmapConverter.hh" #include "Math.hh" #include "likely.hh" #include "unreachable.hh" #include "build-info.hh" #include "components.hh" #include namespace openmsx { template BitmapConverter::BitmapConverter( const Pixel* palette16_, const Pixel* palette256_, const Pixel* palette32768_) : palette16(palette16_) , palette256(palette256_) , palette32768(palette32768_) , dPaletteValid(false) { } template void BitmapConverter::calcDPalette() { dPaletteValid = true; unsigned bits = sizeof(Pixel) * 8; for (unsigned i = 0; i < 16; ++i) { DPixel p0 = palette16[i]; for (unsigned j = 0; j < 16; ++j) { DPixel p1 = palette16[j]; DPixel dp = OPENMSX_BIGENDIAN ? (p0 << bits) | p1 : (p1 << bits) | p0; dPalette[16 * i + j] = dp; } } } template void BitmapConverter::convertLine( Pixel* linePtr, const byte* vramPtr) { switch (mode.getByte()) { case DisplayMode::GRAPHIC4: // screen 5 case DisplayMode::GRAPHIC4 | DisplayMode::YAE: renderGraphic4(linePtr, vramPtr); break; case DisplayMode::GRAPHIC5: // screen 6 case DisplayMode::GRAPHIC5 | DisplayMode::YAE: renderGraphic5(linePtr, vramPtr); break; // These are handled in convertLinePlanar(). case DisplayMode::GRAPHIC6: case DisplayMode::GRAPHIC7: case DisplayMode::GRAPHIC6 | DisplayMode::YAE: case DisplayMode::GRAPHIC7 | DisplayMode::YAE: case DisplayMode::GRAPHIC6 | DisplayMode::YJK: case DisplayMode::GRAPHIC7 | DisplayMode::YJK: case DisplayMode::GRAPHIC6 | DisplayMode::YJK | DisplayMode::YAE: case DisplayMode::GRAPHIC7 | DisplayMode::YJK | DisplayMode::YAE: UNREACHABLE; // TODO: Support YJK on modes other than Graphic 6/7. case DisplayMode::GRAPHIC4 | DisplayMode::YJK: case DisplayMode::GRAPHIC5 | DisplayMode::YJK: case DisplayMode::GRAPHIC4 | DisplayMode::YJK | DisplayMode::YAE: case DisplayMode::GRAPHIC5 | DisplayMode::YJK | DisplayMode::YAE: default: renderBogus(linePtr); break; } } template void BitmapConverter::convertLinePlanar( Pixel* linePtr, const byte* vramPtr0, const byte* vramPtr1) { switch (mode.getByte()) { case DisplayMode::GRAPHIC6: // screen 7 case DisplayMode::GRAPHIC6 | DisplayMode::YAE: renderGraphic6(linePtr, vramPtr0, vramPtr1); break; case DisplayMode::GRAPHIC7: // screen 8 case DisplayMode::GRAPHIC7 | DisplayMode::YAE: renderGraphic7(linePtr, vramPtr0, vramPtr1); break; case DisplayMode::GRAPHIC6 | DisplayMode::YJK: // screen 12 case DisplayMode::GRAPHIC7 | DisplayMode::YJK: renderYJK(linePtr, vramPtr0, vramPtr1); break; case DisplayMode::GRAPHIC6 | DisplayMode::YJK | DisplayMode::YAE: // screen 11 case DisplayMode::GRAPHIC7 | DisplayMode::YJK | DisplayMode::YAE: renderYAE(linePtr, vramPtr0, vramPtr1); break; // These are handled in convertLine(). case DisplayMode::GRAPHIC4: case DisplayMode::GRAPHIC5: case DisplayMode::GRAPHIC4 | DisplayMode::YAE: case DisplayMode::GRAPHIC5 | DisplayMode::YAE: case DisplayMode::GRAPHIC4 | DisplayMode::YJK: case DisplayMode::GRAPHIC5 | DisplayMode::YJK: case DisplayMode::GRAPHIC4 | DisplayMode::YJK | DisplayMode::YAE: case DisplayMode::GRAPHIC5 | DisplayMode::YJK | DisplayMode::YAE: UNREACHABLE; default: renderBogus(linePtr); break; } } template void BitmapConverter::renderGraphic4( Pixel* __restrict pixelPtr, const byte* __restrict vramPtr0) { /*for (unsigned i = 0; i < 128; i += 2) { unsigned data0 = vramPtr0[i + 0]; unsigned data1 = vramPtr0[i + 1]; pixelPtr[2 * i + 0] = palette16[data0 >> 4]; pixelPtr[2 * i + 1] = palette16[data0 & 15]; pixelPtr[2 * i + 2] = palette16[data1 >> 4]; pixelPtr[2 * i + 3] = palette16[data1 & 15]; }*/ if (unlikely(!dPaletteValid)) { calcDPalette(); } #ifdef __arm__ if ((sizeof(Pixel) == 2) && (((int)pixelPtr & 3) == 0)) { // only 16bpp and only when aligned on 64-bit word boundary // otherwise the stmia gives a segfault on ARMv6 unsigned dummy; asm volatile ( "0:\n\t" "ldmia %[vram]!, {r3,r4}\n\t" "and r5, %[m255], r3, lsr #16\n\t" "and r6, %[m255], r3, lsr #24\n\t" "and r8, %[m255], r4\n\t" "and r9, %[m255], r4, lsr #8\n\t" "and r10, %[m255], r4, lsr #16\n\t" "and r12, %[m255], r4, lsr #24\n\t" "and r4, %[m255], r3, lsr #8\n\t" "and r3, %[m255], r3\n\t" "ldr r3, [%[pal], r3, lsl #2]\n\t" "ldr r4, [%[pal], r4, lsl #2]\n\t" "ldr r5, [%[pal], r5, lsl #2]\n\t" "ldr r6, [%[pal], r6, lsl #2]\n\t" "ldr r8, [%[pal], r8, lsl #2]\n\t" "ldr r9, [%[pal], r9, lsl #2]\n\t" "ldr r10, [%[pal], r10,lsl #2]\n\t" "ldr r12, [%[pal], r12,lsl #2]\n\t" "subs %[count], %[count], #1\n\t" "stmia %[out]!, {r3,r4,r5,r6,r8,r9,r10,r12}\n\t" "bne 0b\n\t" : [vram] "=r" (vramPtr0) , [out] "=r" (pixelPtr) , [count] "=r" (dummy) : "[vram]" (vramPtr0) , "[out]" (pixelPtr) , "[count]" (16) , [pal] "r" (dPalette) , [m255] "r" (255) : "r3", "r4", "r5", "r6", "r8", "r9", "r10", "r12" ); return; } #endif if ((sizeof(Pixel) == 2) && ((uintptr_t(pixelPtr) & 1) == 1)) { // Its 16 bit destination but currently not aligned on a word boundary // First write one pixel to get aligned // Then write double pixels in a loop with 4 double pixels (is 8 single pixels) per iteration // Finally write the last pixel unaligned auto in = reinterpret_cast(vramPtr0); unsigned data = in[0]; if (OPENMSX_BIGENDIAN) { pixelPtr[0] = palette16[(data >> 28) & 0x0F]; data <<=4; } else { pixelPtr[0] = palette16[(data >> 0) & 0x0F]; data >>=4; } pixelPtr += 1; // Move to next pixel, which is on word boundary auto out = reinterpret_cast(pixelPtr); for (unsigned i = 0; i < 256 / 8; ++i) { // 8 pixels per iteration if (OPENMSX_BIGENDIAN) { out[4 * i + 0] = dPalette[(data >> 24) & 0xFF]; out[4 * i + 1] = dPalette[(data >> 16) & 0xFF]; out[4 * i + 2] = dPalette[(data >> 8) & 0xFF]; if (i == (256-8) / 8) { // Last pixel in last iteration must be written individually pixelPtr[254] = palette16[(data >> 0) & 0x0F]; } else { // Last double-pixel must be composed of // remaing 4 bits in (previous) data // and first 4 bits from (next) data unsigned prevData = data; data = in[i+1]; out[4 * i + 3] = dPalette[(prevData & 0xF0) | ((data >> 28) & 0x0F)]; data <<= 4; } } else { out[4 * i + 0] = dPalette[(data >> 0) & 0xFF]; out[4 * i + 1] = dPalette[(data >> 8) & 0xFF]; out[4 * i + 2] = dPalette[(data >> 16) & 0xFF]; if (i != (256-8) / 8) { // Last pixel in last iteration must be written individually pixelPtr[254] = palette16[(data >> 24) & 0x0F]; } else { // Last double-pixel must be composed of // remaing 4 bits in (previous) data // and first 4 bits from (next) data unsigned prevData = data; data = in[i+1]; out[4 * i + 3] = dPalette[((prevData >> 24) & 0x0F) | ((data & 0x0F)<<4)]; data >>=4; } } } return; } auto out = reinterpret_cast(pixelPtr); auto in = reinterpret_cast(vramPtr0); for (unsigned i = 0; i < 256 / 8; ++i) { // 8 pixels per iteration unsigned data = in[i]; if (OPENMSX_BIGENDIAN) { out[4 * i + 0] = dPalette[(data >> 24) & 0xFF]; out[4 * i + 1] = dPalette[(data >> 16) & 0xFF]; out[4 * i + 2] = dPalette[(data >> 8) & 0xFF]; out[4 * i + 3] = dPalette[(data >> 0) & 0xFF]; } else { out[4 * i + 0] = dPalette[(data >> 0) & 0xFF]; out[4 * i + 1] = dPalette[(data >> 8) & 0xFF]; out[4 * i + 2] = dPalette[(data >> 16) & 0xFF]; out[4 * i + 3] = dPalette[(data >> 24) & 0xFF]; } } } template void BitmapConverter::renderGraphic5( Pixel* __restrict pixelPtr, const byte* __restrict vramPtr0) { for (unsigned i = 0; i < 128; ++i) { unsigned data = vramPtr0[i]; pixelPtr[4 * i + 0] = palette16[ 0 + (data >> 6) ]; pixelPtr[4 * i + 1] = palette16[16 + ((data >> 4) & 3)]; pixelPtr[4 * i + 2] = palette16[ 0 + ((data >> 2) & 3)]; pixelPtr[4 * i + 3] = palette16[16 + ((data >> 0) & 3)]; } } template void BitmapConverter::renderGraphic6( Pixel* __restrict pixelPtr, const byte* __restrict vramPtr0, const byte* __restrict vramPtr1) { /*for (unsigned i = 0; i < 128; ++i) { unsigned data0 = vramPtr0[i]; unsigned data1 = vramPtr1[i]; pixelPtr[4 * i + 0] = palette16[data0 >> 4]; pixelPtr[4 * i + 1] = palette16[data0 & 15]; pixelPtr[4 * i + 2] = palette16[data1 >> 4]; pixelPtr[4 * i + 3] = palette16[data1 & 15]; }*/ if (unlikely(!dPaletteValid)) { calcDPalette(); } auto out = reinterpret_cast(pixelPtr); auto in0 = reinterpret_cast(vramPtr0); auto in1 = reinterpret_cast(vramPtr1); for (unsigned i = 0; i < 512 / 16; ++i) { // 16 pixels per iteration unsigned data0 = in0[i]; unsigned data1 = in1[i]; if (OPENMSX_BIGENDIAN) { out[8 * i + 0] = dPalette[(data0 >> 24) & 0xFF]; out[8 * i + 1] = dPalette[(data1 >> 24) & 0xFF]; out[8 * i + 2] = dPalette[(data0 >> 16) & 0xFF]; out[8 * i + 3] = dPalette[(data1 >> 16) & 0xFF]; out[8 * i + 4] = dPalette[(data0 >> 8) & 0xFF]; out[8 * i + 5] = dPalette[(data1 >> 8) & 0xFF]; out[8 * i + 6] = dPalette[(data0 >> 0) & 0xFF]; out[8 * i + 7] = dPalette[(data1 >> 0) & 0xFF]; } else { out[8 * i + 0] = dPalette[(data0 >> 0) & 0xFF]; out[8 * i + 1] = dPalette[(data1 >> 0) & 0xFF]; out[8 * i + 2] = dPalette[(data0 >> 8) & 0xFF]; out[8 * i + 3] = dPalette[(data1 >> 8) & 0xFF]; out[8 * i + 4] = dPalette[(data0 >> 16) & 0xFF]; out[8 * i + 5] = dPalette[(data1 >> 16) & 0xFF]; out[8 * i + 6] = dPalette[(data0 >> 24) & 0xFF]; out[8 * i + 7] = dPalette[(data1 >> 24) & 0xFF]; } } } template void BitmapConverter::renderGraphic7( Pixel* __restrict pixelPtr, const byte* __restrict vramPtr0, const byte* __restrict vramPtr1) { for (unsigned i = 0; i < 128; ++i) { pixelPtr[2 * i + 0] = palette256[vramPtr0[i]]; pixelPtr[2 * i + 1] = palette256[vramPtr1[i]]; } } template void BitmapConverter::renderYJK( Pixel* __restrict pixelPtr, const byte* __restrict vramPtr0, const byte* __restrict vramPtr1) { for (unsigned i = 0; i < 64; ++i) { unsigned p[4]; p[0] = vramPtr0[2 * i + 0]; p[1] = vramPtr1[2 * i + 0]; p[2] = vramPtr0[2 * i + 1]; p[3] = vramPtr1[2 * i + 1]; int j = (p[2] & 7) + ((p[3] & 3) << 3) - ((p[3] & 4) << 3); int k = (p[0] & 7) + ((p[1] & 3) << 3) - ((p[1] & 4) << 3); for (unsigned n = 0; n < 4; ++n) { int y = p[n] >> 3; int r = Math::clip<0, 31>(y + j); int g = Math::clip<0, 31>(y + k); int b = Math::clip<0, 31>((5 * y - 2 * j - k) / 4); int col = (r << 10) + (g << 5) + b; pixelPtr[4 * i + n] = palette32768[col]; } } } template void BitmapConverter::renderYAE( Pixel* __restrict pixelPtr, const byte* __restrict vramPtr0, const byte* __restrict vramPtr1) { for (unsigned i = 0; i < 64; ++i) { unsigned p[4]; p[0] = vramPtr0[2 * i + 0]; p[1] = vramPtr1[2 * i + 0]; p[2] = vramPtr0[2 * i + 1]; p[3] = vramPtr1[2 * i + 1]; int j = (p[2] & 7) + ((p[3] & 3) << 3) - ((p[3] & 4) << 3); int k = (p[0] & 7) + ((p[1] & 3) << 3) - ((p[1] & 4) << 3); for (unsigned n = 0; n < 4; ++n) { Pixel pix; if (p[n] & 0x08) { // YAE pix = palette16[p[n] >> 4]; } else { // YJK int y = p[n] >> 3; int r = Math::clip<0, 31>(y + j); int g = Math::clip<0, 31>(y + k); int b = Math::clip<0, 31>((5 * y - 2 * j - k) / 4); pix = palette32768[(r << 10) + (g << 5) + b]; } pixelPtr[4 * i + n] = pix; } } } template void BitmapConverter::renderBogus(Pixel* pixelPtr) { // Verified on real V9958: all bogus modes behave like this, always // show palette color 15. // When this is in effect, the VRAM is not refreshed anymore, but that // is not emulated. for (int n = 256; n--; ) *pixelPtr++ = palette16[15]; } // Force template instantiation. #if HAVE_16BPP template class BitmapConverter; #endif #if HAVE_32BPP || COMPONENT_GL template class BitmapConverter; #endif } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/BitmapConverter.hh000066400000000000000000000067501257557151200217710ustar00rootroot00000000000000#ifndef BITMAPCONVERTER_HH #define BITMAPCONVERTER_HH #include "DisplayMode.hh" #include "openmsx.hh" #include namespace openmsx { template struct DoublePixel; template<> struct DoublePixel<2> { using type = uint32_t; }; template<> struct DoublePixel<4> { using type = uint64_t; }; /** Utility class for converting VRAM contents to host pixels. */ template class BitmapConverter { public: /** Create a new bitmap scanline converter. * @param palette16 Pointer to 2*16-entries array that specifies * VDP color index to host pixel mapping. * This is kept as a pointer, so any changes to the palette * are immediately picked up by convertLine. Though to allow * some internal optimizations, this class should be informed about * changes in this array (not needed for the next two), see * palette16Changed(). * Used for display modes Graphic4, Graphic5 and Graphic6. * First 16 entries are for even pixels, next 16 are for odd pixels * @param palette256 Pointer to 256-entries array that specifies * VDP color index to host pixel mapping. * This is kept as a pointer, so any changes to the palette * are immediately picked up by convertLine. * Used for display mode Graphic7. * @param palette32768 Pointer to 32768-entries array that specifies * VDP color index to host pixel mapping. * This is kept as a pointer, so any changes to the palette * are immediately picked up by convertLine. * Used when YJK filter is active. */ BitmapConverter(const Pixel* palette16, const Pixel* palette256, const Pixel* palette32768); /** Convert a line of V9938 VRAM to 512 host pixels. * Call this method in non-planar display modes (Graphic4 and Graphic5). * @param linePtr Pointer to array of host pixels. * @param vramPtr Pointer to VRAM contents. */ void convertLine(Pixel* linePtr, const byte* vramPtr); /** Convert a line of V9938 VRAM to 512 host pixels. * Call this method in planar display modes (Graphic6 and Graphic7). * @param linePtr Pointer to array of host pixels. * @param vramPtr0 Pointer to VRAM contents, first plane. * @param vramPtr1 Pointer to VRAM contents, second plane. */ void convertLinePlanar(Pixel* linePtr, const byte* vramPtr0, const byte* vramPtr1); /** Select the display mode to use for scanline conversion. * @param mode_ The new display mode. */ inline void setDisplayMode(DisplayMode mode_) { mode = mode_; } /** Inform this class about changes in the palette16 array. */ inline void palette16Changed() { dPaletteValid = false; } private: void calcDPalette(); inline void renderGraphic4(Pixel* pixelPtr, const byte* vramPtr0); inline void renderGraphic5(Pixel* pixelPtr, const byte* vramPtr0); inline void renderGraphic6( Pixel* pixelPtr, const byte* vramPtr0, const byte* vramPtr1); inline void renderGraphic7( Pixel* pixelPtr, const byte* vramPtr0, const byte* vramPtr1); inline void renderYJK( Pixel* pixelPtr, const byte* vramPtr0, const byte* vramPtr1); inline void renderYAE( Pixel* pixelPtr, const byte* vramPtr0, const byte* vramPtr1); inline void renderBogus(Pixel* pixelPtr); const Pixel* const __restrict palette16; const Pixel* const __restrict palette256; const Pixel* const __restrict palette32768; using DPixel = typename DoublePixel::type; DPixel dPalette[16 * 16]; DisplayMode mode; bool dPaletteValid; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/CharacterConverter.cc000066400000000000000000000372311257557151200224350ustar00rootroot00000000000000/* TODO: - Clean up renderGraphics2, it is currently very hard to understand with all the masks and quarters etc. - Correctly implement vertical scroll in text modes. Can be implemented by reordering blitting, but uses a smaller wrap than GFX modes: 8 lines instead of 256 lines. */ #include "CharacterConverter.hh" #include "VDP.hh" #include "VDPVRAM.hh" #include "build-info.hh" #include "components.hh" #include #ifdef __SSE2__ #include "emmintrin.h" // SSE2 #endif namespace openmsx { template CharacterConverter::CharacterConverter( VDP& vdp_, const Pixel* palFg_, const Pixel* palBg_) : vdp(vdp_), vram(vdp.getVRAM()), palFg(palFg_), palBg(palBg_) { modeBase = 0; // not strictly needed, but avoids Coverity warning } template void CharacterConverter::setDisplayMode(DisplayMode mode) { modeBase = mode.getBase(); assert(modeBase < 0x0C); } template void CharacterConverter::convertLine(Pixel* linePtr, int line) { // TODO: Support YJK on modes other than Graphic 6/7. switch (modeBase) { case DisplayMode::GRAPHIC1: // screen 1 renderGraphic1(linePtr, line); break; case DisplayMode::TEXT1: // screen 0, width 40 renderText1(linePtr, line); break; case DisplayMode::MULTICOLOR: // screen 3 renderMulti(linePtr, line); break; case DisplayMode::GRAPHIC2: // screen 2 renderGraphic2(linePtr, line); break; case DisplayMode::GRAPHIC3: // screen 4 renderGraphic2(linePtr, line); // graphic3, actually break; case DisplayMode::TEXT2: // screen 0, width 80 renderText2(linePtr, line); break; case DisplayMode::TEXT1Q: // TMSxxxx only if (vdp.isMSX1VDP()) { renderText1Q(linePtr, line); } else { renderBlank (linePtr); } break; case DisplayMode::MULTIQ: // TMSxxxx only if (vdp.isMSX1VDP()) { renderMultiQ(linePtr, line); } else { renderBlank (linePtr); } break; default: // remaining (non-bitmap) modes if (vdp.isMSX1VDP()) { renderBogus(linePtr); } else { renderBlank(linePtr); } } } #ifdef __SSE2__ // Copied from Scale2xScaler.cc, TODO move to common location? static inline __m128i select(__m128i a0, __m128i a1, __m128i mask) { return _mm_xor_si128(_mm_and_si128(_mm_xor_si128(a0, a1), mask), a0); } #endif template static inline void draw6( Pixel* __restrict & pixelPtr, Pixel fg, Pixel bg, byte pattern) { pixelPtr[0] = (pattern & 0x80) ? fg : bg; pixelPtr[1] = (pattern & 0x40) ? fg : bg; pixelPtr[2] = (pattern & 0x20) ? fg : bg; pixelPtr[3] = (pattern & 0x10) ? fg : bg; pixelPtr[4] = (pattern & 0x08) ? fg : bg; pixelPtr[5] = (pattern & 0x04) ? fg : bg; pixelPtr += 6; } template static inline void draw8( Pixel* __restrict & pixelPtr, Pixel fg, Pixel bg, byte pattern, bool misAligned, uint32_t& partial) { #ifdef __arm__ // ARM version, 16bpp, (32-bit aligned/unaligned destination) if (sizeof(Pixel) == 2) { if (misAligned) { asm volatile ( "mov r0,%[PART]\n\t" "tst %[PAT],#128\n\t" "ite eq\n\t" "orreq r0,r0,%[BG], lsl #16\n\t" "orrne r0,r0,%[FG], lsl #16\n\t" "tst %[PAT],#64\n\t" "ite eq\n\t" "moveq r1,%[BG]\n\t" "movne r1,%[FG]\n\t" "tst %[PAT],#32\n\t" "ite eq\n\t" "orreq r1,r1,%[BG], lsl #16\n\t" "orrne r1,r1,%[FG], lsl #16\n\t" "tst %[PAT],#16\n\t" "ite eq\n\t" "moveq r2,%[BG]\n\t" "movne r2,%[FG]\n\t" "tst %[PAT],#8\n\t" "ite eq\n\t" "orreq r2,r2,%[BG], lsl #16\n\t" "orrne r2,r2,%[FG], lsl #16\n\t" "tst %[PAT],#4\n\t" "ite eq\n\t" "moveq r3,%[BG]\n\t" "movne r3,%[FG]\n\t" "tst %[PAT],#2\n\t" "ite eq\n\t" "orreq r3,r3,%[BG], lsl #16\n\t" "orrne r3,r3,%[FG], lsl #16\n\t" "tst %[PAT],#1\n\t" "ite eq\n\t" "moveq %[PART],%[BG]\n\t" "movne %[PART],%[FG]\n\t" "stmia %[OUT]!,{r0-r3}\n\t" : [OUT] "=r" (pixelPtr) , [PART] "=r" (partial) : "[OUT]" (pixelPtr) , "[PART]" (partial) , [PAT] "r" (pattern) , [FG] "r" (uint32_t(fg)) , [BG] "r" (uint32_t(bg)) : "r0","r1","r2","r3","memory" ); } else { asm volatile ( "tst %[PAT],#128\n\t" "ite eq\n\t" "moveq r0,%[BG]\n\t" "movne r0,%[FG]\n\t" "tst %[PAT],#64\n\t" "ite eq\n\t" "orreq r0,r0,%[BG], lsl #16\n\t" "orrne r0,r0,%[FG], lsl #16\n\t" "tst %[PAT],#32\n\t" "ite eq\n\t" "moveq r1,%[BG]\n\t" "movne r1,%[FG]\n\t" "tst %[PAT],#16\n\t" "ite eq\n\t" "orreq r1,r1,%[BG], lsl #16\n\t" "orrne r1,r1,%[FG], lsl #16\n\t" "tst %[PAT],#8\n\t" "ite eq\n\t" "moveq r2,%[BG]\n\t" "movne r2,%[FG]\n\t" "tst %[PAT],#4\n\t" "ite eq\n\t" "orreq r2,r2,%[BG], lsl #16\n\t" "orrne r2,r2,%[FG], lsl #16\n\t" "tst %[PAT],#2\n\t" "ite eq\n\t" "moveq r3,%[BG]\n\t" "movne r3,%[FG]\n\t" "tst %[PAT],#1\n\t" "ite eq\n\t" "orreq r3,r3,%[BG], lsl #16\n\t" "orrne r3,r3,%[FG], lsl #16\n\t" "stmia %[OUT]!,{r0-r3}\n\t" : [OUT] "=r" (pixelPtr) : "[OUT]" (pixelPtr) , [PAT] "r" (pattern) , [FG] "r" (uint32_t(fg)) , [BG] "r" (uint32_t(bg)) : "r0","r1","r2","r3","memory" ); } return; } #endif (void)misAligned; (void)partial; #ifdef __SSE2__ // SSE2 version, 32bpp (16bpp is possible, but not worth it anymore) if (sizeof(Pixel) == 4) { const __m128i m74 = _mm_set_epi32(0x10, 0x20, 0x40, 0x80); const __m128i m30 = _mm_set_epi32(0x01, 0x02, 0x04, 0x08); const __m128i zero = _mm_setzero_si128(); __m128i fg4 = _mm_set1_epi32(fg); __m128i bg4 = _mm_set1_epi32(bg); __m128i pat = _mm_set1_epi32(pattern); __m128i b74 = _mm_cmpeq_epi32(_mm_and_si128(pat, m74), zero); __m128i b30 = _mm_cmpeq_epi32(_mm_and_si128(pat, m30), zero); __m128i* out = reinterpret_cast<__m128i*>(pixelPtr); _mm_storeu_si128(out + 0, select(fg4, bg4, b74)); _mm_storeu_si128(out + 1, select(fg4, bg4, b30)); pixelPtr += 8; return; } #endif // C++ version pixelPtr[0] = (pattern & 0x80) ? fg : bg; pixelPtr[1] = (pattern & 0x40) ? fg : bg; pixelPtr[2] = (pattern & 0x20) ? fg : bg; pixelPtr[3] = (pattern & 0x10) ? fg : bg; pixelPtr[4] = (pattern & 0x08) ? fg : bg; pixelPtr[5] = (pattern & 0x04) ? fg : bg; pixelPtr[6] = (pattern & 0x02) ? fg : bg; pixelPtr[7] = (pattern & 0x01) ? fg : bg; pixelPtr += 8; } template void CharacterConverter::renderText1( Pixel* __restrict pixelPtr, int line) { Pixel fg = palFg[vdp.getForegroundColor()]; Pixel bg = palFg[vdp.getBackgroundColor()]; // 8 * 256 is small enough to always be contiguous const byte* patternArea = vram.patternTable.getReadArea(0, 256 * 8); patternArea += (line + vdp.getVerticalScroll()) & 7; // Note: Because line width is not a power of two, reading an entire line // from a VRAM pointer returned by readArea will not wrap the index // correctly. Therefore we read one character at a time. unsigned nameStart = (line / 8) * 40; unsigned nameEnd = nameStart + 40; for (unsigned name = nameStart; name < nameEnd; ++name) { unsigned charcode = vram.nameTable.readNP((name + 0xC00) | (~0u << 12)); unsigned pattern = patternArea[charcode * 8]; draw6(pixelPtr, fg, bg, pattern); } } template void CharacterConverter::renderText1Q( Pixel* __restrict pixelPtr, int line) { Pixel fg = palFg[vdp.getForegroundColor()]; Pixel bg = palFg[vdp.getBackgroundColor()]; unsigned patternBaseLine = (~0u << 13) | ((line + vdp.getVerticalScroll()) & 7); // Note: Because line width is not a power of two, reading an entire line // from a VRAM pointer returned by readArea will not wrap the index // correctly. Therefore we read one character at a time. unsigned nameStart = (line / 8) * 40; unsigned nameEnd = nameStart + 40; unsigned patternQuarter = (line & 0xC0) << 2; for (unsigned name = nameStart; name < nameEnd; ++name) { unsigned charcode = vram.nameTable.readNP((name + 0xC00) | (~0u << 12)); unsigned patternNr = patternQuarter | charcode; unsigned pattern = vram.patternTable.readNP( patternBaseLine | (patternNr * 8)); draw6(pixelPtr, fg, bg, pattern); } } template void CharacterConverter::renderText2( Pixel* __restrict pixelPtr, int line) { Pixel plainFg = palFg[vdp.getForegroundColor()]; Pixel plainBg = palFg[vdp.getBackgroundColor()]; Pixel blinkFg, blinkBg; if (vdp.getBlinkState()) { int fg = vdp.getBlinkForegroundColor(); blinkFg = palBg[fg ? fg : vdp.getBlinkBackgroundColor()]; blinkBg = palBg[vdp.getBlinkBackgroundColor()]; } else { blinkFg = plainFg; blinkBg = plainBg; } // 8 * 256 is small enough to always be contiguous const byte* patternArea = vram.patternTable.getReadArea(0, 256 * 8); patternArea += (line + vdp.getVerticalScroll()) & 7; unsigned colorStart = (line / 8) * (80 / 8); unsigned nameStart = (line / 8) * 80; for (unsigned i = 0; i < (80 / 8); ++i) { unsigned colorPattern = vram.colorTable.readNP( (colorStart + i) | (~0u << 9)); const byte* nameArea = vram.nameTable.getReadArea( (nameStart + 8 * i) | (~0u << 12), 8); draw6(pixelPtr, (colorPattern & 0x80) ? blinkFg : plainFg, (colorPattern & 0x80) ? blinkBg : plainBg, patternArea[nameArea[0] * 8]); draw6(pixelPtr, (colorPattern & 0x40) ? blinkFg : plainFg, (colorPattern & 0x40) ? blinkBg : plainBg, patternArea[nameArea[1] * 8]); draw6(pixelPtr, (colorPattern & 0x20) ? blinkFg : plainFg, (colorPattern & 0x20) ? blinkBg : plainBg, patternArea[nameArea[2] * 8]); draw6(pixelPtr, (colorPattern & 0x10) ? blinkFg : plainFg, (colorPattern & 0x10) ? blinkBg : plainBg, patternArea[nameArea[3] * 8]); draw6(pixelPtr, (colorPattern & 0x08) ? blinkFg : plainFg, (colorPattern & 0x08) ? blinkBg : plainBg, patternArea[nameArea[4] * 8]); draw6(pixelPtr, (colorPattern & 0x04) ? blinkFg : plainFg, (colorPattern & 0x04) ? blinkBg : plainBg, patternArea[nameArea[5] * 8]); draw6(pixelPtr, (colorPattern & 0x02) ? blinkFg : plainFg, (colorPattern & 0x02) ? blinkBg : plainBg, patternArea[nameArea[6] * 8]); draw6(pixelPtr, (colorPattern & 0x01) ? blinkFg : plainFg, (colorPattern & 0x01) ? blinkBg : plainBg, patternArea[nameArea[7] * 8]); } } template const byte* CharacterConverter::getNamePtr(int line, int scroll) { // no need to test whether multi-page scrolling is enabled, // indexMask in the nameTable already takes care of it return vram.nameTable.getReadArea( ((line / 8) * 32) | ((scroll & 0x20) ? 0x8000 : 0), 32); } template void CharacterConverter::renderGraphic1( Pixel* __restrict pixelPtr, int line) { bool misAligned = false; // initialize with dummy uint32_t partial = 0; // values to avoid warning #ifdef __arm__ misAligned = sizeof(Pixel) == 2 && (reinterpret_cast(pixelPtr) & 3); if (misAligned) pixelPtr--; partial = *pixelPtr; #endif const byte* patternArea = vram.patternTable.getReadArea(0, 256 * 8); patternArea += line & 7; const byte* colorArea = vram.colorTable.getReadArea(0, 256 / 8); int scroll = vdp.getHorizontalScrollHigh(); const byte* namePtr = getNamePtr(line, scroll); for (unsigned n = 0; n < 32; ++n) { unsigned charcode = namePtr[scroll & 0x1F]; unsigned pattern = patternArea[charcode * 8]; unsigned color = colorArea[charcode / 8]; Pixel fg = palFg[color >> 4]; Pixel bg = palFg[color & 0x0F]; draw8(pixelPtr, fg, bg, pattern, misAligned, partial); if (!(++scroll & 0x1F)) namePtr = getNamePtr(line, scroll); } #ifdef __arm__ if (misAligned) *pixelPtr = static_cast(partial); #endif } template void CharacterConverter::renderGraphic2( Pixel* __restrict pixelPtr, int line) { bool misAligned = false; // initialize with dummy uint32_t partial = 0; // values to avoid warning #ifdef __arm__ misAligned = sizeof(Pixel) == 2 && (reinterpret_cast(pixelPtr) & 3); if (misAligned) pixelPtr--; partial = *pixelPtr; #endif int quarter8 = (((line / 8) * 32) & ~0xFF) * 8; int line7 = line & 7; int scroll = vdp.getHorizontalScrollHigh(); const byte* namePtr = getNamePtr(line, scroll); if (vram.colorTable .isContinuous((8 * 256) - 1) && vram.patternTable.isContinuous((8 * 256) - 1) && ((scroll & 0x1f) == 0)) { // Both color and pattern table can be accessed contiguously // (no mirroring) and there's no v9958 horizontal scrolling. // This is very common, so make an optimized version for this. const byte* patternArea = vram.patternTable.getReadArea(quarter8, 8 * 256) + line7; const byte* colorArea = vram.colorTable .getReadArea(quarter8, 8 * 256) + line7; for (unsigned n = 0; n < 32; ++n) { unsigned charCode8 = namePtr[n] * 8; unsigned pattern = patternArea[charCode8]; unsigned color = colorArea [charCode8]; Pixel fg = palFg[color >> 4]; Pixel bg = palFg[color & 0x0F]; draw8(pixelPtr, fg, bg, pattern, misAligned, partial); } } else { // Slower variant, also works when: // - there is mirroring in the color table // - there is mirroring in the pattern table (TMS9929) // - V9958 horizontal scroll feature is used int baseLine = (~0u << 13) | quarter8 | line7; for (unsigned n = 0; n < 32; ++n) { unsigned charCode8 = namePtr[scroll & 0x1F] * 8; unsigned index = charCode8 | baseLine; unsigned pattern = vram.patternTable.readNP(index); unsigned color = vram.colorTable .readNP(index); Pixel fg = palFg[color >> 4]; Pixel bg = palFg[color & 0x0F]; draw8(pixelPtr, fg, bg, pattern, misAligned, partial); if (!(++scroll & 0x1F)) namePtr = getNamePtr(line, scroll); } } #ifdef __arm__ if (misAligned) *pixelPtr = static_cast(partial); #endif } template void CharacterConverter::renderMultiHelper( Pixel* __restrict pixelPtr, int line, int mask, int patternQuarter) { unsigned baseLine = mask | ((line / 4) & 7); unsigned scroll = vdp.getHorizontalScrollHigh(); const byte* namePtr = getNamePtr(line, scroll); for (unsigned n = 0; n < 32; ++n) { unsigned patternNr = patternQuarter | namePtr[scroll & 0x1F]; unsigned color = vram.patternTable.readNP((patternNr * 8) | baseLine); Pixel cl = palFg[color >> 4]; Pixel cr = palFg[color & 0x0F]; pixelPtr[0] = cl; pixelPtr[1] = cl; pixelPtr[2] = cl; pixelPtr[3] = cl; pixelPtr[4] = cr; pixelPtr[5] = cr; pixelPtr[6] = cr; pixelPtr[7] = cr; pixelPtr += 8; if (!(++scroll & 0x1F)) namePtr = getNamePtr(line, scroll); } } template void CharacterConverter::renderMulti( Pixel* __restrict pixelPtr, int line) { int mask = (~0u << 11); renderMultiHelper(pixelPtr, line, mask, 0); } template void CharacterConverter::renderMultiQ( Pixel* __restrict pixelPtr, int line) { int mask = (~0u << 13); int patternQuarter = (line * 4) & ~0xFF; // (line / 8) * 32 renderMultiHelper(pixelPtr, line, mask, patternQuarter); } template void CharacterConverter::renderBogus( Pixel* __restrict pixelPtr) { Pixel fg = palFg[vdp.getForegroundColor()]; Pixel bg = palFg[vdp.getBackgroundColor()]; for (int n = 8; n--; ) *pixelPtr++ = bg; for (int c = 40; c--; ) { for (int n = 4; n--; ) *pixelPtr++ = fg; for (int n = 2; n--; ) *pixelPtr++ = bg; } for (int n = 8; n--; ) *pixelPtr++ = bg; } template void CharacterConverter::renderBlank( Pixel* __restrict pixelPtr) { // when this is in effect, the VRAM is not refreshed anymore, but that // is not emulated for (int n = 256; n--; ) *pixelPtr++ = palFg[15]; } // Force template instantiation. #if HAVE_16BPP template class CharacterConverter; #endif #if HAVE_32BPP || COMPONENT_GL template class CharacterConverter; #endif } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/CharacterConverter.hh000066400000000000000000000041671257557151200224510ustar00rootroot00000000000000#ifndef CHARACTERCONVERTER_HH #define CHARACTERCONVERTER_HH #include "openmsx.hh" namespace openmsx { class VDP; class VDPVRAM; class DisplayMode; /** Utility class for converting VRAM contents to host pixels. */ template class CharacterConverter { public: /** Create a new bitmap scanline converter. * @param vdp The VDP of which the VRAM will be converted. * @param palFg Pointer to 16-entries array that specifies * VDP foreground color index to host pixel mapping. * This is kept as a pointer, so any changes to the palette * are immediately picked up by convertLine. * @param palBg Pointer to 16-entries array that specifies * VDP background color index to host pixel mapping. * This is kept as a pointer, so any changes to the palette * are immediately picked up by convertLine. */ CharacterConverter(VDP& vdp, const Pixel* palFg, const Pixel* palBg); /** Convert a line of V9938 VRAM to 512 host pixels. * Call this method in non-planar display modes (Graphic4 and Graphic5). * @param linePtr Pointer to array where host pixels will be written to. * @param line Display line number [0..255]. */ void convertLine(Pixel* linePtr, int line); /** Select the display mode to use for scanline conversion. * @param mode The new display mode. */ void setDisplayMode(DisplayMode mode); private: inline void renderText1 (Pixel* pixelPtr, int line); inline void renderText1Q (Pixel* pixelPtr, int line); inline void renderText2 (Pixel* pixelPtr, int line); inline void renderGraphic1(Pixel* pixelPtr, int line); inline void renderGraphic2(Pixel* pixelPtr, int line); inline void renderMulti (Pixel* pixelPtr, int line); inline void renderMultiQ (Pixel* pixelPtr, int line); inline void renderBogus (Pixel* pixelPtr); inline void renderBlank (Pixel* pixelPtr); inline void renderMultiHelper(Pixel* pixelPtr, int line, int mask, int patternQuarter); const byte* getNamePtr(int line, int scroll); VDP& vdp; VDPVRAM& vram; const Pixel* const palFg; const Pixel* const palBg; unsigned modeBase; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/Deflicker.cc000066400000000000000000000130431257557151200205340ustar00rootroot00000000000000#include "Deflicker.hh" #include "RawFrame.hh" #include "PixelOperations.hh" #include "memory.hh" #include "unreachable.hh" #include "vla.hh" #include "build-info.hh" #include #ifdef __SSE2__ #include #endif namespace openmsx { template class DeflickerImpl final : public Deflicker { public: DeflickerImpl(const SDL_PixelFormat& format, std::unique_ptr* lastFrames); private: const void* getLineInfo( unsigned line, unsigned& width, void* buf, unsigned bufWidth) const override; PixelOperations pixelOps; }; std::unique_ptr Deflicker::create( const SDL_PixelFormat& format, std::unique_ptr* lastFrames) { #if HAVE_16BPP if (format.BitsPerPixel == 15 || format.BitsPerPixel == 16) { return make_unique>(format, lastFrames); } #endif #if HAVE_32BPP if (format.BitsPerPixel == 32) { return make_unique>(format, lastFrames); } #endif UNREACHABLE; return nullptr; // avoid warning } Deflicker::Deflicker(const SDL_PixelFormat& format, std::unique_ptr* lastFrames_) : FrameSource(format) , lastFrames(lastFrames_) { } void Deflicker::init() { FrameSource::init(FIELD_NONINTERLACED); setHeight(lastFrames[0]->getHeight()); } unsigned Deflicker::getLineWidth(unsigned line) const { return lastFrames[0]->getLineWidthDirect(line); } template DeflickerImpl::DeflickerImpl(const SDL_PixelFormat& format, std::unique_ptr* lastFrames) : Deflicker(format, lastFrames) , pixelOps(format) { } #ifdef __SSE2__ template static inline __m128i blend(__m128i x, __m128i y, Pixel mask) { if (sizeof(Pixel) == 4) { // 32bpp return _mm_avg_epu8(x, y); } else { // 16bpp, (x & y) + (((x ^ y) & mask) >> 1) __m128i m = _mm_set1_epi16(mask); __m128i a = _mm_and_si128(x, y); __m128i b = _mm_xor_si128(x, y); __m128i c = _mm_and_si128(b, m); __m128i d = _mm_srli_epi16(c, 1); return _mm_add_epi16(a, d); } } #endif template const void* DeflickerImpl::getLineInfo( unsigned line, unsigned& width, void* buf_, unsigned bufWidth) const { unsigned width0 = lastFrames[0]->getLineWidthDirect(line); unsigned width1 = lastFrames[1]->getLineWidthDirect(line); unsigned width2 = lastFrames[2]->getLineWidthDirect(line); unsigned width3 = lastFrames[3]->getLineWidthDirect(line); const Pixel* line0 = lastFrames[0]->template getLinePtrDirect(line); const Pixel* line1 = lastFrames[1]->template getLinePtrDirect(line); const Pixel* line2 = lastFrames[2]->template getLinePtrDirect(line); const Pixel* line3 = lastFrames[3]->template getLinePtrDirect(line); if ((width0 != width3) || (width0 != width2) || (width0 != width1)) { // Not all the same width. width = width0; return line0; } // Prefer to write directly to the output buffer, if that's not // possible store the intermediate result in a temp buffer. VLA_SSE_ALIGNED(Pixel, buf2, width0); Pixel* buf = static_cast(buf_); Pixel* out = (width0 <= bufWidth) ? buf : buf2; // Detect pixels that alternate between two different color values and // replace those with the average color. We search for an alternating // sequence with length (at least) 4. Or IOW we look for "A B A B". // The implementation below also detects a constant pixel value // "A A A A" as alternating between "A" and "A", but that's fine. #ifdef __SSE2__ if (width0 != 1) { size_t numBytes = width0 * sizeof(Pixel); assert((numBytes % sizeof(__m128i)) == 0); assert(numBytes != 0); assert((reinterpret_cast(line0) % sizeof(__m128i)) == 0); assert((reinterpret_cast(line1) % sizeof(__m128i)) == 0); assert((reinterpret_cast(line2) % sizeof(__m128i)) == 0); assert((reinterpret_cast(line3) % sizeof(__m128i)) == 0); assert((reinterpret_cast(out ) % sizeof(__m128i)) == 0); auto* in0 = reinterpret_cast(line0) + numBytes; auto* in1 = reinterpret_cast(line1) + numBytes; auto* in2 = reinterpret_cast(line2) + numBytes; auto* in3 = reinterpret_cast(line3) + numBytes; auto* dst = reinterpret_cast< char*>(out ) + numBytes; Pixel mask = pixelOps.getBlendMask(); auto x = -ptrdiff_t(numBytes); do { __m128i a0 = _mm_load_si128(reinterpret_cast(in0 + x)); __m128i a1 = _mm_load_si128(reinterpret_cast(in1 + x)); __m128i a2 = _mm_load_si128(reinterpret_cast(in2 + x)); __m128i a3 = _mm_load_si128(reinterpret_cast(in3 + x)); __m128i e02 = _mm_cmpeq_epi32(a0, a2); // a0 == a2 __m128i e13 = _mm_cmpeq_epi32(a1, a3); // a1 == a3 __m128i cnd = _mm_and_si128(e02, e13); // (a0==a2) && (a1==a3) __m128i a01 = blend(a0, a1, mask); __m128i p = _mm_xor_si128(a0, a01); __m128i q = _mm_and_si128(p, cnd); __m128i r = _mm_xor_si128(q, a0); // select(a0, a01, cnd) _mm_store_si128(reinterpret_cast<__m128i*>(dst + x), r); x += sizeof(__m128i); } while (x < 0); goto end; } #endif for (unsigned x = 0; x < width0; ++x) { out[x] = ((line0[x] == line2[x]) && (line1[x] == line3[x])) ? pixelOps.template blend<1, 1>(line0[x], line1[x]) : line0[x]; } #ifdef __SSE2__ end: #endif if (width0 <= bufWidth) { // It it already fits, we're done width = width0; } else { // Otherwise scale so that it does fit. width = bufWidth; scaleLine(out, buf, width0, bufWidth); } return buf; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/Deflicker.hh000066400000000000000000000011301257557151200205400ustar00rootroot00000000000000#ifndef DEFLICKER_HH #define DEFLICKER_HH #include "FrameSource.hh" #include namespace openmsx { class RawFrame; class Deflicker : public FrameSource { public: // Factory method, actually returns a Deflicker subclass. static std::unique_ptr create( const SDL_PixelFormat& format, std::unique_ptr* lastFrames); void init(); protected: Deflicker(const SDL_PixelFormat& format, std::unique_ptr* lastFrames); unsigned getLineWidth(unsigned line) const override; std::unique_ptr* lastFrames; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/DeinterlacedFrame.cc000066400000000000000000000017721257557151200222100ustar00rootroot00000000000000#include "DeinterlacedFrame.hh" #include namespace openmsx { DeinterlacedFrame::DeinterlacedFrame(const SDL_PixelFormat& format) : FrameSource(format) { } void DeinterlacedFrame::init(FrameSource* evenField, FrameSource* oddField) { FrameSource::init(FIELD_NONINTERLACED); // TODO: I think these assertions make sense, but we cannot currently // guarantee them. See TODO in PostProcessor::paint. //assert(evenField->getField() == FrameSource::FIELD_EVEN); //assert(oddField->getField() == FrameSource::FIELD_ODD); assert(evenField->getHeight() == oddField->getHeight()); setHeight(2 * evenField->getHeight()); fields[0] = evenField; fields[1] = oddField; } unsigned DeinterlacedFrame::getLineWidth(unsigned line) const { return fields[line & 1]->getLineWidth(line >> 1); } const void* DeinterlacedFrame::getLineInfo( unsigned line, unsigned& width, void* buf, unsigned bufWidth) const { return fields[line & 1]->getLineInfo(line >> 1, width, buf, bufWidth); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/DeinterlacedFrame.hh000066400000000000000000000015561257557151200222220ustar00rootroot00000000000000#ifndef DEINTERLACEDFRAME_HH #define DEINTERLACEDFRAME_HH #include "FrameSource.hh" namespace openmsx { /** Produces a deinterlaced video frame based on two other FrameSources * (typically two RawFrames) containing the even and odd field. * This class does not copy the data from the input FrameSources. */ class DeinterlacedFrame final : public FrameSource { public: explicit DeinterlacedFrame(const SDL_PixelFormat& format); void init(FrameSource* evenField, FrameSource* oddField); private: unsigned getLineWidth(unsigned line) const override; const void* getLineInfo( unsigned line, unsigned& width, void* buf, unsigned bufWidth) const override; /** The original frames whose data will be deinterlaced. * The even frame is at index 0, the odd frame at index 1. */ FrameSource* fields[2]; }; } // namespace openmsx #endif // DEINTERLACEDFRAME_HH openMSX-RELEASE_0_12_0/src/video/Display.cc000066400000000000000000000354271257557151200202630ustar00rootroot00000000000000#include "Display.hh" #include "RendererFactory.hh" #include "Layer.hh" #include "VideoSystem.hh" #include "VideoLayer.hh" #include "EventDistributor.hh" #include "FinishFrameEvent.hh" #include "FileOperations.hh" #include "FileContext.hh" #include "InputEvents.hh" #include "CliComm.hh" #include "Timer.hh" #include "BooleanSetting.hh" #include "IntegerSetting.hh" #include "EnumSetting.hh" #include "Reactor.hh" #include "MSXMotherBoard.hh" #include "HardwareConfig.hh" #include "XMLElement.hh" #include "VideoSystemChangeListener.hh" #include "CommandException.hh" #include "StringOp.hh" #include "Version.hh" #include "build-info.hh" #include "checked_cast.hh" #include "outer.hh" #include "stl.hh" #include "unreachable.hh" #include #include using std::string; using std::vector; namespace openmsx { Display::Display(Reactor& reactor_) : RTSchedulable(reactor_.getRTScheduler()) , screenShotCmd(reactor_.getCommandController()) , fpsInfo(reactor_.getOpenMSXInfoCommand()) , osdGui(reactor_.getCommandController(), *this) , reactor(reactor_) , renderSettings(reactor.getCommandController()) , commandConsole(reactor.getGlobalCommandController(), reactor.getEventDistributor(), *this) , currentRenderer(RenderSettings::UNINITIALIZED) , switchInProgress(false) { frameDurationSum = 0; for (unsigned i = 0; i < NUM_FRAME_DURATIONS; ++i) { frameDurations.addFront(20); frameDurationSum += 20; } prevTimeStamp = Timer::getTime(); EventDistributor& eventDistributor = reactor.getEventDistributor(); eventDistributor.registerEventListener(OPENMSX_FINISH_FRAME_EVENT, *this); eventDistributor.registerEventListener(OPENMSX_SWITCH_RENDERER_EVENT, *this); eventDistributor.registerEventListener(OPENMSX_MACHINE_LOADED_EVENT, *this); eventDistributor.registerEventListener(OPENMSX_EXPOSE_EVENT, *this); #if PLATFORM_ANDROID eventDistributor.registerEventListener(OPENMSX_FOCUS_EVENT, *this); #endif renderSettings.getRendererSetting().attach(*this); renderSettings.getFullScreenSetting().attach(*this); renderSettings.getScaleFactorSetting().attach(*this); renderFrozen = false; } Display::~Display() { renderSettings.getRendererSetting().detach(*this); renderSettings.getFullScreenSetting().detach(*this); renderSettings.getScaleFactorSetting().detach(*this); EventDistributor& eventDistributor = reactor.getEventDistributor(); #if PLATFORM_ANDROID eventDistributor.unregisterEventListener(OPENMSX_FOCUS_EVENT, *this); #endif eventDistributor.unregisterEventListener(OPENMSX_EXPOSE_EVENT, *this); eventDistributor.unregisterEventListener(OPENMSX_MACHINE_LOADED_EVENT, *this); eventDistributor.unregisterEventListener(OPENMSX_SWITCH_RENDERER_EVENT, *this); eventDistributor.unregisterEventListener(OPENMSX_FINISH_FRAME_EVENT, *this); resetVideoSystem(); assert(listeners.empty()); } void Display::createVideoSystem() { assert(!videoSystem); assert(currentRenderer == RenderSettings::UNINITIALIZED); assert(!switchInProgress); currentRenderer = renderSettings.getRenderer(); switchInProgress = true; doRendererSwitch(); } VideoSystem& Display::getVideoSystem() { assert(videoSystem); return *videoSystem; } void Display::resetVideoSystem() { videoSystem.reset(); // At this point all layers expect for the Video9000 layer // should be gone. //assert(layers.empty()); } CliComm& Display::getCliComm() const { return reactor.getCliComm(); } void Display::attach(VideoSystemChangeListener& listener) { assert(!contains(listeners, &listener)); listeners.push_back(&listener); } void Display::detach(VideoSystemChangeListener& listener) { listeners.erase(find_unguarded(listeners, &listener)); } Layer* Display::findActiveLayer() const { for (auto& l : layers) { if (l->getZ() == Layer::Z_MSX_ACTIVE) { return l; } } return nullptr; } Display::Layers::iterator Display::baseLayer() { // Note: It is possible to cache this, but since the number of layers is // low at the moment, it's not really worth it. auto it = end(layers); while (true) { if (it == begin(layers)) { // There should always be at least one opaque layer. // TODO: This is not true for DummyVideoSystem. // Anyway, a missing layer will probably stand out visually, // so do we really have to assert on it? //UNREACHABLE; return it; } --it; if ((*it)->getCoverage() == Layer::COVER_FULL) return it; } } void Display::executeRT() { repaint(); } int Display::signalEvent(const std::shared_ptr& event) { if (event->getType() == OPENMSX_FINISH_FRAME_EVENT) { auto& ffe = checked_cast(*event); if (ffe.needRender()) { repaint(); reactor.getEventDistributor().distributeEvent( std::make_shared( OPENMSX_FRAME_DRAWN_EVENT)); } } else if (event->getType() == OPENMSX_SWITCH_RENDERER_EVENT) { doRendererSwitch(); } else if (event->getType() == OPENMSX_MACHINE_LOADED_EVENT) { setWindowTitle(); } else if (event->getType() == OPENMSX_EXPOSE_EVENT) { // Don't render too often, and certainly not when the screen // will anyway soon be rendered. repaintDelayed(100 * 1000); // 10fps } else if (PLATFORM_ANDROID && event->getType() == OPENMSX_FOCUS_EVENT) { // On Android, the rendering must be frozen when the app is sent to // the background, because Android takes away all graphics resources // from the app. It simply destroys the entire graphics context. // Though, a repaint() must happen within the focus-lost event // so that the SDL Android port realises that the graphix context // is gone and will re-build it again on the first flush to the // surface after the focus has been regained. // Perform a repaint before updating the renderFrozen flag: // -When loosing the focus, this repaint will flush a last // time the SDL surface, making sure that the Android SDL // port discovers that the graphics context is gone. // -When gaining the focus, this repaint does nothing as // the renderFrozen flag is still false repaint(); auto& focusEvent = checked_cast(*event); ad_printf("Setting renderFrozen to %d", !focusEvent.getGain()); renderFrozen = !focusEvent.getGain(); } return 0; } void Display::setWindowTitle() { string title = Version::full(); if (!Version::RELEASE) { title += " ["; title += BUILD_FLAVOUR; title += ']'; } if (MSXMotherBoard* motherboard = reactor.getMotherBoard()) { if (const HardwareConfig* machine = motherboard->getMachineConfig()) { const XMLElement& config = machine->getConfig(); title += " - " + config.getChild("info").getChildData("manufacturer") + ' ' + config.getChild("info").getChildData("code"); } } videoSystem->setWindowTitle(title); } void Display::update(const Setting& setting) { if (&setting == &renderSettings.getRendererSetting()) { checkRendererSwitch(); } else if (&setting == &renderSettings.getFullScreenSetting()) { checkRendererSwitch(); } else if (&setting == &renderSettings.getScaleFactorSetting()) { checkRendererSwitch(); } else { UNREACHABLE; } } void Display::checkRendererSwitch() { if (switchInProgress) { // This method only queues a request to switch renderer (see // comments below why). If there already is such a request // queued we don't need to do it again. return; } auto newRenderer = renderSettings.getRenderer(); if ((newRenderer != currentRenderer) || !getVideoSystem().checkSettings()) { currentRenderer = newRenderer; // don't do the actualing swithing in the Tcl callback // it seems creating and destroying Settings (= Tcl vars) // causes problems??? switchInProgress = true; reactor.getEventDistributor().distributeEvent( std::make_shared( OPENMSX_SWITCH_RENDERER_EVENT)); } } void Display::doRendererSwitch() { assert(switchInProgress); bool success = false; while (!success) { try { doRendererSwitch2(); success = true; } catch (MSXException& e) { auto& rendererSetting = renderSettings.getRendererSetting(); string errorMsg = "Couldn't activate renderer " + rendererSetting.getString() + ": " + e.getMessage(); // now try some things that might work against this: if (rendererSetting.getEnum() != RenderSettings::SDL) { errorMsg += "\nTrying to switch to SDL renderer instead..."; rendererSetting.setEnum(RenderSettings::SDL); currentRenderer = RenderSettings::SDL; } else { auto& scaleFactorSetting = renderSettings.getScaleFactorSetting(); unsigned curval = scaleFactorSetting.getInt(); if (curval == 1) throw MSXException(e.getMessage() + " (and I have no other ideas to try...)"); // give up and die... :( errorMsg += "\nTrying to decrease scale_factor setting from " + StringOp::toString(curval) + " to " + StringOp::toString(curval - 1) + "..."; scaleFactorSetting.setInt(curval - 1); } getCliComm().printWarning(errorMsg); } } switchInProgress = false; } void Display::doRendererSwitch2() { for (auto& l : listeners) { l->preVideoSystemChange(); } resetVideoSystem(); videoSystem = RendererFactory::createVideoSystem(reactor); setWindowTitle(); for (auto& l : listeners) { l->postVideoSystemChange(); } } void Display::repaint() { if (switchInProgress) { // The checkRendererSwitch() method will queue a // SWITCH_RENDERER_EVENT, but before that event is handled // we shouldn't do any repaints (with inconsistent setting // values and render objects). This can happen when this // method gets called because of a DELAYED_REPAINT_EVENT // (DELAYED_REPAINT_EVENT was already queued before // SWITCH_RENDERER_EVENT is queued). return; } cancelRT(); // cancel delayed repaint if (!renderFrozen) { assert(videoSystem); if (OutputSurface* surface = videoSystem->getOutputSurface()) { repaint(*surface); videoSystem->flush(); } } // update fps statistics auto now = Timer::getTime(); auto duration = now - prevTimeStamp; prevTimeStamp = now; frameDurationSum += duration - frameDurations.removeBack(); frameDurations.addFront(duration); } void Display::repaint(OutputSurface& surface) { for (auto it = baseLayer(); it != end(layers); ++it) { if ((*it)->getCoverage() != Layer::COVER_NONE) { (*it)->paint(surface); } } } void Display::repaintDelayed(uint64_t delta) { if (isPendingRT()) { // already a pending repaint return; } scheduleRT(unsigned(delta)); } void Display::addLayer(Layer& layer) { int z = layer.getZ(); auto it = find_if(begin(layers), end(layers), [&](Layer* l) { return l->getZ() > z; }); layers.insert(it, &layer); layer.setDisplay(*this); } void Display::removeLayer(Layer& layer) { layers.erase(find_unguarded(layers, &layer)); } void Display::updateZ(Layer& layer) { // Remove at old Z-index... removeLayer(layer); // ...and re-insert at new Z-index. addLayer(layer); } // ScreenShotCmd Display::ScreenShotCmd::ScreenShotCmd(CommandController& commandController) : Command(commandController, "screenshot") { } void Display::ScreenShotCmd::execute(array_ref tokens, TclObject& result) { auto& display = OUTER(Display, screenShotCmd); bool rawShot = false; bool withOsd = false; bool doubleSize = false; string_ref prefix = "openmsx"; vector arguments; for (unsigned i = 1; i < tokens.size(); ++i) { string_ref tok = tokens[i].getString(); if (StringOp::startsWith(tok, '-')) { if (tok == "--") { arguments.insert(end(arguments), std::begin(tokens) + i + 1, std::end(tokens)); break; } if (tok == "-prefix") { if (++i == tokens.size()) { throw CommandException("Missing argument"); } prefix = tokens[i].getString(); } else if (tok == "-raw") { rawShot = true; } else if (tok == "-msxonly") { display.getCliComm().printWarning( "The -msxonly option has been deprecated and will " "be removed in a future release. Instead, use the " "-raw option for the same effect."); rawShot = true; } else if (tok == "-doublesize") { doubleSize = true; } else if (tok == "-with-osd") { withOsd = true; } else { throw CommandException("Invalid option: " + tok); } } else { arguments.push_back(tokens[i]); } } if (doubleSize && !rawShot) { throw CommandException("-doublesize option can only be used in " "combination with -raw"); } if (rawShot && withOsd) { throw CommandException("-with-osd cannot be used in " "combination with -raw"); } string_ref fname; switch (arguments.size()) { case 0: // nothing break; case 1: fname = arguments[0].getString(); break; default: throw SyntaxError(); } string filename = FileOperations::parseCommandFileArgument( fname, "screenshots", prefix, ".png"); if (!rawShot) { // include all layers (OSD stuff, console) try { display.getVideoSystem().takeScreenShot(filename, withOsd); } catch (MSXException& e) { throw CommandException( "Failed to take screenshot: " + e.getMessage()); } } else { auto videoLayer = dynamic_cast( display.findActiveLayer()); if (!videoLayer) { throw CommandException( "Current renderer doesn't support taking screenshots."); } unsigned height = doubleSize ? 480 : 240; try { videoLayer->takeRawScreenShot(height, filename); } catch (MSXException& e) { throw CommandException( "Failed to take screenshot: " + e.getMessage()); } } display.getCliComm().printInfo("Screen saved to " + filename); result.setString(filename); } string Display::ScreenShotCmd::help(const vector& /*tokens*/) const { // Note: -no-sprites option is implemented in Tcl return "screenshot Write screenshot to file \"openmsxNNNN.png\"\n" "screenshot Write screenshot to indicated file\n" "screenshot -prefix foo Write screenshot to file \"fooNNNN.png\"\n" "screenshot -raw 320x240 raw screenshot (of MSX screen only)\n" "screenshot -raw -doublesize 640x480 raw screenshot (of MSX screen only)\n" "screenshot -with-osd Include OSD elements in the screenshot\n" "screenshot -no-sprites Don't include sprites in the screenshot\n"; } void Display::ScreenShotCmd::tabCompletion(vector& tokens) const { static const char* const extra[] = { "-prefix", "-raw", "-doublesize", "-with-osd", "-no-sprites", }; completeFileName(tokens, userFileContext(), extra); } // FpsInfoTopic Display::FpsInfoTopic::FpsInfoTopic(InfoCommand& openMSXInfoCommand) : InfoTopic(openMSXInfoCommand, "fps") { } void Display::FpsInfoTopic::execute(array_ref /*tokens*/, TclObject& result) const { auto& display = OUTER(Display, fpsInfo); float fps = 1000000.0f * Display::NUM_FRAME_DURATIONS / display.frameDurationSum; result.setDouble(fps); } string Display::FpsInfoTopic::help(const vector& /*tokens*/) const { return "Returns the current rendering speed in frames per second."; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/Display.hh000066400000000000000000000061031257557151200202620ustar00rootroot00000000000000#ifndef DISPLAY_HH #define DISPLAY_HH #include "RenderSettings.hh" #include "Command.hh" #include "CommandConsole.hh" #include "InfoTopic.hh" #include "OSDGUI.hh" #include "EventListener.hh" #include "LayerListener.hh" #include "RTSchedulable.hh" #include "Observer.hh" #include "CircularBuffer.hh" #include #include #include namespace openmsx { class Layer; class Reactor; class VideoSystem; class CliComm; class VideoSystemChangeListener; class Setting; class OutputSurface; /** Represents the output window/screen of openMSX. * A display contains several layers. */ class Display final : public EventListener, private Observer , private LayerListener, private RTSchedulable { public: using Layers = std::vector; explicit Display(Reactor& reactor); ~Display(); void createVideoSystem(); VideoSystem& getVideoSystem(); CliComm& getCliComm() const; RenderSettings& getRenderSettings() { return renderSettings; } OSDGUI& getOSDGUI() { return osdGui; } CommandConsole& getCommandConsole() { return commandConsole; } /** Redraw the display. */ void repaint(); void repaint(OutputSurface& surface); void repaintDelayed(uint64_t delta); void addLayer(Layer& layer); void removeLayer(Layer& layer); void attach(VideoSystemChangeListener& listener); void detach(VideoSystemChangeListener& listener); Layer* findActiveLayer() const; const Layers& getAllLayers() const { return layers; } private: void resetVideoSystem(); void setWindowTitle(); // EventListener interface int signalEvent(const std::shared_ptr& event) override; // RTSchedulable void executeRT() override; // Observer interface void update(const Setting& setting) override; void checkRendererSwitch(); void doRendererSwitch(); void doRendererSwitch2(); /** Find frontmost opaque layer. */ Layers::iterator baseLayer(); // LayerListener interface void updateZ(Layer& layer) override; Layers layers; std::unique_ptr videoSystem; std::vector listeners; // fps related data static const unsigned NUM_FRAME_DURATIONS = 50; CircularBuffer frameDurations; uint64_t frameDurationSum; uint64_t prevTimeStamp; struct ScreenShotCmd final : Command { ScreenShotCmd(CommandController& commandController); void execute(array_ref tokens, TclObject& result) override; std::string help(const std::vector& tokens) const override; void tabCompletion(std::vector& tokens) const override; } screenShotCmd; struct FpsInfoTopic final : InfoTopic { FpsInfoTopic(InfoCommand& openMSXInfoCommand); void execute(array_ref tokens, TclObject& result) const override; std::string help(const std::vector& tokens) const override; } fpsInfo; OSDGUI osdGui; Reactor& reactor; RenderSettings renderSettings; CommandConsole commandConsole; // the current renderer RenderSettings::RendererID currentRenderer; bool renderFrozen; bool switchInProgress; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/DisplayMode.hh000066400000000000000000000132271257557151200210740ustar00rootroot00000000000000#ifndef DISPLAYMODE_HH #define DISPLAYMODE_HH #include "openmsx.hh" namespace openmsx { /** Represents a VDP display mode. * A display mode determines how bytes in the VRAM are converted to pixel * colors. * A display mode consists of a base mode with YJK filters on top. * Only the V9958 supports YJK filters. */ class DisplayMode { private: explicit DisplayMode(byte mode_) : mode(mode_) {} /** Display mode flags: YAE YJK M5..M1. * The YAE flag indicates whether YAE is active, not just the value of * the corresponding mode bit; so if YJK is 0, YAE is 0 as well. */ byte mode; public: enum { GRAPHIC1 = 0x00, // Graphic 1 TEXT1 = 0x01, // Text 1 MULTICOLOR = 0x02, // Multicolor GRAPHIC2 = 0x04, // Graphic 2 TEXT1Q = 0x05, // !! MULTIQ = 0x06, // !! GRAPHIC3 = 0x08, // Graphic 3 TEXT2 = 0x09, // Text 2 GRAPHIC4 = 0x0C, // Graphic 4 GRAPHIC5 = 0x10, // Graphic 5 GRAPHIC6 = 0x14, // Graphic 6 GRAPHIC7 = 0x1C // Graphic 7 }; /** Bits of VDP register 0 that encode part of the display mode. */ static const byte REG0_MASK = 0x0E; /** Bits of VDP register 1 that encode part of the display mode. */ static const byte REG1_MASK = 0x18; /** Bits of VDP register 25 that encode part of the display mode. */ static const byte REG25_MASK = 0x18; /** Encoding of YJK flag. */ static const byte YJK = 0x20; /** Encoding of YAE flag. */ static const byte YAE = 0x40; /** Create the initial display mode. */ DisplayMode() { reset(); } /** Create a specific display mode. * @param reg0 The contents of VDP register 0. * @param reg1 The contents of VDP register 1. * @param reg25 The contents of VDP register 25; * on non-V9958 chips, pass 0. */ DisplayMode(byte reg0, byte reg1, byte reg25) { if ((reg25 & 0x08) == 0) reg25 = 0; // If YJK is off, ignore YAE. mode = ((reg25 & 0x18) << 2) // YAE YJK | ((reg0 & 0x0E) << 1) // M5..M3 | ((reg1 & 0x08) >> 2) // M2 | ((reg1 & 0x10) >> 4); // M1 } inline DisplayMode updateReg25(byte reg25) const { if ((reg25 & 0x08) == 0) reg25 = 0; // If YJK is off, ignore YAE. return DisplayMode(getBase() | ((reg25 & 0x18) << 2)); } /** Bring the display mode to its initial state. */ inline void reset() { mode = 0; } /** Assignment operator. */ inline DisplayMode& operator=(const DisplayMode& newMode) { mode = newMode.mode; return *this; } /** Equals operator. */ inline bool operator==(const DisplayMode& otherMode) const { return mode == otherMode.mode; } /** Does-not-equal operator. */ inline bool operator!=(const DisplayMode& otherMode) const { return mode != otherMode.mode; } /** Get the dispay mode as a byte: YAE YJK M5..M1 combined. * @return The byte representation of this display mode, * in the range [0..0x7F]. */ inline byte getByte() const { return mode; } /** Used for de-serialization. */ inline void setByte(byte mode_) { mode = mode_; } /** Get the base dispay mode as an integer: M5..M1 combined. * If YJK is active, the base mode is the underlying display mode. * @return The integer representation of the base of this display mode, * in the range [0..0x1F]. */ inline byte getBase() const { return mode & 0x1F; } /** Was this mode introduced by the V9938? * @return True iff the base of this mode only is available on V9938/58. */ inline bool isV9938Mode() const { return (mode & 0x18) != 0; } /** Is the current mode a text mode? * Text1 and Text2 are text modes. * @return True iff the current mode is a text mode. */ inline bool isTextMode() const { return (mode == TEXT1) || (mode == TEXT2) || (mode == TEXT1Q); } /** Is the current mode a bitmap mode? * Graphic4 and higher are bitmap modes. * @return True iff the current mode is a bitmap mode. */ inline bool isBitmapMode() const { return getBase() >= 0x0C; } /** Is VRAM "planar" in the current display mode? * Graphic 6 and 7 spread their bytes over two VRAM ICs, * such that the even bytes go to the first half of the address * space and the odd bytes to the second half. * @return True iff the current display mode has planar VRAM. */ inline bool isPlanar() const { // TODO: Is the display mode check OK? Profile undefined modes. return (mode & 0x14) == 0x14; } /** Are sprite pixels narrow? */ inline bool isSpriteNarrow() const { // TODO: Check what happens to sprites in Graphic5 + YJK/YAE. return mode == GRAPHIC5; } /** Get the sprite mode of this display mode. * @return The current sprite mode: * 0 means no sprites, * 1 means sprite mode 1 (MSX1 display modes), * 2 means sprite mode 2 (MSX2 display modes). */ inline int getSpriteMode(bool isMSX1) const { switch (getBase()) { case GRAPHIC1: case MULTICOLOR: case GRAPHIC2: return 1; case MULTIQ: // depends on VDP type return isMSX1 ? 1 : 0; case GRAPHIC3: case GRAPHIC4: case GRAPHIC5: case GRAPHIC6: case GRAPHIC7: return 2; case TEXT1: case TEXT1Q: case TEXT2: default: // and all other (bogus) modes // Verified on real V9958: none of the bogus modes // show sprites. // TODO check on TMSxxxx return 0; } } /** Get number of pixels on a display line in this mode. * @return 512 for Text 2, Graphic 5 and 6, 256 for all other modes. * TODO: Would it make more sense to treat Text 2 as 480 pixels? */ inline unsigned getLineWidth() const { // Note: Testing "mode" instead of "base mode" ensures that YJK // modes are treated as 256 pixels wide. return mode == TEXT2 || mode == GRAPHIC5 || mode == GRAPHIC6 ? 512 : 256; } }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/DoubledFrame.cc000066400000000000000000000014471257557151200212020ustar00rootroot00000000000000#include "DoubledFrame.hh" #include namespace openmsx { DoubledFrame::DoubledFrame(const SDL_PixelFormat& format) : FrameSource(format) { } void DoubledFrame::init(FrameSource* field_, unsigned skip_) { FrameSource::init(FIELD_NONINTERLACED); field = field_; skip = skip_; setHeight(2 * field->getHeight()); } unsigned DoubledFrame::getLineWidth(unsigned line) const { int t = line - skip; return (t >= 0) ? field->getLineWidth(t / 2) : 1; } const void* DoubledFrame::getLineInfo( unsigned line, unsigned& width, void* buf, unsigned bufWidth) const { static const uint32_t blackPixel = 0; // both 16bppp and 32bpp int t = line - skip; if (t >= 0) { return field->getLineInfo(t / 2, width, buf, bufWidth); } else { width = 1; return &blackPixel; } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/DoubledFrame.hh000066400000000000000000000013541257557151200212110ustar00rootroot00000000000000#ifndef DOUBLEDFRAME_HH #define DOUBLEDFRAME_HH #include "FrameSource.hh" namespace openmsx { /** Produces a video frame that has every line from the input frame twice * plus a number of black lines at the top. * This class does not copy the data from the input FrameSource. */ class DoubledFrame final : public FrameSource { public: explicit DoubledFrame(const SDL_PixelFormat& format); void init(FrameSource* field, unsigned skip); private: unsigned getLineWidth(unsigned line) const override; const void* getLineInfo( unsigned line, unsigned& width, void* buf, unsigned bufWidth) const override; /** The original frame whose data will be doubled. */ FrameSource* field; unsigned skip; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/DummyRenderer.cc000066400000000000000000000044661257557151200214370ustar00rootroot00000000000000#include "DummyRenderer.hh" #include "DisplayMode.hh" namespace openmsx { PostProcessor* DummyRenderer::getPostProcessor() const { return nullptr; } void DummyRenderer::reInit() { } void DummyRenderer::frameStart(EmuTime::param /*time*/) { } void DummyRenderer::frameEnd(EmuTime::param /*time*/) { } void DummyRenderer::updateTransparency(bool /*enabled*/, EmuTime::param /*time*/) { } void DummyRenderer::updateSuperimposing(const RawFrame* /*videoSource*/, EmuTime::param /*time*/) { } void DummyRenderer::updateForegroundColor(int /*color*/, EmuTime::param /*time*/) { } void DummyRenderer::updateBackgroundColor(int /*color*/, EmuTime::param /*time*/) { } void DummyRenderer::updateBlinkForegroundColor(int /*color*/, EmuTime::param /*time*/) { } void DummyRenderer::updateBlinkBackgroundColor(int /*color*/, EmuTime::param /*time*/) { } void DummyRenderer::updateBlinkState(bool /*enabled*/, EmuTime::param /*time*/) { } void DummyRenderer::updatePalette(int /*index*/, int /*grb*/, EmuTime::param /*time*/) { } void DummyRenderer::updateVerticalScroll(int /*scroll*/, EmuTime::param /*time*/) { } void DummyRenderer::updateHorizontalScrollLow(byte /*scroll*/, EmuTime::param /*time*/) { } void DummyRenderer::updateHorizontalScrollHigh(byte /*scroll*/, EmuTime::param /*time*/) { } void DummyRenderer::updateBorderMask(bool /*masked*/, EmuTime::param /*time*/) { } void DummyRenderer::updateMultiPage(bool /*multiPage*/, EmuTime::param /*time*/) { } void DummyRenderer::updateHorizontalAdjust(int /*adjust*/, EmuTime::param /*time*/) { } void DummyRenderer::updateDisplayEnabled(bool /*enabled*/, EmuTime::param /*time*/) { } void DummyRenderer::updateDisplayMode(DisplayMode /*mode*/, EmuTime::param /*time*/) { } void DummyRenderer::updateNameBase(int /*addr*/, EmuTime::param /*time*/) { } void DummyRenderer::updatePatternBase(int /*addr*/, EmuTime::param /*time*/) { } void DummyRenderer::updateColorBase(int /*addr*/, EmuTime::param /*time*/) { } void DummyRenderer::updateSpritesEnabled(bool /*enabled*/, EmuTime::param /*time*/) { } void DummyRenderer::updateVRAM(unsigned /*offset*/, EmuTime::param /*time*/) { } void DummyRenderer::updateWindow(bool /*enabled*/, EmuTime::param /*time*/) { } void DummyRenderer::paint(OutputSurface& /*output*/) { } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/DummyRenderer.hh000066400000000000000000000040101257557151200214320ustar00rootroot00000000000000#ifndef DUMMYRENDERER_HH #define DUMMYRENDERER_HH #include "Renderer.hh" #include "Layer.hh" namespace openmsx { /** Dummy Renderer */ class DummyRenderer final : public Renderer, public Layer { public: // Renderer interface: PostProcessor* getPostProcessor() const override; void reInit() override; void frameStart(EmuTime::param time) override; void frameEnd(EmuTime::param time) override; void updateTransparency(bool enabled, EmuTime::param time) override; void updateSuperimposing(const RawFrame* videoSource, EmuTime::param time) override; void updateForegroundColor(int color, EmuTime::param time) override; void updateBackgroundColor(int color, EmuTime::param time) override; void updateBlinkForegroundColor(int color, EmuTime::param time) override; void updateBlinkBackgroundColor(int color, EmuTime::param time) override; void updateBlinkState(bool enabled, EmuTime::param time) override; void updatePalette(int index, int grb, EmuTime::param time) override; void updateVerticalScroll(int scroll, EmuTime::param time) override; void updateHorizontalScrollLow(byte scroll, EmuTime::param time) override; void updateHorizontalScrollHigh(byte scroll, EmuTime::param time) override; void updateBorderMask(bool masked, EmuTime::param time) override; void updateMultiPage(bool multiPage, EmuTime::param time) override; void updateHorizontalAdjust(int adjust, EmuTime::param time) override; void updateDisplayEnabled(bool enabled, EmuTime::param time) override; void updateDisplayMode(DisplayMode mode, EmuTime::param time) override; void updateNameBase(int addr, EmuTime::param time) override; void updatePatternBase(int addr, EmuTime::param time) override; void updateColorBase(int addr, EmuTime::param time) override; void updateSpritesEnabled(bool enabled, EmuTime::param time) override; void updateVRAM(unsigned offset, EmuTime::param time) override; void updateWindow(bool enabled, EmuTime::param time) override; // Layer interface: void paint(OutputSurface& output) override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/DummyVideoSystem.cc000066400000000000000000000013201257557151200221260ustar00rootroot00000000000000#include "DummyVideoSystem.hh" #include "Rasterizer.hh" #include "V9990Rasterizer.hh" #include "LDRasterizer.hh" #include "components.hh" #include "unreachable.hh" namespace openmsx { std::unique_ptr DummyVideoSystem::createRasterizer(VDP& /*vdp*/) { UNREACHABLE; return nullptr; } std::unique_ptr DummyVideoSystem::createV9990Rasterizer( V9990& /*vdp*/) { UNREACHABLE; return nullptr; } #if COMPONENT_LASERDISC std::unique_ptr DummyVideoSystem::createLDRasterizer( LaserdiscPlayer& /*ld*/) { UNREACHABLE; return nullptr; } #endif void DummyVideoSystem::flush() { } OutputSurface* DummyVideoSystem::getOutputSurface() { return nullptr; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/DummyVideoSystem.hh000066400000000000000000000011071257557151200221430ustar00rootroot00000000000000#ifndef DUMMYVIDEOSYSTEM_HH #define DUMMYVIDEOSYSTEM_HH #include "VideoSystem.hh" #include "components.hh" namespace openmsx { class DummyVideoSystem final : public VideoSystem { public: // VideoSystem interface: std::unique_ptr createRasterizer(VDP& vdp) override; std::unique_ptr createV9990Rasterizer( V9990& vdp) override; #if COMPONENT_LASERDISC std::unique_ptr createLDRasterizer( LaserdiscPlayer& ld) override; #endif void flush() override; OutputSurface* getOutputSurface() override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/FBPostProcessor.cc000066400000000000000000000273631257557151200217130ustar00rootroot00000000000000#include "FBPostProcessor.hh" #include "RawFrame.hh" #include "StretchScalerOutput.hh" #include "ScalerOutput.hh" #include "RenderSettings.hh" #include "Scaler.hh" #include "ScalerFactory.hh" #include "OutputSurface.hh" #include "IntegerSetting.hh" #include "FloatSetting.hh" #include "BooleanSetting.hh" #include "EnumSetting.hh" #include "Math.hh" #include "aligned.hh" #include "random.hh" #include "xrange.hh" #include #include #include #ifdef __SSE2__ #include #endif namespace openmsx { static const unsigned NOISE_SHIFT = 8192; static const unsigned NOISE_BUF_SIZE = 2 * NOISE_SHIFT; SSE_ALIGNED(static signed char noiseBuf[NOISE_BUF_SIZE]); template void FBPostProcessor::preCalcNoise(float factor) { // We skip noise drawing if the factor is 0, so there is no point in // initializing the random data in that case. if (factor == 0.0f) return; // for 32bpp groups of 4 consecutive noiseBuf elements (starting at // 4 element boundaries) must have the same value. Later optimizations // depend on it. float scale[4]; if (sizeof(Pixel) == 4) { // 32bpp // TODO ATM we compensate for big endian here. A better // alternative is to turn noiseBuf into an array of ints (it's // now bytes) and in the 16bpp code extract R,G,B components // from those ints const Pixel p = Pixel(OPENMSX_BIGENDIAN ? 0x00010203 : 0x03020100); // TODO we can also fill the array with 'factor' and only set // 'alpha' to 0.0. But PixelOperations doesn't offer a simple // way to get the position of the alpha byte (yet). scale[0] = scale[1] = scale[2] = scale[3] = 0.0f; scale[pixelOps.red (p)] = factor; scale[pixelOps.green(p)] = factor; scale[pixelOps.blue (p)] = factor; } else { // 16bpp scale[0] = (pixelOps.getMaxRed() / 255.0f) * factor; scale[1] = (pixelOps.getMaxGreen() / 255.0f) * factor; scale[2] = (pixelOps.getMaxBlue() / 255.0f) * factor; scale[3] = 0.0f; } auto& generator = global_urng(); // fast (non-cryptographic) random numbers std::normal_distribution distribution(0.0f, 1.0f); for (unsigned i = 0; i < NOISE_BUF_SIZE; i += 4) { float r = distribution(generator); noiseBuf[i + 0] = Math::clip<-128, 127>(roundf(r * scale[0])); noiseBuf[i + 1] = Math::clip<-128, 127>(roundf(r * scale[1])); noiseBuf[i + 2] = Math::clip<-128, 127>(roundf(r * scale[2])); noiseBuf[i + 3] = Math::clip<-128, 127>(roundf(r * scale[3])); } } #ifdef __SSE2__ static inline void drawNoiseLineSse2(uint32_t* buf_, signed char* noise, long width) { // To each of the RGBA color components (a value in range [0..255]) we // want to add a signed noise value (in range [-128..127]) and also clip // the result to the range [0..255]. There is no SSE instruction that // directly performs this operation. But we can: // - subtract 128 from the RGBA component to get a signed byte // - perform the addition with signed saturation // - add 128 to the result to get back to the unsigned byte range // For 8-bit values the following 3 expressions are equivalent: // x + 128 == x - 128 == x ^ 128 // So the expression becomes: // signed_add_sat(value ^ 128, noise) ^ 128 // The follwoing loop does just that, though it processes 64 bytes per // iteration. long x = width * sizeof(uint32_t); assert((x & 63) == 0); assert((uintptr_t(buf_) & 15) == 0); char* buf = reinterpret_cast(buf_) + x; char* nse = reinterpret_cast(noise) + x; x = -x; __m128i b7 = _mm_set1_epi8(-128); // 0x80 do { __m128i i0 = _mm_load_si128(reinterpret_cast<__m128i*>(buf + x + 0)); __m128i i1 = _mm_load_si128(reinterpret_cast<__m128i*>(buf + x + 16)); __m128i i2 = _mm_load_si128(reinterpret_cast<__m128i*>(buf + x + 32)); __m128i i3 = _mm_load_si128(reinterpret_cast<__m128i*>(buf + x + 48)); __m128i n0 = _mm_load_si128(reinterpret_cast<__m128i*>(nse + x + 0)); __m128i n1 = _mm_load_si128(reinterpret_cast<__m128i*>(nse + x + 16)); __m128i n2 = _mm_load_si128(reinterpret_cast<__m128i*>(nse + x + 32)); __m128i n3 = _mm_load_si128(reinterpret_cast<__m128i*>(nse + x + 48)); __m128i o0 = _mm_xor_si128(_mm_adds_epi8(_mm_xor_si128(i0, b7), n0), b7); __m128i o1 = _mm_xor_si128(_mm_adds_epi8(_mm_xor_si128(i1, b7), n1), b7); __m128i o2 = _mm_xor_si128(_mm_adds_epi8(_mm_xor_si128(i2, b7), n2), b7); __m128i o3 = _mm_xor_si128(_mm_adds_epi8(_mm_xor_si128(i3, b7), n3), b7); _mm_store_si128(reinterpret_cast<__m128i*>(buf + x + 0), o0); _mm_store_si128(reinterpret_cast<__m128i*>(buf + x + 16), o1); _mm_store_si128(reinterpret_cast<__m128i*>(buf + x + 32), o2); _mm_store_si128(reinterpret_cast<__m128i*>(buf + x + 48), o3); x += 4 * sizeof(__m128i); } while (x < 0); } #endif /** Add noise to the given pixel. * @param p contains 4 8-bit unsigned components, so components have range [0, 255] * @param n contains 4 8-bit signed components, so components have range [-128, 127] * @result per component result of clip<0, 255>(p + n) */ static inline uint32_t addNoise4(uint32_t p, uint32_t n) { // unclipped result (lower 8 bits of each component) // alternative: // uint32_t s20 = ((p & 0x00FF00FF) + (n & 0x00FF00FF)) & 0x00FF00FF; // uint32_t s31 = ((p & 0xFF00FF00) + (n & 0xFF00FF00)) & 0xFF00FF00; // uint32_t s = s20 | s31; uint32_t s0 = p + n; // carry spills to neighbors uint32_t ci = (p ^ n ^ s0) & 0x01010100; // carry-in bits of prev sum uint32_t s = s0 - ci; // subtract carry bits again // Underflow of a component happens ONLY // WHEN input component is in range [0, 127] // AND noise component is negative // AND result component is in range [128, 255] // Overflow of a component happens ONLY // WHEN input component in in range [128, 255] // AND noise component is positive // AND result component is in range [0, 127] // Create a mask per component containing 00 for no under/overflow, // FF for under/overflow // ((~p & n & s) | (p & ~n & ~s)) == ((p ^ n) & (p ^ s)) uint32_t t = (p ^ n) & (p ^ s) & 0x80808080; uint32_t u1 = t & s; // underflow (alternative: u1 = t & n) // alternative1: uint32_t u2 = u1 | (u1 >> 1); // uint32_t u4 = u2 | (u2 >> 2); // uint32_t u8 = u4 | (u4 >> 4); // alternative2: uint32_t u8 = (u1 >> 7) * 0xFF; uint32_t u8 = (u1 << 1) - (u1 >> 7); uint32_t o1 = t & p; // overflow uint32_t o8 = (o1 << 1) - (o1 >> 7); // clip result return (s & (~u8)) | o8; } template void FBPostProcessor::drawNoiseLine( Pixel* buf, signed char* noise, unsigned long width) { #ifdef __SSE2__ if (sizeof(Pixel) == 4) { // cast to avoid compilation error in case of 16bpp (even // though this code is dead in that case). auto* buf32 = reinterpret_cast(buf); drawNoiseLineSse2(buf32, noise, width); return; } #endif // c++ version if (sizeof(Pixel) == 4) { // optimized version for 32bpp auto noise4 = reinterpret_cast(noise); for (unsigned i = 0; i < width; ++i) { buf[i] = addNoise4(buf[i], noise4[i]); } } else { int mr = pixelOps.getMaxRed(); int mg = pixelOps.getMaxGreen(); int mb = pixelOps.getMaxBlue(); for (unsigned i = 0; i < width; ++i) { Pixel p = buf[i]; int r = pixelOps.red(p); int g = pixelOps.green(p); int b = pixelOps.blue(p); r += noise[4 * i + 0]; g += noise[4 * i + 1]; b += noise[4 * i + 2]; r = std::min(std::max(r, 0), mr); g = std::min(std::max(g, 0), mg); b = std::min(std::max(b, 0), mb); buf[i] = pixelOps.combine(r, g, b); } } } template void FBPostProcessor::drawNoise(OutputSurface& output) { if (renderSettings.getNoise() == 0.0f) return; unsigned height = output.getHeight(); unsigned width = output.getWidth(); output.lock(); for (unsigned y = 0; y < height; ++y) { Pixel* buf = output.getLinePtrDirect(y); drawNoiseLine(buf, &noiseBuf[noiseShift[y]], width); } } template void FBPostProcessor::update(const Setting& setting) { VideoLayer::update(setting); auto& noiseSetting = renderSettings.getNoiseSetting(); if (&setting == &noiseSetting) { preCalcNoise(noiseSetting.getDouble()); } } template FBPostProcessor::FBPostProcessor(MSXMotherBoard& motherBoard, Display& display, OutputSurface& screen_, const std::string& videoSource, unsigned maxWidth, unsigned height, bool canDoInterlace) : PostProcessor( motherBoard, display, screen_, videoSource, maxWidth, height, canDoInterlace) , noiseShift(screen.getHeight()) , pixelOps(screen.getSDLFormat()) { scaleAlgorithm = RenderSettings::NO_SCALER; scaleFactor = unsigned(-1); auto& noiseSetting = renderSettings.getNoiseSetting(); noiseSetting.attach(*this); preCalcNoise(noiseSetting.getDouble()); assert((screen.getWidth() * sizeof(Pixel)) < NOISE_SHIFT); } template FBPostProcessor::~FBPostProcessor() { renderSettings.getNoiseSetting().detach(*this); } template void FBPostProcessor::paint(OutputSurface& output) { if (renderSettings.getInterleaveBlackFrame()) { interleaveCount ^= 1; if (interleaveCount) { output.clearScreen(); return; } } if (!paintFrame) return; // New scaler algorithm selected? auto algo = renderSettings.getScaleAlgorithm(); unsigned factor = renderSettings.getScaleFactor(); if ((scaleAlgorithm != algo) || (scaleFactor != factor)) { scaleAlgorithm = algo; scaleFactor = factor; currScaler = ScalerFactory::createScaler( PixelOperations(output.getSDLFormat()), renderSettings); } // Scale image. const unsigned srcHeight = paintFrame->getHeight(); const unsigned dstHeight = output.getHeight(); unsigned g = Math::gcd(srcHeight, dstHeight); unsigned srcStep = srcHeight / g; unsigned dstStep = dstHeight / g; // TODO: Store all MSX lines in RawFrame and only scale the ones that fit // on the PC screen, as a preparation for resizable output window. unsigned srcStartY = 0; unsigned dstStartY = 0; while (dstStartY < dstHeight) { // Currently this is true because the source frame height // is always >= dstHeight/(dstStep/srcStep). assert(srcStartY < srcHeight); // get region with equal lineWidth unsigned lineWidth = getLineWidth(paintFrame, srcStartY, srcStep); unsigned srcEndY = srcStartY + srcStep; unsigned dstEndY = dstStartY + dstStep; while ((srcEndY < srcHeight) && (dstEndY < dstHeight) && (getLineWidth(paintFrame, srcEndY, srcStep) == lineWidth)) { srcEndY += srcStep; dstEndY += dstStep; } // fill region //fprintf(stderr, "post processing lines %d-%d: %d\n", // srcStartY, srcEndY, lineWidth ); output.lock(); float horStretch = renderSettings.getHorizontalStretch(); unsigned inWidth = unsigned(horStretch + 0.5f); std::unique_ptr> dst( StretchScalerOutputFactory::create( output, pixelOps, inWidth)); currScaler->scaleImage( *paintFrame, superImposeVideoFrame, srcStartY, srcEndY, lineWidth, // source *dst, dstStartY, dstEndY); // dest // next region srcStartY = srcEndY; dstStartY = dstEndY; } drawNoise(output); output.flushFrameBuffer(); // for SDLGL-FBxx } template std::unique_ptr FBPostProcessor::rotateFrames( std::unique_ptr finishedFrame, EmuTime::param time) { auto& generator = global_urng(); // fast (non-cryptographic) random numbers std::uniform_int_distribution distribution(0, NOISE_SHIFT / 16 - 1); for (auto y : xrange(screen.getHeight())) { noiseShift[y] = distribution(generator) * 16; } return PostProcessor::rotateFrames(std::move(finishedFrame), time); } // Force template instantiation. #if HAVE_16BPP template class FBPostProcessor; #endif #if HAVE_32BPP template class FBPostProcessor; #endif } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/FBPostProcessor.hh000066400000000000000000000027541257557151200217220ustar00rootroot00000000000000#ifndef FBPOSTPROCESSOR_HH #define FBPOSTPROCESSOR_HH #include "PostProcessor.hh" #include "RenderSettings.hh" #include "PixelOperations.hh" #include namespace openmsx { class MSXMotherBoard; class Display; template class Scaler; /** Rasterizer using SDL. */ template class FBPostProcessor final : public PostProcessor { public: FBPostProcessor( MSXMotherBoard& motherBoard, Display& display, OutputSurface& screen, const std::string& videoSource, unsigned maxWidth, unsigned height, bool canDoInterlace); ~FBPostProcessor(); // Layer interface: void paint(OutputSurface& output) override; std::unique_ptr rotateFrames( std::unique_ptr finishedFrame, EmuTime::param time) override; private: void preCalcNoise(float factor); void drawNoise(OutputSurface& output); void drawNoiseLine(Pixel* buf, signed char* noise, unsigned long width); // Observer void update(const Setting& setting) override; /** The currently active scaler. */ std::unique_ptr> currScaler; /** Currently active scale algorithm, used to detect scaler changes. */ RenderSettings::ScaleAlgorithm scaleAlgorithm; /** Currently active scale factor, used to detect scaler changes. */ unsigned scaleFactor; /** Remember the noise values to get a stable image when paused. */ std::vector noiseShift; PixelOperations pixelOps; }; } // namespace openmsx #endif // FBPOSTPROCESSOR_HH openMSX-RELEASE_0_12_0/src/video/FrameSource.cc000066400000000000000000000165431257557151200210670ustar00rootroot00000000000000#include "FrameSource.hh" #include "PixelOperations.hh" #include "MemoryOps.hh" #include "LineScalers.hh" #include "unreachable.hh" #include "aligned.hh" #include "likely.hh" #include "vla.hh" #include "build-info.hh" #include "components.hh" #include namespace openmsx { FrameSource::FrameSource(const SDL_PixelFormat& format) : pixelFormat(format) { } template const Pixel* FrameSource::getLinePtr320_240(unsigned line, Pixel* buf0) const { if (getHeight() == 240) { return getLinePtr(line, 320, buf0); } else { assert(getHeight() == 480); SSE_ALIGNED(Pixel buf1[320]); auto* line0 = getLinePtr(2 * line + 0, 320, buf0); auto* line1 = getLinePtr(2 * line + 1, 320, buf1); PixelOperations pixelOps(pixelFormat); BlendLines blend(pixelOps); blend(line0, line1, buf0, 320); // possibly line0 == buf0 return buf0; } } template const Pixel* FrameSource::getLinePtr640_480(unsigned line, Pixel* buf) const { if (getHeight() == 480) { return getLinePtr(line, 640, buf); } else { assert(getHeight() == 240); return getLinePtr(line / 2, 640, buf); } } template const Pixel* FrameSource::getLinePtr960_720(unsigned line, Pixel* buf0) const { if (getHeight() == 480) { unsigned l2 = (2 * line) / 3; auto* line0 = getLinePtr(l2 + 0, 960, buf0); if ((line % 3) != 1) { return line0; } SSE_ALIGNED(Pixel buf1[960]); auto* line1 = getLinePtr(l2 + 1, 960, buf1); PixelOperations pixelOps(pixelFormat); BlendLines blend(pixelOps); blend(line0, line1, buf0, 960); // possibly line0 == buf0 return buf0; } else { assert(getHeight() == 240); return getLinePtr(line / 3, 960, buf0); } } template void FrameSource::scaleLine( const Pixel* in, Pixel* out, unsigned inWidth, unsigned outWidth) const { PixelOperations pixelOps(pixelFormat); VLA_SSE_ALIGNED(Pixel, tmpBuf, inWidth); if (unlikely(in == out)) { // Only happens in case getLineInfo() already used buf. // E.g. when a line of a SuperImposedFrame also needs to be // scaled. // TODO If the LineScaler routines can work in-place then this // copy can be avoided. memcpy(tmpBuf, in, inWidth * sizeof(Pixel)); in = tmpBuf; } // TODO is there a better way to implement this? switch (inWidth) { case 1: // blank MemoryOps::MemSet memset; memset(out, outWidth, in[0]); break; case 213: switch (outWidth) { case 1: out[0] = in[0]; break; case 213: UNREACHABLE; case 320: { Scale_2on3 scale(pixelOps); scale(in, out, outWidth); break; } case 426: { Scale_1on2 scale; scale(in, out, outWidth); break; } case 640: { Scale_1on3 scale; scale(in, out, outWidth); break; } case 853: { Scale_1on4 scale; scale(in, out, outWidth); break; } case 960: { Scale_2on9 scale(pixelOps); scale(in, out, outWidth); break; } case 1280: { Scale_1on6 scale; scale(in, out, outWidth); break; } default: UNREACHABLE; } break; case 320: switch (outWidth) { case 1: out[0] = in[0]; break; case 213: { Scale_3on2 scale(pixelOps); scale(in, out, outWidth); break; } case 320: UNREACHABLE; case 426: { Scale_3on4 scale(pixelOps); scale(in, out, outWidth); break; } case 640: { Scale_1on2 scale; scale(in, out, outWidth); break; } case 853: { Scale_3on8 scale(pixelOps); scale(in, out, outWidth); break; } case 960: { Scale_1on3 scale; scale(in, out, outWidth); break; } case 1280: { Scale_1on4 scale; scale(in, out, outWidth); break; } default: UNREACHABLE; } break; case 426: switch (outWidth) { case 1: out[0] = in[0]; break; case 213: { Scale_2on1 scale(pixelOps); scale(in, out, outWidth); break; } case 320: { Scale_4on3 scale(pixelOps); scale(in, out, outWidth); break; } case 426: UNREACHABLE; case 640: { Scale_2on3 scale(pixelOps); scale(in, out, outWidth); break; } case 853: { Scale_1on2 scale; scale(in, out, outWidth); break; } case 960: { Scale_4on9 scale(pixelOps); scale(in, out, outWidth); break; } case 1280: { Scale_1on3 scale; scale(in, out, outWidth); break; } default: UNREACHABLE; } break; case 640: switch (outWidth) { case 1: out[0] = in[0]; break; case 213: { Scale_3on1 scale(pixelOps); scale(in, out, outWidth); break; } case 320: { Scale_2on1 scale(pixelOps); scale(in, out, outWidth); break; } case 426: { Scale_3on2 scale(pixelOps); scale(in, out, outWidth); break; } case 640: UNREACHABLE; case 853: { Scale_3on4 scale(pixelOps); scale(in, out, outWidth); break; } case 960: { Scale_2on3 scale(pixelOps); scale(in, out, outWidth); break; } case 1280: { Scale_1on2 scale; scale(in, out, outWidth); break; } default: UNREACHABLE; } break; case 853: switch (outWidth) { case 1: out[0] = in[0]; break; case 213: { Scale_4on1 scale(pixelOps); scale(in, out, outWidth); break; } case 320: { Scale_8on3 scale(pixelOps); scale(in, out, outWidth); break; } case 426: { Scale_2on1 scale(pixelOps); scale(in, out, outWidth); break; } case 640: { Scale_4on3 scale(pixelOps); scale(in, out, outWidth); break; } case 853: UNREACHABLE; case 960: { Scale_8on9 scale(pixelOps); scale(in, out, outWidth); break; } case 1280: { Scale_2on3 scale(pixelOps); scale(in, out, outWidth); break; } default: UNREACHABLE; } break; case 1280: switch (outWidth) { case 1: out[0] = in[0]; break; case 213: { Scale_6on1 scale(pixelOps); scale(in, out, outWidth); break; } case 320: { Scale_4on1 scale(pixelOps); scale(in, out, outWidth); break; } case 426: { Scale_3on1 scale(pixelOps); scale(in, out, outWidth); break; } case 640: { Scale_2on1 scale(pixelOps); scale(in, out, outWidth); break; } case 853: { Scale_3on2 scale(pixelOps); scale(in, out, outWidth); break; } case 960: { Scale_4on3 scale(pixelOps); scale(in, out, outWidth); break; } case 1280: UNREACHABLE; default: UNREACHABLE; } break; default: UNREACHABLE; } } // Force template method instantiation #if HAVE_16BPP template const uint16_t* FrameSource::getLinePtr320_240(unsigned, uint16_t*) const; template const uint16_t* FrameSource::getLinePtr640_480(unsigned, uint16_t*) const; template const uint16_t* FrameSource::getLinePtr960_720(unsigned, uint16_t*) const; template void FrameSource::scaleLine(const uint16_t*, uint16_t*, unsigned, unsigned) const; #endif #if HAVE_32BPP || COMPONENT_GL template const uint32_t* FrameSource::getLinePtr320_240(unsigned, uint32_t*) const; template const uint32_t* FrameSource::getLinePtr640_480(unsigned, uint32_t*) const; template const uint32_t* FrameSource::getLinePtr960_720(unsigned, uint32_t*) const; template void FrameSource::scaleLine(const uint32_t*, uint32_t*, unsigned, unsigned) const; #endif } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/FrameSource.hh000066400000000000000000000155361257557151200211020ustar00rootroot00000000000000#ifndef FRAMESOURCE_HH #define FRAMESOURCE_HH #include "noncopyable.hh" #include "aligned.hh" #include #include struct SDL_PixelFormat; namespace openmsx { /** Interface for getting lines from a video frame. */ class FrameSource : private noncopyable { public: /** What role does this frame play in interlacing? */ enum FieldType { /** Interlacing is off for this frame. */ FIELD_NONINTERLACED, /** Interlacing is on and this is an even frame. */ FIELD_EVEN, /** Interlacing is on and this is an odd frame. */ FIELD_ODD }; /** (Re)initialize an existing FrameSource. This method sets the * Fieldtype and flushes the 'getLinePtr' buffers. */ void init(FieldType fieldType_) { fieldType = fieldType_; } /** Gets the role this frame plays in interlacing. */ FieldType getField() const { return fieldType; } /** Gets the number of lines in this frame. */ unsigned getHeight() const { return height; } /** Gets the number of display pixels on the given line. * @return line width (=1 for a vertical border line) */ virtual unsigned getLineWidth(unsigned line) const = 0; /** Get the width of (all) lines in this frame. * This only makes sense when all lines have the same width, so this * methods asserts that all lines actually have the same width. This * is for example not always the case for MSX frames, but it is for * video frames (for superimpose). */ unsigned getWidth() const { unsigned height = getHeight(); assert(height > 0); unsigned result = getLineWidth(0); for (unsigned line = 1; line < height; ++line) { assert(result == getLineWidth(line)); } return result; } /** Get the (single) color of the given line. * Typically this will be used to get the color of a vertical border * line. But it's fine to call this on non-border lines as well, in * that case the color of the first pixel of the line is returned. */ template inline const Pixel getLineColor(unsigned line) const { SSE_ALIGNED(Pixel buf[1280]); // large enough for widest line unsigned width; // not used return reinterpret_cast( getLineInfo(line, width, buf, 1280))[0]; } /** Gets a pointer to the pixels of the given line number. * The line returned is guaranteed to have the given width. If the * original line had a different width the result will be computed in * the provided work buffer. So that buffer should be big enough to * hold the scaled line. This also means the lifetime of the result * is tied to the lifetime of that work buffer. In any case the return * value of this function will point to the line data (some internal * buffer or the work buffer). */ template inline const Pixel* getLinePtr(int line, unsigned width, Pixel* buf) const { line = std::min(std::max(0, line), getHeight() - 1); unsigned internalWidth; auto* internalData = reinterpret_cast( getLineInfo(line, internalWidth, buf, width)); if (internalWidth == width) { return internalData; } else { // slow path, non-inlined // internalData might be equal to buf scaleLine(internalData, buf, internalWidth, width); return buf; } } /** Similar to the above getLinePtr() method, but now tries to get * multiple lines at once. This is not always possible, so the actual * number of lines is returned in 'actualLines', it will always be at * least 1. */ template inline const Pixel* getMultiLinePtr( int line, unsigned numLines, unsigned& actualLines, unsigned width, Pixel* buf) const { actualLines = 1; int height = getHeight(); if ((line < 0) || (height <= line)) { return getLinePtr(line, width, buf); } unsigned internalWidth; auto* internalData = reinterpret_cast( getLineInfo(line, internalWidth, buf, width)); if (internalWidth != width) { scaleLine(internalData, buf, internalWidth, width); return buf; } if (!hasContiguousStorage()) { return internalData; } while (--numLines) { ++line; if ((line == height) || (getLineWidth(line) != width)) { break; } ++actualLines; } return internalData; } /** Abstract implementation of getLinePtr(). * Pixel type is unspecified (implementations that care about the * exact type should get it via some other mechanism). * @param line The line number for the requisted line. * @param lineWidth Output parameter, the width of the returned line * in pixel units. * @param buf Buffer space that can _optionally_ be used by the * implementation. * @param bufWidth The size of the above buffer, in pixel units. * @return Pointer to the first pixel of the requested line. This might * be the same as the given 'buf' parameter or it might be some * internal buffer. */ virtual const void* getLineInfo( unsigned line, unsigned& lineWidth, void* buf, unsigned bufWidth) const = 0; /** Get a pointer to a given line in this frame, the frame is scaled * to 320x240 pixels. The difference between this method and * getLinePtr() is that this method also does vertical scaling. * This is used for video recording. */ template const Pixel* getLinePtr320_240(unsigned line, Pixel* buf) const; /** Get a pointer to a given line in this frame, the frame is scaled * to 640x480 pixels. Same as getLinePtr320_240, but then for a * higher resolution output. */ template const Pixel* getLinePtr640_480(unsigned line, Pixel* buf) const; /** Get a pointer to a given line in this frame, the frame is scaled * to 960x720 pixels. Same as getLinePtr320_240, but then for a * higher resolution output. */ template const Pixel* getLinePtr960_720(unsigned line, Pixel* buf) const; /** Returns the distance (in pixels) between two consecutive lines. * Is meant to be used in combination with getMultiLinePtr(). The * result is only meaningful when hasContiguousStorage() returns * true (also only in that case does getMultiLinePtr() return more * than 1 line). */ virtual unsigned getRowLength() const { return 0; } const SDL_PixelFormat& getSDLPixelFormat() const { return pixelFormat; } protected: explicit FrameSource(const SDL_PixelFormat& format); ~FrameSource() {} void setHeight(unsigned height_) { height = height_; } /** Returns true when two consecutive rows are also consecutive in * memory. */ virtual bool hasContiguousStorage() const { return false; } template void scaleLine( const Pixel* in, Pixel* out, unsigned inWidth, unsigned outWidth) const; private: /** Pixel format. Needed for getLinePtr scaling */ const SDL_PixelFormat& pixelFormat; /** Number of lines in this frame. */ unsigned height; FieldType fieldType; }; } // namespace openmsx #endif // FRAMESOURCE_HH openMSX-RELEASE_0_12_0/src/video/GLContext.cc000066400000000000000000000025071257557151200205160ustar00rootroot00000000000000#include "GLContext.hh" #include "GLDefaultScaler.hh" #include "gl_transform.hh" #include "memory.hh" namespace gl { // Global variables std::unique_ptr context; Context::Context(int width, int height) { VertexShader texVertexShader ("texture.vert"); FragmentShader texFragmentShader("texture.frag"); progTex.allocate(); progTex.attach(texVertexShader); progTex.attach(texFragmentShader); progTex.bindAttribLocation(0, "a_position"); progTex.bindAttribLocation(1, "a_texCoord"); progTex.link(); progTex.activate(); glUniform1i(progTex.getUniformLocation("u_tex"), 0); unifTexColor = progTex.getUniformLocation("u_color"); unifTexMvp = progTex.getUniformLocation("u_mvpMatrix"); VertexShader fillVertexShader ("fill.vert"); FragmentShader fillFragmentShader("fill.frag"); progFill.allocate(); progFill.attach(fillVertexShader); progFill.attach(fillFragmentShader); progFill.bindAttribLocation(0, "a_position"); progFill.bindAttribLocation(1, "a_color"); progFill.link(); progFill.activate(); unifFillMvp = progFill.getUniformLocation("u_mvpMatrix"); pixelMvp = ortho(0, width, height, 0, -1, 1); } Context::~Context() { } openmsx::GLScaler& Context::getFallbackScaler() { if (!fallbackScaler) { fallbackScaler = make_unique(); } return *fallbackScaler; } } // namespace gl openMSX-RELEASE_0_12_0/src/video/GLContext.hh000066400000000000000000000025651257557151200205340ustar00rootroot00000000000000#ifndef GL_CONTEXT_HH #define GL_CONTEXT_HH #include "GLUtil.hh" #include "gl_mat.hh" #include namespace openmsx { class GLScaler; } namespace gl { struct Context { /** Initialize global openGL state */ Context(int width, int height); ~Context(); // Simple texture program. It expects // uniforms: // unifTexColor: values from texture map are multiplied by this 4D color // unifTexMvp: Model-View-Projection-matrix // attributes: // 0: 4D vertex positions, get multiplied by Model-View-Projection-matrix // 1: 2D texture coordinates // textures: // the to be applied texture must be bound to the 1st texture unit ShaderProgram progTex; GLuint unifTexColor; GLuint unifTexMvp; // Simple color-fill program. It expects // uniforms: // unifFillMvp: Model-View-Projection-matrix // attributes: // 0: 4D vertex positions, get multiplied by Model-View-Projection-matrix // 1: 4D vertex color ShaderProgram progFill; GLuint unifFillMvp; // Model-View-Projection-matrix that maps integer vertex positions to host // display pixel positions. (0,0) is the top-left pixel, (width-1,height-1) is // the bottom-right pixel. mat4 pixelMvp; // Fallback scaler openmsx::GLScaler& getFallbackScaler(); private: std::unique_ptr fallbackScaler; }; extern std::unique_ptr context; } // namespace gl #endif openMSX-RELEASE_0_12_0/src/video/GLImage.cc000066400000000000000000000132201257557151200201060ustar00rootroot00000000000000#include "GLImage.hh" #include "GLContext.hh" #include "SDLSurfacePtr.hh" #include "MSXException.hh" #include "Math.hh" #include "PNG.hh" #include "build-info.hh" #include #include using std::string; using namespace gl; namespace openmsx { static gl::Texture loadTexture( SDLSurfacePtr surface, ivec2& size, vec2& texCoord) { size = ivec2(surface->w, surface->h); ivec2 size2(Math::powerOfTwo(size[0]), Math::powerOfTwo(size[1])); texCoord = vec2(size) / vec2(size2); SDLSurfacePtr image2(size2[0], size2[1], 32, OPENMSX_BIGENDIAN ? 0xFF000000 : 0x000000FF, OPENMSX_BIGENDIAN ? 0x00FF0000 : 0x0000FF00, OPENMSX_BIGENDIAN ? 0x0000FF00 : 0x00FF0000, OPENMSX_BIGENDIAN ? 0x000000FF : 0xFF000000); SDL_Rect area; area.x = 0; area.y = 0; area.w = size[0]; area.h = size[1]; SDL_SetAlpha(surface.get(), 0, 0); SDL_BlitSurface(surface.get(), &area, image2.get(), &area); gl::Texture texture(true); // enable interpolation glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, size2[0], size2[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, image2->pixels); return texture; } static gl::Texture loadTexture( const string& filename, ivec2& size, vec2& texCoord) { SDLSurfacePtr surface(PNG::load(filename, false)); try { return loadTexture(std::move(surface), size, texCoord); } catch (MSXException& e) { throw MSXException("Error loading image " + filename + ": " + e.getMessage()); } } GLImage::GLImage(const string& filename) : texture(loadTexture(filename, size, texCoord)) { } GLImage::GLImage(const string& filename, float scalefactor) : texture(loadTexture(filename, size, texCoord)) { size = trunc(vec2(size) * scalefactor); checkSize(size); } GLImage::GLImage(const string& filename, ivec2 size_) : texture(loadTexture(filename, size, texCoord)) { checkSize(size_); size = size_; } GLImage::GLImage(ivec2 size_, unsigned rgba) : texture(gl::Null()) { checkSize(size_); size = size_; borderSize = 0; for (int i = 0; i < 4; ++i) { bgR[i] = (rgba >> 24) & 0xff; bgG[i] = (rgba >> 16) & 0xff; bgB[i] = (rgba >> 8) & 0xff; unsigned alpha = (rgba >> 0) & 0xff; bgA[i] = (alpha == 255) ? 256 : alpha; } } GLImage::GLImage(ivec2 size_, const unsigned* rgba, int borderSize_, unsigned borderRGBA) : texture(gl::Null()) { checkSize(size_); size = size_; borderSize = borderSize_; for (int i = 0; i < 4; ++i) { bgR[i] = (rgba[i] >> 24) & 0xff; bgG[i] = (rgba[i] >> 16) & 0xff; bgB[i] = (rgba[i] >> 8) & 0xff; unsigned alpha = (rgba[i] >> 0) & 0xff; bgA[i] = (alpha == 255) ? 256 : alpha; } borderR = (borderRGBA >> 24) & 0xff; borderG = (borderRGBA >> 16) & 0xff; borderB = (borderRGBA >> 8) & 0xff; unsigned alpha = (borderRGBA >> 0) & 0xff; borderA = (alpha == 255) ? 256 : alpha; } GLImage::GLImage(SDLSurfacePtr image) : texture(loadTexture(std::move(image), size, texCoord)) { } void GLImage::draw(OutputSurface& /*output*/, ivec2 pos, byte r, byte g, byte b, byte alpha) { // 4-----------------7 // | | // | 0---------3 | // | | | | // | | | | // | 1-------- 2 | // | | // 5-----------------6 int bx = (size[0] > 0) ? borderSize : -borderSize; int by = (size[1] > 0) ? borderSize : -borderSize; ivec2 positions[8] = { pos + ivec2( + bx, + by), // 0 pos + ivec2( + bx, size[1] - by), // 1 pos + ivec2(size[0] - bx, size[1] - by), // 2 pos + ivec2(size[0] - bx, + by), // 3 pos + ivec2(0 , 0 ), // 4 pos + ivec2(0 , size[1] ), // 5 pos + ivec2(size[0] , size[1] ), // 6 pos + ivec2(size[0] , 0 ), // 7 }; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnableVertexAttribArray(0); if (texture.get()) { vec2 tex[4] = { vec2( 0.0f, 0.0f), vec2( 0.0f, texCoord[1]), vec2(texCoord[0], texCoord[1]), vec2(texCoord[0], 0.0f), }; gl::context->progTex.activate(); glUniform4f(gl::context->unifTexColor, r / 255.0f, g / 255.0f, b / 255.0f, alpha / 255.0f); glUniformMatrix4fv(gl::context->unifTexMvp, 1, GL_FALSE, &gl::context->pixelMvp[0][0]); glVertexAttribPointer(0, 2, GL_INT, GL_FALSE, 0, positions + 4); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, tex); glEnableVertexAttribArray(1); texture.bind(); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } else { assert(r == 255); assert(g == 255); assert(b == 255); gl::context->progFill.activate(); glUniformMatrix4fv(gl::context->unifFillMvp, 1, GL_FALSE, &gl::context->pixelMvp[0][0]); glVertexAttribPointer(0, 2, GL_INT, GL_FALSE, 0, positions); glVertexAttrib4f(1, borderR / 255.0f, borderG / 255.0f, borderB / 255.0f, (borderA * alpha) / (255.0f * 255.0f)); if ((2 * borderSize >= abs(size[0])) || (2 * borderSize >= abs(size[1]))) { // only border glDisableVertexAttribArray(1); glDrawArrays(GL_TRIANGLE_FAN, 4, 4); } else { // border if (borderSize > 0) { byte indices[10] = { 4,0,5,1,6,2,7,3,4,0 }; glDisableVertexAttribArray(1); glDrawElements(GL_TRIANGLE_STRIP, 10, GL_UNSIGNED_BYTE, indices); } // interior byte col[4][4] = { { bgR[0], bgG[0], bgB[0], byte((bgA[0] * alpha) / 256) }, { bgR[2], bgG[2], bgB[2], byte((bgA[2] * alpha) / 256) }, { bgR[3], bgG[3], bgB[3], byte((bgA[3] * alpha) / 256) }, { bgR[1], bgG[1], bgB[1], byte((bgA[1] * alpha) / 256) }, }; glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, col); glEnableVertexAttribArray(1); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } } glDisable(GL_BLEND); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/GLImage.hh000066400000000000000000000017001257557151200201200ustar00rootroot00000000000000#ifndef GLTEXTURE_HH #define GLTEXTURE_HH #include "BaseImage.hh" #include "GLUtil.hh" #include "openmsx.hh" #include class SDLSurfacePtr; namespace openmsx { class GLImage final : public BaseImage { public: explicit GLImage(const std::string& filename); explicit GLImage(SDLSurfacePtr image); GLImage(const std::string& filename, float scaleFactor); GLImage(const std::string& filename, gl::ivec2 size); GLImage(gl::ivec2 size, unsigned rgba); GLImage(gl::ivec2 size, const unsigned* rgba, int borderSize, unsigned borderRGBA); void draw(OutputSurface& output, gl::ivec2 pos, byte r, byte g, byte b, byte alpha) override; gl::ivec2 getSize() const override { return size; } private: gl::ivec2 size; gl::vec2 texCoord; gl::Texture texture; // must come after size and texCoord int borderSize; int bgA[4], borderA; byte bgR[4], bgG[4], bgB[4]; byte borderR, borderG, borderB; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/GLPostProcessor.cc000066400000000000000000000440401257557151200217150ustar00rootroot00000000000000#include "GLPostProcessor.hh" #include "GLContext.hh" #include "GLScaler.hh" #include "GLScalerFactory.hh" #include "IntegerSetting.hh" #include "FloatSetting.hh" #include "BooleanSetting.hh" #include "EnumSetting.hh" #include "OutputSurface.hh" #include "RawFrame.hh" #include "Math.hh" #include "InitException.hh" #include "gl_transform.hh" #include "random.hh" #include "stl.hh" #include "vla.hh" #include #include using namespace gl; namespace openmsx { GLPostProcessor::TextureData::TextureData() { } GLPostProcessor::TextureData::TextureData(TextureData&& rhs) #if !defined(_MSC_VER) noexcept #endif : tex(std::move(rhs.tex)) , pbo(std::move(rhs.pbo)) { } GLPostProcessor::GLPostProcessor( MSXMotherBoard& motherBoard, Display& display, OutputSurface& screen, const std::string& videoSource, unsigned maxWidth, unsigned height_, bool canDoInterlace) : PostProcessor(motherBoard, display, screen, videoSource, maxWidth, height_, canDoInterlace) , noiseTextureA(true, true) // interpolate + wrap , noiseTextureB(true, true) , height(height_) { if (!glewIsSupported("GL_EXT_framebuffer_object")) { throw InitException( "The OpenGL framebuffer object is not supported by " "this glew library. Please upgrade your glew library.\n" "It's also possible (but less likely) your video card " "or video card driver doesn't support framebuffer " "objects."); } scaleAlgorithm = static_cast(-1); // not a valid scaler frameCounter = 0; noiseX = noiseY = 0.0f; preCalcNoise(renderSettings.getNoise()); storedFrame = false; for (int i = 0; i < 2; ++i) { colorTex[i].bind(); colorTex[i].setInterpolation(true); glTexImage2D(GL_TEXTURE_2D, // target 0, // level GL_RGB8, // internal format screen.getWidth(), // width screen.getHeight(),// height 0, // border GL_RGB, // format GL_UNSIGNED_BYTE, // type nullptr); // data fbo[i] = FrameBufferObject(colorTex[i]); } VertexShader vertexShader ("monitor3D.vert"); FragmentShader fragmentShader("monitor3D.frag"); monitor3DProg.attach(vertexShader); monitor3DProg.attach(fragmentShader); monitor3DProg.bindAttribLocation(0, "a_position"); monitor3DProg.bindAttribLocation(1, "a_normal"); monitor3DProg.bindAttribLocation(2, "a_texCoord"); monitor3DProg.link(); preCalcMonitor3D(renderSettings.getHorizontalStretch()); renderSettings.getNoiseSetting().attach(*this); renderSettings.getHorizontalStretchSetting().attach(*this); } GLPostProcessor::~GLPostProcessor() { renderSettings.getHorizontalStretchSetting().detach(*this); renderSettings.getNoiseSetting().detach(*this); } void GLPostProcessor::createRegions() { regions.clear(); const unsigned srcHeight = paintFrame->getHeight(); const unsigned dstHeight = screen.getHeight(); unsigned g = Math::gcd(srcHeight, dstHeight); unsigned srcStep = srcHeight / g; unsigned dstStep = dstHeight / g; // TODO: Store all MSX lines in RawFrame and only scale the ones that fit // on the PC screen, as a preparation for resizable output window. unsigned srcStartY = 0; unsigned dstStartY = 0; while (dstStartY < dstHeight) { // Currently this is true because the source frame height // is always >= dstHeight/(dstStep/srcStep). assert(srcStartY < srcHeight); // get region with equal lineWidth unsigned lineWidth = getLineWidth(paintFrame, srcStartY, srcStep); unsigned srcEndY = srcStartY + srcStep; unsigned dstEndY = dstStartY + dstStep; while ((srcEndY < srcHeight) && (dstEndY < dstHeight) && (getLineWidth(paintFrame, srcEndY, srcStep) == lineWidth)) { srcEndY += srcStep; dstEndY += dstStep; } regions.emplace_back(srcStartY, srcEndY, dstStartY, dstEndY, lineWidth); // next region srcStartY = srcEndY; dstStartY = dstEndY; } } void GLPostProcessor::paint(OutputSurface& /*output*/) { if (renderSettings.getInterleaveBlackFrame()) { interleaveCount ^= 1; if (interleaveCount) { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); return; } } auto deform = renderSettings.getDisplayDeform(); float horStretch = renderSettings.getHorizontalStretch(); int glow = renderSettings.getGlow(); bool renderToTexture = (deform != RenderSettings::DEFORM_NORMAL) || (horStretch != 320.0f) || (glow != 0); if ((deform == RenderSettings::DEFORM_3D) || !paintFrame) { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); if (!paintFrame) { return; } } // New scaler algorithm selected? auto algo = renderSettings.getScaleAlgorithm(); if (scaleAlgorithm != algo) { scaleAlgorithm = algo; currScaler = GLScalerFactory::createScaler(renderSettings); // Re-upload frame data, this is both // - Chunks of RawFrame with a specific linewidth, possibly // with some extra lines above and below each chunk that are // also converted to this linewidth. // - Extra data that is specific for the scaler (ATM only the // hq and hqlite scalers require this). // Re-uploading the first is not strictly needed. But switching // scalers doesn't happen that often, so it also doesn't hurt // and it keeps the code simpler. uploadFrame(); } if (renderToTexture) { glViewport(0, 0, screen.getWidth(), screen.getHeight()); glBindTexture(GL_TEXTURE_2D, 0); fbo[frameCounter & 1].push(); } glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); for (auto& r : regions) { //fprintf(stderr, "post processing lines %d-%d: %d\n", // r.srcStartY, r.srcEndY, r.lineWidth); auto it = find_if_unguarded(textures, EqualTupleValue<0>(r.lineWidth)); auto superImpose = superImposeVideoFrame ? &superImposeTex : nullptr; currScaler->scaleImage( it->second.tex, superImpose, r.srcStartY, r.srcEndY, r.lineWidth, // src r.dstStartY, r.dstEndY, screen.getWidth(), // dst paintFrame->getHeight()); // dst //GLUtil::checkGLError("GLPostProcessor::paint"); } drawNoise(); drawGlow(glow); if (renderToTexture) { fbo[frameCounter & 1].pop(); colorTex[frameCounter & 1].bind(); glViewport(screen.getX(), screen.getY(), screen.getWidth(), screen.getHeight()); if (deform == RenderSettings::DEFORM_3D) { drawMonitor3D(); } else { float x1 = (320.0f - float(horStretch)) / (2.0f * 320.0f); float x2 = 1.0f - x1; static const vec2 pos[4] = { vec2(-1, 1), vec2(-1,-1), vec2( 1,-1), vec2( 1, 1) }; vec2 tex[4] = { vec2(x1, 1), vec2(x1, 0), vec2(x2, 0), vec2(x2, 1) }; gl::context->progTex.activate(); glUniform4f(gl::context->unifTexColor, 1.0f, 1.0f, 1.0f, 1.0f); mat4 I; glUniformMatrix4fv(gl::context->unifTexMvp, 1, GL_FALSE, &I[0][0]); glDisable(GL_BLEND); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, pos); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, tex); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } storedFrame = true; } else { storedFrame = false; } } std::unique_ptr GLPostProcessor::rotateFrames( std::unique_ptr finishedFrame, EmuTime::param time) { std::unique_ptr reuseFrame = PostProcessor::rotateFrames(std::move(finishedFrame), time); uploadFrame(); ++frameCounter; noiseX = random_float(0.0f, 1.0f); noiseY = random_float(0.0f, 1.0f); return reuseFrame; } void GLPostProcessor::update(const Setting& setting) { VideoLayer::update(setting); auto& noiseSetting = renderSettings.getNoiseSetting(); auto& horizontalStretch = renderSettings.getHorizontalStretchSetting(); if (&setting == &noiseSetting) { preCalcNoise(noiseSetting.getDouble()); } else if (&setting == &horizontalStretch) { preCalcMonitor3D(horizontalStretch.getDouble()); } } void GLPostProcessor::uploadFrame() { createRegions(); const unsigned srcHeight = paintFrame->getHeight(); for (auto& r : regions) { // upload data // TODO get before/after data from scaler unsigned before = 1; unsigned after = 1; uploadBlock(std::max(0, r.srcStartY - before), std::min(srcHeight, r.srcEndY + after), r.lineWidth); } if (superImposeVideoFrame) { int width = superImposeVideoFrame->getWidth(); int height = superImposeVideoFrame->getHeight(); if (superImposeTex.getWidth() != width || superImposeTex.getHeight() != height) { superImposeTex.resize(width, height); superImposeTex.setInterpolation(true); } superImposeTex.bind(); glTexSubImage2D( GL_TEXTURE_2D, // target 0, // level 0, // offset x 0, // offset y width, // width height, // height GL_BGRA, // format GL_UNSIGNED_BYTE, // type const_cast(superImposeVideoFrame)->getLinePtrDirect(0)); // data } } void GLPostProcessor::uploadBlock( unsigned srcStartY, unsigned srcEndY, unsigned lineWidth) { // create texture/pbo if needed auto it = find_if(begin(textures), end(textures), EqualTupleValue<0>(lineWidth)); if (it == end(textures)) { TextureData textureData; textureData.tex.resize(lineWidth, height * 2); // *2 for interlace if (textureData.pbo.openGLSupported()) { textureData.pbo.setImage(lineWidth, height * 2); } textures.emplace_back(lineWidth, std::move(textureData)); it = end(textures) - 1; } auto& tex = it->second.tex; auto& pbo = it->second.pbo; // bind texture tex.bind(); // upload data uint32_t* mapped; if (pbo.openGLSupported()) { pbo.bind(); mapped = pbo.mapWrite(); } else { mapped = nullptr; } if (mapped) { for (unsigned y = srcStartY; y < srcEndY; ++y) { auto* dest = mapped + y * lineWidth; auto* data = paintFrame->getLinePtr(y, lineWidth, dest); if (data != dest) { memcpy(dest, data, lineWidth * sizeof(uint32_t)); } } pbo.unmap(); #if defined(__APPLE__) // The nVidia GL driver for the GeForce 8000/9000 series seems to hang // on texture data replacements that are 1 pixel wide and start on a // line number that is a non-zero multiple of 16. if (lineWidth == 1 && srcStartY != 0 && srcStartY % 16 == 0) { srcStartY--; } #endif glTexSubImage2D( GL_TEXTURE_2D, // target 0, // level 0, // offset x srcStartY, // offset y lineWidth, // width srcEndY - srcStartY, // height GL_BGRA, // format GL_UNSIGNED_BYTE, // type pbo.getOffset(0, srcStartY)); // data } if (pbo.openGLSupported()) { pbo.unbind(); } if (!mapped) { glPixelStorei(GL_UNPACK_ROW_LENGTH, paintFrame->getRowLength()); unsigned y = srcStartY; unsigned remainingLines = srcEndY - srcStartY; VLA_SSE_ALIGNED(uint32_t, buf, lineWidth); while (remainingLines) { unsigned lines; auto* data = paintFrame->getMultiLinePtr( y, remainingLines, lines, lineWidth, buf); glTexSubImage2D( GL_TEXTURE_2D, // target 0, // level 0, // offset x y, // offset y lineWidth, // width lines, // height GL_BGRA, // format GL_UNSIGNED_BYTE, // type data); // data y += lines; remainingLines -= lines; } glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); // restore default } // possibly upload scaler specific data if (currScaler) { currScaler->uploadBlock(srcStartY, srcEndY, lineWidth, *paintFrame); } } void GLPostProcessor::drawGlow(int glow) { if ((glow == 0) || !storedFrame) return; static const vec2 pos[4] = { vec2(-1, 1), vec2(-1,-1), vec2( 1,-1), vec2( 1, 1) }; static const vec2 tex[4] = { vec2( 0, 1), vec2( 0, 0), vec2( 1, 0), vec2( 1, 1) }; gl::context->progTex.activate(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); colorTex[(frameCounter & 1) ^ 1].bind(); glUniform4f(gl::context->unifTexColor, 1.0f, 1.0f, 1.0f, glow * 31 / 3200.0f); mat4 I; glUniformMatrix4fv(gl::context->unifTexMvp, 1, GL_FALSE, &I[0][0]); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, pos); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, tex); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDisable(GL_BLEND); } void GLPostProcessor::preCalcNoise(float factor) { GLbyte buf1[256 * 256]; GLbyte buf2[256 * 256]; auto& generator = global_urng(); // fast (non-cryptographic) random numbers std::normal_distribution distribution(0.0f, 1.0f); for (int i = 0; i < 256 * 256; ++i) { float r = distribution(generator); int s = Math::clip<-255, 255>(roundf(r * factor)); buf1[i] = (s > 0) ? s : 0; buf2[i] = (s < 0) ? -s : 0; } noiseTextureA.bind(); glTexImage2D( GL_TEXTURE_2D, // target 0, // level GL_LUMINANCE, // internal format 256, // width 256, // height 0, // border GL_LUMINANCE, // format GL_UNSIGNED_BYTE, // type buf1); // data noiseTextureB.bind(); glTexImage2D( GL_TEXTURE_2D, // target 0, // level GL_LUMINANCE, // internal format 256, // width 256, // height 0, // border GL_LUMINANCE, // format GL_UNSIGNED_BYTE, // type buf2); // data } void GLPostProcessor::drawNoise() { if (renderSettings.getNoise() == 0.0f) return; // Rotate and mirror noise texture in consecutive frames to avoid // seeing 'patterns' in the noise. static const vec2 pos[8][4] = { { { -1, -1 }, { 1, -1 }, { 1, 1 }, { -1, 1 } }, { { -1, 1 }, { 1, 1 }, { 1, -1 }, { -1, -1 } }, { { -1, 1 }, { -1, -1 }, { 1, -1 }, { 1, 1 } }, { { 1, 1 }, { 1, -1 }, { -1, -1 }, { -1, 1 } }, { { 1, 1 }, { -1, 1 }, { -1, -1 }, { 1, -1 } }, { { 1, -1 }, { -1, -1 }, { -1, 1 }, { 1, 1 } }, { { 1, -1 }, { 1, 1 }, { -1, 1 }, { -1, -1 } }, { { -1, -1 }, { -1, 1 }, { 1, 1 }, { 1, -1 } } }; vec2 noise(noiseX, noiseY); const vec2 tex[4] = { noise + vec2(0.0f, 1.875f), noise + vec2(2.0f, 1.875f), noise + vec2(2.0f, 0.0f ), noise + vec2(0.0f, 0.0f ) }; gl::context->progTex.activate(); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE); glUniform4f(gl::context->unifTexColor, 1.0f, 1.0f, 1.0f, 1.0f); mat4 I; glUniformMatrix4fv(gl::context->unifTexMvp, 1, GL_FALSE, &I[0][0]); unsigned seq = frameCounter & 7; glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, pos[seq]); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, tex); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); noiseTextureA.bind(); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBlendEquation(GL_FUNC_REVERSE_SUBTRACT); noiseTextureB.bind(); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBlendEquation(GL_FUNC_ADD); // restore default } static const int GRID_SIZE = 16; static const int GRID_SIZE1 = GRID_SIZE + 1; static const int NUM_INDICES = (GRID_SIZE1 * 2 + 2) * GRID_SIZE - 2; struct Vertex { vec3 position; vec3 normal; vec2 tex; }; void GLPostProcessor::preCalcMonitor3D(float width) { // precalculate vertex-positions, -normals and -texture-coordinates Vertex vertices[GRID_SIZE1][GRID_SIZE1]; static const float GRID_SIZE2 = float(GRID_SIZE) / 2.0f; float s = width / 320.0f; float b = (320.0f - width) / (2.0f * 320.0f); for (int sx = 0; sx < GRID_SIZE1; ++sx) { for (int sy = 0; sy < GRID_SIZE1; ++sy) { Vertex& v = vertices[sx][sy]; float x = (sx - GRID_SIZE2) / GRID_SIZE2; float y = (sy - GRID_SIZE2) / GRID_SIZE2; v.position = vec3(x, y, (x * x + y * y) / -12.0f); v.normal = normalize(vec3(x / 6.0f, y / 6.0f, 1.0f)) * 1.2f; v.tex = vec2((float(sx) / GRID_SIZE) * s + b, float(sy) / GRID_SIZE); } } // calculate indices unsigned short indices[NUM_INDICES]; unsigned short* ind = indices; for (int y = 0; y < GRID_SIZE; ++y) { for (int x = 0; x < GRID_SIZE1; ++x) { *ind++ = (y + 0) * GRID_SIZE1 + x; *ind++ = (y + 1) * GRID_SIZE1 + x; } // skip 2, filled in later ind += 2; } assert((ind - indices) == NUM_INDICES + 2); ind = indices; for (int y = 0; y < (GRID_SIZE - 1); ++y) { ind += 2 * GRID_SIZE1; // repeat prev and next index to restart strip ind[0] = ind[-1]; ind[1] = ind[ 2]; ind += 2; } // upload calculated values to buffers glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer.get()); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer.get()); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); // calculate transformation matrices mat4 proj = frustum(-1, 1, -1, 1, 1, 10); mat4 tran = translate(vec3(0.0f, 0.4f, -2.0f)); mat4 rotx = rotateX(radians(-10.0f)); mat4 scal = scale(vec3(2.2f, 2.2f, 2.2f)); mat3 normal = mat3(rotx); mat4 mvp = proj * tran * rotx * scal; // set uniforms monitor3DProg.activate(); glUniform1i(monitor3DProg.getUniformLocation("u_tex"), 0); glUniformMatrix4fv(monitor3DProg.getUniformLocation("u_mvpMatrix"), 1, GL_FALSE, &mvp[0][0]); glUniformMatrix3fv(monitor3DProg.getUniformLocation("u_normalMatrix"), 1, GL_FALSE, &normal[0][0]); } void GLPostProcessor::drawMonitor3D() { monitor3DProg.activate(); char* base = nullptr; glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer.get()); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer.get()); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), base); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), base + sizeof(vec3)); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), base + sizeof(vec3) + sizeof(vec3)); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glEnableVertexAttribArray(2); glDrawElements(GL_TRIANGLE_STRIP, NUM_INDICES, GL_UNSIGNED_SHORT, nullptr); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/GLPostProcessor.hh000066400000000000000000000050651257557151200217330ustar00rootroot00000000000000#ifndef GLPOSTPROCESSOR_HH #define GLPOSTPROCESSOR_HH #include "PostProcessor.hh" #include "RenderSettings.hh" #include "GLUtil.hh" #include #include #include namespace openmsx { class GLScaler; class MSXMotherBoard; class Display; /** Rasterizer using SDL. */ class GLPostProcessor final : public PostProcessor { public: GLPostProcessor( MSXMotherBoard& motherBoard, Display& display, OutputSurface& screen, const std::string& videoSource, unsigned maxWidth, unsigned height, bool canDoInterlace); ~GLPostProcessor(); // Layer interface: void paint(OutputSurface& output) override; std::unique_ptr rotateFrames( std::unique_ptr finishedFrame, EmuTime::param time) override; protected: // Observer interface: void update(const Setting& setting) override; private: void createRegions(); void uploadFrame(); void uploadBlock(unsigned srcStartY, unsigned srcEndY, unsigned lineWidth); void preCalcNoise(float factor); void drawNoise(); void drawGlow(int glow); void preCalcMonitor3D(float width); void drawMonitor3D(); /** The currently active scaler. */ std::unique_ptr currScaler; gl::Texture colorTex[2]; gl::FrameBufferObject fbo[2]; // Noise effect: gl::Texture noiseTextureA; gl::Texture noiseTextureB; float noiseX, noiseY; struct TextureData { TextureData(); TextureData(TextureData&& rhs) #if !defined(_MSC_VER) // Visual C++ doesn't support 'noexcept' yet. However, // unless there is a move operation that never throws, // std::vector may require a copy constructor, and in // fact that happens with LLVM's libc++. noexcept #endif ; gl::ColorTexture tex; gl::PixelBuffer pbo; }; std::vector> textures; gl::ColorTexture superImposeTex; struct Region { Region(unsigned srcStartY_, unsigned srcEndY_, unsigned dstStartY_, unsigned dstEndY_, unsigned lineWidth_) : srcStartY(srcStartY_) , srcEndY(srcEndY_) , dstStartY(dstStartY_) , dstEndY(dstEndY_) , lineWidth(lineWidth_) {} unsigned srcStartY; unsigned srcEndY; unsigned dstStartY; unsigned dstEndY; unsigned lineWidth; }; std::vector regions; unsigned height; unsigned frameCounter; /** Currently active scale algorithm, used to detect scaler changes. */ RenderSettings::ScaleAlgorithm scaleAlgorithm; gl::ShaderProgram monitor3DProg; gl::BufferObject arrayBuffer; gl::BufferObject elementbuffer; bool storedFrame; }; } // namespace openmsx #endif // GLPOSTPROCESSOR_HH openMSX-RELEASE_0_12_0/src/video/GLSnow.cc000066400000000000000000000040321257557151200200130ustar00rootroot00000000000000#include "GLSnow.hh" #include "GLContext.hh" #include "gl_mat.hh" #include "Display.hh" #include "gl_vec.hh" #include "openmsx.hh" #include "random.hh" using namespace gl; namespace openmsx { GLSnow::GLSnow(Display& display_) : Layer(COVER_FULL, Z_BACKGROUND) , display(display_) , noiseTexture(true, true) // enable interpolation + wrapping { // Create noise texture. auto& generator = global_urng(); // fast (non-cryptographic) random numbers std::uniform_int_distribution distribution(0, 255); byte buf[128 * 128]; for (auto& b : buf) { b = distribution(generator); } glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 128, 128, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, buf); } void GLSnow::paint(OutputSurface& /*output*/) { // Rotate and mirror noise texture in consecutive frames to avoid // seeing 'patterns' in the noise. static const vec2 pos[8][4] = { { { -1, -1 }, { 1, -1 }, { 1, 1 }, { -1, 1 } }, { { -1, 1 }, { 1, 1 }, { 1, -1 }, { -1, -1 } }, { { -1, 1 }, { -1, -1 }, { 1, -1 }, { 1, 1 } }, { { 1, 1 }, { 1, -1 }, { -1, -1 }, { -1, 1 } }, { { 1, 1 }, { -1, 1 }, { -1, -1 }, { 1, -1 } }, { { 1, -1 }, { -1, -1 }, { -1, 1 }, { 1, 1 } }, { { 1, -1 }, { 1, 1 }, { -1, 1 }, { -1, -1 } }, { { -1, -1 }, { -1, 1 }, { 1, 1 }, { 1, -1 } } }; static unsigned cnt = 0; cnt = (cnt + 1) % 8; vec2 offset(random_float(0.0f, 1.0f) ,random_float(0.0f, 1.0f)); const vec2 tex[4] = { offset + vec2(0.0f, 2.0f), offset + vec2(2.0f, 2.0f), offset + vec2(2.0f, 0.0f), offset + vec2(0.0f, 0.0f) }; gl::context->progTex.activate(); glUniform4f(gl::context->unifTexColor, 1.0f, 1.0f, 1.0f, 1.0f); mat4 I; glUniformMatrix4fv(gl::context->unifTexMvp, 1, GL_FALSE, &I[0][0]); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, pos[cnt]); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, tex); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); noiseTexture.bind(); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); display.repaintDelayed(100 * 1000); // 10fps } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/GLSnow.hh000066400000000000000000000006631257557151200200330ustar00rootroot00000000000000#ifndef GLSNOW_HH #define GLSNOW_HH #include "Layer.hh" #include "GLUtil.hh" #include "noncopyable.hh" namespace openmsx { class Display; /** Snow effect for background layer. */ class GLSnow final : public Layer, private noncopyable { public: GLSnow(Display& display); // Layer interface: void paint(OutputSurface& output) override; private: Display& display; gl::Texture noiseTexture; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/GLUtil.cc000066400000000000000000000200741257557151200200060ustar00rootroot00000000000000#include "GLUtil.hh" #include "File.hh" #include "FileContext.hh" #include "FileException.hh" #include "InitException.hh" #include "vla.hh" #include "Version.hh" #include #include #include #include #ifndef glGetShaderiv #error The version of GLEW you have installed is missing some OpenGL 2.0 entry points. \ Please upgrade to GLEW 1.3.2 or higher. #endif using std::string; using namespace openmsx; namespace gl { /* void checkGLError(const string& prefix) { GLenum error = glGetError(); if (error != GL_NO_ERROR) { string err = (char*)gluErrorString(error); std::cerr << "GL error: " << prefix << ": " << err << std::endl; } } */ // class Texture Texture::Texture(bool interpolation, bool wrap) { allocate(); setInterpolation(interpolation); setWrapMode(wrap); } void Texture::allocate() { glGenTextures(1, &textureId); } void Texture::reset() { glDeleteTextures(1, &textureId); // ok to delete 0-texture textureId = 0; } void Texture::setInterpolation(bool interpolation) { bind(); int mode = interpolation ? GL_LINEAR : GL_NEAREST; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mode); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mode); } void Texture::setWrapMode(bool wrap) { bind(); int mode = wrap ? GL_REPEAT : GL_CLAMP_TO_EDGE; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, mode); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, mode); } // class ColorTexture ColorTexture::ColorTexture(GLsizei width_, GLsizei height_) { resize(width_, height_); } void ColorTexture::resize(GLsizei width_, GLsizei height_) { width = width_; height = height_; bind(); glTexImage2D( GL_TEXTURE_2D, // target 0, // level GL_RGBA8, // internal format width, // width height, // height 0, // border GL_BGRA, // format GL_UNSIGNED_BYTE, // type nullptr); // data } // class FrameBufferObject static GLuint currentId = 0; static std::vector stack; FrameBufferObject::FrameBufferObject() : bufferId(0) // 0 is not a valid openGL name { } FrameBufferObject::FrameBufferObject(Texture& texture) { glGenFramebuffersEXT(1, &bufferId); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, bufferId); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texture.textureId, 0); bool success = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT; glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, currentId); if (!success) { throw InitException( "Your OpenGL implementation support for " "framebuffer objects is too limited."); } } FrameBufferObject::~FrameBufferObject() { // It's ok to delete '0' (it's a NOP), but we anyway have to check // for pop(). if (!bufferId) return; if (currentId == bufferId) { pop(); } glDeleteFramebuffersEXT(1, &bufferId); } void FrameBufferObject::push() { stack.push_back(currentId); currentId = bufferId; glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, currentId); } void FrameBufferObject::pop() { assert(currentId == bufferId); assert(!stack.empty()); currentId = stack.back(); stack.pop_back(); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, currentId); } bool PixelBuffers::enabled = true; // Utility function used by Shader. static string readTextFile(const string& filename) { File file(systemFileContext().resolve(filename)); size_t size; const byte* data = file.mmap(size); return string(reinterpret_cast(data), size); } // class Shader Shader::Shader(GLenum type, const string& filename) { init(type, "", filename); } Shader::Shader(GLenum type, const string& header, const string& filename) { init(type, header, filename); } void Shader::init(GLenum type, const string& header, const string& filename) { // Load shader source. string source = header; try { source += readTextFile("shaders/" + filename); } catch (FileException& e) { std::cerr << "Cannot find shader: " << e.getMessage() << std::endl; handle = 0; return; } // Allocate shader handle. handle = glCreateShader(type); if (handle == 0) { std::cerr << "Failed to allocate shader" << std::endl; return; } // Set shader source. const char* sourcePtr = source.c_str(); glShaderSource(handle, 1, &sourcePtr, nullptr); // Compile shader and print any errors and warnings. glCompileShader(handle); const bool ok = isOK(); GLint infoLogLength = 0; glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &infoLogLength); // note: the null terminator is included, so empty string has length 1 if (!ok || (!Version::RELEASE && infoLogLength > 1)) { VLA(GLchar, infoLog, infoLogLength); glGetShaderInfoLog(handle, infoLogLength, nullptr, infoLog); fprintf(stderr, "%s(s) compiling shader \"%s\":\n%s", ok ? "Warning" : "Error", filename.c_str(), infoLogLength > 1 ? infoLog : "(no details available)\n"); } } Shader::~Shader() { glDeleteShader(handle); // ok to delete '0' } bool Shader::isOK() const { if (handle == 0) return false; GLint compileStatus = GL_FALSE; glGetShaderiv(handle, GL_COMPILE_STATUS, &compileStatus); return compileStatus == GL_TRUE; } // class VertexShader VertexShader::VertexShader(const string& filename) : Shader(GL_VERTEX_SHADER, filename) { } VertexShader::VertexShader(const string& header, const string& filename) : Shader(GL_VERTEX_SHADER, header, filename) { } // class FragmentShader FragmentShader::FragmentShader(const string& filename) : Shader(GL_FRAGMENT_SHADER, filename) { } FragmentShader::FragmentShader(const string& header, const string& filename) : Shader(GL_FRAGMENT_SHADER, header, filename) { } // class ShaderProgram void ShaderProgram::allocate() { handle = glCreateProgram(); if (handle == 0) { std::cerr << "Failed to allocate program" << std::endl; } } void ShaderProgram::reset() { glDeleteProgram(handle); // ok to delete '0' handle = 0; } bool ShaderProgram::isOK() const { if (handle == 0) return false; GLint linkStatus = GL_FALSE; glGetProgramiv(handle, GL_LINK_STATUS, &linkStatus); return linkStatus == GL_TRUE; } void ShaderProgram::attach(const Shader& shader) { // Sanity check on this program. if (handle == 0) return; // Sanity check on the shader. if (!shader.isOK()) return; // Attach it. glAttachShader(handle, shader.handle); } void ShaderProgram::link() { // Sanity check on this program. if (handle == 0) return; // Link the program and print any errors and warnings. glLinkProgram(handle); const bool ok = isOK(); GLint infoLogLength = 0; glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &infoLogLength); // note: the null terminator is included, so empty string has length 1 if (!ok || (!Version::RELEASE && infoLogLength > 1)) { VLA(GLchar, infoLog, infoLogLength); glGetProgramInfoLog(handle, infoLogLength, nullptr, infoLog); fprintf(stderr, "%s(s) linking shader program:\n%s\n", ok ? "Warning" : "Error", infoLogLength > 1 ? infoLog : "(no details available)\n"); } } void ShaderProgram::bindAttribLocation(unsigned index, const char* name) { glBindAttribLocation(handle, index, name); } GLint ShaderProgram::getUniformLocation(const char* name) const { // Sanity check on this program. if (!isOK()) return -1; return glGetUniformLocation(handle, name); } void ShaderProgram::activate() const { glUseProgram(handle); } // only useful for debugging void ShaderProgram::validate() { glValidateProgram(handle); GLint validateStatus = GL_FALSE; glGetProgramiv(handle, GL_VALIDATE_STATUS, &validateStatus); GLint infoLogLength = 0; glGetProgramiv(handle, GL_INFO_LOG_LENGTH, &infoLogLength); // note: the null terminator is included, so empty string has length 1 VLA(GLchar, infoLog, infoLogLength); glGetProgramInfoLog(handle, infoLogLength, nullptr, infoLog); std::cout << "Validate " << ((validateStatus == GL_TRUE) ? string("OK") : string("FAIL")) << ": " << infoLog << std::endl; } // class BufferObject BufferObject::BufferObject() { glGenBuffers(1, &bufferId); } BufferObject::~BufferObject() { glDeleteBuffers(1, &bufferId); // ok to delete 0-buffer } } // namespace gl openMSX-RELEASE_0_12_0/src/video/GLUtil.hh000066400000000000000000000270471257557151200200270ustar00rootroot00000000000000#ifndef GLUTIL_HH #define GLUTIL_HH // Check for availability of OpenGL. #include "components.hh" #if COMPONENT_GL // Include GLEW headers. #include // Include OpenGL headers. #ifdef __APPLE__ #include #else #include #endif #include "MemBuffer.hh" #include "noncopyable.hh" #include "build-info.hh" #include #include namespace gl { // TODO this needs glu, but atm we don't link against glu (in windows) //void checkGLError(const std::string& prefix); // Dummy object, to be able to construct empty handler objects. struct Null {}; /** Most basic/generic texture: only contains a texture ID. * Current implementation always assumes 2D textures. */ class Texture { public: /** Allocate a openGL texture name and enable/disable interpolation. */ explicit Texture(bool interpolation = false, bool wrap = false); /** Create null-handle (not yet allocate an openGL handle). */ explicit Texture(Null) : textureId(0) {} /** Release openGL texture name. */ ~Texture() { reset(); } /** Move constructor and assignment. */ Texture(Texture&& other) : textureId(other.textureId) { other.textureId = 0; // 0 is not a valid openGL texture name } Texture& operator=(Texture&& other) { std::swap(textureId, other.textureId); return *this; } /** Allocate an openGL texture name. */ void allocate(); /** Release openGL texture name. */ void reset(); /** Returns the underlying openGL handler id. * 0 iff no openGL texture is allocated. */ GLuint get() const { return textureId; } /** Makes this texture the active GL texture. * The other methods of this class and its subclasses will implicitly * bind the texture, so you only need this method to explicitly bind * this texture for use in GL function calls outside of this class. */ void bind() { glBindTexture(GL_TEXTURE_2D, textureId); } /** Enable/disable bilinear interpolation for this texture. IOW selects * between GL_NEAREST or GL_LINEAR filtering. */ void setInterpolation(bool interpolation); void setWrapMode(bool wrap); protected: GLuint textureId; private: // Disable copy, assign. Texture(const Texture&); Texture& operator=(const Texture&); friend class FrameBufferObject; }; class ColorTexture : public Texture { public: /** Default constructor, zero-sized texture. */ ColorTexture() : width(0), height(0) {} /** Move constructor and assignment. */ ColorTexture(ColorTexture&& other) : Texture(std::move(other)) { width = other.width; height = other.height; } ColorTexture& operator=(ColorTexture&& other) { Texture::operator=(std::move(other)); width = other.width; height = other.height; return *this; } /** Create color texture with given size. * Initial content is undefined. */ ColorTexture(GLsizei width, GLsizei height); void resize(GLsizei width, GLsizei height); GLsizei getWidth () const { return width; } GLsizei getHeight() const { return height; } private: // Disable copy, assign. ColorTexture(const ColorTexture&); ColorTexture& operator=(const ColorTexture&); GLsizei width; GLsizei height; }; class FrameBufferObject //: public noncopyable { public: FrameBufferObject(); explicit FrameBufferObject(Texture& texture); FrameBufferObject(FrameBufferObject&& other) : bufferId(other.bufferId) { other.bufferId = 0; } FrameBufferObject& operator=(FrameBufferObject&& other) { std::swap(bufferId, other.bufferId); return *this; } ~FrameBufferObject(); void push(); void pop(); private: GLuint bufferId; }; struct PixelBuffers { /** Global switch to disable pixel buffers using the "-nopbo" option. */ static bool enabled; }; /** Wrapper around a pixel buffer. * The pixel buffer will be allocated in VRAM if possible, in main RAM * otherwise. * The pixel type is templatized T. */ template class PixelBuffer //: public noncopyable { public: PixelBuffer(); PixelBuffer(PixelBuffer&& other); PixelBuffer& operator=(PixelBuffer&& other); ~PixelBuffer(); /** Are PBOs supported by this openGL implementation? * This class implements a SW fallback in case PBOs are not directly * supported by this openGL implementation, but it will probably * be a lot slower. */ bool openGLSupported() const; /** Sets the image for this buffer. * TODO: Actually, only image size for now; * later, if we need it, image data too. */ void setImage(GLuint width, GLuint height); /** Bind this PixelBuffer. Must be called before the methods * getOffset() or mapWrite() are used. (Is a wrapper around * glBindBuffer). */ void bind() const; /** Unbind this buffer. */ void unbind() const; /** Gets a pointer relative to the start of this buffer. * You must not dereference this pointer, but you can pass it to * glTexImage etc when this buffer is bound as the source. * @pre This PixelBuffer must be bound (see bind()) before calling * this method. */ T* getOffset(GLuint x, GLuint y); /** Maps the contents of this buffer into memory. The returned buffer * is write-only (reading could be very slow or even result in a * segfault). * @return Pointer through which you can write pixels to this buffer, * or 0 if the buffer could not be mapped. * @pre This PixelBuffer must be bound (see bind()) before calling * this method. */ T* mapWrite(); /** Unmaps the contents of this buffer. * After this call, you must no longer use the pointer returned by * mapWrite. */ void unmap() const; private: /** Buffer for main RAM fallback (not allocated in the normal case). */ openmsx::MemBuffer allocated; /** Handle of the GL buffer, or 0 if no GL buffer is available. */ GLuint bufferId; /** Number of pixels per line. */ GLuint width; /** Number of lines. */ GLuint height; }; // class PixelBuffer template PixelBuffer::PixelBuffer() { if (PixelBuffers::enabled && GLEW_ARB_pixel_buffer_object) { glGenBuffers(1, &bufferId); } else { //std::cerr << "OpenGL pixel buffers are not available" << std::endl; bufferId = 0; } } template PixelBuffer::PixelBuffer(PixelBuffer&& other) : allocated(std::move(other.allocated)) , bufferId(other.bufferId) , width(other.width) , height(other.height) { other.bufferId = 0; } template PixelBuffer& PixelBuffer::operator=(PixelBuffer&& other) { std::swap(allocated, other.allocated); std::swap(bufferId, other.bufferId); std::swap(width, other.width); std::swap(height, other.height); return *this; } template PixelBuffer::~PixelBuffer() { glDeleteBuffers(1, &bufferId); // ok to delete '0' } template bool PixelBuffer::openGLSupported() const { return bufferId != 0; } template void PixelBuffer::setImage(GLuint width, GLuint height) { this->width = width; this->height = height; if (bufferId != 0) { bind(); // TODO make performance hint configurable? glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, width * height * 4, nullptr, // leave data undefined GL_STREAM_DRAW); // performance hint unbind(); } else { allocated.resize(width * height); } } template void PixelBuffer::bind() const { if (bufferId != 0) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, bufferId); } } template void PixelBuffer::unbind() const { if (bufferId != 0) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); } } template T* PixelBuffer::getOffset(GLuint x, GLuint y) { assert(x < width); assert(y < height); unsigned offset = x + width * y; if (bufferId != 0) { return static_cast(nullptr) + offset; } else { return &allocated[offset]; } } template T* PixelBuffer::mapWrite() { if (bufferId != 0) { return reinterpret_cast(glMapBuffer( GL_PIXEL_UNPACK_BUFFER_ARB, GL_WRITE_ONLY)); } else { return allocated.data(); } } template void PixelBuffer::unmap() const { if (bufferId != 0) { glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB); } } /** Wrapper around an OpenGL shader: a program executed on the GPU. * This class is a base class for vertex and fragment shaders. */ class Shader { public: /** Returns true iff this shader is loaded and compiled without errors. */ bool isOK() const; protected: /** Instantiates a shader. * @param type The shader type: GL_VERTEX_SHADER or GL_FRAGMENT_SHADER. * @param filename The GLSL source code for the shader. */ Shader(GLenum type, const std::string& filename); Shader(GLenum type, const std::string& header, const std::string& filename); ~Shader(); private: void init(GLenum type, const std::string& header, const std::string& filename); friend class ShaderProgram; GLuint handle; }; /** Wrapper around an OpenGL vertex shader: * a program executed on the GPU that computes per-vertex stuff. */ class VertexShader : public Shader { public: /** Instantiates a vertex shader. * @param filename The GLSL source code for the shader. */ explicit VertexShader(const std::string& filename); VertexShader(const std::string& header, const std::string& filename); }; /** Wrapper around an OpenGL fragment shader: * a program executed on the GPU that computes the colors of pixels. */ class FragmentShader : public Shader { public: /** Instantiates a fragment shader. * @param filename The GLSL source code for the shader. */ explicit FragmentShader(const std::string& filename); FragmentShader(const std::string& header, const std::string& filename); }; /** Wrapper around an OpenGL program: * a collection of vertex and fragment shaders. */ class ShaderProgram : public noncopyable { public: /** Create handler and allocate underlying openGL object. */ ShaderProgram() { allocate(); } /** Create null handler (don't yet allocate a openGL object). */ explicit ShaderProgram(Null) : handle(0) {} /** Destroy handler object (release the underlying openGL object). */ ~ShaderProgram() { reset(); } /** Allocate a shader program handle. */ void allocate(); /** Release the shader program handle. */ void reset(); /** Returns the underlying openGL handler id. * 0 iff no openGL program is allocated. */ GLuint get() const { return handle; } /** Returns true iff this program was linked without errors. * Note that this will certainly return false until link() is called. */ bool isOK() const; /** Adds a given shader to this program. */ void attach(const Shader& shader); /** Links all attached shaders together into one program. * This should be done before activating the program. */ void link(); /** Bind the given name for a vertex shader attribute to the given * location. */ void bindAttribLocation(unsigned index, const char* name); /** Gets a reference to a uniform variable declared in the shader source. * Note that you have to activate this program before you can change * the uniform variable's value. */ GLint getUniformLocation(const char* name) const; /** Makes this program the active shader program. * This requires that the program is already linked. */ void activate() const; void validate(); private: GLuint handle; }; class BufferObject //: public noncopyable { public: BufferObject(); ~BufferObject(); BufferObject(BufferObject&& other) : bufferId(other.bufferId) { other.bufferId = 0; } BufferObject& operator=(BufferObject&& other) { std::swap(bufferId, other.bufferId); return *this; } GLuint get() const { return bufferId; } private: GLuint bufferId; }; } // namespace gl #endif // COMPONENT_GL #endif // GLUTIL_HH openMSX-RELEASE_0_12_0/src/video/Icon.cc000066400000000000000000000274651257557151200175510ustar00rootroot00000000000000/* GIMP RGBA C-Source image dump (openMSX_32.c) */ // ...but a little bit modified to fit into openMSX nicely :) // To replace it: // - save your image from GIMP with a .c extension, with all options off, // except RGBA // - rename it to Icon.hh // - add the namespace stuff // - remove the const // - rename gimp_image to openMSX_icon // - add these comments #include "Icon.hh" namespace openmsx { OpenMSX_Icon openMSX_icon = { 32, 32, 4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\207\26\0\0\206V\0\0\204d\0\0\201Q\0" "\0\200\17\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\207" "\5\37\37\225\17788\240\366\4\4\205\377\0\0\201\377\24\24\211\377\20\20\205" "\357\0\0zj\0\0x\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\206\1AA\245\240\352" "\352\365\377\377\377\377\377\312\312\345\377\230\230\313\377\375\375\376" "\377\361\361\370\37788\224\377\0\0u\215\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\204N\251\251\325\377\377\377\377\377\377\377\377\377\377\377\377\377\377" "\377\377\377\377\377\377\377\377\377\377\377\266\266\327\377\0\0q\376\0\0" "oN\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\202\275\331\331\354\377\335\335\335\377\324" "\324\324\377\370\370\367\377\367\367\367\377\302\302\302\377\377\377\377" "\377\362\362\370\377\0\0n\377\0\0l\240\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\200\2\0\0\177" "\365\304\304\341\377\335\335\335\377[ZG\377\331\320H\377\312\304k\377ZZZ" "\377\377\377\377\377\342\342\356\377\0\0k\377\0\0i\332\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0{\355]]\252\377\376\376\376\377\334\327\223\377\337\326J\377\343" "\326\12\377\352\352\343\377\377\377\377\377ll\250\377\0\0g\377\0\0f\344\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\25\24i\340\217\213Y\377\327\3150\377\366\360\225\377" "\331\316'\377\356\340\4\377\312\302?\377\255\247A\377=::\377\0\0d\377\0\0" "c\342\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0'%U\345\224\213\7\377\260\245\3\377\352\336\40" "\377\334\320\24\377\341\323\4\377\266\254\5\377sl\3\377VQ3\377\0\0a\377\0" "\0_\343\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0u\3\0\0r\37263J\377@<\1\377\34\14\0\377u?\3\377" ")\1\1\377\6\5\0\377\177y7\377\10\10d\377\0\0]\377\0\0[\376\0\0[0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0[=\0\0n\377\10\10q\377\236\231n\377\254V\5\377\323\10\2\377\313\33\2" "\377\234\220=\377\366\365\356\377\362\362\367\377\243\243\304\377**s\377" "\0\0W\262\0\0S\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\20\24\0\0S\32411\203\377\326\326\347\377\376\376\376\377\344" "\343\307\377\306\274f\377\313\310\225\377\370\370\370\377\376\376\376\377" "\376\376\376\377\376\376\376\377\370\370\372\37711\200\377\0\0^\315\0\0\\" "E\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0A>\0\0c\311\0\0e\36566p\377\360" "\360\366\377\376\376\376\377\376\376\376\377\376\376\376\377\376\376\376" "\377\376\376\376\377\376\376\376\377\376\376\376\377\376\376\376\377\376" "\376\376\377\376\376\376\377{{\247\377\0\0n\377\0\0e\367\0\0P:\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0I4\0\0z\366\0\0o\377\26\26U\377\346\346\357\377\376\376" "\376\377\376\376\376\377\376\376\376\377\376\376\376\377\376\376\376\377" "\376\376\376\377\376\376\376\377\376\376\376\377\376\376\376\377\376\376" "\376\377\376\376\376\377jj\236\377\0\0j\377\0\0d\377\0\0X\331\0\0""0\14\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0i\252\0\0x\377\0\0^\377\245\245\274\377\376\376\376\377" "\376\376\376\377\376\376\376\377\376\376\376\377\376\376\376\377\376\376" "\376\377\376\376\376\377\376\376\376\377\376\376\376\377\376\376\376\377" "\376\376\376\377\337\337\345\377\20\20n\377\0\0e\377\0\0`\377\0\0\\\377\0" "\0G\203\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0d\266\0\0m\377,,e\377\374\374\375\377\376\376\376" "\377\376\376\376\377\376\376\376\377\376\376\376\377\376\376\376\377\376" "\376\376\377\371\371\371\377gg\240\377((\177\377\227\227\266\377\303\303" "\321\377!!w\377\0\0g\377\0\0b\377\0\0\\\377\0\0W\377\0\0N\355\0\0)\15\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0""7a\0\0W\377\223\223\253\377\376\376\376\377\376\376\376\377\375" "\375\375\377\376\376\376\377\376\376\376\377\376\376\376\377\376\376\376" "\377\222\222\271\377\0\0\177\377\0\0y\377\1\1i\377\2\2m\377\0\0i\377\0\0" "c\377\0\0^\377\0\0X\377\0\0S\377\0\0M\377\0\0@Z\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\40\201" "\341\341\354\377\376\376\376\377\376\376\376\377\376\376\376\377\376\376" "\376\377\376\376\376\377\376\376\376\377\376\376\376\377II\221\377\0\0{\377" "\0\0u\377\0\0p\377\0\0j\377\0\0e\377\0\0_\377\0\0Z\377\0\0T\377\0\0O\377" "\0\0I\377\0\0/e\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0""99c\213\376\376\376\377\376\376\376\377\376" "\376\376\377\376\376\376\377\376\376\376\377\376\376\376\377\376\376\376" "\377\376\376\376\377PP\224\377\0\0w\377\0\0q\377\0\0l\377\0\0f\377\0\0a\377" "\0\0[\377\0\0V\377\0\0P\377\0\0K\377\0\0>\377\0\0$\40\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0+(\1\37\227\214\2x\236" "\224%\333\303\274u\377\343\342\335\377\376\376\376\377\376\376\376\377\376" "\376\376\377\376\376\376\377\376\376\376\377\376\376\376\377\241\241\274" "\377\0\0r\377\0\0m\377\0\0h\377\0\0b\377\0\0]\377\0\0W\377\0\0R\377\0\0M" "\377\33\33Y\377--^\377\0\0""6F\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\265\250\3\317\326\305\3\377\310\270\3\377" "\301\260\3\377\274\254\13\377\303\277\236\377\376\376\376\377\376\376\376" "\377\376\376\376\377\376\376\376\377\376\376\376\377\375\375\375\377\212" "\212\256\377QQ\221\377PP\212\377##j\377\0\0O\377\12\12P\37766l\377\205\205" "\240\377\356\356\360\377\223\223\253\377\0\0""1c\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\"\37\0\36\340\316\4\376\354\327" "\4\377\353\326\4\377\353\324\4\377\312\266\3\377\266\243\5\377\310\306\265" "\377\375\375\375\377\375\375\375\377\375\375\375\377\375\375\375\377\375" "\375\375\377\375\375\375\377\375\375\375\377\375\375\375\377\375\375\375" "\377\375\375\375\377\375\375\375\377\375\375\375\377\375\375\375\377\375" "\375\375\377\236\236\255\377\0\0+c\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0rh\2R\354\326\4\377\353\325\4\377\353\323\4\377" "\352\322\4\377\352\320\4\377\312\263\3\377\242\222\36\377\353\353\353\377" "\375\375\375\377\375\375\375\377\375\375\375\377\375\375\375\377\375\375" "\375\377\375\375\375\377\375\375\375\377\375\375\375\377\375\375\375\377" "\375\375\375\377\375\375\375\377\375\375\375\377\375\375\375\377\213\213" "\232\377\0\0\40\330\0\0\37^\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0*&\0\40\327\302\3\373\352\322\4\377\352\321\4\377\352\317\4\377" "\351\316\4\377\351\314\4\377\323\270\3\377\265\261\230\377\357\357\357\377" "\334\334\334\377\375\375\375\377\375\375\375\377\375\375\375\377\375\375" "\375\377\375\375\375\377\375\375\375\377\375\375\375\377\375\375\375\377" "\375\375\375\377\375\375\375\377\377\377\377\377>>T\377\0\0\36\377\0\0\37" "M\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\241\220" "\3\225\352\320\4\377\351\316\4\377\351\315\4\377\351\313\4\377\350\312\4" "\377\350\310\4\377\267\250L\377\336\336\336\377ddd\377\376\376\376\377\375" "\375\375\377\375\375\375\377\375\375\375\377\375\375\375\377\375\375\375" "\377\375\375\375\377\375\375\375\377\375\375\375\377\376\376\376\377\377" "\377\377\377VVj\377\0\0\36\321\0\0\37\3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\17\15\0\14\302\252\3\320\351\314\4\377\350" "\312\4\377\350\310\4\377\350\307\4\377\347\306\4\377\271\240\26\377\375\375" "\375\377\326\326\326\377\323\323\323\377\374\374\374\377\375\375\375\377" "\375\375\375\377\375\375\375\377\375\375\375\377\375\375\375\377\375\375" "\375\377\375\375\375\377\377\377\377\377\377\377\377\377\273\273\277\375" "\4\3\31:\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0OE\1\30\277\246\3\312\350\307\4\377\347\306\4\377\347\304" "\4\377\347\303\4\377\316\267;\377\375\375\375\377\375\375\375\377\364\364" "\364\377\300\300\300\377\375\375\375\377\375\375\375\377\375\375\375\377" "\375\375\375\377\375\375\375\377\376\376\376\377\377\377\377\377\377\377" "\377\377\377\377\377\377\315\302\250\377\242v\2\3479)\1""5\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\1\0" "\10\265\247W\273\314\260\22\376\346\302\4\377\314\254\7\377\326\320\263\377" "\375\375\375\377\375\375\375\377\375\375\375\377\374\374\374\377\375\375" "\375\377\376\376\376\377\376\376\376\377\376\376\376\377\377\377\377\377" "\377\377\377\377\377\377\377\377\377\377\377\377\364\364\364\377\255\213" "1\377\336\257\3\377\236s\2\314\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\2\224\224\223N\210\201" "`\223\244\241\224\233\237\236\234o\223\223\247\212\272\272\306\324\322\322" "\331\374\370\370\371\377\366\366\366\377\275\275\277\377\372\372\372\377" "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\312\310" "\302\377\243\2042\377\334\265\3\377\330\262\2\377\231p\2\276\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0+\7\0\0&" "\34\0\0#\40\0\0\36\40>>>I\177zk\253\256\230`\377\254\223R\377\323\260\13" "\377\325\251\4\377\330\262\2\377\342\271\3\376\316\226\3\366E2\1^\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1gK\1Y\233q\2|\240u\2\365\322\247" "\3\377\333\255\3\377\232p\2\365yX\2x\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\26\22\15\0""8O:\1@.!\0!\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", }; } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/Icon.hh000066400000000000000000000004061257557151200175450ustar00rootroot00000000000000#ifndef ICON_HH #define ICON_HH namespace openmsx { struct OpenMSX_Icon { unsigned width; unsigned height; unsigned bytes_per_pixel; /* 3:RGB, 4:RGBA */ const char* pixel_data; }; extern OpenMSX_Icon openMSX_icon; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/Layer.cc000066400000000000000000000010511257557151200177140ustar00rootroot00000000000000#include "Layer.hh" #include "LayerListener.hh" namespace openmsx { Layer::Layer(Coverage coverage_, ZIndex z_) : display(nullptr), coverage(coverage_), z(z_) { } Layer::~Layer() { } void Layer::setZ(ZIndex z_) { z = z_; if (display) display->updateZ(*this); } // class ScopedLayerHider ScopedLayerHider::ScopedLayerHider(Layer& layer_) : layer(layer_) , originalCoverage(layer.getCoverage()) { layer.setCoverage(Layer::COVER_NONE); } ScopedLayerHider::~ScopedLayerHider() { layer.setCoverage(originalCoverage); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/Layer.hh000066400000000000000000000040531257557151200177330ustar00rootroot00000000000000#ifndef LAYER_HH #define LAYER_HH namespace openmsx { class OutputSurface; class LayerListener; /** Interface for display layers. */ class Layer { public: /** Determines stacking order of layers: * layers with higher Z-indices are closer to the viewer. */ enum ZIndex { Z_DUMMY = -1, Z_BACKGROUND = 0, Z_MSX_PASSIVE = 30, Z_MSX_ACTIVE = 40, Z_OSDGUI = 50, Z_CONSOLE = 100 }; /** Describes how much of the screen is currently covered by a particular * layer. */ enum Coverage { /** Layer fully covers the screen: any underlying layers are invisible. */ COVER_FULL, /** Layer partially covers the screen: it may cover only part of the * screen area, or it may be (semi-)transparent in places. */ COVER_PARTIAL, /** Layer is not visible, that is completely transparent. */ COVER_NONE }; virtual ~Layer(); /** Paint this layer. */ virtual void paint(OutputSurface& output) = 0; /** Query the Z-index of this layer. */ ZIndex getZ() const { return z; } /** Query the coverage of this layer. */ Coverage getCoverage() const { return coverage; } /** Store pointer to Display. * Will be called by Display::addLayer(). */ void setDisplay(LayerListener& display_) { display = &display_; } protected: /** Construct a layer. */ explicit Layer(Coverage coverage = COVER_NONE, ZIndex z = Z_DUMMY); /** Changes the current coverage of this layer. */ void setCoverage(Coverage coverage_) { coverage = coverage_; } /** Changes the current Z-index of this layer. */ void setZ(ZIndex z); private: /** The display this layer is part of. */ LayerListener* display; /** Inspected by Display to determine which layers to paint. */ Coverage coverage; /** Inspected by Display to determine the order in which layers are * painted. */ ZIndex z; friend class ScopedLayerHider; // for setCoverage() }; class ScopedLayerHider { public: explicit ScopedLayerHider(Layer& layer); ~ScopedLayerHider(); private: Layer& layer; Layer::Coverage originalCoverage; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/LayerListener.hh000066400000000000000000000003411257557151200214350ustar00rootroot00000000000000#ifndef LAYERLISTENER_HH #define LAYERLISTENER_HH namespace openmsx { class Layer; class LayerListener { public: virtual void updateZ(Layer& layer) = 0; protected: ~LayerListener() {} }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/OutputRectangle.hh000066400000000000000000000011601257557151200220000ustar00rootroot00000000000000#ifndef OUTPUTRECTANGLE_HH #define OUTPUTRECTANGLE_HH namespace openmsx { class OutputRectangle { public: virtual unsigned getOutputWidth() const = 0; virtual unsigned getOutputHeight() const = 0; protected: ~OutputRectangle() {} }; class DummyOutputRectangle final : public OutputRectangle { public: DummyOutputRectangle(unsigned width_, unsigned height_) : width(width_), height(height_) { } unsigned getOutputWidth() const override { return width; } unsigned getOutputHeight() const override { return height; } private: const unsigned width; const unsigned height; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/OutputSurface.cc000066400000000000000000000027101257557151200214540ustar00rootroot00000000000000#include "OutputSurface.hh" #include "unreachable.hh" #include "build-info.hh" namespace openmsx { OutputSurface::OutputSurface() : surface(nullptr), xOffset(0), yOffset(0), locked(false) { } OutputSurface::~OutputSurface() { } void OutputSurface::lock() { if (isLocked()) return; locked = true; if (surface && SDL_MUSTLOCK(surface)) { // Note: we ignore the return value from SDL_LockSurface() SDL_LockSurface(surface); } } void OutputSurface::unlock() { if (!isLocked()) return; locked = false; if (surface && SDL_MUSTLOCK(surface)) { SDL_UnlockSurface(surface); } } void OutputSurface::setPosition(int x, int y) { xOffset = x; yOffset = y; } void OutputSurface::setSDLFormat(const SDL_PixelFormat& format_) { format = format_; #if HAVE_32BPP // SDL sets an alpha channel only for GL modes. We want an alpha channel // for all 32bpp output surfaces, so we add one ourselves if necessary. if (format.BytesPerPixel == 4 && format.Amask == 0) { unsigned rgbMask = format.Rmask | format.Gmask | format.Bmask; if ((rgbMask & 0x000000FF) == 0) { format.Amask = 0x000000FF; format.Ashift = 0; format.Aloss = 0; } else if ((rgbMask & 0xFF000000) == 0) { format.Amask = 0xFF000000; format.Ashift = 24; format.Aloss = 0; } else { UNREACHABLE; } } #endif } void OutputSurface::setBufferPtr(char* data_, unsigned pitch_) { data = data_; pitch = pitch_; } void OutputSurface::flushFrameBuffer() { } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/OutputSurface.hh000066400000000000000000000103631257557151200214710ustar00rootroot00000000000000#ifndef OUTPUTSURFACE_HH #define OUTPUTSURFACE_HH #include "OutputRectangle.hh" #include "gl_vec.hh" #include "noncopyable.hh" #include #include #include namespace openmsx { /** A frame buffer where pixels can be written to. * It could be an in-memory buffer or a video buffer visible to the user * (see VisibleSurface subclass). */ class OutputSurface : public OutputRectangle, private noncopyable { public: virtual ~OutputSurface(); unsigned getWidth() const { return surface->w; } unsigned getHeight() const { return surface->h; } int getX() const { return xOffset; } int getY() const { return yOffset; } const SDL_PixelFormat& getSDLFormat() const { return format; } SDL_Surface* getSDLSurface() const { return surface; } /** Returns the pixel value for the given RGB color. * No effort is made to ensure that the returned pixel value is not the * color key for this output surface. */ unsigned mapRGB(gl::vec3 rgb) { return mapRGB255(gl::ivec3(rgb * 255.0f)); } /** Same as mapRGB, but RGB components are in range [0..255]. */ unsigned mapRGB255(gl::ivec3 rgb) { return SDL_MapRGB(&format, rgb[0], rgb[1], rgb[2]); // alpha is fully opaque } /** Returns the color key for this output surface. */ template inline Pixel getKeyColor() const { return sizeof(Pixel) == 2 ? 0x0001 // lowest bit of 'some' color component is set : 0x00000000; // alpha = 0 } /** Returns a color that is visually very close to the key color. * The returned color can be used as an alternative for pixels that would * otherwise have the key color. */ template inline Pixel getKeyColorClash() const { assert(sizeof(Pixel) != 4); // shouldn't get clashes in 32bpp return 0; // is visually very close, practically // indistinguishable, from the actual KeyColor } /** Returns the pixel value for the given RGB color. * It is guaranteed that the returned pixel value is different from the * color key for this output surface. */ template Pixel mapKeyedRGB255(gl::ivec3 rgb) { Pixel p = mapRGB255(rgb); if (sizeof(Pixel) == 2) { return (p != getKeyColor()) ? p : getKeyColorClash(); } else { assert(p != getKeyColor()); return p; } } /** Returns the pixel value for the given RGB color. * It is guaranteed that the returned pixel value is different from the * color key for this output surface. */ template Pixel mapKeyedRGB(gl::vec3 rgb) { return mapKeyedRGB255(gl::ivec3(rgb * 255.0f)); } /** Lock this OutputSurface. * Direct pixel access is only allowed on a locked surface. * Locking an already locked surface has no effect. */ void lock(); /** Unlock this OutputSurface. * @see lock(). */ void unlock(); /** Is this OutputSurface currently locked? */ bool isLocked() const { return locked; } /** Returns a pointer to the requested line in the pixel buffer. * Not all implementations support this operation, e.g. in SDLGL * (non FB version) you don't have direct access to a pixel buffer. */ template Pixel* getLinePtrDirect(unsigned y) { assert(isLocked()); return reinterpret_cast(data + y * pitch); } /** For SDLGL-FB-nn, copy frame buffer to OpenGL display. * The default implementation does nothing. */ virtual void flushFrameBuffer(); /** Save the content of this OutputSurface to a PNG file. * @throws MSXException If creating the PNG file fails. */ virtual void saveScreenshot(const std::string& filename) = 0; /** Clear screen (paint it black). */ virtual void clearScreen() = 0; protected: OutputSurface(); void setPosition(int x, int y); void setSDLSurface(SDL_Surface* surface_) { surface = surface_; } void setSDLFormat(const SDL_PixelFormat& format); void setBufferPtr(char* data, unsigned pitch); private: // OutputRectangle unsigned getOutputWidth() const override { return getWidth(); } unsigned getOutputHeight() const override { return getHeight(); } SDL_Surface* surface; SDL_PixelFormat format; char* data; unsigned pitch; int xOffset, yOffset; bool locked; friend class SDLGLOutputSurface; // for setBufferPtr() }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/PNG.cc000066400000000000000000000314111257557151200172670ustar00rootroot00000000000000#include "PNG.hh" #include "SDLSurfacePtr.hh" #include "MSXException.hh" #include "File.hh" #include "StringOp.hh" #include "build-info.hh" #include "Version.hh" #include "vla.hh" #include "cstdiop.hh" #include #include #include #include #include #include #include namespace openmsx { namespace PNG { static void handleError(png_structp png_ptr, png_const_charp error_msg) { auto operation = reinterpret_cast( png_get_error_ptr(png_ptr)); throw MSXException(StringOp::Builder() << "Error while " << operation << " PNG: " << error_msg); } static void handleWarning(png_structp png_ptr, png_const_charp warning_msg) { auto operation = reinterpret_cast( png_get_error_ptr(png_ptr)); std::cerr << "Warning while " << operation << " PNG: " << warning_msg << std::endl; } /* The copyright notice below applies to the original PNG load code, which was imported from SDL_image 1.2.10, file "IMG_png.c", function "IMG_LoadPNG_RW". =============================================================================== File: SDL_png.c Purpose: A PNG loader and saver for the SDL library Revision: Created by: Philippe Lavoie (2 November 1998) lavoie@zeus.genie.uottawa.ca Modified by: Copyright notice: Copyright (C) 1998 Philippe Lavoie This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Comments: The load and save routine are basically the ones you can find in the example.c file from the libpng distribution. Changes: 1999-05-17: Modified to use the new SDL data sources - Sam Lantinga 2009-12-29: Modified for use in openMSX - Maarten ter Huurne and Wouter Vermaelen =============================================================================== */ struct PNGReadHandle { PNGReadHandle() : ptr(nullptr), info(nullptr) { } ~PNGReadHandle() { if (ptr) { png_destroy_read_struct(&ptr, info ? &info : nullptr, nullptr); } } png_structp ptr; png_infop info; }; static void readData(png_structp ctx, png_bytep area, png_size_t size) { auto file = reinterpret_cast(png_get_io_ptr(ctx)); file->read(area, size); } SDLSurfacePtr load(const std::string& filename, bool want32bpp) { File file(filename); try { // Create the PNG loading context structure. PNGReadHandle png; png.ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, const_cast("decoding"), handleError, handleWarning); if (!png.ptr) { throw MSXException("Failed to allocate main struct"); } // Allocate/initialize the memory for image information. png.info = png_create_info_struct(png.ptr); if (!png.info) { throw MSXException("Failed to allocate image info struct"); } // Set up the input control. png_set_read_fn(png.ptr, &file, readData); // Read PNG header info. png_read_info(png.ptr, png.info); png_uint_32 width, height; int bit_depth, color_type, interlace_type; png_get_IHDR(png.ptr, png.info, &width, &height, &bit_depth, &color_type, &interlace_type, nullptr, nullptr); // Tell libpng to strip 16 bit/color files down to 8 bits/color. png_set_strip_16(png.ptr); // Extract multiple pixels with bit depths of 1, 2, and 4 from a single // byte into separate bytes (useful for paletted and grayscale images). png_set_packing(png.ptr); // The following enables: // - transformation of grayscale images of less than 8 to 8 bits // - changes paletted images to RGB // - adds a full alpha channel if there is transparency information in a tRNS chunk png_set_expand(png.ptr); if (want32bpp) { png_set_filler(png.ptr, 0xff, PNG_FILLER_AFTER); } // Try to read the PNG directly in the same format as the video // surface format. The supported formats are // RGBA, BGRA, ARGB, ABGR // When the output surface is 16bpp, still produce PNG in BGRA // format because SDL *seems* to be better optimized for this // format (not documented, but I checked SDL-1.2.15 source code). // if (for some reason) the surface is not available yet, // we just skip this bool bgr(true), swapAlpha(false); SDL_Surface* videoSurface = SDL_GetVideoSurface(); if (videoSurface) { const SDL_PixelFormat& format = *videoSurface->format; if (format.BitsPerPixel < 24) { bgr = true; swapAlpha = false; } else { int r = format.Rshift; int g = format.Gshift; int b = format.Bshift; // Can't trust Ashift in a video surface, but it's safe // to assume Alpha channel is located in the leftover // position. if (r == 0 && g == 8 && b == 16) { // RGBA bgr = false; swapAlpha = false; } else if (r == 16 && g == 8 && b == 0) { // BGRA bgr = true; swapAlpha = false; } else if (r == 8 && g == 16 && b == 24) { // ARGB bgr = false; swapAlpha = true; } else if (r == 24 && g == 16 && b == 8) { // ABGR bgr = true; swapAlpha = true; } else { // Format not directly supported by libpng, // use BGRA and still convert later. // (so, use defaults) } } } if (bgr) png_set_bgr (png.ptr); if (swapAlpha) png_set_swap_alpha(png.ptr); // always convert grayscale to RGB // together with all the above conversions, the resulting image will // be either RGB or RGBA with 8 bits per component. png_set_gray_to_rgb(png.ptr); png_read_update_info(png.ptr, png.info); png_get_IHDR(png.ptr, png.info, &width, &height, &bit_depth, &color_type, &interlace_type, nullptr, nullptr); // Allocate the SDL surface to hold the image. static const unsigned MAX_SIZE = 2048; if (width > MAX_SIZE) { throw MSXException(StringOp::Builder() << "Attempted to create a surface with excessive width: " << width << ", max " << MAX_SIZE); } if (height > MAX_SIZE) { throw MSXException(StringOp::Builder() << "Attempted to create a surface with excessive height: " << height << ", max " << MAX_SIZE); } int bpp = png_get_channels(png.ptr, png.info) * 8; assert(bpp == 24 || bpp == 32); Uint32 redMask, grnMask, bluMask, alpMask; if (OPENMSX_BIGENDIAN) { if (bpp == 32) { if (swapAlpha) { redMask = 0x00FF0000; grnMask = 0x0000FF00; bluMask = 0x000000FF; alpMask = 0xFF000000; } else { redMask = 0xFF000000; grnMask = 0x00FF0000; bluMask = 0x0000FF00; alpMask = 0x000000FF; } } else { redMask = 0x00FF0000; grnMask = 0x0000FF00; bluMask = 0x000000FF; alpMask = 0x00000000; } } else { if (bpp == 32) { if (swapAlpha) { redMask = 0x0000FF00; grnMask = 0x00FF0000; bluMask = 0xFF000000; alpMask = 0x000000FF; } else { redMask = 0x000000FF; grnMask = 0x0000FF00; bluMask = 0x00FF0000; alpMask = 0xFF000000; } } else { redMask = 0x000000FF; grnMask = 0x0000FF00; bluMask = 0x00FF0000; alpMask = 0x00000000; } } if (bgr) std::swap(redMask, bluMask); SDLSurfacePtr surface(width, height, bpp, redMask, grnMask, bluMask, alpMask); // Create the array of pointers to image data. VLA(png_bytep, row_pointers, height); for (png_uint_32 row = 0; row < height; ++row) { row_pointers[row] = reinterpret_cast( surface.getLinePtr(row)); } // Read the entire image in one go. png_read_image(png.ptr, row_pointers); // In some cases it can't read PNG's created by some popular programs // (ACDSEE), we do not want to process comments, so we omit png_read_end //png_read_end(png.ptr, png.info); return surface; } catch (MSXException& e) { throw MSXException(StringOp::Builder() << "Error while loading PNG file \"" << filename << "\": " << e.getMessage()); } } /* PNG save code by Darren Grant sdl@lokigames.com */ /* heavily modified for openMSX by Joost Damad joost@lumatec.be */ struct PNGWriteHandle { PNGWriteHandle() : ptr(nullptr), info(nullptr) { } ~PNGWriteHandle() { if (ptr) { png_destroy_write_struct(&ptr, info ? &info : nullptr); } } png_structp ptr; png_infop info; }; static void writeData(png_structp ctx, png_bytep area, png_size_t size) { auto file = reinterpret_cast(png_get_io_ptr(ctx)); file->write(area, size); } static void flushData(png_structp ctx) { auto file = reinterpret_cast(png_get_io_ptr(ctx)); file->flush(); } static void IMG_SavePNG_RW(int width, int height, const void** row_pointers, const std::string& filename, bool color) { try { File file(filename, File::TRUNCATE); PNGWriteHandle png; png.ptr = png_create_write_struct( PNG_LIBPNG_VER_STRING, const_cast("encoding"), handleError, handleWarning); if (!png.ptr) { throw MSXException("Failed to allocate main struct"); } // Allocate/initialize the image information data. REQUIRED png.info = png_create_info_struct(png.ptr); if (!png.info) { // Couldn't create image information for PNG file throw MSXException("Failed to allocate image info struct"); } // Set up the output control. png_set_write_fn(png.ptr, &file, writeData, flushData); // Mark this image as being generated by openMSX and add creation time. std::string version = Version::full(); png_text text[2]; text[0].compression = PNG_TEXT_COMPRESSION_NONE; text[0].key = const_cast("Software"); text[0].text = const_cast(version.c_str()); text[1].compression = PNG_TEXT_COMPRESSION_NONE; text[1].key = const_cast("Creation Time"); time_t now = time(nullptr); struct tm* tm = localtime(&now); char timeStr[10 + 1 + 8 + 1]; snprintf(timeStr, sizeof(timeStr), "%04d-%02d-%02d %02d:%02d:%02d", 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); text[1].text = timeStr; png_set_text(png.ptr, png.info, text, 2); png_set_IHDR(png.ptr, png.info, width, height, 8, color ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); // Write the file header information. REQUIRED png_write_info(png.ptr, png.info); // Write out the entire image data in one call. png_write_image( png.ptr, reinterpret_cast(const_cast(row_pointers))); png_write_end(png.ptr, png.info); } catch (MSXException& e) { throw MSXException( "Error while writing PNG file \"" + filename + "\": " + e.getMessage()); } } void save(SDL_Surface* surface, const std::string& filename) { SDL_PixelFormat frmt24; frmt24.palette = nullptr; frmt24.BitsPerPixel = 24; frmt24.BytesPerPixel = 3; frmt24.Rmask = OPENMSX_BIGENDIAN ? 0xFF0000 : 0x0000FF; frmt24.Gmask = 0x00FF00; frmt24.Bmask = OPENMSX_BIGENDIAN ? 0x0000FF : 0xFF0000; frmt24.Amask = 0; frmt24.Rshift = 0; frmt24.Gshift = 8; frmt24.Bshift = 16; frmt24.Ashift = 0; frmt24.Rloss = 0; frmt24.Gloss = 0; frmt24.Bloss = 0; frmt24.Aloss = 8; frmt24.colorkey = 0; frmt24.alpha = 0; SDLSurfacePtr surf24(SDL_ConvertSurface(surface, &frmt24, 0)); // Create the array of pointers to image data VLA(const void*, row_pointers, surface->h); for (int i = 0; i < surface->h; ++i) { row_pointers[i] = surf24.getLinePtr(i); } IMG_SavePNG_RW(surface->w, surface->h, row_pointers, filename, true); } void save(unsigned width, unsigned height, const void** rowPointers, const SDL_PixelFormat& format, const std::string& filename) { // this implementation creates 1 extra copy, can be optimized if required SDLSurfacePtr surface( width, height, format.BitsPerPixel, format.Rmask, format.Gmask, format.Bmask, format.Amask); for (unsigned y = 0; y < height; ++y) { memcpy(surface.getLinePtr(y), rowPointers[y], width * format.BytesPerPixel); } save(surface.get(), filename); } void save(unsigned width, unsigned height, const void** rowPointers, const std::string& filename) { IMG_SavePNG_RW(width, height, rowPointers, filename, true); } void saveGrayscale(unsigned width, unsigned height, const void** rowPointers, const std::string& filename) { IMG_SavePNG_RW(width, height, rowPointers, filename, false); } } // namespace PNG } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/PNG.hh000066400000000000000000000021151257557151200173000ustar00rootroot00000000000000#ifndef PNG_HH #define PNG_HH #include "SDLSurfacePtr.hh" #include struct SDL_Surface; struct SDL_PixelFormat; namespace openmsx { /** Utility functions to hide the complexity of saving to a PNG file. */ namespace PNG { /** Load the given PNG file in a SDL_Surface. * This SDL_Surface is either 24bpp or 32bpp, depending on whether the * PNG file had an alpha layer. But it's possible to force a 32bpp * surface. The surface will use RGB(A) or BGR(A) format depending on * the current display format. */ SDLSurfacePtr load(const std::string& filename, bool want32bpp); void save(SDL_Surface* image, const std::string& filename); void save(unsigned width, unsigned height, const void** rowPointers, const SDL_PixelFormat& format, const std::string& filename); void save(unsigned witdh, unsigned height, const void** rowPointers, const std::string& filename); void saveGrayscale(unsigned witdh, unsigned height, const void** rowPointers, const std::string& filename); } // namespace PNG } // namespace openmsx #endif // PNG_HH openMSX-RELEASE_0_12_0/src/video/PixelOperations.hh000066400000000000000000000514411257557151200220070ustar00rootroot00000000000000#ifndef PIXELOPERATIONS_HH #define PIXELOPERATIONS_HH #include "unreachable.hh" #include "build-info.hh" #include namespace openmsx { template class PixelOpBase { public: explicit PixelOpBase(const SDL_PixelFormat& format_) : format(format_) , blendMask(calcBlendMask()) { } const SDL_PixelFormat& getSDLPixelFormat() const { return format; } inline int getRmask() const { return format.Rmask; } inline int getGmask() const { return format.Gmask; } inline int getBmask() const { return format.Bmask; } inline int getAmask() const { return format.Amask; } inline int getRshift() const { return format.Rshift; } inline int getGshift() const { return format.Gshift; } inline int getBshift() const { return format.Bshift; } inline int getAshift() const { return format.Ashift; } inline int getRloss() const { return format.Rloss; } inline int getGloss() const { return format.Gloss; } inline int getBloss() const { return format.Bloss; } inline int getAloss() const { return format.Aloss; } /** Returns a constant that is useful to calculate the average of * two pixel values. See the implementation of blend(p1, p2) for * more details. * For single pixels it's of course better to use the blend(p1, p2) * method directly. This method is typically used as a helper in * older SIMD (MMX/SSE1) routines. */ inline Pixel getBlendMask() const { return blendMask; } /** Return true if it's statically known that the pixelformat has * a 5-6-5 format (not specified wihich component goes where, but * usually it will be BGR). This method is currently used to pick * a faster version for lerp() on dingoo. */ static const bool IS_RGB565 = false; private: inline Pixel calcBlendMask() const { int rBit = ~(getRmask() << 1) & getRmask(); int gBit = ~(getGmask() << 1) & getGmask(); int bBit = ~(getBmask() << 1) & getBmask(); return ~(rBit | gBit | bBit); } const SDL_PixelFormat& format; /** Mask used for blending. * The least significant bit of R,G,B must be 0, * all other bits must be 1. * 0000BBBBGGGGRRRR * --> 1111111011101110 */ const Pixel blendMask; }; // Specialization for 32bpp // No need to store 'blendMask' in a member variable. template<> class PixelOpBase { public: explicit PixelOpBase(const SDL_PixelFormat& format_) : format(format_) {} const SDL_PixelFormat& getSDLPixelFormat() const { return format; } inline int getRmask() const { return format.Rmask; } inline int getGmask() const { return format.Gmask; } inline int getBmask() const { return format.Bmask; } inline int getAmask() const { return format.Amask; } inline int getRshift() const { return format.Rshift; } inline int getGshift() const { return format.Gshift; } inline int getBshift() const { return format.Bshift; } inline int getAshift() const { return format.Ashift; } inline int getRloss() const { return 0; } inline int getGloss() const { return 0; } inline int getBloss() const { return 0; } inline int getAloss() const { return 0; } inline unsigned getBlendMask() const { return 0xFEFEFEFE; } static const bool IS_RGB565 = false; private: const SDL_PixelFormat& format; }; #if PLATFORM_DINGUX // Specialization for dingoo (16bpp) // We know the exact pixel format for this platform. No need for any // members in this class. All values can also be compile-time constant. template<> class PixelOpBase { public: explicit PixelOpBase(const SDL_PixelFormat& /*format*/) {} const SDL_PixelFormat& getSDLPixelFormat() const { static SDL_PixelFormat format; format.palette = nullptr; format.BitsPerPixel = 16; format.BytesPerPixel = 2; format.Rloss = 3; format.Gloss = 2; format.Bloss = 3; format.Aloss = 8; format.Rshift = 0; format.Gshift = 5; format.Bshift = 11; format.Ashift = 0; format.Rmask = 0x001F; format.Gmask = 0x07E0; format.Bmask = 0xF800; format.Amask = 0x0000; return format; } inline int getRmask() const { return 0x001F; } inline int getGmask() const { return 0x07E0; } inline int getBmask() const { return 0xF800; } inline int getAmask() const { return 0x0000; } inline int getRshift() const { return 0; } inline int getGshift() const { return 5; } inline int getBshift() const { return 11; } inline int getAshift() const { return 0; } inline int getRloss() const { return 3; } inline int getGloss() const { return 2; } inline int getBloss() const { return 3; } inline int getAloss() const { return 8; } inline unsigned short getBlendMask() const { return 0xF7DE; } static const bool IS_RGB565 = true; }; #endif template class PixelOperations : public PixelOpBase { public: using PixelOpBase::getSDLPixelFormat; using PixelOpBase::getRmask; using PixelOpBase::getGmask; using PixelOpBase::getBmask; using PixelOpBase::getAmask; using PixelOpBase::getRshift; using PixelOpBase::getGshift; using PixelOpBase::getBshift; using PixelOpBase::getAshift; using PixelOpBase::getRloss; using PixelOpBase::getGloss; using PixelOpBase::getBloss; using PixelOpBase::getAloss; using PixelOpBase::getBlendMask; using PixelOpBase::IS_RGB565; explicit PixelOperations(const SDL_PixelFormat& format); /** Extract RGB componts */ inline unsigned red(Pixel p) const; inline unsigned green(Pixel p) const; inline unsigned blue(Pixel p) const; inline unsigned alpha(Pixel p) const; // alpha is maximum inline bool isFullyOpaque(Pixel p) const; // alpha is minimum inline bool isFullyTransparent(Pixel p) const; /** Same as above, but result is scaled to [0..255] */ inline unsigned red256(Pixel p) const; inline unsigned green256(Pixel p) const; inline unsigned blue256(Pixel p) const; /** Combine RGB components to a pixel */ inline Pixel combine(unsigned r, unsigned g, unsigned b) const; inline Pixel combine256(unsigned r, unsigned g, unsigned b) const; /** Get maximum component value */ inline unsigned getMaxRed() const; inline unsigned getMaxGreen() const; inline unsigned getMaxBlue() const; /** Blend the given colors into a single color. * The special case for blending between two colors with * an equal blend weight has an optimized implementation. */ template inline Pixel blend(Pixel p1, Pixel p2) const; template inline Pixel blend(Pixel p1, Pixel p2, Pixel p3) const; template inline Pixel blend(Pixel p1, Pixel p2, Pixel p3, Pixel p4) const; template inline Pixel blend(Pixel p1, Pixel p2, Pixel p3, Pixel p4, Pixel p5, Pixel p6) const; template inline Pixel blend2(const Pixel* p) const; template inline Pixel blend3(const Pixel* p) const; template inline Pixel blend4(const Pixel* p) const; template inline Pixel blend6(const Pixel* p) const; /** Perform a component wise multiplication of a pixel with an 8-bit * fractional value: * result = (pixel * x) / 256 [component wise] * 'x' must be in range [0..256]. * For x=0 the result is 0. * For x=255 the result in the original value. * Note: ATM only implemented for 32bpp. */ static inline Pixel multiply(Pixel p, unsigned x); /** Perform linear interpolation between two pixels. * This calculates component-wise: * (c1 * (256 - x) + c2 * x) / 256 * with c1, c2 the R,B,G,A components of the pixel. * 'x' must be in range [0..256]. * For x=0 the result is p1. * For x=256 the result is p2. */ inline Pixel lerp(Pixel p1, Pixel p2, unsigned x) const; /** Perform alpha blending of two pixels. * Pixel p1 contains the alpha value. For maximal alpha p1 is * returned, for minimal alpha p2. */ inline Pixel alphaBlend(Pixel p1, Pixel p2) const; private: inline Pixel avgDown(Pixel p1, Pixel p2) const; inline Pixel avgUp (Pixel p1, Pixel p2) const; }; template PixelOperations::PixelOperations(const SDL_PixelFormat& format_) : PixelOpBase(format_) { } template inline unsigned PixelOperations::red(Pixel p) const { if (sizeof(Pixel) == 4) { return (p >> getRshift()) & 0xFF; } else { return (p & getRmask()) >> getRshift(); } } template inline unsigned PixelOperations::green(Pixel p) const { if (sizeof(Pixel) == 4) { return (p >> getGshift()) & 0xFF; } else { return (p & getGmask()) >> getGshift(); } } template inline unsigned PixelOperations::blue(Pixel p) const { if (sizeof(Pixel) == 4) { return (p >> getBshift()) & 0xFF; } else { return (p & getBmask()) >> getBshift(); } } template inline unsigned PixelOperations::alpha(Pixel p) const { if (sizeof(Pixel) == 4) { return (p >> getAshift()) & 0xFF; } else { UNREACHABLE; return 0; //return (p & getAmask()) >> getAshift(); } } template inline bool PixelOperations::isFullyOpaque(Pixel p) const { if (sizeof(Pixel) == 4) { return alpha(p) == 255; } else { return p != 0x0001; } } template inline bool PixelOperations::isFullyTransparent(Pixel p) const { if (sizeof(Pixel) == 4) { return alpha(p) == 0; } else { return p == 0x0001; } } template inline unsigned PixelOperations::red256(Pixel p) const { if (sizeof(Pixel) == 4) { return (p >> getRshift()) & 0xFF; } else { return ((p >> getRshift()) << getRloss()) & 0xFF; } } template inline unsigned PixelOperations::green256(Pixel p) const { if (sizeof(Pixel) == 4) { return (p >> getGshift()) & 0xFF; } else { return ((p >> getGshift()) << getGloss()) & 0xFF; } } template inline unsigned PixelOperations::blue256(Pixel p) const { if (sizeof(Pixel) == 4) { return (p >> getBshift()) & 0xFF; } else { return ((p >> getBshift()) << getBloss()) & 0xFF; } } template inline Pixel PixelOperations::combine( unsigned r, unsigned g, unsigned b) const { return Pixel((r << getRshift()) | (g << getGshift()) | (b << getBshift())); } template inline Pixel PixelOperations::combine256( unsigned r, unsigned g, unsigned b) const { if (sizeof(Pixel) == 4) { return Pixel((r << getRshift()) | (g << getGshift()) | (b << getBshift())); } else { return Pixel(((r >> getRloss()) << getRshift()) | ((g >> getGloss()) << getGshift()) | ((b >> getBloss()) << getBshift())); } } template inline unsigned PixelOperations::getMaxRed() const { if (sizeof(Pixel) == 4) { return 255; } else { return 255 >> getRloss(); } } template inline unsigned PixelOperations::getMaxGreen() const { if (sizeof(Pixel) == 4) { return 255; } else { return 255 >> getGloss(); } } template inline unsigned PixelOperations::getMaxBlue() const { if (sizeof(Pixel) == 4) { return 255; } else { return 255 >> getBloss(); } } template struct IsPow2 { static const bool result = ((N & 1) == 0) && IsPow2::result; static const unsigned log2 = 1 + IsPow2::log2; }; template<> struct IsPow2<1> { static const bool result = true; static const unsigned log2 = 0; }; template inline Pixel PixelOperations::avgDown(Pixel p1, Pixel p2) const { // Average can be calculated as: // floor((x + y) / 2.0) = (x & y) + (x ^ y) / 2 // see "Average of Integers" on http://aggregate.org/MAGIC/ return (p1 & p2) + (((p1 ^ p2) & getBlendMask()) >> 1); } template inline Pixel PixelOperations::avgUp(Pixel p1, Pixel p2) const { // Similar to above, but rounds up // ceil((x + y) / 2.0) = (x | y) - (x ^ y) / 2 return (p1 | p2) - (((p1 ^ p2) & getBlendMask()) >> 1); } template template inline Pixel PixelOperations::blend(Pixel p1, Pixel p2) const { static const unsigned total = w1 + w2; if (w1 == 0) { return p2; } else if (w1 > w2) { return blend(p2, p1); } else if (w1 == w2) { // <1,1> return avgDown(p1, p2); } else if ((3 * w1) == w2) { // <1,3> Pixel p11 = avgDown(p1, p2); return avgUp(p11, p2); } else if ((7 * w1) == w2) { // <1,7> Pixel p11 = avgDown(p1, p2); Pixel p13 = avgDown(p11, p2); return avgUp(p13, p2); } else if ((5 * w1) == (3 * w2)) { // <3,5> mix rounding up/down to get a more accurate result Pixel p11 = avgUp (p1, p2); Pixel p13 = avgDown(p11, p2); return avgDown(p11, p13); } else if (!IsPow2::result) { // approximate with weights that sum to 256 (or 64) // e.g. approximate <1,2> as <85,171> (or <21,43>) // ww1 = round(256 * w1 / total) ww2 = 256 - ww1 static const unsigned newTotal = IS_RGB565 ? 64 : 256; static const unsigned ww1 = (2 * w1 * newTotal + total) / (2 * total); static const unsigned ww2 = 256 - ww1; return blend(p1, p2); } else if (sizeof(Pixel) == 4) { unsigned l2 = IsPow2::log2; unsigned c1 = (((p1 & 0x00FF00FF) * w1 + (p2 & 0x00FF00FF) * w2 ) >> l2) & 0x00FF00FF; unsigned c2 = (((p1 & 0xFF00FF00) >> l2) * w1 + ((p2 & 0xFF00FF00) >> l2) * w2 ) & 0xFF00FF00; return c1 | c2; } else if (IS_RGB565) { if (total > 64) { // reduce to maximum 6-bit // note: DIV64 only exists to work around a // division by zero in dead code static const unsigned DIV64 = (total > 64) ? 64 : 1; static const unsigned factor = total / DIV64; static const unsigned round = factor / 2; static const unsigned ww1 = (w1 + round) / factor; static const unsigned ww2 = 64 - ww1; return blend(p1, p2); } else { unsigned l2 = IsPow2::log2; unsigned c1 = (((unsigned(p1) & 0xF81F) * w1) + ((unsigned(p2) & 0xF81F) * w2)) & (0xF81F << l2); unsigned c2 = (((unsigned(p1) & 0x07E0) * w1) + ((unsigned(p2) & 0x07E0) * w2)) & (0x07E0 << l2); return (c1 | c2) >> l2; } } else { // generic version unsigned r = (red (p1) * w1 + red (p2) * w2) / total; unsigned g = (green(p1) * w1 + green(p2) * w2) / total; unsigned b = (blue (p1) * w1 + blue (p2) * w2) / total; return combine(r, g, b); } } template template inline Pixel PixelOperations::blend(Pixel p1, Pixel p2, Pixel p3) const { static const unsigned total = w1 + w2 + w3; if ((sizeof(Pixel) == 4) && IsPow2::result) { unsigned l2 = IsPow2::log2; unsigned c1 = (((p1 & 0x00FF00FF) * w1 + (p2 & 0x00FF00FF) * w2 + (p3 & 0x00FF00FF) * w3) >> l2) & 0x00FF00FF; unsigned c2 = (((p1 & 0xFF00FF00) >> l2) * w1 + ((p2 & 0xFF00FF00) >> l2) * w2 + ((p3 & 0xFF00FF00) >> l2) * w3) & 0xFF00FF00; return c1 | c2; } else { unsigned r = (red (p1) * w1 + red (p2) * w2 + red (p3) * w3) / total; unsigned g = (green(p1) * w1 + green(p2) * w2 + green(p3) * w3) / total; unsigned b = (blue (p1) * w1 + blue (p2) * w2 + blue (p3) * w3) / total; return combine(r, g, b); } } template template inline Pixel PixelOperations::blend( Pixel p1, Pixel p2, Pixel p3, Pixel p4) const { static const unsigned total = w1 + w2 + w3 + w4; if ((sizeof(Pixel) == 4) && IsPow2::result) { unsigned l2 = IsPow2::log2; unsigned c1 = (((p1 & 0x00FF00FF) * w1 + (p2 & 0x00FF00FF) * w2 + (p3 & 0x00FF00FF) * w3 + (p4 & 0x00FF00FF) * w4) >> l2) & 0x00FF00FF; unsigned c2 = (((p1 & 0xFF00FF00) >> l2) * w1 + ((p2 & 0xFF00FF00) >> l2) * w2 + ((p3 & 0xFF00FF00) >> l2) * w3 + ((p4 & 0xFF00FF00) >> l2) * w4) & 0xFF00FF00; return c1 | c2; } else { unsigned r = (red (p1) * w1 + red (p2) * w2 + red (p3) * w3 + red (p4) * w4) / total; unsigned g = (green(p1) * w1 + green(p2) * w2 + green(p3) * w3 + green(p4) * w4) / total; unsigned b = (blue (p1) * w1 + blue (p2) * w2 + blue (p3) * w3 + blue (p4) * w4) / total; return combine(r, g, b); } } template template inline Pixel PixelOperations::blend( Pixel p1, Pixel p2, Pixel p3, Pixel p4, Pixel p5, Pixel p6) const { static const unsigned total = w1 + w2 + w3 + w4 + w5 + w6; if ((sizeof(Pixel) == 4) && IsPow2::result) { unsigned l2 = IsPow2::log2; unsigned c1 = (((p1 & 0x00FF00FF) * w1 + (p2 & 0x00FF00FF) * w2 + (p3 & 0x00FF00FF) * w3 + (p4 & 0x00FF00FF) * w4 + (p5 & 0x00FF00FF) * w5 + (p6 & 0x00FF00FF) * w6) >> l2) & 0x00FF00FF; unsigned c2 = (((p1 & 0xFF00FF00) >> l2) * w1 + ((p2 & 0xFF00FF00) >> l2) * w2 + ((p3 & 0xFF00FF00) >> l2) * w3 + ((p4 & 0xFF00FF00) >> l2) * w4 + ((p5 & 0xFF00FF00) >> l2) * w5 + ((p6 & 0xFF00FF00) >> l2) * w6) & 0xFF00FF00; return c1 | c2; } else { unsigned r = (red (p1) * w1 + red (p2) * w2 + red (p3) * w3 + red (p4) * w4 + red (p5) * w5 + red (p6) * w6) / total; unsigned g = (green(p1) * w1 + green(p2) * w2 + green(p3) * w3 + green(p4) * w4 + green(p5) * w5 + green(p6) * w6) / total; unsigned b = (blue (p1) * w1 + blue (p2) * w2 + blue (p3) * w3 + blue (p4) * w4 + blue (p5) * w5 + blue (p6) * w6) / total; return combine(r, g, b); } } template template inline Pixel PixelOperations::blend2(const Pixel* p) const { return blend(p[0], p[1]); } template template inline Pixel PixelOperations::blend3(const Pixel* p) const { return blend(p[0], p[1], p[2]); } template template inline Pixel PixelOperations::blend4(const Pixel* p) const { return blend(p[0], p[1], p[2], p[3]); } template template inline Pixel PixelOperations::blend6(const Pixel* p) const { return blend(p[0], p[1], p[2], p[3], p[4], p[5]); } template inline Pixel PixelOperations::multiply(Pixel p, unsigned x) { if (sizeof(Pixel) == 4) { return ((((p & 0x00FF00FF) * x) & 0xFF00FF00) >> 8) | ((((p >> 8) & 0x00FF00FF) * x) & 0xFF00FF00); } else { UNREACHABLE; return 0; } } template inline Pixel PixelOperations::lerp(Pixel p1, Pixel p2, unsigned x) const { if (sizeof(Pixel) == 4) { // 32 bpp unsigned rb1 = (p1 >> 0) & 0x00FF00FF; unsigned ag1 = (p1 >> 8) & 0x00FF00FF; unsigned rb2 = (p2 >> 0) & 0x00FF00FF; unsigned ag2 = (p2 >> 8) & 0x00FF00FF; // Note: the subtraction for the lower component can 'borrow' from // the higher component. Though in the full calculation this error // magically cancels out. unsigned trb = ((rb2 - rb1) * x) >> 8; unsigned tag = ((ag2 - ag1) * x) >> 0; unsigned rb = ((trb + rb1) << 0) & 0x00FF00FF; unsigned ag = (tag + (ag1 << 8)) & 0xFF00FF00; return rb | ag; } else if (IS_RGB565) { unsigned rb1 = p1 & 0xF81F; unsigned rb2 = p2 & 0xF81F; unsigned g1 = p1 & 0x07E0; unsigned g2 = p2 & 0x07E0; x >>= 2; unsigned trb = ((rb2 - rb1) * x) >> 6; unsigned tg = ((g2 - g1 ) * x) >> 6; unsigned rb = (trb + rb1) & 0xF81F; unsigned g = (tg + g1 ) & 0x07E0; return rb | g; } else { int r1 = red(p1), r2 = red(p2); int g1 = green(p1), g2 = green(p2); int b1 = blue(p1), b2 = blue(p2); // note: '/ 256' is not the same as '>> 8' for signed numbers int r = ((r2 - r1) * x) / 256 + r1; int g = ((g2 - g1) * x) / 256 + g1; int b = ((b2 - b1) * x) / 256 + b1; return combine(r, g, b); } } template inline Pixel PixelOperations::alphaBlend(Pixel p1, Pixel p2) const { if (sizeof(Pixel) == 2) { // TODO keep magic value in sync with OutputSurface::getKeyColor() return (p1 == 0x0001) ? p2 : p1; } else { unsigned a = alpha(p1); // Note: 'a' is [0..255], while lerp() expects [0..256]. // We ignore this small error. return lerp(p2, p1, a); } } } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/PixelRenderer.cc000066400000000000000000000420401257557151200214130ustar00rootroot00000000000000/* TODO: - Implement blinking (of page mask) in bitmap modes. */ #include "PixelRenderer.hh" #include "Rasterizer.hh" #include "PostProcessor.hh" #include "Display.hh" #include "VideoSystem.hh" #include "RenderSettings.hh" #include "VideoSourceSetting.hh" #include "IntegerSetting.hh" #include "VDP.hh" #include "VDPVRAM.hh" #include "SpriteChecker.hh" #include "EventDistributor.hh" #include "FinishFrameEvent.hh" #include "RealTime.hh" #include "MSXMotherBoard.hh" #include "Reactor.hh" #include "Timer.hh" #include "unreachable.hh" #include #include namespace openmsx { void PixelRenderer::draw( int startX, int startY, int endX, int endY, DrawType drawType, bool atEnd) { if (drawType == DRAW_BORDER) { rasterizer->drawBorder(startX, startY, endX, endY); } else { assert(drawType == DRAW_DISPLAY); // Calculate display coordinates. int zero = vdp.getLineZero(); int displayX = (startX - vdp.getLeftSprites()) / 2; int displayY = startY - zero; if (!vdp.getDisplayMode().isTextMode()) { displayY += vdp.getVerticalScroll(); } else { // this is not what the real VDP does, but it is good // enough for "Boring scroll" demo part of "Relax" displayY = (displayY & 7) | (textModeCounter * 8); if (atEnd && (drawType == DRAW_DISPLAY)) { int low = std::max(0, (startY - zero)) / 8; int high = std::max(0, (endY - zero)) / 8; textModeCounter += (high - low); } } displayY &= 255; // Page wrap. int displayWidth = (endX - (startX & ~1)) / 2; int displayHeight = endY - startY; assert(0 <= displayX); assert(displayX + displayWidth <= 512); rasterizer->drawDisplay( startX, startY, displayX - vdp.getHorizontalScrollLow() * 2, displayY, displayWidth, displayHeight ); if (vdp.spritesEnabled() && !renderSettings.getDisableSprites()) { rasterizer->drawSprites( startX, startY, displayX / 2, displayY, (displayWidth + 1) / 2, displayHeight); } } } void PixelRenderer::subdivide( int startX, int startY, int endX, int endY, int clipL, int clipR, DrawType drawType ) { // Partial first line. if (startX > clipL) { bool atEnd = (startY != endY) || (endX >= clipR); if (startX < clipR) { draw(startX, startY, (atEnd ? clipR : endX), startY + 1, drawType, atEnd); } if (startY == endY) return; startY++; } // Partial last line. bool drawLast = false; if (endX >= clipR) { endY++; } else if (endX > clipL) { drawLast = true; } // Full middle lines. if (startY < endY) { draw(clipL, startY, clipR, endY, drawType, true); } // Actually draw last line if necessary. // The point of keeping top-to-bottom draw order is that it increases // the locality of memory references, which generally improves cache // hit rates. if (drawLast) draw(clipL, endY, endX, endY + 1, drawType, false); } PixelRenderer::PixelRenderer(VDP& vdp_, Display& display) : vdp(vdp_), vram(vdp.getVRAM()) , eventDistributor(vdp.getReactor().getEventDistributor()) , realTime(vdp.getMotherBoard().getRealTime()) , renderSettings(display.getRenderSettings()) , videoSourceSetting(vdp.getMotherBoard().getVideoSource()) , spriteChecker(vdp.getSpriteChecker()) , rasterizer(display.getVideoSystem().createRasterizer(vdp)) { // In case of loadstate we can't yet query any state from the VDP // (because that object is not yet fully deserialized). But // VDP::serialize() will call Renderer::reInit() again when it is // safe to query. reInit(); finishFrameDuration = 0; frameSkipCounter = 999; // force drawing of frame prevRenderFrame = false; renderSettings.getMaxFrameSkipSetting().attach(*this); renderSettings.getMinFrameSkipSetting().attach(*this); } PixelRenderer::~PixelRenderer() { renderSettings.getMinFrameSkipSetting().detach(*this); renderSettings.getMaxFrameSkipSetting().detach(*this); } PostProcessor* PixelRenderer::getPostProcessor() const { return rasterizer->getPostProcessor(); } void PixelRenderer::reInit() { // Don't draw before frameStart() is called. // This for example can happen after a loadstate or after switching // renderer in the middle of a frame. renderFrame = false; rasterizer->reset(); displayEnabled = vdp.isDisplayEnabled(); } void PixelRenderer::updateDisplayEnabled(bool enabled, EmuTime::param time) { sync(time, true); displayEnabled = enabled; } void PixelRenderer::frameStart(EmuTime::param time) { if (!rasterizer->isActive()) { frameSkipCounter = 999; renderFrame = false; prevRenderFrame = false; return; } prevRenderFrame = renderFrame; if (vdp.isInterlaced() && renderSettings.getDeinterlace() && vdp.getEvenOdd() && vdp.isEvenOddEnabled()) { // deinterlaced odd frame, do same as even frame } else { if (frameSkipCounter < renderSettings.getMinFrameSkip()) { ++frameSkipCounter; renderFrame = false; } else if (frameSkipCounter >= renderSettings.getMaxFrameSkip()) { frameSkipCounter = 0; renderFrame = true; } else { ++frameSkipCounter; if (rasterizer->isRecording()) { renderFrame = true; } else { renderFrame = realTime.timeLeft( unsigned(finishFrameDuration), time); } if (renderFrame) { frameSkipCounter = 0; } } } if (!renderFrame) return; rasterizer->frameStart(time); accuracy = renderSettings.getAccuracy(); nextX = 0; nextY = 0; // This is not what the real VDP does, but it is good enough // for the "Boring scroll" demo part of ANMA's "Relax" demo. textModeCounter = 0; } void PixelRenderer::frameEnd(EmuTime::param time) { bool skipEvent = !renderFrame; if (renderFrame) { // Render changes from this last frame. sync(time, true); // Let underlying graphics system finish rendering this frame. auto time1 = Timer::getTime(); rasterizer->frameEnd(); auto time2 = Timer::getTime(); auto current = time2 - time1; const float ALPHA = 0.2f; finishFrameDuration = finishFrameDuration * (1 - ALPHA) + current * ALPHA; if (vdp.isInterlaced() && vdp.isEvenOddEnabled() && renderSettings.getDeinterlace() && !prevRenderFrame) { // dont send event in deinterlace mode when // previous frame was not rendered skipEvent = true; } } eventDistributor.distributeEvent( std::make_shared( rasterizer->getPostProcessor()->getVideoSource(), videoSourceSetting.getSource(), skipEvent)); } void PixelRenderer::updateHorizontalScrollLow( byte scroll, EmuTime::param time) { if (displayEnabled) sync(time); rasterizer->setHorizontalScrollLow(scroll); } void PixelRenderer::updateHorizontalScrollHigh( byte /*scroll*/, EmuTime::param time) { if (displayEnabled) sync(time); } void PixelRenderer::updateBorderMask( bool masked, EmuTime::param time) { if (displayEnabled) sync(time); rasterizer->setBorderMask(masked); } void PixelRenderer::updateMultiPage( bool /*multiPage*/, EmuTime::param time) { if (displayEnabled) sync(time); } void PixelRenderer::updateTransparency( bool enabled, EmuTime::param time) { if (displayEnabled) sync(time); rasterizer->setTransparency(enabled); } void PixelRenderer::updateSuperimposing( const RawFrame* videoSource, EmuTime::param time) { if (displayEnabled) sync(time); rasterizer->setSuperimposeVideoFrame(videoSource); } void PixelRenderer::updateForegroundColor( int /*color*/, EmuTime::param time) { if (displayEnabled) sync(time); } void PixelRenderer::updateBackgroundColor( int color, EmuTime::param time) { sync(time); rasterizer->setBackgroundColor(color); } void PixelRenderer::updateBlinkForegroundColor( int /*color*/, EmuTime::param time) { if (displayEnabled) sync(time); } void PixelRenderer::updateBlinkBackgroundColor( int /*color*/, EmuTime::param time) { if (displayEnabled) sync(time); } void PixelRenderer::updateBlinkState( bool /*enabled*/, EmuTime::param /*time*/) { // TODO: When the sync call is enabled, the screen flashes on // every call to this method. // I don't know why exactly, but it's probably related to // being called at frame start. //sync(time); } void PixelRenderer::updatePalette( int index, int grb, EmuTime::param time) { if (displayEnabled) { sync(time); } else { // Only sync if border color changed. DisplayMode mode = vdp.getDisplayMode(); if (mode.getBase() == DisplayMode::GRAPHIC5) { int bgColor = vdp.getBackgroundColor(); if (index == (bgColor & 3) || (index == (bgColor >> 2))) { sync(time); } } else if (mode.getByte() != DisplayMode::GRAPHIC7) { if (index == vdp.getBackgroundColor()) { sync(time); } } } rasterizer->setPalette(index, grb); } void PixelRenderer::updateVerticalScroll( int /*scroll*/, EmuTime::param time) { if (displayEnabled) sync(time); } void PixelRenderer::updateHorizontalAdjust( int adjust, EmuTime::param time) { if (displayEnabled) sync(time); rasterizer->setHorizontalAdjust(adjust); } void PixelRenderer::updateDisplayMode( DisplayMode mode, EmuTime::param time) { // Sync if in display area or if border drawing process changes. DisplayMode oldMode = vdp.getDisplayMode(); if (displayEnabled || oldMode.getByte() == DisplayMode::GRAPHIC5 || oldMode.getByte() == DisplayMode::GRAPHIC7 || mode.getByte() == DisplayMode::GRAPHIC5 || mode.getByte() == DisplayMode::GRAPHIC7) { sync(time, true); } rasterizer->setDisplayMode(mode); } void PixelRenderer::updateNameBase( int /*addr*/, EmuTime::param time) { if (displayEnabled) sync(time); } void PixelRenderer::updatePatternBase( int /*addr*/, EmuTime::param time) { if (displayEnabled) sync(time); } void PixelRenderer::updateColorBase( int /*addr*/, EmuTime::param time) { if (displayEnabled) sync(time); } void PixelRenderer::updateSpritesEnabled( bool /*enabled*/, EmuTime::param time ) { if (displayEnabled) sync(time); } static inline bool overlap( int displayY0, // start of display region, inclusive int displayY1, // end of display region, exclusive int vramLine0, // start of VRAM region, inclusive int vramLine1 // end of VRAM region, exclusive // Note: Display region can wrap around: 256 -> 0. // VRAM region cannot wrap around. ) { if (displayY0 <= displayY1) { if (vramLine1 > displayY0) { if (vramLine0 <= displayY1) return true; } } else { if (vramLine1 > displayY0) return true; if (vramLine0 <= displayY1) return true; } return false; } inline bool PixelRenderer::checkSync(int offset, EmuTime::param time) { // TODO: Because range is entire VRAM, offset == address. // If display is disabled, VRAM changes will not affect the // renderer output, therefore sync is not necessary. // TODO: Have bitmapVisibleWindow disabled in this case. if (!displayEnabled) return false; //if (frameSkipCounter != 0) return false; // TODO if (accuracy == RenderSettings::ACC_SCREEN) return false; // Calculate what display lines are scanned between current // renderer time and update-to time. // Note: displayY1 is inclusive. int deltaY = vdp.getVerticalScroll() - vdp.getLineZero(); int limitY = vdp.getTicksThisFrame(time) / VDP::TICKS_PER_LINE; int displayY0 = (nextY + deltaY) & 255; int displayY1 = (limitY + deltaY) & 255; switch(vdp.getDisplayMode().getBase()) { case DisplayMode::GRAPHIC2: case DisplayMode::GRAPHIC3: if (vram.colorTable.isInside(offset)) { int vramQuarter = (offset & 0x1800) >> 11; int mask = (vram.colorTable.getMask() & 0x1800) >> 11; for (int i = 0; i < 4; i++) { if ( (i & mask) == vramQuarter && overlap(displayY0, displayY1, i * 64, (i + 1) * 64) ) { /*fprintf(stderr, "color table: %05X %04X - quarter %d\n", offset, offset & 0x1FFF, i );*/ return true; } } } if (vram.patternTable.isInside(offset)) { int vramQuarter = (offset & 0x1800) >> 11; int mask = (vram.patternTable.getMask() & 0x1800) >> 11; for (int i = 0; i < 4; i++) { if ( (i & mask) == vramQuarter && overlap(displayY0, displayY1, i * 64, (i + 1) * 64) ) { /*fprintf(stderr, "pattern table: %05X %04X - quarter %d\n", offset, offset & 0x1FFF, i );*/ return true; } } } if (vram.nameTable.isInside(offset)) { int vramLine = ((offset & 0x3FF) / 32) * 8; if (overlap(displayY0, displayY1, vramLine, vramLine + 8)) { /*fprintf(stderr, "name table: %05X %03X - line %d\n", offset, offset & 0x3FF, vramLine );*/ return true; } } return false; case DisplayMode::GRAPHIC4: case DisplayMode::GRAPHIC5: { // Is the address inside the visual page(s)? // TODO: Also look at which lines are touched inside pages. int visiblePage = vram.nameTable.getMask() & (0x10000 | (vdp.getEvenOddMask() << 7)); if (vdp.isMultiPageScrolling()) { return (offset & 0x18000) == visiblePage || (offset & 0x18000) == (visiblePage & 0x10000); } else { return (offset & 0x18000) == visiblePage; } } case DisplayMode::GRAPHIC6: case DisplayMode::GRAPHIC7: return true; // TODO: Implement better detection. default: // Range unknown; assume full range. return vram.nameTable.isInside(offset) || vram.colorTable.isInside(offset) || vram.patternTable.isInside(offset); } } void PixelRenderer::updateVRAM(unsigned offset, EmuTime::param time) { // Note: No need to sync if display is disabled, because then the // output does not depend on VRAM (only on background color). if (renderFrame && displayEnabled && checkSync(offset, time)) { //fprintf(stderr, "vram sync @ line %d\n", // vdp.getTicksThisFrame(time) / VDP::TICKS_PER_LINE); renderUntil(time); } } void PixelRenderer::updateWindow(bool /*enabled*/, EmuTime::param /*time*/) { // The bitmapVisibleWindow has moved to a different area. // This update is redundant: Renderer will be notified in another way // as well (updateDisplayEnabled or updateNameBase, for example). // TODO: Can this be used as the main update method instead? } void PixelRenderer::sync(EmuTime::param time, bool force) { if (!renderFrame) return; // Synchronisation is done in two phases: // 1. update VRAM // 2. update other subsystems // Note that as part of step 1, type 2 updates can be triggered. // Executing step 2 takes care of the subsystem changes that occur // after the last VRAM update. // This scheme makes sure type 2 routines such as renderUntil and // checkUntil are not re-entered, which was causing major pain in // the past. // TODO: I wonder if it's possible to enforce this synchronisation // scheme at a higher level. Probably. But how... //if ((frameSkipCounter == 0) && TODO if (accuracy != RenderSettings::ACC_SCREEN || force) { vram.sync(time); renderUntil(time); } } void PixelRenderer::renderUntil(EmuTime::param time) { // Translate from time to pixel position. int limitTicks = vdp.getTicksThisFrame(time); assert(limitTicks <= vdp.getTicksPerFrame()); int limitX, limitY; switch (accuracy) { case RenderSettings::ACC_PIXEL: { limitX = limitTicks % VDP::TICKS_PER_LINE; limitY = limitTicks / VDP::TICKS_PER_LINE; break; } case RenderSettings::ACC_LINE: case RenderSettings::ACC_SCREEN: { // Note: I'm not sure the rounding point is optimal. // It used to be based on the left margin, but that doesn't work // because the margin can change which leads to a line being // rendered even though the time doesn't advance. limitX = 0; limitY = (limitTicks + VDP::TICKS_PER_LINE - 400) / VDP::TICKS_PER_LINE; break; } default: UNREACHABLE; limitX = limitY = 0; // avoid warning } // Stop here if there is nothing to render. // This ensures that no pixels are rendered in a series of updates that // happen at exactly the same time; the VDP subsystem states may be // inconsistent until all updates are performed. // Also it is a small performance optimisation. if (limitX == nextX && limitY == nextY) return; if (displayEnabled) { if (vdp.spritesEnabled()) { // Update sprite checking, so that rasterizer can call getSprites. spriteChecker.checkUntil(time); } // Calculate start and end of borders in ticks since start of line. // The 0..7 extra horizontal scroll low pixels should be drawn in // border color. These will be drawn together with the border, // but sprites above these pixels are clipped at the actual border // rather than the end of the border colored area. // TODO: Move these calculations and getDisplayLeft() to VDP. int borderL = vdp.getLeftBorder(); int displayL = vdp.isBorderMasked() ? borderL : vdp.getLeftBackground(); int borderR = vdp.getRightBorder(); // It's important that right border is drawn last (after left // border and display area). See comment in SDLRasterizer::drawBorder(). // Left border. subdivide(nextX, nextY, limitX, limitY, 0, displayL, DRAW_BORDER); // Display area. subdivide(nextX, nextY, limitX, limitY, displayL, borderR, DRAW_DISPLAY); // Right border. subdivide(nextX, nextY, limitX, limitY, borderR, VDP::TICKS_PER_LINE, DRAW_BORDER); } else { subdivide(nextX, nextY, limitX, limitY, 0, VDP::TICKS_PER_LINE, DRAW_BORDER); } nextX = limitX; nextY = limitY; } void PixelRenderer::update(const Setting& setting) { if (&setting == &renderSettings.getMinFrameSkipSetting() || &setting == &renderSettings.getMaxFrameSkipSetting()) { // Force drawing of frame. frameSkipCounter = 999; } else { UNREACHABLE; } } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/PixelRenderer.hh000066400000000000000000000117361257557151200214350ustar00rootroot00000000000000#ifndef PIXELRENDERER_HH #define PIXELRENDERER_HH #include "Renderer.hh" #include "Observer.hh" #include "RenderSettings.hh" #include "openmsx.hh" #include "noncopyable.hh" #include namespace openmsx { class EventDistributor; class RealTime; class Display; class Rasterizer; class VDP; class VDPVRAM; class SpriteChecker; class DisplayMode; class Setting; class VideoSourceSetting; /** Generic implementation of a pixel-based Renderer. * Uses a Rasterizer to plot actual pixels for a specific video system. */ class PixelRenderer final : public Renderer, private Observer , private noncopyable { public: PixelRenderer(VDP& vdp, Display& display); ~PixelRenderer(); // Renderer interface: PostProcessor* getPostProcessor() const override; void reInit() override; void frameStart(EmuTime::param time) override; void frameEnd(EmuTime::param time) override; void updateHorizontalScrollLow(byte scroll, EmuTime::param time) override; void updateHorizontalScrollHigh(byte scroll, EmuTime::param time) override; void updateBorderMask(bool masked, EmuTime::param time) override; void updateMultiPage(bool multiPage, EmuTime::param time) override; void updateTransparency(bool enabled, EmuTime::param time) override; void updateSuperimposing(const RawFrame* videoSource, EmuTime::param time) override; void updateForegroundColor(int color, EmuTime::param time) override; void updateBackgroundColor(int color, EmuTime::param time) override; void updateBlinkForegroundColor(int color, EmuTime::param time) override; void updateBlinkBackgroundColor(int color, EmuTime::param time) override; void updateBlinkState(bool enabled, EmuTime::param time) override; void updatePalette(int index, int grb, EmuTime::param time) override; void updateVerticalScroll(int scroll, EmuTime::param time) override; void updateHorizontalAdjust(int adjust, EmuTime::param time) override; void updateDisplayEnabled(bool enabled, EmuTime::param time) override; void updateDisplayMode(DisplayMode mode, EmuTime::param time) override; void updateNameBase(int addr, EmuTime::param time) override; void updatePatternBase(int addr, EmuTime::param time) override; void updateColorBase(int addr, EmuTime::param time) override; void updateSpritesEnabled(bool enabled, EmuTime::param time) override; void updateVRAM(unsigned offset, EmuTime::param time) override; void updateWindow(bool enabled, EmuTime::param time) override; private: /** Indicates whether the area to be drawn is border or display. */ enum DrawType { DRAW_BORDER, DRAW_DISPLAY }; // Observer interface: void update(const Setting& setting) override; /** Call the right draw method in the subclass, * depending on passed drawType. */ void draw( int startX, int startY, int endX, int endY, DrawType drawType, bool atEnd); /** Subdivide an area specified by two scan positions into a series of * rectangles. * Clips the rectangles to { (x,y) | clipL <= x < clipR }. * @param drawType * If DRAW_BORDER, draw rectangles using drawBorder; * if DRAW_DISPLAY, draw rectangles using drawDisplay. */ void subdivide( int startX, int startY, int endX, int endY, int clipL, int clipR, DrawType drawType ); inline bool checkSync(int offset, EmuTime::param time); /** Update renderer state to specified moment in time. * @param time Moment in emulated time to update to. * @param force When screen accuracy is used, * rendering is only performed if this parameter is true. */ void sync(EmuTime::param time, bool force = false); /** Render lines until specified moment in time. * Unlike sync(), this method does not sync with VDPVRAM. * The VRAM should be to be up to date and remain unchanged * from the current time to the specified time. * @param time Moment in emulated time to render lines until. */ void renderUntil(EmuTime::param time); /** The VDP of which the video output is being rendered. */ VDP& vdp; /** The VRAM whose contents are rendered. */ VDPVRAM& vram; EventDistributor& eventDistributor; RealTime& realTime; RenderSettings& renderSettings; VideoSourceSetting& videoSourceSetting; /** The sprite checker whose sprites are rendered. */ SpriteChecker& spriteChecker; const std::unique_ptr rasterizer; float finishFrameDuration; int frameSkipCounter; /** Number of the next position within a line to render. * Expressed in VDP clock ticks since start of line. */ int nextX; /** Number of the next line to render. * Expressed in number of lines since start of frame. */ int nextY; // internal VDP counter, actually belongs in VDP int textModeCounter; /** Accuracy setting for current frame. */ RenderSettings::Accuracy accuracy; /** Is display enabled? * Enabled means the current line is in the display area and * forced blanking is off. */ bool displayEnabled; /** Should current frame be draw or can it be skipped. */ bool renderFrame; bool prevRenderFrame; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/PostProcessor.cc000066400000000000000000000175371257557151200215050ustar00rootroot00000000000000#include "PostProcessor.hh" #include "Display.hh" #include "OutputSurface.hh" #include "DeinterlacedFrame.hh" #include "DoubledFrame.hh" #include "Deflicker.hh" #include "SuperImposedFrame.hh" #include "PNG.hh" #include "RenderSettings.hh" #include "RawFrame.hh" #include "AviRecorder.hh" #include "CliComm.hh" #include "MSXMotherBoard.hh" #include "Reactor.hh" #include "EventDistributor.hh" #include "FinishFrameEvent.hh" #include "CommandException.hh" #include "MemBuffer.hh" #include "vla.hh" #include "memory.hh" #include "likely.hh" #include "build-info.hh" #include #include #include namespace openmsx { PostProcessor::PostProcessor(MSXMotherBoard& motherBoard, Display& display_, OutputSurface& screen_, const std::string& videoSource, unsigned maxWidth_, unsigned height_, bool canDoInterlace_) : VideoLayer(motherBoard, videoSource) , Schedulable(motherBoard.getScheduler()) , renderSettings(display_.getRenderSettings()) , screen(screen_) , paintFrame(nullptr) , recorder(nullptr) , superImposeVideoFrame(nullptr) , superImposeVdpFrame(nullptr) , interleaveCount(0) , lastFramesCount(0) , maxWidth(maxWidth_) , height(height_) , display(display_) , canDoInterlace(canDoInterlace_) , lastRotate(motherBoard.getCurrentTime()) , eventDistributor(motherBoard.getReactor().getEventDistributor()) { if (canDoInterlace) { deinterlacedFrame = make_unique( screen.getSDLFormat()); interlacedFrame = make_unique( screen.getSDLFormat()); deflicker = Deflicker::create( screen.getSDLFormat(), lastFrames); superImposedFrame = SuperImposedFrame::create( screen.getSDLFormat()); } else { // Laserdisc always produces non-interlaced frames, so we don't // need lastFrames[1..3], deinterlacedFrame and // interlacedFrame. Also it produces a complete frame at a // time, so we don't need lastFrames[0] (and have a separate // work buffer, for partially rendered frames). } } PostProcessor::~PostProcessor() { if (recorder) { getCliComm().printWarning( "Videorecording stopped, because you " "changed machine or changed a video setting " "during recording."); recorder->stop(); } } CliComm& PostProcessor::getCliComm() { return display.getCliComm(); } unsigned PostProcessor::getLineWidth( FrameSource* frame, unsigned y, unsigned step) { unsigned result = frame->getLineWidth(y); for (unsigned i = 1; i < step; ++i) { result = std::max(result, frame->getLineWidth(y + i)); } return result; } std::unique_ptr PostProcessor::rotateFrames( std::unique_ptr finishedFrame, EmuTime::param time) { if (renderSettings.getInterleaveBlackFrame()) { auto delta = time - lastRotate; // time between last two calls auto middle = time + delta / 2; // estimate for middle between now // and next call setSyncPoint(middle); } lastRotate = time; // Figure out how many past frames we want to use. int numRequired = 1; bool doDeinterlace = false; bool doInterlace = false; bool doDeflicker = false; auto currType = finishedFrame->getField(); if (canDoInterlace) { if (currType != FrameSource::FIELD_NONINTERLACED) { if (renderSettings.getDeinterlace()) { doDeinterlace = true; numRequired = 2; } else { doInterlace = true; } } else if (renderSettings.getDeflicker()) { doDeflicker = true; numRequired = 4; } } // Which frame can be returned (recycled) to caller. Prefer to return // the youngest frame to improve cache locality. int recycleIdx = (lastFramesCount < numRequired) ? lastFramesCount++ // store one more : (numRequired - 1); // youngest that's no longer needed assert(recycleIdx < 4); auto recycleFrame = std::move(lastFrames[recycleIdx]); // might be nullptr // Insert new frame in front of lastFrames[], shift older frames std::move_backward(lastFrames, lastFrames + recycleIdx, lastFrames + recycleIdx + 1); lastFrames[0] = std::move(finishedFrame); // Are enough frames available? if (lastFramesCount >= numRequired) { // Only the last 'numRequired' are kept up to date. lastFramesCount = numRequired; } else { // Not enough past frames, fall back to 'regular' rendering. // This situation can only occur when: // - The very first frame we render needs to be deinterlaced. // In other case we have at least one valid frame from the // past plus one new frame passed via the 'finishedFrame' // parameter. // - Or when (re)enabling the deflicker setting. Typically only // 1 frame in lastFrames[] is kept up-to-date (and we're // given 1 new frame), so it can take up-to 2 frame after // enabling deflicker before it actually takes effect. doDeinterlace = false; doInterlace = false; doDeflicker = false; } // Setup the to-be-painted frame if (doDeinterlace) { if (currType == FrameSource::FIELD_ODD) { deinterlacedFrame->init(lastFrames[1].get(), lastFrames[0].get()); } else { deinterlacedFrame->init(lastFrames[0].get(), lastFrames[1].get()); } paintFrame = deinterlacedFrame.get(); } else if (doInterlace) { interlacedFrame->init( lastFrames[0].get(), (currType == FrameSource::FIELD_ODD) ? 1 : 0); paintFrame = interlacedFrame.get(); } else if (doDeflicker) { deflicker->init(); paintFrame = deflicker.get(); } else { paintFrame = lastFrames[0].get(); } if (superImposeVdpFrame) { superImposedFrame->init(paintFrame, superImposeVdpFrame); paintFrame = superImposedFrame.get(); } // Possibly record this frame if (recorder && needRecord()) { try { recorder->addImage(paintFrame, time); } catch (MSXException& e) { getCliComm().printWarning( "Recording stopped with error: " + e.getMessage()); recorder->stop(); assert(!recorder); } } // Return recycled frame to the caller if (canDoInterlace) { if (unlikely(!recycleFrame)) { recycleFrame = make_unique( screen.getSDLFormat(), maxWidth, height); } return recycleFrame; } else { return std::move(lastFrames[0]); } } void PostProcessor::executeUntil(EmuTime::param /*time*/) { // insert fake end of frame event eventDistributor.distributeEvent( std::make_shared( getVideoSource(), getVideoSourceSetting(), false)); } using WorkBuffer = std::vector>; static void getScaledFrame(FrameSource& paintFrame, unsigned bpp, unsigned height, const void** lines, WorkBuffer& workBuffer) { unsigned width = (height == 240) ? 320 : 640; unsigned pitch = width * ((bpp == 32) ? 4 : 2); const void* line = nullptr; void* work = nullptr; for (unsigned i = 0; i < height; ++i) { if (line == work) { // If work buffer was used in previous iteration, // then allocate a new one. workBuffer.emplace_back(pitch); work = workBuffer.back().data(); } #if HAVE_32BPP if (bpp == 32) { // 32bpp auto* work2 = static_cast(work); if (height == 240) { line = paintFrame.getLinePtr320_240(i, work2); } else { assert (height == 480); line = paintFrame.getLinePtr640_480(i, work2); } } else #endif { #if HAVE_16BPP // 15bpp or 16bpp auto* work2 = static_cast(work); if (height == 240) { line = paintFrame.getLinePtr320_240(i, work2); } else { assert (height == 480); line = paintFrame.getLinePtr640_480(i, work2); } #endif } lines[i] = line; } } void PostProcessor::takeRawScreenShot(unsigned height, const std::string& filename) { if (!paintFrame) { throw CommandException("TODO"); } VLA(const void*, lines, height); WorkBuffer workBuffer; getScaledFrame(*paintFrame, getBpp(), height, lines, workBuffer); unsigned width = (height == 240) ? 320 : 640; PNG::save(width, height, lines, paintFrame->getSDLPixelFormat(), filename); } unsigned PostProcessor::getBpp() const { return screen.getSDLFormat().BitsPerPixel; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/PostProcessor.hh000066400000000000000000000123431257557151200215050ustar00rootroot00000000000000#ifndef POSTPROCESSOR_HH #define POSTPROCESSOR_HH #include "FrameSource.hh" #include "VideoLayer.hh" #include "Schedulable.hh" #include "EmuTime.hh" #include #include namespace openmsx { class Display; class RenderSettings; class RawFrame; class DeinterlacedFrame; class DoubledFrame; class Deflicker; class SuperImposedFrame; class AviRecorder; class CliComm; class EventDistributor; /** Abstract base class for post processors. * A post processor builds the frame that is displayed from the MSX frame, * while applying effects such as scalers, noise etc. * TODO: With some refactoring, it would be possible to move much or even all * of the post processing code here instead of in the subclasses. */ class PostProcessor : public VideoLayer, private Schedulable { public: virtual ~PostProcessor(); /** Sets up the "abcdFrame" variables for a new frame. * TODO: The point of passing the finished frame in and the new workFrame * out is to be able to split off the scaler application as a * separate class. * @param finishedFrame Frame that has just become available. * @param time The moment in time the frame becomes available. Used to * calculate the framerate for recording (depends on * PAL/NTSC, frameskip). * @return RawFrame object that can be used for building the next frame. */ virtual std::unique_ptr rotateFrames( std::unique_ptr finishedFrame, EmuTime::param time); /** Set the Video frame on which to superimpose the 'normal' output of * this PostProcessor. Superimpose is done (preferably) after the * normal output is scaled. IOW the video frame is (preferably) left * unchanged, though exceptions are e.g. scalers that render * scanlines, those are preferably also visible in the video frame. */ void setSuperimposeVideoFrame(const RawFrame* videoSource) { superImposeVideoFrame = videoSource; } /** Set the VDP frame on which to superimpose the 'normal' output of * this PostProcessor. This is similar to the method above, except * that now the superimposing is done before scaling. IOW both frames * get scaled. */ void setSuperimposeVdpFrame(const FrameSource* vdpSource) { superImposeVdpFrame = vdpSource; } /** Start/stop recording. * @param recorder_ Finished frames should be pushed to this * AviRecorder. Can also be nullptr, meaning * recording is stopped. */ void setRecorder(AviRecorder* recorder_) { recorder = recorder_; } /** Is recording active. * ATM used to keep frameskip constant during recording. */ bool isRecording() const { return recorder != nullptr; } /** Get the number of bits per pixel for the pixels in these frames. * @return Possible values are 15, 16 or 32 */ unsigned getBpp() const; /** Get the frame that would be displayed. E.g. so that it can be * superimposed over the output of another PostProcessor, see * setSuperimposeVdpFrame(). */ FrameSource* getPaintFrame() const { return paintFrame; } // VideoLayer void takeRawScreenShot(unsigned height, const std::string& filename) override; CliComm& getCliComm(); protected: /** Returns the maximum width for lines [y..y+step). */ static unsigned getLineWidth(FrameSource* frame, unsigned y, unsigned step); PostProcessor( MSXMotherBoard& motherBoard, Display& display, OutputSurface& screen, const std::string& videoSource, unsigned maxWidth, unsigned height, bool canDoInterlace); /** Render settings */ RenderSettings& renderSettings; /** The surface which is visible to the user. */ OutputSurface& screen; /** The last 4 fully rendered (unscaled) MSX frames. */ std::unique_ptr lastFrames[4]; /** Combined the last two frames in a deinterlaced frame. */ std::unique_ptr deinterlacedFrame; /** Each line of the last frame twice, to get double vertical resolution. */ std::unique_ptr interlacedFrame; /** Combine the last 4 frames into one 'flicker-free' frame. */ std::unique_ptr deflicker; /** Result of superimposing 2 frames. */ std::unique_ptr superImposedFrame; /** Represents a frame as it should be displayed. * This can be simply a RawFrame or two RawFrames combined in a * DeinterlacedFrame or DoubledFrame. */ FrameSource* paintFrame; /** Video recorder, nullptr when not recording. */ AviRecorder* recorder; /** Video frame on which to superimpose the (VDP) output. * nullptr when not superimposing. */ const RawFrame* superImposeVideoFrame; const FrameSource* superImposeVdpFrame; int interleaveCount; // for interleave-black-frame int lastFramesCount; // How many items in lastFrames[] are up-to-date int maxWidth; // we lazily create RawFrame objects in lastFrames[] int height; // these two vars remember how big those should be private: // Schedulable void executeUntil(EmuTime::param time) override; Display& display; /** Laserdisc cannot do interlace (better: the current implementation * is not interlaced). In that case some internal stuff can be done * with less buffers. */ const bool canDoInterlace; EmuTime lastRotate; EventDistributor& eventDistributor; }; } // namespace openmsx #endif // POSTPROCESSOR_HH openMSX-RELEASE_0_12_0/src/video/Rasterizer.hh000066400000000000000000000073771257557151200210250ustar00rootroot00000000000000#ifndef RASTERIZER_HH #define RASTERIZER_HH #include "EmuTime.hh" #include "DisplayMode.hh" namespace openmsx { class PostProcessor; class RawFrame; class Rasterizer { public: virtual ~Rasterizer() {} /** See VDP::getPostProcessor(). */ virtual PostProcessor* getPostProcessor() const = 0; /** Will the output of this Rasterizer be displayed? * There is no point in producing a frame that will not be displayed. * TODO: Is querying the next pipeline step the best way to solve this, * or is it better to explicitly disable the first step in the pipeline? */ virtual bool isActive() = 0; /** Resynchronize with VDP: all cached states are flushed. */ virtual void reset() = 0; /** Indicates the start of a new frame. * The rasterizer can fetch per-frame settings from the VDP. */ virtual void frameStart(EmuTime::param time) = 0; /** Indicates the end of the current frame. * The rasterizer can perform image post processing. */ virtual void frameEnd() = 0; /** Precalc several values that depend on the display mode. * @param mode The new display mode. */ virtual void setDisplayMode(DisplayMode mode) = 0; /** Change an entry in the palette. * @param index The index [0..15] in the palette that changes. * @param grb The new definition for the changed palette index: * bit 10..8 is green, bit 6..4 is red and bit 2..0 is blue; * all other bits are zero. */ virtual void setPalette(int index, int grb) = 0; /** Changes the background color. * @param index Palette index of the new background color. */ virtual void setBackgroundColor(int index) = 0; virtual void setHorizontalAdjust(int adjust) = 0; virtual void setHorizontalScrollLow(byte scroll) = 0; virtual void setBorderMask(bool masked) = 0; virtual void setTransparency(bool enabled) = 0; virtual void setSuperimposeVideoFrame(const RawFrame* videoSource) = 0; /** Render a rectangle of border pixels on the host screen. * The units are absolute lines (Y) and VDP clockticks (X). * @param fromX X coordinate of render start (inclusive). * @param fromY Y coordinate of render start (inclusive). * @param limitX X coordinate of render end (exclusive). * @param limitY Y coordinate of render end (exclusive). */ virtual void drawBorder(int fromX, int fromY, int limitX, int limitY) = 0; /** Render a rectangle of display pixels on the host screen. * @param fromX X coordinate of render start in VDP ticks. * @param fromY Y coordinate of render start in absolute lines. * @param displayX display coordinate of render start: [0..512). * @param displayY display coordinate of render start: [0..256). * @param displayWidth rectangle width in pixels (512 per line). * @param displayHeight rectangle height in lines. */ virtual void drawDisplay( int fromX, int fromY, int displayX, int displayY, int displayWidth, int displayHeight) = 0; /** Render a rectangle of sprite pixels on the host screen. * Although the parameters are very similar to drawDisplay, * the displayX and displayWidth use range [0..256) instead of * [0..512) because VDP sprite coordinates work that way. * @param fromX X coordinate of render start in VDP ticks. * @param fromY Y coordinate of render start in absolute lines. * @param displayX display coordinate of render start: [0..256). * @param displayY display coordinate of render start: [0..256). * @param displayWidth rectangle width in pixels (256 per line). * @param displayHeight rectangle height in lines. */ virtual void drawSprites( int fromX, int fromY, int displayX, int displayY, int displayWidth, int displayHeight) = 0; /** Is video recording active? */ virtual bool isRecording() const = 0; protected: Rasterizer() {} }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/RawFrame.cc000066400000000000000000000025201257557151200203460ustar00rootroot00000000000000#include "RawFrame.hh" #include #include namespace openmsx { RawFrame::RawFrame( const SDL_PixelFormat& format, unsigned maxWidth_, unsigned height) : FrameSource(format) , lineWidths(height) , maxWidth(maxWidth_) { setHeight(height); unsigned bytesPerPixel = format.BytesPerPixel; // Allocate memory, make sure each line starts at a 64 byte boundary: // - SSE instructions need 16 byte aligned data // - cache line size on many CPUs is 64 bytes pitch = ((bytesPerPixel * maxWidth) + 63) & ~63; data.resize(pitch * height); maxWidth = pitch / bytesPerPixel; // adjust maxWidth // Start with a black frame. init(FIELD_NONINTERLACED); for (unsigned line = 0; line < height; line++) { if (bytesPerPixel == 2) { setBlank(line, static_cast(0)); } else { setBlank(line, static_cast(0)); } } } unsigned RawFrame::getLineWidth(unsigned line) const { assert(line < getHeight()); return lineWidths[line]; } const void* RawFrame::getLineInfo( unsigned line, unsigned& width, void* /*buf*/, unsigned /*bufWidth*/) const { assert(line < getHeight()); width = lineWidths[line]; return data.data() + line * pitch; } unsigned RawFrame::getRowLength() const { return maxWidth; // in pixels (not in bytes) } bool RawFrame::hasContiguousStorage() const { return true; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/RawFrame.hh000066400000000000000000000035031257557151200203620ustar00rootroot00000000000000#ifndef RAWFRAME_HH #define RAWFRAME_HH #include "FrameSource.hh" #include "MemBuffer.hh" #include "openmsx.hh" #include namespace openmsx { // Used by SDLRasterizer to implement left/right border drawing optimization. struct V9958RasterizerBorderInfo { V9958RasterizerBorderInfo() : mode(0xff) {} // invalid mode uint32_t color0, color1; byte mode, adjust, scroll; bool masked; }; /** A video frame as output by the VDP scanline conversion unit, * before any postprocessing filters are applied. */ class RawFrame final : public FrameSource { public: RawFrame(const SDL_PixelFormat& format, unsigned maxWidth, unsigned height); template Pixel* getLinePtrDirect(unsigned y) { return reinterpret_cast(data.data() + y * pitch); } unsigned getLineWidthDirect(unsigned y) const { return lineWidths[y]; } inline void setLineWidth(unsigned line, unsigned width) { assert(line < getHeight()); assert(width <= maxWidth); lineWidths[line] = width; } template inline void setBlank(unsigned line, Pixel color) { assert(line < getHeight()); Pixel* pixels = getLinePtrDirect(line); pixels[0] = color; lineWidths[line] = 1; } unsigned getRowLength() const override; // RawFrame is mostly agnostic of the border info struct. The only // thing it does is store the information and give access to it. V9958RasterizerBorderInfo& getBorderInfo() { return borderInfo; } protected: unsigned getLineWidth(unsigned line) const override; const void* getLineInfo( unsigned line, unsigned& width, void* buf, unsigned bufWidth) const override; bool hasContiguousStorage() const override; private: MemBuffer data; MemBuffer lineWidths; unsigned maxWidth; unsigned pitch; V9958RasterizerBorderInfo borderInfo; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/RenderSettings.cc000066400000000000000000000222471257557151200216120ustar00rootroot00000000000000#include "RenderSettings.hh" #include "CommandController.hh" #include "CommandException.hh" #include "Version.hh" #include "unreachable.hh" #include "build-info.hh" #include "components.hh" #include #include #include using namespace gl; namespace openmsx { EnumSetting::Map RenderSettings::getScalerMap() { EnumSetting::Map scalerMap = { { "simple", SCALER_SIMPLE } }; if (MAX_SCALE_FACTOR > 1) { scalerMap.insert(end(scalerMap), { { "SaI", SCALER_SAI }, { "ScaleNx", SCALER_SCALE }, { "hq", SCALER_HQ }, { "hqlite", SCALER_HQLITE }, { "RGBtriplet", SCALER_RGBTRIPLET }, { "TV", SCALER_TV } }); if (!Version::RELEASE) { // This scaler is not ready yet for the upcoming 0.8.1 // release, so disable it. As soon as it is ready we // can remove this test. scalerMap.emplace_back("MLAA", SCALER_MLAA); } } return scalerMap; } EnumSetting::Map RenderSettings::getRendererMap() { EnumSetting::Map rendererMap = { { "none", DUMMY },// TODO: only register when in CliComm mode { "SDL", SDL } }; #if COMPONENT_GL // compiled with OpenGL-2.0, still need to test whether // it's available at run time, but cannot be done here rendererMap.emplace_back("SDLGL-PP", SDLGL_PP); if (!Version::RELEASE) { // disabled for the release: // these renderers don't offer anything more than the existing // renderers and sdlgl-fb32 still has endian problems on PPC // TODO is this still true now that SDLGL is removed? rendererMap.insert(end(rendererMap), { {"SDLGL-FB16", SDLGL_FB16}, {"SDLGL-FB32", SDLGL_FB32}}); } #endif return rendererMap; } RenderSettings::RenderSettings(CommandController& commandController) : accuracySetting(commandController, "accuracy", "rendering accuracy", ACC_PIXEL, EnumSetting::Map{ {"screen", ACC_SCREEN}, {"line", ACC_LINE}, {"pixel", ACC_PIXEL}}) , deinterlaceSetting(commandController, "deinterlace", "deinterlacing on/off", true) , deflickerSetting(commandController, "deflicker", "deflicker on/off", false) , maxFrameSkipSetting(commandController, "maxframeskip", "set the max amount of frameskip", 3, 0, 100) , minFrameSkipSetting(commandController, "minframeskip", "set the min amount of frameskip", 0, 0, 100) , fullScreenSetting(commandController, "fullscreen", "full screen display on/off", false) , gammaSetting(commandController, "gamma", "amount of gamma correction: low is dark, high is bright", 1.1, 0.1, 5.0) , brightnessSetting(commandController, "brightness", "brightness video setting: " "0 is normal, lower is darker, higher is brighter", 0.0, -100.0, 100.0) , contrastSetting(commandController, "contrast", "contrast video setting: " "0 is normal, lower is less contrast, higher is more contrast", 0.0, -100.0, 100.0) , colorMatrixSetting(commandController, "color_matrix", "3x3 matrix to transform MSX RGB to host RGB, see manual for details", "{ 1 0 0 } { 0 1 0 } { 0 0 1 }") , glowSetting(commandController, "glow", "amount of afterglow effect: 0 = none, 100 = lots", 0, 0, 100) , noiseSetting(commandController, "noise", "amount of noise to add to the frame", 0.0, 0.0, 100.0) , rendererSetting(commandController, "renderer", "rendering back-end used to display the MSX screen", SDL, getRendererMap()) , horizontalBlurSetting(commandController, "blur", "amount of horizontal blur effect: 0 = none, 100 = full", 50, 0, 100) , scaleAlgorithmSetting( commandController, "scale_algorithm", "scale algorithm", SCALER_SIMPLE, getScalerMap()) , scaleFactorSetting(commandController, "scale_factor", "scale factor", std::min(2, MAX_SCALE_FACTOR), MIN_SCALE_FACTOR, MAX_SCALE_FACTOR) , scanlineAlphaSetting(commandController, "scanline", "amount of scanline effect: 0 = none, 100 = full", 20, 0, 100) , limitSpritesSetting(commandController, "limitsprites", "limit number of sprites per line " "(on for realism, off to reduce sprite flashing)", true) , disableSpritesSetting(commandController, "disablesprites", "disable sprite rendering", false, Setting::DONT_SAVE) , cmdTimingSetting(commandController, "cmdtiming", "VDP command timing", false, EnumSetting::Map{{"real", false}, {"broken", true}}, Setting::DONT_SAVE) , tooFastAccessSetting(commandController, "too_fast_vram_access", "Should too fast VDP VRAM access be correctly emulated.\n" "Possible values are:\n" " real -> too fast accesses are dropped\n" " ignore -> access speed is ignored, all accesses are executed", false, EnumSetting::Map{{"real", false }, {"ignore", true}}, Setting::DONT_SAVE) , displayDeformSetting( commandController, "display_deform", "Display deform (for the moment this only " "works with the SDLGL-PP renderer", DEFORM_NORMAL, EnumSetting::Map{ {"normal", DEFORM_NORMAL}, {"3d", DEFORM_3D}}) // Many android devices are relatively low powered. Therefore use // no stretch (value 320) as default for Android because it gives // better performance , horizontalStretchSetting(commandController, "horizontal_stretch", "Amount of horizontal stretch: this many MSX pixels will be " "stretched over the complete width of the output screen.\n" " 320 = no stretch\n" " 256 = max stretch (no border visible anymore)\n" " good values are 272 or 280\n" "This setting has only effect when using the SDLGL-PP renderer.", PLATFORM_ANDROID ? 320.0 : 280.0, 256.0, 320.0) , pointerHideDelaySetting(commandController, "pointer_hide_delay", "number of seconds after which the mouse pointer is hidden in the openMSX " "window; negative = no hiding, 0 = immediately", 2.0, -1.0, 60.0) , interleaveBlackFrameSetting(commandController, "interleave_black_frame", "Insert a black frame in between each normal MSX frame. " "Useful on (100Hz+) lightboost enabled monitors to reduce " "motion blur and double frame artifacts.", false) { brightnessSetting.attach(*this); contrastSetting .attach(*this); updateBrightnessAndContrast(); auto& interp = commandController.getInterpreter(); colorMatrixSetting.setChecker([this, &interp](TclObject& newValue) { try { parseColorMatrix(interp, newValue); } catch (CommandException& e) { throw CommandException( "Invalid color matrix: " + e.getMessage()); } }); try { parseColorMatrix(interp, colorMatrixSetting.getValue()); } catch (MSXException& e) { std::cerr << e.getMessage() << std::endl; cmIdentity = true; } // RendererSetting // Make sure the value 'none' never gets saved in settings.xml. // This happened in the following scenario: // - During startup, the renderer is forced to the value 'none'. // - If there's an error in the parsing of the command line (e.g. // because an invalid option is passed) then openmsx will never // get to the point where the actual renderer setting is restored // - After the error, the classes are destructed, part of that is // saving the current settings. But without extra care, this would // save renderer=none rendererSetting.setDontSaveValue(TclObject("none")); // A saved value 'none' can be very confusing. If so change it to default. if (rendererSetting.getEnum() == DUMMY) { rendererSetting.setValue(rendererSetting.getDefaultValue()); } // set saved value as default rendererSetting.setRestoreValue(rendererSetting.getValue()); rendererSetting.setEnum(DUMMY); // always start hidden } RenderSettings::~RenderSettings() { brightnessSetting.detach(*this); contrastSetting .detach(*this); } void RenderSettings::update(const Setting& setting) { if (&setting == &brightnessSetting) { updateBrightnessAndContrast(); } else if (&setting == &contrastSetting) { updateBrightnessAndContrast(); } else { UNREACHABLE; } } void RenderSettings::updateBrightnessAndContrast() { float contrastValue = getContrast(); contrast = (contrastValue >= 0.0f) ? (1.0f + contrastValue / 25.0f) : (1.0f + contrastValue / 125.0f); brightness = (getBrightness() / 100.0f - 0.5f) * contrast + 0.5f; } static float conv2(float x, float gamma) { return ::powf(std::min(std::max(0.0f, x), 1.0f), gamma); } float RenderSettings::transformComponent(float c) const { float c2 = c * contrast + brightness; return conv2(c2, 1.0f / getGamma()); } vec3 RenderSettings::transformRGB(vec3 rgb) const { vec3 t = colorMatrix * (rgb * contrast + vec3(brightness)); float gamma = 1.0f / getGamma(); return vec3(conv2(t[0], gamma), conv2(t[1], gamma), conv2(t[2], gamma)); } void RenderSettings::parseColorMatrix(Interpreter& interp, const TclObject& value) { if (value.getListLength(interp) != 3) { throw CommandException("must have 3 rows"); } bool identity = true; for (int i = 0; i < 3; ++i) { TclObject row = value.getListIndex(interp, i); if (row.getListLength(interp) != 3) { throw CommandException("each row must have 3 elements"); } for (int j = 0; j < 3; ++j) { TclObject element = row.getListIndex(interp, j); float value = element.getDouble(interp); colorMatrix[i][j] = value; identity &= (value == (i == j ? 1.0f : 0.0f)); } } cmIdentity = identity; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/RenderSettings.hh000066400000000000000000000200741257557151200216200ustar00rootroot00000000000000#ifndef RENDERSETTINGS_HH #define RENDERSETTINGS_HH #include "BooleanSetting.hh" #include "EnumSetting.hh" #include "FloatSetting.hh" #include "IntegerSetting.hh" #include "StringSetting.hh" #include "Observer.hh" #include "gl_mat.hh" namespace openmsx { class CommandController; class Interpreter; /** Class containing all settings for renderers. * Keeping the settings here makes sure they are preserved when the user * switches to another renderer. */ class RenderSettings final : private Observer { public: /** Enumeration of Renderers known to openMSX. * This is the full list, the list of available renderers may be smaller. */ enum RendererID { UNINITIALIZED, DUMMY, SDL, SDLGL_PP, SDLGL_FB16, SDLGL_FB32 }; using RendererSetting = EnumSetting; /** Render accuracy: granularity of the rendered area. */ enum Accuracy { ACC_SCREEN, ACC_LINE, ACC_PIXEL }; /** Scaler algorithm */ enum ScaleAlgorithm { SCALER_SIMPLE, SCALER_SAI, SCALER_SCALE, SCALER_HQ, SCALER_HQLITE, SCALER_RGBTRIPLET, SCALER_TV, SCALER_MLAA, NO_SCALER }; enum DisplayDeform { DEFORM_NORMAL, DEFORM_3D }; explicit RenderSettings(CommandController& commandController); ~RenderSettings(); /** Accuracy [screen, line, pixel]. */ Accuracy getAccuracy() const { return accuracySetting.getEnum(); } /** Deinterlacing [on, off]. */ bool getDeinterlace() const { return deinterlaceSetting.getBoolean(); } /** Deflicker [on, off]. */ bool getDeflicker() const { return deflickerSetting.getBoolean(); } /** The current max frameskip. */ IntegerSetting& getMaxFrameSkipSetting() { return maxFrameSkipSetting; } int getMaxFrameSkip() const { return maxFrameSkipSetting.getInt(); } /** The current min frameskip. */ IntegerSetting& getMinFrameSkipSetting() { return minFrameSkipSetting; } int getMinFrameSkip() const { return minFrameSkipSetting.getInt(); } /** Full screen [on, off]. */ BooleanSetting& getFullScreenSetting() { return fullScreenSetting; } bool getFullScreen() const { return fullScreenSetting.getBoolean(); } /** The amount of gamma correction. */ FloatSetting& getGammaSetting() { return gammaSetting; } float getGamma() const { return gammaSetting.getDouble(); } /** Brightness video setting. */ FloatSetting& getBrightnessSetting() { return brightnessSetting; } float getBrightness() const { return brightnessSetting.getDouble(); } /** Contrast video setting. */ FloatSetting& getContrastSetting() { return contrastSetting; } float getContrast() const { return contrastSetting.getDouble(); } /** Color matrix setting. */ StringSetting& getColorMatrixSetting() { return colorMatrixSetting; } /** Returns true iff the current color matrix is the identity matrix. */ bool isColorMatrixIdentity() { return cmIdentity; } /** The amount of glow [0..100]. */ int getGlow() const { return glowSetting.getInt(); } /** The amount of noise to add to the frame. */ FloatSetting& getNoiseSetting() { return noiseSetting; } float getNoise() const { return noiseSetting.getDouble(); } /** The amount of horizontal blur [0..256]. */ int getBlurFactor() const { return (horizontalBlurSetting.getInt()) * 256 / 100; } /** The alpha value [0..255] of the gap between scanlines. */ int getScanlineFactor() const { return 255 - ((scanlineAlphaSetting.getInt() * 255) / 100); } /** The amount of space [0..1] between scanlines. */ float getScanlineGap() const { return scanlineAlphaSetting.getInt() * 0.01f; } /** The current renderer. */ RendererSetting& getRendererSetting() { return rendererSetting; } RendererID getRenderer() const { return rendererSetting.getEnum(); } /** The current scaling algorithm. */ ScaleAlgorithm getScaleAlgorithm() const { return scaleAlgorithmSetting.getEnum(); } /** The current scaling factor. */ IntegerSetting& getScaleFactorSetting() { return scaleFactorSetting; } int getScaleFactor() const { return scaleFactorSetting.getInt(); } /** Limit number of sprites per line? * If true, limit number of sprites per line as real VDP does. * If false, display all sprites. * For accurate emulation, this setting should be on. * Turning it off can improve games with a lot of flashing sprites, * such as Aleste. */ BooleanSetting& getLimitSpritesSetting() { return limitSpritesSetting; } /** Disable sprite rendering? */ bool getDisableSprites() const { return disableSpritesSetting.getBoolean(); } /** CmdTiming [real, broken]. * This setting is intended for debugging only, not for users. */ EnumSetting& getCmdTimingSetting() { return cmdTimingSetting; } /** TooFastAccess [real, ignored]. * Indicates whether too fast VDP VRAM access should be correctly * emulated (= some accesses are dropped) or ignored (= all accesses * are correctly executed). */ EnumSetting& getTooFastAccessSetting() { return tooFastAccessSetting; } /** Display deformation (normal, 3d) * ATM this only works when using the SDLGL-PP renderer. */ DisplayDeform getDisplayDeform() { return displayDeformSetting.getEnum(); } /** Amount of horizontal stretch. * This number represents the amount of MSX pixels (normal width) that * will be stretched to the complete width of the host window. */ FloatSetting& getHorizontalStretchSetting() { return horizontalStretchSetting; } float getHorizontalStretch() const { return horizontalStretchSetting.getDouble(); } /** The amount of time until the pointer is hidden in the openMSX * window. negative means: no hiding, 0 means immediately. */ FloatSetting& getPointerHideDelaySetting() { return pointerHideDelaySetting; } float getPointerHideDelay() const { return pointerHideDelaySetting.getDouble(); } /** Is black frame interleaving enabled? */ bool getInterleaveBlackFrame() const { return interleaveBlackFrameSetting.getBoolean(); } /** Apply brightness, contrast and gamma transformation on the input * color component. The component is expected to be in the range * [0.0 .. 1.0] but it's not an error if it lays outside of this range. * The return value is guaranteed to lay inside this range. * This method skips the cross-influence of color components on each * other that is controlled by the "color_matrix" setting. */ float transformComponent(float c) const; /** Apply brightness, contrast and gamma transformation on the input * color. The R, G and B component are expected to be in the range * [0.0 .. 1.0] but it's not an error if a component lays outside of * this range. After transformation it's guaranteed all components * lay inside this range. */ gl::vec3 transformRGB(gl::vec3 rgb) const; private: static EnumSetting::Map getScalerMap(); static EnumSetting::Map getRendererMap(); // Observer: void update(const Setting&) override; /** Sets the "brightness" and "contrast" fields according to the setting * values. */ void updateBrightnessAndContrast(); void parseColorMatrix(Interpreter& interp, const TclObject& value); EnumSetting accuracySetting; BooleanSetting deinterlaceSetting; BooleanSetting deflickerSetting; IntegerSetting maxFrameSkipSetting; IntegerSetting minFrameSkipSetting; BooleanSetting fullScreenSetting; FloatSetting gammaSetting; FloatSetting brightnessSetting; FloatSetting contrastSetting; StringSetting colorMatrixSetting; IntegerSetting glowSetting; FloatSetting noiseSetting; RendererSetting rendererSetting; IntegerSetting horizontalBlurSetting; EnumSetting scaleAlgorithmSetting; IntegerSetting scaleFactorSetting; IntegerSetting scanlineAlphaSetting; BooleanSetting limitSpritesSetting; BooleanSetting disableSpritesSetting; EnumSetting cmdTimingSetting; EnumSetting tooFastAccessSetting; EnumSetting displayDeformSetting; FloatSetting horizontalStretchSetting; FloatSetting pointerHideDelaySetting; BooleanSetting interleaveBlackFrameSetting; float brightness; float contrast; /** Parsed color matrix, kept in sync with colorMatrix setting. */ gl::mat3 colorMatrix; /** True iff color matrix is identity matrix. */ bool cmIdentity; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/Renderer.cc000066400000000000000000000112031257557151200204060ustar00rootroot00000000000000#include "Renderer.hh" namespace openmsx { /* TMS99X8A palette. Source: TMS9918/28/29 Data Book, page 2-17. http://en.wikipedia.org/wiki/List_of_8-bit_computer_hardware_palettes Originally we used the conversion for MESS by R. Nabet, though those colors were too saturated, especially color 8 (medium red) was very different compared to the real TMS9918 palette. Now we use the palette found on wikipedia. (Though it was still some work to figure out how to get from the voltage levels specified in the TMS9918 datasheet to the wikipedia RGB values). Color Y R-Y B-Y Y Pb Pr Y Pb Pr R G B 0 Transparent 1 Black 0.00 0.47 0.47 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 2 Medium green 0.53 0.07 0.20 0.53 -0.25 -0.38 0.53 -0.14 -0.20 0.24 0.72 0.29 3 Light green 0.67 0.17 0.27 0.67 -0.19 -0.28 0.67 -0.10 -0.15 0.46 0.81 0.49 4 Dark blue 0.40 0.40 1.00 0.40 0.50 -0.07 0.40 0.27 -0.04 0.35 0.33 0.88 5 Light blue 0.53 0.43 0.93 0.53 0.43 -0.04 0.53 0.23 -0.02 0.50 0.46 0.95 6 Dark red 0.47 0.83 0.30 0.47 -0.16 0.34 0.47 -0.09 0.18 0.73 0.37 0.32 7 Cyan 0.73 0.00 0.70 0.73 0.22 -0.44 0.73 0.12 -0.24 0.39 0.86 0.94 8 Medium red 0.53 0.93 0.27 0.53 -0.19 0.43 0.53 -0.10 0.23 0.86 0.40 0.35 9 Light red 0.67 0.93 0.27 0.67 -0.19 0.43 0.67 -0.10 0.23 1.00 0.54 0.49 A Dark yellow 0.73 0.57 0.07 0.73 -0.38 0.09 0.73 -0.20 0.05 0.80 0.76 0.37 B Light yellow 0.80 0.57 0.17 0.80 -0.28 0.09 0.80 -0.15 0.05 0.87 0.82 0.53 C Dark green 0.47 0.13 0.23 0.47 -0.23 -0.32 0.47 -0.12 -0.17 0.23 0.64 0.25 D Magenta 0.53 0.73 0.67 0.53 0.19 0.25 0.53 0.10 0.13 0.72 0.40 0.71 E Gray 0.80 0.47 0.47 0.80 0.00 0.00 0.80 0.00 0.00 0.80 0.80 0.80 F White 1.00 0.47 0.47 1.00 0.00 0.00 1.00 0.00 0.00 1.00 1.00 1.00 The first 3 columns are the voltage levels specified in the TMS datasheet. Next 3 columns convert voltages to YPbPr values. R-Y,B-Y=0.47 seems to correspond to Pb,Pr=0. Also scaled Pb,Pr so that they are in the range [-0.5..+0.5]. Y was already in range [0..1]. Next the Pb,Pr values are scaled with a factor 0.54 (saturation level 54%), this makes sure that in the next step the R,G,B values stay in range [0..1]. In the last 3 columns we convert YPbPr to RGB using the formula: |R| | 1 0 1.402 | |Y | |G| = | 1 -0.344 -0.714 | x |Pb| |B| | 1 1.722 0 | |Pr| And finally in the code below these RGB values are scaled to the range [0..255]. TODO NTSC(TMS99X8A) and PAL(TMS9929A) seem to have slightly different colors. Figure out where this difference comes from and how to emulate that best. */ const uint8_t Renderer::TMS99X8A_PALETTE[16][3] = { { 0, 0, 0 }, { 0, 0, 0 }, { 62, 184, 73 }, { 116, 208, 125 }, { 89, 85, 224 }, { 128, 118, 241 }, { 185, 94, 81 }, { 101, 219, 239 }, { 219, 101, 89 }, { 255, 137, 125 }, { 204, 195, 94 }, { 222, 208, 135 }, { 58, 162, 65 }, { 183, 102, 181 }, { 204, 204, 204 }, { 255, 255, 255 }, }; /* * Roughly measured RGB values in volts. * Voltages were in range of 1.12-5.04, and had 2 digits accuracy (it seems * minimum difference was 0.04 V). * Blue component of color 5 and red component of color 9 were higher than * the components for white... There are several methods to handle this... * 1) clip to values of white * 2) scale all colors by min/max of that component (means white is not 3x 255) * 3) scale per color if components for that color are beyond those of white * 4) assume the analog values are output by a DA converter, derive the digital * values and scale that to the range 0-255 (thanks to FRS for this idea). * This also results in white not being 3x 255, of course. * * Method 4 results in the table below and seems the most accurate (so far). * * Thanks to Tiago Valença and Carlos Mansur for measuring on a T7937A. */ const uint8_t Renderer::TOSHIBA_PALETTE[16][3] = { { 0, 0, 0 }, { 0, 0, 0 }, { 102, 204, 102 }, { 136, 238, 136 }, { 68, 68, 221 }, { 119, 119, 255 }, { 187, 85, 85 }, { 119, 221, 221 }, { 221, 102, 102 }, { 255, 119, 119 }, { 204, 204, 85 }, { 238, 238, 136 }, { 85, 170, 85 }, { 187, 85, 187 }, { 204, 204, 204 }, { 238, 238, 238 }, }; /* Sprite palette in Graphic 7 mode. See page 98 of the V9938 data book. */ const uint16_t Renderer::GRAPHIC7_SPRITE_PALETTE[16] = { 0x000, 0x002, 0x030, 0x032, 0x300, 0x302, 0x330, 0x332, 0x472, 0x007, 0x070, 0x077, 0x700, 0x707, 0x770, 0x777 }; Renderer::Renderer() { } Renderer::~Renderer() { } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/Renderer.hh000066400000000000000000000166701257557151200204350ustar00rootroot00000000000000#ifndef RENDERER_HH #define RENDERER_HH #include "VRAMObserver.hh" #include "openmsx.hh" #include namespace openmsx { class PostProcessor; class DisplayMode; class RawFrame; /** Abstract base class for Renderers. * A Renderer is a class that converts VDP state to visual * information (for example, pixels on a screen). * * The update methods are called exactly before the change occurs in * the VDP, so that the renderer can update itself to the specified * time using the old settings. */ class Renderer : public VRAMObserver { public: virtual ~Renderer(); /** See VDP::getPostProcessor. */ virtual PostProcessor* getPostProcessor() const = 0; /** Reinitialise Renderer state. */ virtual void reInit() = 0; /** Signals the start of a new frame. * The Renderer can use this to get fixed-per-frame settings from * the VDP, such as PAL/NTSC timing. * @param time The moment in emulated time the frame starts. */ virtual void frameStart(EmuTime::param time) = 0; /** Signals the end of a frame. * @param time The moment in emulated time the frame ends. * Note: this is the same time stamp as the start of the next frame. */ virtual void frameEnd(EmuTime::param time) = 0; /** Informs the renderer of a VDP transparency enable/disable change. * @param enabled The new transparency state. * @param time The moment in emulated time this change occurs. */ virtual void updateTransparency(bool enabled, EmuTime::param time) = 0; /** Informs the renderer of a VDP superimposing change. * @param videoSource Video that should be superimposed, nullptr if none. * @param time The moment in emulated time this change occurs. */ virtual void updateSuperimposing(const RawFrame* videoSource, EmuTime::param time) = 0; /** Informs the renderer of a VDP foreground color change. * @param color The new foreground color. * @param time The moment in emulated time this change occurs. */ virtual void updateForegroundColor(int color, EmuTime::param time) = 0; /** Informs the renderer of a VDP background color change. * @param color The new background color. * @param time The moment in emulated time this change occurs. */ virtual void updateBackgroundColor(int color, EmuTime::param time) = 0; /** Informs the renderer of a VDP blink foreground color change. * @param color The new blink foreground color. * @param time The moment in emulated time this change occurs. */ virtual void updateBlinkForegroundColor(int color, EmuTime::param time) = 0; /** Informs the renderer of a VDP blink background color change. * @param color The new blink background color. * @param time The moment in emulated time this change occurs. */ virtual void updateBlinkBackgroundColor(int color, EmuTime::param time) = 0; /** Informs the renderer of a VDP blinking state change. * @param enabled The new blink state. * @param time The moment in emulated time this change occurs. */ virtual void updateBlinkState(bool enabled, EmuTime::param time) = 0; /** Informs the renderer of a VDP palette change. * @param index The index [0..15] in the palette that changes. * @param grb The new definition for the changed palette index: * bit 10..8 is green, bit 6..4 is red and bit 2..0 is blue; * all other bits are zero. * @param time The moment in emulated time this change occurs. */ virtual void updatePalette(int index, int grb, EmuTime::param time) = 0; /** Informs the renderer of a vertical scroll change. * @param scroll The new scroll value. * @param time The moment in emulated time this change occurs. */ virtual void updateVerticalScroll(int scroll, EmuTime::param time) = 0; /** Informs the renderer of a horizontal scroll change: * the lower scroll value has changed. * @param scroll The new scroll value. * @param time The moment in emulated time this change occurs. */ virtual void updateHorizontalScrollLow(byte scroll, EmuTime::param time) = 0; /** Informs the renderer of a horizontal scroll change: * the higher scroll value has changed. * @param scroll The new scroll value. * @param time The moment in emulated time this change occurs. */ virtual void updateHorizontalScrollHigh(byte scroll, EmuTime::param time) = 0; /** Informs the renderer of a horizontal scroll change: * the border mask has been enabled/disabled. * @param masked true iff enabled. * @param time The moment in emulated time this change occurs. */ virtual void updateBorderMask(bool masked, EmuTime::param time) = 0; /** Informs the renderer of a horizontal scroll change: * the multi page setting has changed. * @param multiPage The new multi page flag. * @param time The moment in emulated time this change occurs. */ virtual void updateMultiPage(bool multiPage, EmuTime::param time) = 0; /** Informs the renderer of a horizontal adjust change. * Note that there is no similar method for vertical adjust updates, * because vertical adjust is calculated at start of frame and * then fixed. * @param adjust The new adjust value. * @param time The moment in emulated time this change occurs. */ virtual void updateHorizontalAdjust(int adjust, EmuTime::param time) = 0; /** Informs the renderer of a VDP display enabled change. * Both the regular border start/end and forced blanking by clearing * the display enable bit are considered display enabled changes. * @param enabled The new display enabled state. * @param time The moment in emulated time this change occurs. */ virtual void updateDisplayEnabled(bool enabled, EmuTime::param time) = 0; /** Informs the renderer of a VDP display mode change. * @param mode The new display mode. * @param time The moment in emulated time this change occurs. */ virtual void updateDisplayMode(DisplayMode mode, EmuTime::param time) = 0; /** Informs the renderer of a name table base address change. * @param addr The new base address. * @param time The moment in emulated time this change occurs. */ virtual void updateNameBase(int addr, EmuTime::param time) = 0; /** Informs the renderer of a pattern table base address change. * @param addr The new base address. * @param time The moment in emulated time this change occurs. */ virtual void updatePatternBase(int addr, EmuTime::param time) = 0; /** Informs the renderer of a color table base address change. * @param addr The new base address. * @param time The moment in emulated time this change occurs. */ virtual void updateColorBase(int addr, EmuTime::param time) = 0; /** Informs the renderer of a VDP sprites enabled change. * @param enabled The new sprites enabled state. * @param time The moment in emulated time this change occurs. */ virtual void updateSpritesEnabled(bool enabled, EmuTime::param time) = 0; /** NTSC version of the MSX1 palette. * An array of 16 RGB triples. * Each component ranges from 0 (off) to 255 (full intensity). */ static const uint8_t TMS99X8A_PALETTE[16][3]; /** The MSX1 palette of the Toshiba T6950 and T7937A. * An array of 16 RGB triples. * Each component ranges from 0 (off) to 255 (full intensity). */ static const uint8_t TOSHIBA_PALETTE[16][3]; /** Sprite palette in Graphic 7 mode. * Each palette entry is a word in GRB format: * bit 10..8 is green, bit 6..4 is red and bit 2..0 is blue. */ static const uint16_t GRAPHIC7_SPRITE_PALETTE[16]; protected: Renderer(); }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/RendererFactory.cc000066400000000000000000000046261257557151200217510ustar00rootroot00000000000000#include "RendererFactory.hh" #include "RenderSettings.hh" #include "Reactor.hh" #include "Display.hh" #include "Version.hh" #include "memory.hh" #include "unreachable.hh" // Video systems: #include "components.hh" #include "DummyVideoSystem.hh" #include "SDLVideoSystem.hh" // Renderers: #include "DummyRenderer.hh" #include "PixelRenderer.hh" #include "V9990DummyRenderer.hh" #include "V9990PixelRenderer.hh" #if COMPONENT_LASERDISC #include "LDDummyRenderer.hh" #include "LDPixelRenderer.hh" #endif using std::unique_ptr; namespace openmsx { namespace RendererFactory { unique_ptr createVideoSystem(Reactor& reactor) { Display& display = reactor.getDisplay(); switch (display.getRenderSettings().getRenderer()) { case RenderSettings::DUMMY: return make_unique(); case RenderSettings::SDL: case RenderSettings::SDLGL_PP: case RenderSettings::SDLGL_FB16: case RenderSettings::SDLGL_FB32: return make_unique( reactor, display.getCommandConsole()); default: UNREACHABLE; return nullptr; } } unique_ptr createRenderer(VDP& vdp, Display& display) { switch (display.getRenderSettings().getRenderer()) { case RenderSettings::DUMMY: return make_unique(); case RenderSettings::SDL: case RenderSettings::SDLGL_PP: case RenderSettings::SDLGL_FB16: case RenderSettings::SDLGL_FB32: return make_unique(vdp, display); default: UNREACHABLE; return nullptr; } } unique_ptr createV9990Renderer(V9990& vdp, Display& display) { switch (display.getRenderSettings().getRenderer()) { case RenderSettings::DUMMY: return make_unique(); case RenderSettings::SDL: case RenderSettings::SDLGL_PP: case RenderSettings::SDLGL_FB16: case RenderSettings::SDLGL_FB32: return make_unique(vdp); default: UNREACHABLE; return nullptr; } } #if COMPONENT_LASERDISC unique_ptr createLDRenderer(LaserdiscPlayer& ld, Display& display) { switch (display.getRenderSettings().getRenderer()) { case RenderSettings::DUMMY: return make_unique(); case RenderSettings::SDL: case RenderSettings::SDLGL_PP: case RenderSettings::SDLGL_FB16: case RenderSettings::SDLGL_FB32: return make_unique(ld, display); default: UNREACHABLE; return nullptr; } } #endif } // namespace RendererFactory } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/RendererFactory.hh000066400000000000000000000027711257557151200217620ustar00rootroot00000000000000#ifndef RENDERERFACTORY_HH #define RENDERERFACTORY_HH #include #include "components.hh" namespace openmsx { class Reactor; class CommandController; class Display; class VideoSystem; class Renderer; class VDP; class V9990Renderer; class V9990; class LDRenderer; class LaserdiscPlayer; template class EnumSetting; /** Interface for renderer factories. * Every Renderer type has its own RendererFactory. * A RendererFactory can be queried about the availability of the * associated Renderer and can instantiate that Renderer. */ namespace RendererFactory { /** Create the video system required by the current renderer setting. */ std::unique_ptr createVideoSystem(Reactor& reactor); /** Create the Renderer selected by the current renderer setting. * @param vdp The VDP whose display will be rendered. * @param display TODO */ std::unique_ptr createRenderer(VDP& vdp, Display& display); /** Create the V9990 Renderer selected by the current renderer setting. * @param vdp The V9990 VDP whose display will be rendered. * @param display TODO */ std::unique_ptr createV9990Renderer( V9990& vdp, Display& display); #if COMPONENT_LASERDISC /** Create the Laserdisc Renderer * @param ld The Laserdisc player whose display will be rendered. * @param display TODO */ std::unique_ptr createLDRenderer( LaserdiscPlayer& ld, Display& display); #endif } // namespace RendererFactory } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/SDLGLOffScreenSurface.cc000066400000000000000000000024361257557151200226210ustar00rootroot00000000000000#include "SDLGLOffScreenSurface.hh" #include "SDLGLVisibleSurface.hh" #include "GLUtil.hh" namespace openmsx { SDLGLOffScreenSurface::SDLGLOffScreenSurface(const SDLGLVisibleSurface& output) : SDLGLOutputSurface(output.getFrameBufferType()) , fboTex(true) // enable interpolation TODO why? { // only used for width and height setSDLSurface(const_cast(output.getSDLSurface())); fboTex.bind(); glTexImage2D(GL_TEXTURE_2D, // target 0, // level GL_RGB8, // internal format getWidth(), // width getHeight(), // height 0, // border GL_RGB, // format GL_UNSIGNED_BYTE, // type nullptr); // data fbo = gl::FrameBufferObject(fboTex); fbo.push(); SDLGLOutputSurface::init(*this); } SDLGLOffScreenSurface::~SDLGLOffScreenSurface() { } void SDLGLOffScreenSurface::flushFrameBuffer() { SDLGLOutputSurface::flushFrameBuffer(getWidth(), getHeight()); } void SDLGLOffScreenSurface::clearScreen() { SDLGLOutputSurface::clearScreen(); } void SDLGLOffScreenSurface::saveScreenshot(const std::string& filename) { SDLGLOutputSurface::saveScreenshot(filename, getWidth(), getHeight()); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/SDLGLOffScreenSurface.hh000066400000000000000000000014131257557151200226250ustar00rootroot00000000000000#ifndef SDLGLOFFSCREENSURFACE_HH #define SDLGLOFFSCREENSURFACE_HH #include "OutputSurface.hh" #include "SDLGLOutputSurface.hh" #include "GLUtil.hh" namespace openmsx { class SDLGLVisibleSurface; /** This class installs a FrameBufferObject (FBO). So as long as this object * is live, all openGL draw commands will be redirected to this FBO. */ class SDLGLOffScreenSurface final : public OutputSurface, private SDLGLOutputSurface { public: explicit SDLGLOffScreenSurface(const SDLGLVisibleSurface& output); ~SDLGLOffScreenSurface(); private: // OutputSurface void saveScreenshot(const std::string& filename) override; void flushFrameBuffer() override; void clearScreen() override; gl::Texture fboTex; gl::FrameBufferObject fbo; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/SDLGLOutputSurface.cc000066400000000000000000000105701257557151200222450ustar00rootroot00000000000000#include "SDLGLOutputSurface.hh" #include "GLContext.hh" #include "OutputSurface.hh" #include "PNG.hh" #include "build-info.hh" #include "Math.hh" #include "MemBuffer.hh" #include "memory.hh" #include "vla.hh" #include using namespace gl; namespace openmsx { SDLGLOutputSurface::SDLGLOutputSurface(FrameBuffer frameBuffer_) : fbTex(Null()) , frameBuffer(frameBuffer_) { } SDLGLOutputSurface::~SDLGLOutputSurface() { } void SDLGLOutputSurface::init(OutputSurface& output) { // This is logically a part of the constructor, but the constructor // of the child class needs to run before this code (to create a // openGL context). So we split the constructor in two parts, the // child class is responsible for calling this second part. SDL_PixelFormat format; format.palette = nullptr; format.colorkey = 0; format.alpha = 0; if (frameBuffer == FB_16BPP) { format.BitsPerPixel = 16; format.BytesPerPixel = 2; format.Rloss = 3; format.Gloss = 2; format.Bloss = 3; format.Aloss = 8; format.Rshift = 11; format.Gshift = 5; format.Bshift = 0; format.Ashift = 0; format.Rmask = 0xF800; format.Gmask = 0x07E0; format.Bmask = 0x001F; format.Amask = 0x0000; } else { // BGRA format format.BitsPerPixel = 32; format.BytesPerPixel = 4; format.Rloss = 0; format.Gloss = 0; format.Bloss = 0; format.Aloss = 0; if (OPENMSX_BIGENDIAN) { format.Rshift = 8; format.Gshift = 16; format.Bshift = 24; format.Ashift = 0; format.Rmask = 0x0000FF00; format.Gmask = 0x00FF0000; format.Bmask = 0xFF000000; format.Amask = 0x000000FF; } else { format.Rshift = 16; format.Gshift = 8; format.Bshift = 0; format.Ashift = 24; format.Rmask = 0x00FF0000; format.Gmask = 0x0000FF00; format.Bmask = 0x000000FF; format.Amask = 0xFF000000; } } output.setSDLFormat(format); if (frameBuffer == FB_NONE) { output.setBufferPtr(nullptr, 0); // direct access not allowed } else { // TODO 64 byte aligned (see RawFrame) unsigned width = output.getWidth(); unsigned height = output.getHeight(); unsigned texW = Math::powerOfTwo(width); unsigned texH = Math::powerOfTwo(height); fbBuf.resize(format.BytesPerPixel * texW * texH); unsigned pitch = width * format.BytesPerPixel; output.setBufferPtr(fbBuf.data(), pitch); texCoordX = float(width) / texW; texCoordY = float(height) / texH; fbTex.allocate(); fbTex.setInterpolation(false); if (frameBuffer == FB_16BPP) { // TODO: Why use RGB texture instead of RGBA? glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texW, texH, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, fbBuf.data()); } else { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, texW, texH, 0, GL_BGRA, GL_UNSIGNED_BYTE, fbBuf.data()); } } } void SDLGLOutputSurface::flushFrameBuffer(unsigned width, unsigned height) { assert((frameBuffer == FB_16BPP) || (frameBuffer == FB_32BPP)); fbTex.bind(); if (frameBuffer == FB_16BPP) { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, fbBuf.data()); } else { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, fbBuf.data()); } vec2 pos[4] = { vec2(0, height), vec2(width, height), vec2(width, 0 ), vec2(0, 0 ), }; vec2 tex[4] = { vec2(0.0f, texCoordY), vec2(texCoordX, texCoordY), vec2(texCoordX, 0.0f ), vec2(0.0f, 0.0f ), }; gl::context->progTex.activate(); glUniform4f(gl::context->unifTexColor, 1.0f, 1.0f, 1.0f, 1.0f); glUniformMatrix4fv(gl::context->unifTexMvp, 1, GL_FALSE, &gl::context->pixelMvp[0][0]); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, pos); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, tex); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } void SDLGLOutputSurface::clearScreen() { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); } void SDLGLOutputSurface::saveScreenshot( const std::string& filename, unsigned width, unsigned height) { VLA(const void*, rowPointers, height); MemBuffer buffer(width * height * 3); for (unsigned i = 0; i < height; ++i) { rowPointers[height - 1 - i] = &buffer[width * 3 * i]; } glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, buffer.data()); PNG::save(width, height, rowPointers, filename); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/SDLGLOutputSurface.hh000066400000000000000000000021031257557151200222500ustar00rootroot00000000000000#ifndef SDLGLOUTPUTSURFACE_HH #define SDLGLOUTPUTSURFACE_HH #include "GLUtil.hh" #include "MemBuffer.hh" #include "noncopyable.hh" #include namespace openmsx { class OutputSurface; /** This is a common base class for SDLGLVisibleSurface and * SDLGLOffScreenSurface. It's only purpose is to have a place to put common * code. */ class SDLGLOutputSurface : private noncopyable { public: /** These correspond respectively with the renderers: * SDLGL-PP, SDLGL-FB16, SDLGL-FB32 */ enum FrameBuffer { FB_NONE, FB_16BPP, FB_32BPP }; FrameBuffer getFrameBufferType() const { return frameBuffer; } protected: explicit SDLGLOutputSurface(FrameBuffer frameBuffer = FB_NONE); ~SDLGLOutputSurface(); void init(OutputSurface& output); void flushFrameBuffer(unsigned width, unsigned height); void clearScreen(); void saveScreenshot(const std::string& filename, unsigned width, unsigned height); private: float texCoordX, texCoordY; gl::Texture fbTex; MemBuffer fbBuf; const FrameBuffer frameBuffer; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/SDLGLVisibleSurface.cc000066400000000000000000000100201257557151200223300ustar00rootroot00000000000000#include "SDLGLVisibleSurface.hh" #include "SDLGLOffScreenSurface.hh" #include "GLContext.hh" #include "GLSnow.hh" #include "OSDConsoleRenderer.hh" #include "OSDGUILayer.hh" #include "InitException.hh" #include "RenderSettings.hh" #include "memory.hh" namespace openmsx { SDLGLVisibleSurface::SDLGLVisibleSurface( unsigned width, unsigned height, RenderSettings& renderSettings, RTScheduler& rtScheduler, EventDistributor& eventDistributor, InputEventGenerator& inputEventGenerator, CliComm& cliComm, FrameBuffer frameBuffer) : VisibleSurface(renderSettings, rtScheduler, eventDistributor, inputEventGenerator, cliComm) , SDLGLOutputSurface(frameBuffer) { SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0); int flags = SDL_OPENGL | SDL_HWSURFACE | SDL_DOUBLEBUF | (renderSettings.getFullScreen() ? SDL_FULLSCREEN : 0); //flags |= SDL_RESIZABLE; createSurface(width, height, flags); // The created surface may be larger than requested. // If that happens, center the area that we actually use. SDL_Surface* surface = getSDLSurface(); unsigned actualWidth = surface->w; unsigned actualHeight = surface->h; surface->w = width; surface->h = height; setPosition((actualWidth - width ) / 2, (actualHeight - height) / 2); // From the glew documentation: // GLEW obtains information on the supported extensions from the // graphics driver. Experimental or pre-release drivers, however, // might not report every available extension through the standard // mechanism, in which case GLEW will report it unsupported. To // circumvent this situation, the glewExperimental global switch can // be turned on by setting it to GL_TRUE before calling glewInit(), // which ensures that all extensions with valid entry points will be // exposed. // The 'glewinfo' utility also sets this flag before reporting results, // so I believe it would cause less confusion to do the same here. glewExperimental = GL_TRUE; // Initialise GLEW library. // This must happen after GL itself is initialised, which is done by // the SDL_SetVideoMode() call in createSurface(). GLenum glew_error = glewInit(); if (glew_error != GLEW_OK) { throw InitException( "Failed to init GLEW: " + std::string( reinterpret_cast( glewGetErrorString(glew_error)))); } if (!GLEW_VERSION_2_0) { throw InitException("Need at least openGL version 2.0."); } glViewport(getX(), getY(), width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, width, height, 0, -1, 1); glMatrixMode(GL_MODELVIEW); // This stuff logically belongs in the SDLGLOutputSurface constructor, // but it cannot be executed before the openGL context is created which // is done in this constructor. So construction of SDLGLOutputSurface // is split in two phases. SDLGLOutputSurface::init(*this); gl::context = make_unique(width, height); } SDLGLVisibleSurface::~SDLGLVisibleSurface() { gl::context.reset(); } void SDLGLVisibleSurface::flushFrameBuffer() { SDLGLOutputSurface::flushFrameBuffer(getWidth(), getHeight()); } void SDLGLVisibleSurface::clearScreen() { SDLGLOutputSurface::clearScreen(); } void SDLGLVisibleSurface::saveScreenshot(const std::string& filename) { SDLGLOutputSurface::saveScreenshot(filename, getWidth(), getHeight()); } void SDLGLVisibleSurface::finish() { SDL_GL_SwapBuffers(); } std::unique_ptr SDLGLVisibleSurface::createSnowLayer(Display& display) { return make_unique(display); } std::unique_ptr SDLGLVisibleSurface::createConsoleLayer( Reactor& reactor, CommandConsole& console) { const bool openGL = true; return make_unique( reactor, console, getWidth(), getHeight(), openGL); } std::unique_ptr SDLGLVisibleSurface::createOSDGUILayer(OSDGUI& gui) { return make_unique(gui); } std::unique_ptr SDLGLVisibleSurface::createOffScreenSurface() { return make_unique(*this); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/SDLGLVisibleSurface.hh000066400000000000000000000023731257557151200223560ustar00rootroot00000000000000#ifndef SDLGLVISIBLESURFACE_HH #define SDLGLVISIBLESURFACE_HH #include "VisibleSurface.hh" #include "SDLGLOutputSurface.hh" namespace openmsx { /** Visible surface for openGL renderers, both SDLGL-PP and SDLGL-FBxx */ class SDLGLVisibleSurface final : public VisibleSurface , public SDLGLOutputSurface { public: SDLGLVisibleSurface(unsigned width, unsigned height, RenderSettings& renderSettings, RTScheduler& rtScheduler, EventDistributor& eventDistributor, InputEventGenerator& inputEventGenerator, CliComm& cliComm, FrameBuffer frameBuffer = FB_NONE); ~SDLGLVisibleSurface(); private: // OutputSurface void flushFrameBuffer() override; void saveScreenshot(const std::string& filename) override; void clearScreen() override; // VisibleSurface void finish() override; std::unique_ptr createSnowLayer(Display& display) override; std::unique_ptr createConsoleLayer( Reactor& reactor, CommandConsole& console) override; std::unique_ptr createOSDGUILayer(OSDGUI& gui) override; std::unique_ptr createOffScreenSurface() override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/SDLImage.cc000066400000000000000000000454531257557151200202430ustar00rootroot00000000000000#include "SDLImage.hh" #include "PNG.hh" #include "OutputSurface.hh" #include "PixelOperations.hh" #include "MSXException.hh" #include #include #include using std::string; using namespace gl; namespace openmsx { static bool hasConstantAlpha(const SDL_Surface& surface, byte& alpha) { unsigned amask = surface.format->Amask; if (amask == 0) { // If there's no alpha layer, the surface has a constant // opaque alpha value. alpha = SDL_ALPHA_OPAQUE; return true; } // There is an alpha layer, surface must be 32bpp. assert(surface.format->BitsPerPixel == 32); assert(surface.format->Aloss == 0); // Compare alpha from each pixel. Are they all the same? auto* data = reinterpret_cast(surface.pixels); unsigned alpha0 = data[0] & amask; for (int y = 0; y < surface.h; ++y) { auto* p = data + y * (surface.pitch / sizeof(unsigned)); for (int x = 0; x < surface.w; ++x) { if ((p[x] & amask) != alpha0) return false; } } // The alpha value of each pixel is constant, get that value. alpha = alpha0 >> surface.format->Ashift; return true; } static SDLSurfacePtr convertToDisplayFormat(SDLSurfacePtr input) { auto& inFormat = *input->format; auto& outFormat = *SDL_GetVideoSurface()->format; assert((inFormat.BitsPerPixel == 24) || (inFormat.BitsPerPixel == 32)); byte alpha; if (hasConstantAlpha(*input, alpha)) { Uint32 flags = (alpha == SDL_ALPHA_OPAQUE) ? 0 : SDL_SRCALPHA; SDL_SetAlpha(input.get(), flags, alpha); if ((inFormat.BitsPerPixel == outFormat.BitsPerPixel) && (inFormat.Rmask == outFormat.Rmask) && (inFormat.Gmask == outFormat.Gmask) && (inFormat.Bmask == outFormat.Bmask)) { // Already in the correct format. return input; } // 32bpp should rarely need this conversion (only for exotic // pixel formats, not one of RGBA BGRA ARGB ABGR). return SDLSurfacePtr(SDL_DisplayFormat(input.get())); } else { assert(inFormat.Amask != 0); assert(inFormat.BitsPerPixel == 32); if (outFormat.BitsPerPixel != 32) { // We need an alpha channel, so leave the image in 32bpp format. return input; } if ((inFormat.Rmask == outFormat.Rmask) && (inFormat.Gmask == outFormat.Gmask) && (inFormat.Bmask == outFormat.Bmask)) { // Both input and output are 32bpp and both have already // the same pixel format (should almost always be the // case for 32bpp output) return input; } // An exotic 32bpp pixel format (not one of RGBA, BGRA, ARGB, // ABGR). Convert to display format with alpha channel. return SDLSurfacePtr(SDL_DisplayFormatAlpha(input.get())); } } static void zoomSurface(const SDL_Surface* src, SDL_Surface* dst, bool flipX, bool flipY) { assert(src->format->BitsPerPixel == 32); assert(dst->format->BitsPerPixel == 32); PixelOperations pixelOps(*dst->format); // For interpolation: assume source dimension is one pixel // smaller to avoid overflow on right and bottom edge. int sx = int(65536.0f * float(src->w - 1) / float(dst->w)); int sy = int(65536.0f * float(src->h - 1) / float(dst->h)); // Interpolating Zoom, Scan destination auto sp = static_cast(src->pixels); auto dp = static_cast< unsigned*>(dst->pixels); int srcPitch = src->pitch / sizeof(unsigned); int dstPitch = dst->pitch / sizeof(unsigned); if (flipY) dp += (dst->h - 1) * dstPitch; for (int y = 0, csy = 0; y < dst->h; ++y, csy += sy) { sp += (csy >> 16) * srcPitch; const unsigned* c00 = sp; const unsigned* c10 = sp + srcPitch; csy &= 0xffff; if (!flipX) { // not horizontally mirrored for (int x = 0, csx = 0; x < dst->w; ++x, csx += sx) { int sstep = csx >> 16; c00 += sstep; c10 += sstep; csx &= 0xffff; // Interpolate RGBA unsigned t1 = pixelOps.lerp(c00[0], c00[1], (csx >> 8)); unsigned t2 = pixelOps.lerp(c10[0], c10[1], (csx >> 8)); dp[x] = pixelOps.lerp(t1 , t2 , (csy >> 8)); } } else { // horizontally mirrored for (int x = dst->w - 1, csx = 0; x >= 0; --x, csx += sx) { int sstep = csx >> 16; c00 += sstep; c10 += sstep; csx &= 0xffff; // Interpolate RGBA unsigned t1 = pixelOps.lerp(c00[0], c00[1], (csx >> 8)); unsigned t2 = pixelOps.lerp(c10[0], c10[1], (csx >> 8)); dp[x] = pixelOps.lerp(t1 , t2 , (csy >> 8)); } } dp += flipY ? -dstPitch : dstPitch; } } static void getRGBAmasks32(Uint32& rmask, Uint32& gmask, Uint32& bmask, Uint32& amask) { auto& format = *SDL_GetVideoSurface()->format; if ((format.BitsPerPixel == 32) && (format.Rloss == 0) && (format.Gloss == 0) && (format.Bloss == 0)) { rmask = format.Rmask; gmask = format.Gmask; bmask = format.Bmask; // on a display surface Amask is often 0, so instead // we use the bits that are not yet used for RGB //amask = format.Amask; amask = ~(rmask | gmask | bmask); assert((amask == 0x000000ff) || (amask == 0x0000ff00) || (amask == 0x00ff0000) || (amask == 0xff000000)); } else { // ARGB8888 (this seems to be the 'default' format in SDL) amask = 0xff000000; rmask = 0x00ff0000; gmask = 0x0000ff00; bmask = 0x000000ff; } } static SDLSurfacePtr scaleImage32(SDLSurfacePtr input, ivec2 size) { // create a 32 bpp surface that will hold the scaled version auto& format = *input->format; assert(format.BitsPerPixel == 32); SDLSurfacePtr result(abs(size[0]), abs(size[1]), 32, format.Rmask, format.Gmask, format.Bmask, format.Amask); zoomSurface(input.get(), result.get(), size[0] < 0, size[1] < 0); return result; } static SDLSurfacePtr loadImage(const string& filename) { // If the output surface is 32bpp, then always load the PNG as // 32bpp (even if it has no alpha channel). bool want32bpp = SDL_GetVideoSurface()->format->BitsPerPixel == 32; return convertToDisplayFormat(PNG::load(filename, want32bpp)); } static SDLSurfacePtr loadImage(const string& filename, float scaleFactor) { if (scaleFactor == 1.0f) { return loadImage(filename); } bool want32bpp = true; // scaleImage32 needs 32bpp SDLSurfacePtr picture(PNG::load(filename, want32bpp)); ivec2 size = trunc(vec2(picture->w, picture->h) * scaleFactor); BaseImage::checkSize(size); if ((size[0] == 0) || (size[1] == 0)) { return SDLSurfacePtr(); } return convertToDisplayFormat( scaleImage32(std::move(picture), size)); } static SDLSurfacePtr loadImage( const string& filename, ivec2 size) { BaseImage::checkSize(size); if ((size[0] == 0) || (size[1] == 0)) { return SDLSurfacePtr(); } bool want32bpp = true; // scaleImage32 needs 32bpp return convertToDisplayFormat( scaleImage32(PNG::load(filename, want32bpp), size)); } // Helper functions to draw a gradient // Extract R,G,B,A components to 8.16 bit fixed point. // Note the order R,G,B,A is arbitrary, the actual pixel value may have the // components in a different order. static void unpackRGBA(unsigned rgba, unsigned& r, unsigned&g, unsigned&b, unsigned& a) { r = (((rgba >> 24) & 0xFF) << 16) + 0x8000; g = (((rgba >> 16) & 0xFF) << 16) + 0x8000; b = (((rgba >> 8) & 0xFF) << 16) + 0x8000; a = (((rgba >> 0) & 0xFF) << 16) + 0x8000; } // Setup outer loop (vertical) interpolation parameters. // For each component there is a pair of (initial,delta) values. These values // are 8.16 bit fixed point, delta is signed. static void setupInterp1(unsigned rgba0, unsigned rgba1, unsigned length, unsigned& r0, unsigned& g0, unsigned& b0, unsigned& a0, int& dr, int& dg, int& db, int& da) { unpackRGBA(rgba0, r0, g0, b0, a0); if (length == 1) { dr = dg = db = da = 0; } else { unsigned r1, g1, b1, a1; unpackRGBA(rgba1, r1, g1, b1, a1); dr = int(r1 - r0) / int(length - 1); dg = int(g1 - g0) / int(length - 1); db = int(b1 - b0) / int(length - 1); da = int(a1 - a0) / int(length - 1); } } // Setup inner loop (horizontal) interpolation parameters. // - Like above we also output a pair of (initial,delta) values for each // component. But we pack two components in one 32-bit value. This leaves only // 16 bits per component, so now the values are 8.8 bit fixed point. // - To avoid carry/borrow from the lower to the upper pack, we make the lower // component always a positive number and output a boolean to indicate whether // we should add or subtract the delta from the initial value. // - The 8.8 fixed point calculations in the inner loop are less accurate than // the 8.16 calculations in the outer loop. This could result in not 100% // accurate gradients. Though only on very wide images and the error is // so small that it will hardly be visible (if at all). // - Packing 2 components in one value is not beneficial in the outer loop // because in this routine we need the individual components of the values // that are calculated by setupInterp1(). (It would also make the code even // more complex). static void setupInterp2(unsigned r0, unsigned g0, unsigned b0, unsigned a0, unsigned r1, unsigned g1, unsigned b1, unsigned a1, unsigned length, unsigned& rb, unsigned& ga, unsigned& drb, unsigned& dga, bool& subRB, bool& subGA) { // Pack the initial values for the components R,B and G,A into // a vector-type: two 8.16 scalars -> one [8.8 ; 8.8] vector rb = ((r0 << 8) & 0xffff0000) | ((b0 >> 8) & 0x0000ffff); ga = ((g0 << 8) & 0xffff0000) | ((a0 >> 8) & 0x0000ffff); subRB = subGA = false; if (length == 1) { drb = dga = 0; } else { // calculate delta values int dr = int(r1 - r0) / int(length - 1); int dg = int(g1 - g0) / int(length - 1); int db = int(b1 - b0) / int(length - 1); int da = int(a1 - a0) / int(length - 1); if (db < 0) { // make sure db is positive dr = -dr; db = -db; subRB = true; } if (da < 0) { // make sure da is positive dg = -dg; da = -da; subGA = true; } // also pack two 8.16 delta values in one [8.8 ; 8.8] vector drb = ((unsigned(dr) << 8) & 0xffff0000) | ((unsigned(db) >> 8) & 0x0000ffff); dga = ((unsigned(dg) << 8) & 0xffff0000) | ((unsigned(da) >> 8) & 0x0000ffff); } } // Pack two [8.8 ; 8.8] vectors into one pixel. static unsigned packRGBA(unsigned rb, unsigned ga) { return (rb & 0xff00ff00) | ((ga & 0xff00ff00) >> 8); } // Draw a gradient on the given surface. This is a bilinear interpolation // between 4 RGBA colors. One color for each corner, in this order: // 0 -- 1 // | | // 2 -- 3 static void gradient(const unsigned* rgba, SDL_Surface& surface, unsigned borderSize) { int width = surface.w - 2 * borderSize; int height = surface.h - 2 * borderSize; if ((width <= 0) || (height <= 0)) return; unsigned r0, g0, b0, a0; unsigned r1, g1, b1, a1; int dr02, dg02, db02, da02; int dr13, dg13, db13, da13; setupInterp1(rgba[0], rgba[2], height, r0, g0, b0, a0, dr02, dg02, db02, da02); setupInterp1(rgba[1], rgba[3], height, r1, g1, b1, a1, dr13, dg13, db13, da13); auto buffer = static_cast(surface.pixels); buffer += borderSize; buffer += borderSize * (surface.pitch / sizeof(unsigned)); for (int y = 0; y < height; ++y) { unsigned rb, ga; unsigned drb, dga; bool subRB, subGA; setupInterp2(r0, g0, b0, a0, r1, g1, b1, a1, width, rb, ga, drb, dga, subRB, subGA); // Depending on the subRB/subGA booleans, we need to add or // subtract the delta to/from the initial value. There are // 2 booleans so 4 combinations: if (!subRB) { if (!subGA) { for (int x = 0; x < width; ++x) { buffer[x] = packRGBA(rb, ga); rb += drb; ga += dga; } } else { for (int x = 0; x < width; ++x) { buffer[x] = packRGBA(rb, ga); rb += drb; ga -= dga; } } } else { if (!subGA) { for (int x = 0; x < width; ++x) { buffer[x] = packRGBA(rb, ga); rb -= drb; ga += dga; } } else { for (int x = 0; x < width; ++x) { buffer[x] = packRGBA(rb, ga); rb -= drb; ga -= dga; } } } r0 += dr02; g0 += dg02; b0 += db02; a0 += da02; r1 += dr13; g1 += dg13; b1 += db13; a1 += da13; buffer += (surface.pitch / sizeof(unsigned)); } } // class SDLImage SDLImage::SDLImage(const string& filename) : image(loadImage(filename)) , a(-1), flipX(false), flipY(false) { } SDLImage::SDLImage(const std::string& filename, float scaleFactor) : image(loadImage(filename, scaleFactor)) , a(-1), flipX(scaleFactor < 0.0f), flipY(scaleFactor < 0.0f) { } SDLImage::SDLImage(const string& filename, ivec2 size) : image(loadImage(filename, size)) , a(-1), flipX(size[0] < 0), flipY(size[1] < 0) { } SDLImage::SDLImage(ivec2 size, unsigned rgba) : flipX(size[0] < 0), flipY(size[1] < 0) { initSolid(size, rgba, 0, 0); // no border } SDLImage::SDLImage(ivec2 size, const unsigned* rgba, unsigned borderSize, unsigned borderRGBA) : flipX(size[0] < 0), flipY(size[1] < 0) { if ((rgba[0] == rgba[1]) && (rgba[0] == rgba[2]) && (rgba[0] == rgba[3])) { initSolid (size, rgba[0], borderSize, borderRGBA); } else { initGradient(size, rgba, borderSize, borderRGBA); } } static unsigned convertColor(const SDL_PixelFormat& format, unsigned rgba) { return SDL_MapRGBA( #if SDL_VERSION_ATLEAST(1, 2, 12) &format, #else // Work around const correctness bug in SDL 1.2.11 (bug #421). const_cast(&format), #endif (rgba >> 24) & 0xff, (rgba >> 16) & 0xff, (rgba >> 8) & 0xff, (rgba >> 0) & 0xff); } static void drawBorder(SDL_Surface& image, int size, unsigned rgba) { if (size <= 0) return; unsigned color = convertColor(*image.format, rgba); bool onlyBorder = ((2 * size) >= image.w) || ((2 * size) >= image.h); if (onlyBorder) { SDL_FillRect(&image, nullptr, color); } else { // +--------------------+ // | 1 | // +---+------------+---+ // | | | | // | 3 | | 4 | // | | | | // +---+------------+---+ // | 2 | // +--------------------+ SDL_Rect rect; rect.x = 0; rect.y = 0; rect.w = image.w; rect.h = size; SDL_FillRect(&image, &rect, color); // 1 rect.y = image.h - size; SDL_FillRect(&image, &rect, color); // 2 rect.y = size; rect.w = size; rect.h = image.h - 2 * size; SDL_FillRect(&image, &rect, color); // 3 rect.x = image.w - size; SDL_FillRect(&image, &rect, color); // 4 } } void SDLImage::initSolid(ivec2 size, unsigned rgba, unsigned borderSize, unsigned borderRGBA) { checkSize(size); if ((size[0] == 0) || (size[1] == 0)) { // SDL_FillRect crashes on zero-width surfaces, so check for it return; } unsigned bgAlpha = rgba & 0xff; unsigned borderAlpha = borderRGBA & 0xff; if (bgAlpha == borderAlpha) { a = (bgAlpha == 255) ? 256 : bgAlpha; } else { a = -1; } // Figure out required bpp and color masks. Uint32 rmask, gmask, bmask, amask; unsigned bpp; if (a == -1) { // We need an alpha channel. // The SDL documentation doesn't specify this, but I've // checked the implemenation (SDL-1.2.15): // SDL_DisplayFormatAlpha() always returns a 32bpp surface, // also when the current display surface is 16bpp. bpp = 32; getRGBAmasks32(rmask, gmask, bmask, amask); } else { // No alpha channel, copy format of the display surface. SDL_Surface* videoSurface = SDL_GetVideoSurface(); assert(videoSurface); auto& format = *videoSurface->format; bpp = format.BitsPerPixel; rmask = format.Rmask; gmask = format.Gmask; bmask = format.Bmask; amask = 0; } // Create surface with correct size/masks. image = SDLSurfacePtr(abs(size[0]), abs(size[1]), bpp, rmask, gmask, bmask, amask); // draw interior SDL_FillRect(image.get(), nullptr, convertColor(*image->format, rgba)); drawBorder(*image, borderSize, borderRGBA); } void SDLImage::initGradient(ivec2 size, const unsigned* rgba_, unsigned borderSize, unsigned borderRGBA) { checkSize(size); if ((size[0] == 0) || (size[1] == 0)) { return; } unsigned rgba[4]; for (unsigned i = 0; i < 4; ++i) { rgba[i] = rgba_[i]; } if (((rgba[0] & 0xff) == (rgba[1] & 0xff)) && ((rgba[0] & 0xff) == (rgba[2] & 0xff)) && ((rgba[0] & 0xff) == (rgba[3] & 0xff)) && ((rgba[0] & 0xff) == (borderRGBA & 0xff))) { a = rgba[0] & 0xff; } else { a = -1; } if (flipX) { std::swap(rgba[0], rgba[1]); std::swap(rgba[2], rgba[3]); } if (flipY) { std::swap(rgba[0], rgba[2]); std::swap(rgba[1], rgba[3]); } bool needAlphaChannel = a == -1; Uint32 rmask, gmask, bmask, amask; getRGBAmasks32(rmask, gmask, bmask, amask); if (!needAlphaChannel) amask = 0; SDLSurfacePtr tmp32(abs(size[0]), abs(size[1]), 32, rmask, gmask, bmask, amask); for (auto& c : rgba) { c = convertColor(*tmp32->format, c); } gradient(rgba, *tmp32, borderSize); drawBorder(*tmp32, borderSize, borderRGBA); auto& outFormat = *SDL_GetVideoSurface()->format; if ((outFormat.BitsPerPixel == 32) || needAlphaChannel) { if (outFormat.BitsPerPixel == 32) { // for 32bpp the format must match SDL_PixelFormat& inFormat = *tmp32->format; (void)&inFormat; assert(inFormat.Rmask == outFormat.Rmask); assert(inFormat.Gmask == outFormat.Gmask); assert(inFormat.Bmask == outFormat.Bmask); // don't compare Amask } else { // For 16bpp with alpha channel, also create a 32bpp // image surface. See also comments in initSolid(). } image = std::move(tmp32); } else { image.reset(SDL_DisplayFormat(tmp32.get())); } } SDLImage::SDLImage(SDLSurfacePtr image_) : image(std::move(image_)) , a(-1), flipX(false), flipY(false) { } void SDLImage::allocateWorkImage() { int flags = SDL_SWSURFACE; auto& format = *image->format; workImage.reset(SDL_CreateRGBSurface(flags, image->w, image->h, format.BitsPerPixel, format.Rmask, format.Gmask, format.Bmask, 0)); if (!workImage) { throw FatalError("Couldn't allocate SDLImage workimage"); } } void SDLImage::draw(OutputSurface& output, gl::ivec2 pos, byte r, byte g, byte b, byte alpha) { assert(r == 255); (void)r; assert(g == 255); (void)g; assert(b == 255); (void)b; if (!image) return; if (flipX) pos[0] -= image->w; if (flipY) pos[1] -= image->h; output.unlock(); SDL_Surface* outputSurface = output.getSDLSurface(); SDL_Rect rect; rect.x = pos[0]; rect.y = pos[1]; if (a == -1) { if (alpha == 255) { SDL_BlitSurface(image.get(), nullptr, outputSurface, &rect); } else { if (!workImage) allocateWorkImage(); rect.w = image->w; rect.h = image->h; SDL_BlitSurface(outputSurface, &rect, workImage.get(), nullptr); SDL_BlitSurface(image.get(), nullptr, workImage.get(), nullptr); SDL_SetAlpha(workImage.get(), SDL_SRCALPHA, alpha); SDL_BlitSurface(workImage.get(), nullptr, outputSurface, &rect); } } else { SDL_SetAlpha(image.get(), SDL_SRCALPHA, (a * alpha) / 256); SDL_BlitSurface(image.get(), nullptr, outputSurface, &rect); } } ivec2 SDLImage::getSize() const { return image ? ivec2(image->w, image->h) : ivec2(); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/SDLImage.hh000066400000000000000000000021231257557151200202400ustar00rootroot00000000000000#ifndef SDLIMAGE_HH #define SDLIMAGE_HH #include "BaseImage.hh" #include "SDLSurfacePtr.hh" #include namespace openmsx { class SDLImage final : public BaseImage { public: explicit SDLImage(const std::string& filename); explicit SDLImage(SDLSurfacePtr image); SDLImage(const std::string& filename, float scaleFactor); SDLImage(const std::string& filename, gl::ivec2 size); SDLImage(gl::ivec2 size, unsigned rgba); SDLImage(gl::ivec2 size, const unsigned* rgba, unsigned borderSize, unsigned borderRGBA); void draw(OutputSurface& output, gl::ivec2 pos, byte r, byte g, byte b, byte alpha) override; gl::ivec2 getSize() const override; private: void initSolid(gl::ivec2 size, unsigned rgba, unsigned borderSize, unsigned borderRGBA); void initGradient(gl::ivec2 size, const unsigned* rgba, unsigned borderSize, unsigned borderRGBA); void allocateWorkImage(); SDLSurfacePtr image; SDLSurfacePtr workImage; int a; // whole surface alpha value, -1 if per-pixel alpha const bool flipX, flipY; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/SDLOffScreenSurface.cc000066400000000000000000000024631257557151200223760ustar00rootroot00000000000000#include "SDLOffScreenSurface.hh" #include "PNG.hh" #include namespace openmsx { SDLOffScreenSurface::SDLOffScreenSurface(const SDL_Surface& proto) { // SDL_CreateRGBSurface() allocates an internal buffer, on 32-bit // systems this buffer is only 8-bytes aligned. For some scalers (with // SSE(2) optimizations) we need a 16-byte aligned buffer. So now we // allocate the buffer ourselves and create the SDL_Surface with // SDL_CreateRGBSurfaceFrom(). // Of course it would be better to get rid of SDL_Surface in the // OutputSurface interface. setSDLFormat(*proto.format); const SDL_PixelFormat& format = getSDLFormat(); unsigned pitch = proto.w * format.BitsPerPixel / 8; assert((pitch % 16) == 0); unsigned size = pitch * proto.h; buffer.resize(size); memset(buffer.data(), 0, size); surface.reset(SDL_CreateRGBSurfaceFrom( buffer.data(), proto.w, proto.h, format.BitsPerPixel, pitch, format.Rmask, format.Gmask, format.Bmask, format.Amask)); setSDLSurface(surface.get()); setBufferPtr(static_cast(surface->pixels), surface->pitch); } void SDLOffScreenSurface::saveScreenshot(const std::string& filename) { lock(); PNG::save(getSDLSurface(), filename); } void SDLOffScreenSurface::clearScreen() { memset(surface->pixels, 0, surface->pitch * surface->h); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/SDLOffScreenSurface.hh000066400000000000000000000007761257557151200224150ustar00rootroot00000000000000#ifndef SDLOFFSCREENSURFACE_HH #define SDLOFFSCREENSURFACE_HH #include "OutputSurface.hh" #include "SDLSurfacePtr.hh" #include "MemBuffer.hh" namespace openmsx { class SDLOffScreenSurface final : public OutputSurface { public: explicit SDLOffScreenSurface(const SDL_Surface& prototype); private: // OutputSurface void saveScreenshot(const std::string& filename) override; void clearScreen() override; SDLSurfacePtr surface; MemBuffer buffer; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/SDLRasterizer.cc000066400000000000000000000515021257557151200213430ustar00rootroot00000000000000#include "SDLRasterizer.hh" #include "VDP.hh" #include "VDPVRAM.hh" #include "RawFrame.hh" #include "MSXMotherBoard.hh" #include "Display.hh" #include "Renderer.hh" #include "RenderSettings.hh" #include "PostProcessor.hh" #include "FloatSetting.hh" #include "StringSetting.hh" #include "MemoryOps.hh" #include "VisibleSurface.hh" #include "memory.hh" #include "build-info.hh" #include "components.hh" #include #include #include using namespace gl; namespace openmsx { /** VDP ticks between start of line and start of left border. */ static const int TICKS_LEFT_BORDER = 100 + 102; /** The middle of the visible (display + borders) part of a line, * expressed in VDP ticks since the start of the line. * TODO: Move this to a central location? */ static const int TICKS_VISIBLE_MIDDLE = TICKS_LEFT_BORDER + (VDP::TICKS_PER_LINE - TICKS_LEFT_BORDER - 27) / 2; template inline int SDLRasterizer::translateX(int absoluteX, bool narrow) { int maxX = narrow ? 640 : 320; if (absoluteX == VDP::TICKS_PER_LINE) return maxX; // Note: The ROUND_MASK forces the ticks to a pixel (2-tick) boundary. // If this is not done, rounding errors will occur. // This is especially tricky because division of a negative number // is rounded towards zero instead of down. const int ROUND_MASK = narrow ? ~1 : ~3; int screenX = ((absoluteX & ROUND_MASK) - (TICKS_VISIBLE_MIDDLE & ROUND_MASK)) / (narrow ? 2 : 4) + maxX / 2; return std::max(screenX, 0); } template inline void SDLRasterizer::renderBitmapLine(Pixel* buf, unsigned vramLine) { if (vdp.getDisplayMode().isPlanar()) { const byte* vramPtr0; const byte* vramPtr1; vram.bitmapCacheWindow.getReadAreaPlanar( vramLine * 256, 256, vramPtr0, vramPtr1); bitmapConverter.convertLinePlanar(buf, vramPtr0, vramPtr1); } else { const byte* vramPtr = vram.bitmapCacheWindow.getReadArea(vramLine * 128, 128); bitmapConverter.convertLine(buf, vramPtr); } } template SDLRasterizer::SDLRasterizer( VDP& vdp_, Display& display, VisibleSurface& screen_, std::unique_ptr postProcessor_) : vdp(vdp_), vram(vdp.getVRAM()) , screen(screen_) , postProcessor(std::move(postProcessor_)) , workFrame(make_unique(screen.getSDLFormat(), 640, 240)) , renderSettings(display.getRenderSettings()) , characterConverter(vdp, palFg, palBg) , bitmapConverter(palFg, PALETTE256, V9958_COLORS) , spriteConverter(vdp.getSpriteChecker()) { // Init the palette. precalcPalette(); // Initialize palette (avoid UMR) if (!vdp.isMSX1VDP()) { for (int i = 0; i < 16; ++i) { palFg[i] = palFg[i + 16] = palBg[i] = V9938_COLORS[0][0][0]; } } renderSettings.getGammaSetting() .attach(*this); renderSettings.getBrightnessSetting() .attach(*this); renderSettings.getContrastSetting() .attach(*this); renderSettings.getColorMatrixSetting().attach(*this); } template SDLRasterizer::~SDLRasterizer() { renderSettings.getColorMatrixSetting().detach(*this); renderSettings.getGammaSetting() .detach(*this); renderSettings.getBrightnessSetting() .detach(*this); renderSettings.getContrastSetting() .detach(*this); } template PostProcessor* SDLRasterizer::getPostProcessor() const { return postProcessor.get(); } template bool SDLRasterizer::isActive() { return postProcessor->needRender() && vdp.getMotherBoard().isActive() && !vdp.getMotherBoard().isFastForwarding(); } template void SDLRasterizer::reset() { // Init renderer state. setDisplayMode(vdp.getDisplayMode()); spriteConverter.setTransparency(vdp.getTransparency()); resetPalette(); } template void SDLRasterizer::resetPalette() { if (!vdp.isMSX1VDP()) { // Reset the palette. for (int i = 0; i < 16; i++) { setPalette(i, vdp.getPalette(i)); } } } template void SDLRasterizer::setSuperimposeVideoFrame(const RawFrame* videoSource) { postProcessor->setSuperimposeVideoFrame(videoSource); precalcColorIndex0(vdp.getDisplayMode(), vdp.getTransparency(), videoSource, vdp.getBackgroundColor()); } template void SDLRasterizer::frameStart(EmuTime::param time) { workFrame = postProcessor->rotateFrames(std::move(workFrame), time); workFrame->init( vdp.isInterlaced() ? (vdp.getEvenOdd() ? FrameSource::FIELD_ODD : FrameSource::FIELD_EVEN) : FrameSource::FIELD_NONINTERLACED); // Calculate line to render at top of screen. // Make sure the display area is centered. // 240 - 212 = 28 lines available for top/bottom border; 14 each. // NTSC: display at [32..244), // PAL: display at [59..271). lineRenderTop = vdp.isPalTiming() ? 59 - 14 : 32 - 14; // We haven't drawn any left/right borders yet this frame, thus so far // all is still consistent (same settings for all left/right borders). mixedLeftRightBorders = false; auto& borderInfo = workFrame->getBorderInfo(); Pixel color0, color1; getBorderColors(color0, color1); canSkipLeftRightBorders = (borderInfo.mode == vdp.getDisplayMode().getByte()) && (borderInfo.color0 == color0) && (borderInfo.color1 == color1) && (borderInfo.adjust == vdp.getHorizontalAdjust()) && (borderInfo.scroll == vdp.getHorizontalScrollLow()) && (borderInfo.masked == vdp.isBorderMasked()); } template void SDLRasterizer::frameEnd() { auto& borderInfo = workFrame->getBorderInfo(); if (mixedLeftRightBorders) { // This frame contains left/right borders drawn with different // settings. So don't use it as a starting point for future // border drawing optimizations. borderInfo.mode = 0xff; // invalid mode, other fields don't matter } else { // All left/right borders in this frame are uniform (drawn with // the same settings). If in a later frame the border-related // settings are still the same, we can skip drawing borders. Pixel color0, color1; getBorderColors(color0, color1); borderInfo.mode = vdp.getDisplayMode().getByte(); borderInfo.color0 = color0; borderInfo.color1 = color1; borderInfo.adjust = vdp.getHorizontalAdjust(); borderInfo.scroll = vdp.getHorizontalScrollLow(); borderInfo.masked = vdp.isBorderMasked(); } } template void SDLRasterizer::borderSettingChanged() { // Can no longer use the skip-border drawing optimization this frame. canSkipLeftRightBorders = false; // Cannot use this frame as a starting point for future skip-border // optimizations. mixedLeftRightBorders = true; } template void SDLRasterizer::setDisplayMode(DisplayMode mode) { if (mode.isBitmapMode()) { bitmapConverter.setDisplayMode(mode); } else { characterConverter.setDisplayMode(mode); } precalcColorIndex0(mode, vdp.getTransparency(), vdp.isSuperimposing(), vdp.getBackgroundColor()); spriteConverter.setDisplayMode(mode); spriteConverter.setPalette(mode.getByte() == DisplayMode::GRAPHIC7 ? palGraphic7Sprites : palBg); borderSettingChanged(); } template void SDLRasterizer::setPalette(int index, int grb) { // Update SDL colors in palette. Pixel newColor = V9938_COLORS[(grb >> 4) & 7][grb >> 8][grb & 7]; palFg[index ] = newColor; palFg[index + 16] = newColor; palBg[index ] = newColor; bitmapConverter.palette16Changed(); precalcColorIndex0(vdp.getDisplayMode(), vdp.getTransparency(), vdp.isSuperimposing(), vdp.getBackgroundColor()); borderSettingChanged(); } template void SDLRasterizer::setBackgroundColor(int index) { if (vdp.getDisplayMode().getByte() != DisplayMode::GRAPHIC7) { precalcColorIndex0(vdp.getDisplayMode(), vdp.getTransparency(), vdp.isSuperimposing(), index); } borderSettingChanged(); } template void SDLRasterizer::setHorizontalAdjust(int /*adjust*/) { borderSettingChanged(); } template void SDLRasterizer::setHorizontalScrollLow(byte /*scroll*/) { borderSettingChanged(); } template void SDLRasterizer::setBorderMask(bool /*masked*/) { borderSettingChanged(); } template void SDLRasterizer::setTransparency(bool enabled) { spriteConverter.setTransparency(enabled); precalcColorIndex0(vdp.getDisplayMode(), enabled, vdp.isSuperimposing(), vdp.getBackgroundColor()); } template void SDLRasterizer::precalcPalette() { if (vdp.isMSX1VDP()) { // Fixed palette. for (int i = 0; i < 16; ++i) { const byte* rgb = vdp.hasToshibaPalette() ? Renderer::TOSHIBA_PALETTE[i] : Renderer::TMS99X8A_PALETTE[i]; palFg[i] = palFg[i + 16] = palBg[i] = screen.mapKeyedRGB( renderSettings.transformRGB( vec3(rgb[0], rgb[1], rgb[2]) / 255.0f)); } } else { if (vdp.hasYJK()) { // Precalculate palette for V9958 colors. if (renderSettings.isColorMatrixIdentity()) { // Most users use the "normal" monitor type; making this a // special case speeds up palette precalculation a lot. int intensity[32]; for (int i = 0; i < 32; ++i) { intensity[i] = int(255 * renderSettings.transformComponent(i / 31.0)); } for (int rgb = 0; rgb < (1 << 15); ++rgb) { V9958_COLORS[rgb] = screen.mapKeyedRGB255(ivec3( intensity[(rgb >> 10) & 31], intensity[(rgb >> 5) & 31], intensity[(rgb >> 0) & 31])); } } else { for (int r = 0; r < 32; ++r) { for (int g = 0; g < 32; ++g) { for (int b = 0; b < 32; ++b) { V9958_COLORS[(r << 10) + (g << 5) + b] = screen.mapKeyedRGB( renderSettings.transformRGB( vec3(r, g, b) / 31.0f)); } } } } // Precalculate palette for V9938 colors. // Based on comparing red and green gradients, using palette and // YJK, in SCREEN11 on a real turbo R. for (int r3 = 0; r3 < 8; ++r3) { int r5 = (r3 << 2) | (r3 >> 1); for (int g3 = 0; g3 < 8; ++g3) { int g5 = (g3 << 2) | (g3 >> 1); for (int b3 = 0; b3 < 8; ++b3) { int b5 = (b3 << 2) | (b3 >> 1); V9938_COLORS[r3][g3][b3] = V9958_COLORS[(r5 << 10) + (g5 << 5) + b5]; } } } } else { // Precalculate palette for V9938 colors. if (renderSettings.isColorMatrixIdentity()) { int intensity[8]; for (int i = 0; i < 8; ++i) { intensity[i] = int(255 * renderSettings.transformComponent(i / 7.0f)); } for (int r = 0; r < 8; ++r) { for (int g = 0; g < 8; ++g) { for (int b = 0; b < 8; ++b) { V9938_COLORS[r][g][b] = screen.mapKeyedRGB255(ivec3( intensity[r], intensity[g], intensity[b])); } } } } else { for (int r = 0; r < 8; ++r) { for (int g = 0; g < 8; ++g) { for (int b = 0; b < 8; ++b) { V9938_COLORS[r][g][b] = screen.mapKeyedRGB( renderSettings.transformRGB( vec3(r, g, b) / 7.0f));; } } } } } // Precalculate Graphic 7 bitmap palette. for (int i = 0; i < 256; ++i) { PALETTE256[i] = V9938_COLORS [(i & 0x1C) >> 2] [(i & 0xE0) >> 5] [(i & 0x03) == 3 ? 7 : (i & 0x03) * 2]; } // Precalculate Graphic 7 sprite palette. for (int i = 0; i < 16; ++i) { uint16_t grb = Renderer::GRAPHIC7_SPRITE_PALETTE[i]; palGraphic7Sprites[i] = V9938_COLORS[(grb >> 4) & 7][grb >> 8][grb & 7]; } } } template void SDLRasterizer::precalcColorIndex0(DisplayMode mode, bool transparency, const RawFrame* superimposing, byte bgcolorIndex) { // Graphic7 mode doesn't use transparency. if (mode.getByte() == DisplayMode::GRAPHIC7) { transparency = false; } int tpIndex = transparency ? bgcolorIndex : 0; if (mode.getBase() != DisplayMode::GRAPHIC5) { Pixel c = (superimposing && (bgcolorIndex == 0)) ? screen.getKeyColor() : palBg[tpIndex]; if (palFg[0] != c) { palFg[0] = c; bitmapConverter.palette16Changed(); } } else { // TODO: superimposing if ((palFg[ 0] != palBg[tpIndex >> 2]) || (palFg[16] != palBg[tpIndex & 3])) { palFg[ 0] = palBg[tpIndex >> 2]; palFg[16] = palBg[tpIndex & 3]; bitmapConverter.palette16Changed(); } } } template void SDLRasterizer::getBorderColors(Pixel& border0, Pixel& border1) { DisplayMode mode = vdp.getDisplayMode(); int bgColor = vdp.getBackgroundColor(); if (mode.getBase() == DisplayMode::GRAPHIC5) { // border in SCREEN6 has separate color for even and odd pixels. // TODO odd/even swapped? border0 = palBg[(bgColor & 0x0C) >> 2]; border1 = palBg[(bgColor & 0x03) >> 0]; } else if (mode.getByte() == DisplayMode::GRAPHIC7) { border0 = border1 = PALETTE256[bgColor]; } else { if (!bgColor && vdp.isSuperimposing()) { border0 = border1 = screen.getKeyColor(); } else { border0 = border1 = palBg[bgColor]; } } } template void SDLRasterizer::drawBorder( int fromX, int fromY, int limitX, int limitY) { Pixel border0, border1; getBorderColors(border0, border1); int startY = std::max(fromY - lineRenderTop, 0); int endY = std::min(limitY - lineRenderTop, 240); if ((fromX == 0) && (limitX == VDP::TICKS_PER_LINE) && (border0 == border1)) { // complete lines, non striped for (int y = startY; y < endY; y++) { workFrame->setBlank(y, border0); // setBlank() implies this line is not suitable // for left/right border optimization in a later // frame. } } else { unsigned lineWidth = vdp.getDisplayMode().getLineWidth(); unsigned x = translateX(fromX, (lineWidth == 512)); unsigned num = translateX(limitX, (lineWidth == 512)) - x; unsigned width = (lineWidth == 512) ? 640 : 320; MemoryOps::MemSet2 memset; for (int y = startY; y < endY; ++y) { // workFrame->linewidth != 1 means the line has // left/right borders. if (canSkipLeftRightBorders && (workFrame->getLineWidthDirect(y) != 1)) continue; memset(workFrame->getLinePtrDirect(y) + x, num, border0, border1); if (limitX == VDP::TICKS_PER_LINE) { // Only set line width at the end (right // border) of the line. This ensures we can // keep testing the width of the previous // version of this line for all (partial) // updates of this line. workFrame->setLineWidth(y, width); } } } } template void SDLRasterizer::drawDisplay( int /*fromX*/, int fromY, int displayX, int displayY, int displayWidth, int displayHeight) { // Note: we don't call workFrame->setLineWidth() because that's done in // drawBorder() (for the right border). And the value we set there is // anyway the same as the one we would set here. DisplayMode mode = vdp.getDisplayMode(); unsigned lineWidth = mode.getLineWidth(); if (lineWidth == 256) { int endX = displayX + displayWidth; displayX /= 2; displayWidth = endX / 2 - displayX; } // Clip to screen area. int screenLimitY = std::min( fromY + displayHeight - lineRenderTop, 240); int screenY = fromY - lineRenderTop; if (screenY < 0) { displayY -= screenY; fromY = lineRenderTop; screenY = 0; } displayHeight = screenLimitY - screenY; if (displayHeight <= 0) return; int leftBackground = translateX(vdp.getLeftBackground(), lineWidth == 512); // TODO: Find out why this causes 1-pixel jitter: //dest.x = translateX(fromX); int hScroll = mode.isTextMode() ? 0 : 8 * (lineWidth / 256) * (vdp.getHorizontalScrollHigh() & 0x1F); // Page border is display X coordinate where to stop drawing current page. // This is either the multi page split point, or the right edge of the // rectangle to draw, whichever comes first. // Note that it is possible for pageBorder to be to the left of displayX, // in that case only the second page should be drawn. int pageBorder = displayX + displayWidth; int scrollPage1, scrollPage2; if (vdp.isMultiPageScrolling()) { scrollPage1 = vdp.getHorizontalScrollHigh() >> 5; scrollPage2 = scrollPage1 ^ 1; } else { scrollPage1 = 0; scrollPage2 = 0; } // Because SDL blits do not wrap, unlike GL textures, the pageBorder is // also used if multi page is disabled. int pageSplit = lineWidth - hScroll; if (pageSplit < pageBorder) { pageBorder = pageSplit; } if (mode.isBitmapMode()) { // Which bits in the name mask determine the page? int pageMaskOdd = (mode.isPlanar() ? 0x000 : 0x200) | vdp.getEvenOddMask(); int pageMaskEven = vdp.isMultiPageScrolling() ? (pageMaskOdd & ~0x100) : pageMaskOdd; for (int y = screenY; y < screenLimitY; y++) { const int vramLine[2] = { (vram.nameTable.getMask() >> 7) & (pageMaskEven | displayY), (vram.nameTable.getMask() >> 7) & (pageMaskOdd | displayY) }; Pixel buf[512]; int lineInBuf = -1; // buffer data not valid Pixel* dst = workFrame->getLinePtrDirect(y) + leftBackground + displayX; int firstPageWidth = pageBorder - displayX; if (firstPageWidth > 0) { if ((displayX + hScroll) == 0) { renderBitmapLine(dst, vramLine[scrollPage1]); } else { lineInBuf = vramLine[scrollPage1]; renderBitmapLine(buf, vramLine[scrollPage1]); const Pixel* src = buf + displayX + hScroll; memcpy(dst, src, firstPageWidth * sizeof(Pixel)); } } else { firstPageWidth = 0; } if (firstPageWidth < displayWidth) { if (lineInBuf != vramLine[scrollPage2]) { renderBitmapLine(buf, vramLine[scrollPage2]); } unsigned x = displayX < pageBorder ? 0 : displayX + hScroll - lineWidth; memcpy(dst + firstPageWidth, buf + x, (displayWidth - firstPageWidth) * sizeof(Pixel)); } displayY = (displayY + 1) & 255; } } else { // horizontal scroll (high) is implemented in CharacterConverter for (int y = screenY; y < screenLimitY; y++) { assert(!vdp.isMSX1VDP() || displayY < 192); Pixel* dst = workFrame->getLinePtrDirect(y) + leftBackground + displayX; if (displayX == 0) { characterConverter.convertLine(dst, displayY); } else { Pixel buf[512]; characterConverter.convertLine(buf, displayY); const Pixel* src = buf + displayX; memcpy(dst, src, displayWidth * sizeof(Pixel)); } displayY = (displayY + 1) & 255; } } } template void SDLRasterizer::drawSprites( int /*fromX*/, int fromY, int displayX, int displayY, int displayWidth, int displayHeight) { // Clip to screen area. // TODO: Code duplicated from drawDisplay. int screenLimitY = std::min( fromY + displayHeight - lineRenderTop, 240); int screenY = fromY - lineRenderTop; if (screenY < 0) { displayY -= screenY; fromY = lineRenderTop; screenY = 0; } displayHeight = screenLimitY - screenY; if (displayHeight <= 0) return; // Render sprites. // TODO: Call different SpriteConverter methods depending on narrow/wide // pixels in this display mode? int spriteMode = vdp.getDisplayMode().getSpriteMode(vdp.isMSX1VDP()); int displayLimitX = displayX + displayWidth; int limitY = fromY + displayHeight; int screenX = translateX( vdp.getLeftSprites(), vdp.getDisplayMode().getLineWidth() == 512); if (spriteMode == 1) { for (int y = fromY; y < limitY; y++, screenY++) { Pixel* pixelPtr = workFrame->getLinePtrDirect(screenY) + screenX; spriteConverter.drawMode1(y, displayX, displayLimitX, pixelPtr); } } else { byte mode = vdp.getDisplayMode().getByte(); if (mode == DisplayMode::GRAPHIC5) { for (int y = fromY; y < limitY; y++, screenY++) { Pixel* pixelPtr = workFrame->getLinePtrDirect(screenY) + screenX; spriteConverter.template drawMode2( y, displayX, displayLimitX, pixelPtr); } } else if (mode == DisplayMode::GRAPHIC6) { for (int y = fromY; y < limitY; y++, screenY++) { Pixel* pixelPtr = workFrame->getLinePtrDirect(screenY) + screenX; spriteConverter.template drawMode2( y, displayX, displayLimitX, pixelPtr); } } else { for (int y = fromY; y < limitY; y++, screenY++) { Pixel* pixelPtr = workFrame->getLinePtrDirect(screenY) + screenX; spriteConverter.template drawMode2( y, displayX, displayLimitX, pixelPtr); } } } } template bool SDLRasterizer::isRecording() const { return postProcessor->isRecording(); } template void SDLRasterizer::update(const Setting& setting) { if ((&setting == &renderSettings.getGammaSetting()) || (&setting == &renderSettings.getBrightnessSetting()) || (&setting == &renderSettings.getContrastSetting()) || (&setting == &renderSettings.getColorMatrixSetting())) { precalcPalette(); resetPalette(); } } // Force template instantiation. #if HAVE_16BPP template class SDLRasterizer; #endif #if HAVE_32BPP || COMPONENT_GL template class SDLRasterizer; #endif } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/SDLRasterizer.hh000066400000000000000000000123321257557151200213530ustar00rootroot00000000000000#ifndef SDLRASTERIZER_HH #define SDLRASTERIZER_HH #include "Rasterizer.hh" #include "BitmapConverter.hh" #include "CharacterConverter.hh" #include "SpriteConverter.hh" #include "Observer.hh" #include "openmsx.hh" #include "noncopyable.hh" #include namespace openmsx { class Display; class VDP; class VDPVRAM; class OutputSurface; class VisibleSurface; class RawFrame; class RenderSettings; class Setting; class PostProcessor; /** Rasterizer using a frame buffer approach: it writes pixels to a single * rectangular pixel buffer. */ template class SDLRasterizer final : public Rasterizer, private noncopyable , private Observer { public: SDLRasterizer( VDP& vdp, Display& display, VisibleSurface& screen, std::unique_ptr postProcessor); ~SDLRasterizer(); // Rasterizer interface: PostProcessor* getPostProcessor() const override; bool isActive() override; void reset() override; void frameStart(EmuTime::param time) override; void frameEnd() override; void setDisplayMode(DisplayMode mode) override; void setPalette(int index, int grb) override; void setBackgroundColor(int index) override; void setHorizontalAdjust(int adjust) override; void setHorizontalScrollLow(byte scroll) override; void setBorderMask(bool masked) override; void setTransparency(bool enabled) override; void setSuperimposeVideoFrame(const RawFrame* videoSource) override; void drawBorder(int fromX, int fromY, int limitX, int limitY) override; void drawDisplay( int fromX, int fromY, int displayX, int displayY, int displayWidth, int displayHeight) override; void drawSprites( int fromX, int fromY, int displayX, int displayY, int displayWidth, int displayHeight) override; bool isRecording() const override; private: /** Translate from absolute VDP coordinates to screen coordinates: * Note: In reality, there are only 569.5 visible pixels on a line. * Because it looks better, the borders are extended to 640. * @param absoluteX Absolute VDP coordinate. * @param narrow Is this a narrow (512 pixels wide) display mode? */ inline static int translateX(int absoluteX, bool narrow); inline void renderBitmapLine(Pixel* buf, unsigned vramLine); /** Reload entire palette from VDP. */ void resetPalette(); /** Precalc palette values. * For MSX1 VDPs, results go directly into palFg/palBg. * For higher VDPs, results go into V9938_COLORS and V9958_COLORS. */ void precalcPalette(); /** Precalc foreground color index 0 (palFg[0]). * @param mode Current display mode. * @param transparency True iff transparency is enabled. */ void precalcColorIndex0(DisplayMode mode, bool transparency, const RawFrame* superimposing, byte bgcolorIndex); // Some of the border-related settings changed. void borderSettingChanged(); // Get the border color(s). These are 16bpp or 32bpp host pixels. void getBorderColors(Pixel& border0, Pixel& border1); // Observer void update(const Setting& setting) override; /** The VDP of which the video output is being rendered. */ VDP& vdp; /** The VRAM whose contents are rendered. */ VDPVRAM& vram; /** The surface which is visible to the user. */ OutputSurface& screen; /** The video post processor which displays the frames produced by this * rasterizer. */ const std::unique_ptr postProcessor; /** The next frame as it is delivered by the VDP, work in progress. */ std::unique_ptr workFrame; /** The current renderer settings (gamma, brightness, contrast) */ RenderSettings& renderSettings; /** VRAM to pixels converter for character display modes. */ CharacterConverter characterConverter; /** VRAM to pixels converter for bitmap display modes. */ BitmapConverter bitmapConverter; /** VRAM to pixels converter for sprites. */ SpriteConverter spriteConverter; /** Line to render at top of display. * After all, our screen is 240 lines while display is 262 or 313. */ int lineRenderTop; /** Host colors corresponding to each VDP palette entry. * palFg has entry 0 set to the current background color. * The 16 first entries are for even pixels, the next 16 are for * odd pixels. Second part is only needed (and guaranteed to be * up-to-date) in Graphics5 mode. * palBg has entry 0 set to black. */ Pixel palFg[16 * 2], palBg[16]; /** Host colors corresponding to each Graphic 7 sprite color. */ Pixel palGraphic7Sprites[16]; /** Precalculated host colors corresponding to each possible V9938 color. * Used by updatePalette to adjust palFg and palBg. */ Pixel V9938_COLORS[8][8][8]; /** Host colors corresponding to the 256 color palette of Graphic7. * Used by BitmapConverter. */ Pixel PALETTE256[256]; /** Host colors corresponding to each possible V9958 color. */ Pixel V9958_COLORS[32768]; // True iff left/right border optimization can (still) be applied // this frame. bool canSkipLeftRightBorders; // True iff some of the left/right border related settings changed // during this frame (meaning the border pixels of this frame cannot // be reused for future frames). bool mixedLeftRightBorders; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/SDLSnow.cc000066400000000000000000000023741257557151200201420ustar00rootroot00000000000000#include "SDLSnow.hh" #include "OutputSurface.hh" #include "Display.hh" #include "build-info.hh" #include "random.hh" #include #include namespace openmsx { template SDLSnow::SDLSnow(OutputSurface& output, Display& display_) : Layer(COVER_FULL, Z_BACKGROUND) , display(display_) { // Precalc gray values for noise for (int i = 0; i < 256; ++i) { gray[i] = output.mapRGB255(gl::ivec3(i)); } } template void SDLSnow::paint(OutputSurface& output) { auto& generator = global_urng(); // fast (non-cryptographic) random numbers std::uniform_int_distribution distribution(0, 255); output.lock(); const unsigned width = output.getWidth(); const unsigned height = output.getHeight(); for (unsigned y = 0; y < height; y += 2) { Pixel* p0 = output.getLinePtrDirect(y + 0); Pixel* p1 = output.getLinePtrDirect(y + 1); for (unsigned x = 0; x < width; x += 2) { p0[x + 0] = p0[x + 1] = gray[distribution(generator)]; } memcpy(p1, p0, width * sizeof(Pixel)); } display.repaintDelayed(100 * 1000); // 10fps } // Force template instantiation. #if HAVE_16BPP template class SDLSnow; #endif #if HAVE_32BPP template class SDLSnow; #endif } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/SDLSnow.hh000066400000000000000000000007771257557151200201610ustar00rootroot00000000000000#ifndef SDLSNOW_HH #define SDLSNOW_HH #include "Layer.hh" #include "noncopyable.hh" namespace openmsx { class OutputSurface; class Display; /** Snow effect for background layer. */ template class SDLSnow final : public Layer, private noncopyable { public: SDLSnow(OutputSurface& output, Display& display); // Layer interface: void paint(OutputSurface& output) override; private: Display& display; /** Gray values for noise. */ Pixel gray[256]; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/SDLSurfacePtr.hh000066400000000000000000000064551257557151200213100ustar00rootroot00000000000000#ifndef SDLSURFACEPTR #define SDLSURFACEPTR #include "MemBuffer.hh" #include #include #include #include #include /** Wrapper around a SDL_Surface. * * Makes sure SDL_FreeSurface() is called when this object goes out of scope. * It's modeled after std::unique_ptr, so it has the usual get(), reset() and * release() methods. Like unique_ptr it can be moved but not copied. * * In addition to the SDL_Surface pointer, this wrapper also (optionally) * manages an extra memory buffer. Normally SDL_CreateRGBSurface() will * allocate/free an internal memory buffer for the surface. On construction * of the surface this buffer will be zero-initialized. Though in many cases * the surface will immediately be overwritten (so zero-initialization is * only extra overhead). It's possible to avoid this by creating the surface * using SDL_CreateRGBSurfaceFrom(). Though the downside of this is that you * have to manage the lifetime of the memory buffer yourself. And that's * exactly what this wrapper can do. * * As a bonus this wrapper has a getLinePtr() method to hide some of the * casting. But apart from this it doesn't try to abstract any SDL * functionality. */ class SDLSurfacePtr { public: /** Create a (software) surface with uninitialized pixel content. * throws: bad_alloc (no need to check for nullptr). */ SDLSurfacePtr(unsigned width, unsigned height, unsigned depth, Uint32 rMask, Uint32 gMask, Uint32 bMask, Uint32 aMask) { assert((depth % 8) == 0); unsigned pitch = width * (depth >> 3); unsigned size = height * pitch; buffer.resize(size); surface = SDL_CreateRGBSurfaceFrom( buffer.data(), width, height, depth, pitch, rMask, gMask, bMask, aMask); if (!surface) throw std::bad_alloc(); } // don't allow copy and assign // msvc doesn't yet support deleted functions. SDLSurfacePtr(const SDLSurfacePtr&) /*= delete*/; SDLSurfacePtr& operator=(const SDLSurfacePtr&) /*= delete*/; explicit SDLSurfacePtr(SDL_Surface* surface_ = nullptr) : surface(surface_) { } SDLSurfacePtr(SDLSurfacePtr&& other) : surface(other.surface) , buffer(std::move(other.buffer)) { other.surface = nullptr; } ~SDLSurfacePtr() { if (surface) SDL_FreeSurface(surface); } void reset(SDL_Surface* surface_ = nullptr) { SDLSurfacePtr temp(surface_); temp.swap(*this); } SDL_Surface* get() { return surface; } const SDL_Surface* get() const { return surface; } void swap(SDLSurfacePtr& other) { std::swap(surface, other.surface); std::swap(buffer, other.buffer ); } SDLSurfacePtr& operator=(SDLSurfacePtr&& other) { std::swap(surface, other.surface); std::swap(buffer, other.buffer); return *this; } SDL_Surface& operator*() { return *surface; } const SDL_Surface& operator*() const { return *surface; } SDL_Surface* operator->() { return surface; } const SDL_Surface* operator->() const { return surface; } explicit operator bool() const { return get() != nullptr; } void* getLinePtr(unsigned y) { assert(y < unsigned(surface->h)); return static_cast(surface->pixels) + y * surface->pitch; } const void* getLinePtr(unsigned y) const { return const_cast(this)->getLinePtr(y); } private: SDL_Surface* surface; openmsx::MemBuffer buffer; }; #endif openMSX-RELEASE_0_12_0/src/video/SDLVideoSystem.cc000066400000000000000000000221751257557151200214700ustar00rootroot00000000000000#include "SDLVideoSystem.hh" #include "SDLVisibleSurface.hh" #include "SDLRasterizer.hh" #include "V9990SDLRasterizer.hh" #include "FBPostProcessor.hh" #include "Reactor.hh" #include "Display.hh" #include "RenderSettings.hh" #include "BooleanSetting.hh" #include "EnumSetting.hh" #include "IntegerSetting.hh" #include "EventDistributor.hh" #include "InputEventGenerator.hh" #include "VDP.hh" #include "V9990.hh" #include "build-info.hh" #include "unreachable.hh" #include "memory.hh" #include #ifdef _WIN32 #include "AltSpaceSuppressor.hh" #include "win32-windowhandle.hh" #endif #include "components.hh" #if COMPONENT_GL #include "SDLGLVisibleSurface.hh" #include "GLPostProcessor.hh" #endif #if COMPONENT_LASERDISC #include "LaserdiscPlayer.hh" #include "LDSDLRasterizer.hh" #endif namespace openmsx { SDLVideoSystem::SDLVideoSystem(Reactor& reactor, CommandConsole& console) : reactor(reactor) , display(reactor.getDisplay()) , renderSettings(reactor.getDisplay().getRenderSettings()) { resize(); consoleLayer = screen->createConsoleLayer(reactor, console); snowLayer = screen->createSnowLayer(display); osdGuiLayer = screen->createOSDGUILayer(display.getOSDGUI()); display.addLayer(*consoleLayer); display.addLayer(*snowLayer); display.addLayer(*osdGuiLayer); renderSettings.getScaleFactorSetting().attach(*this); reactor.getEventDistributor().registerEventListener( OPENMSX_RESIZE_EVENT, *this); #ifdef _WIN32 HWND hWnd = getWindowHandle(); assert(hWnd); AltSpaceSuppressor::Start(hWnd); #endif } SDLVideoSystem::~SDLVideoSystem() { #ifdef _WIN32 // This needs to be done while the SDL window handle is still valid assert(getWindowHandle()); AltSpaceSuppressor::Stop(); #endif reactor.getEventDistributor().unregisterEventListener( OPENMSX_RESIZE_EVENT, *this); renderSettings.getScaleFactorSetting().detach(*this); display.removeLayer(*osdGuiLayer); display.removeLayer(*snowLayer); display.removeLayer(*consoleLayer); } std::unique_ptr SDLVideoSystem::createRasterizer(VDP& vdp) { std::string videoSource = (vdp.getName() == "VDP") ? "MSX" // for backwards compatibility : vdp.getName(); auto& motherBoard = vdp.getMotherBoard(); switch (renderSettings.getRenderer()) { case RenderSettings::SDL: case RenderSettings::SDLGL_FB16: case RenderSettings::SDLGL_FB32: switch (screen->getSDLFormat().BytesPerPixel) { #if HAVE_16BPP case 2: return make_unique>( vdp, display, *screen, make_unique>( motherBoard, display, *screen, videoSource, 640, 240, true)); #endif #if HAVE_32BPP case 4: return make_unique>( vdp, display, *screen, make_unique>( motherBoard, display, *screen, videoSource, 640, 240, true)); #endif default: UNREACHABLE; return nullptr; } #if COMPONENT_GL case RenderSettings::SDLGL_PP: return make_unique>( vdp, display, *screen, make_unique( motherBoard, display, *screen, videoSource, 640, 240, true)); #endif default: UNREACHABLE; return nullptr; } } std::unique_ptr SDLVideoSystem::createV9990Rasterizer( V9990& vdp) { std::string videoSource = (vdp.getName() == "Sunrise GFX9000") ? "GFX9000" // for backwards compatibility : vdp.getName(); MSXMotherBoard& motherBoard = vdp.getMotherBoard(); switch (renderSettings.getRenderer()) { case RenderSettings::SDL: case RenderSettings::SDLGL_FB16: case RenderSettings::SDLGL_FB32: switch (screen->getSDLFormat().BytesPerPixel) { #if HAVE_16BPP case 2: return make_unique>( vdp, display, *screen, make_unique>( motherBoard, display, *screen, videoSource, 1280, 240, true)); #endif #if HAVE_32BPP case 4: return make_unique>( vdp, display, *screen, make_unique>( motherBoard, display, *screen, videoSource, 1280, 240, true)); #endif default: UNREACHABLE; return nullptr; } #if COMPONENT_GL case RenderSettings::SDLGL_PP: return make_unique>( vdp, display, *screen, make_unique( motherBoard, display, *screen, videoSource, 1280, 240, true)); #endif default: UNREACHABLE; return nullptr; } } #if COMPONENT_LASERDISC std::unique_ptr SDLVideoSystem::createLDRasterizer( LaserdiscPlayer& ld) { std::string videoSource = "Laserdisc"; // TODO handle multiple??? MSXMotherBoard& motherBoard = ld.getMotherBoard(); switch (renderSettings.getRenderer()) { case RenderSettings::SDL: case RenderSettings::SDLGL_FB16: case RenderSettings::SDLGL_FB32: switch (screen->getSDLFormat().BytesPerPixel) { #if HAVE_16BPP case 2: return make_unique>( *screen, make_unique>( motherBoard, display, *screen, videoSource, 640, 480, false)); #endif #if HAVE_32BPP case 4: return make_unique>( *screen, make_unique>( motherBoard, display, *screen, videoSource, 640, 480, false)); #endif default: UNREACHABLE; return nullptr; } #if COMPONENT_GL case RenderSettings::SDLGL_PP: return make_unique>( *screen, make_unique( motherBoard, display, *screen, videoSource, 640, 480, false)); #endif default: UNREACHABLE; return nullptr; } } #endif void SDLVideoSystem::getWindowSize(unsigned& width, unsigned& height) { unsigned factor = renderSettings.getScaleFactor(); switch (renderSettings.getRenderer()) { case RenderSettings::SDL: case RenderSettings::SDLGL_FB16: case RenderSettings::SDLGL_FB32: // We don't have 4x software scalers yet. if (factor > 3) factor = 3; break; case RenderSettings::SDLGL_PP: // All scale factors are supported. break; case RenderSettings::DUMMY: factor = 0; break; default: UNREACHABLE; } width = 320 * factor; height = 240 * factor; } // TODO: If we can switch video system at any time (not just frame end), // is this polling approach necessary at all? bool SDLVideoSystem::checkSettings() { // Check resolution. unsigned width, height; getWindowSize(width, height); if (width != screen->getWidth() || height != screen->getHeight()) { return false; } // Check fullscreen. return screen->setFullScreen(renderSettings.getFullScreen()); } void SDLVideoSystem::flush() { screen->finish(); } void SDLVideoSystem::takeScreenShot(const std::string& filename, bool withOsd) { if (withOsd) { // we can directly save current content as screenshot screen->saveScreenshot(filename); } else { // we first need to re-render to an off-screen surface // with OSD layers disabled ScopedLayerHider hideConsole(*consoleLayer); ScopedLayerHider hideOsd(*osdGuiLayer); std::unique_ptr surf = screen->createOffScreenSurface(); display.repaint(*surf); surf->saveScreenshot(filename); } } void SDLVideoSystem::setWindowTitle(const std::string& title) { screen->setWindowTitle(title); } OutputSurface* SDLVideoSystem::getOutputSurface() { return screen.get(); } void SDLVideoSystem::resize() { auto& rtScheduler = reactor.getRTScheduler(); auto& eventDistributor = reactor.getEventDistributor(); auto& inputEventGenerator = reactor.getInputEventGenerator(); unsigned width, height; getWindowSize(width, height); // Destruct existing output surface before creating a new one. screen.reset(); switch (renderSettings.getRenderer()) { case RenderSettings::SDL: screen = make_unique( width, height, renderSettings, rtScheduler, eventDistributor, inputEventGenerator, reactor.getCliComm()); break; #if COMPONENT_GL case RenderSettings::SDLGL_PP: screen = make_unique( width, height, renderSettings, rtScheduler, eventDistributor, inputEventGenerator, reactor.getCliComm()); break; case RenderSettings::SDLGL_FB16: screen = make_unique( width, height, renderSettings, rtScheduler, eventDistributor, inputEventGenerator, reactor.getCliComm(), SDLGLVisibleSurface::FB_16BPP); break; case RenderSettings::SDLGL_FB32: screen = make_unique( width, height, renderSettings, rtScheduler, eventDistributor, inputEventGenerator, reactor.getCliComm(), SDLGLVisibleSurface::FB_32BPP); break; #endif default: UNREACHABLE; } inputEventGenerator.reinit(); } void SDLVideoSystem::update(const Setting& subject) { if (&subject == &renderSettings.getScaleFactorSetting()) { // TODO: This is done via checkSettings instead, // but is that still needed? //resize(); } else { UNREACHABLE; } } int SDLVideoSystem::signalEvent(const std::shared_ptr& /*event*/) { // TODO: Currently window size depends only on scale factor. // Maybe in the future it will be handled differently. //auto& resizeEvent = checked_cast(event); //resize(resizeEvent.getX(), resizeEvent.getY()); //resize(); return 0; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/SDLVideoSystem.hh000066400000000000000000000033541257557151200215000ustar00rootroot00000000000000#ifndef SDLVIDEOSYSTEM_HH #define SDLVIDEOSYSTEM_HH #include "VideoSystem.hh" #include "EventListener.hh" #include "Observer.hh" #include "noncopyable.hh" #include "components.hh" #include namespace openmsx { class Reactor; class CommandConsole; class Display; class RenderSettings; class VisibleSurface; class Layer; class Setting; class SDLVideoSystem final : public VideoSystem, private EventListener , private Observer, private noncopyable { public: /** Activates this video system. * @throw InitException If initialisation fails. */ explicit SDLVideoSystem(Reactor& reactor, CommandConsole& console); /** Deactivates this video system. */ ~SDLVideoSystem(); // VideoSystem interface: std::unique_ptr createRasterizer(VDP& vdp) override; std::unique_ptr createV9990Rasterizer( V9990& vdp) override; #if COMPONENT_LASERDISC std::unique_ptr createLDRasterizer( LaserdiscPlayer& ld) override; #endif bool checkSettings() override; void flush() override; void takeScreenShot(const std::string& filename, bool withOsd) override; void setWindowTitle(const std::string& title) override; OutputSurface* getOutputSurface() override; private: // EventListener int signalEvent(const std::shared_ptr& event) override; // Observer void update(const Setting& subject) override; void getWindowSize(unsigned& width, unsigned& height); void resize(); Reactor& reactor; Display& display; RenderSettings& renderSettings; std::unique_ptr screen; std::unique_ptr consoleLayer; std::unique_ptr snowLayer; std::unique_ptr iconLayer; std::unique_ptr osdGuiLayer; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/SDLVisibleSurface.cc000066400000000000000000000060761257557151200221250ustar00rootroot00000000000000#include "SDLVisibleSurface.hh" #include "SDLOffScreenSurface.hh" #include "PNG.hh" #include "SDLSnow.hh" #include "OSDConsoleRenderer.hh" #include "OSDGUILayer.hh" #include "RenderSettings.hh" #include "BooleanSetting.hh" #include "memory.hh" #include "unreachable.hh" #include "build-info.hh" #include namespace openmsx { SDLVisibleSurface::SDLVisibleSurface( unsigned width, unsigned height, RenderSettings& renderSettings, RTScheduler& rtScheduler, EventDistributor& eventDistributor, InputEventGenerator& inputEventGenerator, CliComm& cliComm) : VisibleSurface(renderSettings, rtScheduler, eventDistributor, inputEventGenerator, cliComm) { #if PLATFORM_DINGUX // The OpenDingux kernel supports double buffering, while the legacy // kernel will hang apps that try to use double buffering. // The Dingoo seems to have a hardware problem that makes it hard or // impossible to know when vsync happens: // http://www.dingux.com/2009/07/on-screen-tearing.html // However, double buffering increases performance and does reduce // the tearing somewhat, so it is worth having. int flags = SDL_HWSURFACE | SDL_DOUBLEBUF; #elif PLATFORM_ANDROID // On Android, SDL_HWSURFACE currently crashes although the SDL Android port is // supposed to support it. Probably an incompatibility between how openMSX uses // SDL and how the SDL Android expects an app to use SDL int flags = SDL_SWSURFACE; #else int flags = SDL_SWSURFACE; // Why did we use a SW surface again? #endif if (renderSettings.getFullScreen()) flags |= SDL_FULLSCREEN; createSurface(width, height, flags); SDL_Surface* surface = getSDLSurface(); setSDLFormat(*surface->format); setBufferPtr(static_cast(surface->pixels), surface->pitch); } void SDLVisibleSurface::finish() { unlock(); SDL_Surface* surface = getSDLSurface(); SDL_Flip(surface); // The pixel pointer might be invalidated by the flip. // This is certainly the case when double buffering. setBufferPtr(static_cast(surface->pixels), surface->pitch); } std::unique_ptr SDLVisibleSurface::createSnowLayer(Display& display) { switch (getSDLFormat().BytesPerPixel) { #if HAVE_16BPP case 2: return make_unique>(*this, display); #endif #if HAVE_32BPP case 4: return make_unique>(*this, display); #endif default: UNREACHABLE; return nullptr; } } std::unique_ptr SDLVisibleSurface::createConsoleLayer( Reactor& reactor, CommandConsole& console) { const bool openGL = false; return make_unique( reactor, console, getWidth(), getHeight(), openGL); } std::unique_ptr SDLVisibleSurface::createOSDGUILayer(OSDGUI& gui) { return make_unique(gui); } std::unique_ptr SDLVisibleSurface::createOffScreenSurface() { return make_unique(*getSDLSurface()); } void SDLVisibleSurface::saveScreenshot(const std::string& filename) { lock(); PNG::save(getSDLSurface(), filename); } void SDLVisibleSurface::clearScreen() { unlock(); SDL_FillRect(getSDLSurface(), nullptr, 0); } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/SDLVisibleSurface.hh000066400000000000000000000017151257557151200221320ustar00rootroot00000000000000#ifndef SDLVISIBLESURFACE_HH #define SDLVISIBLESURFACE_HH #include "VisibleSurface.hh" namespace openmsx { class SDLVisibleSurface final : public VisibleSurface { public: SDLVisibleSurface(unsigned width, unsigned height, RenderSettings& renderSettings, RTScheduler& rtScheduler, EventDistributor& eventDistributor, InputEventGenerator& inputEventGenerator, CliComm& cliComm); private: // OutputSurface void saveScreenshot(const std::string& filename) override; void clearScreen() override; // VisibleSurface void finish() override; std::unique_ptr createSnowLayer(Display& display) override; std::unique_ptr createConsoleLayer( Reactor& reactor, CommandConsole& console) override; std::unique_ptr createOSDGUILayer(OSDGUI& gui) override; std::unique_ptr createOffScreenSurface() override; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/SpriteChecker.cc000066400000000000000000000424001257557151200213760ustar00rootroot00000000000000/* TODO: - Verify model for 5th sprite number calculation. For example, does it have the right value in text mode? - Further investigate sprite collision registers: - If there is NO collision, the value of these registers constantly changes. Could this be some kind of indication for the scanline XY coords??? - Bit 9 of the Y coord (odd/even page??) is not yet implemented. */ #include "SpriteChecker.hh" #include "RenderSettings.hh" #include "BooleanSetting.hh" #include "serialize.hh" #include #include namespace openmsx { SpriteChecker::SpriteChecker(VDP& vdp_, RenderSettings& renderSettings, EmuTime::param time) : vdp(vdp_), vram(vdp.getVRAM()) , limitSpritesSetting(renderSettings.getLimitSpritesSetting()) , frameStartTime(time) { vram.spriteAttribTable.setObserver(this); vram.spritePatternTable.setObserver(this); } void SpriteChecker::reset(EmuTime::param time) { vdp.setSpriteStatus(0); // TODO 0x00 or 0x1F (blueMSX has 0x1F) collisionX = 0; collisionY = 0; frameStart(time); updateSpritesMethod = &SpriteChecker::updateSprites1; } static inline SpriteChecker::SpritePattern doublePattern(SpriteChecker::SpritePattern a) { // bit-pattern "abcd...." gets expanded to "aabbccdd" // upper 16 bits (of a 32 bit number) contain the pattern // lower 16 bits must be zero // // abcdefghijklmnop0000000000000000 a = (a | (a >> 8)) & 0xFF00FF00; // abcdefgh00000000ijklmnop00000000 a = (a | (a >> 4)) & 0xF0F0F0F0; // abcd0000efgh0000ijkl0000mnop0000 a = (a | (a >> 2)) & 0xCCCCCCCC; // ab00cd00ef00gh00ij00kl00mn00op00 a = (a | (a >> 1)) & 0xAAAAAAAA; // a0b0c0d0e0f0g0h0i0j0k0l0m0n0o0p0 return a | (a >> 1); // aabbccddeeffgghhiijjkkllmmnnoopp } inline SpriteChecker::SpritePattern SpriteChecker::calculatePatternNP( unsigned patternNr, unsigned y) { const byte* patternPtr = vram.spritePatternTable.getReadArea(0, 256 * 8); unsigned index = patternNr * 8 + y; SpritePattern pattern = patternPtr[index] << 24; if (vdp.getSpriteSize() == 16) { pattern |= patternPtr[index + 16] << 16; } return !vdp.isSpriteMag() ? pattern : doublePattern(pattern); } inline SpriteChecker::SpritePattern SpriteChecker::calculatePatternPlanar( unsigned patternNr, unsigned y) { const byte* ptr0; const byte* ptr1; vram.spritePatternTable.getReadAreaPlanar(0, 256 * 8, ptr0, ptr1); unsigned index = patternNr * 8 + y; const byte* patternPtr = (index & 1) ? ptr1 : ptr0; index /= 2; SpritePattern pattern = patternPtr[index] << 24; if (vdp.getSpriteSize() == 16) { pattern |= patternPtr[index + (16 / 2)] << 16; } return !vdp.isSpriteMag() ? pattern : doublePattern(pattern); } void SpriteChecker::updateSprites1(int limit) { if (vdp.spritesEnabledFast()) { if (vdp.isDisplayEnabled()) { // in display area checkSprites1(currentLine, limit); } else { // in border, only check last line of top border int l0 = vdp.getLineZero() - 1; if ((currentLine <= l0) && (l0 < limit)) { checkSprites1(l0, l0 + 1); } } } currentLine = limit; } inline void SpriteChecker::checkSprites1(int minLine, int maxLine) { // This implementation contains a double for-loop. The outer loop goes // over the sprites, the inner loop over the to-be-checked lines. This // is not the order in which the real VDP performs this operation: the // real VDP renders line-per-line and for each line checks all 32 // sprites. // // Though this 'reverse' order allows to skip over very large regions // of the inner loop: we only have to process the lines were a // particular sprite is actually visible. I measured this makes this // routine 4x-5x faster! // // This routine also needs to detect the sprite number of the 'first' // 5th-sprite-condition. With 'first' meaning the first line where this // condition occurs. Because our loops are swapped compared to the real // VDP, we need some extra fixup logic to correctly detect this. // Calculate display line. // This is the line sprites are checked at; the line they are displayed // at is one lower. int displayDelta = vdp.getVerticalScroll() - vdp.getLineZero(); // Get sprites for this line and detect 5th sprite if any. bool limitSprites = limitSpritesSetting.getBoolean(); int size = vdp.getSpriteSize(); bool mag = vdp.isSpriteMag(); int magSize = (mag + 1) * size; const byte* attributePtr = vram.spriteAttribTable.getReadArea(0, 32 * 4); byte patternIndexMask = size == 16 ? 0xFC : 0xFF; int fifthSpriteNum = -1; // no 5th sprite detected yet int fifthSpriteLine = 999; // larger than any possible valid line int sprite = 0; for (/**/; sprite < 32; ++sprite) { int y = attributePtr[4 * sprite + 0]; if (y == 208) break; for (int line = minLine; line < maxLine; ++line) { // Calculate line number within the sprite. int displayLine = line + displayDelta; int spriteLine = (displayLine - y) & 0xFF; if (spriteLine >= magSize) { // Skip ahead till sprite becomes visible. line += 256 - spriteLine - 1; // -1 because of for-loop continue; } int visibleIndex = spriteCount[line]; if (visibleIndex == 4) { // Find earliest line where this condition occurs. if (line < fifthSpriteLine) { fifthSpriteLine = line; fifthSpriteNum = sprite; } if (limitSprites) continue; } SpriteInfo& sip = spriteBuffer[line][visibleIndex]; int patternIndex = attributePtr[4 * sprite + 2] & patternIndexMask; if (mag) spriteLine /= 2; sip.pattern = calculatePatternNP(patternIndex, spriteLine); sip.x = attributePtr[4 * sprite + 1]; byte colorAttrib = attributePtr[4 * sprite + 3]; if (colorAttrib & 0x80) sip.x -= 32; sip.colorAttrib = colorAttrib; spriteCount[line] = visibleIndex + 1; } } // Update status register. byte status = vdp.getStatusReg0(); if (fifthSpriteNum != -1) { // Five sprites on a line. // According to TMS9918.pdf 5th sprite detection is only // active when F flag is zero. if ((status & 0xC0) == 0) { status = 0x40 | (status & 0x20) | fifthSpriteNum; } } if (~status & 0x40) { // No 5th sprite detected, store number of latest sprite processed. status = (status & 0x20) | std::min(sprite, 31); } vdp.setSpriteStatus(status); // Optimisation: // If collision already occurred, // that state is stable until it is reset by a status reg read, // so no need to execute the checks. // The spriteBuffer array is filled now, so we can bail out. if (vdp.getStatusReg0() & 0x20) return; /* Model for sprite collision: (or "coincidence" in TMS9918 data sheet) - Reset when status reg is read. - Set when sprite patterns overlap. - Color doesn't matter: sprites of color 0 can collide. - Sprites that are partially off-screen position can collide, but only on the in-screen pixels. In other words: sprites cannot collide in the left or right border, only in the visible screen area. Though they can collide in the V9958 extra border mask. This behaviour is the same in sprite mode 1 and 2. Implemented by checking every pair for collisions. For large numbers of sprites that would be slow, but there are max 4 sprites and therefore max 6 pairs. If any collision is found, method returns at once. */ for (int line = minLine; line < maxLine; ++line) { int minXCollision = 999; for (int i = std::min(4, spriteCount[line]); --i >= 1; /**/) { int x_i = spriteBuffer[line][i].x; SpritePattern pattern_i = spriteBuffer[line][i].pattern; for (int j = i; --j >= 0; ) { // Do sprite i and sprite j collide? int x_j = spriteBuffer[line][j].x; int dist = x_j - x_i; if ((-magSize < dist) && (dist < magSize)) { SpritePattern pattern_j = spriteBuffer[line][j].pattern; if (dist < 0) { pattern_j <<= -dist; } else { pattern_j >>= dist; } SpritePattern colPat = pattern_i & pattern_j; if (x_i < 0) { assert(x_i >= -32); colPat &= (1 << (32 + x_i)) - 1; } if (colPat) { int xCollision = x_i + Math::countLeadingZeros(colPat); assert(xCollision >= 0); minXCollision = std::min(minXCollision, xCollision); } } } } if (minXCollision < 256) { vdp.setSpriteStatus(vdp.getStatusReg0() | 0x20); // verified: collision coords are also filled // in for sprite mode 1 // x-coord should be increased by 12 // y-coord 8 collisionX = minXCollision + 12; collisionY = line - vdp.getLineZero() + 8; return; // don't check lines with higher Y-coord } } } void SpriteChecker::updateSprites2(int limit) { // TODO merge this with updateSprites1()? if (vdp.spritesEnabledFast()) { if (vdp.isDisplayEnabled()) { // in display area checkSprites2(currentLine, limit); } else { // in border, only check last line of top border int l0 = vdp.getLineZero() - 1; if ((currentLine <= l0) && (l0 < limit)) { checkSprites2(l0, l0 + 1); } } } currentLine = limit; } inline void SpriteChecker::checkSprites2(int minLine, int maxLine) { // See comment in checkSprites1() about order of inner and outer loops. // Calculate display line. // This is the line sprites are checked at; the line they are displayed // at is one lower. int displayDelta = vdp.getVerticalScroll() - vdp.getLineZero(); // Get sprites for this line and detect 5th sprite if any. bool limitSprites = limitSpritesSetting.getBoolean(); int size = vdp.getSpriteSize(); bool mag = vdp.isSpriteMag(); int magSize = (mag + 1) * size; int patternIndexMask = (size == 16) ? 0xFC : 0xFF; int ninthSpriteNum = -1; // no 9th sprite detected yet int ninthSpriteLine = 999; // larger than any possible valid line // Because it gave a measurable performance boost, we duplicated the // code for planar and non-planar modes. int sprite = 0; if (planar) { const byte* attributePtr0; const byte* attributePtr1; vram.spriteAttribTable.getReadAreaPlanar( 512, 32 * 4, attributePtr0, attributePtr1); // TODO: Verify CC implementation. for (/**/; sprite < 32; ++sprite) { int y = attributePtr0[2 * sprite + 0]; if (y == 216) break; for (int line = minLine; line < maxLine; ++line) { // Calculate line number within the sprite. int displayLine = line + displayDelta; int spriteLine = (displayLine - y) & 0xFF; if (spriteLine >= magSize) { // Skip ahead till sprite is visible. line += 256 - spriteLine - 1; continue; } int visibleIndex = spriteCount[line]; if (visibleIndex == 8) { // Find earliest line where this condition occurs. if (line < ninthSpriteLine) { ninthSpriteLine = line; ninthSpriteNum = sprite; } if (limitSprites) continue; } if (mag) spriteLine /= 2; int colorIndex = (~0u << 10) | (sprite * 16 + spriteLine); byte colorAttrib = vram.spriteAttribTable.readPlanar(colorIndex); // Sprites with CC=1 are only visible if preceded by // a sprite with CC=0. if ((colorAttrib & 0x40) && visibleIndex == 0) continue; SpriteInfo& sip = spriteBuffer[line][visibleIndex]; int patternIndex = attributePtr0[2 * sprite + 1] & patternIndexMask; sip.pattern = calculatePatternPlanar(patternIndex, spriteLine); sip.x = attributePtr1[2 * sprite + 0]; if (colorAttrib & 0x80) sip.x -= 32; sip.colorAttrib = colorAttrib; // set sentinel (see below) spriteBuffer[line][visibleIndex + 1].colorAttrib = 0; spriteCount[line] = visibleIndex + 1; } } } else { const byte* attributePtr0 = vram.spriteAttribTable.getReadArea(512, 32 * 4); // TODO: Verify CC implementation. for (/**/; sprite < 32; ++sprite) { int y = attributePtr0[4 * sprite + 0]; if (y == 216) break; for (int line = minLine; line < maxLine; ++line) { // Calculate line number within the sprite. int displayLine = line + displayDelta; int spriteLine = (displayLine - y) & 0xFF; if (spriteLine >= magSize) { // Skip ahead till sprite is visible. line += 256 - spriteLine - 1; continue; } int visibleIndex = spriteCount[line]; if (visibleIndex == 8) { // Find earliest line where this condition occurs. if (line < ninthSpriteLine) { ninthSpriteLine = line; ninthSpriteNum = sprite; } if (limitSprites) continue; } if (mag) spriteLine /= 2; int colorIndex = (~0u << 10) | (sprite * 16 + spriteLine); byte colorAttrib = vram.spriteAttribTable.readNP(colorIndex); // Sprites with CC=1 are only visible if preceded by // a sprite with CC=0. if ((colorAttrib & 0x40) && visibleIndex == 0) continue; SpriteInfo& sip = spriteBuffer[line][visibleIndex]; int patternIndex = attributePtr0[4 * sprite + 2] & patternIndexMask; sip.pattern = calculatePatternNP(patternIndex, spriteLine); sip.x = attributePtr0[4 * sprite + 1]; if (colorAttrib & 0x80) sip.x -= 32; sip.colorAttrib = colorAttrib; // Set sentinel. Sentinel is actually only // needed for sprites with CC=1. // In the past we set the sentinel (for all // lines) at the end. But it's slightly faster // to do it only for lines that actually // contain sprites (even if sentinel gets // overwritten a couple of times for lines with // many sprites). spriteBuffer[line][visibleIndex + 1].colorAttrib = 0; spriteCount[line] = visibleIndex + 1; } } } // Update status register. byte status = vdp.getStatusReg0(); if (ninthSpriteNum != -1) { // Nine sprites on a line. // According to TMS9918.pdf 5th sprite detection is only // active when F flag is zero. Stuck to this for V9938. // Dragon Quest 2 needs this. if ((status & 0xC0) == 0) { status = 0x40 | (status & 0x20) | ninthSpriteNum; } } if (~status & 0x40) { // No 9th sprite detected, store number of latest sprite processed. status = (status & 0x20) | std::min(sprite, 31); } vdp.setSpriteStatus(status); // Optimisation: // If collision already occurred, // that state is stable until it is reset by a status reg read, // so no need to execute the checks. // The visibleSprites array is filled now, so we can bail out. if (vdp.getStatusReg0() & 0x20) return; /* Model for sprite collision: (or "coincidence" in TMS9918 data sheet) - Reset when status reg is read. - Set when sprite patterns overlap. - Color doesn't matter: sprites of color 0 can collide. TODO: V9938 data book denies this (page 98). - Sprites that are partially off-screen position can collide, but only on the in-screen pixels. In other words: sprites cannot collide in the left or right border, only in the visible screen area. Though they can collide in the V9958 extra border mask. This behaviour is the same in sprite mode 1 and 2. Implemented by checking every pair for collisions. For large numbers of sprites that would be slow. There are max 8 sprites and therefore max 42 pairs. TODO: Maybe this is slow... Think of something faster. Probably new approach is needed anyway for OR-ing. */ for (int line = minLine; line < maxLine; ++line) { int minXCollision = 999; // no collision SpriteInfo* visibleSprites = spriteBuffer[line]; for (int i = std::min(8, spriteCount[line]); --i >= 1; /**/) { // If CC or IC is set, this sprite cannot collide. if (visibleSprites[i].colorAttrib & 0x60) continue; int x_i = visibleSprites[i].x; SpritePattern pattern_i = visibleSprites[i].pattern; for (int j = i; --j >= 0; ) { // If CC or IC is set, this sprite cannot collide. if (visibleSprites[j].colorAttrib & 0x60) continue; // Do sprite i and sprite j collide? int x_j = visibleSprites[j].x; int dist = x_j - x_i; if ((-magSize < dist) && (dist < magSize)) { SpritePattern pattern_j = visibleSprites[j].pattern; if (dist < 0) { pattern_j <<= -dist; } else { pattern_j >>= dist; } SpritePattern colPat = pattern_i & pattern_j; if (x_i < 0) { assert(x_i >= -32); colPat &= (1 << (32 + x_i)) - 1; } if (colPat) { int xCollision = x_i + Math::countLeadingZeros(colPat); assert(xCollision >= 0); minXCollision = std::min(minXCollision, xCollision); } } } } if (minXCollision < 256) { vdp.setSpriteStatus(vdp.getStatusReg0() | 0x20); // x-coord should be increased by 12 // y-coord 8 collisionX = minXCollision + 12; collisionY = line - vdp.getLineZero() + 8; return; // don't check lines with higher Y-coord } } } // version 1: initial version // version 2: bug fix: also serialize 'currentLine' template void SpriteChecker::serialize(Archive& ar, unsigned version) { if (ar.isLoader()) { // Recalculate from VDP state: // - frameStartTime frameStartTime.reset(vdp.getFrameStartTime()); // - updateSpritesMethod, planar setDisplayMode(vdp.getDisplayMode()); // We don't serialize spriteCount[] and spriteBuffer[]. // These are only used to draw the MSX screen, they don't have // any influence on the MSX state. So the effect of not // serializing these two is that no sprites will be shown in the // first (partial) frame after loadstate. for (auto& c : spriteCount) c = 0; // content of spriteBuffer[] doesn't matter if spriteCount[] is 0 } ar.serialize("collisionX", collisionX); ar.serialize("collisionY", collisionY); if (ar.versionAtLeast(version, 2)) { ar.serialize("currentLine", currentLine); } else { currentLine = 0; } } INSTANTIATE_SERIALIZE_METHODS(SpriteChecker); } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/SpriteChecker.hh000066400000000000000000000267431257557151200214240ustar00rootroot00000000000000#ifndef SPRITECHECKER_HH #define SPRITECHECKER_HH #include "VDP.hh" #include "VDPVRAM.hh" #include "VRAMObserver.hh" #include "DisplayMode.hh" #include "serialize_meta.hh" #include "unreachable.hh" #include namespace openmsx { class RenderSettings; class BooleanSetting; class SpriteChecker final : public VRAMObserver { public: /** Bitmap of length 32 describing a sprite pattern. * Visible pixels are 1, transparent pixels are 0. * If the sprite is less than 32 pixels wide, * the lower bits are unused. */ using SpritePattern = uint32_t; /** Contains all the information to draw a line of a sprite. */ struct SpriteInfo { /** Pattern of this sprite line, corrected for magnification. */ SpritePattern pattern; /** X-coordinate of sprite, corrected for early clock. */ int16_t x; /** Bit 3..0 are index in palette. * Bit 6 is 0 for sprite mode 1 like behaviour, * or 1 for OR-ing of sprite colors. * Other bits are undefined. */ byte colorAttrib; }; /** Create a sprite checker. * @param vdp The VDP this sprite checker is part of. * @param renderSettings TODO * @param time TODO */ SpriteChecker(VDP& vdp, RenderSettings& renderSettings, EmuTime::param time); /** Puts the sprite checker in its initial state. * @param time The moment in time this reset occurs. */ void reset(EmuTime::param time); /** Update sprite checking to specified time. * This includes a VRAM sync. * @param time The moment in emulated time to update to. */ inline void sync(EmuTime::param time) { if (!updateSpritesMethod) { // Optimization: skip vram sync and sprite checks // in sprite mode 0. return; } // Debug: // This method is not re-entrant, so check explicitly that it is not // re-entered. This can disappear once the VDP-internal scheduling // has become stable. #ifdef DEBUG static bool syncInProgress = false; assert(!syncInProgress); syncInProgress = true; #endif vram.sync(time); checkUntil(time); #ifdef DEBUG syncInProgress = false; #endif } /** Clear status bits triggered by reading of S#0. */ inline void resetStatus() { // TODO: Used to be 0x5F, but that is contradicted by // TMS9918.pdf. Check on real MSX. vdp.setSpriteStatus(vdp.getStatusReg0() & 0x1F); } /** Informs the sprite checker of a VDP display mode change. * @param mode The new display mode. * @param time The moment in emulated time this change occurs. */ inline void updateDisplayMode(DisplayMode mode, EmuTime::param time) { sync(time); setDisplayMode(mode); // The following is only required when switching from sprite // mode0 to some other mode (in other case it has no effect). // Because in mode 0, currentLine is not updated. currentLine = frameStartTime.getTicksTill_fast(time) / VDP::TICKS_PER_LINE; // Every line in mode0 has 0 sprites, but none of the lines // are ever requested by the renderer, except for the last // line, because sprites are checked one line before they // are displayed. Though frameStart() already makes sure // spriteCount contains zero for all lines. // spriteCount[currentLine - 1] = 0; } /** Informs the sprite checker of a VDP display enabled change. * @param enabled The new display enabled state. * @param time The moment in emulated time this change occurs. */ inline void updateDisplayEnabled(bool enabled, EmuTime::param time) { (void)enabled; sync(time); // TODO: Speed up sprite checking in display disabled case. } /** Informs the sprite checker of sprite enable changes. * @param enabled The new sprite enabled state. * @param time The moment in emulated time this change occurs. */ inline void updateSpritesEnabled(bool enabled, EmuTime::param time) { (void)enabled; sync(time); // TODO: Speed up sprite checking in display disabled case. } /** Informs the sprite checker of sprite size or magnification changes. * @param sizeMag The new size and magnification state. * Bit 0 is magnification: 0 = normal, 1 = doubled. * Bit 1 is size: 0 = 8x8, 1 = 16x16. * @param time The moment in emulated time this change occurs. */ inline void updateSpriteSizeMag(byte sizeMag, EmuTime::param time) { (void)sizeMag; sync(time); // TODO: Precalc something? } /** Informs the sprite checker of a vertical scroll change. * @param scroll The new scroll value. * @param time The moment in emulated time this change occurs. */ inline void updateVerticalScroll(int scroll, EmuTime::param time) { (void)scroll; sync(time); // TODO: Precalc something? } /** Update sprite checking until specified line. * VRAM must be up-to-date before this method is called. * It is not allowed to call this method in a spriteless display mode. * @param time The moment in emulated time to update to. */ inline void checkUntil(EmuTime::param time) { // TODO: // Currently the sprite checking is done atomically at the end of // the display line. In reality, sprite checking is probably done // during most of the line. Run tests on real MSX to make a more // accurate model of sprite checking. int limit = frameStartTime.getTicksTill_fast(time) / VDP::TICKS_PER_LINE; if (currentLine < limit) { // Call the right update method for the current display mode. (this->*updateSpritesMethod)(limit); } } /** Get X coordinate of sprite collision. */ inline int getCollisionX(EmuTime::param time) { sync(time); return collisionX; } /** Get Y coordinate of sprite collision. */ inline int getCollisionY(EmuTime::param time) { sync(time); return collisionY; } /** Reset sprite collision coordinates. * This happens directly after a read, so a timestamp for syncing is * not necessary. */ inline void resetCollision() { collisionX = collisionY = 0; } /** Signals the start of a new frame. * @param time Moment in emulated time the new frame starts. */ inline void frameStart(EmuTime::param time) { frameStartTime.reset(time); currentLine = 0; for (auto& c : spriteCount) c = 0; // TODO: Reset anything else? Does the real VDP? } /** Signals the end of the current frame. * @param time Moment in emulated time the current frame ends. */ inline void frameEnd(EmuTime::param time) { sync(time); } /** Get sprites for a display line. * Returns the contents of the line the last time it was sprite checked; * before getting the sprites, you should sync to a moment in time * after the sprites are checked, or you'll get last frame's sprites. * @param line The absolute line number for which sprites should * be returned. Range is [0..313) for PAL and [0..262) for NTSC. * @param visibleSprites Output parameter in which the pointer to * a SpriteInfo array containing the sprites to be displayed is * returned. * The array's contents are valid until the next time the VDP * is scheduled. * @return The number of sprites stored in the visibleSprites array. */ inline int getSprites(int line, const SpriteInfo*& visibleSprites) const { // Compensate for the fact sprites are checked one line earlier // than they are displayed. line--; // TODO: Is there ever a sprite on absolute line 0? // Maybe there is, but it is never displayed. if (line < 0) return 0; visibleSprites = spriteBuffer[line]; return spriteCount[line]; } // VRAMObserver implementation: void updateVRAM(unsigned /*offset*/, EmuTime::param time) override { checkUntil(time); } void updateWindow(bool /*enabled*/, EmuTime::param time) override { sync(time); } template void serialize(Archive& ar, unsigned version); private: /** Calculate 'updateSpritesMethod' and 'planar'. */ inline void setDisplayMode(DisplayMode mode) { switch (mode.getSpriteMode(vdp.isMSX1VDP())) { case 0: updateSpritesMethod = nullptr; break; case 1: updateSpritesMethod = &SpriteChecker::updateSprites1; break; case 2: updateSpritesMethod = &SpriteChecker::updateSprites2; planar = mode.isPlanar(); // An alternative is to have a planar and non-planar // updateSprites2 method. break; default: UNREACHABLE; } } /** Calculate sprite patterns for sprite mode 1. */ void updateSprites1(int limit); /** Calculate sprite patterns for sprite mode 2. */ void updateSprites2(int limit); /** Calculates a sprite pattern. * @param patternNr Number of the sprite pattern [0..255]. * For 16x16 sprites, patternNr should be a multiple of 4. * @param y The line number within the sprite: 0 <= y < size. * @return A bit field of the sprite pattern. * Bit 31 is the leftmost bit of the sprite. * Unused bits are zero. */ inline SpritePattern calculatePatternNP(unsigned patternNr, unsigned y); inline SpritePattern calculatePatternPlanar(unsigned patternNr, unsigned y); /** Check sprite collision and number of sprites per line. * This routine implements sprite mode 1 (MSX1). * Separated from display code to make MSX behaviour consistent * no matter how displaying is handled. * @param minLine The first line number (inclusive) for which sprites * should be checked. * @param maxLine The last line number (exclusive) for which sprites * should be checked. * @effect Fills in the spriteBuffer and spriteCount arrays. */ inline void checkSprites1(int minLine, int maxLine); /** Check sprite collision and number of sprites per line. * This routine implements sprite mode 2 (MSX2). * Separated from display code to make MSX behaviour consistent * no matter how displaying is handled. * @param minLine The first line number (inclusive) for which sprites * should be checked. * @param maxLine The last line number (exclusive) for which sprites * should be checked. * @effect Fills in the spriteBuffer and spriteCount arrays. */ inline void checkSprites2(int minLine, int maxLine); using UpdateSpritesMethod = void (SpriteChecker::*)(int limit); UpdateSpritesMethod updateSpritesMethod; /** The VDP this sprite checker is part of. */ VDP& vdp; /** The VRAM to get sprites data from. */ VDPVRAM& vram; /** Limit number of sprites per display line? * Option only affects display, not MSX state. * In other words: when false there is no limit to the number of * sprites drawn, but the status register acts like the usual limit * is still effective. */ BooleanSetting& limitSpritesSetting; /** The emulation time when this frame was started (vsync). */ Clock frameStartTime; /** Sprites are checked up to and excluding this display line. */ int currentLine; /** X coordinate of sprite collision. * 9 bits long -> [0..511]? */ int collisionX; /** Y coordinate of sprite collision. * 9 bits long -> [0..511]? * Bit 9 contains EO, I guess that's a copy of the even/odd flag * of the frame on which the collision occurred. */ int collisionY; /** Buffer containing the sprites that are visible on each * display line. */ SpriteInfo spriteBuffer[313][32 + 1]; // +1 for sentinel /** Buffer containing the number of sprites that are visible * on each display line. * In other words, spriteCount[i] is the number of sprites * in spriteBuffer[i]. */ uint8_t spriteCount[313]; /** Is current display mode planar or not? * TODO: Introduce separate update methods for planar/nonplanar modes. */ bool planar; }; SERIALIZE_CLASS_VERSION(SpriteChecker, 2); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/SpriteConverter.hh000066400000000000000000000134161257557151200220200ustar00rootroot00000000000000/* TODO: - Implement sprite pixels in Graphic 5. */ #ifndef SPRITECONVERTER_HH #define SPRITECONVERTER_HH #include "SpriteChecker.hh" #include "DisplayMode.hh" #include "openmsx.hh" namespace openmsx { /** Utility class for converting VRAM contents to host pixels. */ template class SpriteConverter { public: // TODO: Move some methods to .cc? /** Constructor. * After construction, also call the various set methods to complete * initialisation. * @param spriteChecker_ Delivers the sprite data to be rendered. */ explicit SpriteConverter(SpriteChecker& spriteChecker_) : spriteChecker(spriteChecker_) { } /** Update the transparency setting. * @param enabled The new value. */ void setTransparency(bool enabled) { this->transparency = enabled; } /** Notify SpriteConverter of a display mode change. * @param mode The new display mode. */ void setDisplayMode(DisplayMode mode) { this->mode = mode; } /** Set palette to use for converting sprites. * This palette is stored by reference, so any modifications to it * will be used while drawing. * @param palette 16-entry array containing the sprite palette. */ void setPalette(const Pixel* palette) { this->palette = palette; } static bool clipPattern(int& x, SpriteChecker::SpritePattern& pattern, int minX, int maxX) { int before = minX - x; if (before > 0) { if (before >= 32) { // 32 pixels before minX -> not visible return false; } pattern <<= before; x = minX; } int after = maxX - x; if (after < 32) { // close to maxX (or past) if (after <= 0) { // past maxX -> not visible return false; } int mask = 0x80000000; pattern &= (mask >> (after - 1)); } return true; // visible } /** Draw sprites in sprite mode 1. * @param absLine Absolute line number. * Range is [0..262) for NTSC and [0..313) for PAL. * @param minX Minimum X coordinate to draw (inclusive). * @param maxX Maximum X coordinate to draw (exclusive). * @param pixelPtr Pointer to memory to draw to. */ void drawMode1(int absLine, int minX, int maxX, Pixel* __restrict pixelPtr) __restrict { // Determine sprites visible on this line. const SpriteChecker::SpriteInfo* visibleSprites; int visibleIndex = spriteChecker.getSprites(absLine, visibleSprites); // Optimisation: return at once if no sprites on this line. // Lines without any sprites are very common in most programs. if (visibleIndex == 0) return; // Render using overdraw. while (visibleIndex--) { // Get sprite info. const SpriteChecker::SpriteInfo* sip = &visibleSprites[visibleIndex]; Pixel colIndex = sip->colorAttrib & 0x0F; // Don't draw transparent sprites in sprite mode 1. // TODO: Verify on real V9938 that sprite mode 1 indeed // ignores the transparency bit. if (colIndex == 0) continue; Pixel color = palette[colIndex]; SpriteChecker::SpritePattern pattern = sip->pattern; int x = sip->x; // Clip sprite pattern to render range. if (!clipPattern(x, pattern, minX, maxX)) continue; // Convert pattern to pixels. Pixel* p = &pixelPtr[x]; while (pattern) { // Draw pixel if sprite has a dot. if (pattern & 0x80000000) { *p = color; } // Advancing behaviour. pattern <<= 1; p++; } } } /** Draw sprites in sprite mode 2. * Make sure the pixel pointers point to a large enough memory area: * 256 pixels for ZOOM_256 and ZOOM_REAL in 256-pixel wide modes; * 512 pixels for ZOOM_REAL in 512-pixel wide modes. * @param absLine Absolute line number. * Range is [0..262) for NTSC and [0..313) for PAL. * @param minX Minimum X coordinate to draw (inclusive). * @param maxX Maximum X coordinate to draw (exclusive). * @param pixelPtr Pointer to memory to draw to. */ template void drawMode2(int absLine, int minX, int maxX, Pixel* __restrict pixelPtr) __restrict { // Determine sprites visible on this line. const SpriteChecker::SpriteInfo* visibleSprites; int visibleIndex = spriteChecker.getSprites(absLine, visibleSprites); // Optimisation: return at once if no sprites on this line. // Lines without any sprites are very common in most programs. if (visibleIndex == 0) return; for (int i = visibleIndex - 1; i >= 0; --i) { const SpriteChecker::SpriteInfo& info = visibleSprites[i]; int x = info.x; SpriteChecker::SpritePattern pattern = info.pattern; // Clip sprite pattern to render range. if (!clipPattern(x, pattern, minX, maxX)) continue; byte c = info.colorAttrib & 0x0F; if (c == 0 && transparency) continue; while (pattern) { if (pattern & 0x80000000) { byte color = c; // Merge in any following CC=1 sprites. for (int j = i + 1; /*sentinel*/; ++j) { const SpriteChecker::SpriteInfo& info2 = visibleSprites[j]; if (!(info2.colorAttrib & 0x40)) break; unsigned shift2 = x - info2.x; if ((shift2 < 32) && ((info2.pattern << shift2) & 0x80000000)) { color |= info2.colorAttrib & 0x0F; } } if (MODE == DisplayMode::GRAPHIC5) { Pixel pixL = palette[color >> 2]; Pixel pixR = palette[color & 3]; pixelPtr[x * 2 + 0] = pixL; pixelPtr[x * 2 + 1] = pixR; } else { Pixel pix = palette[color]; if (MODE == DisplayMode::GRAPHIC6) { pixelPtr[x * 2 + 0] = pix; pixelPtr[x * 2 + 1] = pix; } else { pixelPtr[x] = pix; } } } ++x; pattern <<= 1; } } } private: SpriteChecker& spriteChecker; /** The current sprite palette. */ const Pixel* palette; /** VDP transparency setting (R#8, bit5). */ bool transparency; /** The current display mode. */ DisplayMode mode; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/SuperImposedFrame.cc000066400000000000000000000053271257557151200222440ustar00rootroot00000000000000#include "SuperImposedFrame.hh" #include "PixelOperations.hh" #include "LineScalers.hh" #include "memory.hh" #include "unreachable.hh" #include "vla.hh" #include "build-info.hh" #include #include namespace openmsx { template class SuperImposedFrameImpl final : public SuperImposedFrame { public: SuperImposedFrameImpl(const SDL_PixelFormat& format); private: unsigned getLineWidth(unsigned line) const override; const void* getLineInfo( unsigned line, unsigned& width, void* buf, unsigned bufWidth) const override; PixelOperations pixelOps; }; // class SuperImposedFrame std::unique_ptr SuperImposedFrame::create( const SDL_PixelFormat& format) { #if HAVE_16BPP if (format.BitsPerPixel == 15 || format.BitsPerPixel == 16) { return make_unique>(format); } #endif #if HAVE_32BPP if (format.BitsPerPixel == 32) { return make_unique>(format); } #endif UNREACHABLE; return nullptr; // avoid warning } SuperImposedFrame::SuperImposedFrame(const SDL_PixelFormat& format) : FrameSource(format) { } void SuperImposedFrame::init( const FrameSource* top_, const FrameSource* bottom_) { top = top_; bottom = bottom_; setHeight(std::max(top->getHeight(), bottom->getHeight())); } // class SuperImposedFrameImpl template SuperImposedFrameImpl::SuperImposedFrameImpl( const SDL_PixelFormat& format) : SuperImposedFrame(format) , pixelOps(format) { } template unsigned SuperImposedFrameImpl::getLineWidth(unsigned line) const { unsigned tNum = (getHeight() == top ->getHeight()) ? line : line / 2; unsigned bNum = (getHeight() == bottom->getHeight()) ? line : line / 2; unsigned tWidth = top ->getLineWidth(tNum); unsigned bWidth = bottom->getLineWidth(bNum); return std::max(tWidth, bWidth); } template const void* SuperImposedFrameImpl::getLineInfo( unsigned line, unsigned& width, void* tBuf_, unsigned bufWidth) const { unsigned tNum = (getHeight() == top ->getHeight()) ? line : line / 2; unsigned bNum = (getHeight() == bottom->getHeight()) ? line : line / 2; unsigned tWidth = top ->getLineWidth(tNum); unsigned bWidth = bottom->getLineWidth(bNum); width = std::max(tWidth, bWidth); // as wide as the widest source width = std::min(width, bufWidth); // but no wider than the output buffer auto* tBuf = static_cast(tBuf_); VLA_SSE_ALIGNED(Pixel, bBuf, width); auto* tLine = top ->getLinePtr(tNum, width, tBuf); auto* bLine = bottom->getLinePtr(bNum, width, bBuf); AlphaBlendLines blend(pixelOps); blend(tLine, bLine, tBuf, width); // possibly tLine == tBuf return tBuf; } } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/SuperImposedFrame.hh000066400000000000000000000014261257557151200222520ustar00rootroot00000000000000#ifndef SUPERIMPOSEDFRAME_HH #define SUPERIMPOSEDFRAME_HH #include "FrameSource.hh" #include namespace openmsx { /** This class represents a frame that is the (per-pixel) alpha-blend of * two other frames. When the two input frames have a different resolution. * The result will have the highest resolution of the two inputs (in other * words, the lower resolution frame gets upscaled to the higher resolution). */ class SuperImposedFrame : public FrameSource { public: static std::unique_ptr create( const SDL_PixelFormat& format); void init(const FrameSource* top, const FrameSource* bottom); protected: SuperImposedFrame(const SDL_PixelFormat& format); const FrameSource* top; const FrameSource* bottom; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/SuperImposedVideoFrame.cc000066400000000000000000000044101257557151200232230ustar00rootroot00000000000000#include "SuperImposedVideoFrame.hh" #include "LineScalers.hh" #include "MemoryOps.hh" #include "vla.hh" #include "build-info.hh" #include namespace openmsx { template SuperImposedVideoFrame::SuperImposedVideoFrame( const FrameSource& src_, const FrameSource& super_, const PixelOperations& pixelOps_) : FrameSource(pixelOps_.getSDLPixelFormat()) , src(src_), super(super_), pixelOps(pixelOps_) { setHeight(src.getHeight()); } template unsigned SuperImposedVideoFrame::getLineWidth(unsigned line) const { unsigned width = src.getLineWidth(line); return (width == 1) ? 320 : width; } template const void* SuperImposedVideoFrame::getLineInfo( unsigned line, unsigned& width, void* buf1_, unsigned bufWidth) const { auto* buf1 = static_cast(buf1_); // Return minimum line width of 320. // We could check whether both inputs have width=1 and in that case // also return a line of width=1. But for now (laserdisc) this will // never happen. auto* srcLine = static_cast( src.getLineInfo(line, width, buf1, bufWidth)); if (width == 1) { width = 320; MemoryOps::MemSet memset; memset(buf1, 320, srcLine[0]); srcLine = buf1; } // (possibly) srcLine == buf1 // Adjust the two inputs to the same height. const Pixel* supLine; VLA_SSE_ALIGNED(Pixel, buf2, width); assert(super.getHeight() == 480); // TODO possibly extend in the future if (src.getHeight() == 240) { VLA_SSE_ALIGNED(Pixel, buf3, width); auto* sup0 = super.getLinePtr(2 * line + 0, width, buf2); auto* sup1 = super.getLinePtr(2 * line + 1, width, buf3); BlendLines blend(pixelOps); blend(sup0, sup1, buf2, width); // possibly sup0 == buf2 supLine = buf2; } else { assert(src.getHeight() == super.getHeight()); supLine = super.getLinePtr(line, width, buf2); // scale line } // (possibly) supLine == buf2 // Actually blend the lines of both frames. AlphaBlendLines blend(pixelOps); blend(srcLine, supLine, buf1, width); // possibly srcLine == buf1 return buf1; } // Force template instantiation. #if HAVE_16BPP template class SuperImposedVideoFrame; #endif #if HAVE_32BPP template class SuperImposedVideoFrame; #endif } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/SuperImposedVideoFrame.hh000066400000000000000000000024431257557151200232410ustar00rootroot00000000000000#ifndef SUPERIMPOSEDVIDEOFRAME_HH #define SUPERIMPOSEDVIDEOFRAME_HH #include "FrameSource.hh" #include "PixelOperations.hh" namespace openmsx { /** This class represents a frame that is the (per-pixel) alpha-blend of a * (laser-disc) video frame and a V99x8 (or tms9918) video frame. This is * different from a generic 'SuperImposedFrame' class because it always * outputs the resolution of the MSX frame. Except for the top/bottom * border line, there we return a line with width=320. * So usually this means the laserdisc video gets downscaled to 320x240 * resolution. The rational for this was that we want the scalers to work * on the proper MSX resolution. So the MSX graphics get scaled in the same * way whether superimpose is enabled or not. */ template class SuperImposedVideoFrame final : public FrameSource { public: SuperImposedVideoFrame(const FrameSource& src, const FrameSource& super, const PixelOperations& pixelOps); // FrameSource unsigned getLineWidth(unsigned line) const override; const void* getLineInfo( unsigned line, unsigned& width, void* buf, unsigned bufWidth) const override; private: const FrameSource& src; const FrameSource& super; PixelOperations pixelOps; }; } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/VDP.cc000066400000000000000000001354471257557151200173120ustar00rootroot00000000000000/* TODO: - Run more measurements on real MSX to find out how horizontal scanning interrupt really works. Finish model and implement it. Especially test this scenario: * IE1 enabled, interrupt occurs * wait until matching line is passed * disable IE1 * read FH * read FH Current implementation would return FH=0 both times. - Check how Z80 should treat interrupts occurring during DI. - Bottom erase suspends display even on overscan. However, it shows black, not border color. How to handle this? Currently it is treated as "overscan" which falls outside of the rendered screen area. */ #include "VDP.hh" #include "VDPVRAM.hh" #include "VDPCmdEngine.hh" #include "SpriteChecker.hh" #include "Display.hh" #include "RendererFactory.hh" #include "Renderer.hh" #include "RenderSettings.hh" #include "EnumSetting.hh" #include "TclObject.hh" #include "MSXMotherBoard.hh" #include "Reactor.hh" #include "MSXException.hh" #include "CliComm.hh" #include "StringOp.hh" #include "unreachable.hh" #include "memory.hh" #include #include using std::string; using std::vector; namespace openmsx { VDP::VDP(const DeviceConfig& config) : MSXDevice(config) , syncVSync(*this) , syncDisplayStart(*this) , syncVScan(*this) , syncHScan(*this) , syncHorAdjust(*this) , syncSetMode(*this) , syncSetBlank(*this) , syncCpuVramAccess(*this) , display(getReactor().getDisplay()) , cmdTiming (display.getRenderSettings().getCmdTimingSetting()) , tooFastAccess(display.getRenderSettings().getTooFastAccessSetting()) , vdpRegDebug (*this) , vdpStatusRegDebug(*this) , vdpPaletteDebug (*this) , vramPointerDebug (*this) , frameCountInfo (*this) , cycleInFrameInfo (*this) , lineInFrameInfo (*this) , cycleInLineInfo (*this) , msxYPosInfo (*this) , msxX256PosInfo (*this) , msxX512PosInfo (*this) , frameStartTime(getCurrentTime()) , irqVertical (getMotherBoard(), getName() + ".IRQvertical", config) , irqHorizontal(getMotherBoard(), getName() + ".IRQhorizontal", config) , displayStartSyncTime(getCurrentTime()) , vScanSyncTime(getCurrentTime()) , hScanSyncTime(getCurrentTime()) , tooFastCallback( getCommandController(), getName() + ".too_fast_vram_access_callback", "Tcl proc called when the VRAM is read or written too fast") , warningPrinted(false) { VDPAccessSlots::initTables(); interlaced = false; std::string versionString = config.getChildData("version"); if (versionString == "TMS99X8A") version = TMS99X8A; else if (versionString == "TMS9918A") version = TMS99X8A; else if (versionString == "TMS9928A") version = TMS99X8A; else if (versionString == "T6950PAL") version = T6950PAL; else if (versionString == "T6950NTSC") version = T6950NTSC; else if (versionString == "T7937APAL") version = T7937APAL; else if (versionString == "T7937ANTSC") version = T7937ANTSC; else if (versionString == "TMS91X8") version = TMS91X8; else if (versionString == "TMS9118") version = TMS91X8; else if (versionString == "TMS9128") version = TMS91X8; else if (versionString == "TMS9929A") version = TMS9929A; else if (versionString == "TMS9129") version = TMS9129; else if (versionString == "V9938") version = V9938; else if (versionString == "V9958") version = V9958; else throw MSXException("Unknown VDP version \"" + versionString + "\""); // Set up control register availability. static const byte VALUE_MASKS_MSX1[32] = { 0x03, 0xFB, 0x0F, 0xFF, 0x07, 0x7F, 0x07, 0xFF // 00..07 }; static const byte VALUE_MASKS_MSX2[32] = { 0x7E, 0x7B, 0x7F, 0xFF, 0x3F, 0xFF, 0x3F, 0xFF, // 00..07 0xFB, 0xBF, 0x07, 0x03, 0xFF, 0xFF, 0x07, 0x0F, // 08..15 0x0F, 0xBF, 0xFF, 0xFF, 0x3F, 0x3F, 0x3F, 0xFF, // 16..23 0, 0, 0, 0, 0, 0, 0, 0, // 24..31 }; controlRegMask = (isMSX1VDP() ? 0x07 : 0x3F); memcpy(controlValueMasks, isMSX1VDP() ? VALUE_MASKS_MSX1 : VALUE_MASKS_MSX2, sizeof(controlValueMasks)); if (version == V9958) { // Enable V9958-specific control registers. controlValueMasks[25] = 0x7F; controlValueMasks[26] = 0x3F; controlValueMasks[27] = 0x07; } resetInit(); // must be done early to avoid UMRs // Video RAM. EmuTime::param time = getCurrentTime(); unsigned vramSize = (isMSX1VDP() ? 16 : config.getChildDataAsInt("vram")); if ((vramSize != 16) && (vramSize != 64) && (vramSize != 128) && (vramSize != 192)) { throw MSXException(StringOp::Builder() << "VRAM size of " << vramSize << "kB is not supported!"); } vram = make_unique(*this, vramSize * 1024, time); RenderSettings& renderSettings = display.getRenderSettings(); // Create sprite checker. spriteChecker = make_unique(*this, renderSettings, time); vram->setSpriteChecker(spriteChecker.get()); // Create command engine. cmdEngine = make_unique( *this, renderSettings, getCommandController()); vram->setCmdEngine(cmdEngine.get()); // Initialise renderer. createRenderer(); // Reset state. powerUp(time); display .attach(*this); cmdTiming .attach(*this); tooFastAccess.attach(*this); update(tooFastAccess); // handles both cmdTiming and tooFastAccess } VDP::~VDP() { tooFastAccess.detach(*this); cmdTiming .detach(*this); display .detach(*this); } void VDP::preVideoSystemChange() { renderer.reset(); } void VDP::postVideoSystemChange() { createRenderer(); } void VDP::createRenderer() { renderer = RendererFactory::createRenderer(*this, display); // TODO: Is it safe to use frameStartTime, // which is most likely in the past? //renderer->reset(frameStartTime.getTime()); vram->setRenderer(renderer.get(), frameStartTime.getTime()); } PostProcessor* VDP::getPostProcessor() const { return renderer->getPostProcessor(); } void VDP::resetInit() { // note: vram, spriteChecker, cmdEngine, renderer may not yet be // created at this point for (auto& reg : controlRegs) { reg = 0; } if (isVDPwithPALonly()) { // Boots (and remains) in PAL mode, all other VDPs boot in NTSC. controlRegs[9] |= 0x02; } // According to page 6 of the V9938 data book the color burst registers // are loaded with these values at power on. controlRegs[21] = 0x3B; controlRegs[22] = 0x05; // Note: frameStart is the actual place palTiming is written, but it // can be read before frameStart is called. // TODO: Clean up initialisation sequence. palTiming = true; // controlRegs[9] & 0x02; displayMode.reset(); vramPointer = 0; cpuVramData = 0; dataLatch = 0; cpuExtendedVram = false; registerDataStored = false; paletteDataStored = false; blinkState = false; blinkCount = 0; horizontalAdjust = 7; // TODO: Real VDP probably resets timing as well. isDisplayArea = false; displayEnabled = false; superimposing = nullptr; externalVideo = nullptr; // Init status registers. statusReg0 = 0x00; statusReg1 = (version == V9958 ? 0x04 : 0x00); statusReg2 = 0x0C; // Update IRQ to reflect new register values. irqVertical.reset(); irqHorizontal.reset(); // From appendix 8 of the V9938 data book (page 148). const word V9938_PALETTE[16] = { 0x000, 0x000, 0x611, 0x733, 0x117, 0x327, 0x151, 0x627, 0x171, 0x373, 0x661, 0x664, 0x411, 0x265, 0x555, 0x777 }; // Init the palette. memcpy(palette, V9938_PALETTE, sizeof(V9938_PALETTE)); } void VDP::resetMasks(EmuTime::param time) { updateNameBase(time); updateColorBase(time); updatePatternBase(time); updateSpriteAttributeBase(time); updateSpritePatternBase(time); // TODO: It is not clear to me yet how bitmapWindow should be used. // Currently it always spans 128K of VRAM. //vram->bitmapWindow.setMask(~(~0u << 17), ~0u << 17, time); } void VDP::powerUp(EmuTime::param time) { vram->clear(); reset(time); } void VDP::reset(EmuTime::param time) { syncVSync .removeSyncPoint(); syncDisplayStart .removeSyncPoint(); syncVScan .removeSyncPoint(); syncHScan .removeSyncPoint(); syncHorAdjust .removeSyncPoint(); syncSetMode .removeSyncPoint(); syncSetBlank .removeSyncPoint(); syncCpuVramAccess.removeSyncPoint(); pendingCpuAccess = false; // Reset subsystems. cmdEngine->sync(time); resetInit(); spriteChecker->reset(time); cmdEngine->reset(time); renderer->reInit(); // Tell the subsystems of the new mask values. resetMasks(time); // Init scheduling. frameCount = -1; frameStart(time); assert(frameCount == 0); } void VDP::execVSync(EmuTime::param time) { // This frame is finished. // Inform VDP subcomponents. // TODO: Do this via VDPVRAM? renderer->frameEnd(time); spriteChecker->frameEnd(time); // Start next frame. frameStart(time); } void VDP::execDisplayStart(EmuTime::param time) { // Display area starts here, unless we're doing overscan and it // was already active. if (!isDisplayArea) { if (displayEnabled) { vram->updateDisplayEnabled(true, time); } isDisplayArea = true; } } void VDP::execVScan(EmuTime::param time) { // VSCAN is the end of display. // This will generate a VBLANK IRQ. Typically MSX software will // poll the keyboard/joystick on this IRQ. So now is a good // time to also poll for host events. getReactor().enterMainLoop(); if (isDisplayEnabled()) { vram->updateDisplayEnabled(false, time); } isDisplayArea = false; // Vertical scanning occurs. statusReg0 |= 0x80; if (controlRegs[1] & 0x20) { irqVertical.set(); } } void VDP::execHScan() { // Horizontal scanning occurs. if (controlRegs[0] & 0x10) { irqHorizontal.set(); } } void VDP::execHorAdjust(EmuTime::param time) { int newHorAdjust = (controlRegs[18] & 0x0F) ^ 0x07; if (controlRegs[25] & 0x08) { newHorAdjust += 4; } renderer->updateHorizontalAdjust(newHorAdjust, time); horizontalAdjust = newHorAdjust; } void VDP::execSetMode(EmuTime::param time) { updateDisplayMode( DisplayMode(controlRegs[0], controlRegs[1], controlRegs[25]), time); } void VDP::execSetBlank(EmuTime::param time) { bool newDisplayEnabled = (controlRegs[1] & 0x40) != 0; if (isDisplayArea) { vram->updateDisplayEnabled(newDisplayEnabled, time); } displayEnabled = newDisplayEnabled; } void VDP::execCpuVramAccess(EmuTime::param time) { assert(!allowTooFastAccess); pendingCpuAccess = false; executeCpuVramAccess(time); } // TODO: This approach assumes that an overscan-like approach can be used // skip display start, so that the border is rendered instead. // This makes sense, but it has not been tested on real MSX yet. void VDP::scheduleDisplayStart(EmuTime::param time) { // Remove pending DISPLAY_START sync point, if any. if (displayStartSyncTime > time) { syncDisplayStart.removeSyncPoint(); //cerr << "removing predicted DISPLAY_START sync point\n"; } // Calculate when (lines and time) display starts. int verticalAdjust = (controlRegs[18] >> 4) ^ 0x07; int lineZero = // sync + top erase: 3 + 13 + // top border: (palTiming ? 36 : 9) + (controlRegs[9] & 0x80 ? 0 : 10) + verticalAdjust; displayStart = lineZero * TICKS_PER_LINE + 100 + 102; // VR flips at start of left border displayStartSyncTime = frameStartTime + displayStart; //cerr << "new DISPLAY_START is " << (displayStart / TICKS_PER_LINE) << "\n"; // Register new DISPLAY_START sync point. if (displayStartSyncTime > time) { syncDisplayStart.setSyncPoint(displayStartSyncTime); //cerr << "inserting new DISPLAY_START sync point\n"; } // HSCAN and VSCAN are relative to display start. scheduleHScan(time); scheduleVScan(time); } void VDP::scheduleVScan(EmuTime::param time) { /* cerr << "scheduleVScan @ " << (getTicksThisFrame(time) / TICKS_PER_LINE) << "\n"; if (vScanSyncTime < frameStartTime) { cerr << "old VSCAN was previous frame\n"; } else { cerr << "old VSCAN was " << (frameStartTime.getTicksTill(vScanSyncTime) / TICKS_PER_LINE) << "\n"; } */ // Remove pending VSCAN sync point, if any. if (vScanSyncTime > time) { syncVScan.removeSyncPoint(); //cerr << "removing predicted VSCAN sync point\n"; } // Calculate moment in time display end occurs. vScanSyncTime = frameStartTime + (displayStart + getNumberOfLines() * TICKS_PER_LINE); //cerr << "new VSCAN is " << (frameStartTime.getTicksTill(vScanSyncTime) / TICKS_PER_LINE) << "\n"; // Register new VSCAN sync point. if (vScanSyncTime > time) { syncVScan.setSyncPoint(vScanSyncTime); //cerr << "inserting new VSCAN sync point\n"; } } void VDP::scheduleHScan(EmuTime::param time) { // Remove pending HSCAN sync point, if any. if (hScanSyncTime > time) { syncHScan.removeSyncPoint(); hScanSyncTime = time; } // Calculate moment in time line match occurs. horizontalScanOffset = displayStart - (100 + 102) + ((controlRegs[19] - controlRegs[23]) & 0xFF) * TICKS_PER_LINE + getRightBorder(); // Display line counter continues into the next frame. // Note that this implementation is not 100% accurate, since the // number of ticks of the *previous* frame should be subtracted. // By switching from NTSC to PAL it may even be possible to get two // HSCANs in a single frame without modifying any other setting. // Fortunately, no known program relies on this. int ticksPerFrame = getTicksPerFrame(); if (horizontalScanOffset >= ticksPerFrame) { horizontalScanOffset -= ticksPerFrame; // Display line counter is reset at the start of the top border. // Any HSCAN that has a higher line number never occurs. if (horizontalScanOffset >= LINE_COUNT_RESET_TICKS) { // This is one way to say "never". horizontalScanOffset = -1000 * TICKS_PER_LINE; } } // Register new HSCAN sync point if interrupt is enabled. if ((controlRegs[0] & 0x10) && horizontalScanOffset >= 0) { // No line interrupt will occur after bottom erase. // NOT TRUE: "after next top border start" is correct. // Note that line interrupt can occur in the next frame. /* EmuTime bottomEraseTime = frameStartTime + getTicksPerFrame() - 3 * TICKS_PER_LINE; */ hScanSyncTime = frameStartTime + horizontalScanOffset; if (hScanSyncTime > time) { syncHScan.setSyncPoint(hScanSyncTime); } } } // TODO: inline? // TODO: Is it possible to get rid of this routine and its sync point? // VSYNC, HSYNC and DISPLAY_START could be scheduled for the next // frame when their callback occurs. // But I'm not sure how to handle the PAL/NTSC setting (which also // influences the frequency at which E/O toggles). void VDP::frameStart(EmuTime::param time) { ++frameCount; //cerr << "VDP::frameStart @ " << time << "\n"; // Toggle E/O. // Actually this should occur half a line earlier, // but for now this is accurate enough. statusReg2 ^= 0x02; // Settings which are fixed at start of frame. // Not sure this is how real MSX does it, but close enough for now. // TODO: Interlace is effectuated in border height, according to // the data book. Exactly when is the fixation point? palTiming = (controlRegs[9] & 0x02) != 0; interlaced = (controlRegs[9] & 0x08) != 0; // Blinking. if (blinkCount != 0) { // counter active? blinkCount--; if (blinkCount == 0) { renderer->updateBlinkState(!blinkState, time); blinkState = !blinkState; blinkCount = ( blinkState ? controlRegs[13] >> 4 : controlRegs[13] & 0x0F ) * 10; } } // TODO: Presumably this is done here // Note that if superimposing is enabled but no external video // signal is provided then the VDP stops producing a signal // (at least on an MSX1, VDP(0)=1 produces "signal lost" on my // monitor) const RawFrame* newSuperimposing = (controlRegs[0] & 1) ? externalVideo : nullptr; if (superimposing != newSuperimposing) { superimposing = newSuperimposing; renderer->updateSuperimposing(superimposing, time); } // Schedule next VSYNC. frameStartTime.reset(time); syncVSync.setSyncPoint(frameStartTime + getTicksPerFrame()); // Schedule DISPLAY_START, VSCAN and HSCAN. scheduleDisplayStart(time); // Inform VDP subcomponents. // TODO: Do this via VDPVRAM? renderer->frameStart(time); spriteChecker->frameStart(time); /* cout << "--> frameStart = " << frameStartTime << ", frameEnd = " << (frameStartTime + getTicksPerFrame()) << ", hscan = " << hScanSyncTime << ", displayStart = " << displayStart << ", timing: " << (palTiming ? "PAL" : "NTSC") << "\n"; */ } // The I/O functions. void VDP::writeIO(word port, byte value, EmuTime::param time) { assert(isInsideFrame(time)); switch (port & (isMSX1VDP() ? 0x01 : 0x03)) { case 0: // VRAM data write vramWrite(value, time); registerDataStored = false; break; case 1: // Register or address write if (registerDataStored) { if (value & 0x80) { if (!(value & 0x40) || isMSX1VDP()) { // Register write. changeRegister( value & controlRegMask, dataLatch, time ); } else { // TODO what happens in this case? // it's not a register write because // that breaks "SNOW26" demo } if (isMSX1VDP()) { // For these VDP's the VRAM pointer is modified when // writing to VDP registers. Without this some demos won't // run as on real MSX1, e.g. Planet of the Epas, Utopia and // Waves 1.2. Thanks to dvik for finding this out. // See also below about not using the latch on MSX1. // Set read/write address. vramPointer = (value << 8 | (vramPointer & 0xFF)) & 0x3FFF; } } else { // Set read/write address. vramPointer = (value << 8 | dataLatch) & 0x3FFF; if (!(value & 0x40)) { // Read ahead. vramRead(time); } } registerDataStored = false; } else { // Note: on MSX1 there seems to be no // latch used, but VDP address writes // are done directly. // Thanks to hap for finding this out. :) if (isMSX1VDP()) { vramPointer = (vramPointer & 0x3F00) | value; } dataLatch = value; registerDataStored = true; } break; case 2: // Palette data write if (paletteDataStored) { int index = controlRegs[16]; int grb = ((value << 8) | dataLatch) & 0x777; setPalette(index, grb, time); controlRegs[16] = (index + 1) & 0x0F; paletteDataStored = false; } else { dataLatch = value; paletteDataStored = true; } break; case 3: { // Indirect register write dataLatch = value; // TODO: What happens if reg 17 is written indirectly? //fprintf(stderr, "VDP indirect register write: %02X\n", value); byte regNr = controlRegs[17]; changeRegister(regNr & 0x3F, value, time); if ((regNr & 0x80) == 0) { // Auto-increment. controlRegs[17] = (regNr + 1) & 0x3F; } break; } } } void VDP::setPalette(int index, word grb, EmuTime::param time) { if (palette[index] != grb) { renderer->updatePalette(index, grb, time); palette[index] = grb; } } void VDP::vramWrite(byte value, EmuTime::param time) { scheduleCpuVramAccess(false, value, time); } byte VDP::vramRead(EmuTime::param time) { // Return the result from a previous read. In case // allowTooFastAccess==true, the call to scheduleCpuVramAccess() // already overwrites that variable, so make a local copy first. byte result = cpuVramData; byte dummy = 0; scheduleCpuVramAccess(true, dummy, time); // schedule next read return result; } void VDP::scheduleCpuVramAccess(bool isRead, byte write, EmuTime::param time) { // Tested on real V9938: 'cpuVramData' is shared between read and write. // E.g. OUT (#98),A followed by IN A,(#98) returns the just written value. if (!isRead) cpuVramData = write; cpuVramReqIsRead = isRead; if (unlikely(pendingCpuAccess)) { // Already scheduled. Do nothing. // The old request has been overwritten by the new request! assert(!allowTooFastAccess); tooFastCallback.execute(); } else { if (unlikely(allowTooFastAccess)) { // Immediately execute request. // In the past, in allowTooFastAccess-mode, we would // still schedule the actual access, but process // pending requests early when a new one arrives before // the old one was handled. Though this would still go // wrong because of the delayed update of // 'vramPointer'. We could _only_ _partly_ work around // that by calculating the actual vram address early // (likely not what the real VDP does). But because // allowTooFastAccess is anyway an artifical situation // we now solve this in a simpler way: simply not // schedule CPU-VRAM accesses. assert(!pendingCpuAccess); executeCpuVramAccess(time); } else { // For V99x8 there are 16 extra cycles, for details see: // doc/internal/vdp-vram-timing/vdp-timing.html // For TMS99x8 the situation is less clear, see // doc/internal/vdp-vram-timing/vdp-timing-2.html // Additional measurements(*) show that picking either 8 or 9 // TMS cycles (equivalent to 32 or 36 V99x8 cycles) gives the // same result as on a real MSX. This corresponds to // respectively 1.49us or 1.68us, the TMS documentation // specifies 2us for this value. // (*) In this test we did a lot of OUT operations (writes to // VRAM) that are exactly N cycles apart. After the writes we // detect whether all were successful by reading VRAM // (slowly). We vary N and found that you get corruption for // N<=26 cycles, but no corruption occurs for N>=27. This test // was done in screen 2 with 4 sprites visible on one line // (though the sprites did not seem to make a difference). // So this test could not decide between 8 or 9 TMS cycles. // To be on the safe side we picked 8. // // Update: 8 cycles (DELTA_32) causes corruption in // 'Chase HQ', see // http://www.msx.org/forum/msx-talk/openmsx/openmsx-about-release-testing-help-wanted // lowering it to 7 cycles seems fine. TODO needs more // investigation. (Just guessing) possibly there are // other variables that influence the exact timing (7 // vs 8 cycles). pendingCpuAccess = true; auto delta = isMSX1VDP() ? VDPAccessSlots::DELTA_28 : VDPAccessSlots::DELTA_16; syncCpuVramAccess.setSyncPoint(getAccessSlot(time, delta)); } } } void VDP::executeCpuVramAccess(EmuTime::param time) { int addr = (controlRegs[14] << 14) | vramPointer; if (displayMode.isPlanar()) { // note: also extended VRAM is interleaved, // because there is only 64kB it's interleaved // with itself (every byte repeated twice) addr = ((addr << 16) | (addr >> 1)) & 0x1FFFF; } bool doAccess; if (likely(!cpuExtendedVram)) { doAccess = true; } else if (likely(vram->getSize() == 192 * 1024)) { addr = 0x20000 | (addr & 0xFFFF); doAccess = true; } else { doAccess = false; } if (doAccess) { if (cpuVramReqIsRead) { cpuVramData = vram->cpuRead(addr, time); } else { vram->cpuWrite(addr, cpuVramData, time); } } else { if (cpuVramReqIsRead) { cpuVramData = 0xFF; } else { // nothing } } vramPointer = (vramPointer + 1) & 0x3FFF; if (vramPointer == 0 && displayMode.isV9938Mode()) { // In MSX2 video modes, pointer range is 128K. controlRegs[14] = (controlRegs[14] + 1) & 0x07; } } EmuTime VDP::getAccessSlot(EmuTime::param time, VDPAccessSlots::Delta delta) const { return VDPAccessSlots::getAccessSlot( getFrameStartTime(), time, delta, *this); } VDPAccessSlots::Calculator VDP::getAccessSlotCalculator( EmuTime::param time, EmuTime::param limit) const { return VDPAccessSlots::getCalculator( getFrameStartTime(), time, limit, *this); } byte VDP::peekStatusReg(byte reg, EmuTime::param time) const { switch (reg) { case 0: spriteChecker->sync(time); return statusReg0; case 1: if (controlRegs[0] & 0x10) { // line int enabled return statusReg1 | (irqHorizontal.getState() ? 1:0); } else { // line int disabled // FH goes up at the start of the right border of IL and // goes down at the start of the next left border. // TODO: Precalc matchLength? int afterMatch = getTicksThisFrame(time) - horizontalScanOffset; if (afterMatch < 0) { afterMatch += getTicksPerFrame(); // afterMatch can still be negative at this // point, see scheduleHScan() } int matchLength = (displayMode.isTextMode() ? 87 : 59) + 27 + 100 + 102; return statusReg1 | (0 <= afterMatch && afterMatch < matchLength); } case 2: { // TODO: Once VDP keeps display/blanking state, keeping // VR is probably part of that, so use it. // --> Is isDisplayArea actually !VR? int ticksThisFrame = getTicksThisFrame(time); int displayEnd = displayStart + getNumberOfLines() * TICKS_PER_LINE; bool vr = ticksThisFrame < displayStart - TICKS_PER_LINE || ticksThisFrame >= displayEnd; return statusReg2 | (getHR(ticksThisFrame) ? 0x20 : 0x00) | (vr ? 0x40 : 0x00) | cmdEngine->getStatus(time); } case 3: return byte(spriteChecker->getCollisionX(time)); case 4: return byte(spriteChecker->getCollisionX(time) >> 8) | 0xFE; case 5: return byte(spriteChecker->getCollisionY(time)); case 6: return byte(spriteChecker->getCollisionY(time) >> 8) | 0xFC; case 7: return cmdEngine->readColor(time); case 8: return byte(cmdEngine->getBorderX(time)); case 9: return byte(cmdEngine->getBorderX(time) >> 8) | 0xFE; default: // non-existent status register return 0xFF; } } byte VDP::readStatusReg(byte reg, EmuTime::param time) { byte ret = peekStatusReg(reg, time); switch (reg) { case 0: spriteChecker->resetStatus(); statusReg0 &= ~0x80; irqVertical.reset(); break; case 1: if (controlRegs[0] & 0x10) { // line int enabled irqHorizontal.reset(); } break; case 5: spriteChecker->resetCollision(); break; case 7: cmdEngine->resetColor(); break; } return ret; } byte VDP::readIO(word port, EmuTime::param time) { assert(isInsideFrame(time)); registerDataStored = false; // Abort any port #1 writes in progress. switch (port & (isMSX1VDP() ? 0x01 : 0x03)) { case 0: // VRAM data read return vramRead(time); case 1: // Status register read // Calculate status register contents. return readStatusReg(controlRegs[15], time); case 2: case 3: return 0xFF; default: UNREACHABLE; return 0xFF; } } byte VDP::peekIO(word /*port*/, EmuTime::param /*time*/) const { // TODO not implemented return 0xFF; } void VDP::changeRegister(byte reg, byte val, EmuTime::param time) { if (reg >= 32) { // MXC belongs to CPU interface; // other bits in this register belong to command engine. if (reg == 45) { cpuExtendedVram = (val & 0x40) != 0; } // Pass command register writes to command engine. if (reg < 47) { cmdEngine->setCmdReg(reg - 32, val, time); } return; } // Make sure only bits that actually exist are written. val &= controlValueMasks[reg]; // Determine the difference between new and old value. byte change = val ^ controlRegs[reg]; // Register 13 is special because writing it resets blinking state, // even if the value in the register doesn't change. if (reg == 13) { // Switch to ON state unless ON period is zero. if (blinkState == ((val & 0xF0) == 0)) { renderer->updateBlinkState(!blinkState, time); blinkState = !blinkState; } if ((val & 0xF0) && (val & 0x0F)) { // Alternating colors, start with ON. blinkCount = (val >> 4) * 10; } else { // Stable color. blinkCount = 0; } } if (!change) return; // Perform additional tasks before new value becomes active. switch (reg) { case 0: if (change & DisplayMode::REG0_MASK) { syncAtNextLine(syncSetMode, time); } break; case 1: if (change & 0x03) { // Update sprites on size and mag changes. spriteChecker->updateSpriteSizeMag(val, time); } // TODO: Reset vertical IRQ if IE0 is reset? if (change & DisplayMode::REG1_MASK) { syncAtNextLine(syncSetMode, time); } if (change & 0x40) { syncAtNextLine(syncSetBlank, time); } break; case 2: { int base = (val << 10) | ~(~0u << 10); // TODO: // I reverted this fix. // Although the code is correct, there is also a counterpart in the // renderer that must be updated. I'm too tired now to find it. // Since name table checking is currently disabled anyway, keeping the // old code does not hurt. // Eventually this line should be re-enabled. /* if (displayMode.isPlanar()) { base = ((base << 16) | (base >> 1)) & 0x1FFFF; } */ renderer->updateNameBase(base, time); break; } case 7: if (getDisplayMode().getByte() != DisplayMode::GRAPHIC7) { if (change & 0xF0) { renderer->updateForegroundColor(val >> 4, time); } if (change & 0x0F) { renderer->updateBackgroundColor(val & 0x0F, time); } } else { renderer->updateBackgroundColor(val, time); } break; case 8: if (change & 0x20) { renderer->updateTransparency((val & 0x20) == 0, time); } if (change & 0x02) { vram->updateSpritesEnabled((val & 0x02) == 0, time); } if (change & 0x08) { vram->updateVRMode((val & 0x08) != 0, time); } break; case 12: if (change & 0xF0) { renderer->updateBlinkForegroundColor(val >> 4, time); } if (change & 0x0F) { renderer->updateBlinkBackgroundColor(val & 0x0F, time); } break; case 16: // Any half-finished palette loads are aborted. paletteDataStored = false; break; case 18: if (change & 0x0F) { syncAtNextLine(syncHorAdjust, time); } break; case 23: spriteChecker->updateVerticalScroll(val, time); renderer->updateVerticalScroll(val, time); break; case 25: if (change & DisplayMode::REG25_MASK) { updateDisplayMode(getDisplayMode().updateReg25(val), time); } if (change & 0x08) { syncAtNextLine(syncHorAdjust, time); } if (change & 0x02) { renderer->updateBorderMask((val & 0x02) != 0, time); } if (change & 0x01) { renderer->updateMultiPage((val & 0x01) != 0, time); } break; case 26: renderer->updateHorizontalScrollHigh(val, time); break; case 27: renderer->updateHorizontalScrollLow(val, time); break; } // Commit the change. controlRegs[reg] = val; // Perform additional tasks after new value became active. // Because base masks cannot be read from the VDP, updating them after // the commit is equivalent to updating before. switch (reg) { case 0: if (change & 0x10) { // IE1 if (val & 0x10) { scheduleHScan(time); } else { irqHorizontal.reset(); } } break; case 1: if (change & 0x20) { // IE0 if (val & 0x20) { // This behaviour is important. Without it, // the intro music in 'Andonis' is way too slow // and the intro logo of 'Zanac' is corrupted. if (statusReg0 & 0x80) { irqVertical.set(); } } else { irqVertical.reset(); } } if ((change & 0x80) && isVDPwithVRAMremapping()) { // confirmed: VRAM remapping only happens on TMS99xx // see VDPVRAM for details on the remapping itself vram->change4k8kMapping((val & 0x80) != 0); } break; case 2: updateNameBase(time); break; case 3: case 10: updateColorBase(time); if (vdpHasPatColMirroring()) updatePatternBase(time); break; case 4: updatePatternBase(time); break; case 5: case 11: updateSpriteAttributeBase(time); break; case 6: updateSpritePatternBase(time); break; case 9: if ((val & 1) && ! warningPrinted) { warningPrinted = true; getCliComm().printWarning ("The running MSX software has set bit 0 of VDP register 9 " "(dot clock direction) to one. In an ordinary MSX, " "the screen would go black and the CPU would stop running."); // TODO: Emulate such behaviour. } if (change & 0x80) { /* cerr << "changed to " << (val & 0x80 ? 212 : 192) << " lines" << " at line " << (getTicksThisFrame(time) / TICKS_PER_LINE) << "\n"; */ // Display lines (192/212) determines display start and end. // TODO: Find out exactly when display start is fixed. // If it is fixed at VSYNC that would simplify things, // but I think it's more likely the current // implementation is accurate. if (time < displayStartSyncTime) { // Display start is not fixed yet. scheduleDisplayStart(time); } else { // Display start is fixed, but display end is not. scheduleVScan(time); } } break; case 19: case 23: scheduleHScan(time); break; case 25: if (change & 0x01) { updateNameBase(time); } break; } } void VDP::syncAtNextLine(SyncBase& type, EmuTime::param time) { int line = getTicksThisFrame(time) / TICKS_PER_LINE; int ticks = (line + 1) * TICKS_PER_LINE; EmuTime nextTime = frameStartTime + ticks; type.setSyncPoint(nextTime); } void VDP::updateNameBase(EmuTime::param time) { int base = (controlRegs[2] << 10) | ~(~0u << 10); // TODO: // I reverted this fix. // Although the code is correct, there is also a counterpart in the // renderer that must be updated. I'm too tired now to find it. // Since name table checking is currently disabled anyway, keeping the // old code does not hurt. // Eventually this line should be re-enabled. /* if (displayMode.isPlanar()) { base = ((base << 16) | (base >> 1)) & 0x1FFFF; } */ int indexMask = displayMode.isBitmapMode() ? ~0u << 17 // TODO: Calculate actual value; how to handle planar? : ~0u << (displayMode.isTextMode() ? 12 : 10); if (controlRegs[25] & 0x01) { // Multi page scrolling. The same bit is used in character and // (non)planar-bitmap modes. // TODO test text modes indexMask &= ~0x8000; } vram->nameTable.setMask(base, indexMask, time); } void VDP::updateColorBase(EmuTime::param time) { int base = (controlRegs[10] << 14) | (controlRegs[3] << 6) | ~(~0u << 6); renderer->updateColorBase(base, time); switch (displayMode.getBase()) { case 0x09: // Text 2. // TODO: Enable this only if dual color is actually active. vram->colorTable.setMask(base, ~0u << 9, time); break; case 0x00: // Graphic 1. vram->colorTable.setMask(base, ~0u << 6, time); break; case 0x04: // Graphic 2. vram->colorTable.setMask(base | (vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time); break; case 0x08: // Graphic 3. vram->colorTable.setMask(base, ~0u << 13, time); break; default: // Other display modes do not use a color table. vram->colorTable.disable(time); } } void VDP::updatePatternBase(EmuTime::param time) { int base = (controlRegs[4] << 11) | ~(~0u << 11); renderer->updatePatternBase(base, time); switch (displayMode.getBase()) { case 0x01: // Text 1. case 0x05: // Text 1 Q. case 0x09: // Text 2. case 0x00: // Graphic 1. case 0x02: // Multicolor. case 0x06: // Multicolor Q. vram->patternTable.setMask(base, ~0u << 11, time); break; case 0x04: // Graphic 2. if (vdpHasPatColMirroring()) { // TMS99XX has weird pattern table behavior: some // bits of the color-base register leak into the // pattern-base. See also: // http://www.youtube.com/watch?v=XJljSJqzDR0 base = (controlRegs[4] << 11) | ((controlRegs[3] & 0x1f) << 6) | ~(~0u << 6); } vram->patternTable.setMask(base | (vdpLacksMirroring() ? 0x1800 : 0), ~0u << 13, time); break; case 0x08: // Graphic 3. vram->patternTable.setMask(base, ~0u << 13, time); break; default: // Other display modes do not use a pattern table. vram->patternTable.disable(time); } } void VDP::updateSpriteAttributeBase(EmuTime::param time) { int mode = displayMode.getSpriteMode(isMSX1VDP()); if (mode == 0) { vram->spriteAttribTable.disable(time); return; } int baseMask = (controlRegs[11] << 15) | (controlRegs[5] << 7) | ~(~0u << 7); int indexMask = mode == 1 ? ~0u << 7 : ~0u << 10; if (displayMode.isPlanar()) { baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF; indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1); } vram->spriteAttribTable.setMask(baseMask, indexMask, time); } void VDP::updateSpritePatternBase(EmuTime::param time) { if (displayMode.getSpriteMode(isMSX1VDP()) == 0) { vram->spritePatternTable.disable(time); return; } int baseMask = (controlRegs[6] << 11) | ~(~0u << 11); int indexMask = ~0u << 11; if (displayMode.isPlanar()) { baseMask = ((baseMask << 16) | (baseMask >> 1)) & 0x1FFFF; indexMask = ((indexMask << 16) | ~(1 << 16)) & (indexMask >> 1); } vram->spritePatternTable.setMask(baseMask, indexMask, time); } void VDP::updateDisplayMode(DisplayMode newMode, EmuTime::param time) { // Synchronise subsystems. vram->updateDisplayMode(newMode, time); // TODO: Is this a useful optimisation, or doesn't it help // in practice? // What aspects have changed: // Switched from planar to nonplanar or vice versa. bool planarChange = newMode.isPlanar() != displayMode.isPlanar(); // Sprite mode changed. bool msx1 = isMSX1VDP(); bool spriteModeChange = newMode.getSpriteMode(msx1) != displayMode.getSpriteMode(msx1); // Commit the new display mode. displayMode = newMode; // Speed up performance of bitmap/character mode splits: // leave last used character mode active. // TODO: Disable it if not used for some time. if (!displayMode.isBitmapMode()) { updateColorBase(time); updatePatternBase(time); } if (planarChange || spriteModeChange) { updateSpritePatternBase(time); updateSpriteAttributeBase(time); } updateNameBase(time); // To be extremely accurate, reschedule hscan when changing // from/to text mode. Text mode has different border width, // which affects the moment hscan occurs. // TODO: Why didn't I implement this yet? // It's one line of code and overhead is not huge either. } void VDP::update(const Setting& setting) { assert((&setting == &cmdTiming) || (&setting == &tooFastAccess)); (void)setting; brokenCmdTiming = cmdTiming .getEnum(); allowTooFastAccess = tooFastAccess.getEnum(); if (unlikely(allowTooFastAccess && pendingCpuAccess)) { // in allowTooFastAccess-mode, don't schedule CPU-VRAM access syncCpuVramAccess.removeSyncPoint(); pendingCpuAccess = false; executeCpuVramAccess(getCurrentTime()); } } // RegDebug VDP::RegDebug::RegDebug(VDP& vdp_) : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() + " regs", "VDP registers.", 0x40) { } byte VDP::RegDebug::read(unsigned address) { auto& vdp = OUTER(VDP, vdpRegDebug); if (address < 0x20) { return vdp.controlRegs[address]; } else if (address < 0x2F) { return vdp.cmdEngine->peekCmdReg(address - 0x20); } else { return 0xFF; } } void VDP::RegDebug::write(unsigned address, byte value, EmuTime::param time) { auto& vdp = OUTER(VDP, vdpRegDebug); vdp.changeRegister(address, value, time); } // StatusRegDebug VDP::StatusRegDebug::StatusRegDebug(VDP& vdp_) : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() + " status regs", "VDP status registers.", 0x10) { } byte VDP::StatusRegDebug::read(unsigned address, EmuTime::param time) { auto& vdp = OUTER(VDP, vdpStatusRegDebug); return vdp.peekStatusReg(address, time); } // PaletteDebug VDP::PaletteDebug::PaletteDebug(VDP& vdp_) : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() + " palette", "V99x8 palette (RBG format)", 0x20) { } byte VDP::PaletteDebug::read(unsigned address) { auto& vdp = OUTER(VDP, vdpPaletteDebug); word grb = vdp.getPalette(address / 2); return (address & 1) ? (grb >> 8) : (grb & 0xff); } void VDP::PaletteDebug::write(unsigned address, byte value, EmuTime::param time) { auto& vdp = OUTER(VDP, vdpPaletteDebug); int index = address / 2; word grb = vdp.getPalette(index); grb = (address & 1) ? (grb & 0x0077) | ((value & 0x07) << 8) : (grb & 0x0700) | (value & 0x77); vdp.setPalette(index, grb, time); } // class VRAMPointerDebug VDP::VRAMPointerDebug::VRAMPointerDebug(VDP& vdp_) : SimpleDebuggable(vdp_.getMotherBoard(), vdp_.getName() == "VDP" ? "VRAM pointer" : vdp_.getName() + " VRAM pointer", "VDP VRAM pointer (14 lower bits)", 2) { } byte VDP::VRAMPointerDebug::read(unsigned address) { auto& vdp = OUTER(VDP, vramPointerDebug); if (address & 1) { return vdp.vramPointer >> 8; // TODO add read/write mode? } else { return vdp.vramPointer & 0xFF; } } void VDP::VRAMPointerDebug::write(unsigned address, byte value, EmuTime::param /*time*/) { auto& vdp = OUTER(VDP, vramPointerDebug); int& ptr = vdp.vramPointer; if (address & 1) { ptr = (ptr & 0x00FF) | ((value & 0x3F) << 8); } else { ptr = (ptr & 0xFF00) | value; } } // class Info VDP::Info::Info(VDP& vdp_, const string& name, string helpText_) : InfoTopic(vdp_.getMotherBoard().getMachineInfoCommand(), vdp_.getName() + '_' + name) , vdp(vdp_) , helpText(std::move(helpText_)) { } void VDP::Info::execute(array_ref /*tokens*/, TclObject& result) const { result.setInt(calc(vdp.getCurrentTime())); } string VDP::Info::help(const vector& /*tokens*/) const { return helpText; } // class FrameCountInfo VDP::FrameCountInfo::FrameCountInfo(VDP& vdp) : Info(vdp, "frame_count", "The current frame number, starts counting at 0 " "when MSX is powered up or reset.") { } int VDP::FrameCountInfo::calc(const EmuTime& /*time*/) const { return vdp.frameCount; } // class CycleInFrameInfo VDP::CycleInFrameInfo::CycleInFrameInfo(VDP& vdp) : Info(vdp, "cycle_in_frame", "The number of VDP cycles since the beginning of " "the current frame. The VDP runs at 6 times the Z80 " "clock frequency, so at approximately 21.5MHz.") { } int VDP::CycleInFrameInfo::calc(const EmuTime& time) const { return vdp.getTicksThisFrame(time); } // class LineInFrameInfo VDP::LineInFrameInfo::LineInFrameInfo(VDP& vdp) : Info(vdp, "line_in_frame", "The absolute line number since the beginning of " "the current frame. Goes from 0 till 262 (NTSC) or " "313 (PAL). Note that this number includes the " "border lines, use 'msx_y_pos' to get MSX " "coordinates.") { } int VDP::LineInFrameInfo::calc(const EmuTime& time) const { return vdp.getTicksThisFrame(time) / VDP::TICKS_PER_LINE; } // class CycleInLineInfo VDP::CycleInLineInfo::CycleInLineInfo(VDP& vdp) : Info(vdp, "cycle_in_line", "The number of VDP cycles since the beginning of " "the current line. See also 'cycle_in_frame'." "Note that this includes the cycles in the border, " "use 'msx_x256_pos' or 'msx_x512_pos' to get MSX " "coordinates.") { } int VDP::CycleInLineInfo::calc(const EmuTime& time) const { return vdp.getTicksThisFrame(time) % VDP::TICKS_PER_LINE; } // class MsxYPosInfo VDP::MsxYPosInfo::MsxYPosInfo(VDP& vdp) : Info(vdp, "msx_y_pos", "Similar to 'line_in_frame', but expressed in MSX " "coordinates. So lines in the top border have " "negative coordinates, lines in the bottom border " "have coordinates bigger or equal to 192 or 212.") { } int VDP::MsxYPosInfo::calc(const EmuTime& time) const { return (vdp.getTicksThisFrame(time) / VDP::TICKS_PER_LINE) - vdp.getLineZero(); } // class MsxX256PosInfo VDP::MsxX256PosInfo::MsxX256PosInfo(VDP& vdp) : Info(vdp, "msx_x256_pos", "Similar to 'cycle_in_frame', but expressed in MSX " "coordinates. So a position in the left border has " "a negative coordinate and a position in the right " "border has a coordinated bigger or equal to 256. " "See also 'msx_x512_pos'.") { } int VDP::MsxX256PosInfo::calc(const EmuTime& time) const { return ((vdp.getTicksThisFrame(time) % VDP::TICKS_PER_LINE) - vdp.getLeftSprites()) / 4; } // class MsxX512PosInfo VDP::MsxX512PosInfo::MsxX512PosInfo(VDP& vdp) : Info(vdp, "msx_x512_pos", "Similar to 'cycle_in_frame', but expressed in " "'narrow' (screen 7) MSX coordinates. So a position " "in the left border has a negative coordinate and " "a position in the right border has a coordinated " "bigger or equal to 512. See also 'msx_x256_pos'.") { } int VDP::MsxX512PosInfo::calc(const EmuTime& time) const { return ((vdp.getTicksThisFrame(time) % VDP::TICKS_PER_LINE) - vdp.getLeftSprites()) / 2; } // version 1: initial version // version 2: added frameCount // version 3: removed verticalAdjust // version 4: removed lineZero // version 5: replace readAhead->cpuVramData, added cpuVramReqIsRead // version 6: added cpuVramReqAddr to solve too_fast_vram_access issue // version 7: removed cpuVramReqAddr again, fixed issue in a different way // version 8: removed 'userData' from Schedulable template void VDP::serialize(Archive& ar, unsigned version) { ar.template serializeBase(*this); if (ar.versionAtLeast(version, 8)) { ar.serialize("syncVSync", syncVSync); ar.serialize("syncDisplayStart", syncDisplayStart); ar.serialize("syncVScan", syncVScan); ar.serialize("syncHScan", syncHScan); ar.serialize("syncHorAdjust", syncHorAdjust); ar.serialize("syncSetMode", syncSetMode); ar.serialize("syncSetBlank", syncSetBlank); ar.serialize("syncCpuVramAccess", syncCpuVramAccess); } else { Schedulable::restoreOld(ar, {&syncVSync, &syncDisplayStart, &syncVScan, &syncHScan, &syncHorAdjust, &syncSetMode, &syncSetBlank, &syncCpuVramAccess}); } // not serialized // std::unique_ptr renderer; // VdpVersion version; // int controlRegMask; // byte controlValueMasks[32]; // bool warningPrinted; ar.serialize("irqVertical", irqVertical); ar.serialize("irqHorizontal", irqHorizontal); ar.serialize("frameStartTime", frameStartTime); ar.serialize("displayStartSyncTime", displayStartSyncTime); ar.serialize("vScanSyncTime", vScanSyncTime); ar.serialize("hScanSyncTime", hScanSyncTime); ar.serialize("displayStart", displayStart); ar.serialize("horizontalScanOffset", horizontalScanOffset); ar.serialize("horizontalAdjust", horizontalAdjust); ar.serialize("registers", controlRegs); ar.serialize("blinkCount", blinkCount); ar.serialize("vramPointer", vramPointer); ar.serialize("palette", palette); ar.serialize("isDisplayArea", isDisplayArea); ar.serialize("palTiming", palTiming); ar.serialize("interlaced", interlaced); ar.serialize("statusReg0", statusReg0); ar.serialize("statusReg1", statusReg1); ar.serialize("statusReg2", statusReg2); ar.serialize("blinkState", blinkState); ar.serialize("dataLatch", dataLatch); ar.serialize("registerDataStored", registerDataStored); ar.serialize("paletteDataStored", paletteDataStored); if (ar.versionAtLeast(version, 5)) { ar.serialize("cpuVramData", cpuVramData); ar.serialize("cpuVramReqIsRead", cpuVramReqIsRead); } else { ar.serialize("readAhead", cpuVramData); } ar.serialize("cpuExtendedVram", cpuExtendedVram); ar.serialize("displayEnabled", displayEnabled); byte mode = displayMode.getByte(); ar.serialize("displayMode", mode); displayMode.setByte(mode); ar.serialize("cmdEngine", *cmdEngine); ar.serialize("spriteChecker", *spriteChecker); // must come after displayMode ar.serialize("vram", *vram); // must come after controlRegs and after spriteChecker if (ar.isLoader()) { pendingCpuAccess = syncCpuVramAccess.pendingSyncPoint(); update(tooFastAccess); } if (ar.versionAtLeast(version, 2)) { ar.serialize("frameCount", frameCount); } else { assert(ar.isLoader()); // We could estimate the frameCount (assume framerate was // constant the whole time). But I think it's better to have // an obviously wrong value than an almost correct value. frameCount = 0; } // externalVideo does not need serializing. It is set on load by the // external video source (e.g. PioneerLDControl). // // TODO should superimposing be serialized? It cannot be recalculated // from register values (it depends on the register values at the start // of this frame). But it will be correct at the start of the next // frame. Probably good enough. if (ar.isLoader()) { renderer->reInit(); } } INSTANTIATE_SERIALIZE_METHODS(VDP); REGISTER_MSXDEVICE(VDP, "VDP"); } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/VDP.hh000066400000000000000000001020671257557151200173140ustar00rootroot00000000000000#ifndef VDP_HH #define VDP_HH #include "MSXDevice.hh" #include "Schedulable.hh" #include "VideoSystemChangeListener.hh" #include "SimpleDebuggable.hh" #include "TclCallback.hh" #include "InfoTopic.hh" #include "IRQHelper.hh" #include "Clock.hh" #include "DisplayMode.hh" #include "Observer.hh" #include "openmsx.hh" #include "outer.hh" #include namespace openmsx { class PostProcessor; class Renderer; class VDPCmdEngine; class VDPVRAM; class SpriteChecker; class Display; class RawFrame; class Setting; template class EnumSetting; namespace VDPAccessSlots { enum Delta : int; class Calculator; } /** Unified implementation of MSX Video Display Processors (VDPs). * MSX1 VDP is Texas Instruments TMS9918A or TMS9928A. * MSX2 VDP is Yamaha V9938. * MSX2+ and turbo R VDP is Yamaha V9958. * * Current implementation is written by Maarten ter Huurne, * with some contributions from other openMSX developers. * The first implementation was based on Sean Young's code from MESS, * which was in turn based on code by Mike Balfour. * Integration of MESS code into openMSX was done by David Heremans. * * All undocumented features as described in the following file * should be emulated: * http://www.msxnet.org/tech/tms9918a.txt * * This class is the VDP core, it uses a separate Renderer to * convert the VRAM contents into pixels on the host machine. * Communicates with the Renderer through a pull variant of the * Observer pattern: a VDP object fires events when its state changes, * the Renderer can retrieve the new state if necessary by calling * methods on the VDP object. * * A note about timing: the start of a frame or line is defined as * the starting time of the corresponding sync (vsync, hsync). */ class VDP final : public MSXDevice, private VideoSystemChangeListener , private Observer { public: /** Number of VDP clock ticks per second. */ static const int TICKS_PER_SECOND = 3579545 * 6; // 21.5MHz; using VDPClock = Clock; /** Number of VDP clock ticks per line. */ static const int TICKS_PER_LINE = 1368; explicit VDP(const DeviceConfig& config); ~VDP(); void powerUp(EmuTime::param time) override; void reset(EmuTime::param time) override; byte readIO(word port, EmuTime::param time) override; byte peekIO(word port, EmuTime::param time) const override; void writeIO(word port, byte value, EmuTime::param time) override; /** Used by Video9000 to be able to couple the VDP and V9990 output. * Can return nullptr in case of renderer=none. This value can change * over the lifetime of the VDP object (on renderer switch). */ PostProcessor* getPostProcessor() const; /** Is this an MSX1 VDP? * @return True if this is an MSX1 VDP * False otherwise. */ inline bool isMSX1VDP() const { return (version & VM_MSX1) != 0; } /** Is this a VDP only capable of PAL? * @return True iff this is a PAL only VDP */ inline bool isVDPwithPALonly() const { return (version & VM_PAL) != 0; } /** Is this a VDP that lacks mirroring? * @return True iff this VDP lacks the screen 2 mirrored mode */ inline bool vdpLacksMirroring() const { return (version & VM_NO_MIRRORING) != 0; } /** Is this a VDP that has pattern/colortable mirroring? * @return True iff this VDP has pattern/colortable mirroring */ inline bool vdpHasPatColMirroring() const { return (version & VM_PALCOL_MIRRORING) != 0; } /** Does this VDP have VRAM remapping when switching from 4k to 8/16k mode? * @return True iff this is a VDP with VRAM remapping */ inline bool isVDPwithVRAMremapping() const { return (version & VM_VRAM_REMAPPING) != 0; } /** Is this a VDP with a Toshiba palette? * @return True iff this VDP has the Toshiba palette */ inline bool hasToshibaPalette() const { return (version & VM_TOSHIBA_PALETTE) != 0; } /** Does this VDP support YJK display? * @return True for V9958, false otherwise. */ inline bool hasYJK() const { return (version & VM_YJK) != 0; } /** Get the display mode the VDP is in. * @return The current display mode. */ inline DisplayMode getDisplayMode() const { return displayMode; } /** Get the VRAM object for this VDP. */ inline VDPVRAM& getVRAM() { return *vram; } /** Are we currently superimposing? * In case of superimpose, returns a pointer to the to-be-superimposed * frame. Returns nullptr if superimpose is not active. */ inline const RawFrame* isSuperimposing() const { // Note that bit 0 of r#0 has no effect on an V9938 or higher, // but this bit is masked out. Also note that on an MSX1, if // bit 0 of r#0 is enabled and there is no external video // source, then we lose sync. // Also note that because this property is fixed per frame we // cannot (re)calculate it from register values. return superimposing; } /** Get the sprite checker for this VDP. */ inline SpriteChecker& getSpriteChecker() { return *spriteChecker; } /** Gets the current transparency setting. * @return True iff color 0 is transparent. */ inline bool getTransparency() const { return (controlRegs[8] & 0x20) == 0; } /** Gets the current foreground color. * @return Color index [0..15]. */ inline int getForegroundColor() const { return controlRegs[7] >> 4; } /** Gets the current background color. * @return Color index. * In Graphic5 mode, the range is [0..15]; * bits 3-2 contains the color for even pixels, * bits 1-0 contains the color for odd pixels. * In Graphic7 mode with YJK off, the range is [0..255]. * In other modes, the range is [0..15]. */ inline int getBackgroundColor() const { byte reg7 = controlRegs[7]; if (displayMode.getByte() == DisplayMode::GRAPHIC7) { return reg7; } else { return reg7 & 0x0F; } } /** Gets the current blinking color for blinking text. * @return Color index [0..15]. */ inline int getBlinkForegroundColor() const { return controlRegs[12] >> 4; } /** Gets the current blinking color for blinking text. * @return Color index [0..15]. */ inline int getBlinkBackgroundColor() const { return controlRegs[12] & 0x0F; } /** Gets the current blink state. * @return True iff alternate colors / page should be displayed. */ inline bool getBlinkState() const { return blinkState; } /** Gets a palette entry. * @param index The index [0..15] in the palette. * @return Color value in the format of the palette registers: * bit 10..8 is green, bit 6..4 is red and bit 2..0 is blue. */ inline word getPalette(int index) const { return palette[index]; } /** Is the display enabled? * Both the regular border and forced blanking by clearing * the display enable bit are considered disabled display. * @return true iff enabled. */ inline bool isDisplayEnabled() const { return isDisplayArea && displayEnabled; } /** Are sprites enabled? * @return True iff blanking is off, the current mode supports * sprites and sprites are not disabled. */ inline bool spritesEnabled() const { return displayEnabled && (displayMode.getSpriteMode(isMSX1VDP()) != 0) && ((controlRegs[8] & 0x02) == 0x00); } /** Same as spritesEnabled(), but may only be called in sprite * mode 1 or 2. Is a tiny bit faster. */ inline bool spritesEnabledFast() const { assert(displayMode.getSpriteMode(isMSX1VDP()) != 0); return displayEnabled && ((controlRegs[8] & 0x02) == 0x00); } /** Still faster variant (just looks at the sprite-enabled-bit). * But only valid in sprite mode 1/2 with screen enabled. */ inline bool spritesEnabledRegister() const { return (controlRegs[8] & 0x02) == 0x00; } /** Gets the current vertical scroll (line displayed at Y=0). * @return Vertical scroll register value. */ inline byte getVerticalScroll() const { return controlRegs[23]; } /** Gets the current horizontal scroll lower bits. * Rather than actually scrolling the screen, this setting shifts the * screen 0..7 bytes to the right. * @return Horizontal scroll low register value. */ inline byte getHorizontalScrollLow() const { return controlRegs[27]; } /** Gets the current horizontal scroll higher bits. * This value determines how many 8-pixel steps the background is * rotated to the left. * @return Horizontal scroll high register value. */ inline byte getHorizontalScrollHigh() const { return controlRegs[26]; } /** Gets the current border mask setting. * Border mask extends the left border by 8 pixels if enabled. * This is a V9958 feature, on older VDPs it always returns false. * @return true iff enabled. */ inline bool isBorderMasked() const { return (controlRegs[25] & 0x02) != 0; } /** Is multi page scrolling enabled? * It is considered enabled if both the multi page scrolling flag is * enabled and the current page is odd. Scrolling wraps into the * lower even page. * @return true iff enabled. */ inline bool isMultiPageScrolling() const { return (controlRegs[25] & 0x01) && (controlRegs[2] & 0x20); } /** Get the absolute line number of display line zero. * Usually this is equal to the height of the top border, * but not so during overscan. */ inline int getLineZero() const { return displayStart / TICKS_PER_LINE; } /** Is PAL timing active? * This setting is fixed at start of frame. * @return True if PAL timing, false if NTSC timing. */ inline bool isPalTiming() const { return palTiming; } /** Get interlace status. * Interlace means the odd fields are displayed half a line lower * than the even fields. Together with even/odd page alternation * this can be used to create an interlaced display. * This setting is fixed at start of frame. * @return True iff interlace is enabled. */ inline bool isInterlaced() const { return interlaced; } /** Get even/odd page alternation status. * Interlace means the odd fields are displayed half a line lower * than the even fields. Together with even/odd page alternation * this can be used to create an interlaced display. * This setting is NOT fixed at start of frame. * TODO: Find out how real VDP does it. * If it fixes it at start of frame, so should we. * If it handles it dynamically (my guess), then an update method * should be added on the Renderer interface. * @return True iff even/odd page alternation is enabled. */ inline bool isEvenOddEnabled() const { return (controlRegs[9] & 4) != 0; } /** Is the even or odd field being displayed? * @return True iff this field should be displayed half a line lower. */ inline bool getEvenOdd() const { return (statusReg2 & 2) != 0; } /** Expresses the state of even/odd page interchange in a mask * on the line number. If even/odd interchange is active, for some * frames lines 256..511 (page 1) are replaced by 0..255 (page 0) * and 768..1023 (page 3, if appicable) by 512..767 (page 2). * Together with the interlace setting this can be used to create * an interlaced display. * Even/odd interchange can also happen because of the 'blink' * feature in bitmap modes. * @return Line number mask that expressed even/odd state. */ inline int getEvenOddMask() const { // TODO: Verify which page is displayed on even fields. return (((~controlRegs[9] & 4) << 6) | ((statusReg2 & 2) << 7)) & (!blinkState << 8); } /** Gets the number of VDP clock ticks (21MHz) elapsed between * a given time and the start of this frame. */ inline int getTicksThisFrame(EmuTime::param time) const { return frameStartTime.getTicksTill_fast(time); } inline EmuTime::param getFrameStartTime() const { return frameStartTime.getTime(); } /** Gets the sprite size in pixels (8/16). */ inline int getSpriteSize() const { return ((controlRegs[1] & 2) << 2) + 8; } /** Are sprites magnified? */ inline bool isSpriteMag() const { return controlRegs[1] & 1; } /** Are commands possible in non Graphic modes? (V9958 only) * @return True iff CMD bit set. */ inline bool getCmdBit() const { return (controlRegs[25] & 0x40) != 0; } /** Gets the number of VDP clockticks (21MHz) per frame. */ inline int getTicksPerFrame() const { return palTiming ? TICKS_PER_LINE * 313 : TICKS_PER_LINE * 262; } /** Is the given timestamp inside the current frame? * Mainly useful for debugging, because relevant timestamps should * always be inside the current frame. * The end time of a frame is still considered inside, * to support the case when the given timestamp is exclusive itself: * a typically "limit" variable. * @param time Timestamp to check. * @return True iff the timestamp is inside the current frame. */ inline bool isInsideFrame(EmuTime::param time) const { return time >= frameStartTime.getTime() && getTicksThisFrame(time) <= getTicksPerFrame(); } /** This is a combination of the (horizontal) set adjust register and * the YJK-mode bit. */ inline int getHorizontalAdjust() const { return horizontalAdjust; } /** Gets the number of VDP clockticks between start of line and the start * of the sprite plane. * The location of the sprite plane is not influenced by horizontal scroll * or border mask. * TODO: Leave out the text mode case, since there are no sprites * in text mode? */ inline int getLeftSprites() const { return 100 + 102 + 56 + (horizontalAdjust - 7) * 4 + (displayMode.isTextMode() ? 36 : 0); } /** Gets the number of VDP clockticks between start of line and the end * of the left border. * Does not include extra pixels of horizontal scroll low, since those * are not actually border pixels (sprites appear in front of them). */ inline int getLeftBorder() const { return getLeftSprites() + (isBorderMasked() ? 8 * 4 : 0); } /** Gets the number of VDP clockticks between start of line and the start * of the right border. */ inline int getRightBorder() const { return getLeftSprites() + (displayMode.isTextMode() ? 960 : 1024); } /** Gets the number of VDP clockticks between start of line and the time * when the background pixel with X coordinate 0 would be drawn. * This includes extra pixels of horizontal scroll low, * but disregards border mask. */ inline int getLeftBackground() const { return getLeftSprites() + getHorizontalScrollLow() * 4; } /** Should only be used by SpriteChecker. Returns the current value * of status register 0 (both the F-flag and the sprite related bits). */ byte getStatusReg0() const { return statusReg0; } /** Should only be used by SpriteChecker. Change the sprite related * bits of status register 0 (leaves the F-flag unchanged). * Bit 6 (5S) is set when more than 4 (sprite mode 1) or 8 (sprite * mode 2) sprites occur on the same line. * Bit 5 (C) is set when sprites collide. * Bit 4..0 (5th sprite number) contains the number of the first * sprite to exceed the limit per line. */ void setSpriteStatus(byte value) { statusReg0 = (statusReg0 & 0x80) | (value & 0x7F); } /** Returns current VR mode. * false -> VR=0, true -> VR=1 */ bool getVRMode() const { return (controlRegs[8] & 8) != 0; } /** Enable superimposing */ void setExternalVideoSource(const RawFrame* externalSource) { externalVideo = externalSource; } /** Value of the cmdTiming setting, true means commands have infinite speed. */ bool getBrokenCmdTiming() const { return brokenCmdTiming; } /** Get the earliest access slot that is at least 'delta' cycles in * the future. */ EmuTime getAccessSlot(EmuTime::param time, VDPAccessSlots::Delta delta) const; /** Same as getAccessSlot(), but it can be _much_ faster for repeated * calls, e.g. in the implementation of VDP commands. However it does * have some limitations: * - The returned calculator only remains valid for as long as * the VDP access timing remains the same (display/sprite enable). * - The calculator needs to see _all_ time changes. * (So this means that in every VDPCmd::execute() method you need * to construct a new calculator). */ VDPAccessSlots::Calculator getAccessSlotCalculator( EmuTime::param time, EmuTime::param limit) const; /** Is there a CPU-VRAM access scheduled. */ bool cpuAccessScheduled() const { return pendingCpuAccess; // pendingSyncPoint(CPU_VRAM_ACCESS) } template void serialize(Archive& ar, unsigned version); private: void initTables(); // VdpVersion bitmasks static const unsigned VM_MSX1 = 1; // set-> MSX1, unset-> MSX2 or MSX2+ static const unsigned VM_PAL = 2; // set-> fixed PAL, unset-> fixed NTSC or switchable static const unsigned VM_NO_MIRRORING = 4; // set-> no (screen2) mirroring static const unsigned VM_PALCOL_MIRRORING = 8; // set-> pattern/color-table mirroring static const unsigned VM_VRAM_REMAPPING = 16; // set-> 4k,8/16k VRAM remapping static const unsigned VM_TOSHIBA_PALETTE = 32; // set-> has Toshiba palette static const unsigned VM_YJK = 64; // set-> has YJK (MSX2+) /** VDP version: the VDP model being emulated. */ enum VdpVersion { /** MSX1 VDP, NTSC version. * TMS9918A has NTSC encoding built in, * while TMS9928A has color difference output; * in emulation there is no difference. */ TMS99X8A = VM_MSX1 | VM_PALCOL_MIRRORING | VM_VRAM_REMAPPING, /** MSX1 VDP, PAL version. */ TMS9929A = VM_MSX1 | VM_PALCOL_MIRRORING | VM_VRAM_REMAPPING | VM_PAL, /** newer variant PAL. */ TMS9129 = VM_MSX1 | VM_PAL, /** newer variant NTSC. */ TMS91X8 = VM_MSX1, /** Toshiba clone (hardwired as PAL). */ T6950PAL = VM_MSX1 | VM_TOSHIBA_PALETTE | VM_NO_MIRRORING | VM_PAL, /** Toshiba clone (hardwired as NTSC). */ T6950NTSC = VM_MSX1 | VM_TOSHIBA_PALETTE | VM_NO_MIRRORING, /** VDP in Toshiba T7937A engine (hardwired as PAL). */ T7937APAL = VM_MSX1 | VM_TOSHIBA_PALETTE | VM_PAL, /** VDP in Toshiba T7937A engine (hardwired as NTSC). */ T7937ANTSC = VM_MSX1 | VM_TOSHIBA_PALETTE, /** MSX2 VDP. */ V9938 = 0, /** MSX2+ and turbo R VDP. */ V9958 = VM_YJK, }; struct SyncBase : public Schedulable { SyncBase(VDP& vdp_) : Schedulable(vdp_.getScheduler()) {} friend class VDP; }; struct SyncVSync : public SyncBase { SyncVSync(VDP& vdp) : SyncBase(vdp) {} void executeUntil(EmuTime::param time) override { auto& vdp = OUTER(VDP, syncVSync); vdp.execVSync(time); } } syncVSync; struct SyncDisplayStart : public SyncBase { SyncDisplayStart(VDP& vdp) : SyncBase(vdp) {} void executeUntil(EmuTime::param time) override { auto& vdp = OUTER(VDP, syncDisplayStart); vdp.execDisplayStart(time); } } syncDisplayStart; struct SyncVScan : public SyncBase { SyncVScan(VDP& vdp) : SyncBase(vdp) {} void executeUntil(EmuTime::param time) override { auto& vdp = OUTER(VDP, syncVScan); vdp.execVScan(time); } } syncVScan; struct SyncHScan : public SyncBase { SyncHScan(VDP& vdp) : SyncBase(vdp) {} void executeUntil(EmuTime::param /*time*/) override { auto& vdp = OUTER(VDP, syncHScan); vdp.execHScan(); } } syncHScan; struct SyncHorAdjust : public SyncBase { SyncHorAdjust(VDP& vdp) : SyncBase(vdp) {} void executeUntil(EmuTime::param time) override { auto& vdp = OUTER(VDP, syncHorAdjust); vdp.execHorAdjust(time); } } syncHorAdjust; struct SyncSetMode : public SyncBase { SyncSetMode(VDP& vdp) : SyncBase(vdp) {} void executeUntil(EmuTime::param time) override { auto& vdp = OUTER(VDP, syncSetMode); vdp.execSetMode(time); } } syncSetMode; struct SyncSetBlank : public SyncBase { SyncSetBlank(VDP& vdp) : SyncBase(vdp) {} void executeUntil(EmuTime::param time) override { auto& vdp = OUTER(VDP, syncSetBlank); vdp.execSetBlank(time); } } syncSetBlank; struct SyncCpuVramAccess : public SyncBase { SyncCpuVramAccess(VDP& vdp) : SyncBase(vdp) {} void executeUntil(EmuTime::param time) override { auto& vdp = OUTER(VDP, syncCpuVramAccess); vdp.execCpuVramAccess(time); } } syncCpuVramAccess; void execVSync(EmuTime::param time); void execDisplayStart(EmuTime::param time); void execVScan(EmuTime::param time); void execHScan(); void execHorAdjust(EmuTime::param time); void execSetMode(EmuTime::param time); void execSetBlank(EmuTime::param time); void execCpuVramAccess(EmuTime::param time); /** Time at which the internal VDP display line counter is reset, * expressed in ticks after vsync. * I would expect the counter to reset at line 16, but measurements * on NMS8250 show it is one line earlier. I'm not sure whether the * actual counter reset happens on line 15 or whether the VDP * timing may be one line off for some reason. * TODO: This is just an assumption, more measurements on real MSX * are necessary to verify there is really such a thing and * if so, that the value is accurate. */ static const int LINE_COUNT_RESET_TICKS = 15 * TICKS_PER_LINE; /** Gets the number of display lines per screen. * @return 192 or 212. */ inline int getNumberOfLines() const { return controlRegs[9] & 0x80 ? 212 : 192; } /** Gets the value of the horizontal retrace status bit. * Note that HR flipping continues at all times, not just during * vertical display range. * @param ticksThisFrame The screen position (in VDP ticks) * to return HR for. * @return True iff the VDP scanning is inside the left/right * border or left/right erase or horizontal sync. * False iff the VDP scanning is in the display range. */ inline bool getHR(int ticksThisFrame) const { // Note: These constants are located inside this function because // GCC 4.0.x won't link if they are in the class scope. /** Length of horizontal blank (HR=1) in text mode, measured in VDP * ticks. */ static const int HBLANK_LEN_TXT = 404; /** Length of horizontal blank (HR=1) in graphics mode, measured in VDP * ticks. */ static const int HBLANK_LEN_GFX = 312; return ( ticksThisFrame + TICKS_PER_LINE - getRightBorder() ) % TICKS_PER_LINE < (displayMode.isTextMode() ? HBLANK_LEN_TXT : HBLANK_LEN_GFX); } // VideoSystemChangeListener interface: void preVideoSystemChange() override; void postVideoSystemChange() override; /** Called both on init and on reset. * Puts VDP into reset state. * Does not call any renderer methods. */ void resetInit(); /** Companion to resetInit: in resetInit the registers are reset, * in this method the new base masks are distributed to the VDP * subsystems. */ void resetMasks(EmuTime::param time); /** Start a new frame. * @param time The moment in emulated time the frame starts. */ void frameStart(EmuTime::param time); /** Schedules a DISPLAY_START sync point. * Also removes a pending DISPLAY_START sync, if any. * Since HSCAN and VSCAN are relative to display start, * their schedule methods are called by this method. * @param time The moment in emulated time this call takes place. * Note: time is not the DISPLAY_START sync time! */ void scheduleDisplayStart(EmuTime::param time); /** Schedules a VSCAN sync point. * Also removes a pending VSCAN sync, if any. * @param time The moment in emulated time this call takes place. * Note: time is not the VSCAN sync time! */ void scheduleVScan(EmuTime::param time); /** Schedules a HSCAN sync point. * Also removes a pending HSCAN sync, if any. * @param time The moment in emulated time this call takes place. * Note: time is not the HSCAN sync time! */ void scheduleHScan(EmuTime::param time); /** Byte is written to VRAM by the CPU. */ void vramWrite(byte value, EmuTime::param time); /** Byte is read from VRAM by the CPU. */ byte vramRead(EmuTime::param time); /** Helper methods for CPU-VRAM access. */ void scheduleCpuVramAccess(bool isRead, byte write, EmuTime::param time); void executeCpuVramAccess(EmuTime::param time); /** Read the contents of a status register */ byte peekStatusReg(byte reg, EmuTime::param time) const; byte readStatusReg(byte reg, EmuTime::param time); /** VDP control register has changed, work out the consequences. */ void changeRegister(byte reg, byte val, EmuTime::param time); /** Schedule a sync point at the start of the next line. */ void syncAtNextLine(SyncBase& type, EmuTime::param time); /** Create a new renderer. */ void createRenderer(); /** Name base mask has changed. * Inform the renderer and the VRAM. */ void updateNameBase(EmuTime::param time); /** Color base mask has changed. * Inform the renderer and the VRAM. */ void updateColorBase(EmuTime::param time); /** Pattern base mask has changed. * Inform the renderer and the VRAM. */ void updatePatternBase(EmuTime::param time); /** Sprite attribute base mask has changed. * Inform the SpriteChecker and the VRAM. */ void updateSpriteAttributeBase(EmuTime::param time); /** Sprite pattern base mask has changed. * Inform the SpriteChecker and the VRAM. */ void updateSpritePatternBase(EmuTime::param time); /** Display mode has changed. * Update displayMode's value and inform the Renderer. */ void updateDisplayMode(DisplayMode newMode, EmuTime::param time); /** Sets a palette entry. * @param index The index [0..15] in the palette. * @param grb value in the format of the palette registers: * bit 10..8 is green, bit 6..4 is red and bit 2..0 is blue. * @param time Moment in time palette change occurs. */ void setPalette(int index, word grb, EmuTime::param time); // Observer void update(const Setting& setting) override; private: Display& display; EnumSetting& cmdTiming; EnumSetting& tooFastAccess; struct RegDebug final : SimpleDebuggable { explicit RegDebug(VDP& vdp); byte read(unsigned address) override; void write(unsigned address, byte value, EmuTime::param time) override; } vdpRegDebug; struct StatusRegDebug final : SimpleDebuggable { explicit StatusRegDebug(VDP& vdp); byte read(unsigned address, EmuTime::param time) override; } vdpStatusRegDebug; struct PaletteDebug final : SimpleDebuggable { explicit PaletteDebug(VDP& vdp); byte read(unsigned address) override; void write(unsigned address, byte value, EmuTime::param time) override; } vdpPaletteDebug; struct VRAMPointerDebug final : SimpleDebuggable { explicit VRAMPointerDebug(VDP& vdp); byte read(unsigned address) override; void write(unsigned address, byte value, EmuTime::param time) override; } vramPointerDebug; class Info : public InfoTopic { public: void execute(array_ref tokens, TclObject& result) const override; std::string help(const std::vector& tokens) const override; virtual int calc(const EmuTime& time) const = 0; protected: Info(VDP& vdp_, const std::string& name, std::string helpText_); VDP& vdp; const std::string helpText; }; struct FrameCountInfo final : Info { FrameCountInfo(VDP& vdp); int calc(const EmuTime& time) const override; } frameCountInfo; struct CycleInFrameInfo final : Info { CycleInFrameInfo(VDP& vdp); int calc(const EmuTime& time) const override; } cycleInFrameInfo; struct LineInFrameInfo final : Info { LineInFrameInfo(VDP& vdp); int calc(const EmuTime& time) const override; } lineInFrameInfo; struct CycleInLineInfo final : Info { CycleInLineInfo(VDP& vdp); int calc(const EmuTime& time) const override; } cycleInLineInfo; struct MsxYPosInfo final : Info { MsxYPosInfo(VDP& vdp); int calc(const EmuTime& time) const override; } msxYPosInfo; struct MsxX256PosInfo final : Info { MsxX256PosInfo(VDP& vdp); int calc(const EmuTime& time) const override; } msxX256PosInfo; struct MsxX512PosInfo final : Info { MsxX512PosInfo(VDP& vdp); int calc(const EmuTime& time) const override; } msxX512PosInfo; /** Renderer that converts this VDP's state into an image. */ std::unique_ptr renderer; /** Command engine: the part of the V9938/58 that executes commands. */ std::unique_ptr cmdEngine; /** Sprite checker: calculates sprite patterns and collisions. */ std::unique_ptr spriteChecker; /** VRAM management object. */ std::unique_ptr vram; /** Is there an external video source which we must superimpose * upon? */ const RawFrame* externalVideo; /** Are we currently superimposing? * This is a combination of the 'externalVideo' member (see above) and * the superimpose-enable bit in VDP register R#0. This property only * changes at most once per frame (at the beginning of the frame). */ const RawFrame* superimposing; /** The emulation time when this frame was started (vsync). */ VDPClock frameStartTime; /** Manages vertical scanning interrupt request. */ OptionalIRQHelper irqVertical; /** Manages horizontal scanning interrupt request. */ OptionalIRQHelper irqHorizontal; /** Time of last set DISPLAY_START sync point. */ EmuTime displayStartSyncTime; /** Time of last set VSCAN sync point. */ EmuTime vScanSyncTime; /** Time of last set HSCAN sync point. */ EmuTime hScanSyncTime; TclCallback tooFastCallback; /** VDP version. */ VdpVersion version; /** The number of already fully rendered frames. * Not used for actual emulation, only for 'frame_count' info topic. * This value cannot be calculated from EmuTime because framerate is * not constant (PAL/NTSC). */ int frameCount; /** VDP ticks between start of frame and start of display. */ int displayStart; /** VDP ticks between start of frame and the moment horizontal * scan match occurs. */ int horizontalScanOffset; /** Horizontal display adjust. * This value is update at the start of a line. */ int horizontalAdjust; /** Control registers. */ byte controlRegs[32]; /** Mask on the control register index: * makes MSX2 registers inaccessible on MSX1, * instead the MSX1 registers are mirrored. */ int controlRegMask; /** Mask on the values of control registers. * This saves a lot of masking when using the register values, * because it is guaranteed non-existant bits are always zero. * It also disables access to VDP features on a VDP model * which does not support those features. */ byte controlValueMasks[32]; /** Blinking count: number of frames until next state. * If the ON or OFF period is 0, blinkCount is fixed to 0. */ int blinkCount; /** VRAM read/write access pointer. * Contains the lower 14 bits of the current VRAM access address. */ int vramPointer; /** V9938 palette. */ word palette[16]; /** Is the current scan position inside the display area? */ bool isDisplayArea; /** Is PAL timing active? False means NTSC timing. * This value is updated at the start of every frame, * to avoid problems with mid-screen PAL/NTSC switching. * @see isPalTiming. */ bool palTiming; /** Is interlace active? * @see isInterlaced. */ bool interlaced; /** Status register 0. * Both the F flag (bit 7) and the sprite related bits (bits 6-0) * are stored here. */ byte statusReg0; /** Status register 1. * Bit 7 and 6 are always zero because light pen is not implemented. * Bit 0 is always zero; its calculation depends on IE1. * So all that remains is the version number. */ byte statusReg1; /** Status register 2. * Bit 7, 4 and 0 of this field are always zero, * their value can be retrieved from the command engine. */ byte statusReg2; /** Blinking state: should alternate color / page be displayed? */ bool blinkState; /** First byte written through port #99, #9A or #9B. */ byte dataLatch; /** Does the data latch have register data (port #99) stored? */ bool registerDataStored; /** Does the data latch have palette data (port #9A) stored? */ bool paletteDataStored; /** VRAM is read as soon as VRAM pointer changes. * TODO: Is this actually what happens? * On TMS9928A the VRAM interface is the only access method. * But on V9938/58 there are other ways to access VRAM; * I wonder if they are consistent with this implementation. * This also holds the soon-to-be-written data for CPU-VRAM writes. */ byte cpuVramData; /** CPU-VRAM requests are not executed immediately (though soon). This * variable indicates whether the pending request is read or write. */ bool cpuVramReqIsRead; bool pendingCpuAccess; // always equal to pendingSyncPoint(CPU_VRAM_ACCESS) /** Does CPU interface access main VRAM (false) or extended VRAM (true)? * This is determined by MXC (R#45, bit 6). */ bool cpuExtendedVram; /** Current dispay mode. Note that this is not always the same as the * display mode that can be obtained by combining the different mode * bits because a mode change only takes place at the start of the * next line. */ DisplayMode displayMode; /** Is display enabled. Note that this is not always the same as bit 6 * of R#1 because the display enable status change only takes place at * the start of the next line. */ bool displayEnabled; /** Has a warning been printed. * This is set when a warning about setting the dotclock direction * is printed. */ bool warningPrinted; /** Cached version of cmdTiming/tooFastAccess setting. */ bool brokenCmdTiming; bool allowTooFastAccess; }; SERIALIZE_CLASS_VERSION(VDP, 8); } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/VDPAccessSlots.cc000066400000000000000000000241161257557151200214470ustar00rootroot00000000000000#include "VDPAccessSlots.hh" namespace openmsx { namespace VDPAccessSlots { // These tables must contain at least one value that is bigger or equal // to 1368+136. So we extend the data with some cyclic duplicates. // Screen rendering disabled (or vertical border). // This is correct (measured on real V9938) for bitmap and character mode. // TODO also correct for text mode? See 'vdp-timing-2.html for more details. static const int16_t slotsScreenOff[154 + 17] = { 0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 164, 172, 180, 188, 196, 204, 212, 220, 228, 236, 244, 252, 260, 268, 276, 292, 300, 308, 316, 324, 332, 340, 348, 356, 364, 372, 380, 388, 396, 404, 420, 428, 436, 444, 452, 460, 468, 476, 484, 492, 500, 508, 516, 524, 532, 548, 556, 564, 572, 580, 588, 596, 604, 612, 620, 628, 636, 644, 652, 660, 676, 684, 692, 700, 708, 716, 724, 732, 740, 748, 756, 764, 772, 780, 788, 804, 812, 820, 828, 836, 844, 852, 860, 868, 876, 884, 892, 900, 908, 916, 932, 940, 948, 956, 964, 972, 980, 988, 996, 1004, 1012, 1020, 1028, 1036, 1044, 1060, 1068, 1076, 1084, 1092, 1100, 1108, 1116, 1124, 1132, 1140, 1148, 1156, 1164, 1172, 1188, 1196, 1204, 1212, 1220, 1228, 1268, 1276, 1284, 1292, 1300, 1308, 1316, 1324, 1334, 1344, 1352, 1360, 1368+ 0, 1368+ 8, 1368+16, 1368+ 24, 1368+ 32, 1368+ 40, 1368+ 48, 1368+56, 1368+ 64, 1368+ 72, 1368+ 80, 1368+ 88, 1368+96, 1368+104, 1368+112, 1368+120, 1368+164 }; // Bitmap mode, sprites disabled. static const int16_t slotsSpritesOff[88 + 16] = { 6, 14, 22, 30, 38, 46, 54, 62, 70, 78, 86, 94, 102, 110, 118, 162, 170, 182, 188, 214, 220, 246, 252, 278, 310, 316, 342, 348, 374, 380, 406, 438, 444, 470, 476, 502, 508, 534, 566, 572, 598, 604, 630, 636, 662, 694, 700, 726, 732, 758, 764, 790, 822, 828, 854, 860, 886, 892, 918, 950, 956, 982, 988, 1014, 1020, 1046, 1078, 1084, 1110, 1116, 1142, 1148, 1174, 1206, 1212, 1266, 1274, 1282, 1290, 1298, 1306, 1314, 1322, 1332, 1342, 1350, 1358, 1366, 1368+ 6, 1368+14, 1368+ 22, 1368+ 30, 1368+ 38, 1368+ 46, 1368+54, 1368+ 62, 1368+ 70, 1368+ 78, 1368+ 86, 1368+94, 1368+102, 1368+110, 1368+118, 1368+162, }; // Character mode, sprites disabled. // TODO these are not actually measured! See 'vdp-timing-2.html'. // [166,1212] is likely correct // [1270,122] is an educated guess, the amount of slots is likely correct, // but they might be shifted a few cycles forwards or backwards. static const int16_t slotsCharSpritesOff[88 + 17] = { 2, 10, 18, 26, 34, 42, 50, 58, 66, 74, 82, 90, 98, 106, 114, 122, 166, 174, 188, 194, 220, 226, 252, 258, 290, 316, 322, 348, 354, 380, 386, 418, 444, 450, 476, 482, 508, 514, 546, 572, 578, 604, 610, 636, 642, 674, 700, 706, 732, 738, 764, 770, 802, 828, 834, 860, 866, 892, 898, 930, 956, 962, 988, 994, 1020, 1026, 1058, 1084, 1090, 1116, 1122, 1148, 1154, 1186, 1212, 1218, 1270, 1278, 1286, 1294, 1302, 1310, 1318, 1326, 1336, 1346, 1354, 1362, 1368+ 2, 1368+ 10, 1368+18, 1368+ 26, 1368+ 34, 1368+ 42, 1368+ 50, 1368+58, 1368+ 66, 1368+ 74, 1368+ 82, 1368+ 90, 1368+98, 1368+106, 1368+114, 1368+122, 1368+166, }; // Bitmap mode, sprites enabled. static const int16_t slotsSpritesOn[31 + 3] = { 28, 92, 162, 170, 188, 220, 252, 316, 348, 380, 444, 476, 508, 572, 604, 636, 700, 732, 764, 828, 860, 892, 956, 988, 1020, 1084, 1116, 1148, 1212, 1264, 1330, 1368+28, 1368+92, 1368+162, }; // Character mode, sprites enabled. static const int16_t slotsCharSpritesOn[31 + 3] = { 32, 96, 166, 174, 188, 220, 252, 316, 348, 380, 444, 476, 508, 572, 604, 636, 700, 732, 764, 828, 860, 892, 956, 988, 1020, 1084, 1116, 1148, 1212, 1268, 1334, 1368+32, 1368+96, 1368+166, }; // Text mode. static const int16_t slotsText[47 + 10] = { 2, 10, 18, 26, 34, 42, 50, 58, 66, 166, 174, 182, 190, 198, 206, 214, 222, 312, 408, 504, 600, 696, 792, 888, 984, 1080, 1176, 1206, 1214, 1222, 1230, 1238, 1246, 1254, 1262, 1270, 1278, 1286, 1294, 1302, 1310, 1318, 1326, 1336, 1346, 1354, 1362, 1368+ 2, 1368+10, 1368+18, 1368+26, 1368+ 34, 1368+42, 1368+50, 1368+58, 1368+66, 1368+166, }; // TMS9918 (MSX1) cycle numbers translated to V99x8 cycles (multiplied by 4). // MSX1 screen off. static const int16_t slotsMsx1ScreenOff[107 + 18] = { 4, 12, 20, 28, 36, 44, 52, 60, 68, 76, 84, 92, 100, 108, 116, 124, 132, 140, 148, 156, 164, 172, 180, 188, 196, 204, 220, 236, 252, 268, 284, 300, 316, 332, 348, 364, 380, 396, 412, 428, 444, 460, 476, 492, 508, 524, 540, 556, 572, 588, 604, 620, 636, 652, 668, 684, 700, 716, 732, 748, 764, 780, 796, 812, 828, 844, 860, 876, 892, 908, 924, 940, 956, 972, 988, 1004, 1020, 1036, 1052, 1068, 1084, 1100, 1116, 1132, 1148, 1164, 1180, 1196, 1212, 1228, 1236, 1244, 1252, 1260, 1268, 1276, 1284, 1292, 1300, 1308, 1316, 1324, 1332, 1340, 1348, 1356, 1364, 1368+ 4, 1368+ 12, 1368+ 20, 1368+ 28, 1368+ 36, 1368+ 44, 1368+ 52, 1368+ 60, 1368+ 68, 1368+ 76, 1368+ 84, 1368+ 92, 1368+100, 1368+108, 1368+116, 1368+124, 1368+132, 1368+140, }; // MSX1 graphic mode 1 and 2 (aka screen 1 and 2). static const int16_t slotsMsx1Gfx12[19 + 8] = { 4, 12, 20, 28, 116, 124, 132, 140, 220, 348, 476, 604, 732, 860, 988, 1116, 1236, 1244, 1364, 1368+ 4, 1368+ 12, 1368+ 20, 1368+ 28, 1368+116, 1368+124, 1368+132, 1368+140, }; // MSX1 graphic mode 3 (aka screen 3). static const int16_t slotsMsx1Gfx3[51 + 8] = { 4, 12, 20, 28, 116, 124, 132, 140, 220, 228, 260, 292, 324, 348, 356, 388, 420, 452, 476, 484, 516, 548, 580, 604, 612, 644, 676, 708, 732, 740, 772, 804, 836, 860, 868, 900, 932, 964, 988, 996, 1028, 1060, 1092, 1116, 1124, 1156, 1188, 1220, 1236, 1244, 1364, 1368+ 4, 1368+ 12, 1368+ 20, 1368+ 28, 1368+116, 1368+124, 1368+132, 1368+140, }; // MSX1 text mode 1 (aka screen 0 width 40). static const int16_t slotsMsx1Text[91 + 18] = { 4, 12, 20, 28, 36, 44, 52, 60, 68, 76, 84, 92, 100, 108, 116, 124, 132, 140, 148, 156, 164, 172, 180, 188, 196, 204, 212, 220, 228, 244, 268, 292, 316, 340, 364, 388, 412, 436, 460, 484, 508, 532, 556, 580, 604, 628, 652, 676, 700, 724, 748, 772, 796, 820, 844, 868, 892, 916, 940, 964, 988, 1012, 1036, 1060, 1084, 1108, 1132, 1156, 1180, 1196, 1204, 1212, 1220, 1228, 1236, 1244, 1252, 1260, 1268, 1276, 1284, 1292, 1300, 1308, 1316, 1324, 1332, 1340, 1348, 1356, 1364, 1368+ 4, 1368+ 12, 1368+ 20, 1368+ 28, 1368+ 36, 1368+ 44, 1368+ 52, 1368+ 60, 1368+ 68, 1368+ 76, 1368+ 84, 1368+ 92, 1368+100, 1368+108, 1368+116, 1368+124, 1368+132, 1368+140, }; // Derived data from the tables above. static uint8_t tabSpritesOn [NUM_DELTAS * TICKS]; static uint8_t tabSpritesOff [NUM_DELTAS * TICKS]; static uint8_t tabCharSpritesOn [NUM_DELTAS * TICKS]; static uint8_t tabCharSpritesOff[NUM_DELTAS * TICKS]; static uint8_t tabText [NUM_DELTAS * TICKS]; static uint8_t tabScreenOff [NUM_DELTAS * TICKS]; static uint8_t tabMsx1Gfx12 [NUM_DELTAS * TICKS]; static uint8_t tabMsx1Gfx3 [NUM_DELTAS * TICKS]; static uint8_t tabMsx1Text [NUM_DELTAS * TICKS]; static uint8_t tabMsx1ScreenOff [NUM_DELTAS * TICKS]; static uint8_t tabBroken [NUM_DELTAS * TICKS]; static void initTable(bool msx1, const int16_t* slots, uint8_t* output) { // !!! Keep this in sync with the 'Delta' enum !!! static const int delta[NUM_DELTAS] = { 0, 1, 16, 24, 28, 32, 40, 48, 64, 72, 88, 104, 120, 128, 136 }; for (auto step : delta) { int p = 0; while (slots[p] < step) ++p; for (int i = 0; i < TICKS; ++i) { if ((slots[p] - i) < step) ++p; assert((slots[p] - i) >= step); unsigned t = slots[p] - i; if (msx1) { if (step <= 40) assert(t < 256); } else { assert(t < 256); } *output++ = t; } } } void initTables() { static bool init = false; if (init) return; init = true; initTable(false, slotsSpritesOn, tabSpritesOn); initTable(false, slotsSpritesOff, tabSpritesOff); initTable(false, slotsCharSpritesOn, tabCharSpritesOn); initTable(false, slotsCharSpritesOff, tabCharSpritesOff); initTable(false, slotsText, tabText); initTable(false, slotsScreenOff, tabScreenOff); initTable(true, slotsMsx1Gfx12, tabMsx1Gfx12); initTable(true, slotsMsx1Gfx3, tabMsx1Gfx3); initTable(true, slotsMsx1Text, tabMsx1Text); initTable(true, slotsMsx1ScreenOff, tabMsx1ScreenOff); for (auto & elem : tabBroken) elem = 0; } static inline const uint8_t* getTab(const VDP& vdp) { if (vdp.getBrokenCmdTiming()) return tabBroken; bool enabled = vdp.isDisplayEnabled(); bool sprites = vdp.spritesEnabledRegister(); auto mode = vdp.getDisplayMode(); bool bitmap = mode.isBitmapMode(); bool text = mode.isTextMode(); bool gfx3 = mode.getBase() == DisplayMode::GRAPHIC3; if (vdp.isMSX1VDP()) { if (!enabled) return tabMsx1ScreenOff; return text ? tabMsx1Text : (gfx3 ? tabMsx1Gfx3 : tabMsx1Gfx12); // TODO undocumented modes } else { if (!enabled) return tabScreenOff; return bitmap ? (sprites ? tabSpritesOn : tabSpritesOff) : (text ? tabText : (sprites ? tabCharSpritesOn : tabCharSpritesOff)); } } EmuTime getAccessSlot( EmuTime::param frame_, EmuTime::param time, Delta delta, const VDP& vdp) { VDP::VDPClock frame(frame_); unsigned ticks = frame.getTicksTill_fast(time) % TICKS; auto* tab = getTab(vdp); return time + VDP::VDPClock::duration(tab[delta + ticks]); } Calculator getCalculator( EmuTime::param frame, EmuTime::param time, EmuTime::param limit, const VDP& vdp) { auto* tab = getTab(vdp); return Calculator(frame, time, limit, tab); } } // namespace VDPAccessSlots } // namespace openmsx openMSX-RELEASE_0_12_0/src/video/VDPAccessSlots.hh000066400000000000000000000055031257557151200214600ustar00rootroot00000000000000#ifndef VDPACCESSSLOTS_HH #define VDPACCESSSLOTS_HH #include "VDP.hh" #include "likely.hh" #include #include namespace openmsx { namespace VDPAccessSlots { static const int TICKS = VDP::TICKS_PER_LINE; enum Delta : int { DELTA_0 = 0 * TICKS, DELTA_1 = 1 * TICKS, DELTA_16 = 2 * TICKS, DELTA_24 = 3 * TICKS, DELTA_28 = 4 * TICKS, DELTA_32 = 5 * TICKS, DELTA_40 = 6 * TICKS, DELTA_48 = 7 * TICKS, DELTA_64 = 8 * TICKS, DELTA_72 = 9 * TICKS, DELTA_88 = 10 * TICKS, DELTA_104 = 11 * TICKS, DELTA_120 = 12 * TICKS, DELTA_128 = 13 * TICKS, DELTA_136 = 14 * TICKS, NUM_DELTAS = 15, }; /** VDP-VRAM access slot calculator, meant to be used in the inner loops of the * VDPCmdEngine commands. Code optimized for the case that: * - timing remains constant (sprites/display enable/disable) * - there are more calls to next() and limitReached() than to getTime() */ class Calculator { public: /** This shouldn't be called directly, instead use getCalculator(). */ Calculator(EmuTime::param frame, EmuTime::param time, EmuTime::param limit_, const uint8_t* tab_) : ref(frame), tab(tab_) { assert(frame <= time); assert(frame <= limit_); // not required that time <= limit ticks = ref.getTicksTill_fast(time); limit = ref.getTicksTill_fast(limit_); int lines = ticks / TICKS; ticks -= lines * TICKS; limit -= lines * TICKS; // might be negative ref += lines * TICKS; assert(0 <= ticks); assert(ticks < TICKS); } /** Has 'time' advanced to or past 'limit'? */ inline bool limitReached() const { return ticks >= limit; } /** Get the current time. Initially this will return the 'time' * constructor parameter. Each call to next() will increase this * value. */ inline EmuTime getTime() const { return ref.getFastAdd(ticks); } /** Advance time to the earliest access slot that is at least 'delta' * ticks later than the current time. */ inline void next(Delta delta) { ticks += tab[delta + ticks]; if (unlikely(ticks >= TICKS)) { ticks -= TICKS; limit -= TICKS; ref += TICKS; } } private: int ticks; int limit; VDP::VDPClock ref; const uint8_t* const tab; }; /** This function should be called (once) before the next functions. */ void initTables(); /** Return the time of the next available access slot that is at least 'delta' * cycles later than 'time'. The start of the current 'frame' is needed for * reference. */ EmuTime getAccessSlot(EmuTime::param frame, EmuTime::param time, Delta delta, const VDP& vdp); /** When many calls to getAccessSlot() are needed, it's more efficient to * instead use this function. */ Calculator getCalculator( EmuTime::param frame, EmuTime::param time, EmuTime::param limit, const VDP& vdp); } // namespace VDPAccessSlots } // namespace openmsx #endif openMSX-RELEASE_0_12_0/src/video/VDPCmdEngine.cc000066400000000000000000001745371257557151200210670ustar00rootroot00000000000000/* TODO: - How is 64K VRAM handled? VRAM size is never inspected by the command engine. How does a real MSX handle it? Mirroring of first 64K or empty memory space? - How is extended VRAM handled? The current VDP implementation does not support it. Since it is not accessed by the renderer, it is possible allocate it here. But maybe it makes more sense to have all RAM managed by the VDP? - Currently all VRAM access is done at the start time of a series of updates: currentTime is not increased until the very end of the sync method. It should ofcourse be updated after every read and write. An acceptable approximation would be an update after every pixel/byte operation. */ /* About NX, NY - for block commands NX = 0 is equivalent to NX = 512 (TODO recheck this) and NY = 0 is equivalent to NY = 1024 - when NX or NY is too large and the VDP command hits the border, the following happens: - when the left or right border is hit, the line terminates - when the top border is hit (line 0) the command terminates - when the bottom border (line 511 or 1023) the command continues (wraps to the top) - in 512 lines modes (e.g. screen 7) NY is NOT limited to 512, so when NY > 512, part of the screen is overdrawn twice - in 256 columns modes (e.g. screen 5) when "SX/DX >= 256", only 1 element (pixel or byte) is processed per horizontal line. The real x-ccordinate is "SX/DX & 255". */ #include "VDPCmdEngine.hh" #include "EmuTime.hh" #include "VDPVRAM.hh" #include "serialize.hh" #include "unreachable.hh" #include "memory.hh" #include #include #include using std::min; using std::max; namespace openmsx { using namespace VDPAccessSlots; // Constants: const byte MXD = 0x20; const byte MXS = 0x10; const byte DIY = 0x08; const byte DIX = 0x04; const byte EQ = 0x02; const byte MAJ = 0x01; // Inline methods first, to make sure they are actually inlined: template static inline unsigned clipNX_1_pixel(unsigned DX, unsigned NX, byte ARG) { if (unlikely(DX >= Mode::PIXELS_PER_LINE)) { return 1; } NX = NX ? NX : Mode::PIXELS_PER_LINE; return (ARG & DIX) ? min(NX, DX + 1) : min(NX, Mode::PIXELS_PER_LINE - DX); } template static inline unsigned clipNX_1_byte(unsigned DX, unsigned NX, byte ARG) { static const unsigned BYTES_PER_LINE = Mode::PIXELS_PER_LINE >> Mode::PIXELS_PER_BYTE_SHIFT; DX >>= Mode::PIXELS_PER_BYTE_SHIFT; if (unlikely(BYTES_PER_LINE <= DX)) { return 1; } NX >>= Mode::PIXELS_PER_BYTE_SHIFT; NX = NX ? NX : BYTES_PER_LINE; return (ARG & DIX) ? min(NX, DX + 1) : min(NX, BYTES_PER_LINE - DX); } template static inline unsigned clipNX_2_pixel(unsigned SX, unsigned DX, unsigned NX, byte ARG) { if (unlikely(SX >= Mode::PIXELS_PER_LINE) || unlikely(DX >= Mode::PIXELS_PER_LINE)) { return 1; } NX = NX ? NX : Mode::PIXELS_PER_LINE; return (ARG & DIX) ? min(NX, min(SX, DX) + 1) : min(NX, Mode::PIXELS_PER_LINE - max(SX, DX)); } template static inline unsigned clipNX_2_byte(unsigned SX, unsigned DX, unsigned NX, byte ARG) { static const unsigned BYTES_PER_LINE = Mode::PIXELS_PER_LINE >> Mode::PIXELS_PER_BYTE_SHIFT; SX >>= Mode::PIXELS_PER_BYTE_SHIFT; DX >>= Mode::PIXELS_PER_BYTE_SHIFT; if (unlikely(BYTES_PER_LINE <= SX) || unlikely(BYTES_PER_LINE <= DX)) { return 1; } NX >>= Mode::PIXELS_PER_BYTE_SHIFT; NX = NX ? NX : BYTES_PER_LINE; return (ARG & DIX) ? min(NX, min(SX, DX) + 1) : min(NX, BYTES_PER_LINE - max(SX, DX)); } static inline unsigned clipNY_1(unsigned DY, unsigned NY, byte ARG) { NY = NY ? NY : 1024; return (ARG & DIY) ? min(NY, DY + 1) : NY; } static inline unsigned clipNY_2(unsigned SY, unsigned DY, unsigned NY, byte ARG) { NY = NY ? NY : 1024; return (ARG & DIY) ? min(NY, min(SY, DY) + 1) : NY; } struct IncrByteAddr4; struct IncrByteAddr5; struct IncrByteAddr6; struct IncrByteAddr7; struct IncrPixelAddr4; struct IncrPixelAddr5; struct IncrPixelAddr6; struct IncrMask4; struct IncrMask5; struct IncrMask7; struct IncrShift4; struct IncrShift5; struct IncrShift7; using IncrPixelAddr7 = IncrByteAddr7; using IncrMask6 = IncrMask4; using IncrShift6 = IncrShift4; template static void psetFast( EmuTime::param time, VDPVRAM& vram, unsigned addr, byte color, byte mask, LogOp op) { byte src = vram.cmdWriteWindow.readNP(addr); op(time, vram, addr, src, color, mask); } /** Represents V9938 Graphic 4 mode (SCREEN5). */ struct Graphic4Mode { using IncrByteAddr = IncrByteAddr4; using IncrPixelAddr = IncrPixelAddr4; using IncrMask = IncrMask4; using IncrShift = IncrShift4; static const byte COLOR_MASK = 0x0F; static const byte PIXELS_PER_BYTE = 2; static const byte PIXELS_PER_BYTE_SHIFT = 1; static const unsigned PIXELS_PER_LINE = 256; static inline unsigned addressOf(unsigned x, unsigned y, bool extVRAM); static inline byte point(VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM); template static inline void pset(EmuTime::param time, VDPVRAM& vram, unsigned x, unsigned addr, byte src, byte color, LogOp op); static inline byte duplicate(byte color); }; inline unsigned Graphic4Mode::addressOf( unsigned x, unsigned y, bool extVRAM) { return likely(!extVRAM) ? (((y & 1023) << 7) | ((x & 255) >> 1)) : (((y & 511) << 7) | ((x & 255) >> 1) | 0x20000); } inline byte Graphic4Mode::point( VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM) { return ( vram.cmdReadWindow.readNP(addressOf(x, y, extVRAM)) >> (((~x) & 1) << 2) ) & 15; } template inline void Graphic4Mode::pset( EmuTime::param time, VDPVRAM& vram, unsigned x, unsigned addr, byte src, byte color, LogOp op) { byte sh = ((~x) & 1) << 2; op(time, vram, addr, src, color << sh, ~(15 << sh)); } inline byte Graphic4Mode::duplicate(byte color) { assert((color & 0xF0) == 0); return color | (color << 4); } /** Represents V9938 Graphic 5 mode (SCREEN6). */ struct Graphic5Mode { using IncrByteAddr = IncrByteAddr5; using IncrPixelAddr = IncrPixelAddr5; using IncrMask = IncrMask5; using IncrShift = IncrShift5; static const byte COLOR_MASK = 0x03; static const byte PIXELS_PER_BYTE = 4; static const byte PIXELS_PER_BYTE_SHIFT = 2; static const unsigned PIXELS_PER_LINE = 512; static inline unsigned addressOf(unsigned x, unsigned y, bool extVRAM); static inline byte point(VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM); template static inline void pset(EmuTime::param time, VDPVRAM& vram, unsigned x, unsigned addr, byte src, byte color, LogOp op); static inline byte duplicate(byte color); }; inline unsigned Graphic5Mode::addressOf( unsigned x, unsigned y, bool extVRAM) { return likely(!extVRAM) ? (((y & 1023) << 7) | ((x & 511) >> 2)) : (((y & 511) << 7) | ((x & 511) >> 2) | 0x20000); } inline byte Graphic5Mode::point( VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM) { return ( vram.cmdReadWindow.readNP(addressOf(x, y, extVRAM)) >> (((~x) & 3) << 1) ) & 3; } template inline void Graphic5Mode::pset( EmuTime::param time, VDPVRAM& vram, unsigned x, unsigned addr, byte src, byte color, LogOp op) { byte sh = ((~x) & 3) << 1; op(time, vram, addr, src, color << sh, ~(3 << sh)); } inline byte Graphic5Mode::duplicate(byte color) { assert((color & 0xFC) == 0); color |= color << 2; color |= color << 4; return color; } /** Represents V9938 Graphic 6 mode (SCREEN7). */ struct Graphic6Mode { using IncrByteAddr = IncrByteAddr6; using IncrPixelAddr = IncrPixelAddr6; using IncrMask = IncrMask6; using IncrShift = IncrShift6; static const byte COLOR_MASK = 0x0F; static const byte PIXELS_PER_BYTE = 2; static const byte PIXELS_PER_BYTE_SHIFT = 1; static const unsigned PIXELS_PER_LINE = 512; static inline unsigned addressOf(unsigned x, unsigned y, bool extVRAM); static inline byte point(VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM); template static inline void pset(EmuTime::param time, VDPVRAM& vram, unsigned x, unsigned addr, byte src, byte color, LogOp op); static inline byte duplicate(byte color); }; inline unsigned Graphic6Mode::addressOf( unsigned x, unsigned y, bool extVRAM) { return likely(!extVRAM) ? (((x & 2) << 15) | ((y & 511) << 7) | ((x & 511) >> 2)) : (0x20000 | ((y & 511) << 7) | ((x & 511) >> 2)); } inline byte Graphic6Mode::point( VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM) { return ( vram.cmdReadWindow.readNP(addressOf(x, y, extVRAM)) >> (((~x) & 1) << 2) ) & 15; } template inline void Graphic6Mode::pset( EmuTime::param time, VDPVRAM& vram, unsigned x, unsigned addr, byte src, byte color, LogOp op) { byte sh = ((~x) & 1) << 2; op(time, vram, addr, src, color << sh, ~(15 << sh)); } inline byte Graphic6Mode::duplicate(byte color) { assert((color & 0xF0) == 0); return color | (color << 4); } /** Represents V9938 Graphic 7 mode (SCREEN8). */ struct Graphic7Mode { using IncrByteAddr = IncrByteAddr7; using IncrPixelAddr = IncrPixelAddr7; using IncrMask = IncrMask7; using IncrShift = IncrShift7; static const byte COLOR_MASK = 0xFF; static const byte PIXELS_PER_BYTE = 1; static const byte PIXELS_PER_BYTE_SHIFT = 0; static const unsigned PIXELS_PER_LINE = 256; static inline unsigned addressOf(unsigned x, unsigned y, bool extVRAM); static inline byte point(VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM); template static inline void pset(EmuTime::param time, VDPVRAM& vram, unsigned x, unsigned addr, byte src, byte color, LogOp op); static inline byte duplicate(byte color); }; inline unsigned Graphic7Mode::addressOf( unsigned x, unsigned y, bool extVRAM) { return likely(!extVRAM) ? (((x & 1) << 16) | ((y & 511) << 7) | ((x & 255) >> 1)) : (0x20000 | ((y & 511) << 7) | ((x & 255) >> 1)); } inline byte Graphic7Mode::point( VDPVRAM& vram, unsigned x, unsigned y, bool extVRAM) { return vram.cmdReadWindow.readNP(addressOf(x, y, extVRAM)); } template inline void Graphic7Mode::pset( EmuTime::param time, VDPVRAM& vram, unsigned /*x*/, unsigned addr, byte src, byte color, LogOp op) { op(time, vram, addr, src, color, 0); } inline byte Graphic7Mode::duplicate(byte color) { return color; } /** Incremental address calculation (byte based, no extended VRAM) */ struct IncrByteAddr4 { IncrByteAddr4(unsigned x, unsigned y, int /*tx*/) { addr = Graphic4Mode::addressOf(x, y, false); } unsigned getAddr() const { return addr; } void step(int tx) { addr += (tx >> 1); } private: unsigned addr; }; struct IncrByteAddr5 { IncrByteAddr5(unsigned x, unsigned y, int /*tx*/) { addr = Graphic5Mode::addressOf(x, y, false); } unsigned getAddr() const { return addr; } void step(int tx) { addr += (tx >> 2); } private: unsigned addr; }; struct IncrByteAddr7 { IncrByteAddr7(unsigned x, unsigned y, int tx) : delta2((tx > 0) ? ( 0x10000 ^ (1 - 0x10000)) : (-0x10000 ^ (0x10000 - 1))) { addr = Graphic7Mode::addressOf(x, y, false); delta = (tx > 0) ? 0x10000 : (0x10000 - 1); if (x & 1) delta ^= delta2; } unsigned getAddr() const { return addr; } void step(int /*tx*/) { addr += delta; delta ^= delta2; } private: unsigned addr; unsigned delta; const unsigned delta2; }; struct IncrByteAddr6 : IncrByteAddr7 { IncrByteAddr6(unsigned x, unsigned y, int tx) : IncrByteAddr7(x >> 1, y, tx) { } }; /** Incremental address calculation (pixel-based) */ struct IncrPixelAddr4 { IncrPixelAddr4(unsigned x, unsigned y, int tx) { addr = Graphic4Mode::addressOf(x, y, false); delta = (tx == 1) ? (x & 1) : ((x & 1) - 1); } unsigned getAddr() const { return addr; } void step(int tx) { addr += delta; delta ^= tx; } private: unsigned addr; unsigned delta; }; struct IncrPixelAddr5 { IncrPixelAddr5(unsigned x, unsigned y, int tx) { addr = Graphic5Mode::addressOf(x, y, false); // x | 0 | 1 | 2 | 3 //----------------------- c1 = -(signed(x) & 1); // | 0 | -1 | 0 | -1 c2 = (x & 2) >> 1; // | 0 | 0 | 1 | 1 if (tx < 0) { c1 = ~c1; // | -1 | 0 | -1 | 0 c2 -= 1; // | -1 | -1 | 0 | 0 } } unsigned getAddr() const { return addr; } void step(int tx) { addr += (c1 & c2); c2 ^= (c1 & tx); c1 = ~c1; } private: unsigned addr; unsigned c1; unsigned c2; }; struct IncrPixelAddr6 { IncrPixelAddr6(unsigned x, unsigned y, int tx) : c3((tx == 1) ? unsigned(0x10000 ^ (1 - 0x10000)) // == -0x1FFFF : unsigned(-0x10000 ^ (0x10000 - 1))) // == -1 { addr = Graphic6Mode::addressOf(x, y, false); c1 = -(signed(x) & 1); if (tx == 1) { c2 = (x & 2) ? (1 - 0x10000) : 0x10000; } else { c1 = ~c1; c2 = (x & 2) ? -0x10000 : (0x10000 - 1); } } unsigned getAddr() const { return addr; } void step(int /*tx*/) { addr += (c1 & c2); c2 ^= (c1 & c3); c1 = ~c1; } private: unsigned addr; unsigned c1; unsigned c2; const unsigned c3; }; /** Incremental mask calculation. * Mask has 0-bits in the position of the pixel, 1-bits elsewhere. */ struct IncrMask4 { IncrMask4(unsigned x, int /*tx*/) { mask = 0x0F << ((x & 1) << 2); } byte getMask() const { return mask; } void step() { mask = ~mask; } private: byte mask; }; struct IncrMask5 { IncrMask5(unsigned x, int tx) : shift((tx > 0) ? 6 : 2) { mask = ~(0xC0 >> ((x & 3) << 1)); } byte getMask() const { return mask; } void step() { mask = (mask << shift) | (mask >> (8 - shift)); } private: byte mask; const byte shift; }; struct IncrMask7 { IncrMask7(unsigned /*x*/, int /*tx*/) {} byte getMask() const { return 0; } void step() {} }; /* Shift between source and destination pixel for LMMM command. */ struct IncrShift4 { IncrShift4(unsigned sx, unsigned dx) : shift(((dx - sx) & 1) * 4) { }; byte doShift(byte color) const { return (color >> shift) | (color << shift); } private: const byte shift; }; struct IncrShift5 { IncrShift5(unsigned sx, unsigned dx) : shift(((dx - sx) & 3) * 2) { }; byte doShift(byte color) const { return (color >> shift) | (color << (8 - shift)); } private: const byte shift; }; struct IncrShift7 { IncrShift7(unsigned /*sx*/, unsigned /*dx*/) {} byte doShift(byte color) const { return color; } }; // Logical operations: struct DummyOp { void operator()(EmuTime::param /*time*/, VDPVRAM& /*vram*/, unsigned /*addr*/, byte /*src*/, byte /*color*/, byte /*mask*/) const { // Undefined logical operations do nothing. } }; struct ImpOp { void operator()(EmuTime::param time, VDPVRAM& vram, unsigned addr, byte src, byte color, byte mask) const { vram.cmdWrite(addr, (src & mask) | color, time); } }; struct AndOp { void operator()(EmuTime::param time, VDPVRAM& vram, unsigned addr, byte src, byte color, byte mask) const { vram.cmdWrite(addr, src & (color | mask), time); } }; struct OrOp { void operator()(EmuTime::param time, VDPVRAM& vram, unsigned addr, byte src, byte color, byte /*mask*/) const { vram.cmdWrite(addr, src | color, time); } }; struct XorOp { void operator()(EmuTime::param time, VDPVRAM& vram, unsigned addr, byte src, byte color, byte /*mask*/) const { vram.cmdWrite(addr, src ^ color, time); } }; struct NotOp { void operator()(EmuTime::param time, VDPVRAM& vram, unsigned addr, byte src, byte color, byte mask) const { vram.cmdWrite(addr, (src & mask) | ~(color | mask), time); } }; template struct TransparentOp : Op { void operator()(EmuTime::param time, VDPVRAM& vram, unsigned addr, byte src, byte color, byte mask) const { // TODO does this skip the write or re-write the original value // might make a difference in case the CPU has written // the same address inbetween the command read and write if (color) Op::operator()(time, vram, addr, src, color, mask); } }; using TImpOp = TransparentOp; using TAndOp = TransparentOp; using TOrOp = TransparentOp; using TXorOp = TransparentOp; using TNotOp = TransparentOp; // Commands /** Abort */ struct AbortCmd : public VDPCmd { void start(EmuTime::param time, VDPCmdEngine& engine) override; void execute(EmuTime::param limit, VDPCmdEngine& engine) override; }; void AbortCmd::start(EmuTime::param time, VDPCmdEngine& engine) { engine.commandDone(time); } void AbortCmd::execute(EmuTime::param /*limit*/, VDPCmdEngine& /*engine*/) { UNREACHABLE; } /** Point */ struct PointBaseCmd : public VDPCmd { void start(EmuTime::param time, VDPCmdEngine& engine) override; }; template struct PointCmd : public PointBaseCmd { void execute(EmuTime::param limit, VDPCmdEngine& engine) override; }; void PointBaseCmd::start(EmuTime::param time, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; vram.cmdReadWindow.setMask(0x3FFFF, ~0u << 18, time); vram.cmdWriteWindow.disable(time); engine.time = time; engine.nextAccessSlot(); engine.statusChangeTime = EmuTime::zero; // will finish soon } template void PointCmd::execute(EmuTime::param limit, VDPCmdEngine& engine) { if (unlikely(engine.time >= limit)) return; VDPVRAM& vram = engine.vram; bool srcExt = (engine.ARG & MXS) != 0; bool doPoint = !srcExt || engine.hasExtendedVRAM; engine.COL = likely(doPoint) ? Mode::point(vram, engine.SX, engine.SY, srcExt) : 0xFF; engine.commandDone(engine.time); } /** Pset */ struct PsetBaseCmd : public VDPCmd { void start(EmuTime::param time, VDPCmdEngine& engine) override; }; template struct PsetCmd : public PsetBaseCmd { void execute(EmuTime::param limit, VDPCmdEngine& engine) override; }; void PsetBaseCmd::start(EmuTime::param time, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; vram.cmdReadWindow.disable(time); vram.cmdWriteWindow.setMask(0x3FFFF, ~0u << 18, time); engine.time = time; engine.nextAccessSlot(); engine.statusChangeTime = EmuTime::zero; // will finish soon engine.phase = 0; } template void PsetCmd::execute(EmuTime::param limit, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; bool dstExt = (engine.ARG & MXD) != 0; bool doPset = !dstExt || engine.hasExtendedVRAM; unsigned addr = Mode::addressOf(engine.DX, engine.DY, dstExt); switch (engine.phase) { case 0: if (unlikely(engine.time >= limit)) { engine.phase = 0; break; } if (likely(doPset)) { engine.tmpDst = vram.cmdWriteWindow.readNP(addr); } engine.nextAccessSlot(DELTA_24); // TODO // fall-through case 1: if (unlikely(engine.time >= limit)) { engine.phase = 1; break; } if (likely(doPset)) { byte col = engine.COL & Mode::COLOR_MASK; Mode::pset(engine.time, vram, engine.DX, addr, engine.tmpDst, col, LogOp()); } engine.commandDone(engine.time); break; default: UNREACHABLE; } } /** Search a dot. */ struct SrchBaseCmd : public VDPCmd { void start(EmuTime::param time, VDPCmdEngine& engine) override; }; template struct SrchCmd : public SrchBaseCmd { void execute(EmuTime::param limit, VDPCmdEngine& engine) override; }; void SrchBaseCmd::start(EmuTime::param time, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; vram.cmdReadWindow.setMask(0x3FFFF, ~0u << 18, time); vram.cmdWriteWindow.disable(time); engine.ASX = engine.SX; engine.time = time; engine.nextAccessSlot(); engine.statusChangeTime = EmuTime::zero; // we can find it any moment } template void SrchCmd::execute(EmuTime::param limit, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; byte CL = engine.COL & Mode::COLOR_MASK; int TX = (engine.ARG & DIX) ? -1 : 1; bool AEQ = (engine.ARG & EQ) != 0; // TODO: Do we look for "==" or "!="? // TODO use MXS or MXD here? // datasheet says MXD but MXS seems more logical bool srcExt = (engine.ARG & MXS) != 0; bool doPoint = !srcExt || engine.hasExtendedVRAM; auto calculator = engine.getSlotCalculator(limit); while (!calculator.limitReached()) { byte p = likely(doPoint) ? Mode::point(vram, engine.ASX, engine.SY, srcExt) : 0xFF; if ((p == CL) ^ AEQ) { engine.status |= 0x10; // border detected engine.commandDone(calculator.getTime()); break; } if ((engine.ASX += TX) & Mode::PIXELS_PER_LINE) { engine.status &= 0xEF; // border not detected engine.commandDone(calculator.getTime()); break; } calculator.next(DELTA_88); // TODO } engine.time = calculator.getTime(); } /** Draw a line. */ struct LineBaseCmd : public VDPCmd { void start(EmuTime::param time, VDPCmdEngine& engine) override; }; template struct LineCmd : public LineBaseCmd { void execute(EmuTime::param limit, VDPCmdEngine& engine) override; }; void LineBaseCmd::start(EmuTime::param time, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; vram.cmdReadWindow.disable(time); vram.cmdWriteWindow.setMask(0x3FFFF, ~0u << 18, time); engine.NY &= 1023; engine.ASX = ((engine.NX - 1) >> 1); engine.ADX = engine.DX; engine.ANX = 0; engine.time = time; engine.nextAccessSlot(); engine.statusChangeTime = EmuTime::zero; // TODO can still be optimized engine.phase = 0; } template void LineCmd::execute(EmuTime::param limit, VDPCmdEngine& engine) { // See doc/line-speed.txt for some background info on the timing. VDPVRAM& vram = engine.vram; byte CL = engine.COL & Mode::COLOR_MASK; int TX = (engine.ARG & DIX) ? -1 : 1; int TY = (engine.ARG & DIY) ? -1 : 1; bool dstExt = (engine.ARG & MXD) != 0; bool doPset = !dstExt || engine.hasExtendedVRAM; unsigned addr = Mode::addressOf(engine.ADX, engine.DY, dstExt); auto calculator = engine.getSlotCalculator(limit); switch (engine.phase) { case 0: loop: if (unlikely(calculator.limitReached())) { engine.phase = 0; break; } if (likely(doPset)) { engine.tmpDst = vram.cmdWriteWindow.readNP(addr); } calculator.next(DELTA_24); // fall-through case 1: { if (unlikely(calculator.limitReached())) { engine.phase = 1; break; } if (likely(doPset)) { Mode::pset(calculator.getTime(), vram, engine.ADX, addr, engine.tmpDst, CL, LogOp()); } Delta delta = DELTA_88; if ((engine.ARG & MAJ) == 0) { // X-Axis is major direction. engine.ADX += TX; // confirmed on real HW: // - end-test happens before DY += TY // - (ADX & PPL) test only happens after first pixel // is drawn. And it does test with 'AND' (not with ==) if (engine.ANX++ == engine.NX || (engine.ADX & Mode::PIXELS_PER_LINE)) { engine.commandDone(calculator.getTime()); break; } if (engine.ASX < engine.NY) { engine.ASX += engine.NX; engine.DY += TY; delta = DELTA_120; // 88 + 32 } engine.ASX -= engine.NY; engine.ASX &= 1023; // mask to 10 bits range } else { // Y-Axis is major direction. // confirmed on real HW: DY += TY happens before end-test engine.DY += TY; if (engine.ASX < engine.NY) { engine.ASX += engine.NX; engine.ADX += TX; delta = DELTA_120; // 88 + 32 } engine.ASX -= engine.NY; engine.ASX &= 1023; // mask to 10 bits range if (engine.ANX++ == engine.NX || (engine.ADX & Mode::PIXELS_PER_LINE)) { engine.commandDone(calculator.getTime()); break; } } addr = Mode::addressOf(engine.ADX, engine.DY, dstExt); calculator.next(delta); goto loop; } default: UNREACHABLE; } engine.time = calculator.getTime(); } /** Abstract base class for block commands. */ class BlockCmd : public VDPCmd { protected: void calcFinishTime(VDPCmdEngine& engine, unsigned NX, unsigned NY, unsigned ticksPerPixel); }; void BlockCmd::calcFinishTime(VDPCmdEngine& engine, unsigned NX, unsigned NY, unsigned ticksPerPixel) { if (!engine.currentCommand) return; // Underestimation for when the command will be finished. This assumes // we never have to wait for access slots and that there's no overhead // per line. auto t = VDP::VDPClock::duration(ticksPerPixel); t *= ((NX * (NY - 1)) + engine.ANX); engine.statusChangeTime = engine.time + t; } /** Logical move VDP -> VRAM. */ template struct LmmvBaseCmd : public BlockCmd { void start(EmuTime::param time, VDPCmdEngine& engine) override; }; template struct LmmvCmd : public LmmvBaseCmd { void execute(EmuTime::param limit, VDPCmdEngine& engine) override; }; template void LmmvBaseCmd::start(EmuTime::param time, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; vram.cmdReadWindow.disable(time); vram.cmdWriteWindow.setMask(0x3FFFF, ~0u << 18, time); engine.NY &= 1023; unsigned NX = clipNX_1_pixel(engine.DX, engine.NX, engine.ARG); unsigned NY = clipNY_1(engine.DY, engine.NY, engine.ARG); engine.ADX = engine.DX; engine.ANX = NX; engine.time = time; engine.nextAccessSlot(); calcFinishTime(engine, NX, NY, 72 + 24); engine.phase = 0; } template void LmmvCmd::execute(EmuTime::param limit, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; engine.NY &= 1023; unsigned NX = clipNX_1_pixel(engine.DX, engine.NX, engine.ARG); unsigned NY = clipNY_1(engine.DY, engine.NY, engine.ARG); int TX = (engine.ARG & DIX) ? -1 : 1; int TY = (engine.ARG & DIY) ? -1 : 1; engine.ANX = clipNX_1_pixel(engine.ADX, engine.ANX, engine.ARG); byte CL = engine.COL & Mode::COLOR_MASK; bool dstExt = (engine.ARG & MXD) != 0; bool doPset = !dstExt || engine.hasExtendedVRAM; unsigned addr = Mode::addressOf(engine.ADX, engine.DY, dstExt); auto calculator = engine.getSlotCalculator(limit); switch (engine.phase) { case 0: loop: if (unlikely(calculator.limitReached())) { engine.phase = 0; break; } if (likely(doPset)) { engine.tmpDst = vram.cmdWriteWindow.readNP(addr); } calculator.next(DELTA_24); // fall-through case 1: { if (unlikely(calculator.limitReached())) { engine.phase = 1; break; } if (likely(doPset)) { Mode::pset(calculator.getTime(), vram, engine.ADX, addr, engine.tmpDst, CL, LogOp()); } engine.ADX += TX; Delta delta = DELTA_72; if (--engine.ANX == 0) { delta = DELTA_136; // 72 + 64; engine.DY += TY; --(engine.NY); engine.ADX = engine.DX; engine.ANX = NX; if (--NY == 0) { engine.commandDone(calculator.getTime()); break; } } addr = Mode::addressOf(engine.ADX, engine.DY, dstExt); calculator.next(delta); goto loop; } default: UNREACHABLE; } engine.time = calculator.getTime(); this->calcFinishTime(engine, NX, NY, 72 + 24); /* if (unlikely(dstExt)) { bool doPset = !dstExt || engine.hasExtendedVRAM; while (engine.time < limit) { if (likely(doPset)) { Mode::pset(engine.time, vram, engine.ADX, engine.DY, dstExt, CL, LogOp()); } engine.time += delta; engine.ADX += TX; if (--engine.ANX == 0) { engine.DY += TY; --(engine.NY); engine.ADX = engine.DX; engine.ANX = NX; if (--NY == 0) { engine.commandDone(engine.time); break; } } } } else { // fast-path, no extended VRAM CL = Mode::duplicate(CL); while (engine.time < limit) { typename Mode::IncrPixelAddr dstAddr(engine.ADX, engine.DY, TX); typename Mode::IncrMask dstMask(engine.ADX, TX); EmuDuration dur = time - engine.time; unsigned num = (delta != EmuDuration::zero) ? std::min(dur.divUp(delta), engine.ANX) : engine.ANX; for (unsigned i = 0; i < num; ++i) { byte mask = dstMask.getMask(); psetFast(engine.time, vram, dstAddr.getAddr(), CL & ~mask, mask, LogOp()); engine.time += delta; dstAddr.step(TX); dstMask.step(); } engine.ANX -= num; if (engine.ANX == 0) { engine.DY += TY; engine.NY -= 1; engine.ADX = engine.DX; engine.ANX = NX; if (--NY == 0) { engine.commandDone(engine.time); break; } } else { engine.ADX += num * TX; assert(engine.time >= limit); break; } } } */ } /** Logical move VRAM -> VRAM. */ template struct LmmmBaseCmd : public BlockCmd { void start(EmuTime::param time, VDPCmdEngine& engine) override; }; template struct LmmmCmd : public LmmmBaseCmd { void execute(EmuTime::param limit, VDPCmdEngine& engine) override; }; template void LmmmBaseCmd::start(EmuTime::param time, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; vram.cmdReadWindow.setMask(0x3FFFF, ~0u << 18, time); vram.cmdWriteWindow.setMask(0x3FFFF, ~0u << 18, time); engine.NY &= 1023; unsigned NX = clipNX_2_pixel( engine.SX, engine.DX, engine.NX, engine.ARG ); unsigned NY = clipNY_2(engine.SY, engine.DY, engine.NY, engine.ARG); engine.ASX = engine.SX; engine.ADX = engine.DX; engine.ANX = NX; engine.time = time; engine.nextAccessSlot(); calcFinishTime(engine, NX, NY, 64 + 32 + 24); engine.phase = 0; } template void LmmmCmd::execute(EmuTime::param limit, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; engine.NY &= 1023; unsigned NX = clipNX_2_pixel( engine.SX, engine.DX, engine.NX, engine.ARG ); unsigned NY = clipNY_2(engine.SY, engine.DY, engine.NY, engine.ARG); int TX = (engine.ARG & DIX) ? -1 : 1; int TY = (engine.ARG & DIY) ? -1 : 1; engine.ANX = clipNX_2_pixel(engine.ASX, engine.ADX, engine.ANX, engine.ARG); bool srcExt = (engine.ARG & MXS) != 0; bool dstExt = (engine.ARG & MXD) != 0; bool doPoint = !srcExt || engine.hasExtendedVRAM; bool doPset = !dstExt || engine.hasExtendedVRAM; unsigned dstAddr = Mode::addressOf(engine.ADX, engine.DY, dstExt); auto calculator = engine.getSlotCalculator(limit); switch (engine.phase) { case 0: loop: if (unlikely(calculator.limitReached())) { engine.phase = 0; break; } engine.tmpSrc = likely(doPoint) ? Mode::point(vram, engine.ASX, engine.SY, srcExt) : 0xFF; calculator.next(DELTA_32); // fall-through case 1: if (unlikely(calculator.limitReached())) { engine.phase = 1; break; } if (likely(doPset)) { engine.tmpDst = vram.cmdWriteWindow.readNP(dstAddr); } calculator.next(DELTA_24); // fall-through case 2: { if (unlikely(calculator.limitReached())) { engine.phase = 2; break; } if (likely(doPset)) { Mode::pset(calculator.getTime(), vram, engine.ADX, dstAddr, engine.tmpDst, engine.tmpSrc, LogOp()); } engine.ASX += TX; engine.ADX += TX; Delta delta = DELTA_64; if (--engine.ANX == 0) { delta = DELTA_128; // 64 + 64 engine.SY += TY; engine.DY += TY; --(engine.NY); engine.ASX = engine.SX; engine.ADX = engine.DX; engine.ANX = NX; if (--NY == 0) { engine.commandDone(calculator.getTime()); break; } } dstAddr = Mode::addressOf(engine.ADX, engine.DY, dstExt); calculator.next(delta); goto loop; } default: UNREACHABLE; } engine.time = calculator.getTime(); this->calcFinishTime(engine, NX, NY, 64 + 32 + 24); /*if (unlikely(srcExt) || unlikely(dstExt)) { bool doPoint = !srcExt || engine.hasExtendedVRAM; bool doPset = !dstExt || engine.hasExtendedVRAM; while (engine.time < limit) { if (likely(doPset)) { byte p = likely(doPoint) ? Mode::point(vram, engine.ASX, engine.SY, srcExt) : 0xFF; Mode::pset(engine.time, vram, engine.ADX, engine.DY, dstExt, p, LogOp()); } engine.time += delta; engine.ASX += TX; engine.ADX += TX; if (--engine.ANX == 0) { engine.SY += TY; engine.DY += TY; --(engine.NY); engine.ASX = engine.SX; engine.ADX = engine.DX; engine.ANX = NX; if (--NY == 0) { engine.commandDone(engine.time); break; } } } } else { // fast-path, no extended VRAM while (engine.time < limit) { typename Mode::IncrPixelAddr srcAddr(engine.ASX, engine.SY, TX); typename Mode::IncrPixelAddr dstAddr(engine.ADX, engine.DY, TX); typename Mode::IncrMask dstMask(engine.ADX, TX); typename Mode::IncrShift shift (engine.ASX, engine.ADX); EmuDuration dur = limit - engine.time; unsigned num = (delta != EmuDuration::zero) ? std::min(dur.divUp(delta), engine.ANX) : engine.ANX; for (unsigned i = 0; i < num; ++i) { byte p = vram.cmdReadWindow.readNP(srcAddr.getAddr()); p = shift.doShift(p); byte mask = dstMask.getMask(); psetFast(engine.time, vram, dstAddr.getAddr(), p & ~mask, mask, LogOp()); engine.time += delta; srcAddr.step(TX); dstAddr.step(TX); dstMask.step(); } engine.ANX -= num; if (engine.ANX == 0) { engine.SY += TY; engine.DY += TY; engine.NY -= 1; engine.ASX = engine.SX; engine.ADX = engine.DX; engine.ANX = NX; if (--NY == 0) { engine.commandDone(engine.time); break; } } else { engine.ASX += num * TX; engine.ADX += num * TX; assert(engine.time >= limit); break; } } } */ } /** Logical move VRAM -> CPU. */ template struct LmcmCmd : public BlockCmd { void start(EmuTime::param time, VDPCmdEngine& engine) override; void execute(EmuTime::param limit, VDPCmdEngine& engine) override; }; template void LmcmCmd::start(EmuTime::param time, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; vram.cmdReadWindow.setMask(0x3FFFF, ~0u << 18, time); vram.cmdWriteWindow.disable(time); engine.NY &= 1023; unsigned NX = clipNX_1_pixel(engine.SX, engine.NX, engine.ARG); engine.ASX = engine.SX; engine.ANX = NX; engine.transfer = true; engine.status |= 0x80; engine.time = time; engine.nextAccessSlot(); engine.statusChangeTime = EmuTime::zero; } template void LmcmCmd::execute(EmuTime::param limit, VDPCmdEngine& engine) { if (!engine.transfer) return; if (unlikely(engine.time >= limit)) return; VDPVRAM& vram = engine.vram; engine.NY &= 1023; unsigned NX = clipNX_1_pixel(engine.SX, engine.NX, engine.ARG); unsigned NY = clipNY_1(engine.SY, engine.NY, engine.ARG); int TX = (engine.ARG & DIX) ? -1 : 1; int TY = (engine.ARG & DIY) ? -1 : 1; engine.ANX = clipNX_1_pixel(engine.ASX, engine.ANX, engine.ARG); bool srcExt = (engine.ARG & MXS) != 0; bool doPoint = !srcExt || engine.hasExtendedVRAM; // TODO we should (most likely) perform the actual read earlier and // buffer it, and on a CPU-IO-read start the next read (just like how // regular reading from VRAM works). engine.COL = likely(doPoint) ? Mode::point(vram, engine.ASX, engine.SY, srcExt) : 0xFF; engine.transfer = false; engine.ASX += TX; --engine.ANX; if (engine.ANX == 0) { engine.SY += TY; --(engine.NY); engine.ASX = engine.SX; engine.ANX = NX; if (--NY == 0) { engine.commandDone(engine.time); } } engine.time = limit; engine.nextAccessSlot(); // TODO } /** Logical move CPU -> VRAM. */ template struct LmmcBaseCmd : public BlockCmd { void start(EmuTime::param time, VDPCmdEngine& engine) override; }; template struct LmmcCmd : public LmmcBaseCmd { void execute(EmuTime::param limit, VDPCmdEngine& engine) override; }; template void LmmcBaseCmd::start(EmuTime::param time, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; vram.cmdReadWindow.disable(time); vram.cmdWriteWindow.setMask(0x3FFFF, ~0u << 18, time); engine.NY &= 1023; unsigned NX = clipNX_1_pixel(engine.DX, engine.NX, engine.ARG); engine.ADX = engine.DX; engine.ANX = NX; engine.statusChangeTime = EmuTime::zero; engine.transfer = true; engine.status |= 0x80; engine.time = time; engine.nextAccessSlot(); } template void LmmcCmd::execute(EmuTime::param limit, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; engine.NY &= 1023; unsigned NX = clipNX_1_pixel(engine.DX, engine.NX, engine.ARG); unsigned NY = clipNY_1(engine.DY, engine.NY, engine.ARG); int TX = (engine.ARG & DIX) ? -1 : 1; int TY = (engine.ARG & DIY) ? -1 : 1; engine.ANX = clipNX_1_pixel(engine.ADX, engine.ANX, engine.ARG); bool dstExt = (engine.ARG & MXD) != 0; bool doPset = !dstExt || engine.hasExtendedVRAM; if (engine.transfer) { byte col = engine.COL & Mode::COLOR_MASK; // TODO: timing is inaccurate, this executes the read and write // in the same access slot. Instead we should // - wait for a byte // - in next access slot read // - in next access slot write if (likely(doPset)) { unsigned addr = Mode::addressOf(engine.ADX, engine.DY, dstExt); engine.tmpDst = vram.cmdWriteWindow.readNP(addr); Mode::pset(limit, vram, engine.ADX, addr, engine.tmpDst, col, LogOp()); } // Execution is emulated as instantaneous, so don't bother // with the timing. // Note: Correct timing would require currentTime to be set // to the moment transfer becomes true. engine.transfer = false; engine.ADX += TX; --engine.ANX; if (engine.ANX == 0) { engine.DY += TY; --(engine.NY); engine.ADX = engine.DX; engine.ANX = NX; if (--NY == 0) { engine.commandDone(limit); } } } engine.time = limit; engine.nextAccessSlot(); // inaccurate, but avoid assert } /** High-speed move VDP -> VRAM. */ template struct HmmvCmd : public BlockCmd { void start(EmuTime::param time, VDPCmdEngine& engine) override; void execute(EmuTime::param limit, VDPCmdEngine& engine) override; }; template void HmmvCmd::start(EmuTime::param time, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; vram.cmdReadWindow.disable(time); vram.cmdWriteWindow.setMask(0x3FFFF, ~0u << 18, time); engine.NY &= 1023; unsigned NX = clipNX_1_byte(engine.DX, engine.NX, engine.ARG); unsigned NY = clipNY_1(engine.DY, engine.NY, engine.ARG); engine.ADX = engine.DX; engine.ANX = NX; engine.time = time; engine.nextAccessSlot(); calcFinishTime(engine, NX, NY, 48); } template void HmmvCmd::execute(EmuTime::param limit, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; engine.NY &= 1023; unsigned NX = clipNX_1_byte(engine.DX, engine.NX, engine.ARG); unsigned NY = clipNY_1(engine.DY, engine.NY, engine.ARG); int TX = (engine.ARG & DIX) ? -Mode::PIXELS_PER_BYTE : Mode::PIXELS_PER_BYTE; int TY = (engine.ARG & DIY) ? -1 : 1; engine.ANX = clipNX_1_byte( engine.ADX, engine.ANX << Mode::PIXELS_PER_BYTE_SHIFT, engine.ARG ); bool dstExt = (engine.ARG & MXD) != 0; bool doPset = !dstExt || engine.hasExtendedVRAM; auto calculator = engine.getSlotCalculator(limit); while (!calculator.limitReached()) { if (likely(doPset)) { vram.cmdWrite(Mode::addressOf(engine.ADX, engine.DY, dstExt), engine.COL, calculator.getTime()); } engine.ADX += TX; Delta delta = DELTA_48; if (--engine.ANX == 0) { delta = DELTA_104; // 48 + 56; engine.DY += TY; --(engine.NY); engine.ADX = engine.DX; engine.ANX = NX; if (--NY == 0) { engine.commandDone(calculator.getTime()); break; } } calculator.next(delta); } engine.time = calculator.getTime(); calcFinishTime(engine, NX, NY, 48); /*if (unlikely(dstExt)) { bool doPset = !dstExt || engine.hasExtendedVRAM; while (engine.time < limit) { if (likely(doPset)) { vram.cmdWrite(Mode::addressOf(engine.ADX, engine.DY, dstExt), engine.COL, engine.time); } engine.time += delta; engine.ADX += TX; if (--engine.ANX == 0) { engine.DY += TY; --(engine.NY); engine.ADX = engine.DX; engine.ANX = NX; if (--NY == 0) { engine.commandDone(engine.time); break; } } } } else { // fast-path, no extended VRAM while (engine.time < limit) { typename Mode::IncrByteAddr dstAddr(engine.ADX, engine.DY, TX); EmuDuration dur = limit - engine.time; unsigned num = (delta != EmuDuration::zero) ? std::min(dur.divUp(delta), engine.ANX) : engine.ANX; for (unsigned i = 0; i < num; ++i) { vram.cmdWrite(dstAddr.getAddr(), engine.COL, engine.time); engine.time += delta; dstAddr.step(TX); } engine.ANX -= num; if (engine.ANX == 0) { engine.DY += TY; engine.NY -= 1; engine.ADX = engine.DX; engine.ANX = NX; if (--NY == 0) { engine.commandDone(engine.time); break; } } else { engine.ADX += num * TX; assert(engine.time >= limit); break; } } } */ } /** High-speed move VRAM -> VRAM. */ template struct HmmmCmd : public BlockCmd { void start(EmuTime::param time, VDPCmdEngine& engine) override; void execute(EmuTime::param limit, VDPCmdEngine& engine) override; }; template void HmmmCmd::start(EmuTime::param time, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; vram.cmdReadWindow.setMask(0x3FFFF, ~0u << 18, time); vram.cmdWriteWindow.setMask(0x3FFFF, ~0u << 18, time); engine.NY &= 1023; unsigned NX = clipNX_2_byte( engine.SX, engine.DX, engine.NX, engine.ARG ); unsigned NY = clipNY_2(engine.SY, engine.DY, engine.NY, engine.ARG); engine.ASX = engine.SX; engine.ADX = engine.DX; engine.ANX = NX; engine.time = time; engine.nextAccessSlot(); calcFinishTime(engine, NX, NY, 24 + 64); engine.phase = 0; } template void HmmmCmd::execute(EmuTime::param limit, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; engine.NY &= 1023; unsigned NX = clipNX_2_byte( engine.SX, engine.DX, engine.NX, engine.ARG); unsigned NY = clipNY_2(engine.SY, engine.DY, engine.NY, engine.ARG); int TX = (engine.ARG & DIX) ? -Mode::PIXELS_PER_BYTE : Mode::PIXELS_PER_BYTE; int TY = (engine.ARG & DIY) ? -1 : 1; engine.ANX = clipNX_2_byte( engine.ASX, engine.ADX, engine.ANX << Mode::PIXELS_PER_BYTE_SHIFT, engine.ARG ); bool srcExt = (engine.ARG & MXS) != 0; bool dstExt = (engine.ARG & MXD) != 0; bool doPoint = !srcExt || engine.hasExtendedVRAM; bool doPset = !dstExt || engine.hasExtendedVRAM; auto calculator = engine.getSlotCalculator(limit); switch (engine.phase) { case 0: loop: if (unlikely(calculator.limitReached())) { engine.phase = 0; break; } engine.tmpSrc = likely(doPoint) ? vram.cmdReadWindow.readNP( Mode::addressOf(engine.ASX, engine.SY, srcExt)) : 0xFF; calculator.next(DELTA_24); // fall-through case 1: { if (unlikely(calculator.limitReached())) { engine.phase = 1; break; } if (likely(doPset)) { vram.cmdWrite(Mode::addressOf(engine.ADX, engine.DY, dstExt), engine.tmpSrc, calculator.getTime()); } engine.ASX += TX; engine.ADX += TX; Delta delta = DELTA_64; if (--engine.ANX == 0) { delta = DELTA_128; // 64 + 64 engine.SY += TY; engine.DY += TY; --(engine.NY); engine.ASX = engine.SX; engine.ADX = engine.DX; engine.ANX = NX; if (--NY == 0) { engine.commandDone(calculator.getTime()); break; } } calculator.next(delta); goto loop; } default: UNREACHABLE; } engine.time = calculator.getTime(); calcFinishTime(engine, NX, NY, 24 + 64); /*if (unlikely(srcExt || dstExt)) { bool doPoint = !srcExt || engine.hasExtendedVRAM; bool doPset = !dstExt || engine.hasExtendedVRAM; while (engine.time < limit) { if (likely(doPset)) { byte p = likely(doPoint) ? vram.cmdReadWindow.readNP( Mode::addressOf(engine.ASX, engine.SY, srcExt)) : 0xFF; vram.cmdWrite(Mode::addressOf(engine.ADX, engine.DY, dstExt), p, engine.time); } engine.time += delta; engine.ASX += TX; engine.ADX += TX; if (--engine.ANX == 0) { engine.SY += TY; engine.DY += TY; --(engine.NY); engine.ASX = engine.SX; engine.ADX = engine.DX; engine.ANX = NX; if (--NY == 0) { engine.commandDone(engine.time); break; } } } } else { // fast-path, no extended VRAM while (engine.time < limit) { typename Mode::IncrByteAddr srcAddr(engine.ASX, engine.SY, TX); typename Mode::IncrByteAddr dstAddr(engine.ADX, engine.DY, TX); EmuDuration dur = limit - engine.time; unsigned num = (delta != EmuDuration::zero) ? std::min(dur.divUp(delta), engine.ANX) : engine.ANX; for (unsigned i = 0; i < num; ++i) { byte p = vram.cmdReadWindow.readNP(srcAddr.getAddr()); vram.cmdWrite(dstAddr.getAddr(), p, engine.time); engine.time += delta; srcAddr.step(TX); dstAddr.step(TX); } engine.ANX -= num; if (engine.ANX == 0) { engine.SY += TY; engine.DY += TY; engine.NY -= 1; engine.ASX = engine.SX; engine.ADX = engine.DX; engine.ANX = NX; if (--NY == 0) { engine.commandDone(engine.time); break; } } else { engine.ASX += num * TX; engine.ADX += num * TX; assert(engine.time >= limit); break; } } } */ } /** High-speed move VRAM -> VRAM (Y direction only). */ template struct YmmmCmd : public BlockCmd { void start(EmuTime::param time, VDPCmdEngine& engine) override; void execute(EmuTime::param limit, VDPCmdEngine& engine) override; }; template void YmmmCmd::start(EmuTime::param time, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; vram.cmdReadWindow.setMask(0x3FFFF, ~0u << 18, time); vram.cmdWriteWindow.setMask(0x3FFFF, ~0u << 18, time); engine.NY &= 1023; unsigned NX = clipNX_1_byte(engine.DX, 512, engine.ARG); // large enough so that it gets clipped unsigned NY = clipNY_2(engine.SY, engine.DY, engine.NY, engine.ARG); engine.ADX = engine.DX; engine.ANX = NX; engine.time = time; engine.nextAccessSlot(); calcFinishTime(engine, NX, NY, 24 + 40); engine.phase = 0; } template void YmmmCmd::execute(EmuTime::param limit, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; engine.NY &= 1023; unsigned NX = clipNX_1_byte(engine.DX, 512, engine.ARG); // large enough so that it gets clipped unsigned NY = clipNY_2(engine.SY, engine.DY, engine.NY, engine.ARG); int TX = (engine.ARG & DIX) ? -Mode::PIXELS_PER_BYTE : Mode::PIXELS_PER_BYTE; int TY = (engine.ARG & DIY) ? -1 : 1; engine.ANX = clipNX_1_byte(engine.ADX, 512, engine.ARG); // TODO does this use MXD for both read and write? // it says so in the datasheet, but it seems unlogical // OTOH YMMM also uses DX for both read and write bool dstExt = (engine.ARG & MXD) != 0; bool doPset = !dstExt || engine.hasExtendedVRAM; auto calculator = engine.getSlotCalculator(limit); switch (engine.phase) { case 0: loop: if (unlikely(calculator.limitReached())) { engine.phase = 0; break; } if (likely(doPset)) { engine.tmpSrc = vram.cmdReadWindow.readNP( Mode::addressOf(engine.ADX, engine.SY, dstExt)); } calculator.next(DELTA_24); // fall-through case 1: if (unlikely(calculator.limitReached())) { engine.phase = 1; break; } if (likely(doPset)) { vram.cmdWrite(Mode::addressOf(engine.ADX, engine.DY, dstExt), engine.tmpSrc, calculator.getTime()); } engine.ADX += TX; if (--engine.ANX == 0) { // note: going to the next line does not take extra time engine.SY += TY; engine.DY += TY; --(engine.NY); engine.ADX = engine.DX; engine.ANX = NX; if (--NY == 0) { engine.commandDone(calculator.getTime()); break; } } calculator.next(DELTA_40); goto loop; default: UNREACHABLE; } engine.time = calculator.getTime(); calcFinishTime(engine, NX, NY, 24 + 40); /* if (unlikely(dstExt)) { bool doPset = !dstExt || engine.hasExtendedVRAM; while (engine.time < limit) { if (likely(doPset)) { byte p = vram.cmdReadWindow.readNP( Mode::addressOf(engine.ADX, engine.SY, dstExt)); vram.cmdWrite(Mode::addressOf(engine.ADX, engine.DY, dstExt), p, engine.time); } engine.time += delta; engine.ADX += TX; if (--engine.ANX == 0) { engine.SY += TY; engine.DY += TY; --(engine.NY); engine.ADX = engine.DX; engine.ANX = NX; if (--NY == 0) { engine.commandDone(engine.time); break; } } } } else { // fast-path, no extended VRAM while (engine.time < limit) { typename Mode::IncrByteAddr srcAddr(engine.ADX, engine.SY, TX); typename Mode::IncrByteAddr dstAddr(engine.ADX, engine.DY, TX); EmuDuration dur = limit - engine.time; unsigned num = (delta != EmuDuration::zero) ? std::min(dur.divUp(delta), engine.ANX) : engine.ANX; for (unsigned i = 0; i < num; ++i) { byte p = vram.cmdReadWindow.readNP(srcAddr.getAddr()); vram.cmdWrite(dstAddr.getAddr(), p, engine.time); engine.time += delta; srcAddr.step(TX); dstAddr.step(TX); } engine.ANX -= num; if (engine.ANX == 0) { engine.SY += TY; engine.DY += TY; engine.NY -= 1; engine.ADX = engine.DX; engine.ANX = NX; if (--NY == 0) { engine.commandDone(engine.time); break; } } else { engine.ADX += num * TX; assert(engine.time >= limit); break; } } } */ } /** High-speed move CPU -> VRAM. */ template struct HmmcCmd : public BlockCmd { void start(EmuTime::param time, VDPCmdEngine& engine) override; void execute(EmuTime::param limit, VDPCmdEngine& engine) override; }; template void HmmcCmd::start(EmuTime::param time, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; vram.cmdReadWindow.disable(time); vram.cmdWriteWindow.setMask(0x3FFFF, ~0u << 18, time); engine.NY &= 1023; unsigned NX = clipNX_1_byte(engine.DX, engine.NX, engine.ARG); engine.ADX = engine.DX; engine.ANX = NX; engine.statusChangeTime = EmuTime::zero; engine.transfer = true; engine.status |= 0x80; engine.time = time; engine.nextAccessSlot(); } template void HmmcCmd::execute(EmuTime::param limit, VDPCmdEngine& engine) { VDPVRAM& vram = engine.vram; engine.NY &= 1023; unsigned NX = clipNX_1_byte(engine.DX, engine.NX, engine.ARG); unsigned NY = clipNY_1(engine.DY, engine.NY, engine.ARG); int TX = (engine.ARG & DIX) ? -Mode::PIXELS_PER_BYTE : Mode::PIXELS_PER_BYTE; int TY = (engine.ARG & DIY) ? -1 : 1; engine.ANX = clipNX_1_byte( engine.ADX, engine.ANX << Mode::PIXELS_PER_BYTE_SHIFT, engine.ARG ); bool dstExt = (engine.ARG & MXD) != 0; bool doPset = !dstExt || engine.hasExtendedVRAM; if (engine.transfer) { // TODO: timing is inaccurate. We should // - wait for a byte // - on the next access slot write that byte if (likely(doPset)) { vram.cmdWrite(Mode::addressOf(engine.ADX, engine.DY, dstExt), engine.COL, limit); } engine.transfer = false; engine.ADX += TX; --engine.ANX; if (engine.ANX == 0) { engine.DY += TY; --(engine.NY); engine.ADX = engine.DX; engine.ANX = NX; if (--NY == 0) { engine.commandDone(limit); } } } engine.time = limit; engine.nextAccessSlot(); // inaccurate, but avoid assert } // Construction and destruction: template